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

An issue I experienced during the Kansas City mission was that the burst weapon using SMs would not move closer. They would shoot from maximum distance, only hitting their pipe wielding SM allies and not making a real impact on the difficulty of that mission.
Interesting. Full disclosure - the same lack of free time that makes FTSE development take longer also means that my latest playthrough is also stalled - my team has been waiting in Bunker Beta to start the Quincy mission since 2018. So I haven't really seen how the burst fix behaves in later missions.

Is this possibly an issue with applying FTSE during a FT 1.27 campaign save, an unintended side effect of the burst fix, or something else?

I don't think it's the first point - FTSE doesn't (yet) persist anything in the save file, so I wouldn't expect there to be an issue with existing vs. new saves.

It likely is an unintended side effect. I don't think the AI itself should be impacted - from what I recall, the AI only used the regular chance-to-hit code when deciding what to do, so the FTSE override of the burst secondary hits shouldn't influence the AI decision making. My speculation (very unproven) is that it may be an issue of game balance for this mission - the playtesters would have done this mission with the original burst behavior in place, so the mutants may have always been firing from beyond their effective range, but because of the burst bug they may have been scoring more hits than they should have, so the difficulty may have seemed right. One way to determine this would be to play through the mission the same way with and without the FTSE (or original) burst bug fixes, and keep track of where the mutants are when they open fire. If they always fire from about the same range, but score more hits without the burst bug fixes, then this may be the explanation. If the mutants consistently fire from longer range with FTSE, then it probably is an effect of the burst fix on the AI, which I could then try to track down and tweak.
 
I don't think it's the first point - FTSE doesn't (yet) persist anything in the save file, so I wouldn't expect there to be an issue with existing vs. new saves.

It likely is an unintended side effect. I don't think the AI itself should be impacted - from what I recall, the AI only used the regular chance-to-hit code when deciding what to do, so the FTSE override of the burst secondary hits shouldn't influence the AI decision making. My speculation (very unproven) is that it may be an issue of game balance for this mission - the playtesters would have done this mission with the original burst behavior in place, so the mutants may have always been firing from beyond their effective range, but because of the burst bug they may have been scoring more hits than they should have, so the difficulty may have seemed right. One way to determine this would be to play through the mission the same way with and without the FTSE (or original) burst bug fixes, and keep track of where the mutants are when they open fire. If they always fire from about the same range, but score more hits without the burst bug fixes, then this may be the explanation. If the mutants consistently fire from longer range with FTSE, then it probably is an effect of the burst fix on the AI, which I could then try to track down and tweak.

Good suggestion on comparing with or without FTSE. Without fixes I found that the ghouls died earlier and the heavy weapons SMs advanced further than with fixes. The big guns SMs began firing from a similar location with and without FTSE. Checking the extracted mission files was incredibly frustrating. The waypoints seem to be poorly planned so that the melee units would always be bunched in front of the ranged units. I would agree with your assessment of the mission being "balanced" around the burst bug and relying on the big guns SMs to whittle down the melee SMs during their advance.

Also, using FTTools to look at the extracted Entities/Actors/Mutants .ent files shows the SM with big guns only have 7 AP and 200 rounds. That's a burst and a reload and probably another reason they take so long to move forward.

Thanks for the input :)
 
Immense thanks to you @Melindil for your hard work on FTSE! It helped me immensely to realise my 2.0 version of The Sum! :D I have so much fun to play with lua codes.

I have a small question :)

I'm attempting to add a simple function to deal with those weapons that are fragile and breaks while being used, but I don't want them to break every single time. I would like to change them from being destroyed instantly (set in the entity editor) to have a XX% chance of being destroyed, and sometimes leave an item, like the glass bottle could leave broken glass upon destruction.

I understand that the VTable "ApplyAttackResultToEntity (param1)" and its CombatMessage table could be used by me to program this, but how would I define and use this CombatMessage table? Let's say I created a function similar to the VTable 83 (OnEntityUse), how can I access and play with the CombatMessage table?
Code:
function OnEntityUse(ent,e,target)


end

Would it be similar to this?

Code:
function OnAttack(CombatMessage)

  if CombatMessage[weapon]:GetName() == "Broken glass" then

  end

end
 
