--[[ | 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*/