Fallout 2 mod FO2 Engine Tweaks (Sfall)

@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.
Exactly, it causes NOTHING to happen, so you can fully customize behavior of the skills. Though I recently discovered that it doesn't get called in some cases, like when lockpicking a door.

Also, try using register_hook_proc, it really improves modularity.
 
Eh, I like the hookscripts in that there's never any kind of overlap involved. Tho I'll definitely use the new register hooks to get in on things like map_exit.

This reminds me, did you ever end up trying to allow for adding spatial scripts dynamically? I imagine that that in combination with a hook to spatial_proc would make adding traps (both from the player and the opposition), a lot easier.
 
This reminds me, did you ever end up trying to allow for adding spatial scripts dynamically? I imagine that that in combination with a hook to spatial_proc would make adding traps (both from the player and the opposition), a lot easier.

Well not yet, and I'm not sure if we really need it, because arrays+iterating over all critters replaces this. And behind the scenes, spatials work exactly like that. If only for the sake of completeness :)
Do note that with the classical approach (no sfall scripts), you need 2 scripts for mines (or 1 combined script for both the spatial and the mine object). But I believe I would've finished my traps system WAY sooner if only I could manage spatials dynamically :) (maybe there would be no advanced arrays right now :D )

I'm currently researching ways to tune AI combat behavior. In my opinion, it works pretty good in most actions (healing, running away, picking up weapons, etc.), but the way critters approach their target can really be tuned. Specifically I want them to flank and use cover when a firefight is taking place. Using new line of sight function, it's very easy to create cover system (just reduce CTH when you are behind something), but much harder to make AI use it (currently impossible, AFAIK).
Still I don't know how to do it properly. It's either adding a whole lot of new scripting functions and some high-level hook script to override attacking behavior altogether, OR some kind of specifically targeted hacks (just like explosions_metarule function). What do you think, Jim?

PS: I need to add new normal script for some object, can anyone hint if there is a possible compatibility problems when adding new scripts to scripts.lst, like older savegames not working?
 
Last edited:
@phobos2077 I'm not really sure about the cover stuff; I like the aggressivity of Fallout combat ;)

I think the part about how the AI approaches its target is spot on. For melee enemies it kind of makes sense that they would blindly storm towards you, but the phenomenon where you're hiding behind a building and a guy with a gun comes round the corner and spends his last few AP coming even closer for an easy target is ridiculous (he should really take that last AP and step back behind the wall). Maybe a quick fix where ranged weapon AI doesn't spend surplus AP's storming towards its enemy would already come a long way.
 
Last edited by a moderator:
The critical table overriding isn't working as expected.

