Jump to content

Custom Sleep Effects


Recommended Posts

So I'm creating a new character. I want her to have buffed sleeping. Changes I want to make are:
bedroll_straw (+0,67 Sanity, -1 Hunger) >>> (+1 Sanity, -0,67 Hunger)
bedroll_furry (+1 Sanity, +1 Health, -1 Hunger) >>> (+1,33 Sanity, +1,33 Health, -0,67 Hunger)
tent (+1 Sanity, +2 Health. -1 Hunger) >>> (+1,33 Sanity, +2,33 Health, -0,67 Hunger)
siestahut (+1 Sanity, +2 Health, -0.33 Hunger) >>> (+1,33 Sanity, +2,33 Health)

(don't worry about the balance, she gets hungry more quickly than any other mortal)

also if possible I want her to be able to sleep regardless of the time of day.

Thanks for reading!

Link to comment
Share on other sites

I just answered something like this in another thread. Just look at the sleep-part, as there is a lot going on in that thread. All you need to do in order to use that code, is say something like:

if comp.inst.prefab == "bedroll_straw" then
	-- do the DoDelta call with the diference between the original value for bedroll_straw and the value you want it to give
elseif comp.inst.prefab == "bedroll_furry" then
...

I also explain in that thread why I think this is the best and least intrusive way to inject this kind of behavior.

Edited by Ultroman
Link to comment
Share on other sites

Thanks for your reply! Alrgiht, so I wrote the code but it doesn't seem to take any effects.

(it's modmain.lua)

Spoiler

AddComponentPostInit("sleepingbag", function(comp)
	local old_onsleep = comp.onsleep
	comp.onsleep = function(inst, sleeper)
		old_onsleep(inst, sleeper) -- call the original function
		if sleeper.prefab == "oshi" then
			if comp.inst.prefab == "bedroll_straw" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif comp.inst.prefab == "bedroll_furry" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif comp.inst.prefab == "tent" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif comp.inst.prefab == "bsiestahut" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			end
		end
	end
end
)

 

I tried writing character name as "oshi", "Oshi" and "OSHI" but neither of them works.

EDIT: I tested 1 == 1 condition and it seems the problem lays somewhere else.

Edited by Kemezryp
Link to comment
Share on other sites

EDIT: THIS APPROACH DOESN'T WORK! Look at CarlZalph's reply further down for an explanation and some working code!

I think I made a mistake...though it SHOULD amount to the same, try this instead (replaced "comp.inst" with "inst"):

AddComponentPostInit("sleepingbag", function(comp)
	local old_onsleep = comp.onsleep
	comp.onsleep = function(inst, sleeper)
		old_onsleep(inst, sleeper) -- call the original function
		if sleeper.prefab == "oshi" then
			if inst.prefab == "bedroll_straw" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "bedroll_furry" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "tent" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "bsiestahut" then
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			end
		end
	end
end
)

If that doesn't work, it's time to break out the print-statements. If you're not sure how to use those to your advantage, look at the "Debugging" part of this post.

Before even calling old_onsleep(inst, sleeper), print the value of sleeper.prefab and comp.inst.prefab and inst.prefab, to make sure you got everything right.

Edited by Ultroman
Link to comment
Share on other sites

alright, I'm not sure if I did it correctly, but here's code:

Spoiler

AddComponentPostInit("sleepingbag", function(comp)
	print("step 1")
	local old_onsleep = comp.onsleep
	print("step 2")
	comp.onsleep = function(inst, sleeper)
	print("step 3")
		old_onsleep(inst, sleeper) -- call the original function
		print("step 4")
		if 1 == 1 then
			print("yes 1 is equal to 1")
			if inst.prefab == "bedroll_straw" then
				print("Bedroll Straw")
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "bedroll_furry" then
				print("Bedroll Furry")
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "tent" then
				print("Tent")
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			elseif inst.prefab == "bsiestahut" then
				print("Siesta Hut")
				sleeper.components.health:DoDelta(2, true)
				sleeper.components.sanity:DoDelta(2, true)
				sleeper.components.hunger:DoDelta(2, true)
			end
		end
	end
	print("finished!")
end
)

 

I went to sleep to the Tent. Here are results from client_log.txt:

(I think it should uptade itself more frequently? idk)

client_log.txt

Link to comment
Share on other sites

Not just that. Your "step 1" and "step 2" should happen every time a piece of sleeping-equipment is created on the server (even those existing in the world or on your character every time you load the game). Something is wrong. Where did you put this AddComponentPostInit call? It's supposed to be at the bottom of your modmain.lua

Edited by Ultroman
Link to comment
Share on other sites

Yes, it is on the very bottom of the file.

I retested the process. Yes, steps are repeated when I place the tent. Nothing happens when I go to sleep.

I gave myself right to craft anything:
[00:02:44]: Warning: GetPlayer() is deprecated. Please use ThePlayer instead. (@GetPlayer().components.builder:GiveAllRecipes():1 in ?)	
I created first tent:
[00:03:05]: step 1	
[00:03:05]: step 2	
[00:03:05]: finished!
I created another 2 tents:
[00:05:23]: step 1	
[00:05:23]: step 2	
[00:05:23]: finished!	
[00:05:28]: step 1	
[00:05:28]: step 2	
[00:05:28]: finished!

 

Link to comment
Share on other sites

That is super-weird...now we know that AddComponentPostInit is being done, at least. We gotta figure out what's going on.

After print("step 2") try adding something like this:

print("Printing comp: ")
print(comp)
print("Printing comp.onsleep: ")
print(comp.onsleep)

It should either print nil, crash saying you've tried to concatenate nil, or write the current state of the function. Let's see what it says.

Link to comment
Share on other sites

EDIT: THIS APPROACH DOESN'T WORK! Look at CarlZalph's reply further down for an explanation and some working code!

AHA! I know what's going on! It's because we're doing AddComponentPostInit. That function queues a function to be run after a component has been initialized, but since a component is always initialized first and has its functions changed later, what happens is this:

  1. a prefab has the sleepingbag-component added to it
  2. our function is called, and makes its changes
  3. the prefab makes changes its to the sleepingbag-component (overwriting our onsleep function)

There are two ways we can fix this:

  1. We move this code to four AddPrefabPostInit calls (one for each sleeping equipment) and refactor it slightly.
  2. We simply delay the changing of our code a tiny bit into the future using DoTaskInTime, because DoTaskInTime waits at least until next game-tick, even if you tell it to wait for 0 time.

Number 1 would be the obvious thing to do (and is also how I do it in my Sleepy Time mod, but if we do number 2, then we actually accommodate even the sleeping-equipment added by mods (or by Klei at a later date), since we go directly for the sleepingbag-component instead of individual prefabs.

Let's try number 2, since it's also the quickest one to change our current code for:

AddComponentPostInit("sleepingbag", function(comp)
	print("step 1")
	comp.inst:DoTaskInTime(0, function(comp)
		print("step 2")
		local old_onsleep = comp.onsleep
		comp.onsleep = function(inst, sleeper)
			print("step 3")
			old_onsleep(inst, sleeper) -- call the original function
			print("step 4")
			if sleeper.prefab == "oshi" then
				print("Sleeper is Oshi")
				if inst.prefab == "bedroll_straw" then
					print("Bedroll Straw")
					sleeper.components.health:DoDelta(2, true)
					sleeper.components.sanity:DoDelta(2, true)
					sleeper.components.hunger:DoDelta(2, true)
				elseif inst.prefab == "bedroll_furry" then
					print("Bedroll Furry")
					sleeper.components.health:DoDelta(2, true)
					sleeper.components.sanity:DoDelta(2, true)
					sleeper.components.hunger:DoDelta(2, true)
				elseif inst.prefab == "tent" then
					print("Tent")
					sleeper.components.health:DoDelta(2, true)
					sleeper.components.sanity:DoDelta(2, true)
					sleeper.components.hunger:DoDelta(2, true)
				elseif inst.prefab == "bsiestahut" then
					print("Siesta Hut")
					sleeper.components.health:DoDelta(2, true)
					sleeper.components.sanity:DoDelta(2, true)
					sleeper.components.hunger:DoDelta(2, true)
				end
			end
		end
		print("finished!")
	end
	)
end
)

 

Edited by Ultroman
Link to comment
Share on other sites

The thing is that the onsleep/onwake functions are called once, and in the prefabs they're creating a timer from these callbacks.  So it's a oneshot.

The callbacks here shouldn't be created for these when the component postinit is done, since they're defined in the prefabs after the component is created.

Then there's the thing that the callback for the timer for each prefab in the base game follow a nonstandard way on using variables for doing the deltas- some store the tuning variable for (hunger/health/sanity) while the others don't.

To top it all off the timer variable when it's killed isn't being set to nil like it should, so can't even use a metatable hook for that.

 

These all scream of 'old code from DS' which should have been rewritten if time permitted; even if they're functional they're not modder friendly.

Luckily all of the default prefabs for the base game use the same variable name for the sleep timer called 'sleeptask', so it's not as bad as it could have been for a blanket hook.

 

Last note you had a typo for the siestahut with a 'b' in front of it.

 

 

Here's what I did to get this all functional and generic so you can apply it to anything really:

local HookSleepingData = {
    wolfgang = {
        bedroll_straw = {
            sanity = 1.0,
            health = 1.0, -- This doesn't do anything due to prefab stopping it, this is here in case Klei changes that.
            hunger = -0.67,
        },
        bedroll_furry = {
            sanity = 1.33,
            health = 1.33,
            hunger = -0.67,
        },
        tent = {
            sanity = 1.33,
            health = 2.33,
            hunger = -0.67,
        },
        siestahut = {
            sanity = 1.33,
            health = 2.33,
            hunger = -0.33,
        },
    },
    waxwell = {
        tent = {
            sanity = 400.0,
            health = 900.0,
            hunger = -50.0,
        },
    },
}
local HookThese = {
    "bedroll_straw",
    "bedroll_furry",
    "tent",
    "siestahut",
}
local HookDoDelta = function(hooks, sleeper, component, value)
    if value == nil
    then
        return
    end
    local tohook = sleeper.components[component]
    if tohook and tohook.DoDelta
    then
        local DoDelta_old = tohook.DoDelta
        tohook.DoDelta = function(self, amount, ...)
            -- You could do math here with the old value 'amount' and the new replacement 'value' if you wanted it to be additive or multiplicative.
            -- Right now it's just flat out replacement.
            return DoDelta_old(self, value, ...)
        end
        table.insert(
            hooks,
            {
                component = component,
                DoDelta_old = DoDelta_old,
            }
        )
    end
end
local UnHookDoDelta = function(hooks, sleeper)
    for _, hook in ipairs(hooks)
    do
        sleeper.components[hook.component].DoDelta = hook.DoDelta_old
    end
end
local HookSleepingbags = function(prefab, sleeptask)
    local fn_old = sleeptask.fn
    sleeptask.fn = function(inst, sleeper, ...)
        local data = HookSleepingData[sleeper.prefab or "None"]
        if data and data[prefab]
        then
            local hooks = {}
            HookDoDelta(hooks, sleeper, "sanity", data[prefab].sanity)
            HookDoDelta(hooks, sleeper, "health", data[prefab].health)
            HookDoDelta(hooks, sleeper, "hunger", data[prefab].hunger)
            -- Temperature/Woodiness/custom component/etc could be done here.
            fn_old(inst, sleeper, ...)
            UnHookDoDelta(hooks, sleeper)
        else
            fn_old(inst, sleeper, ...)
        end
    end
end
for _, prefab in ipairs(HookThese)
do
    AddPrefabPostInit(
        prefab,
        function(inst)
            if not GLOBAL.TheWorld.ismastersim
            then
                return
            end
            if inst.components.sleepingbag and inst.components.sleepingbag.onsleep
            then
                local onsleep_old = inst.components.sleepingbag.onsleep
                inst.components.sleepingbag.onsleep = function(...)
                    onsleep_old(...)
                    if inst.sleeptask -- Hopefully other 'sleepingbag' mod stuff uses this exact same variable name Klei uses, otherwise fix it here.
                    then
                        HookSleepingbags(prefab, inst.sleeptask)
                    end
                end
            end
        end
    )
end

 

Edited by CarlZalph
Added case for if top defined isn't for default behaviour on hooks.
Link to comment
Share on other sites

1 hour ago, CarlZalph said:

....

Wow! That's crazy! And you're totally right, of course :) The reason why my approach works in my own mod, is because it replaces the onsleep functions entirely, and creates its own sleeptask. This is an awful lot of code just to do this simple thing, but from what you're saying it's necessary.

Thanks for blowing my mind! :D

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