Jump to content

Recommended Posts

Good catch! This is related to other things I'm working on right now so I'll have a look at it right away.

Now that you are fixing package.path and thus resolvefilepath, please switch to using resolvefilepath for the following codepieces:characterselectscreen.lua line 210
self.heroportait:SetTexture("data/bigportraits/"..character..".tex")
loadgamescreen.lua line 106
widget.portrait:SetTexture("data/images/saveslot_portraits/"..character..".tex")
newgamescreen.lua line 102
self.portrait:SetTexture("data/images/saveslot_portraits/"..self.character..".tex")
slotdetailscreen.lua line 56
self.portrait:SetTexture("data/images/saveslot_portraits/"..character..".tex")
These are all textures that we want to replace when implementing new custom characters. As it stands now, we must provide our own overwrites to the files in order for the game to load our textures (or place the new textures in install dir = bad). Changing these to use resolvefilepath would increase the ease by which we place new characters in /mod/ folder by reducing dependency on script overwrite files.
  • Developer

These are all textures that we want to replace when implementing new custom characters. As it stands now, we must provide our own overwrites to the files in order for the game to load our textures (or place the new textures in install dir = bad). Changing these to use resolvefilepath would increase the ease by which we place new characters in /mod/ folder by reducing dependency on script overwrite files.

Cool, thanks for digging into this! I have no problem making simple changes like this to make everyone's lives easier.

Anyone:

Is there a way to re-define an actions.lua function in your modmain.lua? What I'm trying to do is replace a few lines in the existing FERTILIZE function.

I want to replace local obj = act.doer.components.inventory:RemoveItem(act.invobject) with

local obj = act.invobject

in all three places in the FERTILIZE function.

ACTIONS.FERTILIZE.fn = function(act)    if act.target.components.crop and not act.target.components.crop:IsReadyForHarvest() and act.invobject and act.invobject.components.fertilizer then		local obj = act.doer.components.inventory:RemoveItem(act.invobject)        if act.target.components.crop:Fertilize(obj) then			return true		else			act.doer.components.inventory:GiveItem(obj)			return false		end    elseif act.target.components.grower and act.target.components.grower:IsEmpty() and act.invobject and act.invobject.components.fertilizer then		local obj = act.doer.components.inventory:RemoveItem(act.invobject)        act.target.components.grower:Fertilize(obj)	elseif act.target.components.pickable and act.target.components.pickable:CanBeFertilized() and act.invobject and act.invobject.components.fertilizer then		local obj = act.doer.components.inventory:RemoveItem(act.invobject)        act.target.components.pickable:Fertilize(obj)		return true			endend

This is without testing or checking, buuttttttttt... couldn't you just rewrite the function in your modmain like so:

-- WARNING: You are rewriting the function. If the core code changes,-- you're out of luck. Unfortunately, it's very difficult to change-- local variables defined in a function, so this is (likely) your-- best bet if you must change the variables.GLOBAL.ACTIONS.FERTILIZE.fn = function(act)    if act.target.components.crop and not act.target.components.crop:IsReadyForHarvest() and act.invobject and act.invobject.components.fertilizer then		-- I usually leave code I comment out for a checkin or two until        -- I'm really sure I don't need it.        --local obj = act.doer.components.inventory:RemoveItem(act.invobject)        -- And we make our change        local obj = act.invobject        if act.target.components.crop:Fertilize(obj) then			return true		else			act.doer.components.inventory:GiveItem(obj)			return false		end    elseif act.target.components.grower and act.target.components.grower:IsEmpty() and act.invobject and act.invobject.components.fertilizer then		--local obj = act.doer.components.inventory:RemoveItem(act.invobject)        -- And we make our change        local obj = act.invobject        act.target.components.grower:Fertilize(obj)	elseif act.target.components.pickable and act.target.components.pickable:CanBeFertilized() and act.invobject and act.invobject.components.fertilizer then		--local obj = act.doer.components.inventory:RemoveItem(act.invobject)        -- And we make our change        local obj = act.invobject        act.target.components.pickable:Fertilize(obj)		return true			endend
I'm someone who really doesn't why things are being done, unless I'm educating myself, so I'll just leave this here for you to try out. Good luck, you can use my name in vane if I mislead you :)EDIT: I do a lot of stuff like this with the GLOBAL.RECIPES object so as not to fubar the existing sorting, and also not modify the recipes.lua file.