1. static void __declspec(naked) funcResetCriticalTable(), mistakenly called ResetCriticalTable.
Plus it is broken, causing (exception flow?) the following opcodes not executed.
=> mov dl, 4; before "loops:" is also wrong, should be at least dx.
(BTW it's nonsense to do less-than-32bit scalar operation, since code size is not an issue.)

2. The critical table overriding isn't working as expected. My settings have no effect.
With OverrideCriticalTable=3 and debug enabled, "Setting up critical hit table" doesn't appear in debug log.
=> debugging/ddraw.ini is misleading and tells that the logging file "debug.log". Should be sfall-log.txt.
Looks like overrided critical table isn't loaded at all.
=> config_files/CriticalOverrides.ini gives wrong example. "Part0=1" should be "Part_0=1".
 
Last edited:
2. The critical table overriding isn't working as expected. My settings have no effect.
With OverrideCriticalTable=3 and debug enabled, "Setting up critical hit table" doesn't appear in debug log.
Looks like overrided critical table isn't loaded at all.
It works for me, at least I did some tests with my CriticalOverrides.ini to verify the armor bypass effect before.
EDIT: even with the latest SVN build it still works. Are you sure you set up the INI correctly?
 
Last edited:
@phobos2077 one last thing about the AI, the biggest AI problem besides target approach is in my mind target selection. Tho I must admit I haven't played the game outside of testing in a long while, I distinctly remember being able to lure enemies by firing at them and retreating, causing them to ignore your party members firing at them.

This, however, should be amenable through hs_findtarget, but that one still is a bit vague to me; for one it definitely doesn't always gets called in combat, but that could have been because I wasn't testing properly.
 
Last edited by a moderator:
2. The critical table overriding isn't working as expected. My settings have no effect.
With OverrideCriticalTable=3 and debug enabled, "Setting up critical hit table" doesn't appear in debug log.
Looks like overrided critical table isn't loaded at all.
It works for me, at least I did some tests with my CriticalOverrides.ini to verify the armor bypass effect before.
EDIT: even with the latest SVN build it still works. Are you sure you set up the INI correctly?
I was confused because I can't find any relevant debug output in debug.log, until I read code and found out that sfall log is sfall-log.txt, not debug.log
I was a fool to believe in sfall documentation again.:shock:

And as for the critical table:
Part0=1 ; Tell sfall that we want to modify the head entries
; If we had set 'Enabled=2' then sfall will assume you want to modify all
; parts, and will not check the PartX entries
Misleading doc again.

Code reads
int all;
if(!(all=GetPrivateProfileIntA(buf, "Enabled", 0, ".\\CriticalOverrides.ini"))) continue;
for(int part=0;part<9;part++) {
if(all<2) {
sprintf_s(buf2, "Part_%d", part);
if(!GetPrivateProfileIntA(buf, buf2, 0, ".\\CriticalOverrides.ini")) continue;
}
So it should be
Code:
[c_00]
Enabled=1
Part_0=1
Part_3=1
Part_6=1
 
Last edited:
Has anyone ever used get_script function? The person who created it clearly has mistaken to think that it returns script ID (in terms of script indexes from scripts.lst, the one that we pass to create_object_sid), but in reality it's entirely different entity - ID of the script in some internal data structure. Do we need it or should I better change it to return actual script index instead?
set_script also works incorrectly, it accepts script index directly, but it should be decremented by 1 to be consistent with other SIDs. I'm really tempted to fix this, but it WILL break existing scripts which might be used it (RP does NOT use it).

One possible application of get_script returning internal ID is ability to move script to another critter along with all local variables (probably), but set_script won't do it currently.

I did some research on what structures are involved with scripts and come up of this:
1) script program (basically contents of .INT file with some additional info maybe, like stack and stuff) is loaded for each instance of a script (example - many rats on one map have one SID, but each will have it's own copy of the program in memory)
2) script meta data structure - it contains a proc lookup table, pointer to script program, pointers to self_obj, target_obj, etc (fun fact - target_obj and obj_being_used_with are the same variable!), spatial data (see below)
3) spatial scripts are script meta structures (with their programs of course). The main difference from normal objects (maybe a reason why you can't create spatial with create_object_sid), is that normally - game object is primary, and script is secondary (there can be no script assigned to an object). However it's the other way around with spatials, the script is primary (it is iterated directly when checking from spatial) and the object is secondary. It doesn't exist by default, ONLY created when script calls self_obj from inside the spatial script.

Using this I managed to add ability to iterate over all spatials using list_as_array(6) and create spatial using new function. You can't currently move the spatial or change it's properties (moving associated object with move_to will not move the script itself), but you can delete it with with destroy_object and create again. Will this suffice?
 
Last edited:
I'm fairly sure nobody's using the set_script function (probably because the only use I can find for it lies in changing an npc's art (say upgrading them from leather armor to metal armor), but without get_script working properly like you said, that's hardly possible).

The spatial script stuff sounds great, tho if like you said it's already pretty much possible to recreate them with global scripts it's not that important to sink a lot of time in.
 
I'm fairly sure nobody's using the set_script function (probably because the only use I can find for it lies in changing an npc's art (say upgrading them from leather armor to metal armor), but without get_script working properly like you said, that's hardly possible).

The spatial script stuff sounds great, tho if like you said it's already pretty much possible to recreate them with global scripts it's not that important to sink a lot of time in.

Well, fortunately, it was interesting enough to spend last couple of days on this, so the job is done :) I had to manually create those scripts as there was no function for this. And when iterating over spatials in list_* functions, I had to make it create proper fake object for each spatial. You can use it to delete spatial or get it's tile, elevation or scriptID (tested it). Not sure if we need function to get/set the radius.

This stuff may be useful to add dynamic traps against the player (eg. IIMINE script is standalone), without using complex trap system (like mine).

I've gone ahead and fixed set_script and get_script functions to work like you would expect it (get and set scriptID by line number in scripts.lst). Also set_script was broken, as it created invalid script type and critter_p_proc wasn't working. It should work nicely now.

PS: how would change art by set_script? This is already done by some other function (party appearance mod), is it not?
 
Last edited:
PS: how would change art by set_script? This is already done by some other function (party appearance mod), is it not?

