Sign in to follow this  
MrDoge124

How would I make an area of effect stun attack?

Recommended Posts

MrDoge124    3

I had a character that can glow in the dark, I though of an idea where they could use this light to stun enemies. I've looked at examples but I couldn't quite get it right.
I wanted to make it so when he activates it his light will be gone for 8 minutes (a day) and he'll get a sanity penalty. (About -10 to -15 maybe?)
I was going to make it activated by a key press by I couldn't quite figure it out.
Any help would be appreciated.

Share this post


Link to post
Share on other sites
Ultroman    697

Take a look at this

If you think of the "inst" as the instance of an entity you've found, which you want to inflict a (de)buff on, i.e., your stun, and use the movement speed example to give them a movementspeed multiplier of 0, they should at least stop moving on their own volition, but still be affected by physics.

But that code is for an aura-buff. Gimme a sec.

Share this post


Link to post
Share on other sites
MrDoge124    3

So far I have this,

inst.lightstunisapplied = false

local applybuff = function(inst)
	-- Apply all your buff stuff here
	inst.components.locomotor:SetExternalSpeedMultiplier(inst, "lightstunkey", 0)
end

local removebuff = function(inst)
	-- Remove all your buff stuff here
	inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "lightstunkey")
end

inst:DoPeriodicTask(0.5, function(inst)
	if inst == nil or not inst:IsValid() or inst.components.health:IsDead() then
		-- If the player instance is invalid or they're dead, then we don't want to do anything.
		return
	end
	
	-- If our buff is applied and our measurements say it should not be applied anymore, we remove it.
	-- Else, if our buff is not applied and our measurements say it should be, we apply it.
	if inst.lightstunisapplied and inst.components.sanity:GetPercentWithPenalty() > 0.07 then
		removebuff(inst)
	elseif not inst.lightstunisapplied and inst.components.sanity:GetPercentWithPenalty() <= 0.07 then
		applybuff(inst)
	end
end)

This seems to slow him down when on 7% sanity. I'm not sure how to find an entity and slow them down with the inst thing and I am not sure how I would activate this with a key press.

I do know however that I'll need a keyhandler.lua as other mods use this when dealing with key inputs.

Share this post


Link to post
Share on other sites
Ultroman    697

That other code is for aura-buffs and doesn't handle duplicate buffs (woopsie!). I made this for you. It's a better way of handling timed buffs.

 

Share this post


Link to post
Share on other sites
MrDoge124    3

I've been using this code to activate the buff

TheInput:AddKeyDownHandler(KEY_Z, function()
	ApplyBuff(Buffs["stunbuff"], inst)
end)

However this seems to target me and nothing else. I'm not sure how to target things around me and not myself. I also need a way of adding a cool down so that I can't just use it constantly. 

Here is what the rest of the code looks like.

Spoiler

-- This function is rather complicated. You don't need to change anything in here.
local function ApplyBuff(buffData, instToBuff)
    local buffUniqueName = buffData.uniqueName
    
    -- Cancel any existing duplicate timed buff.
    if instToBuff[buffUniqueName.."Task"] ~= nil then
        buffData.removeFunction(instToBuff)
        instToBuff[buffUniqueName.."Task"]:Cancel()
        instToBuff[buffUniqueName.."Task"] = nil
    end
    
    -- Make sure that our buff is removed BEFORE the entity is saved (the server is closed or player quits).
    -- This is only necessary if you change variables which are saved, like current health, health-penalty, etc.
    -- Without it, you could apply a max-health buff, quit before it ran out, rejoin, and you would have kept
    -- the extra max health!
    local buffOnSave = function(self, inst, data)
        if inst[buffUniqueName.."Task"] ~= nil then
            buffData.removeFunction(inst)
            inst[buffUniqueName.."Task"]:Cancel()
            inst[buffUniqueName.."Task"] = nil
        end
    end
    
    if instToBuff.OnSave ~= nil then
        local oldOnSave = instToBuff.OnSave
        instToBuff.OnSave = function(self, inst, data)
            buffOnSave(self, inst, data)
            oldOnSave(self, inst, data)
        end
    else
        instToBuff.OnSave = buffOnSave
    end
    
    -- Apply the buff.
    buffData.applyFunction(instToBuff)
    
    -- Set up removal after a certain amount of time.
    instToBuff[buffUniqueName.."Task"] = instToBuff:DoTaskInTime(buffData.duration, function(inst)
        buffData.removeFunction(inst)
        inst[buffUniqueName.."Task"] = nil
    end)
end


-- Now to make some buffs and put them in a Buffs-dictionary.
local Buffs = {}

