--[[ | 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/ --]] /*-------------------------------------------------- =============== Global Functions & Variables =============== *** 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. --------------------------------------------------*/ if (!file.Exists("autorun/vj_base_autorun.lua","LUA")) then return end include('autorun/vj_controls.lua') -- Localized static values local CurTime = CurTime local IsValid = IsValid local GetConVar = GetConVar local CreateSound = CreateSound local istable = istable local isstring = isstring local isnumber = isnumber local tonumber = tonumber local string_find = string.find local string_Replace = string.Replace local string_StartWith = string.StartWith local string_lower = string.lower local table_remove = table.remove local math_clamp = math.Clamp local math_random = math.random local math_round = math.Round local math_floor = math.floor local bAND = bit.band local bShiftL = bit.lshift local bShiftR = bit.rshift local sdEmitHint = sound.EmitHint local defAng = Angle(0, 0, 0) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Global Functions & Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ VJ_CVAR_IGNOREPLAYERS = GetConVar("ai_ignoreplayers"):GetInt() != 0 -- NPC movement types, information is located inside the NPC bases... VJ_MOVETYPE_GROUND = 1 VJ_MOVETYPE_AERIAL = 2 VJ_MOVETYPE_AQUATIC = 3 VJ_MOVETYPE_STATIONARY = 4 VJ_MOVETYPE_PHYSICS = 5 -- NPC behavior types, information is located inside the NPC bases... VJ_BEHAVIOR_AGGRESSIVE = 1 VJ_BEHAVIOR_NEUTRAL = 2 VJ_BEHAVIOR_PASSIVE = 3 VJ_BEHAVIOR_PASSIVE_NATURE = 4 -- NPC AI states VJ_STATE_NONE = 0 -- No state is set (Default) VJ_STATE_FREEZE = 1 -- AI Completely freezes, basically applies Disable AI on the NPC (Including relationship system!) VJ_STATE_ONLY_ANIMATION = 100 -- Only plays animation tasks, attacks. Disables: Movements, turning and other non-animation tasks! VJ_STATE_ONLY_ANIMATION_CONSTANT = 101 -- Same as VJ_STATE_ONLY_ANIMATION + Idle animation will not play! VJ_STATE_ONLY_ANIMATION_NOATTACK = 102 -- Same as VJ_STATE_ONLY_ANIMATION + Attacks will be disabled -- NPC attack type VJ_ATTACK_NONE = 0 -- No state is set (Default) VJ_ATTACK_CUSTOM = 1 -- Custom attack (Used by developers to make custom attacks) VJ_ATTACK_MELEE = 2 -- Melee attack VJ_ATTACK_RANGE = 3 -- Ranged attack VJ_ATTACK_LEAP = 4 -- Leap attack VJ_ATTACK_GRENADE = 5 -- Grenade attack -- NPC attack status VJ_ATTACK_STATUS_NONE = 0 -- No state is set (Default) VJ_ATTACK_STATUS_DONE = 1 -- The current attack has been executed completely and is marked as done VJ_ATTACK_STATUS_STARTED = 2 -- The current attack has started and is expected to execute soon VJ_ATTACK_STATUS_EXECUTED = 10 -- The current attack has been executed at least once VJ_ATTACK_STATUS_EXECUTED_HIT = 11 -- The current attack has been executed at least once AND hit an entity at least once (Melee & Leap attacks) -- NPC weapon states for the human base VJ_WEP_STATE_READY = 0 -- No state is set (Default) VJ_WEP_STATE_HOLSTERED = 1 -- Weapon is holstered VJ_WEP_STATE_RELOADING = 2 -- Weapon is reloading -- NPC weapon inventory status VJ_WEP_INVENTORY_NONE = 0 -- Currently using no weapon (Default) VJ_WEP_INVENTORY_PRIMARY = 1 -- Currently using its primary weapon VJ_WEP_INVENTORY_SECONDARY = 2 -- Currently using its secondary weapon VJ_WEP_INVENTORY_MELEE = 3 -- Currently using its melee weapon VJ_WEP_INVENTORY_ANTI_ARMOR = 4 -- Currently using its anti-armor weapon -- NPC facing status VJ_FACING_NONE = 0 -- No status is set (Default) VJ_FACING_ENEMY = 1 -- Currently attempting to face the enemy VJ_FACING_ENTITY = 2 -- Currently attempting to face a specific entity VJ_FACING_POSITION = 3 -- Currently attempting to face a specific position -- NPC model animation set VJ_MODEL_ANIMSET_NONE = 0 -- No model animation set detected (Default) VJ_MODEL_ANIMSET_COMBINE = 1 -- Current model's animation set is combine VJ_MODEL_ANIMSET_METROCOP = 2 -- Current model's animation set is metrocop VJ_MODEL_ANIMSET_REBEL = 3 -- Current model's animation set is citizen / rebel VJ_MODEL_ANIMSET_PLAYER = 4 -- Current model's animation set is player VJ_MODEL_ANIMSET_CUSTOM = 10 -- Use this when defining a custom model set -- Source NPC condition definitions because they are not defined in GMod for some reason ?? COND_BEHIND_ENEMY = 29 COND_BETTER_WEAPON_AVAILABLE = 46 COND_CAN_MELEE_ATTACK1 = 23 COND_CAN_MELEE_ATTACK2 = 24 COND_CAN_RANGE_ATTACK1 = 21 COND_CAN_RANGE_ATTACK2 = 22 COND_ENEMY_DEAD = 30 COND_ENEMY_FACING_ME = 28 COND_ENEMY_OCCLUDED = 13 COND_ENEMY_TOO_FAR = 27 COND_ENEMY_UNREACHABLE = 31 COND_ENEMY_WENT_NULL = 12 COND_FLOATING_OFF_GROUND = 61 COND_GIVE_WAY = 48 COND_HAVE_ENEMY_LOS = 15 COND_HAVE_TARGET_LOS = 16 COND_HEALTH_ITEM_AVAILABLE = 47 COND_HEAR_BUGBAIT = 52 COND_HEAR_BULLET_IMPACT = 56 COND_HEAR_COMBAT = 53 COND_HEAR_DANGER = 50 COND_HEAR_MOVE_AWAY = 58 COND_HEAR_PHYSICS_DANGER = 57 COND_HEAR_PLAYER = 55 COND_HEAR_SPOOKY = 59 COND_HEAR_THUMPER = 51 COND_HEAR_WORLD = 54 COND_HEAVY_DAMAGE = 18 COND_IDLE_INTERRUPT = 2 COND_IN_PVS = 1 COND_LIGHT_DAMAGE = 17 COND_LOST_ENEMY = 11 COND_LOST_PLAYER = 33 COND_LOW_PRIMARY_AMMO = 3 COND_MOBBED_BY_ENEMIES = 62 COND_NEW_ENEMY = 26 COND_NO_CUSTOM_INTERRUPTS = 70 COND_NO_HEAR_DANGER = 60 COND_NO_PRIMARY_AMMO = 4 COND_NO_SECONDARY_AMMO = 5 COND_NO_WEAPON = 6 COND_NONE = 0 COND_NOT_FACING_ATTACK = 40 COND_NPC_FREEZE = 67 COND_NPC_UNFREEZE = 68 COND_PHYSICS_DAMAGE = 19 COND_PLAYER_ADDED_TO_SQUAD = 64 COND_PLAYER_PUSHING = 66 COND_PLAYER_REMOVED_FROM_SQUAD = 65 COND_PROVOKED = 25 COND_RECEIVED_ORDERS = 63 COND_REPEATED_DAMAGE = 20 COND_SCHEDULE_DONE = 36 COND_SEE_DISLIKE = 9 COND_SEE_ENEMY = 10 COND_SEE_FEAR = 8 COND_SEE_HATE = 7 COND_SEE_NEMESIS = 34 COND_SEE_PLAYER = 32 COND_SMELL = 37 COND_TALKER_RESPOND_TO_QUESTION = 69 COND_TARGET_OCCLUDED = 14 COND_TASK_FAILED = 35 COND_TOO_CLOSE_TO_ATTACK = 38 COND_TOO_FAR_TO_ATTACK = 39 COND_WAY_CLEAR = 49 COND_WEAPON_BLOCKED_BY_FRIEND = 42 COND_WEAPON_HAS_LOS = 41 COND_WEAPON_PLAYER_IN_SPREAD = 43 COND_WEAPON_PLAYER_NEAR_TARGET = 44 COND_WEAPON_SIGHT_OCCLUDED = 45 --------------------------------------------------------------------------------------------------------------------------------------------- if SERVER then util.AddNetworkString("vj_music_run") require("ai_vj_schedule") local getSched = ai_vj_schedule.New function ai_vj_schedule.New(name) local actualSched = getSched(name) actualSched.Name = name return actualSched end end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_PICK(tbl) if not tbl then return false end -- Yete table pame choone meche, veratartsour false! if istable(tbl) then if #tbl < 1 then return false end -- Yete table barabe (meg en aveli kich), getsoor! tbl = tbl[math_random(1, #tbl)] return tbl else return tbl -- Yete table che, veratartsour abranke end return false end -- !!!!!!!!!!!!!! DO NOT USE THIS FUNCTION !!!!!!!!!!!!!! [Backwards Compatibility!] function VJ_PICKRANDOMTABLE(tbl) if not tbl then return false end -- Yete table pame choone meche, veratartsour false! if istable(tbl) then if #tbl < 1 then return false end -- Yete table barabe (meg en aveli kich), getsoor! tbl = tbl[math_random(1,#tbl)] return tbl else return tbl -- Yete table che, veratartsour abranke end return false end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_STOPSOUND(sdName) if sdName then sdName:Stop() end end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_Set(a, b) -- A set of 2 numbers: a, b return {a = a, b = b} end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_HasValue(tbl, val) if !istable(tbl) then return false end for x = 1, #tbl do if tbl[x] == val then return true end end return false end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_RoundToMultiple(num, multiple) -- Credits to Bizzclaw for pointing me to the right direction! if math_round(num / multiple) == num / multiple then return num else return math_round(num / multiple) * multiple end end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_Color2Byte(color) return bShiftL(math_floor(color.r*7/255), 5) + bShiftL(math_floor(color.g*7/255), 2) + math_floor(color.b*3/255) end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_Color8Bit2Color(bits) return Color(bShiftR(bits,5)*255/7, bAND(bShiftR(bits,2), 0x07)*255/7, bAND(bits,0x03)*255/3) end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_FindInCone(pos, dir, dist, deg, extraOptions) extraOptions = extraOptions or {} local allEntities = extraOptions.AllEntities or false -- Should it detect all types of entities? | False = NPCs and Players only! local foundEnts = {} local cosDeg = math.cos(math.rad(deg)) for _,v in ipairs(ents.FindInSphere(pos, dist)) do if ((allEntities == true) or (allEntities == false && (v:IsNPC() or v:IsPlayer()))) && (dir:Dot((v:GetPos() - pos):GetNormalized()) > cosDeg) then foundEnts[#foundEnts + 1] = v end end return foundEnts end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_CreateSound(ent, sdFile, sdLevel, sdPitch, customFunc) if not sdFile then return end if istable(sdFile) then if #sdFile < 1 then return end -- If the table is empty then end it sdFile = sdFile[math_random(1, #sdFile)] end if ent.OnCreateSound then -- Will allow people to alter sounds before they are played sdFile = ent:OnCreateSound(sdFile) end local sdID = CreateSound(ent, sdFile) sdID:SetSoundLevel(sdLevel or 75) if (customFunc) then customFunc(sdID) end sdID:PlayEx(1, sdPitch or 100) ent.LastPlayedVJSound = sdID if ent.OnPlayCreateSound then ent:OnPlayCreateSound(sdID, sdFile) end return sdID end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_EmitSound(ent, sdFile, sdLevel, sdPitch, sdVolume, sdChannel) if not sdFile then return end if istable(sdFile) then if #sdFile < 1 then return end -- If the table is empty then end it sdFile = sdFile[math_random(1, #sdFile)] end if ent.OnCreateSound then -- Will allow people to alter sounds before they are played sdFile = ent:OnCreateSound(sdFile) end ent:EmitSound(sdFile, sdLevel, sdPitch, sdVolume, sdChannel) ent.LastPlayedVJSound = sdFile if ent.OnPlayEmitSound then ent:OnPlayEmitSound(sdFile) end end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_AnimationExists(ent, anim) if anim == nil or isbool(anim) then return false end -- Get rid of the gesture prefix if string_find(anim, "vjges_") then anim = string_Replace(anim, "vjges_", "") if ent:LookupSequence(anim) == -1 then anim = tonumber(anim) end end if isnumber(anim) then -- Activity if (ent:SelectWeightedSequence(anim) == -1 or ent:SelectWeightedSequence(anim) == 0) && (ent:GetSequenceName(ent:SelectWeightedSequence(anim)) == "Not Found!" or ent:GetSequenceName(ent:SelectWeightedSequence(anim)) == "No model!") then return false end elseif isstring(anim) then -- Sequence if string_find(anim, "vjseq_") then anim = string_Replace(anim, "vjseq_", "") end if ent:LookupSequence(anim) == -1 then return false end end return true end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_GetSequenceDuration(ent, anim) if VJ_AnimationExists(ent, anim) == false then return 0 end -- Invalid animation, so 0 -- Get rid of the gesture prefix if string_find(anim, "vjges_") then anim = string_Replace(anim, "vjges_", "") if ent:LookupSequence(anim) == -1 then anim = tonumber(anim) end end if isnumber(anim) then -- Activity return ent:SequenceDuration(ent:SelectWeightedSequence(anim)) elseif isstring(anim) then -- Sequence if string_find(anim, "vjseq_") then anim = string_Replace(anim, "vjseq_", "") end return ent:SequenceDuration(ent:LookupSequence(anim)) end return 0 end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_GetSequenceName(ent, anim) if VJ_AnimationExists(ent, anim) == false then return 0 end -- Invalid animation, so 0 if string_find(anim, "vjges_") then anim = string_Replace(anim,"vjges_","") if ent:LookupSequence(anim) == -1 then anim = tonumber(anim) end end if isnumber(anim) then return ent:GetSequenceName(ent:SelectWeightedSequence(anim)) end if isstring(anim) then if string_find(anim, "vjseq_") then anim = string_Replace(anim,"vjseq_","") end return ent:GetSequenceName(ent:LookupSequence(anim)) end return nil end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_SequenceToActivity(ent, anim) if isstring(anim) then -- Sequence local result = ent:GetSequenceActivity(ent:LookupSequence(anim)) if result == nil or result == -1 then return false else return result end elseif isnumber(anim) then -- If it's a number, then it's already an activity! return anim else return false end end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_IsCurrentAnimation(ent, anim) anim = anim or {} if istable(anim) then if #anim < 1 then return false end -- If the table is empty then end it else anim = {anim} end for _, v in ipairs(anim) do if isnumber(v) && v != -1 then v = ent:GetSequenceName(ent:SelectWeightedSequence(v)) end -- Translate activity to sequence if string_lower(v) == string_lower(ent:GetSequenceName(ent:GetSequence())) then return true end end //if anim == ent:GetSequenceName(ent:GetSequence()) then return true end return false end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_RemoveAnimExtensions(ent, anim) if string_find(anim, "vjseq_") then anim = string_Replace(anim, "vjseq_", "") end if string_find(anim, "vjges_") then anim = string_Replace(anim, "vjges_", "") end return anim end --------------------------------------------------------------------------------------------------------------------------------------------- local props = {prop_physics=true, prop_physics_multiplayer=true, prop_physics_respawnable=true} -- function VJ_IsProp(ent) return props[ent:GetClass()] == true -- Without == check, it would return nil on false end --------------------------------------------------------------------------------------------------------------------------------------------- function VJ_IsAlive(ent) return ent:Health() > 0 && !ent.Dead end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Causes a Combine turret to self destruct, useful to run this in attacks to make sure turrets can be destroyed - selfEnt = The entity that is destroying the turret - ent = The turret to destroy (If it's NOT a turret, it will return false) Returns - false, turret was NOT destroyed - true, turret was destroyed -----------------------------------------------------------]] function VJ_DestroyCombineTurret(selfEnt, ent) if ent:GetClass() == "npc_turret_floor" then ent:Fire("selfdestruct") ent:SetHealth(0) local phys = ent:GetPhysicsObject() if IsValid(phys) then phys:EnableMotion(true) phys:ApplyForceCenter(selfEnt:GetForward()*10000) end return true end return false end -------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Applies speed effect to the given NPC/Player, if another speed effect is already applied, it will skip! - ent = The entity to apply the speed modification - speed = The speed, 1.0 is the normal speed - setTime = How long should this be in effect? | DEFAULT = 1 Returns - false, effect did NOT apply - true, effect applied -----------------------------------------------------------]] function VJ_ApplySpeedEffect(ent, speed, setTime) ent.VJ_SpeedEffectT = ent.VJ_SpeedEffectT or 0 if ent.VJ_SpeedEffectT < CurTime() then ent.VJ_SpeedEffectT = CurTime() + (setTime or 1) local orgPlayback = ent:GetPlaybackRate() local plyOrgWalk, plyOrgRun; if ent:IsPlayer() then plyOrgWalk = ent:GetWalkSpeed() plyOrgRun = ent:GetRunSpeed() end local hookName = "VJ_SpeedEffect" .. ent:EntIndex() hook.Add("Think", hookName, function() if !IsValid(ent) then hook.Remove("Think", hookName) return elseif (ent.VJ_SpeedEffectT < CurTime()) or (ent:Health() <= 0) then hook.Remove("Think", hookName) if ent.IsVJBaseSNPC then ent.AnimationPlaybackRate = orgPlayback end ent:SetPlaybackRate(orgPlayback) if ent:IsPlayer() then ent:SetWalkSpeed(plyOrgWalk) ent:SetRunSpeed(plyOrgRun) end return end if ent.IsVJBaseSNPC then ent.AnimationPlaybackRate = speed end ent:SetPlaybackRate(speed) if ent:IsPlayer() then ent:SetWalkSpeed(plyOrgWalk * speed) ent:SetRunSpeed(plyOrgRun * speed) end end) return true end return false end -------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Makes the entity utilize its ragdoll for collisions rather than the normal box collision. Note: Collision bounds should still be set, otherwise certain position functions will not work correctly! - ent = The entity to apply the ragdoll collision - mdl = The model to override and use for the collision. By default it should be nil unless you're trying stuff Returns - false, bone follower as NOT created - Entity, the bone follower entity that was created -----------------------------------------------------------]] local boneFollowerClass = "phys_bone_follower" -- function VJ_CreateBoneFollower(ent, mdl) if !IsValid(ent) then return false end local ragdoll = mdl or ent:GetModel() if !util.IsValidRagdoll(ragdoll) then return false end ent:SetCustomCollisionCheck(true) -- Required for the "ShouldCollide" hook! local boneFollower = ents.Create("obj_vj_bonefollower") boneFollower:SetModel(ragdoll) boneFollower:SetPos(ent:GetPos()) boneFollower:SetAngles(ent:GetAngles()) boneFollower:SetParent(ent) boneFollower:AddEffects(EF_BONEMERGE) boneFollower:Spawn() boneFollower:SetOwner(ent) ent:DeleteOnRemove(boneFollower) ent.VJ_BoneFollowerEntity = boneFollower hook.Add("ShouldCollide", boneFollower, function(self, ent1, ent2) if (ent1 == ent && ent2:GetClass() == boneFollowerClass) or (ent2 == ent && ent1:GetClass() == boneFollowerClass) then return false end return true end) return boneFollower end --------------------------------------------------------------------------------------------------------------------------------------------- -- Run in Console: lua_run for k,v in ipairs(ents.GetAll()) do if v:GetClass() == "prop_dynamic" then v:Remove() end end function VJ_CreateTestObject(pos, ang, color, time, mdl) local obj = ents.Create("prop_dynamic") obj:SetModel(mdl or "models/hunter/blocks/cube025x025x025.mdl") obj:SetPos(pos) obj:SetAngles(ang or defAng) obj:SetColor(color or Color(255, 0, 0)) obj:Spawn() obj:Activate() timer.Simple(time or 3, function() if IsValid(obj) then obj:Remove() end end) return obj end --------------------------------------------------------------------------------------------------------------------------------------------- /* Do multiple test and compare the 2 results using this: https://calculla.com/columnar_addition_calculator VJ_StressTest(1000, function() end) */ function VJ_StressTest(count, func) count = count or 1 local startTime = SysTime() for _ = 1, count do func() end local totalTime = SysTime() - startTime print("Total: " .. string.format("%f", totalTime) .. " sec | Average: " .. string.format("%f", totalTime / count) .. " sec") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ NPC / Player Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ local Entity_MetaTable = FindMetaTable("Entity") local NPC_MetaTable = FindMetaTable("NPC") //local Player_MetaTable = FindMetaTable("Player") --------------------------------------------------------------------------------------------------------------------------------------------- function NPC_MetaTable:VJ_Controller_InitialMessage(ply) if !IsValid(ply) then return end ply:ChatPrint("#vjbase.print.npccontroller.entrance") if self.IsVJBaseSNPC == true then self:Controller_IntMsg(ply, controlEnt) end end /* Disabled for now until further testing (especially performance-wise) --------------------------------------------------------------------------------------------------------------------------------------------- local AddEntityRelationship = NPC_MetaTable.AddEntityRelationship function NPC_MetaTable:AddEntityRelationship(...) local args = {...} local ent = args[1] local disp = args[2] self.StoredDispositions = self.StoredDispositions or {} self.StoredDispositions[ent] = disp return AddEntityRelationship(self,...) end --------------------------------------------------------------------------------------------------------------------------------------------- local Disposition = NPC_MetaTable.Disposition function NPC_MetaTable:Disposition(...) local args = {...} local ent = args[1] self.StoredDispositions = self.StoredDispositions or {} if IsValid(ent) && self:GetModel() == ent:GetModel() then return self.StoredDispositions[ent] or D_ER end return Disposition(self,...) end */ --------------------------------------------------------------------------------------------------------------------------------------------- -- override = Used internally by the base, overrides the result and returns Val instead (Useful for variables that allow "false" to let the base decide the time) function NPC_MetaTable:DecideAnimationLength(anim, override, decrease) if isbool(anim) then return 0 end if override == false then -- Base decides return (VJ_GetSequenceDuration(self, anim) - (decrease or 0)) / self:GetPlaybackRate() elseif isnumber(override) then -- User decides return override / self:GetPlaybackRate() else return 0 end end --------------------------------------------------------------------------------------------------------------------------------------------- function Entity_MetaTable:CalculateProjectile(projType, startPos, endPos, projVel) if projType == "Line" then -- Suggested to disable gravity! return ((endPos - startPos):GetNormal()) * projVel elseif projType == "Curve" then -- Oknoutyoun: https://gamedev.stackexchange.com/questions/53552/how-can-i-find-a-projectiles-launch-angle -- Negar: https://wikimedia.org/api/rest_v1/media/math/render/svg/4db61cb4c3140b763d9480e51f90050967288397 local result = Vector(endPos.x - startPos.x, endPos.y - startPos.y, 0) -- Verchnagan deghe local pos_x = result:Length() local pos_y = endPos.z - startPos.z local grav = physenv.GetGravity():Length() local sqrtcalc1 = (projVel * projVel * projVel * projVel) local sqrtcalc2 = grav * ((grav * (pos_x * pos_x)) + (2 * pos_y * (projVel * projVel))) local calcsum = sqrtcalc1 - sqrtcalc2 -- Yergou tevere aveltsour if calcsum < 0 then -- Yete teve nevas e, ooremen sharnage calcsum = math.abs(calcsum) end local angsqrt = math.sqrt(calcsum) local angpos = math.atan(((projVel * projVel) + angsqrt) / (grav * pos_x)) local angneg = math.atan(((projVel * projVel) - angsqrt) / (grav * pos_x)) local pitch = 1 if angpos > angneg then pitch = angneg -- Yete asiga angpos enes ne, aveli veregele else pitch = angpos end result.z = math.tan(pitch) * pos_x return result:GetNormal() * projVel end end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Uses the given number to return a scaled number that accounts for the selected difficulty - int = The number to scale Returns - number, the scaled number -----------------------------------------------------------]] function NPC_MetaTable:VJ_GetDifficultyValue(int) if self.SelectedDifficulty == -3 then return math_clamp(int - (int * 0.99), 1, int) elseif self.SelectedDifficulty == -2 then return math_clamp(int - (int * 0.75), 1, int) elseif self.SelectedDifficulty == -1 then return int / 2 elseif self.SelectedDifficulty == 1 then return int + (int * 0.5) elseif self.SelectedDifficulty == 2 then return int * 2 elseif self.SelectedDifficulty == 3 then return int + (int * 1.5) elseif self.SelectedDifficulty == 4 then return int + (int * 2.5) elseif self.SelectedDifficulty == 5 then return int + (int * 3.5) elseif self.SelectedDifficulty == 6 then return int + (int * 5.0) end return int -- Normal end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Tags ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ /* -- Variables that are used by VJ Base as tags -- [Variable] [Description] VJ_IsBeingControlled NPC that is being controlled by the VJ NPC Controller VJ_IsBeingControlled_Tool NPC that is being controlled by the VJ NPC Mover Tool VJ_AddEntityToSNPCAttackList Entity that should be attacked by Creature NPCs if it's in the way VJ_IsDetectableDanger Entity that should be detected as danger by human NPCs VJ_IsDetectableGrenade Entity that should be detected as a grenade danger by human NPCs VJ_IsPickupableDanger Entity that CAN be picked up by human NPCs (Ex: Grenades) VJ_IsPickedUpDanger Entity that is currently picked up by a human NPC and most likely throwing it away (Ex: Grenades) VJ_LastInvestigateSd Last time this NPC/Player has made a sound that should be investigated by enemy NPCs VJ_LastInvestigateSdLevel The sound level of the above variable VJ_IsHugeMonster NPC that is considered to be very large or a boss */ -- Variable: self.VJTags -- Access: self.VJTags[VJ_TAG_X] -- Remove: self.VJTags[VJ_TAG_X] = nil -- Add: self:VJTags_Add(VJ_TAG_X, VJ_TAG_Y, ...) -- Enums VJ_TAG_HEALING = 1 -- Ent is healing (either itself or by another ent) VJ_TAG_EATING = 2 -- Ent is eating something (Ex: a corpse) VJ_TAG_BEING_EATEN = 3 -- Ent is being eaten by something VJ_TAG_VJ_FRIENDLY = 4 -- Friendly to VJ NPCs VJ_TAG_SD_PLAYING_MUSIC = 10 -- Ent is playing a sound track VJ_TAG_HEADCRAB = 20 VJ_TAG_POLICE = 21 VJ_TAG_CIVILIAN = 22 VJ_TAG_TURRET = 23 VJ_TAG_VEHICLE = 24 VJ_TAG_AIRCRAFT = 25 --------------------------------------------------------------------------------------------------------------------------------------------- function Entity_MetaTable:VJTags_Add(...) if !self.VJTags then self.VJTags = {} end //PrintTable({...}) for _, tag in ipairs({...}) do self.VJTags[tag] = true end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Hooks ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ hook.Add("Initialize", "VJ_Initialize", function() RunConsoleCommand("sv_pvsskipanimation", "0") -- Fix attachments, bones, positions, angles etc. being broken in NPCs! end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PhysgunPickup", "VJ_PhysgunPickup", function(ply, ent) if ent:GetClass() == "sent_vj_ply_spawnpoint" then return ply:IsAdmin() end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PlayerSelectSpawn", "VJ_PlayerSelectSpawn", function(ply) local points = {} for _,v in ipairs(ents.FindByClass("sent_vj_ply_spawnpoint")) do if (v.Active == true) then points[#points + 1] = v end end local result = VJ_PICK(points) if result != false then return result end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PlayerSpawnedNPC", "VJ_PlayerSpawnedNPC", function(ply, ent) if ent.IsVJBaseSNPC == true or ent.IsVJBaseSpawner == true then ent:SetCreator(ply) end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PlayerInitialSpawn", "VJ_PlayerInitialSpawn", function(ply) if IsValid(ply) then ply.VJTags = {} ply.VJ_LastInvestigateSd = 0 ply.VJ_LastInvestigateSdLevel = 0 if !VJ_CVAR_IGNOREPLAYERS then local EntsTbl = ents.GetAll() for x = 1, #EntsTbl do local v = EntsTbl[x] if v:IsNPC() && v.IsVJBaseSNPC == true then v.CurrentPossibleEnemies[#v.CurrentPossibleEnemies+1] = ply end end end end -- Old system /*local getall = ents.GetAll() for k,v in ipairs(getall) do v.VJ_LastInvestigateSd = 0 v.VJ_LastInvestigateSdLevel = 0 if v.IsVJBaseSNPC == true && (v.IsVJBaseSNPC_Human == true or v.IsVJBaseSNPC_Creature == true) then v.CurrentPossibleEnemies = v:DoHardEntityCheck(getall) end end*/ end) --------------------------------------------------------------------------------------------------------------------------------------------- if SERVER then local ignoreEnts = {monster_generic=true, monster_furniture=true, npc_furniture=true, monster_gman=true, npc_grenade_frag=true, bullseye_strider_focus=true, npc_bullseye=true, npc_enemyfinder=true, hornet=true} local grenadeEnts = {npc_grenade_frag=true,grenade_hand=true,obj_spore=true,obj_grenade=true,obj_handgrenade=true,doom3_grenade=true,fas2_thrown_m67=true,cw_grenade_thrown=true,obj_cpt_grenade=true,cw_flash_thrown=true,ent_hl1_grenade=true} local grenadeThrowBackEnts = {npc_grenade_frag=true,obj_spore=true,obj_handgrenade=true,obj_cpt_grenade=true,cw_grenade_thrown=true,cw_flash_thrown=true,cw_smoke_thrown=true,ent_hl1_grenade=true} -- hook.Add("OnEntityCreated", "VJ_OnEntityCreated", function(ent) local myClass = ent:GetClass() ent.VJTags = {} if ent:IsNPC() then if !ignoreEnts[myClass] then local isVJ = ent.IsVJBaseSNPC if isVJ then ent.NextProcessT = CurTime() + 0.15 end timer.Simple(0.1, function() -- Make sure the NPC is initialized properly if IsValid(ent) then if isVJ == true && ent.CurrentPossibleEnemies == nil then ent.CurrentPossibleEnemies = {} end local EntsTbl = ents.GetAll() local count = 1 local cvSeePlys = !VJ_CVAR_IGNOREPLAYERS local isPossibleEnemy = ((ent:IsNPC() && ent:Health() > 0 && (ent.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE)) or (ent:IsPlayer())) for x = 1, #EntsTbl do local v = EntsTbl[x] if (v:IsNPC() or v:IsPlayer()) && !ignoreEnts[v:GetClass()] then -- Add enemies to the created entity (if it's a VJ Base SNPC) if isVJ == true then ent:EntitiesToNoCollideCode(v) if (v:IsNPC() && (v:GetClass() != myClass && (v.Behavior != VJ_BEHAVIOR_PASSIVE_NATURE)) && v:Health() > 0) or (v:IsPlayer() && cvSeePlys /*&& v:Alive()*/) then ent.CurrentPossibleEnemies[count] = v count = count + 1 end end -- Add the created entity to the list of possible enemies of VJ Base SNPCs if isPossibleEnemy && myClass != v:GetClass() && v.IsVJBaseSNPC then v.CurrentPossibleEnemies[#v.CurrentPossibleEnemies+1] = ent //v.CurrentPossibleEnemies = v:DoHardEntityCheck(getall) end end end end end) end elseif grenadeEnts[myClass] then ent.VJ_IsDetectableGrenade = true if grenadeThrowBackEnts[myClass] then ent.VJ_IsPickupableDanger = true end end -- Old system /*if ent:GetClass() != "npc_grenade_frag" && ent:GetClass() != "bullseye_strider_focus" && ent:GetClass() != "npc_bullseye" && ent:GetClass() != "npc_enemyfinder" && ent:GetClass() != "hornet" then timer.Simple(0.15,function() if IsValid(ent) then local getall = ents.GetAll() for k,v in ipairs(getall) do if IsValid(v) && v != ent && v.IsVJBaseSNPC == true && (v.IsVJBaseSNPC_Human == true or v.IsVJBaseSNPC_Creature == true) then v.CurrentPossibleEnemies = v:DoHardEntityCheck(getall) end end end end) end*/ end) /* -- Retrieving outputs from NPCs or other entities | Outputs: https://developer.valvesoftware.com/wiki/Base.fgd/Garry%27s_Mod local triggerLua = ents.Create("lua_run") triggerLua:SetName("triggerhook") triggerLua:Spawn() hook.Add("OnEntityCreated", "VJ_OnEntityCreated", function(ent) if ent:IsNPC() && ent.IsVJBaseSNPC == true then -- Format: :::: self:Fire("AddOutput", "OnIgnite triggerhook:RunPassedCode:hook.Run( 'OnOutput' ):0:-1") end end) hook.Add("OnOutput", "OnOutput", function() local activator, caller = ACTIVATOR, CALLER print(activator, caller) end ) */ end --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("EntityEmitSound", "VJ_EntityEmitSound", function(data) local ent = data.Entity if IsValid(ent) then -- Investigate System if SERVER && (ent:IsPlayer() or ent:IsNPC()) && data.SoundLevel >= 75 then //print("---------------------------") //PrintTable(data) local quiet = (string_StartWith(data.OriginalSoundName, "player/footsteps") and (ent:IsPlayer() && (ent:Crouching() or ent:KeyDown(IN_WALK)))) or false if quiet != true && ent.Dead != true then ent.VJ_LastInvestigateSd = CurTime() ent.VJ_LastInvestigateSdLevel = (data.SoundLevel * data.Volume) + (((data.Volume <= 0.4) and 15) or 0) end -- Disable the built-in footstep sounds for the player footstep sound for VJ NPCs unless specified otherwise -- Plays only on client-side, making it useless to play material-specific elseif ent:IsNPC() && ent.IsVJBaseSNPC == true && (string.EndsWith(data.OriginalSoundName, "stepleft") or string.EndsWith(data.OriginalSoundName, "stepright")) then return ent:MatFootStepQCEvent(data) end end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("EntityFireBullets", "VJ_NPC_FIREBULLET", function(ent, data) if IsValid(ent) && ent:IsNPC() && ent.IsVJBaseSNPC == true then local wep = ent:GetActiveWeapon() local ene = ent:GetEnemy() local edited = false if IsValid(wep) && IsValid(ene) then if wep.IsVJBaseWeapon then -- Ammo counter for VJ weapons wep:SetClip1(wep:Clip1() - 1) //ent.Weapon_TimeSinceLastShot = CurTime() -- We don't want to change this here! else -- START: Bullet spawn for non-VJ weapons -- local getmuzzle; for i = 1, #wep:GetAttachments() do if wep:GetAttachments()[i].name == "muzzle" then getmuzzle = "muzzle" break elseif wep:GetAttachments()[i].name == "muzzleA" then getmuzzle = "muzzleA" break elseif wep:GetAttachments()[i].name == "muzzle_flash" then getmuzzle = "muzzle_flash" break elseif wep:GetAttachments()[i].name == "muzzle_flash1" then getmuzzle = "muzzle_flash1" break elseif wep:GetAttachments()[i].name == "muzzle_flash2" then getmuzzle = "muzzle_flash2" break elseif wep:GetAttachments()[i].name == "ValveBiped.muzzle" then getmuzzle = "ValveBiped.muzzle" break else getmuzzle = false end end if !getmuzzle then if ent:LookupBone("ValveBiped.Bip01_R_Hand") then data.Src = ent:GetBonePosition(ent:LookupBone("ValveBiped.Bip01_R_Hand")) else -- No attachment found, just use eye pos data.Src = ent:EyePos() end else data.Src = wep:GetAttachment(wep:LookupAttachment(getmuzzle)).Pos end -- END: Bullet spawn for non-VJ weapons -- end -- Bullet spread // ent:GetPos():Distance(ent.VJ_TheController:GetEyeTrace().HitPos) -- Was used when NPC was being controlled local fSpread = (ent:GetPos():Distance(ene:GetPos()) / 28) * (ent.WeaponSpread or 1) * (wep.NPC_CustomSpread or 1) data.Spread = Vector(fSpread, fSpread, 0) -- Bullet direction // data.Dir = ent.VJ_TheController:GetAimVector() -- Was used when NPC was being controlled if ent.WeaponUseEnemyEyePos == true then data.Dir = (ene:EyePos() + ene:GetUp()*-5) - data.Src else data.Dir = (ene:GetPos() + ene:OBBCenter()) - data.Src end //ent.WeaponUseEnemyEyePos = false edited = true end ent:OnFireBullet(ent, data) if edited then return true end end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("EntityTakeDamage", "VJ_EntityTakeDamage", function(target, dmginfo) local attacker = dmginfo:GetAttacker() if IsValid(target) && IsValid(attacker) && target.IsVJBaseSNPC && attacker:IsNPC() && dmginfo:IsBulletDamage() && attacker:Disposition(target) != D_HT && (attacker:GetClass() == target:GetClass() or target:Disposition(attacker) == D_LI /*or target:Disposition(attacker) == 4*/) then dmginfo:SetDamage(0) end end) --------------------------------------------------------------------------------------------------------------------------------------------- local function VJ_NPCPLY_DEATH(npc, attacker, inflictor) if IsValid(attacker) && attacker.IsVJBaseSNPC == true then attacker:DoKilledEnemy(npc, attacker, inflictor) attacker:SetupRelationships() end end hook.Add("OnNPCKilled", "VJ_OnNPCKilled", VJ_NPCPLY_DEATH) hook.Add("PlayerDeath", "VJ_PlayerDeath", function(victim, inflictor, attacker) VJ_NPCPLY_DEATH(victim, attacker, inflictor) -- Arguments are flipped between the hooks for some reason... -- Let allied SNPCs know that the player died for _,v in ipairs(ents.FindInSphere(victim:GetPos(), 400)) do if v.IsVJBaseSNPC == true && v:Disposition(victim) == D_LI then v:CustomOnAllyDeath(victim) v:PlaySoundSystem("AllyDeath") end end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PlayerCanPickupWeapon", "VJ_PLAYER_CANPICKUPWEAPON", function(ply, wep) if wep.IsVJBaseWeapon then if ply.VJ_CurPickupWithoutUse == wep:GetClass() && !ply:HasWeapon(wep:GetClass()) then ply.VJ_CurPickupWithoutUse = nil return true end //if wep.VJ_CurPickupWithoutUse then return true end return GetConVar("vj_npc_plypickupdropwep"):GetInt() == 1 && ply:KeyPressed(IN_USE) && ply:GetEyeTrace().Entity == wep end end) --------------------------------------------------------------------------------------------------------------------------------------------- hook.Add("PlayerGiveSWEP", "VJ_PLAYER_GIVESWEP", function(ply, class, swep) //PrintTable(swep) //if swep.IsVJBaseWeapon == true then ply.VJ_CurPickupWithoutUse = class timer.Simple(0.1, function() if IsValid(ply) then ply.VJ_CurPickupWithoutUse = nil end end) //end end) --------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Corpse & Stink System ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ if SERVER then VJ_Corpses = {} VJ_StinkyEnts = {} -- local function VJ_Stink_StartThink() timer.Create("vj_stink_think", 0.3, 0, function() for k, ent in RandomPairs(VJ_StinkyEnts) do if IsValid(ent) then sdEmitHint(SOUND_CARCASS, ent:GetPos(), 400, 0.15, ent) else -- No longer valid, remove it from the list table_remove(VJ_StinkyEnts, k) if #VJ_StinkyEnts == 0 then -- If this is the last stinky ent then destroy the timer! timer.Remove("vj_stink_think") end end end end) end --------------------------------------------------------------------------------------------------------------------------------------------- local stinkyMatTypes = {alienflesh=true, antlion=true, armorflesh=true, bloodyflesh=true, flesh=true, zombieflesh=true, player=true} -- Material types: https://developer.valvesoftware.com/wiki/Material_surface_properties -- --[[--------------------------------------------------------- Adds an entity to the stinky entity list and makes it produce a stink - ent = The entity to add to the list - checkMat = Should it check the entity's material type? Returns - false, Entity NOT added to stinky the list - true, Entity added to the stinky list -----------------------------------------------------------]] function VJ_AddStinkyEnt(ent, checkMat) local physObj = ent:GetPhysicsObject() -- Clear out all removed ents from the table for k, v in ipairs(VJ_StinkyEnts) do if !IsValid(v) then table_remove(VJ_StinkyEnts, k) end end -- Add the entity to the stinky list (if possible) if (!checkMat) or (IsValid(physObj) && stinkyMatTypes[physObj:GetMaterial()]) then VJ_StinkyEnts[#VJ_StinkyEnts + 1] = ent -- Add entity to the table if !timer.Exists("vj_stink_think") then VJ_Stink_StartThink() end -- Start the stinky timer if it does NOT exist return true end return false end --------------------------------------------------------------------------------------------------------------------------------------------- --[[--------------------------------------------------------- Adds an entity to the VJ corpse list (Entities here respect all VJ rules including corpse limit!) - ent = The entity to add to the corpse list -----------------------------------------------------------]] function VJ_AddCorpse(ent) -- Clear out all removed corpses from the table for k, v in ipairs(VJ_Corpses) do if !IsValid(v) then table_remove(VJ_Corpses, k) end end local count = #VJ_Corpses + 1 VJ_Corpses[count] = ent -- Check if we surpassed the limit, if we did, remove the oldest corpse if count > GetConVar("vj_npc_globalcorpselimit"):GetInt() then local oldestCorpse = table_remove(VJ_Corpses, 1) if IsValid(oldestCorpse) then local fadeType = oldestCorpse.FadeCorpseType if fadeType then oldestCorpse:Fire(fadeType, "", 0) end -- Fade out timer.Simple(1, function() if IsValid(oldestCorpse) then oldestCorpse:Remove() end end) -- Make sure it's removed end end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Convar Callbacks ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ cvars.AddChangeCallback("ai_ignoreplayers", function(convar_name, oldValue, newValue) if tonumber(newValue) == 0 then -- Turn off ignore players VJ_CVAR_IGNOREPLAYERS = false local getPlys = player.GetAll() local getAll = ents.GetAll() for x = 1, #getAll do local v = getAll[x] if v:IsNPC() && v.IsVJBaseSNPC then for _, ply in ipairs(getPlys) do v.CurrentPossibleEnemies[#v.CurrentPossibleEnemies + 1] = ply end end end else -- Turn on ignore players VJ_CVAR_IGNOREPLAYERS = true for _, v in ipairs(ents.GetAll()) do if v.IsVJBaseSNPC then if v.FollowingPlayer == true then v:FollowReset() end -- Reset the NPC's follow system if it's following a player //v.CurrentPossibleEnemies = v:DoHardEntityCheck(getall) local posEnemies = v.CurrentPossibleEnemies local it = 1 while it <= #posEnemies do local x = posEnemies[it] if IsValid(x) && x:IsPlayer() then v:AddEntityRelationship(x, D_NU, 10) -- Make the player neutral if IsValid(v:GetEnemy()) && v:GetEnemy() == x then v:ResetEnemy() end -- Reset the NPC's enemy if it's a player table_remove(posEnemies, it) -- Remove the player from possible enemy table else it = it + 1 end end end end end end) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Net Messages ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ if CLIENT then net.Receive("vj_music_run",function(len) VJ_MUSIC_QUEUE_LIST = VJ_MUSIC_QUEUE_LIST or {} local ent = net.ReadEntity() local sdTbl = net.ReadTable() local sdVol = net.ReadFloat() local sdPlayback = net.ReadFloat() -- Flags: "noplay" = Forces the sound not to play as soon as this function is called sound.PlayFile("sound/" .. VJ_PICK(sdTbl), "noplay", function(sdChan, errorID, errorName) if IsValid(sdChan) then if #VJ_MUSIC_QUEUE_LIST <= 0 then sdChan:Play() end sdChan:EnableLooping(true) sdChan:SetVolume(sdVol) sdChan:SetPlaybackRate(sdPlayback) table.insert(VJ_MUSIC_QUEUE_LIST, {npc=ent, channel=sdChan}) else print("[VJ Base Music] Error adding sound track!", errorID, errorName) end end) timer.Create("vj_music_think", 1, 0, function() //PrintTable(VJ_MUSIC_QUEUE_LIST) for k, v in pairs(VJ_MUSIC_QUEUE_LIST) do //PrintTable(v) if !IsValid(v.npc) then v.channel:Stop() v.channel = nil table_remove(VJ_MUSIC_QUEUE_LIST, k) end end if #VJ_MUSIC_QUEUE_LIST <= 0 then timer.Remove("vj_music_think") VJ_MUSIC_QUEUE_LIST = {} else for _,v in pairs(VJ_MUSIC_QUEUE_LIST) do if IsValid(v.npc) && IsValid(v.channel) then v.channel:Play() break end end end end) end) end --------------------------------------------------------------------------------------------------------------------------------------------- /*if CLIENT then require("sound_vj_track") sound_vj_track.Add("VJ_SpiderQueenThemeMusic","vj_dm_spidermonster/Dark Messiah - Avatar of the Spider Goddess.wav",161) end*/ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Utility Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --[[--------------------------------------------------------- Customizable function that deals radius damage with the given properties - attacker = The entity that is dealing the damage | REQUIRED - inflictor = The entity that is inflicting the damage | REQUIRED - startPos = Start position of the radius | DEFAULT = attacker:GetPos() - dmgRadius = How far the damage radius goes | DEFAULT = 150 - dmgMax = Maximum amount of damage it deals to an entity | DEFAULT = 15 - dmgType = The damage type | DEFAULT = DMG_BLAST - ignoreInnocents = Should it ignore NPCs/Players that are friendly OR have no-target on (Including ignore players) | DEFAULT = true - realisticRadius = Should it use a realistic radius? Entities farther away receive less damage and force | DEFAULT = true - extraOptions = Table that holds extra options to modify parts of the code - DisableVisibilityCheck = Should it disable the visibility check? | DEFAULT = false - Force = The force to apply when damage is applied | DEFAULT = false - UpForce = Optional setting for extraOptions.Force that override the up force | DEFAULT = extraOptions.Force - DamageAttacker = Should it damage the attacker as well? | DEFAULT = false - UseConeDegree = If set to a number, it will use a cone-based radius | DEFAULT = nil - UseConeDirection = The direction (position) the cone goes to | DEFAULT = attacker:GetForward() - customFunc(ent) = Use this to edit the entity which is given as parameter "ent" Returns - table, the entities it damaged (Can be empty!) -----------------------------------------------------------]] local specialDmgEnts = {npc_strider=true, npc_combinedropship=true, npc_combinegunship=true, npc_helicopter=true} -- function util.VJ_SphereDamage(attacker, inflictor, startPos, dmgRadius, dmgMax, dmgType, ignoreInnocents, realisticRadius, extraOptions, customFunc) startPos = startPos or attacker:GetPos() dmgRadius = dmgRadius or 150 dmgMax = dmgMax or 15 extraOptions = extraOptions or {} local disableVisibilityCheck = extraOptions.DisableVisibilityCheck or false local baseForce = extraOptions.Force or false local dmgFinal = dmgMax local hitEnts = {} for _, v in ipairs((isnumber(extraOptions.UseConeDegree) and VJ_FindInCone(startPos, extraOptions.UseConeDirection or attacker:GetForward(), dmgRadius, extraOptions.UseConeDegree or 90, {AllEntities=true})) or ents.FindInSphere(startPos, dmgRadius)) do if (attacker.VJ_IsBeingControlled == true && attacker.VJ_TheControllerBullseye == v) or (v:IsPlayer() && v.IsControlingNPC == true) then continue end -- Don't damage controller bullseye and player local nearestPos = v:NearestPoint(startPos) -- From the enemy position to the given position if realisticRadius != false then -- Decrease damage from the nearest point all the way to the enemy point then clamp it! dmgFinal = math_clamp(dmgFinal * ((dmgRadius - startPos:Distance(nearestPos)) + 150) / dmgRadius, dmgMax / 2, dmgFinal) end if (disableVisibilityCheck == false && (v:VisibleVec(startPos) or v:Visible(attacker))) or (disableVisibilityCheck == true) then local function DoDamageCode() if (customFunc) then customFunc(v) end hitEnts[#hitEnts + 1] = v if specialDmgEnts[v:GetClass()] then v:TakeDamage(dmgFinal, attacker, inflictor) else local dmgInfo = DamageInfo() dmgInfo:SetDamage(dmgFinal) dmgInfo:SetAttacker(attacker) dmgInfo:SetInflictor(inflictor) dmgInfo:SetDamageType(dmgType or DMG_BLAST) dmgInfo:SetDamagePosition(nearestPos) if baseForce != false then local force = baseForce local forceUp = extraOptions.UpForce or false if VJ_IsProp(v) or v:GetClass() == "prop_ragdoll" then local phys = v:GetPhysicsObject() if IsValid(phys) then if forceUp == false then forceUp = force / 9.4 end //v:SetVelocity(v:GetUp()*100000) if v:GetClass() == "prop_ragdoll" then force = force * 1.5 end phys:ApplyForceCenter(((v:GetPos() + v:OBBCenter() + v:GetUp() * forceUp) - startPos) * force) //+attacker:GetForward()*vForcePropPhysics end else force = force * 1.2 if forceUp == false then forceUp = force end dmgInfo:SetDamageForce(((v:GetPos() + v:OBBCenter() + v:GetUp() * forceUp) - startPos) * force) end end v:TakeDamageInfo(dmgInfo) VJ_DestroyCombineTurret(attacker, v) end end -- Self if v:EntIndex() == attacker:EntIndex() then if extraOptions.DamageAttacker then DoDamageCode() end -- If it can't self hit, then skip -- NPCs / Players elseif (ignoreInnocents == false) or (v:IsNPC() && v:Disposition(attacker) != D_LI && v:Health() > 0 && (v:GetClass() != attacker:GetClass())) or (v:IsPlayer() && !VJ_CVAR_IGNOREPLAYERS && v:Alive() && !v:IsFlagSet(FL_NOTARGET)) then DoDamageCode() -- Other types of entities elseif !v:IsNPC() && !v:IsPlayer() then DoDamageCode() end end end return hitEnts end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------ Tests ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- Working test but no uses at the moment /* local metaNPC = FindMetaTable("NPC") local metaVJ = {} local function __index(self, key) local val = metaVJ[key] if val != nil then return val end //if ( key == "Example1" ) then return self.Example2 end return metaNPC.__index(self, key) end function metaVJ:GetIdealMoveSpeed(example) if example == true then return 1000 else return metaNPC.GetIdealMoveSpeed(self) end end hook.Add("OnEntityCreated", "vjmetatabletest", function(ent) if scripted_ents.IsBasedOn(ent:GetClass(), "npc_vj_creature_base") or scripted_ents.IsBasedOn(ent:GetClass(), "npc_vj_human_base") then local mt = table.Merge({}, debug.getmetatable(ent)) -- Create a new table to avoid overflow! mt.__index = __index debug.setmetatable(ent, mt) end end) */ -- Version for individual NPCs (Tests show loss of performance, avoid) /* local metaOrg = debug.getmetatable(self) local metaVJ = {} local function newIndex(ent, key) local val = metaVJ[key] if val != nil then return val end return metaOrg.__index(ent, key) end function metaVJ:SetMaxLookDistance(dist) metaOrg.SetMaxLookDistance(self, dist) end local mt = table.Merge({}, metaOrg) -- Create a new table to avoid overflow! mt.__index = newIndex debug.setmetatable(self, mt) */