--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] if (!file.Exists("autorun/vj_base_autorun.lua","LUA")) then return end AddCSLuaFile("shared.lua") include("vj_base/npc_general.lua") include("vj_base/npc_schedules.lua") include("vj_base/npc_movetype_aa.lua") include("shared.lua") /*-------------------------------------------------- *** Copyright (c) 2012-2023 by DrVrej, All rights reserved. *** No parts of this code or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials. --------------------------------------------------*/ AccessorFunc(ENT, "m_iClass", "NPCClass", FORCE_NUMBER) AccessorFunc(ENT, "m_fMaxYawSpeed", "MaxYawSpeed", FORCE_NUMBER) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Core Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.Model = {} -- The game will pick a random model from the table when the SNPC is spawned | Add as many as you want ENT.VJ_IsHugeMonster = false -- This is mostly used for massive or boss SNPCs, it affects certain part of the SNPC, for example the SNPC won't receive any knock back -- ====== Health ====== -- ENT.StartHealth = 50 -- The starting health of the NPC ENT.HasHealthRegeneration = false -- Can the SNPC regenerate its health? ENT.HealthRegenerationAmount = 4 -- How much should the health increase after every delay? ENT.HealthRegenerationDelay = VJ_Set(2, 4) -- How much time until the health increases ENT.HealthRegenerationResetOnDmg = true -- Should the delay reset when it receives damage? -- ====== Collision / Hitbox Variables ====== -- ENT.HullType = HULL_HUMAN ENT.HasHull = true -- Set to false to disable HULL ENT.HullSizeNormal = true -- set to false to cancel out the self:SetHullSizeNormal() ENT.HasSetSolid = true -- set to false to disable SetSolid -- ====== Sight & Speed Variables ====== -- ENT.SightDistance = 10000 -- How far it can see | Remember to call "self:SetSightDistance(dist)" if you want to set a new value after initialize! ENT.SightAngle = 80 -- The sight angle | Example: 180 would make the it see all around it | Measured in degrees and then converted to radians ENT.TurningSpeed = 20 -- How fast it can turn ENT.TurningUseAllAxis = false -- If set to true, angles will not be restricted to y-axis, it will change all axes (plural axis) ENT.AnimationPlaybackRate = 1 -- Controls the playback rate of all the animations -- ====== Movement Variables ====== -- -- Types: VJ_MOVETYPE_GROUND | VJ_MOVETYPE_AERIAL | VJ_MOVETYPE_AQUATIC | VJ_MOVETYPE_STATIONARY | VJ_MOVETYPE_PHYSICS ENT.MovementType = VJ_MOVETYPE_GROUND -- How does the SNPC move? ENT.UsePlayerModelMovement = false -- If true, it will allow the NPC to use player models properly by calculating the direction it needs to go to and setting the appropriate values -- Jumping Variables: -- Requires "CAP_MOVE_JUMP" | Applied automatically by the base if "ACT_JUMP" is valid on the NPC's model ENT.AllowMovementJumping = true -- Should the NPC be allowed to jump from one node to another? ENT.MaxJumpLegalDistance = VJ_Set(400, 550) -- The max distance the NPC can jump (Usually from one node to another) | ( UP, DOWN ) -- Stationary Move Type Variables: ENT.CanTurnWhileStationary = true -- If true, the NPC will be able to turn while it's stationary ENT.Stationary_UseNoneMoveType = false -- Technical variable, used if there is any issues with the NPC's position (It has its downsides, use only when needed!) -- Aerial Move Type Variables: ENT.Aerial_FlyingSpeed_Calm = 80 -- The speed it should fly with, when it's wandering, moving slowly, etc. | Basically walking compared to ground SNPCs ENT.Aerial_FlyingSpeed_Alerted = 200 -- The speed it should fly with, when it's chasing an enemy, moving away quickly, etc. | Basically running compared to ground SNPCs ENT.Aerial_AnimTbl_Calm = {} -- Animations it plays when it's wandering around while idle ENT.Aerial_AnimTbl_Alerted = {} -- Animations it plays when it's moving while alerted -- Aquatic Move Type Variables: ENT.Aquatic_SwimmingSpeed_Calm = 80 -- The speed it should swim with, when it's wandering, moving slowly, etc. | Basically walking compared to ground SNPCs ENT.Aquatic_SwimmingSpeed_Alerted = 200 -- The speed it should swim with, when it's chasing an enemy, moving away quickly, etc. | Basically running compared to ground SNPCs ENT.Aquatic_AnimTbl_Calm = {} -- Animations it plays when it's wandering around while idle ENT.Aquatic_AnimTbl_Alerted = {} -- Animations it plays when it's moving while alerted -- Aerial & Aquatic Move Type Variables: ENT.AA_GroundLimit = 100 -- If the NPC's distance from itself to the ground is less than this, it will attempt to move up ENT.AA_MinWanderDist = 150 -- Minimum distance that the NPC should go to when wandering ENT.AA_MoveAccelerate = 5 -- The NPC will gradually speed up to the max movement speed as it moves towards its destination | Calculation = FrameTime * x -- 0 = Constant max speed | 1 = Increase very slowly | 50 = Increase very quickly ENT.AA_MoveDecelerate = 5 -- The NPC will slow down as it approaches its destination | Calculation = MaxSpeed / x -- 1 = Constant max speed | 2 = Slow down slightly | 5 = Slow down normally | 50 = Slow down extremely slow -- ====== NPC Controller Data ====== -- ENT.VJC_Data = { 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 } -- ====== Miscellaneous Variables ====== -- ENT.HasEntitiesToNoCollide = true -- If set to false, it won't run the EntitiesToNoCollide code ENT.EntitiesToNoCollide = {} -- Entities to not collide with when HasEntitiesToNoCollide is set to true ENT.AllowPrintingInChat = true -- Should this SNPC be allowed to post in player's chat? Example: "Blank no longer likes you." ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ AI / Relationship Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.CanOpenDoors = true -- Can it open doors? ENT.HasAllies = true -- Put to false if you want it not to have any allies ENT.VJ_NPC_Class = {} -- NPCs with the same class with be allied to each other -- Common Classes: Combine = CLASS_COMBINE || Zombie = CLASS_ZOMBIE || Antlions = CLASS_ANTLION || Xen = CLASS_XEN || Player Friendly = CLASS_PLAYER_ALLY ENT.FriendsWithAllPlayerAllies = false -- Should this SNPC be friends with all other player allies that are running on VJ Base? ENT.Behavior = VJ_BEHAVIOR_AGGRESSIVE -- The behavior of the SNPC -- VJ_BEHAVIOR_AGGRESSIVE = Default behavior, attacks enemies || VJ_BEHAVIOR_NEUTRAL = Neutral to everything, unless provoked -- VJ_BEHAVIOR_PASSIVE = Doesn't attack, but can be attacked by others || VJ_BEHAVIOR_PASSIVE_NATURE = Doesn't attack and is allied with everyone ENT.IsGuard = false -- If set to false, it will attempt to stick to its current position at all times ENT.AlertedToIdleTime = VJ_Set(4, 6) -- How much time until it calms down after the enemy has been killed/disappeared | Sets self.Alerted to false after the timer expires ENT.MoveOutOfFriendlyPlayersWay = true -- Should the SNPC move out of the way when a friendly player comes close to it? ENT.BecomeEnemyToPlayer = false -- Should the friendly SNPC become enemy towards the player if it's damaged by it or it witnesses another ally killed by it ENT.BecomeEnemyToPlayerLevel = 2 -- Any time the player does something bad, the NPC's anger level raises by 1, if it surpasses this, it will become enemy! -- ====== Old Variables (Can still be used, but it's recommended not to use them) ====== -- ENT.PlayerFriendly = false -- Makes the SNPC friendly to the player and HL2 Resistance -- ====== Passive Behavior Variables ====== -- ENT.Passive_RunOnTouch = true -- Should it run away and make a alert sound when something collides with it? ENT.Passive_NextRunOnTouchTime = VJ_Set(3, 4) -- How much until it can run away again when something collides with it? ENT.Passive_RunOnDamage = true -- Should it run when it's damaged? | This doesn't impact how self.Passive_AlliesRunOnDamage works ENT.Passive_AlliesRunOnDamage = true -- Should its allies (other passive SNPCs) also run when it's damaged? ENT.Passive_AlliesRunOnDamageDistance = 800 -- Any allies within this distance will run when it's damaged ENT.Passive_NextRunOnDamageTime = VJ_Set(6, 7) -- How much until it can run the code again? -- ====== On Player Sight Variables ====== -- ENT.HasOnPlayerSight = false -- Should do something when it sees the enemy? Example: Play a sound 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 the code once | Sets self.HasOnPlayerSight to false once it runs! ENT.OnPlayerSightNextTime = VJ_Set(15, 20) -- How much time should it pass until it runs the code again? -- ====== Call For Help Variables ====== -- ENT.CallForHelp = true -- Does the SNPC call for help? ENT.CallForHelpDistance = 2000 -- -- How far away the SNPC's call for help goes | Counted in World Units ENT.NextCallForHelpTime = 4 -- Time until it calls for help again ENT.HasCallForHelpAnimation = true -- if true, it will play the call for help animation ENT.AnimTbl_CallForHelp = {} -- Call For Help Animations ENT.CallForHelpAnimationDelay = 0 -- It will wait certain amount of time before playing the animation ENT.CallForHelpAnimationPlayBackRate = 1 -- How fast should the animation play? | Currently only for gestures! ENT.CallForHelpStopAnimations = true -- Should it stop attacks for a certain amount of time? -- To let the base automatically detect the animation duration, set this to false: ENT.CallForHelpStopAnimationsTime = false -- How long should it stop attacks? ENT.CallForHelpAnimationFaceEnemy = true -- Should it face the enemy when playing the animation? ENT.NextCallForHelpAnimationTime = 30 -- How much time until it can play the animation again? -- ====== Medic Variables ====== -- ENT.IsMedicSNPC = false -- Is this SNPC a medic? Does it heal other friendly friendly SNPCs, and players(If friendly) ENT.AnimTbl_Medic_GiveHealth = {ACT_SPECIAL_ATTACK1} -- Animations is plays when giving health to an ally ENT.Medic_DisableAnimation = false -- if true, it will disable the animation code ENT.Medic_TimeUntilHeal = false -- Time until the ally receives health | Set to false to let the base decide the time ENT.Medic_CheckDistance = 600 -- How far does it check for allies that are hurt? | World units ENT.Medic_HealDistance = 100 -- How close does it have to be until it stops moving and heals its ally? ENT.Medic_HealthAmount = 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 ENT.Medic_CanBeHealed = true -- If set to false, this SNPC can't be healed! -- ====== Follow System Variables ====== -- -- Associated variables: self.FollowData, self.IsFollowing ENT.FollowMinDistance = 150 -- Minimum distance the NPC should come to the player | The base automatically adds the NPC's size to this variable to account for different sizes! ENT.NextFollowUpdateTime = 0.5 -- Time until it checks if it should move to the player again | Lower number = More performance loss ENT.FollowPlayer = true -- Should the NPC follow the player when the player presses a certain key? | Restrictions: Player has to be friendly and the NPC's move type cannot be stationary! -- ====== Movement & Idle Variables ====== -- ENT.AnimTbl_IdleStand = nil -- The idle animation table when AI is enabled | DEFAULT: {ACT_IDLE} ENT.AnimTbl_Walk = {ACT_WALK} -- Set the walking animations | Put multiple to let the base pick a random animation when it moves ENT.AnimTbl_Run = {ACT_RUN} -- Set the running animations | Put multiple to let the base pick a random animation when it moves ENT.IdleAlwaysWander = false -- If set to true, it will make the SNPC always wander when idling ENT.DisableWandering = false -- Disables wandering when the SNPC is idle ENT.DisableChasingEnemy = false -- Disables the SNPC chasing the enemy -- ====== Constantly Face Enemy Variables ====== -- 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.ConstantlyFaceEnemyDistance = 2500 -- How close does it have to be until it starts to face the enemy? -- ====== Combat Face Enemy Variables ====== -- -- Mostly used by base tasks ENT.CombatFaceEnemy = true -- If enemy exists and is visible -- ====== Pose Parameter Variables ====== -- ENT.HasPoseParameterLooking = true -- Does it look at its enemy using poseparameters? ENT.PoseParameterLooking_CanReset = true -- Should it reset its pose parameters if there is no enemies? ENT.PoseParameterLooking_InvertPitch = false -- Inverts the pitch poseparameters (X) ENT.PoseParameterLooking_InvertYaw = false -- Inverts the yaw poseparameters (Y) ENT.PoseParameterLooking_InvertRoll = false -- Inverts the roll poseparameters (Z) ENT.PoseParameterLooking_TurningSpeed = 10 -- How fast does the parameter turn? ENT.PoseParameterLooking_Names = {pitch={}, yaw={}, roll={}} -- Custom pose parameters to use, can put as many as needed -- ====== Investigation Variables ====== -- -- Showcase: https://www.youtube.com/watch?v=cCqoqSDFyC4 ENT.CanInvestigate = true -- Can it detect and investigate possible enemy disturbances? | EX: Sounds, movement and flashlight ENT.InvestigateSoundDistance = 9 -- How far can the NPC hear sounds? | This number is multiplied by the calculated volume of the detectable sound -- ====== Eating Variables ====== -- ENT.CanEat = false -- Should it search and eat organic stuff when idle? -- ====== No Chase After Certain Distance Variables ====== -- ENT.NoChaseAfterCertainRange = false -- Should the SNPC not be able to chase when it's between number x and y? ENT.NoChaseAfterCertainRange_FarDistance = 2000 -- How far until it can chase again? | "UseRangeDistance" = Use the number provided by the range attack instead ENT.NoChaseAfterCertainRange_CloseDistance = 300 -- How near until it can chase again? | "UseRangeDistance" = Use the number provided by the range attack instead ENT.NoChaseAfterCertainRange_Type = "Regular" -- "Regular" = Default behavior | "OnlyRange" = Only does it if it's able to range attack -- ====== Miscellaneous Variables ====== -- ENT.AttackProps = true -- Should it attack props when trying to move? ENT.PushProps = true -- Should it push props when trying to move? ENT.PropAP_MaxSize = 1 -- This is a scale number for the max size it can attack/push | x < 1 = Smaller props & x > 1 = Larger props | Default base value: 1 -- ====== Control Variables ====== -- -- Use these variables very careful! One wrong change can mess up the whole SNPC! ENT.FindEnemy_UseSphere = false -- Should the SNPC be able to see all around him? (360) | Objects and walls can still block its sight! ENT.FindEnemy_CanSeeThroughWalls = false -- Should it be able to see through walls and objects? | Can be useful if you want to make it know where the enemy is at all times ENT.DisableFindEnemy = false -- Disables FindEnemy code, friendly code still works though ENT.DisableSelectSchedule = false -- Disables Schedule code, Custom Schedule can still work ENT.DisableTakeDamageFindEnemy = false -- Disable the SNPC finding the enemy when being damaged ENT.DisableTouchFindEnemy = false -- Disable the SNPC finding the enemy when being touched ENT.DisableMakingSelfEnemyToNPCs = false -- Disables the "AddEntityRelationship" that runs in think ENT.TimeUntilEnemyLost = 15 -- Time until it resets its enemy if the enemy is not visible ENT.NextProcessTime = 1 -- Time until it runs the essential part of the AI, which can be performance heavy! -- ====== Miscellaneous Variables ====== -- ENT.DisableInitializeCapabilities = false -- If enabled, all of the Capabilities will be disabled, allowing you to add your own ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Damaged / Injured Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- ====== Blood-Related Variables ====== -- -- Leave custom blood tables empty to let the base decide depending on the blood type ENT.Bleeds = true -- Does the SNPC bleed? (Blood decal, particle, etc.) ENT.BloodColor = "" -- The blood type, this will determine what it should use (decal, particle, etc.) -- Types: "Red" || "Yellow" || "Green" || "Orange" || "Blue" || "Purple" || "White" || "Oil" ENT.HasBloodParticle = true -- Does it spawn a particle when damaged? ENT.CustomBlood_Particle = {} -- Particles to spawn when it's damaged ENT.HasBloodPool = true -- Does it have a blood pool? ENT.CustomBlood_Pool = {} -- Blood pool types after it dies ENT.BloodPoolSize = "Normal" -- What's the size of the blood pool? | Sizes: "Normal" || "Small" || "Tiny" ENT.HasBloodDecal = true -- Does it spawn a decal when damaged? ENT.CustomBlood_Decal = {} -- Decals to spawn when it's damaged ENT.BloodDecalUseGMod = false -- Should use the current default decals defined by Garry's Mod? (This only applies for certain blood types only!) ENT.BloodDecalDistance = 150 -- How far the decal can spawn in world units -- ====== Immunity Variables ====== -- ENT.GodMode = false -- Immune to everything ENT.Immune_AcidPoisonRadiation = false -- Immune to Acid, Poison and Radiation ENT.Immune_Bullet = false -- Immune to bullet type damages ENT.Immune_Blast = false -- Immune to explosive-type damages ENT.Immune_Dissolve = false -- Immune to dissolving | Example: Combine Ball ENT.Immune_Electricity = false -- Immune to electrical-type damages | Example: shock or laser ENT.Immune_Fire = false -- Immune to fire-type damages ENT.Immune_Melee = false -- Immune to melee-type damage | Example: Crowbar, slash damages ENT.Immune_Physics = false -- Immune to physics impacts, won't take damage from props ENT.Immune_Sonic = false -- Immune to sonic-type damages ENT.ImmuneDamagesTable = {} -- Makes the SNPC immune to specific type of damages | Takes DMG_ enumerations ENT.GetDamageFromIsHugeMonster = false -- Should it get damaged no matter what by SNPCs that are tagged as VJ_IsHugeMonster? ENT.AllowIgnition = true -- Can this SNPC be set on fire? -- ====== Flinching Variables ====== -- ENT.CanFlinch = 0 -- 0 = Don't flinch | 1 = Flinch at any damage | 2 = Flinch only from certain damages ENT.FlinchDamageTypes = {DMG_BLAST} -- If it uses damage-based flinching, which types of damages should it flinch from? ENT.FlinchChance = 16 -- Chance of it flinching from 1 to x | 1 will make it always flinch -- To let the base automatically detect the animation duration, set this to false: ENT.NextMoveAfterFlinchTime = false -- How much time until it can move, attack, etc. ENT.NextFlinchTime = 5 -- How much time until it can flinch again? ENT.AnimTbl_Flinch = {ACT_FLINCH_PHYSICS} -- If it uses normal based animation, use this ENT.FlinchAnimationDecreaseLengthAmount = 0 -- This will decrease the time it can move, attack, etc. | Use it to fix animation pauses after it finished the flinch animation ENT.HitGroupFlinching_DefaultWhenNotHit = true -- If it uses hitgroup flinching, should it do the regular flinch if it doesn't hit any of the specified hitgroups? ENT.HitGroupFlinching_Values = nil -- EXAMPLES: {{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}}} -- ====== Damage By Player Variables ====== -- ENT.HasDamageByPlayer = true -- Should the SNPC do something when it's hit by a player? Example: Play a sound or animation ENT.DamageByPlayerDispositionLevel = 1 -- 0 = Run it every time | 1 = Run it only when friendly to player | 2 = Run it only when enemy to player ENT.DamageByPlayerTime = VJ_Set(2, 2) -- How much time until it can run the Damage By Player code? -- ====== Call For Back On Damage Variables ====== -- -- NOTE: This AI component only runs when there is NO enemy detected! ENT.CallForBackUpOnDamage = true -- Should the NPC call for help when damaged? ENT.CallForBackUpOnDamageDistance = 800 -- How far away does the call for help go? ENT.CallForBackUpOnDamageLimit = 4 -- How many allies should it call? | 0 = Unlimited ENT.NextCallForBackUpOnDamageTime = VJ_Set(9, 11) -- How much time until it can run this AI component again ENT.CallForBackUpOnDamageAnimation = {} -- Animations played when it calls for help on damage ENT.CallForBackUpOnDamageAnimationTime = false -- How much time until it can use activities | false = Base automatically decides the animation duration ENT.DisableCallForBackUpOnDamageAnimation = false -- Disables the animations from playing -- ====== Miscellaneous Variables ====== -- ENT.HideOnUnknownDamage = 5 -- number = Hide on unknown damage, defines the time until it can hide again | false = Disable this AI component ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Death & Corpse Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- ====== Ally Reaction On Death Variables ====== -- -- Default: Creature base uses BringFriends and Human base uses AlertFriends -- BringFriendsOnDeath takes priority over AlertFriendsOnDeath! ENT.BringFriendsOnDeath = true -- Should the SNPC's friends come to its position before it dies? ENT.BringFriendsOnDeathDistance = 800 -- How far away does the signal go? | Counted in World Units ENT.BringFriendsOnDeathLimit = 3 -- How many people should it call? | 0 = Unlimited ENT.AlertFriendsOnDeath = false -- Should the SNPCs allies get alerted when it dies? | Its allies will also need to have this variable set to true! ENT.AlertFriendsOnDeathDistance = 800 -- How far away does the signal go? | Counted in World Units ENT.AlertFriendsOnDeathLimit = 50 -- How many people should it alert? ENT.AnimTbl_AlertFriendsOnDeath = {ACT_RANGE_ATTACK1} -- Animations it plays when an ally dies that also has AlertFriendsOnDeath set to true -- ====== Death Animation Variables ====== -- ENT.HasDeathAnimation = false -- Does it play an animation when it dies? ENT.AnimTbl_Death = {} -- Death Animations -- To let the base automatically detect the animation duration, set this to false: ENT.DeathAnimationTime = false -- Time until the SNPC spawns its corpse and gets removed 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 Variables ====== -- ENT.HasDeathRagdoll = true -- If set to false, it will not spawn the regular ragdoll of the SNPC ENT.DeathCorpseEntityClass = "UseDefaultBehavior" -- The entity class it creates | "UseDefaultBehavior" = Let the base automatically detect the type ENT.DeathCorpseModel = {} -- The corpse model that it will spawn when it dies | Leave empty to use the NPC's model | Put as many models as desired, the base will pick a random one. ENT.DeathCorpseCollisionType = COLLISION_GROUP_DEBRIS -- Collision type for the corpse | SNPC Options Menu can only override this value if it's set to COLLISION_GROUP_DEBRIS! ENT.DeathCorpseSkin = -1 -- Used to override the death skin | -1 = Use the skin that the SNPC had before it died ENT.DeathCorpseSetBodyGroup = true -- Should it get the models bodygroups and set it to the corpse? When set to false, it uses the model's default bodygroups ENT.DeathCorpseBodyGroup = VJ_Set(-1, -1) -- #1 = the category of the first bodygroup | #2 = the value of the second bodygroup | Set -1 for #1 to let the base decide the corpse's bodygroup 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. ENT.DeathCorpseFade = false -- Fades the ragdoll on death ENT.DeathCorpseFadeTime = 10 -- How much time until the ragdoll fades | Unit = Seconds ENT.DeathCorpseSetBoneAngles = true -- This can be used to stop the corpse glitching or flying on death ENT.DeathCorpseApplyForce = true -- Disables the damage force on death | Useful for SNPCs with Death Animations ENT.WaitBeforeDeathTime = 0 -- Time until the SNPC spawns its corpse and gets removed -- ====== Dismemberment/Gib Variables ====== -- ENT.AllowedToGib = true -- Is it allowed to gib in general? This can be on death or when shot in a certain place ENT.HasGibOnDeath = true -- Is it allowed to gib on death? ENT.GibOnDeathDamagesTable = {"UseDefault"} -- Damages that it gibs from | "UseDefault" = Uses default damage types | "All" = Gib from any damage ENT.HasGibOnDeathSounds = true -- Does it have gib sounds? | Mostly used for the settings menu ENT.HasGibDeathParticles = true -- Does it spawn particles on death or when it gibs? | Mostly used for the settings menu -- ====== Item Drops On Death Variables ====== -- ENT.HasItemDropsOnDeath = true -- Should it drop items on death? ENT.ItemDropsOnDeathChance = 14 -- If set to 1, it will always drop it ENT.ItemDropsOnDeath_EntityList = {} -- List of items it will randomly pick from | Leave it empty to drop nothing or to make your own dropping code (Using CustomOn...) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Melee Attack Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasMeleeAttack = true -- Should the SNPC have a melee attack? ENT.MeleeAttackDamage = 10 ENT.MeleeAttackDamageType = DMG_SLASH -- Type of Damage ENT.HasMeleeAttackKnockBack = false -- Should knockback be applied on melee hit? | Use self:MeleeAttackKnockbackVelocity() to edit the velocity -- ====== Animation Variables ====== -- ENT.AnimTbl_MeleeAttack = {ACT_MELEE_ATTACK1} -- Melee Attack Animations ENT.MeleeAttackAnimationDelay = 0 -- It will wait certain amount of time before playing the animation ENT.MeleeAttackAnimationFaceEnemy = true -- Should it face the enemy while playing the melee attack animation? ENT.MeleeAttackAnimationDecreaseLengthAmount = 0 -- This will decrease the time until starts chasing again. Use it to fix animation pauses until it chases the enemy. ENT.MeleeAttackAnimationAllowOtherTasks = false -- If set to true, the animation will not stop other tasks from playing, such as chasing | Useful for gesture attacks! -- ====== Distance Variables ====== -- ENT.MeleeAttackDistance = 60 -- How close does it have to be until it attacks? ENT.MeleeAttackAngleRadius = 100 -- What is the attack angle radius? | 100 = In front of the SNPC | 180 = All around the SNPC ENT.MeleeAttackDamageDistance = 80 -- How far does the damage go? ENT.MeleeAttackDamageAngleRadius = 100 -- What is the damage angle radius? | 100 = In front of the SNPC | 180 = All around the SNPC -- ====== Timer Variables ====== -- -- To use event-based attacks, set this to false: ENT.TimeUntilMeleeAttackDamage = 0.6 -- This counted in seconds | This calculates the time until it hits something ENT.NextMeleeAttackTime = 0 -- How much time until it can use a melee attack? ENT.NextMeleeAttackTime_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer -- To let the base automatically detect the attack duration, set this to false: ENT.NextAnyAttackTime_Melee = false -- How much time until it can use any attack again? | Counted in Seconds ENT.NextAnyAttackTime_Melee_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer ENT.MeleeAttackReps = 1 -- How many times does it run the melee attack code? ENT.MeleeAttackExtraTimers = nil -- Extra melee attack timers, EX: {1, 1.4} | it will run the damage code after the given amount of seconds ENT.StopMeleeAttackAfterFirstHit = false -- Should it stop the melee attack from running rest of timers when it hits an enemy? -- ====== Control Variables ====== -- ENT.DisableMeleeAttackAnimation = false -- if true, it will disable the animation code ENT.DisableDefaultMeleeAttackCode = false -- When set to true, it will completely disable the melee attack code ENT.DisableDefaultMeleeAttackDamageCode = false -- Disables the default melee attack damage code -- ====== Bleed Enemy Variables ====== -- -- Causes the affected enemy to continue taking damage after the attack for x amount of time ENT.MeleeAttackBleedEnemy = false -- Should the enemy bleed when attacked by melee? ENT.MeleeAttackBleedEnemyChance = 3 -- Chance that the enemy bleeds | 1 = always ENT.MeleeAttackBleedEnemyDamage = 1 -- How much damage per repetition ENT.MeleeAttackBleedEnemyTime = 1 -- How much time until the next repetition? ENT.MeleeAttackBleedEnemyReps = 4 -- How many repetitions? -- ====== Slow Player Variables ====== -- -- Causes the affected player to slow down ENT.SlowPlayerOnMeleeAttack = false -- If true, then the player will slow down ENT.SlowPlayerOnMeleeAttack_WalkSpeed = 100 -- Walking Speed when Slow Player is on ENT.SlowPlayerOnMeleeAttack_RunSpeed = 100 -- Running Speed when Slow Player is on ENT.SlowPlayerOnMeleeAttackTime = 5 -- How much time until player's Speed resets -- ====== Digital Signal Processor (DSP) Variables ====== -- -- Applies a DSP (Digital Signal Processor) to the player(s) that got hit -- DSP Presents: https://developer.valvesoftware.com/wiki/Dsp_presets ENT.MeleeAttackDSPSoundType = 32 -- DSP type | false = Disables the system completely ENT.MeleeAttackDSPSoundUseDamage = true -- true = Only apply the DSP effect past certain damage| false = Always apply the DSP effect! ENT.MeleeAttackDSPSoundUseDamageAmount = 60 -- Any damage that is greater than or equal to this number will cause the DSP effect to apply ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Range Attack Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasRangeAttack = false -- Should the SNPC have a range attack? ENT.RangeAttackEntityToSpawn = "grenade_spit" -- The entity that is spawned when range attacking -- ====== Animation Variables ====== -- ENT.AnimTbl_RangeAttack = {ACT_RANGE_ATTACK1} -- Range Attack Animations ENT.RangeAttackAnimationDelay = 0 -- It will wait certain amount of time before playing the animation ENT.RangeAttackAnimationFaceEnemy = true -- Should it face the enemy while playing the range attack animation? ENT.RangeAttackAnimationDecreaseLengthAmount = 0 -- This will decrease the time until starts chasing again. Use it to fix animation pauses until it chases the enemy. ENT.RangeAttackAnimationStopMovement = true -- Should it stop moving when performing a range attack? -- ====== Distance Variables ====== -- ENT.RangeDistance = 2000 -- This is how far away it can shoot ENT.RangeToMeleeDistance = 800 -- How close does it have to be until it uses melee? ENT.RangeAttackAngleRadius = 100 -- What is the attack angle radius? | 100 = In front of the SNPC | 180 = All around the SNPC -- ====== Timer Variables ====== -- -- To use event-based attacks, set this to false: ENT.TimeUntilRangeAttackProjectileRelease = 1.5 -- How much time until the projectile code is ran? ENT.NextRangeAttackTime = 3 -- How much time until it can use a range attack? ENT.NextRangeAttackTime_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer -- To let the base automatically detect the attack duration, set this to false: ENT.NextAnyAttackTime_Range = false -- How much time until it can use any attack again? | Counted in Seconds ENT.NextAnyAttackTime_Range_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer ENT.RangeAttackReps = 1 -- How many times does it run the projectile code? ENT.RangeAttackExtraTimers = nil -- Extra range attack timers, EX: {1, 1.4} | it will run the projectile code after the given amount of seconds -- ====== Projectile Spawn Position Variables ====== -- ENT.RangeUseAttachmentForPos = false -- Should the projectile spawn on a attachment? ENT.RangeUseAttachmentForPosID = "muzzle" -- The attachment used on the range attack if RangeUseAttachmentForPos is set to true ENT.RangeAttackPos_Up = 20 -- Up/Down spawning position for range attack ENT.RangeAttackPos_Forward = 0 -- Forward/Backward spawning position for range attack ENT.RangeAttackPos_Right = 0 -- Right/Left spawning position for range attack -- ====== Control Variables ====== -- ENT.DisableRangeAttackAnimation = false -- if true, it will disable the animation code ENT.DisableDefaultRangeAttackCode = false -- When true, it won't spawn the range attack entity, allowing you to make your own ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Leap Attack Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasLeapAttack = false -- Should the SNPC have a leap attack? ENT.LeapAttackDamage = 15 ENT.LeapAttackDamageType = DMG_SLASH -- Type of Damage -- ====== Animation Variables ====== -- ENT.AnimTbl_LeapAttack = {ACT_SPECIAL_ATTACK1} -- Melee Attack Animations ENT.LeapAttackAnimationDelay = 0 -- It will wait certain amount of time before playing the animation ENT.LeapAttackAnimationFaceEnemy = 2 -- true = Face the enemy the entire time! | 2 = Face the enemy UNTIL it jumps! | false = Don't face the enemy AT ALL! ENT.LeapAttackAnimationDecreaseLengthAmount = 0 -- This will decrease the time until starts chasing again. Use it to fix animation pauses until it chases the enemy. -- ====== Distance Variables ====== -- ENT.LeapDistance = 500 -- The distance of the leap, for example if it is set to 500, when the SNPC is 500 Unit away, it will jump ENT.LeapToMeleeDistance = 200 -- How close does it have to be until it uses melee? ENT.LeapAttackDamageDistance = 100 -- How far does the damage go? ENT.LeapAttackAngleRadius = 60 -- What is the attack angle radius? | 100 = In front of the SNPC | 180 = All around the SNPC -- ====== Timer Variables ====== -- -- To use event-based attacks, set this to false: ENT.TimeUntilLeapAttackDamage = 0.2 -- How much time until it runs the leap damage code? ENT.TimeUntilLeapAttackVelocity = 0.1 -- How much time until it runs the velocity code? ENT.NextLeapAttackTime = 3 -- How much time until it can use a leap attack? ENT.NextLeapAttackTime_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer -- To let the base automatically detect the attack duration, set this to false: ENT.NextAnyAttackTime_Leap = false -- How much time until it can use any attack again? | Counted in Seconds ENT.NextAnyAttackTime_Leap_DoRand = false -- False = Don't use random time | Number = Picks a random number between the regular timer and this timer ENT.LeapAttackReps = 1 -- How many times does it run the leap attack code? ENT.LeapAttackExtraTimers = nil -- Extra leap attack timers, EX: {1, 1.4} | it will run the damage code after the given amount of seconds ENT.StopLeapAttackAfterFirstHit = true -- Should it stop the leap attack from running rest of timers when it hits an enemy? -- ====== Velocity Variables ====== -- ENT.LeapAttackVelocityForward = 2000 -- How much forward force should it apply? ENT.LeapAttackVelocityUp = 200 -- How much upward force should it apply? ENT.LeapAttackVelocityRight = 0 -- How much right force should it apply? -- ====== Control Variables ====== -- ENT.DisableLeapAttackAnimation = false -- if true, it will disable the animation code ENT.DisableDefaultLeapAttackDamageCode = false -- Disables the default leap attack damage code ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Sound Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.HasSounds = true -- Put to false to disable ALL sounds! -- ====== Footstep Sound / World Shake On Move Variables ====== -- ENT.DisableFootStepSoundTimer = false -- If set to true, it will disable the time system for the footstep sound code, allowing you to use other ways like model events ENT.FootStepTimeRun = 0.5 -- Next foot step sound when it is running ENT.FootStepTimeWalk = 1 -- Next foot step sound when it is walking ENT.DisableFootStepOnRun = false -- It will not play the footstep sound when running ENT.DisableFootStepOnWalk = false -- It will not play the footstep sound when walking ENT.HasWorldShakeOnMove = false -- Should the world shake when it's moving? ENT.WorldShakeOnMoveAmplitude = 10 -- How much the screen will shake | From 1 to 16, 1 = really low 16 = really high ENT.WorldShakeOnMoveRadius = 1000 -- How far the screen shake goes, in world units ENT.WorldShakeOnMoveDuration = 0.4 -- How long the screen shake will last, in seconds ENT.WorldShakeOnMoveFrequency = 100 -- Just leave it to 100 -- ====== Idle Sound Variables ====== -- ENT.IdleSounds_PlayOnAttacks = false -- It will be able to continue and play idle sounds when it performs an attack ENT.IdleSounds_NoRegularIdleOnAlerted = false -- if set to true, it will not play the regular idle sound table if the combat idle sound table is empty -- ====== Idle dialogue Sound Variables ====== -- -- When an allied SNPC or a allied player is in range, the SNPC will play a different sound table. If the ally is a VJ SNPC and has dialogue answer sounds, it will respond to this SNPC ENT.HasIdleDialogueSounds = true -- If set to false, it won't play the idle dialogue sounds ENT.HasIdleDialogueAnswerSounds = true -- If set to false, it won't play the idle dialogue answer sounds ENT.IdleDialogueDistance = 400 -- How close should the ally be for the SNPC to talk to the ally? ENT.IdleDialogueCanTurn = true -- If set to false, it won't turn when a dialogue occurs -- ====== Miscellaneous Variables ====== -- ENT.AlertSounds_OnlyOnce = false -- After it plays it once, it will never play it again ENT.BeforeMeleeAttackSounds_WaitTime = 0 -- Time until it starts playing the Before Melee Attack sounds ENT.OnlyDoKillEnemyWhenClear = true -- If set to true, it will only play the OnKilledEnemy sound when there isn't any other enemies -- ====== Main Control Variables ====== -- ENT.HasFootStepSound = true -- Should the SNPC make a footstep sound when it's moving? ENT.HasBreathSound = true -- Should it make a breathing sound? ENT.HasIdleSounds = true -- If set to false, it won't play the idle sounds ENT.HasOnReceiveOrderSounds = true -- If set to false, it won't play any sound when it receives an order from an ally ENT.HasFollowPlayerSounds_Follow = true -- If set to false, it won't play the follow player sounds ENT.HasFollowPlayerSounds_UnFollow = true -- If set to false, it won't play the unfollow player sounds ENT.HasMoveOutOfPlayersWaySounds = true -- If set to false, it won't play any sounds when it moves out of the player's way ENT.HasMedicSounds_BeforeHeal = true -- If set to false, it won't play any sounds before it gives a med kit to an ally ENT.HasMedicSounds_AfterHeal = true -- If set to false, it won't play any sounds after it gives a med kit to an ally ENT.HasMedicSounds_ReceiveHeal = true -- If set to false, it won't play any sounds when it receives a medkit ENT.HasOnPlayerSightSounds = true -- If set to false, it won't play the saw player sounds ENT.HasInvestigateSounds = true -- If set to false, it won't play any sounds when it's investigating something ENT.HasLostEnemySounds = true -- If set to false, it won't play any sounds when it looses it enemy ENT.HasAlertSounds = true -- If set to false, it won't play the alert sounds ENT.HasCallForHelpSounds = true -- If set to false, it won't play any sounds when it calls for help ENT.HasBecomeEnemyToPlayerSounds = true -- If set to false, it won't play the become enemy to player sounds ENT.HasMeleeAttackSounds = true -- If set to false, it won't play the melee attack sound ENT.HasExtraMeleeAttackSounds = false -- Set to true to use the extra melee attack sounds ENT.HasMeleeAttackMissSounds = true -- If set to false, it won't play the melee attack miss sound ENT.HasMeleeAttackSlowPlayerSound = true -- Does it have a sound when it slows down the player? ENT.HasBeforeRangeAttackSound = true -- If set to false, it won't play the before range attack sounds ENT.HasRangeAttackSound = true -- If set to false, it won't play the range attack sounds ENT.HasBeforeLeapAttackSound = true -- If set to false, it won't play any sounds before leap attack code is ran ENT.HasLeapAttackJumpSound = true -- If set to false, it won't play any sounds when it leaps at the enemy while leap attacking ENT.HasLeapAttackDamageSound = true -- If set to false, it won't play any sounds when it successfully hits the enemy while leap attacking ENT.HasLeapAttackDamageMissSound = true -- If set to false, it won't play any sounds when it misses the enemy while leap attacking ENT.HasOnKilledEnemySound = true -- Should it play a sound when it kills an enemy? ENT.HasAllyDeathSound = true -- Should it paly a sound when an ally dies? ENT.HasPainSounds = true -- If set to false, it won't play the pain sounds ENT.HasImpactSounds = true -- If set to false, it won't play the impact sounds ENT.HasDamageByPlayerSounds = true -- If set to false, it won't play the damage by player sounds ENT.HasDeathSounds = true -- If set to false, it won't play the death sounds ENT.HasSoundTrack = false -- Does the SNPC have a sound track? -- ====== File Path Variables ====== -- -- Leave blank if you don't want any sounds to play ENT.SoundTbl_FootStep = {} ENT.SoundTbl_Breath = {} ENT.SoundTbl_Idle = {} ENT.SoundTbl_IdleDialogue = {} ENT.SoundTbl_IdleDialogueAnswer = {} ENT.SoundTbl_CombatIdle = {} ENT.SoundTbl_OnReceiveOrder = {} ENT.SoundTbl_FollowPlayer = {} ENT.SoundTbl_UnFollowPlayer = {} ENT.SoundTbl_MoveOutOfPlayersWay = {} ENT.SoundTbl_MedicBeforeHeal = {} ENT.SoundTbl_MedicAfterHeal = {} ENT.SoundTbl_MedicReceiveHeal = {} ENT.SoundTbl_OnPlayerSight = {} ENT.SoundTbl_Investigate = {} ENT.SoundTbl_LostEnemy = {} ENT.SoundTbl_Alert = {} ENT.SoundTbl_CallForHelp = {} ENT.SoundTbl_BecomeEnemyToPlayer = {} ENT.SoundTbl_BeforeMeleeAttack = {} ENT.SoundTbl_MeleeAttack = {} ENT.SoundTbl_MeleeAttackExtra = {} ENT.SoundTbl_MeleeAttackMiss = {} ENT.SoundTbl_MeleeAttackSlowPlayer = {"vj_player/heartbeat.wav"} ENT.SoundTbl_BeforeRangeAttack = {} ENT.SoundTbl_RangeAttack = {} ENT.SoundTbl_BeforeLeapAttack = {} ENT.SoundTbl_LeapAttackJump = {} ENT.SoundTbl_LeapAttackDamage = {} ENT.SoundTbl_LeapAttackDamageMiss = {} ENT.SoundTbl_OnKilledEnemy = {} ENT.SoundTbl_AllyDeath = {} ENT.SoundTbl_Pain = {} ENT.SoundTbl_Impact = {} ENT.SoundTbl_DamageByPlayer = {} ENT.SoundTbl_Death = {} ENT.SoundTbl_SoundTrack = {} ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ ///// WARNING: Don't change anything in this box! \\\\\ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- These are the default file paths in case the user doesn't put one (tables above). local DefaultSoundTbl_MedicAfterHeal = {"items/smallmedkit1.wav"} local DefaultSoundTbl_MeleeAttackExtra = {"npc/zombie/claw_strike1.wav","npc/zombie/claw_strike2.wav","npc/zombie/claw_strike3.wav"} local DefaultSoundTbl_Impact = {"vj_flesh/alien_flesh1.wav"} ------ ///// WARNING: Don't change anything in this box! \\\\\ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- ====== Fade Out Time Variables ====== -- -- Put to 0 if you want it to stop instantly ENT.MeleeAttackSlowPlayerSoundFadeOutTime = 1 ENT.SoundTrackFadeOutTime = 2 -- ====== Sound Chance Variables ====== -- -- Higher number = less chance of playing | 1 = Always play ENT.IdleSoundChance = 2 ENT.IdleDialogueAnswerSoundChance = 1 ENT.CombatIdleSoundChance = 1 ENT.OnReceiveOrderSoundChance = 1 ENT.FollowPlayerSoundChance = 1 ENT.UnFollowPlayerSoundChance = 1 ENT.MoveOutOfPlayersWaySoundChance = 2 ENT.MedicBeforeHealSoundChance = 1 ENT.MedicAfterHealSoundChance = 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.BeforeRangeAttackSoundChance = 1 ENT.RangeAttackSoundChance = 1 ENT.BeforeLeapAttackSoundChance = 1 ENT.LeapAttackJumpSoundChance = 1 ENT.LeapAttackDamageSoundChance = 1 ENT.LeapAttackDamageMissSoundChance = 1 ENT.OnKilledEnemySoundChance = 1 ENT.AllyDeathSoundChance = 4 ENT.PainSoundChance = 1 ENT.ImpactSoundChance = 1 ENT.DamageByPlayerSoundChance = 1 ENT.DeathSoundChance = 1 ENT.SoundTrackChance = 1 -- ====== Timer Variables ====== -- -- Randomized time between the two variables, x amount of time has to pass for the sound to play again | Counted in seconds ENT.NextSoundTime_Breath = true -- true = Base will decide the time | VJ_Set(1, 2) = Custom time ENT.NextSoundTime_Idle = VJ_Set(4, 11) ENT.NextSoundTime_Investigate = VJ_Set(5, 5) ENT.NextSoundTime_LostEnemy = VJ_Set(5, 6) ENT.NextSoundTime_Alert = VJ_Set(2, 3) ENT.NextSoundTime_OnKilledEnemy = VJ_Set(3, 5) ENT.NextSoundTime_AllyDeath = VJ_Set(3, 5) ENT.NextSoundTime_Pain = true -- true = Base will decide the time | VJ_Set(1, 2) = Custom time ENT.NextSoundTime_DamageByPlayer = VJ_Set(2, 2.3) -- ====== Volume Variables ====== -- -- Number must be between 0 and 1 -- 0 = No sound, 1 = normal/loudest ENT.SoundTrackVolume = 1 -- ====== Sound Level Variables ====== -- -- 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 ENT.IdleDialogueAnswerSoundLevel = 75 ENT.CombatIdleSoundLevel = 80 ENT.OnReceiveOrderSoundLevel = 80 ENT.FollowPlayerSoundLevel = 75 ENT.UnFollowPlayerSoundLevel = 75 ENT.MoveOutOfPlayersWaySoundLevel = 75 ENT.BeforeHealSoundLevel = 75 ENT.AfterHealSoundLevel = 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.MeleeAttackSlowPlayerSoundLevel = 100 ENT.BeforeRangeAttackSoundLevel = 75 ENT.RangeAttackSoundLevel = 75 ENT.BeforeLeapAttackSoundLevel = 75 ENT.LeapAttackJumpSoundLevel = 75 ENT.LeapAttackDamageSoundLevel = 75 ENT.LeapAttackDamageMissSoundLevel = 75 ENT.OnKilledEnemySoundLevel = 80 ENT.AllyDeathSoundLevel = 80 ENT.PainSoundLevel = 80 ENT.ImpactSoundLevel = 60 ENT.DamageByPlayerSoundLevel = 75 ENT.DeathSoundLevel = 80 //ENT.SoundTrackLevel = 0.9 -- ====== Sound Pitch Variables ====== -- -- Range: 0 - 255 | Lower pitch < x > Higher pitch ENT.UseTheSameGeneralSoundPitch = true -- If set to true, the base will decide a number when the NPC spawns and uses it for all sound pitches set to false -- It picks the number between these two variables below: -- These two variables control any sound pitch variable that is set to false ENT.GeneralSoundPitch1 = 90 ENT.GeneralSoundPitch2 = 100 -- To not use the variables above, set the pitch to something other than false ENT.FootStepPitch = VJ_Set(80, 100) ENT.BreathSoundPitch = VJ_Set(100, 100) ENT.IdleSoundPitch = VJ_Set(false, false) ENT.IdleDialogueSoundPitch = VJ_Set(false, false) ENT.IdleDialogueAnswerSoundPitch = VJ_Set(false, false) ENT.CombatIdleSoundPitch = VJ_Set(false, false) ENT.OnReceiveOrderSoundPitch = VJ_Set(false, false) ENT.FollowPlayerPitch = VJ_Set(false, false) ENT.UnFollowPlayerPitch = VJ_Set(false, false) ENT.MoveOutOfPlayersWaySoundPitch = VJ_Set(false, false) ENT.BeforeHealSoundPitch = VJ_Set(false, false) ENT.AfterHealSoundPitch = VJ_Set(100, 100) ENT.MedicReceiveHealSoundPitch = VJ_Set(false, false) ENT.OnPlayerSightSoundPitch = VJ_Set(false, false) ENT.InvestigateSoundPitch = VJ_Set(false, false) ENT.LostEnemySoundPitch = VJ_Set(false, false) ENT.AlertSoundPitch = VJ_Set(false, false) ENT.CallForHelpSoundPitch = VJ_Set(false, false) ENT.BecomeEnemyToPlayerPitch = VJ_Set(false, false) ENT.BeforeMeleeAttackSoundPitch = VJ_Set(false, false) ENT.MeleeAttackSoundPitch = VJ_Set(false, false) ENT.ExtraMeleeSoundPitch = VJ_Set(80, 100) ENT.MeleeAttackMissSoundPitch = VJ_Set(90, 100) ENT.BeforeRangeAttackPitch = VJ_Set(false, false) ENT.RangeAttackPitch = VJ_Set(false, false) ENT.BeforeLeapAttackSoundPitch = VJ_Set(false, false) ENT.LeapAttackJumpSoundPitch = VJ_Set(false, false) ENT.LeapAttackDamageSoundPitch = VJ_Set(false, false) ENT.LeapAttackDamageMissSoundPitch = VJ_Set(false, false) ENT.OnKilledEnemySoundPitch = VJ_Set(false, false) ENT.AllyDeathSoundPitch = VJ_Set(false, false) ENT.PainSoundPitch = VJ_Set(false, false) ENT.ImpactSoundPitch = VJ_Set(80, 100) ENT.DamageByPlayerPitch = VJ_Set(false, false) ENT.DeathSoundPitch = VJ_Set(false, false) -- ====== Playback Rate Variables ====== -- -- Decides how fast the sound should play -- Examples: 1 = normal, 2 = twice the normal speed, 0.5 = half the normal speed ENT.SoundTrackPlaybackRate = 1 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Customization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- Use the functions below to customize certain parts of the base or to add new custom systems --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnPreInitialize() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnInitialize() -- self:SetCollisionBounds(Vector(50, 50, 100), Vector(-50, -50, 0)) -- Collision bounds of the NPC | WARNING: All 4 Xs and Ys should be the same! -- self:SetSurroundingBounds(Vector(-300, -300, 0), Vector(300, 300, 500)) -- Damage bounds of the NPC, doesn't effect collision or OBB | NOTE: Only set this if the base one is not good enough! | Use "cl_ent_absbox" to view the bounds end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnThink() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnThink_AIEnabled() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnSetupRelationships(ent, entFri, entDist) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnChangeMovementType(movType) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnIsJumpLegal(startPos, apex, endPos) end -- Return nothing to let base decide, return true to make it jump, return false to disallow jumping --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOn_PoseParameterLookingCode(pitch, yaw, roll) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnSchedule() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnChangeActivity(newAct) end --------------------------------------------------------------------------------------------------------------------------------------------- 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:OnCreateSound(sdFile) return "example/sound.wav" end --------------------------------------------------------------------------------------------------------------------------------------------- -- Uncomment to use | Called whenever a sound starts playing through VJ_CreateSound -- function ENT:OnPlayCreateSound(sdData, sdFile) end --------------------------------------------------------------------------------------------------------------------------------------------- -- Uncomment to use | Called whenever a sound starts playing through VJ_EmitSound -- function ENT:OnPlayEmitSound(sdFile) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnFireBullet(ent, data) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnTouch(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnCondition(cond) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnAcceptInput(key, activator, caller, data) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnHandleAnimEvent(ev, evTime, evCycle, evType, evOptions) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFollowPlayer(ply) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnUnFollowPlayer(ply) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Called every time a change occurs in the eating system - ent = The entity that it is checking OR speaking with - status = The change that occurred, possible changes: - "CheckEnt" = Possible friendly entity found, should we speak to it? | return anything other than nil or "false" 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 nil or "false" to not play an answer back dialogue! - statusInfo = 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:CustomOnIdleDialogue(ent, status, statusInfo) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMedic_BeforeHeal() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMedic_OnHeal(ent) return true end -- Return false to NOT update its ally's health and NOT clear its decals, allowing to custom code it --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMedic_OnReset() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnPlayerSight(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFootStepSound() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFootStepSound_Run() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFootStepSound_Walk() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnWorldShakeOnMove() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnInvestigate(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnResetEnemy() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnAlert(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnCallForHelp(ally) end --------------------------------------------------------------------------------------------------------------------------------------------- -- The NPC's sight direction | Used by main sight angle, all attack angle radiuses, etc. function ENT:GetSightDirection() //return self:GetAttachment(self:LookupAttachment("mouth")).Ang:Forward() -- Attachment example //return select(2, self:GetBonePosition(self:LookupBone("bip01 head"))):Forward() -- Bone example return self:GetForward() -- Make sure to return a direction! end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:GetDynamicOrigin() return self:GetPos() + self:GetForward() -- Override this to use a different position end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomAttack(ene, eneVisible) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MultipleMeleeAttacks() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomAttackCheck_MeleeAttack() return true end -- Not returning true will not let the melee attack code run! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMeleeAttack_BeforeStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMeleeAttack_AfterStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMeleeAttack_BeforeChecks() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:GetMeleeAttackDamageOrigin() return self:GetPos() + self:GetForward() -- Override this to use a different position end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMeleeAttack_AfterChecks(hitEnt, isProp) return false end -- return true to disable the attack and move onto the next entity! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MeleeAttackKnockbackVelocity(hitEnt) return self:GetForward()*math.random(100, 140) + self:GetUp()*10 end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnMeleeAttack_Miss() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MultipleRangeAttacks() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomAttackCheck_RangeAttack() return true end -- Not returning true will not let the range attack code run! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnRangeAttack_BeforeStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnRangeAttack_AfterStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomRangeAttackCode() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomRangeAttackCode_BeforeProjectileSpawn(projectile) end -- This is ran before Spawn() is called --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomRangeAttackCode_AfterProjectileSpawn(projectile) end -- Called after Spawn() --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:RangeAttackCode_OverrideProjectilePos(projectile) return 0 end -- return other value then 0 to override the projectile's position --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:RangeAttackCode_GetShootPos(projectile) return (self:GetEnemy():GetPos() - self:LocalToWorld(Vector(0, 0, 0)))*2 + self:GetUp()*1 end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MultipleLeapAttacks() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomAttackCheck_LeapAttack() return true end -- Not returning true will not let the leap attack code run! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttack_BeforeStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttack_AfterStartTimer(seed) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttackVelocityCode() end -- Return true here to override the default velocity code --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttack_BeforeChecks() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttack_AfterChecks(hitEnt) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnLeapAttack_Miss() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnDoKilledEnemy(ent, attacker, inflictor) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnTakeDamage_BeforeImmuneChecks(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnTakeDamage_BeforeDamage(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnTakeDamage_AfterDamage(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnTakeDamage_OnBleed(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFlinch_BeforeFlinch(dmginfo, hitgroup) end -- Return false to disallow the flinch from playing --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnFlinch_AfterFlinch(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnDamageByPlayer(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnBecomeEnemyToPlayer(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnSetEnemyOnDamage(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:SetUpGibesOnDeath(dmginfo, hitgroup) return false -- Return to true if it gibbed! /*-------------------------------------- -- Extra Features -- Extra features allow you to have more control over the gibbing system. --/// Types \\\-- AllowCorpse -- Should it allow corpse to spawn? DeathAnim -- Should it allow death animation? --/// Implementing it \\\-- 1. Let's use type DeathAnim as an example. NOTE: You can have as many types as you want! 2. Put a comma next to return. ===> return true, 3. Make a table after the comma. ===> return true, {} 4. Put the type(s) that you want. ===> return true, {DeathAnim=true} 5. And you are done! Example with multiple types: ===> return true, {DeathAnim=true,AllowCorpse=true} --------------------------------------*/ end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomGibOnDeathSounds(dmginfo, hitgroup) return true end -- returning false will make the default gibbing sounds not play --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnAllyDeath(ent) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnInitialKilled(dmginfo, hitgroup) end -- Ran the moment the NPC dies! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnPriorToKilled(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomDeathAnimationCode(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnKilled(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomRareDropsOnDeathCode(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnDeath_BeforeCorpseSpawned(dmginfo, hitgroup) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnDeath_AfterCorpseSpawned(dmginfo, hitgroup, corpseEnt) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:CustomOnRemove() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Controller_Initialize(ply, controlEnt) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Controller_IntMsg(ply, controlEnt) //ply:ChatPrint("CTRL + MOUSE2: Rocket Attack") -- Example end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ ///// WARNING: Don't touch anything below this line! \\\\\ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ENT.Alerted = false ENT.Dead = false ENT.Flinching = false ENT.vACT_StopAttacks = false ENT.IsFollowing = false ENT.FollowingPlayer = false ENT.VJ_IsBeingControlled = false ENT.VJ_PlayingSequence = false ENT.PlayingAttackAnimation = false ENT.VJ_DEBUG = false ENT.MeleeAttack_DoingPropAttack = false ENT.Medic_Status = false -- false = Not active | "Active" = Attempting to heal ally (Going after etc.) | "Healing" = Has reached ally and is healing it ENT.IsAbleToMeleeAttack = true ENT.IsAbleToRangeAttack = true ENT.IsAbleToLeapAttack = true ENT.VJ_PlayingInterruptSequence = false ENT.HasBeenGibbedOnDeath = false ENT.DeathAnimationCodeRan = false ENT.VJ_IsBeingControlled_Tool = false ENT.LastHiddenZone_CanWander = true ENT.LeapAttackHasJumped = false ENT.CurIdleStandMove = false ENT.PropAP_IsVisible = false ENT.VJ_TheController = NULL ENT.VJ_TheControllerEntity = NULL ENT.VJ_TheControllerBullseye = NULL ENT.Medic_CurrentEntToHeal = NULL ENT.Medic_SpawnedProp = NULL ENT.LastPlayedVJSound = nil ENT.NextFollowUpdateT = 0 ENT.AngerLevelTowardsPlayer = 0 ENT.NextBreathSoundT = 0 ENT.FootStepT = 0 ENT.PainSoundT = 0 ENT.AllyDeathSoundT = 0 ENT.WorldShakeWalkT = 0 ENT.NextIdleSoundT = 0 ENT.NextProcessT = 0 ENT.NextPropAPCheckT = 0 ENT.NextCallForHelpT = 0 ENT.NextCallForBackUpOnDamageT = 0 ENT.NextAlertSoundT = 0 ENT.NextCallForHelpAnimationT = 0 ENT.CurrentAttackAnimation = 0 ENT.CurrentAttackAnimationDuration = 0 ENT.NextIdleTime = 0 ENT.NextChaseTime = 0 ENT.OnPlayerSightNextT = 0 ENT.NextDamageByPlayerT = 0 ENT.NextDamageByPlayerSoundT = 0 ENT.Medic_NextHealT = 0 ENT.CurrentAnim_IdleStand = 0 ENT.NextFlinchT = 0 ENT.NextCanGetCombineBallDamageT = 0 ENT.UseTheSameGeneralSoundPitch_PickedNumber = 0 ENT.OnKilledEnemySoundT = 0 ENT.LastHiddenZoneT = 0 ENT.NextIdleStandTime = 0 ENT.NextWanderTime = 0 ENT.TakingCoverT = 0 ENT.NextInvestigationMove = 0 ENT.NextInvestigateSoundT = 0 ENT.NextCallForHelpSoundT = 0 ENT.LostEnemySoundT = 0 ENT.NextDoAnyAttackT = 0 ENT.NearestPointToEnemyDistance = 0 ENT.LatestEnemyDistance = 0 ENT.HealthRegenerationDelayT = 0 ENT.CurAttackSeed = 0 ENT.CurAnimationSeed = 0 ENT.GuardingPosition = nil ENT.GuardingFacePosition = nil ENT.SelectedDifficulty = 1 ENT.AIState = VJ_STATE_NONE ENT.AttackType = VJ_ATTACK_NONE ENT.AttackStatus = VJ_ATTACK_STATUS_NONE ENT.FacingStatus = VJ_FACING_NONE ENT.FacingData = nil ENT.TimersToRemove = {"timer_state_reset","timer_act_seqreset","timer_facing_end","timer_act_flinching","timer_act_playingattack","timer_act_stopattacks","timer_melee_finished","timer_melee_start","timer_melee_finished_abletomelee","timer_range_start","timer_range_finished","timer_range_finished_abletorange","timer_leap_start_jump","timer_leap_start","timer_leap_finished","timer_leap_finished_abletoleap","timer_alerted_reset"} ENT.FollowData = {Ent = NULL, MinDist = 0, Moving = false, StopAct = false, IsLiving = false} ENT.EnemyData = { TimeSet = 0, -- Last time an enemy was set | Updated whenever "VJ_DoSetEnemy" is ran successfully TimeSinceAcquired = 0, -- Time since it acquired an enemy (Switching enemies does NOT reset this!) IsVisible = false, -- Is the enemy visible? | Updated every "Think" run! LastVisibleTime = 0, -- Last time it saw the current enemy LastVisiblePos = Vector(0, 0, 0), -- Last visible position of the current enemy VisibleCount = 0, -- Number of visible enemies SightDiff = 0, -- Difference between enemy's position and NPC's sight direction | Examples: Determine if the enemy is within the NPC's sight angle or melee attack radius Reset = true, -- Enemy has reset | Mostly a backend function } ENT.EatingData = nil //ENT.DefaultGibOnDeathDamageTypes = {[DMG_ALWAYSGIB]=true,[DMG_ENERGYBEAM]=true,[DMG_BLAST]=true,[DMG_VEHICLE]=true,[DMG_CRUSH]=true,[DMG_DISSOLVE]=true,[DMG_SLOWBURN]=true,[DMG_PHYSGUN]=true,[DMG_PLASMA]=true,[DMG_SONIC]=true} //ENT.SavedDmgInfo = {} -- Set later -- Localized static values local destructibleEnts = {func_breakable=true, func_physbox=true, prop_door_rotating=true} // func_breakable_surf local defPos = Vector(0, 0, 0) local IsProp = VJ_IsProp local CurTime = CurTime local IsValid = IsValid local GetConVar = GetConVar local isstring = isstring local isnumber = isnumber local tonumber = tonumber local math_clamp = math.Clamp local math_rad = math.rad local math_cos = math.cos local math_angApproach = math.ApproachAngle local math_angDif = math.AngleDifference local varCAnt = "CLASS_ANTLION" local varCCom = "CLASS_COMBINE" local varCZom = "CLASS_ZOMBIE" --------------------------------------------------------------------------------------------------------------------------------------------- local function ConvarsOnInit(self) --<>-- Convars that run on Initialize --<>-- if GetConVar("vj_npc_usedevcommands"):GetInt() == 1 then self.VJ_DEBUG = true end self.NextProcessTime = GetConVar("vj_npc_processtime"):GetInt() if GetConVar("vj_npc_sd_nosounds"):GetInt() == 1 then self.HasSounds = false end if GetConVar("vj_npc_vjfriendly"):GetInt() == 1 then self:VJTags_Add(VJ_TAG_VJ_FRIENDLY) end if GetConVar("vj_npc_playerfriendly"):GetInt() == 1 then self.PlayerFriendly = true end if GetConVar("vj_npc_antlionfriendly"):GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = varCAnt end if GetConVar("vj_npc_combinefriendly"):GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = varCCom end if GetConVar("vj_npc_zombiefriendly"):GetInt() == 1 then self.VJ_NPC_Class[#self.VJ_NPC_Class + 1] = varCZom end if GetConVar("vj_npc_noallies"):GetInt() == 1 then self.HasAllies = false self.PlayerFriendly = false end if GetConVar("vj_npc_nocorpses"):GetInt() == 1 then self.HasDeathRagdoll = false end if GetConVar("vj_npc_itemdrops"):GetInt() == 0 then self.HasItemDropsOnDeath = false end if GetConVar("vj_npc_noproppush"):GetInt() == 1 then self.PushProps = false end if GetConVar("vj_npc_nopropattack"):GetInt() == 1 then self.AttackProps = false end if GetConVar("vj_npc_bleedenemyonmelee"):GetInt() == 1 then self.MeleeAttackBleedEnemy = false end if GetConVar("vj_npc_slowplayer"):GetInt() == 1 then self.SlowPlayerOnMeleeAttack = false end if GetConVar("vj_npc_nowandering"):GetInt() == 1 then self.DisableWandering = true end if GetConVar("vj_npc_nochasingenemy"):GetInt() == 1 then self.DisableChasingEnemy = true end if GetConVar("vj_npc_noflinching"):GetInt() == 1 then self.CanFlinch = false end if GetConVar("vj_npc_nomelee"):GetInt() == 1 then self.HasMeleeAttack = false end if GetConVar("vj_npc_norange"):GetInt() == 1 then self.HasRangeAttack = false end if GetConVar("vj_npc_noleap"):GetInt() == 1 then self.HasLeapAttack = false end if GetConVar("vj_npc_nobleed"):GetInt() == 1 then self.Bleeds = false end if GetConVar("vj_npc_godmodesnpc"):GetInt() == 1 then self.GodMode = true end if GetConVar("vj_npc_nobecomeenemytoply"):GetInt() == 1 then self.BecomeEnemyToPlayer = false end if GetConVar("vj_npc_nocallhelp"):GetInt() == 1 then self.CallForHelp = false end if GetConVar("vj_npc_noeating"):GetInt() == 1 then self.CanEat = false end if GetConVar("vj_npc_nofollowplayer"):GetInt() == 1 then self.FollowPlayer = false end if GetConVar("vj_npc_nosnpcchat"):GetInt() == 1 then self.AllowPrintingInChat = false end if GetConVar("vj_npc_nomedics"):GetInt() == 1 then self.IsMedicSNPC = false end if GetConVar("vj_npc_novfx_gibdeath"):GetInt() == 1 then self.HasGibDeathParticles = false end if GetConVar("vj_npc_nogib"):GetInt() == 1 then self.AllowedToGib = false self.HasGibOnDeath = false end if GetConVar("vj_npc_usegmoddecals"):GetInt() == 1 then self.BloodDecalUseGMod = true end if GetConVar("vj_npc_knowenemylocation"):GetInt() == 1 then self.FindEnemy_UseSphere = true self.FindEnemy_CanSeeThroughWalls = true end if GetConVar("vj_npc_sd_gibbing"):GetInt() == 1 then self.HasGibOnDeathSounds = false end if GetConVar("vj_npc_sd_soundtrack"):GetInt() == 1 then self.HasSoundTrack = false end if GetConVar("vj_npc_sd_footstep"):GetInt() == 1 then self.HasFootStepSound = false end if GetConVar("vj_npc_sd_idle"):GetInt() == 1 then self.HasIdleSounds = false end if GetConVar("vj_npc_sd_breath"):GetInt() == 1 then self.HasBreathSound = false end if GetConVar("vj_npc_sd_alert"):GetInt() == 1 then self.HasAlertSounds = false end if GetConVar("vj_npc_sd_meleeattack"):GetInt() == 1 then self.HasMeleeAttackSounds = false self.HasExtraMeleeAttackSounds = false end if GetConVar("vj_npc_sd_meleeattackmiss"):GetInt() == 1 then self.HasMeleeAttackMissSounds = false end if GetConVar("vj_npc_sd_slowplayer"):GetInt() == 1 then self.HasMeleeAttackSlowPlayerSound = false end if GetConVar("vj_npc_sd_rangeattack"):GetInt() == 1 then self.HasBeforeRangeAttackSound = false self.HasRangeAttackSound = false end if GetConVar("vj_npc_sd_leapattack"):GetInt() == 1 then self.HasBeforeLeapAttackSound = false self.HasLeapAttackJumpSound = false self.HasLeapAttackDamageSound = false self.HasLeapAttackDamageMissSound = false end if GetConVar("vj_npc_sd_pain"):GetInt() == 1 then self.HasPainSounds = false end if GetConVar("vj_npc_sd_death"):GetInt() == 1 then self.HasDeathSounds = false end if GetConVar("vj_npc_sd_followplayer"):GetInt() == 1 then self.HasFollowPlayerSounds_Follow = false self.HasFollowPlayerSounds_UnFollow = false end if GetConVar("vj_npc_sd_becomenemytoply"):GetInt() == 1 then self.HasBecomeEnemyToPlayerSounds = false end if GetConVar("vj_npc_sd_onplayersight"):GetInt() == 1 then self.HasOnPlayerSightSounds = false end if GetConVar("vj_npc_sd_medic"):GetInt() == 1 then self.HasMedicSounds_BeforeHeal = false self.HasMedicSounds_AfterHeal = false self.HasMedicSounds_ReceiveHeal = false end if GetConVar("vj_npc_sd_callforhelp"):GetInt() == 1 then self.HasCallForHelpSounds = false end if GetConVar("vj_npc_sd_onreceiveorder"):GetInt() == 1 then self.HasOnReceiveOrderSounds = false end if GetConVar("vj_npc_creatureopendoor"):GetInt() == 0 then self.CanOpenDoors = false end local corpseCollision = GetConVar("vj_npc_corpsecollision"):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 end --------------------------------------------------------------------------------------------------------------------------------------------- local defIdleTbl = {ACT_IDLE} -- function ENT:Initialize() if self.AnimTbl_IdleStand == nil then self.AnimTbl_IdleStand = defIdleTbl end self:CustomOnPreInitialize() self:SetSpawnEffect(false) self:SetRenderMode(RENDERMODE_NORMAL) // RENDERMODE_TRANSALPHA self:AddEFlags(EFL_NO_DISSOLVE) self:SetUseType(SIMPLE_USE) if self:GetName() == "" then self:SetName((self.PrintName == "" and list.Get("NPC")[self:GetClass()].Name) or self.PrintName) end self.SelectedDifficulty = GetConVar("vj_npc_difficulty"):GetInt() if VJ_PICK(self.Model) != false then self:SetModel(VJ_PICK(self.Model)) end if self.HasHull == true then self:SetHullType(self.HullType) end if self.HullSizeNormal == true then self:SetHullSizeNormal() end if self.HasSetSolid == true then self:SetSolid(SOLID_BBOX) end // SOLID_OBB self:SetCollisionGroup(COLLISION_GROUP_NPC) //self:SetCustomCollisionCheck() -- Used for the hook GM:ShouldCollide, not reliable! self:SetMaxYawSpeed(self.TurningSpeed) ConvarsOnInit(self) self:DoChangeMovementType() if self.SetSurroundingBoundsType then -- !!!!!!!!!!!!!! Outdated GMod Compatibility! !!!!!!!!!!!!!! self:SetSurroundingBoundsType(BOUNDS_HITBOXES) // BOUNDS_COLLISION end self.ExtraCorpsesToRemove_Transition = {} self.VJ_AddCertainEntityAsEnemy = {} self.VJ_AddCertainEntityAsFriendly = {} self.CurrentPossibleEnemies = {} self.NextIdleSoundT_RegularChange = CurTime() + math.random(0.3, 6) self.UseTheSameGeneralSoundPitch_PickedNumber = (self.UseTheSameGeneralSoundPitch and math.random(self.GeneralSoundPitch1, self.GeneralSoundPitch2)) or 0 self:SetupBloodColor(self.BloodColor) if self.DisableInitializeCapabilities == false then self:SetInitializeCapabilities() end self:SetHealth((GetConVar("vj_npc_allhealth"):GetInt() > 0) and GetConVar("vj_npc_allhealth"):GetInt() or self:VJ_GetDifficultyValue(self.StartHealth)) self.StartHealth = self:Health() self:CustomOnInitialize() self:CustomInitialize() -- !!!!!!!!!!!!!! DO NOT USE THIS FUNCTION !!!!!!!!!!!!!! [Backwards Compatibility!] 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 self.SightDistance = (GetConVar("vj_npc_seedistance"):GetInt() > 0) and GetConVar("vj_npc_seedistance"):GetInt() or self.SightDistance timer.Simple(0.15, function() if IsValid(self) then self:SetSightDistance(self.SightDistance) 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() -- pitch if self:LookupPoseParameter("aim_pitch") then self.PoseParameterLooking_Names.pitch[#self.PoseParameterLooking_Names.pitch + 1] = "aim_pitch" end if self:LookupPoseParameter("head_pitch") then self.PoseParameterLooking_Names.pitch[#self.PoseParameterLooking_Names.pitch + 1] = "head_pitch" end -- yaw if self:LookupPoseParameter("aim_yaw") then self.PoseParameterLooking_Names.yaw[#self.PoseParameterLooking_Names.yaw + 1] = "aim_yaw" end if self:LookupPoseParameter("head_yaw") then self.PoseParameterLooking_Names.yaw[#self.PoseParameterLooking_Names.yaw + 1] = "head_yaw" end -- roll if self:LookupPoseParameter("aim_roll") then self.PoseParameterLooking_Names.roll[#self.PoseParameterLooking_Names.roll + 1] = "aim_roll" end if self:LookupPoseParameter("head_roll") then self.PoseParameterLooking_Names.roll[#self.PoseParameterLooking_Names.roll + 1] = "head_roll" end end end) duplicator.RegisterEntityClass(self:GetClass(), VJ.CreateDupe_NPC, "Class", "Equipment", "SpawnFlags", "Data") //self:SetSaveValue("m_debugOverlays", 1) -- Enables source engine debug overlays (some commands like 'npc_conditions' need it) end -- !!!!!!!!!!!!!! DO NOT USE THESE !!!!!!!!!!!!!! [Backwards Compatibility!] ENT.MeleeAttacking = false ENT.RangeAttacking = false ENT.LeapAttacking = false function ENT:CustomInitialize() end function ENT:SetNearestPointToEntityPosition() return self:GetDynamicOrigin() end function ENT:SetMeleeAttackDamagePosition() return self:GetMeleeAttackDamageOrigin() end -- !!!!!!!!!!!!!!!!!!!!!!!!!!!! --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:SetInitializeCapabilities() self:CapabilitiesAdd(bit.bor(CAP_SKIP_NAV_GROUND_CHECK)) //self:CapabilitiesAdd(bit.bor(CAP_ANIMATEDFACE)) -- Breaks some NPCs because during high velocity, the model tilts (EX: leap attacks) self:CapabilitiesAdd(bit.bor(CAP_TURN_HEAD)) if self.CanOpenDoors == true then self:CapabilitiesAdd(bit.bor(CAP_OPEN_DOORS)) self:CapabilitiesAdd(bit.bor(CAP_AUTO_DOORS)) self:CapabilitiesAdd(bit.bor(CAP_USE)) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:DoChangeMovementType(movType) movType = movType or -1 if movType != -1 then self.MovementType = movType end if self.MovementType == VJ_MOVETYPE_GROUND then self:RemoveFlags(FL_FLY) self:SetNavType(NAV_GROUND) self:SetMoveType(MOVETYPE_STEP) self:CapabilitiesRemove(CAP_MOVE_FLY) self:CapabilitiesAdd(bit.bor(CAP_MOVE_GROUND)) if VJ_AnimationExists(self,ACT_JUMP) == true or self.UsePlayerModelMovement == true then self:CapabilitiesAdd(bit.bor(CAP_MOVE_JUMP)) end if VJ_AnimationExists(self,ACT_CLIMB_UP) == true then self:CapabilitiesAdd(bit.bor(CAP_MOVE_CLIMB)) end elseif self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then 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:CapabilitiesRemove(CAP_MOVE_GROUND) self:CapabilitiesRemove(CAP_MOVE_JUMP) self:CapabilitiesRemove(CAP_MOVE_CLIMB) self:CapabilitiesRemove(CAP_MOVE_SHOOT) self:CapabilitiesAdd(bit.bor(CAP_MOVE_FLY)) elseif self.MovementType == VJ_MOVETYPE_STATIONARY then self:RemoveFlags(FL_FLY) self:SetNavType(NAV_NONE) if self.Stationary_UseNoneMoveType == true then self:SetMoveType(MOVETYPE_NONE) else self:SetMoveType(MOVETYPE_FLY) end self:CapabilitiesRemove(CAP_MOVE_GROUND) self:CapabilitiesRemove(CAP_MOVE_JUMP) self:CapabilitiesRemove(CAP_MOVE_CLIMB) self:CapabilitiesRemove(CAP_MOVE_SHOOT) self:CapabilitiesRemove(CAP_MOVE_FLY) elseif self.MovementType == VJ_MOVETYPE_PHYSICS then self:RemoveFlags(FL_FLY) self:SetNavType(NAV_NONE) self:SetMoveType(MOVETYPE_VPHYSICS) self:CapabilitiesRemove(CAP_MOVE_GROUND) self:CapabilitiesRemove(CAP_MOVE_JUMP) self:CapabilitiesRemove(CAP_MOVE_CLIMB) self:CapabilitiesRemove(CAP_MOVE_SHOOT) self:CapabilitiesRemove(CAP_MOVE_FLY) end self:CustomOnChangeMovementType(movType) end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- The main animation function, it can play activities, sequences and gestures - animation = The animation to play, it can be a table OR string OR ACT_* - Adding "vjseq_" to a string will make it play as a sequence - Adding "vjges_" to a string will make it play as a gesture - If it's a string AND "vjseq_" or "vjges_" is NOT added: - The base will attempt to convert it activity, if it fails, it will play it as a sequence - This behavior can be overridden by AlwaysUseSequence & AlwaysUseGesture options - stopActivities = If true, it will stop activities such as idle, chasing, attacking, etc. for a given amount of time | DEFAULT: false - stopActivitiesTime = How long it will stop the activities such as idle, chasing, attacking, etc. | DEFAULT: 0 - false = Base calculates the time (recommended) - faceEnemy = Should it constantly face the enemy while playing this animation? | DEFAULT: false - animDelay = Delays the animation by the given number | DEFAULT: 0 - extraOptions = Table that holds extra options to modify parts of the code - OnFinish(interrupted, anim) = A function that runs when the animation finishes | DEFAULT: nil - interrupted = Was the animation cut off? Basically something else stopped it before the animation fully completed - anim = The animation it played, it can be a string or an activity enumeration - AlwaysUseSequence = The base will force attempt to play this animation as a sequence regardless of the other options | DEFAULT: false - AlwaysUseGesture = The base will force attempt to play this animation as a gesture regardless of the other options | DEFAULT: false - SequenceInterruptible = Can this sequence be interrupted? | DEFAULT: false - SequenceDuration = How long is the sequence? | DEFAULT: Base decides - WARNING: Recommended to not change this option, it's mostly used internally by the base! - PlayBackRate = How fast should the animation play? | DEFAULT: self.AnimationPlaybackRate - PlayBackRateCalculated = If the playback rate is already calculated in the stopActivitiesTime, then set this to true! | DEFAULT: false - customFunc() = TODO: NOT FINISHED Returns - Number, Accurate animation play time after taking everything in account - WARNING: If "animDelay" parameter is used, result may be inaccurate! -----------------------------------------------------------]] local varGes = "vjges_" local varSeq = "vjseq_" -- function ENT:VJ_ACT_PLAYACTIVITY(animation, stopActivities, stopActivitiesTime, faceEnemy, animDelay, extraOptions, customFunc) animation = VJ_PICK(animation) if animation == false then return 0 end stopActivities = stopActivities or false if stopActivitiesTime == nil then -- If user didn't put anything, then default it to 0 stopActivitiesTime = 0 -- Set this value to false to let the base calculate the time end faceEnemy = faceEnemy or false -- Should it face the enemy while playing this animation? animDelay = tonumber(animDelay) or 0 -- How much time until it starts playing the animation (seconds) extraOptions = extraOptions or {} local finalPlayBackRate = extraOptions.PlayBackRate or self.AnimationPlaybackRate -- How fast should the animation play? local isGesture = false local isSequence = false local isString = isstring(animation) -- Handle "vjges_" and "vjseq_" if isString then local strExpGes = string.Explode(varGes, animation) -- Gestures if strExpGes[2] then -- If 2 exists, then vjges_ was found! isGesture = true animation = strExpGes[2] -- If current name is -1 then it's probably han activity, so turn it into an activity | EX: "vjges_"..ACT_MELEE_ATTACK1 if self:LookupSequence(animation) == -1 then animation = tonumber(animation) isString = false end else -- Sequences local strExpSeq = string.Explode(varSeq, animation) if strExpSeq[2] then -- If 2 exists, then vjseq_ was found! isSequence = true animation = strExpSeq[2] end end end if extraOptions.AlwaysUseGesture == true then isGesture = true end -- Must play a gesture if extraOptions.AlwaysUseSequence == true then -- Must play a sequence isGesture = false isSequence = true if isnumber(animation) then -- If it's an activity, then convert it to a string animation = self:GetSequenceName(self:SelectWeightedSequence(animation)) end elseif isString && !isSequence then -- Only for regular & gesture strings -- If it can be played as an activity, then convert it! local result = self:GetSequenceActivity(self:LookupSequence(animation)) if result == nil or result == -1 then -- Leave it as string isSequence = true else -- Set it as an activity animation = result end end -- If the given animation doesn't exist, then check to see if it does in the weapon translation list if VJ_AnimationExists(self, animation) == false then return -- This isn't a human SNPC, no need to check for weapon translation /*if !isString && IsValid(self:GetActiveWeapon()) then -- If it's an activity and has a valid weapon then check for weapon translation -- If it returns the same activity as animation, then there isn't even a translation for it so don't play any animation =( if self:GetActiveWeapon().IsVJBaseWeapon && self:TranslateToWeaponAnim(animation) == animation then return 0 end else return 0 -- No animation =( end*/ end -- Seed the current animation, used for animation delaying & on complete check local seed = CurTime(); self.CurAnimationSeed = seed local function PlayAct() local animTime = self:DecideAnimationLength(animation, false) if stopActivities == true then if stopActivitiesTime == false then -- false = Let the base calculate the time stopActivitiesTime = animTime elseif !extraOptions.PlayBackRateCalculated then -- Make sure not to calculate the playback rate when it already has! stopActivitiesTime = stopActivitiesTime / self:GetPlaybackRate() animTime = stopActivitiesTime end self:StopAttacks(true) self.vACT_StopAttacks = true self.NextChaseTime = CurTime() + stopActivitiesTime self.NextIdleTime = CurTime() + stopActivitiesTime -- If there is already a timer, then adjust it instead of creating a new one if !timer.Adjust("timer_act_stopattacks"..self:EntIndex(), stopActivitiesTime, 1, function() self.vACT_StopAttacks = false end) then timer.Create("timer_act_stopattacks"..self:EntIndex(), stopActivitiesTime, 1, function() self.vACT_StopAttacks = false end) end end self.CurAnimationSeed = seed -- We need to set it again because self:StopAttacks() above will reset it when it calls to chase enemy! local vsched = ai_vj_schedule.New("vj_act_"..animation) if (customFunc) then customFunc(vsched, animation) end self.NextIdleStandTime = 0 self.VJ_PlayingSequence = false self.VJ_PlayingInterruptSequence = false self.AnimationPlaybackRate = finalPlayBackRate self:SetPlaybackRate(finalPlayBackRate) if isGesture == true then local gesture = false if isstring(animation) then gesture = self:AddGestureSequence(self:LookupSequence(animation)) else gesture = self:AddGesture(animation) end if gesture != false then //self:ClearSchedule() //self:SetLayerBlendIn(1, 0) //self:SetLayerBlendOut(1, 0) self:SetLayerPriority(gesture, 1) // 2 //self:SetLayerWeight(gesture, 1) self:SetLayerPlaybackRate(gesture, finalPlayBackRate * 0.5) //self:SetLayerDuration(gesture, 3) //print(self:GetLayerDuration(gesture)) end elseif isSequence == true then local dur = (extraOptions.SequenceDuration or self:SequenceDuration(self:LookupSequence(animation))) / self.AnimationPlaybackRate if faceEnemy == true then self:FaceCertainEntity(self:GetEnemy(), true, dur) end self:VJ_PlaySequence(animation, finalPlayBackRate, extraOptions.SequenceDuration != false, dur, extraOptions.SequenceInterruptible or false) end if isGesture == false then -- If it's sequence or activity //self:StartEngineTask(GetTaskList("TASK_RESET_ACTIVITY"), 0) //vsched:EngTask("TASK_RESET_ACTIVITY", 0) //if self.Dead then vsched:EngTask("TASK_STOP_MOVING", 0) end //self:FrameAdvance(0) self:TaskComplete() self:StopMoving() self:ClearSchedule() self:ClearGoal() if isSequence == false then -- Only if activity //self:SetActivity(ACT_RESET) self.VJ_PlayingSequence = false if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then self:ResetIdealActivity(animation) //vsched:EngTask("TASK_SET_ACTIVITY", animation) -- To avoid AutoMovement stopping the velocity //elseif faceEnemy == true then //vsched:EngTask("TASK_PLAY_SEQUENCE_FACE_ENEMY", animation) else if faceEnemy == true then self:FaceCertainEntity(self:GetEnemy(), true, animTime) end -- This fixes: Animation NOT applying walk frames if the previous animation was the same if self:GetActivity() == animation then self:ResetSequenceInfo() self:SetSaveValue("sequence", 0) end vsched:EngTask("TASK_PLAY_SEQUENCE", animation) //self:ResetIdealActivity(animation) //self:AutoMovement(self:GetAnimTimeInterval()) end end vsched.IsPlayActivity = true self:StartSchedule(vsched) end -- If it has a OnFinish function, then set the timer to run it when it finishes! if (extraOptions.OnFinish) then timer.Simple(animTime, function() if IsValid(self) && !self.Dead then extraOptions.OnFinish(self.CurAnimationSeed != seed, animation) end end) end return animTime end -- For delay system if animDelay > 0 then timer.Simple(animDelay, function() if IsValid(self) && self.CurAnimationSeed == seed then PlayAct() end end) return animDelay + self:DecideAnimationLength(animation, false) -- Approximation, this may be inaccurate! else return PlayAct() end end --------------------------------------------------------------------------------------------------------------------------------------------- local task_chaseEnemyLOS = ai_vj_schedule.New("vj_chase_enemy_los") task_chaseEnemyLOS:EngTask("TASK_GET_PATH_TO_ENEMY_LOS", 0) //task_chaseEnemyLOS:EngTask("TASK_RUN_PATH", 0) task_chaseEnemyLOS:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) //task_chaseEnemyLOS:EngTask("TASK_FACE_ENEMY", 0) //task_chaseEnemyLOS.ResetOnFail = true task_chaseEnemyLOS.CanShootWhenMoving = true task_chaseEnemyLOS.CanBeInterrupted = true task_chaseEnemyLOS.IsMovingTask = true task_chaseEnemyLOS.MoveType = 1 -- local task_chaseEnemy = ai_vj_schedule.New("vj_chase_enemy") task_chaseEnemy:EngTask("TASK_GET_PATH_TO_ENEMY", 0) //task_chaseEnemy:EngTask("TASK_RUN_PATH", 0) task_chaseEnemy:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) //task_chaseEnemy:EngTask("TASK_FACE_ENEMY", 0) //task_chaseEnemy.ResetOnFail = true task_chaseEnemy.CanShootWhenMoving = true //task_chaseEnemy.StopScheduleIfNotMoving = true task_chaseEnemy.CanBeInterrupted = true task_chaseEnemy.IsMovingTask = true task_chaseEnemy.MoveType = 1 -- local varChaseEnemy = "vj_chase_enemy" function ENT:VJ_TASK_CHASE_ENEMY(doLOSChase) doLOSChase = doLOSChase or false self:ClearCondition(COND_ENEMY_UNREACHABLE) if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then self:AA_ChaseEnemy() return end //if self.CurrentSchedule != nil && self.CurrentSchedule.Name == "vj_chase_enemy" then return end if self:GetNavType() == NAV_JUMP or self:GetNavType() == NAV_CLIMB then return end //if (CurTime() <= self.JumpLegalLandingTime && (self:GetActivity() == ACT_JUMP or self:GetActivity() == ACT_GLIDE or self:GetActivity() == ACT_LAND)) or self:GetActivity() == ACT_CLIMB_UP or self:GetActivity() == ACT_CLIMB_DOWN or self:GetActivity() == ACT_CLIMB_DISMOUNT then return end if (self:GetEnemyLastKnownPos():Distance(self:GetEnemy():GetPos()) <= 12) && self.CurrentSchedule != nil && self.CurrentSchedule.Name == varChaseEnemy then return end self:SetMovementActivity(VJ_PICK(self.AnimTbl_Run)) if doLOSChase == true then task_chaseEnemyLOS.RunCode_OnFinish = function() local ene = self:GetEnemy() if IsValid(ene) then //self:RememberUnreachable(ene, 0) self:VJ_TASK_CHASE_ENEMY(false) end end self:StartSchedule(task_chaseEnemyLOS) else task_chaseEnemy.RunCode_OnFail = function() if self.VJ_TASK_IDLE_STAND then self:VJ_TASK_IDLE_STAND() end end self:StartSchedule(task_chaseEnemy) end end --------------------------------------------------------------------------------------------------------------------------------------------- local table_remove = table.remove -- function ENT:VJ_TASK_IDLE_STAND() if self:IsMoving() or (self.NextIdleTime > CurTime()) or (self.AA_CurrentMoveTime > CurTime()) or self:GetNavType() == NAV_JUMP or self:GetNavType() == NAV_CLIMB then return end // self.CurrentSchedule != nil if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) && self:BusyWithActivity() then return end //if (self.CurrentSchedule != nil && self.CurrentSchedule.Name == "vj_idle_stand") or (self.CurrentAnim_CustomIdle != 0 && VJ_IsCurrentAnimation(self,self.CurrentAnim_CustomIdle) == true) then return end //if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) && self:GetVelocity():Length() > 0 then return end //if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then self:AA_StopMoving() return end /*local vschedIdleStand = ai_vj_schedule.New("vj_idle_stand") //vschedIdleStand:EngTask("TASK_FACE_REASONABLE") vschedIdleStand:EngTask("TASK_STOP_MOVING") vschedIdleStand:EngTask("TASK_WAIT_INDEFINITE") vschedIdleStand.CanBeInterrupted = true self:StartSchedule(vschedIdleStand)*/ local idleAnimTbl = self.AnimTbl_IdleStand local sameAnimFound = false -- If true then it one of the animations in the table is the same as the current! //local numOfAnims = 0 -- Number of valid animations found for k, v in ipairs(idleAnimTbl) do v = VJ_SequenceToActivity(self, v) -- Translate any sequence to activity if v != false then -- Its a valid activity //numOfAnims = numOfAnims + 1 idleAnimTbl[k] = v -- In case it was a sequence, override it with the translated activity number -- Check if its the current idle animation... if sameAnimFound == false && self.CurrentAnim_IdleStand == v then sameAnimFound = true //break end else -- Get rid of any animations that aren't valid! table_remove(idleAnimTbl, k) end end //PrintTable(idleAnimTbl) -- If there is more than 1 animation in the table AND one of the animations is the current animation AND time hasn't expired, then return! /*if #idleAnimTbl > 1 && sameAnimFound == true && self.NextIdleStandTime > CurTime() then return end*/ local pickedAnim = VJ_PICK(idleAnimTbl) -- If no animation was found, then use ACT_IDLE if pickedAnim == false then pickedAnim = ACT_IDLE //sameAnimFound = true end -- If sequence and it has no activity, then don't continue! //pickedAnim = VJ_SequenceToActivity(self,pickedAnim) //if pickedAnim == false then return false end if (!sameAnimFound /*or (sameAnimFound && numOfAnims == 1 && CurTime() > self.NextIdleStandTime)*/) or (CurTime() > self.NextIdleStandTime) then self.CurrentAnim_IdleStand = pickedAnim self.CurIdleStandMove = false -- Old system /*if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) then if self:BusyWithActivity() == true then return end // self:GetSequence() == 0 self:AA_StopMoving() self:VJ_ACT_PLAYACTIVITY(pickedAnim, false, 0, false, 0, {SequenceDuration=false, SequenceInterruptible=true}) // AlwaysUseSequence=true end if self.CurrentSchedule == nil then -- If it's not doing a schedule then reset the activity to make sure it's not already playing the same idle activity! self:StartEngineTask(GetTaskList("TASK_RESET_ACTIVITY"), 0) //self:SetIdealActivity(ACT_RESET) end*/ //self:StartEngineTask(GetTaskList("TASK_PLAY_SEQUENCE"),pickedAnim) if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) then self:AA_StopMoving() end self.CurAnimationSeed = 0 self.VJ_PlayingSequence = false self.VJ_PlayingInterruptSequence = false self:ResetIdealActivity(pickedAnim) timer.Simple(0.01, function() -- So we can make sure the engine has enough time to set the animation if IsValid(self) && self.NextIdleStandTime != 0 then local getSeq = self:GetSequence() self.CurIdleStandMove = self:GetSequenceMoveDist(getSeq) > 0 if VJ_SequenceToActivity(self,self:GetSequenceName(getSeq)) == pickedAnim then -- Nayir yete himagva animation e nooynene self.NextIdleStandTime = CurTime() + ((self:SequenceDuration(getSeq) - 0.01) / self:GetPlaybackRate()) -- Yete nooynene ooremen jamanage tir animation-en yergarootyan chap! end end end) self.NextIdleStandTime = CurTime() + 0.15 -- This is temp, timer above overrides it elseif self.CurIdleStandMove && !self:IsSequenceFinished() then self:AutoMovement(self:GetAnimTimeInterval()) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:DoIdleAnimation(idleType) if self:GetState() == VJ_STATE_ONLY_ANIMATION_CONSTANT or self.Dead or self.VJ_IsBeingControlled or self.PlayingAttackAnimation == true or (self.NextIdleTime > CurTime()) or (self.AA_CurrentMoveTime > CurTime()) or (self.CurrentSchedule != nil && self.CurrentSchedule.Name == "vj_act_resetenemy") then return end idleType = idleType or 0 -- 0 = Random | 1 = Wander | 2 = Idle Stand if self.IdleAlwaysWander == true then idleType = 1 end -- Things that override can't bypass, Forces the NPC to ONLY idle stand! if self.DisableWandering == true or self.IsGuard == true or self.MovementType == VJ_MOVETYPE_STATIONARY or self.IsVJBaseSNPC_Tank == true or self.LastHiddenZone_CanWander == false or self.NextWanderTime > CurTime() or self.IsFollowing == true or self.Medic_Status then idleType = 2 end if idleType == 0 then -- Random (Wander & Idle Stand) if math.random(1, 3) == 1 then self:VJ_TASK_IDLE_WANDER() else self:VJ_TASK_IDLE_STAND() end elseif idleType == 1 then -- Wander self:VJ_TASK_IDLE_WANDER() elseif idleType == 2 then -- Idle Stand self:VJ_TASK_IDLE_STAND() return -- Don't set self.NextWanderTime below end self.NextWanderTime = CurTime() + math.Rand(3, 6) // self.NextIdleTime end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:DoChaseAnimation(alwaysChase) local ene = self:GetEnemy() if self:GetState() == VJ_STATE_ONLY_ANIMATION_CONSTANT or self.Dead or self.VJ_IsBeingControlled or self.Flinching == true or self.IsVJBaseSNPC_Tank == true or !IsValid(ene) or (self.NextChaseTime > CurTime()) or (CurTime() < self.TakingCoverT) or (self.PlayingAttackAnimation == true && self.MovementType != VJ_MOVETYPE_AERIAL && self.MovementType != VJ_MOVETYPE_AQUATIC) then return end if self:VJ_GetNearestPointToEntityDistance(ene) < self.MeleeAttackDistance && self.EnemyData.IsVisible && (self.EnemyData.SightDiff > math_cos(math_rad(self.MeleeAttackAngleRadius))) then if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) then self:AA_StopMoving() end self:VJ_TASK_IDLE_STAND() return end -- Not melee attacking yet but it is in range, so stop moving! alwaysChase = alwaysChase or false -- true = Chase no matter what -- Things that override can't bypass, Forces the NPC to ONLY idle stand! if self.MovementType == VJ_MOVETYPE_STATIONARY or self.IsFollowing == true or self.Medic_Status or self:GetState() == VJ_STATE_ONLY_ANIMATION then self:VJ_TASK_IDLE_STAND() return end -- For non-aggressive SNPCs if self.Behavior == VJ_BEHAVIOR_PASSIVE or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE then self:VJ_TASK_COVER_FROM_ENEMY("TASK_RUN_PATH") self.NextChaseTime = CurTime() + 3 return end if alwaysChase == false && (self.DisableChasingEnemy == true or self.IsGuard == true) then self:VJ_TASK_IDLE_STAND() return end -- If the enemy is not reachable then wander around if self:IsUnreachable(ene) == true then if self.HasRangeAttack == true then -- Ranged NPCs self:VJ_TASK_CHASE_ENEMY(true) elseif math.random(1, 30) == 1 && !self:IsMoving() then self.NextWanderTime = 0 self:DoIdleAnimation(1) self:RememberUnreachable(ene, 4) else self:VJ_TASK_IDLE_STAND() end else -- Is reachable, so chase the enemy! self:VJ_TASK_CHASE_ENEMY() end -- Set the next chase time if self.NextChaseTime > CurTime() then return end -- Don't set it if it's already set! self.NextChaseTime = CurTime() + (((self.LatestEnemyDistance > 2000) and 1) or 0.1) -- If the enemy is far, increase the delay! end --------------------------------------------------------------------------------------------------------------------------------------------- local finishAttack = { [VJ_ATTACK_MELEE] = function(self, skipStopAttacks) if skipStopAttacks != true then timer.Create("timer_melee_finished"..self:EntIndex(), self:DecideAttackTimer(self.NextAnyAttackTime_Melee, self.NextAnyAttackTime_Melee_DoRand, self.TimeUntilMeleeAttackDamage, self.CurrentAttackAnimationDuration), 1, function() self:StopAttacks() self:DoChaseAnimation() end) end timer.Create("timer_melee_finished_abletomelee"..self:EntIndex(), self:DecideAttackTimer(self.NextMeleeAttackTime, self.NextMeleeAttackTime_DoRand), 1, function() self.IsAbleToMeleeAttack = true end) end, [VJ_ATTACK_RANGE] = function(self, skipStopAttacks) if skipStopAttacks != true then timer.Create("timer_range_finished"..self:EntIndex(), self:DecideAttackTimer(self.NextAnyAttackTime_Range, self.NextAnyAttackTime_Range_DoRand, self.TimeUntilRangeAttackProjectileRelease, self.CurrentAttackAnimationDuration), 1, function() self:StopAttacks() self:DoChaseAnimation() end) end timer.Create("timer_range_finished_abletorange"..self:EntIndex(), self:DecideAttackTimer(self.NextRangeAttackTime, self.NextRangeAttackTime_DoRand), 1, function() self.IsAbleToRangeAttack = true end) end, [VJ_ATTACK_LEAP] = function(self, skipStopAttacks) if skipStopAttacks != true then timer.Create("timer_leap_finished"..self:EntIndex(), self:DecideAttackTimer(self.NextAnyAttackTime_Leap, self.NextAnyAttackTime_Leap_DoRand, self.TimeUntilLeapAttackDamage, self.CurrentAttackAnimationDuration), 1, function() self:StopAttacks() self:DoChaseAnimation() end) end timer.Create("timer_leap_finished_abletoleap"..self:EntIndex(), self:DecideAttackTimer(self.NextLeapAttackTime, self.NextLeapAttackTime_DoRand), 1, function() self.IsAbleToLeapAttack = true end) end } -- Climbing Test /*print(self:GetNavType()) self.NextIdleStandTime = 0 self:SetNavType(NAV_CLIMB) climbDest = self:GetPos()+self:GetUp()*5000 climbDir = climbDest - self:GetPos() climbDist = climbDir:GetNormalized() print(climbDir) self:NavSetGoal(climbDest, 100, climbDir) self:MoveClimbStart(climbDest, climbDir, 100, 100) self:MoveClimbExec(climbDest, climbDir, climbDist:Length(), 100, 4) self:SetNavType(NAV_CLIMB) if self:GetNavType() == NAV_CLIMB then climbDest = self:GetPos()+self:GetUp()*500 climbDir = climbDest - self:GetPos() climbDist = climbDir:GetNormalized() local result = self:MoveClimbExec(climbDest, climbDir, climbDist:Length(), 100, 4) print("RESULT:", result) end*/ --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:Think() self:SetCondition(1) -- Fix attachments, bones, positions, angles etc. being broken in NPCs! This condition is used as a backup in case sv_pvsskipanimation isn't disabled! //self:SetPoseParameter("move_yaw",180) //if self.CurrentSchedule != nil then PrintTable(self.CurrentSchedule) end //if self.CurrentTask != nil then PrintTable(self.CurrentTask) end if self.MovementType == VJ_MOVETYPE_GROUND && self:GetVelocity():Length() <= 0 && !self:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) /*&& curSched.IsMovingTask == true*/ then self:DropToFloor() end local curSched = self.CurrentSchedule if curSched != nil then if self:IsMoving() then if curSched.MoveType == 0 && !VJ_HasValue(self.AnimTbl_Walk, self:GetMovementActivity()) then self:SetMovementActivity(VJ_PICK(self.AnimTbl_Walk)) elseif curSched.MoveType == 1 && !VJ_HasValue(self.AnimTbl_Run, self:GetMovementActivity()) then self:SetMovementActivity(VJ_PICK(self.AnimTbl_Run)) end end local blockingEnt = self:GetBlockingEntity() -- No longer needed as the engine now does detects and opens the doors //if self.CanOpenDoors && IsValid(blockingEnt) && (blockingEnt:GetClass() == "func_door" or blockingEnt:GetClass() == "func_door_rotating") && (blockingEnt:HasSpawnFlags(256) or blockingEnt:HasSpawnFlags(1024)) && !blockingEnt:HasSpawnFlags(512) then //blockingEnt:Fire("Open") //end if (curSched.StopScheduleIfNotMoving == true or curSched.StopScheduleIfNotMoving_Any == true) && (!self:IsMoving() or (IsValid(blockingEnt) && (blockingEnt:IsNPC() or curSched.StopScheduleIfNotMoving_Any == true))) then // (self:GetGroundSpeedVelocity():Length() <= 0) == true self:ScheduleFinished(curSched) //self:SetCondition(COND_TASK_FAILED) //self:StopMoving() end -- self:OnMovementFailed() handles some of them, but we do still need this for non-movement failures (EX: Finding cover area) if self:HasCondition(COND_TASK_FAILED) then //print("VJ Base: Task Failed Condition Identified! "..self:GetName()) if self:DoRunCode_OnFail(curSched) == true then self:ClearCondition(COND_TASK_FAILED) end if curSched.ResetOnFail == true then self:ClearCondition(COND_TASK_FAILED) self:StopMoving() //self:SelectSchedule() end end end self:CustomOnThink() local curTime = CurTime() if !self.Dead && self.HasBreathSound && self.HasSounds && curTime > self.NextBreathSoundT then local sdtbl = VJ_PICK(self.SoundTbl_Breath) local dur = 1 if sdtbl != false then VJ_STOPSOUND(self.CurrentBreathSound) dur = (self.NextSoundTime_Breath == true and SoundDuration(sdtbl)) or math.Rand(self.NextSoundTime_Breath.a, self.NextSoundTime_Breath.b) self.CurrentBreathSound = VJ_CreateSound(self, sdtbl, self.BreathSoundLevel, self:VJ_DecideSoundPitch(self.BreathSoundPitch.a, self.BreathSoundPitch.b)) end self.NextBreathSoundT = curTime + dur end --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-- if GetConVar("ai_disabled"):GetInt() == 0 && self:GetState() != VJ_STATE_FREEZE && !self:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) then if self.VJ_DEBUG == true then if GetConVar("vj_npc_printcurenemy"):GetInt() == 1 then print(self:GetClass().."'s Enemy: ",self:GetEnemy()," Alerted? ",self.Alerted) end if GetConVar("vj_npc_printtakingcover"):GetInt() == 1 then if curTime > self.TakingCoverT == true then print(self:GetClass().." Is Not Taking Cover") else print(self:GetClass().." Is Taking Cover ("..self.TakingCoverT-curTime..")") end end if GetConVar("vj_npc_printlastseenenemy"):GetInt() == 1 then PrintMessage(HUD_PRINTTALK, (curTime - self.EnemyData.LastVisibleTime).." ("..self:GetName()..")") end end local eneData = self.EnemyData self:SetPlaybackRate(self.AnimationPlaybackRate) if self:GetArrivalActivity() == -1 then self:SetArrivalActivity(self.CurrentAnim_IdleStand) end if self.UsePlayerModelMovement == true && self.MovementType == VJ_MOVETYPE_GROUND then local moveDir = self:GetMoveDirection(true) if moveDir != defPos then self:SetPoseParameter("move_x", moveDir.x) self:SetPoseParameter("move_y", moveDir.y) if curSched != nil && !curSched.ConstantlyFaceEnemy then self:FaceCertainPosition(self:GetCurWaypointPos()) end end end self:CustomOnThink_AIEnabled() self:IdleSoundCode() if self.DisableFootStepSoundTimer == false then self:FootStepSoundCode() end -- For AA move types if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then local myVelLen = self:GetVelocity():Length() if myVelLen > 0 then if self.AA_CurrentMovePos then local dist = self.AA_CurrentMovePos:Distance(self:GetPos()) -- Make sure we are making progress so we don't get stuck in a infinite movement! if self.AA_CurrentMoveDist == -1 or self.AA_CurrentMoveDist >= dist then self.AA_CurrentMoveDist = dist local moveSpeed = self.AA_CurrentMoveMaxSpeed; -- Only decelerate if the distance is smaller than the max speed! if self.AA_MoveDecelerate > 1 && dist < moveSpeed then moveSpeed = math_clamp(dist, self.AA_CurrentMoveMaxSpeed / self.AA_MoveDecelerate, moveSpeed) elseif self.AA_MoveAccelerate > 0 then moveSpeed = Lerp(FrameTime()*self.AA_MoveAccelerate, myVelLen, moveSpeed) end local velPos = self.AA_CurrentMovePosDir:GetNormal()*moveSpeed local velTimeCur = curTime + (dist / velPos:Length()) if velTimeCur == velTimeCur then -- Check for NaN self.AA_CurrentMoveTime = velTimeCur end self:SetLocalVelocity(velPos) -- We are NOT making any progress, stop the movement else self:AA_StopMoving() end end -- Is aquatic and is NOT completely in water then attempt to go down! if self.MovementType == VJ_MOVETYPE_AQUATIC && self:WaterLevel() <= 2 then self:AA_IdleWander() end if self.CurrentAnim_AAMovement != false then self:AA_MoveAnimation() end -- Not moving, reset its move time! else self.AA_CurrentMoveTime = 0 end end -- Update follow system's data //print("------------------") //PrintTable(self.FollowData) if self.IsFollowing == true then local followData = self.FollowData local followEnt = followData.Ent local followIsLiving = followData.IsLiving //print(self:GetTarget()) if IsValid(followEnt) && (!followIsLiving or (followIsLiving && (self:Disposition(followEnt) == D_LI or self:GetClass() == followEnt:GetClass()) && VJ_IsAlive(followEnt))) then if curTime > self.NextFollowUpdateT && !self.VJTags[VJ_TAG_HEALING] then local distToPly = self:GetPos():Distance(followEnt:GetPos()) local busy = self:BusyWithActivity() 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 we are close then walk otherwise run self:VJ_TASK_GOTO_TARGET((distToPly < (followData.MinDist * 1.5) and "TASK_WALK_PATH") or "TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.ConstantlyFaceEnemyVisible = (IsValid(self:GetActiveWeapon()) and true) or false 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:StopMoving() self:SelectSchedule() end followData.Moving = false end self.NextFollowUpdateT = curTime + self.NextFollowUpdateTime end else self:FollowReset() end end -- Used for AA SNPCs (Deprecated) /*if self.AA_CurrentTurnAng then local setAngs = self.AA_CurrentTurnAng self:SetAngles(Angle(setAngs.p, self:GetAngles().y, setAngs.r)) self:SetIdealYawAndUpdate(setAngs.y) //self:SetAngles(Angle(math_angApproach(self:GetAngles().p, self.AA_CurrentTurnAng.p, self.TurningSpeed),math_angApproach(self:GetAngles().y, self.AA_CurrentTurnAng.y, self.TurningSpeed),math_angApproach(self:GetAngles().r, self.AA_CurrentTurnAng.r, self.TurningSpeed))) end*/ -- Turn to the current face position or entity if self.FacingStatus == VJ_FACING_POSITION then local faceAng = self.FacingData if self.TurningUseAllAxis == true then local myAngs = self:GetAngles() self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r))) end self:SetIdealYawAndUpdate(faceAng.y) elseif self.FacingStatus == VJ_FACING_ENTITY then local faceEnt = self.FacingData if IsValid(faceEnt) then local faceAng = self:GetFaceAngle((faceEnt:GetPos() - self:GetPos()):Angle()) if self.TurningUseAllAxis == true then local myAngs = self:GetAngles() self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r))) end self:SetIdealYawAndUpdate(faceAng.y) end end if !self.Dead then -- Health Regeneration System if self.HasHealthRegeneration == true && curTime > self.HealthRegenerationDelayT then local myHP = self:Health() self:SetHealth(math_clamp(myHP + self.HealthRegenerationAmount, myHP, self:GetMaxHealth())) self.HealthRegenerationDelayT = curTime + math.Rand(self.HealthRegenerationDelay.a, self.HealthRegenerationDelay.b) end -- Run the heavy processes if curTime > self.NextProcessT then self:SetupRelationships() self:DoMedicCheck() self.NextProcessT = curTime + self.NextProcessTime end local plyControlled = self.VJ_IsBeingControlled local myPos = self:GetPos() local ene = self:GetEnemy() local eneValid = IsValid(ene) if eneData.Reset == false then -- Reset enemy if it doesn't exist or it's dead if (!eneValid) or (eneValid && ene:Health() <= 0) then eneData.Reset = true self:ResetEnemy(true) ene = self:GetEnemy() eneValid = IsValid(ene) end -- Reset enemy if it has been unseen for a while if (curTime - eneData.LastVisibleTime) > self.TimeUntilEnemyLost && (!self.IsVJBaseSNPC_Tank) then self:PlaySoundSystem("LostEnemy") eneData.Reset = true self:ResetEnemy(true) ene = self:GetEnemy() eneValid = IsValid(ene) end end -- Eating system if self.CanEat && !plyControlled then local eatingData = self.EatingData if !eatingData then -- Eating data has NOT been initialized, so initialize it! self.EatingData = {Ent = NULL, NextCheck = 0, AnimStatus = "None", OldIdleTbl = nil} -- AnimStatus: "None" = Not prepared (Probably moving to food location) | "Prepared" = Prepared (Ex: Played crouch down anim) | "Eating" = Prepared and is actively eating eatingData = self.EatingData end if eneValid or self.Alerted then if self.VJTags[VJ_TAG_EATING] then eatingData.NextCheck = curTime + 15 self:EatingReset("Enemy") end elseif curTime > eatingData.NextCheck then if self.VJTags[VJ_TAG_EATING] then local food = eatingData.Ent if !IsValid(food) then -- Food no longer exists, reset! eatingData.NextCheck = curTime + 10 self:EatingReset("Unspecified") elseif !self:IsMoving() then local foodDist = self:VJ_GetNearestPointToEntityDistance(food) // myPos:Distance(food:GetPos()) if foodDist > 400 then -- Food too far away, reset! eatingData.NextCheck = curTime + 10 self:EatingReset("Unspecified") elseif foodDist > 30 then -- Food moved a bit, go to new location if self:IsBusy() then -- Something else has come up, stop eating completely! eatingData.NextCheck = curTime + 15 self:EatingReset("Unspecified") else if eatingData.AnimStatus != "None" then -- We need to play get up anim first! eatingData.AnimStatus = "None" self:SetIdleAnimation(eatingData.OldIdleTbl, true) -- Reset the idle animation table in case it changed! eatingData.NextCheck = curTime + (self:CustomOnEat("StopEating", "HaltOnly") or 1) else self.NextWanderTime = CurTime() + math.Rand(3, 5) self:SetState(VJ_STATE_NONE) self:SetLastPosition(select(2, self:VJ_GetNearestPointToEntity(food))) self:VJ_TASK_GOTO_LASTPOS("TASK_WALK_PATH") //self:SetTarget(food) //self:VJ_TASK_GOTO_TARGET("TASK_WALK_PATH") eatingData.NextCheck = curTime + 1 end end else -- No changes, continue eating self:FaceCertainEntity(food, false, 1) self:SetState(VJ_STATE_ONLY_ANIMATION_NOATTACK) if eatingData.AnimStatus != "None" then -- We are already prepared, so eat! eatingData.AnimStatus = "Eating" eatingData.NextCheck = curTime + self:CustomOnEat("Eat") if food:Health() <= 0 then -- Finished eating! eatingData.NextCheck = curTime + 30 self:EatingReset("Devoured") food:TakeDamage(100, self, self) -- For entities that react to dmg, Ex: HLR corpses food:Remove() end else -- We need to first prepare before eating! (Ex: Crouch-down animation eatingData.AnimStatus = "Prepared" eatingData.NextCheck = curTime + (self:CustomOnEat("BeginEating") or 1) end end end elseif self:HasCondition(COND_SMELL) && !self:IsMoving() && !self:IsBusy() then local hint = sound.GetLoudestSoundHint(SOUND_CARCASS, myPos) // GetBestSoundHint = Do NOT use, completely broken! if hint then local food = hint.owner if IsValid(food) /*&& !food.VJTags[VJ_TAG_BEING_EATEN]*/ then if !food.FoodData then local size = food:OBBMaxs():Distance(food:OBBMins()) * 2 food.FoodData = { NumConsumers = 0, Size = size, SizeRemaining = size, } end //print("food", food, self) if food.FoodData.SizeRemaining > 0 && self:CustomOnEat("CheckFood", hint) then local foodData = food.FoodData foodData.NumConsumers = foodData.NumConsumers + 1 foodData.SizeRemaining = foodData.SizeRemaining - self:OBBMaxs():Distance(self:OBBMins()) //PrintTable(hint) self:VJTags_Add(VJ_TAG_EATING) food:VJTags_Add(VJ_TAG_BEING_EATEN) self.EatingData.OldIdleTbl = self.AnimTbl_IdleStand -- Save the current idle anim table in case we gonna change it while eating! eatingData.Ent = food self:CustomOnEat("StartBehavior") self:SetState(VJ_STATE_ONLY_ANIMATION_NOATTACK) self.NextWanderTime = CurTime() + math.Rand(3, 5) end end end else -- No food was found OR it's not eating //eatingData.NextCheck = curTime + 3 end end end if eneValid then local enePos = ene:GetPos() -- Set latest enemy information self:UpdateEnemyMemory(ene, enePos) eneData.Reset = false eneData.IsVisible = plyControlled and self:VisibleVec(enePos) or self:Visible(ene) -- Need to use VisibleVec when controlled because "Visible" will return false randomly eneData.SightDiff = self:GetSightDirection():Dot((enePos - myPos):GetNormalized()) self.LatestEnemyDistance = myPos:Distance(enePos) self.NearestPointToEnemyDistance = self:VJ_GetNearestPointToEntityDistance(ene) if (eneData.SightDiff > math_cos(math_rad(self.SightAngle))) && (self.LatestEnemyDistance < self:GetMaxLookDistance()) && eneData.IsVisible then eneData.LastVisibleTime = curTime eneData.LastVisiblePos = enePos end -- Turning / Facing Enemy if self.ConstantlyFaceEnemy then self:DoConstantlyFaceEnemy() end if self.FacingStatus == VJ_FACING_ENEMY or (self.CombatFaceEnemy == true && self.CurrentSchedule != nil && ((self.CurrentSchedule.ConstantlyFaceEnemy == true) or (self.CurrentSchedule.ConstantlyFaceEnemyVisible == true && eneData.IsVisible))) then local faceAng = self:GetFaceAngle((enePos - myPos):Angle()) if self.TurningUseAllAxis == true then local myAngs = self:GetAngles() self:SetAngles(LerpAngle(FrameTime()*self.TurningSpeed, myAngs, Angle(faceAng.p, myAngs.y, faceAng.r))) end self:SetIdealYawAndUpdate(faceAng.y) end -- Call for help if self.CallForHelp == true && curTime > self.NextCallForHelpT then self:Allies_CallHelp(self.CallForHelpDistance) self.NextCallForHelpT = curTime + self.NextCallForHelpTime end -- Stop chasing at certain distance if self.NoChaseAfterCertainRange && !plyControlled && ((self.NoChaseAfterCertainRange_Type == "OnlyRange" && self.HasRangeAttack) or (self.NoChaseAfterCertainRange_Type == "Regular")) && eneData.IsVisible then local farDist = self.NoChaseAfterCertainRange_FarDistance local closeDist = self.NoChaseAfterCertainRange_CloseDistance if farDist == "UseRangeDistance" then farDist = self.RangeDistance end if closeDist == "UseRangeDistance" then closeDist = self.RangeToMeleeDistance end if (self.LatestEnemyDistance < farDist) && (self.LatestEnemyDistance > closeDist) then -- If the self.NextChaseTime is about to expire, then give it 0.5 delay so it does NOT chase! if (self.NextChaseTime - curTime) < 0.1 then self.NextChaseTime = curTime + 0.5 end local moveType = self.MovementType curSched = self.CurrentSchedule -- Already defined if curSched != nil && curSched.Name == "vj_chase_enemy" then self:StopMoving() end -- Interrupt enemy chasing because we are in range! if moveType == VJ_MOVETYPE_GROUND && !self:IsMoving() && self:OnGround() then self:FaceCertainEntity(ene) end if (moveType == VJ_MOVETYPE_AERIAL or moveType == VJ_MOVETYPE_AQUATIC) then if self.AA_CurrentMoveType == 3 then self:AA_StopMoving() end -- Interrupt enemy chasing because we are in range! if curTime > self.AA_CurrentMoveTime then self:AA_IdleWander(true, "Calm", {FaceDest = !self.ConstantlyFaceEnemy}) /*self:AA_StopMoving()*/ end -- Only face the position if self.ConstantlyFaceEnemy is false! end else if self.CurrentSchedule != nil && self.CurrentSchedule.Name != "vj_chase_enemy" then self:DoChaseAnimation() end end end self:DoPoseParameterLooking() -- Face enemy for stationary types OR attacks if (self.MovementType == VJ_MOVETYPE_STATIONARY && self.CanTurnWhileStationary == true) or (self.MeleeAttackAnimationFaceEnemy == true && self.MeleeAttack_DoingPropAttack == false && self.AttackType == VJ_ATTACK_MELEE) or (self.RangeAttackAnimationFaceEnemy == true && self.AttackType == VJ_ATTACK_RANGE) or ((self.LeapAttackAnimationFaceEnemy == true or (self.LeapAttackAnimationFaceEnemy == 2 && !self.LeapAttackHasJumped)) && self.AttackType == VJ_ATTACK_LEAP) then self:FaceCertainEntity(ene, true) end -- Attacks if !self.vACT_StopAttacks && self:GetState() != VJ_STATE_ONLY_ANIMATION_NOATTACK && self.Behavior != VJ_BEHAVIOR_PASSIVE && self.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && curTime > self.NextDoAnyAttackT then self:CustomAttack(ene, eneData.IsVisible) -- Custom attack if !self.Flinching && !self.FollowData.StopAct && self.AttackType == VJ_ATTACK_NONE then -- Melee Attack if self.HasMeleeAttack == true && self.IsAbleToMeleeAttack then -- Check for possible props that we can attack/push if curTime > self.NextPropAPCheckT then self.PropAP_IsVisible = self:DoPropAPCheck() self.NextPropAPCheckT = curTime + 0.5 end self:MultipleMeleeAttacks() local atkType = 0 -- 0 = No attack | 1 = Normal attack | 2 = Prop attack if (plyControlled == true && self.VJ_TheController:KeyDown(IN_ATTACK)) or (plyControlled == false && self.NearestPointToEnemyDistance < self.MeleeAttackDistance && eneData.IsVisible) then atkType = 1 elseif self.PropAP_IsVisible then -- Check for props to attack/push atkType = 2 end if self:CustomAttackCheck_MeleeAttack() == true && ((plyControlled == true && atkType == 1) or (plyControlled == false && atkType != 0 && (eneData.SightDiff > math_cos(math_rad(self.MeleeAttackAngleRadius))))) then local seed = curTime; self.CurAttackSeed = seed self.AttackType = VJ_ATTACK_MELEE self.AttackStatus = VJ_ATTACK_STATUS_STARTED self.MeleeAttacking = true self.IsAbleToMeleeAttack = false self.RangeAttacking = false self.NextAlertSoundT = curTime + 0.4 if atkType == 2 then self.MeleeAttack_DoingPropAttack = true else self:FaceCertainEntity(ene, true) self.MeleeAttack_DoingPropAttack = false end self:CustomOnMeleeAttack_BeforeStartTimer(seed) timer.Simple(self.BeforeMeleeAttackSounds_WaitTime, function() if IsValid(self) then self:PlaySoundSystem("BeforeMeleeAttack") end end) if self.DisableMeleeAttackAnimation == false then self.CurrentAttackAnimation = VJ_PICK(self.AnimTbl_MeleeAttack) self.CurrentAttackAnimationDuration = self:DecideAnimationLength(self.CurrentAttackAnimation, false, self.MeleeAttackAnimationDecreaseLengthAmount) if self.MeleeAttackAnimationAllowOtherTasks == false then -- Useful for gesture-based attacks self.PlayingAttackAnimation = true timer.Create("timer_act_playingattack"..self:EntIndex(), self.CurrentAttackAnimationDuration, 1, function() self.PlayingAttackAnimation = false end) end self:VJ_ACT_PLAYACTIVITY(self.CurrentAttackAnimation, false, 0, false, self.MeleeAttackAnimationDelay, {SequenceDuration=self.CurrentAttackAnimationDuration}) end if self.TimeUntilMeleeAttackDamage == false then finishAttack[VJ_ATTACK_MELEE](self) else -- If it's not event based... timer.Create("timer_melee_start"..self:EntIndex(), self.TimeUntilMeleeAttackDamage / self:GetPlaybackRate(), self.MeleeAttackReps, function() if self.CurAttackSeed == seed then if atkType == 2 then self:MeleeAttackCode(true) else self:MeleeAttackCode() end end end) if self.MeleeAttackExtraTimers then for k, t in ipairs(self.MeleeAttackExtraTimers) do self:DoAddExtraAttackTimers("timer_melee_start_"..curTime + k, t, function() if self.CurAttackSeed == seed then if atkType == 2 then self:MeleeAttackCode(true) else self:MeleeAttackCode() end end end) end end end self:CustomOnMeleeAttack_AfterStartTimer(seed) end end -- Range Attack if self.HasRangeAttack == true && self.IsAbleToRangeAttack && eneData.IsVisible then self:MultipleRangeAttacks() if self:CustomAttackCheck_RangeAttack() == true && ((plyControlled == true && self.VJ_TheController:KeyDown(IN_ATTACK2)) or (plyControlled == false && (self.LatestEnemyDistance < self.RangeDistance) && (self.LatestEnemyDistance > self.RangeToMeleeDistance) && (eneData.SightDiff > math_cos(math_rad(self.RangeAttackAngleRadius))))) then local seed = curTime; self.CurAttackSeed = seed self.AttackType = VJ_ATTACK_RANGE self.AttackStatus = VJ_ATTACK_STATUS_STARTED self.RangeAttacking = true self.IsAbleToRangeAttack = false if self.RangeAttackAnimationStopMovement == true then self:StopMoving() end self:CustomOnRangeAttack_BeforeStartTimer(seed) self:PlaySoundSystem("BeforeRangeAttack") if self.DisableRangeAttackAnimation == false then self.CurrentAttackAnimation = VJ_PICK(self.AnimTbl_RangeAttack) self.CurrentAttackAnimationDuration = self:DecideAnimationLength(self.CurrentAttackAnimation, false, self.RangeAttackAnimationDecreaseLengthAmount) self.PlayingAttackAnimation = true timer.Create("timer_act_playingattack"..self:EntIndex(), self.CurrentAttackAnimationDuration, 1, function() self.PlayingAttackAnimation = false end) self:VJ_ACT_PLAYACTIVITY(self.CurrentAttackAnimation, false, 0, false, self.RangeAttackAnimationDelay, {SequenceDuration=self.CurrentAttackAnimationDuration}) end if self.TimeUntilRangeAttackProjectileRelease == false then finishAttack[VJ_ATTACK_RANGE](self) else -- If it's not event based... timer.Create("timer_range_start"..self:EntIndex(), self.TimeUntilRangeAttackProjectileRelease / self:GetPlaybackRate(), self.RangeAttackReps, function() if self.CurAttackSeed == seed then self:RangeAttackCode() end end) if self.RangeAttackExtraTimers then for k, t in ipairs(self.RangeAttackExtraTimers) do self:DoAddExtraAttackTimers("timer_range_start_"..curTime + k, t, function() if self.CurAttackSeed == seed then self:RangeAttackCode() end end) end end end self:CustomOnRangeAttack_AfterStartTimer(seed) end end -- Leap Attack if self.HasLeapAttack == true && self.IsAbleToLeapAttack && eneData.IsVisible then self:MultipleLeapAttacks() if self:CustomAttackCheck_LeapAttack() == true && ((plyControlled == true && self.VJ_TheController:KeyDown(IN_JUMP)) or (plyControlled == false && (self:IsOnGround() && self.LatestEnemyDistance < self.LeapDistance) && (self.LatestEnemyDistance > self.LeapToMeleeDistance) && (eneData.SightDiff > math_cos(math_rad(self.LeapAttackAngleRadius))))) then local seed = curTime; self.CurAttackSeed = seed self.AttackType = VJ_ATTACK_LEAP self.AttackStatus = VJ_ATTACK_STATUS_STARTED self.LeapAttacking = true self.IsAbleToLeapAttack = false self.LeapAttackHasJumped = false //self.JumpLegalLandingTime = 0 self:CustomOnLeapAttack_BeforeStartTimer(seed) self:PlaySoundSystem("BeforeRangeAttack") timer.Create("timer_leap_start_jump"..self:EntIndex(), self.TimeUntilLeapAttackVelocity / self:GetPlaybackRate(), 1, function() self:LeapAttackVelocityCode() end) if self.DisableLeapAttackAnimation == false then self.CurrentAttackAnimation = VJ_PICK(self.AnimTbl_LeapAttack) self.CurrentAttackAnimationDuration = self:DecideAnimationLength(self.CurrentAttackAnimation, false, self.LeapAttackAnimationDecreaseLengthAmount) self.PlayingAttackAnimation = true timer.Create("timer_act_playingattack"..self:EntIndex(), self.CurrentAttackAnimationDuration, 1, function() self.PlayingAttackAnimation = false end) self:VJ_ACT_PLAYACTIVITY(self.CurrentAttackAnimation, false, 0, false, self.LeapAttackAnimationDelay, {SequenceDuration=self.CurrentAttackAnimationDuration}) end if self.TimeUntilLeapAttackDamage == false then finishAttack[VJ_ATTACK_LEAP](self) else -- If it's not event based... timer.Create("timer_leap_start"..self:EntIndex(), self.TimeUntilLeapAttackDamage / self:GetPlaybackRate(), self.LeapAttackReps, function() if self.CurAttackSeed == seed then self:LeapDamageCode() end end) if self.LeapAttackExtraTimers then for k, t in ipairs(self.LeapAttackExtraTimers) do self:DoAddExtraAttackTimers("timer_leap_start_"..curTime + k, t, function() if self.CurAttackSeed == seed then self:LeapDamageCode() end end) end end end self:CustomOnLeapAttack_AfterStartTimer(seed) end end end end else -- No enemy if !plyControlled then self:DoPoseParameterLooking(true) //self:ClearPoseParameters() end eneData.TimeSinceAcquired = 0 if eneData.Reset == false && (!self.IsVJBaseSNPC_Tank) then self:PlaySoundSystem("LostEnemy") eneData.Reset = true self:ResetEnemy(true) end end if self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then if IsValid(ene) && self.PlayingAttackAnimation == true && self:VJ_GetNearestPointToEntityDistance(ene) < self.MeleeAttackDistance then self:AA_StopMoving() else self:SelectSchedule() end end -- Guarding Position if self.IsGuard == true && self.IsFollowing == false then if self.GuardingPosition == nil then -- If it hasn't been set then set the guard position to its current position self.GuardingPosition = myPos self.GuardingFacePosition = myPos + self:GetForward()*51 end -- If it's far from the guarding position, then go there! if !self:IsMoving() && self:BusyWithActivity() == false then local dist = myPos:Distance(self.GuardingPosition) -- Distance to the guard position if dist > 50 then self:SetLastPosition(self.GuardingPosition) self:VJ_TASK_GOTO_LASTPOS(dist <= 800 and "TASK_WALK_PATH" or "TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.ConstantlyFaceEnemy = true x.RunCode_OnFinish = function() timer.Simple(0.01, function() if IsValid(self) && !self:IsMoving() && self:BusyWithActivity() == false && self.GuardingFacePosition != nil then self:SetLastPosition(self.GuardingFacePosition) self:VJ_TASK_FACE_X("TASK_FACE_LASTPOSITION") end end) end end) end end end end else -- AI Not enabled if (self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC) then self:AA_StopMoving() end end self:NextThink(curTime + (0.069696968793869 + FrameTime())) return true end -------------------------------------------------------------------------------------------------------------------------------------------- local propColBlacklist = {[COLLISION_GROUP_DEBRIS]=true, [COLLISION_GROUP_DEBRIS_TRIGGER]=true, [COLLISION_GROUP_DISSOLVING]=true, [COLLISION_GROUP_IN_VEHICLE]=true, [COLLISION_GROUP_WORLD]=true} -- function ENT:DoPropAPCheck(customEnts, customMeleeDistance) if !self.PushProps && !self.AttackProps then return false end local myPos = self:GetPos() for _, v in ipairs(customEnts or ents.FindInSphere(self:GetMeleeAttackDamageOrigin(), customMeleeDistance or math_clamp(self.MeleeAttackDamageDistance - 30, self.MeleeAttackDistance, self.MeleeAttackDamageDistance))) do local verifiedEnt = ((destructibleEnts[v:GetClass()] or v.VJ_AddEntityToSNPCAttackList == true) and true) or false -- Whether or not it's a prop or an entity to attack if v:GetClass() == "prop_door_rotating" && v:Health() <= 0 then verifiedEnt = false end -- If it's a door and it has no health, then don't attack it! if IsProp(v) or verifiedEnt then --If it's a prop or a entity then attack local phys = v:GetPhysicsObject() -- Serpevadz abrankner: self:VJ_GetNearestPointToEntityDistance(v) < (customMeleeDistance) && self:Visible(v) if IsValid(phys) && !propColBlacklist[v:GetCollisionGroup()] then local vPos = v:GetPos() local tr = util.TraceLine({ start = myPos, endpos = vPos + v:GetUp()*10, filter = self }) if (IsValid(tr.Entity) && !tr.HitWorld && !tr.HitSky) && (self:GetSightDirection():Dot((vPos - myPos):GetNormalized()) > math_cos(math_rad(self.MeleeAttackAngleRadius / 1.3))) then if verifiedEnt then return true end -- Since it's an entity, no need to check for size etc. -- Attacking: Make sure it has health if self.AttackProps == true && v:Health() > 0 then return true end -- Pushing: Make sure it's not a small object and the NPC is appropriately sized to push the object if self.PushProps == true && phys:GetMass() > 4 && phys:GetSurfaceArea() > 800 then local selfPhys = self:GetPhysicsObject() if IsValid(selfPhys) && (selfPhys:GetSurfaceArea() * self.PropAP_MaxSize) >= phys:GetSurfaceArea() then return true end end end end end end return false end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:MeleeAttackCode(isPropAttack, attackDist, customEnt) if self.Dead or self.vACT_StopAttacks or self.Flinching or (self.StopMeleeAttackAfterFirstHit && self.AttackStatus == VJ_ATTACK_STATUS_EXECUTED_HIT) then return end isPropAttack = isPropAttack or self.MeleeAttack_DoingPropAttack -- Is this a prop attack? attackDist = attackDist or self.MeleeAttackDamageDistance -- How far should the attack go? local curEnemy = customEnt or self:GetEnemy() if self.MeleeAttackAnimationFaceEnemy && !isPropAttack then self:FaceCertainEntity(curEnemy, true) end //self.MeleeAttacking = true self:CustomOnMeleeAttack_BeforeChecks() if self.DisableDefaultMeleeAttackCode then return end local myPos = self:GetPos() local hitRegistered = false for _, v in ipairs(ents.FindInSphere(self:GetMeleeAttackDamageOrigin(), attackDist)) do if (self.VJ_IsBeingControlled && self.VJ_TheControllerBullseye == v) or (v:IsPlayer() && v.IsControlingNPC == true) then continue end -- If controlled and v is the bullseye OR it's a player controlling then don't damage! if v != self && v:GetClass() != self:GetClass() && (((v:IsNPC() or (v:IsPlayer() && v:Alive() && !VJ_CVAR_IGNOREPLAYERS)) && self:Disposition(v) != D_LI) or IsProp(v) == true or v:GetClass() == "func_breakable_surf" or destructibleEnts[v:GetClass()] or v.VJ_AddEntityToSNPCAttackList == true) && self:GetSightDirection():Dot((Vector(v:GetPos().x, v:GetPos().y, 0) - Vector(myPos.x, myPos.y, 0)):GetNormalized()) > math_cos(math_rad(self.MeleeAttackDamageAngleRadius)) then if isPropAttack == true && (v:IsPlayer() or v:IsNPC()) && self:VJ_GetNearestPointToEntityDistance(v) > self.MeleeAttackDistance then continue end //if (self:GetPos():Distance(v:GetPos()) <= self:VJ_GetNearestPointToEntityDistance(v) && self:VJ_GetNearestPointToEntityDistance(v) <= self.MeleeAttackDistance) == false then local vProp = IsProp(v) if self:CustomOnMeleeAttack_AfterChecks(v, vProp) == true then continue end -- Remove prop constraints and push it (If possible) if vProp then local phys = v:GetPhysicsObject() if IsValid(phys) && self:DoPropAPCheck({v}, attackDist) then hitRegistered = true phys:EnableMotion(true) //phys:EnableGravity(true) phys:Wake() //constraint.RemoveAll(v) //if util.IsValidPhysicsObject(v, 1) then constraint.RemoveConstraints(v, "Weld") //end if self.PushProps then phys:ApplyForceCenter((curEnemy != nil and curEnemy:GetPos() or myPos) + self:GetForward()*(phys:GetMass() * 700) + self:GetUp()*(phys:GetMass() * 200)) end end end -- Knockback if self.HasMeleeAttackKnockBack && v.MovementType != VJ_MOVETYPE_STATIONARY && (!v.VJ_IsHugeMonster or v.IsVJBaseSNPC_Tank) then v:SetGroundEntity(NULL) -- !!!!!!!!!!!!!! DO NOT USE THESE !!!!!!!!!!!!!! [Backwards Compatibility!] if self.MeleeAttackKnockBack_Forward1 or self.MeleeAttackKnockBack_Forward2 or self.MeleeAttackKnockBack_Up1 or self.MeleeAttackKnockBack_Up2 then v:SetVelocity(self:GetForward()*math.random(self.MeleeAttackKnockBack_Forward1 or 100, self.MeleeAttackKnockBack_Forward2 or 100) + self:GetUp()*math.random(self.MeleeAttackKnockBack_Up1 or 10, self.MeleeAttackKnockBack_Up2 or 10) + self:GetRight()*math.random(self.MeleeAttackKnockBack_Right1 or 0, self.MeleeAttackKnockBack_Right2 or 0)) else -- !!!!!!!!!!!!!!!!!!!!!!!!!!!! v:SetVelocity(self:MeleeAttackKnockbackVelocity(v)) end end -- Apply actual damage if !self.DisableDefaultMeleeAttackDamageCode then local applyDmg = DamageInfo() applyDmg:SetDamage(self:VJ_GetDifficultyValue(self.MeleeAttackDamage)) applyDmg:SetDamageType(self.MeleeAttackDamageType) //applyDmg:SetDamagePosition(self:VJ_GetNearestPointToEntity(v).MyPosition) if v:IsNPC() or v:IsPlayer() then applyDmg:SetDamageForce(self:GetForward() * ((applyDmg:GetDamage() + 100) * 70)) end applyDmg:SetInflictor(self) applyDmg:SetAttacker(self) v:TakeDamageInfo(applyDmg, self) end -- Bleed Enemy if self.MeleeAttackBleedEnemy == true && math.random(1, self.MeleeAttackBleedEnemyChance) == 1 && ((v:IsNPC() && (!VJ_IsHugeMonster)) or v:IsPlayer()) then local tName = "timer_melee_bleedply"..v:EntIndex() -- Timer's name local tDmg = self.MeleeAttackBleedEnemyDamage -- How much damage each rep does timer.Create(tName, self.MeleeAttackBleedEnemyTime, self.MeleeAttackBleedEnemyReps, function() if IsValid(v) && v:Health() > 0 then v:TakeDamage(tDmg, self, self) else -- Remove the timer if the entity is dead in attempt to remove it before the entity respawns (Essential for players) timer.Remove(tName) end end) end if v:IsPlayer() then -- Apply DSP if self.MeleeAttackDSPSoundType != false && ((self.MeleeAttackDSPSoundUseDamage == false) or (self.MeleeAttackDSPSoundUseDamage == true && self.MeleeAttackDamage >= self.MeleeAttackDSPSoundUseDamageAmount && GetConVar("vj_npc_nomeleedmgdsp"):GetInt() == 0)) then v:SetDSP(self.MeleeAttackDSPSoundType, false) end v:ViewPunch(Angle(math.random(-1, 1) * self.MeleeAttackDamage, math.random(-1, 1) * self.MeleeAttackDamage, math.random(-1, 1) * self.MeleeAttackDamage)) -- Slow Player if self.SlowPlayerOnMeleeAttack == true then self:VJ_DoSlowPlayer(v, self.SlowPlayerOnMeleeAttack_WalkSpeed, self.SlowPlayerOnMeleeAttack_RunSpeed, self.SlowPlayerOnMeleeAttackTime, {PlaySound=self.HasMeleeAttackSlowPlayerSound, SoundTable=self.SoundTbl_MeleeAttackSlowPlayer, SoundLevel=self.MeleeAttackSlowPlayerSoundLevel, FadeOutTime=self.MeleeAttackSlowPlayerSoundFadeOutTime}) end end VJ_DestroyCombineTurret(self,v) if !vProp then -- Only for non-props... hitRegistered = true end end end if self.AttackStatus < VJ_ATTACK_STATUS_EXECUTED then self.AttackStatus = VJ_ATTACK_STATUS_EXECUTED if self.TimeUntilMeleeAttackDamage != false then finishAttack[VJ_ATTACK_MELEE](self) end end if hitRegistered == true then self:PlaySoundSystem("MeleeAttack") self.AttackStatus = VJ_ATTACK_STATUS_EXECUTED_HIT else self:CustomOnMeleeAttack_Miss() -- !!!!!!!!!!!!!! DO NOT USE THESE !!!!!!!!!!!!!! [Backwards Compatibility!] if self.MeleeAttackWorldShakeOnMiss then util.ScreenShake(myPos, self.MeleeAttackWorldShakeOnMissAmplitude or 16, 100, self.MeleeAttackWorldShakeOnMissDuration or 1, self.MeleeAttackWorldShakeOnMissRadius or 2000) end -- !!!!!!!!!!!!!!!!!!!!!!!!!!!! self:PlaySoundSystem("MeleeAttackMiss", {}, VJ_EmitSound) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:VJ_DoSlowPlayer(ent, WalkSpeed, RunSpeed, SlowTime, sdData, ExtraFeatures, customFunc) WalkSpeed = WalkSpeed or 50 RunSpeed = RunSpeed or 50 SlowTime = SlowTime or 5 sdData = sdData or {} local vSD_PlaySound = sdData.PlaySound or false -- Should it play a sound? local vSD_SoundTable = sdData.SoundTable or {} -- Sounds it should play (Picks randomly) local vSD_SoundLevel = sdData.SoundLevel or 100 -- How loud should the sound play? local vSD_FadeOutTime = sdData.FadeOutTime or 1 -- How long until it the sound fully fades out? ExtraFeatures = ExtraFeatures or {} vEF_NoInterrupt = ExtraFeatures.NoInterrupt or false -- If set to true, the player's speed won't change by another instance of this code local walkspeed_before = ent:GetWalkSpeed() local runspeed_before = ent:GetRunSpeed() if ent.VJ_HasAlreadyBeenSlowedDown == true && ent.VJ_HasAlreadyBeenSlowedDown_NoInterrupt == true then return end if (!ent.VJ_HasAlreadyBeenSlowedDown) then ent.VJ_HasAlreadyBeenSlowedDown = true if vEF_NoInterrupt == true then ent.VJ_HasAlreadyBeenSlowedDown_NoInterrupt = true end ent.VJ_SlowDownPlayerWalkSpeed = walkspeed_before ent.VJ_SlowDownPlayerRunSpeed = runspeed_before end ent:SetWalkSpeed(WalkSpeed) ent:SetRunSpeed(RunSpeed) if (customFunc) then customFunc() end if self.HasSounds == true && vSD_PlaySound == true then self.CurrentSlowPlayerSound = CreateSound(ent,VJ_PICK(vSD_SoundTable)) self.CurrentSlowPlayerSound:Play() self.CurrentSlowPlayerSound:SetSoundLevel(vSD_SoundLevel) if !ent:Alive() && self.CurrentSlowPlayerSound then self.CurrentSlowPlayerSound:FadeOut(vSD_FadeOutTime) end end local slowplysd = self.CurrentSlowPlayerSound local slowplysd_fade = vSD_FadeOutTime local timername = "timer_melee_slowply"..ent:EntIndex() if timer.Exists(timername) && timer.TimeLeft(timername) > SlowTime then return end timer.Create(timername, SlowTime, 1, function() ent:SetWalkSpeed(ent.VJ_SlowDownPlayerWalkSpeed) ent:SetRunSpeed(ent.VJ_SlowDownPlayerRunSpeed) ent.VJ_HasAlreadyBeenSlowedDown = false ent.VJ_HasAlreadyBeenSlowedDown_NoInterrupt = false if slowplysd then slowplysd:FadeOut(slowplysd_fade) end if !IsValid(ent) then timer.Remove(timername) end end) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:RangeAttackCode() if self.Dead or self.vACT_StopAttacks == true or self.Flinching == true or self.AttackType == VJ_ATTACK_MELEE then return end local ene = self:GetEnemy() if IsValid(ene) then self.AttackType = VJ_ATTACK_RANGE self.RangeAttacking = true self:PlaySoundSystem("RangeAttack") if self.RangeAttackAnimationStopMovement == true then self:StopMoving() end if self.RangeAttackAnimationFaceEnemy == true then self:FaceCertainEntity(ene, true) end //self:PointAtEntity(ene) self:CustomRangeAttackCode() -- Default projectile code if self.DisableDefaultRangeAttackCode == false then local projectile = ents.Create(self.RangeAttackEntityToSpawn) local spawnPosOverride = self:RangeAttackCode_OverrideProjectilePos(projectile) if spawnPosOverride == 0 then -- 0 = Let base decide if self.RangeUseAttachmentForPos == false then projectile:SetPos(self:GetPos() + self:GetUp()*self.RangeAttackPos_Up + self:GetForward()*self.RangeAttackPos_Forward + self:GetRight()*self.RangeAttackPos_Right) else projectile:SetPos(self:GetAttachment(self:LookupAttachment(self.RangeUseAttachmentForPosID)).Pos) end else -- Custom position projectile:SetPos(spawnPosOverride) end projectile:SetAngles((ene:GetPos() - projectile:GetPos()):Angle()) self:CustomRangeAttackCode_BeforeProjectileSpawn(projectile) projectile:SetOwner(self) projectile:SetPhysicsAttacker(self) projectile:Spawn() projectile:Activate() //constraint.NoCollide(self, projectile, 0, 0) local phys = projectile:GetPhysicsObject() if IsValid(phys) then phys:Wake() local vel = self:RangeAttackCode_GetShootPos(projectile) phys:SetVelocity(vel) //ApplyForceCenter projectile:SetAngles(vel:GetNormal():Angle()) end self:CustomRangeAttackCode_AfterProjectileSpawn(projectile) end end if self.AttackStatus < VJ_ATTACK_STATUS_EXECUTED then self.AttackStatus = VJ_ATTACK_STATUS_EXECUTED if self.TimeUntilRangeAttackProjectileRelease != false then finishAttack[VJ_ATTACK_RANGE](self) end end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:LeapDamageCode() if self.Dead or self.vACT_StopAttacks == true or self.Flinching == true or (self.StopLeapAttackAfterFirstHit && self.AttackStatus == VJ_ATTACK_STATUS_EXECUTED_HIT) then return end self:CustomOnLeapAttack_BeforeChecks() local hitRegistered = false for _,v in ipairs(ents.FindInSphere(self:GetPos(), self.LeapAttackDamageDistance)) do if (self.VJ_IsBeingControlled && self.VJ_TheControllerBullseye == v) or (v:IsPlayer() && v.IsControlingNPC == true) then continue end if (v:IsNPC() or (v:IsPlayer() && v:Alive()) && !VJ_CVAR_IGNOREPLAYERS) && (self:Disposition(v) != D_LI) && (v != self) && (v:GetClass() != self:GetClass()) or IsProp(v) == true or v:GetClass() == "func_breakable_surf" or v:GetClass() == "func_breakable" then self:CustomOnLeapAttack_AfterChecks(v) -- Damage if self.DisableDefaultLeapAttackDamageCode == false then local leapdmg = DamageInfo() leapdmg:SetDamage(self:VJ_GetDifficultyValue(self.LeapAttackDamage)) leapdmg:SetInflictor(self) leapdmg:SetDamageType(self.LeapAttackDamageType) leapdmg:SetAttacker(self) if v:IsNPC() or v:IsPlayer() then leapdmg:SetDamageForce(self:GetForward() * ((leapdmg:GetDamage() + 100) * 70)) end v:TakeDamageInfo(leapdmg, self) end if v:IsPlayer() then v:ViewPunch(Angle(math.random(-1,1 ) * self.LeapAttackDamage, math.random(-1, 1) * self.LeapAttackDamage,math.random(-1, 1) * self.LeapAttackDamage)) end hitRegistered = true end end if self.AttackStatus < VJ_ATTACK_STATUS_EXECUTED then self.AttackStatus = VJ_ATTACK_STATUS_EXECUTED if self.TimeUntilLeapAttackDamage != false then finishAttack[VJ_ATTACK_LEAP](self) end end if hitRegistered == true then self:PlaySoundSystem("LeapAttackDamage") self.AttackStatus = VJ_ATTACK_STATUS_EXECUTED_HIT else self:CustomOnLeapAttack_Miss() self:PlaySoundSystem("LeapAttackDamageMiss", nil, VJ_EmitSound) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:LeapAttackVelocityCode() local ene = self:GetEnemy() if !IsValid(ene) then return end self:SetGroundEntity(NULL) if self.LeapAttackAnimationFaceEnemy == true then self:FaceCertainEntity(ene, true) end self.LeapAttackHasJumped = true if self:CustomOnLeapAttackVelocityCode() != true then self:SetLocalVelocity(((ene:GetPos() + ene:OBBCenter()) - (self:GetPos() + self:OBBCenter())):GetNormal()*400 + self:GetForward()*self.LeapAttackVelocityForward + self:GetUp()*self.LeapAttackVelocityUp + self:GetRight()*self.LeapAttackVelocityRight) end self:PlaySoundSystem("LeapAttackJump") end --------------------------------------------------------------------------------------------------------------------------------------------- local stopAtkTypes = { [VJ_ATTACK_MELEE] = function(self) finishAttack[VJ_ATTACK_MELEE](self, true) end, [VJ_ATTACK_RANGE] = function(self) finishAttack[VJ_ATTACK_RANGE](self, true) end, [VJ_ATTACK_LEAP] = function(self) finishAttack[VJ_ATTACK_LEAP](self, true) end } -- function ENT:StopAttacks(checkTimers) if self:Health() <= 0 then return end if self.VJ_DEBUG == true && GetConVar("vj_npc_printstoppedattacks"):GetInt() == 1 then print(self:GetClass().." Stopped all Attacks!") end if checkTimers == true && stopAtkTypes[self.AttackType] && self.AttackStatus < VJ_ATTACK_STATUS_EXECUTED then stopAtkTypes[self.AttackType](self) end self.AttackType = VJ_ATTACK_NONE self.AttackStatus = VJ_ATTACK_STATUS_DONE self.CurAttackSeed = 0 self.MeleeAttacking = false self.RangeAttacking = false self.LeapAttacking = false self.LeapAttackHasJumped = false self:DoChaseAnimation() end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:DoPoseParameterLooking(resetPoses) if self.HasPoseParameterLooking == false then return end resetPoses = resetPoses or false //self:GetPoseParameters(true) local ent = (self.VJ_IsBeingControlled and self.VJ_TheController) or self:GetEnemy() local p_enemy = 0 -- Pitch local y_enemy = 0 -- Yaw local r_enemy = 0 -- Roll if IsValid(ent) && !resetPoses then local enemy_pos = (self.VJ_IsBeingControlled and self.VJ_TheControllerBullseye:GetPos()) or ent:GetPos() + ent:OBBCenter() local self_ang = self:GetAngles() local enemy_ang = (enemy_pos - (self:GetPos() + self:OBBCenter())):Angle() p_enemy = math_angDif(enemy_ang.p, self_ang.p) if self.PoseParameterLooking_InvertPitch == true then p_enemy = -p_enemy end y_enemy = math_angDif(enemy_ang.y, self_ang.y) if self.PoseParameterLooking_InvertYaw == true then y_enemy = -y_enemy end r_enemy = math_angDif(enemy_ang.z, self_ang.z) if self.PoseParameterLooking_InvertRoll == true then r_enemy = -r_enemy end elseif !self.PoseParameterLooking_CanReset then -- Should it reset its pose parameters if there is no enemies? return end self:CustomOn_PoseParameterLookingCode(p_enemy, y_enemy, r_enemy) local names = self.PoseParameterLooking_Names for x = 1, #names.pitch do self:SetPoseParameter(names.pitch[x], math_angApproach(self:GetPoseParameter(names.pitch[x]), p_enemy, self.PoseParameterLooking_TurningSpeed)) end for x = 1, #names.yaw do self:SetPoseParameter(names.yaw[x], math_angApproach(self:GetPoseParameter(names.yaw[x]), y_enemy, self.PoseParameterLooking_TurningSpeed)) end for x = 1, #names.roll do self:SetPoseParameter(names.roll[x], math_angApproach(self:GetPoseParameter(names.roll[x]), r_enemy, self.PoseParameterLooking_TurningSpeed)) end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:SelectSchedule() if self.VJ_IsBeingControlled then return end self:CustomOnSchedule() if self.DisableSelectSchedule == true or self.Dead then return end local eneValid = IsValid(self:GetEnemy()) if eneValid && (self.LatestEnemyDistance > self:GetMaxLookDistance()) then -- If the enemy is out of reach, then reset the enemy! self.TakingCoverT = 0 self:DoIdleAnimation() self:ResetEnemy() elseif !self.PlayingAttackAnimation or self.MovementType == VJ_MOVETYPE_AERIAL or self.MovementType == VJ_MOVETYPE_AQUATIC then if eneValid then -- Chase the enemy self:DoChaseAnimation() /*elseif self.Alerted == true then -- No enemy, but alerted self.TakingCoverT = 0 self:DoIdleAnimation()*/ else -- Idle self.TakingCoverT = 0 self:DoIdleAnimation() end end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:ResetEnemy(checkAlliesEnemy) if /*self.NextResetEnemyT > CurTime() or*/ self.Dead then self.EnemyData.Reset = false return false end checkAlliesEnemy = checkAlliesEnemy or false local moveToEnemy = false local ene = self:GetEnemy() local eneValid = IsValid(ene) if checkAlliesEnemy == true then local eneData = self.EnemyData local getAllies = self:Allies_Check(1000) if getAllies != false then for _, v in ipairs(getAllies) do local allyEne = v:GetEnemy() if IsValid(allyEne) && (CurTime() - v.EnemyData.LastVisibleTime) < self.TimeUntilEnemyLost && VJ_IsAlive(allyEne) && self:CheckRelationship(allyEne) == D_HT && self:GetPos():Distance(allyEne:GetPos()) <= self:GetMaxLookDistance() then self:VJ_DoSetEnemy(allyEne, false) eneData.Reset = false return false end end end local curEnemies = eneData.VisibleCount //self.CurrentReachableEnemies -- If the current number of reachable enemies is higher then 1, then don't reset if (eneValid && (curEnemies - 1) >= 1) or (!eneValid && curEnemies >= 1) then //self:VJ_DoSetEnemy(v, false, true) self:SetupRelationships() -- Select a new enemy self.NextProcessT = CurTime() + self.NextProcessTime eneData.Reset = false return false end end self:SetNPCState(NPC_STATE_ALERT) timer.Create("timer_alerted_reset"..self:EntIndex(), math.Rand(self.AlertedToIdleTime.a, self.AlertedToIdleTime.b), 1, function() if !IsValid(self:GetEnemy()) then self.Alerted = false self:SetNPCState(NPC_STATE_IDLE) end end) self:CustomOnResetEnemy() if self.VJ_DEBUG == true && GetConVar("vj_npc_printresetenemy"):GetInt() == 1 then print(self:GetName().." has reseted its enemy") end if eneValid then if self.IsFollowing == false && self.VJ_PlayingSequence == false && (!self.IsVJBaseSNPC_Tank) && self:GetEnemyLastKnownPos() != defPos then self:SetLastPosition(self:GetEnemyLastKnownPos()) moveToEnemy = true end self:MarkEnemyAsEluded(ene) //self:ClearEnemyMemory(ene) // Completely resets the enemy memory self:AddEntityRelationship(ene, D_NU, 10) end -- Clear memory of the enemy if it's not a player AND it's dead if eneValid && !ene:IsPlayer() && !VJ_IsAlive(ene) then //print("Clear memory", ene) self:ClearEnemyMemory(ene) end //self:UpdateEnemyMemory(self,self:GetPos()) //local vsched = ai_vj_schedule.New("vj_act_resetenemy") //if eneValid then vsched:EngTask("TASK_FORGET", ene) end //vsched:EngTask("TASK_IGNORE_OLD_ENEMIES", 0) self.NextWanderTime = CurTime() + math.Rand(3, 5) if !self:IsBusy() && !self.IsGuard && self.Behavior != VJ_BEHAVIOR_PASSIVE && self.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && self.VJ_IsBeingControlled == false && moveToEnemy == true && self.LastHiddenZone_CanWander == true then //ParticleEffect("explosion_turret_break", self.LatestEnemyPosition, Angle(0,0,0)) self:SetMovementActivity(VJ_PICK(self.AnimTbl_Walk)) local vsched = ai_vj_schedule.New("vj_act_resetenemy") vsched:EngTask("TASK_GET_PATH_TO_LASTPOSITION", 0) //vsched:EngTask("TASK_WALK_PATH", 0) vsched:EngTask("TASK_WAIT_FOR_MOVEMENT", 0) vsched.ResetOnFail = true vsched.CanShootWhenMoving = true vsched.ConstantlyFaceEnemy = true vsched.CanBeInterrupted = true vsched.IsMovingTask = true vsched.MoveType = 0 //self.NextIdleTime = CurTime() + 10 self:StartSchedule(vsched) end //if vsched.TaskCount > 0 then //self:StartSchedule(vsched) //end self:SetEnemy(NULL) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnTakeDamage(dmginfo) local dmgInflictor = dmginfo:GetInflictor() local hitgroup = self:GetLastDamageHitGroup() if IsValid(dmgInflictor) && dmgInflictor:GetClass() == "prop_ragdoll" && dmgInflictor:GetVelocity():Length() <= 100 then return 0 end -- Avoid taking damage when walking on ragdolls self:CustomOnTakeDamage_BeforeImmuneChecks(dmginfo, hitgroup) if self.GodMode or dmginfo:GetDamage() <= 0 then return 0 end if self:IsOnFire() && self:WaterLevel() == 2 then self:Extinguish() end -- If we are in water, then extinguish the fire local dmgAttacker = dmginfo:GetAttacker() local dmgType = dmginfo:GetDamageType() local curTime = CurTime() local isFireDmg = self:IsOnFire() && IsValid(dmgInflictor) && IsValid(dmgAttacker) && dmgInflictor:GetClass() == "entityflame" && dmgAttacker:GetClass() == "entityflame" -- If it should always take damage from huge monsters, then skip immunity checks! if self.GetDamageFromIsHugeMonster && dmgAttacker.VJ_IsHugeMonster then goto skip_immunity end if VJ_HasValue(self.ImmuneDamagesTable, dmgType) then return 0 end if self.AllowIgnition == false && isFireDmg then self:Extinguish() return 0 end if self.Immune_Fire == true && (dmgType == DMG_BURN or dmgType == DMG_SLOWBURN or isFireDmg) then return 0 end if (self.Immune_AcidPoisonRadiation == true && (dmgType == DMG_ACID or dmgType == DMG_RADIATION or dmgType == DMG_POISON or dmgType == DMG_NERVEGAS or dmgType == DMG_PARALYZE)) or (self.Immune_Bullet == true && (dmginfo:IsBulletDamage() or dmgType == DMG_BULLET or dmgType == DMG_AIRBOAT or dmgType == DMG_BUCKSHOT)) or (self.Immune_Blast == true && (dmgType == DMG_BLAST or dmgType == DMG_BLAST_SURFACE)) or (self.Immune_Dissolve == true && dmgType == DMG_DISSOLVE) or (self.Immune_Electricity == true && (dmgType == DMG_SHOCK or dmgType == DMG_ENERGYBEAM or dmgType == DMG_PHYSGUN)) or (self.Immune_Melee == true && (dmgType == DMG_CLUB or dmgType == DMG_SLASH)) or (self.Immune_Physics == true && dmgType == DMG_CRUSH) or (self.Immune_Sonic == true && dmgType == DMG_SONIC) then return 0 end if (IsValid(dmgInflictor) && dmgInflictor:GetClass() == "prop_combine_ball") or (IsValid(dmgAttacker) && dmgAttacker:GetClass() == "prop_combine_ball") then if self.Immune_Dissolve == true then return 0 end -- Make sure combine ball does reasonable damage and doesn't spam it! if curTime > self.NextCanGetCombineBallDamageT then dmginfo:SetDamage(math.random(400, 500)) dmginfo:SetDamageType(DMG_DISSOLVE) self.NextCanGetCombineBallDamageT = curTime + 0.2 else return 0 end end ::skip_immunity:: local function DoBleed() if self.Bleeds == true then self:CustomOnTakeDamage_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 self.HasBloodParticle == true && !isFireDmg then self:SpawnBloodParticles(dmginfo, hitgroup) end if self.HasBloodDecal == true then self:SpawnBloodDecal(dmginfo, hitgroup) end self:PlaySoundSystem("Impact", nil, VJ_EmitSound) end end if self.Dead then DoBleed() return 0 end -- If dead then just bleed but take no damage self:CustomOnTakeDamage_BeforeDamage(dmginfo, hitgroup) 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... 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, } self:SetHealth(self:Health() - dmginfo:GetDamage()) if self.VJ_DEBUG == true && GetConVar("vj_npc_printondamage"):GetInt() == 1 then print(self:GetClass().." Got Damaged! | Amount = "..dmginfo:GetDamage()) end if self.HasHealthRegeneration == true && self.HealthRegenerationResetOnDmg == true then self.HealthRegenerationDelayT = curTime + (math.Rand(self.HealthRegenerationDelay.a, self.HealthRegenerationDelay.b) * 1.5) end self:SetSaveValue("m_iDamageCount", self:GetSaveTable().m_iDamageCount + 1) self:SetSaveValue("m_flLastDamageTime", curTime) self:CustomOnTakeDamage_AfterDamage(dmginfo, hitgroup) DoBleed() -- I/O events, from: https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/ai_basenpc.cpp#L764 if IsValid(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 GetConVar("ai_disabled"):GetInt() == 0 && self:GetState() != VJ_STATE_FREEZE then -- Make passive NPCs move away | RESULT: May move away AND may cause other passive NPCs to move as well if (self.Behavior == VJ_BEHAVIOR_PASSIVE or self.Behavior == VJ_BEHAVIOR_PASSIVE_NATURE) && curTime > self.TakingCoverT then if stillAlive && self.Passive_RunOnDamage then self:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH") end if self.Passive_AlliesRunOnDamage then -- Make passive allies run too! local allies = self:Allies_Check(self.Passive_AlliesRunOnDamageDistance) if allies != false then for _, v in ipairs(allies) do v.TakingCoverT = curTime + math.Rand(v.Passive_NextRunOnDamageTime.b, v.Passive_NextRunOnDamageTime.a) v:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH") v:PlaySoundSystem("Alert") end end end self.TakingCoverT = curTime + math.Rand(self.Passive_NextRunOnDamageTime.a, self.Passive_NextRunOnDamageTime.b) end if stillAlive then self:DoFlinch(dmginfo, hitgroup) -- 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 self.HasDamageByPlayer && dmgAttacker:IsPlayer() && curTime > self.NextDamageByPlayerT && self:Visible(dmgAttacker) && (self.DamageByPlayerDispositionLevel == 0 or (self.DamageByPlayerDispositionLevel == 1 && (self:Disposition(dmgAttacker) == D_LI or self:Disposition(dmgAttacker) == D_NU)) or (self.DamageByPlayerDispositionLevel == 2 && self:Disposition(dmgAttacker) != D_HT)) then self:CustomOnDamageByPlayer(dmginfo, hitgroup) self:PlaySoundSystem("DamageByPlayer") self.NextDamageByPlayerT = curTime + math.Rand(self.DamageByPlayerTime.a, self.DamageByPlayerTime.b) end self:PlaySoundSystem("Pain") -- Call for back on damage | RESULT: May play an animation OR it may move away, AND it may bring allies to its location if self.CallForBackUpOnDamage && curTime > self.NextCallForBackUpOnDamageT && !IsValid(self:GetEnemy()) && self.IsFollowing == false && self.Behavior != VJ_BEHAVIOR_PASSIVE && self.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE && !isFireDmg then local allies = self:Allies_Check(self.CallForBackUpOnDamageDistance) if allies != false then self:Allies_Bring("Random", self.CallForBackUpOnDamageDistance, allies, self.CallForBackUpOnDamageLimit) self:ClearSchedule() self.NextFlinchT = curTime + 1 local chosenAnim = VJ_PICK(self.CallForBackUpOnDamageAnimation) local playedAnim = !self.DisableCallForBackUpOnDamageAnimation and self:VJ_ACT_PLAYACTIVITY(chosenAnim, true, self:DecideAnimationLength(chosenAnim, self.CallForBackUpOnDamageAnimationTime), true, 0, {PlayBackRateCalculated=true}) or 0 if playedAnim == 0 && !self:BusyWithActivity() then self:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.ConstantlyFaceEnemy = true end) end self.NextCallForBackUpOnDamageT = curTime + math.Rand(self.NextCallForBackUpOnDamageTime.a, self.NextCallForBackUpOnDamageTime.b) end end -- Become enemy to a friendly player | RESULT: May become alerted if self.BecomeEnemyToPlayer == true && self.VJ_IsBeingControlled == false && dmgAttacker:IsPlayer() && self:CheckRelationship(dmgAttacker) == D_LI then self.AngerLevelTowardsPlayer = self.AngerLevelTowardsPlayer + 1 if self.AngerLevelTowardsPlayer > self.BecomeEnemyToPlayerLevel then if self:Disposition(dmgAttacker) != D_HT then self:CustomOnBecomeEnemyToPlayer(dmginfo, hitgroup) if self.IsFollowing == true && self.FollowData.Ent == dmgAttacker then self:FollowReset() end self.VJ_AddCertainEntityAsEnemy[#self.VJ_AddCertainEntityAsEnemy + 1] = dmgAttacker self:AddEntityRelationship(dmgAttacker, D_HT, 2) self.TakingCoverT = curTime + 2 self:PlaySoundSystem("BecomeEnemyToPlayer") if !IsValid(self:GetEnemy()) then self:StopMoving() self:SetTarget(dmgAttacker) self:VJ_TASK_FACE_X("TASK_FACE_TARGET") end if self.AllowPrintingInChat == true then dmgAttacker:PrintMessage(HUD_PRINTTALK, self:GetName().." no longer likes you.") end end self.Alerted = true self:SetNPCState(NPC_STATE_ALERT) end end -- Attempt to find who damaged me | RESULT: May become alerted if enemy is visible OR it may move away if !self.DisableTakeDamageFindEnemy && !self:BusyWithActivity() && !IsValid(self:GetEnemy()) && curTime > self.TakingCoverT && self.VJ_IsBeingControlled == false && self.Behavior != VJ_BEHAVIOR_PASSIVE && self.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE then // self.Alerted == false local eneFound = false local sightDist = self:GetMaxLookDistance() sightDist = math_clamp(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 for _, v in ipairs(ents.FindInSphere(self:GetPos(), sightDist)) do if (curTime - self.EnemyData.TimeSet) > 2 && self:Visible(v) && self:CheckRelationship(v) == D_HT then self:CustomOnSetEnemyOnDamage(dmginfo, hitgroup) self.NextCallForHelpT = curTime + 1 self:VJ_DoSetEnemy(v, true) self:DoChaseAnimation() eneFound = true break end end if !eneFound && self.HideOnUnknownDamage && !self.IsFollowing && self.MovementType != VJ_MOVETYPE_STATIONARY then self:VJ_TASK_COVER_FROM_ORIGIN("TASK_RUN_PATH", function(x) x.CanShootWhenMoving = true x.ConstantlyFaceEnemy = true end) self.TakingCoverT = curTime + self.HideOnUnknownDamage end end -- Test that makes crossbow bolts stick to the NPC's model /*if dmgInflictor:GetClass() == "crossbow_bolt" then local mdlBolt = ents.Create("prop_dynamic_override") mdlBolt:SetPos(dmginfo:GetDamagePosition()) mdlBolt:SetAngles(dmgAttacker:GetAngles()) mdlBolt:SetModel("models/crossbow_bolt.mdl") mdlBolt:SetParent(self) mdlBolt:Spawn() mdlBolt:Activate() end*/ end end -- If eating, stop! if self.CanEat && self.VJTags[VJ_TAG_EATING] then self.EatingData.NextCheck = curTime + 15 self:EatingReset("Injured") end if self:Health() <= 0 && !self.Dead then self:RemoveEFlags(EFL_NO_DISSOLVE) if (dmginfo:IsDamageType(DMG_DISSOLVE)) or (IsValid(dmgInflictor) && dmgInflictor:GetClass() == "prop_combine_ball") then local dissolve = DamageInfo() dissolve:SetDamage(self:Health()) dissolve:SetAttacker(dmgAttacker) dissolve:SetDamageType(DMG_DISSOLVE) self:TakeDamageInfo(dissolve) end self:PriorToKilled(dmginfo, hitgroup) end return 1 end --------------------------------------------------------------------------------------------------------------------------------------------- local vecZ500 = Vector(0, 0, 500) local vecZ4 = Vector(0, 0, 4) -- function ENT:PriorToKilled(dmginfo, hitgroup) self:CustomOnInitialKilled(dmginfo, hitgroup) if self.Medic_Status then self:DoMedicReset() end local dmgInflictor = dmginfo:GetInflictor() local dmgAttacker = dmginfo:GetAttacker() local allies = self:Allies_Check(math.max(800, self.BringFriendsOnDeathDistance, self.AlertFriendsOnDeathDistance)) if allies != false then local noAlert = true -- Don't run the AlertFriendsOnDeath if we have BringFriendsOnDeath enabled! if self.BringFriendsOnDeath == true then self:Allies_Bring("Random", self.BringFriendsOnDeathDistance, allies, self.BringFriendsOnDeathLimit, true) noAlert = false end local doBecomeEnemyToPlayer = (self.BecomeEnemyToPlayer == true && dmgAttacker:IsPlayer() && GetConVar("ai_disabled"):GetInt() == 0 && !VJ_CVAR_IGNOREPLAYERS) or false local it = 0 -- Number of allies that have been alerted for _, v in ipairs(allies) do v:CustomOnAllyDeath(self) v:PlaySoundSystem("AllyDeath") -- AlertFriendsOnDeath if noAlert == true && self.AlertFriendsOnDeath == true && !IsValid(v:GetEnemy()) && v.AlertFriendsOnDeath == true && it != self.AlertFriendsOnDeathLimit && self:GetPos():Distance(v:GetPos()) < self.AlertFriendsOnDeathDistance then it = it + 1 v:FaceCertainPosition(self:GetPos(), 1) v:VJ_ACT_PLAYACTIVITY(VJ_PICK(v.AnimTbl_AlertFriendsOnDeath)) v.NextIdleTime = CurTime() + math.Rand(5, 8) end -- BecomeEnemyToPlayer if doBecomeEnemyToPlayer && v.BecomeEnemyToPlayer == true && v:Disposition(dmgAttacker) == D_LI then v.AngerLevelTowardsPlayer = v.AngerLevelTowardsPlayer + 1 if v.AngerLevelTowardsPlayer > v.BecomeEnemyToPlayerLevel then if v:Disposition(dmgAttacker) != D_HT then v:CustomOnBecomeEnemyToPlayer(dmginfo, hitgroup) if v.IsFollowing == true && v.FollowData.Ent == dmgAttacker then v:FollowReset() end v.VJ_AddCertainEntityAsEnemy[#v.VJ_AddCertainEntityAsEnemy+1] = dmgAttacker v:AddEntityRelationship(dmgAttacker,D_HT,2) if v.AllowPrintingInChat == true then dmgAttacker:PrintMessage(HUD_PRINTTALK, v:GetName().." no longer likes you.") end v:PlaySoundSystem("BecomeEnemyToPlayer") end v.Alerted = true end end end end local function DoKilled() if IsValid(self) then if self.WaitBeforeDeathTime == 0 then self:OnKilled(dmginfo, hitgroup) else timer.Simple(self.WaitBeforeDeathTime, function() if IsValid(self) then self:OnKilled(dmginfo, hitgroup) end end) end end end -- Blood decal on the ground if self.Bleeds == true && self.HasBloodDecal == true then local bloodDecal = VJ_PICK(self.CustomBlood_Decal) if bloodDecal != false then local decalPos = self:GetPos() + 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.Dead = true if self.IsFollowing == true then self:FollowReset() end self:RemoveTimers() self.AttackType = VJ_ATTACK_NONE self.MeleeAttacking = false self.RangeAttacking = false self.LeapAttacking = false self.HasMeleeAttack = false self.HasRangeAttack = false self.HasLeapAttack = false self:StopAllCommonSounds() if IsValid(dmgAttacker) then if dmgAttacker:GetClass() == "npc_barnacle" then self.HasDeathRagdoll = false end -- Don't make a corpse if it's killed by a barnacle! if GetConVar("vj_npc_addfrags"):GetInt() == 1 && dmgAttacker:IsPlayer() then dmgAttacker:AddFrags(1) end if IsValid(dmgInflictor) then gamemode.Call("OnNPCKilled", self, dmgAttacker, dmgInflictor, dmginfo) end end self:CustomOnPriorToKilled(dmginfo, hitgroup) self:SetCollisionGroup(1) self:RunGibOnDeathCode(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 if self.HasDeathAnimation == true && !dmginfo:IsDamageType(DMG_REMOVENORAGDOLL) then if IsValid(dmgInflictor) && dmgInflictor:GetClass() == "prop_combine_ball" then DoKilled() return end if GetConVar("vj_npc_nodeathanimation"):GetInt() == 0 && GetConVar("ai_disabled"):GetInt() == 0 && !dmginfo:IsDamageType(DMG_DISSOLVE) && math.random(1, self.DeathAnimationChance) == 1 then self:RemoveAllGestures() self:CustomDeathAnimationCode(dmginfo, hitgroup) local chosenAnim = VJ_PICK(self.AnimTbl_Death) local animTime = self:DecideAnimationLength(chosenAnim, self.DeathAnimationTime) - self.DeathAnimationDecreaseLengthAmount self:VJ_ACT_PLAYACTIVITY(chosenAnim, true, animTime, false, 0, {PlayBackRateCalculated=true}) self.DeathAnimationCodeRan = true timer.Simple(animTime, DoKilled) else DoKilled() end else DoKilled() end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:OnKilled(dmginfo, hitgroup) if self.VJ_DEBUG == true && GetConVar("vj_npc_printdied"):GetInt() == 1 then print(self:GetClass().." Died!") end self:CustomOnKilled(dmginfo, hitgroup) self:RunItemDropsOnDeathCode(dmginfo, hitgroup) -- Item drops on death self:ClearEnemyMemory() //self:ClearSchedule() //self:SetNPCState(NPC_STATE_DEAD) if bit.band(self.SavedDmgInfo.type, DMG_REMOVENORAGDOLL) == 0 then 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 self:CustomOnDeath_BeforeCorpseSpawned(dmginfo, hitgroup) if self.HasDeathRagdoll == true then local corpseMdl = self:GetModel() local corpseMdlCustom = VJ_PICK(self.DeathCorpseModel) if corpseMdlCustom != false then corpseMdl = corpseMdlCustom end local corpseType = "prop_physics" if self.DeathCorpseEntityClass == "UseDefaultBehavior" then if util.IsValidRagdoll(corpseMdl) == true then corpseType = "prop_ragdoll" elseif util.IsValidProp(corpseMdl) == false or util.IsValidModel(corpseMdl) == false then return false end else corpseType = self.DeathCorpseEntityClass end //if self.VJCorpseDeleted == true then self.Corpse = ents.Create(corpseType) //end self.Corpse:SetModel(corpseMdl) self.Corpse:SetPos(self:GetPos()) self.Corpse:SetAngles(self:GetAngles()) self.Corpse:Spawn() self.Corpse:Activate() self.Corpse:SetColor(self:GetColor()) self.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 self.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 self.Corpse:SetSubMaterial(x, self:GetSubMaterial(x)) end end*/ end //self.Corpse:SetName("self.Corpse" .. self:EntIndex()) //self.Corpse:SetModelScale(self:GetModelScale()) self.Corpse.FadeCorpseType = (self.Corpse:GetClass() == "prop_ragdoll" and "FadeAndRemove") or "kill" self.Corpse.IsVJBaseCorpse = true self.Corpse.DamageInfo = dmginfo self.Corpse.ExtraCorpsesToRemove = self.ExtraCorpsesToRemove_Transition self.Corpse.BloodData = {Color = self.BloodColor, Particle = self.CustomBlood_Particle, Decal = self.CustomBlood_Decal} if self.Bleeds == true && self.HasBloodPool == true && GetConVar("vj_npc_nobloodpool"):GetInt() == 0 then self:SpawnBloodPool(dmginfo, hitgroup) end -- Collision -- self.Corpse:SetCollisionGroup(self.DeathCorpseCollisionType) if GetConVar("ai_serverragdolls"):GetInt() == 1 then undo.ReplaceEntity(self, self.Corpse) else -- Keep corpses is not enabled... VJ_AddCorpse(self.Corpse) //hook.Call("VJ_CreateSNPCCorpse", nil, self.Corpse, self) if GetConVar("vj_npc_undocorpse"):GetInt() == 1 then undo.ReplaceEntity(self, self.Corpse) end -- Undoable end cleanup.ReplaceEntity(self, self.Corpse) -- Delete on cleanup -- Miscellaneous -- self.Corpse:SetSkin((self.DeathCorpseSkin == -1 and self:GetSkin()) or self.DeathCorpseSkin) if self.DeathCorpseSetBodyGroup == true then -- Yete asega true-e, ooremen gerna bodygroup tenel for i = 0,18 do -- 18 = Bodygroup limit self.Corpse:SetBodygroup(i,self:GetBodygroup(i)) end if self.DeathCorpseBodyGroup.a != -1 then -- Yete asiga nevaz meg chene, user-in teradz tevere kordzadze self.Corpse:SetBodygroup(self.DeathCorpseBodyGroup.a, self.DeathCorpseBodyGroup.b) end end if self:IsOnFire() then -- If was on fire then... self.Corpse:Ignite(math.Rand(8, 10), 0) self.Corpse:SetColor(colorGrey) //self.Corpse:SetMaterial("models/props_foliage/tree_deciduous_01a_trunk") end //gamemode.Call("CreateEntityRagdoll",self,self.Corpse) -- Dissolve -- if (bit.band(self.SavedDmgInfo.type, DMG_DISSOLVE) != 0) or (IsValid(self.SavedDmgInfo.inflictor) && self.SavedDmgInfo.inflictor:GetClass() == "prop_combine_ball") then self.Corpse:SetName("vj_dissolve_corpse") local dissolver = ents.Create("env_entity_dissolver") dissolver:SetPos(self.Corpse:GetPos()) dissolver:Spawn() dissolver:Activate() //dissolver:SetKeyValue("target","vj_dissolve_corpse") dissolver:SetKeyValue("magnitude",100) dissolver:SetKeyValue("dissolvetype",0) dissolver:Fire("Dissolve","vj_dissolve_corpse") if IsValid(self.TheDroppedWeapon) then self.TheDroppedWeapon:SetName("vj_dissolve_weapon") dissolver:Fire("Dissolve","vj_dissolve_weapon") end dissolver:Fire("Kill", "", 0.1) //dissolver:Remove() 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:GetMoveVelocity() == defPos and self:GetGroundSpeedVelocity() or self:GetMoveVelocity() end local totalSurface = 0 local physCount = self.Corpse:GetPhysicsObjectCount() for boneLimit = 0, physCount - 1 do -- 128 = Bone Limit local childphys = self.Corpse:GetPhysicsObjectNum(boneLimit) if IsValid(childphys) then totalSurface = totalSurface + childphys:GetSurfaceArea() local childphys_bonepos, childphys_boneang = self:GetBonePosition(self.Corpse:TranslatePhysBoneToBone(boneLimit)) if (childphys_bonepos) then //if math.Round(math.abs(childphys_boneang.r)) != 90 then -- Fixes ragdolls rotating, no longer needed! ---> sv_pvsskipanimation 0 if self.DeathCorpseSetBoneAngles == true then childphys:SetAngles(childphys_boneang) end childphys:SetPos(childphys_bonepos) //end if self.Corpse:GetName() == "vj_dissolve_corpse" then childphys:EnableGravity(false) childphys:SetVelocity(self:GetForward()*-150 + self:GetRight()*math.Rand(100,-100) + self:GetUp()*50) else if self.DeathCorpseApplyForce == true /*&& self.DeathAnimationCodeRan == false*/ then childphys:SetVelocity(dmgForce / math.max(1, (useLocalVel and childphys_bonepos:Distance(self.SavedDmgInfo.pos)/12) or 1)) end end elseif physCount == 1 then -- If it's only 1, then it's likely a regular physics model with no bones if self.Corpse:GetName() == "vj_dissolve_corpse" then childphys:EnableGravity(false) childphys:SetVelocity(self:GetForward()*-150 + self:GetRight()*math.Rand(100,-100) + self:GetUp()*50) else if self.DeathCorpseApplyForce == true /*&& self.DeathAnimationCodeRan == false*/ then childphys:SetVelocity(dmgForce / math.max(1, (useLocalVel and self.Corpse:GetPos():Distance(self.SavedDmgInfo.pos)/12) or 1)) end end end end end if self.Corpse:Health() <= 0 then local hpCalc = totalSurface / 60 // self.Corpse:OBBMaxs():Distance(self.Corpse:OBBMins()) self.Corpse:SetMaxHealth(hpCalc) self.Corpse:SetHealth(hpCalc) end VJ_AddStinkyEnt(self.Corpse, true) if self.DeathCorpseFade == true then self.Corpse:Fire(self.Corpse.FadeCorpseType,"",self.DeathCorpseFadeTime) end if GetConVar("vj_npc_corpsefade"):GetInt() == 1 then self.Corpse:Fire(self.Corpse.FadeCorpseType,"",GetConVar("vj_npc_corpsefadetime"):GetInt()) end self:CustomOnDeath_AfterCorpseSpawned(dmginfo, hitgroup, self.Corpse) self.Corpse:CallOnRemove("vj_"..self.Corpse:EntIndex(),function(ent,exttbl) for _,v in ipairs(exttbl) do if IsValid(v) then if v:GetClass() == "prop_ragdoll" then v:Fire("FadeAndRemove","",0) else v:Fire("kill","",0) end end end end,self.Corpse.ExtraCorpsesToRemove) hook.Call("CreateEntityRagdoll", nil, self, self.Corpse) return self.Corpse else for _,v in ipairs(self.ExtraCorpsesToRemove_Transition) do if v.IsVJBase_Gib == true && v.RemoveOnCorpseDelete == true then v:Remove() end end end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:PlaySoundSystem(sdSet, customSd, sdType) if self.HasSounds == false or sdSet == nil then return end sdType = sdType or VJ_CreateSound local cTbl = VJ_PICK(customSd) if sdSet == "GeneralSpeech" then -- Used to just play general speech sounds (Custom by developers) if cTbl != false then self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + ((((SoundDuration(cTbl) > 0) and SoundDuration(cTbl)) or 2) + 1) self.CurrentGeneralSpeechSound = sdType(self, cTbl, 80, self:VJ_DecideSoundPitch(self.GeneralSoundPitch1, self.GeneralSoundPitch2)) end return elseif sdSet == "FollowPlayer" then if self.HasFollowPlayerSounds_Follow == true then local sdtbl = VJ_PICK(self.SoundTbl_FollowPlayer) if (math.random(1, self.FollowPlayerSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentFollowPlayerSound = sdType(self, sdtbl, self.FollowPlayerSoundLevel, self:VJ_DecideSoundPitch(self.FollowPlayerPitch.a, self.FollowPlayerPitch.b)) end end return elseif sdSet == "UnFollowPlayer" then if self.HasFollowPlayerSounds_UnFollow == true then local sdtbl = VJ_PICK(self.SoundTbl_UnFollowPlayer) if (math.random(1, self.UnFollowPlayerSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentUnFollowPlayerSound = sdType(self, sdtbl, self.UnFollowPlayerSoundLevel, self:VJ_DecideSoundPitch(self.UnFollowPlayerPitch.a, self.UnFollowPlayerPitch.b)) end end return elseif sdSet == "OnReceiveOrder" then if self.HasOnReceiveOrderSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_OnReceiveOrder) if (math.random(1, self.OnReceiveOrderSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.NextAlertSoundT = CurTime() + 2 self.CurrentOnReceiveOrderSound = sdType(self, sdtbl, self.OnReceiveOrderSoundLevel, self:VJ_DecideSoundPitch(self.OnReceiveOrderSoundPitch.a, self.OnReceiveOrderSoundPitch.b)) end end return elseif sdSet == "MoveOutOfPlayersWay" then if self.HasMoveOutOfPlayersWaySounds == true then local sdtbl = VJ_PICK(self.SoundTbl_MoveOutOfPlayersWay) if (math.random(1, self.MoveOutOfPlayersWaySoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentMoveOutOfPlayersWaySound = sdType(self, sdtbl, self.MoveOutOfPlayersWaySoundLevel, self:VJ_DecideSoundPitch(self.MoveOutOfPlayersWaySoundPitch.a, self.MoveOutOfPlayersWaySoundPitch.b)) end end return elseif sdSet == "MedicBeforeHeal" then if self.HasMedicSounds_BeforeHeal == true then local sdtbl = VJ_PICK(self.SoundTbl_MedicBeforeHeal) if (math.random(1, self.MedicBeforeHealSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentMedicBeforeHealSound = sdType(self, sdtbl, self.BeforeHealSoundLevel, self:VJ_DecideSoundPitch(self.BeforeHealSoundPitch.a, self.BeforeHealSoundPitch.b)) end end return elseif sdSet == "MedicOnHeal" then if self.HasMedicSounds_AfterHeal == true then local sdtbl = VJ_PICK(self.SoundTbl_MedicAfterHeal) if sdtbl == false then sdtbl = VJ_PICK(DefaultSoundTbl_MedicAfterHeal) end -- Default table if (math.random(1, self.MedicAfterHealSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentMedicAfterHealSound = sdType(self, sdtbl, self.AfterHealSoundLevel, self:VJ_DecideSoundPitch(self.AfterHealSoundPitch.a, self.AfterHealSoundPitch.b)) end end return elseif sdSet == "MedicReceiveHeal" then if self.HasMedicSounds_ReceiveHeal == true then local sdtbl = VJ_PICK(self.SoundTbl_MedicReceiveHeal) if (math.random(1, self.MedicReceiveHealSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.CurrentMedicReceiveHealSound = sdType(self, sdtbl, self.MedicReceiveHealSoundLevel, self:VJ_DecideSoundPitch(self.MedicReceiveHealSoundPitch.a, self.MedicReceiveHealSoundPitch.b)) end end return elseif sdSet == "OnPlayerSight" then if self.HasOnPlayerSightSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_OnPlayerSight) if (math.random(1, self.OnPlayerSightSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT_RegularChange = CurTime() + math.random(3, 4) self.NextAlertSoundT = CurTime() + math.random(1,2) self.CurrentOnPlayerSightSound = sdType(self, sdtbl, self.OnPlayerSightSoundLevel, self:VJ_DecideSoundPitch(self.OnPlayerSightSoundPitch.a, self.OnPlayerSightSoundPitch.b)) end end return elseif sdSet == "InvestigateSound" then if self.HasInvestigateSounds == true && CurTime() > self.NextInvestigateSoundT then local sdtbl = VJ_PICK(self.SoundTbl_Investigate) if (math.random(1, self.InvestigateSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.CurrentInvestigateSound = sdType(self, sdtbl, self.InvestigateSoundLevel, self:VJ_DecideSoundPitch(self.InvestigateSoundPitch.a, self.InvestigateSoundPitch.b)) end self.NextInvestigateSoundT = CurTime() + math.Rand(self.NextSoundTime_Investigate.a, self.NextSoundTime_Investigate.b) end return elseif sdSet == "LostEnemy" then if self.HasLostEnemySounds == true && CurTime() > self.LostEnemySoundT then local sdtbl = VJ_PICK(self.SoundTbl_LostEnemy) if (math.random(1, self.LostEnemySoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.CurrentLostEnemySound = sdType(self, sdtbl, self.LostEnemySoundLevel, self:VJ_DecideSoundPitch(self.LostEnemySoundPitch.a, self.LostEnemySoundPitch.b)) end self.LostEnemySoundT = CurTime() + math.Rand(self.NextSoundTime_LostEnemy.a, self.NextSoundTime_LostEnemy.b) end return elseif sdSet == "Alert" then if self.HasAlertSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_Alert) if (math.random(1, self.AlertSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() local dur = CurTime() + ((((SoundDuration(sdtbl) > 0) and SoundDuration(sdtbl)) or 2) + 1) self.NextIdleSoundT = dur self.PainSoundT = dur self.NextAlertSoundT = CurTime() + math.Rand(self.NextSoundTime_Alert.a, self.NextSoundTime_Alert.b) self.CurrentAlertSound = sdType(self, sdtbl, self.AlertSoundLevel, self:VJ_DecideSoundPitch(self.AlertSoundPitch.a, self.AlertSoundPitch.b)) end end return elseif sdSet == "CallForHelp" then if self.HasCallForHelpSounds == true && CurTime() > self.NextCallForHelpSoundT then local sdtbl = VJ_PICK(self.SoundTbl_CallForHelp) if (math.random(1, self.CallForHelpSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.CurrentCallForHelpSound = sdType(self, sdtbl, self.CallForHelpSoundLevel, self:VJ_DecideSoundPitch(self.CallForHelpSoundPitch.a, self.CallForHelpSoundPitch.b)) self.NextCallForHelpSoundT = CurTime() + 2 end end return elseif sdSet == "BecomeEnemyToPlayer" then if self.HasBecomeEnemyToPlayerSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_BecomeEnemyToPlayer) if (math.random(1, self.BecomeEnemyToPlayerChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() timer.Simple(0.05,function() if IsValid(self) then VJ_STOPSOUND(self.CurrentPainSound) end end) timer.Simple(1.3,function() if IsValid(self) then VJ_STOPSOUND(self.CurrentAlertSound) end end) local dur = CurTime() + ((((SoundDuration(sdtbl) > 0) and SoundDuration(sdtbl)) or 2) + 1) self.PainSoundT = dur self.NextAlertSoundT = dur self.NextInvestigateSoundT = CurTime() + 2 self.NextIdleSoundT_RegularChange = CurTime() + math.random(2, 3) self.CurrentBecomeEnemyToPlayerSound = sdType(self, sdtbl, self.BecomeEnemyToPlayerSoundLevel, self:VJ_DecideSoundPitch(self.BecomeEnemyToPlayerPitch.a, self.BecomeEnemyToPlayerPitch.b)) end end return elseif sdSet == "OnKilledEnemy" then if self.HasOnKilledEnemySound == true && CurTime() > self.OnKilledEnemySoundT then local sdtbl = VJ_PICK(self.SoundTbl_OnKilledEnemy) if (math.random(1, self.OnKilledEnemySoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.CurrentOnKilledEnemySound = sdType(self, sdtbl, self.OnKilledEnemySoundLevel, self:VJ_DecideSoundPitch(self.OnKilledEnemySoundPitch.a, self.OnKilledEnemySoundPitch.b)) end self.OnKilledEnemySoundT = CurTime() + math.Rand(self.NextSoundTime_OnKilledEnemy.a, self.NextSoundTime_OnKilledEnemy.b) end return elseif sdSet == "AllyDeath" then if self.HasOnKilledEnemySound == true && CurTime() > self.AllyDeathSoundT then local sdtbl = VJ_PICK(self.SoundTbl_AllyDeath) if (math.random(1, self.AllyDeathSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() self.NextIdleSoundT = self.NextIdleSoundT + 2 self.CurrentAllyDeathSound = sdType(self, sdtbl, self.AllyDeathSoundLevel, self:VJ_DecideSoundPitch(self.AllyDeathSoundPitch.a, self.AllyDeathSoundPitch.b)) end self.AllyDeathSoundT = CurTime() + math.Rand(self.NextSoundTime_AllyDeath.a, self.NextSoundTime_AllyDeath.b) end return elseif sdSet == "Pain" then if self.HasPainSounds == true && CurTime() > self.PainSoundT then local sdtbl = VJ_PICK(self.SoundTbl_Pain) local sdDur = 2 if (math.random(1, self.PainSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() VJ_STOPSOUND(self.CurrentIdleSound) self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentPainSound = sdType(self, sdtbl, self.PainSoundLevel, self:VJ_DecideSoundPitch(self.PainSoundPitch.a, self.PainSoundPitch.b)) sdDur = (SoundDuration(sdtbl) > 0 and SoundDuration(sdtbl)) or sdDur end self.PainSoundT = CurTime() + ((self.NextSoundTime_Pain == true and sdDur) or math.Rand(self.NextSoundTime_Pain.a, self.NextSoundTime_Pain.b)) end return elseif sdSet == "Impact" then if self.HasImpactSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_Impact) if sdtbl == false then sdtbl = VJ_PICK(DefaultSoundTbl_Impact) end -- Default table if (math.random(1, self.ImpactSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self.CurrentImpactSound = sdType(self, sdtbl, self.ImpactSoundLevel, self:VJ_DecideSoundPitch(self.ImpactSoundPitch.a, self.ImpactSoundPitch.b)) end end return elseif sdSet == "DamageByPlayer" then if self.HasDamageByPlayerSounds == true && CurTime() > self.NextDamageByPlayerSoundT then local sdtbl = VJ_PICK(self.SoundTbl_DamageByPlayer) if (math.random(1, self.DamageByPlayerSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self:StopAllCommonSpeechSounds() local dur = CurTime() + ((((SoundDuration(sdtbl) > 0) and SoundDuration(sdtbl)) or 2) + 1) self.PainSoundT = dur self.NextIdleSoundT_RegularChange = CurTime() + dur timer.Simple(0.05, function() if IsValid(self) then VJ_STOPSOUND(self.CurrentPainSound) end end) self.CurrentDamageByPlayerSound = sdType(self, sdtbl, self.DamageByPlayerSoundLevel, self:VJ_DecideSoundPitch(self.DamageByPlayerPitch.a, self.DamageByPlayerPitch.b)) end self.NextDamageByPlayerSoundT = CurTime() + math.Rand(self.NextSoundTime_DamageByPlayer.a, self.NextSoundTime_DamageByPlayer.b) end return elseif sdSet == "Death" then if self.HasDeathSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_Death) if (math.random(1, self.DeathSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end self.CurrentDeathSound = sdType(self, sdtbl, self.DeathSoundLevel, self:VJ_DecideSoundPitch(self.DeathSoundPitch.a, self.DeathSoundPitch.b)) end end return --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-- Base-Specific Sound Tables --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-- elseif sdSet == "BeforeMeleeAttack" then if self.HasMeleeAttackSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_BeforeMeleeAttack) if (math.random(1, self.BeforeMeleeAttackSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentBeforeMeleeAttackSound = sdType(self, sdtbl, self.BeforeMeleeAttackSoundLevel, self:VJ_DecideSoundPitch(self.BeforeMeleeAttackSoundPitch.a, self.BeforeMeleeAttackSoundPitch.b)) end end return elseif sdSet == "MeleeAttack" then if self.HasMeleeAttackSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_MeleeAttack) if (math.random(1, self.MeleeAttackSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentMeleeAttackSound = sdType(self, sdtbl, self.MeleeAttackSoundLevel, self:VJ_DecideSoundPitch(self.MeleeAttackSoundPitch.a, self.MeleeAttackSoundPitch.b)) end if self.HasExtraMeleeAttackSounds == true then sdtbl = VJ_PICK(self.SoundTbl_MeleeAttackExtra) if sdtbl == false then sdtbl = VJ_PICK(DefaultSoundTbl_MeleeAttackExtra) end -- Default table if (math.random(1, self.ExtraMeleeSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.CurrentExtraMeleeAttackSound = VJ_EmitSound(self, sdtbl, self.ExtraMeleeAttackSoundLevel, self:VJ_DecideSoundPitch(self.ExtraMeleeSoundPitch.a, self.ExtraMeleeSoundPitch.b)) end end end return elseif sdSet == "MeleeAttackMiss" then if self.HasMeleeAttackMissSounds == true then local sdtbl = VJ_PICK(self.SoundTbl_MeleeAttackMiss) if (math.random(1, self.MeleeAttackMissSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentMeleeAttackMissSound = sdType(self, sdtbl, self.MeleeAttackMissSoundLevel, self:VJ_DecideSoundPitch(self.MeleeAttackMissSoundPitch.a, self.MeleeAttackMissSoundPitch.b)) end end return elseif sdSet == "BeforeRangeAttack" then if self.HasBeforeRangeAttackSound == true then local sdtbl = VJ_PICK(self.SoundTbl_BeforeRangeAttack) if (math.random(1, self.BeforeRangeAttackSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentBeforeRangeAttackSound = sdType(self, sdtbl, self.BeforeRangeAttackSoundLevel, self:VJ_DecideSoundPitch(self.BeforeRangeAttackPitch.a, self.BeforeRangeAttackPitch.b)) end end return elseif sdSet == "RangeAttack" then if self.HasRangeAttackSound == true then local sdtbl = VJ_PICK(self.SoundTbl_RangeAttack) if (math.random(1, self.RangeAttackSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentRangeAttackSound = sdType(self, sdtbl, self.RangeAttackSoundLevel, self:VJ_DecideSoundPitch(self.RangeAttackPitch.a, self.RangeAttackPitch.b)) end end return elseif sdSet == "BeforeLeapAttack" then if self.HasBeforeLeapAttackSound == true then local sdtbl = VJ_PICK(self.SoundTbl_BeforeLeapAttack) if (math.random(1, self.BeforeLeapAttackSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentBeforeLeapAttackSound = sdType(self, sdtbl, self.BeforeLeapAttackSoundLevel, self:VJ_DecideSoundPitch(self.BeforeLeapAttackSoundPitch.a, self.BeforeLeapAttackSoundPitch.b)) end end return elseif sdSet == "LeapAttackJump" then if self.HasLeapAttackJumpSound == true then local sdtbl = VJ_PICK(self.SoundTbl_LeapAttackJump) if (math.random(1, self.LeapAttackJumpSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentLeapAttackJumpSound = sdType(self, sdtbl, self.LeapAttackJumpSoundLevel, self:VJ_DecideSoundPitch(self.LeapAttackJumpSoundPitch.a, self.LeapAttackJumpSoundPitch.b)) end end return elseif sdSet == "LeapAttackDamage" then if self.HasLeapAttackDamageSound == true then local sdtbl = VJ_PICK(self.SoundTbl_LeapAttackDamage) if (math.random(1, self.LeapAttackDamageSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentLeapAttackDamageSound = sdType(self, sdtbl, self.LeapAttackDamageSoundLevel, self:VJ_DecideSoundPitch(self.LeapAttackDamageSoundPitch.a, self.LeapAttackDamageSoundPitch.b)) end end return elseif sdSet == "LeapAttackDamageMiss" then if self.HasLeapAttackDamageMissSound == true then local sdtbl = VJ_PICK(self.SoundTbl_LeapAttackDamageMiss) if (math.random(1, self.LeapAttackDamageMissSoundChance) == 1 && sdtbl != false) or (cTbl != false) then if cTbl != false then sdtbl = cTbl end if self.IdleSounds_PlayOnAttacks == false then VJ_STOPSOUND(self.CurrentIdleSound) end -- Don't stop idle sounds if we aren't suppose to self.NextIdleSoundT_RegularChange = CurTime() + 1 self.CurrentLeapAttackDamageMissSound = sdType(self, sdtbl, self.LeapAttackDamageMissSoundLevel, self:VJ_DecideSoundPitch(self.LeapAttackDamageMissSoundPitch.a, self.LeapAttackDamageMissSoundPitch.b)) end end return end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:FootStepSoundCode(CustomTbl) if self.HasSounds == false or self.HasFootStepSound == false or self.MovementType == VJ_MOVETYPE_STATIONARY then return end if self:IsOnGround() && self:GetGroundEntity() != NULL then if self.DisableFootStepSoundTimer == true then self:CustomOnFootStepSound() local soundtbl = self.SoundTbl_FootStep if CustomTbl != nil && #CustomTbl != 0 then soundtbl = CustomTbl end if VJ_PICK(soundtbl) != false then VJ_EmitSound(self,soundtbl,self.FootStepSoundLevel,self:VJ_DecideSoundPitch(self.FootStepPitch.a,self.FootStepPitch.b)) end if self.HasWorldShakeOnMove == true then util.ScreenShake(self:GetPos(), self.WorldShakeOnMoveAmplitude, self.WorldShakeOnMoveFrequency, self.WorldShakeOnMoveDuration, self.WorldShakeOnMoveRadius) end return elseif self:IsMoving() && CurTime() > self.FootStepT && self:GetInternalVariable("m_flMoveWaitFinished") <= 0 then self:CustomOnFootStepSound() local soundtbl = self.SoundTbl_FootStep if CustomTbl != nil && #CustomTbl != 0 then soundtbl = CustomTbl end if VJ_PICK(soundtbl) != false then local curSched = self.CurrentSchedule if self.DisableFootStepOnRun == false && ((VJ_HasValue(self.AnimTbl_Run,self:GetMovementActivity())) or (curSched != nil && curSched.MoveType == 1)) /*(VJ_HasValue(VJ_RunActivites,self:GetMovementActivity()) or VJ_HasValue(self.CustomRunActivites,self:GetMovementActivity()))*/ then self:CustomOnFootStepSound_Run() VJ_EmitSound(self,soundtbl,self.FootStepSoundLevel,self:VJ_DecideSoundPitch(self.FootStepPitch.a,self.FootStepPitch.b)) if self.HasWorldShakeOnMove == true then util.ScreenShake(self:GetPos(), self.WorldShakeOnMoveAmplitude, self.WorldShakeOnMoveFrequency, self.WorldShakeOnMoveDuration, self.WorldShakeOnMoveRadius) end self.FootStepT = CurTime() + self.FootStepTimeRun return elseif self.DisableFootStepOnWalk == false && (VJ_HasValue(self.AnimTbl_Walk,self:GetMovementActivity()) or (curSched != nil && curSched.MoveType == 0)) /*(VJ_HasValue(VJ_WalkActivites,self:GetMovementActivity()) or VJ_HasValue(self.CustomWalkActivites,self:GetMovementActivity()))*/ then self:CustomOnFootStepSound_Walk() VJ_EmitSound(self,soundtbl,self.FootStepSoundLevel,self:VJ_DecideSoundPitch(self.FootStepPitch.a,self.FootStepPitch.b)) if self.HasWorldShakeOnMove == true then util.ScreenShake(self:GetPos(), self.WorldShakeOnMoveAmplitude, self.WorldShakeOnMoveFrequency, self.WorldShakeOnMoveDuration, self.WorldShakeOnMoveRadius) end self.FootStepT = CurTime() + self.FootStepTimeWalk return end end end end end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:StopAllCommonSpeechSounds() VJ_STOPSOUND(self.CurrentGeneralSpeechSound) VJ_STOPSOUND(self.CurrentIdleSound) VJ_STOPSOUND(self.CurrentIdleDialogueAnswerSound) VJ_STOPSOUND(self.CurrentInvestigateSound) VJ_STOPSOUND(self.CurrentLostEnemySound) VJ_STOPSOUND(self.CurrentAlertSound) VJ_STOPSOUND(self.CurrentFollowPlayerSound) VJ_STOPSOUND(self.CurrentUnFollowPlayerSound) VJ_STOPSOUND(self.CurrentMoveOutOfPlayersWaySound) VJ_STOPSOUND(self.CurrentBecomeEnemyToPlayerSound) VJ_STOPSOUND(self.CurrentOnPlayerSightSound) VJ_STOPSOUND(self.CurrentDamageByPlayerSound) VJ_STOPSOUND(self.CurrentMedicBeforeHealSound) VJ_STOPSOUND(self.CurrentMedicAfterHealSound) VJ_STOPSOUND(self.CurrentMedicReceiveHealSound) VJ_STOPSOUND(self.CurrentCallForHelpSound) VJ_STOPSOUND(self.CurrentOnReceiveOrderSound) VJ_STOPSOUND(self.CurrentOnKilledEnemySound) VJ_STOPSOUND(self.CurrentAllyDeathSound) end --------------------------------------------------------------------------------------------------------------------------------------------- function ENT:StopAllCommonSounds() VJ_STOPSOUND(self.CurrentGeneralSpeechSound) VJ_STOPSOUND(self.CurrentBreathSound) VJ_STOPSOUND(self.CurrentIdleSound) VJ_STOPSOUND(self.CurrentIdleDialogueAnswerSound) VJ_STOPSOUND(self.CurrentInvestigateSound) VJ_STOPSOUND(self.CurrentAlertSound) VJ_STOPSOUND(self.CurrentBeforeMeleeAttackSound) VJ_STOPSOUND(self.CurrentMeleeAttackSound) VJ_STOPSOUND(self.CurrentExtraMeleeAttackSound) //VJ_STOPSOUND(self.CurrentMeleeAttackMissSound) VJ_STOPSOUND(self.CurrentBeforeRangeAttackSound) VJ_STOPSOUND(self.CurrentRangeAttackSound) VJ_STOPSOUND(self.CurrentBeforeLeapAttackSound) VJ_STOPSOUND(self.CurrentLeapAttackJumpSound) VJ_STOPSOUND(self.CurrentLeapAttackDamageSound) VJ_STOPSOUND(self.CurrentPainSound) VJ_STOPSOUND(self.CurrentFollowPlayerSound) VJ_STOPSOUND(self.CurrentUnFollowPlayerSound) VJ_STOPSOUND(self.CurrentMoveOutOfPlayersWaySound) VJ_STOPSOUND(self.CurrentBecomeEnemyToPlayerSound) VJ_STOPSOUND(self.CurrentOnPlayerSightSound) VJ_STOPSOUND(self.CurrentDamageByPlayerSound) VJ_STOPSOUND(self.CurrentMedicBeforeHealSound) VJ_STOPSOUND(self.CurrentMedicAfterHealSound) VJ_STOPSOUND(self.CurrentMedicReceiveHealSound) VJ_STOPSOUND(self.CurrentCallForHelpSound) VJ_STOPSOUND(self.CurrentOnReceiveOrderSound) VJ_STOPSOUND(self.CurrentOnKilledEnemySound) VJ_STOPSOUND(self.CurrentAllyDeathSound) end