Fallout 2 mod FO2 Engine Tweaks (Sfall)

2. There IS import/export. You can (theoretically) have a global script running with all global functions used by other scripts.

How to get it work?

I tested: (used a recent release; not latest svn)
Code:
global script:
export procedure foo;
procedure foo begin
    display_msg("foo");
end

obj_dude:
import procedure foo;
procedure map_enter_p_proc begin
    display_msg("dude map_enter");
    call foo();
end
It does't work, plus random crash at load.


BTW:
When I wrote:
Code:
export procedure foo;
procedure foo(variable bar) begin
end
compiler crashes. Export function cannot have arguments?
 
How to get it work?

I tested: (used a recent release; not latest svn)

It does't work, plus random crash at load.
There was a crash bug related to exported procedures in 3.4, it was fixed in 3.5.

BTW:
When I wrote:
Code:
export procedure foo;
procedure foo(variable bar) begin
end
compiler crashes. Export function cannot have arguments?

That's a compiler bug, thanks for the report, I've fixed it for the next release. Exported procs can have any number of arguments.

Just tested it again myself and here's the deal:
  • you can use exported procedures between normal scripts (objects and maps), but you must respect scripts loading order. In other words, scripts with "export" should be the one's that loaded first. Typically export everything in map script and import in object script
  • however, there is obviously something wrong with them when using from sfall scripts, when I call imported procedure, it works, but the script stops working after that call, and nothing is printed in debug.log (no crash)
  • they were even more broken before sfall, some TeamX guy made patches very long time ago, which Timeslip incorporated - it fixed several crashes. Though, looks like more fixing is required for full usage.

Script load order is:
  1. Map script
  2. Map objects scripts
  3. Sfall Hook scripts
  4. Sfall global scripts
 
Last edited:
Another question about the engine: how to properly implement a (short-term) timed behavior?
For example, a timed explosion to fire 10-seconds later.

I tried to poll game_time for +100 in a repeated global script.
But the condition always seem to fire early at 1s-8s. (huge drift)
(The global script repeating interval varies badly between maps, but sort of consistent in the same map.)

That's a compiler bug, thanks for the report, I've fixed it for the next release. Exported procs can have any number of arguments.

Just tested it again myself and here's the deal:
  • you can use exported procedures between normal scripts (objects and maps), but you must respect scripts loading order. In other words, scripts with "export" should be the one's that loaded first. Typically export everything in map script and import in object script
  • however, there is obviously something wrong with them when using from sfall scripts, when I call imported procedure, it works, but the script stops working after that call, and nothing is printed in debug.log (no crash)
  • they were even more broken before sfall, some TeamX guy made patches very long time ago, which Timeslip incorporated - it fixed several crashes. Though, looks like more fixing is required for full usage.

Script load order is:
  1. Map script
  2. Map objects scripts
  3. Sfall Hook scripts
  4. Sfall global scripts

So it seems that export/import is not yet ready to use for better modularization.

BTW maybe sfall hooks should cover all normal map/object events?
For example, it seems that proto data is reloaded between each map load, while global/hook scripts don't have map-enter hook.
 
Another question about the engine: how to properly implement a (short-term) timed behavior?
For example, a timed explosion to fire 10-seconds later.

I tried to poll game_time for +100 in a repeated global script.
But the condition always seem to fire early at 1s-8s. (huge drift)
That's strange, since game_time should return number of tics of game time, where one second is 10 tics. There is an sfall function which returns current system time or something like that, you could try to use it.

(The global script repeating interval varies badly between maps, but sort of consistent in the same map.)
That's because when you specify set_global_script_repeat it actually means how many frames to skip before executing "start" proc, so the frequency is closely tied to current frame rate.

So it seems that export/import is not yet ready to use for better modularization.
Seems so. However, try out export variables. They should be reliable now (and if not, easily fixable since all related engine hooks are already in place).

BTW maybe sfall hooks should cover all normal map/object events?
For example, it seems that proto data is reloaded between each map load, while global/hook scripts don't have map-enter hook.

