Recommended Posts

Ultroman    537

IMPORTANT: The code should work in both DS and DST, but it was primarily written for DST, so I will leave a disclaimer here saying it might not be 100% the same code that is needed for DS.

Please tell me of any problems you run into with these code snippets.

How To Use The Examples Below

IMPORTANT: The code snippets in the sections below all alter either a character or a food. Look for the parenthesis in the titles for the code snippets to see which type of entity each code snippet is for, and then consult the application methods in this section to apply them to the entities you want (or in some other way, if you know what you're doing).

 

Applying Changes Specific To Your Own Food Item Mod

Put the code snippet in the prefab Lua code for your food, at the bottom of the fn function, after adding the edible component to it.

 

Applying Changes To A Specific Food Entity

Put this code in your modmain.lua, and put the code snippet you want where the comment says to put it:

-- Replace FOOD_PREFAB_NAME with the name of the food prefab you want to change.
AddPrefabPostInit("FOOD_PREFAB_NAME", function(inst)
	-- Put the code snippet here.
end)

 

Applying THE SAME Values To Several Food Entities

Put this code in your modmain.lua, and put the code snippet you want where the comment says to put it, and fill in the list of affected food prefabs:

local affected_foods = { "monstermeat", "corn" }

for i, v in ipairs(affected_foods) do
	AddPrefabPostInit(v, function(inst)
		-- Put the code snippet here.
	end)
end

 

Applying DIFFERENT Values To Several Food Entities

Put this code in your modmain.lua, and change the values in the food_stat_dict. NOTE that this approach needs none of the code snippets below. Just change the values in food_stat_dict.

-- food_stat_dict is a dictionary with food prefab names as keys and stat-dictionaries as values.
-- The stat-dictionaries have stat-names as keys and the effect on each stat as values.
-- If you want a stat not to be affected, you can just omit it from the stat-dictionary.
local food_stat_dict = {
	twigs = { sanity = 10 },
	monsterlasagna = { health = 25, sanity = -10, hunger = -10 },
	fishsticks = { health = 20, sanity = 20, hunger = 20 },
}

for food_prefab, food_stats in pairs(food_stat_dict) do
	AddPrefabPostInit(food_prefab, function(inst)
		inst.components.edible.healthvalue = food_stats["health"] or 0
		inst.components.edible.hungervalue = food_stats["hunger"] or 0
		inst.components.edible.sanityvalue = food_stats["sanity"] or 0
	end)
end

 

Applying Changes Specific To Your Own Character Mod

Put the code snippet in the prefab Lua code for your character, at the bottom of the master_postinit function.

 

Applying Changes To One Particular Character That Is Not Your Own Character Mod

Put the code snippet in the prefab Lua code for your character, at the bottom of the master_postinit function.

-- Replace CHARACTER_PREFAB_NAME with the name of the character prefab you want to change.
AddPrefabPostInit("CHARACTER_PREFAB_NAME", function(inst)
	-- Put the code snippet here.
end)

 

Applying Changes To Several Characters

Put the code snippet in the prefab Lua code for your character, at the bottom of the master_postinit function.

local affected_characters = { "wilson", "webber" }

for i, v in ipairs(affected_characters) do
	AddPrefabPostInit(v, function(inst)
		-- Put the code snippet here.
	end)
end

 

Applying Changes To All Characters

Put this code in your modmain.lua, and put the code snippet you want where the comment says to put it:

AddPlayerPostInit(function(inst)
	-- Put the code snippet here.
end)

 

Making Certain Foods Inedible (CHARACTER)

You can extend the Eater:CanEat function of the eater component on your character (or entity), and return false early if it has a certain tag or foodtype, or if it is a specific food.

Making All Foods With A Tag Or Specific Foodtype Inedible

To make all foods with a certain tag or food type inedible for a character, use this code snippet. You can replace "mushroom" with the tag you want to make inedible or remove the tag check, just like you can replace "VEGGIES" with whatever foodtype you want to exclude or remove the foodtype check. You can add as many of both kinds of checks as you'd like.

Spoiler

local oldCanEat = inst.components.eater.CanEat
function inst.components.eater:CanEat(inst)
	-- Check for certain tags. This example makes all foods with the "mushroom" tag inedible.
	if inst:HasTag("mushroom") then
		return false
	end
	
	-- Check for certain foodtypes. This example makes all VEGGIES inedible.
	if inst.components.edible then
		if inst.components.edible.foodtype == "VEGGIES" then
			return false
		end
	end
	
	if oldCanEat ~= nil then
		return oldCanEat(inst)
	end
	return true
end

 

Now, not all food items have an appropriate tag or foodtype to single them out. In the example above, not all mushroom items have the "mushroom" tag, so if you want to make your character unable to eat more foods incl. all the mushrooms, you can always make a list of inedible food items and keep them from being eaten using similar code.

Making Specific Foods Inedible

Spoiler

local inedibles = {
	"monstermeat",
	"corn",
}

local old_CanEat = inst.components.eater.CanEat
inst.components.eater.CanEat = function(self, food_inst)
	for i, v in ipairs(inedibles) do
		if food_inst.prefab == v then
			return false
		end
	end
	return old_CanEat(self, food_inst)
end

 


Removing ONLY Negative Stat Effects From Certain Foods (CHARACTER)

There are a few ways to go about this. Lets start with the least specific one, since it is the easiest to implement.

There is a special function in the eater component called DoFoodEffects, which checks whether to apply any negative effect on sanity or health (so not negative hunger effects) from eating certain food items. If we take a cooked green mushroom cap as an example, it adds health but removes some sanity. We can make that negative sanity not be added at all, while still keeping the positive health effect.

To be clear, this code removes all negative sanity and health effects from the foods in the "protected_foods" list, while still allowing any positive sanity and health effects that the food applies.

Spoiler

local protected_foods = { "monstermeat", "green_cap_cooked" }

local old_DoFoodEffects = inst.components.eater.DoFoodEffects
inst.components.eater.DoFoodEffects = function(self, food_inst)
	for i, v in ipairs(protected_foods) do
		if food_inst.prefab == v then
			return false
		end
	end
	return old_DoFoodEffects(self, food_inst)
end

 

 

Changing Food Values (CHARACTER)

I only recently thought of this "new" solution to this problem. In the past, I have said that you can only "change" food values for your character by manually applying the altered effects AFTER the original food values have been applied by the game. This was because the "oneat" event is called on the character AFTER the original effects have already been added, and I did not see a way to affect this before that happened. But I completely missed this "new" way of changing the food values BEFORE the original food values are applied, so sorry to those people I have given more difficult solutions in the past.

Again, we alter a function on the entity, but his time it's the Eat function on the eater component. We intercept the eater as they are eating the food, see if we have a set of special stats we would like this food to have for our character, and if we do, we change the food to have these stats, then eat it, and then revert the food stats back to normal immediately afterwards. Note that the values you enter REPLACE the value given by the food, so they are not an ADDITION. The values you enter, are the values your character will be given from eating that food.

Spoiler

-- food_values is a dictionary with food prefab names as keys and stat-dictionaries as values.
-- The stat-dictionaries have stat-names as keys and the effect on each stat as values.
-- If you want a stat not to be affected, you can just omit it from the stat-dictionary.
local food_stat_dict = {
	twigs = { sanity = 10 },
	monsterlasagna = { health = 25, sanity = -10, hunger = -10 },
	fishsticks = { health = 20, sanity = 20, hunger = 20 },
}

-- This is the ONLY function you should be making changes to.
local calculateFoodValues(food)
	-- We want the caller of this function to be told whether our code made changes to the food.
	-- Therefore, we also send back a bool, called changesweremade, which we set to true if we change anything.
	-- In this case it's very simple. If we find the food in our food_stats, we will make changes to it.
	local changesweremade = false
	
	-- Local variables to hold our food values.
	local healthval, hungerval, sanityval = 0, 0, 0
	
	---------- ONLY EDIT BELOW THIS LINE ----------
	
	-- HERE you make your changes to the values, if you want to affect this particular food.
	
	-- For this example, we will make use of our food_stat_dict to determine whether to change the food,
	-- and what to change its values to.
	
	-- We look up the food values in our dictionary, using the prefab-variable (prefab identifier)
	-- of the item we are eating.
	local food_stats = food_stat_dict[food.prefab]
	
	-- If we found an entry in our food_stat_dict dictionary for the food...
	if food_stats ~= nil then
		-- We indicate that we made changes to the food.
		changesweremade = true
		
		-- Then set our values to whatever is set for them in the dictionary,
		-- and default to 0 if no value is given for the stat.
		healthval = food_stats["health"] or 0
		hungerval = food_stats["hunger"] or 0
		sanityval = food_stats["sanity"] or 0
	end
	
	---------- ONLY EDIT ABOVE THIS LINE ----------
	
	-- Return the results.
	return changesweremade, healthval, hungerval, sanityval
end

local old_Eat = inst.components.eater.Eat
inst.components.eater.Eat =  function(self, food)
	-- Make a local variable holding the edible component of the food (optimization).
	local edible_comp = food.components.edible

	-- Make a local variable saying whether we made changes to the food.
	local changesweremade = false
	
	-- If the food has an edible component...
	if edible_comp then
		-- Local variables to hold the new food values.
		local healthval, hungerval, sanityval
		
		-- Calculate the food values, and let us know if changes were made to them.
		changesweremade, healthval, hungerval, sanityval = calculateFoodValues(food)
		
		if changesweremade then
			-- We first save the original food values, since we want to reset them after changing them temporarily for our character.
			edible_comp.originalhealthvalue = edible_comp.healthvalue
			edible_comp.originalhungervalue = edible_comp.hungervalue
			edible_comp.originalsanityvalue = edible_comp.sanityvalue
			
			-- We change the food to have our new stat values, and default to 0 if the stat was omitted from the dictionary entry.
			edible_comp.healthvalue = healthval
			edible_comp.hungervalue = hungerval
			edible_comp.sanityvalue = sanityval
		end
	end
	
	-- Call the original Eat function, while the food has our new values, and save the result in a variable.
	local returnvalue = old_Eat(self, food)
	
	-- If we made changes to the food, and the food is still valid (meaning it has not been destroyed
	-- because it was the last in the stack), and the edible component is still accessible...
	if food:IsValid() and changesweremade then
		-- We reset the food values after eating it.
		edible_comp.healthvalue = edible_comp.originalhealthvalue
		edible_comp.hungervalue = edible_comp.originalhungervalue
		edible_comp.sanityvalue = edible_comp.originalsanityvalue
		
		-- Remove the temporary values from the food to save memory.
		edible_comp.originalhealthvalue = nil
		edible_comp.originalhungervalue = nil
		edible_comp.originalsanityvalue = nil
	end
	
	-- Then we return the value returned by the original Eat function.
	return returnvalue
end

-- This last piece of code will make the food values display correctly when using the popular ShowMe mod.
inst.FoodValuesChanger = function(player, food)
	local changesweremade, healthval, hungerval, sanityval = calculateFoodValues(food)
	if changesweremade then
		return healthval, hungerval, sanityval
	end
	local e = food.components.edible
	return e.healthvalue, e.hungervalue, e.sanityvalue
end

 

 

Changing Food Values Based On Foodtype (CHARACTER)

In the code-snippet below, you will find an example which removes the positive sanity- and health-effects of all foods with the foodtype "VEGGIES", while still allowing negative effects of the foods to be applied. Again, we alter the Eat function on the eater component. We intercept the eater as they are eating the food, see if we have a set of special stats we would like this food to have for our character, and if we do, we change the food to have these stats, then eat it, and then revert the food stats back to normal immediately afterwards. Note that the values you enter REPLACE the value given by the food, so they are not an ADDITION. The values you enter, are the values your character will be given from eating that food.

Spoiler

-- This is the ONLY function you should be making changes to.
local calculateFoodValues(player, food)
	-- We want the caller of this function to be told whether our code made changes to the food.
	-- Therefore, we also send back a bool, called changesweremade, which we set to true if we change anything.
	-- In this case it's very simple. If we find the food in our food_stats, we will make changes to it.
	local changesweremade = false
	
	-- Local variables to hold our food values.
	local healthval, hungerval, sanityval = 0, 0, 0
	
	---------- ONLY EDIT BELOW THIS LINE ----------
	
	-- HERE you calculate the values you want this food to have.
	
	-- In this example, we just want to remove any beneficial(!) health- and sanity-effects of VEGGIEs.
	
	local edible_comp = food.components.edible
	
	if edible_comp and edible_comp.foodtype == "VEGGIE" then
		changesweremade = true
		
		-- We just want to remove any beneficial health- and sanity-effects of VEGGIES,
		-- so, e.g., if healthvalue is negative, we will still use it. Otherwise, we make it 0.
		healthval = edible_comp.healthvalue < 0 and edible_comp.healthvalue or 0
		sanityval = edible_comp.sanityvalue < 0 and edible_comp.sanityvalue or 0
		
		-- We don't want to affect hunger, so we just set our hungerval to the original value.
		hungerval = edible_comp.hungervalue
	end
	
	---------- ONLY EDIT ABOVE THIS LINE ----------
	
	-- Return the results.
	return changesweremade, healthval, hungerval, sanityval
end

local old_Eat = inst.components.eater.Eat
inst.components.eater.Eat =  function(self, food)
	-- Make a local variable holding the edible component of the food (optimization).
	local edible_comp = food.components.edible

	-- Make a local variable saying whether we made changes to the food.
	local changesweremade = false
	
	-- If the food has an edible component...
	if edible_comp then
		-- Local variables to hold the new food values.
		local healthval, hungerval, sanityval
		
		-- Calculate the food values, and let us know if changes were made to them.
		changesweremade, healthval, hungerval, sanityval = calculateFoodValues(self.inst, food)
		
		if changesweremade then
			-- We first save the original food values, since we want to reset them after changing them temporarily for our character.
			edible_comp.originalhealthvalue = edible_comp.healthvalue
			edible_comp.originalhungervalue = edible_comp.hungervalue
			edible_comp.originalsanityvalue = edible_comp.sanityvalue
			
			-- We change the food to have our new stat values, and default to 0 if the stat was omitted from the dictionary entry.
			edible_comp.healthvalue = healthval
			edible_comp.hungervalue = hungerval
			edible_comp.sanityvalue = sanityval
		end
	end
	
	-- Call the original Eat function, while the food has our new values, and save the result in a variable.
	local returnvalue = old_Eat(self, food)
	
	-- If we made changes to the food, and the food is still valid (meaning it has not been destroyed
	-- because it was the last in the stack), and the edible component is still accessible...
	if food:IsValid() and changesweremade then
		-- We reset the food values after eating it.
		edible_comp.healthvalue = edible_comp.originalhealthvalue
		edible_comp.hungervalue = edible_comp.originalhungervalue
		edible_comp.sanityvalue = edible_comp.originalsanityvalue
		
		-- Remove the temporary values from the food to save memory.
		edible_comp.originalhealthvalue = nil
		edible_comp.originalhungervalue = nil
		edible_comp.originalsanityvalue = nil
	end
	
	-- Then we return the value returned by the original Eat function.
	return returnvalue
end

-- This last piece of code will make the food values display correctly when using the popular ShowMe mod.
inst.FoodValuesChanger = function(player, food)
	local changesweremade, healthval, hungerval, sanityval = calculateFoodValues(food)
	if changesweremade then
		return healthval, hungerval, sanityval
	end
	local e = food.components.edible
	return e.healthvalue, e.hungervalue, e.sanityvalue
end

 

 

Changing Food Values (FOOD)

Changing the stats given by a particular food to all entities eating it is pretty straight-forward. You can set the values to whatever you want. NOTE that if you use this code-snippet to change several foods by using the Applying THE SAME Values To Several Food Entities approach at the top of this post, all the foods in your list will get the same values, which is usually not what you want. See the Applying DIFFERENT Values To Several Food Entities approach above for how to set different food values for many foods at once.

Spoiler

inst.components.edible.healthvalue = 10
inst.components.edible.hungervalue = 20
inst.components.edible.sanityvalue = 5

 

 

I hope this helps some people :)

