Jump to content

Recommended Posts

Hi everyone,

I'm currently working on a small part of a mod for Don't Starve Together and need some assistance. My goal is to replace a specific item in the players' inventory and backpack with another item during every full moon. This change should be permanent (i.e., the item does not revert back after the full moon ends). The items outside the player's inventory and backpack should remain unaffected.

I've been studying the code for prefabs like Chester and have found it quite insightful. However, I haven't been able to figure out an effective way to globally detect the full moon and replace items in the inventory and backpack in place.

Any help or guidance would be greatly appreciated. Thanks!

Hi, welcome to the forums!

You're on the right track. Chester waits for the full moon to see if he can morph (l. 694), though the MorphChester function is actually called from his stategraph. As for how to replace the item, the perisable component has some similar logic (the Perish function, in particular ll. 291 & 306-311) - including checking for the owner of the item; here, the perished item is deleted and a new one is spawned in. And regarding the detection itself, I'd leave it on the prefab.

So, wtih all that, the suggestion would be:

  • In your prefab .lua file, listen for the full moon. You might also want to check when the item is picked up, in case a player picks up the item during the full moon.
  • Create a callback function that handles the transformation.
    • Check that it's indeed full moon, that the item is in an inventory or backpack, and that the inventory or backpack is held by a player.
    • Spawn your new item into the player's possession and delete the old item.

And that should be all! Good luck!

--

Spoiler

Here's a possible solution, in case you need it. Untested!

Spoiler
-- In your prefab .lua file:
local function CheckForTransformation(inst)
  if not TheWorld.state.isfullmoon then return end

  local owner = inst.components.inventoryitem ~= nil and inst.components.inventoryitem.owner or nil
  if not owner.HasTag("player") then return end

  local holder = owner ~= nil and owner.components.inventory or nil
  if holder ~= nil then
    local newItem = SpawnPrefab(YOUR_NEW_PREFAB_HERE)
    local slot = holder:GetItemSlot(inst)
    inst:Remove()
    holder:GiveItem(newItem, slot)
  end
end

-- In your prefab .lua file, in the main function, after checking that we're in the master sim:
  inst:WatchWorldState("isfullmoon", CheckForTransformation)
  inst:ListenForEvent("onpickup", CheckForTransformation)

 

 

 

11 hours ago, alainmcd said:

Hi, welcome to the forums!

You're on the right track. Chester waits for the full moon to see if he can morph (l. 694), though the MorphChester function is actually called from his stategraph. As for how to replace the item, the perisable component has some similar logic (the Perish function, in particular ll. 291 & 306-311) - including checking for the owner of the item; here, the perished item is deleted and a new one is spawned in. And regarding the detection itself, I'd leave it on the prefab.

So, wtih all that, the suggestion would be:

  • In your prefab .lua file, listen for the full moon. You might also want to check when the item is picked up, in case a player picks up the item during the full moon.
  • Create a callback function that handles the transformation.
    • Check that it's indeed full moon, that the item is in an inventory or backpack, and that the inventory or backpack is held by a player.
    • Spawn your new item into the player's possession and delete the old item.

And that should be all! Good luck!

--

  Reveal hidden contents

Here's a possible solution, in case you need it. Untested!

  Reveal hidden contents
-- In your prefab .lua file:
local function CheckForTransformation(inst)
  if not TheWorld.state.isfullmoon then return end

  local owner = inst.components.inventoryitem ~= nil and inst.components.inventoryitem.owner or nil
  if not owner.HasTag("player") then return end

  local holder = owner ~= nil and owner.components.inventory or nil
  if holder ~= nil then
    local newItem = SpawnPrefab(YOUR_NEW_PREFAB_HERE)
    local slot = holder:GetItemSlot(inst)
    inst:Remove()
    holder:GiveItem(newItem, slot)
  end
end

-- In your prefab .lua file, in the main function, after checking that we're in the master sim:
  inst:WatchWorldState("isfullmoon", CheckForTransformation)
  inst:ListenForEvent("onpickup", CheckForTransformation)

 

 

 

Thank you for your prompt reply.

But no luck, the code you provided causes a disconnection from the server when the full moon arrives.

Currently, I am using the following code:

local function OnFullMoon(inst)
    if not TheWorld.state.isfullmoon then return end  
    for _, player in ipairs(AllPlayers) do  
        for i = 1, player.components.inventory:GetNumSlots() do  
            local item = player.components.inventory:GetItemInSlot(i)
            if item and item.prefab == "fhl_x" then  
                local newItem = SpawnPrefab("fhl_x2")
                newItem.components.perishable:SetPercent(item.components.perishable:GetPercent())
                player.components.inventory:RemoveItem(item, false)
                player.components.inventory:GiveItem(newItem, i)
            end  
        end  
    end  
end

local function fn(Sim)
    ...
    inst.entity:SetPristine()
    if TheWorld.ismastersim then  
        inst:WatchWorldState("isfullmoon", OnFullMoon)
    else  
        return inst  
    end  
    ...