That's a good question. I already started to move in direction of making sfall scripts more flexible and event-oriented rather than abusing set_global_script_repeat:
  • no more need to place "start" proc in the beginning
  • map_enter_p_proc, map_exit_p_proc, map_update_p_proc is already executed for all sfall scripts since 3.4
  • register_hook_proc function (make specific proc to be called instead of "start")
  • many functions which relied on current script having a SID (reference in scripts.lst) now work with sfall scripts
  • there is also set_self function from earlier sfall versions (see question below)

Now the question is - what to do next? You obviously can't hook object events (like use_p_proc) on global scripts, so they are like map script in this regard. I might implement add_timer_event alternative for global scripts. Also there is a timed and event procedures, though I'm not entirely sure how they work, since no one ever used them in fallout scripting.
My long term plans in this regards is to find out how to pass argument values to and get return value from procedures. This will not only allow to take sfall hook arguments directly without calling get_sfall_arg (and same for return value), but also to completely replace vanilla opcodes related to exported procedures (may easier than fixing existing code). Interpreter code is very hard to understand from the asm...

Since there is one interested person, I have another question regarding set_self function. Currently it allows to use functions like pickup_obj(item) from sfall scripts, but only if you call set_self right before the relevant function call (original "self" is reverted automatically after next function call). While this is safer (in case you forget to "set_self" back), it is also not very user-friendly when you need to make chains of such calls. I can leave it as it is, since there are not many functions which actually use self_obj, what do you think?
(other options include reverting it back automatically after each event is fired for a given script, or at the end of the frame, etc.)
 
sfall 3.5 is released on SourceForge, along with the modders pack and win2k version.

changelog said:
v3.5
>The build environment is reverted back to visual studio 2010. The project itself still can be compiled with visual studio 2012/2013.
>Added options to control the speed of combat/dialogue panel animations (From Tehnokrat)
>Added an error message (with sfx) when sfall cannot create sfallgv.sav file during saving the game
>sfall globals are now removed from savegame when setting them to zero (0) for a cleaner save file
>Array-related code refactoring and bug fixing
>Changed how save/load_array functions work when key is 0
>Added special cases for array_resize operator to perform efficient array sorting, reversing and shuffling (lists only)
>Added the ability to resize or clear maps (arrays)
>Zero values are now saved when using associative array expressions (assigning zero to existing array still removes corresponding element from array)
>Fixed the offset for the exported procedure patch
>You can now export variables from hook/global scripts and import them into any other scripts,
no longer have to use set_sfall_global or arrays passing values between hook/global and other scripts.
>Code refactoring of hook scripts. Now there is no limitation as to where you put the start procedure in hook/global scripts.
>Added a new and better way to use hook scripts - register_hook_proc function will tie a sfall hook to a given procedure in the current script
>New script functions: charcode, message_str_game, sneak_success, tile_light
>Added some math scripting functions: ^ operator, log, exponent, round
>New hook scripts: hs_keypress, hs_mouseclick, hs_useskill, hs_steal, hs_withinperception
>Added the ability to use virtual key codes in the keypress hook script and key_pressed function
>hs_calcapcost hook script is now called for all misc items in hands as well as weapons
>hs_useobjon hook script is now also called when the player or AI uses any drug
>Fixed bugs in pickpocket script functions that max and mod values were not initialized and not reset after the game loads
>AP counter in UI is now redrawn when using set_critter_current_ap on obj_dude
>Fixed a minor bug in the sprintf script function
>Rewritten code related to set_self script function, and fixed many vanilla opcodes not working from sfall scripts (roll_vs_skill, do_check, etc.)
 
sfall gets AllowUnsafeScripting from wrong section. It should be Debugging according to shipped .ini
Code:
sfall\ScriptExtender.cpp(828):    bool AllowUnsafeScripting=GetPrivateProfileIntA("Debug", "AllowUnsafeScripting", 0, ini)!=0;

BTW to build sfall, which D3D9 SDK release should I get?
 
sfall gets AllowUnsafeScripting from wrong section. It should be Debugging according to shipped .ini
Code:
sfall\ScriptExtender.cpp(828):    bool AllowUnsafeScripting=GetPrivateProfileIntA("Debug", "AllowUnsafeScripting", 0, ini)!=0;

BTW to build sfall, which D3D9 SDK release should I get?