I meant in the sense that, for instance, you eliminate the slavers in NCR before taking care of metzger, and then metzger gets wealthy with less competition, so his guard gets beefed up with better weapons, stats, xp value, and armor. All of these you could do without get/set-script, but to get them to look like they have better armor I think you need to place a new PID (and thus reapply its script). I could have been wrong about this, I didn't look into it for very long tbh.
 
I meant in the sense that, for instance, you eliminate the slavers in NCR before taking care of metzger, and then metzger gets wealthy with less competition, so his guard gets beefed up with better weapons, stats, xp value, and armor. All of these you could do without get/set-script, but to get them to look like they have better armor I think you need to place a new PID (and thus reapply its script). I could have been wrong about this, I didn't look into it for very long tbh.

That's a fun idea... For my mod :D
If you need to change critter to a more powerful one, you better off just deleting him and creating with a new PID - it will be simpler and less error-prone. You want better inventory anyway.
 
Scripts set on a critter no long run if the critter is dead?

Just looked at the engine code - script is removed when critters dies. I had an idea for my mod - some medical stuff to revive fallen party members. I was disappointed when couldn't find any way to revive them by script.
However, now I see that there is no point in this, because the only thing you can't reproduce by removing dead body and creating new critter by create_object_sid is his local variable values. Which are erased anyway when he dies :)
So if you blindly revive your follower and reattach his script - all kinds of funny bugs will happen in his dialogs or reactions. Need to find a way to extract local variables from an object script :)

I spent last couple of days working on compiler and script editor again. I wanted to share some important changes:
  • implemented optional arguments for user-defined procedures (like in other languages, just set default value in last arguments using ":=" )
  • implemented stringify procedure names using @ operator, which is helpful to pass procedures around to call them from variables (it will properly handle references)
  • now it is possible to call user-defined procedures inside argument list of scripting functions, without compiler attempting to treat them as procedure references and probably fail (procedures will still be passed, but only to appropriate scripting functions at appropriate argument positions)

I will probably start a new thread about this. Especially the "calling procedures by string" feature needs some light, as it brings some nice possibility for functional programming. It's important to note that this is a vanilla scripting feature. Sfall is not required. I used it for arrays when I needed to quickly print names of objects in an array without writing the actual loop:
Code:
procedure _name(variable a) begin
   if not(a) then return "NULL";
   return obj_name(a)+"("+obj_type(a)+","+obj_pid(a)+")";
end

// use callback on each array element
procedure array_map_func(variable arr, variable callback) begin
   variable k, v, i;
   foreach (k: v in arr)
      arr[k] := callback(v);
   return arr;
end

// show name and other important info about each critter on a map
// @_name is converted to string constant "_name" on compile time and called in array_map_func
display_array(array_map_func(list_as_array(LIST_CRITTERS), @_name));


Edit: have anyone ever used set_self ?
I've added ability to get/set local vars from another object's script using set_self. Now I'm again tempted to change it so you don't have to call it before EVERY other function call. How about reset it only at the end of the frame?
 
Last edited:
drug proto values for the extra define.h:

Code:
// drugs
#define PROTO_DR_STAT_TYPE_ONE     (36)
#define PROTO_DR_STAT_TYPE_TWO     (40)
#define PROTO_DR_STAT_TYPE_THREE     (44)
#define PROTO_DR_FIRST_EFFECT_ONE     (48)
#define PROTO_DR_FIRST_EFFECT_TWO     (52)
#define PROTO_DR_FIRST_EFFECT_THREE     (56)
#define PROTO_DR_FIRST_DURATION    (60)
#define PROTO_DR_SECOND_EFFECT_ONE      (64)
#define PROTO_DR_SECOND_EFFECT_TWO    (68)
#define PROTO_DR_SECOND_EFFECT_THREE    (72)
#define PROTO_DR_SECOND_DURATION    (76)
#define PROTO_DR_THIRD_EFFECT_ONE       (80)
#define PROTO_DR_THIRD_EFFECT_TWO     (84)
#define PROTO_DR_THIRD_EFFECT_THREE    (88)
#define PROTO_DR_ADDICTION_CHANCE    (92)
 
drug proto values for the extra define.h:

Thanks, but how about something less verbose?