I'm attempting to add a simple function to deal with those weapons that are fragile and breaks while being used, but I don't want them to break every single time. I would like to change them from being destroyed instantly (set in the entity editor) to have a XX% chance of being destroyed, and sometimes leave an item, like the glass bottle could leave broken glass upon destruction.

I understand that the VTable "ApplyAttackResultToEntity (param1)" and its CombatMessage table could be used by me to program this, but how would I define and use this CombatMessage table? Let's say I created a function similar to the VTable 83 (OnEntityUse), how can I access and play with the CombatMessage table?
I think something similar to the example you gave should work, but I can't currently sit down and test to be sure. What happens if you add a log message as the sample code for your example case? Does it get printed at the right time?

Actually destroying and replacing the object may be tricky. I haven't gone through the full process for how a CombatMessage is fully resolved, but I know there are at least three steps:

1. The ApplyAttackResultToEntity vtable function will damage the target, apply critical effects, etc.

2. The ResolveCombatMessageForAttacker vtable function will update the attacker entity based on the combat event. This does things like use ammo and deduct AP. (The fact that these two steps are separate is very likely what causes the mode-switch bug; e.g. switching mode while firing allowing a burst attack to use 1 ammo and less AP. But I still need to really walk through these two to find exactly why and how that occurs.)

3. Somewhere I haven't found yet, the CombatMessage struct is used to build out the message that goes to the combat log.

The trick is, if the weapon entity is destroyed at the end of step 1 (e.g. you call the original vtable function first to apply effects to the target, then destroy and recreate the new weapon entity), I don't know how it will impact steps 2 and 3. Best case, it could give a missing weapon name in the combat log. Worst case, it could crash because it expects a weapon in that ID slot and instead finds a null entity. It might be necessary for ApplyAttackResult to flag something in the attacker when the weapon needs to be destroyed, then have a vtable override for ResolveCombatMessageForAttacker actually destroy and replace the weapon (again, probably best to call the original vtable first, to make sure step 2 completes correctly).

If I can somehow find time, I can try to run through the above and see if it works, and if not then maybe come up with another workaround.
 
I think something similar to the example you gave should work, but I can't currently sit down and test to be sure. What happens if you add a log message as the sample code for your example case? Does it get printed at the right time?

Actually destroying and replacing the object may be tricky. I haven't gone through the full process for how a CombatMessage is fully resolved, but I know there are at least three steps:

1. The ApplyAttackResultToEntity vtable function will damage the target, apply critical effects, etc.

2. The ResolveCombatMessageForAttacker vtable function will update the attacker entity based on the combat event. This does things like use ammo and deduct AP. (The fact that these two steps are separate is very likely what causes the mode-switch bug; e.g. switching mode while firing allowing a burst attack to use 1 ammo and less AP. But I still need to really walk through these two to find exactly why and how that occurs.)

3. Somewhere I haven't found yet, the CombatMessage struct is used to build out the message that goes to the combat log.

The trick is, if the weapon entity is destroyed at the end of step 1 (e.g. you call the original vtable function first to apply effects to the target, then destroy and recreate the new weapon entity), I don't know how it will impact steps 2 and 3. Best case, it could give a missing weapon name in the combat log. Worst case, it could crash because it expects a weapon in that ID slot and instead finds a null entity. It might be necessary for ApplyAttackResult to flag something in the attacker when the weapon needs to be destroyed, then have a vtable override for ResolveCombatMessageForAttacker actually destroy and replace the weapon (again, probably best to call the original vtable first, to make sure step 2 completes correctly).

If I can somehow find time, I can try to run through the above and see if it works, and if not then maybe come up with another workaround.
Fantastic! can't wait to test this out. And this syntax would be appropriate?

"CombatMessage[weapon]:GetName()"
 
Thanks a bunch. I'll test those and tell you of my experience shortly. Probably next week? If you want, I can also "clean" my Vtable functions to give you as examples. Way too much lines as of yet, but if I remove most of my repetitions, they could be clear enough to act as examples.
 
Quite a long time later, sorry for that, I was able to sucessfully implement a "fragile weapon" feature, thanks to the 298 Vtable! Really happy of it, sinced it is simple and makes the fragile weapons way more useful. Now, there is a flat 20% chance of them breaking when used. Here is a simplified version featuring only the broken bottle used as a weapon, with the hook :

