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

A small question for you Melindil :D I just read that on FTSE description :
Added hooks when items are added to or removed from inventories (Actor, Vehicle, Container))
I remember not being able to detect or play with inventory items from a containter, this is why my "3D printer" and "Crafting bench" both have a dead entity to serve as a box to detect and destroy what is inside. So I assume it no longer necessary and I can use regular containters in my code?
 
I remember not being able to detect or play with inventory items from a containter, this is why my "3D printer" and "Crafting bench" both have a dead entity to serve as a box to detect and destroy what is inside. So I assume it no longer necessary and I can use regular containters in my code?

The OnInventoryAdd and OnInventoryRemove hooks should work for containers and vehicles as well as actors - there's a separate machine code hook implemented for each. I don't remember how well these were tested, though. Also not sure if the inventory transfer from the window is immediate, or only after window is closed (I think it was immediate for this case).

Similarly, the AddToInventory and RemoveInventory entity functions should also work for containers and vehicles.

If you try them out and they don't seem to work, I can take a look and expedite a fix if something's broken.
 
The OnInventoryAdd and OnInventoryRemove hooks should work for containers and vehicles as well as actors - there's a separate machine code hook implemented for each. I don't remember how well these were tested, though. Also not sure if the inventory transfer from the window is immediate, or only after window is closed (I think it was immediate for this case).

Similarly, the AddToInventory and RemoveInventory entity functions should also work for containers and vehicles.

If you try them out and they don't seem to work, I can take a look and expedite a fix if something's broken.

Tganks a bunch! So I tested my working scripts with a container instead of a dead turret, and it works fine, but after a couple of crafted items, it strangely stop working until the container inventory is opened again, then I can fire my scripts a couple times, then it stop working again, etc. There must be a small bug that prevent hooks to start or the inventory to be accessed, maybe? I use GetEntitiesByTag() function in the OnVariableChanged() hook after a "speech occured" with the crafting bench in the mission map.
 
Tganks a bunch! So I tested my working scripts with a container instead of a dead turret, and it works fine, but after a couple of crafted items, it strangely stop working until the container inventory is opened again, then I can fire my scripts a couple times, then it stop working again, etc. There must be a small bug that prevent hooks to start or the inventory to be accessed, maybe?

That's strange. Do you happen to have the Lua code for this case that I can take a look at? I'd like to run the same scenario with a breakpoint on my hook to see if I can tell why it doesn't activate.
 
Yes of course. Here you go :

Code:
require "FTSE_Crafting"

function OnVariableChanged(key,value,iscampaign)
  local p = world:GetPlayer()
  local s = world:GetSquad()
  local fr = world:GetCampaignVar("Francais")
  local nuit = p:GetAttribute("gainLuck", ACTOR_TABLE_TEMPORARY) --boolean
   
  if key == "Crafting" and value == "oui" then
    world:SetMissionVar("Crafting","non") --Reset Crafting variable
    Crafting(p,fr)
  end
end

In crafting.lua :

Code:
--
-- ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████
function Craft_this(p,fr,box_items,recipe_items,path,quantity)
    local enough_ingredients = 1
   
    for i=1,#box_items do
        if box_items[i] < recipe_items[i] then
            enough_ingredients = 0
            break
        end  
    end
    if enough_ingredients == 1 then
        local newEnt = world:CreateEntity(path,quantity)
        p:AddToInventory(newEnt)
        if tonumber(quantity) > 1 then
            if fr == "non" then
                world:CombatLog(COMBATLOG_FEW,"<Cg>"..p:GetName().." crafts " .. quantity .. " items<C->")
            else
                world:CombatLog(COMBATLOG_FEW,"<Cg>"..p:GetName().." fabrique " .. quantity .. " objets<C->")
            end
        else
            if fr == "non" then
                world:CombatLog(COMBATLOG_FEW,"<Cg>"..p:GetName().." crafts an item<C->")
            else
                world:CombatLog(COMBATLOG_FEW,"<Cg>"..p:GetName().." fabrique un objet<C->")
            end
        end
        return 1
    else
        return 0
    end
end

-- ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████
function Remove_this(recipe,icount,box,invent)
    if recipe > 0 then
        if icount >= recipe then
            local removedent = box:RemoveInventory(invent,recipe)
            if type(removedent) == "table" then removedent:Destruct() end --Clear memory
            recipe = 0 --Removing of this ingredient completed
        else
            local removedent = box:RemoveInventory(invent,icount)
            if type(removedent) == "table" then removedent:Destruct() end --Clear memory
            recipe = recipe - icount --Will need to remove more of this ingredient
        end
    end
    return recipe
end

-- ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████
function Crafting_spend_items(recipe) --Remove items
    local box = world:GetEntitiesByTag("Crafting_bench_box")
    if box[1] then --Crafting bench found
        local items = box[1]:GetInventory()
        for _,invent in ipairs(items) do --Scan all available ingredients to remove
            local nom = invent:GetName()
            local nomi = invent:GetInternalName()
            local icount = invent:GetCount()
           
            --**********************************************Fill list**********************************************
            if nom == "Bois" or nom == "Wood" then
                recipe[1] = Remove_this(recipe[1],icount,box[1],invent)
            elseif nom == "Papiers" or nom == "Papers" then
                recipe[2] = Remove_this(recipe[2],icount,box[1],invent)
            elseif nom == "Pièces" or nom == "Parts" then
                recipe[3] = Remove_this(recipe[3],icount,box[1],invent)
            elseif nom == "Métal" or nom == "Metal" then
                recipe[4] = Remove_this(recipe[4],icount,box[1],invent)
            elseif nom == "Produit chimique" or nom == "Chemicals" then
                recipe[5] = Remove_this(recipe[5],icount,box[1],invent)
            elseif nom == "Plastique" or nom == "Plastic" then
                recipe[6] = Remove_this(recipe[6],icount,box[1],invent)
            elseif nom == "Verre" or nom == "Glass" then
                recipe[7] = Remove_this(recipe[7],icount,box[1],invent)
            elseif nom == "Caoutchouc" or nom == "Rubber" then
                recipe[8] = Remove_this(recipe[8],icount,box[1],invent)
            elseif nom == "Tuyau" or nom == "Pipe" then
                recipe[9] = Remove_this(recipe[9],icount,box[1],invent)
            elseif nom == "Corde" or nom == "Rope" then
                recipe[10] = Remove_this(recipe[10],icount,box[1],invent)
            elseif nom == "Fils" or nom == "Wires" then
                recipe[11] = Remove_this(recipe[11],icount,box[1],invent)
            elseif nom == "Chiffons" or nom == "Rags" then
                recipe[12] = Remove_this(recipe[12],icount,box[1],invent)
            elseif nom == "Peau" or nom == "Pelt" then
                recipe[13] = Remove_this(recipe[13],icount,box[1],invent)
            elseif nom == "Plumes" or nom == "Feathers" then
                recipe[14] = Remove_this(recipe[14],icount,box[1],invent)
            elseif nom == "Pierre" or nom == "Rock" then
                recipe[15] = Remove_this(recipe[15],icount,box[1],invent)
            elseif nom == "Vracs" or nom == "Trinks" then
                recipe[16] = Remove_this(recipe[16],icount,box[1],invent)
            elseif invent.ClassType == "Consumable" then --Is consumable
                if invent:GetEffectedMainAttribute() == "radPoints" then --Is food
                    local hunred = invent:GetEffectMinimum() --Get hunger reduction
                    if hunred <= -150 then
                        if invent:GetEffectAttribute(0, "fireResist") >= 400 then --Food (meat)
                            recipe[17] = Remove_this(recipe[17],icount,box[1],invent)
                        else
                            recipe[18] = Remove_this(recipe[18],icount,box[1],invent)
                        end
                    end
                    if invent:GetEffectAttribute(0, "fireResist") >= 400 then --Food (meat)
                        recipe[19] = Remove_this(recipe[19],icount,box[1],invent)
                    else
                        recipe[20] = Remove_this(recipe[20],icount,box[1],invent)
                    end
                end  
            end  
        end  
    end
end

-- ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████
function Crafting_scan(box_items) --Populate the box_items list
    local box = world:GetEntitiesByTag("Crafting_bench_box")
    if box[1] then --Crafting bench found
        local items = box[1]:GetInventory() --Scan all available ingredients
        for _,invent in ipairs(items) do
            local nom = invent:GetName()
            local nomi = invent:GetInternalName()
            local icount = invent:GetCount()
            --**********************************************Fill list**********************************************
            if nom == "Bois" or nom == "Wood" then
                box_items[1] = box_items[1] + icount --Add item to "Wood" ingredients list
            elseif nom == "Papiers" or nom == "Papers" then
                box_items[2] = box_items[2] + icount --Add item to "Papers" ingredients list
            elseif nom == "Pièces" or nom == "Parts" then
                box_items[3] = box_items[3] + icount --Add item to "Parts" ingredients list      
            elseif nom == "Métal" or nom == "Metal" then
                box_items[4] = box_items[4] + icount --Add item to "Metal" ingredients list      
            elseif nom == "Produit chimique" or nom == "Chemicals" then
                box_items[5] = box_items[5] + icount --Add item to "Chemicals" ingredients list  
            elseif nom == "Plastique" or nom == "Plastic" then
                box_items[6] = box_items[6] + icount --Add item to "Plastic" ingredients list  
            elseif nom == "Verre" or nom == "Glass" then
                box_items[7] = box_items[7] + icount --Add item to "Glass" ingredients list  
            elseif nom == "Caoutchouc" or nom == "Rubber" then
                box_items[8] = box_items[8] + icount --Add item to "Rubber" ingredients list  
            elseif nom == "Tuyau" or nom == "Pipe" then
                box_items[9] = box_items[9] + icount --Add item to "Pipe" ingredients list  
            elseif nom == "Corde" or nom == "Rope" then
                box_items[10] = box_items[10] + icount --Add item to "Rope" ingredients list  
            elseif nom == "Fils" or nom == "Wires" then
                box_items[11] = box_items[11] + icount --Add item to "Wires" ingredients list  
            elseif nom == "Chiffons" or nom == "Rags" then
                box_items[12] = box_items[12] + icount --Add item to "Rags" ingredients list  
            elseif nom == "Peau" or nom == "Pelt" then
                box_items[13] = box_items[13] + icount --Add item to "Pelt" ingredients list  
            elseif nom == "Plumes" or nom == "Feathers" then
                box_items[14] = box_items[14] + icount --Add item to "Pelt" ingredients list  
            elseif nom == "Pierre" or nom == "Rock" then
                box_items[15] = box_items[15] + icount --Add item to "Rock" ingredients list  
            elseif nom == "Vracs" or nom == "Trinks" then
                box_items[16] = box_items[16] + icount --Add item to "Trinks" ingredients list
            elseif invent.ClassType == "Consumable" then --Is consumable
                if invent:GetEffectedMainAttribute() == "radPoints" then --Is food
                    local hunred = invent:GetEffectMinimum() --Get hunger reduction
                    if hunred <= -150 then
                        if invent:GetEffectAttribute(0, "fireResist") >= 400 then                 --Food (meat)
                            box_items[17] = box_items[17] + icount --Add item to "Meat" ingredients list
                        else
                            box_items[18] = box_items[18] + icount --Add item to "Vege" ingredients list
                        end
                    end
                    if invent:GetEffectAttribute(0, "fireResist") >= 400 then                 --Food (meat)
                        box_items[19] = box_items[19] + icount --Add any food to "Small Meat" ingredients list
                    else
                        box_items[20] = box_items[20] + icount --Add any food to "Small Vege" ingredients list
                    end
                end  
            end  
        end  
    end
    return box_items
end

-- ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████
function Crafting(p,fr)
    local recipe = {}
    local to_craft = {}
    local box_items = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} --20 available ingredients

    --Scan box inventory for all available ingredients, adding them to items
    box_items = Crafting_scan(box_items)
   
    --Fill each recipe and crafted items informations
    --to_craft variables : Difficulty (Repair needed), Path of item crafted, Quantity to craft
   
    recipe[1] = {0,0,2,3,0,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0}
    to_craft[1] = {75,"entities/items/Armor/raiderArmor2.ent",1}
    recipe[2] = {1,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[2] = {50,"entities/items/Useful/LampeDePoche3.ent",1}
    recipe[3] = {1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[3] = {50,"entities/weapons/Pistols/flamerPistol.ent",1}  
    recipe[4] = {2,0,1,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[4] = {75,"entities/weapons/Rifles/9mmPipeGun.ent",1}
    recipe[5] = {0,0,2,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[5] = {75,"entities/weapons/heavy/FlamerLeger2.ent",1}
    recipe[6] = {0,0,1,2,0,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0}
    to_craft[6] = {50,"entities/items/Armor/raiderArmor.ent",1}
    recipe[7] = {0,0,1,3,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[7] = {50,"entities/weapons/heavy/FlamerLeger3.ent",1}
    recipe[8] = {0,0,0,2,0,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0}
    to_craft[8] = {25,"entities/items/Armor/ArmureImprovisee.ent",1}  
    recipe[9] = {1,0,1,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[9] = {50,"entities/weapons/Pistols/zipGunPistol2.ent",1}
    recipe[10] = {2,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[10] = {50,"entities/weapons/Pistols/zipGunPistol.ent",1}
    recipe[11] = {0,0,0,0,0,0,0,0,0,0,3,0,5,0,0,0,0,0,0,0}
    to_craft[11] = {75,"entities/items/Armor/bosLeather2.ent",1}
    recipe[12] = {0,0,3,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[12] = {75,"entities/items/Armor/bosMetal.ent",1}
    recipe[13] = {0,0,2,3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}
    to_craft[13] = {75,"entities/items/Useful/lockPicksEXP2.ent",1}
    recipe[14] = {0,0,3,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0}
    to_craft[14] = {50,"entities/weapons/Melee/cattleProdDIY.ent",1}  
    recipe[15] = {0,0,1,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[15] = {75,"entities/weapons/Explosives/PipeBomb.ent",1}  
    recipe[16] = {0,0,1,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[16] = {50,"entities/weapons/Grenades/Stick.ent",1}  
    recipe[17] = {2,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0}
    to_craft[17] = {50,"entities/items/Useful/CANNEPECHEDIY.ent",1}  
    recipe[18] = {0,1,0,0,0,3,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[18] = {0,"entities/items/Armor/Veste_Dechets.ent",1}
    recipe[19] = {3,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[19] = {0,"entities/weapons/Rifles/sniperRifle.ent",1}  
    recipe[20] = {0,0,0,0,0,0,0,0,0,0,1,0,5,0,0,0,0,0,0,0}
    to_craft[20] = {50,"entities/items/Armor/bosLeather.ent",1}  
    recipe[21] = {0,0,1,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[21] = {50,"entities/items/Armor/bosMetalRouille2.ent",1}
    recipe[22] = {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,5,0,0,0,0}
    to_craft[22] = {50,"entities/items/Armor/ArmureCaps.ent",1}
    recipe[23] = {0,0,0,0,0,0,0,5,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[23] = {50,"entities/items/Armor/ArmurePneus.ent",1}
    recipe[24] = {0,0,0,0,0,0,0,0,0,0,1,5,0,0,0,0,0,0,0,0}
    to_craft[24] = {50,"entities/items/Useful/SleepingBag1_IMPROVISE.ent",1}
    recipe[25] = {0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,0,0,0,0,0}
    to_craft[25] = {25,"entities/items/Armor/CostumeMutantCuir.ent",1}
    recipe[26] = {0,0,0,0,0,5,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[26] = {25,"entities/items/Armor/ArmurePlastique.ent",1}
    recipe[27] = {0,0,1,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[27] = {50,"entities/weapons/SMG/ArbaletePlastique.ent",1}
    recipe[28] = {2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[28] = {50,"entities/weapons/SMG/spearGunSub.ent",1}  
    recipe[29] = {0,0,0,0,1,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0}
    to_craft[29] = {50,"entities/items/Useful/medsupplies.ent",1}
    recipe[30] = {0,0,1,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}
    to_craft[30] = {50,"entities/items/Useful/lockPicks2.ent",1}
    recipe[31] = {0,0,0,0,2,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0}
    to_craft[31] = {25,"entities/weapons/Grenades/molotov.ent",1}
    recipe[32] = {1,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0}
    to_craft[32] = {25,"entities/items/Useful/CANNEPECHEDIY.ent",1}
    recipe[33] = {2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}
    to_craft[33] = {25,"entities/items/Armor/BouclierPlanche.ent",1}  
    recipe[34] = {0,0,0,4,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}
    to_craft[34] = {75,"entities/weapons/Blades/Epee_DIY.ent",1}  
    recipe[35] = {0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[35] = {50,"entities/items/Ammo/ammo9mmBall_2.ent",10}  
    recipe[36] = {3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[36] = {50,"entities/weapons/Pistols/SlingShotPistol.ent",1}
    recipe[37] = {0,0,0,0,0,0,0,0,0,0,1,0,4,0,0,0,0,0,0,0}
    to_craft[37] = {50,"entities/items/Useful/SleepingBag1_IMPROVISE.ent",1}
    recipe[38] = {0,0,0,4,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[38] = {50,"entities/items/___RECUS/Repair.ent",1}  
    recipe[39] = {0,0,0,0,0,0,0,0,0,0,1,4,0,0,0,0,0,0,0,0}
    to_craft[39] = {25,"entities/items/Useful/SleepingBag1_IMPROVISE.ent",1}
    recipe[40] = {0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,3,0,0,0,0}
    to_craft[40] = {25,"entities/items/Ammo/ammo12Guage_2.ent",10}
    recipe[41] = {1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}
    to_craft[41] = {75,"entities/weapons/Blades/Neshca.ent",1}
    recipe[42] = {0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0}
    to_craft[42] = {25,"entities/weapons/Explosives/poo.ent",1}
    recipe[43] = {3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}
    to_craft[43] = {25,"entities/weapons/thrown/Atlatl.ent",1}  
    recipe[44] = {0,0,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[44] = {25,"entities/weapons/Grenades/chemicalGrenade.ent",1}  
    recipe[45] = {3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[45] = {25,"entities/weapons/Spear/Spear.ent",1}
    recipe[46] = {3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}
    to_craft[46] = {0,"entities/weapons/Rifles/ak47Rifle.ent",1}
    recipe[47] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0}
    to_craft[47] = {0,"entities/items/Food/RepasVege.ent",1}
    recipe[48] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4}
    to_craft[48] = {0,"entities/items/Food/RepasVege.ent",1}
    recipe[49] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0}
    to_craft[49] = {0,"entities/items/Food/Repas.ent",1}
    recipe[50] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0}
    to_craft[50] = {0,"entities/items/Food/Repas.ent",1}  
    recipe[51] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0}
    to_craft[51] = {0,"entities/items/Food/Repas.ent",1}
    recipe[52] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2}
    to_craft[52] = {0,"entities/items/Food/Repas.ent",1}
    recipe[53] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0}
    to_craft[53] = {25,"entities/items/Armor/ArmurePullTabs.ent",1}  
    recipe[54] = {0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0}
    to_craft[54] = {25,"entities/items/Misc/rope.ent",1}  
    recipe[55] = {0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[55] = {0,"entities/items/Armor/ArmureCarton.ent",1}
    recipe[56] = {0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[56] = {50,"entities/items/Ammo/ammo762mm_2.ent",10}
    recipe[57] = {0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0}
    to_craft[57] = {50,"entities/weapons/Melee/HachePrimitive.ent",1}
    recipe[58] = {2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[58] = {50,"entities/weapons/Pistols/SlingShotPistol.ent",1}
    recipe[59] = {2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[59] = {25,"entities/weapons/thrown/throwingStars.ent",5}  
    recipe[60] = {0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[60] = {25,"entities/items/Armor/Chaussures_improvisees1.ent",1}
    recipe[61] = {0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0}
    to_craft[61] = {25,"entities/items/Armor/Chaussures_improvisees4.ent",1}
    recipe[62] = {0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[62] = {25,"entities/items/Armor/Chaussures_improvisees3.ent",1}
    recipe[63] = {2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}
    to_craft[63] = {25,"entities/weapons/Melee/HacheTresPrimitive.ent",1}
    recipe[64] = {0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[64] = {50,"entities/items/Ammo/ammo22.ent",10}
    recipe[65] = {0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0}
    to_craft[65] = {50,"entities/weapons/Blades/CouteauPierre.ent",1}
    recipe[66] = {0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[66] = {50,"entities/weapons/Blades/knife.ent",1}
    recipe[67] = {0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[67] = {50,"entities/items/Drugs/healingPowder.ent",1}
    recipe[68] = {0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}
    to_craft[68] = {50,"entities/items/Useful/Flint_Steel.ent",1}  
    recipe[69] = {1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0}
    to_craft[69] = {25,"entities/items/Ammo/ammoBoltFleche.ent",5}  
    recipe[70] = {0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[70] = {25,"entities/items/Useful/FireStarter.ent",2}
    recipe[71] = {0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0}
    to_craft[71] = {25,"entities/weapons/Melee/fleauDIY.ent",1}  
    recipe[72] = {0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[72] = {25,"entities/weapons/Blades/shiv2.ent",1}  
    recipe[73] = {1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[73] = {25,"entities/weapons/Blades/shiv4.ent",1}  
    recipe[74] = {1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[74] = {25,"entities/weapons/thrown/SlingShotBranche.ent",1}  
    recipe[75] = {1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0}
    to_craft[75] = {0,"entities/weapons/Spear/Sstick.ent",1}
    recipe[76] = {0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0}
    to_craft[76] = {0,"entities/weapons/Misc/Fauxbras.ent",1}
    recipe[77] = {0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}
    to_craft[77] = {0,"entities/items/Misc/Cache-Yeux.ent",1}
    recipe[78] = {0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[78] = {25,"entities/weapons/Melee/ClubDIY.ent",1}
    recipe[79] = {3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[79] = {25,"entities/weapons/Spear/sharpenedPole.ent",1}
    recipe[80] = {0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0}
    to_craft[80] = {25,"entities/items/Enhancers/bag.ent",1}
    recipe[81] = {0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[81] = {50,"entities/weapons/Melee/PipeHammer.ent",1}
    recipe[82] = {0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[82] = {50,"entities/items/Misc/SavonNoir.ent",1}  
    recipe[83] = {0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0}
    to_craft[83] = {25,"entities/items/Enhancers/Sacoche1.ent",1}  
    recipe[84] = {0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0}
    to_craft[84] = {25,"entities/items/Useful/Masque_DIY.ent",1}  
    recipe[85] = {0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[85] = {25,"entities/weapons/Blades/CouteauPlastique.ent",1}
    recipe[86] = {0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[86] = {0,"entities/weapons/Unarmed/brassKnucklesDIY.ent",1}
    recipe[87] = {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[87] = {20,"entities/items/Ammo/ammoBBs.ent",1}
    recipe[88] = {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    to_craft[88] = {0,"entities/weapons/Blades/WoodDagger.ent",1}
    recipe[89] = {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0}
    to_craft[89] = {25,"entities/items/Useful/Pansement.ent",1}  

    --For each recipe, compare ressources
    local crafted = 0
    for i=1,#recipe do
        if p:GetAttribute("repair",ACTOR_TABLE_CURRENT) >= to_craft[i][1] then
            crafted = Craft_this(p,fr,box_items,recipe[i],to_craft[i][2],to_craft[i][3])
        end
        if crafted == 1 then
            Crafting_spend_items(recipe[i])
            break
        end  
    end
   
    if crafted == 0 then
        if fr == "non" then
                world:CombatLog(COMBATLOG_FEW,"<Co>"..p:GetName().." didn't find anything to craft<C->")
        else
            world:CombatLog(COMBATLOG_FEW,"<Co>"..p:GetName().." n'a rien trouvé à fabriquer<C->")
        end
    end  
end

Sorry, it's kind of lenghty...
 
Being tired of collecting items from corpses and thanks to FTSE, I have made a simple script for mass looting to a vehicle or a nearest corpse by taking a fruit in a hand. Equipping a fruit also removes corpses of big robots, which tend to block passage, in small radius. The script prevents vehicle damage from crashing into piles of cardboard boxes - at first I was trying to prevent entering turn based mode, but failed.

Installation is simple: just copy the file into the game folder, change the extension from .txt to .lua and add require("FruitQoL"):apply_hooks() line to the end of ftse_base.lua.
Keep in mind that some missions may remove entities (both alive and dead) on trigger or after leaving the map.

I have noticed the following issues:
1. logger doesn't work (nil)
2. Collectable.cpp line 149 exports wrong function (duplicate)
3. Moving an item with both lootable and non lootable non zero counts adds "invisible" count of the item in the destination inventory that become visible after changing the count through the game (eg moving grenades from an actor with 2 lootable and 3 non lootable counts will add 2 grenades to the destination inventory, but if you add 1 grenade to the inventory in-game, it will show that you added 1+3 grenades).
4. Traps items preserve parents event after leaving a map and reloading the game (just fun fact).
5. Some hooks functions require to return something and there is no way to fallback to original functions (expecting you to implement original mechanics).
6. Passing nil to CombatLog just crashes the game without any message in the log.
 

Attachments

The white box appears to be a forum advertisement. Why the new forum puts ads in the middle of a post, I have no idea. But it's annoying as hell.

Installation instructions are in the following paragraph:

Download the latest .ZIP release from the Release directory, and uncompress. Included is FTSESetup.exe - running this will bring up a small UI. Select the BOS.EXE file, and click "Install". The setup utility will patch the EXE and copy all necessary files to the game directory. A backup of the EXE will be produced.
 
I have a problem with save corruption with duplicate invisible Farsights

1. Finished first mission in tough guy mode
2. Put Farsight and Stitch into recruit pool
3. Save and reload
4. Farsight is missing from the recruit pool
5. Recruit anyone else
6. Save and reload
7. Now you have invisible Farsight in your team

Googling this issue turns up nothing. I had this save corruption two times now. Sometimes it doesn't happen. I think it maybe has something to do with Farsight ranking up to "senior initiate" while the main character is initiate?
 
I have a problem with save corruption with duplicate invisible Farsights

1. Finished first mission in tough guy mode
2. Put Farsight and Stitch into recruit pool
3. Save and reload
4. Farsight is missing from the recruit pool
5. Recruit anyone else
6. Save and reload
7. Now you have invisible Farsight in your team

Googling this issue turns up nothing. I had this save corruption two times now. Sometimes it doesn't happen. I think it maybe has something to do with Farsight ranking up to "senior initiate" while the main character is initiate?
I’ve noticed that the problems with duplicate recruits or incorrect main character happens to saves in bunkers. Exit the bunker and save anywhere else, it should fix the problem.
 
Hi all! Sorry, my English is not very good. My search brought me here) In the game, the maximum chance of hitting the target is 95%. That is, no matter how good things are, something can always go wrong. Then it would be logical that the minimum chance of getting in was 5%. That is, no matter how bad you are, you can always succeed (without going beyond the weapon’s range, of course). Okay, logic aside. Enemies shoot very frequently with a 0% hit chance. That is, moves are wasted. If the minimum chance of hitting is 5%, playing with the computer will be more interesting. The situation will turn out like in DnD, a constant chance of success is 5% (1 in 20) and a constant chance of failure is 5% (1 in 20). Please tell me how to implement this. FTSE installed. Thank you in advance!
 
Hi all! Sorry, my English is not very good. My search brought me here) In the game, the maximum chance of hitting the target is 95%. That is, no matter how good things are, something can always go wrong. Then it would be logical that the minimum chance of getting in was 5%. That is, no matter how bad you are, you can always succeed (without going beyond the weapon’s range, of course). Okay, logic aside. Enemies shoot very frequently with a 0% hit chance. That is, moves are wasted. If the minimum chance of hitting is 5%, playing with the computer will be more interesting. The situation will turn out like in DnD, a constant chance of success is 5% (1 in 20) and a constant chance of failure is 5% (1 in 20). Please tell me how to implement this. FTSE installed. Thank you in advance!
There is a file named ftse.lua in the game directory - you can put your code there. Try to tinker with OnChanceToHitCalc hook, you can also look how to work with hooks in armorattachedweapon.lua and could start with something like that:
Code:
function OnChanceToHitCalc (attacker, target, weapon, chanceInfo)
    if chanceInfo.hit_chance < 5 then
        chanceInfo.hit_chance = 5
    end
    if chanceInfo.raw_chance < 35 then -- 5 + 30 as the penalty for the hardest part aim
        chanceInfo.raw_chance = 35
    end
    return chanceInfo
end
 
Last edited:
Sorry all - was away for a little while. I see there's been a lot of activity recently!

Current status: I'm working on a mostly bugfix release, 0.56a, to fix some of the most recently reported issues. Since I'm back to having a bit of time to work, I hope to get this out in a couple of weeks (depends on how difficult some of the pending fixes end up being).

---

Replies to recent messages (apologies for length):

@Silica Pathways : Regarding the installation issue you had (I don't see the message anymore) - it looks like that behavior happens if the BOS executable file is unable to be read by the installer for some reason. It's a bit odd - the file selection dialog normally checks that the file exists and is readable prior to allowing selection (at least on my machine), but there may have been a condition it missed. In any event, I've added a bit better error handling here in the installer, so at least an error message will be displayed when this occurs. If you're still having trouble, you can try 0.56a when it's released, or I can try to talk you through a manual installation process.

I have a problem with save corruption with duplicate invisible Farsights

1. Finished first mission in tough guy mode
2. Put Farsight and Stitch into recruit pool
3. Save and reload
4. Farsight is missing from the recruit pool
5. Recruit anyone else
6. Save and reload
7. Now you have invisible Farsight in your team

Googling this issue turns up nothing. I had this save corruption two times now. Sometimes it doesn't happen. I think it maybe has something to do with Farsight ranking up to "senior initiate" while the main character is initiate?
Did a quick test of this, and can confirm the behavior is as you describe, and the speculated cause of rank is almost certainly the culprit. It's not an FTSE issue - I get the same thing with FTSE not installed. But I'm very curious on what's going on here. A later item for me to work on is the remaining stuff in the World object, some of which I expect to be recruits list and quartermaster items. Once I get to looking at that area, I should be able to figure out what's going on here. I guess the (temporary) workaround is to not drop a squad member that is higher level than the main character (or, maybe, to use a save editor to reduce Farsight's "missionscom" value, since that seems to be how other squad members level up).

Being tired of collecting items from corpses and thanks to FTSE, I have made a simple script for mass looting to a vehicle or a nearest corpse by taking a fruit in a hand. Equipping a fruit also removes corpses of big robots, which tend to block passage, in small radius.
That's a great script! Interesting to see the things that are already possible.

Regarding your other items:

1. logger doesn't work (nil)
Odd - does this mean that including the line :

Code:
logger:log("Hello!")

Results in a nil exception from Lua? Or is the issue something else?

2. Collectable.cpp line 149 exports wrong function (duplicate)
Whoops - cut and paste issue. Will be fixed in 0.56a bugfix release.

3. Moving an item with both lootable and non lootable non zero counts adds "invisible" count of the item in the destination inventory that become visible after changing the count through the game (eg moving grenades from an actor with 2 lootable and 3 non lootable counts will add 2 grenades to the destination inventory, but if you add 1 grenade to the inventory in-game, it will show that you added 1+3 grenades).
Yeah. I expect that this may be caused by the code that "merges" stacks of items, and that the code never expects to have to merge to or from a non-lootable stack. I tried recreating this without FTSE, but couldn't quite match your behavior. I did end up with a case where dragging a grenade from a character to a dead actor with a non-zero "non-lootable" count resulted in the grenade disappearing (probably being merged with the nonlootable entity in the dead character inventory).

4. Traps items preserve parents event after leaving a map and reloading the game (just fun fact).
Definitely interesting - the routine that bundles entities related to the player + squad + vehicles will follow linked entities, though I'm surprised it would follow the parent entity for Trap. I'll check the code there. (Especially since I plan to make use of this behavior for including custom entity references accessible by FTSE scripts - would be useful for e.g. adding new inventory slots for the player, or having weapons with multiple ammo types.)

5. Some hooks functions require to return something and there is no way to fallback to original functions (expecting you to implement original mechanics).
I think the only hook that fully requires an implementation is the time conversion one. Most of the others can either return nothing, or in the case of the critical hooks, return the parameter value that was passed to the hook. In any event, I'll probably make all hooks allowed to return nothing, as I'll probably need this once I add module support.

6. Passing nil to CombatLog just crashes the game without any message in the log.
Expected. A lot of areas don't have great error checking yet. That's coming once I get a bit more functionality in place.
 
About recruits. Started the game, first mission, Stich was killed. Then in the bunker he was available for hiring. The team is level 3. Hired Stich, he is also level 3 and the level up button is available. I press the button, no points to distribute, ok. Then in the next mission I don’t remember at what point I see that he has the ability “Additional melee damage”. He had “Escort”, right? I’m not sure I translated the abilities correctly. Maybe this is somehow related to the problem discussed above (I think he was resurrected by voodoo shamans and he just hit his head hard). In the second mission he died again and I never saw him again. Unofficial Patch Plus (HRP+Improver+Fixes) + FTSE installed.
 
@Silica Pathways : Regarding the installation issue you had (I don't see the message anymore) - it looks like that behavior happens if the BOS executable file is unable to be read by the installer for some reason. It's a bit odd - the file selection dialog normally checks that the file exists and is readable prior to allowing selection (at least on my machine), but there may have been a condition it missed. In any event, I've added a bit better error handling here in the installer, so at least an error message will be displayed when this occurs. If you're still having trouble, you can try 0.56a when it's released, or I can try to talk you through a manual installation process.
I deleted the posts, since I had solved the issue myself - it turned out that the game directory had wonky permission setup, so I simply copied the bos.exe elsewhere, patched it and copied it back. I suppose normally the OS should have prompted the permission dialogue when patching bos.exe in the original location, but for some reason it didn't.
 
@nadeauhugo : I've recreated the issue with the crafting script not working with a container, and figured out why it happens. I'll provide a full fix in 0.56a, but in the meantime, you can use the following workaround - Change the Remove_this function to read as follows (note the added "AddToInventory" call):

Code:
function Remove_this(recipe,icount,box,invent)
    if recipe > 0 then
        if icount >= recipe then
            local removedent = box:RemoveInventory(invent,recipe)
            if type(removedent) == "table" then removedent:Destruct() end --Clear memory
            recipe = 0 --Removing of this ingredient completed
        else
            local removedent = box:RemoveInventory(invent,icount)
            if type(removedent) == "table" then removedent:Destruct() end --Clear memory
            recipe = recipe - icount --Will need to remove more of this ingredient
        end
        box:AddToInventory(box,0)
    end
    return recipe
end

---

Technical details on the issue for anyone interested:

When removing a count of items from an inventory, the game will duplicate the removed entity with a count of number of items removed (to generate the "removed" object(s)), then reduce the internal counter in the original item's entity definition. If this counter reaches zero, then the original entity is destructed. However, it is not automatically removed from the Actor or Container inventory linked list. So what is left is a "stale" entity reference. This reference will generally point to a "null" entity with ID zero.

In the crafting script case, these null entities were being returned in the GetInventory call. Since the null entity isn't derived from Collectable, it doesn't have a counter, so the call to GetCount fails with an error. Since the Lua script didn't have any error handling, this error went unreported.

What's interesting is why using a dead actor worked. The RemoveInventory vtable function for the Actor class is different than that for Container and Vehicle, in that it will check if removing the item unencumbers the character, and to reset the encumbered flag if so. Checking encumberance means checking the weight of all objects in that character's inventory. And in the function to check weights, there's a call to a function that will "validate" all entries in the inventory - that is, it will remove any stale references. So the stale reference would be removed immediately in that case, and the Lua script would never end up receiving the null entity in the GetInventory list.

The permanent fix will be to ensure that 1) the FTSE code handing RemoveInventory always calls this validation function immediately after, and 2) if GetInventory encounters a null entity in the list, to not return it to Lua.

The temporary workaround above, to add a bogus AddToInventory call, works because AddToInventory calls the same validation function. Since the entity passed is not of type Collectable (the game checks for this), and the count to add is zero (game checks for this too), nothing will actually get added to the inventory. But the validate function is called before checking these, so the inventory will still be cleaned up properly.
 
FTSE 0.56a is now available. Highlights are a few bug fixes for problems listed earlier in the thread, a new hex patch for Night Person, Die Hard, and Adrenaline Rush, and a few new Entity and Actor functions to allow changes to tag string, XP, reputation, HP, and AP.
 
Back
Top