Serpens Posted July 28, 2016 Share Posted July 28, 2016 (edited) I want to mod theEntityScript:GetIsWet() function in the entityscript.lua. The problem is, that this is no component and alsoAddClassPostConstruct("entityscript", EntityScriptPostConstruct) does not work: Class file path 'entityscript' doesn't seem to return a valid class. The GetIsWet function is called in the game scripts with justinst.GetIsWet() so I thought about modding every prefab withAddPrefabPostInitAny but of course this is a bad idea, because they are too many and the game will crash So how can I mod this function? related to my topic here:http://forums.kleientertainment.com/topic/69171-wettness-of-items-and-buildings-how-to-change/ Edited July 28, 2016 by Serpens Link to comment Share on other sites More sharing options...
Serpens Posted July 28, 2016 Author Share Posted July 28, 2016 solved! AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) Link to comment Share on other sites More sharing options...
Andmann Posted July 29, 2016 Share Posted July 29, 2016 (edited) could you explain ? I will be facing a similar problem with combat:CanBeAttacked(attacker) what does AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) do? Edited July 29, 2016 by Andmann Link to comment Share on other sites More sharing options...
Serpens Posted July 29, 2016 Author Share Posted July 29, 2016 (edited) Do you know in general how AddClassPostConstruct works and what to do with it? If yes, the global version AddGlobalClassPostConstruct does mostly the same, but for scripts that are not in any additional subfolder, but in the scripts folder. The first argument is the filename (always lowercase). The second is the name of the class (take a look at the file to find out how it is spelled exactly). And the third argument is the function in which you will write, what you want to add to this Class. Keep in mind, that if you make a self.CanBeAttacked() function, you will override the original function. edit: you can also take a look at scritps/modutils.lua (and mod.lua is also interesting) to find out what the functions are you can use for modding. Edited July 29, 2016 by Serpens Link to comment Share on other sites More sharing options...
Andmann Posted July 29, 2016 Share Posted July 29, 2016 (edited) well if I understand correctly Addclasspost construct basically does what it is named to do? define a class "before" the world is constructed? So if I understood correctly with this in my main AddGlobalClassPostConstruct("combat_replica","Combat", CanBeAttacked) I can rewrite this function? in combat_replica.lua function Combat:CanBeAttacked(attacker) if self.inst:HasTag("playerghost") or self.inst:HasTag("noattack") or self.inst:HasTag("flight") or self.inst:HasTag("invisible") then --Can't be attacked by anyone return false elseif attacker ~= nil then --Attacker checks if self.inst:HasTag("birchnutdrake") and (attacker:HasTag("birchnutdrake") or attacker:HasTag("birchnutroot") or attacker:HasTag("birchnut")) then --Birchnut check return false elseif attacker ~= self.inst and self.inst:HasTag("player") then --Player target check if not TheNet:GetPVPEnabled() and attacker:HasTag("player") then --PVP check return false elseif self._target:value() ~= attacker then local follower = attacker.replica.follower if follower ~= nil then local leader = follower:GetLeader() if leader ~= nil and leader ~= self._target:value() and leader:HasTag("player") then local combat = attacker.replica.combat if combat ~= nil and combat:GetTarget() ~= self.inst then --Follower check return false end end end end end local sanity = attacker.replica.sanity if sanity ~= nil and sanity:IsCrazy() then --Insane attacker can pretty much attack anything return true end end if self.inst:HasTag("shadowcreature") and self._target:value() == nil then --Not insane attacker cannot attack shadow creatures --(unless shadow creature has a target) return false end --Passed all checks, can be attacked by anyone return true end or what would be the correct syntax? btw thank you man this will help me a lot in understanding the structure of modding. Edited July 29, 2016 by Andmann Link to comment Share on other sites More sharing options...
Serpens Posted July 29, 2016 Author Share Posted July 29, 2016 (edited) "Post" means "after", not before And I think it is after the class is constructed. But that is not that important. You use it this way: local function EntityScriptPostConstruct(self) local _GetIsWet = self.GetIsWet self.GetIsWet = function(inst) local replica = inst.replica.inventoryitem or inst.replica.moisture -- items and character have their own thing if replica ~= nil or inst==nil or inst:IsInLimbo() then return _GetIsWet(inst) end if inst:HasTag("wet") then return inst:HasTag("wet") end local heaterPower = math.min(1500,GetExternalheaterpowerr(inst,_GetIsWet(inst)) * GetModConfigData("hot_dry_fire")) -- 0 to 150 ? -- at least 115 near a big firepit fire, but they sum up, so 10 fires will be 1150 and a forest fire will be more than 1500, but we will max it with 1500 local additional = 0.2316 * math.pow(heaterPower,0.8337) -- from 0 to ~100. normal fire 12.5 , 2 fires 22.3 ,~6 burining trees 45 ,very big forest fire no wet structures near that... could be that with this change forest fires will be more dangerous while it rains, but I think rain did not stop forest fires before anyway... if GLOBAL.TheWorld.state.wetness < GLOBAL.TUNING.MOISTURE_DRY_THRESHOLD + additional then -- default 15 return false elseif GLOBAL.TheWorld.state.wetness > GLOBAL.TUNING.MOISTURE_WET_THRESHOLD + additional then -- default 35 return true end return _GetIsWet(inst) end end if GetModConfigData("hot_dry_fire") then AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) end This is from my wetness mod I'm currently working on. You see that the function "EntityScriptPostConstruct" is not the game function I want to edit, but the function which includes the changes I want to make. The function I want to change is "GetIsWet()". So in this skript at first I save the old GetIsWet() function, because the stuff that follows will override the original one. Then I define a "new" function which is name GetIsWet so it will override. Now I can write what is should do and return. At the end I do return the old original function, just in case none of my if and elseif requirements are filled, to return the original value. In your case you should first think about, if you want to override the function completely, then there is no need to save the original one.. but it could cause problems when game is updating. Or if you want only a small change with the rest unchanged. If you only want to change, that players with Tag "hostile" can be attacked, you should save the orignal funciton. Then define the new one and check for the Tag. If the tag is there, you return true. If it is not there you return the original fucntion. And in your case the new function should be: self.CanBeAttacked = function(attacker) and you can use self.inst from the class itself If you want to call any globals like TheNet:GetPVPEnabled(), you have to call GLOBAL.TheNet:GetPVPEnabled() edit: ah and if you are not within a class, you have to define functions you are referring to before. So you cant put AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) over the EntityScriptPostConstruct function. Edited July 29, 2016 by Serpens Link to comment Share on other sites More sharing options...
Andmann Posted July 29, 2016 Share Posted July 29, 2016 (edited) I think I get what you're saying local function CombatPostConstruct(self) local _CanBeAttacked = self.CanBeAttacked self.CanBeAttacked = function(self, attacker) if self.inst:HasTag("player") and attacker:HasTag("player") and self.inst.mymod.getFaction() == attacker.mymod.getFaction() then return false else return _CanBeAttacked(self, attacker) end end end AddComponentPostInit("combat",CombatPostConstruct) so now when CanBeAttacked is called it will use my line of code beforehand and if pvp is concerned it will now consider my modded in factions instead. Anything else should be handled as normal. Edited July 30, 2016 by Andmann syntax errors Link to comment Share on other sites More sharing options...
Serpens Posted July 30, 2016 Author Share Posted July 30, 2016 (edited) looks good, but I was surprised by the filename combat_replica. This is normally used in components. And yes, this file is in the components, so you don't need the global version (don't know if it still will work, but I doubt). Maybe AddClassPostConstruct will work, but in case of components better use the function that is made for components (see modutils.lua in the scripts folder from the game) So use: AddComponentPostInit("combat",CombatPostConstruct) (the function does also exist in combat.lua) Of course you can name your function CombatPostConstruct like you want, can also be OnCombat for example. The rest should look the same. Edited July 30, 2016 by Serpens Link to comment Share on other sites More sharing options...
Andmann Posted July 30, 2016 Share Posted July 30, 2016 well it did give me an error, that I could fix by doing this : AddGlobalClassPostConstruct("components/combat_replica","Combat", CombatPostConstruct) gonna try the component version Link to comment Share on other sites More sharing options...
DarkXero Posted July 30, 2016 Share Posted July 30, 2016 (edited) The component version won't work. To edit the combat replica: local function CombatReplicaPostInit(self) local _CanBeAttacked = self.CanBeAttacked self.CanBeAttacked = function(self, attacker) if self.inst:HasTag("player") and attacker:HasTag("player") and self.inst.mymod.getFaction() == attacker.mymod.getFaction() then return false else return _CanBeAttacked(self, attacker) end end end AddClassPostConstruct("components/combat_replica", CombatReplicaPostInit) Which stores old function in a variable. Then assigns a new function that does stuff and returns false, or returns the old value (runs the old function stored, gets the result, and returns it). Keep in mind that function combat:func(arg) equals function combat.func(combat, arg) where lua turns "combat" into "self", to give it that object oriented feel. Edited July 30, 2016 by DarkXero Link to comment Share on other sites More sharing options...
Serpens Posted July 30, 2016 Author Share Posted July 30, 2016 (edited) @DarkXero thank you. But why won't AddComponentPostInit won't work? btw. since you know alot about modding, I would appricicate, if you could take a look at my mod here:http://forums.kleientertainment.com/files/file/1570-home-base-bonus-mod/?changelog=0 I'm quite sure there are alot of beginner errors in it and I'm also sure it won't work for clients, at least showing the icon... Edited July 30, 2016 by Serpens Link to comment Share on other sites More sharing options...
DarkXero Posted July 30, 2016 Share Posted July 30, 2016 6 minutes ago, Serpens said: But why won't AddComponentPostInit won't work? This is the relevant code that takes the postinits and applies them to components: function EntityScript:AddComponent(name) local lower_name = string.lower(name) if self.lower_components_shadow[lower_name] ~= nil then print("component "..name.." already exists!"..debugstack_oneline(3)) end local cmp = LoadComponent(name) assert(cmp, "component ".. name .. " does not exist!") self:ReplicateComponent(name) local loadedcmp = cmp(self) self.components[name] = loadedcmp self.lower_components_shadow[lower_name] = true local postinitfns = ModManager:GetPostInitFns("ComponentPostInit", name) for k,fn in ipairs(postinitfns) do fn(loadedcmp,self) end self:RegisterComponentActions(name) end inst:AddComponent("combat_replica") never runs. What runs is inst:AddComponent("combat"), and the replica gets attached with the ReplicateComponent method (defined in entityreplica.lua). You could ask to have a AddReplicaPostInit method, that does the same, but for replicas. You can also notice that AddComponentPostInit is useful to edit replicas, but only for the host/server (and if you edit replicas you definitely want to edit them in the client side too). 13 minutes ago, Serpens said: I'm quite sure there are alot of beginner errors in it and I'm also sure it won't work for clients, at least showing the icon... I will take a look. It doesn't work for clients, because the icon showing is shoved inside the logic, and it doesn't run on the componentless clients. Link to comment Share on other sites More sharing options...
Andmann Posted July 30, 2016 Share Posted July 30, 2016 (edited) AddComponentPostInit does work for combat but not for combat_replica... no idea why though o_o Edited July 30, 2016 by Andmann Link to comment Share on other sites More sharing options...
Serpens Posted July 30, 2016 Author Share Posted July 30, 2016 (edited) 10 hours ago, Andmann said: AddComponentPostInit does work for combat but not for combat_replica... no idea why though o_o Yes, I said make it for "combat", because I DarkXero already said, that these replica things are special. That's why I asked him why it should not work for combat. ^^ Interpreting his answer I think for combat it will work flawless? I think this should work (but of course DarkXero is the expert here, not me): local function CombatPostInit(self) local _CanBeAttacked = self.CanBeAttacked self.CanBeAttacked = function(self, attacker) if self.inst:HasTag("player") and attacker:HasTag("player") and self.inst.mymod.getFaction() == attacker.mymod.getFaction() then return false else return _CanBeAttacked(self, attacker) end end end AddComponentPostInit("combat", CombatPostInit) This should work, because in the combat.lua is also a CanBeAttacked function that is refering to the replica thing. Or are there again client problems connected to this? 10 hours ago, DarkXero said: I will take a look. It doesn't work for clients, because the icon showing is shoved inside the logic, and it doesn't run on the componentless clients. Thanks. Sorry my english is not the best. What is the meaning of "shoved inside the logic" and how to solve this? And what are componentless clients? Yes clients without components, but do you say that clients don't have anything from the components folder? Or do you mean with componentless that they are missing components like a mod? So how to solve this? Edited July 30, 2016 by Serpens Link to comment Share on other sites More sharing options...
DarkXero Posted July 30, 2016 Share Posted July 30, 2016 3 hours ago, Serpens said: This should work, because in the combat.lua is also a CanBeAttacked function that is refering to the replica thing. Or are there again client problems connected to this? I said that 14 hours ago, DarkXero said: You can also notice that AddComponentPostInit is useful to edit replicas, but only for the host/server (and if you edit replicas you definitely want to edit them in the client side too). because clients don't get added sanity, or health. Therefore, there is nothing to post init. 3 hours ago, Serpens said: And what are componentless clients? Imagine you host a server with caves (game runs a dedicated server and makes you join the local network). Open console, press Ctrl (to disable "remote:" and run commands locally instead of sending to server). Do print(ThePlayer.components.sanity) to see it is a nil value. Do print(ThePlayer.components.health) to see it is a nil value. Now host a server without caves. Open console, there is no remote mode. Repeat, see that a table value is printed. Read any prefab file. if not TheWorld.ismastersim then return inst end Components are mostly added after those lines. You are the master simulation if you play as the server (if you host a caveless world or you are the dedicated machine). 4 hours ago, Serpens said: Yes clients without components, but do you say that clients don't have anything from the components folder? Or do you mean with componentless that they are missing components like a mod? So how to solve this? The files exist for clients. They just do not perform the inst:AddComponent operations, most of the time. There are exceptions. For example, a server and client component (defined in player_common.lua) is inst:AddComponent("talker") and another one is inst:AddComponent("playervision") What adding components server side does client side is generate replicas, if the component is replicatable (defined in entityreplica.lua). Basically the "how to solve this" is the understanding of what net variables and replicas are. But right now I'm trying to make sense of your Home Base Bonus, especially this cached base thing. I don't get it. Link to comment Share on other sites More sharing options...
Serpens Posted July 30, 2016 Author Share Posted July 30, 2016 (edited) @darkxero: thank you very much for this explanation! I did not know that components do not run at clients. I just found out, that I can play with caves enabled (like you said) to test my mod from a client perspective (before I was not able to test it this way, and all my knowledge I have is from testing, cause sth like a documentation sadly does not exist =/) Why is everyone confused about my "base thing" ? also IvanX was confused and wrote a "simpler" version of the CheckBase function, which did not work correctly cause he did not understand what it should do The base thing is one of the things that work flawless, as host and as client So there is no need to check this (exceot for the icon, but the bonusses do work) At the moment the problems from my mod are: Client - HUD and Client - GetIsWet (the EntityScript I posted here to explain how AddGlobalClassPostConstruct is working). For some reason the GetIsWet modding does work for host, but not for client. Have to test more tomorrow... Edited July 30, 2016 by Serpens Link to comment Share on other sites More sharing options...
DarkXero Posted July 30, 2016 Share Posted July 30, 2016 15 minutes ago, Serpens said: At the moment the problems from my mod are: Client - HUD and Client - GetIsWet (the EntityScript I posted here to explain how AddGlobalClassPostConstruct is working). For some reason the GetIsWet modding does work for host, but not for client. Have to test more tomorrow... if not GLOBAL.TheNet:GetIsServer() then return end The mod "stops executing" right at the beginning for clients. So they never get the chance to edit their widgets or their entityscript class. Link to comment Share on other sites More sharing options...
Serpens Posted July 31, 2016 Author Share Posted July 31, 2016 Hm.. I did not understood it completly... since there are alot of components that do not have replica... these are only callable by host/server? At the moment the problem is, that my modded GetIsWet function needs to find out the externalheat from everything that is using this function. Only players have a temperature component, so I can't use the externalheat defined there for items and other stuff. That's why I made my own GetExternalheaterpowerr function. This is searching for heats in range. But the problem is, that as a client, we are not able to find outcomponents.heater:GetHeat(inst) because as a client we can't access this component. But heater has also no replica. So what can I do? Because of the entityscript I have to make this as a client, right? But as a client I don't have access to this component... =/ And I also did not get, when to use inst and when self.inst. For example in my EntityScriptPostConstruct modding, I have to use "inst" and not "self.inst" (because self.inst is nil), even if I writeself.GetIsWet = function(self) While with moisture modding:AddComponentPostInit("moisture", function(self) local _GetMoistureRate = self.GetMoistureRate -- max is 0.75 self.GetMoistureRate = function(self) ... I can use self.inst Link to comment Share on other sites More sharing options...
DarkXero Posted July 31, 2016 Share Posted July 31, 2016 3 hours ago, Serpens said: At the moment the problem is, that my modded GetIsWet function needs to find out the externalheat from everything that is using this function. Only players have a temperature component, so I can't use the externalheat defined there for items and other stuff. That's why I made my own GetExternalheaterpowerr function. This is searching for heats in range. But the problem is, that as a client, we are not able to find outcomponents.heater:GetHeat(inst) because as a client we can't access this component. But heater has also no replica. So what can I do? Explain: why and how do you use the GetHeat method? I can't see it on the Home Base Bonus mod. 3 hours ago, Serpens said: And I also did not get, when to use inst and when self.inst. For example in my EntityScriptPostConstruct modding, I have to use "inst" and not "self.inst" (because self.inst is nil) You can use any name you want. When you use AddClassPostConstruct, you give a function fn that will be run like fn(class), so you can do "local function fn(self)" or "local function fn(inst)" or "local function fn(whatever)". In general, we use self as argument name when we know we will be working with a class like a component (look at any component file. The constructor function has a "self.inst = inst"), or some classes like any of the widgets (self.owner = owner). We use inst as argument name when we know we will be working with an entity. Entityscript is a bit special, because it doesn't need a "self.inst = inst" reference (it won't be a class attached to another, like how components attach to entities), because it is, itself, the inst. Link to comment Share on other sites More sharing options...
Serpens Posted July 31, 2016 Author Share Posted July 31, 2016 (edited) thank you for the self.inst explanation The uploaded Home Base Mod is quite outdated already But the functions like BaseCheck and UpdateOrGet and all the base stuff did not change. The versoin I have at the moment is also full of TODO and other comments by me in mixed language, to help me remembering how everything works. The main goal from modding the GetIsWet function is, that eg. things on a 100% rainproof floor or in range of heat (fire), can't get wet (or at least don't get wet at 35 wetness, but e.g 75 wetness is needed) The unmodded game does not look at the position of anything, if it is wet or not. It simply makes everything wet, as soon world wetness is over 35. Here my functions that are used to achieve this, they are on the top of the script, so before the TheNet:GetIsServer returns. Spoiler local function GetExternalheaterpowerr(inst,iswet) -- eventuell zu arbeitsintensiv, wenn jedes einzelne objekt die umgebung auf feuer prüft? local externalheaterpower = 0 local x, y, z = inst.Transform:GetWorldPosition() if not x or not y or not z then -- items hold by the player have no coordinates, so we have to use the ones from owner local owner = nil if inst.components then if inst.components.inventoryitem then owner = inst.components.inventoryitem.owner end end if owner then x, y, z = owner.Transform:GetWorldPosition() if not x or not y or not z then return externalheaterpower end else return externalheaterpower end end local ents = GLOBAL.TheSim:FindEntities(x, y, z, 10, nil, { "INLIMBO" }, { "HASHEATER" }) or GLOBAL.TheSim:FindEntities(x, y, z, 10, { "HASHEATER" }, { "INLIMBO" }) --(x, y, z, radius, musttags, canttags, mustoneoftags) for i, v in ipairs(ents) do -- loop through all found heaters in range if v ~= inst and not v:HasTag("INLIMBO") and v.components.heater ~= nil and -- components.heater is nil for clients... (v.components.heater:IsExothermic() or v.components.heater:IsEndothermic()) then local heat = v.components.heater:GetHeat(inst) if heat ~= nil then local heatfactor = 1 - inst:GetDistanceSqToInst(v) / (10*10) if iswet then heatfactor = heatfactor * GLOBAL.TUNING.WET_HEAT_FACTOR_PENALTY end if v.components.heater:IsExothermic() then local warmingtemp = heat * heatfactor externalheaterpower = externalheaterpower + warmingtemp -- several sources will sum up.. this is like it is also done in game files end end end end return externalheaterpower end local function EntityScriptPostConstruct(self) local _GetIsWet = self.GetIsWet self.GetIsWet = function(inst) if GLOBAL.TheWorld.state.wetness==0 or GLOBAL.TheWorld.state.wetness==nil then -- if no wetness, nothing can be wet return _GetIsWet(inst) end local replica = inst.replica.inventoryitem or inst.replica.moisture -- items and character have their own thing if replica ~= nil or inst==nil or inst:HasTag("INLIMBO") then return _GetIsWet(inst) end if inst:HasTag("wet") then return inst:HasTag("wet") end local additional = 0 local heaterPower = 0 if GetModConfigData("hot_dry_fire")==1 then heaterPower = math.min(1500,GetExternalheaterpowerr(inst,_GetIsWet(inst)) * GetModConfigData("hot_dry_fire"))--115 near a big firepit fire, but they sum up, so 10 fires will be 1150 and a forest fire will be more than 1500, but we will max it with 1500 additional = 0.2316 * math.pow(heaterPower,0.8337) -- from 0 to ~100. normal fire 12.5 , 2 fires 22.3 ,~6 burining trees 45 end local tilebonus = 0 if GetModConfigData("rain_reduction_wooden") ~= 1 or GetModConfigData("rain_reduction_checker")~= 1 or GetModConfigData("rain_reduction_carpet")~= 1 then local x, y, z = inst.Transform:GetWorldPosition() -- first test if all coordinates exist, because if not, getcurrentile will throw an error -.- if x and y and z then local tile, data = inst:GetCurrentTileType() if GetModConfigData("base_required_rainproof")==0 or UpdateorGet(inst,"rain",tile) then if (tile == GLOBAL.GROUND.CARPET) then additional = additional + (1-GetModConfigData("rain_reduction_carpet"))*40 -- if 100% rainprotection, the moddata is 0, so 40 will bei added tilebonus = tilebonus + (1-GetModConfigData("rain_reduction_carpet"))*40 elseif (tile == GLOBAL.GROUND.WOODFLOOR) then additional = additional + (1-GetModConfigData("rain_reduction_wooden"))*40 tilebonus = tilebonus + (1-GetModConfigData("rain_reduction_wooden"))*40 elseif (tile == GLOBAL.GROUND.CHECKER) then additional = additional + (1-GetModConfigData("rain_reduction_checker"))*40 tilebonus = tilebonus + (1-GetModConfigData("rain_reduction_checker"))*40 end end end end if additional>0 and GLOBAL.GetRandomMinMax(0,1)>0.95 then -- print for debugging... print("GetIsWet, worldwettnes: "..GLOBAL.TheWorld.state.wetness.." wet if over:"..additional+GLOBAL.TUNING.MOISTURE_WET_THRESHOLD.." dry if under:"..additional+GLOBAL.TUNING.MOISTURE_DRY_THRESHOLD.." heaterPower: "..heaterPower.." tilebonus: "..tilebonus) print(inst) end if GLOBAL.TheWorld.state.wetness < GLOBAL.TUNING.MOISTURE_DRY_THRESHOLD + additional then -- default 15 if inst:HasTag("IsNewWet") then inst:RemoveTag("IsNewWet") end return false -- not wet elseif GLOBAL.TheWorld.state.wetness > GLOBAL.TUNING.MOISTURE_WET_THRESHOLD + additional then -- default 35 if not inst:HasTag("IsNewWet") then inst:AddTag("IsNewWet") end return true -- wet end return inst:HasTag("IsNewWet") -- return the last known state end end if GetModConfigData("hot_dry_fire")==1 or GetModConfigData("rain_reduction_wooden") ~= 1 or GetModConfigData("rain_reduction_checker")~= 1 or GetModConfigData("rain_reduction_carpet")~= 1 then AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) end It does work if I play as host. But not if I'm client. As soon as this works, I plan to add also a chached externalpower. Because the GetIsWet function is called by the game very often, especially when hovering over an object to show the tooltip. It is like 100 times per second for one single object, this is just ridiculous Calling everytime the GetExernalheaterpowerr function is too mcuh I think. So I will implement that this function will only update the externalheatpower if at least ~1 second passed, and otherwise return the last known value, which will be saved simular to the aChachedBase. Edited July 31, 2016 by Serpens Link to comment Share on other sites More sharing options...
DarkXero Posted July 31, 2016 Share Posted July 31, 2016 1 hour ago, Serpens said: It does work if I play as host. But not if I'm client. local function EntityScriptPostConstruct(self) local _GetIsWet = self.GetIsWet self.GetIsWet = function(inst) return _GetIsWet(inst) and not inst:HasTag("wetprotected") end end AddGlobalClassPostConstruct("entityscript", "EntityScript", EntityScriptPostConstruct) if not GLOBAL.TheNet:GetIsServer() then return end local function IsItemWetProtected(inst) if GLOBAL.TheWorld.state.wetness == 0 or GLOBAL.TheWorld.state.wetness == nil then -- if no wetness, nothing can be wet return false end local replica = inst.replica.inventoryitem or inst.replica.moisture -- items and character have their own thing if replica ~= nil or inst == nil or inst:HasTag("INLIMBO") then return false end if inst:HasTag("wet") then return not inst:HasTag("wet") end -- bla bla bla everything else -- external heater power something etc -- ... -- ... return inst:HasTag("IsNewWet") -- return the last known state end local function UpdateWetnessProtection(inst) -- depending on logic result, update tag if IsItemWetProtected(inst) then inst:AddTag("wetprotected") else inst:RemoveTag("wetprotected") end -- schedule the function to run again in the next 5-15 seconds inst:DoTaskInTime(5 + math.random() * 10, UpdateWetnessProtection) end -- all prefabs run this function AddPrefabPostInitAny(UpdateWetnessProtection) Here server does its thing and updates a tag. It propagates to clients. Clients only need to check for the tag. They don't care about the actual value, just if the item is wet or not. Link to comment Share on other sites More sharing options...
Serpens Posted August 1, 2016 Author Share Posted August 1, 2016 @DarkXero Using tags is a fantastic idea! This will also simplify my aChachedBase solution. Thank you very much Link to comment Share on other sites More sharing options...
Serpens Posted August 1, 2016 Author Share Posted August 1, 2016 (edited) @DarkXero Okay... here you posted, how to send a variable from server to client http://forums.kleientertainment.com/topic/69230-farm-harvest-x3-multiplier/?do=findComment&comment=799019 And you also told me here and there, that the Tags will be automatically be the same on server and every client. But how can a client make the server call a function? Your solution here adds the Update function to all prefabs and they will update wetness regulary. But I think this is too much, since 99% of all prefabs don't need to update, cause they are not in range of any player. So my thought was something like that: local function EntityScriptPostConstruct(self) -- funktioniert, nur Formel muss noch ne gute gefunden werden... bei 1 feuer ist heat in nähe ungefähr 120. bei 10 feuern ists 1200.... local _GetIsWet = self.GetIsWet self.GetIsWet = function(inst) -- does not work with self and using self.inst ! self.inst will be nil if GLOBAL.TheWorld.state.wetness==0 or GLOBAL.TheWorld.state.wetness==nil then -- if not wetness, nothing can be wet. works at server and client return _GetIsWet(inst) end local replica = inst.replica.inventoryitem or inst.replica.moisture -- items and character have their own thing if replica ~= nil or inst==nil or inst:HasTag("INLIMBO") then -- replica and hastag do work at server and at client. inst:IsInLimbo() --V2C: faster than checking tag, but only valid on mastersim return _GetIsWet(inst) end if inst:HasTag("wet") then return inst:HasTag("wet") end -- things that are always wet inst:PushEvent("GetNewWetEvent") -- if a client pushes a event, only the client functions hear it, not the server -.- return inst:HasTag("IsNewWet") end end if GetModConfigData("hot_dry_fire")==1 or GetModConfigData("rain_reduction_wooden") ~= 1 or GetModConfigData("rain_reduction_checker")~= 1 or GetModConfigData("rain_reduction_carpet")~= 1 then AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) end if not GLOBAL.TheNet:GetIsServer() then -- what is the difference between checking GetIsSwerver or mastersim ? return end local function UpdateNewWetTag(inst) local additional = 0 local heaterPower = 0 print("UpdateNewWetTag") -- .. -- .. check stuf... -- .. if GLOBAL.TheWorld.state.wetness < GLOBAL.TUNING.MOISTURE_DRY_THRESHOLD + additional then -- default 15 if inst:HasTag("IsNewWet") then inst:RemoveTag("IsNewWet") end return false elseif GLOBAL.TheWorld.state.wetness > GLOBAL.TUNING.MOISTURE_WET_THRESHOLD + additional then -- default 35 if not inst:HasTag("IsNewWet") then inst:AddTag("IsNewWet") end return true end return inst:HasTag("IsNewWet") end local function OnGetNewWetEvent(inst,data) UpdateNewWetTag(inst) end local function ListenGetNewWetEvent(inst) inst:ListenForEvent("GetNewWetEvent",OnGetNewWetEvent) end -- all prefabs run this function AddPrefabPostInitAny(ListenGetNewWetEvent) -- make this only for server, cause as a client we can't get heaterpower So I hoped that I can push an event as client, and the server will hear it.... but the server does not hear it... I only want to update if it is wet or not, if it is really needed, so only if the GetIsWet is called. So how can this be solved? And what is the difference between checkingif not GLOBAL.TheWorld.ismastersim then andif not GLOBAL.TheNet:GetIsServer() then Edited August 1, 2016 by Serpens Link to comment Share on other sites More sharing options...
DarkXero Posted August 1, 2016 Share Posted August 1, 2016 2 hours ago, Serpens said: So I hoped that I can push an event as client, and the server will hear it.... but the server does not hear it... I only want to update if it is wet or not, if it is really needed, so only if the GetIsWet is called. So how can this be solved? Clients communicate with the server via remote procedure calls. Example: AddModRPCHandler("unique_mod_identifier", "unique_handler_name", function(player, inst) if player and inst and type(inst) == "table" then UpdateNewWetTag(inst) end end) local function EntityScriptPostConstruct(self) -- funktioniert, nur Formel muss noch ne gute gefunden werden... bei 1 feuer ist heat in nähe ungefähr 120. bei 10 feuern ists 1200.... local _GetIsWet = self.GetIsWet self.GetIsWet = function(inst) -- does not work with self and using self.inst ! self.inst will be nil if GLOBAL.TheWorld.state.wetness==0 or GLOBAL.TheWorld.state.wetness==nil then -- if not wetness, nothing can be wet. works at server and client return _GetIsWet(inst) end local replica = inst.replica.inventoryitem or inst.replica.moisture -- items and character have their own thing if replica ~= nil or inst==nil or inst:HasTag("INLIMBO") then -- replica and hastag do work at server and at client. inst:IsInLimbo() --V2C: faster than checking tag, but only valid on mastersim return _GetIsWet(inst) end if inst:HasTag("wet") then return inst:HasTag("wet") end -- things that are always wet local RPC = GetModRPC("unique_mod_identifier", "unique_handler_name") SendModRPCToServer(RPC, inst) return inst:HasTag("IsNewWet") end end if GetModConfigData("hot_dry_fire")==1 or GetModConfigData("rain_reduction_wooden") ~= 1 or GetModConfigData("rain_reduction_checker")~= 1 or GetModConfigData("rain_reduction_carpet")~= 1 then AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct) end 2 hours ago, Serpens said: And what is the difference between checkingif not GLOBAL.TheWorld.ismastersim then andif not GLOBAL.TheNet:GetIsServer() then Usually, no difference. However, when modmain loads, the world hasn't initialized yet, so TheWorld is nil. But TheNet isn't, and you can use it to load server side code only. Link to comment Share on other sites More sharing options...
Serpens Posted August 1, 2016 Author Share Posted August 1, 2016 (edited) fantastic, now I have everything I need and a short test shows it does work on dedicated server! On 25.7.2016 at 5:39 PM, IvanX said: Also do not forget to put inst.entity:SetPristine() within prefab 'fn' function to tell DS engine that the code above runs on client and on the server, with data from client and the server, so there's no need for syncing the result, as both client and server with their data will have a simillar output. When do I have to add this Pristine? Is this only necessary if my code is running at client AND server? I can see if it runs on both, if I just add print statements all over my code and compare the logfile from server and from client. And if something is in both, I should consider using Pristine, right? Or should I add a GetIsServer check instead, so that it only runs on one of them? Or does this depend on the situation? Anyway, if I have nothing, that is printed in both, I don't have to care about Pristine, right? Edit: For showing the icon, that the Base is complete, I added a DoTaskInTime in PlayerInit , that checks every second if the player has the Tag "hasbasebonus", and if so, it shows the icon. Or is there a better way to force clients to show an HUD icon, wehn code is running only on the server (I have the inst:HasTag("player") , but HUD is nil on server side of course) ? Edited August 1, 2016 by Serpens 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