Fallout Tactics utility FTSE - Fallout Tactics Scripting Engine (0.55a)

Right. Thanks for detailed answer. Also...

* Disabled hex patch for burst bug (replaced by scripting fix)

I assume this means that I don't need to change "Burst bug fix" - "false" to "true", because it was fixed by some other method?
 
Hi Melindil!

OK, I think I made enough tests to understand what happened why trying to launch the game in Mac OSX version.

It is clear to me that the engine is not starting at all. Both ftse.lua and FTSE_config.json have no effect on the game, and no log is produced.

If you remember, colors where a bit tempered with, even without the engine starting, so I was wondering. I realised that I did tempered with def_style.cfg file, following some codes you managed to find. I had this applied which explain everything :

Code:
color_bosText = {Color(255,255,255)} //yellow text on most panels
color_consoleGreen = {Color(89,89,89)} //default green text
color_consoleSelect = {Color(255,167,0)} //highlighted text

At this point, I manually HEX edited bos.exe to make a temporary MAC version without your engine. My question is this : How do I translate the rest of the color codes you have there into def_style.cfg? And do they even exist?

Code:
style:SetColorOptionsPages(0.20,0.20,0.20)
style:SetColorPanelTitles(1.0,1.0,1.0) 
style:SetColorBuffs(1.0,1.0,0.0)
style:SetColorDebuffs(1.0,0.0,0.0)
style:SetColorTags(1.0,1.0,0.0)

Also, any luck on finding the color code for the tooltips background? It is currently transparent black.

Thanks a lot!
 
Hi Melindil!

OK, I think I made enough tests to understand what happened why trying to launch the game in Mac OSX version.

It is clear to me that the engine is not starting at all. Both ftse.lua and FTSE_config.json have no effect on the game, and no log is produced.

I still owe you a debugging session on this. I've been ridiculously busy lately, so I don't know when I'll be able to get to this.

If you remember, colors where a bit tempered with, even without the engine starting, so I was wondering. I realised that I did tempered with def_style.cfg file, following some codes you managed to find. I had this applied which explain everything :

Code:
color_bosText = {Color(255,255,255)} //yellow text on most panels
color_consoleGreen = {Color(89,89,89)} //default green text
color_consoleSelect = {Color(255,167,0)} //highlighted text

At this point, I manually HEX edited bos.exe to make a temporary MAC version without your engine. My question is this : How do I translate the rest of the color codes you have there into def_style.cfg? And do they even exist?

Code:
style:SetColorOptionsPages(0.20,0.20,0.20)
style:SetColorPanelTitles(1.0,1.0,1.0) 
style:SetColorBuffs(1.0,1.0,0.0)
style:SetColorDebuffs(1.0,0.0,0.0)
style:SetColorTags(1.0,1.0,0.0)

Also, any luck on finding the color code for the tooltips background? It is currently transparent black.

Thanks a lot!

I think the options pages and panel titles were already in the default .cfg file, but I don't recall which ones they were.

The buffs, debuffs, and tags were only changeable in the character sheet code, since they didn't use the DefaultStyle object. These won't work without FTSE.
 
The buffs, debuffs, and tags were only changeable in the character sheet code, since they didn't use the DefaultStyle object. These won't work without FTSE.

Thanks for the anwser to this. Too bad for the color stuff, I'll be patient then. I think I'll postpone the Mac version for now, it is not as I have to complete it right now. I'll wait your pointers on what might be happening. Must be something pretty small, I can't see why Wine could now handle some extra .dll after all...
 
When I installed this, certain features of my game seems to role back to earlier version of the game.

Quick Keys help shows me the un-comprehensive letters, certain weapon has a description before the patches, etc.

It happened after I installed this patch.
 
When I installed this, certain features of my game seems to role back to earlier version of the game.

Quick Keys help shows me the un-comprehensive letters, certain weapon has a description before the patches, etc.

It happened after I installed this patch.
That .. shouldn't happen.

The FTSE installer only modifies part of the startup code of the game executable, to load and run init for the hook library. This should have zero effect on game text or functionality beyond that provided in the hooks.

To double-check, you can rename the .ftse.bak file back to .exe, and try running the game again. If the problem persists, then it is unrelated to FTSE.

Also - would it be possible to get a screenshot, or text of the wrong weapon description?
 
