[Fo1 & Fo2] HOW TO: Replace broken "explosion(tile, elev, dmg)" script function.

Sduibek

Creator of Fallout Fixt
Moderator
Modder
The damage calculations in the Fallout games are very strange. For example, if I have
Code:
	explosion(TILE_NUM(source_obj), ELEVATION(source_obj), DAMAGE_AMOUNT);
Source being whatever critter ran over or near the script hex, there are times when the source object is hit for 1 damage. I just tested it with 100 at the end, and it did 93 and 98 damage on the first two tests, respectively. On the third test, it did 4 damage. * This was tested with a naked character so it has nothing to do with armor.

It seems that both games randomize the damage so that the value at the end is the "max rolled value". In other words, explosion() rolls (1, X) where X is whatever value you put into DAMAGE_AMOUNT. Which is a really stupid way to do it. I get the feeling that the coders of Fallout and Fallout 2 didn't know this, because almost all scripts that use explosion() insert a "random(X, Y)" value in DAMAGE_AMOUNT. Why would you further randomize a random value?



FALLOUT1 METHOD: From what I can tell, this is the only way to simulate how explosion() actually should work, in Fallout1:
Code:
	play_sfx("WHN1XXX1"); // explosion sound - Dynamite, etc.
	critter_dmg(source_obj, random(MINIMUM_DAMAGE, MAXIMUM_DAMAGE), 6);
In Fallout 2 you could replace the "6" with damage type Explosion, so it would be
Code:
	play_sfx("WHN1XXX1"); // explosion sound - Dynamite, etc.
	critter_dmg(source_obj, random(MINIMUM_DAMAGE, MAXIMUM_DAMAGE), DMG_explosion);

So you get the explosion sound and proper damage type and proper damage amount without the visual blast animation.



FALLOUT2 METHODS: (Or just scroll down)
by phobos2077: http://www.nma-fallout.com/showthread.php?201274&p=4004939&viewfull=1#post4004939

by JimTheDinosaur: http://www.nma-fallout.com/showthread.php?201274&p=4007953&viewfull=1#post4007953

Properly removing the effect after animation: http://www.nma-fallout.com/showthread.php?201274&p=4005147&viewfull=1#post4005147
 
Last edited:
How to create visual explosion:
Code:
play_sfx("WHN1XXX1");
obj := create_object(PID_EXPLOSION_3, tile, elevation);
anim(obj,ANIMATE_FORWARD,0);

then you have to delete your eplosion after some delay (about 15-20 ticks). There are several explosion PIDs available:

Code:
#define PID_EXPLOSION_1          (0x05000003)
#define PID_EXPLOSION_EMP_1      (0x05000008)
#define PID_EXPLOSION_2          (0x0500000B)
#define PID_EXPLOSION_3          (0x0500000E)
#define PID_EXPLOSION_EMP_2      (0x0500000F)
#define PID_EXPLOSION_4          (0x0500001B)
#define PID_EXPLOSION_5          (0x0500001C)
#define PID_EXPLOSION_PLASMA_1      (0x0500001D)
#define PID_EXPLOSION_6          (0x05000030)

Edit: This is explosion function that I use:
Code:
/**
  Make explosion by emulating all of it's components: area damage, explosion effect, sound
*/
procedure manual_explosion(variable tile, variable elev, variable dmgMin, variable dmgMax, variable radius, variable dmgType, variable pid, variable sfx) begin
  variable crit;
  variable dist;
  variable ar;
  variable i;
  if (pid == 0) then pid := PID_EXPLOSION_1;
  if (sfx == 0) then sfx := "WHN1XXX1";
  foreach crit in list_as_array(LIST_CRITTERS) begin
    dist := tile_distance(tile, tile_num(crit));
    if (dist <= radius) then begin
      critter_dmg(crit, floor(random(dmgMin, dmgMax) * (1.0 - (0.5 / radius)*dist)), dmgType);
    end
  end
  ar := get_sfall_global_array(SGVAR_FAKE_EXPLOSIONS, 0, 4);
  i := len_array(ar);
  resize_array(ar, len_array(ar) + 2);
  ar[i] := create_object(pid, tile, elev);
  ar[i+1] := game_time + 12;
  anim(ar[i],ANIMATE_FORWARD,0);
  play_sfx(sfx);
end

Edit: PID_EXPLOSION_EMP_3 was actually a plasma grenade effect (looks like RP introduced some of the new animations). I wonder if you can simulate the "default" explosion...
 
Last edited:
Haha, just noticed that phobos2077 is from Russia. It seems that all the great Fallout modders are Russian. :revolution:

EDIT: Wait, there's killap and stuff. Okay so maybe it should be edited to say 'lots of' Fallout modders are Russian.
 
Last edited:
I want to suggest I way to properly delete explosion effect after animation is finished:

Code:
  expl_obj := create_object(pid, tile, elev);
  set_script(expl_obj, (SCRIPT_TEST2 - 1));
  anim(expl_obj, ANIM_stand, 0);
  add_timer_event(expl_obj, 13, 1);

