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
 
Back
Top