This is without testing or checking, buuttttttttt... couldn't you just rewrite the function in your modmain like so:I'm someone who really doesn't why things are being done, unless I'm educating myself, so I'll just leave this here for you to try out. Good luck, you can use my name in vane if I mislead you :)EDIT: I do a lot of stuff like this with the GLOBAL.RECIPES object so as not to fubar the existing sorting, and also not modify the recipes.lua file.

Hey, worked like a charm. So simple in retrospect... Thanks!

This is without testing or checking, buuttttttttt... couldn't you just rewrite the function in your modmain like so:I'm someone who really doesn't why things are being done, unless I'm educating myself, so I'll just leave this here for you to try out. Good luck, you can use my name in vane if I mislead you :)EDIT: I do a lot of stuff like this with the GLOBAL.RECIPES object so as not to fubar the existing sorting, and also not modify the recipes.lua file.

Hey, worked like a charm. So simple in retrospect... Thanks!

I would love to know where to look to find the draw distance? I have searched all the camera scripts but I'm assuming it's coded somewhere else? I didn't see anything in the tuning scripts either. Also, is it possible to set the camera to free movement as opposed to the incremented rotations?

@Ipsquiggle

Is there a way to find all the instances of a prefab on the map? I know there is the FindEntity func, however that takes a radius, also I believe it returns only one instance, I want all ents on the map of a certain prefab. If that is not possible, at least is there a way to iterate over all prefabs, this is to attach an event for the entities so I have a hook point for creation and I will manage them locally. Nevermind found it GLOBAL.Ents

And one other thing, could you add hooks before serializing and after deserializing the map? I already did this however main.lua and gamelogic.lua need to be changed and I don't want to provide my own overwrites for these. main.lua will not event load from the mod because that is where the mods are initialized, gamelogic perhaps (did not check).

-------------- main.lua ----------------

-- Handler for the save / load event, before data is serialized--        params save .. object that will be serialized, contains ents, playerinfo, mapinfoSaveGameBeforeSerialization = nilLoadGameAfterDeserialization = nil
In function SaveGame(savename, callback)

if SaveGameBeforeSerialization ~= nil then    SaveGameBeforeSerialization(save)end    local data = DataDumper(save, nil, BRANCH ~= "dev")local insz, outsz = TheSim:SetPersistentString(savename, data, ENCODE_SAVES, callback)print ("Saved", savename, outsz)
----------------- gamelogic.lua ----------------

local function DoLoadWorld(saveslot, playerdataoverride)    local function onload(savedata)        if LoadGameAfterDeserialization ~= nil then            LoadGameAfterDeserialization(savedata)        end        DoInitGame(SaveGameIndex:GetSlotCharacter(saveslot), savedata, Profile, playerdataoverride)    end    SaveGameIndex:GetSaveData(saveslot, SaveGameIndex:GetCurrentMode(saveslot), onload)end
This will allow us to store custom data in the savefile (not on player, not in the ents list, not in the map)

modmain.lua

GLOBAL.SaveGameBeforeSerialization = function(savedata)    savedata.customField = 10endGLOBAL.LoadGameAfterDeserialization = function(savedata)    print(savedata.customField)end
Regarding conflicts, well you can give a man a hammer and he can still hit his head, it is up to the modders to make sure the saved data does not conflict with something else. For example the fields playerinfo, map, ents are reserved, or, you could add a separate table in the save data and send that to the mod for saving (one single for all mods) so

save.mapsave.playerinfosave.entssave.modstorageSaveGameBeforeSerialization(save.modstorage)LoadGameAfterDeserialization(save.modstorage)
PS. In the way I implemented it, only the last mod that sets the function will get called, so it needs a different implementation (Add("modname", fn) ???) In this case you could add a different table for each mod to save their data (conflicts resolved)

PPS. The last solution will still give conflicts with the mod key, however that problem exists even now, so it can be disregarded, however that solution will not pose a problem of mods overwriting maps / playerinfo / ents

I believe WrathOf had this idea initially

Implementation

------ main.lua ------

---------- gamelogic.lua ------------

---------- modmain.lua ----------

This will add another field in the savedata

modstorage = { RPGSystem = { customField = 10 } }

