[SOLVED] Items that store energy

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
Post Reply
User avatar
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

[SOLVED] Items that store energy

Post by Zo Kath Ra »

I want to make an item that stores energy (or health).
(using version 2.2.4)

Reason:
There are items that increase a champion's maximum energy, like the gear_necklace in
/assets/scripts/items/accessories.lua
But when you un-equip such an item, the surplus energy is lost.

My first attempt looked like this:
(it uses a simplified version of JKos's code from the thread Storing data to entities and other scripting tricks)

Code: Select all

defineObject{
    name = "necklace_of_energy_1",
        baseObject = "base_item",
        components = {
            {
                class = "Model",
                model = "assets/models/items/gear_necklace.fbx",
            },
            {
                class = "Item",
                uiName = "Necklace of Energy 1",
                gfxIndex = 219,
                weight = 1.0,
                traits = { "necklace" },
                description = "A necklace that stores energy.",
                
                onEquipItem = function(self, champion, slot)
                    if (slot == ItemSlot.Necklace) then
                        champion:regainEnergy(self.go.data:get("stored_energy"))
                        self.go.data:set("stored_energy", 0)
                    end
                end,
                
                onUnequipItem = function(self, champion, slot)
                    if (slot == ItemSlot.Necklace) then
                        -- store the surplus energy in the item
                        if (champion:getEnergy() > (champion:getMaxEnergy() - 15)) then
                            self.go.data:set("stored_energy", champion:getEnergy() - (champion:getMaxEnergy() - 15))
                        end
                    end
                end,
            },
            {
                class = "EquipmentItem",
                energy = 15,
            },
            {
                class = "Script",
                name = "data",
                source = [[
                    data = {["stored_energy"] = 15}
                    
                    get = function(self, name)
                        return self.data[name]
                    end
                    
                    set = function(self, name, value)
                        self.data[name] = value
                    end
                ]]
        }
    },
}
It doesn't work, though, because 'onEquipItem' is called *before* the 15 energy modifier is applied, so 'champion:regainEnergy(self.go.data:get("stored_energy"))' doesn't do anything.
(is this a bug or intended behavior?)

My second attempt was to call 'champion:regainEnergy(self.go.data:get("stored_energy"))' when the 15 energy modifier has been applied:

Code: Select all

defineObject{
    name = "necklace_of_energy_2",
        baseObject = "base_item",
        components = {
            {
                class = "Model",
                model = "assets/models/items/gear_necklace.fbx",
            },
            {
                class = "Item",
                uiName = "Necklace of Energy 2",
                gfxIndex = 219,
                weight = 1.0,
                traits = { "necklace" },
                description = "A necklace that stores energy.",
                
                onEquipItem = function(self, champion, slot)
                    if (slot == ItemSlot.Necklace) then
                        if (self.go.data:get("stored_energy") > 0) then
                            self.go.data:set("new_max_energy", champion:getMaxEnergy() + 15)
                        end
                    end
                end,
                
                onUnequipItem = function(self, champion, slot)
                    if (slot == ItemSlot.Necklace) then
                        -- store the surplus energy in the item
                        if (champion:getEnergy() > (champion:getMaxEnergy() - 15)) then
                            self.go.data:set("stored_energy", champion:getEnergy() - (champion:getMaxEnergy() - 15))
                        end
                    end
                end,
            },
            {
                class = "EquipmentItem",
                energy = 15,
                
                onRecomputeStats = function(self, champion)
                    if (champion:getMaxEnergy() == self.go.data:get("new_max_energy")) then
                        self.go.data:set("new_max_energy", -1)
                        champion:regainEnergy(self.go.data:get("stored_energy"))
                        self.go.data:set("stored_energy", 0)
                    end
                end
            },
            {
                class = "Script",
                name = "data",
                source = [[
                    data = {["stored_energy"] = 15, ["new_max_energy"] = -1}
                    
                    get = function(self, name)
                        return self.data[name]
                    end
                    
                    set = function(self, name, value)
                        self.data[name] = value
                    end
                ]]
        }
    },
}
This doesn't work either, because 'champion:getMaxEnergy()' in the function 'onRecomputeStats' doesn't return the champion's maximum energy. Instead, it always returns 15 (the energy modifier).
(again, is this a bug or intended behavior?)
Last edited by Zo Kath Ra on Thu May 28, 2015 3:21 pm, edited 1 time in total.
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Items that store energy

Post by minmay »

dude, you shouldn't try to check current stats in onRecomputeStats... look at the name of the function!
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Items that store energy

Post by Zo Kath Ra »

Place a script entity called "script_entity_energy" anywhere in the game, and put this code in it:

Code: Select all

function gainEnergy(ordinal, energy)
	party.party:getChampionByOrdinal(ordinal):regainEnergy(energy)
end
The above function is called from "onRecomputeStats" using "delayedCall":

Code: Select all

defineObject{
    name = "necklace_of_energy",
    baseObject = "base_item",
    components = {
        {
            class = "Model",
            model = "assets/models/items/gear_necklace.fbx",
        },
        {
            class = "Item",
            uiName = "Necklace of Energy",
            gfxIndex = 219,
            weight = 1.0,
            traits = { "necklace" },
            description = "A necklace that stores energy.",
            
            onEquipItem = function(self, champion, slot)
                if (slot == ItemSlot.Necklace) then
                    if (self.go.data:get("stored_energy") > 0) then
                        self.go.data:set("delayed_call", true)
                    end
                end
            end,
            
            onUnequipItem = function(self, champion, slot)
                if (slot == ItemSlot.Necklace) then
                    -- store the surplus energy in the item
                    if (champion:getEnergy() > (champion:getMaxEnergy() - self.go.equipmentitem:getEnergy())) then
                        self.go.data:set("stored_energy", champion:getEnergy() - (champion:getMaxEnergy() - self.go.equipmentitem:getEnergy()))
                        self.go.item:setDescription("A necklace that stores energy.\nIt contains " .. math.floor(self.go.data:get("stored_energy")) .. " points of energy.")
                    end
                end
            end,
        },
        {
            class = "EquipmentItem",
            energy = 15,
            
            onRecomputeStats = function(self, champion)
                if (self.go.data:get("delayed_call") == true) then
                    self.go.data:set("delayed_call", false)
                    delayedCall("script_entity_energy", 0, "gainEnergy", champion:getOrdinal(), self.go.data:get("stored_energy"))
                    self.go.data:set("stored_energy", 0)
                    self.go.item:setDescription("A necklace that stores energy.\nIt contains " .. math.floor(self.go.data:get("stored_energy")) .. " points of energy.")
                end
            end,
        },
        {
            class = "Script",
            name = "data",
            source = [[
                data = {["stored_energy"] = 15, ["delayed_call"] = false}
                
                get = function(self, name)
                    return self.data[name]
                end
                
                set = function(self, name, value)
                    self.data[name] = value
                end
            ]],
            
            onInit = function(self)
                self.go.item:setDescription("A necklace that stores energy.\nIt contains " .. math.floor(self.go.data:get("stored_energy")) .. " points of energy.")
            end,
        }
    },
}
I've tested this code and so far, it appears to work reliably.
User avatar
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Items that store energy

Post by Zo Kath Ra »

The code from the last post doesn't work properly if
- in each hand, the party member is carrying an item that stores health/energy
- you un-equip both items by changing the equipment set (the I/II tabs to the left of the equipped items)

It doesn't work correctly because both items receive the same values when they call
- champion:getHealth() / champion:getEnergy()
- champion:getMaxHealth() / champion:getMaxEnergy()

=> the item that is un-equipped second (always the one in ItemSlot.OffHand) stores the wrong amount of health/energy

Solution:
The item that is un-equipped first tells the item that is un-equipped second by how much the latter must correct these values.

Place a script entity called "regain_health_or_energy" anywhere in the game, and put this code in it:

Code: Select all

function regainHealthOrEnergy(ordinal, health, energy)
    if (health > 0) then
        party.party:getChampionByOrdinal(ordinal):regainHealth(health)
    end
    
    if (energy > 0) then
        party.party:getChampionByOrdinal(ordinal):regainEnergy(energy)
    end
end
And this code in items.lua:

Code: Select all

defineObject{
    name = "wand_of_health_and_energy",
    baseObject = "base_item",
    components = {
        {
            class = "Model",
            model = "assets/models/items/whitewood_wand.fbx",
        },
        {
            class = "Item",
            uiName = "Wand of Health and Energy",
            description = "",
            gfxIndex = 56,
            impactSound = "impact_blunt",
            weight = 3.5,
            traits = { "stores_health_or_energy" },
            
            onEquipItem = function(self, champion, slot)
                if ((slot == ItemSlot.Weapon) or (slot == ItemSlot.OffHand)) then
                    if ((self.go.data:get("stored_health") > 0) or (self.go.data:get("stored_energy") > 0)) then
                        self.go.data:set("delayed_call", true)
                    end
                end
            end,
            
            onUnequipItem = function(self, champion, slot)
                if ((slot == ItemSlot.Weapon) or (slot == ItemSlot.OffHand)) then
                    if (getMouseItem() ~= self) then   -- was the current item un-equipped manually or by changing equipment sets?
                        -- the current item was un-equipped by changing equipment sets
                        if (slot == ItemSlot.Weapon) then   -- was the current item un-equipped first?
                            -- the current item was un-equipped first
                            -- send a message to the item in the other slot, if it has the trait "stores_health_or_energy"
                            
                            local other_item = champion:getItem(ItemSlot.OffHand)
                            
                            if (other_item ~= nil) then
                                if (other_item:hasTrait("stores_health_or_energy")) then
                                    -- send a message to the item in the other slot
                                    
                                    other_item.go.data:set("correction_max_health", self.go.equipmentitem:getHealth())
                                    if (champion:getHealth() > (champion:getMaxHealth() - self.go.equipmentitem:getHealth())) then
                                        other_item.go.data:set("correction_health", champion:getHealth() - (champion:getMaxHealth() - self.go.equipmentitem:getHealth()))
                                    else
                                        other_item.go.data:set("correction_health", 0)
                                    end
                                    
                                    other_item.go.data:set("correction_max_energy", self.go.equipmentitem:getEnergy())
                                    if (champion:getEnergy() > (champion:getMaxEnergy() - self.go.equipmentitem:getEnergy())) then
                                        other_item.go.data:set("correction_energy", champion:getEnergy() - (champion:getMaxEnergy() - self.go.equipmentitem:getEnergy()))
                                    else
                                        other_item.go.data:set("correction_energy", 0)
                                    end
                                end
                            end
                        end
                    end
                    
                    -- store the surplus health in the item
                    if ((champion:getHealth() - self.go.data:get("correction_health")) > (champion:getMaxHealth() - self.go.data:get("correction_max_health") - self.go.equipmentitem:getHealth())) then
                        self.go.data:set("stored_health", (champion:getHealth() - self.go.data:get("correction_health")) - (champion:getMaxHealth() - self.go.data:get("correction_max_health") - self.go.equipmentitem:getHealth()))
                    end
                    self.go.data:set("correction_max_health", 0)
                    self.go.data:set("correction_health", 0)
                    
                    -- store the surplus energy in the item
                    if ((champion:getEnergy() - self.go.data:get("correction_energy")) > (champion:getMaxEnergy() - self.go.data:get("correction_max_energy") - self.go.equipmentitem:getEnergy())) then
                        self.go.data:set("stored_energy", (champion:getEnergy() - self.go.data:get("correction_energy")) - (champion:getMaxEnergy() - self.go.data:get("correction_max_energy") - self.go.equipmentitem:getEnergy()))
                    end
                    self.go.data:set("correction_max_energy", 0)
                    self.go.data:set("correction_energy", 0)
                    
                    self.go.data:set_description()
                end
            end,
        },
        {
            class = "EquipmentItem",
            slot = "Weapon",
            health = 50,
            energy = 50,
            
            onRecomputeStats = function(self, champion)
                if (self.go.data:get("delayed_call") == true) then
                    self.go.data:set("delayed_call", false)
                    delayedCall("regain_health_or_energy", 0, "regainHealthOrEnergy", champion:getOrdinal(), self.go.data:get("stored_health"), self.go.data:get("stored_energy"))
                    self.go.data:set("stored_health", 0)
                    self.go.data:set("stored_energy", 0)
                    self.go.data:set_description()
                end
            end,
        },
        {
            class = "Script",
            name = "data",
            source = [[
                data = { ["stored_health"] = 50, ["stored_energy"] = 50, ["correction_max_health"] = 0, ["correction_health"] = 0, ["correction_max_energy"] = 0, ["correction_energy"] = 0, ["delayed_call"] = false, ["description_string"] = "A wand that stores health and energy." }
                
                get = function(self, name)
                    if (name == "stored_health") then
                        if ((self.go.equipmentitem:getHealth() > 0) and (self.data[name] ~= nil)) then
                            return self.data[name]
                        else
                            return 0
                        end
                    elseif (name == "stored_energy") then
                        if ((self.go.equipmentitem:getEnergy() > 0) and (self.data[name] ~= nil)) then
                            return self.data[name]
                        else
                            return 0
                        end
                    else
                        return self.data[name]
                    end
                end
                
                set = function(self, name, value)
                    if (name == "stored_health") then
                        if (self.go.equipmentitem:getHealth() > 0) then
                            self.data[name] = value
                        else
                            self.data[name] = nil
                        end
                    elseif (name == "stored_energy") then
                        if (self.go.equipmentitem:getEnergy() > 0) then
                            self.data[name] = value
                        else
                            self.data[name] = nil
                        end
                    else
                        self.data[name] = value
                    end
                end
                
                set_description = function(self)
                    local description_health = ""
                    local description_energy = ""
                    
                    if (self.go.equipmentitem:getHealth() > 0) then
                        description_health = math.floor(self.go.data:get("stored_health")) .. " point of health"
                        
                        if (points_health ~= 1) then
                            description_health = string.gsub(description_health, "point", "points")
                        end
                    end
                    
                    if (self.go.equipmentitem:getEnergy() > 0) then
                        description_energy = math.floor(self.go.data:get("stored_energy")) .. " point of energy"
                        
                        if (points_energy ~= 1) then
                            description_energy = string.gsub(description_energy, "point", "points")
                        end
                    end
                    
                    if ((description_health == "") and (description_energy == "")) then
                        self.go.item:setDescription(self.go.data:get("description_string"))
                    elseif ((description_health ~= "") and (description_energy ~= "")) then
                        self.go.item:setDescription(self.go.data:get("description_string") .. "\nIt contains " .. description_health .. " and " .. description_energy .. ".")
                    else
                        self.go.item:setDescription(self.go.data:get("description_string") .. "\nIt contains " .. description_health .. description_energy .. ".")
                    end
                end
            ]],
            
            onInit = function(self)
                self.go.data:set_description()
            end,
        },
    }
}
Post Reply