mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 13:23:46 +03:00
2282 lines
115 KiB
Lua
2282 lines
115 KiB
Lua
--[[
|
|
| This file was obtained through the combined efforts
|
|
| of Madbluntz & Plymouth Antiquarian Society.
|
|
|
|
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
|
| Maloy, DrPepper10 @ RIP, Atle!
|
|
|
|
|
| Visit for more: https://plymouth.thetwilightzone.ru/
|
|
--]]
|
|
|
|
/*--------------------------------------------------
|
|
*** Copyright (c) 2012-2023 by DrVrej, All rights reserved. ***
|
|
No parts of this code or any of its contents may be reproduced, copied, modified or adapted,
|
|
without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
|
|
///// NOTES \\\\\
|
|
- This file contains functions and variables shared between all the NPC bases.
|
|
- There are useful functions that are commonly called when making custom code in an NPC. (Custom-Friendly Functions)
|
|
- There are also functions that should be called with caution in a custom code. (General Functions)
|
|
--------------------------------------------------*/
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
## ## ### ######## #### ### ######## ## ######## ######
|
|
## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
|
|
## ## ## ## ## ## ## ## ## ## ## ## ## ##
|
|
## ## ## ## ######## ## ## ## ######## ## ###### ######
|
|
## ## ######### ## ## ## ######### ## ## ## ## ##
|
|
## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
|
|
### ## ## ## ## #### ## ## ######## ######## ######## ######
|
|
*/
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Localized static values
|
|
local NPCTbl_Animals = {npc_barnacle=true,npc_crow=true,npc_pigeon=true,npc_seagull=true,monster_cockroach=true}
|
|
local NPCTbl_Resistance = {npc_magnusson=true,npc_vortigaunt=true,npc_mossman=true,npc_monk=true,npc_kleiner=true,npc_fisherman=true,npc_eli=true,npc_dog=true,npc_barney=true,npc_alyx=true,npc_citizen=true,monster_scientist=true,monster_barney=true}
|
|
local NPCTbl_Combine = {npc_stalker=true,npc_rollermine=true,npc_turret_ground=true,npc_turret_floor=true,npc_turret_ceiling=true,npc_strider=true,npc_sniper=true,npc_metropolice=true,npc_hunter=true,npc_breen=true,npc_combine_camera=true,npc_combine_s=true,npc_combinedropship=true,npc_combinegunship=true,npc_cscanner=true,npc_clawscanner=true,npc_helicopter=true,npc_manhack=true}
|
|
local NPCTbl_Zombies = {npc_fastzombie_torso=true,npc_zombine=true,npc_zombie_torso=true,npc_zombie=true,npc_poisonzombie=true,npc_headcrab_fast=true,npc_headcrab_black=true,npc_headcrab=true,npc_fastzombie=true,monster_zombie=true,monster_headcrab=true,monster_babycrab=true}
|
|
local NPCTbl_Antlions = {npc_antlion=true,npc_antlionguard=true,npc_antlion_worker=true}
|
|
local NPCTbl_Xen = {monster_bullchicken=true,monster_alien_grunt=true,monster_alien_slave=true,monster_alien_controller=true,monster_houndeye=true,monster_gargantua=true,monster_nihilanth=true}
|
|
local defPos = Vector(0, 0, 0)
|
|
local defAng = Angle(0, 0, 0)
|
|
local CurTime = CurTime
|
|
local IsValid = IsValid
|
|
local GetConVar = GetConVar
|
|
local istable = istable
|
|
//local isstring = isstring
|
|
local isnumber = isnumber
|
|
//local tonumber = tonumber
|
|
//local string_find = string.find
|
|
//local string_Replace = string.Replace
|
|
local string_sub = string.sub
|
|
local table_remove = table.remove
|
|
local bAND = bit.band
|
|
local math_rad = math.rad
|
|
local math_cos = math.cos
|
|
local math_clamp = math.Clamp
|
|
local varCPly = "CLASS_PLAYER_ALLY"
|
|
local varCAnt = "CLASS_ANTLION"
|
|
local varCCom = "CLASS_COMBINE"
|
|
local varCXen = "CLASS_XEN"
|
|
local varCZom = "CLASS_ZOMBIE"
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
###### ## ## ###### ######## ####### ## ## ######## ######## #### ######## ## ## ######## ## ## ## ######## ## ## ## ## ###### ######## #### ####### ## ## ######
|
|
## ## ## ## ## ## ## ## ## ### ### ## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## ### ## ## ##
|
|
## ## ## ## ## ## ## #### #### ## ## ## ## ## #### ## ## ## ## #### ## ## ## #### ## ## ## ## ## ## #### ## ##
|
|
## ## ## ###### ## ## ## ## ### ## ####### ###### ######## ## ###### ## ## ## ## ## ## ## ###### ## ## ## ## ## ## ## ## ## ## ## ## ## ######
|
|
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## #### ##
|
|
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## ### ## ##
|
|
###### ####### ###### ## ####### ## ## ## ## ## #### ######## ## ## ######## ######## ## ## ####### ## ## ###### ## #### ####### ## ## ######
|
|
*/
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Creates a extra corpse entity, use this function to create extra corpse entities when the NPC is killed
|
|
- class = The object class to use, common types: "prop_ragdoll", "prop_physics"
|
|
- models = Model(s) to use, can be a table which it will pick randomly from it OR a string | "None" = Doesn't set a model
|
|
- extraOptions = Table that holds extra options to modify parts of the code
|
|
- Pos = Sets the spawn position
|
|
- Ang = Sets the spawn angle
|
|
- Vel = Sets the velocity | "UseDamageForce" = To use the damage's force only | DEFAULT = Uses damage force
|
|
- HasVel = If set to false, it won't set any velocity, allowing you to code your own in customFunc | DEFAULT = true
|
|
- ShouldFade = Should it fade away after certain time | DEFAULT = false
|
|
- ShouldFadeTime = How much time until the entity fades away | DEFAULT = 0
|
|
- RemoveOnCorpseDelete = Should the entity get removed if the corpse is removed? | DEFAULT = true
|
|
- customFunc(gib) = Use this to edit the entity which is given as parameter "gib"
|
|
-----------------------------------------------------------]]
|
|
local colorGrey = Color(90, 90, 90)
|
|
--
|
|
function ENT:CreateExtraDeathCorpse(class, models, extraOptions, customFunc)
|
|
-- Should only be ran after self.Corpse has been created!
|
|
if !IsValid(self.Corpse) then return end
|
|
local dmginfo = self.Corpse.DamageInfo
|
|
if dmginfo == nil then return end
|
|
class = class or "prop_ragdoll"
|
|
extraOptions = extraOptions or {}
|
|
local ent = ents.Create(class)
|
|
if models != "None" then ent:SetModel(VJ_PICK(models)) end
|
|
ent:SetPos(extraOptions.Pos or self:GetPos())
|
|
ent:SetAngles(extraOptions.Ang or self:GetAngles())
|
|
ent:Spawn()
|
|
ent:Activate()
|
|
ent:SetColor(self.Corpse:GetColor())
|
|
ent:SetMaterial(self.Corpse:GetMaterial())
|
|
ent:SetCollisionGroup(self.DeathCorpseCollisionType)
|
|
if self.Corpse:IsOnFire() then
|
|
ent:Ignite(math.Rand(8,10),0)
|
|
ent:SetColor(colorGrey)
|
|
end
|
|
if extraOptions.HasVel != false then
|
|
local dmgForce = (self.SavedDmgInfo.force / 40) + self:GetMoveVelocity() + self:GetVelocity()
|
|
if self.DeathAnimationCodeRan then
|
|
dmgForce = self:GetMoveVelocity() == defPos and self:GetGroundSpeedVelocity() or self:GetMoveVelocity()
|
|
end
|
|
ent:GetPhysicsObject():AddVelocity(extraOptions.Vel or dmgForce)
|
|
end
|
|
if extraOptions.ShouldFade == true then
|
|
local fadeTime = extraOptions.ShouldFadeTime or 0
|
|
if ent:GetClass() == "prop_ragdoll" then
|
|
ent:Fire("FadeAndRemove", "", fadeTime)
|
|
else
|
|
ent:Fire("kill", "", fadeTime)
|
|
end
|
|
end
|
|
if extraOptions.RemoveOnCorpseDelete != false then//self.Corpse:DeleteOnRemove(ent)
|
|
self.Corpse.ExtraCorpsesToRemove[#self.Corpse.ExtraCorpsesToRemove + 1] = ent
|
|
end
|
|
if (customFunc) then customFunc(ent) end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Creates a gib entity, use this function to create gib!
|
|
- class = The object class to use, recommended to use "obj_vj_gib", and for ragdoll type of gib use "prop_ragdoll"
|
|
- models = Model(s) to use, can be a table which it will pick randomly from it OR a string
|
|
- Defined strings: "UseAlien_Small", "UseAlien_Big", "UseHuman_Small", "UseHuman_Big"
|
|
- extraOptions = Table that holds extra options to modify parts of the code
|
|
- Pos = Sets the spawn position
|
|
- Ang = Sets the spawn angle | DEFAULT = Random angle
|
|
- Vel = Sets the velocity | "UseDamageForce" = To use the damage's force only | DEFAULT = Random velocity
|
|
- Vel_ApplyDmgForce = If set to false, it won't add the damage force to the given velocity | DEFAULT = true
|
|
- AngVel = Angle velocity, basically the speed it rotates as it's flying | DEFAULT = Random velocity
|
|
- BloodDecal = Decal it spawns when it collides with something | DEFAULT = Base decides
|
|
- BloodType = Sets the blood type by overriding the BloodDecal option | Works only on "obj_vj_gib" and it uses the same values as a VJ NPC blood types!
|
|
- CollideSound = The sound it plays when it collides with something | DEFAULT = Base decides
|
|
- NoFade = Should it let the base make it fade & remove (Adjusted in the SNPC settings menu) | DEFAULT = false
|
|
- RemoveOnCorpseDelete = Should the entity get removed if the corpse is removed? | DEFAULT = false
|
|
- customFunc(gib) = Use this to edit the entity which is given as parameter "gib"
|
|
-----------------------------------------------------------]]
|
|
local gib_mdlAAll = {"models/gibs/xenians/sgib_01.mdl","models/gibs/xenians/sgib_02.mdl","models/gibs/xenians/sgib_03.mdl","models/gibs/xenians/mgib_01.mdl","models/gibs/xenians/mgib_02.mdl","models/gibs/xenians/mgib_03.mdl","models/gibs/xenians/mgib_04.mdl","models/gibs/xenians/mgib_05.mdl","models/gibs/xenians/mgib_06.mdl","models/gibs/xenians/mgib_07.mdl"}
|
|
local gib_mdlASmall = {"models/gibs/xenians/sgib_01.mdl","models/gibs/xenians/sgib_02.mdl","models/gibs/xenians/sgib_03.mdl"}
|
|
local gib_mdlABig = {"models/gibs/xenians/mgib_01.mdl","models/gibs/xenians/mgib_02.mdl","models/gibs/xenians/mgib_03.mdl","models/gibs/xenians/mgib_04.mdl","models/gibs/xenians/mgib_05.mdl","models/gibs/xenians/mgib_06.mdl","models/gibs/xenians/mgib_07.mdl"}
|
|
local gib_mdlHSmall = {"models/gibs/humans/sgib_01.mdl","models/gibs/humans/sgib_02.mdl","models/gibs/humans/sgib_03.mdl"}
|
|
local gib_mdlHBig = {"models/gibs/humans/mgib_01.mdl","models/gibs/humans/mgib_02.mdl","models/gibs/humans/mgib_03.mdl","models/gibs/humans/mgib_04.mdl","models/gibs/humans/mgib_05.mdl","models/gibs/humans/mgib_06.mdl","models/gibs/humans/mgib_07.mdl"}
|
|
--
|
|
function ENT:CreateGibEntity(class, models, extraOptions, customFunc)
|
|
// self:CreateGibEntity("prop_ragdoll", "", {Pos=self:LocalToWorld(Vector(0,3,0)), Ang=self:GetAngles(), Vel=})
|
|
if self.AllowedToGib == false then return end
|
|
local bloodType = false
|
|
class = class or "obj_vj_gib"
|
|
if models == "UseAlien_Small" then
|
|
models = VJ_PICK(gib_mdlASmall)
|
|
bloodType = "Yellow"
|
|
elseif models == "UseAlien_Big" then
|
|
models = VJ_PICK(gib_mdlABig)
|
|
bloodType = "Yellow"
|
|
elseif models == "UseHuman_Small" then
|
|
models = VJ_PICK(gib_mdlHSmall)
|
|
bloodType = "Red"
|
|
elseif models == "UseHuman_Big" then
|
|
models = VJ_PICK(gib_mdlHBig)
|
|
bloodType = "Red"
|
|
else -- Custom models
|
|
models = VJ_PICK(models)
|
|
if VJ_HasValue(gib_mdlAAll, models) then
|
|
bloodType = "Yellow"
|
|
end
|
|
end
|
|
extraOptions = extraOptions or {}
|
|
local vel = extraOptions.Vel or Vector(math.Rand(-100, 100), math.Rand(-100, 100), math.Rand(150, 250))
|
|
if self.SavedDmgInfo then
|
|
local dmgForce = self.SavedDmgInfo.force / 70
|
|
if extraOptions.Vel_ApplyDmgForce != false && extraOptions.Vel != "UseDamageForce" then -- Use both damage force AND given velocity
|
|
vel = vel + dmgForce
|
|
elseif extraOptions.Vel == "UseDamageForce" then -- Use damage force
|
|
vel = dmgForce
|
|
end
|
|
end
|
|
bloodType = (extraOptions.BloodType or bloodType or self.BloodColor) -- Certain entities such as the VJ Gib entity, you can use this to set its gib type
|
|
local removeOnCorpseDelete = extraOptions.RemoveOnCorpseDelete or false -- Should the entity get removed if the corpse is removed?
|
|
local gib = ents.Create(class)
|
|
gib:SetModel(models)
|
|
gib:SetPos(extraOptions.Pos or (self:GetPos() + self:OBBCenter()))
|
|
gib:SetAngles(extraOptions.Ang or Angle(math.Rand(-180, 180), math.Rand(-180, 180), math.Rand(-180, 180)))
|
|
if gib:GetClass() == "obj_vj_gib" then
|
|
gib.BloodType = bloodType
|
|
gib.Collide_Decal = extraOptions.BloodDecal or "Default"
|
|
gib.CollideSound = extraOptions.CollideSound or "Default"
|
|
//gib.BloodData = {Color = bloodType, Particle = self.CustomBlood_Particle, Decal = self.Collide_Decal} -- For eating system
|
|
end
|
|
gib:Spawn()
|
|
gib:Activate()
|
|
gib.IsVJBase_Gib = true
|
|
gib.RemoveOnCorpseDelete = removeOnCorpseDelete
|
|
if GetConVar("vj_npc_gibcollidable"):GetInt() == 0 then gib:SetCollisionGroup(1) end
|
|
local phys = gib:GetPhysicsObject()
|
|
if IsValid(phys) then
|
|
phys:AddVelocity(vel)
|
|
phys:AddAngleVelocity(extraOptions.AngVel or Vector(math.Rand(-200, 200), math.Rand(-200, 200), math.Rand(-200, 200)))
|
|
end
|
|
if extraOptions.NoFade != true && GetConVar("vj_npc_fadegibs"):GetInt() == 1 then
|
|
if gib:GetClass() == "obj_vj_gib" then timer.Simple(GetConVar("vj_npc_fadegibstime"):GetInt(), function() SafeRemoveEntity(gib) end)
|
|
elseif gib:GetClass() == "prop_ragdoll" then gib:Fire("FadeAndRemove", "", GetConVar("vj_npc_fadegibstime"):GetInt())
|
|
elseif gib:GetClass() == "prop_physics" then gib:Fire("kill", "", GetConVar("vj_npc_fadegibstime"):GetInt()) end
|
|
end
|
|
if removeOnCorpseDelete == true then //self.Corpse:DeleteOnRemove(extraent)
|
|
self.ExtraCorpsesToRemove_Transition[#self.ExtraCorpsesToRemove_Transition + 1] = gib
|
|
end
|
|
if (customFunc) then customFunc(gib) end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[
|
|
More info about sound hints: https://github.com/DrVrej/VJ-Base/wiki/Developer-Notes#sound-hints
|
|
-- Condition -- -- Sound bit -- -- Suggested Use --
|
|
COND_HEAR_DANGER SOUND_DANGER Danger
|
|
COND_HEAR_PHYSICS_DANGER SOUND_PHYSICS_DANGER Danger
|
|
COND_HEAR_MOVE_AWAY SOUND_MOVE_AWAY Danger
|
|
COND_HEAR_COMBAT SOUND_COMBAT Interest
|
|
COND_HEAR_WORLD SOUND_WORLD Interest
|
|
COND_HEAR_BULLET_IMPACT SOUND_BULLET_IMPACT Interest
|
|
COND_HEAR_PLAYER SOUND_PLAYER Interest
|
|
COND_SMELL SOUND_CARCASS/SOUND_MEAT/SOUND_GARBAGE Smell
|
|
COND_HEAR_THUMPER SOUND_THUMPER Special case
|
|
COND_HEAR_BUGBAIT SOUND_BUGBAIT Special case
|
|
COND_NO_HEAR_DANGER none No danger detected
|
|
COND_HEAR_SPOOKY none Not possible in GMod due to the missing SOUNDENT_CHANNEL_SPOOKY_NOISE
|
|
--]]
|
|
local sdInterests = bit.bor(SOUND_COMBAT, SOUND_DANGER, SOUND_BULLET_IMPACT, SOUND_PHYSICS_DANGER, SOUND_MOVE_AWAY, SOUND_PLAYER_VEHICLE, SOUND_PLAYER, SOUND_WORLD, SOUND_CARCASS, SOUND_MEAT, SOUND_GARBAGE)
|
|
--
|
|
function ENT:GetSoundInterests()
|
|
return sdInterests
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Reset and stop the eating behavior
|
|
- statusInfo = Status info to pass to "CustomOnEat" (info types defined in that function)
|
|
-----------------------------------------------------------]]
|
|
function ENT:EatingReset(resetType)
|
|
local eatingData = self.EatingData
|
|
self:SetState(VJ_STATE_NONE)
|
|
self:CustomOnEat("StopEating", resetType)
|
|
self.VJTags[VJ_TAG_EATING] = nil
|
|
self:SetIdleAnimation(eatingData.OldIdleTbl, true) -- Reset the idle animation table in case it changed!
|
|
local food = eatingData.Ent
|
|
if IsValid(food) then
|
|
local foodData = food.FoodData
|
|
-- if we are the last person eating, then reset the food data!
|
|
if foodData.NumConsumers <= 1 then
|
|
food.VJTags[VJ_TAG_BEING_EATEN] = nil
|
|
foodData.NumConsumers = 0
|
|
foodData.SizeRemaining = foodData.Size
|
|
else
|
|
foodData.NumConsumers = foodData.NumConsumers - 1
|
|
foodData.SizeRemaining = foodData.SizeRemaining + self:OBBMaxs():Distance(self:OBBMins())
|
|
end
|
|
end
|
|
self.EatingData = {Ent = NULL, NextCheck = eatingData.NextCheck, AnimStatus = "None", OldIdleTbl = nil}
|
|
-- AnimStatus: "None" = Not prepared (Probably moving to food location) | "Prepared" = Prepared (Ex: Played crouch down anim) | "Eating" = Prepared and is actively eating
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Called every time a change occurs in the eating system
|
|
- status = The change that occurred, possible changes:
|
|
- "CheckFood" = Possible food found, check if it's good
|
|
- "StartBehavior" = Food found, start the eating behavior
|
|
- "BeginEating" = Food location reached
|
|
- "Eat" = Actively eating food
|
|
- "StopEating" = Food may have moved, removed, or finished
|
|
- statusInfo = Some status may have extra info, possible infos:
|
|
- "CheckFood": SoundHintData table, more info: https://wiki.facepunch.com/gmod/Structures/SoundHintData
|
|
- "StopEating":
|
|
- "HaltOnly" = This is ONLY a halt, not complete reset! | Recommendation: Play normal get up anim
|
|
- "Unspecified" = Ex: Food suddenly removed or moved far away | Recommendation: Play normal get up anim
|
|
- "Devoured" = Has completely devoured the food! | Recommendation: Play normal get up anim and play a sound
|
|
- "Enemy" = Has been alerted or detected an enemy | Recommendation: Play scared get up anim
|
|
- "Injured" = Has been injured by something | Recommendation: Play scared get up anim
|
|
- "Dead" = Has died, usually called in "OnRemove" | Recommendation: Do NOT play any!
|
|
Returns
|
|
- Boolean, ONLY used for "CheckFood", returning true will tell the base the possible food is valid
|
|
- Number, Delay to add before moving to another status, useful to make sure animations aren't cut off!
|
|
-----------------------------------------------------------]]
|
|
local vecZ50 = Vector(0, 0, -50)
|
|
--
|
|
function ENT:CustomOnEat(status, statusInfo)
|
|
-- The following code is a ideal example based on Half-Life 1 Zombie
|
|
//print(self, "Eating Status: ", status, statusInfo)
|
|
if status == "CheckFood" then
|
|
return true //statusInfo.owner.BloodData && statusInfo.owner.BloodData.Color == "Red"
|
|
elseif status == "BeginEating" then
|
|
self:SetIdleAnimation({ACT_GESTURE_RANGE_ATTACK1}, true)
|
|
return self:VJ_ACT_PLAYACTIVITY(ACT_ARM, true, false)
|
|
elseif status == "Eat" then
|
|
VJ_EmitSound(self, "barnacle/bcl_chew"..math.random(1, 3)..".wav", 55)
|
|
-- Health changes
|
|
local food = self.EatingData.Ent
|
|
local damage = 15 -- How much damage food will receive
|
|
local foodHP = food:Health() -- Food's health
|
|
self:SetHealth(math.Clamp(self:Health() + ((damage > foodHP and foodHP) or damage), self:Health(), self:GetMaxHealth())) -- Give health to the NPC
|
|
food:SetHealth(foodHP - damage) -- Decrease corpse health
|
|
-- Blood effects
|
|
local bloodData = food.BloodData
|
|
if bloodData then
|
|
local bloodPos = food:GetPos() + food:OBBCenter()
|
|
local bloodParticle = VJ_PICK(bloodData.Particle)
|
|
if bloodParticle then
|
|
ParticleEffect(bloodParticle, bloodPos, self:GetAngles())
|
|
end
|
|
local bloodDecal = VJ_PICK(bloodData.Decal)
|
|
if bloodDecal then
|
|
local tr = util.TraceLine({start = bloodPos, endpos = bloodPos + vecZ50, filter = {food, self}})
|
|
util.Decal(bloodDecal, tr.HitPos + tr.HitNormal + Vector(math.random(-45, 45), math.random(-45, 45), 0), tr.HitPos - tr.HitNormal, food)
|
|
end
|
|
end
|
|
return 2 -- Eat every this seconds
|
|
elseif status == "StopEating" then
|
|
if statusInfo != "Dead" && self.EatingData.AnimStatus != "None" then -- Do NOT play anim while dead or has NOT prepared to eat
|
|
return self:VJ_ACT_PLAYACTIVITY(ACT_DISARM, true, false)
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Sets the NPC's idle animation table
|
|
- anims = Table with animation(s)
|
|
- reset = Should it reset the idle animation?
|
|
-----------------------------------------------------------]]
|
|
function ENT:SetIdleAnimation(anims, reset)
|
|
self.AnimTbl_IdleStand = anims
|
|
if reset then
|
|
self.CurrentAnim_IdleStand = -1
|
|
//self.NextIdleStandTime = 0
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks if the NPC is playing an animation that shouldn't be interrupted OR is playing an attack!
|
|
Returns
|
|
- false, NOT busy
|
|
- true, Busy
|
|
-----------------------------------------------------------]]
|
|
function ENT:BusyWithActivity()
|
|
return self.vACT_StopAttacks or self.PlayingAttackAnimation or self:GetNavType() == NAV_JUMP or self:GetNavType() == NAV_CLIMB
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks if the NPC is busy with advanced behaviors like following player or moving to heal an ally
|
|
Returns
|
|
- false, NOT busy
|
|
- true, Busy
|
|
-----------------------------------------------------------]]
|
|
function ENT:IsBusyWithBehavior()
|
|
return self.FollowData.Moving or self.Medic_Status
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks if the NPC is busy with an animation or activity AND if it's busy with an advanced behavior
|
|
Returns
|
|
- false, NOT busy
|
|
- true, Busy
|
|
-----------------------------------------------------------]]
|
|
function ENT:IsBusy()
|
|
return self:BusyWithActivity() or self:IsBusyWithBehavior()
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Sets the state of the NPC, states are prefixed with VJ_STATE_*
|
|
- state = The state it should set it to | DEFAULT = VJ_STATE_NONE
|
|
- time = How long should the state apply before it's reset to VJ_STATE_NONE? | DEFAULT = -1
|
|
-1 = State stays indefinitely until reset or changed
|
|
-----------------------------------------------------------]]
|
|
function ENT:SetState(state, time)
|
|
state = state or VJ_STATE_NONE
|
|
time = time or -1
|
|
self.AIState = state
|
|
if state == VJ_STATE_FREEZE or self:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) then -- Reset the tasks
|
|
self:TaskComplete()
|
|
self:VJ_TASK_IDLE_STAND()
|
|
end
|
|
if time >= 0 then
|
|
timer.Create("timer_state_reset"..self:EntIndex(), time, 1, function()
|
|
self:SetState()
|
|
end)
|
|
else
|
|
timer.Remove("timer_state_reset"..self:EntIndex())
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Returns the current state of the NPC
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetState()
|
|
return self.AIState
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks the relationship with the given entity | Use with caution, it can reduce performance!
|
|
- ent = The entity to check its relation with
|
|
Returns
|
|
- Disposition value, list: https://wiki.facepunch.com/gmod/Enums/D
|
|
-----------------------------------------------------------]]
|
|
function ENT:CheckRelationship(ent)
|
|
if ent.VJ_AlwaysEnemyToEnt == self then return D_HT end -- Always enemy to me (Used by the bullseye under certain circumstances)
|
|
if ent:IsFlagSet(FL_NOTARGET) or ent.VJ_NoTarget or NPCTbl_Animals[ent:GetClass()] then return D_NU end
|
|
if self:GetClass() == ent:GetClass() then return D_LI end
|
|
if ent:Health() > 0 && self:Disposition(ent) != D_LI then
|
|
local isPly = ent:IsPlayer()
|
|
if isPly && VJ_CVAR_IGNOREPLAYERS then return D_NU end
|
|
if VJ_HasValue(self.VJ_AddCertainEntityAsFriendly, ent) then return D_LI end
|
|
if VJ_HasValue(self.VJ_AddCertainEntityAsEnemy, ent) then return D_HT end
|
|
local entDisp = ent.Disposition and ent:Disposition(self)
|
|
if (ent:IsNPC() && ((entDisp == D_HT) or (entDisp == D_NU && ent.VJ_IsBeingControlled))) or (isPly && self.PlayerFriendly == false && ent:Alive()) then
|
|
return D_HT
|
|
else
|
|
return D_NU
|
|
end
|
|
end
|
|
return D_LI
|
|
end
|
|
-- !!!!!!!!!!!!!! DO NOT USE THESE !!!!!!!!!!!!!! [Backwards Compatibility!]
|
|
local dispToVal = {[D_LI] = false, [D_HT] = true, [D_NU] = "Neutral"}
|
|
function ENT:DoRelationshipCheck(ent) return dispToVal[self:CheckRelationship(ent)] end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Helps you decide the pitch for the NPC, very useful for speech-type of sounds!
|
|
- pitch1 = Random min, set to false to self.GeneralSoundPitch1 | DEFAULT = self.GeneralSoundPitch1
|
|
- pitch2 = Random max, set to false to self.GeneralSoundPitch2 | DEFAULT = self.GeneralSoundPitch2
|
|
NOTE: if self.UseTheSameGeneralSoundPitch is true then the default values will be self.UseTheSameGeneralSoundPitch_PickedNumber
|
|
Returns
|
|
- Number, the randomized number between pitch1 & pitch2
|
|
-----------------------------------------------------------]]
|
|
function ENT:VJ_DecideSoundPitch(pitch1, pitch2)
|
|
local finalPitch1 = self.GeneralSoundPitch1
|
|
local finalPitch2 = self.GeneralSoundPitch2
|
|
local picknum = self.UseTheSameGeneralSoundPitch_PickedNumber
|
|
-- If the NPC is set to use the same sound pitch all the time and it's not 0 then use that pitch
|
|
if self.UseTheSameGeneralSoundPitch == true && picknum != 0 then
|
|
finalPitch1 = picknum
|
|
finalPitch2 = picknum
|
|
end
|
|
if pitch1 != false && isnumber(pitch1) then finalPitch1 = pitch1 end
|
|
if pitch2 != false && isnumber(pitch2) then finalPitch2 = pitch2 end
|
|
return math.random(finalPitch1, finalPitch2)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Gets the forward vector that the NPC is moving towards and returns it
|
|
- ignoreZ = Ignores the Z axis of the direction during calculations | DEFAULT = false
|
|
Returns
|
|
- Vector, the direction the NPC is moving towards
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetMoveDirection(ignoreZ)
|
|
if !self:IsMoving() then return defPos end
|
|
local myPos = self:GetPos()
|
|
local dir = ((self:GetCurWaypointPos() or myPos) - myPos)
|
|
if ignoreZ then dir.z = 0 end
|
|
return (self:GetAngles() - dir:Angle()):Forward()
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Gets all the pose the parameters of the NPC and returns it.
|
|
- prt = Prints the pose parameters
|
|
Returns
|
|
- table of all the pose parameters
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetPoseParameters(prt)
|
|
local result = {}
|
|
for i = 0, self:GetNumPoseParameters() - 1 do
|
|
if prt == true then
|
|
local min, max = self:GetPoseParameterRange(i)
|
|
print(self:GetPoseParameterName(i)..' '..min.." / "..max)
|
|
end
|
|
table.insert(result, self:GetPoseParameterName(i))
|
|
end
|
|
return result
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:GetFaceAngle(ang)
|
|
if self.TurningUseAllAxis == true then
|
|
return Angle(ang.x, ang.y, ang.z)
|
|
end
|
|
return Angle(0, ang.y, 0)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Makes the NPC face a certain position
|
|
- pos = Position to face
|
|
- faceTime = How long should it face that position? | DEFAULT = 0
|
|
Returns
|
|
- Angle, the angle it's going to face
|
|
-----------------------------------------------------------]]
|
|
function ENT:FaceCertainPosition(pos, faceTime)
|
|
local faceAng = self:GetFaceAngle(((pos or defPos) - self:GetPos()):Angle())
|
|
if self.TurningUseAllAxis == true then
|
|
local myAngs = self:GetAngles()
|
|
self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r)))
|
|
end
|
|
self:SetIdealYawAndUpdate(faceAng.y)
|
|
//if self:IsSequenceFinished() then self:UpdateTurnActivity() end
|
|
self.FacingStatus = VJ_FACING_POSITION
|
|
self.FacingData = faceAng
|
|
timer.Create("timer_facing_end"..self:EntIndex(), faceTime or 0, 1, function() self.FacingStatus = VJ_FACING_NONE; self.FacingData = nil end)
|
|
return faceAng
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Makes the NPC face a certain entity
|
|
- ent = Entity to face
|
|
- faceCurEnemy = Should this be registered as enemy facing and constantly switch to the current enemy during its face time? | DEFAULT = true
|
|
- faceTime = How long should it face the entity? | DEFAULT = 0
|
|
Returns
|
|
- Angle, the angle it's going to face
|
|
- false, if it didn't face the entity
|
|
-----------------------------------------------------------]]
|
|
function ENT:FaceCertainEntity(ent, faceCurEnemy, faceTime)
|
|
if !IsValid(ent) or GetConVar("ai_disabled"):GetInt() == 1 or (self.MovementType == VJ_MOVETYPE_STATIONARY && !self.CanTurnWhileStationary) then return false end
|
|
if faceCurEnemy then
|
|
if IsValid(self:GetEnemy()) then -- Make sure to only do it if it even has an enemy
|
|
local faceAng = self:GetFaceAngle((ent:GetPos() - self:GetPos()):Angle())
|
|
if self.TurningUseAllAxis == true then
|
|
local myAngs = self:GetAngles()
|
|
self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r)))
|
|
end
|
|
self:SetIdealYawAndUpdate(faceAng.y)
|
|
//if self:IsSequenceFinished() then self:UpdateTurnActivity() end
|
|
self.FacingStatus = VJ_FACING_ENEMY
|
|
self.FacingData = ent
|
|
timer.Create("timer_facing_end"..self:EntIndex(), faceTime or 0, 1, function() self.FacingStatus = VJ_FACING_NONE; self.FacingData = nil end)
|
|
return faceAng
|
|
end
|
|
return false
|
|
else
|
|
local faceAng = self:GetFaceAngle((ent:GetPos() - self:GetPos()):Angle())
|
|
if self.TurningUseAllAxis == true then
|
|
local myAngs = self:GetAngles()
|
|
self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r)))
|
|
end
|
|
self:SetIdealYawAndUpdate(faceAng.y)
|
|
//if self:IsSequenceFinished() then self:UpdateTurnActivity() end
|
|
self.FacingStatus = VJ_FACING_ENTITY
|
|
self.FacingData = ent
|
|
timer.Create("timer_facing_end"..self:EntIndex(), faceTime or 0, 1, function() self.FacingStatus = VJ_FACING_NONE; self.FacingData = nil end)
|
|
return faceAng
|
|
end
|
|
return false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Gets the position set usually by the function self:SetLastPosition(vec)
|
|
Returns
|
|
- Vector, the last position
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetLastPosition()
|
|
return self:GetInternalVariable("m_vecLastPosition")
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Performs a group formation
|
|
- formType = Type of formation it should do
|
|
- Types: "Diamond"
|
|
- baseEnt = The entity to base its position on, should be the same for all the members in the group!
|
|
- it = The place of the NPC in the group | DEFAULT = 0
|
|
- spacing = How far apart should they be? | DEFAULT = 50
|
|
-----------------------------------------------------------]]
|
|
function ENT:DoGroupFormation(formType, baseEnt, it, spacing)
|
|
it = it or 0
|
|
spacing = spacing or 50
|
|
if formType == "Diamond" then
|
|
if it == 0 then
|
|
self:SetLastPosition(baseEnt:GetPos() + baseEnt:GetForward()*spacing + baseEnt:GetRight()*spacing)
|
|
elseif it == 1 then
|
|
self:SetLastPosition(baseEnt:GetPos() + baseEnt:GetForward()*-spacing + baseEnt:GetRight()*spacing)
|
|
elseif it == 2 then
|
|
self:SetLastPosition(baseEnt:GetPos() + baseEnt:GetForward()*spacing + baseEnt:GetRight()*-spacing)
|
|
elseif it == 3 then
|
|
self:SetLastPosition(baseEnt:GetPos() + baseEnt:GetForward()*-spacing + baseEnt:GetRight()*-spacing)
|
|
else
|
|
self:SetLastPosition(baseEnt:GetPos() + baseEnt:GetForward()*(spacing + (3 * it)) + baseEnt:GetRight()*(spacing + (3 * it)))
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks if the front of the NPC can be used to take cover.
|
|
- startPos = Start position of the trace | DEFAULT = Center of the NPC
|
|
- endPos = End position of the trace | DEFAULT = Enemy's eye position
|
|
- acceptWorld = If it hits the world, it will accept it as a cover | DEFAULT = false
|
|
- extraOptions = Table that holds extra options to modify parts of the code
|
|
- SetLastHiddenTime = If true, it will reset the "LastHidden" time, which makes the NPC stick to a position if it's well covered | DEFAULT = false
|
|
- Debug = Used for debugging, spawns a cube at the hit position and prints the trace result | DEFAULT = false
|
|
Returns 2 values
|
|
- 1:
|
|
- true, Hidden
|
|
- false, NOT hidden
|
|
- 2:
|
|
- Table, trace result
|
|
-----------------------------------------------------------]]
|
|
function ENT:VJ_ForwardIsHidingZone(startPos, endPos, acceptWorld, extraOptions)
|
|
local ene = self:GetEnemy()
|
|
if !IsValid(ene) then return false, {} end
|
|
startPos = startPos or self:GetPos() + self:OBBCenter()
|
|
endPos = endPos or ene:EyePos()
|
|
acceptWorld = acceptWorld or false
|
|
extraOptions = extraOptions or {}
|
|
local setLastHiddenTime = extraOptions.SetLastHiddenTime or false
|
|
local tr = util.TraceLine({
|
|
start = startPos,
|
|
endpos = endPos,
|
|
filter = self
|
|
})
|
|
local hitPos = tr.HitPos
|
|
local hitEnt = tr.Entity
|
|
if extraOptions.Debug == true then
|
|
print("--------------------------------------------")
|
|
PrintTable(tr)
|
|
VJ_CreateTestObject(hitPos)
|
|
end
|
|
-- Sometimes the trace isn't 100%, a tiny find in sphere check fixes this issue...
|
|
local sphereInvalidate = false
|
|
for _,v in ipairs(ents.FindInSphere(hitPos, 5)) do
|
|
if v == ene or v:IsNPC() or v:IsPlayer() then
|
|
sphereInvalidate = true
|
|
end
|
|
end
|
|
|
|
-- Not a hiding zone: (Sphere found an enemy/NPC/Player) OR (Trace ent is an enemy/NPC/Player) OR (End pos is far from the hit position) OR (World is NOT accepted as a hiding zone)
|
|
if sphereInvalidate or (IsValid(hitEnt) && (hitEnt == ene or hitEnt:IsNPC() or hitEnt:IsPlayer())) or endPos:Distance(hitPos) <= 10 or (acceptWorld == false && tr.HitWorld == true) then
|
|
if setLastHiddenTime == true then self.LastHiddenZoneT = 0 end
|
|
return false, tr
|
|
-- Hiding zone: It hit world AND it's close
|
|
elseif tr.HitWorld == true && self:GetPos():Distance(hitPos) < 200 then
|
|
if setLastHiddenTime == true then self.LastHiddenZoneT = CurTime() + 20 end
|
|
return true, tr
|
|
else -- Hidden!
|
|
if setLastHiddenTime == true then self.LastHiddenZoneT = CurTime() + 20 end
|
|
return true, tr
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks all 4 sides around the NPC
|
|
- checkDist = How far should each trace go? | DEFAULT = 200
|
|
- returnPos = Instead of returning a table of sides, it will return a table of actual positions | DEFAULT: false
|
|
- Use this whenever possible as it is much more optimized to utilize!
|
|
- sides = Use this to disable checking certain positions by setting the 1 to 0, "Forward-Backward-Right-Left" | DEFAULT = "1111"
|
|
Returns
|
|
- When returnPos is true:
|
|
- Table of positions (4 max)
|
|
- When returnPos is false:
|
|
- Table dictionary, includes 4 values, if true then that side isn't blocked!
|
|
- Values: Forward, Backward, Right, Left
|
|
-----------------------------------------------------------]]
|
|
local str1111 = "1111"
|
|
local str1 = "1"
|
|
--
|
|
function ENT:VJ_CheckAllFourSides(checkDist, returnPos, sides)
|
|
checkDist = checkDist or 200
|
|
sides = sides or str1111
|
|
local result = returnPos == true and {} or {Forward=false, Backward=false, Right=false, Left=false}
|
|
local i = 0
|
|
local myPos = self:GetPos()
|
|
local myPosCentered = myPos + self:OBBCenter()
|
|
local myForward = self:GetForward()
|
|
local myRight = self:GetRight()
|
|
local positions = { -- Set the positions that we need to check
|
|
string_sub(sides, 1, 1) == str1 and myForward or 0,
|
|
string_sub(sides, 2, 2) == str1 and -myForward or 0,
|
|
string_sub(sides, 3, 3) == str1 and myRight or 0,
|
|
string_sub(sides, 4, 4) == str1 and -myRight or 0
|
|
}
|
|
for _, v in ipairs(positions) do
|
|
i = i + 1
|
|
if v == 0 then continue end -- If 0 then we have the tag to skip this!
|
|
local tr = util.TraceLine({
|
|
start = myPosCentered,
|
|
endpos = myPosCentered + v*checkDist,
|
|
filter = self
|
|
})
|
|
local hitPos = tr.HitPos
|
|
if myPos:Distance(hitPos) >= checkDist then
|
|
if returnPos == true then
|
|
hitPos.z = myPos.z -- Reset it to self:GetPos() z-axis
|
|
result[#result + 1] = hitPos
|
|
elseif i == 1 then
|
|
result.Forward = true
|
|
elseif i == 2 then
|
|
result.Backward = true
|
|
elseif i == 3 then
|
|
result.Right = true
|
|
elseif i == 4 then
|
|
result.Left = true
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Sets the enemy for the NPC, this function should always be used over the default GMod self:SetEnemy()!
|
|
- ent = The entity to set as the enemy
|
|
- stopMoving = Should it stop moving? Will not run if doQuickIfActiveEnemy passes! | DEFAULT = false
|
|
- doQuickIfActiveEnemy = Runs a quicker set enemy without resetting everything, it must have a active enemy! | DEFAULT = false
|
|
-----------------------------------------------------------]]
|
|
function ENT:VJ_DoSetEnemy(ent, stopMoving, doQuickIfActiveEnemy)
|
|
if !IsValid(ent) or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE or ent:Health() <= 0 or (ent:IsPlayer() && (!ent:Alive() or VJ_CVAR_IGNOREPLAYERS)) then return end
|
|
stopMoving = stopMoving or false -- Will not run if doQuickIfActiveEnemy passes!
|
|
doQuickIfActiveEnemy = doQuickIfActiveEnemy or false -- It will run a much quicker set enemy without resetting everything (Only if it has an active enemy!)
|
|
if IsValid(self.Medic_CurrentEntToHeal) && self.Medic_CurrentEntToHeal == ent then self:DoMedicReset() end
|
|
local eneData = self.EnemyData
|
|
eneData.TimeSet = CurTime()
|
|
self:AddEntityRelationship(ent, D_HT, 0)
|
|
self:UpdateEnemyMemory(ent, ent:GetPos())
|
|
self:SetNPCState(NPC_STATE_COMBAT)
|
|
if doQuickIfActiveEnemy == true && IsValid(self:GetEnemy()) then
|
|
self:SetEnemy(ent)
|
|
return -- End it here if it's a minor set enemy
|
|
end
|
|
self:SetEnemy(ent)
|
|
eneData.TimeSinceAcquired = CurTime()
|
|
//self.NextResetEnemyT = CurTime() + 0.5 //2
|
|
if stopMoving == true then
|
|
self:ClearGoal()
|
|
self:StopMoving()
|
|
end
|
|
if self.Alerted == false then
|
|
self.LatestEnemyDistance = self:GetPos():Distance(ent:GetPos())
|
|
self:DoAlert(ent)
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Sets the NPC's sight distance
|
|
- dist = The new sight distance to set
|
|
-----------------------------------------------------------]]
|
|
function ENT:SetSightDistance(dist)
|
|
//self:Fire("SetMaxLookDistance", dist) -- For Source sensing distance (OLD)
|
|
self:SetMaxLookDistance(dist) -- For Source sight & sensing distance
|
|
self:SetSaveValue("m_flDistTooFar", dist) -- For certain Source attack, weapon, and condition distances
|
|
self.SightDistance = dist -- For VJ Base
|
|
//print(self:GetInternalVariable("m_flDistTooFar"), self:GetMaxLookDistance())
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Forces the NPC to jump.
|
|
- vel = Velocity for the jump
|
|
EX: Force the NPC to jump to the location of another entity:
|
|
self:ForceMoveJump((activator:GetPos() - self:GetPos()):GetNormal()*200 + Vector(0, 0, 300))
|
|
-----------------------------------------------------------]]
|
|
function ENT:ForceMoveJump(vel)
|
|
self.NextIdleStandTime = 0
|
|
self:SetNavType(NAV_JUMP)
|
|
self:MoveJumpStart(vel)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Checks if the given damage type(s) contains 1 or more of the default gibbing damage types.
|
|
- dmgType = The damage type(s) to check for
|
|
EX: dmginfo:GetDamageType()
|
|
Returns
|
|
- true, At least 1 damage type is included
|
|
- false, NO damage type is included
|
|
-----------------------------------------------------------]]
|
|
// - DMG_DIRECT = Disabled because fire uses it!
|
|
// - DMG_ALWAYSGIB = Make sure damage is NOT a bullet because GMod sets DMG_ALWAYSGIB randomly for certain bullets (Maybe if the damage is high?)
|
|
function ENT:IsDefaultGibDamageType(dmgType)
|
|
return (bAND(dmgType, DMG_ALWAYSGIB) != 0 && bAND(dmgType, DMG_BULLET) == 0) or bAND(dmgType, DMG_ENERGYBEAM) != 0 or bAND(dmgType, DMG_BLAST) != 0 or bAND(dmgType, DMG_VEHICLE) != 0 or bAND(dmgType, DMG_CRUSH) != 0 or bAND(dmgType, DMG_DISSOLVE) != 0 or bAND(dmgType, DMG_SLOWBURN) != 0 or bAND(dmgType, DMG_PHYSGUN) != 0 or bAND(dmgType, DMG_PLASMA) != 0 or bAND(dmgType, DMG_SONIC) != 0
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
The last damage hit group that the NPC received.
|
|
Returns
|
|
- number, the hit group
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetLastDamageHitGroup()
|
|
return self:GetInternalVariable("m_LastHitGroup")
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Time since the NPC has been damaged (Used CurTime!)
|
|
Returns
|
|
- number, time
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetLastDamageTime()
|
|
return self:GetInternalVariable("m_flLastDamageTime")
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Number of times NPC has been damaged. Useful for tracking 1-shot kills.
|
|
Returns
|
|
- number, the damage count
|
|
-----------------------------------------------------------]]
|
|
function ENT:GetTotalDamageCount()
|
|
return self:GetInternalVariable("m_iDamageCount")
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:VJ_GetNearestPointToVector(pos, sameZ)
|
|
-- sameZ = Should the Z of the result pos (resPos) be the same as my Z ?
|
|
local myZ = self:GetPos().z
|
|
local resMe = self:NearestPoint(pos + self:OBBCenter())
|
|
resMe.z = myZ
|
|
local resPos = pos
|
|
resPos.z = sameZ and myZ or pos.z
|
|
return resMe, resPos
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:VJ_GetNearestPointToEntity(ent, sameZ)
|
|
-- sameZ = Should the Z of the other entity's result pos (resEnt) be the same as my Z ?
|
|
local myNearPoint = self:GetDynamicOrigin()
|
|
local resMe = self:NearestPoint(ent:GetPos() + self:OBBCenter())
|
|
resMe.z = myNearPoint.z
|
|
local resEnt = ent:NearestPoint(myNearPoint + ent:OBBCenter())
|
|
resEnt.z = sameZ and myNearPoint.z or ent:GetPos().z
|
|
return resMe, resEnt
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:VJ_GetNearestPointToEntityDistance(ent)
|
|
local entPos = ent:GetPos()
|
|
local myNearPoint = self:GetDynamicOrigin()
|
|
local resMe = self:NearestPoint(entPos + self:OBBCenter())
|
|
resMe.z = myNearPoint.z
|
|
local resEnt = ent:NearestPoint(myNearPoint + ent:OBBCenter())
|
|
resEnt.z = entPos.z
|
|
return resEnt:Distance(resMe)
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
###### ######## ## ## ######## ######## ### ## ######## ## ## ## ## ###### ######## #### ####### ## ## ######
|
|
## ## ## ### ## ## ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## ### ## ## ##
|
|
## ## #### ## ## ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## #### ## ##
|
|
## #### ###### ## ## ## ###### ######## ## ## ## ###### ## ## ## ## ## ## ## ## ## ## ## ## ## ######
|
|
## ## ## ## #### ## ## ## ######### ## ## ## ## ## #### ## ## ## ## ## ## #### ##
|
|
## ## ## ## ### ## ## ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## ### ## ##
|
|
###### ######## ## ## ######## ## ## ## ## ######## ## ####### ## ## ###### ## #### ####### ## ## ######
|
|
*/
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:IsJumpLegal(startPos, apex, endPos)
|
|
/*print("---------------------")
|
|
print(startPos)
|
|
print(apex)
|
|
print(endPos)*/
|
|
if !self.AllowMovementJumping then return false end
|
|
local result = self:CustomOnIsJumpLegal(startPos, apex, endPos)
|
|
if result != nil then
|
|
/*if result == true then
|
|
self.JumpLegalLandingTime = CurTime() + (endPos:Distance(startPos) / 190)
|
|
end*/
|
|
return result
|
|
end
|
|
local dist_apex = startPos:Distance(apex)
|
|
local dist_end = startPos:Distance(endPos)
|
|
local maxdist = self.MaxJumpLegalDistance.a -- Var gam Ver | Arachin tive varva hamar ter
|
|
-- Aravel = Ver, Nevaz = Var
|
|
if (endPos.z - startPos.z) <= 0 then maxdist = self.MaxJumpLegalDistance.b end -- Ver bidi tsadke
|
|
/*print("---------------------")
|
|
print(endPos.z - startPos.z)
|
|
print("Apex: "..dist_apex)
|
|
print("End Pos: "..dist_end)*/
|
|
if (dist_apex > maxdist) or (dist_end > maxdist) then return false end
|
|
//self.JumpLegalLandingTime = CurTime() + (endPos:Distance(startPos) / 190)
|
|
return true
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:OnChangeActivity(newAct)
|
|
//print(newAct)
|
|
self:CustomOnChangeActivity(newAct)
|
|
if newAct == ACT_TURN_LEFT or newAct == ACT_TURN_RIGHT then
|
|
self.NextIdleStandTime = CurTime() + VJ_GetSequenceDuration(self, self:GetSequenceName(self:GetSequence()))
|
|
//self.NextIdleStandTime = CurTime() + 1.2
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:KeyValue(key, value)
|
|
//print(self, key, value)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local defIdleTbl = {ACT_IDLE}
|
|
--
|
|
// lua_run PrintTable(Entity(1):GetEyeTrace().Entity:GetTable())
|
|
function ENT:OnRestore()
|
|
//print("RELOAD:", self)
|
|
self:StopMoving()
|
|
self:ResetMoveCalc()
|
|
if !istable(self.AnimTbl_IdleStand) then self.AnimTbl_IdleStand = defIdleTbl end -- Resets to nil for some reason...
|
|
-- Reset the current schedule because often times GMod attempts to run it before AI task modules have loaded!
|
|
if self.CurrentSchedule then
|
|
self.CurrentSchedule = nil
|
|
self.CurrentTask = nil
|
|
self.CurrentTaskID = nil
|
|
end
|
|
-- Readd the weapon think hook because the transition / save does NOT do it!
|
|
local wep = self:GetActiveWeapon()
|
|
if IsValid(wep) then
|
|
hook.Add("Think", wep, wep.NPC_ServerNextFire)
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:AcceptInput(key, activator, caller, data)
|
|
//print(self, key, activator, caller, data)
|
|
self:CustomOnAcceptInput(key, activator, caller, data)
|
|
if key == "Use" then
|
|
if self.FollowPlayer then
|
|
self:Follow(activator, true)
|
|
end
|
|
elseif key == "StartScripting" then
|
|
self:SetState(VJ_STATE_FREEZE)
|
|
elseif key == "StopScripting" then
|
|
self:SetState(VJ_STATE_NONE)
|
|
elseif key == "SetHealth" then
|
|
self:SetHealth(data)
|
|
self:SetMaxHealth(data)
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:HandleAnimEvent(ev, evTime, evCycle, evType, evOptions)
|
|
self:CustomOnHandleAnimEvent(ev, evTime, evCycle, evType, evOptions)
|
|
/*
|
|
print("----------------------------")
|
|
print(ev)
|
|
print(evTime)
|
|
print(evCycle)
|
|
print(evType)
|
|
print(evOptions)
|
|
*/
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:OnCondition(cond)
|
|
//print(self, " Condition: ", cond, " - ", self:ConditionName(cond))
|
|
self:CustomOnCondition(cond)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Touch(entity)
|
|
if self.VJ_DEBUG == true && GetConVar("vj_npc_printontouch"):GetInt() == 1 then print(self:GetClass().." Has Touched "..entity:GetClass()) end
|
|
self:CustomOnTouch(entity)
|
|
if GetConVar("ai_disabled"):GetInt() == 1 or self.VJ_IsBeingControlled then return end
|
|
|
|
-- If it's a passive SNPC...
|
|
if self.Behavior == VJ_BEHAVIOR_PASSIVE or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE then
|
|
if self.Passive_RunOnTouch == true && (entity:IsNPC() or entity:IsPlayer()) && CurTime() > self.TakingCoverT && entity.Behavior != VJ_BEHAVIOR_PASSIVE && entity.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && self:CheckRelationship(entity) != D_LI then
|
|
self:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH")
|
|
self:PlaySoundSystem("Alert")
|
|
self.TakingCoverT = CurTime() + math.Rand(self.Passive_NextRunOnTouchTime.a, self.Passive_NextRunOnTouchTime.b)
|
|
end
|
|
elseif self.DisableTouchFindEnemy == false && !IsValid(self:GetEnemy()) && self.IsFollowing == false && (entity:IsNPC() or (entity:IsPlayer() && !VJ_CVAR_IGNOREPLAYERS)) && self:CheckRelationship(entity) != D_LI then
|
|
self:StopMoving()
|
|
self:SetTarget(entity)
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Resets and stops following the current entity (If it's following any)
|
|
-----------------------------------------------------------]]
|
|
function ENT:FollowReset()
|
|
local followData = self.FollowData
|
|
local followEnt = followData.Ent
|
|
if IsValid(followEnt) && followEnt:IsPlayer() && self.AllowPrintingInChat then
|
|
if self.Dead then
|
|
followEnt:PrintMessage(HUD_PRINTTALK, self:GetName().." has been killed.")
|
|
else
|
|
followEnt:PrintMessage(HUD_PRINTTALK, self:GetName().." is no longer following you.")
|
|
end
|
|
end
|
|
self.IsFollowing = false
|
|
self.FollowingPlayer = false
|
|
followData.Ent = NULL -- Need to recall it here because localized can't update the table
|
|
followData.MinDist = 0
|
|
followData.Moving = false
|
|
followData.StopAct = false
|
|
followData.IsLiving = false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Attempts to start following an entity, if it's already following another entity, it will return false!
|
|
- ent = Entity to follow
|
|
- stopIfFollowing = If true, it will stop following if it's already following the same entity
|
|
Returns
|
|
- true, successfully started following the entity
|
|
- false, failed or stopped following the entity
|
|
-----------------------------------------------------------]]
|
|
function ENT:Follow(ent, stopIfFollowing)
|
|
if !IsValid(ent) or self.Dead or GetConVar("ai_disabled"):GetInt() == 1 or self == ent then return false end
|
|
|
|
local isPly = ent:IsPlayer()
|
|
local isLiving = isPly or ent:IsNPC() -- Is it a living entity?
|
|
if VJ_IsAlive(ent) && ((isPly && !VJ_CVAR_IGNOREPLAYERS) or (!isPly)) then
|
|
local followData = self.FollowData
|
|
-- Refusal messages
|
|
if isLiving && self:GetClass() != ent:GetClass() && (self:Disposition(ent) == D_HT or self:Disposition(ent) == D_NU) then -- Check for enemy/neutral
|
|
if isPly && self.AllowPrintingInChat then
|
|
ent:PrintMessage(HUD_PRINTTALK, self:GetName().." isn't friendly to you, therefore it won't follow you.")
|
|
end
|
|
return false
|
|
elseif self.IsFollowing == true && ent != followData.Ent then -- Already following another entity
|
|
if isPly && self.AllowPrintingInChat then
|
|
ent:PrintMessage(HUD_PRINTTALK, self:GetName().." is already following another entity, therefore it won't follow you.")
|
|
end
|
|
return false
|
|
elseif self.MovementType == VJ_MOVETYPE_STATIONARY or self.MovementType == VJ_MOVETYPE_PHYSICS then
|
|
if isPly && self.AllowPrintingInChat then
|
|
ent:PrintMessage(HUD_PRINTTALK, self:GetName().." is currently stationary, therefore it's unable follow you.")
|
|
end
|
|
return false
|
|
end
|
|
|
|
if !self.IsFollowing then
|
|
if isPly then
|
|
if self.AllowPrintingInChat then
|
|
ent:PrintMessage(HUD_PRINTTALK, self:GetName().." is now following you.")
|
|
end
|
|
self.GuardingPosition = nil -- Reset the guarding position
|
|
self.GuardingFacePosition = nil
|
|
self.FollowingPlayer = true
|
|
self:PlaySoundSystem("FollowPlayer")
|
|
end
|
|
followData.Ent = ent
|
|
followData.MinDist = self.FollowMinDistance + (self:OBBMaxs().y * 3)
|
|
followData.IsLiving = isLiving
|
|
self.IsFollowing = true
|
|
self:SetTarget(ent)
|
|
if !self:BusyWithActivity() then -- Face the entity and then move to it
|
|
self:StopMoving()
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET", function(x)
|
|
x.RunCode_OnFinish = function()
|
|
if IsValid(self.FollowData.Ent) then
|
|
self:VJ_TASK_GOTO_TARGET(((self:GetPos():Distance(self.FollowData.Ent:GetPos()) < (followData.MinDist * 1.5)) and "TASK_WALK_PATH") or "TASK_RUN_PATH", function(y) y.CanShootWhenMoving = true y.ConstantlyFaceEnemy = true end)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
if isPly then self:CustomOnFollowPlayer(ent) end
|
|
return true
|
|
elseif stopIfFollowing then -- Unfollow the entity
|
|
if isPly then
|
|
self:PlaySoundSystem("UnFollowPlayer")
|
|
self:CustomOnUnFollowPlayer(ent)
|
|
end
|
|
self:StopMoving()
|
|
self.NextWanderTime = CurTime() + 2
|
|
if !self:BusyWithActivity() then
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
end
|
|
self:FollowReset()
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoMedicReset()
|
|
self:CustomOnMedic_OnReset()
|
|
if IsValid(self.Medic_CurrentEntToHeal) then self.Medic_CurrentEntToHeal.VJTags[VJ_TAG_HEALING] = nil end
|
|
if IsValid(self.Medic_SpawnedProp) then self.Medic_SpawnedProp:Remove() end
|
|
self.Medic_NextHealT = CurTime() + math.Rand(self.Medic_NextHealTime.a, self.Medic_NextHealTime.b)
|
|
self.Medic_Status = false
|
|
self.Medic_CurrentEntToHeal = NULL
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoMedicCheck()
|
|
if !self.IsMedicSNPC or self.NoWeapon_UseScaredBehavior_Active then return end
|
|
if !self.Medic_Status then -- Not healing anyone, so check around for allies
|
|
if CurTime() < self.Medic_NextHealT or self.VJ_IsBeingControlled then return end
|
|
for _,v in ipairs(ents.FindInSphere(self:GetPos(), self.Medic_CheckDistance)) do
|
|
if v.IsVJBaseSNPC != true && !v:IsPlayer() then continue end -- If it's not a VJ Base SNPC or a player, then move on
|
|
if v:EntIndex() != self:EntIndex() && !v.VJTags[VJ_TAG_HEALING] && !v.VJTags[VJ_TAG_VEHICLE] && (v:Health() <= v:GetMaxHealth() * 0.75) && ((v.Medic_CanBeHealed == true && !IsValid(self:GetEnemy()) && !IsValid(v:GetEnemy())) or (v:IsPlayer() && !VJ_CVAR_IGNOREPLAYERS)) && self:CheckRelationship(v) == D_LI then
|
|
self.Medic_CurrentEntToHeal = v
|
|
self.Medic_Status = "Active"
|
|
v:VJTags_Add(VJ_TAG_HEALING)
|
|
self:StopMoving()
|
|
self:SetTarget(v)
|
|
self:VJ_TASK_GOTO_TARGET()
|
|
return
|
|
end
|
|
end
|
|
elseif self.Medic_Status != "Healing" then
|
|
if !IsValid(self.Medic_CurrentEntToHeal) or VJ_IsAlive(self.Medic_CurrentEntToHeal) != true or (self.Medic_CurrentEntToHeal:Health() > self.Medic_CurrentEntToHeal:GetMaxHealth() * 0.75) then self:DoMedicReset() return end
|
|
if self:Visible(self.Medic_CurrentEntToHeal) && self:GetPos():Distance(self.Medic_CurrentEntToHeal:GetPos()) <= self.Medic_HealDistance then -- Are we in healing distance?
|
|
self.Medic_Status = "Healing"
|
|
self:CustomOnMedic_BeforeHeal()
|
|
self:PlaySoundSystem("MedicBeforeHeal")
|
|
|
|
-- Spawn the prop
|
|
if self.Medic_SpawnPropOnHeal == true && self:LookupAttachment(self.Medic_SpawnPropOnHealAttachment) != 0 then
|
|
self.Medic_SpawnedProp = ents.Create("prop_physics")
|
|
self.Medic_SpawnedProp:SetModel(self.Medic_SpawnPropOnHealModel)
|
|
self.Medic_SpawnedProp:SetLocalPos(self:GetPos())
|
|
self.Medic_SpawnedProp:SetOwner(self)
|
|
self.Medic_SpawnedProp:SetParent(self)
|
|
self.Medic_SpawnedProp:Fire("SetParentAttachment", self.Medic_SpawnPropOnHealAttachment)
|
|
self.Medic_SpawnedProp:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE)
|
|
self.Medic_SpawnedProp:Spawn()
|
|
self.Medic_SpawnedProp:Activate()
|
|
self.Medic_SpawnedProp:SetSolid(SOLID_NONE)
|
|
//self.Medic_SpawnedProp:AddEffects(EF_BONEMERGE)
|
|
self.Medic_SpawnedProp:SetRenderMode(RENDERMODE_TRANSALPHA)
|
|
self:DeleteOnRemove(self.Medic_SpawnedProp)
|
|
end
|
|
|
|
local anim = VJ_PICK(self.AnimTbl_Medic_GiveHealth)
|
|
local timeUntilHeal = self:DecideAnimationLength(anim, self.Medic_TimeUntilHeal, 0)
|
|
if self.Medic_DisableAnimation != true then
|
|
self:VJ_ACT_PLAYACTIVITY(anim, true, false, false)
|
|
end
|
|
|
|
self:FaceCertainEntity(self.Medic_CurrentEntToHeal, false, timeUntilHeal)
|
|
|
|
-- Make the ally turn and look at me
|
|
local noturn = (self.Medic_CurrentEntToHeal.MovementType == VJ_MOVETYPE_STATIONARY and self.Medic_CurrentEntToHeal.CanTurnWhileStationary == false) or false
|
|
if !self.Medic_CurrentEntToHeal:IsPlayer() && noturn == false then
|
|
self.NextWanderTime = CurTime() + 2
|
|
self.NextChaseTime = CurTime() + 2
|
|
self.Medic_CurrentEntToHeal:StopMoving()
|
|
self.Medic_CurrentEntToHeal:SetTarget(self)
|
|
self.Medic_CurrentEntToHeal:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
end
|
|
|
|
timer.Simple(timeUntilHeal, function()
|
|
if IsValid(self) then
|
|
if !IsValid(self.Medic_CurrentEntToHeal) then -- Ally doesn't exist anymore, reset
|
|
self:DoMedicReset()
|
|
else -- If it exists...
|
|
if self:GetPos():Distance(self.Medic_CurrentEntToHeal:GetPos()) <= self.Medic_HealDistance then -- Are we still in healing distance?
|
|
if self:CustomOnMedic_OnHeal(self.Medic_CurrentEntToHeal) != false then
|
|
self.Medic_CurrentEntToHeal:RemoveAllDecals()
|
|
local friCurHP = self.Medic_CurrentEntToHeal:Health()
|
|
self.Medic_CurrentEntToHeal:SetHealth(math_clamp(friCurHP + self.Medic_HealthAmount, friCurHP, self.Medic_CurrentEntToHeal:GetMaxHealth()))
|
|
end
|
|
self:PlaySoundSystem("MedicOnHeal")
|
|
if self.Medic_CurrentEntToHeal.IsVJBaseSNPC == true then
|
|
self.Medic_CurrentEntToHeal:PlaySoundSystem("MedicReceiveHeal")
|
|
end
|
|
self:DoMedicReset()
|
|
else -- If we are no longer in healing distance, go after the ally again
|
|
self.Medic_Status = "Active"
|
|
if IsValid(self.Medic_SpawnedProp) then self.Medic_SpawnedProp:Remove() end
|
|
self:CustomOnMedic_OnReset()
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
elseif !self:BusyWithActivity() then -- If we aren't in healing distance, then go after the ally
|
|
self.NextIdleTime = CurTime() + 4
|
|
self.NextChaseTime = CurTime() + 4
|
|
self:SetTarget(self.Medic_CurrentEntToHeal)
|
|
self:VJ_TASK_GOTO_TARGET()
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoConstantlyFaceEnemy()
|
|
if self.VJ_IsBeingControlled then return false end
|
|
if self.LatestEnemyDistance < self.ConstantlyFaceEnemyDistance then
|
|
-- Only face if the enemy is visible ?
|
|
if self.ConstantlyFaceEnemy_IfVisible && !self.EnemyData.IsVisible then
|
|
return false
|
|
-- Do NOT face if attacking ?
|
|
elseif self.ConstantlyFaceEnemy_IfAttacking == false && self.AttackType != VJ_ATTACK_NONE then
|
|
return false
|
|
elseif (self.ConstantlyFaceEnemy_Postures == "Both") or (self.ConstantlyFaceEnemy_Postures == "Moving" && self:IsMoving()) or (self.ConstantlyFaceEnemy_Postures == "Standing" && !self:IsMoving()) then
|
|
self:FaceCertainEntity(self:GetEnemy())
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local angY45 = Angle(0, 45, 0)
|
|
local angYN45 = Angle(0, -45, 0)
|
|
local angY90 = Angle(0, 90, 0)
|
|
local angYN90 = Angle(0, -90, 0)
|
|
--
|
|
function ENT:Controller_Movement(cont, ply, bullseyePos)
|
|
if self.MovementType != VJ_MOVETYPE_STATIONARY then
|
|
local gerta_lef = ply:KeyDown(IN_MOVELEFT)
|
|
local gerta_rig = ply:KeyDown(IN_MOVERIGHT)
|
|
local gerta_arak = ply:KeyDown(IN_SPEED)
|
|
local aimVector = ply:GetAimVector()
|
|
|
|
if ply:KeyDown(IN_FORWARD) then
|
|
if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then
|
|
self:AA_MoveTo(cont.VJCE_Bullseye, true, gerta_arak and "Alert" or "Calm", {IgnoreGround=true})
|
|
else
|
|
if gerta_lef then
|
|
cont:StartMovement(aimVector, angY45)
|
|
elseif gerta_rig then
|
|
cont:StartMovement(aimVector, angYN45)
|
|
else
|
|
cont:StartMovement(aimVector, defAng)
|
|
end
|
|
end
|
|
elseif ply:KeyDown(IN_BACK) then
|
|
if gerta_lef then
|
|
cont:StartMovement(aimVector*-1, angYN45)
|
|
elseif gerta_rig then
|
|
cont:StartMovement(aimVector*-1, angY45)
|
|
else
|
|
cont:StartMovement(aimVector*-1, defAng)
|
|
end
|
|
elseif gerta_lef then
|
|
cont:StartMovement(aimVector, angY90)
|
|
elseif gerta_rig then
|
|
cont:StartMovement(aimVector, angYN90)
|
|
else
|
|
self:StopMoving()
|
|
if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then
|
|
self:AA_StopMoving()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:VJ_PlaySequence(seq, playbackRate, reset, resetTime, interruptible)
|
|
if !seq then return end
|
|
if interruptible == true then
|
|
self.VJ_PlayingSequence = false
|
|
self.VJ_PlayingInterruptSequence = true
|
|
else
|
|
self.VJ_PlayingSequence = true
|
|
self.VJ_PlayingInterruptSequence = false
|
|
end
|
|
|
|
self:ClearSchedule()
|
|
self:StopMoving()
|
|
self:ResetSequence(self:LookupSequence(VJ_PICK(seq)))
|
|
self:ResetSequenceInfo()
|
|
self:SetCycle(0) -- Start from the beginning
|
|
if isnumber(playbackRate) then
|
|
self.AnimationPlaybackRate = playbackRate
|
|
self:SetPlaybackRate(playbackRate)
|
|
end
|
|
if reset == true then
|
|
timer.Create("timer_act_seqreset"..self:EntIndex(), resetTime, 1, function()
|
|
self.VJ_PlayingInterruptSequence = false
|
|
self.VJ_PlayingSequence = false
|
|
//self.vACT_StopAttacks = false
|
|
end)
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Creates more timers for an attack | Note: it calculates playback rate!
|
|
- name = The name of the timer, ent index is concatenated at the end | DEFAULT: "timer_unknown"
|
|
- time = How long until the timer expires | DEFAULT: 0.5
|
|
- func = The function to run when timer expires
|
|
-----------------------------------------------------------]]
|
|
function ENT:DoAddExtraAttackTimers(name, time, func)
|
|
name = name or "timer_unknown"
|
|
self.TimersToRemove[#self.TimersToRemove + 1] = name
|
|
timer.Create(name..self:EntIndex(), (time or 0.5) / self:GetPlaybackRate(), 1, func)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoAlert(ent)
|
|
if !IsValid(self:GetEnemy()) or self.Alerted == true then return end
|
|
self.Alerted = true
|
|
self:SetNPCState(NPC_STATE_ALERT)
|
|
self.EnemyData.LastVisibleTime = CurTime()
|
|
self:CustomOnAlert(ent)
|
|
if CurTime() > self.NextAlertSoundT then
|
|
self:PlaySoundSystem("Alert")
|
|
if self.AlertSounds_OnlyOnce == true then
|
|
self.HasAlertSounds = false
|
|
end
|
|
self.NextAlertSoundT = CurTime() + math.Rand(self.NextSoundTime_Alert.a, self.NextSoundTime_Alert.b)
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local cosRad20 = math_cos(math_rad(20))
|
|
--
|
|
function ENT:SetupRelationships()
|
|
if self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE /*or self.Behavior == VJ_BEHAVIOR_PASSIVE*/ then return false end
|
|
local posEnemies = self.CurrentPossibleEnemies
|
|
if posEnemies == nil then return false end
|
|
//if CurTime() > self.NextHardEntityCheckT then
|
|
//self.CurrentPossibleEnemies = self:DoHardEntityCheck()
|
|
//self.NextHardEntityCheckT = CurTime() + math.random(self.NextHardEntityCheck1,self.NextHardEntityCheck2) end
|
|
//print(self:GetName().."'s Enemies:")
|
|
//PrintTable(posEnemies)
|
|
|
|
/*if table.Count(self.CurrentPossibleEnemies) == 0 && CurTime() > self.NextHardEntityCheckT then
|
|
self.CurrentPossibleEnemies = self:DoHardEntityCheck()
|
|
self.NextHardEntityCheckT = CurTime() + math.random(50,70) end*/
|
|
|
|
local eneData = self.EnemyData
|
|
eneData.VisibleCount = 0
|
|
//local distlist = {}
|
|
local eneSeen = false
|
|
local myPos = self:GetPos()
|
|
local nearestDist = nil
|
|
local mySDir = self:GetSightDirection()
|
|
local mySightDist = self:GetMaxLookDistance()
|
|
local mySightAng = math_cos(math_rad(self.SightAngle))
|
|
local plyControlled = self.VJ_IsBeingControlled
|
|
local sdHintBullet = sound.GetLoudestSoundHint(SOUND_BULLET_IMPACT, myPos)
|
|
local sdHintBulletOwner = nil;
|
|
if sdHintBullet then
|
|
sdHintBulletOwner = sdHintBullet.owner
|
|
end
|
|
local it = 1
|
|
//for k, v in ipairs(posEnemies) do
|
|
//for it = 1, #posEnemies do
|
|
while it <= #posEnemies do
|
|
local v = posEnemies[it]
|
|
if !IsValid(v) then
|
|
table_remove(posEnemies, it)
|
|
else
|
|
it = it + 1
|
|
//if !IsValid(v) then table_remove(self.CurrentPossibleEnemies,tonumber(v)) continue end
|
|
//if !IsValid(v) then continue end
|
|
if v:IsFlagSet(FL_NOTARGET) or v.VJ_NoTarget or (v.VJ_AlwaysEnemyToEnt && v.VJ_AlwaysEnemyToEnt != self) then
|
|
if IsValid(self:GetEnemy()) && self:GetEnemy() == v then
|
|
self:ResetEnemy(false)
|
|
end
|
|
continue
|
|
end
|
|
//if v:Health() <= 0 then table_remove(self.CurrentPossibleEnemies,k) continue end
|
|
local vPos = v:GetPos()
|
|
local vDistanceToMy = vPos:Distance(myPos)
|
|
if vDistanceToMy > mySightDist then continue end
|
|
local entFri = false
|
|
local vClass = v:GetClass()
|
|
local vNPC = v:IsNPC()
|
|
local vPlayer = v:IsPlayer()
|
|
if vClass != self:GetClass() && (vPlayer or (vNPC && v.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE)) /*&& MyVisibleTov && self:Disposition(v) != D_LI*/ then
|
|
local inEneTbl = VJ_HasValue(self.VJ_AddCertainEntityAsEnemy, v)
|
|
if self.HasAllies == true && inEneTbl == false then
|
|
for _,friClass in ipairs(self.VJ_NPC_Class) do
|
|
if friClass == varCPly && self.PlayerFriendly == false then self.PlayerFriendly = true end -- If player ally then set the PlayerFriendly to true
|
|
-- Handle common class types
|
|
if (friClass == varCCom && NPCTbl_Combine[vClass]) or (friClass == varCZom && NPCTbl_Zombies[vClass]) or (friClass == varCAnt && NPCTbl_Antlions[vClass]) or (friClass == varCXen && NPCTbl_Xen[vClass]) then
|
|
v:AddEntityRelationship(self, D_LI, 0)
|
|
self:AddEntityRelationship(v, D_LI, 0)
|
|
entFri = true
|
|
end
|
|
if (v.VJ_NPC_Class && VJ_HasValue(v.VJ_NPC_Class, friClass)) or (entFri == true) then
|
|
if friClass == varCPly then -- If we have the player ally class then check if we both of us are supposed to be friends
|
|
if self.FriendsWithAllPlayerAllies == true && v.FriendsWithAllPlayerAllies == true then
|
|
entFri = true
|
|
if vNPC then v:AddEntityRelationship(self, D_LI, 0) end
|
|
self:AddEntityRelationship(v, D_LI, 0)
|
|
end
|
|
else
|
|
entFri = true
|
|
-- If I am enemy to it, then reset it!
|
|
if IsValid(self:GetEnemy()) && self:GetEnemy() == v then
|
|
eneData.Reset = true
|
|
self:ResetEnemy(false)
|
|
end
|
|
if vNPC then v:AddEntityRelationship(self, D_LI, 0) end
|
|
self:AddEntityRelationship(v, D_LI, 0)
|
|
end
|
|
end
|
|
end
|
|
-- Handle self.VJ Friendly AND HL2 Resistance + self.FriendsWithAllPlayerAllies
|
|
if vNPC && !entFri && ((self.VJTags[VJ_TAG_VJ_FRIENDLY] && v.IsVJBaseSNPC == true) or (self.PlayerFriendly == true && (NPCTbl_Resistance[vClass] or (self.FriendsWithAllPlayerAllies == true && v.PlayerFriendly == true && v.FriendsWithAllPlayerAllies == true)))) then
|
|
v:AddEntityRelationship(self, D_LI, 0)
|
|
self:AddEntityRelationship(v, D_LI, 0)
|
|
entFri = true
|
|
end
|
|
end
|
|
if !entFri && vNPC /*&& MyVisibleTov*/ && !self.DisableMakingSelfEnemyToNPCs && (v.VJ_IsBeingControlled != true) then v:AddEntityRelationship(self, D_HT, 0) end
|
|
if vPlayer && (self.PlayerFriendly == true or entFri == true) then
|
|
if inEneTbl == false then
|
|
entFri = true
|
|
self:AddEntityRelationship(v, D_LI, 0)
|
|
//DoPlayerSight()
|
|
else
|
|
entFri = false
|
|
end
|
|
end
|
|
-- Investigation detection systems, including sound, movement and flashlight
|
|
if !IsValid(self:GetEnemy()) && !entFri then
|
|
if vPlayer then
|
|
self:AddEntityRelationship(v, D_NU, 0) -- Make the player neutral since it's not supposed to be a friend
|
|
//if v:Crouching() && v:GetMoveType() != MOVETYPE_NOCLIP then -- Old and broken
|
|
//mySightDist = self.VJ_IsHugeMonster == true and 5000 or 2000
|
|
//end
|
|
end
|
|
if self.CanInvestigate && self.NextInvestigationMove < CurTime() then
|
|
-- When a sound is detected
|
|
if v.VJ_LastInvestigateSdLevel && vDistanceToMy < (self.InvestigateSoundDistance * v.VJ_LastInvestigateSdLevel) && ((CurTime() - v.VJ_LastInvestigateSd) <= 1) then
|
|
if self:Visible(v) then
|
|
self:StopMoving()
|
|
self:SetTarget(v)
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
self.NextInvestigationMove = CurTime() + 0.3 -- Short delay, since it's only turning
|
|
elseif self.IsFollowing == false then
|
|
self:SetLastPosition(vPos)
|
|
self:VJ_TASK_GOTO_LASTPOS("TASK_WALK_PATH")
|
|
self.NextInvestigationMove = CurTime() + 2 -- Long delay, so it doesn't spam movement
|
|
end
|
|
self:CustomOnInvestigate(v)
|
|
self:PlaySoundSystem("InvestigateSound")
|
|
-- When a bullet hit is detected
|
|
elseif IsValid(sdHintBulletOwner) && sdHintBulletOwner == v then
|
|
self:StopMoving()
|
|
self:SetLastPosition(sdHintBullet.origin)
|
|
self:VJ_TASK_FACE_X("TASK_FACE_LASTPOSITION")
|
|
self:CustomOnInvestigate(v)
|
|
self:PlaySoundSystem("InvestigateSound")
|
|
self.NextInvestigationMove = CurTime() + 0.3 -- Short delay because many bullets could hit
|
|
-- PLAYER ONLY: Flashlight shining on the NPC
|
|
elseif vPlayer && vDistanceToMy < 350 && v:FlashlightIsOn() && (v:GetForward():Dot((myPos - vPos):GetNormalized()) > cosRad20) then
|
|
// Asiga hoser ^ (!v:Crouching() && v:GetVelocity():Length() > 0 && v:GetMoveType() != MOVETYPE_NOCLIP && ((!v:KeyDown(IN_WALK) && (v:KeyDown(IN_FORWARD) or v:KeyDown(IN_BACK) or v:KeyDown(IN_MOVELEFT) or v:KeyDown(IN_MOVERIGHT))) or (v:KeyDown(IN_SPEED) or v:KeyDown(IN_JUMP)))) or
|
|
self:StopMoving()
|
|
self:SetTarget(v)
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
self.NextInvestigationMove = CurTime() + 0.1 -- Short delay, since it's only turning
|
|
end
|
|
end
|
|
end
|
|
end
|
|
/*print("----------")
|
|
print(self:HasEnemyEluded(v))
|
|
print(self:HasEnemyMemory(v))
|
|
print(CurTime() - self:GetEnemyLastTimeSeen(v))
|
|
print(CurTime() - self:GetEnemyFirstTimeSeen(v))*/
|
|
-- We have to do this here so we make sure non-VJ NPCs can still target this NPC, even if it's being controlled!
|
|
if plyControlled && self.VJ_TheControllerBullseye != v then
|
|
//self:AddEntityRelationship(v, D_NU, 0)
|
|
v = self.VJ_TheControllerBullseye
|
|
vPlayer = false
|
|
end
|
|
-- Check in order: Can find enemy + Neutral or not + Is visible + In sight
|
|
if self.DisableFindEnemy == false && (self.Behavior != VJ_BEHAVIOR_NEUTRAL or self.Alerted) && (self.FindEnemy_CanSeeThroughWalls or self:Visible(v)) && (self.FindEnemy_UseSphere or (mySDir:Dot((vPos - myPos):GetNormalized()) > mySightAng)) then
|
|
local check = self:CheckRelationship(v)
|
|
if check == D_HT then -- Is enemy
|
|
eneSeen = true
|
|
eneData.VisibleCount = eneData.VisibleCount + 1
|
|
self:AddEntityRelationship(v, D_HT, 0)
|
|
-- If the detected enemy is closer than the previous enemy, the set this as the enemy!
|
|
if (nearestDist == nil) or (vDistanceToMy < nearestDist) then
|
|
nearestDist = vDistanceToMy
|
|
self:VJ_DoSetEnemy(v, true, true)
|
|
end
|
|
-- If the current enemy is a friendly player, then reset the enemy!
|
|
elseif check == D_LI && vPlayer && IsValid(self:GetEnemy()) && self:GetEnemy() == v then
|
|
eneData.Reset = true
|
|
self:ResetEnemy(false)
|
|
end
|
|
end
|
|
if vPlayer then
|
|
-- MoveOutOfFriendlyPlayersWay system
|
|
if entFri && self.MoveOutOfFriendlyPlayersWay && !self.IsGuard && !self:IsMoving() && CurTime() > self.TakingCoverT && !plyControlled && !self:BusyWithActivity() then
|
|
local dist = self.FollowingPlayer and 10 or 20
|
|
if /*self:Disposition(v) == D_LI &&*/ (self:VJ_GetNearestPointToEntityDistance(v) < dist) && v:GetVelocity():Length() > 0 && v:GetMoveType() != MOVETYPE_NOCLIP then
|
|
self.NextFollowUpdateT = CurTime() + 2
|
|
self:PlaySoundSystem("MoveOutOfPlayersWay")
|
|
//self:SetLastPosition(myPos + self:GetRight()*math.random(-50,-50))
|
|
self:SetMovementActivity(VJ_PICK(self.AnimTbl_Run))
|
|
local vsched = ai_vj_schedule.New("vj_move_away")
|
|
vsched:EngTask("TASK_MOVE_AWAY_PATH", 120)
|
|
vsched:EngTask("TASK_RUN_PATH", 0)
|
|
vsched:EngTask("TASK_WAIT_FOR_MOVEMENT", 0)
|
|
/*vsched.RunCode_OnFinish = function()
|
|
timer.Simple(0.1,function()
|
|
if IsValid(self) then
|
|
self:SetTarget(v)
|
|
local vschedMoveAwayFail = ai_vj_schedule.New("vj_move_away_fail")
|
|
vschedMoveAwayFail:EngTask("TASK_FACE_TARGET", 0)
|
|
self:StartSchedule(vschedMoveAwayFail)
|
|
end
|
|
end)
|
|
end*/
|
|
//vsched.CanShootWhenMoving = true
|
|
//vsched.ConstantlyFaceEnemy = true
|
|
vsched.IsMovingTask = true
|
|
vsched.MoveType = 1
|
|
self:StartSchedule(vsched)
|
|
self.TakingCoverT = CurTime() + 0.2
|
|
end
|
|
end
|
|
-- HasOnPlayerSight system, used to do certain actions when it sees the player
|
|
if self.HasOnPlayerSight == true && v:Alive() &&(CurTime() > self.OnPlayerSightNextT) && (vDistanceToMy < self.OnPlayerSightDistance) && self:Visible(v) && (mySDir:Dot((v:GetPos() - myPos):GetNormalized()) > mySightAng) then
|
|
-- 0 = Run it every time | 1 = Run it only when friendly to player | 2 = Run it only when enemy to player
|
|
local disp = self.OnPlayerSightDispositionLevel
|
|
if (disp == 0) or (disp == 1 && (self:Disposition(v) == D_LI or self:Disposition(v) == D_NU)) or (disp == 2 && self:Disposition(v) != D_LI) then
|
|
self:CustomOnPlayerSight(v)
|
|
self:PlaySoundSystem("OnPlayerSight")
|
|
if self.OnPlayerSightOnlyOnce == true then -- If it's only suppose to play it once then turn the system off
|
|
self.HasOnPlayerSight = false
|
|
else
|
|
self.OnPlayerSightNextT = CurTime() + math.Rand(self.OnPlayerSightNextTime.a, self.OnPlayerSightNextTime.b)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:CustomOnSetupRelationships(v, entFri, vDistanceToMy)
|
|
end
|
|
//return true
|
|
end
|
|
if eneSeen == true then return true else return false end
|
|
//return false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Allies_CallHelp(dist)
|
|
if !self.CallForHelp or self.AttackType == VJ_ATTACK_GRENADE then return false end
|
|
local entsTbl = ents.FindInSphere(self:GetPos(), dist)
|
|
if (!entsTbl) then return false end
|
|
local myClass = self:GetClass()
|
|
for _,v in ipairs(entsTbl) do
|
|
if v:IsNPC() && v != self && v.IsVJBaseSNPC && VJ_IsAlive(v) && (v:GetClass() == myClass or v:Disposition(self) == D_LI) && v.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE /*&& v.IsFollowing == false*/ && !v.VJ_IsBeingControlled && (!v.IsVJBaseSNPC_Tank) && v.CallForHelp then
|
|
local ene = self:GetEnemy()
|
|
if IsValid(ene) then
|
|
if v:GetPos():Distance(ene:GetPos()) > v.SightDistance then continue end -- Enemy to far away for ally, discontinue!
|
|
//if v:CheckRelationship(ene) == D_HT then
|
|
if !IsValid(v:GetEnemy()) && ((!ene:IsPlayer() && v:Disposition(ene) != D_LI) or (ene:IsPlayer())) then
|
|
if v.IsGuard == true && !v:Visible(ene) then continue end -- If it's guarding and enemy is not visible, then don't call!
|
|
self:CustomOnCallForHelp(v)
|
|
self:PlaySoundSystem("CallForHelp")
|
|
-- Play the animation
|
|
if self.HasCallForHelpAnimation == true && CurTime() > self.NextCallForHelpAnimationT then
|
|
local pickanim = VJ_PICK(self.AnimTbl_CallForHelp)
|
|
self:VJ_ACT_PLAYACTIVITY(pickanim, self.CallForHelpStopAnimations, self:DecideAnimationLength(pickanim, self.CallForHelpStopAnimationsTime), self.CallForHelpAnimationFaceEnemy, self.CallForHelpAnimationDelay, {PlayBackRate=self.CallForHelpAnimationPlayBackRate, PlayBackRateCalculated=true})
|
|
self.NextCallForHelpAnimationT = CurTime() + self.NextCallForHelpAnimationTime
|
|
end
|
|
-- If the enemy is a player and the ally is player-friendly then make that player an enemy to the ally
|
|
if ene:IsPlayer() && v.PlayerFriendly == true then
|
|
v.VJ_AddCertainEntityAsEnemy[#v.VJ_AddCertainEntityAsEnemy + 1] = ene
|
|
end
|
|
v:VJ_DoSetEnemy(ene, true)
|
|
if CurTime() > v.NextChaseTime then
|
|
if v.Behavior != VJ_BEHAVIOR_PASSIVE && v:Visible(ene) then
|
|
v:SetTarget(ene)
|
|
v:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
else
|
|
v:PlaySoundSystem("OnReceiveOrder")
|
|
v:DoChaseAnimation()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Allies_Check(dist)
|
|
dist = dist or 800 -- How far can it check for allies
|
|
local entsTbl = ents.FindInSphere(self:GetPos(), dist)
|
|
if (!entsTbl) then return false end
|
|
local FoundAlliesTbl = {}
|
|
local it = 0
|
|
for _, v in ipairs(entsTbl) do
|
|
if VJ_IsAlive(v) == true && v:IsNPC() && v != self && (v:GetClass() == self:GetClass() or (v:Disposition(self) == D_LI or v.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE)) && (v.BringFriendsOnDeath == true or v.CallForBackUpOnDamage == true or v.CallForHelp == true) then
|
|
if self.Behavior == VJ_BEHAVIOR_PASSIVE or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE then
|
|
if v.Behavior == VJ_BEHAVIOR_PASSIVE or v.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE then
|
|
it = it + 1
|
|
FoundAlliesTbl[it] = v
|
|
end
|
|
else
|
|
it = it + 1
|
|
FoundAlliesTbl[it] = v
|
|
end
|
|
end
|
|
end
|
|
if it > 0 then
|
|
return FoundAlliesTbl
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:Allies_Bring(formType, dist, entsTbl, limit, onlyVis)
|
|
formType = formType or "Random" -- Formation type: "Random" || "Diamond"
|
|
dist = dist or 800 -- How far can it check for allies
|
|
entsTbl = entsTbl or ents.FindInSphere(self:GetPos(), dist)
|
|
limit = limit or 3 -- Setting to 0 will automatically become 1
|
|
onlyVis = onlyVis or false -- Check only entities that are visible
|
|
if (!entsTbl) then return false end
|
|
local myClass = self:GetClass()
|
|
local it = 0
|
|
for _, v in ipairs(entsTbl) do
|
|
if VJ_IsAlive(v) == true && v:IsNPC() && v != self && (v:GetClass() == myClass or v:Disposition(self) == D_LI) && v.Behavior != VJ_BEHAVIOR_PASSIVE && v.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && v.IsFollowing == false && v.VJ_IsBeingControlled == false && !v.IsGuard && (!v.IsVJBaseSNPC_Tank) && (v.BringFriendsOnDeath == true or v.CallForBackUpOnDamage == true or v.CallForHelp == true) then
|
|
if onlyVis == true && !v:Visible(self) then continue end
|
|
if !IsValid(v:GetEnemy()) && self:GetPos():Distance(v:GetPos()) < dist then
|
|
self.NextWanderTime = CurTime() + 8
|
|
v.NextWanderTime = CurTime() + 8
|
|
it = it + 1
|
|
-- Formation
|
|
if formType == "Random" then
|
|
local randPos = math.random(1, 4)
|
|
if randPos == 1 then
|
|
v:SetLastPosition(self:GetPos() + self:GetRight()*math.random(20, 50))
|
|
elseif randPos == 2 then
|
|
v:SetLastPosition(self:GetPos() + self:GetRight()*math.random(-20, -50))
|
|
elseif randPos == 3 then
|
|
v:SetLastPosition(self:GetPos() + self:GetForward()*math.random(20, 50))
|
|
elseif randPos == 4 then
|
|
v:SetLastPosition(self:GetPos() + self:GetForward()*math.random(-20, -50))
|
|
end
|
|
elseif formType == "Diamond" then
|
|
v:DoGroupFormation("Diamond", self, it)
|
|
end
|
|
-- Move type
|
|
if v.IsVJBaseSNPC_Human == true && !IsValid(v:GetActiveWeapon()) then
|
|
v:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH")
|
|
else
|
|
v:VJ_TASK_GOTO_LASTPOS("TASK_WALK_PATH", function(x) x.CanShootWhenMoving = true x.ConstantlyFaceEnemy = true end)
|
|
end
|
|
end
|
|
if limit != 0 && it >= limit then return true end -- Return true if it reached the limit
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Decides the attack time.
|
|
- timer2 = Use for randomization, leave to "false" to just use timer1
|
|
- untilDamage = Used for timer-based attacks, decreases timer1
|
|
- animDur = Used when timer1 is set to "false", it over takes timer1
|
|
Returns
|
|
- Number, the decided time
|
|
-----------------------------------------------------------]]
|
|
function ENT:DecideAttackTimer(timer1, timer2, untilDamage, animDur)
|
|
local result = timer1
|
|
-- animDur has already calculated the playback rate!
|
|
if timer1 == false then -- Let the base decide..
|
|
if untilDamage == false then -- Event-based
|
|
result = animDur
|
|
else -- Timer-based
|
|
if animDur <= 0 then -- If it's 0 or less, then this attack probably did NOT play an animation, so don't use animDur!
|
|
result = untilDamage / self:GetPlaybackRate()
|
|
else
|
|
result = animDur - (untilDamage / self:GetPlaybackRate())
|
|
end
|
|
end
|
|
else -- If a specific number has been put then make sure to calculate its playback rate
|
|
result = result / self:GetPlaybackRate()
|
|
end
|
|
|
|
-- If a 2nd value is given (Used for randomization), calculate its playback rate as well and then get a random value between it and the result
|
|
if isnumber(timer2) then
|
|
result = math.Rand(result, timer2 / self:GetPlaybackRate())
|
|
end
|
|
|
|
return result // / self:GetPlaybackRate() -- No need, playback is already calculated above
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:DoKilledEnemy(ent, attacker, inflictor)
|
|
if !IsValid(ent) then return end
|
|
-- If it can only do it if there is no enemies left then check --> (If there no valid enemy) OR (The number of enemies is 1 or less)
|
|
if (self.OnlyDoKillEnemyWhenClear == false) or (self.OnlyDoKillEnemyWhenClear == true && (!IsValid(self:GetEnemy()) or (self.EnemyData.VisibleCount <= 1))) then
|
|
self:CustomOnDoKilledEnemy(ent, attacker, inflictor)
|
|
self:PlaySoundSystem("OnKilledEnemy")
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local function FlinchDamageTypeCheck(checkTbl, dmgType)
|
|
for k = 1, #checkTbl do
|
|
if bAND(dmgType, checkTbl[k]) != 0 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
--
|
|
function ENT:DoFlinch(dmginfo, hitgroup)
|
|
if self.CanFlinch == 0 or self.Flinching == true or self.TakingCoverT > CurTime() or self.NextFlinchT > CurTime() or (IsValid(dmginfo:GetInflictor()) && IsValid(dmginfo:GetAttacker()) && dmginfo:GetInflictor():GetClass() == "entityflame" && dmginfo:GetAttacker():GetClass() == "entityflame") then return end
|
|
|
|
local function RunFlinchCode(HitgroupInfo)
|
|
self.Flinching = true
|
|
self:StopAttacks(true)
|
|
self.PlayingAttackAnimation = false
|
|
local animTbl = self.AnimTbl_Flinch
|
|
if HitgroupInfo != nil then animTbl = HitgroupInfo.Animation end
|
|
local anim = VJ_PICK(animTbl)
|
|
local animDur = self.NextMoveAfterFlinchTime == false and self:DecideAnimationLength(anim, false, self.FlinchAnimationDecreaseLengthAmount) or self.NextMoveAfterFlinchTime
|
|
self:VJ_ACT_PLAYACTIVITY(anim, true, animDur, false, 0, {SequenceDuration=animDur, PlayBackRateCalculated=true})
|
|
timer.Create("timer_act_flinching"..self:EntIndex(), animDur, 1, function() self.Flinching = false end)
|
|
self:CustomOnFlinch_AfterFlinch(dmginfo, hitgroup)
|
|
self.NextFlinchT = CurTime() + self.NextFlinchTime
|
|
end
|
|
|
|
if math.random(1, self.FlinchChance) == 1 && ((self.CanFlinch == 1) or (self.CanFlinch == 2 && FlinchDamageTypeCheck(self.FlinchDamageTypes, dmginfo:GetDamageType()))) then
|
|
if self:CustomOnFlinch_BeforeFlinch(dmginfo, hitgroup) == false then return end
|
|
|
|
local hitgroupTbl = self.HitGroupFlinching_Values
|
|
-- Hitgroup flinching
|
|
if istable(hitgroupTbl) then
|
|
for _, v in ipairs(hitgroupTbl) do
|
|
hitgroups = v.HitGroup
|
|
-- Sub-table of hitgroups
|
|
for hitgroupX = 1, #hitgroups do
|
|
if hitgroups[hitgroupX] == hitgroup then
|
|
HitGroupFound = true
|
|
RunFlinchCode(v)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
if self.HitGroupFlinching_DefaultWhenNotHit == true then
|
|
RunFlinchCode(nil)
|
|
end
|
|
-- Non-hitgroup flinching
|
|
else
|
|
RunFlinchCode(nil)
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
--[[---------------------------------------------------------
|
|
Sets the blood color including damage particle, decal and blood pool
|
|
- blColor = The blood color to set it to
|
|
-----------------------------------------------------------]]
|
|
-- self.CurrentChoosenBlood_Particle, self.CurrentChoosenBlood_Decal, self.CurrentChoosenBlood_Pool
|
|
function ENT:SetupBloodColor(blColor)
|
|
blColor = blColor or ""
|
|
if blColor == "" then return end -- If it's empty then return
|
|
if blColor == "Red" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"blood_impact_red_01"} end // vj_impact1_red
|
|
if !VJ_PICK(self.CustomBlood_Decal) then
|
|
if self.BloodDecalUseGMod == true then
|
|
self.CustomBlood_Decal = {"Blood"}
|
|
else
|
|
self.CustomBlood_Decal = {"VJ_Blood_Red"}
|
|
end
|
|
end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_red_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_red_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_red"}
|
|
end
|
|
end
|
|
elseif blColor == "Yellow" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"blood_impact_yellow_01"} end // vj_impact1_yellow
|
|
if !VJ_PICK(self.CustomBlood_Decal) then
|
|
if self.BloodDecalUseGMod == true then
|
|
self.CustomBlood_Decal = {"YellowBlood"}
|
|
else
|
|
self.CustomBlood_Decal = {"VJ_Blood_Yellow"}
|
|
end
|
|
end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_yellow_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_yellow_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_yellow"}
|
|
end
|
|
end
|
|
elseif blColor == "Green" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_green"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_Green"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_green_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_green_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_green"}
|
|
end
|
|
end
|
|
elseif blColor == "Orange" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_orange"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_Orange"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_orange_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_orange_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_orange"}
|
|
end
|
|
end
|
|
elseif blColor == "Blue" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_blue"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_Blue"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_blue_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_blue_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_blue"}
|
|
end
|
|
end
|
|
elseif blColor == "Purple" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_purple"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_Purple"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_purple_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_purple_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_purple"}
|
|
end
|
|
end
|
|
elseif blColor == "White" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_white"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_White"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_white_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_white_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_white"}
|
|
end
|
|
end
|
|
elseif blColor == "Oil" then
|
|
if !VJ_PICK(self.CustomBlood_Particle) then self.CustomBlood_Particle = {"vj_impact1_black"} end
|
|
if !VJ_PICK(self.CustomBlood_Decal) then self.CustomBlood_Decal = {"VJ_Blood_Oil"} end
|
|
if !VJ_PICK(self.CustomBlood_Pool) then
|
|
if self.BloodPoolSize == "Small" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_oil_small"}
|
|
elseif self.BloodPoolSize == "Tiny" then
|
|
self.CustomBlood_Pool = {"vj_bleedout_oil_tiny"}
|
|
else
|
|
self.CustomBlood_Pool = {"vj_bleedout_oil"}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:SpawnBloodParticles(dmginfo, hitgroup)
|
|
local name = VJ_PICK(self.CustomBlood_Particle)
|
|
if name == false then return end
|
|
|
|
local pos = dmginfo:GetDamagePosition()
|
|
if pos == defPos then pos = self:GetPos() + self:OBBCenter() end
|
|
|
|
local particle = ents.Create("info_particle_system")
|
|
particle:SetKeyValue("effect_name", name)
|
|
particle:SetPos(pos)
|
|
particle:Spawn()
|
|
particle:Activate()
|
|
particle:Fire("Start")
|
|
particle:Fire("Kill", "", 0.1)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:SpawnBloodDecal(dmginfo, hitgroup)
|
|
if VJ_PICK(self.CustomBlood_Decal) == false then return end
|
|
local force = dmginfo:GetDamageForce()
|
|
local pos = dmginfo:GetDamagePosition()
|
|
if pos == defPos then pos = self:GetPos() + self:OBBCenter() end
|
|
|
|
-- Badi ayroun
|
|
local tr = util.TraceLine({start = pos, endpos = pos + force:GetNormal() * math_clamp(force:Length() * 10, 100, self.BloodDecalDistance), filter = self})
|
|
//if !tr.HitWorld then return end
|
|
local trNormalP = tr.HitPos + tr.HitNormal
|
|
local trNormalN = tr.HitPos - tr.HitNormal
|
|
util.Decal(VJ_PICK(self.CustomBlood_Decal), trNormalP, trNormalN, self)
|
|
for _ = 1, 2 do
|
|
if math.random(1, 2) == 1 then util.Decal(VJ_PICK(self.CustomBlood_Decal), trNormalP + Vector(math.random(-70,70), math.random(-70,70), 0), trNormalN, self) end
|
|
end
|
|
|
|
-- Kedni ayroun
|
|
if math.random(1,2) == 1 then
|
|
local d2_endpos = pos + Vector(0, 0, - math_clamp(force:Length() * 10, 100, self.BloodDecalDistance))
|
|
util.Decal(VJ_PICK(self.CustomBlood_Decal), pos, d2_endpos, self)
|
|
if math.random(1, 2) == 1 then util.Decal(VJ_PICK(self.CustomBlood_Decal), pos, d2_endpos + Vector(math.random(-120,120), math.random(-120,120), 0), self) end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local vecZ30 = Vector(0, 0, 30)
|
|
local vecZ1 = Vector(0, 0, 1)
|
|
--
|
|
function ENT:SpawnBloodPool(dmginfo, hitgroup)
|
|
if !IsValid(self.Corpse) then return end
|
|
local getBloodPool = VJ_PICK(self.CustomBlood_Pool)
|
|
if getBloodPool == false then return end
|
|
local corpseEnt = self.Corpse
|
|
timer.Simple(2.2, function()
|
|
if IsValid(corpseEnt) then
|
|
local pos = corpseEnt:GetPos() + corpseEnt:OBBCenter()
|
|
local tr = util.TraceLine({
|
|
start = pos,
|
|
endpos = pos - vecZ30,
|
|
filter = corpseEnt, //function( ent ) if ( ent:GetClass() == "prop_physics" ) then return true end end
|
|
mask = CONTENTS_SOLID
|
|
})
|
|
if tr.HitWorld && (tr.HitNormal == vecZ1) then // (tr.Fraction <= 0.405)
|
|
ParticleEffect(getBloodPool, tr.HitPos, defAng, nil)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:IdleSoundCode(customSd, sdType)
|
|
if !self.HasSounds or !self.HasIdleSounds or self.Dead then return end
|
|
if (self.NextIdleSoundT_RegularChange < CurTime()) && (CurTime() > self.NextIdleSoundT) then
|
|
sdType = sdType or VJ_CreateSound
|
|
|
|
local hasEnemy = IsValid(self:GetEnemy()) -- Ayo yete teshnami ouni
|
|
-- Yete CombatIdle tsayn chouni YEV gerna barz tsayn hanel, ere vor barz tsayn han e
|
|
if hasEnemy && VJ_PICK(self.SoundTbl_CombatIdle) == false && !self.IdleSounds_NoRegularIdleOnAlerted then
|
|
hasEnemy = false
|
|
end
|
|
|
|
local cTbl = VJ_PICK(customSd)
|
|
local setTimer = true
|
|
if hasEnemy == true then
|
|
local sdtbl = VJ_PICK(self.SoundTbl_CombatIdle)
|
|
if (math.random(1,self.CombatIdleSoundChance) == 1 && sdtbl != false) or (cTbl != false) then
|
|
if cTbl != false then sdtbl = cTbl end
|
|
self.CurrentIdleSound = sdType(self, sdtbl, self.CombatIdleSoundLevel, self:VJ_DecideSoundPitch(self.CombatIdleSoundPitch.a, self.CombatIdleSoundPitch.b))
|
|
end
|
|
else
|
|
local sdtbl = VJ_PICK(self.SoundTbl_Idle)
|
|
local sdtbl2 = VJ_PICK(self.SoundTbl_IdleDialogue)
|
|
local sdrand = math.random(1, self.IdleSoundChance)
|
|
local function RegularIdle()
|
|
if (sdrand == 1 && sdtbl != false) or (cTbl != false) then
|
|
if cTbl != false then sdtbl = cTbl end
|
|
self.CurrentIdleSound = sdType(self, sdtbl, self.IdleSoundLevel, self:VJ_DecideSoundPitch(self.IdleSoundPitch.a, self.IdleSoundPitch.b))
|
|
end
|
|
end
|
|
if sdtbl2 != false && sdrand == 1 && self.HasIdleDialogueSounds == true && math.random(1, 2) == 1 then
|
|
local foundEnt, canAnswer = self:IdleDialogueFindEnt()
|
|
if foundEnt != false then
|
|
self.CurrentIdleSound = sdType(self, sdtbl2, self.IdleDialogueSoundLevel, self:VJ_DecideSoundPitch(self.IdleDialogueSoundPitch.a, self.IdleDialogueSoundPitch.b))
|
|
if canAnswer == true then -- If we have a VJ NPC that can answer
|
|
local dur = SoundDuration(sdtbl2)
|
|
if dur == 0 then dur = 3 end -- Since some file types don't return a proper duration =(
|
|
local talkTime = CurTime() + (dur + 0.5)
|
|
setTimer = false
|
|
self.NextIdleSoundT = talkTime
|
|
self.NextWanderTime = talkTime
|
|
foundEnt.NextIdleSoundT = talkTime
|
|
foundEnt.NextWanderTime = talkTime
|
|
|
|
self:CustomOnIdleDialogue(foundEnt, "Speak", talkTime)
|
|
|
|
-- Stop moving and look at each other
|
|
if self.IdleDialogueCanTurn == true then
|
|
self:StopMoving()
|
|
self:SetTarget(foundEnt)
|
|
self:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
end
|
|
if foundEnt.IdleDialogueCanTurn == true then
|
|
foundEnt:StopMoving()
|
|
foundEnt:SetTarget(self)
|
|
foundEnt:VJ_TASK_FACE_X("TASK_FACE_TARGET")
|
|
end
|
|
|
|
-- For the other SNPC to answer back:
|
|
timer.Simple(dur + 0.3, function()
|
|
if IsValid(self) && IsValid(foundEnt) && !foundEnt:CustomOnIdleDialogue(self, "Answer") then
|
|
local response = foundEnt:IdleDialogueAnswerSoundCode()
|
|
if response > 0 then -- If the ally responded, then make sure both SNPCs stand still & don't play another idle sound until the whole conversation is finished!
|
|
self.NextIdleSoundT = CurTime() + (response + 0.5)
|
|
self.NextWanderTime = CurTime() + (response + 1)
|
|
foundEnt.NextIdleSoundT = CurTime() + (response + 0.5)
|
|
foundEnt.NextWanderTime = CurTime() + (response + 1)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
else
|
|
RegularIdle()
|
|
end
|
|
else
|
|
RegularIdle()
|
|
end
|
|
end
|
|
if setTimer == true then
|
|
self.NextIdleSoundT = CurTime() + math.Rand(self.NextSoundTime_Idle.a, self.NextSoundTime_Idle.b)
|
|
end
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:IdleDialogueFindEnt()
|
|
-- Don't break the loop unless we hit a VJ SNPC
|
|
-- If no VJ SNPC is hit, then just simply return the last checked friendly player or NPC
|
|
local returnEnt = false
|
|
for _, v in ipairs(ents.FindInSphere(self:GetPos(), self.IdleDialogueDistance)) do
|
|
if v:IsPlayer() then
|
|
if self:CheckRelationship(v) == D_LI && !self:CustomOnIdleDialogue(v, "CheckEnt", false) then
|
|
returnEnt = v
|
|
end
|
|
elseif v != self && ((self:GetClass() == v:GetClass()) or (v:IsNPC() && self:CheckRelationship(v) == D_LI)) && self:Visible(v) then
|
|
local canAnswer = (v.IsVJBaseSNPC and VJ_PICK(v.SoundTbl_IdleDialogueAnswer)) or false
|
|
if !self:CustomOnIdleDialogue(v, "CheckEnt", canAnswer) then
|
|
returnEnt = v
|
|
if v.IsVJBaseSNPC && !v.Dead && canAnswer then -- VJ NPC hit!
|
|
return v, true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return returnEnt, false
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:IdleDialogueAnswerSoundCode(customSd, sdType)
|
|
if self.Dead or self.HasSounds == false or self.HasIdleDialogueAnswerSounds == false then return 0 end
|
|
sdType = sdType or VJ_CreateSound
|
|
local cTbl = VJ_PICK(customSd)
|
|
local sdtbl = VJ_PICK(self.SoundTbl_IdleDialogueAnswer)
|
|
if (math.random(1,self.IdleDialogueAnswerSoundChance) == 1 && sdtbl != false) or (cTbl != false) then
|
|
if cTbl != false then sdtbl = cTbl end
|
|
self:StopAllCommonSpeechSounds()
|
|
self.NextIdleSoundT_RegularChange = CurTime() + math.random(2, 3)
|
|
self.CurrentIdleDialogueAnswerSound = sdType(self, sdtbl, self.IdleDialogueAnswerSoundLevel, self:VJ_DecideSoundPitch(self.IdleDialogueAnswerSoundPitch.a, self.IdleDialogueAnswerSoundPitch.b))
|
|
return SoundDuration(sdtbl) -- Return the duration of the sound, which will be used to make the other SNPC stand still
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:RemoveTimers()
|
|
local myIndex = self:EntIndex()
|
|
for _,v in ipairs(self.TimersToRemove) do
|
|
timer.Remove(v .. myIndex)
|
|
end
|
|
if self.AttackTimersCustom then -- !!!!!!!!!!!!!! DO NOT USE THIS FUNCTION !!!!!!!!!!!!!! [Backwards Compatibility!]
|
|
for _,v in ipairs(self.AttackTimersCustom) do
|
|
timer.Remove(v .. myIndex)
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:EntitiesToNoCollideCode(ent)
|
|
if !self.HasEntitiesToNoCollide or !istable(self.EntitiesToNoCollide) or !IsValid(ent) then return end
|
|
for i = 1, #self.EntitiesToNoCollide do
|
|
if self.EntitiesToNoCollide[i] == ent:GetClass() then
|
|
constraint.NoCollide(self, ent, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:RunGibOnDeathCode(dmginfo, hitgroup, extraOptions)
|
|
if self.AllowedToGib == false or self.HasGibOnDeath == false or self.HasBeenGibbedOnDeath == true then return end
|
|
extraOptions = extraOptions or {}
|
|
local dmgTbl = extraOptions.CustomDmgTbl or self.GibOnDeathDamagesTable
|
|
local dmgType = dmginfo:GetDamageType()
|
|
for k = 1, #dmgTbl do
|
|
local v = dmgTbl[k]
|
|
if (v == "All") or (v == "UseDefault" && self:IsDefaultGibDamageType(dmgType) && bAND(dmgType, DMG_NEVERGIB) == 0) or (v != "UseDefault" && bAND(dmgType, v) != 0) then
|
|
local setupgib, setupgib_extra = self:SetUpGibesOnDeath(dmginfo, hitgroup)
|
|
if setupgib_extra == nil then setupgib_extra = {} end
|
|
if setupgib == true then
|
|
if setupgib_extra.AllowCorpse != true then self.HasDeathRagdoll = false end
|
|
if setupgib_extra.DeathAnim != true then self.HasDeathAnimation = false end
|
|
self.HasBeenGibbedOnDeath = true
|
|
self:PlayGibOnDeathSounds(dmginfo, hitgroup)
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
local gib_sd1 = "vj_gib/default_gib_splat.wav"
|
|
local gib_sd2 = "vj_gib/gibbing1.wav"
|
|
local gib_sd3 = "vj_gib/gibbing2.wav"
|
|
local gib_sd4 = "vj_gib/gibbing3.wav"
|
|
--
|
|
function ENT:PlayGibOnDeathSounds(dmginfo, hitgroup)
|
|
if self.HasGibOnDeathSounds == false then return end
|
|
if self:CustomGibOnDeathSounds(dmginfo, hitgroup) == true then
|
|
VJ_EmitSound(self, gib_sd1, 90, math.random(80, 100))
|
|
VJ_EmitSound(self, gib_sd2, 90, math.random(80, 100))
|
|
VJ_EmitSound(self, gib_sd3, 90, math.random(80, 100))
|
|
VJ_EmitSound(self, gib_sd4, 90, math.random(80, 100))
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:VJ_SetSchedule(schedID)
|
|
if self.VJ_PlayingSequence == true then return end
|
|
self.VJ_PlayingInterruptSequence = false
|
|
//if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then return end
|
|
//print(self:GetName().." - "..schedID)
|
|
self:SetSchedule(schedID)
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:StartSoundTrack()
|
|
if self.HasSounds == false or self.HasSoundTrack == false then return end
|
|
if math.random(1, self.SoundTrackChance) == 1 then
|
|
self:VJTags_Add(VJ_TAG_SD_PLAYING_MUSIC)
|
|
net.Start("vj_music_run")
|
|
net.WriteEntity(self)
|
|
net.WriteTable(self.SoundTbl_SoundTrack)
|
|
net.WriteFloat(self.SoundTrackVolume)
|
|
net.WriteFloat(self.SoundTrackPlaybackRate)
|
|
//net.WriteFloat(self.SoundTrackFadeOutTime)
|
|
net.Broadcast()
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:RunItemDropsOnDeathCode(dmginfo, hitgroup)
|
|
if self.HasItemDropsOnDeath == false then return end
|
|
if math.random(1, self.ItemDropsOnDeathChance) == 1 then
|
|
self:CustomRareDropsOnDeathCode(dmginfo, hitgroup)
|
|
local pickedEnt = VJ_PICK(self.ItemDropsOnDeath_EntityList)
|
|
if pickedEnt != false then
|
|
local ent = ents.Create(pickedEnt)
|
|
ent:SetPos(self:GetPos() + self:OBBCenter())
|
|
ent:SetAngles(self:GetAngles())
|
|
ent:Spawn()
|
|
ent:Activate()
|
|
local phys = ent:GetPhysicsObject()
|
|
if IsValid(phys) then
|
|
local dmgForce = (self.SavedDmgInfo.force / 40) + self:GetMoveVelocity() + self:GetVelocity()
|
|
if self.DeathAnimationCodeRan then
|
|
dmgForce = self:GetMoveVelocity() == defPos and self:GetGroundSpeedVelocity() or self:GetMoveVelocity()
|
|
end
|
|
phys:SetMass(1)
|
|
phys:ApplyForceCenter(dmgForce)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
function ENT:OnRemove()
|
|
self:CustomOnRemove()
|
|
self.Dead = true
|
|
if self.Medic_Status then self:DoMedicReset() end
|
|
if self.VJTags[VJ_TAG_EATING] then self:EatingReset("Dead") end
|
|
self:RemoveTimers()
|
|
self:StopAllCommonSounds()
|
|
self:StopParticles()
|
|
end
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------ ///// OBSOLETE FUNCTIONS | Recommended not to use! \\\\\ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
function ENT:DoHardEntityCheck(CustomTbl)
|
|
local EntsTbl = CustomTbl or ents.GetAll()
|
|
local EntsFinal = {}
|
|
local count = 1
|
|
//for k, v in ipairs(CustomTbl) do //ents.FindInSphere(self:GetPos(),30000)
|
|
for x=1, #EntsTbl do
|
|
if !EntsTbl[x]:IsNPC() && !EntsTbl[x]:IsPlayer() then continue end
|
|
local v = EntsTbl[x]
|
|
self:EntitiesToNoCollideCode(v)
|
|
if (v:IsNPC() && v:GetClass() != self:GetClass() && v:GetClass() != "npc_grenade_frag" && v:GetClass() != "bullseye_strider_focus" && v:GetClass() != "npc_bullseye" && v:GetClass() != "npc_enemyfinder" && v:GetClass() != "hornet" && (!v.IsVJBaseSNPC_Animal) && (v.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE) && v:Health() > 0) or (v:IsPlayer() && !VJ_CVAR_IGNOREPLAYERS) then
|
|
EntsFinal[count] = v
|
|
count = count + 1
|
|
end
|
|
end
|
|
//table.Merge(EntsFinal,self.CurrentPossibleEnemies)
|
|
return EntsFinal
|
|
end
|
|
*/
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
function ENT:NoCollide_CombineBall()
|
|
for k, v in ipairs(ents.GetAll()) do
|
|
if v:GetClass() == "prop_combine_ball" then
|
|
constraint.NoCollide(self, v, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
*/
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*function ENT:FindEnemy()
|
|
//self:AddRelationship( "npc_barnacle D_LI 99" )
|
|
if self.FindEnemy_UseSphere == true then
|
|
self:FindEnemySphere()
|
|
end
|
|
//if self.UseConeForFindEnemy == false then return end -- NOTE: This function got crossed out because the option at the top got deleted!
|
|
local EnemyTargets = VJ_FindInCone(self:GetPos(),self:GetForward(),self.SightDistance,self.SightAngle)
|
|
//local LocalTargetTable = {}
|
|
if (!EnemyTargets) then return end
|
|
//table.Add(EnemyTargets)
|
|
for k,v in ipairs(EnemyTargets) do
|
|
//if (v:GetClass() != self:GetClass() && v:GetClass() != "npc_grenade_frag") && v:IsNPC() or (v:IsPlayer() && self.PlayerFriendly == false && !VJ_CVAR_IGNOREPLAYERS) && self:Visible(v) then
|
|
//if self.CombineFriendly == true then if VJ_HasValue(NPCTbl_Combine,v:GetClass()) then return end end
|
|
//if self.ZombieFriendly == true then if VJ_HasValue(NPCTbl_Zombies,v:GetClass()) then return end end
|
|
//if self.AntlionFriendly == true then if VJ_HasValue(NPCTbl_Antlions,v:GetClass()) then return end end
|
|
//if self.PlayerFriendly == true then if VJ_HasValue(NPCTbl_Resistance,v:GetClass()) then return end end
|
|
//if GetConVar("vj_npc_vjfriendly"):GetInt() == 1 then
|
|
//local frivj = ents.FindByClass("npc_vj_*") table.Add(frivj) for _, x in ipairs(frivj) do return end end
|
|
//local vjanimalfriendly = ents.FindByClass("npc_vjanimal_*") table.Add(vjanimalfriendly) for _, x in ipairs(vjanimalfriendly) do return end
|
|
//self:AddEntityRelationship( v, D_HT, 99 )
|
|
//if !v:IsPlayer() then if self:Visible(v) then v:AddEntityRelationship( self, D_HT, 99 ) end end
|
|
if self:DoRelationshipCheck(v) == true && self:Visible(v) then
|
|
self.EnemyReset = true
|
|
self:AddEntityRelationship(v,D_HT,99)
|
|
if !IsValid(self:GetEnemy()) then
|
|
self:SetEnemy(v) //self:GetClosestInTable(EnemyTargets)
|
|
self.MyEnemy = v
|
|
self:UpdateEnemyMemory(v,v:GetPos())
|
|
end
|
|
//table.insert(LocalTargetTable,v)
|
|
//self.EnemyTable = LocalTargetTable
|
|
self:DoAlert()
|
|
//return
|
|
end
|
|
end
|
|
//self:ResetEnemy()
|
|
end*/
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*function ENT:FindEnemySphere()
|
|
local EnemyTargets = ents.FindInSphere(self:GetPos(),self.SightDistance)
|
|
if (!EnemyTargets) then return end
|
|
table.Add(EnemyTargets)
|
|
for k,v in ipairs(EnemyTargets) do
|
|
if self:DoRelationshipCheck(v) == true && self:Visible(v) then
|
|
self.EnemyReset = true
|
|
if !IsValid(self:GetEnemy()) then
|
|
self:SetEnemy(v)
|
|
self.MyEnemy = v
|
|
self:UpdateEnemyMemory(v,v:GetPos())
|
|
end
|
|
self:DoAlert()
|
|
//return
|
|
end
|
|
end
|
|
end*/
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*function ENT:VJ_EyeTrace(GetUpNum)
|
|
GetUpNum = GetUpNum or 50
|
|
if IsValid(self:GetEnemy()) && !self.Dead then
|
|
local tr = util.TraceLine({
|
|
start = self:NearestPoint(self:GetEnemy():GetPos() +self:GetEnemy():OBBCenter() +self:GetUp()*GetUpNum),
|
|
endpos = self:GetEnemy():GetPos() +self:GetEnemy():OBBCenter(),
|
|
filter = self
|
|
})
|
|
//if tr.Entity != NULL then print(tr.Entity) end
|
|
//print(tr.Entity)
|
|
//local testprop = ents.Create("prop_dynamic")
|
|
//testprop:SetModel("models/props_wasteland/dockplank01b.mdl")
|
|
//testprop:SetPos(self:NearestPoint(self:GetEnemy():GetPos() +self:GetEnemy():OBBMaxs() +self:GetUp()*50))
|
|
//testprop:Spawn()
|
|
//if tr.HitWorld == false && tr.Entity != NULL && tr.Entity:GetClass() != "prop_physics" then
|
|
//print("true") return true else
|
|
//print("false") return false end
|
|
//print("false") return false
|
|
if tr.HitWorld == true then return false end
|
|
if tr.Entity != NULL then
|
|
return tr
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
return false
|
|
end*/
|
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*function ENT:ThemeMusicCode()
|
|
if GetConVar("vj_npc_sd_nosounds"):GetInt() == 0 then
|
|
if GetConVar("vj_npc_sd_soundtrack"):GetInt() == 0 then
|
|
self.thememusicsd = CreateSound( player.GetByID( 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,71,72,73,74,75,76,77,78,79,80,81,82,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100 ), self.Theme )
|
|
self.thememusicsd:Play();
|
|
self.thememusicsd:Stop();
|
|
self.thememusicsd:SetSoundLevel( self.SoundTrackLevel )
|
|
if self.thememusicsd:IsPlaying() == false then self.thememusicsd:Play();
|
|
end
|
|
end
|
|
end
|
|
end*/
|
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
|
/*function ENT:GetRelationship(entity)
|
|
if self.HasAllies == false then return end
|
|
|
|
local friendslist = {"", "", "", "", "", ""} -- List
|
|
for _,x in ipairs( friendslist ) do
|
|
local hl_friendlys = ents.FindByClass( x )
|
|
for _,x in ipairs( hl_friendlys ) do
|
|
if entity == x then
|
|
return D_LI
|
|
end
|
|
end
|
|
end
|
|
|
|
local groupone = ents.FindByClass("npc_vj_example_*") -- Group
|
|
table.Add(groupone)
|
|
for _, x in ipairs(groupone) do
|
|
if entity == x then
|
|
return D_LI
|
|
end
|
|
end
|
|
|
|
local groupone = ents.FindByClass("npc_vj_example") -- Single
|
|
for _, x in ipairs(groupone) do
|
|
if entity == x then
|
|
return D_LI
|
|
end
|
|
end
|
|
end*/ |