aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--entities/entities/ws_npc_ambient/init.lua5
-rw-r--r--entities/entities/ws_npc_ambient/shared.lua75
-rw-r--r--gamemode/npcsystem/aidirector.lua34
-rw-r--r--gamemode/npcsystem/npcs/base.lua14
-rw-r--r--gamemode/npcsystem/npcs/bird.lua124
-rw-r--r--gamemode/npcsystem/npcs/zombie.lua203
6 files changed, 313 insertions, 142 deletions
diff --git a/entities/entities/ws_npc_ambient/init.lua b/entities/entities/ws_npc_ambient/init.lua
index 485b6c9..a0497dd 100644
--- a/entities/entities/ws_npc_ambient/init.lua
+++ b/entities/entities/ws_npc_ambient/init.lua
@@ -8,6 +8,11 @@ function ENT:Initialize()
--print("NPC spawned!")
if(self.Model) then self:SetModel(self.Model)
else print("NPC created without model, this might be a bug!") end
+ if(self.Stats["Vitality"]) then self.Vitality = self.Stats["Vitality"]
+ else print("NPC created with no stat for vitality, this might be a bug!")end
+ if(self.Stats["Accel"]) then self.loco:SetAcceleration(self.Stats["Accel"])end
+ if(self.Stats["Decel"]) then self.loco:SetDeceleration(self.Stats["Decel"]) end
+ if(self.Stats["Step"]) then self.loco:SetJumpHeight(self.Stats["Step"]) end
--self:SetModel( "models/Humans/Group01/Female_01.mdl" )
--[[
self:SetHullType( HULL_HUMAN );
diff --git a/entities/entities/ws_npc_ambient/shared.lua b/entities/entities/ws_npc_ambient/shared.lua
index ba65e2b..7fa3661 100644
--- a/entities/entities/ws_npc_ambient/shared.lua
+++ b/entities/entities/ws_npc_ambient/shared.lua
@@ -17,6 +17,79 @@ ENT.Act = nil
function ENT:OnRemove()
end
+function ENT:DefaultBehaviour()
+ while ( true ) do
+ --Main loop for ai
+
+ --Update aware enemies
+ local players = ents.FindByClass("Player")
+ for k,v in pairs(players) do
+ if(v:IsPigeon()) then continue end
+ local dist = v:GetPos():Distance(self:GetPos())
+ if( dist < self.Stats["AwareDist"]) then
+ table.insert(self.AwareEnemies,v)
+ end
+ end
+
+ --Find the enemy with the highest priority
+ local maxpriority = -1
+ local maxprioritytarget = nil
+ for k,v in pairs(self.AwareEnemies) do
+ local priority = self:AttackPriority(v)
+ if(priority > maxpriority) then
+ maxpriority = priority
+ maxprioritytarget = v
+ end
+ end
+ self.Target = maxprioritytarget
+
+ --If we can't find anyone to attack, just stay idle
+ if(self.Target == nil) then
+ print("Couldn't find anyone to attack!")
+ --Play an idle sequence
+ local randanim = math.Round(math.Rand(0,#self.IdleSequences))
+ self:PlaySequenceAndWait( self.IdleSequences[randanim] )
+ self:StartActivity( ACT_IDLE )
+ --If there's noone within 4000 units, just remove ourselves to save server resources
+ local closest = 5000
+ for k,v in pairs(player.GetAll()) do
+ local thisdist = self:GetPos():Distance(v:GetPos())
+ if(thisdist > closest) then
+ closest = thisdist
+ end
+ end
+ if(closest > 4000) then self:BecomeRagdoll(DamageInfo()) end
+ else
+ --We have a target to attack!
+
+ --Find which attack will do the most dammage
+ local maxdammage = -1
+ local maxdammagefunc = nil
+ for k,v in pairs(self.Attacks) do
+ local dammagefunc = nil
+ local attackfunc = nil
+ for i,j in pairs(v) do
+ dammagefunc = i
+ attackfunc = j
+ end
+ local dammage = dammagefunc(self, self.Target)
+ if(dammage > maxdammage) then
+ maxdammage = dammage
+ maxdammagefunc = attackfunc
+ end
+ end
+
+ --Do that attack
+ if(maxdammagefunc) then
+ maxdammagefunc(self, self.Target)
+ end
+ end
+ coroutine.yield()
+ end
+
+ coroutine.yield()
+end
+
function ENT:BehaveAct()
if(self.Act) then
self:Act()
@@ -29,6 +102,6 @@ function ENT:RunBehaviour()
if(self.Behave) then
self:Behave()
else
- print("NPC spawned without a Behave function, this might be an error!")
+ self:DefaultBehaviour()
end
end
diff --git a/gamemode/npcsystem/aidirector.lua b/gamemode/npcsystem/aidirector.lua
index 073eea7..4051204 100644
--- a/gamemode/npcsystem/aidirector.lua
+++ b/gamemode/npcsystem/aidirector.lua
@@ -11,18 +11,16 @@ end)
function SpawnNpcByName(name, position)
if(CLIENT) then return end
entdata = GetNpcByName(name)
+ if not entdata then
+ print("Could not find npc data for name " .. name)
+ return
+ end
ent = ents.Create("ws_npc_ambient")
ent:SetPos(position)
- if(entdata.Speed) then
- ent.Speed = entdata.Speed
- end
if(entdata.Model) then
ent.Model = entdata.Model
end
- if(entdata.vitality) then
- ent:SetHealth(entdata.vitality)
- end
if(entdata.Drops) then
ent.Drops = entdata.Drops
end
@@ -35,6 +33,21 @@ function SpawnNpcByName(name, position)
if(entdata.Act) then
ent.Act = entdata.Act
end
+ if(entdata.Stats) then
+ ent.Stats = entdata.Stats
+ end
+ if(entdata.IdleSequences) then
+ ent.IdleSequences = entdata.IdleSequences
+ end
+ if(entdata.Attacks) then
+ ent.Attacks = entdata.Attacks
+ end
+ if(entdata.AttackPriority) then
+ ent.AttackPriority = entdata.AttackPriority
+ end
+ if(entdata.AwareEnemies) then
+ ent.AwareEnemies = entdata.AwareEnemies
+ end
ent:Spawn()
end
@@ -43,7 +56,11 @@ local traceline = util.TraceLine
local contents = util.PointContents
local Up = Vector(0,0,1)
---Randomly spawn bird npc's around?
+--Randomly spawn npc's around?
+local ambientnpcs = {
+ [0] = "Bird",
+ [1] = "Zombie"
+}
local Tick = CurTime()
hook.Add("Tick","SpawnAmbient",function()
if(CLIENT) then return end
@@ -75,7 +92,8 @@ hook.Add("Tick","SpawnAmbient",function()
if (C != CONTENTS_WATER and C != CONTENTS_WATER+CONTENTS_TRANSLUCENT) then
--print("Appropriate place found, spawning bird)")
- SpawnNpcByName("Bird",Pos)
+ local randnpcnum = math.Round(math.Rand(0, #ambientnpcs))
+ SpawnNpcByName(ambientnpcs[randnpcnum],Pos)
break
end
end
diff --git a/gamemode/npcsystem/npcs/base.lua b/gamemode/npcsystem/npcs/base.lua
index 7afb4fe..670c476 100644
--- a/gamemode/npcsystem/npcs/base.lua
+++ b/gamemode/npcsystem/npcs/base.lua
@@ -6,14 +6,24 @@ NPC.Icon = Material("wintersurvival2/hud/ws1_icons/icon_rock")
NPC.Social = "Pack" --Solo, Pack
-NPC.Vitality = 0
-NPC.Speed = 0
+NPC.Stats = {
+ ["Vitality"] = 100,
+ ["Speed"] = 50,
+ ["AwareDist"] = 1000,
+}
+
+--Some npc's like birds have diffent names for their idle sequence
+NPC.IdleSequence = "Idle"
+
--Drops should be formated as [index]={["item name"], percent_drop} where percent_drop is a number from 0 to 100
NPC.Drops = nil
--Attacks should be formated as [i]={function attackpriority() = function doattack()}
NPC.Attacks = nil
+--Attack priority should be formated as [i] = func tion(return int priority) attackpriority
+NPC.AttackPriority = nil
+
--A function that takes a position and returns true if this is an acceptable place to spawn
NPC.SpawnLocations = nil
diff --git a/gamemode/npcsystem/npcs/bird.lua b/gamemode/npcsystem/npcs/bird.lua
index fa3ad8d..0b05e62 100644
--- a/gamemode/npcsystem/npcs/bird.lua
+++ b/gamemode/npcsystem/npcs/bird.lua
@@ -6,17 +6,58 @@ NPC.Icon = Material("wintersurvival2/hud/ws1_icons/icon_rock")
NPC.Social = "Pack" --Solo, Pack
-NPC.Vitality = 10
-NPC.Speed = 100
+NPC.Stats = {
+ ["Vitality"] = 10,
+ ["Speed"] = 50,
+ ["AwareDist"] = 800,
+}
+
+--Some npc's like birds have diffent names for their idle sequences
+NPC.IdleSequences = {
+ [0] = "Idle01",
+ [1] = "Eat_A",
+}
+
--Drops should be formated as [index]={["item name"], percent_drop} where percent_drop is a number from 0 to 100
NPC.Drops = {
- [0] = {"Meat",100},--Birds will drop at least 1 meat, and have a 50% chance of dropping 2
- [1] = {"Meat",50},
- [2] = {"Feather",50},
+ [0] = {"Meat",100},
+ [1] = {"Meat",100},
+ [2] = {"Meat",100},
+ [3] = {"Meat",100},
+ [4] = {"Meat",80},
+ [5] = {"Meat",40},
+ [6] = {"Meat",10},
}
--Attacks should be formated as [i]={function attackpriority() = function doattack()}
-NPC.Attacks = nil
+local checkrun = function(self,ply)
+ --If we're awayre of any enemies, run away!
+ return 1
+end
+local dorun = function(self,ply)
+ self:StartActivity(ACT_FLY)
+ self:PlaySequenceAndWait( "Fly01" )
+
+ --Find a position in roughly the oposite direction of the player
+ local tpos = self:GetPos()
+ local ppos = ply:GetPos()
+ local direction = Vector(tpos.x - ppos.x, tpos.y - ppos.y, tpos.z - ppos.z)
+ direction:Normalize()
+ local addition = direction * 1000
+ local topos = self:GetPos() + addition
+ local navarea = navmesh.GetNavArea(self:GetPos(), 100)
+ if navarea:IsValid() then
+ self:MoveToPos(topos)
+ else
+ print("Suicideing a bird to prevent server crash!")
+ self:BecomeRagdoll(DamageInfo())
+ end
+end
+NPC.Attacks = {
+ [1] = {--run away from the player
+ [checkrun] = dorun
+ },
+}
--A function that takes a position and returns true if this is an acceptable place to spawn
NPC.SpawnLocations = nil
@@ -25,7 +66,13 @@ NPC.SpawnLocations = nil
NPC.Target = nil
--All enemies that this NPC is aware of
-NPC.AwareEnemies = nil
+NPC.AwareEnemies = {}
+
+--Attack priority is a fucntion that takes a player, and returns an int describing it's prority to attack (higher = more important) NPC will always attack the player with the highest priority
+function NPC:AttackPriority(ply)
+ local dist = ply:GetPos():Distance(self:GetPos())
+ return self.Stats["AwareDist"] - dist
+end
--What to replace the ENT:BehaveAct with
function NPC:Act()
@@ -36,69 +83,6 @@ function NPC:Stuck()
end
---What to replace ENT:RunBehaviour with
-function NPC:Behave()
- --print("Going into bird's custom behaviour")
- while ( true ) do
- self:StartActivity( ACT_IDLE ) -- walk anims
- self:PlaySequenceAndWait( "Idle01" ) -- Sit on the floor
- --Check if there are any players nearby
- local players = ents.FindByClass("Player")
- local playernearby = false
- for k,v in pairs(players) do
- local fardist = 800
- local closedist = 300
- local removedist = 2000
- local iscrouched = v:Crouching()
- local dist = v:GetPos():Distance(self:GetPos())
- --Remove ourselves if there's noone nearby
- if(dist > 2000) then
- playernearby = true
- end
- --Keep flying away as long as we're being chased
- while((dist < fardist and not iscrouched) or (dist < closedist)) do
- self:StartActivity(ACT_FLY)
- self:PlaySequenceAndWait( "Fly01" )
-
- --Find a position in roughly the oposite direction of the player
- local tpos = self:GetPos()
- local ppos = v:GetPos()
- local direction = Vector(tpos.x - ppos.x, tpos.y - ppos.y, tpos.z - ppos.z)
- direction:Normalize()
- local addition = direction * 1000
- local topos = self:GetPos() + addition
-
- --Check to make sure we can make it somewhere, so we don't crash
- --[[
- if not self.loco:IsAreaTraversable(navmesh.GetNavArea(self:GetPos(),100)) then
- print("No way to go forward!")
- break
- end
- --]]
- local navarea = navmesh.GetNavArea(self:GetPos(), 100)
-
- if navarea:IsValid() then
- --print("Looks like nav area is valid")
- self:MoveToPos(topos)
- else
- --print("Looks like nav area is invalid")
- self:BecomeRagdoll(DamageInfo())
- end
-
-
-
- --Check to see if we're being chased
- iscrouched = v:Crouching()
- dist = v:GetPos():Distance(self:GetPos())
- end
- end
- if not playernearby then
- self:BecomeRagdoll(DamageInfo())
- end
- end
- coroutine.yield()
-end
-
--These are just here to tell the editors/develoeprs what functions are available.. dont un-comment them out, as this could affect all the items.
/*
function NPC:OnSpawn()
diff --git a/gamemode/npcsystem/npcs/zombie.lua b/gamemode/npcsystem/npcs/zombie.lua
index d9232a7..7c598fd 100644
--- a/gamemode/npcsystem/npcs/zombie.lua
+++ b/gamemode/npcsystem/npcs/zombie.lua
@@ -1,13 +1,21 @@
-NPC.Name = "Bird"
-NPC.Desc = "A flappy little guy"
+NPC.Name = "Zombie"
+NPC.Desc = "A fearsome monster!"
NPC.Class = "Ambient" --Ambient, Agressive, Boss
-NPC.Model = "models/pigeon.mdl"
+NPC.Model = "models/Zombie/Classic.mdl"
NPC.Icon = Material("wintersurvival2/hud/ws1_icons/icon_rock")
-NPC.Social = "Pack" --Solo, Pack
+NPC.Social = "Solo" --Solo, Pack
+
+NPC.Stats = {
+ ["Vitality"] = 100,
+ ["Speed"] = 50,
+ ["AwareDist"] = 1000,
+ ["Accel"] = 100,
+ ["Decel"] = 200,
+ ["Step"] = 20, --Step height
+ ["Hull"] = HULL_HUMAN
+}
-NPC.Vitality = 10
-NPC.Speed = 100
--Drops should be formated as [index]={["item name"], percent_drop} where percent_drop is a number from 0 to 100
NPC.Drops = {
[0] = {"Meat",100},--Birds will drop at least 1 meat, and have a 50% chance of dropping 2
@@ -15,8 +23,70 @@ NPC.Drops = {
[2] = {"Feather",50},
}
---Attacks should be formated as [i]={function (return bool) canattack(ply) = function doattack(ply)}
-NPC.Attacks = nil
+--Some npc's like birds have diffent names for their idle sequences
+NPC.IdleSequences = {
+ [0] = "Idle",
+}
+
+--Distance to be made aware of players
+
+--Attacks should be formated as [i]={function (return int dammage) canattack(ply) = function doattack(ply)}
+--NPC will do the most dammage possible per attack
+local checkmele = function(self, ply)
+ if(ply:GetPos():Distance(self:GetPos()) < 100) then return 20 end
+ return -1
+end
+
+local domele = function(self, ply)
+ self:StartActivity(ACT_MELEE_ATTACK1)
+ --Antlion has 6 attack animations
+ local attackanim = math.Round(math.Rand(1,6))
+ coroutine.wait(0.75)
+ --If the player is still in front of us after the animation, they didn't dodge! apply dammage!
+ if(ply:GetPos():Distance(self:GetPos()) < 100) then
+ ply:TakeDamage(20)
+ end
+ --Finish up the animation
+ coroutine.wait(0.25)
+end
+
+local checkrun = function(self, ply) return 0 end
+
+local dorun = function(self, ply)
+ local navarea = navmesh.GetNavArea(self:GetPos(), 100)
+ self.loco:SetDesiredSpeed( 50 )
+ if navarea:IsValid() then
+ local moveop = {}
+ moveop.tolerance = 50
+ moveop.repath = 2
+ moveop.lookahead = 3
+ moveop.draw = true
+ self:StartActivity(ACT_WALK)
+ self:MoveToPos(ply:GetPos(),moveop)
+ else
+ print("Could not find valid navmesh, suicideing to prevent server crash!")
+ self:BecomeRagdoll(DamageInfo())
+ end
+end
+
+NPC.Attacks = {
+ [1] = { --A mele attack
+ [checkmele] = domele
+ },
+
+ [2] = {--Move to the player
+ [checkrun] = dorun
+
+ },
+
+
+}
+
+--Attack priority is a fucntion that takes a player, and returns an int describing it's prority to attack (higher = more important) NPC will always attack the player with the highest priority
+function NPC:AttackPriority(ply)
+ local dist = ply:GetPos():Distance(self:GetPos())
+ return self.Stats["AwareDist"] - dist
+end
--A function that takes a position and returns true if this is an acceptable place to spawn
NPC.SpawnLocations = nil
@@ -25,7 +95,7 @@ NPC.SpawnLocations = nil
NPC.Target = nil
--All enemies that this NPC is aware of
-NPC.AwareEnemies = nil
+NPC.AwareEnemies = {}
--What to replace the ENT:BehaveAct with
function NPC:Act()
@@ -33,74 +103,85 @@ end
--What to replace ENT:OnStuck with
function NPC:Stuck()
-
+ --If we're stuck, jump backwards
end
--What to replace ENT:RunBehaviour with
+--[[
function NPC:Behave()
+ --[[
print("Going into antlion's custom behaviour")
while ( true ) do
- self:StartActivity( ACT_IDLE ) -- walk anims
- self:PlaySequenceAndWait( "Idle01" ) -- Sit on the floor
- --Check if there are any players nearby
+ --self:PlaySequenceAndWait( "Idle" )
+
+ --Main loop for ai
+
+ --Update aware enemies
local players = ents.FindByClass("Player")
- local playernearby = false
for k,v in pairs(players) do
- local fardist = 800
- local closedist = 300
- local removedist = 2000
- local iscrouched = v:Crouching()
+ if(v:IsPigeon()) then continue end
local dist = v:GetPos():Distance(self:GetPos())
- --Remove ourselves if there's noone nearby
- if(dist > 2000) then
- playernearby = true
+ if( dist < self.Stats["AwareDist"]) then
+ table.insert(self.AwareEnemies,v)
end
- --Keep flying away as long as we're being chased
- while((dist < fardist and not iscrouched) or (dist < closedist)) do -- We found a player!
- if(dist < 100) then -- We're in attack range!
- local target = v;
- local targetpos = v:GetPos()
- self:StartActivity(ACT_MELEE_ATTACK1)
- --Antlion has 6 attack animations
- local attackanim = math.Round(math.Rand(1,6))
- self:PlaySequenceAndWait("attack" .. attackanim)
- --If the player is still in front of us after the animation, they didn't dodge apply dammage!
- if(v:GetPos():Distance(targetps) < 100) then
- v:TakeDamage(20)
- end
- else
- self:StartActivity(ACT_WALK)
- self:PlaySequenceAndWait( "walk_all" )
-
- --Find a position in roughly the oposite direction of the player
- local tpos = self:GetPos()
- local ppos = v:GetPos()
- local direction = Vector(ppos.x - tpos.x, ppos.y - tpos.y, ppos.z - tpos.z)
- direction:Normalize()
- local addition = direction * 1000
- local topos = self:GetPos() + addition
-
- local navarea = navmesh.GetNavArea(self:GetPos(), 100)
-
- if navarea:IsValid() then
- self:MoveToPos(topos)
- else
- self:BecomeRagdoll(DamageInfo())
- end
-
- --Check to see if we're being chased
- iscrouched = v:Crouching()
- dist = v:GetPos():Distance(self:GetPos())
- end
+ end
+
+ --Find the enemy with the highest priority
+ local maxpriority = -1
+ local maxprioritytarget = nil
+ for k,v in pairs(self.AwareEnemies) do
+ local priority = self:AttackPriority(v)
+ if(priority > maxpriority) then
+ maxpriority = priority
+ maxprioritytarget = v
end
end
- if not playernearby then
- self:BecomeRagdoll(DamageInfo())
+ self.Target = maxprioritytarget
+
+ --If we can't find anyone to attack, just stay idle
+ if(self.Target == nil) then
+ print("Couldn't find anyone to attack!")
+ --Play an idle sequence
+ local randanim = math.Round(math.Rand(0,#self.IdleSequences))
+ self:PlaySequenceAndWait( self.IdleSequences[randanim] )
+ self:StartActivity( ACT_IDLE )
+ else
+ --We have a target to attack!
+ print("We found something to attack!")
+ --Find which attack will do the most dammage
+ local maxdammage = -1
+ local maxdammagefunc = nil
+ for k,v in pairs(self.Attacks) do
+ local dammagefunc = nil
+ local attackfunc = nil
+ for i,j in pairs(v) do
+ dammagefunc = i
+ attackfunc = j
+ end
+ print("Using damagefunc:")
+ print(dammagefunc)
+ local dammage = dammagefunc(self, self.Target)
+ print("dammage is ")
+ print(dammage)
+ if(dammage > maxdammage) then
+ maxdammage = dammage
+ maxdammagefunc = attackfunc
+ end
+ end
+
+ --Do that attack
+ if(maxdammagefunc) then
+ maxdammagefunc(self, self.Target)
+ else
+ print("It looked like we could attack, but we can't!")
+ end
end
+ --coroutine.yield()
end
+
coroutine.yield()
end
-
+--]]
--These are just here to tell the editors/develoeprs what functions are available.. dont un-comment them out, as this could affect all the items.
/*
function NPC:OnSpawn()