Edited by Ultroman
Made changes to make the code simpler to use
  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites
Serpens    516

If you want the popular "Show Me" mod to also show your character specific food values (eg only your character gets +10 sanity from every food), you should do this:
https://steamcommunity.com/workshop/filedetails/discussion/666155465/133256080235167618/#c133256080239418690

So put this into your masterpostinit or into the code Ultroman wrote above "applying changes to .. character". inst and player are both your character in the following code:

inst.FoodValuesChanger = function(player, food)
    local e = food.components.edible
    return e.healthvalue, e.hungervalue, (e.sanityvalue + 10)
end

this will add 10 sanity to the DISPLAYED value from Show Me mod for every food your character mouses over. It does not change the actual sanity value, only the display.
As you see, you have "food" in this code, so of course you can also add a check for specific food, like - if food.prefab=="fishsticks" then - or other checks, to only apply this to specific foods.

(this code was added by star/maris to the Show Me mod, so it wont work by default for other mods like Item Info. You have to ask the author of Item Info, if he added or can add a similar way to show custom food values)

Share this post


Link to post
Share on other sites
Ultroman    537
42 minutes ago, Serpens said:

If you want the popular "Show Me" mod to also show your character specific food values (eg only your character gets +10 sanity from every food), you should do this

I will amend the code above to reflect this later today. Thanks! I LOVE ShowMe!

