Jump to content

Possible to mod entityscript.lua function?


Recommended Posts

I want to mod the
EntityScript:GetIsWet()
function in the entityscript.lua.

The problem is, that this is no component and also
AddClassPostConstruct("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 just
inst.GetIsWet()
so I thought about modding every prefab with
AddPrefabPostInitAny
but of course this is a bad idea, because they are too many and the game will crash :D

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 by Serpens
Link to comment
Share on other sites

could you explain ? I will be facing a similar problem with combat:CanBeAttacked(attacker)

 

what does

AddGlobalClassPostConstruct("entityscript","EntityScript", EntityScriptPostConstruct)

do?

Edited by Andmann
Link to comment
Share on other sites

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 by Serpens
Link to comment
Share on other sites

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 by Andmann
Link to comment
Share on other sites

"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 by Serpens
Link to comment
Share on other sites

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 by Andmann
syntax errors
Link to comment
Share on other sites

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 by Serpens
Link to comment
Share on other sites

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 by DarkXero
Link to comment
Share on other sites

@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 by Serpens
Link to comment
Share on other sites

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

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 by Serpens
Link to comment
Share on other sites

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

@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" ? :D 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 :D
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 by Serpens
Link to comment
Share on other sites

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

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 out
components.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 write
self.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

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

thank you for the self.inst explanation :)

The uploaded Home Base Mod is quite outdated already :D 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. :D

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 :D
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 by Serpens
Link to comment
Share on other sites

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

@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 checking
if not GLOBAL.TheWorld.ismastersim then
and
if not GLOBAL.TheNet:GetIsServer() then

Edited by Serpens
Link to comment
Share on other sites

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 checking
if not GLOBAL.TheWorld.ismastersim then
and
if 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

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 by Serpens
Link to comment
Share on other sites

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
 Share

×
  • Create New...