-- This function creates a new buff-data object and adds it to the Buffs-dictionary
-- with the uniqueName as key.
local function CreateBuff(uniqueName, duration, applyFunction, removeFunction)
    local newBuff = {}
    newBuff.uniqueName = uniqueName
    newBuff.duration = duration
    newBuff.applyFunction = applyFunction
    newBuff.removeFunction = removeFunction
    Buffs[uniqueName] = newBuff
end

-- This is an example of how to use the CreateBuff() function to create a new buff.
CreateBuff("stunbuff", 5.0,
    function(inst)
        -- Apply all your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.-- Store the current maxhealth in a variable.
        -- Add 0% speed boost in all situations
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "stunkey", 0)
    end,
    function(inst)
        -- Revert your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.
    -- Remove speed bonus.
    inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "stunkey")
    end
)

TheInput:AddKeyDownHandler(KEY_Z, function()
    ApplyBuff(Buffs["stunbuff"], inst)
end)

 

 

Share this post


Link to post
Share on other sites
Ultroman    697

Well, that's because you're casting it on yourself. If you put that code in a character, then "inst" is the character. You need to trigger the function with a listener or something.

Use the tutorials here to familiarize yourself with how to do things in the code. Check out the perk/buff tutorials.

 

Share this post


Link to post
Share on other sites
MrDoge124    3

I think I'm missing something, because I get this error when I press the key

[00:01:43]: [string "../mods/Green/modmain.lua"]:198: attempt to index local 'instToBuff' (a nil value)
LUA ERROR stack traceback:
../mods/Green/modmain.lua:198 in (upvalue) ApplyBuff (Lua) <194-234>
   buffData = table: 4B41C1D0
   instToBuff = nil
   buffUniqueName = stunbuff
../mods/Green/modmain.lua:273 in (field) fn (Lua) <272-274>
scripts/events.lua:46 in (method) HandleEvent (Lua) <42-49>
   self =
      events = table: 29B1A1C0
   event = 122
   arg = nil
   handlers = table: 4B41C2C0
   k = table: 4B41C270
   v = true
scripts/input.lua:187 in (method) OnRawKey (Lua) <184-191>
   self =
      onkeyup = table: 29B1A3F0
      entitiesundermouse = table: 8E1BCF40
      onmousebutton = table: 29B19F40
      controllerid_cached = 0
      onkey = table: 29B1A3C8
      enabledebugtoggle = true
      ongesture = table: 29B1A418
      mouse_enabled = true
      ontextinput = table: 29B1A648
      position = table: 29B1A760
      onkeydown = table: 29B1A198
      oncontrol = table: 29B1A8C8
   key = 122
   down = true
scripts/input.lua:396 in () ? (Lua) <395-397>
   key = 122
   is_up = true

[00:01:43]: [string "../mods/Green/modmain.lua"]:198: attempt to index local 'instToBuff' (a nil value)
LUA ERROR stack traceback:
    ../mods/Green/modmain.lua:198 in (upvalue) ApplyBuff (Lua) <194-234>
    ../mods/Green/modmain.lua:273 in (field) fn (Lua) <272-274>
    scripts/events.lua:46 in (method) HandleEvent (Lua) <42-49>
    scripts/input.lua:187 in (method) OnRawKey (Lua) <184-191>
    scripts/input.lua:396 in () ? (Lua) <395-397>
	

I think I'm just being stupid here, but I'm not sure what is wrong.

Spoiler

-- This function is rather complicated. You don't need to change anything in here.
local function ApplyBuff(buffData, instToBuff)
    local buffUniqueName = buffData.uniqueName
    
    -- Cancel any existing duplicate timed buff.
    if instToBuff[buffUniqueName.."Task"] ~= nil then
        buffData.removeFunction(instToBuff)
        instToBuff[buffUniqueName.."Task"]:Cancel()
        instToBuff[buffUniqueName.."Task"] = nil
    end
    
    -- Make sure that our buff is removed BEFORE the entity is saved (the server is closed or player quits).
    -- This is only necessary if you change variables which are saved, like current health, health-penalty, etc.
    -- Without it, you could apply a max-health buff, quit before it ran out, rejoin, and you would have kept
    -- the extra max health!
    local buffOnSave = function(self, inst, data)
        if inst[buffUniqueName.."Task"] ~= nil then
            buffData.removeFunction(inst)
            inst[buffUniqueName.."Task"]:Cancel()
            inst[buffUniqueName.."Task"] = nil
        end
    end
    
    if instToBuff.OnSave ~= nil then
        local oldOnSave = instToBuff.OnSave
        instToBuff.OnSave = function(self, inst, data)
            buffOnSave(self, inst, data)
            oldOnSave(self, inst, data)
        end
    else
        instToBuff.OnSave = buffOnSave
    end
    
    -- Apply the buff.
    buffData.applyFunction(instToBuff)
    
    -- Set up removal after a certain amount of time.
    instToBuff[buffUniqueName.."Task"] = instToBuff:DoTaskInTime(buffData.duration, function(inst)
        buffData.removeFunction(inst)
        inst[buffUniqueName.."Task"] = nil
    end)