Code:
#define PROTO_DR_STAT_A          (36)
#define PROTO_DR_STAT_B          (40)
#define PROTO_DR_STAT_C          (44)
#define PROTO_DR_AMOUNT_1_A      (48)
#define PROTO_DR_AMOUNT_1_B      (52)
#define PROTO_DR_AMOUNT_1_C      (56)
#define PROTO_DR_DURATION_1      (60)
#define PROTO_DR_AMOUNT_2_A      (64)
#define PROTO_DR_AMOUNT_2_B      (68)
#define PROTO_DR_AMOUNT_2_C      (72)
#define PROTO_DR_DURATION_2      (76)
#define PROTO_DR_AMOUNT_3_A      (80)
#define PROTO_DR_AMOUNT_3_B      (84)
#define PROTO_DR_AMOUNT_3_C      (88)
#define PROTO_DR_ADDICT_CHANCE   (92)
#define PROTO_DR_ADDICT_PERK     (96)
#define PROTO_DR_ADDICT_DELAY   (100)


CombatDamage hook script was expanded with hooks in more places (where original function is called by the engine), additional arguments and return values. Here's updated description:
Code:
Runs when:
1) Game calculates how much damage each target will get. This includes primary target as well as all extras (explosions and bursts). This happens BEFORE the actual attack animation.
2) AI decides whether it is safe to use area attack (burst, grenades), if he might hit friendlies.
Does not run for misses, or non-combat damage like dynamite explosions.

critter arg1  - The target
critter arg2  - The attacker
int     arg3  - The amount of damage to the target
int     arg4  - The amount of damage to the attacker
int     arg5  - The special effect flags for the target
int     arg6  - The special effect flags for the attacker
int     arg7  - The weapon used in the attack
int     arg8  - The bodypart that was struck
int     arg9  - Roll result of the attack (check with is_success, etc functions; basically: 0-crit. fail, 1-fail, 2-success, 3-crit. success)
int     arg10 - Number of bullets actually hit the target (1 for melee attacks)
int     arg11 - The amount of knockback to the target

int     ret1 - The damage to the target
int     ret2 - The damage to the attacker
int     ret3 - The special effect flags for the target
int     ret4 - The special effect flags for the attacker
int     ret5 - The amount of knockback to the target

If anyone was considering to make yet another "ammo mod" (damage formula), please use this hook instead. It has all necessary info to replace damage calculations from scratch.
 
Last edited:
I must post this... I've finally fixed sfall scripts so they work in line with normal scripts:

1) ALL event functions should work now, I tested UI events such as AddRegionProc, AddButtonProc - this allows to move custom UI's code from dude_obj into global scripts!
2) Exported procedure finally work for sfall scripts too! I tested calling map procedure from global script and global script procedure from another global. You can pass arguments and receive return value like "local" calls. Finally some proper programming!

And the problem was simple... solution was at plain sight all this time, I just needed to see how game initializes scripts and for some reason, Timeslip and me didn't notice one extra function call which adds loaded program into array or something.

In other news:
Added script function "obj_is_carrying_obj" - like obj_is_carrying_obj_pid, but by objectPtr. Useful when dealing with different stacks of the same item type.
added script function "art_exists", useful for example to check if critter can use weapon
added hs_inventorymove hook script, called when wielding/unwielding items from inventory screen and reloading your weapons by dragging ammo over

PS: also stumbled upon those functions today: AddNamedHandler , AddNamedEvent , SignalNamed , ClearNamed. You can hook events to specific names to be callable one or multiple times. They basically implement Event Dispatcher pattern. Not sure if they will be useful, but I've added them to reference here.
 
Last edited:
@phobos2077, I was wondering: could it be possible, now that combatdamage can tell how many bullets entered the target and gets called for all extra targets, if afterhitroll (or a new hookscript) could be used to override what these extra targets are and how many bullets hit? You can already change which "mistaken" target the attack hits with afterhitroll, so it doesn't seem that much of a stretch.

You could do all kinds of fun things this way, like lasers passing through targets to those standing behind, plasma weapons doing "splash" damage to those around the target, shotguns having spray, etc. etc. (I actually already do all these things but in the extremely inelegant way of calling critter_dmg in afterhitroll).
 
@phobos2077, I was wondering: could it be possible, now that combatdamage can tell how many bullets entered the target and gets called for all extra targets, if afterhitroll (or a new hookscript) could be used to override what these extra targets are and how many bullets hit? You can already change which "mistaken" target the attack hits with afterhitroll, so it doesn't seem that much of a stretch.

You could do all kinds of fun things this way, like lasers passing through targets to those standing behind, plasma weapons doing "splash" damage to those around the target, shotguns having spray, etc. etc. (I actually already do all these things but in the extremely inelegant way of calling critter_dmg in afterhitroll).

Well of of course this is possible. I'm not sure how many efforts it will take to accomplish though. This won't be a simple hook script, you will need to make it possible to pass a list of targets to sfall, which must call compute_damage function for each target.
 
Back
Top