silktie Posted March 8, 2015 Share Posted March 8, 2015 I've found a curious quirk in the gameplay logic and am wondering the best way to work around it I've got a few variables floating around modmain.lua, which operate and are called just fine when playing. Let's say like this:example = Class(function(self, inst) self.powerLevel = 9000 self.cake = falseend)As the game progresses, these get updated (which is important) However, the challenge is that I'd like these variables to be carried over when a player enters a cave (as, for all intents and purposes, it's continuing the same game) The problem I've found though is that caves are treated as a new game in itself (it loads/saves a completely different save file) - As such, all of my variables get reset when I enter one Is there a way I can carry them over? My only guess is that I have to attach them to the player (whose data gets transferred between the 2) - If so, how do I go about this? Both to save the variables and retrieve them later on Link to comment Share on other sites More sharing options...
Mobbstar Posted March 8, 2015 Share Posted March 8, 2015 In my opinion, the best method would probably be the one you stated: to save them in a custom component, and attach that to the player. Link to comment Share on other sites More sharing options...
Corrosive Posted March 9, 2015 Share Posted March 9, 2015 self.powerLevel = 9000 Careful now, this is getting dangerously close. Link to comment Share on other sites More sharing options...
silktie Posted March 9, 2015 Author Share Posted March 9, 2015 Good to know. Thank you Mobbstar Is there a particular convention or isself.data.slots[self.current_slot].modes.survival.playerdata.NEW_VARIABLE = example... pretty much the way to do it? (refactoring 'self' to an external call of 'SaveIndex' of course) Or are we looking at the AddPlayerPostInit() method? Link to comment Share on other sites More sharing options...
Corrosive Posted March 9, 2015 Share Posted March 9, 2015 Note that this takes a bit more work if you wish the variables to NOT persist over sessions, and only persist when traveling between islands. Create a custom component, e.g. "moddata". scripts/components/moddata.lua:local ModData = Class(function(self, inst) self.inst = inst self.data = {}end)function ModData:SetData(data) self.data = dataendfunction ModData:GetData() return self.dataendfunction ModData:OnSave() return { data = self.data }endfunction ModData:OnLoad(saved) self.data = saved.data or {}end return ModDataNote-- getters/setters aren't "mandatory", and naturally you can use more than just a "data" property. modmain.lua:AddPlayerPostInit(function(inst) inst:AddComponent("moddata") end)(or add the component to your existing AddPlayerPostInit) Link to comment Share on other sites More sharing options...
silktie Posted March 11, 2015 Author Share Posted March 11, 2015 Wonderful - Thank you Corrosive Luckily I have the onSave and onLoad functions already in place. So it's just sticking it onto the player I already have an AddPlayerPostInit function, however. Adding a previously-defined function:AddPlayerPostInit(function1)How would I go about adding a second function to this? I know LUA has the capacity to support lists for args, so would something like:AddPlayerPostInit(function1, function(inst) inst:AddComponent("exmaple") end)... work? Can AddPlayerPostInit support multiple calls, or will the later ones overwrite the first? I considered trying to amend the logic of the first function... but it doesn't include inst as an arg, so I'm wary of inadvertently interfering with its logic (plus it'd really be bad coding to shoehorn unrelated stuff within another function) Link to comment Share on other sites More sharing options...
Corrosive Posted March 11, 2015 Share Posted March 11, 2015 Take a look at the AddPrefabPostInitAny definition, which is what AddPlayerPostInit uses (it just filters out any non-player prefab before passing the prefab to AddPrefabPostInitAny): env.AddPrefabPostInitAny = function(fn) initprint("AddPrefabPostInitAny") table.insert(env.postinitfns.PrefabPostInitAny, fn) endAs you can see, it expects exactly one argument, and for that argument to be a function. Any additional argument would just get ignored. You technically could just call AddPlayerPostInit twice, but there's no real reason to. Calling it once allows you to "hook" into the player prefab, so you can do anything you wish to do with it from that one function. You can write your code a number of ways, depending on how you want to organize it. For example:local function function1 ...your code... inst:AddComponent("example")endAddPlayerPostInit(function1)or if you really wanted to segregate the code:local function function1(inst)...your code...endlocal function function2(inst) inst:AddComponent("exmaple")endlocal function playerpostinitfn(inst) function1(inst) function2(inst)endor a more compact version:local function function1(inst) ...your code...endlocal function function2(inst) inst:AddComponent("exmaple")endAddPlayerPostInit(function(inst) function1(inst) function2(inst) end) Link to comment Share on other sites More sharing options...
Corrosive Posted March 11, 2015 Share Posted March 11, 2015 @silktie, Just noticed your edit-- what does your function1 look like? Link to comment Share on other sites More sharing options...
silktie Posted March 11, 2015 Author Share Posted March 11, 2015 Sure Corrosive: The code's been leveraged off the 'Compromising Survival' mod (the event listener on death and a question pop-up was exactly what I needed)local function tweak_death_eventlistening(player) player:ListenForEvent("death", handle_death) player.ListenForEvent = (function() local ListenForEvent_base = player.ListenForEvent return function(self, ev, fn, source) if ev == "death" and (not source or source == self) then return Outcomes.AddDyingCallback(fn) else return ListenForEvent_base(self, ev, fn, source) end end end)()endWhich of course leads to:AddPlayerPostInit(tweak_death_eventlistening)It's just dawned on me though... My initial thought is that inst was missing as an arg in tweak_death_eventlistening (so I couldn't easily add it in). But thinking about it... considering how the method is used (and that it's being added to the player) - inst and player are the same thing - It's just the author of this one chose 'player' as a more meaningful name It just clicked it doesn't matter what variable name you pass in is, the arg name can be whatever it damn-well likes After all, though completely meaningless in terms of var names, so long as all of the variables being thrown around below are, say, strings - this would worklocal function testA(iceCreamVan) print(iceCreamVan)endlocal function testB(chinchilla, aubergine, those_annoying_fragments_of_paper_after_cutting) testA(chinchilla) testA(aubergine) testA(those_annoying_fragments_of_paper_after_cutting)endSo really, as you alluded to. I just augment the method with Player:AddComponent("example")? So last point for it: once it's hooked onto the player, what's the best way of getting it back? I'm guessing we start with GLOBAL.getPlayer() and it's located on that somewhere? Link to comment Share on other sites More sharing options...
Corrosive Posted March 12, 2015 Share Posted March 12, 2015 inst and player are the same thing That's why I asked, I thought that might have been the case And yeah, GLOBAL.GetPlayer() will always return the player. The player prefab is a singleton-- that is-- there is only ever one. TheSim and TheInput are other examples of singletons(I imagine that's why they used the prefix "The" for them. In DST you can use ThePlayer instead of GetPlayer(), but not in DS. Though in your modmain, you can always do local ThePlayer = GetPlayer() to emulate that. located on that somewhere? Not on that, it's effectively the same thing. You can verify from your PlayerPostInit function: print("inst = GetPlayer()? " .. inst == GLOBAL.GetPlayer()) Link to comment Share on other sites More sharing options...
silktie Posted March 12, 2015 Author Share Posted March 12, 2015 Not on that, it's effectively the same thing My apologies - I mean getting the variable itself I save to the player Getting the player is easy enough, it's just figuring out where on the player the variable will be saved (the one downside with LUA's ability to just stick on variables - hard to know where it's saving them unless you can track down the setter methods) Based upon a scout of the code... I'm guessing something like:GLOBAL.GetPlayer().components["example"] Link to comment Share on other sites More sharing options...
Blueberrys Posted March 12, 2015 Share Posted March 12, 2015 @silktie That is correct. Components are added to the components table in the player's instance (which is also a table).player_inst = GLOBAL.GetPlayer()-- You can also write it like this:player_inst.components.example- The AddComponent function is located in entityscript.luafunction EntityScript:AddComponent(name) if self.components[name] then print("component "..name.." already exists!") end local cmp = LoadComponent(name) assert(cmp, "component ".. name .. " does not exist!") local loadedcmp = cmp(self) self.components[name] = loadedcmp local postinitfns = ModManager:GetPostInitFns("ComponentPostInit", name) for k,fn in ipairs(postinitfns) do fn(loadedcmp,self) endend Link to comment Share on other sites More sharing options...
silktie Posted March 12, 2015 Author Share Posted March 12, 2015 Aaaah yes, EntityScript! Glad to know that is indeed the setter (I'm always wary of how thorough a findstr search is) @Blueberrys, @Corrosive - You are both legends. Thank you ever so much so your advice Link to comment Share on other sites More sharing options...
Mobbstar Posted April 7, 2015 Share Posted April 7, 2015 I'm sorry to more or less necro this topic, but I am having a save/load issue, and thought it'd be fitting to post here than in a new topic. I am trying to save (and load, obviously) some arbitrary data, together with entity references (based on objectspawner component). However, the method explained here doesn't work for me, the LoadPostPass doesn't fire. Here's the component in question, if you want to take a look:--This component is attached to the player and tracks all entities that have effects with duration local Cmp = Class(function(self, inst) self.anchor = inst --player self.affected = {} --affected[guid_of_inst][number] = Effect Classend)function Cmp:Track(inst,eff) --adds an effect to the list print(VERBOSITY.DEBUG,"TRACKING",eff,inst) if not self.affected[inst.GUID] then self.affected[inst.GUID] = {} end table.insert(self.affected[inst.GUID],eff)endfunction Cmp:OnSave(data) if DumpEffects then return end local data = {} local references = {} for guid,fx in pairs(self.affected) do print(VERBOSITY.DEBUG,"SAVING FOR",Ents[guid]) data[guid] = {} for _,eff in pairs(fx) do print(VERBOSITY.DEBUG,"SAVING EFFECT",eff.name) table.insert(data[guid],eff:GetSaveData()) end if guid ~= self.anchor.GUID then --saving when the player had an effect crashed the game, idk about other entities print(VERBOSITY.DEBUG,"REFERENCING",guid) table.insert(references,guid) end end if #references == 0 then references = nil end if #data == 0 then data = nil end print(VERBOSITY.DEBUG,"DONE WRITING SAVE DATA, RETURNING") return data, referencesendfunction Cmp:LoadPostPass(newents,data) if data and not DumpEffects then for guid,fx in pairs(data) do if newents[guid] then print(VERBOSITY.DEBUG,"LOADING DATA FOR",newents[guid]) self.affected[guid] = {} for _,v in pairs(fx) do eff = POTIONEFFECTBASIC():UseData(v) table.insert(self.affected[guid],eff) eff:drinkfn(newents[guid].entity) end else Print(VERBOSITY.WARNING,"FAILED TO FIND SAVED ENTITY",guid,"EFFECT #1:",fx[1].name) end end endendI've considered using one of the two following methods instead:This method (already used by debugman's "feats" mod), and then test for the save slot Make a persistent dummy prefab with the save and load functions, and spawn it only once per world Am I doing something wrong with the PostInit method? Is there any reason I should use the former alternative rather than the latter? If I get no replies, I'll just make a dummy. I like me some dummies. Link to comment Share on other sites More sharing options...
Blueberrys Posted April 7, 2015 Share Posted April 7, 2015 @Mobbstar Have you seen/tried this?Nevermind, ignore that! Edit: No wait, don't ignore that. Why exactly is that crossed out? Did it not work? Edit 2: If you were referring to that as the former alternative (as opposed to the OnSave things mentioned previously in the thread) then.. I can't say how it will be affected performance wise, but it just seems to make more sense than having an empty prefab that doesn't do anything else in the game.If you don't want to make a new file for your data, you can even use one of the existing files. Just be sure to check which keys are empty. Link to comment Share on other sites More sharing options...
Mobbstar Posted April 7, 2015 Share Posted April 7, 2015 @Mobbstar Have you seen/tried this?Nevermind, ignore that! Edit: No wait, don't ignore that. Why exactly is that crossed out? Did it not work? Edit 2: If you were referring to that as the former alternative (as opposed to the OnSave things mentioned previously in the thread) then.. I can't say how it will be affected performance wise, but it just seems to make more sense than having an empty prefab that doesn't do anything else in the game.If you don't want to make a new file for your data, you can even use one of the existing files. Just be sure to check which keys are empty. I've tried the dummy prefab, and the LoadPostPass works now (I think), but since saving crashes the game, it's pointless anyways. See for yourself. Link to comment Share on other sites More sharing options...
Blueberrys Posted April 7, 2015 Share Posted April 7, 2015 @Mobbstar I just noticed that your link is doing it the old way. Have you tried the new one? (which is also currently used in feats) Edit: I've updated the API doc to match the new information as well. Link to comment Share on other sites More sharing options...
Corrosive Posted April 8, 2015 Share Posted April 8, 2015 disregard, I need to test further Yeah, you're going to have some problems with saving the drinkfns on the potions like that, I think. Since by the time they get stored as values they're compiled into bytecode, if you try to retrieve them in a new execution context, they aren't going to be able to find upvalues or other stale references. You might need to rebuild each drinkfn on load using saved data. Link to comment Share on other sites More sharing options...
Blueberrys Posted April 8, 2015 Share Posted April 8, 2015 @Corrosive Thank you for pointing that out. It persuaded me to research more on how upvalues work.if anyone else is interested, here's a long explanation which I found quite helpful. As for SpawnPrefab, wouldn't the reference to the upvalue be stored within the function's own scope? What do you mean by "longer than the original context of SpawnPrefab", is it being changed or removed anywhere? Edit: Just saw your edit. You probably should've left the original post too so people know what you're referring to, haha. Link to comment Share on other sites More sharing options...
Corrosive Posted April 8, 2015 Share Posted April 8, 2015 @Blueberrys, <.< ... >.> I may or may not have copied the original post to a notepad then closed the notepad thinking I wouldn't need to repost it. Anyhoo, yeah the reference to the upvalue (or other referenced value) is stored within the scope, but once the actual data the reference to the upvalue is referencing goes away, that reference becomes stale. I'm not sure what kind of error Lua would trigger, but I'm near positive that it should trigger one. It's like retaining an address book between tearing down and rebuilding a city. You still have the names and addresses, but chances are the same people aren't living at those addresses. Then again I may be wrong about how the game serializes the data-- I'll have to take a closer look and/or do some testing. Link to comment Share on other sites More sharing options...
Blueberrys Posted April 8, 2015 Share Posted April 8, 2015 @Corrosive Hmm. But I don't see any reason for SpawnPrefab's original data to be discarded at any time while the game world is still running. I think it should be fine as long as the code doesn't execute outside of the game world. Link to comment Share on other sites More sharing options...
Corrosive Posted April 8, 2015 Share Posted April 8, 2015 @Corrosive Hmm. But I don't see any reason for SpawnPrefab's original data to be discarded at any time while the game world is still running. I think it should be fine as long as the code doesn't execute outside of the game world.IIRC The OnSave/Load methods on potions were storing/retrieving fx objects (that contain fx.drinkfn or something to that effect. I think what would be preferable is just to have a registry containing each type of potion effect, so that potions could store a table containing the list of effects they confer & any variables needed to pass to that effect's function (like a slightly more advanced version of the ACTIONS registry). (Writing this on my phone atm but I'll check to make sure when I get back to my comp.) Link to comment Share on other sites More sharing options...
Mobbstar Posted April 8, 2015 Share Posted April 8, 2015 Hmm. But I don't see any reason for SpawnPrefab's original data to be discarded at any time while the game world is still running. I think it should be fine as long as the code doesn't execute outside of the game world. Dumb question, but is this all about my mod or another issue? nvm it is. I've thought about what you stated, and it's probably a lot more stable and efficient to store all of the ongoing effects in Profile, if only I would build up the courage and free time to mess with those systems. Maybe tomorrow And yes, @Corrosive, I've considered to use string.dump on the functions for saving, but for some reason it seems to work without. That's how I roll: When it works, it's fine. Also, on a scale from 1 to 10, how aching is the fact that I construct a new class for every effect on every instance? (stacks act as one instance, so it's suprisingly tame so far) EDIT: Why the heck did I do that?! There's only the data and three functions on the standart Effect class, all three of which could also be declared in the potion, or even better: modmain (aka once). Gosh I'll need to rewrite half of the code I think. Link to comment Share on other sites More sharing options...
Blueberrys Posted April 8, 2015 Share Posted April 8, 2015 Dumb question, but is this all about my mod or another issue? It's about your mod, but not the issue you mentioned. Corrosive found something else in your mod and we started talking about that, but he edited the original post now. I've thought about what you stated, and it's probably a lot more stable and efficient to store all of the ongoing effects in Profile, if only I would build up the courage and free time to mess with those systems. Maybe tomorrowEr, again, have you tried using that module? xD It's fairly straight forward, should only take you about 5 minutes to figure it out. Or are you talking about the second issue, too? Also, on a scale from 1 to 10, how aching is the fact that I construct a new class for every effect on every instance? (stacks act as one instance, so it's suprisingly tame so far)That doesn't sound right.. I think I'll have a look at your code. >->If 1 is "unconcerned" and 10 is "Noo whyyyyyyyy" then probably 9. Link to comment Share on other sites More sharing options...
Corrosive Posted April 8, 2015 Share Posted April 8, 2015 @Mobbstar, I considered string.dump too, but although I had never had to use it for anything before, I did notice while screwing around that it output an unreadable string, so I assumed that this was also binary bytecode. After checking with the documentation(just now), it says:Returns a string containing a binary representation of the given function, so that a later loadstring on this string returns a copy of the function. function must be a Lua function without upvalues. Them pesky upvalues @Mobbstar, @Blueberrys,I had a little bit of time to test just now, and I'm next to positive that what I described is happening. You should be able to easily recreate this test I did: Here's the pertinent section of modmain.lua for reference: POTIONEFFECTS.Light = Class(POTIONEFFECTBASIC,function(self, str, val, altfx, anti) POTIONEFFECTBASIC._ctor(self,str,"light",altfx) if val == 0 then Print(VERBOSITY.WARNING,"WARNING, used Light effect with value 0. That can confuse other effects!") end self.args = {val = val} function self:drinkfn(doer) if doer then if not doer.wormlight then local light = _G.SpawnPrefab("wormlight_light") light.components.spell:SetTarget(doer) if not light.components.spell.target then light:Remove() end light.components.spell:StartSpell() end doer.wormlight.components.spell.duration = val doer.wormlight.components.spell:ResumeSpell() end end self.solutions = antiend)Part 1:Create two potions (specifically, a wormlight and an empty alchemypotion). Drink 1 to verify that it works. Save and quit game. Reload the save. Now when you try to drink the remaining potion, you'll get this error:Here's that line:local light = SpawnPrefab("wormlight_light")(SpawnPrefab is imported as a local in modmain's context at the top of the file, outside of the drinkfn) Part 2: Edit line 514 of modmain.lua to be the following:local light = _G.SpawnPrefab("wormlight_light") Reload the save. Discard the potion from Part 1. Since its stale references persist, it'll always cause an error. Create a new potion using a wormlight and an alchemypotion. Save and quit game. Reload the save. Try drinking potion. You'll get this new error: Well that got us past the SpawnPrefab hurdle, but unfortunately it looks like there's another stale reference (or some other error) once it it gets to line 522 and calls ResumeSpell(). I haven't tested this, though it would be pretty easy to... but my guess is that it's caused by the preceding statement at 521: doer.wormlight.components.spell.duration = val doer.wormlight.components.spell:ResumeSpell()val is another upvalue, this time coming from the class constructor. I imagine(but haven't tested) that since the upvalue is on the right side of an assignment operator, nil is getting assigned to duration. Later when I have some time I'll check if the error still occurs when assigning a numeric literal to duration. Part 3: (just to confirm that the functions really are getting stored as compiled bytecode) Modify the code of the drinkfn so that it's guaranteed to raise an error before any other problems arise. Ex: function self:drinkfn(doer) assert(false, "YOU DONE MESSED UP NOW") if doer then if not doer.wormlight then...Load game. Brew one last wormlight potion. Save and quit. Before loading the saved game, modify the intentionally erroring code to something innocuous. Blank spaces work just fine: function self:drinkfn(doer) if doer then if not doer.wormlight then...Load saved game. Drink potion. Error as expected: Edit: Also, on a scale from 1 to 10, how aching is the fact that I construct a new class for every effect on every instance? If it was a strongly typed language, 9.2. Since it's Lua... ehhhhh... I'm gonna go with 7.5. It's still a very Rube-Goldbergian way of going about it, lol. Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.
Please be aware that the content of this thread may be outdated and no longer applicable.