local registeredSerializationMods = {}local registeredDeserializationMods = {}function RegisterForSerialization(modkey, fn)    registeredSerializationMods[modkey] = fnendfunction RegisterFoDeserialization(modkey, fn)    registeredDeserializationMods[modkey] = fnendfunction CallModSerialization(savedata)    savedata.modstorage = {}    local modstorage = savedata.modstorage    for modKey, fn in pairs(registeredSerializationMods) do        modstorage[modKey] = { }        fn(modstorage[modKey])    endendfunction CallModDeserialization(savedata)    if savedata.modstorage == nil then return end    local modstorage = savedata.modstorage    for modKey, fn in pairs(registeredDeserializationMods) do        if modstorage[modKey] ~= nil then            fn(modstorage[modKey])        end    endend...............................CallModSerialization(save)    local data = DataDumper(save, nil, BRANCH ~= "dev")local insz, outsz = TheSim:SetPersistentString(savename, data, ENCODE_SAVES, callback)print ("Saved", savename, outsz)
local function DoLoadWorld(saveslot, playerdataoverride)    local function onload(savedata)        CallModDeserialization(savedata)        DoInitGame(SaveGameIndex:GetSlotCharacter(saveslot), savedata, Profile, playerdataoverride)    end    SaveGameIndex:GetSaveData(saveslot, SaveGameIndex:GetCurrentMode(saveslot), onload)end
----- NO SPACE PERMITED IN THE KEYlocal rpgKey = "RPGSystem"GLOBAL.RegisterForSerialization(rpgKey, function(savedata)    savedata.customField = 10end)GLOBAL.RegisterFoDeserialization(rpgKey, function(savedata)    print(savedata.customField)end)

Edited by RazvanM

[MENTION=23863]RazvanM[/MENTION] instead of FindEntity there is FindEntities, returning a table. Although it is not used in the unmodded game, I know for a fact that it accepts a 5th passed variable, a string name of a tag, limiting the find. SoTheSim:FindEntities(x,y,z, range, "tag_goes_gohere" )as for how CPU intensive this function is, I can't say. Probably very.

Edited by Heavenfall