Share this post


Link to post
Share on other sites
Ultroman    537
9 hours ago, Serpens said:

blabla

Hey Serpens. Do you use the DoFoodEffects function to determine what values a food yields for a player? Its use in the game code would suggest you should, if you don't.

Share this post


Link to post
Share on other sites
Serpens    516
1 minute ago, Ultroman said:

Hey Serpens. Do you use the DoFoodEffects function to determine what values a food yields for a player? Its use in the game code would suggest you should, if you don't.

for what purpose? Are you reffering to the ShowMe mod and how it gets the health/hunger/sanity stats?

Share this post


Link to post
Share on other sites
Ultroman    537
2 minutes ago, Serpens said:

for what purpose? Are you reffering to the ShowMe mod and how it gets the health/hunger/sanity stats?

Yes. It is used in the Eat function of the eater component. When figuring out whether to apply a negative food value, it first checks with DoFoodEffects. The standard version of that function returns false if the food is "monstermeat" and the eater does not have the "strongstomach" tag. If your code does not use DoFoodEffects (or a similar check) then characters with the "strongstomach" tag (and whatever else other mods have extended this check-function with) will still see foods like "monstermeat" as giving them negative effects, when in fact they don't.

You can see in my code snippet above how I made some foods not have negative effects using DoFoodEffects.