Then in test2 script I set up map_exit_p_proc and timed_event_p_proc with destroy_object(self_obj);

1) If you exit the map before timed event, the effect object will remain forever (timed event will not run). That's why you need to use map_exit_p_proc.
2) For some reason, if you use create_object_sid and pass script number to it, map_exit_p_proc won't work. It does when you use set_script() sfall command.

I'm also working on code to simulate proper death animation when killing critter using critter_dmg (if you use NORMAL damage type, it always plays animation like he got minigunned or something). Is there are thread to post code snippets like this? (may be usefull to someone)
 
Hey @phobos2077, I don't really like adding new regular scripts, so I did something like this.

Code:
procedure manual_explosion(variable tile, variable elev, variable dmgMin, variable dmgMax, variable radius, variable dmgType, variable pid, variable sfx) begin
  variable crit;
  variable dist;
  variable ar;
  variable i;
  if (pid == 0) then pid := PID_EXPLOSION_3;
  if (sfx == 0) then sfx := "WHN1XXX1";
  set_sfall_global("explopid", create_object(pid, tile, elev));
  set_sfall_global("explotim", game_time + 8);
  anim(get_sfall_global_int("explopid"),ANIMATE_FORWARD,0);
  play_sfx(sfx);
  foreach crit in list_as_array(LIST_CRITTERS) begin
    dist := tile_distance(tile, tile_num(crit));
    if (dist <= radius) then begin
      critter_dmg(crit, floor(random(dmgMin, dmgMax) * (1.0 - (0.5 / radius)*dist)), dmgType);
    end
  end
end

with this checked continuously:

Code:
  if game_time >= get_sfall_global_int("explotim") and get_sfall_global_int("explopid")>0 then begin
      destroy_object(get_sfall_global_int("explopid"));
      set_sfall_global("explopid", 0);
  end

and this in the dude_obj's map_exit_p_proc:

Code:
  if get_sfall_global_int("explopid")>0 then begin
      destroy_object(get_sfall_global_int("explopid"));
      set_sfall_global("explopid", 0);
  end

Seems to work fine, but maybe I'm missing something.
 
Last edited by a moderator:
@phobos2077 @JimTheDinosaur Does any part of you guys' code not require Sfall, i.e. would work in Fallout1? EDIT: Actually better yet, could you both please send me any .INT files that this uses? I can decompile them into Fallout1 format and thus easily see if they're workable into Fallout1 methods.

I have updated the thread title and OP for this to be a How-To, so if either of you need to consolidate or modify your provided code to make it "public-ready", or, if I only need to provide part of the code from your posts, please let me know and I'll add it to the OP. :nod: I don't know Sfall scripting very well so I don't know which bits to include. Thanks guys 8-)
 
Last edited by a moderator:
@phobos2077 @JimTheDinosaur Does any part of you guys' code not require Sfall, i.e. would work in Fallout1? EDIT: Actually better yet, could you both please send me any .INT files that this uses? I can decompile them into Fallout1 format and thus easily see if they're workable into Fallout1 methods.

I have updated the thread title and OP for this to be a How-To, so if either of you need to consolidate or modify your provided code to make it "public-ready", or, if I only need to provide part of the code from your posts, please let me know and I'll add it to the OP. :nod: I don't know Sfall scripting very well so I don't know which bits to include. Thanks guys 8-)

Pretty sure it isn't. The magic part's "foreach crit in list_as_array(LIST_CRITTERS)": you can't check for every critter on the map without sfall scripts (except for the completely insane method of including it in every single critter script separately, but even that I wouldn't be sure you could make work). And without checking for every critter, you can't tell who gets damaged by the blast.

Afraid you'll have to get back to that engine porting business Sduibek B).
 
with this checked continuously:

Code:
  if game_time >= get_sfall_global_int("explotim") and get_sfall_global_int("explopid")>0 then begin
      destroy_object(get_sfall_global_int("explopid"));
      set_sfall_global("explopid", 0);
  end


Seems to work fine, but maybe I'm missing something.

In your example there is only one simultaneous explosion allowed? It's better be done with array, in real-time it's common to have several explosions happen at the same time.
 
In your example there is only one simultaneous explosion allowed? It's better be done with array, in real-time it's common to have several explosions happen at the same time.

Oh right, thanks, but can you explain to me how you get "get_sfall_global_array" to work? Doesn't compile for me. I have that economy script with a lot of arrays but I thought I had to use a workaround of setting a global var with the number of the array after the fact, so you can do that directly?
 
In your example there is only one simultaneous explosion allowed? It's better be done with array, in real-time it's common to have several explosions happen at the same time.

Oh right, thanks, but can you explain to me how you get "get_sfall_global_array" to work? Doesn't compile for me. I have that economy script with a lot of arrays but I thought I had to use a workaround of setting a global var with the number of the array after the fact, so you can do that directly?