[MENTION=23863]RazvanM[/MENTION] instead of FindEntity there is FindEntities, returning a table. Although it is not used in the unmodded game, I know for a fact that it accepts a 5th variable, a string name of a tag, limiting the find. SoTheSim:FindEntities(x,y,z, range, tag_goes_gohere" )

This very resource heavy operation and can slow down your game.

@RazvanM instead of FindEntity there is FindEntities, returning a table. Although it is not used in the unmodded game, I know for a fact that it accepts a 5th variable, a string name of a tag, limiting the find. So

TheSim:FindEntities(x,y,z, range, tag_goes_gohere" )

Did not know about that one, it still requires a range, I want all entities on the map,

Example : You have a quest to destroy a spider den, I can't attach in the AddPrefabPostInit, because I don't know what will be required for a quest beforehand ( i know i'm creating them so I should know, that is not the point, I want to make so you modify in one place - quest definition ), I also don't know what spider den the player will destroy (the one beside him or the one on the other side of the map) , so I need to find all spider den's on map and attach to the remove event or some other event to indicate it was destroyed.

@_Q_ I still think that there is a solution for this, we just need the devs input, they do store the ents in the game somehow, so perhaps they have a method for retrieving all instances of a certain prefab.

May I suggest a different approach? It is no problem at all adding something to every single prefab of a certain type at sim start. Why not simply add a function to the desired prefab that checks if the player is on the quest, then sets the player's progress? Then you can easily set it to listen to the prefab's death if you want to. I did a very similar function just a week ago:modmain.lua

function pigman_listenfordeath(inst)	inst:ListenForEvent( "death", function(inst, data) 		if data.cause == "wend2" then			local child = GLOBAL.SpawnPrefab("friendly_ghost")			local pos = Point(inst.Transform:GetWorldPosition())			pos.x = pos.x -.3			pos.z = pos.z -.3			if child then				child.Transform:SetPosition(pos.x, pos.y, pos.z)			end		end	end)endAddPrefabPostInit("pigguard", pigman_listenfordeath)AddPrefabPostInit("merm", pigman_listenfordeath)AddPrefabPostInit("walrus", pigman_listenfordeath)AddPrefabPostInit("little_walrus", pigman_listenfordeath)AddPrefabPostInit("krampus", pigman_listenfordeath)AddPrefabPostInit("leif", pigman_listenfordeath)AddPrefabPostInit("leif_sparse", pigman_listenfordeath)AddPrefabPostInit("pigman", pigman_listenfordeath)
Edit: Maybe that's what you meant above, but I don't see what problem you ran into. The function can be as complex as you want it to be after all. Store the player quest progress in a component, then perform the checks in the prefab like above. Edited by Heavenfall

[MENTION=55]Ipsquiggle[/MENTION]I need to get random world coordinates, is there a way to find spawn point for something not dependent on player position in the world? Right now it gets player position and then spawn things at some distance from the player, If I use big distance from the player it can't find valid spawn point. I also need access to list of prefabs and their coordinates in the world, FindEntity function with big radius can slow down the game.I want to spawn some things like walruses camps at random locations during winter, in saved data I can see the names of rooms or tasks that are in the world is there a way to use that data from component?Next thing is I would like to despawn some chunk of terrain on map and then spawn different room or set piece in place of old terrain so world would be changing with time.Next thing is can you make set pieces independent from world settings, right now if I have berry bushes set piece (static layout) and I set berries to less I have like the half ring of berry bushes instead of full, you can find some other ways to disable static layout if the recourse that static layout is using is disabled or make versions of static layouts for every resource setting.

Edited by _Q_

@Heavenfall , I get that idea, I also thought of it, the problem is i have something like this for quest definitions

Quest("Spider extermination",     {         "{Quest desc placeholder}",     },     5, -- level    {  -- requirements        Kill("spider", 15)        Destroy("spiderden", 3)    },     { -- rewards        GiveAttr("dexterity", 30),         GiveExperience(100),         GiveRecipe("nightsword")     },     false -- multiple)
So Destroy "spiderden", I know that i need to track the prefab spiderden, however this means I need to modify the modmain.lua and add a PostPrefabInit handler for that prefab, I don't want that, I want in the Destroy class to take the ent given and search all ents of that type to add a handler. If I change my mind and want a pighouse again I need to go to modmain and change that

The other solution is to attach to all prefabs in modmain and keep that list local (like yours only for all prefabs). And I can't get all prefabs.

@_Q_ Regarding the spawn could you use the map? and use the Map:GetTile(x,y) method to get the type of the tile at a certain point, so you could just check that ? I belive GROUND.IMPASSIBLE is the one to look out for. You could expand on that ( Map:GetTiles(center, radius) )

Edited by RazvanM

Here's another thought, maybe [MENTION=55]Ipsquiggle[/MENTION] can confirm/deny. Findentity and findentities are probably just functions that access the global prefab list. I think it is unlikely that they are performing some kind of search on each square within distance, because that would require them to access the global prefab list anyway first to build the square information.My guess: findentity()1) loop through all currently existing prefabs2) if tag = valid3) if distance = valid4) return prefab tablefindentities probably uses the same method except it doesn't return on first found value, rather continues sorting throgh all and returns its finds.Just speculation, this is how I would build it anyway. But if this is the case, findentity/findentities already give us access to the global prefab list.Edit: On second thought, this is probably not how it works since increasing distance increases cpu drain (apparently) which wouldn't make sense if it was the above logic.

Edited by Heavenfall

[MENTION=12700]Heavenfall[/MENTION], [MENTION=5551]_Q_[/MENTION] Found itGLOBAL.Prefabs - all prefabsGLOBAL.Ents - all the entities on the map, everythingThose have meaning only after the Simulation has started.So for finding all entities of a certain prefab, for each GLOBAL.Ents if ent.prefab == {what you are looking}

[MENTION=55]Ipsquiggle[/MENTION] BTW, I made a mod to remap the keys for AZERTY keyboards and that's all it does and felt like sharing it. But it would be nice if we didn't need it at all. Do you know if there are plans for an update to recognize automatically the keyboard localization ? (didn't want to create a thread just for this so I posted it here)

Hey, do you think we'll ever be able to make our own build.bin files for our textures? Making characters would be a LOT easier if we had control over that.

I'm working on a set of tools to let ya do that. I can't promise any dates sadly since it all has to be done in my free time.

Did not know about that one, it still requires a range, I want all entities on the map,

Example : You have a quest to destroy a spider den, I can't attach in the AddPrefabPostInit, because I don't know what will be required for a quest beforehand ( i know i'm creating them so I should know, that is not the point, I want to make so you modify in one place - quest definition ), I also don't know what spider den the player will destroy (the one beside him or the one on the other side of the map) , so I need to find all spider den's on map and attach to the remove event or some other event to indicate it was destroyed.

@_Q_ I still think that there is a solution for this, we just need the devs input, they do store the ents in the game somehow, so perhaps they have a method for retrieving all instances of a certain prefab.

Hey RazvanM, what about this idea:

On prefab post init you add the prefab to a table in your component if it's not already there.

If you want to enumerate all Entities at some point, you can just do so and add them to your table.

IE: Open the console and try:

for i,v in pairs(Ents) do if string.find(tostring(v), "spiderden") then print(v) end end
That way, you have a table of spiderden entities, so when you search them it's faster than searching every single entity in the game world.

Take a look at how the divining rod does it:

    if inst.tracking_parts == nil then        inst.tracking_parts = {}        for k,v in pairs(Ents) do            if v:HasTag("teleportato") or v:HasTag("teleportato_part") or v.prefab == "adventure_portal" then                table.insert(inst.tracking_parts, v)            end        end    end
[edit] Erp. I just saw your post where you found Ents yourself. Oh well! Edited by zeidrich

I've tried to search/read/click through old posts, but I can't find what, if anything, pathname does in the prefab.xml file. Does it matter what string I put there? I've seen everything from:

pathname="forest/monsters/bee"
and
pathname='common/monsters/friend'
and
pathname='common/adventure_portal'
Is there any restriction on what goes in pathname?EDIT: Duh. I really should grep before asking questions. It seems to just be some sort of namespace or reference for use in Prefab def.I guess I could reword my question: is pathname arbitrary? Can I just put whatever I want in it? Edited by borrisb

It's pretty arbitrary I think, but I think it's best to use some caution when considering the structure to make sure you avoid collisions. The main use I see of the hierarchy is to do something like:

data\scripts\prefabs\evergreens.lua:501:    return Prefab("forest/objects/trees/"..name, makefn(build, stage, data), assets, prefabs)
In that case, it doesn't really matter what tree names get put in there. First of all, by the name you can intuit that its a tree, an object (as opposed to a monster or something) and be relatively certain that if anywhere else you define a prefab named "evergreen", unless it's also somehow put in forest/objects/trees/evergreen it will be unique. I think if you want to be safe, you might as well make your own sort of namespace, like common/monsters/mymod/cthulu and stick all of your stuff in mymod.[e] I guess safer still would just be mymod/monsters/cthulu

@zeidrich - I managed to do it, your solution meant that I need to know the prefab beforehand, and that is what I did not know (actually I did), I see from your tuts / posts that you have experience in programming, so you should be able to understand that I wanted to make changes in one place only. I managed to circumvent that problem eventually, and also found a way to filter all ents based on prefab faster

-------------------------------------------------------------------------- REPLACE THE SPAWNPREFAB METHOD --- BE VERY VERY CAREFULL WITH THISlocal oldFunc = GLOBAL.SpawnPrefabGLOBAL._RPG.ENTS = {}GLOBAL.SpawnPrefab = function(name)    local inst = oldFunc(name)    if inst ~= nil then        local key = inst.prefab        if key == nil then            key = inst.name        end        if key ~= nil then            if GLOBAL._RPG.ENTS[key] == nil then                GLOBAL._RPG.ENTS[key] = { }                GLOBAL._RPG.ENTS[key].instances = { }                GLOBAL._RPG.ENTS[key].count = 0            end                table.insert(GLOBAL._RPG.ENTS[key].instances, inst)            GLOBAL._RPG.ENTS[key].count = GLOBAL._RPG.ENTS[key].count + 1        end    end    return instend
So now I could just go GLOBAL._RPG.ENTS[prefab] and get all instances of a certain prefab. Edited by RazvanM

Anyone know how to replace a function in a component with your own (via modmain.lua)? I actually want to dig into pickables.lua and do this:

function Pickable:Fertilize(fertilizer)

--fertilizer:Remove() --This is all I want to do, but I don't want to include the whole file in the mod

self.cycles_left = self.max_cycles

self:MakeEmpty()

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...