Code:
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--The hook to name and call the function ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
hookexecutor:InstallVtableHook("ALL",298,OnEntityAttacked)

--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--On attacked (from any entity) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
function OnEntityAttacked(e, attInfo)
    math.randomseed(os.time())
    local bchance = math.random(1,100)
    local winame = attInfo["weapon"]:GetInternalName()
    local attname = attInfo["attacker"]:GetName()
    local attslot = "EQUIP_SLOT_LEFT"
    local newEnt = nil
    
    if attInfo["attacker"].ClassType == "Actor" then --If attacker is an actor
    
        if bchance <= 20 then
            if winame == "brokenBottle" then
                if attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_LEFT):GetInternalName() == winame then
                    attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_LEFT):Destruct()
                    attslot = "EQUIP_SLOT_LEFT"
                elseif attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_RIGHT):GetInternalName() == winame then
                    attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_RIGHT):Destruct()
                    attslot = "EQUIP_SLOT_RIGHT"
                end
            
                world:CombatLog(COMBATLOG_FEW, "<Cr>Crack! ".. attname .." weapon breaks<C->")
                
                --Create broken glass to replace the destroyed broken bottle item
                if winame == "brokenBottle" then
                    newEnt = world:CreateEntity("entities/weapons/Explosives/vitreVertebrise.ent",1)
                end
                
                --Equip the replacement item if it exists
                if newEnt ~= nil and newEnt:GetName() ~= "" then
                    if attslot == "EQUIP_SLOT_LEFT" then
                        local prevequip,errstr = attInfo["attacker"]:EquipItem(newEnt, EQUIP_SLOT_LEFT)
                        if errstr ~= nil then
                            attInfo["attacker"]:AddToInventory(newEnt)
                        end
                    else
                        local prevequip,errstr = attInfo["attacker"]:EquipItem(newEnt, EQUIP_SLOT_RIGHT)
                        if errstr ~= nil then
                            attInfo["attacker"]:AddToInventory(newEnt)
                        end
                    end
                end
            end   
        end
    end
    
    return e:CallOrigVtable(298,attInfo)
end


Thanks again!
 
Quite a long time later, sorry for that, I was able to sucessfully implement a "fragile weapon" feature, thanks to the 298 Vtable! Really happy of it, sinced it is simple and makes the fragile weapons way more useful. Now, there is a flat 20% chance of them breaking when used. Here is a simplified version featuring only the broken bottle used as a weapon, with the hook :

Code:
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--The hook to name and call the function ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
hookexecutor:InstallVtableHook("ALL",298,OnEntityAttacked)

--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
--On attacked (from any entity) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
function OnEntityAttacked(e, attInfo)
    math.randomseed(os.time())
    local bchance = math.random(1,100)
    local winame = attInfo["weapon"]:GetInternalName()
    local attname = attInfo["attacker"]:GetName()
    local attslot = "EQUIP_SLOT_LEFT"
    local newEnt = nil
   
    if attInfo["attacker"].ClassType == "Actor" then --If attacker is an actor
   
        if bchance <= 20 then
            if winame == "brokenBottle" then
                if attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_LEFT):GetInternalName() == winame then
                    attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_LEFT):Destruct()
                    attslot = "EQUIP_SLOT_LEFT"
                elseif attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_RIGHT):GetInternalName() == winame then
                    attInfo["attacker"]:GetEquippedItem(EQUIP_SLOT_RIGHT):Destruct()
                    attslot = "EQUIP_SLOT_RIGHT"
                end
           
                world:CombatLog(COMBATLOG_FEW, "<Cr>Crack! ".. attname .." weapon breaks<C->")
               
                --Create broken glass to replace the destroyed broken bottle item
                if winame == "brokenBottle" then
                    newEnt = world:CreateEntity("entities/weapons/Explosives/vitreVertebrise.ent",1)
                end
               
                --Equip the replacement item if it exists
                if newEnt ~= nil and newEnt:GetName() ~= "" then
                    if attslot == "EQUIP_SLOT_LEFT" then
                        local prevequip,errstr = attInfo["attacker"]:EquipItem(newEnt, EQUIP_SLOT_LEFT)
                        if errstr ~= nil then
                            attInfo["attacker"]:AddToInventory(newEnt)
                        end
                    else
                        local prevequip,errstr = attInfo["attacker"]:EquipItem(newEnt, EQUIP_SLOT_RIGHT)
                        if errstr ~= nil then
                            attInfo["attacker"]:AddToInventory(newEnt)
                        end
                    end
                end
            end  
        end
    end
   
    return e:CallOrigVtable(298,attInfo)