AFAIK, you can't enforce fixed array ID when create one, sfall just creates new one and gives you it's ID. So you have to create array first and then put it's ID into sfall global. BUT, the point of using get_sfall_global_array is so you abstract this process away into single function. First, test this function to be 100% sure that every array created is always referenced in sfall global and they both end up in the same savegame. And then use this function everytime and don't bother. That is VERY damn important, because simple mistake can easily leak memory AND savegame to megabytes and beyond! Basically damaging the savegame, and removing excess arrays from save file is very difficult (you never know what array IDs to delete).
What compiler do you use? Here is version I currently use: View attachment lib.global_array.zip
The *_new function always gives you a "new" empty array with size you requested. While the first one creates array only first time. Both functions will create exactly one sfall array for each sfall global.

Also, In my mod I store mines in structure that functions like an array of objects (if you're into OOP programming). There is several ways how you can store arrays of objects with sfall:
1) each object in array is a pointer to another array. When object deleted, array is deleted, etc.
2) all objects reside inside single large array, each object occupy fixed number of indexes
I use (2) option because it uses only one array, and given how dangerous arrays are in sfall, it is more safe in my opinion.
To access "properties" of those "objects", you can define getters like this (in my mod):
#define trap_tile(ar, index) array_get(ar, index + TRAP_INDEX_TILE)
where ar is an array and index is a starting index of that object in global array (think of it as on object ID)

Arrays of objects give an opportunity to make "infinite" number of stuff in your mod, like effects for drugs (drug type, effected stat, timers, etc.), explosions, etc. If you are interested I can post code that I use to add/remove object from such arrays.
 
Last edited:
Thanks for those procedures! Yeah, arrays are real delicate business, but you seem to be doing some really interesting things with them (shows that unlike me, you clearly don't suck at programming). I just did everything separately earlier (in the case of my economy/gear randomization script, going over each PID to count which belonged in which price array just to ascertain array size, then doing the whole thing again to put them in the arrays, then assigning global scripts to the id of each array).

I didn't think of using arrays for the traps (I just kept things managable by restricting them to 5), but your method looks very interesting. If you could post that code that'd be great!
 
Thanks for those procedures! Yeah, arrays are real delicate business, but you seem to be doing some really interesting things with them (shows that unlike me, you clearly don't suck at programming). I just did everything separately earlier (in the case of my economy/gear randomization script, going over each PID to count which belonged in which price array just to ascertain array size, then doing the whole thing again to put them in the arrays, then assigning global scripts to the id of each array).

I didn't think of using arrays for the traps (I just kept things managable by restricting them to 5), but your method looks very interesting. If you could post that code that'd be great!

To add new record (this is an example):
Code:
procedure add_trap_info(variable trapobj, variable charges) begin
  variable begin
    index := 0;
    zero := false;
    ar_global;
    tile;
    elev;
  end
  ar_global := get_sfall_global_array(SGVAR_TRAPS_BY_DUDE, 0, 4);
  // find empty array index
  index := 0;
  while index < len_array(ar_global) and not(zero) do begin
    if (trap_state(ar_global, index) == TRAP_STATE_REMOVED) then begin // this is just indication to make sure this block in array is vacant
      // this index is empty, place struct here
      zero := true; // break
    end else begin
      index += TRAPINFO_SIZE;
    end
  end
  if (index == len_array(ar_global)) then begin
    resize_array(ar_global, index + TRAPINFO_SIZE);
  end
  tile := tile_num(trapobj);
  elev := elevation(trapobj);
  ar_global[index + TRAPINFO_OFS_INDEX] := index;
  ar_global[index + TRAPINFO_OFS_TILE] := tile;
  ar_global[index + TRAPINFO_OFS_ELEV] := elev;
 ..... other stuff
end
Explanation:
1) Search for empty "spaces" in array (where "objects" where previously removed)
2) if none found, increase overall array size and place in end
3) if found, occupy this space with new object

To remove:
Code:
procedure remove_trap_info(variable index) begin
  variable ar_global;
  variable len;
  ar_global := get_sfall_global_array(SGVAR_TRAPS_BY_DUDE, 0, 4);
  len := len_array(ar_global);
  if (index + TRAPINFO_SIZE == len) then begin
    // if this is last trap, reduce the array
    resize_array(ar_global, len - TRAPINFO_SIZE);
  end else begin
    // make sure it doesn't get used
    ar_global[index + TRAPINFO_OFS_PID] := 0;
    ar_global[index + TRAPINFO_OFS_OBJPID] := 0;
    ar_global[index + TRAPINFO_OFS_STATE] := TRAP_STATE_REMOVED;
  end
end
Explanation:
1) If this is the last block in array, cut it from array by reducing size
2) if not, add indication that this block is vacant for Add function

I'll maybe write an article about it later with a general-purpose array library abstract from traps (with other stuff like sets and associative arrays).
 
Hey @phobos2077, I'm wondering whether it's a good idea to use game_time for removing the explosions. I've noticed that game_time stops (as it should I guess) once you start your turn in combat, which I'm guessing might produce some problems at some point. Might be a better idea to use get_uptime.
 
Last edited by a moderator:
Back
Top