end


-- Now to make some buffs and put them in a Buffs-dictionary.
local Buffs = {"stunbuff",}

-- This function creates a new buff-data object and adds it to the Buffs-dictionary
-- with the uniqueName as key.
local function CreateBuff(uniqueName, duration, applyFunction, removeFunction)
    local newBuff = {}
    newBuff.uniqueName = uniqueName
    newBuff.duration = duration
    newBuff.applyFunction = applyFunction
    newBuff.removeFunction = removeFunction
    Buffs[uniqueName] = newBuff
end

-- This is an example of how to use the CreateBuff() function to create a new buff.
CreateBuff("stunbuff", 5.0,
    function(inst)
        -- Apply all your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.-- Store the current maxhealth in a variable.
        -- Add 0% speed boost in all situations
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "stunkey", 0)
    end,
    function(inst)
        -- Revert your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.
    -- Remove speed bonus.
    inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "stunkey")
    end
)

TheInput = GLOBAL.TheInput
local KEY_Z = GLOBAL.KEY_Z

TheInput:AddKeyDownHandler(KEY_Z, function()
    ApplyBuff(Buffs["stunbuff"], inst)
end)

 

 

Edited by MrDoge124
Added code

Share this post


Link to post
Share on other sites
Ultroman    697
14 hours ago, MrDoge124 said:

I think I'm missing something, because I get this error when I press the key

I think I'm just being stupid here, but I'm not sure what is wrong.

  Hide contents


TheInput:AddKeyDownHandler(KEY_Z, function()
    ApplyBuff(Buffs["stunbuff"], inst)
end)

 

 

In a keyhandler function like this, there is no inst in the scope. You need to educate yourself on scopes, if you want to understand what's happening here. When you want to use a variable, it has to be available in the scope you're currently working in. In this case, you're using a keyhandler-function which takes a key and a function, The function cannot have any parameters, so the only things you have access to in there are any local variables you might have next to it. In modmain there is no inst. This code is not meant for modmain. Not in the way you've used it, at least. It's supposed to go in the Lua file for your character/item. Take a step back, educate yourself on scopes and perhaps revisit the Lua Crash Course you can find in the newcomer post and perhaps just read some of the tutorials. The ones about character perks are very informative.

Share this post


Link to post
Share on other sites
MrDoge124    3

So I got it to find other things and the basics work