end

This code does convert items in the inventory when the full moon comes, but there are still several issues that need to be resolved:

1. When multiple fhl_x items are stacked, the converted fhl_x2 does not stay in the original slots.
2. It cannot convert items in the backpack.
3. The item has the following recipes, and during the full moon, crafting should directly yield fhl_x2, whereas at other times it should yield fhl_x.

AddCharacterRecipe("fhl_x_1",
    { Ingredient("berries", 2), Ingredient("froglegs_cooked", 2), Ingredient("petals_evil", 1) }, TECH.NONE,
    { product = "fhl_x", builder_tag = "fhl" })
AddCharacterRecipe("fhl_x_2",
    { Ingredient("berries_juicy", 2), Ingredient("froglegs_cooked", 2), Ingredient("petals_evil", 1) }, TECH.NONE,
    { product = "fhl_x", builder_tag = "fhl" })

 

Sorry that didn't work out. :-/

  1. Might be a timing issue. Why not remove the item directly with item:Remove()? If that doesn't work, you could try to defer the removal with a DoTaskInTime(0, ...) or DoTaskInTime(1, ...).
  2. You'll need to check for items in containers. Check out the Has function in the inventory component and its checkallcontainers argument, ll. 1311-1319. Note that this loops through all open containers, including crockpots, chests and such. If you want to restrict it to equipped backpacks, check for its owner.
  3. I don't see a way to swap or remove recipes. Maybe give them different tags to check for and swap your character's tags according to the moon phase?
Quote

Why not remove the item directly with item:Remove()?

Using item:Remove() alone increases items for some unknown reason.

Now significant advancements have been made. The code below works almost as expected.

local function OnFullMoon(inst)
    if not TheWorld.state.isfullmoon then return end
    for _, player in ipairs(AllPlayers) do
        local inventory = player.components.inventory
        local items = inventory:FindItems(function(item) return item.prefab == "fhl_x" end)
        for _, item in ipairs(items) do
            local newItem = SpawnPrefab("fhl_x2")
            local slot = inventory:GetItemSlot(item)
            newItem.components.stackable:SetStackSize(item.components.stackable:StackSize())
            newItem.components.perishable:SetPercent(item.components.perishable:GetPercent())
            inventory:RemoveItem(item, false)
            item:Remove()
            inventory:GiveItem(newItem, slot)
        end
    end
end

There's one last step left before reaching the goal: producing different items during the full moon period.

What could be done here? To listen for a certain event to replace 'fhl_x' with 'fhl_x2' during the full moon, or update the recipe product at the beginning and end of the full moon?

Edited by yanecc

Not sure if it'd work, but: make two recipes, one with builder_tag = "fhl_regular" and the other with builder_tag = "fhl_fullmoon", give your character the "fhl_regular" tag, listen for full moon and swap the tags accordingly. If that doesn't work, you could try the same but with tech trees: create two recipes with different, new techs; create two tech trees, one with each new tech; give your character the prototyper component; and swap out its tech tree according to the moon phase. But I'm running out of good ideas. :grin:

Quote

What could be done here? To listen for a certain event to replace 'fhl_x' with 'fhl_x2' during the full moon, or update the recipe product at the beginning and end of the full moon?

I consulted the Tutorial and found a seemingly good implementation. All requirements in this post are now achieved, feel free to provide suggestions for further improvement! :)

-- modmain.lua
local function UseFullMoonRecipe()
    AddRecipePostInit("fhl_x_1", function(recipe)
        recipe.product = "fhl_x2"
    end)
    AddRecipePostInit("fhl_x_2", function(recipe)
        recipe.product = "fhl_x2"
    end)
end

local function RestoreOriginalRecipe()
    AddRecipePostInit("fhl_x_1", function(recipe)
        recipe.product = "fhl_x"
    end)
    AddRecipePostInit("fhl_x_2", function(recipe)
        recipe.product = "fhl_x"
    end)
end

AddPrefabPostInit("world", function(inst)
    inst:WatchWorldState("isfullmoon", function(inst, isfullmoon)
        if isfullmoon then
            UseFullMoonRecipe()
        else
            RestoreOriginalRecipe()
        end
    end)
end)

 

  • Like 1

As a supplement to the integrity record here, it is also necessary to add a listen for the "ms_playerjoined" event to avoid the condition that it's during the full moon night that players enter the world in which it won't trigger the "isfullmoon" check.

local function CheckFullMoon()
    if GLOBAL.TheWorld.state.isfullmoon then
        UseFullMoonRecipe()
    end
end

AddPrefabPostInit("world", function(inst)
    inst:ListenForEvent("ms_playerjoined", CheckFullMoon)
    inst:WatchWorldState("isfullmoon", function(inst, isfullmoon)
        if isfullmoon then
            UseFullMoonRecipe()
        else
            RestoreOriginalRecipe()
        end
    end)
end)

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...