Jump to content

[Character Mod] How to make a character slow in the rain? (DST)


Recommended Posts

Hiya! 

I am looking to create a character that moves at half speed in the rain, but can also move at normal speed if they are holding an umbrella.

I have written up an example piece of code which will be below:

-- Checks if an umbrella is equipped    


    local umbrella_equipped = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v.components.dapperness then
            if v.components.dapperness.mitigates_rain then
                umbrella_equipped = true
            end
        end        
    end


-- Slows down the character if it is raining and there isn't an umbrella equipped

    if TheWorld.state.israining() = true and umbrella_equipped = false then
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 0.5)
    else
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 1)
    end

(I removed the character's name as this is a personal mod)

I am very new to coding so I am not sure what it is I am doing wrong. 

A lot of my coding knowledge comes from snooping around in the game's files so I apologize if this makes you cringe.

The example code below gives me the following error

Image

(once again the name has been removed for personal reasons)

Please Note: I am using Don't Starve Together

[[Also an optional thing that isn't really a priority is if you are offering help, could you also try making the character get progressively slower the more damp they become?]]

 

EDIT: I am aware of the existance of 

but it does not work with Don't Starve Together

Edited by Midnight_Monday
Link to comment
Share on other sites

it's complaining about this line of code:

 if TheWorld.state.israining() = true and mitigates_rain = false then

When comparing conditional checks you need to ensure you use the "==" comparison statement, It's an easy enough thing to overlook but remember when you state "=" you are assigning a value to a variable, if you are using ==, >=, <=, ~= then you are using a conditional check.

 

-- Ternary function: Handy tip for conditional checking for variable results.

If you ever test with print() on a "true" or "false" statement then be aware that print() will unlikely be able to print it out the boolean result, ternary functions can help you here.

Anything after the "AND" statement is what happens when the condition is successfully triggered, this can include returning a result based on the condition or even triggering a specific function.

(pseudo code) :

-- logic examples
var = (statement) AND "True string or value" OR "False string or value".
print( (statement) AND "True string or value" OR "False string or value" )

-- code examples
print ( true AND "It's true" OR nil) -- will print: "It's true" in the DST console

my_int = 1
my_bool = my_int < 1 AND true OR false -- value of my_bool would be set 
                   			-- false unless my_int reflects the initial condition differently

 

Edited by MorickClive
Link to comment
Share on other sites

Change

if TheWorld.state.israining() = true and umbrella_equipped = false then

to

if TheWorld.state.israining and not umbrella_equipped then

Checking every tenth of a second seems overkill, you could probably tune it to every second or two.

Link to comment
Share on other sites

I want to thank the both of you, I could make my character slow down in the rain like I wanted! 

I also was able to understand coding a bit better, so thank you for that as well.

 

If it's not too much to ask, I could you help me figure out why the character's speed does not return to normal when an umbrella is equipped?

I know it's just me being a doof but I would prefer the help of someone experienced then going through hours of trial and error by myself. Not much has changed in the code but if you need another look then here you go:

-- Slows character in the rain
local function slowinrain(inst)
    --this bit will let you move freely with an umbrella
    local mitigates_rain = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v.components.dapperness then
            if v.components.dapperness.mitigates_rain then
                mitigates_rain = true
            end
        end        
    end
	
    --this will check if it is raining and either set your speed to normal if it is not, or reduce it to half if it is raining
    if TheWorld.state.israining == true and mitigates_rain == false then
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 0.5)
    else
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 1)
    end
    
end

 

I feel like I'm going to be frequent on these forums now...

Link to comment
Share on other sites

dapperness is probably not what you are looking for and I don't find any reference to mitigates_rain in the game code, so that probably isn't it either. You can either check if the player is wearing an item that has rain protection (this includes a lot of headgear, Raincoat, Umbrella, etc.):

    local mitigates_rain = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v:HasTag("waterproofer") then
            mitigates_rain = true
        end
    end

or check whether it's an "umbrella" (Umbrella, Pretty Parasol and Eyebrella):

    local mitigates_rain = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v:HasTag("umbrella") then
            mitigates_rain = true
        end
    end

or check directly if it's the specific item you want, in this case an Umbrella or a Pretty Parasol:

    local mitigates_rain = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v.prefab == "umbrella" or v.prefab == "grass_umbrella" then
            mitigates_rain = true
        end
    end

All code untested, but hopefully at least enough to nudge you in the right direction.

Link to comment
Share on other sites

Thank you so very much, this now completely works as planned! 

This has been very helpful to me in terms of coding, but there are a few more things planned for this character that I don't know how to do for sure.

You see I would like for the character to be able to heal themselves using sewing kits (and nothing else, I also want healing salves and even food if possible to do nothing health-wise) and I believe I did it correctly but i'm not sure.

 

Here is the code if you need it:

--character.lua:




local assets = {
    Asset("SCRIPT", "scripts/prefabs/player_common.lua"),
	Asset("SCRIPT", "scripts/components/sewinghealing.lua"),
	Asset( "ANIM", "anim/character.zip" ),
    Asset( "SOUNDPACKAGE", "sound/character.fev" ),
	Asset( "SOUND", "sound/character.fsb" ),
}







--Sewinghealing.lua:



local SewingHealer = Class(function(self, inst)
    self.inst = inst
    self.health = TUNING.HEALING_LARGE
end)

function SewingHealer:SetHealthAmount(health)
    self.health = health
end

function SewingHealer:CollectInventoryActions(doer, actions)
    if doer.components.health and doer.prefab == "character" then
        table.insert(actions, ACTIONS.SEWINGHEAL)
    end
end

function SewingHealer:Heal(target)
    if target.components.health then
        target.components.health:DoDelta(self.health,false,self.inst.prefab)
        if self.inst.components.stackable and self.inst.components.stackable.stacksize > 1 then
            self.inst.components.stackable:Get():Remove()
        else
            self.inst:Remove()
        end
        return true
    end
end

return SewingHealer









--Modmain.lua:

--Makes Character able to heal herself with Sewing Kits

AddPrefabPostInit("sewing_kit", function(inst)
    inst:AddComponent("sewinghealing")
    	inst:AddTag("irreplaceable")
end)

local SEWINGHEAL = GLOBAL.Action()
SEWINGHEAL.fn = function(act)
    if act.invobject and act.invobject.components.sewinghealing then
        local target = act.target or act.doer
        return act.invobject.components.sewinghealing:Heal(target)
    end
end
SEWINGHEAL.str = "Sew-heal"
SEWINGHEAL.id = "SEWINGHEAL"

AddAction(SEWINGHEAL)
AddStategraphActionHandler("wilson", GLOBAL.ActionHandler(SEWINGHEAL, "dolongaction"))

 And by extension the character should also start off with the ability to create a Sewing Kit without an Alchemy Engine

 

 

(I also wanted to say sorry for my huge noobish-ness, I might be way in over my head with this mod but if I can get this done I'll only have 2 things left to create!)

Link to comment
Share on other sites

2 hours ago, Midnight_Monday said:

You see I would like for the character to be able to heal themselves using sewing kits (and nothing else, I also want healing salves and even food if possible to do nothing health-wise) and I believe I did it correctly but i'm not sure.

Try it out! I don't see why it wouldn't work. Minor correction:

AddPrefabPostInit("sewing_kit", function(inst)
    inst:AddTag("irreplaceable")
    if not GLOBAL.TheWorld.ismastersim then
        return
    end
    inst:AddComponent("sewinghealing")
end)

Most components are only defined and executed server-side, but tags should be added to clients too. I'm not sure why you'd want Sewing Kits to be irreplaceable, I'm guessing maybe you intend to later change it to a character-exclusive item? Not that it makes a great difference, though.

If you want your character to not be healed by healing items, you can try adding something like this to your modmain.lua:

local function healerpostinit(self)
	local OldHeal = self.Heal
	self.Heal = function(self, target)
		if target.prefab ~= "yourcharacter" then
			return OldHeal(self, target)
		end
	end
end

AddComponentPostInit("healer", healerpostinit)

And similarly for edibles:

local function ediblepostinit(self)
	local OldGetHealth = self.GetHealth
	self.GetHealth = function(self, eater)
		local health = OldGetHealth(self, eater)
		return eater.prefab == "yourcharacter" and health > 0 and 0 or health
	end
end

AddComponentPostInit("edible", ediblepostinit)

This will make your character receive health penalties from food, but gain no bonuses. If you want your character to completely ignore health deltas from food items, use the Eater component's SetAbsorptionModifiers in your character's master_postinit:

inst.components.eater:SetAbsorptionModifiers(0, 1, 1)

Usual disclaimer: all code untested, I'm not responsible if your PC threatens to burn your house, or marries or refuses to marry your cat.

Link to comment
Share on other sites

The code works just as it should for disabling alternative sources of health gain, but now my computer is on a honeymoon with a cat I didn't even know I had.

 

In all seriousness, I have yet another dilemma (I'm sorry for using you as a solution to all of my inadequacies) when I try and heal the character using a sewing kit the option just doesn't appear. Like how when you are using Healing Salves you can see an option for right-clicking to heal yourself with, however this option doesn't appear on the Sewing Kit and only has "Examine"

 

EDIT: I also attempted copying the script in the character.lua as well as the modmain.lua but it gave me an error that the coding wasn't valid to be in the character.lua file.

Edited by Midnight_Monday
Link to comment
Share on other sites

Add this towards the end of your modmain.lua:

AddComponentAction("USEITEM", "sewinghealing", function(inst, doer, target, actions, right)
	if doer.prefab == "yourcharacter" and target.replica.health ~= nil and target.replica.health:CanHeal() then
		table.insert(actions, SEWINGHEAL)
	end
end)

AddComponentAction("INVENTORY", "sewinghealing", function(inst, doer, actions, right)
	if doer.prefab == "yourcharacter" then
		table.insert(actions, SEWINGHEAL)
	end
end)

Note: I don't have any experience with adding new actions and again, untested code, so this is mostly guesswork from briefly looking at other mods.

Explanation on what's going on/what I expect to happen: AddComponentAction does what it says (see data/scripts/componentactions.lua - you are actually calling AddComponentAction from data/scripts/modutil.lua but it just calls the one from componentactions.lua along with the modname). Its first argument is the actiontype: "USEITEM" lets you use the item directly on something, "INVENTORY" lets you use it from your inventory, usually on yourself (again, see componentactions.lua). The second argument is the component that carries your action. The last argument should be a function to determine whether the action can be performed.

As an example, I've added a reasonable restriction: you only want your character to be able to heal with a Sewing Kit, and you only want it to be used on things that can be healed. CanHeal is used to avoid healing entities with health that shouldn't be healed normally (like walls). Notice that I'm using target.replica.health instead of target.components.health here: this is because, as noted before, components are usually handled by the server only. Replicas are handled client-side and contain only enough information for the client to work on its own. This is so clients can see whether the action can be performed. If the player chooses to perform an action, this is sent to the server where it is actually executed.

Good luck!

Link to comment
Share on other sites

@Midnight_Monday Hi! Thanks for sharing your code (for walking slower in the rain) but I can't seem to make it work, I don't see any difference for my character. How did you get it to work? Can you help me?

local function slowinrain(inst)
    --this bit will let you move freely with an umbrella
    local mitigates_rain = false
    for k,v in pairs (inst.components.inventory.equipslots) do
        if v:HasTag("waterproofer") then
            mitigates_rain = true
        end
    end
	
    --this will check if it is raining and either set your speed to normal if it is not, or reduce it to half if it is raining
    if TheWorld.state.israining == true and mitigates_rain == false then
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 0.25)
    else
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "character_speed_mod", 1)
    end
    
end

 

Link to comment
Share on other sites

@kaachiel It's really not anything that you did wrong, it's just something that wasn't in the code to begin with.

You see in the master_postinit section of your character.lua you have to add a line of coding in somewhere below the hunger rate option. Here is an example

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
	-- choose which sounds this character will play
	inst.soundsname = "wilson"	
    
	
	-- Stats	
	inst.components.health:SetMaxHealth(200)
	inst.components.hunger:SetMax(150)
	inst.components.sanity:SetMax(150)
	
	-- Damage multiplier (optional)
    inst.components.combat.damagemultiplier = 1
	
	-- Hunger rate (optional)
	inst.components.hunger.hungerrate = 1 * TUNING.WILSON_HUNGER_RATE
	inst:DoPeriodicTask(1/10, function() slowinrain(inst) end)
	
	inst.OnLoad = onload
    inst.OnNewSpawn = onload
	
end

.

The code you are missing is:

inst:DoPeriodicTask(1/10, function() slowinrain(inst) end)

 

That will check every 1/10th of a second for the slowinrain function. (Checks if it's raining, and if they have an umbrella equipped) 

On 6/13/2017 at 0:43 PM, alainmcd said:

Checking every tenth of a second seems overkill, you could probably tune it to every second or two.

but as alainmcd said, you can adjust this number to whatever you think fits.

Running constant checks in the background might slow the game down for anybody not playing as a host, but this all relies on the person's internet in general. But it is something to keep in mind.

Edited by Midnight_Monday
Link to comment
Share on other sites

@Midnight_Monday, I just saw you asked about knowing how to craft Sewing Kits from the get-go (and I ignored you - sorry!). Try this in your master_postinit:

inst.components.builder:UnlockRecipe("sewing_kit")

Sometimes the game doesn't like performing certain tasks during initialisation because not everything is properly set up yet. The usual workaround is to tell the game to perform the task in the next frame. Try this instead if the game crashes:

inst:DoTaskInTime(0, function(inst)
	inst.components.builder:UnlockRecipe("sewing_kit")
end)

Usual disclaimer: untested. What could go wrong?

Btw, did you get your new actions to work properly?

Link to comment
Share on other sites

Don't worry it's fine, everything is working exactly as it should now thanks to you! You've been a big help, now there's one final thing to add to my character mod before they can finally be put to rest.

I am trying to add a custom (and character specific) crafting tab, but i'm not sure how to go about it. From looking at Wickerbottom's files I know that you have to give them a tag to gain access to the tab, and I've established prefabs (of course not in the code as it would cause a game crash)

I'm using the mod Camp Cuisine as reference, but I don't want to do anything too major based off of that mod as the version I have is from DS and not DST.

I'm not looking to add anything fancy into this tab, in fact everything my character should be able to craft will just be weaker versions of pre-existing items in the game, if you need specifics. 

 

A Walking Cane  [50% less durability than a normal walking cane]

A Craftable Touch Stone [Naturally, very expensive.]

A Lazy Forager [But a hand-held version if possible]

A Pig Helmet [25% less durability than a normal helmet]

A Lantern  [50% less durability than a normal lantern]

A Blow Dart  [ 1 time use, and half the strength.]

A Stronger Blow Dart [ 1 time use, and a little less than the same strength as a normal blow dart]

A Slot Machine [has a higher chance of giving you better items the more health you sacrifice]

 

The Crafting Tab's name is going to be Powerup's and I have recipes in mind for each item, but they might require fine tuning.

If there is anything you need me to elaborate on just ask, all Art of course is going to be handled by me, it's the one thing I know how to do.

 

 

Edited by Midnight_Monday
Link to comment
Share on other sites

So...

Spoiler

local Ingredient = GLOBAL.Ingredient

local myrecipetab = AddRecipeTab(
	"MYRECIPETAB",			--tab name
	999,				--sort: force to last pos
	"images/hub/mytab_atlas.xml",	--atlas: place your atlas here
	"mytab_icon.tex",		--icon: place it next to your atlas
	"mycharacter",			--owner_tag
	,--unused			--crafting_station: only used by special crafting stations like sculpting
)


AddRecipe(
	"weakcane",			--name
	{Ingredient("gold", 3), Ingredient("twigs", 5)},
					--ingredients
	myrecipetab,			--tab: neat trick from SageOfLegend's Wynn mod
	GLOBAL.TECH.NONE,		--level: can always be crafted
	nil,				--placer
	nil,				--min_spacing
	nil,				--nounlock
	nil,				--numtogive: defaults to 1 if nil
	nil,				--builder_tag: not needed since the tab is already character-restricted
	"images/inventoryimages/weakcane.xml",	--atlas
	,				--image: leave blank and place your "weakcane.tex" next to its atlas
	,				--testfn: not needed
)

AddRecipe(
	"weakdart",			--name
	{Ingredient("gold", 3), Ingredient("feather_canary", 5)},
					--ingredients
	myrecipetab,			--tab: neat trick from SageOfLegend's Wynn mod
	GLOBAL.TECH.MAGIC_TWO,		--level: magic required
	nil,				--placer
	nil,				--min_spacing
	true,				--nounlock: can't be learned if nounlock is set to true
	3,				--numtogive: defaults to 1 if nil
	nil,				--builder_tag: not needed since the tab is already character-restricted
	"images/inventoryimages/weakdart.xml",	--atlas
	,				--image: leave blank and place your "weakdart.tex" next to its atlas
	,				--testfn: not needed
)

AddRecipe(
	"resurrectionstone",		--name
	{Ingredient(GLOBAL.CHARACTER_INGREDIENT.HEALTH, 50), Ingredient(GLOBAL.CHARACTER_INGREDIENT.SANITY, 100), Ingredient("rocks"), 100},
					--ingredients: special ingredients CHARACTER_INGREDIENTS
	myrecipetab,			--tab: neat trick from SageOfLegend's Wynn mod
	GLOBAL.TECH.MAGIC_THREE,	--level: magic required
	"resurrectionstone_placer",	--placer: you need to create a placer
	6.4,				--min_spacing: defaults to 3.2 if nil
	nil,				--nounlock
	nil,				--numtogive
	nil,				--builder_tag: not needed since the tab is already character-restricted
	"images/inventoryimages/resurrectionstone.xml",	--atlas
	,				--image: leave blank and place your "resurrectionstone.tex" next to its atlas
	,				--testfn: not needed
)

 

Lots of placeholder-y code and more illustrative than game-intented, but most of it should be understandable. The recipe tab is fairly straightforward, only to note that you don't need to declare a variable for it, but it makes things much simpler when adding recibes to the tab, as commented.

As for recipes:

  • name: The recipe's name must be the same as the prefab to craft.
  • ingredients: As a rule, recipes only require three different ingredient types. I'm not sure if this is a hard limit.
  • special ingredients: There are four CHARACTER_INGREDIENT's, namely HEALTH, MAX_HEALTH, SANITY and MAX_SANITY. All of them must be used in multiples of 5. HEALTH and SANITY apply a simple stat penalty, whereas MAX_HEALTH and MAX_SANITY place a cap on the stat (Maxwell's minions, Resurrection Statues in single player). I strongly recommend you avoid the MAX_ special ingredients, since you'd then want to add somewhere else some code to remove those caps.
  • tab: as noted, use the local variable to your tab.
  • placer: A prefab to act as a placer for structures. More below.
  • min_spacing: Craftable structures can't be placed within a certain radius of any other structure or mob. Default value is 3.2. For reference, a floor tile's length is 4 units.
  • nounlock: A character won't learn the recipe if set to true.
  • numtogive: 1 if nil.
  • builder_tag: Restrict the recipe to characters with a certain tag. The whole tab is already restricted, so no need.
  • atlas: A regular atlas file.
  • Your image HAS to be named prefabtocraft.tex and be placed alongside its atlas.
  • testfn: Don't use.

Placer: You need a prefab to act as a placer for your structure. Since the Resurrection Stone doesn't have a placer prefab, you need to create one. You can probably get away with something extremely simple: create a prefabfile (e.g. resurrectionstone_placer.lua in scripts/prefabs) with this code:

return MakePlacer("resurrectionstone_placer", "resurrectionstone", "resurrectionstone", "idle_activate")

And, hopefully, it'll work. I looked at resurrectionstatue.lua, which does have a placer. The only mention of it is the very last line, it doesn't reference any assets. The MakePlacer function is defined in scripts/prefabutil.lua and seems clear enough. So that should take care of itself.

And... that's it? If it works, obviously.

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