1. Is there any chance you would update your wiki with Available LUA functions soon?
2. What is your time schedule for this year? I'm asking because the amount of implement features would make some of my modding easier and I don't want waste my rare free time to create another triggering hell which may be soon obsolete thanks to your new feratures and bug fixes...
 
That .. shouldn't happen. The FTSE installer only modifies part of the startup code of the game executable, to load and run init for the hook library. This should have zero effect on game text or functionality beyond that provided in the hooks.
I'm glad it's not supposed to be happen because of this patch.

Also - would it be possible to get a screenshot, or text of the wrong weapon description?
fbos0012.jpg

Here is that infamous Quick Key help text, which is the sign of outdated locale file,

fbos0011.jpg

DevilThorn Jacket is called DevilThron Coat instead,

fbos0013.jpg

And the description of M16A1 is outdated. It supposedly starts with "The M16A1 was the chief rifle used by (...)" with the latest patch.

I think I have extracted .BOS file that contains all the outdated contents and overwritten the patched content with them, although I did carefully try not to overwrite anything while extracting .BOS files.

Only solution I can think of is to try verifying game files with Steam; that way the files I have overwrote will role back to the latest patch version.
 
I can confirm the Devilthorn Coat/Jacket discrepancy is from overwriting core/locale/game/items.txt from the compressed BOS file. The items.txt file in my install has the correct "Devilthorn Jacket" name, but the locale_0.bos file has "Devilthorn Coat".

For the M16A1, the description I have in my game matches your screenshot. But this is also in items.txt, so any change to that might have replaced a fix to that text from another mod.

I can also confirm the help file text works correctly in my install. I'm not sure how this bug ends up being triggered if locale files are replaced - I'd have to dig into how this text gets loaded to see why a file overwrite would break it. But it looks like the game is displaying uninitialized memory of some sort.
 
Updated (finally!) to 0.45a.

Key additions:

Added a new installer UI, which allows for patching/unpatching the game EXE, as well as selecting or selecting hex patches to apply.

Hex patches to fix the special encounter crash, fix the critical effect behaviors for melee and unarmed attacks, and fix the 15% bonus for Bonus H2H Damage.

Scripting hooks for many parts of the combat calculation - chance to hit, chance to critical, critical effect, damage, and behavior of shot types (burst, cone, spray, etc.).

Refactoring of objects passed to Lua to better match the entity class hierarchy from the game. (Not everything is an Actor anymore!)

Class types for Ammo, Breakable, Trap, Vehicle, VehicleWeapon, and Weapon are now recognized, and functions to retrieve class-specific values are provided. Remaining class types will be (hopefully) completed in 0.50.
 
Hello everyone! This is my first comment here.

Thank you for the excellent mod Melindil!

I'm currently working on a complete unarmed/melee combat overhaul and made a few simple extensions to FTSE. Would it be possible to include them in some future version of FTSE or functions similar in purpose? The mod is still work in progress, but other modders might also find the new methods useful.