end


Thanks again!
Thats great. i think were going to check this out too! thanks hugo!
 
Hi all,

Apologies for the long delay between releases. Work is continuing, and the next release should be a large one.

I've been working on a research thread, going through the game's UI code to see how that could be controlled via FTSE. It turns out it's quite possible to add and modify windows, controls, etc., and to trap mouse and keyboard events to do customized behavior. So as an experiment, I tried to add a basic interactive dialogue window when talking to an NPC, and this was the result:



There's still quite a bit of work to make it more functional, fix a couple of bugs, and add some features to make using this in a mod easier. So I wanted to try to collect some input on what kinds of things I should try to add to this:

1. The UI itself is just an extension of the existing WSpeech class in the game, with an extra portrait and text box (the second text box is also custom in order to hook the mouse events). What else could I do here? Something like the Fallout 2 talking heads? Or video clips (will take more work, as I'm not sure I'd want to use Bink, so I might have to add a video render if that's possible). Should Barter and Gamble still be buttons, or just have them controlled by conversation nodes?

2. For the dialogue content, right now I'm using a Lua table (the full content I used for the example is attached below), as this was simplest to get working with FTSE. Right now there are "code" nodes which can execute arbitrary Lua code (and jump to a new node based on the result), and "text" nodes which specify NPC text and player choices. I will be adding further options for speech-gated dialogue options (easier than writing a code block for each one), nested trees, etc. Any other ideas? And are there any other formats that might work better? (I think Fallout 2 uses some script language that's then compiled into a binary form. I'm leaning against having a form that requires binary formats or compilation.)

3. What about toolsets? Is it necessary to add a GUI for constructing a dialogue tree? This may be quite a lot of work, so I don't know where it would be prioritized (though it's probably something that could be worked on independently from FTSE, if anyone wanted to give it a try).

