Библиотека VJ Base
July 11
VJ-Base - npc_vj_human_base - init
Путь к файлу «VJ-Base/lua/entities/npc_vj_human_base/init.lua»
AddCSLuaFile("shared.lua") include("vj_base/ai/core.lua") include("vj_base/ai/schedules.lua") include("vj_base/ai/base_aa.lua") include("shared.lua") /*-------------------------------------------------- *** Copyright (c) 2012-2025 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. --------------------------------------------------*/ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Main & Misc ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.Model = false -- Model(s) to spawn with | Picks a random one if it's a table ENT.CanChatMessage = true -- Is it allowed to post in a player's chat? | Example: "Blank no longer likes you." -- ====== Health ====== -- ENT.StartHealth = 50 ENT.HealthRegenParams = { Enabled = false, -- Can it regenerate its health? Amount = 4, -- How much should the health increase after every delay? Delay = VJ.SET(2, 4), -- How much time until the health increases ResetOnDmg = true, -- Should the delay reset when it receives damage? } -- ====== Collision ====== -- ENT.HullType = HULL_HUMAN -- List of Hull types: https://wiki.facepunch.com/gmod/Enums/HULL ENT.EntitiesToNoCollide = false -- Set to a table of entity class names for it to not collide with otherwise leave it to false -- ====== NPC Controller ====== -- ENT.ControllerParams = { CameraMode = 1, -- Sets the default camera mode | 1 = Third Person, 2 = First Person ThirdP_Offset = Vector(0, 0, 0), -- The offset for the controller when the camera is in third person FirstP_Bone = "ValveBiped.Bip01_Head1", -- If left empty, the base will attempt to calculate a position for first person FirstP_Offset = Vector(0, 0, 5), -- The offset for the controller when the camera is in first person FirstP_ShrinkBone = true, -- Should the bone shrink? Useful if the bone is obscuring the player's view FirstP_CameraBoneAng = 0, -- Should the camera's angle be affected by the bone's angle? | 0 = No, 1 = Pitch, 2 = Yaw, 3 = Roll FirstP_CameraBoneAng_Offset = 0, -- How much should the camera's angle be rotated by? | Useful for weird bone angles } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Movement ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.SightDistance = 6500 -- Initial sight distance | To retrieve: "self:GetMaxLookDistance()" | To change: "self:SetMaxLookDistance(distance)" ENT.SightAngle = 177 -- Initial field of view | To retrieve: "self:GetFOV()" | To change: "self:SetFOV(degree)" | 360 = See all around ENT.TurningSpeed = 20 -- Initial turning speed | To retrieve: "self:GetMaxYawSpeed()" | To change: "self:SetMaxYawSpeed(speed)" ENT.TurningUseAllAxis = false -- If set to true, angles will not be restricted to y-axis, it will change all axes (plural axis) ENT.CanTurnWhileMoving = true -- Can it turn while moving? | EX: GoldSrc NPCs, Facing enemy while running to cover, Facing the player while moving out of the way ENT.MovementType = VJ_MOVETYPE_GROUND -- Types: VJ_MOVETYPE_GROUND | VJ_MOVETYPE_AERIAL | VJ_MOVETYPE_AQUATIC | VJ_MOVETYPE_STATIONARY | VJ_MOVETYPE_PHYSICS ENT.UsePoseParameterMovement = false -- Sets the model's "move_x" and "move_y" pose parameters while moving | Required for player models to move properly! -- ====== JUMPING ====== -- -- Requires "CAP_MOVE_JUMP" capability -- Applied automatically by the base if "ACT_JUMP" is valid on the NPC's model -- Example scenario: -- [A] <- Apex -- / \ -- / [S] <- Start -- [E] <- End ENT.JumpParams = { Enabled = true, -- Can it do movement jumps? MaxRise = 80, -- How high it can jump up ((S -> A) AND (S -> E)) MaxDrop = 230, -- How low it can jump down (E -> S) MaxDistance = 275, -- Maximum distance between Start and End } -- ====== STATIONARY ====== -- ENT.CanTurnWhileStationary = true -- Can it turn while using stationary move type? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ AI & Relationship ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.Behavior = VJ_BEHAVIOR_AGGRESSIVE -- What type of AI behavior is it? ENT.IsGuard = false -- Should it guard its position? | Will attempt to stay around its guarding position ENT.NextProcessTime = 1 -- Time until it runs the essential performance-heavy AI components ENT.EnemyDetection = true -- Can it search and detect for enemies? ENT.EnemyTouchDetection = true -- Can it turn and detect enemies that collide with it? ENT.EnemyXRayDetection = false -- Can it detect enemies through walls & objects? ENT.EnemyTimeout = 15 -- Time until the enemy target is reset if it's not visible ENT.AlertTimeout = VJ.SET(14, 16) -- Time until it transitions from alerted state to idle state assuming it has no enemy ENT.IdleAlwaysWander = false -- Should it constantly wander while idle? ENT.DisableWandering = false ENT.DisableChasingEnemy = false ENT.CanOpenDoors = true -- Can it open doors? -- ====== Alliances ====== -- ENT.CanAlly = true -- Can it ally with other entities? ENT.VJ_NPC_Class = {} -- Relationship classes, any entity with the same class will be seen as an ally -- Common Classes: -- Players / Resistance / Black Mesa = "CLASS_PLAYER_ALLY" || HECU = "CLASS_UNITED_STATES" || Portal = "CLASS_APERTURE" -- Combine = "CLASS_COMBINE" || Zombie = "CLASS_ZOMBIE" || Antlions = "CLASS_ANTLION" || Xen = "CLASS_XEN" || Black-Ops = "CLASS_BLACKOPS" ENT.AlliedWithPlayerAllies = false -- Should it be allied with other player allies? | Both entities must have "CLASS_PLAYER_ALLY" ENT.YieldToAlliedPlayers = true -- Should it give space to allied players? ENT.BecomeEnemyToPlayer = false -- Should it become enemy towards an allied player if it's damaged by them or it witnesses another ally killed by them? -- false = Don't turn hostile to allied players | number = Threshold, where each negative event increases it by 1, if it passes this number it will become hostile ENT.CanReceiveOrders = true -- Can it receive orders from allies? | Ex: Allies calling for help, allies requesting backup on damage, etc. -- false = Will not receive the following: "CallForHelp", "DamageAllyResponse", "DeathAllyResponse", "Passive_AlliesRunOnDamage" -- ====== Passive Behaviors ====== -- ENT.Passive_RunOnTouch = true -- Should it run and make a alert sound when something collides with it? ENT.Passive_AlliesRunOnDamage = true -- Should its allies (other passive NPCs) also run when it's damaged? -- ====== On Player Sight ====== -- ENT.HasOnPlayerSight = false -- Should do something when it a player? ENT.OnPlayerSightDistance = 200 -- How close should the player be until it runs the code? ENT.OnPlayerSightDispositionLevel = 1 -- 0 = Run it every time | 1 = Run it only when friendly to player | 2 = Run it only when enemy to player ENT.OnPlayerSightOnlyOnce = true -- If true, it will only run it once | Sets "self.HasOnPlayerSight" to false after it runs! ENT.OnPlayerSightNextTime = VJ.SET(15, 20) -- How much time should it pass until it runs the code again? -- ====== Call For Help ====== -- ENT.CallForHelp = true -- Can it request allies for help while in combat? ENT.CallForHelpDistance = 2000 -- Max distance its request for help travels ENT.CallForHelpCooldown = 4 -- Time until it calls for help again ENT.AnimTbl_CallForHelp = {ACT_SIGNAL_ADVANCE, ACT_SIGNAL_FORWARD} -- Call for help animations | false = Don't play an animation ENT.CallForHelpAnimFaceEnemy = true -- Should it face the enemy while playing the animation? ENT.CallForHelpAnimCooldown = 30 -- How much time until it can play an animation again? -- ====== Medic ====== -- -- Medics only heal allied entities that are tagged with "self.VJ_ID_Healable", by default it includes VJ NPCs and players ENT.IsMedic = false -- Should it heal allied entities? ENT.Medic_CheckDistance = 600 -- Max distance to check for injured allies ENT.Medic_HealDistance = 30 -- How close does it have to be until it stops moving and heals its ally? ENT.Medic_TimeUntilHeal = false -- Time until the ally receives health | false = Base auto calculates the duration ENT.AnimTbl_Medic_GiveHealth = ACT_SPECIAL_ATTACK1 -- Animations to play when it heals an ally | false = Don't play an animation ENT.Medic_HealAmount = 25 -- How health does it give? ENT.Medic_NextHealTime = VJ.SET(10, 15) -- How much time until it can give health to an ally again ENT.Medic_SpawnPropOnHeal = true -- Should it spawn a prop, such as small health vial at a attachment when healing an ally? ENT.Medic_SpawnPropOnHealModel = "models/healthvial.mdl" -- The model that it spawns ENT.Medic_SpawnPropOnHealAttachment = "anim_attachment_LH" -- The attachment it spawns on -- ====== Follow System ====== -- -- Associated variables: self.FollowData, self.IsFollowing -- NOTE: Stationary NPCs can't use follow system! ENT.FollowPlayer = true -- Should it follow allied players when the player presses the USE key? ENT.FollowMinDistance = 100 -- Minimum distance it should come when following something | The base automatically adds the NPC's size to this variable to account for different sizes! -- ====== Constantly Face Enemy ====== -- ENT.ConstantlyFaceEnemy = false -- Should it face the enemy constantly? ENT.ConstantlyFaceEnemy_IfVisible = true -- Should it only face the enemy if it's visible? ENT.ConstantlyFaceEnemy_IfAttacking = false -- Should it face the enemy when attacking? ENT.ConstantlyFaceEnemy_Postures = "Both" -- "Both" = Moving or standing | "Moving" = Only when moving | "Standing" = Only when standing ENT.ConstantlyFaceEnemy_MinDistance = 2500 -- How close does it have to be until it starts to face the enemy? -- ====== Pose Parameter Tracking ====== -- ENT.HasPoseParameterLooking = true -- Does it look at its enemy using pose parameters? ENT.PoseParameterLooking_Names = {pitch = {}, yaw = {}, roll = {}} -- Custom pose parameters to use, can put as many as needed ENT.PoseParameterLooking_InvertPitch = false -- Inverts the pitch pose parameters (X) ENT.PoseParameterLooking_InvertYaw = false -- Inverts the yaw pose parameters (Y) ENT.PoseParameterLooking_InvertRoll = false -- Inverts the roll pose parameters (Z) ENT.PoseParameterLooking_TurningSpeed = 10 -- How fast does the parameter turn? ENT.PoseParameterLooking_CanReset = true -- Should it reset its pose parameters if there is no enemies? -- ====== Investigation ====== -- -- Showcase: https://www.youtube.com/watch?v=cCqoqSDFyC4 ENT.CanInvestigate = true -- Can it detect and investigate disturbances? | EX: Sounds, movement, flashlight, bullet hits ENT.InvestigateSoundMultiplier = 9 -- Max sound hearing distance multiplier | This multiplies the calculated volume of the sound -- ====== Danger & Grenade Detection ====== -- -- Showcase: https://www.youtube.com/watch?v=XuaMWPTe6rA -- EXAMPLES: Props that are one fire, especially objects like barrels that are about to explode, Combine mine that is triggered and about to explode, The location that the Antlion Worker's spit is going to hit, Combine Flechette that is about to explode, -- Antlion Guard that is charging towards the NPC, Player that is driving a vehicle at high speed towards the NPC, Manhack that has opened its blades, Rollermine that is about to self-destruct, Combine Helicopter that is about to drop bombs or is firing a turret near the NPC, -- Combine Gunship's is about to fire its belly cannon near the NPC, Turret impact locations fired by Combine Gunships, or Combine Dropships, or Striders, The location that a Combine Dropship is going to deploy soldiers, Strider is moving on top of the NPC, -- The location that the Combine or HECU mortar is going to hit, SMG1 grenades that are flying close by, A Combine soldier that is rappelling on top of the NPC, Stalker's laser impact location, Combine APC that is driving towards the NPC ENT.CanDetectDangers = true -- Can it detect dangers? | Ex: Grenades, fire, bombs, explosives, etc. ENT.DangerDetectionDistance = 400 -- Max danger detection distance | WARNING: Most of the non-grenade dangers ignore this max value ENT.CanRedirectGrenades = true -- Can it pick up detected grenades and throw it away or to the enemy? -- NOTE: Can only throw grenades away if it has a grenade attack AND can detect dangers -- ====== Taking Cover ====== -- ENT.AnimTbl_TakingCover = ACT_COVER_LOW -- Animations it plays when hiding behind a covered position ENT.AnimTbl_MoveToCover = ACT_RUN_CROUCH -- Movement animations it plays when moving to a covered position ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Damaged / Injured ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- ====== Blood ====== -- -- Leave blood tables empty to let the base decide depending on the blood type ENT.Bleeds = true -- Can it bleed? Controls all bleeding related components such blood decal, particle, pool, etc. ENT.BloodColor = VJ.BLOOD_COLOR_NONE -- Its blood type, this will determine the blood decal, particle, etc. ENT.HasBloodDecal = true -- Should it spawn a decal when damaged? ENT.BloodDecal = {} -- Decals to spawn when it's damaged ENT.BloodDecalUseGMod = false -- Should it use the current default decals defined by Garry's Mod? | Only applies for certain blood types! ENT.BloodDecalDistance = 150 -- Max distance blood decals can splatter ENT.HasBloodParticle = true -- Should it spawn a particle when damaged? ENT.BloodParticle = {} -- Particles to spawn when it's damaged ENT.HasBloodPool = true -- Should a blood pool spawn by its corpse? ENT.BloodPool = {} -- Blood pools to be spawned by the corpse -- ====== Immunity ====== -- ENT.GodMode = false -- Immune to everything ENT.ForceDamageFromBosses = false -- Should it receive damage by bosses regardless of its immunities? | Bosses are attackers tagged with "VJ_ID_Boss" ENT.AllowIgnition = true -- Can it be set on fire? ENT.Immune_Bullet = false -- Immune to bullet damages ENT.Immune_Melee = false -- Immune to melee damages (Ex: Slashes, stabs, punches, claws, crowbar, blunt attacks) ENT.Immune_Explosive = false -- Immune to explosive damages (Ex: Grenades, rockets, bombs, missiles) ENT.Immune_Dissolve = false -- Immune to dissolving damage (Ex: Combine ball) ENT.Immune_Toxic = false -- Immune to toxic effect damages (Ex: Acid, poison, radiation, gas) ENT.Immune_Fire = false -- Immune to fire / flame damages ENT.Immune_Electricity = false -- Immune to electrical damages (Ex: Shocks, lasers, gravity gun) ENT.Immune_Sonic = false -- Immune to sonic damages (Ex: Sound blasts) -- ====== Flinching ====== -- ENT.CanFlinch = false -- Can it flinch? | false = Don't flinch | true = Always flinch | "DamageTypes" = Flinch only from certain damages types ENT.FlinchDamageTypes = {DMG_BLAST} -- Which types of damage types should it flinch from when "DamageTypes" is used? ENT.FlinchChance = 16 -- Chance of flinching from 1 to x | 1 = Always flinch ENT.FlinchCooldown = 5 -- How much time until it can flinch again? | false = Base auto calculates the duration ENT.AnimTbl_Flinch = ACT_FLINCH_PHYSICS ENT.FlinchHitGroupMap = false -- EXAMPLE: {{HitGroup = HITGROUP_HEAD, Animation = ACT_FLINCH_HEAD}, {HitGroup = HITGROUP_LEFTARM, Animation = ACT_FLINCH_LEFTARM}, {HitGroup = HITGROUP_RIGHTARM, Animation = ACT_FLINCH_RIGHTARM}, {HitGroup = HITGROUP_LEFTLEG, Animation = ACT_FLINCH_LEFTLEG}, {HitGroup = HITGROUP_RIGHTLEG, Animation = ACT_FLINCH_RIGHTLEG}} ENT.FlinchHitGroupPlayDefault = true -- Should it play "self.AnimTbl_Flinch" when none of the mapped hit groups hit? -- ====== Non-Combat Damage Response Behaviors ====== -- -- For passive behavior NPC, these responses will run regardless if it has an active enemy or not ENT.DamageResponse = true -- Should it respond to damages while it has no enemy? -- true = Search for enemies or run to a covered position | "OnlyMove" = Will only run to a covered position | "OnlySearch" = Will only search for enemies ENT.DamageAllyResponse = true -- Should allies respond when it's damaged while it has no enemy? ENT.AnimTbl_DamageAllyResponse = ACT_SIGNAL_GROUP -- Animations to play when it calls allies to respond | false = Don't play an animation ENT.DamageAllyResponse_Cooldown = VJ.SET(9, 12) -- How long until it can call allies again? -- ====== Combat Damage Response Behaviors ====== -- -- Hiding behind objects uses "self.AnimTbl_TakingCover" ENT.CombatDamageResponse = true -- Should it respond to damages while it has an active enemy? | true = Hide behind an object if possible otherwise run to a covered position ENT.CombatDamageResponse_CoverTime = VJ.SET(3, 5) -- If it found an object to hide behind, how long should it stay hidden? ENT.CombatDamageResponse_Cooldown = VJ.SET(3, 3.5) -- How long until it can do any combat damage response? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Death & Corpse ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.DeathDelayTime = 0 -- Time until it spawns the corpse, removes itself, etc. -- ====== Ally Responses ====== -- -- An ally must have "self.CanReceiveOrders" enabled to respond! ENT.DeathAllyResponse = "OnlyAlert" -- How should allies response when it dies? -- false = No reactions | true = Allies respond by becoming alert and moving to its location | "OnlyAlert" = Allies respond by becoming alert ENT.DeathAllyResponse_MoveLimit = 4 -- Max number of allies that can move to its location when responding to its death -- ====== Death Animation ====== -- -- NOTE: This is added on top of "self.DeathDelayTime" ENT.HasDeathAnimation = false -- Should it play death animations? ENT.AnimTbl_Death = {} ENT.DeathAnimationTime = false -- How long should the death animation play? | false = Base auto calculates the duration ENT.DeathAnimationChance = 1 -- Put 1 if you want it to play the animation all the time ENT.DeathAnimationDecreaseLengthAmount = 0 -- This will decrease the time until it turns into a corpse -- ====== Corpse ====== -- ENT.HasDeathCorpse = true -- Should a corpse spawn when it's killed? ENT.DeathCorpseEntityClass = false -- Corpse's class | false = Let the base automatically detect the class ENT.DeathCorpseModel = false -- Model(s) to use as the corpse | false = Use its current model | Can be a string or a table of strings ENT.DeathCorpseCollisionType = COLLISION_GROUP_DEBRIS -- Collision type for the corpse | NPC Options Menu can only override this value if it's set to COLLISION_GROUP_DEBRIS! ENT.DeathCorpseFade = false -- Should the corpse fade after the given amount of seconds? | false = Don't fade | number = Fade out time ENT.DeathCorpseSetBoneAngles = true -- This can be used to stop the corpse glitching or flying on death ENT.DeathCorpseApplyForce = true -- Should the force of the damage be applied to the corpse? ENT.DeathCorpseSubMaterials = nil -- Apply a table of indexes that correspond to a sub material index, this will cause the base to copy the NPC's sub material to the corpse. -- ====== Dismemberment / Gib ====== -- ENT.CanGib = true -- Can it dismember? | Makes "CreateGibEntity" fail and overrides "CanGibOnDeath" to false ENT.CanGibOnDeath = true -- Can it dismember on death? ENT.GibOnDeathFilter = true -- Should it only gib and call "self:HandleGibOnDeath" when it's killed by a specific damage types? | false = Call "self:HandleGibOnDeath" from any damage type ENT.HasGibOnDeathSounds = true -- Does it have gib sounds? | Mostly used for the settings menu ENT.HasGibOnDeathEffects = true -- Does it spawn particles on death or when it gibs? | Mostly used for the settings menu -- ====== Drops On Death ====== -- ENT.DropWeaponOnDeath = true -- Should it drop its weapon on death? ENT.DropDeathLoot = true -- Should it drop loot on death? ENT.DeathLoot = {"weapon_frag", "item_healthvial"} -- List of entities it will randomly pick to drop | Leave it empty to drop nothing ENT.DeathLootChance = 14 -- If set to 1, it will always drop loot ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Melee Attack ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasMeleeAttack = true ENT.MeleeAttackDamage = 10 ENT.MeleeAttackDamageType = DMG_CLUB ENT.HasMeleeAttackKnockBack = true -- Should knockback be applied on melee hit? | Use "MeleeAttackKnockbackVelocity" function to edit the velocity ENT.DisableDefaultMeleeAttackDamageCode = false -- Disables the default melee attack damage code -- ====== Animation ====== -- ENT.AnimTbl_MeleeAttack = ACT_MELEE_ATTACK1 -- Animations to play when it melee attacks | false = Don't play an animation ENT.MeleeAttackAnimationFaceEnemy = true -- Should it face the enemy while playing the melee attack animation? ENT.MeleeAttackAnimationDecreaseLengthAmount = 0 -- Decreases animation time | Use it to fix animations that have extra frames at the end -- ====== Distance ====== -- ENT.MeleeAttackDistance = false -- How close an enemy has to be to trigger a melee attack | false = Auto calculate on initialize based on its collision bounds ENT.MeleeAttackAngleRadius = 100 -- What is the attack angle radius? | 100 = In front of it | 180 = All around it ENT.MeleeAttackDamageDistance = false -- How far does the damage go? | false = Auto calculate on initialize based on its collision bounds ENT.MeleeAttackDamageAngleRadius = 100 -- What is the damage angle radius? | 100 = In front of it | 180 = All around it -- ====== Timer ====== -- ENT.TimeUntilMeleeAttackDamage = 0.5 -- How much time until it executes the damage? | false = Make the attack event-based ENT.NextMeleeAttackTime = 0 -- How much time until it can use a melee attack? | number = Specific time | VJ.SET = Randomized between the 2 numbers ENT.NextAnyAttackTime_Melee = false -- How much time until it can do any attack again? | false = Base auto calculates the duration | number = Specific time | VJ.SET = Randomized between the 2 numbers ENT.MeleeAttackReps = 1 -- How many times does it run the melee attack code? ENT.MeleeAttackExtraTimers = false -- Extra melee attack timers | EX: {1, 1.4} ENT.MeleeAttackStopOnHit = false -- Should it stop executing the melee attack after it hits an enemy? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Grenade Attack ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasGrenadeAttack = false ENT.GrenadeAttackEntity = "obj_vj_grenade" -- Entities that it can spawn when throwing a grenade | If set as a table, it picks a random entity | VJ: "obj_vj_grenade" | HL2: "npc_grenade_frag" ENT.GrenadeAttackMinDistance = 400 -- Min distance it can throw a grenade ENT.GrenadeAttackMaxDistance = 1500 -- Max distance it can throw a grenade ENT.GrenadeAttackChance = 4 -- 1 in x chance that it will throw a grenade when all the requirements are met | 1 = Throw it every time ENT.GrenadeAttackModel = false -- Overrides the grenade model | Can be string or table | Does NOT apply to picked up grenades and forced grenade attacks with custom entity ENT.GrenadeAttackAttachment = "anim_attachment_LH" -- The attachment that the grenade will be set to | -1 = Skip to use "self.GrenadeAttackBone" instead ENT.GrenadeAttackBone = "ValveBiped.Bip01_L_Finger1" -- The bone that the grenade will be set to | -1 = Skip to use fail safe instead -- ====== Animation ====== -- ENT.AnimTbl_GrenadeAttack = "grenThrow" -- Animations to play when it throws a grenade | false = Don't play an animation ENT.GrenadeAttackAnimationFaceEnemy = true -- Should it face the enemy while playing the grenade attack animation? -- ====== Timer ====== -- ENT.GrenadeAttackThrowTime = 0.72 -- Time until the grenade is thrown | false = Make the attack event-based ENT.NextGrenadeAttackTime = VJ.SET(10, 15) -- Time until it can do a grenade attack again | number = Specific time | VJ.SET = Randomized between the 2 numbers ENT.NextAnyAttackTime_Grenade = false -- How much time until it can do any attack again? | false = Base auto calculates the duration | number = Specific time | VJ.SET = Randomized between the 2 numbers ENT.GrenadeAttackFuseTime = 3 -- Grenade's fuse time after it's thrown ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Weapon Attack ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.Weapon_Disabled = false -- Disable the ability for it to use weapons ENT.Weapon_IgnoreSpawnMenu = false -- Should it ignore weapon overrides from the spawn menu? ENT.Weapon_UnarmedBehavior = true -- Should it flee from enemies when it's unarmed? ENT.Weapon_Accuracy = 1 -- Its accuracy with weapons, affects bullet spread! | x < 1 = Better accuracy | x > 1 = Worse accuracy ENT.Weapon_CanMoveFire = true -- Can it fire its weapon while it's moving? ENT.Weapon_Strafe = true -- Should it strafe around while firing a weapon? ENT.Weapon_StrafeCooldown = VJ.SET(3, 6) -- How much time until it can strafe again? ENT.Weapon_OcclusionDelay = true -- Should it wait before leaving its position to pursue the enemy after its been occluded? ENT.Weapon_OcclusionDelayTime = VJ.SET(3, 5) -- How long should it wait before it starts to pursue? ENT.Weapon_OcclusionDelayMinDist = 100 -- Skip this behavior if the occluded enemy is within this distance -- ====== Distance ====== -- ENT.Weapon_MinDistance = 10 -- Min distance it can fire a weapon ENT.Weapon_MaxDistance = 3000 -- Max distance it can fire a weapon ENT.Weapon_RetreatDistance = 150 -- Minimum distance an enemy has to be for it to retreat back | 0 = Never retreat ENT.Weapon_AimTurnDiff = false -- Weapon aim turning threshold between 0 and 1 | "self.HasPoseParameterLooking" must be set to true! -- EXAMPLES: 0.707106781187 = 45 degrees | 0.866025403784 = 30 degrees | 1 = 0 degrees, always turn! -- false = Let base decide based on animation set and weapon hold type -- ====== Primary Fire ====== -- ENT.AnimTbl_WeaponAttack = ACT_RANGE_ATTACK1 -- Animations to play while firing a weapon ENT.AnimTbl_WeaponAttackGesture = ACT_GESTURE_RANGE_ATTACK1 -- Gesture animations to play while firing a weapon | false = Don't play an animation ENT.Weapon_CanCrouchAttack = true -- Can it crouch while firing a weapon? ENT.Weapon_CrouchAttackChance = 2 -- What is the chance of it crouching? | 1 = Crouch whenever possible ENT.AnimTbl_WeaponAttackCrouch = ACT_RANGE_ATTACK1_LOW -- Animations to play while firing a weapon in crouched position -- ====== Secondary Fire ====== -- ENT.Weapon_CanSecondaryFire = true -- Can it use a weapon's secondary fire if it's available? ENT.Weapon_SecondaryFireTime = false -- How much time until the secondary fire's projectile is released | false = Base auto calculates the duration ENT.AnimTbl_WeaponAttackSecondary = ACT_RANGE_ATTACK2 -- Animations to play while firing the weapon's secondary attack -- ====== Reloading ====== -- ENT.Weapon_CanReload = true -- Can it reload weapons? ENT.Weapon_FindCoverOnReload = true -- Should it attempt to find cover before proceeding to reload? ENT.AnimTbl_WeaponReload = ACT_RELOAD ENT.AnimTbl_WeaponReloadCovered = ACT_RELOAD_LOW ENT.DisableWeaponReloadAnimation = false -- Disables the default reload animation code -- ====== Weapon Inventory ====== -- -- Weapons are given on spawn and it will only switch to those if the requirements are met -- All are stored in "self.WeaponInventory" with the following keys: -- Primary : Default weapon -- AntiArmor : Enemy is an armored tank/vehicle or a boss -- Melee : Enemy is (very close and the NPC is out of ammo) OR (in melee attack distance) + NPC must have more than 25% health ENT.WeaponInventory_AntiArmorList = false -- Anti-armor weapons to give on spawn | Can be table or string ENT.WeaponInventory_MeleeList = false -- Melee weapons to give on spawn | Can be table or string ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Sound ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasSounds = true -- Can it play sounds? | false = Disable ALL sounds ENT.DamageByPlayerDispositionLevel = 1 -- When should it play "DamageByPlayer" sounds? | 0 = Always | 1 = ONLY when friendly to player | 2 = ONLY when enemy to player -- ====== Footstep Sound ====== -- ENT.HasFootstepSounds = true -- Can it play footstep sounds? ENT.DisableFootStepSoundTimer = false -- Disables the timer system, allowing to utilize model events ENT.FootstepSoundTimerWalk = 0.5 -- Delay between footstep sounds while it is walking | false = Disable while walking ENT.FootstepSoundTimerRun = 0.25 -- Delay between footstep sounds while it is running | false = Disable while running -- ====== Idle Sound ====== -- ENT.HasIdleSounds = true -- Can it play idle sounds? | Controls "self.SoundTbl_Idle", "self.SoundTbl_IdleDialogue", "self.SoundTbl_CombatIdle" ENT.IdleSoundsWhileAttacking = false -- Can it play idle sounds while performing an attack? ENT.IdleSoundsRegWhileAlert = false -- Should it disable playing regular idle sounds when combat idle sound is empty? -- ====== Idle Dialogue Sound ====== -- -- When an allied NPC or player is within range, it will play these sounds rather than regular idle sounds -- If the ally is a VJ NPC and has dialogue answer sounds, it will respond back ENT.HasIdleDialogueSounds = true -- Can it play idle dialogue sounds? ENT.HasIdleDialogueAnswerSounds = true -- Can it play idle dialogue answer sounds? ENT.IdleDialogueDistance = 400 -- How close should an ally be for it to initiate a dialogue ENT.IdleDialogueCanTurn = true -- Should it turn to to face its dialogue target? -- ====== On Killed Enemy ====== -- ENT.HasKilledEnemySounds = true -- Can it play sounds when it kills an enemy? ENT.KilledEnemySoundLast = true -- Should it only play "self.SoundTbl_KilledEnemy" if there is no enemies left? -- ====== Sound Track ====== -- ENT.HasSoundTrack = false -- Can it play sound tracks? ENT.SoundTrackVolume = 1 -- Volume of the sound track | 1 = Normal | 2 = 200% | 0.5 = 50% ENT.SoundTrackPlaybackRate = 1 -- Playback speed of sound tracks | 1 = Normal | 2 = Twice the speed | 0.5 = Half the speed -- ====== Other Sound Controls ====== -- ENT.HasBreathSound = true -- Can it play breathing sounds? ENT.HasReceiveOrderSounds = true -- Can it play sounds when it receives an order? ENT.HasFollowPlayerSounds = true -- Can it play follow and unfollow player sounds? | Controls "self.SoundTbl_FollowPlayer", "self.SoundTbl_UnFollowPlayer" ENT.HasYieldToPlayerSounds = true -- Can it play sounds when it yields to an allied player? ENT.HasMedicSounds = true -- Can it play medic sounds? | Controls "self.SoundTbl_MedicBeforeHeal", "self.SoundTbl_MedicOnHeal", "self.SoundTbl_MedicReceiveHeal" ENT.HasOnPlayerSightSounds = true -- Can it play sounds when it sees a player? ENT.HasInvestigateSounds = true -- Can it play sounds when it investigates something? ENT.HasLostEnemySounds = true -- Can it play sounds when it looses its enemy? ENT.HasAlertSounds = true -- Can it play alert sounds? ENT.HasCallForHelpSounds = true -- Can it play sounds when it call allies for help? ENT.HasBecomeEnemyToPlayerSounds = true -- Can it play sounds when it becomes hostile to an allied player? ENT.HasSuppressingSounds = true -- Can it play weapon suppressing sounds? ENT.HasWeaponReloadSounds = true -- Can it play weapon reload sounds? ENT.HasMeleeAttackSounds = true -- Can it play melee attack sounds? | Controls "self.SoundTbl_BeforeMeleeAttack", "self.SoundTbl_MeleeAttack", "self.SoundTbl_MeleeAttackExtra" ENT.HasExtraMeleeAttackSounds = true -- Can it play extra melee attack sound effects? ENT.HasMeleeAttackMissSounds = true -- Can it play melee attack miss sounds? ENT.HasGrenadeAttackSounds = true -- Can it play grenade attack sounds? ENT.HasDangerSightSounds = true -- Can it play sounds with detects a danger? | Controls "self.SoundTbl_DangerSight", "self.SoundTbl_GrenadeSight" ENT.HasAllyDeathSounds = true -- Can it play sounds when an ally dies? ENT.HasPainSounds = true -- Can it play pain sounds? ENT.HasImpactSounds = true -- Can it play impact sound effects? ENT.HasDamageByPlayerSounds = true -- Can it play sounds when it's damaged by a player? ENT.HasDeathSounds = true -- Can it play death sounds? -- ====== Sound Paths ====== -- -- There are 2 types of sounds: "Speech" and "EFFECT" | Most sound tables are "SPEECH" unless stated -- SPEECH : Mostly play speech sounds | Will stop when another speech sound is played -- EFFECT : Mostly play sound effects | EX: Movement sound, impact sound, attack swipe sound, etc. ENT.SoundTbl_SoundTrack = false ENT.SoundTbl_FootStep = "VJ.Footstep.Human" -- EFFECT ENT.SoundTbl_Breath = false -- EFFECT ENT.SoundTbl_Idle = false ENT.SoundTbl_IdleDialogue = false ENT.SoundTbl_IdleDialogueAnswer = false ENT.SoundTbl_CombatIdle = false ENT.SoundTbl_ReceiveOrder = false ENT.SoundTbl_FollowPlayer = false ENT.SoundTbl_UnFollowPlayer = false ENT.SoundTbl_YieldToPlayer = false ENT.SoundTbl_MedicBeforeHeal = false ENT.SoundTbl_MedicOnHeal = "items/smallmedkit1.wav" -- EFFECT ENT.SoundTbl_MedicReceiveHeal = false ENT.SoundTbl_OnPlayerSight = false ENT.SoundTbl_Investigate = false ENT.SoundTbl_LostEnemy = false ENT.SoundTbl_Alert = false ENT.SoundTbl_CallForHelp = false ENT.SoundTbl_BecomeEnemyToPlayer = false ENT.SoundTbl_Suppressing = false ENT.SoundTbl_WeaponReload = false ENT.SoundTbl_BeforeMeleeAttack = false ENT.SoundTbl_MeleeAttack = false ENT.SoundTbl_MeleeAttackExtra = "Flesh.ImpactHard" -- EFFECT ENT.SoundTbl_MeleeAttackMiss = "Zombie.AttackMiss" -- EFFECT ENT.SoundTbl_GrenadeAttack = false ENT.SoundTbl_DangerSight = false ENT.SoundTbl_GrenadeSight = false -- If empty it will play "self.SoundTbl_DangerSight" ENT.SoundTbl_KilledEnemy = false ENT.SoundTbl_AllyDeath = false ENT.SoundTbl_Pain = false ENT.SoundTbl_Impact = "Flesh.BulletImpact" -- EFFECT ENT.SoundTbl_DamageByPlayer = false ENT.SoundTbl_Death = false -- ====== Sound Chance ====== -- -- Higher number = less chance of playing | 1 = Always play ENT.IdleSoundChance = 3 ENT.IdleDialogueAnswerSoundChance = 1 ENT.CombatIdleSoundChance = 1 ENT.ReceiveOrderSoundChance = 1 ENT.FollowPlayerSoundChance = 1 -- Controls "self.SoundTbl_FollowPlayer", "self.SoundTbl_UnFollowPlayer" ENT.YieldToPlayerSoundChance = 2 ENT.MedicBeforeHealSoundChance = 1 ENT.MedicOnHealSoundChance = 1 ENT.MedicReceiveHealSoundChance = 1 ENT.OnPlayerSightSoundChance = 1 ENT.InvestigateSoundChance = 1 ENT.LostEnemySoundChance = 1 ENT.AlertSoundChance = 1 ENT.CallForHelpSoundChance = 1 ENT.BecomeEnemyToPlayerChance = 1 ENT.BeforeMeleeAttackSoundChance = 1 ENT.MeleeAttackSoundChance = 1 ENT.ExtraMeleeSoundChance = 1 ENT.MeleeAttackMissSoundChance = 1 ENT.GrenadeAttackSoundChance = 1 ENT.DangerSightSoundChance = 1 -- Controls "self.SoundTbl_DangerSight", "self.SoundTbl_GrenadeSight" ENT.SuppressingSoundChance = 2 ENT.WeaponReloadSoundChance = 1 ENT.KilledEnemySoundChance = 1 ENT.AllyDeathSoundChance = 4 ENT.PainSoundChance = 1 ENT.ImpactSoundChance = 1 ENT.DamageByPlayerSoundChance = 1 ENT.DeathSoundChance = 1 ENT.SoundTrackChance = 1 -- ====== Timer ====== -- -- Randomized time between the two variables, x amount of time has to pass for the sound to play again | Counted in seconds -- false = Base will decide the time ENT.NextSoundTime_Breath = false ENT.NextSoundTime_Idle = VJ.SET(8, 25) ENT.NextSoundTime_Investigate = VJ.SET(5, 5) ENT.NextSoundTime_LostEnemy = VJ.SET(5, 6) ENT.NextSoundTime_Alert = VJ.SET(2, 3) ENT.NextSoundTime_Suppressing = VJ.SET(7, 15) ENT.NextSoundTime_KilledEnemy = VJ.SET(3, 5) ENT.NextSoundTime_AllyDeath = VJ.SET(3, 5) -- ====== Sound Level ====== -- -- The proper number are usually range from 0 to 180, though it can go as high as 511 -- More Information: https://developer.valvesoftware.com/wiki/Soundscripts#SoundLevel_Flags ENT.FootstepSoundLevel = 70 ENT.BreathSoundLevel = 60 ENT.IdleSoundLevel = 75 ENT.IdleDialogueSoundLevel = 75 -- Controls "self.SoundTbl_IdleDialogue", "self.SoundTbl_IdleDialogueAnswer" ENT.CombatIdleSoundLevel = 80 ENT.ReceiveOrderSoundLevel = 80 ENT.FollowPlayerSoundLevel = 75 -- Controls "self.SoundTbl_FollowPlayer", "self.SoundTbl_UnFollowPlayer" ENT.YieldToPlayerSoundLevel = 75 ENT.MedicBeforeHealSoundLevel = 75 ENT.MedicOnHealSoundLevel = 75 ENT.MedicReceiveHealSoundLevel = 75 ENT.OnPlayerSightSoundLevel = 75 ENT.InvestigateSoundLevel = 80 ENT.LostEnemySoundLevel = 75 ENT.AlertSoundLevel = 80 ENT.CallForHelpSoundLevel = 80 ENT.BecomeEnemyToPlayerSoundLevel = 75 ENT.BeforeMeleeAttackSoundLevel = 75 ENT.MeleeAttackSoundLevel = 75 ENT.ExtraMeleeAttackSoundLevel = 75 ENT.MeleeAttackMissSoundLevel = 75 ENT.SuppressingSoundLevel = 80 ENT.WeaponReloadSoundLevel = 80 ENT.GrenadeAttackSoundLevel = 80 ENT.DangerSightSoundLevel = 80 -- Controls "self.SoundTbl_DangerSight", "self.SoundTbl_GrenadeSight" ENT.KilledEnemySoundLevel = 80 ENT.AllyDeathSoundLevel = 80 ENT.PainSoundLevel = 80 ENT.ImpactSoundLevel = 60 ENT.DamageByPlayerSoundLevel = 75 ENT.DeathSoundLevel = 80 -- ====== Sound Pitch ====== -- -- Range: 0 - 255 | Lower pitch < x > Higher pitch ENT.MainSoundPitch = VJ.SET(90, 100) -- Can be a number or VJ.SET ENT.MainSoundPitchStatic = true -- Should it decide a number on spawn and use it as the main pitch? -- false = Use main pitch | number = Use a specific pitch | VJ.SET = Pick randomly between numbers every time it plays ENT.FootstepSoundPitch = VJ.SET(80, 100) ENT.BreathSoundPitch = 100 ENT.IdleSoundPitch = false ENT.IdleDialogueSoundPitch = false -- Controls "self.SoundTbl_IdleDialogue", "self.SoundTbl_IdleDialogueAnswer" ENT.CombatIdleSoundPitch = false ENT.ReceiveOrderSoundPitch = false ENT.FollowPlayerPitch = false -- Controls "self.SoundTbl_FollowPlayer", "self.SoundTbl_UnFollowPlayer" ENT.YieldToPlayerSoundPitch = false ENT.MedicBeforeHealSoundPitch = false ENT.MedicOnHealSoundPitch = 100 ENT.MedicReceiveHealSoundPitch = false ENT.OnPlayerSightSoundPitch = false ENT.InvestigateSoundPitch = false ENT.LostEnemySoundPitch = false ENT.AlertSoundPitch = false ENT.CallForHelpSoundPitch = false ENT.BecomeEnemyToPlayerPitch = false ENT.BeforeMeleeAttackSoundPitch = false ENT.MeleeAttackSoundPitch = VJ.SET(95, 100) ENT.ExtraMeleeSoundPitch = VJ.SET(80, 100) ENT.MeleeAttackMissSoundPitch = VJ.SET(90, 100) ENT.SuppressingPitch = false ENT.WeaponReloadSoundPitch = false ENT.GrenadeAttackSoundPitch = false ENT.DangerSightSoundPitch = false -- Controls "self.SoundTbl_DangerSight", "self.SoundTbl_GrenadeSight" ENT.KilledEnemySoundPitch = false ENT.AllyDeathSoundPitch = false ENT.PainSoundPitch = false ENT.ImpactSoundPitch = VJ.SET(80, 100) ENT.DamageByPlayerPitch = false ENT.DeathSoundPitch = false ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Customization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- Use the functions below to customize parts of the NPC or add new systems without overriding parts of the base -- Some base functions don't have a hook because you can simply override them | Call "self.BaseClass.FuncName(self)" or "baseclass.Get(baseName)" to run the base code as well local PICK = VJ.PICK -- --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:PreInit() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Init() -- Collision bounds of the NPC | NOTE: Both Xs and Ys should be the same! | To view: "cl_ent_bbox" -- self:SetCollisionBounds(Vector(50, 50, 100), Vector(-50, -50, 0)) -- Damage bounds of the NPC | NOTE: Both Xs and Ys should be the same! | To view: "cl_ent_absbox" -- self:SetSurroundingBounds(Vector(150, 150, 200), Vector(-150, -150, 0)) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnThink() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnThinkActive() end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called at the end of every entity it checks every process time -- NOTE: "calculatedDisp" can in some cases be nil -- function ENT:OnMaintainRelationships(ent, calculatedDisp, entDist) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE -- function ENT:OnUpdatePoseParamTracking(pitch, yaw, roll) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called from the engine -- function ENT:ExpressionFinished(strExp) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called whenever "VJ.CreateSound" or "VJ.EmitSound" is called | return a new file path to replace the one that is about to play -- function ENT:OnPlaySound(sdFile) return "example/sound.wav" end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called whenever a sound starts playing through "VJ.CreateSound" -- function ENT:OnCreateSound(sdData, sdFile) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called whenever a sound starts playing through "VJ.EmitSound" -- function ENT:OnEmitSound(sdFile) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called every time "self:FireBullets" is called -- function ENT:OnFireBullet(data) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called whenever something collides with the NPC -- function ENT:OnTouch(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE | Called from the engine -- function ENT:OnCondition(cond) VJ.DEBUG_Print(self, "OnCondition", cond, " = ", self:ConditionName(cond)) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE -- function ENT:OnInput(key, activator, caller, data) VJ.DEBUG_Print(self, "OnInput", key, activator, caller, data) end --------------------------------------------------------------------------------------------------------------------------------------------- -- UNCOMMENT TO USE -- local getEventName = util.GetAnimEventNameByID -- -- -- function ENT:OnAnimEvent(ev, evTime, evCycle, evType, evOptions) -- local eventName = getEventName(ev) -- VJ.DEBUG_Print(self, "OnAnimEvent", eventName, ev, evTime, evCycle, evType, evOptions) -- end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Called whenever the NPC begins following or stops following an entity - status = Type of call: - "Start" = NPC is now following the given entity - "Stop" = NPC is now unfollowing the given entity - ent = The entity that the NPC is now following or unfollowing -----------------------------------------------------------]] function ENT:OnFollow(status, ent) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Called every time a change occurs in the eating system - ent = The entity that it is checking OR speaking with - status = Type of update that is occurring, holds one of the following states: - "CheckEnt" = Possible friendly entity found, should we speak to it? | return anything other than true to skip and not speak to this entity! - "Speak" = Everything passed, start speaking - "Answer" = Another entity has spoken to me, answer back! | return anything other than true to not play an answer back dialogue! - statusData = Some status may have extra info, possible infos: - For "CheckEnt" = Boolean value, whether or not the entity can answer back - For "Speak" = Duration of our sentence Returns - ONLY used for "CheckEnt" & "Answer" | Check above for what each status return does -----------------------------------------------------------]] function ENT:OnIdleDialogue(ent, status, statusData) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called whenever the medic behavior updates =-=-=| PARAMETERS |=-=-= 1. status [string] : Type of update that is occurring, holds one of the following states: -> "BeforeHeal" : Right before it's about to heal an entity USAGE EXAMPLES -> Play chain of animations | Additional sound effect PARAMETERS 2. statusData [nil] RETURNS -> [nil] -> "OnHeal" : When the timer expires and is about to give health USAGE EXAMPLES -> Override healing code | Play an after heal animation PARAMETERS 2. statusData [entity] : The entity that it's about to heal RETURNS -> [bool] : Returning false will NOT update entity's health and will NOT clear its decals (Useful for custom code) -> "OnReset" : When the behavior ends OR has to move because entity moved USAGE EXAMPLES -> Cleanup bodygroups | Play a sound PARAMETERS 2. statusData [string] : Holds one of the following states: --> "Retry" : When it attempts to retry healing the entity, such as when the entity moved away so it has to chase again --> "End" : When the medic behavior exits completely RETURNS -> [nil] 2. statusData [nil | entity | string] : Depends on `status` value, refer to it for more details =-=-=| RETURNS |=-=-= -> [nil | bool] : Depends on `status` value, refer to it for more details --]] function ENT:OnMedicBehavior(status, statusData) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnPlayerSight(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- UNCOMMENT TO USE | Called every time footstep sound plays - moveType = Type of movement | Types: "Walk", "Run", "Event" - sdFile = Sound that it just played -----------------------------------------------------------]] -- function ENT:OnFootstepSound(moveType, sdFile) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- UNCOMMENT TO USE | Called when the NPC detects a danger - dangerType = Type of danger detected | Enum: VJ.NPC_DANGER_TYPE_* - data = Danger / grenade entity for types "DANGER_TYPE_ENTITY" and "DANGER_TYPE_GRENADE" -- Currently empty for danger type "DANGER_TYPE_HINT" -----------------------------------------------------------]] -- function ENT:OnDangerDetected(dangerType, data) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnInvestigate(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnResetEnemy() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnAlert(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- -- "ally" = Ally that we called for help -- "isFirst" = Is this the first ally that received this call? Use this to avoid running certain multiple times when many allies are around! function ENT:OnCallForHelp(ally, isFirst) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- UNCOMMENT TO USE | Called constantly on think as long as it can attack and has an enemy This can be used to create a completely new attack system OR switch between multiple attacks (such as multiple melee attacks with varying distances) 1. isAttacking [boolean] : Whether or not the base has detected that performing an attacking 2. enemy [entity] : Current active enemy -----------------------------------------------------------]] function ENT:OnThinkAttack(isAttacking, enemy) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called when melee attack is triggered =-=-=| PARAMETERS |=-=-= 1. status [string] : Type of update that is occurring, holds one of the following states: -> "PreInit" : Before the attack is initialized | Before anything is set, useful to prevent the attack completely RETURNS -> [nil | boolean] : Return true to prevent the attack from being triggered -> "Init" : When the attack initially starts | Before sound, timers, and animations are set! RETURNS -> [nil] -> "PostInit" : After the sound, timers, and animations are set! RETURNS -> [nil] 2. enemy [entity] : Enemy that caused the attack to trigger =-=-=| RETURNS |=-=-= -> [nil | boolean] : Depends on `status` value, refer to it for more details --]] function ENT:OnMeleeAttack(status, enemy) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MeleeAttackTraceOrigin() return (IsValid(self:GetEnemy()) and VJ.GetNearestPositions(self, self:GetEnemy(), true)) or self:GetPos() + self:GetForward() end --------------------------------------------------------------------------------------------------------------------------------------------- -- "self.MeleeAttackDamageAngleRadius" uses this to determine the direction of the attack and if something is within the angle radius function ENT:MeleeAttackTraceDirection() return self:GetHeadDirection() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MeleeAttackKnockbackVelocity(ent) return self:GetForward() * math.random(100, 140) + self:GetUp() * 10 end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called when melee attack is executed =-=-=| PARAMETERS |=-=-= 1. status [string] : Type of update that is occurring, holds one of the following states: -> "Init" : When the attack initially executed | Before entities are checked and damaged RETURNS -> [nil | boolean] : Return true to skip running the default execution (Useful for custom code) -> "PreDamage" : Right before the damage is applied to an entity PARAMETERS 2. ent [entity] : The entity that is about to be damaged 3. isProp [entity] : Is the entity detected as a prop? RETURNS -> [nil | boolean] : Return true to skip hitting this entity -> "Miss" : When the attack misses and doesn't hit anything RETURNS -> [nil] 2. ent [nil | entity] : Depends on `status` value, refer to it for more details 3. isProp [nil | entity] : Depends on `status` value, refer to it for more details =-=-=| RETURNS |=-=-= -> [nil | boolean] : Depends on `status` value, refer to it for more details --]] function ENT:OnMeleeAttackExecute(status, ent, isProp) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnWeaponChange(newWeapon, oldWeapon, invSwitch) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnWeaponCanFire() end -- Return false to disallow firing the weapon --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnWeaponAttack() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnWeaponStrafe() end -- Return false to disable default behavior, cooldown will still apply! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnWeaponReload() end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called when grenade attack is triggered or grenade position is requested =-=-=| PARAMETERS |=-=-= 1. status [string] : Type of update that is occurring, holds one of the following states: -> "Init" : When the attack initially starts | Before sound, timers, and animations are set! USAGE EXAMPLES -> Change grenade attack sounds or make changes to "self.GrenadeAttackThrowTime" RETURNS -> [nil | boolean] : Return true to disallow throwing a grenade -> "PostInit" : After the sound, timers, and animations are set! RETURNS -> [nil] -> "SpawnPos" : When the spawn position is requested USAGE EXAMPLES -> Override the spawn position if needed by returning a vector RETURNS -> [nil] : Do NOT override the spawn position -> [vector] : Override the spawn position 2. overrideEnt [nil | string | entity] : string or entity if the grenade attack was triggered through an override -> [nil] : Using the default grenade class set by "self.GrenadeAttackEntity" | DEFAULT -> [string] : Using the given class name as an override -> [entity] : Using an existing entity as an override | EX: When the NPC is throwing back an enemy grenade 3. landDir [string | vector] : Direction the grenade should land, used to align where the grenade should land -> "Enemy" : Use enemy's position -> "EnemyLastVis" : Use enemy's last visible position -> "FindBest" : Find the best random position -> [vector] : Use given vector =-=-=| RETURNS |=-=-= -> [nil | vector | boolean] : Depends on `status` value, refer to it for more details --]] function ENT:OnGrenadeAttack(status, overrideEnt, landDir) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called when grenade attack is executed =-=-=| PARAMETERS |=-=-= 1. status [string] : Type of update that is occurring, holds one of the following states: -> "PreSpawn" : Right before "Spawn()" is called on the grenade | Not called for grenade entity overrides, such as throwing back an enemy grenade RETURNS -> [nil] -> "PostSpawn" : After "Spawn()" is called on the grenade | Can be used to override the throw velocity or not apply it at all RETURNS -> [nil] : Apply the default velocity -> [vector] : Override the velocity to the given vector -> [boolean] : Return true to not apply any velocity 2. grenade [nil | entity] : The grenade entity that is being thrown 3. overrideEnt [nil | string | entity] : string or entity if the grenade attack was triggered through an override -> [nil] : Using the default grenade class set by "self.GrenadeAttackEntity" | DEFAULT -> [string] : Using the given class name as an override -> [entity] : Using an existing entity as an override | EX: When the NPC is throwing back an enemy grenade 4. landDir [string | vector] : Direction the grenade should land, used to align where the grenade should land -> "Enemy" : Use enemy's position -> "EnemyLastVis" : Use enemy's last visible position -> "FindBest" : Find the best random position -> [vector] : Use given vector 5. landingPos [nil | vector] : The position the grenade is aimed to land =-=-=| RETURNS |=-=-= -> [nil | vector | boolean] : Depends on `status` value, refer to it for more details --]] function ENT:OnGrenadeAttackExecute(status, grenade, overrideEnt, landDir, landingPos) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnKilledEnemy(ent, inflictor, wasLast) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnAllyKilled(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called whenever the NPC takes damage =-=-=| PARAMETERS |=-=-= 1. dmginfo [object] = CTakeDamageInfo object 2. hitgroup [number] = The hitgroup that it hit 3. status [string] : Type of update that is occurring, holds one of the following states: -> "Init" : First call on take damage, even before immune checks -> "PreDamage" : Right before the damage is applied to the NPC -> "PostDamage" : Right after the damage is applied to the NPC --]] function ENT:OnDamaged(dmginfo, hitgroup, status) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnBleed(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called whenever the NPC attempts to play flinch =-=-=| PARAMETERS |=-=-= 1. dmginfo [object] = CTakeDamageInfo object 2. hitgroup [number] = The hitgroup that it hit 3. status [string] : Type of update that is occurring, holds one of the following states: -> "Init" : Before the animation is played or any values are set USAGE EXAMPLES -> Disallow flinch | Override the animation | Add a extra check RETURNS -> [nil | bool] : Return true to disallow the flinch from playing -> "Execute" : Right after the flinch animation starts playing and all the values are set RETURNS -> [nil] =-=-=| RETURNS |=-=-= -> [nil | bool] : Depends on `status` value, refer to it for more details --]] function ENT:OnFlinch(dmginfo, hitgroup, status) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnBecomeEnemyToPlayer(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnSetEnemyFromDamage(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called on death when the NPC is supposed to gib =-=-=| PARAMETERS |=-=-= 1. dmginfo [object] = CTakeDamageInfo object 2. hitgroup [number] = The hitgroup that it hit =-=-=| RETURNS |=-=-= -> [bool] : Notifies the base if the NPC gibbed or not - false : Spawns death corpse | Plays death animations | Does NOT play gib sounds - true : Disallows death corpse | Disallows death animations | Plays gib sounds -> [nil | table] : Overrides default actions, first return must be "true" for this to apply! - AllowCorpse : Allows death corpse to spawn | DEFAULT: false - AllowAnim : Allows death animations to play | DEFAULT: false - AllowSound : Allows default gib sounds to play | DEFAULT: true EXAMPLE: - {AllowCorpse = true} : Will spawn death corpse --]] function ENT:HandleGibOnDeath(dmginfo, hitgroup) return false end --------------------------------------------------------------------------------------------------------------------------------------------- --[[ Called when the NPC dies =-=-=| PARAMETERS |=-=-= 1. dmginfo [object] = CTakeDamageInfo object 2. hitgroup [number] = The hitgroup that it hit 3. status [string] : Type of update that is occurring, holds one of the following states: -> "Init" : First call when it dies before anything is changed or reset -> "DeathAnim" : Right before the death animation plays -> "Finish" : Right before the corpse is spawned, the active weapon is dropped and the NPC is removed --]] function ENT:OnDeath(dmginfo, hitgroup, status) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnDeathWeaponDrop(dmginfo, hitgroup, wepEnt) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnCreateDeathCorpse(dmginfo, hitgroup, corpse) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnRemove() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Controller_Initialize(ply, controlEnt) //ply:ChatPrint("CTRL + MOUSE2: Rocket Attack") -- Example key binding message end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:SetAnimationTranslations(wepHoldType) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Combine ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ if self.AnimModelSet == VJ.ANIM_SET_COMBINE then if !self.Weapon_AimTurnDiff then self.Weapon_AimTurnDiff_Def = 0.71120220422745 end self.AnimationTranslations[ACT_RANGE_ATTACK2] = VJ.SequenceToActivity(self, "shootAR2alt") self.AnimationTranslations[ACT_COVER_LOW] = {ACT_COVER, "vjseq_Leanwall_CrouchLeft_A_idle", "vjseq_Leanwall_CrouchLeft_B_idle", "vjseq_Leanwall_CrouchLeft_C_idle", "vjseq_Leanwall_CrouchLeft_D_idle"} //self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 -- No need to translate, it's already the correct animation //self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW -- No need to translate, it's already the correct animation self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = ACT_RUN_RIFLE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN_RIFLE self.AnimationTranslations[ACT_RUN_PROTECTED] = ACT_RUN_CROUCH_RIFLE self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE if wepHoldType == "ar2" or wepHoldType == "smg" or wepHoldType == "rpg" then if wepHoldType == "ar2" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_AR2_LOW //self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY -- No need to translate, it's already the correct animation elseif wepHoldType == "smg" or wepHoldType == "rpg" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SMG1 end self.AnimationTranslations[ACT_IDLE] = ACT_IDLE_SMG1 self.AnimationTranslations[ACT_WALK] = VJ.SequenceToActivity(self, "walkeasy_all") self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_RIFLE self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE elseif wepHoldType == "pistol" or wepHoldType == "revolver" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_AR2_LOW self.AnimationTranslations[ACT_IDLE] = VJ.SequenceToActivity(self, "idle_unarmed") //self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY -- No need to translate, it's already the correct animation self.AnimationTranslations[ACT_WALK] = VJ.SequenceToActivity(self, "walkunarmed_all") //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_RIFLE -- No need uses same as ACT_WALK self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE elseif wepHoldType == "crossbow" or wepHoldType == "shotgun" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = wepHoldType == "crossbow" and ACT_GESTURE_RANGE_ATTACK_AR2 or ACT_GESTURE_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SHOTGUN_LOW self.AnimationTranslations[ACT_IDLE] = ACT_IDLE_SMG1 self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SHOTGUN self.AnimationTranslations[ACT_WALK] = ACT_WALK_AIM_SHOTGUN //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_AIM_SHOTGUN -- No need uses same as ACT_WALK self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_SHOTGUN self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_SHOTGUN elseif wepHoldType == "melee" or wepHoldType == "melee2" or wepHoldType == "knife" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_MELEE_ATTACK1 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = false -- Don't play anything for melee! //self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW -- Not used for melee self.AnimationTranslations[ACT_IDLE] = VJ.SequenceToActivity(self, "idle_unarmed") self.AnimationTranslations[ACT_IDLE_ANGRY] = VJ.SequenceToActivity(self, "idle_unarmed") self.AnimationTranslations[ACT_WALK] = VJ.SequenceToActivity(self, "walkunarmed_all") //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_AIM_SHOTGUN -- No need uses same as ACT_WALK self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE else -- Unarmed! self.AnimationTranslations[ACT_IDLE] = VJ.SequenceToActivity(self, "idle_unarmed") self.AnimationTranslations[ACT_WALK] = VJ.SequenceToActivity(self, "walkunarmed_all") //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_AIM_SHOTGUN -- No need uses same as ACT_WALK end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Metrocop ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ elseif self.AnimModelSet == VJ.ANIM_SET_METROCOP then if !self.Weapon_AimTurnDiff then self.Weapon_AimTurnDiff_Def = 0.71120220422745 end //self.AnimationTranslations[ACT_RANGE_ATTACK2] = VJ.SequenceToActivity(self, "shootAR2alt") -- They don't have secondary animation! -- Do not translate crouch walking and also make the crouch running a walking one instead self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_WALK_CROUCH if wepHoldType == "smg" or wepHoldType == "rpg" or wepHoldType == "ar2" or wepHoldType == "crossbow" or wepHoldType == "shotgun" then -- Note: Metrocops must use smg animation, they don't have any animations for AR2! self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 self.AnimationTranslations[ACT_COVER_LOW] = ACT_COVER_SMG1_LOW self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW self.AnimationTranslations[ACT_IDLE] = ACT_IDLE_SMG1 self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SMG1 self.AnimationTranslations[ACT_WALK] = ACT_WALK_RIFLE //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_RIFLE -- No need uses same as ACT_WALK self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = ACT_RUN_RIFLE //self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN_RIFLE -- No need uses same as ACT_RUN self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "pistol" or wepHoldType == "revolver" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_PISTOL self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_PISTOL self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_PISTOL_LOW self.AnimationTranslations[ACT_COVER_LOW] = ACT_COVER_PISTOL_LOW self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_PISTOL self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_PISTOL_LOW self.AnimationTranslations[ACT_IDLE] = ACT_IDLE_PISTOL self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_PISTOL //self.AnimationTranslations[ACT_WALK] = ACT_WALK -- No need to translate self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_PISTOL self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_PISTOL //self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE -- No need to translate self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE //self.AnimationTranslations[ACT_RUN] = ACT_RUN -- No need to translate self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN_PISTOL self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_PISTOL //self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE -- No need to translate self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "melee" or wepHoldType == "melee2" or wepHoldType == "knife" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_MELEE_ATTACK_SWING self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = false //ACT_MELEE_ATTACK_SWING_GESTURE -- Don't play anything! //self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW -- Not used for melee self.AnimationTranslations[ACT_COVER_LOW] = ACT_COWER //self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 -- Not used for melee //self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW -- Not used for melee self.AnimationTranslations[ACT_IDLE] = {ACT_IDLE, ACT_IDLE, ACT_IDLE, ACT_IDLE, VJ.SequenceToActivity(self, "plazathreat1")} self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_MELEE //self.AnimationTranslations[ACT_WALK] = ACT_WALK -- No need to translate self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_ANGRY //self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE -- Not used for melee //self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE -- No need to translate //self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE -- Not used for melee //self.AnimationTranslations[ACT_RUN] = ACT_RUN -- No need to translate //self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN -- No need to translate //self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE -- Not used for melee //self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE -- No need to translate //self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE -- Not used for melee end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Rebel / Citizen ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ elseif self.AnimModelSet == VJ.ANIM_SET_REBEL then local isFemale = VJ.AnimExists(self, ACT_IDLE_ANGRY_PISTOL) if !self.Weapon_AimTurnDiff then self.Weapon_AimTurnDiff_Def = 0.78187280893326 end self.AnimationTranslations[ACT_RANGE_ATTACK2] = VJ.SequenceToActivity(self, "shoot_ar2_alt") -- Handguns use a different set! self.AnimationTranslations[ACT_COVER_LOW] = {ACT_COVER_LOW_RPG, ACT_COVER_LOW, "vjseq_coverlow_l", "vjseq_coverlow_r"} if wepHoldType == "ar2" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_AR2_LOW self.AnimationTranslations[ACT_RELOAD] = VJ.SequenceToActivity(self, "reload_ar2") self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW self.AnimationTranslations[ACT_IDLE] = PICK({VJ.SequenceToActivity(self, "idle_relaxed_ar2_1"), VJ.SequenceToActivity(self, "idle_alert_ar2_1"), VJ.SequenceToActivity(self, "idle_angry_ar2")}) self.AnimationTranslations[ACT_IDLE_ANGRY] = VJ.SequenceToActivity(self, "idle_ar2_aim") self.AnimationTranslations[ACT_WALK] = PICK({VJ.SequenceToActivity(self, "walk_ar2_relaxed_all"), VJ.SequenceToActivity(self, "walkalerthold_ar2_all1"), VJ.SequenceToActivity(self, "walkholdall1_ar2")}) self.AnimationTranslations[ACT_WALK_AGITATED] = VJ.SequenceToActivity(self, "walkalerthold_ar2_all1") self.AnimationTranslations[ACT_WALK_AIM] = PICK({VJ.SequenceToActivity(self, "walkaimall1_ar2"), VJ.SequenceToActivity(self, "walkalertaim_ar2_all1")}) self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RPG self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = PICK({VJ.SequenceToActivity(self, "run_alert_holding_ar2_all"), VJ.SequenceToActivity(self, "run_ar2_relaxed_all"), VJ.SequenceToActivity(self, "run_holding_ar2_all")}) self.AnimationTranslations[ACT_RUN_AGITATED] = PICK({VJ.SequenceToActivity(self, "run_alert_holding_ar2_all"), VJ.SequenceToActivity(self, "run_holding_ar2_all")}) self.AnimationTranslations[ACT_RUN_AIM] = PICK({ACT_RUN_AIM_RIFLE, VJ.SequenceToActivity(self, "run_alert_aiming_ar2_all")}) self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RPG self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "smg" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW self.AnimationTranslations[ACT_IDLE] = PICK({ACT_IDLE_SMG1_RELAXED, ACT_IDLE_SMG1_STIMULATED, ACT_IDLE_SMG1, VJ.SequenceToActivity(self, "idle_smg1_relaxed")}) self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SMG1 self.AnimationTranslations[ACT_WALK] = PICK({ACT_WALK_RIFLE_RELAXED, ACT_WALK_RIFLE_STIMULATED}) self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_RIFLE self.AnimationTranslations[ACT_WALK_AIM] = PICK({ACT_WALK_AIM_RIFLE, ACT_WALK_AIM_RIFLE_STIMULATED}) self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = PICK({ACT_RUN_RIFLE, ACT_RUN_RIFLE_STIMULATED, ACT_RUN_RIFLE_RELAXED}) self.AnimationTranslations[ACT_RUN_AGITATED] = PICK({ACT_RUN_RIFLE, ACT_RUN_RIFLE_STIMULATED}) self.AnimationTranslations[ACT_RUN_AIM] = PICK({ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE_STIMULATED}) self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "crossbow" or wepHoldType == "shotgun" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SHOTGUN self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW //ACT_RELOAD_SHOTGUN_LOW self.AnimationTranslations[ACT_IDLE] = PICK({ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_SHOTGUN_STIMULATED}) self.AnimationTranslations[ACT_IDLE_ANGRY] = VJ.SequenceToActivity(self, "idle_ar2_aim") self.AnimationTranslations[ACT_WALK] = PICK({VJ.SequenceToActivity(self, "walk_ar2_relaxed_all"), VJ.SequenceToActivity(self, "walkalerthold_ar2_all1"), VJ.SequenceToActivity(self, "walkholdall1_ar2")}) self.AnimationTranslations[ACT_WALK_AGITATED] = VJ.SequenceToActivity(self, "walkalerthold_ar2_all1") self.AnimationTranslations[ACT_WALK_AIM] = PICK({VJ.SequenceToActivity(self, "walkaimall1_ar2"), VJ.SequenceToActivity(self, "walkalertaim_ar2_all1")}) self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RPG self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = PICK({VJ.SequenceToActivity(self, "run_alert_holding_ar2_all"), VJ.SequenceToActivity(self, "run_ar2_relaxed_all"), VJ.SequenceToActivity(self, "run_holding_ar2_all")}) self.AnimationTranslations[ACT_RUN_AGITATED] = PICK({VJ.SequenceToActivity(self, "run_alert_holding_ar2_all"), VJ.SequenceToActivity(self, "run_holding_ar2_all")}) self.AnimationTranslations[ACT_RUN_AIM] = PICK({ACT_RUN_AIM_RIFLE, VJ.SequenceToActivity(self, "run_alert_aiming_ar2_all")}) self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RPG self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "rpg" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_RPG self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_RPG self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW self.AnimationTranslations[ACT_IDLE] = PICK({ACT_IDLE_RPG, ACT_IDLE_RPG_RELAXED}) self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_RPG self.AnimationTranslations[ACT_WALK] = PICK({ACT_WALK_RPG, ACT_WALK_RPG_RELAXED}) self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK_RPG self.AnimationTranslations[ACT_WALK_AIM] = PICK({VJ.SequenceToActivity(self, "walkaimall1_ar2"), VJ.SequenceToActivity(self, "walkalertaim_ar2_all1")}) self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RPG self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE self.AnimationTranslations[ACT_RUN] = PICK({ACT_RUN_RPG, ACT_RUN_RPG_RELAXED}) self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN_RPG self.AnimationTranslations[ACT_RUN_AIM] = PICK({ACT_RUN_AIM_RIFLE, VJ.SequenceToActivity(self, "run_alert_aiming_ar2_all")}) self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RPG self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "pistol" or wepHoldType == "revolver" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_PISTOL self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_PISTOL self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_PISTOL_LOW self.AnimationTranslations[ACT_COVER_LOW] = {"crouchidle_panicked4", "vjseq_crouchidlehide"} self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_PISTOL self.AnimationTranslations[ACT_RELOAD_LOW] = isFemale and ACT_RELOAD_SMG1_LOW or ACT_RELOAD_PISTOL_LOW -- Only males have covered pistol reload self.AnimationTranslations[ACT_IDLE] = isFemale and ACT_IDLE_PISTOL or ACT_IDLE -- Only females have pistol idle animation self.AnimationTranslations[ACT_IDLE_ANGRY] = isFemale and ACT_IDLE_ANGRY_PISTOL or VJ.SequenceToActivity(self, "idle_ar2_aim") -- Only females have angry pistol animation //self.AnimationTranslations[ACT_WALK] = ACT_WALK -- No need to translate //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK -- No need, same as ACT_WALK self.AnimationTranslations[ACT_WALK_AIM] = isFemale and ACT_WALK_AIM_PISTOL or PICK({VJ.SequenceToActivity(self, "walkaimall1_ar2"), VJ.SequenceToActivity(self, "walkalertaim_ar2_all1")}) //self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE -- No need to translate self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE //self.AnimationTranslations[ACT_RUN] = ACT_RUN -- No need to translate //self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_RUN -- No need, same as ACT_RUN self.AnimationTranslations[ACT_RUN_AIM] = isFemale and ACT_RUN_AIM_PISTOL or VJ.SequenceToActivity(self, "run_alert_aiming_ar2_all") //self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE -- No need to translate self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE elseif wepHoldType == "melee" or wepHoldType == "melee2" or wepHoldType == "knife" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_MELEE_ATTACK_SWING self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = false -- Don't play anything! //self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW -- Not used for melee self.AnimationTranslations[ACT_COVER_LOW] = {"crouchidle_panicked4", "vjseq_crouchidlehide"} //self.AnimationTranslations[ACT_RELOAD] = ACT_RELOAD_SMG1 -- Not used for melee //self.AnimationTranslations[ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW -- Not used for melee self.AnimationTranslations[ACT_IDLE] = ACT_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = isFemale and ACT_IDLE_ANGRY or ACT_IDLE_ANGRY_MELEE -- Only males have unique idle angry for melee weapons! //self.AnimationTranslations[ACT_WALK] = ACT_WALK -- No need to translate //self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_WALK -- No need, same as ACT_WALK //self.AnimationTranslations[ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE -- Not used for melee //self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE -- No need to translate //self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE -- Not used for melee self.AnimationTranslations[ACT_RUN] = ACT_RUN if !isFemale then -- Females don't have this sequence self.AnimationTranslations[ACT_RUN_AGITATED] = VJ.SequenceToActivity(self, "run_all_panicked") end //self.AnimationTranslations[ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE -- Not used for melee //self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE -- No need to translate //self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE -- Not used for melee end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Player ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ elseif self.AnimModelSet == VJ.ANIM_SET_PLAYER then if !self.Weapon_AimTurnDiff then self.Weapon_AimTurnDiff_Def = 0.61155587434769 end self.AnimationTranslations[ACT_COWER] = ACT_HL2MP_IDLE_COWER self.AnimationTranslations[ACT_RUN_PROTECTED] = ACT_HL2MP_RUN_PROTECTED if wepHoldType == "ar2" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_AR2 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_AR2 self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_ar2" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_ar2" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_AR2 self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_AR2 self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_AR2 self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_AR2 self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_AR2 self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_AR2 self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_AR2 self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_AR2 self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_AR2 self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_AR2 self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_AR2 elseif wepHoldType == "pistol" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_PISTOL self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_PISTOL self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_PISTOL self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_PISTOL self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_PISTOL self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_PISTOL self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_PISTOL self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_PISTOL self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_PISTOL self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_PISTOL self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_FAST self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_PISTOL self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_PISTOL self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_PISTOL elseif wepHoldType == "smg" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_SMG1 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_SMG1 self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_smg1" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_smg1" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_SMG1 self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_SMG1 self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_SMG1 self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_SMG1 self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_SMG1 self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_SMG1 self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_SMG1 self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SMG1 self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_SMG1 self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_SMG1 self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SMG1 elseif wepHoldType == "shotgun" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_SHOTGUN self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_SHOTGUN self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_shotgun" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_shotgun" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_SHOTGUN self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_SHOTGUN self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_SHOTGUN self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_SHOTGUN self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_SHOTGUN self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_SHOTGUN self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_SHOTGUN self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SHOTGUN self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_SHOTGUN self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_SHOTGUN self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SHOTGUN elseif wepHoldType == "rpg" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_RPG self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_RPG self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_ar2" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_ar2" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_RPG self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_RPG self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_RPG self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_RPG self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_RPG self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_RPG self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_RPG self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_RPG self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_RPG self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_RPG self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_RPG elseif wepHoldType == "physgun" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_PHYSGUN self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_PHYSGUN self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_ar2" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_ar2" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_PHYSGUN self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_PHYSGUN self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_PHYSGUN self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_PHYSGUN self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_PHYSGUN self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_PHYSGUN self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_PHYSGUN self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_PHYSGUN self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_PHYSGUN self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_PHYSGUN self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_PHYSGUN elseif wepHoldType == "crossbow" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_CROSSBOW self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_CROSSBOW self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_ar2" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_ar2" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_CROSSBOW self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE_PASSIVE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_CROSSBOW self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_CROSSBOW self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_CROSSBOW self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_CROSSBOW self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK_PASSIVE self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_CROSSBOW self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_CROSSBOW self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_CROSSBOW self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_PASSIVE self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_CROSSBOW self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_CROSSBOW self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH_PASSIVE self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_CROSSBOW elseif wepHoldType == "slam" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_SLAM self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_SLAM //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_SLAM self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_SLAM self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_SLAM self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_SLAM self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_SLAM self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_SLAM self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_SLAM self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SLAM self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_SLAM self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_SLAM self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_SLAM elseif wepHoldType == "duel" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_DUEL self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_DUEL self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_DUEL self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_duel" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_duel" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_DUEL self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_DUEL self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_DUEL self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_DUEL self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_DUEL self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_DUEL self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_DUEL self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_DUEL self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_DUEL self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_DUEL self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_DUEL elseif wepHoldType == "revolver" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_REVOLVER self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_REVOLVER self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_revolver" self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_revolver" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_REVOLVER self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_REVOLVER self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_REVOLVER self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_REVOLVER self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_REVOLVER self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_REVOLVER self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_REVOLVER self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_REVOLVER self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_REVOLVER self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_REVOLVER self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_REVOLVER elseif wepHoldType == "melee" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_MELEE self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_MELEE //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_MELEE self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_MELEE self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_MELEE self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_MELEE self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_MELEE self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_MELEE self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_MELEE self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_MELEE self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_MELEE self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_MELEE self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_MELEE elseif wepHoldType == "melee2" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_MELEE2 self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2 self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_MELEE2 //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_MELEE2 self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_MELEE2 self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_MELEE2 self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_MELEE2 self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_MELEE2 self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_MELEE2 self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_MELEE2 self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_MELEE2 self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_MELEE2 self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_MELEE2 self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_MELEE2 elseif wepHoldType == "knife" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_KNIFE self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_KNIFE //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_KNIFE self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_KNIFE self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_KNIFE self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_KNIFE self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_KNIFE self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_KNIFE self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_KNIFE self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_KNIFE self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_KNIFE self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_KNIFE self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_KNIFE elseif wepHoldType == "grenade" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_GRENADE self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_GRENADE //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_GRENADE self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_GRENADE self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_GRENADE self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_GRENADE self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_GRENADE self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_GRENADE self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_GRENADE self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_GRENADE self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_GRENADE self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_GRENADE self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_GRENADE elseif wepHoldType == "camera" then self.AnimationTranslations[ACT_RANGE_ATTACK1] = ACT_HL2MP_IDLE_CAMERA //self.AnimationTranslations[ACT_GESTURE_RANGE_ATTACK1] = ACT_HL2MP_GESTURE_RANGE_ATTACK_CAMERA self.AnimationTranslations[ACT_RANGE_ATTACK1_LOW] = ACT_HL2MP_IDLE_CROUCH_CAMERA //self.AnimationTranslations[ACT_RELOAD] = "vjges_reload_pistol" //self.AnimationTranslations[ACT_RELOAD_LOW] = "vjges_reload_pistol" self.AnimationTranslations[ACT_COVER_LOW] = ACT_HL2MP_IDLE_CROUCH_CAMERA self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_CAMERA self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_CAMERA self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_CAMERA self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_CAMERA self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_AGITATED] = ACT_HL2MP_WALK_CAMERA self.AnimationTranslations[ACT_WALK_AIM] = ACT_HL2MP_WALK_CAMERA self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_WALK_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_CAMERA self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN self.AnimationTranslations[ACT_RUN_AGITATED] = ACT_HL2MP_RUN_CAMERA self.AnimationTranslations[ACT_RUN_AIM] = ACT_HL2MP_RUN_CAMERA self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN_CROUCH_AIM] = ACT_HL2MP_WALK_CROUCH_CAMERA else -- Unarmed! self.AnimationTranslations[ACT_IDLE] = ACT_HL2MP_IDLE self.AnimationTranslations[ACT_IDLE_ANGRY] = ACT_HL2MP_IDLE_ANGRY self.AnimationTranslations[ACT_JUMP] = ACT_HL2MP_JUMP_PISTOL self.AnimationTranslations[ACT_GLIDE] = ACT_HL2MP_JUMP_PISTOL self.AnimationTranslations[ACT_LAND] = ACT_HL2MP_IDLE_PISTOL self.AnimationTranslations[ACT_WALK] = ACT_HL2MP_WALK self.AnimationTranslations[ACT_WALK_CROUCH] = ACT_HL2MP_WALK_CROUCH self.AnimationTranslations[ACT_RUN] = ACT_HL2MP_RUN_FAST self.AnimationTranslations[ACT_RUN_CROUCH] = ACT_HL2MP_WALK_CROUCH end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ ///// WARNING: Don't touch anything below this line! \\\\\ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ local defPos = Vector(0, 0, 0) local StopSD = VJ.STOPSOUND local CurTime = CurTime local IsValid = IsValid local GetConVar = GetConVar local math_min = math.min local math_max = math.max local math_rad = math.rad local math_cos = math.cos local math_angApproach = math.ApproachAngle local VJ_STATE_FREEZE = VJ_STATE_FREEZE local VJ_STATE_ONLY_ANIMATION = VJ_STATE_ONLY_ANIMATION local VJ_STATE_ONLY_ANIMATION_CONSTANT = VJ_STATE_ONLY_ANIMATION_CONSTANT local VJ_STATE_ONLY_ANIMATION_NOATTACK = VJ_STATE_ONLY_ANIMATION_NOATTACK local VJ_BEHAVIOR_PASSIVE = VJ_BEHAVIOR_PASSIVE local VJ_BEHAVIOR_PASSIVE_NATURE = VJ_BEHAVIOR_PASSIVE_NATURE local VJ_MOVETYPE_GROUND = VJ_MOVETYPE_GROUND local VJ_MOVETYPE_AERIAL = VJ_MOVETYPE_AERIAL local VJ_MOVETYPE_AQUATIC = VJ_MOVETYPE_AQUATIC local VJ_MOVETYPE_STATIONARY = VJ_MOVETYPE_STATIONARY local VJ_MOVETYPE_PHYSICS = VJ_MOVETYPE_PHYSICS local ANIM_TYPE_GESTURE = VJ.ANIM_TYPE_GESTURE local metaEntity = FindMetaTable("Entity") local funcGetPoseParameter = metaEntity.GetPoseParameter local funcSetPoseParameter = metaEntity.SetPoseParameter -- local metaNPC = FindMetaTable("NPC") local funcHasCondition = metaNPC.HasCondition ENT.UpdatedPoseParam = false ENT.Weapon_UnarmedBehavior_Active = false ENT.WeaponEntity = NULL ENT.WeaponState = VJ.WEP_STATE_READY ENT.WeaponInventoryStatus = VJ.WEP_INVENTORY_NONE ENT.AllowWeaponOcclusionDelay = true ENT.WeaponLastShotTime = 0 ENT.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE ENT.WeaponAttackAnim = ACT_INVALID ENT.Weapon_AimTurnDiff_Def = 1 -- Default value to use when "self.Weapon_AimTurnDiff" false, this is auto calculated depending on anim set and weapon hold type ENT.NextWeaponAttackT = 0 ENT.NextWeaponAttackT_Base = 0 -- Handled by the base, used to avoid running shoot animation twice ENT.NextWeaponStrafeT = 0 ENT.NextMeleeWeaponAttackT = 0 ENT.NextMoveOnGunCoveredT = 0 ENT.NextThrowGrenadeT = 0 ENT.NextGrenadeAttackSoundT = 0 ENT.NextSuppressingSoundT = 0 ENT.NextDangerDetectionT = 0 ENT.NextDangerSightSoundT = 0 ENT.NextCombatDamageResponseT = 0 local vj_npc_debug = GetConVar("vj_npc_debug") local vj_npc_processtime = GetConVar("vj_npc_processtime") local vj_npc_poseparams = GetConVar("vj_npc_poseparams") local vj_npc_shadows = GetConVar("vj_npc_shadows") local vj_npc_snd = GetConVar("vj_npc_snd") local vj_npc_fri_base = GetConVar("vj_npc_fri_base") local vj_npc_fri_player = GetConVar("vj_npc_fri_player") local vj_npc_fri_antlion = GetConVar("vj_npc_fri_antlion") local vj_npc_fri_combine = GetConVar("vj_npc_fri_combine") local vj_npc_fri_zombie = GetConVar("vj_npc_fri_zombie") local vj_npc_allies = GetConVar("vj_npc_allies") local vj_npc_anim_death = GetConVar("vj_npc_anim_death") local vj_npc_corpse = GetConVar("vj_npc_corpse") local vj_npc_loot = GetConVar("vj_npc_loot") local vj_npc_wander = GetConVar("vj_npc_wander") local vj_npc_chase = GetConVar("vj_npc_chase") local vj_npc_flinch = GetConVar("vj_npc_flinch") local vj_npc_melee = GetConVar("vj_npc_melee") local vj_npc_blood = GetConVar("vj_npc_blood") local vj_npc_god = GetConVar("vj_npc_god") local vj_npc_wep_reload = GetConVar("vj_npc_wep_reload") local vj_npc_ply_betray = GetConVar("vj_npc_ply_betray") local vj_npc_callhelp = GetConVar("vj_npc_callhelp") local vj_npc_investigate = GetConVar("vj_npc_investigate") local vj_npc_eat = GetConVar("vj_npc_eat") local vj_npc_ply_follow = GetConVar("vj_npc_ply_follow") local vj_npc_ply_chat = GetConVar("vj_npc_ply_chat") local vj_npc_medic = GetConVar("vj_npc_medic") local vj_npc_wep = GetConVar("vj_npc_wep") local vj_npc_grenade = GetConVar("vj_npc_grenade") local vj_npc_dangerdetection = GetConVar("vj_npc_dangerdetection") local vj_npc_wep_drop = GetConVar("vj_npc_wep_drop") local vj_npc_gib_vfx = GetConVar("vj_npc_gib_vfx") local vj_npc_gib = GetConVar("vj_npc_gib") local vj_npc_blood_gmod = GetConVar("vj_npc_blood_gmod") local vj_npc_sight_xray = GetConVar("vj_npc_sight_xray") local vj_npc_snd_gib = GetConVar("vj_npc_snd_gib") local vj_npc_snd_track = GetConVar("vj_npc_snd_track") local vj_npc_snd_footstep = GetConVar("vj_npc_snd_footstep") local vj_npc_snd_idle = GetConVar("vj_npc_snd_idle") local vj_npc_snd_breath = GetConVar("vj_npc_snd_breath") local vj_npc_snd_alert = GetConVar("vj_npc_snd_alert") local vj_npc_snd_danger = GetConVar("vj_npc_snd_danger") local vj_npc_snd_melee = GetConVar("vj_npc_snd_melee") local vj_npc_snd_pain = GetConVar("vj_npc_snd_pain") local vj_npc_snd_death = GetConVar("vj_npc_snd_death") local vj_npc_snd_plyfollow = GetConVar("vj_npc_snd_plyfollow") local vj_npc_snd_plybetrayal = GetConVar("vj_npc_snd_plybetrayal") local vj_npc_snd_plydamage = GetConVar("vj_npc_snd_plydamage") local vj_npc_snd_plysight = GetConVar("vj_npc_snd_plysight") local vj_npc_snd_medic = GetConVar("vj_npc_snd_medic") local vj_npc_snd_wep_reload = GetConVar("vj_npc_snd_wep_reload") local vj_npc_snd_grenade = GetConVar("vj_npc_snd_grenade") local vj_npc_snd_wep_suppressing = GetConVar("vj_npc_snd_wep_suppressing") local vj_npc_snd_callhelp = GetConVar("vj_npc_snd_callhelp") local vj_npc_snd_receiveorder = GetConVar("vj_npc_snd_receiveorder") local vj_npc_corpse_collision = GetConVar("vj_npc_corpse_collision") local vj_npc_debug_engine = GetConVar("vj_npc_debug_engine") local vj_npc_difficulty = GetConVar("vj_npc_difficulty") local vj_npc_sight_distance = GetConVar("vj_npc_sight_distance") local vj_npc_health = GetConVar("vj_npc_health") local vj_npc_human_jump = GetConVar("vj_npc_human_jump") local vj_npc_ply_frag = GetConVar("vj_npc_ply_frag") local vj_npc_blood_pool = GetConVar("vj_npc_blood_pool") local vj_npc_corpse_undo = GetConVar("vj_npc_corpse_undo") local vj_npc_corpse_fade = GetConVar("vj_npc_corpse_fade") local vj_npc_corpse_fadetime = GetConVar("vj_npc_corpse_fadetime") local ai_serverragdolls = GetConVar("ai_serverragdolls") --------------------------------------------------------------------------------------------------------------------------------------------- local function InitConvars(self) if vj_npc_debug:GetInt() == 1 then self.VJ_DEBUG = true end if vj_npc_poseparams:GetInt() == 0 && !self.OnUpdatePoseParamTracking then self.HasPoseParameterLooking = false end if vj_npc_shadows:GetInt() == 0 then self:DrawShadow(false) end if vj_npc_snd:GetInt() == 0 then self.HasSounds = false end if vj_npc_fri_base:GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_VJ_BASE" end if vj_npc_fri_player:GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_PLAYER_ALLY" end if vj_npc_fri_antlion:GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_ANTLION" end if vj_npc_fri_combine:GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_COMBINE" end if vj_npc_fri_zombie:GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_ZOMBIE" end if vj_npc_allies:GetInt() == 0 then self.CanAlly = false end if vj_npc_anim_death:GetInt() == 0 then self.HasDeathAnimation = false end if vj_npc_corpse:GetInt() == 0 then self.HasDeathCorpse = false end if vj_npc_loot:GetInt() == 0 then self.DropDeathLoot = false end if vj_npc_wander:GetInt() == 0 then self.DisableWandering = true end if vj_npc_chase:GetInt() == 0 then self.DisableChasingEnemy = true end if vj_npc_flinch:GetInt() == 0 then self.CanFlinch = false end if vj_npc_melee:GetInt() == 0 then self.HasMeleeAttack = false end if vj_npc_blood:GetInt() == 0 then self.Bleeds = false end if vj_npc_god:GetInt() == 1 then self.GodMode = true end if vj_npc_wep_reload:GetInt() == 0 then self.Weapon_CanReload = false end if vj_npc_ply_betray:GetInt() == 0 then self.BecomeEnemyToPlayer = false end if vj_npc_callhelp:GetInt() == 0 then self.CallForHelp = false end if vj_npc_investigate:GetInt() == 0 then self.CanInvestigate = false end if vj_npc_eat:GetInt() == 0 then self.CanEat = false end if vj_npc_ply_follow:GetInt() == 0 then self.FollowPlayer = false end if vj_npc_ply_chat:GetInt() == 0 then self.CanChatMessage = false end if vj_npc_medic:GetInt() == 0 then self.IsMedic = false end if vj_npc_wep:GetInt() == 0 then self.Weapon_Disabled = true end if vj_npc_grenade:GetInt() == 0 then self.HasGrenadeAttack = false end if vj_npc_dangerdetection:GetInt() == 0 then self.CanDetectDangers = false end if vj_npc_wep_drop:GetInt() == 0 then self.DropWeaponOnDeath = false end if vj_npc_gib_vfx:GetInt() == 0 then self.HasGibOnDeathEffects = false end if vj_npc_gib:GetInt() == 0 then self.CanGib = false self.CanGibOnDeath = false end if vj_npc_blood_gmod:GetInt() == 1 then self.BloodDecalUseGMod = true end if vj_npc_sight_xray:GetInt() == 1 then self.SightAngle = 360 self.EnemyXRayDetection = true end if vj_npc_snd_gib:GetInt() == 0 then self.HasGibOnDeathSounds = false end if vj_npc_snd_track:GetInt() == 0 then self.HasSoundTrack = false end if vj_npc_snd_footstep:GetInt() == 0 then self.HasFootstepSounds = false end if vj_npc_snd_idle:GetInt() == 0 then self.HasIdleSounds = false end if vj_npc_snd_breath:GetInt() == 0 then self.HasBreathSound = false end if vj_npc_snd_alert:GetInt() == 0 then self.HasAlertSounds = false end if vj_npc_snd_danger:GetInt() == 0 then self.HasDangerSightSounds = false end if vj_npc_snd_melee:GetInt() == 0 then self.HasMeleeAttackSounds = false self.HasExtraMeleeAttackSounds = false self.HasMeleeAttackMissSounds = false end if vj_npc_snd_pain:GetInt() == 0 then self.HasPainSounds = false end if vj_npc_snd_death:GetInt() == 0 then self.HasDeathSounds = false end if vj_npc_snd_plyfollow:GetInt() == 0 then self.HasFollowPlayerSounds = false end if vj_npc_snd_plybetrayal:GetInt() == 0 then self.HasBecomeEnemyToPlayerSounds = false end if vj_npc_snd_plydamage:GetInt() == 0 then self.HasDamageByPlayerSounds = false end if vj_npc_snd_plysight:GetInt() == 0 then self.HasOnPlayerSightSounds = false end if vj_npc_snd_medic:GetInt() == 0 then self.HasMedicSounds = false end if vj_npc_snd_wep_reload:GetInt() == 0 then self.HasWeaponReloadSounds = false end if vj_npc_snd_grenade:GetInt() == 0 then self.HasGrenadeAttackSounds = false end if vj_npc_snd_wep_suppressing:GetInt() == 0 then self.HasSuppressingSounds = false end if vj_npc_snd_callhelp:GetInt() == 0 then self.HasCallForHelpSounds = false end if vj_npc_snd_receiveorder:GetInt() == 0 then self.HasReceiveOrderSounds = false end local corpseCollision = vj_npc_corpse_collision:GetInt() if corpseCollision != 0 && self.DeathCorpseCollisionType == COLLISION_GROUP_DEBRIS then if corpseCollision == 1 then self.DeathCorpseCollisionType = COLLISION_GROUP_NONE elseif corpseCollision == 2 then self.DeathCorpseCollisionType = COLLISION_GROUP_WORLD elseif corpseCollision == 3 then self.DeathCorpseCollisionType = COLLISION_GROUP_INTERACTIVE elseif corpseCollision == 4 then self.DeathCorpseCollisionType = COLLISION_GROUP_WEAPON elseif corpseCollision == 5 then self.DeathCorpseCollisionType = COLLISION_GROUP_PASSABLE_DOOR elseif corpseCollision == 6 then self.DeathCorpseCollisionType = COLLISION_GROUP_NONE end end -- Enables source engine debug overlays (some commands like 'npc_conditions' need it) if self.VJ_DEBUG && vj_npc_debug_engine:GetInt() == 1 then self:SetSaveValue("m_debugOverlays", bit.bor(0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00001000, 0x00002000, 0x00004000, 0x00008000, 0x00020000, 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000)) end end --------------------------------------------------------------------------------------------------------------------------------------------- local function ApplyBackwardsCompatibility(self) -- !!!!!!!!!!!!!! DO NOT USE ANY OF THESE !!!!!!!!!!!!!! [Backwards Compatibility!] -- Most of these are pre-revamp variables & functions if self.CustomOnInitialize then self:CustomOnInitialize() end if self.CustomInitialize then self:CustomInitialize() end if self.CustomOn_PoseParameterLookingCode then self.OnUpdatePoseParamTracking = function(_, pitch, yaw, roll) self:CustomOn_PoseParameterLookingCode(pitch, yaw, roll) end end if self.CustomOnAlert then self.OnAlert = function(_, ent) self:CustomOnAlert(ent) end end if self.CustomOnInvestigate then self.OnInvestigate = function(_, ent) self:CustomOnInvestigate(ent) end end if self.CustomOnFootStepSound then self.OnFootstepSound = function(_, moveType, sdFile) self:CustomOnFootStepSound(moveType, sdFile) end end if self.CustomOnCallForHelp then self.OnCallForHelp = function(_, ally, isFirst) self:CustomOnCallForHelp(ally, isFirst) end end if self.CustomOnPlayerSight then self.OnPlayerSight = function(_, ent) self:CustomOnPlayerSight(ent) end end if self.CustomOnThink then self.OnThink = function() self:CustomOnThink() end end if self.CustomOnThink_AIEnabled then self.OnThinkActive = function() self:CustomOnThink_AIEnabled() end end if self.CustomOnTakeDamage_OnBleed then self.OnBleed = function(_, dmginfo, hitgroup) self:CustomOnTakeDamage_OnBleed(dmginfo, hitgroup) end end if self.CustomOnMoveRandomlyWhenShooting then self.OnWeaponStrafe = function() return self:CustomOnMoveRandomlyWhenShooting() end end if self.CustomOnAcceptInput then self.OnInput = function(_, key, activator, caller, data) self:CustomOnAcceptInput(key, activator, caller, data) end end if self.CustomOnHandleAnimEvent then self.OnAnimEvent = function(_, ev, evTime, evCycle, evType, evOptions) self:CustomOnHandleAnimEvent(ev, evTime, evCycle, evType, evOptions) end end if self.CustomOnWeaponReload then self.OnWeaponReload = function() self:CustomOnWeaponReload() end end if self.CustomOnWeaponAttack then self.OnWeaponAttack = function() self:CustomOnWeaponAttack() end end if self.CustomOnDropWeapon then self.OnDeathWeaponDrop = function(_, dmginfo, hitgroup, wepEnt) self:CustomOnDropWeapon(dmginfo, hitgroup, wepEnt) end end if self.CustomOnDeath_AfterCorpseSpawned then self.OnCreateDeathCorpse = function(_, dmginfo, hitgroup, corpse) self:CustomOnDeath_AfterCorpseSpawned(dmginfo, hitgroup, corpse) end end if self.PlayerFriendly == true then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = "CLASS_PLAYER_ALLY" end if self.HasHealthRegeneration then self.HealthRegenParams.Enabled = true end if self.HealthRegenerationAmount then self.HealthRegenParams.Amount = self.HealthRegenerationAmount end if self.HealthRegenerationDelay then self.HealthRegenParams.Delay = self.HealthRegenerationDelay end if self.HealthRegenerationResetOnDmg then self.HealthRegenParams.ResetOnDmg = self.HealthRegenerationResetOnDmg end if self.FriendsWithAllPlayerAllies != nil then self.AlliedWithPlayerAllies = self.FriendsWithAllPlayerAllies end if self.MoveRandomlyWhenShooting != nil then self.Weapon_Strafe = self.MoveRandomlyWhenShooting end if self.GrenadeAttackThrowDistance then self.GrenadeAttackMaxDistance = self.GrenadeAttackThrowDistance end if self.GrenadeAttackThrowDistanceClose then self.GrenadeAttackMinDistance = self.GrenadeAttackThrowDistanceClose end if self.NextThrowGrenadeTime != nil then self.NextGrenadeAttackTime = self.NextThrowGrenadeTime end if self.TimeUntilGrenadeIsReleased != nil then self.GrenadeAttackThrowTime = self.TimeUntilGrenadeIsReleased end if self.NextMoveRandomlyWhenShootingTime1 or self.NextMoveRandomlyWhenShootingTime2 then self.Weapon_StrafeCooldown = VJ.SET(self.NextMoveRandomlyWhenShootingTime1 or 3, self.NextMoveRandomlyWhenShootingTime2 or 6) end if self.WaitForEnemyToComeOut != nil then self.Weapon_OcclusionDelay = self.WaitForEnemyToComeOut end if self.WaitForEnemyToComeOutTime then self.Weapon_OcclusionDelayTime = self.WaitForEnemyToComeOutTime end if self.MoveOrHideOnDamageByEnemy != nil then self.CombatDamageResponse = self.MoveOrHideOnDamageByEnemy end if self.MoveOrHideOnDamageByEnemy_HideTime then self.CombatDamageResponse_CoverTime = self.MoveOrHideOnDamageByEnemy_HideTime end if self.Medic_CanBeHealed == false then self.VJ_ID_Healable = false end if self.DisableWeaponFiringGesture == true then self.AnimTbl_WeaponAttackGesture = false end if self.Immune_AcidPoisonRadiation != nil then self.Immune_Toxic = self.Immune_AcidPoisonRadiation end if self.Immune_Blast != nil then self.Immune_Explosive = self.Immune_Blast end if self.FindEnemy_CanSeeThroughWalls == true then self.EnemyXRayDetection = true end if self.DisableFindEnemy == true then self.EnemyDetection = false end if self.DisableTouchFindEnemy == true then self.EnemyTouchDetection = false end if self.HasFootStepSound then self.HasFootstepSounds = self.HasFootStepSound end if self.FootStepPitch then self.FootstepSoundPitch = self.FootStepPitch end if self.FootStepSoundLevel then self.FootstepSoundLevel = self.FootStepSoundLevel end if self.FootStepTimeWalk then self.FootstepSoundTimerWalk = self.FootStepTimeWalk end if self.FootStepTimeRun then self.FootstepSoundTimerRun = self.FootStepTimeRun end if self.HitGroupFlinching_Values then self.FlinchHitGroupMap = self.HitGroupFlinching_Values end if self.HitGroupFlinching_DefaultWhenNotHit != nil then self.FlinchHitGroupPlayDefault = self.HitGroupFlinching_DefaultWhenNotHit end if self.NextFlinchTime != nil then self.FlinchCooldown = self.NextFlinchTime end if self.NextCallForHelpTime then self.CallForHelpCooldown = self.NextCallForHelpTime end if self.CallForHelpAnimationFaceEnemy != nil then self.CallForHelpAnimFaceEnemy = self.CallForHelpAnimationFaceEnemy end if self.NextCallForHelpAnimationTime != nil then self.CallForHelpAnimCooldown = self.NextCallForHelpAnimationTime end if self.InvestigateSoundDistance != nil then self.InvestigateSoundMultiplier = self.InvestigateSoundDistance end if self.CanThrowBackDetectedGrenades != nil then self.CanRedirectGrenades = self.CanThrowBackDetectedGrenades end if self.SoundTbl_OnKilledEnemy != nil then self.SoundTbl_KilledEnemy = self.SoundTbl_OnKilledEnemy end if self.HasOnKilledEnemySounds != nil then self.HasKilledEnemySounds = self.HasOnKilledEnemySounds end if self.OnKilledEnemySoundChance then self.OnKilledEnemySoundChance = self.OnKilledEnemySoundChance end if self.NextSoundTime_OnKilledEnemy then self.NextSoundTime_KilledEnemy = self.NextSoundTime_OnKilledEnemy end if self.OnKilledEnemySoundLevel then self.KilledEnemySoundLevel = self.OnKilledEnemySoundLevel end if self.OnKilledEnemySoundPitch != nil then self.KilledEnemySoundPitch = self.OnKilledEnemySoundPitch end if self.IdleSounds_PlayOnAttacks != nil then self.IdleSoundsWhileAttacking = self.IdleSounds_PlayOnAttacks end if self.IdleSounds_NoRegularIdleOnAlerted != nil then self.IdleSoundsRegWhileAlert = self.IdleSounds_NoRegularIdleOnAlerted end if self.HasOnReceiveOrderSounds != nil then self.HasReceiveOrderSounds = self.HasOnReceiveOrderSounds end if self.SoundTbl_OnReceiveOrder != nil then self.SoundTbl_ReceiveOrder = self.SoundTbl_OnReceiveOrder end if self.OnReceiveOrderSoundChance != nil then self.ReceiveOrderSoundChance = self.OnReceiveOrderSoundChance end if self.OnReceiveOrderSoundLevel != nil then self.ReceiveOrderSoundLevel = self.OnReceiveOrderSoundLevel end if self.OnReceiveOrderSoundPitch != nil then self.ReceiveOrderSoundPitch = self.OnReceiveOrderSoundPitch end if self.OnGrenadeSightSoundLevel != nil then self.DangerSightSoundLevel = self.OnGrenadeSightSoundLevel end if self.SoundTbl_OnDangerSight != nil then self.SoundTbl_DangerSight = self.SoundTbl_OnDangerSight end if self.SoundTbl_OnGrenadeSight != nil then self.SoundTbl_GrenadeSight = self.SoundTbl_OnGrenadeSight end if self.SoundTbl_MedicAfterHeal != nil then self.SoundTbl_MedicOnHeal = self.SoundTbl_MedicAfterHeal end if self.MedicAfterHealSoundChance != nil then self.MedicOnHealSoundChance = self.MedicAfterHealSoundChance end if self.BeforeHealSoundLevel != nil then self.MedicBeforeHealSoundLevel = self.BeforeHealSoundLevel end if self.AfterHealSoundLevel != nil then self.MedicOnHealSoundLevel = self.AfterHealSoundLevel end if self.BeforeHealSoundPitch != nil then self.MedicBeforeHealSoundPitch = self.BeforeHealSoundPitch end if self.AfterHealSoundPitch != nil then self.MedicOnHealSoundPitch = self.AfterHealSoundPitch end if self.Immune_Physics then self:SetPhysicsDamageScale(0) end if self.StopMeleeAttackAfterFirstHit != nil then self.MeleeAttackStopOnHit = self.StopMeleeAttackAfterFirstHit end if self.DisableMeleeAttackAnimation == true then self.AnimTbl_MeleeAttack = false end if self.Weapon_FiringDistanceClose then self.Weapon_MinDistance = self.Weapon_FiringDistanceClose end if self.Weapon_FiringDistanceFar then self.Weapon_MaxDistance = self.Weapon_FiringDistanceFar end if self.DisableWeapons != nil then self.Weapon_Disabled = self.DisableWeapons end if self.Passive_RunOnDamage == false then self.DamageResponse = false end if self.HideOnUnknownDamage == false then self.DamageResponse = "OnlySearch" end if self.DisableTakeDamageFindEnemy == true then if self.HideOnUnknownDamage == false then self.DamageResponse = false else self.DamageResponse = "OnlyMove" end end if self.CanFlinch == 0 then self.CanFlinch = false end if self.CanFlinch == 1 then self.CanFlinch = true end if self.CanFlinch == 2 then self.CanFlinch = "DamageTypes" end if self.BringFriendsOnDeath != nil or self.AlertFriendsOnDeath != nil then if self.AlertFriendsOnDeath == true && (self.BringFriendsOnDeath == false or self.BringFriendsOnDeath == nil) then self.DeathAllyResponse = "OnlyAlert" elseif self.BringFriendsOnDeath == false && self.AlertFriendsOnDeath == false then self.DeathAllyResponse = false end end if self.BringFriendsOnDeathLimit then self.DeathAllyResponse_MoveLimit = self.BringFriendsOnDeathLimit end if self.VJC_Data then self.ControllerParams = self.VJC_Data end if self.HasCallForHelpAnimation == false then self.AnimTbl_CallForHelp = false end if self.Medic_DisableAnimation == true then self.AnimTbl_Medic_GiveHealth = false end if self.ConstantlyFaceEnemyDistance then self.ConstantlyFaceEnemy_MinDistance = self.ConstantlyFaceEnemyDistance end if self.CallForBackUpOnDamage != nil then self.DamageAllyResponse = self.CallForBackUpOnDamage end if self.NextCallForBackUpOnDamageTime then self.DamageAllyResponse_Cooldown = self.NextCallForBackUpOnDamageTime end if self.CallForBackUpOnDamageAnimation then self.AnimTbl_DamageAllyResponse = self.CallForBackUpOnDamageAnimation end if self.UseTheSameGeneralSoundPitch != nil then self.MainSoundPitchStatic = self.UseTheSameGeneralSoundPitch end if self.GeneralSoundPitch1 or self.GeneralSoundPitch2 then self.MainSoundPitch = VJ.SET(self.GeneralSoundPitch1 or 90, self.GeneralSoundPitch2 or 100) end if self.AlertedToIdleTime then self.AlertTimeout = self.AlertedToIdleTime end if self.SoundTbl_MoveOutOfPlayersWay then self.SoundTbl_YieldToPlayer = self.SoundTbl_MoveOutOfPlayersWay end if self.MaxJumpLegalDistance then self.JumpParams.MaxRise = self.MaxJumpLegalDistance.a; self.JumpParams.MaxDrop = self.MaxJumpLegalDistance.b end if self.VJ_IsHugeMonster then self.VJ_ID_Boss = self.VJ_IsHugeMonster end if self.Medic_HealthAmount then self.Medic_HealAmount = self.Medic_HealthAmount end if self.UsePlayerModelMovement then self.UsePoseParameterMovement = true end if self.MoveOutOfFriendlyPlayersWay != nil then self.YieldToAlliedPlayers = self.MoveOutOfFriendlyPlayersWay end if self.WaitBeforeDeathTime then self.DeathDelayTime = self.WaitBeforeDeathTime end if self.HasDeathRagdoll != nil then self.HasDeathCorpse = self.HasDeathRagdoll end if self.AllowedToGib != nil then self.CanGib = self.AllowedToGib end if self.HasGibOnDeath != nil then self.CanGibOnDeath = self.HasGibOnDeath end if self.HasGibDeathParticles != nil then self.HasGibOnDeathEffects = self.HasGibDeathParticles else self.HasGibDeathParticles = self.HasGibOnDeathEffects end if self.HasItemDropsOnDeath != nil then self.DropDeathLoot = self.HasItemDropsOnDeath end if self.ItemDropsOnDeathChance != nil then self.DeathLootChance = self.ItemDropsOnDeathChance end if self.ItemDropsOnDeath_EntityList != nil then self.DeathLoot = self.ItemDropsOnDeath_EntityList end if self.AllowMovementJumping != nil then self.JumpParams.Enabled = self.AllowMovementJumping end if self.HasShootWhileMoving == false then self.Weapon_CanMoveFire = false end if self.HasWeaponBackAway == false then self.Weapon_RetreatDistance = 0 end if self.WeaponBackAway_Distance then self.Weapon_RetreatDistance = self.WeaponBackAway_Distance end if self.WeaponSpread then self.Weapon_Accuracy = self.WeaponSpread end if self.AllowWeaponReloading != nil then self.Weapon_CanReload = self.AllowWeaponReloading end if self.WeaponReload_FindCover != nil then self.Weapon_FindCoverOnReload = self.WeaponReload_FindCover end if self.ThrowGrenadeChance then self.GrenadeAttackChance = self.ThrowGrenadeChance end if self.OnlyDoKillEnemyWhenClear != nil then self.KilledEnemySoundLast = self.OnlyDoKillEnemyWhenClear end if self.NoWeapon_UseScaredBehavior != nil then self.Weapon_UnarmedBehavior = self.NoWeapon_UseScaredBehavior end if self.CanCrouchOnWeaponAttack != nil then self.Weapon_CanCrouchAttack = self.CanCrouchOnWeaponAttack end if self.CanCrouchOnWeaponAttackChance != nil then self.Weapon_CrouchAttackChance = self.CanCrouchOnWeaponAttackChance end if self.AnimTbl_WeaponAttackFiringGesture != nil then self.AnimTbl_WeaponAttackGesture = self.AnimTbl_WeaponAttackFiringGesture end if self.CanUseSecondaryOnWeaponAttack != nil then self.Weapon_CanSecondaryFire = self.CanUseSecondaryOnWeaponAttack end if self.WeaponAttackSecondaryTimeUntilFire != nil then self.Weapon_SecondaryFireTime = self.WeaponAttackSecondaryTimeUntilFire end if self.DisableFootStepOnWalk then self.FootstepSoundTimerWalk = false end if self.DisableFootStepOnRun then self.FootstepSoundTimerRun = false end if self.FindEnemy_UseSphere then self.SightAngle = 360 end if self.IsMedicSNPC then self.IsMedic = self.IsMedicSNPC end if self.BecomeEnemyToPlayer == true then self.BecomeEnemyToPlayer = self.BecomeEnemyToPlayerLevel or 2 end if self.CustomBlood_Particle then self.BloodParticle = self.CustomBlood_Particle end if self.CustomBlood_Pool then self.BloodPool = self.CustomBlood_Pool end if self.CustomBlood_Decal then self.BloodDecal = self.CustomBlood_Decal end if self.GibOnDeathDamagesTable then for _, v in ipairs(self.GibOnDeathDamagesTable) do if v == "All" then self.GibOnDeathFilter = false end end end if self.SetUpGibesOnDeath then self.HandleGibOnDeath = function(_, dmginfo, hitgroup) local gibbed, overrides = self:SetUpGibesOnDeath(dmginfo, hitgroup) local tbl = {} if overrides then if overrides.AllowCorpse then tbl.AllowCorpse = true end if overrides.DeathAnim then tbl.AllowAnim = true end end if self.CustomGibOnDeathSounds && !self:CustomGibOnDeathSounds(dmginfo, hitgroup) then tbl.AllowSound = false end return gibbed, tbl end end if self.CustomOnDoKilledEnemy then self.OnKilledEnemy = function(_, ent, inflictor, wasLast) if (self.KilledEnemySoundLast == false) or (self.KilledEnemySoundLast == true && wasLast) then self:CustomOnDoKilledEnemy(ent, self, inflictor) end end end if self.CustomOnMedic_BeforeHeal or self.CustomOnMedic_OnHeal or self.CustomOnMedic_OnReset then self.OnMedicBehavior = function(_, status, statusData) if status == "BeforeHeal" && self.CustomOnMedic_BeforeHeal then self:CustomOnMedic_BeforeHeal() elseif status == "OnHeal" && self.CustomOnMedic_OnHeal then return self:CustomOnMedic_OnHeal(statusData) elseif status == "OnReset" && self.CustomOnMedic_OnReset then self:CustomOnMedic_OnReset() end end end if self.CustomOnTakeDamage_BeforeImmuneChecks or self.CustomOnTakeDamage_BeforeDamage or self.CustomOnTakeDamage_AfterDamage then self.OnDamaged = function(_, dmginfo, hitgroup, status) if status == "Init" && self.CustomOnTakeDamage_BeforeImmuneChecks then self:CustomOnTakeDamage_BeforeImmuneChecks(dmginfo, hitgroup) elseif status == "PreDamage" && self.CustomOnTakeDamage_BeforeDamage then self:CustomOnTakeDamage_BeforeDamage(dmginfo, hitgroup) elseif status == "PostDamage" && self.CustomOnTakeDamage_AfterDamage then self:CustomOnTakeDamage_AfterDamage(dmginfo, hitgroup) end end end if self.CustomOnFlinch_BeforeFlinch or self.CustomOnFlinch_AfterFlinch then self.OnFlinch = function(_, dmginfo, hitgroup, status) if status == "Init" then if self.CustomOnFlinch_BeforeFlinch then return !self:CustomOnFlinch_BeforeFlinch(dmginfo, hitgroup) end elseif status == "Execute" then if self.CustomOnFlinch_AfterFlinch then self:CustomOnFlinch_AfterFlinch(dmginfo, hitgroup) end end end end if self.CustomOnInitialKilled or self.CustomOnPriorToKilled or self.CustomDeathAnimationCode or self.CustomOnKilled or self.CustomOnDeath_BeforeCorpseSpawned then self.OnDeath = function(_, dmginfo, hitgroup, status) if status == "Init" then if self.CustomOnInitialKilled then self:CustomOnInitialKilled(dmginfo, hitgroup) end if self.CustomOnPriorToKilled then self:CustomOnPriorToKilled(dmginfo, hitgroup) end elseif status == "DeathAnim" && self.CustomDeathAnimationCode then self:CustomDeathAnimationCode(dmginfo, hitgroup) elseif status == "Finish" then if self.CustomOnKilled then self:CustomOnKilled(dmginfo, hitgroup) end if self.CustomOnDeath_BeforeCorpseSpawned then self:CustomOnDeath_BeforeCorpseSpawned(dmginfo, hitgroup) end end end end if self.HasWorldShakeOnMove && !self.OnFootstepSound then self.OnFootstepSound = function() util.ScreenShake(self:GetPos(), self.WorldShakeOnMoveAmplitude or 10, self.WorldShakeOnMoveFrequency or 100, self.WorldShakeOnMoveDuration or 0.4, self.WorldShakeOnMoveRadius or 1000) end end if self.DeathCorpseSkin && self.DeathCorpseSkin != -1 then local orgFunc = self.OnCreateDeathCorpse self.OnCreateDeathCorpse = function(_, dmginfo, hitgroup, corpse) orgFunc(self, dmginfo, hitgroup, corpse) corpse:SetSkin(self.DeathCorpseSkin) end end if self.CustomOnTouch then self.OnTouch = function(_, ent) self:CustomOnTouch(ent) end end if self.CustomAttack then self.OnThinkAttack = function(_, isAttacking, enemy) if self.CustomAttack then self:CustomAttack(enemy, self.EnemyData.Visible) end end end if self.CustomAttackCheck_MeleeAttack or self.CustomOnMeleeAttack_BeforeStartTimer or self.CustomOnMeleeAttack_AfterStartTimer then self.OnMeleeAttack = function(_, status, enemy) if status == "PreInit" && self.CustomAttackCheck_MeleeAttack then return !self:CustomAttackCheck_MeleeAttack(enemy) elseif status == "Init" && self.CustomOnMeleeAttack_BeforeStartTimer then self:CustomOnMeleeAttack_BeforeStartTimer(self.AttackSeed) elseif status == "PostInit" && self.CustomOnMeleeAttack_AfterStartTimer then self:CustomOnMeleeAttack_AfterStartTimer(self.AttackSeed) end end end if self.DisableDefaultMeleeAttackCode or self.MeleeAttackWorldShakeOnMiss or self.CustomOnMeleeAttack_BeforeChecks or self.CustomOnMeleeAttack_AfterChecks or self.CustomOnMeleeAttack_Miss then self.OnMeleeAttackExecute = function(_, status, ent, isProp) if status == "Init" && (self.CustomOnMeleeAttack_BeforeChecks or self.DisableDefaultMeleeAttackCode) then if self.CustomOnMeleeAttack_BeforeChecks then self:CustomOnMeleeAttack_BeforeChecks() end if self.DisableDefaultMeleeAttackCode then return true end elseif status == "PreDamage" && self.CustomOnMeleeAttack_AfterChecks then return self:CustomOnMeleeAttack_AfterChecks(ent, isProp) elseif status == "Miss" && (self.CustomOnMeleeAttack_Miss or self.MeleeAttackWorldShakeOnMiss) then if self.CustomOnMeleeAttack_Miss then self:CustomOnMeleeAttack_Miss() end if self.MeleeAttackWorldShakeOnMiss then util.ScreenShake(self:GetPos(), self.MeleeAttackWorldShakeOnMissAmplitude or 16, 100, self.MeleeAttackWorldShakeOnMissDuration or 1, self.MeleeAttackWorldShakeOnMissRadius or 2000) end end end end if self.GetMeleeAttackDamageOrigin then self.MeleeAttackTraceOrigin = function() return self:GetMeleeAttackDamageOrigin() end end -- !!!!!!!!!!!!!! DO NOT USE ANY OF THESE !!!!!!!!!!!!!! [Backwards Compatibility!] end --------------------------------------------------------------------------------------------------------------------------------------------- local defShootVec = Vector(0, 0, 55) local capBitsDefault = bit.bor(CAP_SKIP_NAV_GROUND_CHECK, CAP_TURN_HEAD, CAP_DUCK) local capBitsDoors = bit.bor(CAP_OPEN_DOORS, CAP_AUTO_DOORS, CAP_USE) local capBitsWeapons = bit.bor(CAP_USE_WEAPONS, CAP_WEAPON_RANGE_ATTACK1) -- function ENT:Initialize() self:PreInit() if self.CustomOnPreInitialize then self:CustomOnPreInitialize() end -- !!!!!!!!!!!!!! DO NOT USE !!!!!!!!!!!!!! [Backwards Compatibility!] self:SetSpawnEffect(false) self:SetRenderMode(RENDERMODE_NORMAL) self:AddEFlags(EFL_NO_DISSOLVE) self:SetUseType(SIMPLE_USE) if !self:GetModel() then local models = PICK(self.Model) if models then self:SetModel(models) end end self:SetHullType(self.HullType) self:SetHullSizeNormal() self:SetSolid(SOLID_BBOX) self:SetCollisionGroup(COLLISION_GROUP_NPC) self:SetMaxYawSpeed(self.TurningSpeed) self:SetSaveValue("m_HackedGunPos", defShootVec) -- Overrides the location of self:GetShootPos() -- Set a name if it doesn't have one if self:GetName() == "" then local findListing = list.Get("NPC")[self:GetClass()] if findListing then self:SetName((self.PrintName == "" and findListing.Name) or self.PrintName) end end -- Initialize variables InitConvars(self) self.NextProcessTime = vj_npc_processtime:GetInt() self.SelectedDifficulty = vj_npc_difficulty:GetInt() if !self.RelationshipEnts then self.RelationshipEnts = {} end if !self.RelationshipMemory then self.RelationshipMemory = {} end self.AnimationTranslations = {} self.WeaponInventory = {} self.NextIdleSoundT_Reg = CurTime() + math.random(0.3, 6) self.MainSoundPitchValue = (self.MainSoundPitchStatic and (istable(self.MainSoundPitch) and math.random(self.MainSoundPitch.a, self.MainSoundPitch.b) or self.MainSoundPitch)) or 0 local sightConvar = vj_npc_sight_distance:GetInt(); if sightConvar > 0 then self.SightDistance = sightConvar end -- Capabilities & Movement self:DoChangeMovementType(self.MovementType) self:CapabilitiesAdd(capBitsDefault) if self.CanOpenDoors then self:CapabilitiesAdd(capBitsDoors) end -- Both of these attachments have to be valid for "ai_baseactor" to work properly! if self:LookupAttachment("eyes") > 0 && self:LookupAttachment("forward") > 0 then self:CapabilitiesAdd(CAP_ANIMATEDFACE) end if self.Behavior == VJ_BEHAVIOR_PASSIVE or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE then self.Weapon_Disabled = true self.Weapon_IgnoreSpawnMenu = true elseif !self.Weapon_Disabled && !self.Weapon_IgnoreSpawnMenu then self:CapabilitiesAdd(capBitsWeapons) end -- Health local hpConvar = vj_npc_health:GetInt() local hp = hpConvar > 0 && hpConvar or self:ScaleByDifficulty(self.StartHealth) self:SetHealth(hp) self.StartHealth = hp self:Init() ApplyBackwardsCompatibility(self) -- Collision-based computations //self:SetSurroundingBoundsType(BOUNDS_HITBOXES) -- AVOID! Has to constantly recompute the bounds! | Issues: Entities get stuck inside the NPC, movements failing, unable to grab the NPC with physgun local collisionMin, collisionMax = self:GetCollisionBounds() -- Auto compute damage bounds if the damage bounds == collision bounds then the developer has NOT changed it | Call after "Init" if self:GetSurroundingBounds() == self:WorldSpaceAABB() then self:SetSurroundingBounds(Vector(collisionMin.x * 2, collisionMin.y * 2, collisionMin.z * 1.2), Vector(collisionMax.x * 2, collisionMax.y * 2, collisionMax.z * 1.2)) end if !self.MeleeAttackDistance then self.MeleeAttackDistance = math.abs(collisionMax.x) + 30 end if !self.MeleeAttackDamageDistance then self.MeleeAttackDamageDistance = math.abs(collisionMax.x) + 60 end self:SetupBloodColor(self.BloodColor) -- Collision bounds dependent self.NextWanderTime = ((self.NextWanderTime != 0) and self.NextWanderTime) or (CurTime() + (self.IdleAlwaysWander and 0 or 1)) -- If self.NextWanderTime isn't given a value THEN if self.IdleAlwaysWander isn't true, wait at least 1 sec before wandering duplicator.RegisterEntityClass(self:GetClass(), VJ.CreateDupe_NPC, "Model", "Class", "Equipment", "SpawnFlags", "Data") -- Delayed init timer.Simple(0.1, function() if IsValid(self) then self:SetMaxLookDistance(self.SightDistance) self:SetFOV(self.SightAngle) if self:GetNPCState() <= NPC_STATE_NONE then self:SetNPCState(NPC_STATE_IDLE) end if IsValid(self:GetCreator()) && self:GetCreator():GetInfoNum("vj_npc_spawn_guard", 0) == 1 then self.IsGuard = true end self:StartSoundTrack() -- Setup common default pose parameters if self:LookupPoseParameter("aim_pitch") != -1 then self.PoseParameterLooking_Names.pitch[#self.PoseParameterLooking_Names.pitch + 1] = "aim_pitch" end if self:LookupPoseParameter("head_pitch") != -1 then self.PoseParameterLooking_Names.pitch[#self.PoseParameterLooking_Names.pitch + 1] = "head_pitch" end if self:LookupPoseParameter("aim_yaw") != -1 then self.PoseParameterLooking_Names.yaw[#self.PoseParameterLooking_Names.yaw + 1] = "aim_yaw" end if self:LookupPoseParameter("head_yaw") != -1 then self.PoseParameterLooking_Names.yaw[#self.PoseParameterLooking_Names.yaw + 1] = "head_yaw" end if self:LookupPoseParameter("aim_roll") != -1 then self.PoseParameterLooking_Names.roll[#self.PoseParameterLooking_Names.roll + 1] = "aim_roll" end if self:LookupPoseParameter("head_roll") != -1 then self.PoseParameterLooking_Names.roll[#self.PoseParameterLooking_Names.roll + 1] = "head_roll" end if self.Weapon_Disabled then self:UpdateAnimationTranslations() else local wep = self:GetActiveWeapon() if IsValid(wep) then self.WeaponEntity = self:DoChangeWeapon() -- Setup the weapon self.WeaponInventory.Primary = wep if !wep.IsVJBaseWeapon && self.CanChatMessage && IsValid(self:GetCreator()) then self:GetCreator():PrintMessage(HUD_PRINTTALK, "WARNING: " .. self:GetName() .. " requires a VJ Base weapon to work properly!") end local antiArmor = PICK(self.WeaponInventory_AntiArmorList) if antiArmor && wep:GetClass() != antiArmor then -- If the list isn't empty and it's not the current active weapon self.WeaponInventory.AntiArmor = self:Give(antiArmor) self:SelectWeapon(wep) -- Change the weapon back to the primary weapon wep:Equip(self) end local melee = PICK(self.WeaponInventory_MeleeList) if melee && wep:GetClass() != melee then -- If the list isn't empty and it's not the current active weapon self.WeaponInventory.Melee = self:Give(melee) self:SelectWeapon(wep) -- Change the weapon back to the primary weapon wep:Equip(self) end else self:UpdateAnimationTranslations() if IsValid(self:GetCreator()) && self.CanChatMessage && !self.Weapon_IgnoreSpawnMenu then self:GetCreator():PrintMessage(HUD_PRINTTALK, "WARNING: " .. self:GetName() .. " requires a weapon!") end end end if self:GetIdealActivity() == ACT_IDLE then -- Reset the idle animation in case animation translations changed it! self:MaintainIdleAnimation(true) end -- This is needed as setting "NextThink" to CurTime will cause performance drops, so we set the idle maintain in a separate hook that runs every tick local thinkHook = hook.GetTable()["Think"] if (thinkHook && !thinkHook[self]) or (!thinkHook) then local idleFunc = self.MaintainIdleAnimation if #self:GetBoneFollowers() > 0 then hook.Add("Think", self, function() if VJ_CVAR_AI_ENABLED then idleFunc(self) end self:UpdateBoneFollowers() end) else hook.Add("Think", self, function() if VJ_CVAR_AI_ENABLED then idleFunc(self) end end) end else VJ.DEBUG_Print(self, false, "warn", "has an existing embedded \"Think\" hook already, which is disallowing the default base hook from assigning. Make sure to handle \"MaintainIdleAnimation\" in the overridden hook!") end end end) end --------------------------------------------------------------------------------------------------------------------------------------------- local capBitsGround = bit.bor(CAP_MOVE_GROUND, CAP_MOVE_JUMP, CAP_MOVE_CLIMB, CAP_MOVE_SHOOT) local capBitsShared = bit.bor(CAP_MOVE_GROUND, CAP_MOVE_JUMP, CAP_MOVE_CLIMB, CAP_MOVE_SHOOT, CAP_MOVE_FLY) -- function ENT:DoChangeMovementType(movType) if movType then self.MovementType = movType if movType == VJ_MOVETYPE_GROUND then self:RemoveFlags(FL_FLY) self:CapabilitiesRemove(CAP_MOVE_FLY) self:SetNavType(NAV_GROUND) self:SetMoveType(MOVETYPE_STEP) self:CapabilitiesAdd(CAP_MOVE_GROUND) if (VJ.AnimExists(self, ACT_JUMP) && vj_npc_human_jump:GetInt() == 1) or self.UsePoseParameterMovement then self:CapabilitiesAdd(CAP_MOVE_JUMP) end //if VJ.AnimExists(self, ACT_CLIMB_UP) then self:CapabilitiesAdd(bit.bor(CAP_MOVE_CLIMB)) end if !self.Weapon_Disabled && self.Weapon_CanMoveFire then self:CapabilitiesAdd(CAP_MOVE_SHOOT) end elseif movType == VJ_MOVETYPE_AERIAL or movType == VJ_MOVETYPE_AQUATIC then self:CapabilitiesRemove(capBitsGround) self:SetGroundEntity(NULL) self:AddFlags(FL_FLY) self:SetNavType(NAV_FLY) self:SetMoveType(MOVETYPE_STEP) // MOVETYPE_FLY = causes issues like Lerp functions not being smooth self:CapabilitiesAdd(CAP_MOVE_FLY) elseif movType == VJ_MOVETYPE_STATIONARY then self:RemoveFlags(FL_FLY) self:CapabilitiesRemove(capBitsShared) self:SetNavType(NAV_NONE) if !IsValid(self:GetParent()) then -- Only set move type if it does NOT have a parent! self:SetMoveType(MOVETYPE_FLY) end elseif movType == VJ_MOVETYPE_PHYSICS then self:RemoveFlags(FL_FLY) self:CapabilitiesRemove(capBitsShared) self:SetNavType(NAV_NONE) self:SetMoveType(MOVETYPE_VPHYSICS) end end end --------------------------------------------------------------------------------------------------------------------------------------------- local schedule_alert_chaseLOS = vj_ai_schedule.New("SCHEDULE_ALERT_CHASE_LOS") schedule_alert_chaseLOS:EngTask("TASK_GET_PATH_TO_ENEMY_LOS", 0) //schedule_alert_chaseLOS:EngTask("TASK_RUN_PATH", 0) schedule_alert_chaseLOS:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) //schedule_alert_chaseLOS:EngTask("TASK_FACE_ENEMY", 0) //schedule_alert_chaseLOS.ResetOnFail = true schedule_alert_chaseLOS.CanShootWhenMoving = true schedule_alert_chaseLOS.CanBeInterrupted = true -- local schedule_alert_chase = vj_ai_schedule.New("SCHEDULE_ALERT_CHASE") schedule_alert_chase:EngTask("TASK_GET_PATH_TO_ENEMY", 0) schedule_alert_chase:EngTask("TASK_RUN_PATH", 0) schedule_alert_chase:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) //schedule_alert_chase:EngTask("TASK_FACE_ENEMY", 0) //schedule_alert_chase.ResetOnFail = true schedule_alert_chase.CanShootWhenMoving = true schedule_alert_chase.CanBeInterrupted = true -- function ENT:SCHEDULE_ALERT_CHASE(doLOSChase) self:ClearCondition(COND_ENEMY_UNREACHABLE) local moveType = self.MovementType; if moveType == VJ_MOVETYPE_AERIAL or moveType == VJ_MOVETYPE_AQUATIC then self:AA_ChaseEnemy() return end if self.CurrentScheduleName == "SCHEDULE_ALERT_CHASE" then return end // && (self:GetEnemyLastKnownPos():Distance(self:GetEnemy():GetPos()) <= 12) local navType = self:GetNavType(); if navType == NAV_JUMP or navType == NAV_CLIMB then return end if doLOSChase then schedule_alert_chaseLOS.RunCode_OnFinish = function() local ene = self:GetEnemy() if IsValid(ene) then self:RememberUnreachable(ene, 0) self:SCHEDULE_ALERT_CHASE(false) end end self:StartSchedule(schedule_alert_chaseLOS) else self:StartSchedule(schedule_alert_chase) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MaintainAlertBehavior(alwaysChase) -- alwaysChase = Override to always make the NPC chase local curTime = CurTime() local selfData = self:GetTable() if selfData.NextChaseTime > curTime or selfData.Dead or selfData.VJ_IsBeingControlled or selfData.Flinching or self:GetState() == VJ_STATE_ONLY_ANIMATION_CONSTANT then return end local eneData = selfData.EnemyData local ene = eneData.Target local moveType = selfData.MovementType if !IsValid(ene) or selfData.TakingCoverT > curTime or (selfData.AttackAnimTime > curTime && moveType != VJ_MOVETYPE_AERIAL && moveType != VJ_MOVETYPE_AQUATIC) then return end -- Not melee attacking yet but it is in range, so don't chase the enemy! if selfData.HasMeleeAttack && eneData.DistanceNearest < selfData.MeleeAttackDistance && eneData.Visible && (self:GetHeadDirection():Dot((ene:GetPos() - self:GetPos()):GetNormalized()) > math_cos(math_rad(selfData.MeleeAttackAngleRadius))) then if moveType == VJ_MOVETYPE_AERIAL or moveType == VJ_MOVETYPE_AQUATIC then self:AA_StopMoving() end self:SCHEDULE_IDLE_STAND() return end -- Things that override can't bypass, Forces the NPC to ONLY idle stand! if moveType == VJ_MOVETYPE_STATIONARY or selfData.IsFollowing or selfData.MedicData.Status or self:GetState() == VJ_STATE_ONLY_ANIMATION then self:SCHEDULE_IDLE_STAND() return end -- Non-aggressive NPCs local behaviorType = selfData.Behavior if behaviorType == VJ_BEHAVIOR_PASSIVE or behaviorType == VJ_BEHAVIOR_PASSIVE_NATURE then self:SCHEDULE_COVER_ENEMY("TASK_RUN_PATH") selfData.NextChaseTime = curTime + 3 return end if !alwaysChase && (selfData.DisableChasingEnemy or selfData.IsGuard) then self:SCHEDULE_IDLE_STAND() return end -- If the enemy is not reachable if (funcHasCondition(self, COND_ENEMY_UNREACHABLE) or self:IsUnreachable(ene)) && (IsValid(self:GetActiveWeapon()) && (!self:GetActiveWeapon().IsMeleeWeapon)) then self:SCHEDULE_ALERT_CHASE(true) self:RememberUnreachable(ene, 2) else -- Is reachable, so chase the enemy! self:SCHEDULE_ALERT_CHASE(false) end -- Set the next chase time if selfData.NextChaseTime > curTime then return end -- Don't set it if it's already set! selfData.NextChaseTime = curTime + (((eneData.Distance > 2000) and 1) or 0.1) -- If the enemy is far, increase the delay! end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Overrides any activity by returning another activity - act = Activity that is being called to be translated Returns - Activity, the translated activity, otherwise it will return the given activity back RULES 1. Always return an activity, never return nothing or a table! - Suggested to call `return self.BaseClass.TranslateActivity(self, act)` at the end of the function 2. If you are replacing ACT_IDLE from a randomized table, then you must call `self:ResolveAnimation` - This is to ensure the idle animation system properly detects if it should be setting a new idle animation -----------------------------------------------------------]] function ENT:TranslateActivity(act) //VJ.DEBUG_Print(self, "TranslateActivity", act) local selfData = self:GetTable() -- Handle idle scared and angry animations if act == ACT_IDLE then if selfData.Weapon_UnarmedBehavior_Active then //return PICK(selfData.AnimTbl_ScaredBehaviorStand) return ACT_COWER elseif selfData.Alerted && self:GetWeaponState() != VJ.WEP_STATE_HOLSTERED && IsValid(self:GetActiveWeapon()) then //return PICK(selfData.AnimTbl_WeaponAim) return ACT_IDLE_ANGRY end -- Handle running while scared animation elseif act == ACT_RUN && selfData.Weapon_UnarmedBehavior_Active && !selfData.VJ_IsBeingControlled then // PICK(selfData.AnimTbl_ScaredBehaviorMovement) return ACT_RUN_PROTECTED elseif (act == ACT_RUN or act == ACT_WALK) && selfData.Alerted then -- Handle aiming while moving animations local eneData = selfData.EnemyData if selfData.Weapon_CanMoveFire && IsValid(eneData.Target) && (eneData.Visible or (eneData.VisibleTime + 5) > CurTime()) && selfData.CurrentSchedule && selfData.CurrentSchedule.CanShootWhenMoving && self:CanFireWeapon(true, false) then local anim = self:TranslateActivity(act == ACT_RUN and ACT_RUN_AIM or ACT_WALK_AIM) if VJ.AnimExists(self, anim) then if eneData.Visible then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_FIRE else -- Not visible but keep aiming selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_AIM_MOVE end return anim end end -- Handle walk/run angry animations local anim = self:TranslateActivity(act == ACT_RUN and ACT_RUN_AGITATED or ACT_WALK_AGITATED) if VJ.AnimExists(self, anim) then return anim end end -- Handle translations table local translation = selfData.AnimationTranslations[act] if translation then if istable(translation) then if act == ACT_IDLE then return self:ResolveAnimation(translation) end return translation[math.random(1, #translation)] or act -- "or act" = To make sure it doesn't return nil when the table is empty! end return translation end return act end --------------------------------------------------------------------------------------------------------------------------------------------- local sdWepSwitch = {"physics/metal/weapon_impact_soft1.wav", "physics/metal/weapon_impact_soft2.wav", "physics/metal/weapon_impact_soft3.wav"} -- function ENT:DoChangeWeapon(wep, invSwitch) wep = wep or nil -- The weapon to give or setup | Setting it nil will only setup the current active weapon invSwitch = invSwitch or false -- If true, it will not delete the previous weapon! local curWep = self:GetActiveWeapon() -- If not supposed to have a weapon, then return! if self.Weapon_Disabled && IsValid(curWep) then curWep:Remove() return NULL end -- Only remove and actually give the weapon if the function is given a weapon class to set if wep != nil then if invSwitch then self:SelectWeapon(wep) VJ.EmitSound(self, sdWepSwitch, 70) curWep = wep else if IsValid(curWep) && self.WeaponInventoryStatus <= VJ.WEP_INVENTORY_PRIMARY then curWep:Remove() end curWep = self:Give(wep) self.WeaponInventory.Primary = curWep end end -- If we are given a new weapon or switching weapon, then do all of the necessary set up if IsValid(curWep) then self.WeaponAttackAnim = ACT_INVALID self:SetWeaponState() -- Reset the weapon state because we do NOT want previous weapon's state to be used! if invSwitch then if curWep.IsVJBaseWeapon then curWep:Equip(self) end else -- If we are not switching weapons, then we know curWep is the primary weapon self.WeaponInventoryStatus = VJ.WEP_INVENTORY_PRIMARY -- If this is completely new weapon, then set the weapon inventory's primary to this weapon local curPrimary = self.WeaponInventory.Primary if curWep != self.WeaponInventory.Primary then if IsValid(curPrimary) then curPrimary:Remove() end -- Remove the old primary weapon self.WeaponInventory.Primary = curWep end end self:UpdateAnimationTranslations(curWep:GetHoldType()) self:OnWeaponChange(curWep, self.WeaponEntity, invSwitch) self.WeaponEntity = curWep else self.WeaponInventoryStatus = VJ.WEP_INVENTORY_NONE end return curWep end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:SetWeaponState(state, time) time = time or -1 self.WeaponState = state or VJ.WEP_STATE_READY if time >= 0 then timer.Create("wep_state_reset" .. self:EntIndex(), time, 1, function() self:SetWeaponState() end) else timer.Remove("wep_state_reset" .. self:EntIndex()) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:GetWeaponState() return self.WeaponState end --------------------------------------------------------------------------------------------------------------------------------------------- local attackTimers = { [VJ.ATTACK_TYPE_MELEE] = function(self, skipStopAttacks) if !skipStopAttacks then timer.Create("attack_melee_reset" .. self:EntIndex(), self:GetAttackTimer(self.NextAnyAttackTime_Melee, self.TimeUntilMeleeAttackDamage, self.AttackAnimDuration), 1, function() self:StopAttacks() self:MaintainAlertBehavior() end) end timer.Create("attack_melee_reset_able" .. self:EntIndex(), self:GetAttackTimer(self.NextMeleeAttackTime), 1, function() self.IsAbleToMeleeAttack = true end) end, [VJ.ATTACK_TYPE_GRENADE] = function(self, skipStopAttacks) if !skipStopAttacks then timer.Create("attack_grenade_reset" .. self:EntIndex(), self:GetAttackTimer(self.NextAnyAttackTime_Grenade, self.GrenadeAttackThrowTime, self.AttackAnimDuration), 1, function() self:StopAttacks() self:MaintainAlertBehavior() end) end //timer.Create("attack_grenade_reset_able" .. self:EntIndex(), self:GetAttackTimer(self.NextGrenadeAttackTime), 1, function() //self.IsAbleToGrenadeAttack = true //end) self.NextThrowGrenadeT = CurTime() + self:GetAttackTimer(self.NextGrenadeAttackTime) end } --------------------------------------------------------------------------------------------------------------------------------------------- local function playReloadAnimation(self, anims) local anim, animDur, animType = self:PlayAnim(anims, true, false, "Visible") if anim != ACT_INVALID then local wep = self.WeaponEntity if wep.IsVJBaseWeapon then wep:NPC_Reload() end timer.Create("wep_reload_reset" .. self:EntIndex(), animDur, 1, function() if IsValid(self) && IsValid(wep) && self:GetWeaponState() == VJ.WEP_STATE_RELOADING then wep:SetClip1(wep:GetMaxClip1()) if wep.IsVJBaseWeapon then wep:OnReload("Finish") end self:SetWeaponState() end end) self.AllowWeaponOcclusionDelay = false -- If NOT controlled by a player AND is a gesture make it stop moving so it doesn't run after the enemy right away if !self.VJ_IsBeingControlled && animType == ANIM_TYPE_GESTURE then self:StopMoving() end return true -- We have successfully ran the reload animation! end return false -- The given animation was invalid! end --------------------------------------------------------------------------------------------------------------------------------------------- //function ENT:OnActiveWeaponChanged(old, new) print(old, new) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Think() //if self.NextActualThink <= CurTime() then //self.NextActualThink = CurTime() + 0.065 -- Schedule debug //if self.CurrentSchedule then PrintTable(self.CurrentSchedule) end //if self.CurrentTask then PrintTable(self.CurrentTask) end //self:SetCondition(1) -- Probably not needed as "sv_pvsskipanimation" handles it | Fix attachments, bones, positions, angles etc. being broken in NPCs! This condition is used as a backup in case "sv_pvsskipanimation" isn't disabled! //if self.MovementType == VJ_MOVETYPE_GROUND && self:GetVelocity():Length() <= 0 && !self:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) /*&& curSchedule.IsMovingTask*/ then self:DropToFloor() end -- No need, already handled by the engine local curTime = CurTime() local selfData = self:GetTable() -- This is here to make sure the initialized process time stays in place... -- otherwise if AI is disabled then reenabled, all the NPCs will now start processing at the same exact CurTime! local doHeavyProcesses = curTime > selfData.NextProcessT if doHeavyProcesses then selfData.NextProcessT = curTime + selfData.NextProcessTime end if !selfData.Dead then -- Detect any weapon change, unless the NPC is dead because the variable is used by self:DeathWeaponDrop() if selfData.WeaponEntity != self:GetActiveWeapon() then selfData.WeaponEntity = self:DoChangeWeapon() end -- Breath sound system if selfData.HasBreathSound && selfData.HasSounds && curTime > selfData.NextBreathSoundT then local pickedSD = PICK(selfData.SoundTbl_Breath) local dur = 10 -- Make the default value large so we don't check it too much! if pickedSD then StopSD(selfData.CurrentBreathSound) dur = (selfData.NextSoundTime_Breath == false and SoundDuration(pickedSD)) or math.Rand(selfData.NextSoundTime_Breath.a, selfData.NextSoundTime_Breath.b) selfData.CurrentBreathSound = VJ.CreateSound(self, pickedSD, selfData.BreathSoundLevel, self:GetSoundPitch(selfData.BreathSoundPitch)) end selfData.NextBreathSoundT = curTime + dur end end self:OnThink() --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-- if VJ_CVAR_AI_ENABLED && self:GetState() != VJ_STATE_FREEZE && !self:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) then if selfData.VJ_DEBUG then if GetConVar("vj_npc_debug_enemy"):GetInt() == 1 then VJ.DEBUG_Print(self, false, "Enemy -> " .. tostring(self:GetEnemy() or "NULL") .. " | Alerted? " .. tostring(selfData.Alerted)) end if GetConVar("vj_npc_debug_takingcover"):GetInt() == 1 then if curTime > selfData.TakingCoverT then VJ.DEBUG_Print(self, false, "NOT taking cover") else VJ.DEBUG_Print(self, false, "Taking cover (" .. selfData.TakingCoverT - curTime .. ")") end end if GetConVar("vj_npc_debug_lastseenenemytime"):GetInt() == 1 then PrintMessage(HUD_PRINTTALK, (curTime - selfData.EnemyData.VisibleTime) .. " (" .. self:GetName() .. ")") end if IsValid(selfData.WeaponEntity) && GetConVar("vj_npc_debug_weapon"):GetInt() == 1 then VJ.DEBUG_Print(self, false, " : Weapon -> " .. tostring(selfData.WeaponEntity) .. " | Ammo: " .. selfData.WeaponEntity:Clip1() .. " / " .. selfData.WeaponEntity:GetMaxClip1() .. " | Accuracy: " .. selfData.Weapon_Accuracy) end end //self:SetPlaybackRate(self.AnimationPlaybackRate) self:OnThinkActive() -- Update follow system's data //print("------------------") //PrintTable(selfData.FollowData) if selfData.IsFollowing && self:GetNavType() != NAV_JUMP && self:GetNavType() != NAV_CLIMB then local followData = selfData.FollowData local followEnt = followData.Target local followIsLiving = followEnt.VJ_ID_Living //print(self:GetTarget()) if IsValid(followEnt) && (!followIsLiving or (followIsLiving && (self:Disposition(followEnt) == D_LI or self:GetClass() == followEnt:GetClass()) && followEnt:Alive())) then if curTime > followData.NextUpdateT && !selfData.VJ_ST_Healing then local distToPly = self:GetPos():Distance(followEnt:GetPos()) local busy = self:IsBusy("Activities") self:SetTarget(followEnt) followData.StopAct = false if distToPly > followData.MinDist then -- Entity is far away, move towards it! local isFar = distToPly > (followData.MinDist * 4) -- IF (we are busy but far) OR (not busy) THEN move if (busy && isFar) or (!busy) then followData.Moving = true -- If we are far then stop all activities (ex: attacks) and just go there already! if isFar then followData.StopAct = true end if selfData.MovementType == VJ_MOVETYPE_AERIAL or selfData.MovementType == VJ_MOVETYPE_AQUATIC then self:AA_MoveTo(self:GetTarget(), true, (distToPly < (followData.MinDist * 1.5) and "Calm") or "Alert", {FaceDestTarget = true}) elseif !self:IsMoving() or self:GetCurGoalType() != 1 then //self:NavSetGoalTarget(followEnt) // local goalTarget = -- No longer works, a recent GMod commit broke it -- Do NOT check for validity! Let it be sent to "OnTaskFailed" so an NPC can capture it! (Ex: HL1 scientist complaining to the player) //if goalTarget then local schedule = vj_ai_schedule.New("SCHEDULE_FOLLOW") schedule:EngTask("TASK_GET_PATH_TO_TARGET", 0) -- Required to generate the path! schedule:EngTask("TASK_MOVE_TO_TARGET_RANGE", followData.MinDist * 0.8) schedule:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) schedule:EngTask("TASK_FACE_TARGET", 1) schedule.CanShootWhenMoving = true if IsValid(self:GetActiveWeapon()) then schedule.TurnData = {Type = VJ.FACE_ENEMY_VISIBLE} end self:StartSchedule(schedule) //else // self:ClearGoal() //end /*self:SCHEDULE_GOTO_TARGET((distToPly < (followData.MinDist * 1.5) and "TASK_WALK_PATH") or "TASK_RUN_PATH", function(schedule) schedule.CanShootWhenMoving = true if IsValid(self:GetActiveWeapon()) then schedule.TurnData = {Type = VJ.FACE_ENEMY_VISIBLE} end end)*/ end end elseif followData.Moving == true then -- Entity is very close, stop moving! if !busy then -- If not busy then make it stop moving and do something self:TaskComplete() self:StopMoving(false) self:SelectSchedule() end followData.Moving = false end followData.NextUpdateT = curTime + 0.5 end else self:ResetFollowBehavior() end end //print("MAX CLIP: ", selfData.WeaponEntity:GetMaxClip1()) //print("CLIP: ", selfData.WeaponEntity:Clip1()) if !selfData.Dead then -- Health Regeneration System local healthRegen = selfData.HealthRegenParams if healthRegen.Enabled && curTime > selfData.HealthRegenDelayT then local myHP = self:Health() self:SetHealth(math_min(math_max(myHP + healthRegen.Amount, myHP), self:GetMaxHealth())) selfData.HealthRegenDelayT = curTime + math.Rand(healthRegen.Delay.a, healthRegen.Delay.b) end -- Run the heavy processes if doHeavyProcesses then self:MaintainRelationships() self:CheckForDangers() if selfData.IsMedic then self:MaintainMedicBehavior() end //selfData.NextProcessT = curTime + selfData.NextProcessTime end local plyControlled = selfData.VJ_IsBeingControlled local myPos = self:GetPos() local ene = self:GetEnemy() local eneValid = IsValid(ene) local eneData = selfData.EnemyData if !eneData.Reset then -- Reset enemy if it doesn't exist or it's dead if !eneValid then self:ResetEnemy(true, true) ene = self:GetEnemy() eneValid = IsValid(ene) -- Reset enemy if it has been unseen for a while elseif (curTime - eneData.VisibleTime) > ((eneData.Distance < 4000 and selfData.EnemyTimeout) or (selfData.EnemyTimeout / 2)) && !selfData.IsVJBaseSNPC_Tank then self:PlaySoundSystem("LostEnemy") self:ResetEnemy(true, true) ene = self:GetEnemy() eneValid = IsValid(ene) end end local curWep = selfData.WeaponEntity //if selfData.WeaponAttackState then self:CapabilitiesRemove(CAP_TURN_HEAD) else self:CapabilitiesAdd(CAP_TURN_HEAD) end -- Fixes their heads breaking -- If we have a valid weapon... if IsValid(curWep) && !self:IsBusy("Activities") then -- Weapon Inventory System if !plyControlled then if eneValid then -- Switch to melee if !selfData.IsGuard && IsValid(selfData.WeaponInventory.Melee) && ((eneData.Distance < selfData.MeleeAttackDistance) or (eneData.Distance < 300 && curWep:Clip1() <= 0)) && (self:Health() > self:GetMaxHealth() * 0.25) && curWep != selfData.WeaponInventory.Melee then if self:GetWeaponState() == VJ.WEP_STATE_RELOADING then self:SetWeaponState() end -- Since the reloading can be cut off, reset it back to false, or else it can mess up its behavior! //timer.Remove("wep_reload_reset" .. self:EntIndex()) -- No longer needed selfData.WeaponInventoryStatus = VJ.WEP_INVENTORY_MELEE self:DoChangeWeapon(selfData.WeaponInventory.Melee, true) curWep = selfData.WeaponEntity -- Switch to anti-armor elseif self:GetWeaponState() != VJ.WEP_STATE_RELOADING && IsValid(selfData.WeaponInventory.AntiArmor) && (ene.IsVJBaseSNPC_Tank or ene.VJ_ID_Boss) && curWep != selfData.WeaponInventory.AntiArmor then selfData.WeaponInventoryStatus = VJ.WEP_INVENTORY_ANTI_ARMOR self:DoChangeWeapon(selfData.WeaponInventory.AntiArmor, true) curWep = selfData.WeaponEntity end end if self:GetWeaponState() != VJ.WEP_STATE_RELOADING then -- Reset weapon status from melee to primary if selfData.WeaponInventoryStatus == VJ.WEP_INVENTORY_MELEE && (!eneValid or (eneValid && eneData.Distance >= 300)) then selfData.WeaponInventoryStatus = VJ.WEP_INVENTORY_PRIMARY self:DoChangeWeapon(selfData.WeaponInventory.Primary, true) curWep = selfData.WeaponEntity -- Reset weapon status from anti-armor to primary elseif selfData.WeaponInventoryStatus == VJ.WEP_INVENTORY_ANTI_ARMOR && (!eneValid or (eneValid && !ene.IsVJBaseSNPC_Tank && !ene.VJ_ID_Boss)) then selfData.WeaponInventoryStatus = VJ.WEP_INVENTORY_PRIMARY self:DoChangeWeapon(selfData.WeaponInventory.Primary, true) curWep = selfData.WeaponEntity end end end -- Weapon Reloading if selfData.Weapon_CanReload && !selfData.AttackType && !curWep.IsMeleeWeapon && self:GetWeaponState() == VJ.WEP_STATE_READY && ((!plyControlled && ((!eneValid && curWep:GetMaxClip1() > curWep:Clip1() && (curTime - eneData.TimeSet) > math.random(3, 8) && !self:IsMoving()) or (eneValid && curWep:Clip1() <= 0))) or (plyControlled && selfData.VJ_TheController:KeyDown(IN_RELOAD) && curWep:GetMaxClip1() > curWep:Clip1())) then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE selfData.NextChaseTime = curTime + 2 if !plyControlled then self:SetWeaponState(VJ.WEP_STATE_RELOADING) end if eneValid then self:PlaySoundSystem("WeaponReload") end -- tsayn han e minag yete teshnami ga! self:OnWeaponReload() if selfData.DisableWeaponReloadAnimation then -- Reload animation is disabled if self:GetWeaponState() == VJ.WEP_STATE_RELOADING then self:SetWeaponState() end curWep:SetClip1(curWep:GetMaxClip1()) if curWep.IsVJBaseWeapon then curWep:NPC_Reload() end else -- Controlled by a player... if plyControlled then self:SetWeaponState(VJ.WEP_STATE_RELOADING) playReloadAnimation(self, self:TranslateActivity(PICK(selfData.AnimTbl_WeaponReload))) -- NOT controlled by a player... else -- NPC is hidden, so attempt to crouch reload if eneValid && self:DoCoverTrace(myPos + self:OBBCenter(), ene:EyePos(), false, {SetLastHiddenTime = true}) then -- if It does NOT have a cover reload animation, then just play the regular standing reload animation if !playReloadAnimation(self, self:TranslateActivity(PICK(selfData.AnimTbl_WeaponReloadCovered))) then playReloadAnimation(self, self:TranslateActivity(PICK(selfData.AnimTbl_WeaponReload))) end -- NPC is NOT hidden... else -- Under certain situations, simply do standing reload without running to a hiding spot if !selfData.Weapon_FindCoverOnReload or selfData.IsGuard or selfData.IsFollowing or selfData.VJ_IsBeingControlled_Tool or !eneValid or selfData.MovementType == VJ_MOVETYPE_STATIONARY or eneData.Distance < 650 then playReloadAnimation(self, self:TranslateActivity(PICK(selfData.AnimTbl_WeaponReload))) else -- If all is good, then run to a hiding spot and reload! local schedule = vj_ai_schedule.New("SCHEDULE_COVER_RELOAD") schedule:EngTask("TASK_FIND_COVER_FROM_ENEMY", 0) schedule:EngTask("TASK_RUN_PATH", 0) schedule:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) schedule.RunCode_OnFinish = function() if self:GetWeaponState() == VJ.WEP_STATE_RELOADING then -- If the current situation isn't favorable, then abandon the current reload, and try again! if self.AttackType or (IsValid(self:GetEnemy()) && eneData.Distance <= self.Weapon_RetreatDistance) then self:SetWeaponState() //timer.Remove("wep_reload_reset" .. self:EntIndex()) -- Remove the timer to make sure it doesn't set reloading to false at a random time (later on) else -- Our hiding spot is good, so reload! playReloadAnimation(self, self:TranslateActivity(PICK(self.AnimTbl_WeaponReload))) end end end self:StartSchedule(schedule) end end end end end end if eneValid then local enePos = ene:GetPos() local eneDist = myPos:Distance(enePos) local eneDistNear = VJ.GetNearestDistance(self, ene, true) local eneIsVisible = plyControlled and true or self:Visible(ene) -- Set latest enemy information self:UpdateEnemyMemory(ene, enePos) eneData.Target = ene eneData.Reset = false eneData.Visible = eneIsVisible eneData.Distance = eneDist eneData.DistanceNearest = eneDistNear local firingWep = selfData.WeaponAttackState && selfData.WeaponAttackState >= VJ.WEP_ATTACK_STATE_FIRE if eneIsVisible then if self:IsInViewCone(enePos) && (eneDist < self:GetMaxLookDistance()) then eneData.VisibleTime = curTime -- Why 2 vars? Because the last "Visible" tick is usually not updated in time, causing the engine to give false positive, thinking the enemy IS visible eneData.VisiblePos = eneData.VisiblePosReal eneData.VisiblePosReal = ene:EyePos() -- Use EyePos because "Visible" uses it to run the trace in the engine! | For origin, use "self:GetEnemyLastSeenPos()" end if firingWep then self:PlaySoundSystem("Suppressing") end elseif firingWep then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE end -- Call for help if selfData.CallForHelp && curTime > selfData.NextCallForHelpT && !selfData.AttackType then self:Allies_CallHelp(selfData.CallForHelpDistance) selfData.NextCallForHelpT = curTime + selfData.CallForHelpCooldown end self:UpdatePoseParamTracking() -- Attacks if !selfData.PauseAttacks && !selfData.Flinching && !selfData.FollowData.StopAct && curTime > selfData.NextDoAnyAttackT && self:GetState() != VJ_STATE_ONLY_ANIMATION_NOATTACK && selfData.Behavior != VJ_BEHAVIOR_PASSIVE && selfData.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE then -- Attack priority in order: Custom --> Melee --> Grenade local funcThinkAtk = self.OnThinkAttack; if funcThinkAtk then funcThinkAtk(self, !!selfData.AttackType, ene) end -- Melee Attack if selfData.HasMeleeAttack && selfData.IsAbleToMeleeAttack && !selfData.AttackType && (!IsValid(curWep) or (IsValid(curWep) && (!curWep.IsMeleeWeapon))) && ((plyControlled && selfData.VJ_TheController:KeyDown(IN_ATTACK)) or (!plyControlled && (eneDistNear < selfData.MeleeAttackDistance && eneIsVisible) && (self:GetHeadDirection():Dot((enePos - myPos):GetNormalized()) > math_cos(math_rad(selfData.MeleeAttackAngleRadius))))) && self:OnMeleeAttack("PreInit", ene) != true then local seed = curTime; selfData.AttackSeed = seed selfData.IsAbleToMeleeAttack = false selfData.AttackType = VJ.ATTACK_TYPE_MELEE selfData.AttackState = VJ.ATTACK_STATE_STARTED selfData.AttackAnim = ACT_INVALID selfData.AttackAnimDuration = 0 selfData.AttackAnimTime = 0 self:SetTurnTarget("Enemy") -- Always turn towards the enemy at the start self:OnMeleeAttack("Init", ene) self:PlaySoundSystem("BeforeMeleeAttack") selfData.NextAlertSoundT = curTime + 0.4 if selfData.AnimTbl_MeleeAttack then local anim, animDur, animType = self:PlayAnim(selfData.AnimTbl_MeleeAttack, false, 0, false) if anim != ACT_INVALID then selfData.AttackAnim = anim selfData.AttackAnimDuration = animDur - (selfData.MeleeAttackAnimationDecreaseLengthAmount / selfData.AnimPlaybackRate) if animType != ANIM_TYPE_GESTURE then -- Allow things like chasing to continue for gestures selfData.AttackAnimTime = curTime + selfData.AttackAnimDuration end end end if !selfData.TimeUntilMeleeAttackDamage then attackTimers[VJ.ATTACK_TYPE_MELEE](self) else -- NOT event based... timer.Create("attack_melee_start" .. self:EntIndex(), selfData.TimeUntilMeleeAttackDamage / selfData.AnimPlaybackRate, selfData.MeleeAttackReps, function() if selfData.AttackSeed == seed then self:ExecuteMeleeAttack() end end) if selfData.MeleeAttackExtraTimers then for k, t in ipairs(selfData.MeleeAttackExtraTimers) do self:AddExtraAttackTimer("attack_melee_start" .. curTime + k, t, function() if selfData.AttackSeed == seed then self:ExecuteMeleeAttack() end end) end end end self:OnMeleeAttack("PostInit", ene) end -- Grenade attack if selfData.HasGrenadeAttack && curTime > selfData.NextThrowGrenadeT && curTime > selfData.TakingCoverT && self:GetWeaponState() != VJ.WEP_STATE_RELOADING && !self:IsBusy("Activities") then if plyControlled then if selfData.VJ_TheController:KeyDown(IN_JUMP) then self:GrenadeAttack() selfData.NextThrowGrenadeT = curTime + self:GetAttackTimer(selfData.NextGrenadeAttackTime) end else local chance = selfData.GrenadeAttackChance -- If chance is above 4, then half it by 2 if the enemy is a tank OR not visible if math.random(1, (chance > 3 && (ene.IsVJBaseSNPC_Tank or !eneIsVisible) and math.floor(chance / 2)) or chance) == 1 && eneDist < selfData.GrenadeAttackMaxDistance && eneDist > selfData.GrenadeAttackMinDistance then self:GrenadeAttack() end selfData.NextThrowGrenadeT = curTime + self:GetAttackTimer(selfData.NextGrenadeAttackTime) end end end else -- No Enemy selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE if !selfData.Alerted && selfData.UpdatedPoseParam && !plyControlled then self:ClearPoseParameters() selfData.UpdatedPoseParam = false end eneData.TimeAcquired = 0 end -- Guarding Behavior if selfData.IsGuard && !selfData.IsFollowing && !selfData.IsVJBaseSNPC_Tank then local guardData = selfData.GuardData if !guardData.Position then -- If we don't have a position, then set it! guardData.Position = myPos guardData.Direction = myPos + self:GetForward() * 51 end -- If it's far from the guarding position, then go there! if !self:IsMoving() && !self:IsBusy("Activities") then local dist = myPos:Distance(guardData.Position) -- Distance to the guard position if dist > 50 then self:SetLastPosition(guardData.Position) self:SCHEDULE_GOTO_POSITION(dist <= 800 and "TASK_WALK_PATH" or "TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} x.RunCode_OnFinish = function() timer.Simple(0.01, function() if IsValid(self) && !self:IsMoving() && !self:IsBusy("Activities") && selfData.IsGuard && guardData.Position then self:SetLastPosition(guardData.Direction) self:SCHEDULE_FACE("TASK_FACE_LASTPOSITION") end end) end end) end end end end -- Handle the unique movement system for player models if selfData.UsePoseParameterMovement && selfData.MovementType == VJ_MOVETYPE_GROUND then local moveDir = VJ.GetMoveDirection(self, true) if moveDir then funcSetPoseParameter(self, "move_x", moveDir.x) funcSetPoseParameter(self, "move_y", moveDir.y) else -- I am not moving, reset the pose parameters, otherwise I will run in place! funcSetPoseParameter(self, "move_x", 0) funcSetPoseParameter(self, "move_y", 0) end end else -- AI Not enabled selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE end //if aiEnabled then //self:MaintainIdleAnimation() //end -- Maintain turning when needed otherwise Engine will take over during movements! -- No longer needed as "OverrideMoveFacing" now handles it! /*if !didTurn then local curTurnData = self.TurnData if curTurnData.Type && curTurnData.LastYaw != 0 then self:SetIdealYawAndUpdate(curTurnData.LastYaw) didTurn = true end end*/ self:NextThink(curTime + 0.065) return true end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:ExecuteMeleeAttack() local selfData = self:GetTable() if selfData.Dead or selfData.PauseAttacks or selfData.Flinching or selfData.AttackType == VJ.ATTACK_TYPE_GRENADE or (selfData.MeleeAttackStopOnHit && selfData.AttackState == VJ.ATTACK_STATE_EXECUTED_HIT) then return end local skip = self:OnMeleeAttackExecute("Init") local hitRegistered = false if !skip then local myPos = self:GetPos() local myClass = self:GetClass() for _, ent in ipairs(ents.FindInSphere(self:MeleeAttackTraceOrigin(), selfData.MeleeAttackDamageDistance)) do if ent == self or ent:GetClass() == myClass or (ent.IsVJBaseBullseye && ent.VJ_IsBeingControlled) then continue end if ent:IsPlayer() && (ent.VJ_IsControllingNPC or !ent:Alive() or VJ_CVAR_IGNOREPLAYERS) then continue end if ((ent.VJ_ID_Living && self:Disposition(ent) != D_LI) or ent.VJ_ID_Attackable or ent.VJ_ID_Destructible) && self:MeleeAttackTraceDirection():Dot((Vector(ent:GetPos().x, ent:GetPos().y, 0) - Vector(myPos.x, myPos.y, 0)):GetNormalized()) > math_cos(math_rad(selfData.MeleeAttackDamageAngleRadius)) then local isProp = ent.VJ_ID_Attackable if self:OnMeleeAttackExecute("PreDamage", ent, isProp) == true then continue end local dmgAmount = self:ScaleByDifficulty(selfData.MeleeAttackDamage) -- Knockback (Don't push things like doors, trains, elevators as it will make them fly when activated) if selfData.HasMeleeAttackKnockBack && ent:GetMoveType() != MOVETYPE_PUSH && ent.MovementType != VJ_MOVETYPE_STATIONARY && (!ent.VJ_ID_Boss or ent.IsVJBaseSNPC_Tank) then local isNextBot = ent:IsNextBot() if !isNextBot then ent:SetGroundEntity(NULL) end local vel = self:MeleeAttackKnockbackVelocity(ent) ent:SetVelocity(vel) if isNextBot then ent.loco:Approach(vel, 1) ent.loco:Jump() ent.loco:SetVelocity(vel) end end -- Apply actual damage if !selfData.DisableDefaultMeleeAttackDamageCode then local applyDmg = DamageInfo() applyDmg:SetDamage(dmgAmount) applyDmg:SetDamageType(selfData.MeleeAttackDamageType) if ent.VJ_ID_Living then applyDmg:SetDamageForce(self:GetForward() * ((applyDmg:GetDamage() + 100) * 70)) end applyDmg:SetInflictor(self) applyDmg:SetAttacker(self) VJ.DamageSpecialEnts(self, ent, applyDmg) ent:TakeDamageInfo(applyDmg, self) end if ent:IsPlayer() then ent:ViewPunch(Angle(math.random(-1, 1) * dmgAmount, math.random(-1, 1) * dmgAmount, math.random(-1, 1) * dmgAmount)) end if !isProp then -- Only for non-props... hitRegistered = true if selfData.MeleeAttackStopOnHit then break end end end end end if selfData.AttackState < VJ.ATTACK_STATE_EXECUTED then selfData.AttackState = VJ.ATTACK_STATE_EXECUTED if selfData.TimeUntilMeleeAttackDamage then attackTimers[VJ.ATTACK_TYPE_MELEE](self) end end if !skip then if hitRegistered then self:PlaySoundSystem("MeleeAttack") selfData.AttackState = VJ.ATTACK_STATE_EXECUTED_HIT else self:OnMeleeAttackExecute("Miss") self:PlaySoundSystem("MeleeAttackMiss") end end end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Triggers a grenade attack - customEnt = What entity it should throw | DEFAULT: nil - nil = Spawn the NPC's default grenade class usually set by "self.GrenadeAttackEntity" - string = Override to use given entity class - entity = Override to use the given entity as the grenade by changing its parent to the NPC and altering its position - disableOwner = If true, the NPC will not be set as the owner of the grenade, allowing it to damage itself and its allies when applicable! Returns - [boolean] = Whether or not it successfully triggered the attack -----------------------------------------------------------]] function ENT:GrenadeAttack(customEnt, disableOwner) if self.Dead or self.Flinching or self.AttackType == VJ.ATTACK_TYPE_MELEE then return false end local eneData = self.EnemyData local ene = eneData.Target local isLiveEnt = IsValid(customEnt) local landDir = "FindBest" -- Handle possible destinations: -- Enemy not valid --> Best position away from the NPC and allies -- Enemy valid AND Enemy visible --> Enemy's position -- Enemy valid AND Enemy not visible AND last seen position is visible --> Enemy's last visible position -- Enemy valid AND Enemy not visible AND last seen position is not visible --> Cancel attack -- Enemy valid AND Enemy not visible AND last seen position is not visible AND grenade is live throw back --> Best position away from the NPC and allies if IsValid(ene) then -- If enemy is visible then face them! if eneData.Visible then landDir = "Enemy" -- Do NOT face random pos, even if "self.GrenadeAttackAnimationFaceEnemy" is disabled! else -- We have a hidden enemy... -- Attempt to flush the enemy out of hiding if self:VisibleVec(eneData.VisiblePos) && ene:GetPos():Distance(eneData.VisiblePos) <= self.GrenadeAttackMaxDistance then // self.GrenadeAttackMaxDistance landDir = "EnemyLastVis" -- We are going to face flush position, do NOT face random pos! -- If can't flush the enemy, then face random open position ONLY if we are given a live entity, otherwise... -- If live entity is NOT given and it's allowed to continue, it will cause the NPC to throw a grenade when both the enemy and its flush position are hidden! elseif !isLiveEnt then return false end end end if self:OnGrenadeAttack("Init", customEnt) then return false end local seed = CurTime(); self.AttackSeed = seed -- Handle animations self.AttackAnim = ACT_INVALID self.AttackAnimDuration = 0 self.AttackAnimTime = 0 if self.AnimTbl_GrenadeAttack then local anim, animDur = self:PlayAnim(self.AnimTbl_GrenadeAttack, false, 0, false) if anim != ACT_INVALID then self.AttackAnim = anim self.AttackAnimDuration = animDur self.AttackAnimTime = seed + self.AttackAnimDuration end end if landDir == "Enemy" then -- Face enemy if self.GrenadeAttackAnimationFaceEnemy then self:SetTurnTarget("Enemy") end elseif landDir == "EnemyLastVis" then -- Face enemy's last visible pos self:SetTurnTarget(eneData.VisiblePos, self.AttackAnimDuration or 1.5) else -- Face best pos local bestPos = PICK(VJ.TraceDirections(self, "Quick", 200, true, false, 8)) if bestPos then landDir = bestPos -- Save the position so it can be used when it's thrown self:SetTurnTarget(bestPos, self.AttackAnimDuration or 1.5) end end -- Handle situation where already spawned entity is given | EX: Grenade picked up by the NPC if isLiveEnt then customEnt.VJ_ST_Grabbed = true customEnt.VJ_ST_GrabOrgMoveType = customEnt:GetMoveType() -- Change the grenade's position so the NPC is actively holding it, in order with priority: -- 1. CUSTOM If custom position is given then use that, otherwise... -- 2. ATTACHMENT If a valid attachment is given then use that, otherwise... -- 3. BONE If a valid bone is given then use that, otherwise... -- 4. FAIL Use the NPC's shoot position (fail safe) local customPos = self:OnGrenadeAttack("SpawnPos", customEnt, landDir) if !customPos then -- If no custom position is given... local getAttach = self:LookupAttachment(self.GrenadeAttackAttachment) if !getAttach or getAttach == 0 or getAttach == -1 then -- Attachment invalid, try bone... local getBone = self:LookupBone(self.GrenadeAttackBone) if getBone then -- Bone valid local bonePos, boneAng = self:GetBonePosition(getBone) customEnt:SetPos(bonePos) customEnt:SetAngles(boneAng) customEnt:FollowBone(self, getBone) -- Calls SetParent internally else -- Fail safe! (All cases failed) customEnt:SetPos(self:GetShootPos()) //customEnt:SetAngles(self:GetShootPos():Angle()) -- No need as this is a fail safe anyway customEnt:SetParent(self) end else -- Attachment valid local attachData = self:GetAttachment(getAttach) customEnt:SetMoveType(MOVETYPE_NONE) -- Must set this for attachments to have any effect! customEnt:SetParent(self, getAttach) customEnt:SetPos(attachData.Pos) customEnt:SetAngles(attachData.Ang) end else -- Custom position valid customEnt:SetPos(customPos) customEnt:SetAngles(customPos:Angle()) customEnt:SetParent(self) end end self.AttackType = VJ.ATTACK_TYPE_GRENADE self.AttackState = VJ.ATTACK_STATE_STARTED self:PlaySoundSystem("GrenadeAttack") local releaseTime = self.GrenadeAttackThrowTime if !releaseTime then -- Call this right away for event-based attacks! attackTimers[VJ.ATTACK_TYPE_GRENADE](self) end -- "attack_grenade_start" is still called on event-based attacks unlike other attacks because we need to retain the data (customEnt, disableOwner, landDir)... -- ...But the timer will be based off of "attack_grenade_reset" to be used as a fail safe in case the animation is cut off! -- Call "timer.Adjust("attack_grenade_start" .. self:EntIndex(), 0)" in the event code to make it throw the grenade timer.Create("attack_grenade_start" .. self:EntIndex(), (!releaseTime and timer.TimeLeft("attack_grenade_reset" .. self:EntIndex())) or releaseTime / self.AnimPlaybackRate, 1, function() if self.AttackSeed == seed then if isLiveEnt && !IsValid(customEnt) then return end self:ExecuteGrenadeAttack(customEnt, disableOwner, landDir) end end) self:OnGrenadeAttack("PostInit", customEnt, landDir) return true end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Performs a grenade attack - customEnt = What entity it should throw | DEFAULT: nil - nil = Spawn the NPC's default grenade class usually set by "self.GrenadeAttackEntity" - string = Spawn the given entity class as the grenade - entity = Use the given entity as the grenade by changing its parent to the NPC and altering its position - disableOwner = If set to true, the NPC will not be set as the owner of the grenade, allowing it to damage itself and its allies when applicable! - landDir = Direction the grenade should land | Used by the base to align where the grenade is gonna land - "Enemy" = Use enemy's position - "EnemyLastVis" = Use enemy's last visible position - vector = Use given vector's position - "FindBest" or nil = Find a best random position Returns - false, Grenade attack was canceled - entity, Grenade entity that was thrown -----------------------------------------------------------]] function ENT:ExecuteGrenadeAttack(customEnt, disableOwner, landDir) if self.Dead or self.PauseAttacks or self.Flinching or self.AttackType == VJ.ATTACK_TYPE_MELEE then return false end local grenade; local isLiveEnt = IsValid(customEnt) -- Whether or not the given custom entity is live local fuseTime = self.GrenadeAttackFuseTime -- Handle the grenade's spawn position and angle, in order with priority: -- 1. CUSTOM If custom position is given then use that, otherwise... -- 2. ATTACHMENT If a valid attachment is given then use that, otherwise... -- 3. BONE If a valid bone is given then use that, otherwise... -- 4. FAIL Use the NPC's shoot position (fail safe) local spawnPos = self:OnGrenadeAttack("SpawnPos", customEnt, landDir) local spawnAng; if !spawnPos then -- If no custom position is given... local getAttach = self:LookupAttachment(self.GrenadeAttackAttachment) if !getAttach or getAttach == 0 or getAttach == -1 then -- Attachment invalid, try bone... local getBone = self:LookupBone(self.GrenadeAttackBone) if getBone then -- Bone valid spawnPos, spawnAng = self:GetBonePosition(getBone) else -- Fail safe! (All cases failed) spawnPos = self:GetShootPos() spawnAng = self:GetAngles() end else -- Attachment valid local attachData = self:GetAttachment(getAttach) spawnPos = attachData.Pos spawnAng = attachData.Ang end else -- Custom position valid spawnAng = spawnPos:Angle() end -- Handle NPC turning and grenade landing position -- Do NOT set it to actually turn & face because it's pointless at this point since the grenade is already being released! local landingPos = self:GetPos() + self:GetForward()*200 if landDir == "Enemy" then -- Use enemy's position landingPos = self:GetEnemyLastKnownPos() //if self.GrenadeAttackAnimationFaceEnemy then self:SetTurnTarget("Enemy") end elseif landDir == "EnemyLastVis" then -- Use enemy's last visible position local eneData = self.EnemyData landingPos = eneData.VisiblePos //self:SetTurnTarget(eneData.VisiblePos, self.AttackAnimDuration - self.GrenadeAttackThrowTime) elseif isvector(landDir) then -- Use given vector's position landingPos = landDir else -- Find a best random position | Includes "FindBest" local bestPos = PICK(VJ.TraceDirections(self, "Quick", 200, true, false, 8)) if bestPos then landingPos = bestPos //self:SetTurnTarget(bestPos, self.AttackAnimDuration - self.GrenadeAttackThrowTime) end end -- If its a live entity then clean it up and set it as the grenade... -- Otherwise, create a new entity with the given custom entity name OR one of NPC's default grenades if isLiveEnt then -- It's an existing entity customEnt.VJ_ST_Grabbed = false -- Set this to false, as we are no longer holding it! -- Clean up by removing the parent, move type, and follow bone effect customEnt:SetParent(NULL) if customEnt:GetMoveType() == MOVETYPE_NONE && customEnt.VJ_ST_GrabOrgMoveType then customEnt:SetMoveType(customEnt.VJ_ST_GrabOrgMoveType) end customEnt:RemoveEffects(EF_FOLLOWBONE) grenade = customEnt //customEnt:Remove() else grenade = ents.Create(customEnt or PICK(self.GrenadeAttackEntity)) if !customEnt then -- Skip model override if function is called with a custom entity string local setModel = PICK(self.GrenadeAttackModel) if setModel then grenade:SetModel(setModel) end end end if !disableOwner then grenade:SetOwner(self) end grenade:SetPos(spawnPos) grenade:SetAngles(spawnAng) if !isLiveEnt then -- Set the fuse timers for all the different grenade entities local gerClass = grenade:GetClass() if gerClass == "obj_vj_grenade" then grenade.FuseTime = fuseTime elseif gerClass == "npc_grenade_frag" then grenade:Input("SetTimer", grenade:GetOwner(), grenade:GetOwner(), fuseTime) elseif gerClass == "obj_cpt_grenade" then grenade:SetTimer(fuseTime) elseif gerClass == "obj_spore" then grenade:SetGrenade(true) elseif gerClass == "ent_hl1_grenade" then grenade:ShootTimed(grenade:GetOwner(), defPos, fuseTime) elseif gerClass == "doom3_grenade" or gerClass == "obj_handgrenade" then grenade:SetExplodeDelay(fuseTime) elseif gerClass == "cw_grenade_thrown" or gerClass == "cw_flash_thrown" or gerClass == "cw_smoke_thrown" then grenade:SetOwner(self) grenade:Fuse(fuseTime) end self:OnGrenadeAttackExecute("PreSpawn", grenade, customEnt, landDir, landingPos) grenade:Spawn() grenade:Activate() end -- Handle throw velocity local postSpawnResult = self:OnGrenadeAttackExecute("PostSpawn", grenade, customEnt, landDir, landingPos) if postSpawnResult != true then local vel = postSpawnResult or ((landingPos - grenade:GetPos()) + (self:GetUp()*200 + self:GetForward()*500 + self:GetRight()*math.Rand(-20, 20))) local phys = grenade:GetPhysicsObject() if IsValid(phys) then phys:Wake() phys:AddAngleVelocity(Vector(math.Rand(500, 500), math.Rand(500, 500), math.Rand(500, 500))) phys:SetVelocity(vel) else -- If we don't have a physics object, then attempt to set the entity's velocity directly grenade:SetVelocity(vel) end end if self.AttackState < VJ.ATTACK_STATE_EXECUTED then self.AttackState = VJ.ATTACK_STATE_EXECUTED if self.GrenadeAttackThrowTime then attackTimers[VJ.ATTACK_TYPE_GRENADE](self) end end return grenade end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- local sdBitSource = bit.bor(SOUND_DANGER, SOUND_CONTEXT_REACT_TO_SOURCE) ---> Combine dropship impact position, Combine gunship turret impact position, Strider minigun impact position local sdBitCombine = bit.bor(SOUND_DANGER, SOUND_CONTEXT_EXCLUDE_COMBINE) ---> Flechette impact position, Strider foot impact position local sdBitPlyVehicle = bit.bor(SOUND_DANGER, SOUND_CONTEXT_PLAYER_VEHICLE) ---> Player driving a vehicle local sdBitMortar = bit.bor(SOUND_DANGER, SOUND_CONTEXT_MORTAR) ---> Combine mortars impact position 3 types of danger detections: - ent.VJ_ID_Grenade - Detected as a grenade - Distance based on self.DangerDetectionDistance - Ignores grenades from allies - BEST USE: Grenade type of entities - ent.VJ_ID_Danger - Detected as a danger - Distance based on self.DangerDetectionDistance - Ignores dangers from allies - BEST USE: Entities that should NOT scare the owner's allies, commonly used for projectiles - NPC Conditions - Detected as a danger - Distance based on the sound hint's volume/distance - Does NOT ignore, is detected by everyone that catches the hint, including allies - BEST USE: Sounds that should scare the owner's allies -----------------------------------------------------------]] function ENT:CheckForDangers() local selfData = self:GetTable() if !selfData.CanDetectDangers or selfData.AttackType == VJ.ATTACK_TYPE_GRENADE or selfData.NextDangerDetectionT > CurTime() then return end local regDangerDetected = false -- A regular non-grenade danger has been found (This is done to make sure grenades take priority over other dangers!) for _, ent in ipairs(ents.FindInSphere(self:GetPos(), selfData.DangerDetectionDistance)) do if (ent.VJ_ID_Danger or ent.VJ_ID_Grenade) && self:Visible(ent) then local owner = ent:GetOwner() if !(IsValid(owner) && owner.IsVJBaseSNPC && ((self:GetClass() == owner:GetClass()) or (self:Disposition(owner) == D_LI))) then if ent.VJ_ID_Danger then regDangerDetected = ent continue end -- If it's a regular danger then just skip it for now local funcCustom = self.OnDangerDetected; if funcCustom then funcCustom(self, VJ.DANGER_TYPE_GRENADE, ent) end local curTime = CurTime() if !self:PlaySoundSystem("GrenadeSight") then self:PlaySoundSystem("DangerSight") end -- No grenade sight sounds? See if we have danger sight sounds selfData.NextDangerDetectionT = curTime + 4 selfData.TakingCoverT = curTime + 4 -- If has the ability to throw it back, then throw the grenade! if selfData.CanRedirectGrenades && selfData.HasGrenadeAttack && ent.VJ_ID_Grabbable && !ent.VJ_ST_Grabbed && ent:GetVelocity():Length() < 400 && VJ.GetNearestDistance(self, ent) < 100 && self:GrenadeAttack(ent, true) then selfData.NextGrenadeAttackSoundT = curTime + 3 return end -- Was not able to throw back the grenade, so take cover instead! self:SCHEDULE_COVER_ORIGIN("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) return end end end if regDangerDetected or funcHasCondition(self, COND_HEAR_DANGER) or funcHasCondition(self, COND_HEAR_PHYSICS_DANGER) or funcHasCondition(self, COND_HEAR_MOVE_AWAY) then local funcCustom = self.OnDangerDetected if funcCustom then if regDangerDetected then funcCustom(self, VJ.DANGER_TYPE_ENTITY, regDangerDetected) else funcCustom(self, VJ.DANGER_TYPE_HINT, nil) end end self:PlaySoundSystem("DangerSight") local curTime = CurTime() selfData.NextDangerDetectionT = curTime + 4 selfData.TakingCoverT = curTime + 4 self:SCHEDULE_COVER_ORIGIN("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) return end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:StopAttacks(checkTimers) if !self:Alive() then return end local selfData = self:GetTable() if selfData.VJ_DEBUG && GetConVar("vj_npc_debug_attack"):GetInt() == 1 then VJ.DEBUG_Print(self, "StopAttacks", "Attack type = " .. selfData.AttackType) end if checkTimers && selfData.AttackType == VJ.ATTACK_TYPE_MELEE && selfData.AttackState < VJ.ATTACK_STATE_EXECUTED then attackTimers[VJ.ATTACK_TYPE_MELEE](self, true) end selfData.AttackType = VJ.ATTACK_TYPE_NONE selfData.AttackState = VJ.ATTACK_STATE_DONE selfData.AttackSeed = 0 self:MaintainAlertBehavior() end --------------------------------------------------------------------------------------------------------------------------------------------- local function math_angDif(diff) diff = diff % 360 return diff > 180 and (diff - 360) or diff end -- function ENT:UpdatePoseParamTracking(resetPoses) local selfData = self:GetTable() if !selfData.HasPoseParameterLooking or (!selfData.VJ_IsBeingControlled && (!selfData.WeaponAttackState or (!selfData.EnemyData.Visible && selfData.WeaponAttackState < VJ.WEP_ATTACK_STATE_FIRE))) then return end //VJ.GetPoseParameters(self) local ene = self:GetEnemy() local newPitch = 0 local newYaw = 0 local newRoll = 0 if !resetPoses && IsValid(ene) then local myEyePos = self:EyePos() local myAng = self:GetAngles() local eneAng = (self:GetAimPosition(ene, myEyePos) - myEyePos):Angle() newPitch = math_angDif(eneAng.p - myAng.p) if selfData.PoseParameterLooking_InvertPitch then newPitch = -newPitch end newYaw = math_angDif(eneAng.y - myAng.y) if selfData.PoseParameterLooking_InvertYaw then newYaw = -newYaw end newRoll = math_angDif(eneAng.z - myAng.z) if selfData.PoseParameterLooking_InvertRoll then newRoll = -newRoll end elseif !selfData.PoseParameterLooking_CanReset then return -- Should it reset its pose parameters if there is no enemies? end local funcCustom = self.OnUpdatePoseParamTracking; if funcCustom then funcCustom(self, newPitch, newYaw, newRoll) end local names = selfData.PoseParameterLooking_Names local namesPitch = names.pitch local namesYaw = names.yaw local namesRoll = names.roll local speed = selfData.PoseParameterLooking_TurningSpeed for x = 1, #namesPitch do local pose = namesPitch[x] funcSetPoseParameter(self, pose, math_angApproach(funcGetPoseParameter(self, pose), newPitch, speed)) end for x = 1, #namesYaw do local pose = namesYaw[x] funcSetPoseParameter(self, pose, math_angApproach(funcGetPoseParameter(self, pose), newYaw, speed)) end for x = 1, #namesRoll do local pose = namesRoll[x] funcSetPoseParameter(self, pose, math_angApproach(funcGetPoseParameter(self, pose), newRoll, speed)) end selfData.UpdatedPoseParam = true end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Determines whether it's about to fire its current weapon - checkDistance = Should it check for distance and weapon time too? | DEFAULT = false - checkDistanceOnly = Should it only check the above statement? | DEFAULT = false Returns - Boolean, Whether or not it can fire its active weapon -----------------------------------------------------------]] function ENT:CanFireWeapon(checkDistance, checkDistanceOnly) if self:OnWeaponCanFire() == false then return false end local hasDist = false local hasChecks = false local selfData = self:GetTable() local curWep = selfData.WeaponEntity if selfData.PauseAttacks or !IsValid(curWep) or self:GetWeaponState() != VJ.WEP_STATE_READY then return false end if selfData.VJ_IsBeingControlled then checkDistance = false else local enemyDist = selfData.EnemyData.Distance if checkDistance && CurTime() > selfData.NextWeaponAttackT && enemyDist < selfData.Weapon_MaxDistance && ((enemyDist > selfData.Weapon_MinDistance) or curWep.IsMeleeWeapon) then hasDist = true end if checkDistanceOnly then return hasDist end end if !selfData.AttackType && !self:IsBusy("Activities") then hasChecks = true if !checkDistance then return true end end return hasDist && hasChecks end --------------------------------------------------------------------------------------------------------------------------------------------- local schedule_yield_player = vj_ai_schedule.New("SCHEDULE_YIELD_PLAYER") schedule_yield_player:EngTask("TASK_MOVE_AWAY_PATH", 120) schedule_yield_player:EngTask("TASK_RUN_PATH", 0) schedule_yield_player:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) schedule_yield_player.CanShootWhenMoving = true schedule_yield_player.TurnData = {} -- This is constantly edited! local bitsDanger = bit.bor(SOUND_BULLET_IMPACT, SOUND_COMBAT, SOUND_WORLD, SOUND_DANGER) // SOUND_PLAYER, SOUND_PLAYER_VEHICLE -- function ENT:SelectSchedule() local selfData = self:GetTable() if selfData.VJ_IsBeingControlled or selfData.Dead then return end local curTime = CurTime() local ene = self:GetEnemy() local eneValid = IsValid(ene) self:PlayIdleSound(nil, nil, eneValid) -- Idle Behavior -- if !eneValid then if selfData.AttackType != VJ.ATTACK_TYPE_GRENADE then self:MaintainIdleBehavior() end if !selfData.Alerted then selfData.TakingCoverT = 0 end selfData.Weapon_UnarmedBehavior_Active = false -- Investigation: Conditions // funcHasCondition(self, COND_HEAR_PLAYER) if selfData.CanInvestigate && (funcHasCondition(self, COND_HEAR_BULLET_IMPACT) or funcHasCondition(self, COND_HEAR_COMBAT) or funcHasCondition(self, COND_HEAR_WORLD) or funcHasCondition(self, COND_HEAR_DANGER)) && selfData.NextInvestigationMove < curTime && selfData.TakingCoverT < curTime && !self:IsBusy() then local sdSrc = self:GetBestSoundHint(bitsDanger) if sdSrc then //PrintTable(sdSrc) local allowed = true local sdOwner = sdSrc.owner if IsValid(sdOwner) then -- Ignore dangers produced by vehicles driven by an allies if sdSrc.type == SOUND_DANGER && sdOwner:IsVehicle() && IsValid(sdOwner:GetDriver()) && self:Disposition(sdOwner:GetDriver()) == D_LI then allowed = false -- Ignore dangers by allies and combat sounds (such as death sounds) from dead NPCs elseif self:Disposition(sdOwner) == D_LI or (sdSrc.type == SOUND_COMBAT && sdOwner:IsNPC() && !sdOwner:Alive()) then allowed = false end end -- For now ignore player sounds because friendly NPCs also see it since the sound owner is NULL //if sdSrc.type == SOUND_PLAYER then // if VJ_CVAR_IGNOREPLAYERS or self:IsMoving() or self.IsGuard then // skip = true // end //end if allowed then self:DoReadyAlert() self:StopMoving() self:SetLastPosition(sdSrc.origin) self:SCHEDULE_FACE("TASK_FACE_LASTPOSITION") -- Works but just faces the enemy that fired at //local sched = vj_ai_schedule.New("SCHEDULE_HEAR_SOUND") //sched:EngTask("TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION", 0) //sched:EngTask("TASK_STOP_MOVING", 0) //sched:EngTask("TASK_FACE_SAVEPOSITION", 0) //self:StartSchedule(sched) self:OnInvestigate(sdOwner) self:PlaySoundSystem("Investigate") selfData.TakingCoverT = curTime + 1 end end end -- Combat Behavior -- else local wep = self:GetActiveWeapon() local eneData = selfData.EnemyData -- Check for weapon validity if !IsValid(wep) then -- Scared behavior system if selfData.Weapon_UnarmedBehavior then if !self:IsBusy() && curTime > selfData.NextChaseTime then selfData.Weapon_UnarmedBehavior_Active = true -- Tells the idle system to use the scared behavior animation if !selfData.IsFollowing && eneData.Visible then self:SCHEDULE_COVER_ENEMY("TASK_RUN_PATH") return end end -- If it doesn't do scared behavior, then make it chase the enemy if it can melee! elseif selfData.HasMeleeAttack then selfData.Weapon_UnarmedBehavior_Active = false -- In case it was scared, return it back to normal selfData.NextDangerDetectionT = curTime + 4 -- Ignore dangers while chasing! self:MaintainAlertBehavior() return end self:MaintainIdleBehavior(2) //return -- Allow other behaviors like "COND_PLAYER_PUSHING", etc to run else selfData.Weapon_UnarmedBehavior_Active = false -- In case it was scared, return it back to normal local enePos_Eye = ene:EyePos() local myPos = self:GetPos() local myPosCentered = myPos + self:OBBCenter() -- Retreat from enemy if it's to close if eneData.Distance <= selfData.Weapon_RetreatDistance && !wep.IsMeleeWeapon && curTime > selfData.TakingCoverT && curTime > selfData.NextChaseTime && !selfData.AttackType && !selfData.IsFollowing && ene.Behavior != VJ_BEHAVIOR_PASSIVE && !self:DoCoverTrace(myPosCentered, enePos_Eye) then local moveCheck = PICK(VJ.TraceDirections(self, "Quick", 200, true, false, 8, true)) if moveCheck then self:SetLastPosition(moveCheck) if self:GetWeaponState() == VJ.WEP_STATE_RELOADING then self:SetWeaponState() end selfData.TakingCoverT = curTime + 2 self:SCHEDULE_GOTO_POSITION("TASK_RUN_PATH", function(x) x:EngTask("TASK_FACE_ENEMY", 0) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) goto goto_conditions end end if self:CanFireWeapon(false, false) && self:GetState() != VJ_STATE_ONLY_ANIMATION_NOATTACK then -- Enemy to far away or not allowed to fire a weapon if eneData.Distance > selfData.Weapon_MaxDistance or curTime < selfData.NextWeaponAttackT then self:MaintainAlertBehavior() selfData.AllowWeaponOcclusionDelay = false -- Check if enemy is in sight, then continue... elseif self:CanFireWeapon(true, true) then -- I can't see the enemy from my eyes if self:DoCoverTrace(self:EyePos(), enePos_Eye, true) then // or (!eneData.Visible) if selfData.TakingCoverT > curTime then return end -- Do NOT interrupt when taking cover (such as "CombatDamageResponse") if self:GetWeaponState() != VJ.WEP_STATE_RELOADING then -- Wait when enemy is occluded if selfData.Weapon_OcclusionDelay && selfData.WeaponAttackState != VJ.WEP_ATTACK_STATE_AIM_OCCLUSION && !wep.IsMeleeWeapon && selfData.AllowWeaponOcclusionDelay && (curTime - selfData.WeaponLastShotTime) <= 4.5 && eneData.Distance > selfData.Weapon_OcclusionDelayMinDist then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_AIM_OCCLUSION self:MaintainIdleBehavior(2) -- Make it play idle stand (Which will turn into ACT_IDLE_ANGRY) selfData.NextChaseTime = curTime + math.Rand(selfData.Weapon_OcclusionDelayTime.a, selfData.Weapon_OcclusionDelayTime.b) -- I am hidden, so stand up in case I am crouching if I had detected to be in a hidden position and the enemy may be visible! elseif curTime < selfData.LastHiddenZoneT && !self:DoCoverTrace(myPosCentered + self:GetUp()*30, enePos_Eye + self:GetUp()*30, true) then self:MaintainIdleBehavior(2) -- Make it play idle stand (Which will turn into ACT_IDLE_ANGRY) goto goto_checkwep else -- Everything failed, go after the enemy! if selfData.WeaponAttackState && selfData.WeaponAttackState >= VJ.WEP_ATTACK_STATE_FIRE && selfData.CurrentScheduleName != "SCHEDULE_ALERT_CHASE" && selfData.CurrentScheduleName != "SCHEDULE_ALERT_CHASE_LOS" then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE end self:MaintainAlertBehavior() end end goto goto_conditions end -- I can see the enemy... ::goto_checkwep:: if wep.IsVJBaseWeapon then -- VJ Base weapons -- Do proper weapon aim turning, based on "FInAimCone" - https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/game/server/ai_basenpc.cpp#L2584 if !selfData.HasPoseParameterLooking then -- Pose parameter looking is disabled then always face self:SetTurnTarget("Enemy") else local wepDif = selfData.Weapon_AimTurnDiff or selfData.Weapon_AimTurnDiff_Def local los = ene:GetPos() - myPos los.z = 0 local facingDir = self:GetAngles():Forward() -- Do NOT use sight dir bec some NPCs use their eyes as the dir, it will trick the system to think the NPC is facing the enemy facingDir.z = 0 local coneCalc = facingDir:Dot((los):GetNormalized()) if coneCalc < wepDif then self:SetTurnTarget("Enemy") self:UpdatePoseParamTracking(true) -- Reset pose parameters to help with turning snaps end end // self:MaintainAlertBehavior() -- if covered, try to move forward by calculating the distance between the prop and the NPC local inCover, inCoverTrace = self:DoCoverTrace(myPosCentered, enePos_Eye, false, {SetLastHiddenTime = true}) local inCoverEnt = inCoverTrace.Entity local wepInCover, wepInCoverTrace = self:DoCoverTrace(wep:GetBulletPos(), enePos_Eye, false) local wepInCoverEnt = wepInCoverTrace.Entity //print("Is covered? ", inCover) //print("Is gun covered? ", wepInCover) local inCoverEntLiving = false -- The covered entity is NOT a living entity if IsValid(inCoverEnt) && inCoverEnt.VJ_ID_Living then inCoverEntLiving = true end if !wep.IsMeleeWeapon then -- If friendly in line of fire, then move! if inCoverEntLiving && selfData.WeaponAttackState == VJ.WEP_ATTACK_STATE_FIRE_STAND && curTime > selfData.TakingCoverT && IsValid(wepInCoverEnt) && wepInCoverEnt:IsNPC() && wepInCoverEnt != self && (self:Disposition(wepInCoverEnt) == D_LI or self:Disposition(wepInCoverEnt) == D_NU) && wepInCoverTrace.HitPos:Distance(wepInCoverTrace.StartPos) <= 3000 then local moveCheck = PICK(VJ.TraceDirections(self, "Quick", 50, true, false, 4, true, true)) if moveCheck then self:StopMoving() if selfData.IsGuard then self.GuardData.Position = moveCheck; self.GuardData.Direction = moveCheck + self:GetForward() * 51 end -- Set the guard position to this new position that avoids friendly fire self:SetLastPosition(moveCheck) selfData.NextChaseTime = curTime + 1 self:SCHEDULE_GOTO_POSITION("TASK_WALK_PATH", function(x) x:EngTask("TASK_FACE_ENEMY", 0) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) end end -- NPC is behind cover... if inCover then -- Behind cover and I am taking cover, don't fire! if curTime < selfData.TakingCoverT then goto goto_conditions elseif curTime > selfData.NextMoveOnGunCoveredT && ((inCoverTrace.HitPos:Distance(myPos) > 150 && !inCoverEntLiving) or (wepInCover && !wepInCoverEnt.VJ_ID_Living)) then selfData.AllowWeaponOcclusionDelay = false local nearestPos; local nearestEntPos; if IsValid(inCoverEnt) then nearestPos, nearestEntPos = VJ.GetNearestPositions(self, inCoverEnt) nearestPos.z = myPos.z; nearestEntPos.z = myPos.z -- Floor the Z-axis as it can be used for a movement position! else nearestPos, nearestEntPos = self:NearestPoint(inCoverTrace.HitPos), inCoverTrace.HitPos end nearestEntPos = nearestEntPos - self:GetForward()*15 if nearestPos:Distance(nearestEntPos) <= (selfData.IsGuard and 100 or 1000) then if selfData.IsGuard then self.GuardData.Position = nearestEntPos; self.GuardData.Direction = nearestEntPos + self:GetForward() * 51 end -- Set the guard position to this new position that provides cover self:SetLastPosition(nearestEntPos) //VJ.DEBUG_TempEnt(nearestEntPos, self:GetAngles(), Color(0, 255, 255)) local schedule = vj_ai_schedule.New("SCHEDULE_GOTO_POSITION") schedule:EngTask("TASK_GET_PATH_TO_LASTPOSITION", 0) local coverRunAnim = self:TranslateActivity(PICK(selfData.AnimTbl_MoveToCover)) if VJ.AnimExists(self, coverRunAnim) then self:SetMovementActivity(coverRunAnim) else -- Only shoot if we aren't crouching running! schedule.CanShootWhenMoving = true end schedule:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) schedule.TurnData = {Type = VJ.FACE_ENEMY} //schedule.StopScheduleIfNotMoving_Any = true self:StartSchedule(schedule) //self:SCHEDULE_GOTO_POSITION("TASK_WALK_PATH", function(x) x:EngTask("TASK_FACE_ENEMY", 0) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) end selfData.NextMoveOnGunCoveredT = curTime + 2 return end //else -- NPC is NOT behind cover end end if curTime > selfData.NextWeaponAttackT && curTime > selfData.NextWeaponAttackT_Base then -- Melee weapons if wep.IsMeleeWeapon then self:OnWeaponAttack() local finalAnim = self:TranslateActivity(PICK(selfData.AnimTbl_WeaponAttack)) if curTime > selfData.NextMeleeWeaponAttackT && VJ.AnimExists(self, finalAnim) then // && !VJ.IsCurrentAnim(self, finalAnim) local animDur = VJ.AnimDuration(self, finalAnim) wep.NPC_NextPrimaryFire = animDur -- Make melee weapons dynamically change the next primary fire wep:NPCShoot_Primary() VJ.EmitSound(self, wep.NPC_BeforeFireSound, wep.NPC_BeforeFireSoundLevel, math.Rand(wep.NPC_BeforeFireSoundPitch.a, wep.NPC_BeforeFireSoundPitch.b)) selfData.NextMeleeWeaponAttackT = curTime + animDur selfData.WeaponAttackAnim = finalAnim self:PlayAnim(finalAnim, false, false, true) selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_FIRE_STAND end -- Ranged weapons else selfData.AllowWeaponOcclusionDelay = true local hasAmmo = wep:Clip1() > 0 -- Does it have ammo? if !hasAmmo && selfData.WeaponAttackState != VJ.WEP_ATTACK_STATE_AIM then selfData.WeaponAttackAnim = ACT_INVALID end -- If it's already doing a firing animation, then do NOT restart the animation if VJ.IsCurrentAnim(self, self:TranslateActivity(selfData.WeaponAttackAnim)) then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_FIRE_STAND -- If the current activity isn't the last weapon animation and it's not a transition, then continue elseif self:GetActivity() != selfData.WeaponAttackAnim && self:GetActivity() != ACT_TRANSITION then self:OnWeaponAttack() if selfData.WeaponAttackState == VJ.WEP_ATTACK_STATE_AIM_OCCLUSION then selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE end selfData.WeaponLastShotTime = curTime //selfData.NextWeaponStrafeT = curTime + 2 local finalAnim = false -- Check if the NPC has ammo if !hasAmmo then self:MaintainIdleBehavior(2) -- Make it play idle stand (Which will turn into ACT_IDLE_ANGRY) //finalAnim = self:TranslateActivity(PICK(selfData.AnimTbl_WeaponAim)) selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_AIM else -- Crouch fire local anim_crouch = self:TranslateActivity(PICK(selfData.AnimTbl_WeaponAttackCrouch)) if selfData.Weapon_CanCrouchAttack && !inCover && !wepInCover && eneData.Distance > 500 && VJ.AnimExists(self, anim_crouch) && math.random(1, selfData.Weapon_CrouchAttackChance) == 1 && !self:DoCoverTrace(wep:GetBulletPos() + self:GetUp() * -18, enePos_Eye, true) then finalAnim = anim_crouch -- Standing fire else finalAnim = self:TranslateActivity(PICK(selfData.AnimTbl_WeaponAttack)) end end if finalAnim && VJ.AnimExists(self, finalAnim) && (!VJ.IsCurrentAnim(self, finalAnim) or !selfData.WeaponAttackState) then VJ.EmitSound(self, wep.NPC_BeforeFireSound, wep.NPC_BeforeFireSoundLevel, math.Rand(wep.NPC_BeforeFireSoundPitch.a, wep.NPC_BeforeFireSoundPitch.b)) self:PlayAnim(finalAnim, false, 0, true) selfData.WeaponAttackAnim = finalAnim selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_FIRE_STAND selfData.NextWeaponAttackT_Base = curTime + 0.2 end end end end -- Move randomly when shooting if selfData.Weapon_Strafe && !inCover && !selfData.IsGuard && !selfData.IsFollowing && !wep.IsMeleeWeapon && (!wep.NPC_StandingOnly) && selfData.WeaponAttackState == VJ.WEP_ATTACK_STATE_FIRE_STAND && curTime > selfData.NextWeaponStrafeT && (curTime - eneData.TimeAcquired) > 2 && (eneData.Distance < (selfData.Weapon_MaxDistance / 1.25)) then if self:OnWeaponStrafe() != false then local moveCheck = PICK(VJ.TraceDirections(self, "Radial", math.random(150, 400), true, false, 12, true)) if moveCheck then self:StopMoving() self:SetLastPosition(moveCheck) self:SCHEDULE_GOTO_POSITION(math.random(1, 2) == 1 and "TASK_RUN_PATH" or "TASK_WALK_PATH", function(x) x:EngTask("TASK_FACE_ENEMY", 0) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) end end selfData.NextWeaponStrafeT = curTime + math.Rand(selfData.Weapon_StrafeCooldown.a, selfData.Weapon_StrafeCooldown.b) end else -- None VJ Base weapons self:SetTurnTarget("Enemy") selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_FIRE_STAND self:OnWeaponAttack() selfData.WeaponLastShotTime = curTime //wep:SetClip1(99999) self:SetSchedule(SCHED_RANGE_ATTACK1) end end end end end ::goto_conditions:: -- Handle move away behavior if funcHasCondition(self, COND_PLAYER_PUSHING) && curTime > selfData.TakingCoverT && !self:IsBusy("Activities") then self:PlaySoundSystem("YieldToPlayer") if eneValid then -- Face current enemy schedule_yield_player.TurnData.Type = VJ.FACE_ENEMY_VISIBLE schedule_yield_player.TurnData.Target = nil elseif IsValid(self:GetTarget()) then -- Face current target schedule_yield_player.TurnData.Type = VJ.FACE_ENTITY_VISIBLE schedule_yield_player.TurnData.Target = self:GetTarget() else -- Reset if both others fail! (Remember this is a localized table shared between all NPCs!) schedule_yield_player.TurnData.Type = nil schedule_yield_player.TurnData.Target = nil end self:StartSchedule(schedule_yield_player) selfData.TakingCoverT = curTime + 2 end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:ResetEnemy(checkAllies, checkVis) local selfData = self:GetTable() if selfData.Dead or (selfData.VJ_IsBeingControlled && selfData.VJ_TheControllerBullseye == self:GetEnemy()) then selfData.EnemyData.Reset = false return false end local ene = self:GetEnemy() local eneValid = IsValid(ene) local eneData = selfData.EnemyData local curTime = CurTime() if checkAllies then local getAllies = self:Allies_Check(1000) if getAllies then for _, ally in ipairs(getAllies) do local allyEne = ally:GetEnemy() if IsValid(allyEne) && (curTime - ally.EnemyData.VisibleTime) < selfData.EnemyTimeout && allyEne:Alive() && self:GetPos():Distance(allyEne:GetPos()) <= self:GetMaxLookDistance() && self:CheckRelationship(allyEne) == D_HT then selfData.AllowWeaponOcclusionDelay = false self:ForceSetEnemy(allyEne, false) eneData.VisibleTime = curTime -- Reset the time otherwise it will run "ResetEnemy" none-stop! eneData.Reset = false return false end end end end if checkVis then -- If the current number of reachable enemies is higher then 1, then don't reset local curEnemies = eneData.VisibleCount //selfData.CurrentReachableEnemies if (eneValid && (curEnemies - 1) >= 1) or (!eneValid && curEnemies >= 1) then self:MaintainRelationships() -- Select a new enemy -- Check that the reset enemy wasn't the only visible enemy -- If we don't this, it will call "ResetEnemy" again! if eneData.VisibleCount > 0 then eneData.Reset = false return false end end end if selfData.VJ_DEBUG && GetConVar("vj_npc_debug_resetenemy"):GetInt() == 1 then VJ.DEBUG_Print(self, "ResetEnemy", tostring(ene)) end eneData.Reset = true self:SetNPCState(NPC_STATE_ALERT) timer.Create("alert_reset" .. self:EntIndex(), math.Rand(selfData.AlertTimeout.a, selfData.AlertTimeout.b), 1, function() if !IsValid(self:GetEnemy()) then selfData.Alerted = false self:SetNPCState(NPC_STATE_IDLE) end end) self:OnResetEnemy() local moveToEnemy = false if eneValid then if !selfData.IsFollowing && !selfData.IsGuard && !selfData.IsVJBaseSNPC_Tank && !selfData.VJ_IsBeingControlled && selfData.LastHiddenZone_CanWander == true && !selfData.Weapon_UnarmedBehavior_Active && selfData.Behavior != VJ_BEHAVIOR_PASSIVE && selfData.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && !self:IsBusy() && !self:Visible(ene) && self:GetEnemyLastKnownPos() != defPos then moveToEnemy = self:GetEnemyLastKnownPos() end self:MarkEnemyAsEluded(ene) //self:ClearEnemyMemory(ene) // Completely resets the enemy memory self:AddEntityRelationship(ene, D_NU, 10) end selfData.LastHiddenZone_CanWander = curTime > selfData.LastHiddenZoneT and true or false selfData.LastHiddenZoneT = 0 -- Clear memory of the enemy if it's not a player AND it's dead if eneValid && !ene:IsPlayer() && !ene:Alive() then //print("Clear memory", ene) self:ClearEnemyMemory(ene) end -- This is needed for the human base because when taking cover from enemy, the AI can get stuck in a loop (EX: When selfData.Weapon_UnarmedBehavior_Active is true!) if selfData.CurrentScheduleName == "SCHEDULE_COVER_ENEMY" or selfData.CurrentScheduleName == "SCHEDULE_COVER_ENEMY_FAIL" then self:StopMoving() end selfData.NextWanderTime = curTime + math.Rand(3, 5) self:SetEnemy(NULL) if moveToEnemy then self:SetLastPosition(moveToEnemy) self:SCHEDULE_GOTO_POSITION("TASK_WALK_PATH", function(schedule) //if eneValid then schedule:EngTask("TASK_FORGET", ene) end //schedule:EngTask("TASK_IGNORE_OLD_ENEMIES", 0) schedule.ResetOnFail = true schedule.CanShootWhenMoving = true schedule.CanBeInterrupted = true schedule.TurnData = {Type = VJ.FACE_ENEMY} end) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnTakeDamage(dmginfo) local dmgAttacker = dmginfo:GetAttacker() if !IsValid(dmgAttacker) then dmgAttacker = false end -- Don't take bullet damage from friendly NPCs if dmgAttacker && dmginfo:IsBulletDamage() && dmgAttacker:IsNPC() && dmgAttacker:Disposition(self) != D_HT && (dmgAttacker:GetClass() == self:GetClass() or self:Disposition(dmgAttacker) == D_LI) then return 0 end local dmgInflictor = dmginfo:GetInflictor() if !IsValid(dmgInflictor) then dmgInflictor = false end -- Attempt to avoid taking damage when walking on ragdolls if dmgInflictor && dmgInflictor:GetClass() == "prop_ragdoll" && dmgInflictor:GetVelocity():Length() <= 100 then return 0 end local selfData = self:GetTable() local hitgroup = self:GetLastDamageHitGroup() self:OnDamaged(dmginfo, hitgroup, "Init") if selfData.GodMode or dmginfo:GetDamage() <= 0 then return 0 end local dmgType = dmginfo:GetDamageType() local curTime = CurTime() local isFireEnt = false if self:IsOnFire() then isFireEnt = dmgInflictor && dmgAttacker && dmgInflictor:GetClass() == "entityflame" && dmgAttacker:GetClass() == "entityflame" if self:WaterLevel() > 1 then self:Extinguish() end -- If we are in water, then extinguish the fire end -- If it should always take damage from huge monsters, then skip immunity checks! if dmgAttacker && selfData.ForceDamageFromBosses && dmgAttacker.VJ_ID_Boss then goto skip_immunity end -- Immunity checks if isFireEnt && !selfData.AllowIgnition then self:Extinguish() return 0 end if (selfData.Immune_Fire && (dmgType == DMG_BURN or dmgType == DMG_SLOWBURN or isFireEnt)) or (selfData.Immune_Toxic && (dmgType == DMG_ACID or dmgType == DMG_RADIATION or dmgType == DMG_POISON or dmgType == DMG_NERVEGAS or dmgType == DMG_PARALYZE)) or (selfData.Immune_Bullet && (dmginfo:IsBulletDamage() or dmgType == DMG_BULLET or dmgType == DMG_AIRBOAT or dmgType == DMG_BUCKSHOT or dmgType == DMG_SNIPER)) or (selfData.Immune_Explosive && (dmgType == DMG_BLAST or dmgType == DMG_BLAST_SURFACE or dmgType == DMG_MISSILEDEFENSE)) or (selfData.Immune_Dissolve && dmginfo:IsDamageType(DMG_DISSOLVE)) or (selfData.Immune_Electricity && (dmgType == DMG_SHOCK or dmgType == DMG_ENERGYBEAM or dmgType == DMG_PHYSGUN)) or (selfData.Immune_Melee && (dmgType == DMG_CLUB or dmgType == DMG_SLASH)) or (selfData.Immune_Sonic && dmgType == DMG_SONIC) then return 0 end -- Make sure combine ball does reasonable damage and doesn't spam! if (dmgInflictor && dmgInflictor:GetClass() == "prop_combine_ball") or (dmgAttacker && dmgAttacker:GetClass() == "prop_combine_ball") then if selfData.Immune_Dissolve then return 0 end if curTime > selfData.NextCombineBallDmgT then dmginfo:SetDamage(math.random(400, 500)) dmginfo:SetDamageType(DMG_DISSOLVE) selfData.NextCombineBallDmgT = curTime + 0.2 else return 0 end end ::skip_immunity:: local function DoBleed() if selfData.Bleeds then self:OnBleed(dmginfo, hitgroup) -- Spawn the blood particle only if it's not caused by the default fire entity [Causes the damage position to be at Vector(0, 0, 0)] if selfData.HasBloodParticle && !isFireEnt then self:SpawnBloodParticles(dmginfo, hitgroup) end if selfData.HasBloodDecal then self:SpawnBloodDecals(dmginfo, hitgroup) end self:PlaySoundSystem("Impact") end end if selfData.Dead then DoBleed() return 0 end -- If dead then just bleed but take no damage self:OnDamaged(dmginfo, hitgroup, "PreDamage") if dmginfo:GetDamage() <= 0 then return 0 end -- Only take damage if it's above 0! -- Why? Because GMod resets/randomizes dmginfo after a tick... selfData.SavedDmgInfo = { dmginfo = dmginfo, -- The actual CTakeDamageInfo object | WARNING: Can be corrupted after a tick, recommended not to use this! attacker = dmginfo:GetAttacker(), inflictor = dmginfo:GetInflictor(), amount = dmginfo:GetDamage(), pos = dmginfo:GetDamagePosition(), type = dmginfo:GetDamageType(), force = dmginfo:GetDamageForce(), ammoType = dmginfo:GetAmmoType(), hitgroup = hitgroup, } self:SetHealth(self:Health() - dmginfo:GetDamage()) if selfData.VJ_DEBUG && GetConVar("vj_npc_debug_damage"):GetInt() == 1 then VJ.DEBUG_Print(self, "OnTakeDamage", "Amount = ", dmginfo:GetDamage(), " | Attacker = ", dmgAttacker, " | Inflictor = ", dmgInflictor) end local healthRegen = selfData.HealthRegenParams if healthRegen.Enabled && healthRegen.ResetOnDmg then selfData.HealthRegenDelayT = curTime + (math.Rand(healthRegen.Delay.a, healthRegen.Delay.b) * 1.5) end self:SetSaveValue("m_iDamageCount", self:GetTotalDamageCount() + 1) self:SetSaveValue("m_flLastDamageTime", curTime) self:OnDamaged(dmginfo, hitgroup, "PostDamage") DoBleed() -- I/O events, from: https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/ai_basenpc.cpp#L764 if dmgAttacker then self:TriggerOutput("OnDamaged", dmgAttacker) self:MarkTookDamageFromEnemy(dmgAttacker) else self:TriggerOutput("OnDamaged", self) end local stillAlive = self:Health() > 0 if stillAlive then self:PlaySoundSystem("Pain") end if VJ_CVAR_AI_ENABLED && self:GetState() != VJ_STATE_FREEZE then local isPassive = selfData.Behavior == VJ_BEHAVIOR_PASSIVE or selfData.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE if stillAlive then if !isFireEnt then self:Flinch(dmginfo, hitgroup) end -- Player attackers if dmgAttacker && dmgAttacker:IsPlayer() then -- Become enemy to a friendly player | RESULT: May become hostile to an allied player if selfData.BecomeEnemyToPlayer && self:CheckRelationship(dmgAttacker) == D_LI then local relationMemory = selfData.RelationshipMemory[dmgAttacker] self:SetRelationshipMemory(dmgAttacker, VJ.MEM_HOSTILITY_LEVEL, relationMemory[VJ.MEM_HOSTILITY_LEVEL] and relationMemory[VJ.MEM_HOSTILITY_LEVEL] + 1 or 1) if relationMemory[VJ.MEM_HOSTILITY_LEVEL] > selfData.BecomeEnemyToPlayer && self:Disposition(dmgAttacker) != D_HT then self:OnBecomeEnemyToPlayer(dmginfo, hitgroup) if selfData.IsFollowing && selfData.FollowData.Target == dmgAttacker then self:ResetFollowBehavior() end self:SetRelationshipMemory(dmgAttacker, VJ.MEM_OVERRIDE_DISPOSITION, D_HT) self:AddEntityRelationship(dmgAttacker, D_HT, 2) selfData.TakingCoverT = curTime + 2 self:PlaySoundSystem("BecomeEnemyToPlayer") if !IsValid(self:GetEnemy()) then self:StopMoving() self:SetTarget(dmgAttacker) self:SCHEDULE_FACE("TASK_FACE_TARGET") end if selfData.CanChatMessage then dmgAttacker:PrintMessage(HUD_PRINTTALK, self:GetName() .. " no longer likes you.") end end end -- React to damage by a player -- 0 = Run it every time | 1 = Run it only when friendly to player | 2 = Run it only when enemy to player if selfData.HasDamageByPlayerSounds && curTime > selfData.NextDamageByPlayerSoundT && self:Visible(dmgAttacker) then local dispLvl = selfData.DamageByPlayerDispositionLevel if (dispLvl == 0 or (dispLvl == 1 && self:Disposition(dmgAttacker) == D_LI) or (dispLvl == 2 && self:Disposition(dmgAttacker) != D_HT)) then self:PlaySoundSystem("DamageByPlayer") end end end self:PlaySoundSystem("Pain") -- Move away or hide behind object when damaged while enemy is valid | RESULT: May play a hiding animation OR move to take cover from enemy local eneData = selfData.EnemyData if !isPassive && selfData.CombatDamageResponse && IsValid(eneData.Target) && curTime > selfData.NextCombatDamageResponseT && !selfData.IsFollowing && !selfData.AttackType && !self:IsBusy() && curTime > selfData.TakingCoverT && eneData.Visible && self:GetWeaponState() != VJ.WEP_STATE_RELOADING && eneData.Distance < selfData.Weapon_MaxDistance then local wep = self:GetActiveWeapon() local canMove = true if self:DoCoverTrace(self:GetPos() + self:OBBCenter(), eneData.Target:EyePos()) then local hideTime = math.Rand(selfData.CombatDamageResponse_CoverTime.a, selfData.CombatDamageResponse_CoverTime.b) local anim = self:PlayAnim(selfData.AnimTbl_TakingCover, false, hideTime, false) -- Don't set lockAnim because we want it to shoot if an enemy is suddenly visible! if anim != ACT_INVALID then selfData.NextChaseTime = curTime + hideTime selfData.TakingCoverT = curTime + hideTime selfData.WeaponAttackState = VJ.WEP_ATTACK_STATE_NONE selfData.NextCombatDamageResponseT = curTime + math.random(selfData.CombatDamageResponse_Cooldown.a, selfData.CombatDamageResponse_Cooldown.b) canMove = false end end if canMove && !self:IsMoving() && (!IsValid(wep) or (IsValid(wep) && !wep.IsMeleeWeapon)) then -- Run away if not moving AND has a non-melee weapon self:SCHEDULE_COVER_ENEMY("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) selfData.NextCombatDamageResponseT = curTime + math.random(selfData.CombatDamageResponse_Cooldown.a, selfData.CombatDamageResponse_Cooldown.b) end end if !isPassive && !IsValid(self:GetEnemy()) then local canMove = true -- How allies respond when it's damaged if selfData.DamageAllyResponse && curTime > selfData.NextDamageAllyResponseT && !selfData.IsFollowing then local responseDist = math_max(800, self:OBBMaxs():Distance(self:OBBMins()) * 12) local allies = self:Allies_Check(responseDist) if allies != false then if !isFireEnt then self:Allies_Bring("Diamond", responseDist, allies, 4) end for _, ally in ipairs(allies) do ally:DoReadyAlert() end if !isFireEnt && !self:IsBusy("Activities") then self:DoReadyAlert() local anim = self:PlayAnim(selfData.AnimTbl_DamageAllyResponse, true, false, true) if anim != ACT_INVALID then canMove = false selfData.NextFlinchT = curTime + 1 end end selfData.NextDamageAllyResponseT = curTime + math.Rand(selfData.DamageAllyResponse_Cooldown.a, selfData.DamageAllyResponse_Cooldown.b) end end local dmgResponse = selfData.DamageResponse if dmgResponse && curTime > selfData.TakingCoverT && !self:IsBusy("Activities") then -- Attempt to find who damaged me | RESULT: May become alerted if attacker is visible OR it may hide if it didn't find the attacker if dmgAttacker && (dmgResponse == true or dmgResponse == "OnlySearch") then local sightDist = self:GetMaxLookDistance() sightDist = math_min(math_max(sightDist / 2, sightDist <= 1000 and sightDist or 1000), sightDist) -- IF normal sight dist is less than 1000 then change nothing, OR ELSE use half the distance with 1000 as minimum if self:GetPos():Distance(dmgAttacker:GetPos()) <= sightDist && self:Visible(dmgAttacker) then local dispLvl = self:CheckRelationship(dmgAttacker) if dispLvl == D_HT or dispLvl == D_NU then //self:AddEntityRelationship(dmgAttacker, D_HT, 10) self:OnSetEnemyFromDamage(dmginfo, hitgroup) selfData.NextCallForHelpT = curTime + 1 self:ForceSetEnemy(dmgAttacker, true) self:MaintainAlertBehavior() canMove = false end end end -- If all else failed then take cover! if canMove && (dmgResponse == true or dmgResponse == "OnlyMove") && !selfData.IsFollowing && selfData.MovementType != VJ_MOVETYPE_STATIONARY && dmginfo:GetDamageCustom() != VJ.DMG_BLEED then self:SCHEDULE_COVER_ORIGIN("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.TurnData = {Type = VJ.FACE_ENEMY} end) selfData.TakingCoverT = curTime + 5 end end -- Passive NPCs elseif isPassive && curTime > selfData.TakingCoverT then if selfData.DamageResponse && !self:IsBusy() then self:SCHEDULE_COVER_ORIGIN("TASK_RUN_PATH") end end end -- Make passive NPCs move away | RESULT: May move away AND may cause other passive NPCs to move as well if isPassive && curTime > selfData.TakingCoverT then if selfData.Passive_AlliesRunOnDamage then -- Make passive allies run too! local allies = self:Allies_Check(math_max(800, self:OBBMaxs():Distance(self:OBBMins()) * 20)) if allies != false then for _, ally in ipairs(allies) do ally.TakingCoverT = curTime + math.Rand(6, 7) ally:SCHEDULE_COVER_ORIGIN("TASK_RUN_PATH") ally:PlaySoundSystem("Alert") end end end selfData.TakingCoverT = curTime + math.Rand(6, 7) end end -- If eating, stop! if selfData.CanEat && selfData.VJ_ST_Eating then selfData.EatingData.NextCheck = curTime + 15 self:ResetEatingBehavior("Injured") end if self:Health() <= 0 && !selfData.Dead then self:RemoveEFlags(EFL_NO_DISSOLVE) if (dmginfo:IsDamageType(DMG_DISSOLVE)) or (dmgInflictor && dmgInflictor:GetClass() == "prop_combine_ball") then local dissolve = DamageInfo() dissolve:SetDamage(self:Health()) dissolve:SetAttacker(dmginfo:GetAttacker()) dissolve:SetDamageType(DMG_DISSOLVE) self:TakeDamageInfo(dissolve) end self:BeginDeath(dmginfo, hitgroup) end return 1 end --------------------------------------------------------------------------------------------------------------------------------------------- local vecZ500 = Vector(0, 0, 500) local vecZ4 = Vector(0, 0, 4) -- function ENT:BeginDeath(dmginfo, hitgroup) self.Dead = true self:SetSaveValue("m_lifeState", 1) -- LIFE_DYING self:OnDeath(dmginfo, hitgroup, "Init") if self.MedicData.Status then self:ResetMedicBehavior() end if self.IsFollowing then self:ResetFollowBehavior() end local dmgInflictor = dmginfo:GetInflictor() local dmgAttacker = dmginfo:GetAttacker() local myPos = self:GetPos() if VJ_CVAR_AI_ENABLED then local responseDist = math_max(800, self:OBBMaxs():Distance(self:OBBMins()) * 12) local allies = self:Allies_Check(responseDist) if allies then local doBecomeEnemyToPlayer = (self.BecomeEnemyToPlayer && dmgAttacker:IsPlayer() && !VJ_CVAR_IGNOREPLAYERS) or false local responseType = self.DeathAllyResponse local movedAllyNum = 0 -- Number of allies that have moved for _, ally in ipairs(allies) do ally:OnAllyKilled(self) ally:PlaySoundSystem("AllyDeath") if responseType && myPos:Distance(ally:GetPos()) < responseDist then local moved = false -- Bring ally if responseType == true && movedAllyNum < self.DeathAllyResponse_MoveLimit then moved = self:Allies_Bring("Random", responseDist, {ally}, 0, true) if moved then movedAllyNum = movedAllyNum + 1 end end -- Alert ally if (responseType == true or responseType == "OnlyAlert") && !IsValid(ally:GetEnemy()) then ally:DoReadyAlert() if !moved then local faceTime = math.Rand(5, 8) ally:SetTurnTarget(myPos, faceTime, true) ally.NextIdleTime = CurTime() + faceTime end end end -- BecomeEnemyToPlayer if doBecomeEnemyToPlayer && ally.BecomeEnemyToPlayer && ally:Disposition(dmgAttacker) == D_LI then local relationMemory = ally.RelationshipMemory[dmgAttacker] ally:SetRelationshipMemory(dmgAttacker, VJ.MEM_HOSTILITY_LEVEL, relationMemory[VJ.MEM_HOSTILITY_LEVEL] and relationMemory[VJ.MEM_HOSTILITY_LEVEL] + 1 or 1) if relationMemory[VJ.MEM_HOSTILITY_LEVEL] > ally.BecomeEnemyToPlayer then if ally:Disposition(dmgAttacker) != D_HT then ally:OnBecomeEnemyToPlayer(dmginfo, hitgroup) if ally.IsFollowing && ally.FollowData.Target == dmgAttacker then ally:ResetFollowBehavior() end ally:SetRelationshipMemory(dmgAttacker, VJ.MEM_OVERRIDE_DISPOSITION, D_HT) ally:AddEntityRelationship(dmgAttacker, D_HT, 2) if ally.CanChatMessage then dmgAttacker:PrintMessage(HUD_PRINTTALK, ally:GetName() .. " no longer likes you.") end ally:PlaySoundSystem("BecomeEnemyToPlayer") end ally.Alerted = true end end end end end -- Blood decal on the ground if self.Bleeds && self.HasBloodDecal then local bloodDecal = PICK(self.BloodDecal) if bloodDecal then local decalPos = myPos + vecZ4 self:SetLocalPos(decalPos) -- NPC is too close to the ground, we need to move it up a bit local tr = util.TraceLine({start = decalPos, endpos = decalPos - vecZ500, filter = self}) util.Decal(bloodDecal, tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal) end end self:RemoveTimers() self:StopAllSounds() self.AttackType = VJ.ATTACK_TYPE_NONE self.HasMeleeAttack = false if IsValid(dmgAttacker) then if dmgAttacker:GetClass() == "npc_barnacle" then self.HasDeathCorpse = false end -- Don't make a corpse if it's killed by a barnacle! if vj_npc_ply_frag:GetInt() == 1 && dmgAttacker:IsPlayer() then dmgAttacker:AddFrags(1) end if IsValid(dmgInflictor) then gamemode.Call("OnNPCKilled", self, dmgAttacker, dmgInflictor, dmginfo) end end self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) self:GibOnDeath(dmginfo, hitgroup) self:PlaySoundSystem("Death") //if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) then self:AA_StopMoving() end -- I/O events, from: https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/game/server/basecombatcharacter.cpp#L1582 if IsValid(dmgAttacker) then -- Someone else killed me self:TriggerOutput("OnDeath", dmgAttacker) dmgAttacker:Fire("KilledNPC", "", 0, self, self) -- Allows player companions (npc_citizen) to respond to kill else self:TriggerOutput("OnDeath", self) end -- Handle death animation, death delay, and the final death phase local deathTime = self.DeathDelayTime if IsValid(dmgInflictor) && dmgInflictor:GetClass() == "prop_combine_ball" then self.HasDeathAnimation = false end if self.HasDeathAnimation && VJ_CVAR_AI_ENABLED && !dmginfo:IsDamageType(DMG_REMOVENORAGDOLL) && !dmginfo:IsDamageType(DMG_DISSOLVE) && self:GetNavType() != NAV_CLIMB && math.random(1, self.DeathAnimationChance) == 1 then self:RemoveAllGestures() self:OnDeath(dmginfo, hitgroup, "DeathAnim") local chosenAnim = PICK(self.AnimTbl_Death) local animTime = VJ.AnimDurationEx(self, chosenAnim, self.DeathAnimationTime) - self.DeathAnimationDecreaseLengthAmount self:PlayAnim(chosenAnim, true, animTime, false, 0, {PlayBackRateCalculated = true}) deathTime = deathTime + animTime self.DeathAnimationCodeRan = true else -- If no death anim then just set the NPC to dead even if it has a delayed remove self:SetSaveValue("m_lifeState", 2) -- LIFE_DEAD end if deathTime > 0 then timer.Simple(deathTime, function() if IsValid(self) then self:FinishDeath(dmginfo, hitgroup) end end) else self:FinishDeath(dmginfo, hitgroup) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:FinishDeath(dmginfo, hitgroup) if self.VJ_DEBUG && GetConVar("vj_npc_debug_damage"):GetInt() == 1 then VJ.DEBUG_Print(self, "FinishDeath", "Attacker = ", self.SavedDmgInfo.attacker, " | Inflictor = ", self.SavedDmgInfo.inflictor) end self:SetSaveValue("m_lifeState", 2) -- LIFE_DEAD //self:SetNPCState(NPC_STATE_DEAD) self:OnDeath(dmginfo, hitgroup, "Finish") if self.DropDeathLoot then self:CreateDeathLoot(dmginfo, hitgroup) end if bit.band(self.SavedDmgInfo.type, DMG_REMOVENORAGDOLL) == 0 then self:DeathWeaponDrop(dmginfo, hitgroup) self:CreateDeathCorpse(dmginfo, hitgroup) end self:Remove() end --------------------------------------------------------------------------------------------------------------------------------------------- local colorGrey = Color(90, 90, 90) -- function ENT:CreateDeathCorpse(dmginfo, hitgroup) -- In case it was not set -- NOTE: dmginfo at this point can be incorrect/corrupted, but its better than leaving the self.SavedDmgInfo empty! if !self.SavedDmgInfo then self.SavedDmgInfo = { dmginfo = dmginfo, -- The actual CTakeDamageInfo object | WARNING: Can be corrupted after a tick, recommended not to use this! attacker = dmginfo:GetAttacker(), inflictor = dmginfo:GetInflictor(), amount = dmginfo:GetDamage(), pos = dmginfo:GetDamagePosition(), type = dmginfo:GetDamageType(), force = dmginfo:GetDamageForce(), ammoType = dmginfo:GetAmmoType(), hitgroup = hitgroup, } end if self.HasDeathCorpse && self.HasDeathRagdoll != false then local corpseMdl = self:GetModel() local corpseMdlCustom = PICK(self.DeathCorpseModel) if corpseMdlCustom then corpseMdl = corpseMdlCustom end local corpseClass = "prop_physics" if self.DeathCorpseEntityClass then corpseClass = self.DeathCorpseEntityClass else if util.IsValidRagdoll(corpseMdl) then corpseClass = "prop_ragdoll" elseif !util.IsValidProp(corpseMdl) or !util.IsValidModel(corpseMdl) then if IsValid(self.WeaponEntity) then self.WeaponEntity:Remove() end return false end end self.Corpse = ents.Create(corpseClass) local corpse = self.Corpse corpse:SetModel(corpseMdl) corpse:SetPos(self:GetPos()) corpse:SetAngles(self:GetAngles()) corpse:Spawn() corpse:Activate() corpse:SetSkin(self:GetSkin()) for i = 0, self:GetNumBodyGroups() do corpse:SetBodygroup(i, self:GetBodygroup(i)) end corpse:SetColor(self:GetColor()) corpse:SetMaterial(self:GetMaterial()) if corpseMdlCustom == false && self.DeathCorpseSubMaterials != nil then -- Take care of sub materials for _, x in ipairs(self.DeathCorpseSubMaterials) do if self:GetSubMaterial(x) != "" then corpse:SetSubMaterial(x, self:GetSubMaterial(x)) end end -- This causes lag, not a very good way to do it. /*for x = 0, #self:GetMaterials() do if self:GetSubMaterial(x) != "" then corpse:SetSubMaterial(x, self:GetSubMaterial(x)) end end*/ end //corpse:SetName("corpse" .. self:EntIndex()) //corpse:SetModelScale(self:GetModelScale()) corpse.FadeCorpseType = (corpse:GetClass() == "prop_ragdoll" and "FadeAndRemove") or "kill" corpse.IsVJBaseCorpse = true corpse.DamageInfo = dmginfo corpse.ChildEnts = self.DeathCorpse_ChildEnts or {} corpse.BloodData = {Color = self.BloodColor, Particle = self.BloodParticle, Decal = self.BloodDecal} if self.Bleeds && self.HasBloodPool && vj_npc_blood_pool:GetInt() == 1 then self:SpawnBloodPool(dmginfo, hitgroup, corpse) end -- Collision corpse:SetCollisionGroup(self.DeathCorpseCollisionType) if ai_serverragdolls:GetInt() == 1 then undo.ReplaceEntity(self, corpse) else -- Keep corpses is not enabled... VJ.Corpse_Add(corpse) if vj_npc_corpse_undo:GetInt() == 1 then undo.ReplaceEntity(self, corpse) end -- Undoable end cleanup.ReplaceEntity(self, corpse) -- Delete on cleanup -- On fire if self:IsOnFire() then corpse:Ignite(math.Rand(8, 10), 0) if !self.Immune_Fire then -- Don't darken the corpse if we are immune to fire! corpse:SetColor(colorGrey) //corpse:SetMaterial("models/props_foliage/tree_deciduous_01a_trunk") end end -- Dissolve if (bit.band(self.SavedDmgInfo.type, DMG_DISSOLVE) != 0) or (IsValid(self.SavedDmgInfo.inflictor) && self.SavedDmgInfo.inflictor:GetClass() == "prop_combine_ball") then corpse:Dissolve(0, 1) end -- Bone and Angle -- If it's a bullet, it will use localized velocity on each bone depending on how far away the bone is from the dmg position local useLocalVel = (bit.band(self.SavedDmgInfo.type, DMG_BULLET) != 0 and self.SavedDmgInfo.pos != defPos) or false local dmgForce = (self.SavedDmgInfo.force / 40) + self:GetMoveVelocity() + self:GetVelocity() if self.DeathAnimationCodeRan then useLocalVel = false dmgForce = self:GetGroundSpeedVelocity() end local totalSurface = 0 local physCount = corpse:GetPhysicsObjectCount() for childNum = 0, physCount - 1 do -- 128 = Bone Limit local childPhysObj = corpse:GetPhysicsObjectNum(childNum) if IsValid(childPhysObj) then totalSurface = totalSurface + childPhysObj:GetSurfaceArea() local childPhysObj_BonePos, childPhysObj_BoneAng = self:GetBonePosition(corpse:TranslatePhysBoneToBone(childNum)) if childPhysObj_BonePos then if self.DeathCorpseSetBoneAngles then childPhysObj:SetAngles(childPhysObj_BoneAng) end childPhysObj:SetPos(childPhysObj_BonePos) if self.DeathCorpseApplyForce then childPhysObj:SetVelocity(dmgForce / math_max(1, (useLocalVel and childPhysObj_BonePos:Distance(self.SavedDmgInfo.pos) / 12) or 1)) end -- If it's 1, then it's likely a regular physics model with no bones elseif physCount == 1 then if self.DeathCorpseApplyForce then childPhysObj:SetVelocity(dmgForce / math_max(1, (useLocalVel and corpse:GetPos():Distance(self.SavedDmgInfo.pos) / 12) or 1)) end end end end -- Health & stink system if corpse:Health() <= 0 then local hpCalc = totalSurface / 60 corpse:SetMaxHealth(hpCalc) corpse:SetHealth(hpCalc) end VJ.Corpse_AddStinky(corpse, true) if IsValid(self.WeaponEntity) then corpse.ChildEnts[#corpse.ChildEnts + 1] = self.WeaponEntity end if self.DeathCorpseFade then corpse:Fire(corpse.FadeCorpseType, "", self.DeathCorpseFade) end if vj_npc_corpse_fade:GetInt() == 1 then corpse:Fire(corpse.FadeCorpseType, "", vj_npc_corpse_fadetime:GetInt()) end self:OnCreateDeathCorpse(dmginfo, hitgroup, corpse) if corpse:IsFlagSet(FL_DISSOLVING) then if IsValid(self.WeaponEntity) then self.WeaponEntity:Dissolve(0, 1) end if corpse.ChildEnts then for _, child in ipairs(corpse.ChildEnts) do child:Dissolve(0, 1) end end end corpse:CallOnRemove("vj_" .. corpse:EntIndex(), function(ent, childPieces) for _, child in ipairs(childPieces) do if IsValid(child) then if child:GetClass() == "prop_ragdoll" then -- Make ragdolls fade child:Fire("FadeAndRemove", "", 0) else child:Fire("kill", "", 0) end end end end, corpse.ChildEnts) hook.Call("CreateEntityRagdoll", nil, self, corpse) return corpse else if IsValid(self.WeaponEntity) then self.WeaponEntity:Remove() end -- Remove dropped weapon -- Remove child entities | No fade effects as it will look weird, remove it instantly! if self.DeathCorpse_ChildEnts then for _, child in ipairs(self.DeathCorpse_ChildEnts) do child:Remove() end end end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:DeathWeaponDrop(dmginfo, hitgroup) local activeWep = self:GetActiveWeapon() if !self.DropWeaponOnDeath or !IsValid(activeWep) then return end -- Save its original pos & ang in case the weapon uses custom world model pos & ang -- because doing DropWeapon will mess up its spawn pos and ang, example: K-3 will spawn floating above the NPC local orgPos, orgAng = activeWep:GetPos(), activeWep:GetAngles() self:DropWeapon(activeWep, nil, self:GetForward()) -- Override the velocity so it doesn't throw the weapon (default source behavior) if activeWep.WorldModel_UseCustomPosition then activeWep:SetPos(orgPos) activeWep:SetAngles(orgAng) end local phys = activeWep:GetPhysicsObject() if IsValid(phys) then if (bit.band(self.SavedDmgInfo.type, DMG_DISSOLVE) != 0) or (IsValid(self.SavedDmgInfo.inflictor) && self.SavedDmgInfo.inflictor:GetClass() == "prop_combine_ball") then phys:EnableGravity(false) phys:SetVelocity(self:GetForward()*-150 + self:GetRight()*math.Rand(100, -100) + self:GetUp()*50) else local dmgForce = (self.SavedDmgInfo.force / 40) + self:GetMoveVelocity() + self:GetVelocity() if self.DeathAnimationCodeRan then dmgForce = self:GetGroundSpeedVelocity() end phys:SetMass(1) phys:ApplyForceCenter(dmgForce) end end self.WeaponEntity = activeWep self:OnDeathWeaponDrop(dmginfo, hitgroup, activeWep) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:GetAttackSpread(wep, target) return end