The first batch is about the equipped slot1, slot2 weapons and which one is active, also the punch and kick entity belonging to the actor. The address offsets: weapon slot1 - 0x1010, weapon slot2 - 0x1014, unarmed punch: 0xfa0, unarmed kick: 0xfa8 (returns weapon's id), active weapon slot: 0x1029 (returns boolean - false: slot1, true: slot2).
Code:
const std::map<std::string, Actor::ActorFOTOffset> Actor::offsets{
...
{ "issecondweaponslotactive", {0x1029, Actor::FieldType::BOOLEAN} },
...
};

void Actor::RegisterLua(lua_State* l, Logger* tmp)
{
...
lua_pushcfunction(l, l_actor_getactiveweapon);
lua_setfield(l, -2, "GetActiveWeapon");
...
}

int l_actor_getactiveweapon(lua_State* l)
{
   Actor e(LuaHelper::GetEntityId(l));
   uint16_t active_weapon_id = e.GetActiveWeaponId();
   Entity::GetEntityByID(active_weapon_id)->MakeLuaObject(l);
   return 1;
}

uint16_t Actor::GetActiveWeaponId()
{
   auto iter = offsets.find("issecondweaponslotactive");
   if (iter == offsets.end())
   {
       return 0;
   }

   uint32_t base = (uint32_t)GetEntityPointer();

   char second_active = *((char*)(base + iter->second.offset));
   uint32_t weapon_offset = second_active ? 0x1014 : 0x1010;
   uint32_t unarmed_offset = second_active ? 0xfa8 : 0xfa0;

   uint16_t active_weapon_id = *((uint16_t*)(base + weapon_offset));
   if (!active_weapon_id)
   {
       active_weapon_id = *((uint16_t*)(base + unarmed_offset));
   }

   return active_weapon_id;
}
The second list of functions help with converting the ap field number to a more comprehensive action point value.
Code:
void Actor::RegisterLua(lua_State* l, Logger* tmp)
{
...
lua_pushcfunction(l, l_actor_getapvalue);
lua_setfield(l, -2, "GetAPValue");
lua_pushcfunction(l, l_actor_setapvalue);
lua_setfield(l, -2, "SetAPValue");
lua_pushcfunction(l, l_actor_addapvalue);
lua_setfield(l, -2, "AddAPValue");
...
}

int l_actor_getapvalue(lua_State* l)
{
   Actor e(LuaHelper::GetEntityId(l));
   int32_t apValue = e.GetAPValue();
   lua_pushinteger(l, apValue);
   return 1;
}

int l_actor_setapvalue(lua_State* l)
{
   Actor e(LuaHelper::GetEntityId(l));
   int32_t apValue = (int32_t)lua_tointeger(l, 2);
   e.SetAPValue(apValue);
   return 0;
}

int l_actor_addapvalue(lua_State* l)
{
   Actor e(LuaHelper::GetEntityId(l));
   int32_t apValue = (int32_t)lua_tointeger(l, 2);
   e.AddAPValue(apValue);
   return 0;
}

int32_t Actor::GetAPValue(int32_t apNumber)
{
   if (apNumber <= 0) return 0;

   float level = (apNumber - 1065353216) / (float)8388608;
   int32_t exponent = std::floor(level);
   float mult = 1 + level - exponent;

   return std::pow(2, exponent) * mult;
}

int32_t Actor::GetAPValue()
{
   int32_t apNumber = GetAPNumber();
   return GetAPValue(apNumber);
}

void Actor::SetAPValue(int32_t apValue)
{
   auto iter = offsets.find("ap");
   if (iter == offsets.end())
   {
       return;
   }

   *((int32_t*)GetAPNumberAddress()) = GetAPNumber(apValue);
}

void Actor::AddAPValue(int32_t apValue)
{
   int32_t apNumber = GetAPNumber();

   if (apValue == 1)
       *((int32_t*)GetAPNumberAddress()) = GetNextAPNumber(apNumber);
   else if (apValue == -1)
       *((int32_t*)GetAPNumberAddress()) = GetPreviousAPNumber(apNumber);
   else
       *((int32_t*)GetAPNumberAddress()) = GetAPNumber(GetAPValue(apNumber) + apValue);
}

int32_t Actor::GetAPNumber(int32_t apValue)
{
   if (apValue <= 0) return 0;

   int32_t exponent = std::floor(std::log10(apValue) / std::log10(2));
   float mult = apValue / std::powf(2, exponent);
   float level = exponent + mult - 1;
   return level * 8388608 + 1065353216;
}

int32_t Actor::GetAPNumber()
{
   return *((int32_t*)GetAPNumberAddress());
}

int32_t Actor::GetAPNumberAddress()
{
   auto iter = offsets.find("ap");
   if (iter == offsets.end())
   {
       return 0;
   }

   return (uint32_t)GetEntityPointer() + iter->second.offset;
}

int32_t Actor::GetNextAPNumber(int32_t currNumber)
{
   if (currNumber == 0) return 1065353216;

   return currNumber + 8388608 / std::pow(2, (std::floor((currNumber - 1065353216) / 8388608)));
}

int32_t Actor::GetPreviousAPNumber(int32_t currNumber)
{
   if (currNumber == 0) return 0;

   return currNumber - 8388608 / std::pow(2, (std::ceil((currNumber - 1065353216) / 8388608 - 1)));
}
The third ones are just some bounding box getters.
Code:
int l_entity_getboundingboxsum(lua_State* l)
{
   Entity e(LuaHelper::GetEntityId(l));
   float bboxsum = e.GetBoundingBoxSum();
   lua_pushnumber(l, bboxsum);
   return 1;
}

int l_entity_getheight(lua_State* l)
{
   Entity e(LuaHelper::GetEntityId(l));
   float height = e.GetHeight();
   lua_pushnumber(l, height);
   return 1;
}

void Entity::SetLuaSubclass(lua_State * l)
{
...
lua_pushcfunction(l, l_entity_getboundingboxsum);
lua_setfield(l, -2, "GetBoundingBoxSum");
lua_pushcfunction(l, l_entity_getheight);
lua_setfield(l, -2, "GetHeight");
...
}
If I understand it correctly, the true distance between two actors: position distance - (actor1 bounding box sum + actor2 bounding box sum) * 0.25. The GetHeight is for example to determine whether an actor is knocked down (not out). Hopefully there is a more robust solution, unfortunately I did not find it (would be still great to compare the height of two actors).

I'm not much of Lua coder (only started four days ago), so this is probably a rookie question: is there a reason why you do not use local variables in your Lua code?

I know this is already a long comment, but would you be so kind as to verify two things about the FT engine:
The https://github.com/melindil/FTSE/wiki/Critical-hit-chance-and-effects states that the critical roll is between 1 and 100 (inclusive). On the other hand the OnCriticalEffect1-2 hook's roll parameter can go above 100. Does this mean it's reduced after the hook has returned?

The following formula "If attacker is using a ranged weapon, target is using melee/unarmed, and the attacker is within distance of 3: subtract 1 - (distance / 3.0) * target's melee/unarmed skill * 0.33" can be found in https://github.com/melindil/FTSE/wiki/Chance-to-hit-calculations. Is this correct (if it is please explain)?
 
Last edited:
Hello everyone! This is my first comment here.

Welcome!

Thank you for the excellent mod Melindil!


I'm currently working on a complete unarmed/melee combat overhaul and made a few simple extensions to FTSE. Would it be possible to include them in some future version of FTSE or functions similar in purpose? The mod is still work in progress, but other modders might also find the new methods useful.

Of course! I will gladly accept enhancements and contributions to the mod. I'll include these in the 0.50 version currently in progress. I'll also try to keep the Git repo up-to-date, in case some of the in-progress work might be helpful for you.

The first batch is about the equipped slot1, slot2 weapons and which one is active, also the punch and kick entity belonging to the actor. The address offsets: weapon slot1 - 0x1010, weapon slot2 - 0x1014, unarmed punch: 0xfa0, unarmed kick: 0xfa8 (returns weapon's

Perfect timing on this one - I have been planning to come back and add more to the Actor entity type after getting the other entity types done. It's still very incomplete, even though it was the first type I worked on decoding. I'll eventually get a full struct definition for Actor like the other entity types have (this will replace most of the "offsets" map), but the code you have for selecting the proper active weapon will still work well with only minor changes.

The second list of functions help with converting the ap field number to a more comprehensive action point value.

This one actually looks like a bug on my part. The game appears to hold the AP value as a float rather than an int type. Thinking about it, this probably makes sense, as fractional AP could be useful for both CTB recharge timing, as well as movement allowance for the turn-based modes. I'll change the definition and return type to float, and see if this gives better results.

The third ones are just some bounding box getters.

I'll add these in. While I'm at it, I'll also add something in the Entity class to get distance between two entities, both as center-to-center distance and boundingbox-to-boundingbox (different parts of the BOS code use each calculation, so both may be useful).


I'm not much of Lua coder (only started four days ago), so this is probably a rookie question: is there a reason why you do not use local variables in your Lua code?

I'm not all that experienced with Lua either :). I mainly like it for how easy it is to integrate into C/C++. In this case, it looks like I'm getting confused with Python, where function-assigned variables are by default local. I'll fix the base Lua code and the Wiki examples.

The Critical-hit-chance-and-effects states that the critical roll is between 1 and 100 (inclusive). On the other hand the OnCriticalEffect1-2 hook's roll parameter can go above 100. Does this mean it's reduced after the hook has returned?

Correct. I pass the full value of the effect roll, in case a mod wants to reduce the roll prior to applying the cap (reducing after the cap makes a 100 roll impossible even if other bonuses could allow for it), or if a mod wants to add further effects for rolls greater than 100 (e.g. by returning a table with flags set, rather than a roll value). If the script doesn't set explicit flags, the BOS code will cap the value to 1-100 after the script runs. Chance to hit and chance to crit work similarly, though chance-to-hit splits the value into pre-cap and post-cap, as BOS code actually tracks both values; e.g. to apply the location-specific adjustments for the aimed shot window.

The following formula "If attacker is using a ranged weapon, target is using melee/unarmed, and the attacker is within distance of 3: subtract 1 - (distance / 3.0) * target's melee/unarmed skill * 0.33" can be found in Chance-to-hit-calculations. Is this correct (if it is please explain)?

This one was an interesting find. The game actually grants the equivalent of an AC bonus if a melee/unarmed character gets close enough to an attacker using a ranged weapon (presumably, their experience in melee/unarmed helps them dodge or deflect the weapon?). The maximum AC bonus is equal to the character's corresponding active weapon skill (Melee Weapons or Unarmed depending on active weapon or attack type - note that it is required to actually have a melee/unarmed weapon or attack selected) multiplied by 0.33. This maximum bonus applies if the melee character is immediately adjacent to the attacker's bounding box. If the melee character is further away, the bonus reduces linearly with increasing distance, until it disappears entirely at a distance of 3 hexes. This is a big factor in the "Peoria turrets can't hit close-range attackers" observation - a 100% melee/unarmed skill (achievable by that mission) drops the turret's hit chance by 33%, which, along with armor AC, is enough to make it almost always miss. I've actually been curious to see if a high unarmed build (+ H2H evade + armor + Dodger + etc.) can give enough AC to let a character survive at close range to burst and other strong weapons. Even without that, there's still a small benefit even for normally ranged units to switch to a secondary melee/unarmed weapon near end-of-turn if an enemy is close.
 
Of course! I will gladly accept enhancements and contributions to the mod. I'll include these in the 0.50 version currently in progress. I'll also try to keep the Git repo up-to-date, in case some of the in-progress work might be helpful for you.

Perfect timing on this one - I have been planning to come back and add more to the Actor entity type after getting the other entity types done. It's still very incomplete, even though it was the first type I worked on decoding. I'll eventually get a full struct definition for Actor like the other entity types have (this will replace most of the "offsets" map), but the code you have for selecting the proper active weapon will still work well with only minor changes.

I'll add these in. While I'm at it, I'll also add something in the Entity class to get distance between two entities, both as center-to-center distance and boundingbox-to-boundingbox (different parts of the BOS code use each calculation, so both may be useful).

I'm not all that experienced with Lua either :). I mainly like it for how easy it is to integrate into C/C++. In this case, it looks like I'm getting confused with Python, where function-assigned variables are by default local. I'll fix the base Lua code and the Wiki examples.
Awesome, thank you!
This one actually looks like a bug on my part. The game appears to hold the AP value as a float rather than an int type. Thinking about it, this probably makes sense, as fractional AP could be useful for both CTB recharge timing, as well as movement allowance for the turn-based modes. I'll change the definition and return type to float, and see if this gives better results.
Yes it is float... It's official: I'm an idiot. If I had taken a closer look at the numbers I could have (more like should have) realised it, instead I just draged said numbers through the calculator and wrote down the equation.
This one was an interesting find. The game actually grants the equivalent of an AC bonus if a melee/unarmed character gets close enough to an attacker using a ranged weapon (presumably, their experience in melee/unarmed helps them dodge or deflect the weapon?). The maximum AC bonus is equal to the character's corresponding active weapon skill (Melee Weapons or Unarmed depending on active weapon or attack type - note that it is required to actually have a melee/unarmed weapon or attack selected) multiplied by 0.33. This maximum bonus applies if the melee character is immediately adjacent to the attacker's bounding box. If the melee character is further away, the bonus reduces linearly with increasing distance, until it disappears entirely at a distance of 3 hexes. This is a big factor in the "Peoria turrets can't hit close-range attackers" observation - a 100% melee/unarmed skill (achievable by that mission) drops the turret's hit chance by 33%, which, along with armor AC, is enough to make it almost always miss. I've actually been curious to see if a high unarmed build (+ H2H evade + armor + Dodger + etc.) can give enough AC to let a character survive at close range to burst and other strong weapons. Even without that, there's still a small benefit even for normally ranged units to switch to a secondary melee/unarmed weapon near end-of-turn if an enemy is close.
So, it is as I suspected (did a few tests using the distance calculations), just got a bit confused by the formula. In the overhaul mod I made some rules to counter the effect, if and when the shooter's choise of weapon is a pistol.

Currently I'm trying to modify the locational penaltys for targeted shots without success. Do you know perhaps their relative memory address or a method to find them?
 
Currently I'm trying to modify the locational penaltys for targeted shots without success. Do you know perhaps their relative memory address or a method to find them?

Yep - the table for aiming locations is in the EXE at location 0x4a8dac. Each entry is 12 bytes - first four bytes points to the string naming that location (in order: torso, head, eyes, right arm, left arm, groin, left leg, right leg), second four bytes is an int for the accuracy penalty, and final four bytes is an int for the critical chance bonus. You can patch the EXE directly here, or add a patch definition to FTSE_config.json with the changes you want (note that FTSE takes memory offsets rather than EXE offsets, so you have to add 0x400000 to the offset - e.g. first entry starts at 0x8a8dac.

In a later version, I'll be adding code to make changing these tables easier, similar to how the perks table can be changed from the script code now.
 
Yep - the table for aiming locations is in the EXE at location 0x4a8dac. Each entry is 12 bytes - first four bytes points to the string naming that location (in order: torso, head, eyes, right arm, left arm, groin, left leg, right leg), second four bytes is an int for the accuracy penalty, and final four bytes is an int for the critical chance bonus. You can patch the EXE directly here, or add a patch definition to FTSE_config.json with the changes you want (note that FTSE takes memory offsets rather than EXE offsets, so you have to add 0x400000 to the offset - e.g. first entry starts at 0x8a8dac.

In a later version, I'll be adding code to make changing these tables easier, similar to how the perks table can be changed from the script code now.

Thanks! With your help I added a simple getter/setter to the World class. The locational targeted shot penalties now can be adjusted by Lua calls. It's nothing of a code, but if it helps I can share it.

Armed with this, the overhaul mod can assign different penalties to aimed melee attacks (ranged defaults back to normal).
 
Could you provide description for changes in FTSE installer? It just says "doNightPerson fix", but what does that even do? And the other changes; what do they do?

EDIT:
Fixed burst attack hook to properly remove crouching allies in front of shooter

Deathclaws seemed to be still caught in the attacks even they are crouching or in prone.
 
Last edited:
Hi there! I must congratulate you for the new version, with custom scripts files that are created automatically during installation and will not get in the way of future installations, this is awsomely made!

To get you a feedback on old comments, I found a easy way to translate the game combat log entries between my two game languages by checking for an in-game global variable named "Francais" with a simple if and sending texts based on that variable.

As for the Tactics mac version (using Wine) debugging, could you tell me if you plan on looking into it in the near future? Still I might be the only one here that want to create a mac version of the game, I understand if that is not at all in your priority list. :P
 
Could you provide description for changes in FTSE installer? It just says "doNightPerson fix", but what does that even do? And the other changes; what do they do?

I'll add this for 0.50a. Some of these, like the NightPerson one, I haven't had a chance to validate yet - they came from an older list of hex edits. I'll include whatever info I have on each, and update it once I get a chance to look further (fixing broken traits/perks is on my to-do list).

Deathclaws seemed to be still caught in the attacks even they are crouching or in prone.

I'll double check this too. Thanks for catching it.
 
To get you a feedback on old comments, I found a easy way to translate the game combat log entries between my two game languages by checking for an in-game global variable named "Francais" with a simple if and sending texts based on that variable.

Sounds good. As I get farther along, I hope to have a few command-line parameters. One of these could be to select a different FTSE_config and/or Lua file, to avoid the overhead of constantly checking the flag.

As for the Tactics mac version (using Wine) debugging, could you tell me if you plan on looking into it in the near future? Still I might be the only one here that want to create a mac version of the game, I understand if that is not at all in your priority list.

I made a couple of changes to the compiler setup in 0.45 - have you checked if it improved anything? If not, I'll get a proper error box when the DLL fails to load, so that I can see exactly why it's not working.
 
Back
Top