I use June 2010 SDK. Thanks for the report.
 
Hey, just a couple of ideas you might consider if they're easy enough and might be of use for your own mod:

- Carrying over variables in dialogue. I have absolutely no idea how viable this is, but it has always bugged me how you can only do:

Code:
NOption("herp",NodeHerp,001);

and not

Code:
NOption("herp",NodeHerp(herp),001);

forcing you to make a large amount of additional nodes even if all you want to do is offer different prices for an item through dialogue.

- something like hs_criticalfail. It would be neat to change up the way critical failures work, making the effect not depend on luck alone:

e.g. the gun crits could be the result of a combination of luck and:

0 miss (I'd eliminate this one, not very interesting crit fail)
1 lost rest of ammo (traps/explosives skill; I imagine this means the ammo was poorly maintained and faulty)
2 weapon dropped (agility)
3 hit randomly (perception)
4 weapon destroyed (repair skill)
 
- Carrying over variables in dialogue. I have absolutely no idea how viable this is, but it has always bugged me how you can only do:

Code:
NOption("herp",NodeHerp,001);

and not

Code:
NOption("herp",NodeHerp(herp),001);

forcing you to make a large amount of additional nodes even if all you want to do is offer different prices for an item through dialogue.

Not possible with how procedures are passed as arguments. This particular problem is solved by anonymous functions in other languages and since we don't have such luxury here, you will have to find other options.
  • When one node is referenced once from each of the other nodes - just use normal variable in global scope.
  • In other case (several references from one node) - you have to make several procedures, but try using preprocessor in it's full (I recommend this resource), for example you can make one macro that defines procedure with postfixed name, using ##:

Code:
#define NodeFork(x)  \
procedure NodeFork##x begin \
   ... \
end

NodeFork(1)
NodeFork(2)
NodeFork(3)
// etc.

- something like hs_criticalfail. It would be neat to change up the way critical failures work, making the effect not depend on luck alone:

e.g. the gun crits could be the result of a combination of luck and:

0 miss (I'd eliminate this one, not very interesting crit fail)
1 lost rest of ammo (traps/explosives skill; I imagine this means the ammo was poorly maintained and faulty)
2 weapon dropped (agility)
3 hit randomly (perception)
4 weapon destroyed (repair skill)

Isn't there already a CriticalOverrides.ini which allows to customize critical table? This stuff seem very complex to me and I was never interested enough to study how it works :)
 
Isn't there already a CriticalOverrides.ini which allows to customize critical table? This stuff seem very complex to me and I was never interested enough to study how it works :)

That's just for critical hits, and I override those in hs_combatdamage already; plus I like the versatility of hookscripts way more than static configuration files anyway ;)
 
Which reminds me of something else you might possibly find interesting for your mod as well.

It's always bothered me how little attributes meaningfully figure into character creation. If you want to play a character who speaks with a velvet tongue and can barter with the best of them, should you invest in Charisma? Not at all. If you want to be a master lockpicker should you invest in Agility or Perception? Not at all. Attributes other than Intelligence really don't figure into anything other than combat and attribute checks in dialogue (except for the pretty silly npc cap of Charisma).

The reason for this isn't hard to see: combat has plenty of variables involved (range, damage, thc, etc.), while skill checks usually just have one, namely difficulty. This way, combat can involve not just skills but also different attributes, while skill checks can only involve skills.

While you could address this by introducing additional variables to skill checks, there's only so much you can do this way (introducing noise generation based on player Agility during lockpicking which alerts those closeby would be one example). So what I was thinking is how interesting it would be if the skill cost increments, which Timeslip already made adjustable on a per skill basis in the skills.ini configuration file, could be changed through a script function as well.

This way, you could make skill costs depend not just on how high your investment already is, but also how high your attributes are. E.g., a low charisma character would have to spend more skill points to increase his speech or barter skill than a high charisma character, thus making attribute selection a more important part of character creation.
 
It seems that variables can be called, like opcode? Without a warning or error.
Like:
Code:
variable xxx;
xxx := ...
if (cond and xxx()) ...
And debug.log gets "Error during execution: Wrong type given to lookup_string_proc", plus random crash. (xxx should have int 0 or 1)

For some reason, this coding error lurks for weeks and doesn't bit me until today. (recompiled a dozen or more times in the meanwhile)
Though it is in hs_calcapcost, apparently a hot call place.
 
It seems that variables can be called, like opcode? Without a warning or error.
Like:
Code:
variable xxx;
xxx := ...
if (cond and xxx()) ...
And debug.log gets "Error during execution: Wrong type given to lookup_string_proc", plus random crash. (xxx should have int 0 or 1)

For some reason, this coding error lurks for weeks and doesn't bit me until today. (recompiled a dozen or more times in the meanwhile)
Though it is in hs_calcapcost, apparently a hot call place.

This is a feature of original SSL. You can assign string to a variable and call it. String contains the name of some procedure in the script.
 
Preview of new feature in script editor:
nAJe5bw.png


This will include argument and result types for every sfall and vanilla scripting function. Descriptions are currently available for a subset of sfall functions (taken from sfall function notes.txt) and for all vanilla functions (from falloutmods.wikia.com)

New scripting functions:
  • checking if a straight line between two tiles is blocked and by which object (can check for walk blocking, shoot blocking, AI blocking)
  • similar as above, but checks given tile
  • get a list of all objects at given tile
  • get a list of all party members

Hook script change:
  • ability to override blocking check after "within perception", so you can allow critters to see you through windows, barrels, other critters, etc.
 
Last edited:
Awesome dude :salute:

I was wondering two things:

- is it perhaps possible to allow for bigger integers and/or longer floats? Maybe I'm just working stupidly, but I've already encountered a situation where I multiplied a number by 100 a couple of times in a row (to allow for high card tiebreakers in my poker game) resulting in a bad number coming out (it wasn't even that ridiculously long iirc maybe 10^12 or something), and a situation where the five allowed digits weren't enough for me (I wanted a value of 0.000009 or something). It's of course possible to find workarounds, but it feels fairly limited on both "sides". Especially with the bigger integers I'd like to at least see an error message instead of it spouting out a random number.

- maybe a simple check for whether a value is in an array? It could be that I've just missed it, and I've been using the workaround of "if scan_array(array, value) > 0 then...", but just in case it's been looked over by you as well I thought I might as well mention it.

edit: btw, I'm still using the previous version, so might be that some of these have been changed in the meanwhile, sorry.
 
I was wondering two things:

- is it perhaps possible to allow for bigger integers and/or longer floats? Maybe I'm just working stupidly, but I've already encountered a situation where I multiplied a number by 100 a couple of times in a row (to allow for high card tiebreakers in my poker game) resulting in a bad number coming out (it wasn't even that ridiculously long iirc maybe 10^12 or something), and a situation where the five allowed digits weren't enough for me (I wanted a value of 0.000009 or something). It's of course possible to find workarounds, but it feels fairly limited on both "sides". Especially with the bigger integers I'd like to at least see an error message instead of it spouting out a random number.
Well, all integers and floats are 4 bytes, so there is not much you can do about number of possible values for each variable (though If you really want it, I know there are workarounds...). However, try putting your large integer number into a float variable, because of how floats work, you can have extremely large number (only the precision may be lost).

As for the 5 digits after problem, I've added sprintf function just for that (your float can actually store the 0.000009 value, it's just when you add it to a string, vanilla stringification function is called, which always uses format %.5f).

- maybe a simple check for whether a value is in an array? It could be that I've just missed it, and I've been using the workaround of "if scan_array(array, value) > 0 then...", but just in case it's been looked over by you as well I thought I might as well mention it.
There is a macro in the latest sfall.h:
Code:
#define is_in_array(item, array)    (scan_array(array, item) != -1)
If you need to check if some key in associative array exists, just: if (array[key] != 0)

Check out the latest sfall and SSL libraries in my signature, I've written a lot of SSL helper procedures to work with arrays.

edit: btw, I'm still using the previous version, so might be that some of these have been changed in the meanwhile, sorry.
sprintf was added in 3.4, scan_array was always there :)
 
In case anyone is interested, here are some templates for the hookscripts phobos added (I really should have done all of them and included them in the modderspack but I'm lazy):

Code:
// hs_withinperception.int

procedure start;
variable watcher, target, in_range;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      watcher:=get_sfall_arg;   //Watcher object
      target:=get_sfall_arg;   //Target objet
      in_range:=get_sfall_arg;   //Result of vanilla function: 1 - within perception range, 0 - otherwise


      //set_sfall_return(-1);       //overrides the returned result of the function (1 or 0)
      
      //display_msg("hs_withinperception watcher " + watcher);
      //display_msg("hs_withinperception target " + target);
      //display_msg("hs_withinperception in_range " + in_range);
    
   end
end
Code:
// hs_steal.int

procedure start;
variable thief, target, item, is_planting;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      thief:=get_sfall_arg;   //Thief
      target:=get_sfall_arg;   //The target
      item:=get_sfall_arg;   //Item being stolen/planted
      is_planting:=get_sfall_arg;   //0 when stealing, 1 when planting
      
      //set_sfall_return(-1);       //overrides hard-coded handler (1 - force success, 0 - force fail, -1 - use engine handler)
      
      //display_msg("hs_steal thief " + thief);
      //display_msg("hs_steal target " + target);
      //display_msg("hs_steal item " + item);
      //display_msg("hs_steal is_planting " + is_planting);
    
   end
end
Code:
// hs_useskill.int

procedure start;
variable critter, object, skill, bonus;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      critter:=get_sfall_arg;   //The user critter
      object:=get_sfall_arg;   //The target object
      skill:=get_sfall_arg;   //skill being used
      bonus:=get_sfall_arg;   //skill bonus from items such as first aid kits
    
      //set_sfall_return(-1);       //overrides hard-coded handler (-1 - use engine handler, any other value - override)
      
      //display_msg("hs_useskill type " + type);
      //display_msg("hs_useskill object " + object);
      //display_msg("hs_useskill skill " + skill);
      //display_msg("hs_useskill bonus " + bonus);
    
   end
end
Code:
// hs_keypress.int

procedure start;
   variable type, key_vk, key_dx;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      type:=get_sfall_arg;   //event type: 1 - pressed, 0 - released
      key_dx:=get_sfall_arg;   //key DX scancode
      key_vk:=get_sfall_arg;   //key VK code (very similar to ASCII codes)
    
      //display_msg("hs_keypress type " + type);
      //display_msg("hs_keypress key_dx " + key_dx);
      //display_msg("hs_keypress key_vk " + key_vk);
    
   end
end
Code:
// hs_mouseclick.int

procedure start;
   variable type, button;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      type:=get_sfall_arg;   //event type: 1 - pressed, 0 - released
      button:=get_sfall_arg;   //button number (0 - left, 1 - right, up to 7)
    
      //display_msg("hs_mouseclick type " + type);
      //display_msg("hs_mouseclick button " + button);
    
   end
end
Code:
// hs_ammocost.int

procedure start;
variable weapon, number, cost, type;


#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\sfall.h"
#include "C:\GOG.com\Fallout 2 Mapper\scripts\HEADERS\define.h"


procedure start begin
   if init_hook then begin
      //you can put stuff here that only runs at game loaded
   end else begin
      weapon:=get_sfall_arg;   //weapon
      number:=get_sfall_arg;   //Number of bullets in burst (1 for single shots)
      cost:=get_sfall_arg;   //Ammo cost calculated by original function (this is basically 2 for Super Cattle Prod and Mega Power Fist)
      type:=get_sfall_arg;   //Type of hook (0 - when substracting ammo after attack, 1 - when checking for "out of ammo" before attack)


      //set_sfall_return(-1);       //new ammo cost value (set to 0 for unlimited ammo)
      
      //display_msg("hs_ammocost weapon " + weapon);
      //display_msg("hs_ammocost number " + number);
      //display_msg("hs_ammocost cost " + cost);
      //display_msg("hs_ammocost type " + type);
    
   end
end
 
Last edited:
@phobos2077, what exactly does the return value for hs_useskill do? I tried all kinds of different values for doctor when crippled and first aid when healing but all changing it from -1 does is cause nothing at all to happen.
 
Last edited by a moderator:
Back
Top