Here is what I did to get it to work. (It probably isn't very efficient.)

Spoiler

-- This is an example of how to use the CreateBuff() function to create a new buff.
CreateBuff("stunbuff", 5.0,
    function(inst)
        -- Apply all your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.-- Store the current maxhealth in a variable.
        -- Add 0% speed boost in all situations
	local x, y, z = inst.Transform:GetWorldPosition()
	local range = 5
    local ents = TheSim:FindEntities(x, y, z, range, { "_combat"}, { "player", "structure", "INLIMBO", "NOCLICK" }, {"hostile", "pig"})
    for i, ent in ipairs(ents) do
		--local ds = ent.entity:GetDebugString()
		--local tagsstr = string.match(ds, "Tags: ([^\n]+)\n")
		--print(tagsstr)
		ent.components.locomotor:SetExternalSpeedMultiplier(inst, "stunkey", 0)
	end
    end,
    function(inst)
        -- Revert your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.
    -- Remove speed bonus.
	local x, y, z = inst.Transform:GetWorldPosition()
	local range = 45
    local ents = TheSim:FindEntities(x, y, z, range, { "_combat"}, { "player", "structure", "INLIMBO", "NOCLICK" }, { "hostile", "pig"})
    for i, ent in ipairs(ents) do
		--local ds = ent.entity:GetDebugString()
		--local tagsstr = string.match(ds, "Tags: ([^\n]+)\n")
		--print(tagsstr)
    ent.components.locomotor:RemoveExternalSpeedMultiplier(inst, "stunkey")
    end
	end
)

 

I'm not sure how to get cool downs to work and the effects to the character. (Turning off the light and the sanity penalty.)

Share this post


Link to post
Share on other sites
Ultroman    697

You're approaching this the wrong way. Perhaps I didn't explain properly. The CreateBuff function just adds a single timed buff effect on a single entity. You should not be looking for entities around you inside the ApplyBuff function. That function ONLY has to apply the buff, and ONLY to the inst given to the CreateBuff. What you're supposed to do, is where you attack or wherever you want to trigger this buff, you do a FindEntities-call, and then call CreateBuff on the entities that are eligible for it.

Share this post


Link to post
Share on other sites
MrDoge124    3

Ok I got it to work with some help from a friend.

Here is the code

Spoiler

-- This function is rather complicated. You don't need to change anything in here.
local function ApplyBuff(buffData, instToBuff, ents)
    --print(ents)
    local buffUniqueName = buffData.uniqueName
    
    -- Cancel any existing duplicate timed buff.
    if instToBuff[buffUniqueName.."Task"] ~= nil then
        buffData.removeFunction(instToBuff, ents)
        instToBuff[buffUniqueName.."Task"]:Cancel()
        instToBuff[buffUniqueName.."Task"] = nil
    end
    
    -- Make sure that our buff is removed BEFORE the entity is saved (the server is closed or player quits).
    -- This is only necessary if you change variables which are saved, like current health, health-penalty, etc.
    -- Without it, you could apply a max-health buff, quit before it ran out, rejoin, and you would have kept
    -- the extra max health!
    local buffOnSave = function(self, inst, data)
        if inst[buffUniqueName.."Task"] ~= nil then
            buffData.removeFunction(inst, ents)
            inst[buffUniqueName.."Task"]:Cancel()
            inst[buffUniqueName.."Task"] = nil
        end
    end
    
    if instToBuff.OnSave ~= nil then
        local oldOnSave = instToBuff.OnSave
        instToBuff.OnSave = function(self, inst, data)
            buffOnSave(self, inst, data)
            oldOnSave(self, inst, data)
        end
    else
        instToBuff.OnSave = buffOnSave
    end
    
    -- Apply the buff.
    buffData.applyFunction(instToBuff, ents)
    
    -- Set up removal after a certain amount of time.
    instToBuff[buffUniqueName.."Task"] = instToBuff:DoTaskInTime(buffData.duration, function(inst)
        buffData.removeFunction(inst, ents)
        inst[buffUniqueName.."Task"] = nil
    end)
end


-- Now to make some buffs and put them in a Buffs-dictionary.
local Buffs = {"stunbuff",}

-- This function creates a new buff-data object and adds it to the Buffs-dictionary
-- with the uniqueName as key.
local function CreateBuff(uniqueName, duration, applyFunction, removeFunction)
    local newBuff = {}
    newBuff.uniqueName = uniqueName
    newBuff.duration = duration
    newBuff.applyFunction = applyFunction
    newBuff.removeFunction = removeFunction
    Buffs[uniqueName] = newBuff
end

-- This is an example of how to use the CreateBuff() function to create a new buff.
CreateBuff("stunbuff", 5.0,
    function(inst, ents)
        -- Apply all your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.-- Store the current maxhealth in a variable.
        -- Add 0% speed boost in all situations
    for i, ent in ipairs(ents) do
		--local ds = ent.entity:GetDebugString()
		--local tagsstr = string.match(ds, "Tags: ([^\n]+)\n")
		--print(tagsstr)
        ent.components.locomotor:SetExternalSpeedMultiplier(inst, "stunkey", 0)
        if ent.brain then
            ent.brain:Stop()
        end
	end
    end,
    function(inst, ents)
        -- Revert your buff code here
        -- Remember to always check whether the components you want to change are actually present on the
        -- entity before you try to mess with them.
    -- Remove speed bonus.
    for i, ent in ipairs(ents) do
        ent.components.locomotor:RemoveExternalSpeedMultiplier(inst, "stunkey")
        if ent.brain then
            ent.brain:Start()
        end
    end
end
)

TheInput:AddKeyDownHandler(KEY_Z, function()
    local x, y, z = inst.Transform:GetWorldPosition()
    local range = 5
    local ents = TheSim:FindEntities(x, y, z, range, { "_combat"}, { "player", "structure", "INLIMBO", "NOCLICK" }, {"hostile", "pig"})
    --Find Entity nearby
    if not chargeincooldown and inst.components.sanity.current > 10 and table.getn(ents) > 0 then
    --If chargeincooldown is false and current sanity is larger than 10 and there is more that 0 entities nearby
        chargeincooldown = true --Disable the move by starting the cooldown
        ApplyBuff(Buffs["stunbuff"], inst, ents) --Run the apply buff function, inputting the entity table. God only knows how the rest of the function works.
        inst.components.sanity.current = inst.components.sanity.current - 10 --Remove 10 sanity
    	inst.SoundEmitter:PlaySound("dontstarve/characters/green/stun")
        inst.Light:SetIntensity(0.75)
        inst.Light:SetFalloff(.1)
        inst.Light:SetRadius(1.5)--Deal with lighting
        inst:DoTaskInTime(0.2, function()
            inst.Light:Enable (false)--Hide light after 1 second
        end)
        --Reset everything after 15 seconds
        inst:DoTaskInTime(480, function()
            inst.Light:SetIntensity(.75)
            inst.Light:SetFalloff(.5)
            inst.Light:SetRadius(0.8)
            inst.Light:Enable (true)
            chargeincooldown = false
        end)
    end
    end)
end

 

Also the keyhandler.lua which goes in a components folder in scripts.

Spoiler

local KeyHandler = Class(function(self, inst)
	self.inst = inst
	self.handler = TheInput:AddKeyHandler(function(key, down) self:OnRawKey(key, down) end )
end)

function KeyHandler:OnRawKey(key, down)
	local player = ThePlayer
  	if (key and not down) and not IsPaused() then
      	player:PushEvent("keypressed", {inst = self.inst, player = player, key = key})
    elseif key and down and not IsPaused() then
      	player:PushEvent("keydown", {inst = self.inst, player = player, key = key})
  	end
end

function KeyHandler:AddActionListener(Namespace, Key, Action)
	self.inst:ListenForEvent("keypressed", function(inst, data)
		if data.inst == ThePlayer then
			if data.key == Key then
				if TheWorld.ismastersim then
					ThePlayer:PushEvent("keyaction"..Namespace..Action, { Namespace = Namespace, Action = Action, Fn = MOD_RPC_HANDLERS[Namespace][MOD_RPC[Namespace][Action].id] })
				else
					SendModRPCToServer( MOD_RPC[Namespace][Action] )
				end
			end
		end
	end)

	if TheWorld.ismastersim then
      self.inst:ListenForEvent("keyaction"..Namespace..Action, function(inst, data)
          if not data.Action == Action and not data.Namespace == Namespace then
              return
          end
          
          data.Fn(inst)
      end, self.inst) 
    end
end

return KeyHandler

 

Thank you for the help! I'm sorry if i was being a pain.

  • Like 1

Share this post


Link to post
Share on other sites
MrDoge124    3

Sorry to bother you but it doesn't seem to work when I enable caves. I think this might be because I don't have any networking code and I have no idea how to do that...

Any help would be appreciated.

Share this post


Link to post
Share on other sites
MrDoge124    3

I'm not sure what to do. I've looked at other aoe attacks and they seem to have stuff like "SendRPCToServer" and the code mentions actions.

I've attached the code I'm using right now if you need it.

green.lua

Share this post


Link to post
Share on other sites
Ultroman    697

I can't really help without knowing what error it encountered and having the full file (because the errors are logged with line numbers). Also, the indentation of your code makes it difficult to read, so the only thing I'm seeing that's out of place, is that you add the "green" tag in the master_postinit, and tags should usually be added in the common_postinit, so they get added on both the server and the client.

I don't know why the other AoE attacks use SendRPCToServer. Maybe they set up completely new actions for their attacks and do some syncing using RPCs. I don't know. I don't think you'll need that for this.

Share this post


Link to post
Share on other sites
MrDoge124    3

When I enable caves it does nothing, no errors. Nothing is printed even if I tell it to print something.

I think something isn't being triggered properly. My best guess is the "TheInput:AddKeyDownHandler(KEY_Z, function()" part isn't working right as nothing happens when I press Z.

I might have to add an action for the attack, but I'm not sure how that works. I've found an example where actions are used and it seems to work fine.

Edited by MrDoge124
Added information

Share this post


Link to post
Share on other sites
Ultroman    697

The AddKeyDownHandler-part should only happen on the client or if the host is not a dedicated server. I haven't seen your modmain.lua file, so I can't tell you what's wrong with it.

Share this post


Link to post
Share on other sites
Ultroman    697

Since your keyhandler is in master_postinit, it is only added on the server. I'm not too knowledgeable on keyhandlers and where to put them, so try looking at other character mods which have keyhandlers, to see how and where they add these keyhandlers so they work correctly.

Share this post


Link to post
Share on other sites
MrDoge124    3

I've looked at other examples and they all seem to use an action handler. I'm going to have to look into this and either figure out how these things work or try to find another way to do it.

Share this post


Link to post
Share on other sites
Ultroman    697

Try looking into it, but otherwise make a new thread for this specific problem. I'm sure someone else will come along and help. This thread looks to other people like it's already being handled.

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
Sign in to follow this