Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

Sukoushi

[Character] Wren The Stormcaller

Recommended Posts

Sukoushi    192

post-262695-0-13416800-1378845447.png

post-262695-0-53268700-1378845434_thumb.

 
[A Moderately Friendly Update] version.
Download Here
Also available on the Steam Workshop!
 
Stats:
  • Health - 200
  • Hunger - 100
  • Sanity - 150

 

Features:

  • Hits like a wet noodle, but moves around a bit faster to compensate!
  • Has an affinity for the rain, gaining sanity when it falls.
    • If sanity gets too low, rain will start falling in an attempt to recuperate.
    • Frogs will fall from the sky if sanity is critically low.
  • Gets hungry quickly, and is picky about the foods she eats and the state they're in.
    • Some foods, such as monster-variety meat, she just won't eat.
    • If the food is stale or spoiled, it suffers a penalty.
  • Has a phobia of bugs. Bees, killer bees, and mosquitoes now give off an insanity aura.

----------------------

 

New in version 1.1:

  • Nightvision has been removed.
  • Upon request, a new craftable lightning staff!
    • You can now strike your foes with lightning! -15 sanity and -10% durability per attack.
    • Doubles as a torch! Has a passive -1% durability loss per tick when equipped.
    • Will be found under the Magic tab, craftable with a Prestihatitator. Requires Nightmare Fuel x4, Living Logs x2, and Gold x3.
  • When Wren's insane and is hit by an enemy, lightning will strike back at them. This will hit 1 enemy and have a 1-minute cooldown.

 

** Bugs or crashes? **

  • State what you were doing at the time of the crash. Be as descriptive as possible!
  • See if you can find the error in your log.txt in Documents/Klei/DoNotStarve/
  • If you cannot identify the error, please either attach your log.txt directly to your post or use spoiler tags!

 

Special Thanks:

  • debugman18
  • simplex
  • Heavenfall
  • Psycosis
  • DaVinci557

 

Feel free to leave suggestions!

Share this post


Link to post
Share on other sites
simplex    2,608

Very nice work! :grin:

The art is very well done. And, on top of that, you were very picky about getting the code to do just what you wanted.

Share this post


Link to post
Share on other sites
Lauri455    11

Art style is matching the game perfectly, interesting unique trait and overall good attempt at balance. The best custom character so far.

Well done.

9/10

 

Would be 10/10 with custom counds :encouragement:

Share this post


Link to post
Share on other sites
Sukoushi    192

Thanks for the kind words, guys. c:

 

Based upon a suggestion made over on the Steam Workshop, I've been working on making it so lightning will strike hostiles, but I've kind of hit a little bump in the road when it comes to limiting it so it only happens once every minute.

 

This is what I've got so far:

inst:ListenForEvent("attacked", function(inst,data)	local pt = data.attacker:GetPosition()	GetSeasonManager():DoLightningStrike(pt)	data.attacker.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")end)

I'm aware of inst:DoPeriodicTask(), but I'm afraid I don't know exactly how it works when trying to time things. Can anyone give me a little bit of insight?

Share this post


Link to post
Share on other sites
simplex    2,608

@Sukoushi

This will strike an attacker at most once per minute:

do	local laststrike = GLOBAL.GetTime() - 60	inst:ListenForEvent("attacked", function(inst,data)		local now = GLOBAL.GetTime()		if laststrike <= now - 60 then			local pt = data.attacker:GetPosition()			GetSeasonManager():DoLightningStrike(pt)			data.attacker.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")						laststrike = now		end	end)end
This will do the same, but using tasks:

do	local can_strike = true	inst:ListenForEvent("attacked", function(inst,data)		if can_strike then			local pt = data.attacker:GetPosition()			GetSeasonManager():DoLightningStrike(pt)			data.attacker.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")						can_strike = false			inst:DoTaskInTime(60, function() can_strike = true end)		end	end)end
This will try to strike a random hostile creature, not only at the instant it attacks you:

inst:DoPeriodicTask(60, function(inst)	--[[	-- The 10 below is the search radius around Wren. Tweak according to your preference.	--]]	local target = GetRandomInstWithTag("hostile", inst, 10)	if target then		local pt = target:GetPosition()		GetSeasonManager():DoLightningStrike(pt)		target.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")	endend)
Finally, this will do the same as the previous one, except that it will only choose between hostiles currently targeting you:

inst:DoPeriodicTask(60, function(inst)	local x, y, z = inst:GetPosition():Get()		--[[	-- The 10 below is the search radius around Wren. Tweak according to your preference.	--]]	local aggressive_targets = {}	for _, v in ipairs(TheSim:FindEntities(x, y, z, 10, {"hostile"})) do		if v:IsValid() and v.components.combat and v.components.combat.target == inst then			table.insert(aggressive_targets, v)		end	end		if #aggressive_targets > 0 then		local target = aggressive_targets[math.random(#aggressive_targets)]		local pt = target:GetPosition()		GetSeasonManager():DoLightningStrike(pt)		target.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")	endend)

Share this post


Link to post
Share on other sites
Sukoushi    192

@simplex I just tested all of them out, and of the four, only the 2nd seems to work. I don't think I did anything wrong, these are supposed to go under fn, correct? The 1st block ends up in an error "variable 'GLOBAL' is not declared", the 3rd and 4th just seem to do nothing.

 

Also, I forgot to ask before, but is there a way that I could make it work within this code?

local function stormcaller(inst)	if inst.components.sanity.current <= 50 and not GetSeasonManager():IsRaining() then		GetSeasonManager():StartPrecip()		if inst.components.sanity.current <=30 then			TUNING.FROG_RAIN_MOISTURE = 3			TUNING.FROG_RAIN_PRECIPITATION = 0.01			--[[Lightning Code]]		end	end	if inst.components.sanity.current >= 75 and GetSeasonManager():IsRaining() and inst.rainstartedatsanity <= 50 then		GetSeasonManager():StopPrecip()	end	if not GetSeasonManager():IsRaining() then		TUNING.FROG_RAIN_PRECIPITATION = 999		TUNING.FROG_RAIN_MOISTURE = 999999	endend

I tried inserting the 2nd one, but it didn't do anything.

Share this post


Link to post
Share on other sites
simplex    2,608

@simplex I just tested all of them out, and of the four, only the 2nd seems to work. I don't think I did anything wrong, these are supposed to go under fn, correct? The 1st block ends up in an error "variable 'GLOBAL' is not declared", the 3rd and 4th just seem to do nothing.

 

Also, I forgot to ask before, but is there a way that I could make it work within this code?

local function stormcaller(inst)	if inst.components.sanity.current <= 50 and not GetSeasonManager():IsRaining() then		GetSeasonManager():StartPrecip()		if inst.components.sanity.current <=30 then			TUNING.FROG_RAIN_MOISTURE = 3			TUNING.FROG_RAIN_PRECIPITATION = 0.01			--[[Lightning Code]]		end	end	if inst.components.sanity.current >= 75 and GetSeasonManager():IsRaining() and inst.rainstartedatsanity <= 50 then		GetSeasonManager():StopPrecip()	end	if not GetSeasonManager():IsRaining() then		TUNING.FROG_RAIN_PRECIPITATION = 999		TUNING.FROG_RAIN_MOISTURE = 999999	endend

I tried inserting the 2nd one, but it didn't do anything.

@simplex I just tested all of them out, and of the four, only the 2nd seems to work. I don't think I did anything wrong, these are supposed to go under fn, correct? The 1st block ends up in an error "variable 'GLOBAL' is not declared", the 3rd and 4th just seem to do nothing.

 

Also, I forgot to ask before, but is there a way that I could make it work within this code?

local function stormcaller(inst)	if inst.components.sanity.current <= 50 and not GetSeasonManager():IsRaining() then		GetSeasonManager():StartPrecip()		if inst.components.sanity.current <=30 then			TUNING.FROG_RAIN_MOISTURE = 3			TUNING.FROG_RAIN_PRECIPITATION = 0.01			--[[Lightning Code]]		end	end	if inst.components.sanity.current >= 75 and GetSeasonManager():IsRaining() and inst.rainstartedatsanity <= 50 then		GetSeasonManager():StopPrecip()	end	if not GetSeasonManager():IsRaining() then		TUNING.FROG_RAIN_PRECIPITATION = 999		TUNING.FROG_RAIN_MOISTURE = 999999	endend

I tried inserting the 2nd one, but it didn't do anything.

Oh, I assumed you were doing it through modmain, my bad (it's more of a habit, there's no reason why you should put in it modmain instead). The second one really is the only that would work from anywhere. To get the others working, just erase the "GLOBAL." anywhere it appears.

But the lightning code really can't go in that particular spot, because you'll only enter that block when it's not raining, and as soon as you do you'll make rain start. And more generally, it really shouldn't go inside something that's periodically called. Put it directly inside the "fn".

You can get that sanity/rain check logic by tweaking them in this way:

do	local laststrike = GLOBAL.GetTime() - 60	inst:ListenForEvent("attacked", function(inst,data)		local now = GLOBAL.GetTime()		if inst.components.sanity.current <= 30 and GetSeasonManager():IsRaining() and laststrike <= now - 60 then			local pt = data.attacker:GetPosition()			GetSeasonManager():DoLightningStrike(pt)			data.attacker.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")						laststrike = now		end	end)end
do	local can_strike = true	inst:ListenForEvent("attacked", function(inst,data)		if inst.components.sanity.current <= 30 and GetSeasonManager():IsRaining() and can_strike then			local pt = data.attacker:GetPosition()			GetSeasonManager():DoLightningStrike(pt)			data.attacker.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")						can_strike = false			inst:DoTaskInTime(60, function() can_strike = true end)		end	end)end
inst:DoPeriodicTask(60, function(inst)	if not (inst.components.sanity.current <= 30 and GetSeasonManager():IsRaining()) then return end	--[[	-- The 10 below is the search radius around Wren. Tweak according to your preference.	--]]	local target = GetRandomInstWithTag("hostile", inst, 10)	if target then		local pt = target:GetPosition()		GetSeasonManager():DoLightningStrike(pt)		target.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")	endend)
inst:DoPeriodicTask(60, function(inst)	if not (inst.components.sanity.current <= 30 and GetSeasonManager():IsRaining()) then return end	local x, y, z = inst:GetPosition():Get()		--[[	-- The 10 below is the search radius around Wren. Tweak according to your preference.	--]]	local aggressive_targets = {}	for _, v in ipairs(TheSim:FindEntities(x, y, z, 10, {"hostile"})) do		if v:IsValid() and v.components.combat and v.components.combat.target == inst then			table.insert(aggressive_targets, v)		end	end		if #aggressive_targets > 0 then		local target = aggressive_targets[math.random(#aggressive_targets)]		local pt = target:GetPosition()		GetSeasonManager():DoLightningStrike(pt)		target.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")	endend)

Share this post


Link to post
Share on other sites
Sukoushi    192

To get the others working, just erase the "GLOBAL." anywhere it appears.

GLOBAL is only in the 1st, though, so that still leaves the 3rd and 4th in a non-working state (from what I've been able to tell, anyway) - which is a shame, really, since those two actually seem like the most interesting to me.

Share this post


Link to post
Share on other sites
simplex    2,608

Where in the code did you put them?

EDIT: Yes, I did make a mess by doing some of them modmain-like and others not, didn't I? :razz:

EDIT 2: Though the last two only check for a target every minute. Since a minute is quite a long time, that may cause many minutes to be lost just because at the instant the check was made there wasn't a viable target, but I can't "fix" it without knowing what's the precise effect you're after.

Share this post


Link to post
Share on other sites
Sukoushi    192

I put them inside fn.

 

Edit: Essentially, what I'm aiming to do is if sanity is <=30, then check if being attacked by something. If so, then have lightning strike at the enemy, with a cooldown of 1 minute before the next strike.

Share this post


Link to post
Share on other sites
simplex    2,608

I put them inside fn.

 

Edit: Essentially, what I'm aiming to do is if sanity is <=30, then check if being attacked by something. If so, then have lightning strike at the enemy, with a cooldown of 1 minute before the next strike.

Yes, but what do you mean with "when attacked"? A literal interpretation of that would be the first two codes. Since you prefered the last two, I'm not quite sure what you want as the trigger. Should the first strike happen when the enemy hits you (with following strikes happening as long as it's chasing you), should the first strike happen as soon as it starts chasing you, even if it doesn't reach you... ?

Share this post


Link to post
Share on other sites
simplex    2,608

All strikes should happen when the enemy hits you.

 

Well, then the first two are the ones doing that.

 

I'm not sure why you prefered the latter two, then. I can tweak the first two to include what you liked in the last ones, I just don't know what that is.

Share this post


Link to post
Share on other sites
Sukoushi    192

Well, then the first two are the ones doing that.

 

I'm not sure why you prefered the latter two, then. I can tweak the first two to include what you liked in the last ones, I just don't know what that is.

It was the search radius that interested me. But looking back on it now, I guess I misinterpreted what the last two codes did. Sorry for that. I'll stick to the first two, then. Thank you for your help. c:

 

(Ugh. Sleep deprivation screws with my thinking process more often than i'd like...)

Share this post


Link to post
Share on other sites
simplex    2,608

It was the search radius that interested me. But looking back on it now, I guess I misinterpreted what the last two codes did. Sorry for that. I'll stick to the first two, then. Thank you for your help. c:

 

(Ugh. Sleep deprivation screws with my thinking process more often than i'd like...)

No problem. The search radius was being used precisely because they weren't triggered on attack. When you have an attacker, no search is needed. But anyway, if you'd like something a bit different just let me know. [:

Share this post


Link to post
Share on other sites
Sukoushi    192

Ugh. So I some help again (shocking).

 

I'm trying to make a lightning staff that double functions as a torch when equipped. I want it to passively lose 1% durability per tick when functioning as a torch and 10% durability when using it for an attack. As of now it's max uses is 10.

 

I've kind of - and I say that loosely - got the passive -1% durability taken care of, but:

- Upon initial equip, it loses 3-4% durability right off the bat. (I figured this out. Was a silly mistake.)

- When attacking, it doesn't register a permanent* 10% durability loss. While looking at the durability, it flashes down -10%, but it regains it back almost instantly (I guess because the passive loss is overriding it.)


* Edit: Upon further testing, it actually does register as a permanent loss. The staff at the moment seems to have two separate durabilities: one as the torch, and one as a weapon. However, I want it to have just one combined durability.

 

This is what I've got so far:

local assets ={    Asset("ANIM", "anim/lightstaff.zip"),   	Asset("ANIM", "anim/swap_lightstaff.zip"),    Asset("ATLAS", "images/inventoryimages/lightstaff.xml")}local prefabs ={	"torchfire",}local function onattack_light(inst, attacker, target)    if attacker and attacker.components.sanity then        attacker.components.sanity:DoDelta(-TUNING.SANITY_MED)    end        if target.components.sleeper and target.components.sleeper:IsAsleep() then        target.components.sleeper:WakeUp()    end    if target.components.combat then        target.components.combat:SuggestTarget(attacker)        if target.sg and target.sg.sg.states.hit then            target.sg:GoToState("hit")        end    end		local pt = target:GetPosition()	GetSeasonManager():DoLightningStrike(pt)	target.SoundEmitter:PlaySound("dontstarve/rain/thunder_close")	endlocal function onfinished(inst)	inst.SoundEmitter:PlaySound("dontstarve/common/gem_shatter")    inst:Remove()endlocal function onequip(inst, owner)    owner.AnimState:OverrideSymbol("swap_object", "swap_lightstaff", "swap_nightmaresword")    owner.AnimState:Show("ARM_carry")     owner.AnimState:Hide("ARM_normal")		inst.components.burnable:Ignite()		inst.fire = SpawnPrefab( "torchfire" )	local follower = inst.fire.entity:AddFollower()    follower:FollowSymbol( owner.GUID, "swap_object", 5, -110, 1 )		--take a percent of fuel next frame instead of this one, so we can remove the torch properly if it runs out at that point	inst:DoTaskInTime(0, function() 	    if inst.components.fueled.currentfuel < inst.components.fueled.maxfuel then		    inst.components.fueled:DoDelta(-inst.components.fueled.maxfuel*.01)	    end	end)endlocal function onunequip(inst, owner) 	inst.fire:Remove()    inst.fire = nil	inst.components.burnable:Extinguish()    owner.AnimState:Hide("ARM_carry")     owner.AnimState:Show("ARM_normal")endlocal function fn(Sim)	local inst = CreateEntity()	local trans = inst.entity:AddTransform()	local anim = inst.entity:AddAnimState()	local sound = inst.entity:AddSoundEmitter()	MakeInventoryPhysics(inst)	 	anim:SetBank("nightmaresword")	anim:SetBuild("lightstaff")	anim:PlayAnimation("idle")	inst:AddComponent("weapon")    inst.components.weapon:SetDamage(0)    inst.components.weapon:SetRange(8, 10)	inst.components.weapon:SetOnAttack(onattack_light)		-------	inst:AddComponent("burnable")    inst.components.burnable.canlight = false    inst.components.burnable.fxprefab = nil	-------	inst:AddComponent("fueled")		inst.components.fueled:SetUpdateFn( function()		inst.components.fueled.rate = .1	end)		inst.components.fueled:SetSectionCallback(        function(section)            if section == 0 then                if inst.components.inventoryitem and inst.components.inventoryitem:IsHeld() then                    local owner = inst.components.inventoryitem.owner                    inst:Remove()                                        if owner then                        owner:PushEvent("torchranout", {torch = inst})                    end                end                            end        end)    inst.components.fueled:InitializeFuelLevel(TUNING.TORCH_FUEL)    inst.components.fueled:SetDepletedFn(function(inst) inst:Remove() end)	-------		inst:AddComponent("finiteuses")    inst.components.finiteuses:SetOnFinished( onfinished )	inst.components.finiteuses:SetMaxUses(10)    inst.components.finiteuses:SetUses(10)		inst:AddComponent("inspectable")	inst:AddComponent("inventoryitem")	inst.components.inventoryitem.atlasname = "images/inventoryimages/lightstaff.xml"	inst:AddComponent("equippable")	inst.components.equippable:SetOnEquip( onequip )	inst.components.equippable:SetOnUnequip( onunequip )    	return instendreturn Prefab( "common/inventory/lightstaff", fn, assets)

 

Edit 2: Heeeey, I'm learning! All I needed to do was get rid of finiteuses and add inst.components.fueled:DoDelta(-7.5) to my onattack.  :biggrin:

 

All I need now is the below:

 

As a side-question, is it possible to get rid of the torchfire's particle trail?

post-262695-0-20627500-1379380465.png

Share this post


Link to post
Share on other sites
TheDanaAddams    574

Try taking a look at the torch fire prefab - I knew it was going to be a pain, so I didn't bother, when I used it... but if you made a copy, and tweaked it, you could theoretically alter the behaviour. It's physics stuff, so you'd have to do a lot of messing about, but all it is when it comes down to it is a prefab being pinned to a place, with particle generation based on defined parameters.

Share this post


Link to post
Share on other sites
Sukoushi    192

I did actually try making a copy of the prefab, as I wanted to have my own custom "torchfire" texture, but every time I tried to cut out the particles bits, I got some crash on load.

Share this post


Link to post
Share on other sites
simplex    2,608

is it possible to get rid of the torchfire's particle trail?

I don't think it is, precisely because it's a particle emitter. The particles it emitted before are independent from the source. If there's a way around it, I'd bet it's based on reducing the particle's lifetime so this effect ceases to be visible. But since the actual particle emitter code is not in Lua, there's not much lead to follow on specifics tweaks like that...

Share this post


Link to post
Share on other sites
Sukoushi    192

I don't think it is, precisely because it's a particle emitter. The particles it emitted before are independent from the source. If there's a way around it, I'd bet it's based on reducing the particle's lifetime so this effect ceases to be visible. But since the actual particle emitter code is not in Lua, there's not much lead to follow on specifics tweaks like that...

That's a shame. Would you happen to know if there's a way to replace torchfire with a custom texture, instead? I took a peek at FutaraDragon's Light Edge Rod, but just looking at all the coding behind it is... overwhelming, to say the least.

Share this post


Link to post
Share on other sites
simplex    2,608

That's a shame. Would you happen to know if there's a way to replace torchfire with a custom texture, instead? I took a peek at FutaraDragon's Light Edge Rod, but just looking at all the coding behind it is... overwhelming, to say the least.

It wouldn't be so much about replacing textures, because that trailing effect would remain.

But I'm not sure what's your ideal goal here. Do you want to just shorten the trail? Because removing it altogether would look off, since it'd be breaking the laws of physics (and for that same reason I doubt it's achievable through a particle emitter).

Share this post


Link to post
Share on other sites