4. Any other thoughts? I do have plans to look at other UI elements to (particularly the inventory window - there's a lot of cool stuff that could be done there too, like adding more inventory slots).

---

Below is the Lua code from the example video (in spoiler tags due to length):

Code:
return {
    start={
        code=function(speech, conversation, node, npc, player)
            if world:GetMissionVar("M01_Raider_Leader_Dead") == "T" then
                if world:GetMissionVar("M01_Tribals_Attacked") == "T" then
                    return speech:RunNode(conversation,"End_Partial_1",npc,player)
                else
                    return speech:RunNode(conversation,"End_Success_1",npc,player)
                end
            else
                if world:GetMissionVar("M01_Charon_Talk") == "T" then
                    return speech:RunNode(conversation,"Repeat_Visit_1",npc,player)
                else
                    world:SetMissionVar("M01_Charon_Talk","T")
                    return speech:RunNode(conversation,"First_Visit_1",npc,player)
                end
            end
        end
    },
    First_Visit_1={
        text = "Greetings to the Brotherhood of Steel. You are speaking with Charon, Elder and tribal Father to Brahmin Wood.",
        audio = "locale/missions/mission01/Charon_First_Visit_1.mp3",
        choices = {
            {
                node = "Greet",
                text = "Greetings to you, I am <rank> <player>. I have been sent here to help you.",
                priority = 1
            },
            {
                node = "Demand",
                text = "Uh huh. Look, the Brotherhood expects to be well compensated for our help.",
                priority = 2
            },
            {
                node = "exit",
                text = "I don't have time for this. Get lost.",
                priority = 99
            }
        }
    },
    Greet = {
        text = "You have arrived none too soon. Our village has fallen to raiders who rode out of the darkness. Some of my people escaped death or bondage by fleeing into the wasteland, but without tools and shelter. This must be the doing of the Brahmin God of Fate.",
        audio = "locale/missions/mission01/Charon_Greet.mp3",
        choices = {
            {
                node = "Help",
                text = "Where are your people now?",
                priority = 2
            },
            {
                node = "Demand_soft",
                text = "Do not worry, we can handle the raiders. However, our leadership expects to be compensated for our help. We need new recruits to help with our growing borders.",
                priority = 1
            },
            {
                node = "Demand",
                priority = -1
            },
            {
                node = "exit",
                text = "Understood. We will clear out the raiders and protect your people. Wait here until we return.",
                priority = 99
            }
        }
    },
    Demand = {
        text = "With the bandits we trade away our present, in the Brotherhood we trade away our future. *Sigh*... very well Initiate, I fully agree to your terms, but I fear this decision will haunt me till I reach the Eternal Grazing Field in the heavens.",
        audio = "locale/missions/mission01/Charon_Demand.mp3",
        choices = {
            {
                node = "Greet",
                priority = -1
            },
            {
                node = "Help",
                text = "Yeah, yeah. So what do we gotta do?",
                priority = 1
            },
            {
                node = "exit",
                text = "Just stay out of our way. (Leave.)",
                priority=99
            }
        }
    },
    Demand_soft = {
        text = "With the bandits we trade away our present, in the Brotherhood we trade away our future. *Sigh*... very well Initiate, I fully agree to your terms, but I fear this decision will haunt me till I reach the Eternal Grazing Field in the heavens.",
        audio = "locale/missions/mission01/Charon_Demand.mp3",
        choices = {
        }
    },
    Help = {
        text = "Our villagers are being held prisoner in the camp. Save them, and you will have more people to aid your cause. Lose them, and you will be cutting Brahmin from your own herd.",
        audio = "locale/missions/mission01/Charon_Help.mp3",
        choices = {}
    },
    
    Repeat_Visit_1 = {
        text = "Please hurry. My people are in grave danger.",
        choices = {
            {
                node = "exit",
                text = "(Leave.)",
                priority = 1
            }
        }
    },
    
    End_Success_1 = {
        text = "Hah hah hah. Your grace in battle is wondrous to behold Initiate. The death screams of the bandits reminded me of angels singing. They will think twice before attacking this village again.",
        audio = "locale/missions/mission01/Charon_End_Success_1.mp3",
        choices = {
            {
                node = "Recruits",
                text = "The Brotherhood of Steel keeps its word. Do you keep yours?",
                priority = 1
            },
            {
                node = "Reward",
                text = "Is there any reward for the squad who saved your people?",
                priority = 2
            },
            {
                node = "exit",
                text = "Until we meet again, elder.",
                priority = 99
            }
        }
    },
    Recruits = {
        text = "Of course. All the recruits will be delivered to your base as agreed. Well done. You have our thanks.",
        audio = "locale/missions/mission01/Charon_Recruits.mp3",
        choices = {}
    },
    Reward = {
        code = function(speech, conversation, node, npc, player)
            local ent
            for _,it in pairs(npc:GetInventory()) do
                if it:GetName() == "Detonator" then
                    local it2 = npc:RemoveInventory(it,1)
                    player:AddToInventory(it2,1)
                    return speech:RunNode(conversation,"Reward_given",npc,player)
                end
            end
            return speech:RunNode(conversation,"Reward_already",npc,player)
        end
    },
    
    Reward_given = {
        text = "Initiate, you defended my people as fiercely as a Deathclaw protects her cubs. Please accept this ancient gift from us. You are now considered family in my humble village.",
        audio = "locale/missions/mission01/Charon_Reward.mp3",
        choices = {}
    },
    Reward_already = {
        text = "You have our precious gift already. We can not part with any more.",
        choices = {}
    },
    
    End_Partial_1 = {
        text = "The raiders have been dealt a heavy blow. They will need weeks to fill their ranks. While our losses are smaller, they are infinitely graver. Only much time and \"hiding the spear\" will replenish our ranks.",
        audio = "locale/missions/mission01/Charon_End_Partial_1.mp3",
        choices = {
            {
                node = "Recruits_2",
                text = "We did what we could for your people. Will you still provide recruits for the Brotherhood?",
                priority = 1
            },
            {
                node = "Reward_2",
                text = "We did save many of your people. Isn't that worth some reward?",
                priority = 2
            },
            {
                node = "exit",
                text = "Until we meet again, elder.",
                priority = 99
            }
        }
    },
    Recruits_2 = {
        text = "You will get only half the agreed recruits. You have our ... thanks.",
        audio = "locale/missions/mission01/Charon_Recruits_2.mp3",
        choices = {}
    },
    Reward_2 = {
        text = "I am thankful many of my people survived, Initiate, but no reward can be offered. We must preserve what little we have left if we are to survive this tragedy.",
        choices = {}
    }
        
}
 
Last edited:
Hi all,

Apologies for the long delay between releases. Work is continuing, and the next release should be a large one.

I've been working on a research thread, going through the game's UI code to see how that could be controlled via FTSE. It turns out it's quite possible to add and modify windows, controls, etc., and to trap mouse and keyboard events to do customized behavior. So as an experiment, I tried to add a basic interactive dialogue window when talking to an NPC, and this was the result:


Great job! never though the engine can have dialogue system, would there be skill/special checks like fallout 1/2?
 
Great job! never though the engine can have dialogue system, would there be skill/special checks like fallout 1/2?
Yes, this was what I meant by "speech-gated" (probably should have been "skill-gated" instead). Right now, the code blocks in the prototype can do this, but it involves writing a Lua function each time. Better would be to add an optional "condition" field to each player dialogue choice, such that the choice is only given if the condition is true.

Speaking of which - another consideration is the possible need of a "Speech" skill. Choices here involve getting rid of an unnecessary or redundant skill (First Aid? Pilot? Gambling?) and replacing it with Speech, or else making Speech a FTSE-managed skill, and adding to the existing UI to allow the player to see and/or increase it. The latter would involve a lot more work, but would at least provide a framework in case other skills need to be added too.
 
Yes, this was what I meant by "speech-gated" (probably should have been "skill-gated" instead). Right now, the code blocks in the prototype can do this, but it involves writing a Lua function each time. Better would be to add an optional "condition" field to each player dialogue choice, such that the choice is only given if the condition is true.

Speaking of which - another consideration is the possible need of a "Speech" skill. Choices here involve getting rid of an unnecessary or redundant skill (First Aid? Pilot? Gambling?) and replacing it with Speech, or else making Speech a FTSE-managed skill, and adding to the existing UI to allow the player to see and/or increase it. The latter would involve a lot more work, but would at least provide a framework in case other skills need to be added too.
is it possible to add scroll bar to the ui? it exist in perks/traits so maybe you can do the same?
if not then replace it with science, from what i know it just make you better at damaging robots, add this to repair and delete the skill
 
Holy shit, Melindil, this is brutal. Where were you 15 years ago? :D

My opinion on your questions:
1) Don't waste time with video support or talking heads (well, an optional screen with a large (800*600) static picture could be atmospheric). Very small gain for a lot of work. Keep the barter and gamble buttons. I prefer them in games instead of finding the dialogue choice.

