ShinyMoogle Posted March 7, 2019 Share Posted March 7, 2019 (edited) Hi all! I'm currently trying to create a sort of "clown car" chest, meaning a chest that can store more items than initially expected. To do this, the chest iterates over every item in the container once it is closed, and rolls a chance for that item to disappear from the main chest storage and instead get saved in a hidden "vault" that players cannot access. The next time the chest is opened, any empty slots have a chance to be filled in with a random item from the chest's hidden vault. The resulting effect is that items sort of just disappear and reappear at random times, often leaving empty spaces to stuff more items in. Currently, all of that is working fine. What I'm having trouble with is saving that hidden vault so it's loaded in the next session. I made an attempt at it, but I'm just taking ineffective shots in the dark here. Hopefully someone more familiar with the game's data structures will be able to help. I tried saving the vault as-is, and saving the save records in a table, but neither seems to work properly. Thanks guys! I've included the relevant code snippets below. OnOpen local function onopen (inst) local opener = inst.components.container.opener -- Require sanity tribute and set perishable multiplier back to normal -- Chance to restore items from vault if opener.components.sanity and not opener.components.sanity:IsCrazy() then opener.components.sanity:DoDelta(-TUNING.SANITY_SMALL) local inv = inst.components.container for i = 1, inv:GetNumSlots() do local item = inv:GetItemInSlot(i) if item then if item.components.perishable then item.components.perishable:SetLocalMultiplier(1) end elseif #inst.vault > 0 then if math.random() < SHUFFLECHANCE then local vaultindex = math.random(1,#inst.vault) local founditem = table.remove(inst.vault, vaultindex) if founditem.components.perishable then founditem.components.perishable:StartPerishing() end inv:GiveItem(founditem, i) end end end -- If insufficient sanity, prevent from opening else opener.components.talker:Say("Huh? Th-there's a box here right?") inst.components.container:Close() return end -- [additional code redacted for brevity] -- [basically a chance to spawn a few random items on open] end OnClose local function onclose(inst) if not inst:HasTag("burnt") then inst.AnimState:PlayAnimation("close") inst.AnimState:PushAnimation("closed", false) inst.SoundEmitter:PlaySound("dontstarve/wilson/chest_close") local inv = inst.components.container for i = 1, inv:GetNumSlots() do local item = inv:GetItemInSlot(i) if item then -- Chance to disappear into chest vault if not item:HasTag("irreplaceable") and math.random() < SHUFFLECHANCE then if item.components.perishable then item.components.perishable:StopPerishing() end table.insert (inst.vault, inv:RemoveItemBySlot(i)) elseif item.components.perishable then item.components.perishable:SetLocalMultiplier(0.25) end end end end end Non-functional OnSave and OnLoad local function onsave(inst, data) if inst.components.burnable ~= nil and inst.components.burnable:IsBurning() or inst:HasTag("burnt") then data.burnt = true end data.vault = {} for k,v in ipairs(inst.vault) do if v:IsValid() and v.persists then --only save the valid items table.insert(data.vault, v:GetSaveRecord()) end end end local function onload(inst, data) if data ~= nil and data.burnt and inst.components.burnable ~= nil then inst.components.burnable.onburnt(inst) end if data and data.vault then for k,v in ipairs(data.vault) do local item = SpawnSaveRecord(v) if item then table.insert(inst.vault, item) end end end end Main Function local function fn() local inst = CreateEntity() inst.entity:AddTransform() inst.entity:AddAnimState() inst.entity:AddSoundEmitter() inst.entity:AddLight() inst.entity:AddMiniMapEntity() inst.entity:AddNetwork() inst.MiniMapEntity:SetIcon("pandoraschest.png") inst:AddTag("structure") inst:AddTag("chest") inst:AddTag("fridge") inst.Light:SetFalloff(1) inst.Light:SetIntensity(0.5) inst.Light:SetRadius(2) inst.Light:SetColour(180/255, 195/255, 150/255) inst.Light:Enable(true) inst.Light:EnableClientModulation(true) inst._fadeval = net_float(inst.GUID, "fireflies._fadeval") inst._faderate = net_smallbyte(inst.GUID, "fireflies._faderate", "onfaderatedirty") inst._fadetask = nil inst.AnimState:SetBank("pandoras_chest") inst.AnimState:SetBuild("pandoras_chest") inst.AnimState:PlayAnimation("closed") MakeSnowCoveredPristine(inst) inst.entity:SetPristine() if not TheWorld.ismastersim then inst:ListenForEvent("onfaderatedirty", OnFadeRateDirty) inst:DoTaskInTime(0.1, function(inst) inst.replica.container:WidgetSetup("shadowchester") end) return inst end inst:AddComponent("playerprox") inst.components.playerprox:SetOnPlayerNear(onplayerprox) inst.components.playerprox:SetOnPlayerFar(fadeout) inst.components.playerprox:SetDist(3,4) inst:AddComponent("inspectable") inst:AddComponent("container") inst.components.container:WidgetSetup("shadowchester") inst.components.container.onopenfn = onopen inst.components.container.onclosefn = onclose inst:AddComponent("sanityaura") inst.components.sanityaura.aura = -TUNING.SANITYAURA_TINY inst:AddComponent("lootdropper") inst:AddComponent("workable") inst.components.workable:SetWorkAction(ACTIONS.HAMMER) inst.components.workable:SetWorkLeft(5) inst.components.workable:SetOnFinishCallback(onhammered) inst.components.workable:SetOnWorkCallback(onhit) inst:AddComponent("hauntable") inst.components.hauntable:SetHauntValue(TUNING.HAUNT_TINY) inst.vault = {} inst:ListenForEvent("onbuilt", onbuilt) MakeSnowCovered(inst) inst.OnSave = onsave inst.OnLoad = onload updatelight(inst) return inst end Error log from last session [string "../mods/Pandora - prealpha/scripts/prefabs/..."]:598: bad argument #2 to 'insert' (number expected, got table) LUA ERROR stack traceback: =[C]:-1 in (field) insert (C) <-1--1> ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:598 in (field) OnSave (Lua) <590-601> inst = 107566 - strangebox (valid:true) data = table: 07CD99F0 k = 1 v = 113492 - cutreeds (valid:true) scripts/entityscript.lua:1521 in (method) GetPersistData (Lua) <1489-1538> self (valid:true) = GUID = 107566 Transform = Transform (1F72EFC8) inlimbo = false actionreplica = table: 1F8D53E0 _faderate = net_smallbyte (1F8E4618) actioncomponents = table: 1F8D5228 event_listening = table: 1F8D5FC0 lower_components_shadow = table: 1F8D52A0 loot = table: 07257A60 lootaggro = table: 07257AD8 entity = Entity (1F855120) AnimState = AnimState (1F72EE68) prefab = strangebox Light = Light (1F72EE88) children = table: 1F8F0358 OnSave = function - ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:590 Network = Network (1F72EF68) persists = true OnLoad = function - ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:603 MiniMapEntity = MiniMapEntity (1F72EEE8) pendingtasks = table: 1F8D5818 SoundEmitter = SoundEmitter (1F72EFE8) vault = table: 1F8D67E0 _fadeval = net_float (1F8E4880) name = Ornate Chest last_prox_sfx_time = 0.43333335593343 replica = table: 1F8D52C8 spawntime = 0 components = table: 1F8D5200 event_listeners = table: 1F8D59D0 references = table: 07CD9E00 data = table: 07CD99F0 scripts/entityscript.lua:288 in (method) GetSaveRecord (Lua) <243-293> self (valid:true) = GUID = 107566 Transform = Transform (1F72EFC8) inlimbo = false actionreplica = table: 1F8D53E0 _faderate = net_smallbyte (1F8E4618) actioncomponents = table: 1F8D5228 event_listening = table: 1F8D5FC0 lower_components_shadow = table: 1F8D52A0 loot = table: 07257A60 lootaggro = table: 07257AD8 entity = Entity (1F855120) AnimState = AnimState (1F72EE68) prefab = strangebox Light = Light (1F72EE88) children = table: 1F8F0358 OnSave = function - ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:590 Network = Network (1F72EF68) persists = true OnLoad = function - ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:603 MiniMapEntity = MiniMapEntity (1F72EEE8) pendingtasks = table: 1F8D5818 SoundEmitter = SoundEmitter (1F72EFE8) vault = table: 1F8D67E0 _fadeval = net_float (1F8E4880) name = Ornate Chest last_prox_sfx_time = 0.43333335593343 replica = table: 1F8D52C8 spawntime = 0 components = table: 1F8D5200 event_listeners = table: 1F8D59D0 record = table: 07CD93B0 references = nil scripts/mainfunctions.lua:678 in (global) SaveGame (Lua) <658-786> isshutdown = true cb = function - scripts/mainfunctions.lua:1060 save = table: 06C7A708 nument = 1319 saved_ents = table: 06C7A398 references = table: 06C7A550 k = 107566 v = 107566 - strangebox (valid:true) x = -113.06099700928 y = 0 z = 203.61099243164 scripts/saveindex.lua:343 in (method) SaveCurrent (Lua) <332-344> self = data = table: 09BE8FF0 current_slot = 1 onsavedcb = function - scripts/mainfunctions.lua:1060 isshutdown = true slotdata = table: 09BE8ED8 scripts/mainfunctions.lua:1591 in () ? (Lua) <1582-1593> [00:01:56]: [string "../mods/Pandora - prealpha/scripts/prefabs/..."]:598: bad argument #2 to 'insert' (number expected, got table) LUA ERROR stack traceback: =[C]:-1 in (field) insert (C) <-1--1> ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:598 in (field) OnSave (Lua) <590-601> scripts/entityscript.lua:1521 in (method) GetPersistData (Lua) <1489-1538> scripts/entityscript.lua:288 in (method) GetSaveRecord (Lua) <243-293> scripts/mainfunctions.lua:678 in (global) SaveGame (Lua) <658-786> scripts/saveindex.lua:343 in (method) SaveCurrent (Lua) <332-344> scripts/mainfunctions.lua:1591 in () ? (Lua) <1582-1593> strangebox.lua Pandora.zip Edited March 8, 2019 by ShinyMoogle Added attachment Link to comment Share on other sites More sharing options...
YakumoYukari Posted March 7, 2019 Share Posted March 7, 2019 The reason it crashes is the value of the table you're trying to insert is not a proper argument of the method table.insert. ../mods/Pandora - prealpha/scripts/prefabs/strangebox.lua:598 in (field) OnSave (Lua) <590-601> inst = 107566 - strangebox (valid:true) data = table: 07CD99F0 k = 1 v = 113492 - cutreeds (valid:true) We're looking at the last line, v = 113492 - cutreeds (valid:true) The compiler(interpreter) considered "113492 - cutreeds" as a number((meta)table actually) value because it starts with numeric. Quote [string "../mods/Pandora - prealpha/scripts/prefabs/..."]:598: bad argument #2 to 'insert' (number expected, got table) #2 argument of 'insert' is [, pos] which must be number. So the error happens. However, to solve the problem, I need to know how inst.vault is filled. Full-script of your mod or Github reference would help. Link to comment Share on other sites More sharing options...
ShinyMoogle Posted March 7, 2019 Author Share Posted March 7, 2019 (edited) Sure - attached full script to original post. That explains the "number expected" error - I was wondering about that. inst.vault is filled in the container's OnClose function. It goes through each item slot in the container and, if the slot has an item that isn't tagged as "irreplaceable" and passes an RNG check, removes the item from the chest and uses table.insert to shunt it into inst.vault instead. Items in inst.vault then have a chance to be filled back into empty slots using table.remove in the OnOpen function. Edited March 7, 2019 by ShinyMoogle Link to comment Share on other sites More sharing options...
YakumoYukari Posted March 7, 2019 Share Posted March 7, 2019 I think I should test this with your full mod. Could you send me a full mod if you can? Link to comment Share on other sites More sharing options...
ShinyMoogle Posted March 7, 2019 Author Share Posted March 7, 2019 (edited) Sure, thanks for looking this over. Attached full mod directory to first post. EDIT: May actually have gotten something working. Testing now. Alright, going to call this tentatively solved. I was able to work around the "number expected" error by writing to the save data table directly instead of using table.insert. local function onsave(inst, data) if inst.components.burnable ~= nil and inst.components.burnable:IsBurning() or inst:HasTag("burnt") then data.burnt = true end data.vault = {} for k,v in pairs(inst.vault) do if v:IsValid() and v.persists then --only save the valid items data.vault[k] = v:GetSaveRecord() end end end local function onload(inst, data) if data ~= nil and data.burnt and inst.components.burnable ~= nil then inst.components.burnable.onburnt(inst) end if data and data.vault then for k,v in pairs(data.vault) do local item = SpawnSaveRecord(v) if item then if item:IsValid() then print ("Valid item.") if item.components.perishable then item.components.perishable:StopPerishing() end table.insert(inst.vault, item) else print ("Item is invalid! Not loaded into chest vault. ") end print (item) end end end end One unintended effect that did turn up though is that Ash will consistently pop an error saying that it's not valid. I had to code in a manual workaround to check if the item is still valid when re-filling chest slots. I suspect it's because ash will disappear if not in inventory or container, and when archived, an item is neither. This will have to do for now unless I figure out a workaround. EDIT EDIT: Looking at print logs, seems ash is valid on game load, but will become not valid at some point, presumably due to time passing. Currently untested, but I'm checking for the disappears component and calling Disappears:StopDisappear() when filling the vault from save data. I think that should do it. strangebox.lua Edited March 8, 2019 by ShinyMoogle Link to comment Share on other sites More sharing options...
YakumoYukari Posted March 8, 2019 Share Posted March 8, 2019 (edited) I think it is not efficient to solve this in problem-specific. Figuring out the exact cause is difficult to find because we wouldn't know how entities are being valid or not. function EntityScript:IsValid() return self.entity:IsValid() end IsValid() is the direct callback from the engine side of the game which is not able to access. Fortunately, I've found a clue what could cause this issue. table.insert (inst.vault, inv:RemoveItemBySlot(i)) Line in onclose(), RemoveItemBySlot() returns item in container while removing itself from the container. item.components.inventoryitem:OnRemoved() However, this line calls; OnRemoved is used to being pulled out from the inventory(container). Which means, function InventoryItem:OnRemoved() if self.owner then self.owner:RemoveChild(self.inst) end self:ClearOwner() self.inst:ReturnToScene() self:WakeLivingItem() end The item will "wake up", leaving its LIMBO state. LIMBO is... I don't know.. I can't match the spelling because I'm not a native in English. But is the state when the object is being put inside the inventory. Normally, if the item is going to the other inventory, it will then be back to LIMBO state again. But the written code didn't. I checked that item in the vault is out of LIMBO state and I assume the ash issue is derived from here. Also, I'm not sure you're overlooked about it or just intended but it is permanently losing items' reference. In OnSave in Container.lua, data.items[k], refs = v:GetSaveRecord() if refs then for k,v in pairs(refs) do table.insert(references, v) end end It clearly saves outer references so I think you should follow this code. Although I don't see examples that leave ref except for the character entity. Edited March 8, 2019 by YakumoYukari Link to comment Share on other sites More sharing options...
ShinyMoogle Posted March 9, 2019 Author Share Posted March 9, 2019 Good catch on the wake item/return to scene part. I didn't think to look there in InventoryItem. I do want the item to be removed from the accessible container, so I suspect it'll involve calling *.components.inventoryitem:RemoveFromScene() somewhere. As for the references, that was intended in the sense that I had absolutely no idea what the game does with references and how they're called again... so I just didn't use them. Clearly not the best coding practice. More specifically, OnSave/OnLoad seems to be called differently between components and prefabs, and none of the prefab files actually return anything in their OnSave functions, so I figured I'd do likewise. Still, that's probably not a good idea and might cause some unforeseen issues. I'll say it seemed to work fine from cursory testing. Anyway really I was just trying to avoid needing to write up the extra code for a component, since I think that would be best if I were to follow the Container.lua code. But.. I guess I should. So I did. It's not the most efficient code right now (there are a few redundant calls to count table items), but it seems to be working as intended, and I added functionality to dump all hidden stash items when the chest is worked/destroyed so items aren't lost. So for the time being, I think this will do. archive.lua strangebox.lua Link to comment Share on other sites More sharing options...
YakumoYukari Posted March 9, 2019 Share Posted March 9, 2019 (edited) Each OnSave/OnLoad is called in the same function. Saves/Loads entity's components first and then the prefab's. You can see this in the method GetPersistData() and SetPersistData() Also, the entity's OnPreload part is loaded before any other components' data to be loaded. function EntityScript:SetPersistData(data, newents) if self.OnPreLoad ~= nil then self:OnPreLoad(data, newents) end if data ~= nil then for k, v in pairs(data) do local cmp = self.components[k] if cmp ~= nil and cmp.OnLoad ~= nil then cmp:OnLoad(v, newents) end end end if self.OnLoad ~= nil then self:OnLoad(data, newents) end end Well anyways. Hope no more errors to appear. Edited March 9, 2019 by YakumoYukari Link to comment Share on other sites More sharing options...
ShinyMoogle Posted March 9, 2019 Author Share Posted March 9, 2019 Oh. Ohhh, I see. I was having some difficulty following the entityscript logic but I'm kinda starting to see how it comes together. I guess I didn't really need to make a component for the one thing. ... Well, that's done so I'm happy if it's working. Thanks again! Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now