aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--entities/entities/npc_huntable/cl_init.lua66
-rw-r--r--entities/entities/npc_huntable/init.lua70
-rw-r--r--entities/entities/npc_huntable/shared.lua144
-rw-r--r--gamemode/shared/loadnpcs.lua36
-rw-r--r--gamemode/shared/npcsystem/rat.lua113
5 files changed, 429 insertions, 0 deletions
diff --git a/entities/entities/npc_huntable/cl_init.lua b/entities/entities/npc_huntable/cl_init.lua
new file mode 100644
index 0000000..f941737
--- /dev/null
+++ b/entities/entities/npc_huntable/cl_init.lua
@@ -0,0 +1,66 @@
+include('shared.lua')
+
+ENT.RenderGroup = RENDERGROUP_BOTH
+
+/*---------------------------------------------------------
+ Name: Draw
+ Desc: Draw it!
+---------------------------------------------------------*/
+function ENT:Draw()
+ self:DrawModel()
+end
+
+/*---------------------------------------------------------
+ Name: DrawTranslucent
+ Desc: Draw translucent
+---------------------------------------------------------*/
+function ENT:DrawTranslucent()
+
+ // This is here just to make it backwards compatible.
+ // You shouldn't really be drawing your model here unless it's translucent
+
+ self:Draw()
+
+end
+
+/*---------------------------------------------------------
+ Name: BuildBonePositions
+ Desc:
+---------------------------------------------------------*/
+function ENT:BuildBonePositions( NumBones, NumPhysBones )
+
+ // You can use this section to position the bones of
+ // any animated model using self:SetBonePosition( BoneNum, Pos, Angle )
+
+ // This will override any animation data and isn't meant as a
+ // replacement for animations. We're using this to position the limbs
+ // of ragdolls.
+
+end
+
+
+
+/*---------------------------------------------------------
+ Name: SetRagdollBones
+ Desc:
+---------------------------------------------------------*/
+function ENT:SetRagdollBones( bIn )
+
+ // If this is set to true then the engine will call
+ // DoRagdollBone (below) for each ragdoll bone.
+ // It will then automatically fill in the rest of the bones
+
+ self.m_bRagdollSetup = bIn
+
+end
+
+
+/*---------------------------------------------------------
+ Name: DoRagdollBone
+ Desc:
+---------------------------------------------------------*/
+function ENT:DoRagdollBone( PhysBoneNum, BoneNum )
+
+ // self:SetBonePosition( BoneNum, Pos, Angle )
+
+end
diff --git a/entities/entities/npc_huntable/init.lua b/entities/entities/npc_huntable/init.lua
new file mode 100644
index 0000000..b136878
--- /dev/null
+++ b/entities/entities/npc_huntable/init.lua
@@ -0,0 +1,70 @@
+AddCSLuaFile("cl_init.lua")
+AddCSLuaFile("shared.lua")
+include("shared.lua")
+local applyfields = {"Model", "Stats"}
+
+function ENT:Initialize()
+ --print("NPC spawned!")
+ --self:SetMoveType(MOVETYPE_STEP)
+ self:SetSolid(SOLID_OBB)
+
+ --self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE)
+ for _, v in pairs(applyfields) do
+ assert(self[v], "NPC created without " .. v .. " this might be a bug!")
+ end
+
+ self:SetModel(self.Model)
+
+ if (self.Stats["Vitality"]) then
+ self:SetHealth(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
+
+ if (self.OnSpawn) then
+ self:OnSpawn()
+ end
+end
+
+function ENT:OnInjured(dmg)
+ if self.OnDammage ~= nil then
+ self:OnDammage(dmg)
+ end
+end
+
+function ENT:OnKilled(dmg)
+ if (CLIENT) then return end
+ if not self.Drops then return end
+ --print("Looks like we have some drops")
+ error("You need to code how item drops work!")
+
+ for k, v in pairs(self.Drops) do
+ local rng = math.random(0, 100)
+ local itemname = self.Drops[k][1]
+ local itemchance = self.Drops[k][2]
+ local heightoffset = 10
+
+ if rng < itemchance then
+ local drop = ents.Create("ws_item")
+ drop.Item = GetItemByName(itemname)
+ drop:SetModel(drop.Item.Model)
+ drop:SetPos(self:GetPos() + (self:GetUp() * heightoffset))
+ drop:Spawn()
+ heightoffset = heightoffset + 10
+ end
+ end
+
+ self:BecomeRagdoll(dmg)
+end
diff --git a/entities/entities/npc_huntable/shared.lua b/entities/entities/npc_huntable/shared.lua
new file mode 100644
index 0000000..2f34721
--- /dev/null
+++ b/entities/entities/npc_huntable/shared.lua
@@ -0,0 +1,144 @@
+ENT.Base = "base_nextbot"
+--ART stuff
+ENT.Drops = nil
+ENT.OnDammage = nil
+ENT.Speed = 0
+ENT.Model = nil
+ENT.Behave = nil
+ENT.Act = nil
+
+--[[---------------------------------------------------------
+Name: OnRemove
+Desc: Called just before entity is deleted
+---------------------------------------------------------]]
+function ENT:OnRemove()
+end
+
+
+function ENT:DefaultBehaviour()
+ self.lastrun = CurTime()
+ while (true) do
+ print("Inside defaultbehaviour")
+ local delta = CurTime() - self.lastrun
+ self:AI(delta)
+ --Main loop for ai
+ --print("Going into behavior for " .. self.Name)
+ --Update aware enemies
+ if self.TargetPos ~= nil then
+ self:MoveToPos(self.TargetPos,{
+ ["lookahead"] = 5,
+ ["tolerance"] = 50,
+ ["draw"] = true,
+ ["maxage"] = 20,
+ ["repath"] = 4,
+ })
+ end
+ self.lastrun = CurTime()
+ coroutine.yield()
+ end
+
+ coroutine.yield()
+end
+
+
+function ENT:AI(num)
+ print("Inside behaveupdate")
+ local players = player.GetAll()
+
+ for k, v in pairs(players) do
+ 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 == nil) then
+ print("Nill priority hit after ")
+ PrintTable(self)
+ end
+
+ if (priority > maxpriority) then
+ maxpriority = priority
+ maxprioritytarget = v
+ end
+ end
+
+ self.Target = maxprioritytarget
+ print("My target is",self.Target)
+ --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))
+ print("Playing sequence",self.IdleSequences[randanim])
+ self:PlaySequenceAndWait(self.IdleSequences[randanim])
+ self:StartActivity(ACT_IDLE)
+ --print("Acting 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
+ print("Closes player is " .. closest .. " removeing self...")
+ 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
+
+ if (self.Act) then
+ self:Act(num)
+ else
+ print("NPC spawned without an Act function, this might be an error!")
+ end
+end
+
+function ENT:RunBehaviour()
+ print("Running behavior")
+ if (self.Behave) then
+ print("Doing self.behave")
+ self:Behave()
+ else
+ print("Doing default behavior")
+ self:DefaultBehaviour()
+ end
+end
diff --git a/gamemode/shared/loadnpcs.lua b/gamemode/shared/loadnpcs.lua
new file mode 100644
index 0000000..bff5135
--- /dev/null
+++ b/gamemode/shared/loadnpcs.lua
@@ -0,0 +1,36 @@
+
+local f = include("concommands.lua")
+
+ART = ART or {}
+
+local npcs = {}
+
+function ART.RegisterNPC(npc)
+ assert(npc ~= nil, "Attempted to register a nil npc")
+ assert(npc.Name ~= nil, "Attempted to register an npc without a name")
+ npcs[npc.Name] = npc
+end
+
+function ART.CreateNPCByName(npcname, pos)
+ print("Createing a " ,npcname ," at ", pos)
+ local npctbl = npcs[npcname]
+ local npc = ents.Create("npc_huntable")
+ npc:SetPos(pos)
+ for k,v in pairs(npctbl) do
+ npc[k] = v
+ end
+ npc:Spawn()
+end
+
+local autocompletef
+if SERVER then
+ autocompletef = nil
+else
+ autocompletef = f.AutocompleteFunction(npcs)
+end
+concommand.Add("artery_makenpc",function(ply,cmd,args)
+ if not ply:IsAdmin() then return end
+ local n = args[1]
+ ART.CreateNPCByName(n,ply:GetEyeTrace().HitPos)
+end,
+autocompletef)
diff --git a/gamemode/shared/npcsystem/rat.lua b/gamemode/shared/npcsystem/rat.lua
new file mode 100644
index 0000000..d1c5422
--- /dev/null
+++ b/gamemode/shared/npcsystem/rat.lua
@@ -0,0 +1,113 @@
+local NPC = {}
+NPC.Name = "Rat"
+NPC.Desc = "A nasty little guy"
+NPC.Class = "Ambient" --Ambient, Agressive, Boss
+NPC.Model = "models/headcrab.mdl"
+
+NPC.Stats = {
+ ["Vitality"] = 10,
+ ["Speed"] = 400,
+ ["AwareDist"] = 1000,
+ ["Accel"] = 100,
+ ["Decel"] = 200,
+ ["Step"] = 20, --Step height
+ ["Hull"] = HULL_TINY
+}
+
+--Some npc's like birds have diffent names for their idle sequences
+NPC.IdleSequences = {
+ [0] = "lookaround",
+ [1] = "Idle01",
+}
+
+--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},--Rats will drop at least 1 meat, and have a 50% chance of dropping 2
+ [1] = {"Meat",50},
+}
+
+--Attacks should be formated as [i]={function attackpriority() = function doattack()}
+local checkrun = function(self,ply)
+ --If we're aware of any enemies, run away!
+ return 1
+end
+local runseq
+local dorun = function(self,ply)
+ if runseq == nil then
+ runseq = self:LookupSequence("Run1")
+ end
+ self:StartActivity(ACT_FLY)
+ self:SetSequence( runseq )
+ if(not ply or not ply:IsValid()) then return end
+ --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
+ print("I want to go to ", topos)
+ self.TargetPos = topos
+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
+function NPC:SpawnLocations(pos)
+ return true
+end
+
+--The entity that is this npc's current target, if it has one. Nil otherwise
+NPC.Target = nil
+
+--All enemies that this NPC is aware of
+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)
+ if not ply then return 0 end
+ local plypos = ply:GetPos()
+ local mypos = self:GetPos()
+ if not plypos then return 0 end
+ local dist = plypos:Distance(mypos)
+ return self.Stats["AwareDist"] - dist
+end
+
+--What to replace the ENT:RunBehaviour with
+function NPC:Act(deltat)
+end
+
+--What to replace ENT:OnStuck with
+function NPC:Stuck()
+
+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()
+end
+
+--If we need to do more than just reduce health on dammage
+function NPC:OnDammage(ammount)
+end
+
+--If we need to do more than just drop items on death
+function NPC:OnDeath()
+end
+
+--A particular spell was cast on this npc by player
+function NPC:OnSpell(spell, player)
+end
+
+function NPC:OnFindEnemy(enemy)
+end
+
+--Called when the npc is attacking anything with any attack
+function NPC:OnAttack(target)
+end
+*/
+
+ART.RegisterNPC(NPC)