2) I think your current format is okay. Xml file would be fine, too, but you would have to somehow link the dialogue nodes with the scripts.
Things to consider:
a) The amount of dialogues in a project can be massive, leading to thousands of lines (with all the brackets and scripts in there). It would be nice if the dialogues could be broken into multiple files, for example per mission -> an attribute somewhere at the top of hierarchy?
b) Localization. I run into a problem with special characters when I was printing someting into the combat log. Maybe something with encoding, because standard localized names of characters or items are fine.

3) GUI is unnecessary, but welcome with complex trees. Instead of reinventing the wheel, some existing tool could be used (I am not familiar with any, but there are dozens of them on the internet, at least one of them must be suitable). But that's something of a stretch goal.

4) Apart from skill checks (which are mandatory), companion checks would be nice, too. But I guess that's something you can already do with the scripting once it supports proper conditions for each line. Preparing the right utility methods to use instead of writing blocks of code each time will make it convenient, but modders can do it themselves in time and share it (uh, if there's a plural, my spring has dried up).

Speaking of speech skill, I think the original barter skill makes the most sense. It is already tied to the charisma attribute and I always used it this way. The modder would just rename the skill. In other games, a speech skill lowers prices at shops, so why not here as well.
Don't touch the science skill, it's actually a very useful skill because of science switches ;)
 