Share this post


Link to post
Share on other sites
Serpens    516
8 minutes ago, Ultroman said:

Yes. It is used in the Eat function of the eater component. When figuring out whether to apply a negative food value, it first checks with DoFoodEffects. The standard version of that function returns false if the food is "monstermeat" and the eater does not have the "strongstomach" tag. If your code does not use DoFoodEffects (or a similar check) then characters with the "strongstomach" tag (and whatever else other mods have extended this check-function with) will still see foods like "monstermeat" as giving them negative effects, when in fact they don't.

not sure if stars version already shows character specific values (beside the FoodValueChanger).
I did not change his code regarding this, I only enabled it on my version.

Just took a look at it, and it seems the the FoodValueChanger code above (from his modpage) is not perfect.
The ShowMe mod has 2 possible food values it shows, depending of the settings: Show always the food value of fresh food or use the GetXY() function from edible component to show values based on current freshness.  The FoodValueChanger code above uses directly the hungervalue and so on, so this only works for the "only fresh" setting. While it will show nonsense if the user has the should_Estimate_Stale setting enabled.

And now to your question:
No, the DoFoodEffects function is currently not used within ShowMe. But at least as soon as star decides to also show character specific values (like for warly or wortox), it should use that function.  So I will add it to my version and notify @Maris

So summary for maris:
1) How is FoodValueChanger supposed to work depending on the should_Estimate_Stale true/false?
2) DoFoodEffects should be added to calcualtion, as soon as you decide to also show character specific values.
3) FoodValueChanger is already some kind of character specifc. So it is not consequent to have this one enabled, while other things are disabled :D

  • Like 1

Share this post


Link to post
Share on other sites
drakkigirl    0

You outlined how to make an edible item inedible, but what if you wanted to do the reverse and make an inedible item edible for a character?  Like bone shards, or nightmare fuel?

Share this post


Link to post
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