diff options
| -rw-r--r-- | entities/entities/npc_huntable/cl_init.lua | 66 | ||||
| -rw-r--r-- | entities/entities/npc_huntable/init.lua | 70 | ||||
| -rw-r--r-- | entities/entities/npc_huntable/shared.lua | 144 | ||||
| -rw-r--r-- | gamemode/shared/loadnpcs.lua | 36 | ||||
| -rw-r--r-- | gamemode/shared/npcsystem/rat.lua | 113 |
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) |