1) Don't waste time with video support or talking heads (well, an optional screen with a large (800*600) static picture could be atmospheric). Very small gain for a lot of work.
Short-term, I'll probably keep it as it is now. Longer term, I'll look into how to get an (optionally animated) sprite into the UI.


Keep the barter and gamble buttons. I prefer them in games instead of finding the dialogue choice.
I just realized that nothing stops me from calling the barter/Gamble window routines even if the "Can Barter/Gamble" flags are not set. So I should be able to offer both - have the buttons show up as normal if those flags are set, while still allowing a conversation option to open them too (so e.g. barter can be locked behind conversation choice or scriptable events if desired by a mod).

The amount of dialogues in a project can be massive, leading to thousands of lines (with all the brackets and scripts in there). It would be nice if the dialogues could be broken into multiple files, for example per mission -> an attribute somewhere at the top of hierarchy?
This is already possible - the Lua code which actually manages the dialogue progress loads the conversation file via the dofile() function. This can load as many separate files as necessary. And I can probably also set it to pull all files from a directory, and name the conversations automatically based on filename.



Localization. I run into a problem with special characters when I was printing someting into the combat log. Maybe something with encoding, because standard localized names of characters or items are fine.
I'll need to check this. Lua files are UTF-8, and I convert from that to UCS-2 when calling FOT routines, so this ideally should work. I only have the English version, so I'd have to see what characters it does or does not accept.

I also need to consider how to deal with mods that offer multiple languages. With my latest FTSE version, initialization happens prior to process start now, so I probably can't easily use bos.cfg. Maybe have an option in the FTSE configuration to add an optional localization prefix (or separate directory) depending on language selection?


GUI is unnecessary, but welcome with complex trees. Instead of reinventing the wheel, some existing tool could be used (I am not familiar with any, but there are dozens of them on the internet, at least one of them must be suitable). But that's something of a stretch goal.
Will wait on this.


Apart from skill checks (which are mandatory), companion checks would be nice, too. But I guess that's something you can already do with the scripting once it supports proper conditions for each line. Preparing the right utility methods to use instead of writing blocks of code each time will make it convenient, but modders can do it themselves in time and share it (uh, if there's a plural, my spring has dried up).
Yeah - right now, the game engine hook keeps track of which companion started the conversation, and any shortcut skill check syntax I add will use that character's abilities. But I can add an optional flag to always use main character instead, and/or check for presence of certain characters in the squad to unlock options. For longer-term, I'll need to think about how to do proper "party banter"; e.g. more than two participants in a conversation.

Speaking of speech skill, I think the original barter skill makes the most sense. It is already tied to the charisma attribute and I always used it this way. The modder would just rename the skill. In other games, a speech skill lowers prices at shops, so why not here as well.
Don't touch the science skill, it's actually a very useful skill because of science switches ;)
Agreed with not using Science - later capabilities I'm trying to add may be able to make good use of that skill. Dual-purpose Barter/Speech is easiest to implement. I mainly considered Pilot/FirstAid due to the former having very little gameplay impact, and the latter having substantial overlap with Doctor.
 
Last edited:
I just realized that nothing stops me from calling the barter/Gamble window routines even if the "Can Barter/Gamble" flags are not set. So I should be able to offer both - have the buttons show up as normal if those flags are set, while still allowing a conversation option to open them too (so e.g. barter can be locked behind conversation choice or scriptable events if desired by a mod).
Oh, that's actually pretty cool.

I also need to consider how to deal with mods that offer multiple languages. With my latest FTSE version, initialization happens prior to process start now, so I probably can't easily use bos.cfg. Maybe have an option in the FTSE configuration to add an optional localization prefix (or separate directory) depending on language selection?
I think this can be on the modder's shoulder to provide separate files. My point was more in the direction of separation of actual dialogue text (which can contain special characters) and the functionality (methods). Maybe this could circumvent the crash I encountered. Or maybe not, just a thought. Anyway, if you need me to debug it, I can prepare a test case and run it on my Czech version of the game.

Agreed with not using Science - later capabilities I'm trying to add may be able to make good use of that skill. Dual-purpose Barter/Speech is easiest to implement. I mainly considered Pilot/FirstAid due to the former having very little gameplay impact, and the latter having substantial overlap with Doctor.
While pilot and first aid seem unimpressive, they still have their place. Pilot is boring, yeah, but maybe someone has a mod heavily depending on vehicles. And while I removed first aid in my latest campaign and would do it again and again, it's still there in the core game. Unless a modder edits the FA items to use doctor skill, they might need it. Right now, I wouldn't pick barter on my character. I never had a problem with money in any game. And I am not the guy who loots everything, quite the contrary. But if it was also tied to speech, well, that's something else.


You know what, once the dialogues in FT are a fact, not a wet dream, I can think of one other feature that is really lacking in this game. A cover system. Right now, it doesn't matter if you crouch behind a sandbag or in open field. In fact, crouching behind a sandbag is worse than lying prone in the open field. So if you do some drugs irl and get a mania, maybe you could look into this. Directional cover system like in Wasteland/Shadowrun/X-Com etc. The TACTICS in the game would finally be justified.
I know this is far-fetched, but the same could be said about a dialogue system and here we are. It's something that would work in a vanilla campaign as well. And it could attract more players to this old, abandoned game.
 
That dialogue system would be fricking amazing. But it would mean I would have to redo thousands of lines of text and would open up so many options (and extra work) that I personally would be hesitant to implement it at all, outside of the functions for different characters (and their stats picture etc) being the specific ones used during dialogue...

Still though, it's a worthy project.

Directional cover system like in Wasteland/Shadowrun/X-Com etc. The TACTICS in the game would finally be justified.
I know this is far-fetched, but the same could be said about a dialogue system and here we are. It's something that would work in a vanilla campaign as well. And it could attract more players to this old, abandoned game.

That would be awesome, even better if modifiable too.
Even for things like "window" tiles and open fences providing some "cover" bonuses would be great too. I would have no idea how to go about it, but it would be cool
 
Actually destroying and replacing the object may be tricky.

I remember a workaround for that I made in a different game where instead of creating a percentage for an item to be destroyed, I added a chance for an item to be added to inventory AFTER usage.

This wouldn't be perfect for weapons, as if you are down to your last one adding one to the player's inventory after attacking with only a single stacked item would result in it being "unequipped" in a sense. Though it would surely work fine for a stack of 2 or more.

This way also adds the ability to have items "drop" other items, like drinking a nuka cola adding a bottle cap to the player's inventory, or using a stimpack creating an empty hypodermic needle, etc etc etc.
 
You know what, once the dialogues in FT are a fact, not a wet dream, I can think of one other feature that is really lacking in this game. A cover system. Right now, it doesn't matter if you crouch behind a sandbag or in open field. In fact, crouching behind a sandbag is worse than lying prone in the open field. So if you do some drugs irl and get a mania, maybe you could look into this. Directional cover system like in Wasteland/Shadowrun/X-Com etc. The TACTICS in the game would finally be justified.
Something like this already exists in the game engine, but (unsurprisingly) it's completely broken. From my old notes on chance to hit calculations:
  • Nearby cover: This appears to be badly bugged. The net effect is, if the target is east or west of the attacker by at least 12, then chance to hit is reduced for closely adjacent walls or obstacles, based on the screen direction of the wall from the target: -7 if wall/obstacle is up-right or down-left from the target (based on the angled isometric view), -15 if wall/obstacle is down-right, and 0 if wall/obstacle is up-left or no nearby wall/obstacle. The penalty applies even if the wall/obstacle is not between the attacker and target, so just standing next to a wall can give a defender an effective AC bonus. (It appears the first part was supposed to be just a range check, distance > 12.0, but isn't being calculated right. And, the direction checks appear wrong for the second part - probably should have been -7 for all directions. Not sure why the attacker's direction wasn't taken into account.)

An effective +7 to AC (after the direction bug is fixed) isn't much. So it would probably be better to examine how the code determines where cover exists, and put in my own hook there to let the Lua script decide what bonus (if any) to give.
 
Hrm, yeah that's pretty lackluster, even if you could just edit the base rates to be double that it'd be brilliant.

Finding a way to make it a variable would be above and beyond, so I wish you luck on that one Melindil. Thanks for all you've done already.
 
Back
Top