From 92a3c7e805e38df41f109e57d2a513edb7ec2ca0 Mon Sep 17 00:00:00 2001 From: rafradek Date: Wed, 18 May 2022 21:45:38 +0200 Subject: [PATCH] Update with lua support --- .gitignore | 2 + .vscode/c_cpp_properties.json | 1 + .vscode/settings.json | 72 +- AMBuildScript | 1 + AMBuilder | 4 + PackageScript | 8 + src/abi.h | 14 +- src/common.h | 3 +- src/lua/globals.lua | 746 +++++++ src/lua/natives.lua | 436 ++++ src/mod/etc/extra_player_slots.cpp | 147 +- src/mod/etc/mapentity_additions.cpp | 2639 ++++------------------- src/mod/etc/mapentity_additions.h | 159 ++ src/mod/etc/mapentity_inputs.cpp | 1869 +++++++++++++++++ src/mod/mvm/robot_limit.cpp | 100 +- src/mod/perf/input_optimize.cpp | 2 +- src/mod/pop/ecattr_extensions.cpp | 91 + src/mod/pop/popmgr_extensions.cpp | 116 +- src/mod/pop/tfbot_extensions.cpp | 282 +-- src/mod/util/client_cmds.cpp | 2 +- src/sdk2013/takedamageinfo.h | 421 ++++ src/stub/baseentity.cpp | 1 + src/stub/baseentity.h | 7 +- src/stub/baseplayer.cpp | 1 + src/stub/baseplayer.h | 1 + src/stub/entities.cpp | 4 +- src/stub/entities.h | 12 + src/stub/extraentitydata.h | 18 +- src/stub/misc.cpp | 41 +- src/stub/tfbot.cpp | 17 +- src/stub/tfbot.h | 4 +- src/stub/tfplayer.cpp | 18 +- src/stub/tfplayer.h | 1 + src/stub/trace.cpp | 11 + src/util/admin.cpp | 18 + src/util/admin.h | 1 + src/util/lua.cpp | 3016 +++++++++++++++++++++++++++ src/util/lua.h | 83 + src/util/misc.h | 54 + 39 files changed, 8002 insertions(+), 2421 deletions(-) create mode 100644 src/lua/globals.lua create mode 100644 src/lua/natives.lua create mode 100644 src/mod/etc/mapentity_additions.h create mode 100644 src/mod/etc/mapentity_inputs.cpp create mode 100644 src/sdk2013/takedamageinfo.h create mode 100644 src/util/lua.cpp create mode 100644 src/util/lua.h diff --git a/.gitignore b/.gitignore index 07c25e09..a601723b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ MSVC/Report*.csv *.sublime-project *.sublime-workspace + +libs/lua/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index f261f788..6b29b552 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -11,6 +11,7 @@ "${workspaceFolder}/libs/capstone/include", "${workspaceFolder}/libs/ann/include", "${workspaceFolder}/libs/fmt/include", + "${workspaceFolder}/libs/lua/src", "${workspaceFolder}/../alliedmodders/sourcemod/public", "${workspaceFolder}/../alliedmodders/sourcemod/public/extensions", "${workspaceFolder}/../alliedmodders/sourcemod/sourcepawn/include", diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a6ae76c..0773a3de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,74 @@ "files.associations": { "typeindex": "cpp", "typeinfo": "cpp", - "new": "cpp" - } + "new": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "set": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "ostream": "cpp", + "shared_mutex": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "variant": "cpp", + "charconv": "cpp", + "bit": "cpp", + "forward_list": "cpp", + "*.inc": "cpp", + "unordered_set": "cpp", + "mutex": "cpp", + "thread": "cpp" + }, + "Lua.runtime.fileEncoding": "utf8", + "Lua.diagnostics.disable": [ + "lowercase-global" + ] } \ No newline at end of file diff --git a/AMBuildScript b/AMBuildScript index f4d06b0c..cb1fc5e9 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -165,6 +165,7 @@ class ExtensionConfig(object): cxx.defines += ['GNUC'] cxx.defines += ['PLATFORM_X86'] cxx.defines += ['FMT_HEADER_ONLY'] + cxx.defines += ['LUAJIT_ENABLE_LUA52COMPAT'] ############################################################################ # C & C++ COMPILER ######################################################### diff --git a/AMBuilder b/AMBuilder index 416a2658..79696e56 100644 --- a/AMBuilder +++ b/AMBuilder @@ -70,6 +70,7 @@ sourceFiles += [ 'src/util/firehose_base.cpp', 'src/util/firehose_recv.cpp', 'src/util/firehose_send.cpp', + 'src/util/lua.cpp', 'src/util/pooled_string.cpp', 'src/util/misc.cpp', 'src/util/rtti.cpp', @@ -165,6 +166,7 @@ sourceFiles += [ 'src/mod/etc/limp_projectiles.cpp', 'src/mod/etc/loose_cannon_damage_fix.cpp', 'src/mod/etc/mapentity_additions.cpp', + 'src/mod/etc/mapentity_inputs.cpp', 'src/mod/etc/medicgun_beam_machinery.cpp', 'src/mod/etc/melee_ignore_teammates.cpp', 'src/mod/etc/misc.cpp', @@ -474,6 +476,7 @@ binary.compiler.linkflags += [ '-liberty', # os.path.join(builder.currentSourcePath, 'libs', 'capstone', 'libcapstone.a'), os.path.join(builder.currentSourcePath, 'libs', 'ann', 'lib', 'libANN.a'), + os.path.join(builder.currentSourcePath, 'libs', 'lua', 'src', 'liblua.a'), ] if builder.options.debug == '1': @@ -490,6 +493,7 @@ binary.compiler.includes += [ os.path.join(builder.currentSourcePath, 'libs', 'capstone', 'include'), os.path.join(builder.currentSourcePath, 'libs', 'ann', 'include'), os.path.join(builder.currentSourcePath, 'libs', 'fmt', 'include'), + os.path.join(builder.currentSourcePath, 'libs', 'lua', 'src'), ] # add libstrcompat dependency (for Linux only) diff --git a/PackageScript b/PackageScript index e569ca0b..3358b03d 100644 --- a/PackageScript +++ b/PackageScript @@ -100,6 +100,14 @@ CopyFiles('scripts', 'custom/sigsegv/scripts', ] ) +# Lua Script files +CopyFiles('src/lua', 'custom/sigsegv/scripts', + [ + 'globals.lua', + 'natives.lua' + ] +) + builder.AddCopy('cfg/sigsegv_convars.cfg', folder_map['cfg']) builder.AddCopy('sigsegv.autoload', folder_map['addons/sourcemod/extensions']) builder.AddCopy('scripts/mvm_bigrock_sigdemo.pop', folder_map['custom/sigsegv/scripts/population']) diff --git a/src/abi.h b/src/abi.h index 39172c19..fc21cc09 100644 --- a/src/abi.h +++ b/src/abi.h @@ -142,7 +142,7 @@ union MemberPtrUnion template -MemberPtrType MakePtrToMemberFunc(const void *ptr) +inline MemberPtrType MakePtrToMemberFunc(const void *ptr) { MemberPtrUnion u; @@ -152,34 +152,34 @@ MemberPtrType MakePtrToMemberFunc(const void *ptr) return u.fptr; } template -MemberPtrTypeConst MakePtrToConstMemberFunc(const void *ptr) +inline MemberPtrTypeConst MakePtrToConstMemberFunc(const void *ptr) { return reinterpret_cast>(MakePtrToMemberFunc(ptr)); } template -void *GetAddrOfMemberFunc(MemberPtrType ptr) +inline void *GetAddrOfMemberFunc(MemberPtrType ptr) { MemberPtrUnion u; u.fptr = ptr; - assert((uintptr_t)u.guts.ptr % 2 == 0); +// assert((uintptr_t)u.guts.ptr % 2 == 0); return (void *)u.guts.ptr; } template -void *GetAddrOfMemberFunc(MemberPtrTypeConst ptr) +inline void *GetAddrOfMemberFunc(MemberPtrTypeConst ptr) { return GetAddrOfMemberFunc(reinterpret_cast>(ptr)); } template -void *GetAddrOfMemberFunc(MemberPtrTypeVa ptr) +inline void *GetAddrOfMemberFunc(MemberPtrTypeVa ptr) { return GetAddrOfMemberFunc(reinterpret_cast>(ptr)); } template -void *GetAddrOfMemberFunc(MemberPtrTypeVaConst ptr) +inline void *GetAddrOfMemberFunc(MemberPtrTypeVaConst ptr) { return GetAddrOfMemberFunc(reinterpret_cast>(ptr)); } diff --git a/src/common.h b/src/common.h index 2b42eb80..3a4a1641 100644 --- a/src/common.h +++ b/src/common.h @@ -355,7 +355,8 @@ WARN_RESTORE() #include #include #include -#include +#include "sdk2013/takedamageinfo.h" +//#include #include WARN_IGNORE__ADDRESS() WARN_IGNORE__NONNULL_COMPARE() diff --git a/src/lua/globals.lua b/src/lua/globals.lua new file mode 100644 index 00000000..c76abd17 --- /dev/null +++ b/src/lua/globals.lua @@ -0,0 +1,746 @@ +---------------- +-- Collision groups +---------------- +COLLISION_GROUP_NONE = 0 +COLLISION_GROUP_DEBRIS = 1 -- Collides with nothing but world and static stuff +COLLISION_GROUP_DEBRIS_TRIGGER = 2 -- Same as debris, but hits triggers +COLLISION_GROUP_INTERACTIVE_DEBRIS = 3 -- Collides with everything except other interactive debris or debris +COLLISION_GROUP_INTERACTIVE = 4 -- Collides with everything except interactive debris or debris +COLLISION_GROUP_PLAYER = 5 +COLLISION_GROUP_BREAKABLE_GLASS = 6 +COLLISION_GROUP_VEHICLE = 7 +COLLISION_GROUP_PLAYER_MOVEMENT = 8 -- For HL2, same as Collision_Group_Player, for + -- TF2, this filters out other players and CBaseObjects +COLLISION_GROUP_NPC = 9 -- Generic NPC group +COLLISION_GROUP_IN_VEHICLE = 10 -- for any entity inside a vehicle +COLLISION_GROUP_WEAPON = 11 -- for any weapons that need collision detection +COLLISION_GROUP_VEHICLE_CLIP = 12 -- vehicle clip brush to restrict vehicle movement +COLLISION_GROUP_PROJECTILE = 13 -- Projectiles! +COLLISION_GROUP_DOOR_BLOCKER = 14 -- Blocks entities not permitted to get near moving doors +COLLISION_GROUP_PASSABLE_DOOR = 15 -- Doors that the player shouldn't collide with +COLLISION_GROUP_DISSOLVING = 16 -- Things that are dissolving are in this group +COLLISION_GROUP_PUSHAWAY = 17 -- Nonsolid on client and server, pushaway in player code + +COLLISION_GROUP_NPC_ACTOR = 18 -- Used so NPCs in scripts ignore the player. +COLLISION_GROUP_NPC_SCRIPTED = 19 -- USed for NPCs in scripts that should not collide with each other + +---------------- +-- Hit groups +---------------- +HITGROUP_GENERIC = 0 +HITGROUP_HEAD = 1 +HITGROUP_CHEST = 2 +HITGROUP_STOMACH = 3 +HITGROUP_LEFTARM = 4 +HITGROUP_RIGHTARM = 5 +HITGROUP_LEFTLEG = 6 +HITGROUP_RIGHTLEG = 7 +HITGROUP_GEAR = 10 + +---------------- +-- Contents +---------------- +CONTENTS_EMPTY = 0 -- No contents + +CONTENTS_SOLID = 0x1 -- an eye is never valid in a solid +CONTENTS_WINDOW = 0x2 -- translucent, but not watery (glass) +CONTENTS_AUX = 0x4 +CONTENTS_GRATE = 0x8 -- alpha-tested "grate" textures. Bullets/sight pass through, but solids don't +CONTENTS_SLIME = 0x10 +CONTENTS_WATER = 0x20 +CONTENTS_BLOCKLOS = 0x40 -- block AI line of sight +CONTENTS_OPAQUE = 0x80 -- things that cannot be seen through (may be non-solid though) +LAST_VISIBLE_CONTENTS = 0x80 + +ALL_VISIBLE_CONTENTS = (LAST_VISIBLE_CONTENTS | (LAST_VISIBLE_CONTENTS-1)) + +CONTENTS_TESTFOGVOLUME = 0x100 +CONTENTS_UNUSED = 0x200 + +-- unused +-- NOTE: If it's visible, grab from the top + update LAST_VISIBLE_CONTENTS +-- if not visible, then grab from the bottom. +CONTENTS_UNUSED6 = 0x400 + +CONTENTS_TEAM1 = 0x800 -- per team contents used to differentiate collisions +CONTENTS_TEAM2 = 0x1000 -- between players and objects on different teams + +-- ignore CONTENTS_OPAQUE on surfaces that have SURF_NODRAW +CONTENTS_IGNORE_NODRAW_OPAQUE = 0x2000 + +-- hits entities which are MOVETYPE_PUSH (doors, plats, etc.) +CONTENTS_MOVEABLE = 0x4000 + +-- remaining contents are non-visible, and don't eat brushes +CONTENTS_AREAPORTAL = 0x8000 + +CONTENTS_PLAYERCLIP = 0x10000 +CONTENTS_MONSTERCLIP = 0x20000 + +-- currents can be added to any other contents, and may be mixed +CONTENTS_CURRENT_0 = 0x40000 +CONTENTS_CURRENT_90 = 0x80000 +CONTENTS_CURRENT_180 = 0x100000 +CONTENTS_CURRENT_270 = 0x200000 +CONTENTS_CURRENT_UP = 0x400000 +CONTENTS_CURRENT_DOWN = 0x800000 + +CONTENTS_ORIGIN = 0x1000000 -- removed before bsping an entity + +CONTENTS_MONSTER = 0x2000000 -- should never be on a brush, only in game +CONTENTS_DEBRIS = 0x4000000 +CONTENTS_DETAIL = 0x8000000 -- brushes to be added after vis leafs +CONTENTS_TRANSLUCENT = 0x10000000 -- auto set if any surface has trans +CONTENTS_LADDER = 0x20000000 +CONTENTS_HITBOX = 0x40000000 -- use accurate hitboxes on trace + +---------------- +-- Masks +---------------- +MASK_ALL = (0xFFFFFFFF) -- everything that is normally solid + +MASK_SOLID = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) -- everything that blocks player movement + +MASK_PLAYERSOLID = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) -- blocks npc movement + +MASK_NPCSOLID = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) -- water physics in these contents + +MASK_WATER = (CONTENTS_WATER|CONTENTS_MOVEABLE|CONTENTS_SLIME) -- everything that blocks lighting + +MASK_OPAQUE = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_OPAQUE) -- everything that blocks lighting, but with monsters added. + +MASK_OPAQUE_AND_NPCS = (MASK_OPAQUE|CONTENTS_MONSTER) -- everything that blocks line of sight for AI + +MASK_BLOCKLOS = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_BLOCKLOS) -- everything that blocks line of sight for AI plus NPCs + +MASK_BLOCKLOS_AND_NPCS = (MASK_BLOCKLOS|CONTENTS_MONSTER) -- everything that blocks line of sight for players + +MASK_VISIBLE = (MASK_OPAQUE|CONTENTS_IGNORE_NODRAW_OPAQUE) -- everything that blocks line of sight for players, but with monsters added. + +MASK_VISIBLE_AND_NPCS = (MASK_OPAQUE_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE) -- bullets see these as solid + +MASK_SHOT = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_HITBOX) -- non-raycasted weapons see this as solid = (includes grates) + +MASK_SHOT_HULL = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_GRATE) -- hits solids = (not grates) and passes through everything else + +MASK_SHOT_PORTAL = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTER) -- everything normally solid, except monsters = (world+brush only) + +MASK_SOLID_BRUSHONLY = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE) -- everything normally solid for player movement, except monsters = (world+brush only) + +MASK_PLAYERSOLID_BRUSHONLY = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_GRATE) -- everything normally solid for npc movement, except monsters = (world+brush only) + +MASK_NPCSOLID_BRUSHONLY = (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE) -- just the world, used for route rebuilding + +MASK_NPCWORLDSTATIC = (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE) -- These are things that can split areaportals + +MASK_SPLITAREAPORTAL = (CONTENTS_WATER|CONTENTS_SLIME) + +MASK_DEADSOLID = (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_GRATE) -- everything that blocks corpse movement + +---------------- +-- Surfaces +---------------- +SURF_LIGHT = 0x0001 -- value will hold the light strength +SURF_SKY2D = 0x0002 -- don't draw, indicates we should skylight + draw 2d sky but not draw the 3D skybox +SURF_SKY = 0x0004 -- don't draw, but add to skybox +SURF_WARP = 0x0008 -- turbulent water warp +SURF_TRANS = 0x0010 +SURF_NOPORTAL = 0x0020 -- the surface can not have a portal placed on it +SURF_TRIGGER = 0x0040 -- FIXME: This is an xbox hack to work around elimination of trigger surfaces, which breaks occluders +SURF_NODRAW = 0x0080 -- don't bother referencing the texture + +SURF_HINT = 0x0100 -- make a primary bsp splitter + +SURF_SKIP = 0x0200 -- completely ignore, allowing non-closed brushes +SURF_NOLIGHT = 0x0400 -- Don't calculate light +SURF_BUMPLIGHT = 0x0800 -- calculate three lightmaps for the surface for bumpmapping +SURF_NOSHADOWS = 0x1000 -- Don't receive shadows +SURF_NODECALS = 0x2000 -- Don't receive decals +SURF_NOCHOP = 0x4000 -- Don't subdivide patches on this surface +SURF_HITBOX = 0x8000 -- surface is part of a hitbox + +---------------- +-- Displacement surface flags +---------------- +DISPSURF_FLAG_SURFACE = (1<<0) +DISPSURF_FLAG_WALKABLE = (1<<1) +DISPSURF_FLAG_BUILDABLE = (1<<2) +DISPSURF_FLAG_SURFPROP1 = (1<<3) +DISPSURF_FLAG_SURFPROP2 = (1<<4) + +---------------- +-- Damage types +---------------- +DMG_GENERIC = 0 -- generic damage -- do not use if you want players to flinch and bleed! +DMG_CRUSH = (1 << 0) -- crushed by falling or moving object. + -- NOTE: It's assumed crush damage is occurring as a result of physics collision, so no extra physics force is generated by crush damage. + -- DON'T use DMG_CRUSH when damaging entities unless it's the result of a physics collision. You probably want DMG_CLUB instead. +DMG_BULLET = (1 << 1) -- shot +DMG_SLASH = (1 << 2) -- cut, clawed, stabbed +DMG_BURN = (1 << 3) -- heat burned +DMG_VEHICLE = (1 << 4) -- hit by a vehicle +DMG_FALL = (1 << 5) -- fell too far +DMG_BLAST = (1 << 6) -- explosive blast damage +DMG_CLUB = (1 << 7) -- crowbar, punch, headbutt +DMG_SHOCK = (1 << 8) -- electric shock +DMG_SONIC = (1 << 9) -- sound pulse shockwave +DMG_RADIUS_MAX = (1 << 10) -- Explosive damage has no falloff (100% regardless of distance from explosion center) +DMG_PREVENT_PHYSICS_FORCE = (1 << 11) -- Prevent a physics force +DMG_NEVERGIB = (1 << 12) -- with this bit OR'd in, no damage type will be able to gib victims upon death +DMG_ALWAYSGIB = (1 << 13) -- with this bit OR'd in, any damage type can be made to gib victims upon death. +DMG_DROWN = (1 << 14) -- Drowning + + +DMG_PARALYZE = (1 << 15) -- slows affected creature down +DMG_NERVEGAS = (1 << 16) -- nerve toxins, very bad +DMG_NOCLOSEDISTANCEMOD =(1 << 17) -- damage rampup decreased to 20% +DMG_HALF_FALLOFF = (1 << 18) -- Explosive damage has falloff reduced in half (75% damage minimum instead of 50%) +DMG_DROWNRECOVER = (1 << 19) -- drowning recovery +DMG_CRITICAL = (1 << 20) -- critical damage +DMG_USEDISTANCEMOD =(1 << 21) -- deals reduced or increased damage based on distance + +DMG_REMOVENORAGDOLL =(1<<22) -- with this bit OR'd in, no ragdoll will be created, and the target will be quietly removed. + -- use this to kill an entity that you've already got a server-side ragdoll for + +DMG_PHYSGUN = (1<<23) -- Hit by manipulator. Usually doesn't do any damage. +DMG_IGNITE = (1<<24) -- Damage caused by ignition +DMG_USE_HITLOCATIONS = (1<<25) -- For most hitscan guns: Headshot causes critical hits + +DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE = (1<<26) -- Damage does not count towards crit rate +DMG_MELEE = (1<<27) -- Melee damage +DMG_DIRECT = (1<<28) +DMG_BUCKSHOT = (1<<29) -- not quite a bullet. Little, rounder, different. + +DMG_FROM_OTHER_SAPPER = DMG_IGNITE + +---------------- +-- Custom damage types +---------------- +TF_DMG_CUSTOM_NONE = 0 +TF_DMG_CUSTOM_HEADSHOT = 1 +TF_DMG_CUSTOM_BACKSTAB = 2 +TF_DMG_CUSTOM_BURNING = 3 +TF_DMG_WRENCH_FIX = 4 +TF_DMG_CUSTOM_MINIGUN = 5 +TF_DMG_CUSTOM_SUICIDE = 6 +TF_DMG_CUSTOM_TAUNTATK_HADOUKEN = 7 +TF_DMG_CUSTOM_BURNING_FLARE = 8 +TF_DMG_CUSTOM_TAUNTATK_HIGH_NOON = 9 +TF_DMG_CUSTOM_TAUNTATK_GRAND_SLAM = 10 +TF_DMG_CUSTOM_PENETRATE_MY_TEAM = 11 +TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS = 12 +TF_DMG_CUSTOM_TAUNTATK_FENCING = 13 +TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE = 14 +TF_DMG_CUSTOM_TAUNTATK_ARROW_STAB = 15 +TF_DMG_CUSTOM_TELEFRAG = 16 +TF_DMG_CUSTOM_BURNING_ARROW = 17 +TF_DMG_CUSTOM_FLYINGBURN = 18 +TF_DMG_CUSTOM_PUMPKIN_BOMB = 19 +TF_DMG_CUSTOM_DECAPITATION = 20 +TF_DMG_CUSTOM_TAUNTATK_GRENADE = 21 +TF_DMG_CUSTOM_BASEBALL = 22 +TF_DMG_CUSTOM_CHARGE_IMPACT = 23 +TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING = 24 +TF_DMG_CUSTOM_AIR_STICKY_BURST = 25 +TF_DMG_CUSTOM_DEFENSIVE_STICKY = 26 +TF_DMG_CUSTOM_PICKAXE = 27 +TF_DMG_CUSTOM_ROCKET_DIRECTHIT = 28 +TF_DMG_CUSTOM_TAUNTATK_UBERSLICE = 29 +TF_DMG_CUSTOM_PLAYER_SENTRY = 30 +TF_DMG_CUSTOM_STANDARD_STICKY = 31 +TF_DMG_CUSTOM_SHOTGUN_REVENGE_CRIT = 32 +TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH = 33 +TF_DMG_CUSTOM_BLEEDING = 34 +TF_DMG_CUSTOM_GOLD_WRENCH = 35 +TF_DMG_CUSTOM_CARRIED_BUILDING = 36 +TF_DMG_CUSTOM_COMBO_PUNCH = 37 +TF_DMG_CUSTOM_TAUNTATK_ENGINEER_ARM_KILL = 38 +TF_DMG_CUSTOM_FISH_KILL = 39 +TF_DMG_CUSTOM_TRIGGER_HURT = 40 +TF_DMG_CUSTOM_DECAPITATION_BOSS = 41 +TF_DMG_CUSTOM_STICKBOMB_EXPLOSION = 42 +TF_DMG_CUSTOM_AEGIS_ROUND = 43 +TF_DMG_CUSTOM_FLARE_EXPLOSION = 44 +TF_DMG_CUSTOM_BOOTS_STOMP = 45 +TF_DMG_CUSTOM_PLASMA = 46 +TF_DMG_CUSTOM_PLASMA_CHARGED = 47 +TF_DMG_CUSTOM_PLASMA_GIB = 48 +TF_DMG_CUSTOM_PRACTICE_STICKY = 49 +TF_DMG_CUSTOM_EYEBALL_ROCKET = 50 +TF_DMG_CUSTOM_HEADSHOT_DECAPITATION = 51 +TF_DMG_CUSTOM_TAUNTATK_ARMAGEDDON = 52 +TF_DMG_CUSTOM_FLARE_PELLET = 53 +TF_DMG_CUSTOM_CLEAVER = 54 +TF_DMG_CUSTOM_CLEAVER_CRIT = 55 +TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH = 56 +TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB = 57 +TF_DMG_CUSTOM_MERASMUS_GRENADE = 58 +TF_DMG_CUSTOM_MERASMUS_ZAP = 59 +TF_DMG_CUSTOM_MERASMUS_DECAPITATION = 60 +TF_DMG_CUSTOM_CANNONBALL_PUSH = 61 +TF_DMG_CUSTOM_TAUNTATK_ALLCLASS_GUITAR_RIFF = 62 +TF_DMG_CUSTOM_THROWABLE = 63 +TF_DMG_CUSTOM_THROWABLE_KILL = 64 +TF_DMG_CUSTOM_SPELL_TELEPORT = 65 +TF_DMG_CUSTOM_SPELL_SKELETON = 66 +TF_DMG_CUSTOM_SPELL_MIRV = 67 +TF_DMG_CUSTOM_SPELL_METEOR = 68 +TF_DMG_CUSTOM_SPELL_LIGHTNING = 69 +TF_DMG_CUSTOM_SPELL_FIREBALL = 70 +TF_DMG_CUSTOM_SPELL_MONOCULUS = 71 +TF_DMG_CUSTOM_SPELL_BLASTJUMP = 72 +TF_DMG_CUSTOM_SPELL_BATS = 73 +TF_DMG_CUSTOM_SPELL_TINY = 74 +TF_DMG_CUSTOM_KART = 75 +TF_DMG_CUSTOM_GIANT_HAMMER = 76 +TF_DMG_CUSTOM_RUNE_REFLECT = 77 +TF_DMG_CUSTOM_DRAGONS_FURY_IGNITE = 78 +TF_DMG_CUSTOM_DRAGONS_FURY_BONUS_BURNING = 79 +TF_DMG_CUSTOM_SLAP_KILL = 80 +TF_DMG_CUSTOM_CROC = 81 +TF_DMG_CUSTOM_TAUNTATK_GASBLAST = 82 + +---------------- +-- TF2 Classes +---------------- +TF_CLASS_UNDEFINED = 0 +TF_CLASS_SCOUT = 1 +TF_CLASS_SNIPER = 2 +TF_CLASS_SOLDIER = 3 +TF_CLASS_DEMOMAN = 4 +TF_CLASS_MEDIC = 5 +TF_CLASS_HEAVYWEAPONS = 6 +TF_CLASS_PYRO = 7 +TF_CLASS_SPY = 8 +TF_CLASS_ENGINEER = 9 + +---------------- +-- AddCond conditions +---------------- +TF_COND_INVALID = -1 +TF_COND_AIMING = 0 +TF_COND_ZOOMED = 1 +TF_COND_DISGUISING = 2 +TF_COND_DISGUISED = 3 +TF_COND_STEALTHED = 4 +TF_COND_INVULNERABLE = 5 +TF_COND_TELEPORTED = 6 +TF_COND_TAUNTING = 7 +TF_COND_INVULNERABLE_WEARINGOFF = 8 +TF_COND_STEALTHED_BLINK = 9 +TF_COND_SELECTED_TO_TELEPORT = 10 +TF_COND_CRITBOOSTED = 11 +TF_COND_TMPDAMAGEBONUS = 12 +TF_COND_FEIGN_DEATH = 13 +TF_COND_PHASE = 14 +TF_COND_STUNNED = 15 +TF_COND_OFFENSEBUFF = 16 +TF_COND_SHIELD_CHARGE = 17 +TF_COND_DEMO_BUFF = 18 +TF_COND_ENERGY_BUFF = 19 +TF_COND_RADIUSHEAL = 20 +TF_COND_HEALTH_BUFF = 21 +TF_COND_BURNING = 22 +TF_COND_HEALTH_OVERHEALED = 23 +TF_COND_URINE = 24 +TF_COND_BLEEDING = 25 +TF_COND_DEFENSEBUFF = 26 +TF_COND_MAD_MILK = 27 +TF_COND_MEGAHEAL = 28 +TF_COND_REGENONDAMAGEBUFF = 29 +TF_COND_MARKEDFORDEATH = 30 +TF_COND_NOHEALINGDAMAGEBUFF = 31 +TF_COND_SPEED_BOOST = 32 +TF_COND_CRITBOOSTED_PUMPKIN = 33 +TF_COND_CRITBOOSTED_USER_BUFF = 34 +TF_COND_CRITBOOSTED_DEMO_CHARGE = 35 +TF_COND_SODAPOPPER_HYPE = 36 +TF_COND_CRITBOOSTED_FIRST_BLOOD = 37 +TF_COND_CRITBOOSTED_BONUS_TIME = 38 +TF_COND_CRITBOOSTED_CTF_CAPTURE = 39 +TF_COND_CRITBOOSTED_ON_KILL = 40 +TF_COND_CANNOT_SWITCH_FROM_MELEE = 41 +TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK = 42 +TF_COND_REPROGRAMMED = 43 +TF_COND_CRITBOOSTED_RAGE_BUFF = 44 +TF_COND_DEFENSEBUFF_HIGH = 45 +TF_COND_SNIPERCHARGE_RAGE_BUFF = 46 +TF_COND_DISGUISE_WEARINGOFF = 47 +TF_COND_MARKEDFORDEATH_SILENT = 48 +TF_COND_DISGUISED_AS_DISPENSER = 49 +TF_COND_SAPPED = 50 +TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED = 51 +TF_COND_INVULNERABLE_USER_BUFF = 52 +TF_COND_HALLOWEEN_BOMB_HEAD = 53 +TF_COND_HALLOWEEN_THRILLER = 54 +TF_COND_RADIUSHEAL_ON_DAMAGE = 55 +TF_COND_CRITBOOSTED_CARD_EFFECT = 56 +TF_COND_INVULNERABLE_CARD_EFFECT = 57 +TF_COND_MEDIGUN_UBER_BULLET_RESIST = 58 +TF_COND_MEDIGUN_UBER_BLAST_RESIST = 59 +TF_COND_MEDIGUN_UBER_FIRE_RESIST = 60 +TF_COND_MEDIGUN_SMALL_BULLET_RESIST = 61 +TF_COND_MEDIGUN_SMALL_BLAST_RESIST = 62 +TF_COND_MEDIGUN_SMALL_FIRE_RESIST = 63 +TF_COND_STEALTHED_USER_BUFF = 64 +TF_COND_MEDIGUN_DEBUFF = 65 +TF_COND_STEALTHED_USER_BUFF_FADING = 66 +TF_COND_BULLET_IMMUNE = 67 +TF_COND_BLAST_IMMUNE = 68 +TF_COND_FIRE_IMMUNE = 69 +TF_COND_PREVENT_DEATH = 70 +TF_COND_MVM_BOT_STUN_RADIOWAVE = 71 +TF_COND_HALLOWEEN_SPEED_BOOST = 72 +TF_COND_HALLOWEEN_QUICK_HEAL = 73 +TF_COND_HALLOWEEN_GIANT = 74 +TF_COND_HALLOWEEN_TINY = 75 +TF_COND_HALLOWEEN_IN_HELL = 76 +TF_COND_HALLOWEEN_GHOST_MODE = 77 +TF_COND_MINICRITBOOSTED_ON_KILL = 78 +TF_COND_OBSCURED_SMOKE = 79 +TF_COND_PARACHUTE_ACTIVE = 80 +TF_COND_BLASTJUMPING = 81 +TF_COND_HALLOWEEN_KART = 82 +TF_COND_HALLOWEEN_KART_DASH = 83 +TF_COND_BALLOON_HEAD = 84 +TF_COND_MELEE_ONLY = 85 +TF_COND_SWIMMING_CURSE = 86 +TF_COND_FREEZE_INPUT = 87 +TF_COND_HALLOWEEN_KART_CAGE = 88 +TF_COND_DONOTUSE_0 = 89 +TF_COND_RUNE_STRENGTH = 90 +TF_COND_RUNE_HASTE = 91 +TF_COND_RUNE_REGEN = 92 +TF_COND_RUNE_RESIST = 93 +TF_COND_RUNE_VAMPIRE = 94 +TF_COND_RUNE_REFLECT = 95 +TF_COND_RUNE_PRECISION = 96 +TF_COND_RUNE_AGILITY = 97 +TF_COND_GRAPPLINGHOOK = 98 +TF_COND_GRAPPLINGHOOK_SAFEFALL = 99 +TF_COND_GRAPPLINGHOOK_LATCHED = 100 +TF_COND_GRAPPLINGHOOK_BLEEDING = 101 +TF_COND_AFTERBURN_IMMUNE = 102 +TF_COND_RUNE_KNOCKOUT = 103 +TF_COND_RUNE_IMBALANCE = 104 +TF_COND_CRITBOOSTED_RUNE_TEMP = 105 +TF_COND_PASSTIME_INTERCEPTION = 106 +TF_COND_SWIMMING_NO_EFFECTS = 107 +TF_COND_PURGATORY = 108 +TF_COND_RUNE_KING = 109 +TF_COND_RUNE_PLAGUE = 110 +TF_COND_RUNE_SUPERNOVA = 111 +TF_COND_PLAGUE = 112 +TF_COND_KING_BUFFED = 113 +TF_COND_TEAM_GLOWS = 114 +TF_COND_KNOCKED_INTO_AIR = 115 +TF_COND_COMPETITIVE_WINNER = 116 +TF_COND_COMPETITIVE_LOSER = 117 +TF_COND_HEALING_DEBUFF = 118 +TF_COND_PASSTIME_PENALTY_DEBUFF = 119 +TF_COND_GRAPPLED_TO_PLAYER = 120 +TF_COND_GRAPPLED_BY_PLAYER = 121 +TF_COND_PARACHUTE_DEPLOYED = 122 +TF_COND_GAS = 123 +TF_COND_BURNING_PYRO = 124 +TF_COND_ROCKETPACK = 125 +TF_COND_LOST_FOOTING = 126 +TF_COND_AIR_CURRENT = 127 +TF_COND_HALLOWEEN_HELL_HEAL = 128 +TF_COND_POWERUPMODE_DOMINANT = 129 + +---------------- +-- Stun types +---------------- + +TF_STUNFLAG_SLOWDOWN = (1 << 0) --< activates slowdown modifier +TF_STUNFLAG_BONKSTUCK = (1 << 1) --< bonk sound, stuck +TF_STUNFLAG_LIMITMOVEMENT = (1 << 2) --< disable forward/backward movement +TF_STUNFLAG_CHEERSOUND = (1 << 3) --< cheering sound +TF_STUNFLAG_NOSOUNDOREFFECT = (1 << 5) --< no sound or particle +TF_STUNFLAG_THIRDPERSON = (1 << 6) --< panic animation +TF_STUNFLAG_GHOSTEFFECT = (1 << 7) --< ghost particles + +TF_STUNFLAGS_LOSERSTATE = TF_STUNFLAG_SLOWDOWN | TF_STUNFLAG_NOSOUNDOREFFECT | TF_STUNFLAG_THIRDPERSON +TF_STUNFLAGS_GHOSTSCARE = TF_STUNFLAG_GHOSTEFFECT | TF_STUNFLAG_THIRDPERSON +TF_STUNFLAGS_SMALLBONK = TF_STUNFLAG_THIRDPERSON | TF_STUNFLAG_SLOWDOWN +TF_STUNFLAGS_NORMALBONK = TF_STUNFLAG_BONKSTUCK +TF_STUNFLAGS_BIGBONK = TF_STUNFLAG_CHEERSOUND | TF_STUNFLAG_BONKSTUCK + +---------------- +-- Input keys +---------------- +IN_ATTACK = (1 << 0) +IN_JUMP = (1 << 1) +IN_DUCK = (1 << 2) +IN_FORWARD = (1 << 3) +IN_BACK = (1 << 4) +IN_USE = (1 << 5) +IN_CANCEL = (1 << 6) +IN_LEFT = (1 << 7) +IN_RIGHT = (1 << 8) +IN_MOVELEFT = (1 << 9) +IN_MOVERIGHT = (1 << 10) +IN_ATTACK2 = (1 << 11) +IN_RUN = (1 << 12) +IN_RELOAD = (1 << 13) +IN_ALT1 = (1 << 14) +IN_ALT2 = (1 << 15) +IN_SCORE = (1 << 16) -- Used by client.dll for when scoreboard is held down +IN_SPEED = (1 << 17) -- Player is holding the speed key +IN_WALK = (1 << 18) -- Player holding walk key +IN_ZOOM = (1 << 19) -- Zoom key for HUD zoom +IN_WEAPON1 = (1 << 20) -- weapon defines these bits +IN_WEAPON2 = (1 << 21) -- weapon defines these bits +IN_BULLRUSH = (1 << 22) +IN_GRENADE1 = (1 << 23) -- grenade 1 +IN_GRENADE2 = (1 << 24) -- grenade 2 +IN_ATTACK3 = (1 << 25) + +---------------- +-- Entity Effects +---------------- +EF_BONEMERGE = 0x001 -- Performs bone merge on client side +EF_BRIGHTLIGHT = 0x002 -- DLIGHT centered at entity origin +EF_DIMLIGHT = 0x004 -- player flashlight +EF_NOINTERP = 0x008 -- don't interpolate the next frame +EF_NOSHADOW = 0x010 -- Don't cast no shadow +EF_NODRAW = 0x020 -- don't draw entity +EF_NORECEIVESHADOW = 0x040 -- Don't receive no shadow +EF_BONEMERGE_FASTCULL = 0x080 -- For use with EF_BONEMERGE. If this is set then it places this ent's origin at its + -- parent and uses the parent's bbox + the max extents of the aiment. + -- Otherwise it sets up the parent's bones every frame to figure out where to place + -- the aiment which is inefficient because it'll setup the parent's bones even if + -- the parent is not in the PVS. +EF_ITEM_BLINK = 0x100 -- blink an item so that the user notices it. +EF_PARENT_ANIMATES = 0x200 -- always assume that the parent entity is animating + +---------------- +-- Entity Render Modes +---------------- +RenderNormal = 0 -- src +RenderTransColor = 1 -- c*a+dest*= (1-a) +RenderTransTexture = 2 -- src*a+dest*= (1-a) +RenderGlow = 3 -- src*a+dest -- No Z buffer checks -- Fixed size in screen space +RenderTransAlpha = 4 -- src*srca+dest*= (1-srca) +RenderTransAdd = 5 -- src*a+dest +RenderEnvironmental = 6 -- not drawn used for environmental effects +RenderTransAddFrameBlend = 7 -- use a fractional frame value to blend between animation frames +RenderTransAlphaAdd = 8 -- src + dest*= (1-a) +RenderWorldGlow = 9 -- Same as kRenderGlow but not fixed size in screen space +RenderNone = 10 -- Don't render. + +---------------- +-- Entity Render FX +---------------- +RenderFxNone = 0 +RenderFxPulseSlow = 1 +RenderFxPulseFast = 2 +RenderFxPulseSlowWide = 3 +RenderFxPulseFastWide = 4 +RenderFxFadeSlow = 5 +RenderFxFadeFast = 6 +RenderFxSolidSlow = 7 +RenderFxSolidFast = 8 +RenderFxStrobeSlow = 9 +RenderFxStrobeFast = 10 +RenderFxStrobeFaster = 11 +RenderFxFlickerSlow = 12 +RenderFxFlickerFast = 13 +RenderFxNoDissipation = 14 +RenderFxDistort = 15 -- Distort/scale/translate flicker +RenderFxHologram = 16 -- kRenderFxDistort + distance fade +RenderFxExplode = 17 -- Scale up really big! +RenderFxGlowShell = 18 -- Glowing Shell +RenderFxClampMinScale = 19 -- Keep this sprite from getting very small = (SPRITES only!) +RenderFxEnvRain = 20 -- for environmental rendermode make rain +RenderFxEnvSnow = 21 -- " " " make snow +RenderFxSpotlight = 22 -- TEST CODE for experimental spotlight +RenderFxRagdoll = 23 -- HACKHACK: TEST CODE for signalling death of a ragdoll character +RenderFxPulseFastWider = 24 + +---------------- +-- Entity Solid Type +---------------- +SOLID_NONE = 0 -- no solid model +SOLID_BSP = 1 -- a BSP tree +SOLID_BBOX = 2 -- an AABB +SOLID_OBB = 3 -- an OBB = (not implemented yet) +SOLID_OBB_YAW = 4 -- an OBB constrained so that it can only yaw +SOLID_CUSTOM = 5 -- Always call into the entity for tests +SOLID_VPHYSICS = 6 -- solid vphysics object get vcollide from the model and collide with that + +---------------- +-- Entity Move Type +---------------- +MOVETYPE_NONE = 0 -- never moves +MOVETYPE_ISOMETRIC = 1 -- For players -- in TF2 commander view etc. +MOVETYPE_WALK = 2 -- Player only - moving on the ground +MOVETYPE_STEP = 3 -- gravity special edge handling -- monsters use this +MOVETYPE_FLY = 4 -- No gravity but still collides with stuff +MOVETYPE_FLYGRAVITY = 5 -- flies through the air + is affected by gravity +MOVETYPE_VPHYSICS = 6 -- uses VPHYSICS for simulation +MOVETYPE_PUSH = 7 -- no clip to world push and crush +MOVETYPE_NOCLIP = 8 -- No gravity no collisions still do velocity/avelocity +MOVETYPE_LADDER = 9 -- Used by players only when going onto a ladder +MOVETYPE_OBSERVER = 10 -- Observer movement depends on player's observer mode +MOVETYPE_CUSTOM = 11 -- Allows the entity to describe its own physics + +---------------- +-- Entity Flags m_fFlags +---------------- +FL_ONGROUND = (1<<0) -- At rest / on the ground +FL_DUCKING = (1<<1) -- Player flag -- Player is fully crouched +FL_ANIMDUCKING = (1<<2) -- Player flag -- Player is in the process of crouching or uncrouching but could be in transition +-- examples: Fully ducked: FL_DUCKING & FL_ANIMDUCKING +-- Previously fully ducked unducking in progress: FL_DUCKING & !FL_ANIMDUCKING +-- Fully unducked: !FL_DUCKING & !FL_ANIMDUCKING +-- Previously fully unducked ducking in progress: !FL_DUCKING & FL_ANIMDUCKING +FL_WATERJUMP = (1<<3) -- player jumping out of water +FL_ONTRAIN = (1<<4) -- Player is _controlling_ a train so movement commands should be ignored on client during prediction. +FL_INRAIN = (1<<5) -- Indicates the entity is standing in rain +FL_FROZEN = (1<<6) -- Player is frozen for 3rd person camera +FL_ATCONTROLS = (1<<7) -- Player can't move but keeps key inputs for controlling another entity +FL_CLIENT = (1<<8) -- Is a player +FL_FAKECLIENT = (1<<9) -- Fake client simulated server side; don't send network messages to them +-- NON-PLAYER SPECIFIC = (i.e. not used by GameMovement or the client .dll ) -- Can still be applied to players though +FL_INWATER = (1<<10) -- In water + +FL_FLY = (1<<11) -- Changes the SV_Movestep= () behavior to not need to be on ground +FL_SWIM = (1<<12) -- Changes the SV_Movestep= () behavior to not need to be on ground = (but stay in water) +FL_CONVEYOR = (1<<13) +FL_NPC = (1<<14) +FL_GODMODE = (1<<15) +FL_NOTARGET = (1<<16) +FL_AIMTARGET = (1<<17) -- set if the crosshair needs to aim onto the entity +FL_PARTIALGROUND = (1<<18) -- not all corners are valid +FL_STATICPROP = (1<<19) -- Eetsa static prop! +FL_GRAPHED = (1<<20) -- worldgraph has this ent listed as something that blocks a connection +FL_GRENADE = (1<<21) +FL_STEPMOVEMENT = (1<<22) -- Changes the SV_Movestep= () behavior to not do any processing +FL_DONTTOUCH = (1<<23) -- Doesn't generate touch functions generates Untouch= () for anything it was touching when this flag was set +FL_BASEVELOCITY = (1<<24) -- Base velocity has been applied this frame = (used to convert base velocity into momentum) +FL_WORLDBRUSH = (1<<25) -- Not moveable/removeable brush entity = (really part of the world but represented as an entity for transparency or something) +FL_OBJECT = (1<<26) -- Terrible name. This is an object that NPCs should see. Missiles for example. +FL_KILLME = (1<<27) -- This entity is marked for death -- will be freed by game DLL +FL_ONFIRE = (1<<28) -- You know... +FL_DISSOLVING = (1<<29) -- We're dissolving! +FL_TRANSRAGDOLL = (1<<30) -- In the process of turning into a client side ragdoll. +FL_UNBLOCKABLE_BY_PLAYER = (1<<31) -- pusher that can't be blocked by the player + +LOADOUT_POSITION_PRIMARY = 0 +LOADOUT_POSITION_SECONDARY = 1 +LOADOUT_POSITION_MELEE = 2 +LOADOUT_POSITION_UTILITY = 3 +LOADOUT_POSITION_BUILDING = 4 +LOADOUT_POSITION_PDA = 5 +LOADOUT_POSITION_PDA2 = 6 + +-- wearables +LOADOUT_POSITION_HEAD = 7 +LOADOUT_POSITION_MISC = 8 + +-- other +LOADOUT_POSITION_ACTION = 9 + +LOADOUT_POSITION_MISC2 = 10 + +-- taunts +LOADOUT_POSITION_TAUNT = 11 +LOADOUT_POSITION_TAUNT2 = 12 +LOADOUT_POSITION_TAUNT3 = 13 +LOADOUT_POSITION_TAUNT4 = 14 +LOADOUT_POSITION_TAUNT5 = 15 +LOADOUT_POSITION_TAUNT6 = 16 +LOADOUT_POSITION_TAUNT7 = 17 +LOADOUT_POSITION_TAUNT8 = 18 + +---------------- +-- Entity callbacks +---------------- +ON_REMOVE = 0 +ON_SPAWN = 1 +ON_ACTIVATE = 2 +ON_DAMAGE_RECEIVED_PRE = 3 +ON_DAMAGE_RECEIVED_POST = 4 +ON_INPUT = 5 +ON_OUTPUT = 6 +ON_KEY_PRESSED = 7 +ON_KEY_RELEASED = 8 +ON_DEATH = 9 + +function table.ForEach(tab, funcname) + + for k, v in pairs(tab) do + funcname(k, v) + end + +end + +function table.HasValue(t, val) + for k, v in pairs(t) do + if (v == val) then return true end + end + return false +end + +function table.Random(t) + local rk = math.random(1, table.Count(t)) + local i = 1 + for k, v in pairs(t) do + if (i == rk) then return v, k end + i = i + 1 + end +end + +function table.Count(t) + local i = 0 + for k in pairs(t) do i = i + 1 end + return i +end + +-- Returns an array containing keys from a table +function table.GetKeys(tab) + local keys = {} + local id = 1 + + for k, v in pairs(tab) do + keys[id] = k + id = id + 1 + end + + return keys +end + +function PrintTable(t, indent, done) + done = done or {} + indent = indent or 0 + local keys = table.GetKeys(t) + local keysize = 0; + table.sort(keys, function(a, b) + if (type(a) == "number" and type(b) == "number") then return a < b end + + return tostring(a) < tostring(b) + end) + + for k, v in ipairs(keys) do + keysize = math.max(keysize, #tostring(v)); + end + + done[t] = true + + for i = 1, #keys do + local key = keys[i] + local value = t[key] + local line = string.rep("\t", indent) + + if (type(value) == "table" and not done[value] ) then + done[value] = true + print(line..key..":") + PrintTable(value, indent + 1, done) + done[value] = nil + else + print(line..key..string.rep(" ", keysize - #tostring(key) + 1).."=\t"..tostring(value)) + end + end +end \ No newline at end of file diff --git a/src/lua/natives.lua b/src/lua/natives.lua new file mode 100644 index 00000000..690ad8ae --- /dev/null +++ b/src/lua/natives.lua @@ -0,0 +1,436 @@ +---@class Vector +---@field x number +---@field y number +---@field z number +local vector = {} + +-- Creates a vector with set x, y, z coordinates +---@param x number +---@param y number +---@param z number +---@return Vector +function Vector(x, y, z) end + +---@return Vector +function vector:Normalize() end + +---@return number +function vector:Length() end + +---@param other Vector +---@return number +function vector:Distance(other) end + +---@param other Vector +---@return number +function vector:Dot(other) end + +---@param other Vector +---@return Vector +function vector:Cross(other) end + +---@param angle Vector +---@return Vector +function vector:Rotate(angle) end + +---@return Vector +function vector:ToAngles() end + +---@return Vector +function vector:GetForward() end + +---@return Vector, Vector, Vector +function vector:GetAngleVectors() end + +---@param vector Vector +---@return nil +function vector:Copy(vector) end + +---@param x number +---@param y number +---@param z number +---@return nil +function vector:CopyUnpacked(x, y, z) end + +---@class Entity +local entity = {} + +--Creates an entity with specified classname +--Alternatively, if classname is a number, returns an entity with handle/network index if it exists +---@param classname string +---@param spawn? boolean = true. Spawn entity after creation +---@param activate? boolean = true. Activate entity after creation +---@overload fun(classname: number, spawn: nil, activate: nil) +---@return Entity +function Entity(classname, spawn, activate) end + +---@return boolean +function entity:IsValid() end + +---@return number handleId +function entity:GetHandleIndex() end + +---@return number networkId +function entity:GetNetworkIndex() end + +---@return nil +function entity:Spawn() end + +---@return nil +function entity:Activate() end + +---@return nil +function entity:Remove() end + +-- Returns targetname of the entity +---@return string +function entity:GetName() end + +-- Set targetname of the entity +---@param name string +---@return nil +function entity:SetName(name) end + +---@return string +function entity:GetPlayerName() end + +---@return boolean +function entity:IsAlive() end + +---@return boolean # `true` if the entity is a player (real or bot) +function entity:IsPlayer() end + +---@return boolean # `true` if the entity has AI but is not a player bot. Engineer buildings are not NPC +function entity:IsNPC() end + +---@return boolean # `true` if the entity is a player bot, `false` otherwise. Returns `false` if entity is SourceTV bot +function entity:IsBot() end + +---@return boolean # `true` if the entity is a real player, and not a bot, `false` otherwise +function entity:IsRealPlayer() end + +---@return boolean +function entity:IsWeapon() end + +---@return boolean # `true` if the entity is an Engineer building or sapper, `false` otherwise +function entity:IsObject() end + +---@return boolean # `true` if the entity is an NPC, player or a building, `false` otherwise +function entity:IsCombatCharacter() end + +---@return boolean # `true` if the entity is a player cosmetic item, `false` otherwise +function entity:IsWearable() end + +---@return string +function entity:GetClassname() end + +--Adds callback function for a specific action, check ON_* globals for more info +---@param type number Action to use, check ON_* globals +---@param func function Callback function. Function parameters depend on the callback type +---@return number id Can be used to remove a callback with `RemoveCallback` function +function entity:AddCallback(type, func) end + +--Removes callback added with `AddCallback` function +---@param type number Action to use, check ON_* globals +---@param id number Callback id +---@return nil +function entity:RemoveCallback(type, id) end + +--Fires an entity input +---@param name string Name of the input +---@param value? any Value passed to the input +---@param activator? Entity The activator entity +---@param caller? Entity The caller entity +---@return boolean `true` if the input exists and was called successfully, `false` otherwise +function entity:AcceptInput(name, value, activator, caller) end + +--Returns player item in a slot number +---@param slot number Slot number, check LOADOUT_POSITION_* globals +---@return Entity +---@return nil #No item found in the specified slot +function entity:GetPlayerItemBySlot(slot) end + +--Returns player item by name +---@param name string Item definition name +---@return Entity +---@return nil #No item found with the specified name +function entity:GetPlayerItemByName(name) end + +--Returns item or player attribute value +---@param name string Attribute definition name +---@return string|number|nil value Value of the attribute +function entity:GetAttributeValue(name) end + +--Sets item or player attribute value. Value of nil removes the attribute +---@param name string Attribute definition name +---@param value string|number|nil Attribute value +---@return nil +function entity:SetAttributeValue(name, value) end + +--Returns a table of all properties (datamap, sendprop, custom) as keys and their values. +--The table is read only, changes must be written to the entity variable itself +---@return table +function entity:DumpProperties() end + +--Returns a table containing all inputs of the entity. +--The inputs can be called directly as functions. Example: `ent:FireUser1(value,activator,caller)` +---@return table +function entity:DumpInputs() end + +---@class TakeDamageInfo +---@field Attacker Entity|nil +---@field Inflictor Entity|nil +---@field Weapon Entity|nil +---@field Damage number +---@field DamageType number +---@field DamageCustom number +---@field CritType number + +--Deal damage to the entity +---@param damageInfo TakeDamageInfo +---@return number damageDealt Damage dealt to the entity +function entity:TakeDamage(damageInfo) end + +--Add condition to player. Check TF_COND_* globals for the list of conditions +---@param condition number +---@param duration? number #Optional duration in seconds +---@param provider? Entity #Optional player that caused the condition +---@return nil +function entity:AddCond(condition, duration, provider) end + +--Remove condition from player. Check TF_COND_* globals for the list of conditions +---@param condition number +---@return nil +function entity:RemoveCond(condition) end + +--Check if player has the condition applied. Check TF_COND_* globals for the list of conditions +---@param condition number +---@return boolean +function entity:InCond(condition) end + +--Get player that provided the condition. Check TF_COND_* globals for the list of conditions +---@param condition number +---@return boolean +function entity:GetConditionProvider(condition) end + +--Stun a player, slowing him down and/or making him unable to attack. Check TF_STUNFLAG_* globals +---@param duration number How long should the stun last in seconds +---@param amount number Movement speed penalty when TF_STUNFLAG_SLOWDOWN flag is set. The number should be between 0 and 1. 0 - 450 speed limit, 1 - no movement +---@param flags number Stun flags to set +---@param stunner? Entity Optional player that caused the stun +---@return boolean +function entity:StunPlayer(duration, amount, flags, stunner) end + +---@return Vector +function entity:GetAbsOrigin() end + +---@param vec Vector +---@return nil +function entity:SetAbsOrigin(vec) end + +---@return Vector +function entity:GetAbsAngles() end + +---@param angles Vector +---@return nil +function entity:SetAbsAngles(angles) end + +--Teleports entity to a location, optionally also sets angles and velocity +---@param pos Vector|nil +---@param angles? Vector|nil +---@param velocity? Vector|nil +---@return nil +function entity:Teleport(pos, angles, velocity) end + +--Creates an item. The item will be given to the player +---@param name string +---@param attrs? table Optional table with attribute key value pairs applied to the item +---@param noRemove? boolean = false. Do not remove previous item in the slot. +---@param forceGive? boolean = true. Forcibly give an item even if the player class does not match. +---@return Entity|nil item The created item or nil on failure +function entity:GiveItem(name, attrs, noRemove, forceGive) end + +--Fire entity output by name +---@param name string +---@param value? any +---@param activator? Entity +---@param delay? number +---@return nil +function entity:FireOutput(name, value, activator, delay) end + +--Set fake send prop value. This value is only seen by clients, not the server +---@param name string +---@param value any +---@return nil +function entity:SetFakeSendProp(name, value) end + +--Reset fake send prop value. This value is only seen by clients, not the server +---@param name string +---@return nil +function entity:ResetFakeSendProp(name) end + +--Get fake send prop value. This value is only seen by clients, not the server +---@param name string +---@return any +function entity:GetFakeSendProp(name) end + +--Add effects to an entity. Check EF_* globals +---@param effect number +---@return nil +function entity:AddEffects(effect) end + +--Remove effects from an entity. Check EF_* globals +---@param effect number +---@return nil +function entity:RemoveEffects(effect) end + +--Returns if effect is active. Check EF_* globals +---@param effect number +---@return boolean +function entity:IsEffectActive(effect) end + +ents = {} + +--Finds first matching entity by targetname. Trailing wildcards and @ selectors apply +---@param name string +---@param prev ?Entity #Find next matching entity after this entity +---@return Entity|nil #Entity if found, nil otherwise +function ents.FindByName(name, prev) end + +--Finds first matching entity by classname. Trailing wildcards and @ selectors apply +---@param classname string +---@param prev ?Entity #Find next matching entity after this entity +---@return Entity|nil #Entity if found, nil otherwise +function ents.FindByClassname(classname, prev) end + +--Finds all matching entities by name. Trailing wildcards and @ selectors apply +---@param name string +---@return table entities All entities that matched the criteria +function ents.FindAllByName(name) end + +--Finds all matching entities by classname. Trailing wildcards and @ selectors apply +---@param classname string +---@return table entities All entities that matched the criteria +function ents.FindAllByClassname(classname) end + +--Finds all entities in a box +---@param mins Vector Starting box coordinates +---@param maxs Vector Ending box coordinates +---@return table entities All entities that matched the criteria +function ents.FindAllInBox(mins, maxs) end + +--Finds all entities in a sphere +---@param center Vector Sphere center coordinates +---@param radius number Sphere radius +---@return table entities All entities that matched the criteria +function ents.FindAllInSphere(center, radius) end + +--Returns first entity +---@return Entity first First entity in the list +function ents.FirstEntity() end + +--Returns next entity after previous entity +---@param prev Entity The previous entity +---@return Entity first The next entity in the list +function ents.NextEntity(prev) end + +timer = {} + +--Creates a simple timer that calls the function after delay +---@param delay number Delay in seconds before firing the function +---@param func function Function to call +---@return number id #id that can be used to stop the timer with `timer.Stop` +function timer.Simple(delay, func) end + +--Creates a timer that calls the function after delay, with repeat count, and paramater +---@param delay number Delay in seconds before firing the function +---@param func function Function to call. If param is set, calls the function with a sigle provided value +---@param repeats? number = 1. Number of timer repeats. 0 = Infinite +---@param param? any Parameter to pass to the function +---@return number id #id that can be used to stop the timer with `timer.Stop` +function timer.Create(delay, func, repeats, param) end + +--Stops the timer with specified id +---@param id number id of the timer +---@return nil +function timer.Stop(id) end + +util = {} + +---@class TraceInfo +---@field start Vector|Entity +---@field endpos Entity +---@field distance number +---@field angles Vector +---@field mask number +---@field collisiongroup number +---@field mins Vector +---@field maxs Vector +---@field filter function|table|Entity + +---@class TraceResultInfo +---@field Entity Entity +---@field Fraction number +---@field FractionLeftSolid number +---@field Hit boolean +---@field HitBox number +---@field HitGroup number +---@field HitNoDraw boolean +---@field HitNonWorld boolean +---@field HitNormal Vector +---@field HitPos Vector +---@field HitSky boolean +---@field HitTexture string +---@field HitWorld boolean +---@field Normal Vector +---@field StartPos Vector +---@field StartSolid boolean +---@field SurfaceFlags number +---@field DispFlags number +---@field Contents number +---@field SurfaceProps number +---@field PhysicsBone number + +--Fires a trace +---@param trace TraceInfo trace table to use +---@return TraceResultInfo #trace result table +function util.Trace(trace) end + +--Prints message to player's console +---@param player Entity +---@vararg any +---@return nil +function util.PrintToConsole(player, ...) end + +--Prints console message to all players +---@vararg any +---@return nil +function util.PrintToConsoleAll(...) end + +--Prints message to player's chat +---@param player Entity +---@vararg any +---@return nil +function util.PrintToChat(player, ...) end + +--Prints chat message to all players +---@vararg any +---@return nil +function util.PrintToChatAll(...) end + +--Returns time in seconds since map load +---@return number +function CurTime() end + +--Returns tick coount since map load +---@return number +function TickCount() end + +--Returns current map name +---@return string +function GetMapName() end + +--Checks if the passed value is not nil and a valid Entity +---@param value any +---@return boolean +function IsValid(value) end \ No newline at end of file diff --git a/src/mod/etc/extra_player_slots.cpp b/src/mod/etc/extra_player_slots.cpp index ad2cdc62..e2eaf61b 100644 --- a/src/mod/etc/extra_player_slots.cpp +++ b/src/mod/etc/extra_player_slots.cpp @@ -25,6 +25,9 @@ namespace Mod::Etc::Extra_Player_Slots ConVar sig_etc_extra_player_slots_voice_display_fix("sig_etc_extra_player_slots_voice_display_fix", "0", FCVAR_NOTIFY, "Fixes voice chat indicator showing with more than 64 slots, but also disables all of voice chat"); + + ConVar sig_etc_extra_player_slots_no_death_cam("sig_etc_extra_player_slots_no_death_cam", "0", FCVAR_NOTIFY, + "Does not display death cam when killed by players in extra slots"); RefCount rc_CBaseServer_CreateFakeClient_HLTV; RefCount rc_CBaseServer_CreateFakeClient; @@ -55,11 +58,16 @@ namespace Mod::Etc::Extra_Player_Slots return 32; } + int force_create_at_slot = -1; + void SetForceCreateAtSlot(int slot) { + force_create_at_slot = slot; + } + DETOUR_DECL_MEMBER(CBaseClient *, CBaseServer_GetFreeClient, netadr_t &adr) { - if (!ExtraSlotsEnabled() || gpGlobals->maxClients < 34) return DETOUR_MEMBER_CALL(CBaseServer_GetFreeClient)(adr); + if ((!ExtraSlotsEnabled() || gpGlobals->maxClients < 34) && force_create_at_slot == -1) return DETOUR_MEMBER_CALL(CBaseServer_GetFreeClient)(adr); - if (rc_CBaseServer_CreateFakeClient) { + if (rc_CBaseServer_CreateFakeClient || force_create_at_slot != -1) { static ConVarRef tv_enable("tv_enable"); std::vector clientList; auto server = reinterpret_cast(this); @@ -88,6 +96,12 @@ namespace Mod::Etc::Extra_Player_Slots } } + if (force_create_at_slot != -1) { + CBaseClient *client = static_cast(server->GetClient(force_create_at_slot)); + force_create_at_slot = -1; + return !client->IsConnected() && !client->IsFakeClient() ? client : nullptr; + } + int desiredSlot = GetHLTVSlot(); if (!rc_CBaseServer_CreateFakeClient_HLTV) { if (!sig_etc_extra_player_slots_allow_bots.GetBool()) { @@ -105,7 +119,7 @@ namespace Mod::Etc::Extra_Player_Slots } } } - + for (int i = desiredSlot; i >= 0; i--) { CBaseClient *client = static_cast(server->GetClient(i)); if (!client->IsConnected() @@ -526,11 +540,50 @@ namespace Mod::Etc::Extra_Player_Slots { float time; IGameEvent *event; + bool send = false; + int fakePlayersIndex[3] {-1,-1,-1}; + std::string fakePlayersOldNames[3]; + int fakePlayersTeams[3] {-1, -1, -1}; }; std::deque kill_events; int player33_fake_team_num=-1; float player33_fake_kill_time=FLT_MIN; + CBasePlayer *FindFreeFakePlayer(std::vector &checkVec) + { + for (int i = 33; i >= 1; i--) { + bool found = false; + + auto playeri = UTIL_PlayerByIndex(i); + if (playeri == nullptr) continue; + + if (!playeri->IsHLTV() && !playeri->IsBot()) continue; + + for (auto &event : kill_events) { + for (auto index : event.fakePlayersIndex) { + if (index == i) { + found = true; + break; + } + } + if (found) break; + } + if (found) continue; + + for (auto player : checkVec) { + if (ENTINDEX(player) == i) { + found = true; + break; + } + } + if (found) continue; + + return playeri; + } + + return UTIL_PlayerByIndex(33); + } + MemberFuncThunk ft_CBaseClient_FireGameEvent("CBaseClient::FireGameEvent"); bool sending_delayed_event = false; @@ -549,19 +602,26 @@ namespace Mod::Etc::Extra_Player_Slots auto assister = UTIL_PlayerByUserId(event->GetInt("assister")); auto player33 = UTIL_PlayerByIndex(33); if (ENTINDEX(victim) > 33 || ENTINDEX(attacker) > 33 || ENTINDEX(assister) > 33) { - CBasePlayer *copyNameFromPlayer = nullptr; + std::vector participants; + std::vector fakePlayers; duplicate = gameeventmanager->DuplicateEvent(event); if (ENTINDEX(victim) > 33) { - duplicate->SetInt("userid", player33->GetUserID()); - copyNameFromPlayer = victim; + auto fakePlayer = FindFreeFakePlayer(fakePlayers); + duplicate->SetInt("userid", fakePlayer->GetUserID()); + fakePlayers.push_back(fakePlayer); + participants.push_back(victim); } if (ENTINDEX(attacker) > 33) { - duplicate->SetInt("attacker", player33->GetUserID()); - copyNameFromPlayer = attacker; + auto fakePlayer = FindFreeFakePlayer(fakePlayers); + duplicate->SetInt("attacker", fakePlayer->GetUserID()); + fakePlayers.push_back(fakePlayer); + participants.push_back(attacker); } if (ENTINDEX(assister) > 33) { - duplicate->SetInt("assister", player33->GetUserID()); - copyNameFromPlayer = assister; + auto fakePlayer = FindFreeFakePlayer(fakePlayers); + duplicate->SetInt("assister", fakePlayer->GetUserID()); + fakePlayers.push_back(fakePlayer); + participants.push_back(assister); } //auto fakePlayer = nullptr; /*for (int i = 33; i >= 1; i--) { @@ -571,12 +631,17 @@ namespace Mod::Etc::Extra_Player_Slots } }*/ //gameeventmanager->SerializeEvent(duplicate); - player33->SetPlayerName(copyNameFromPlayer->GetPlayerName()); - engine->SetFakeClientConVarValue(player33->edict(), "name", copyNameFromPlayer->GetPlayerName()); kill_events.push_back({gpGlobals->curtime, duplicate}); + for (size_t i = 0; i < participants.size(); i++) { + kill_events.back().fakePlayersOldNames[i] = fakePlayers[i]->GetPlayerName(); + kill_events.back().fakePlayersIndex[i] = ENTINDEX(fakePlayers[i]); + kill_events.back().fakePlayersTeams[i] = participants[i]->GetTeamNumber(); + fakePlayers[i]->SetPlayerName(participants[i]->GetPlayerName()); + engine->SetFakeClientConVarValue(fakePlayers[i]->edict(), "name", participants[i]->GetPlayerName()); + TFPlayerResource()->m_iTeam.SetIndex(participants[i]->GetTeamNumber(), fakePlayers[i]->entindex()); + } player33_fake_kill_time = gpGlobals->curtime; - player33_fake_team_num = copyNameFromPlayer->GetTeamNumber(); - TFPlayerResource()->m_iTeam.SetIndex(copyNameFromPlayer->GetTeamNumber(), 33); + //player33_fake_team_num = copyNameFromPlayer->GetTeamNumber(); return; //event = duplicate; } @@ -593,7 +658,8 @@ namespace Mod::Etc::Extra_Player_Slots for (auto it = kill_events.begin(); it != kill_events.end();){ auto &killEvent = (*it); - if (killEvent.time + 0.11f < gpGlobals->curtime) { + if (killEvent.time + 0.2f < gpGlobals->curtime && !killEvent.send) { + killEvent.send = true; sending_delayed_event = true; for (int i = 0; i < sv->GetMaxClients(); i++) { auto client = static_cast(sv->GetClient(i)); @@ -604,6 +670,26 @@ namespace Mod::Etc::Extra_Player_Slots sending_delayed_event = false; gameeventmanager->FreeEvent(killEvent.event); + } + + for (int i = 0; i < 3; i++) { + if (killEvent.fakePlayersIndex[i] != -1) { + TFPlayerResource()->m_iTeam.SetIndex(killEvent.fakePlayersTeams[i], killEvent.fakePlayersIndex[i]); + } + } + + if (killEvent.time + 0.35f < gpGlobals->curtime) { + + for (int i = 0; i < 3; i++) { + if (killEvent.fakePlayersIndex[i] != -1 && killEvent.fakePlayersIndex[i] != 33) { + + auto player = UTIL_PlayerByIndex(killEvent.fakePlayersIndex[i]); + if (player != nullptr) + player->SetPlayerName(killEvent.fakePlayersOldNames[i].c_str()); + engine->SetFakeClientConVarValue(INDEXENT(killEvent.fakePlayersIndex[i]), "name", killEvent.fakePlayersOldNames[i].c_str()); + } + } + it = kill_events.erase(it); } else { @@ -611,15 +697,14 @@ namespace Mod::Etc::Extra_Player_Slots } } } - if (player33_fake_kill_time + 2 > gpGlobals->curtime) { - realTeam = TFPlayerResource()->m_iTeam[33]; - TFPlayerResource()->m_iTeam.SetIndex(player33_fake_team_num, 33); - } + //if (player33_fake_kill_time + 2 > gpGlobals->curtime) { + // realTeam = TFPlayerResource()->m_iTeam[33]; + //} DETOUR_STATIC_CALL(SV_ComputeClientPacks)(clientCount, clients, snapshot); - if (realTeam != -1) { - - TFPlayerResource()->m_iTeam.SetIndex(realTeam, 33); - } + //if (realTeam != -1) { +// + // TFPlayerResource()->m_iTeam.SetIndex(realTeam, 33); + //} } DETOUR_DECL_MEMBER(void, CSteam3Server_SendUpdatedServerDetails) @@ -644,15 +729,9 @@ namespace Mod::Etc::Extra_Player_Slots DETOUR_DECL_MEMBER(void, CTFGameRules_CalcDominationAndRevenge, CTFPlayer *pAttacker, CBaseEntity *pWeapon, CTFPlayer *pVictim, bool bIsAssist, int *piDeathFlags) { - //int &players = static_cast(sv)->GetMaxClientsRef(); - //int oldPlayers = players; - //if (players > 33) { - // players = 33; - //} if (ENTINDEX(pAttacker) > 33 || ENTINDEX(pVictim) > 33) return; DETOUR_MEMBER_CALL(CTFGameRules_CalcDominationAndRevenge)(pAttacker, pWeapon, pVictim, bIsAssist, piDeathFlags); - //players = oldPlayers; } @@ -666,6 +745,15 @@ namespace Mod::Etc::Extra_Player_Slots } return DETOUR_MEMBER_CALL(CTFBot_GetNextSpawnClassname)(); } + + DETOUR_DECL_MEMBER(void, CTFPlayer_Event_Killed, const CTakeDamageInfo& info) + { + auto player = reinterpret_cast(this); + DETOUR_MEMBER_CALL(CTFPlayer_Event_Killed)(info); + if (sig_etc_extra_player_slots_no_death_cam.GetBool() && ToTFPlayer(player->m_hObserverTarget) != nullptr && ENTINDEX(player->m_hObserverTarget) > 33) { + player->m_hObserverTarget = nullptr; + } + } class CMod : public IMod, public IModCallbackListener { @@ -730,6 +818,7 @@ namespace Mod::Etc::Extra_Player_Slots MOD_ADD_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks"); MOD_ADD_DETOUR_MEMBER(CTFBot_GetNextSpawnClassname, "CTFBot::GetNextSpawnClassname"); + MOD_ADD_DETOUR_MEMBER(CTFPlayer_Event_Killed, "CTFPlayer::Event_Killed"); diff --git a/src/mod/etc/mapentity_additions.cpp b/src/mod/etc/mapentity_additions.cpp index be99c81d..4464e54a 100644 --- a/src/mod/etc/mapentity_additions.cpp +++ b/src/mod/etc/mapentity_additions.cpp @@ -27,6 +27,7 @@ #include "mod/pop/popmgr_extensions.h" #include "util/vi.h" #include "util/expression_eval.h" +#include "mod/etc/mapentity_additions.h" namespace Mod::Etc::Mapentity_Additions { @@ -46,50 +47,6 @@ namespace Mod::Etc::Mapentity_Additions "Summon Skeletons" }; - class CaseMenuHandler : public IMenuHandler - { - public: - - CaseMenuHandler(CTFPlayer * pPlayer, CLogicCase *pProvider) : IMenuHandler() { - this->player = pPlayer; - this->provider = pProvider; - } - - void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) { - - if (provider == nullptr) - return; - - const char *info = menu->GetItemInfo(item, nullptr); - - provider->FireCase(item + 1, player); - variant_t variant; - provider->FireCustomOutput<"onselect">(player, provider, variant); - } - - virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) - { - if (provider == nullptr) - return; - - variant_t variant; - provider->m_OnDefault->FireOutput(variant, player, provider); - } - - virtual void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) - { - menu->Destroy(false); - } - - void OnMenuDestroy(IBaseMenu *menu) { - DevMsg("Menu destroy\n"); - delete this; - } - - CHandle player; - CHandle provider; - }; - struct SendPropCacheEntry { ServerClass *serverClass; std::string name; @@ -106,8 +63,12 @@ namespace Mod::Etc::Mapentity_Additions int size; }; - std::vector send_prop_cache; - std::vector datamap_cache; + std::vector send_prop_cache_classes; + std::vector, std::vector>> send_prop_cache; + + std::vector datamap_cache_classes; + std::vector, std::vector>> datamap_cache; + std::vector>> entity_listeners; PooledString trigger_detector_class("$trigger_detector"); @@ -139,24 +100,23 @@ namespace Mod::Etc::Mapentity_Additions } } - void SetCustomVariable(CBaseEntity *entity, const char *key, variant_t &value) + bool SetCustomVariable(CBaseEntity *entity, std::string &key, variant_t &value, bool create = true, bool find = true, int vecIndex = -1) { if (value.FieldType() == FIELD_STRING) { ParseNumberOrVectorFromString(value.String(), value); } - - std::string nameNoArray = key; - int vecOffset; - ReadVectorIndexFromString(nameNoArray, vecOffset); - if (vecOffset != -1) { + + if (vecIndex != -1) { Vector vec; - entity->GetCustomVariableByText(nameNoArray.c_str(), value); + entity->GetCustomVariableByText(key.c_str(), value); value.Vector3D(vec); - vec[vecOffset] = value.Float(); + vec[vecIndex] = value.Float(); value.SetVector3D(vec); } - entity->SetCustomVariable(nameNoArray.c_str(), value); + + auto ret = entity->SetCustomVariable(key.c_str(), value, create, find); + return ret; } bool FindSendProp(int& off, SendTable *s_table, const char *name, SendProp *&prop, int index = -1) @@ -179,6 +139,12 @@ namespace Mod::Etc::Mapentity_Additions } } } + else { + if (s_prop->IsInsideArray()) { + off -= s_prop->GetOffset(); + continue; + } + } prop = s_prop; return true; } @@ -195,18 +161,20 @@ namespace Mod::Etc::Mapentity_Additions return false; } - bool ReadArrayIndexFromString(std::string &name, int &arrayPos, int &offset, bool &isVecAxis) + bool ReadArrayIndexFromString(std::string &name, int &arrayPos, int &vecAxis) { size_t arrayStr = name.find('$'); const char *vecChar = nullptr; + vecAxis = -1; + arrayPos = 0; if (arrayStr != std::string::npos) { StringToIntStrict(name.c_str() + arrayStr + 1, arrayPos, 0, &vecChar); name.resize(arrayStr); if (vecChar != nullptr) { switch (*vecChar) { - case 'x': case 'X': isVecAxis = true; break; - case 'y': case 'Y': isVecAxis = true; offset += 4; break; - case 'z': case 'Z': isVecAxis = true; offset += 8; break; + case 'x': case 'X': vecAxis = 0; break; + case 'y': case 'Y': vecAxis = 1; break; + case 'z': case 'Z': vecAxis = 2; break; } } return true; @@ -214,2217 +182,403 @@ namespace Mod::Etc::Mapentity_Additions return false; } - SendPropCacheEntry &GetSendPropOffset(ServerClass *serverClass, const char *name) { - - for (auto &entry : send_prop_cache) { - if (entry.serverClass == serverClass && entry.name == name) { - return entry; - } - } - - std::string nameNoArray = name; - int arrayPos = -1; - int offset = 0; - bool isVecAxis = false; - ReadArrayIndexFromString(nameNoArray, arrayPos, offset, isVecAxis); - - SendProp *prop = nullptr; - FindSendProp(offset,serverClass->m_pTable, nameNoArray.c_str(), prop, arrayPos); - if (prop == nullptr) { - offset = 0; - } - else if (prop->GetArrayProp() != nullptr) { - prop = prop->GetArrayProp(); - } - - send_prop_cache.push_back({serverClass, name, offset, isVecAxis, prop}); - return send_prop_cache.back(); - } - - DatamapCacheEntry &GetDataMapOffset(datamap_t *datamap, const char *name) { - - for (auto &entry : datamap_cache) { - if (entry.datamap == datamap && entry.name == name) { - return entry; - } - } - - std::string nameNoArray = name; - int arrayPos = 0; - int extraOffset = 0; - bool isVecAxis = false; - ReadArrayIndexFromString(nameNoArray, arrayPos, extraOffset, isVecAxis); - - for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { - // search through all the readable fields in the data description, looking for a match - for (int i = 0; i < dmap->dataNumFields; i++) { - if (dmap->dataDesc[i].fieldName != nullptr && strcmp(dmap->dataDesc[i].fieldName, nameNoArray.c_str()) == 0) { - fieldtype_t fieldType = isVecAxis ? FIELD_FLOAT : dmap->dataDesc[i].fieldType; - int offset = dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] + extraOffset; - - offset += clamp(arrayPos, 0, (int)dmap->dataDesc[i].fieldSize) * (dmap->dataDesc[i].fieldSizeInBytes / dmap->dataDesc[i].fieldSize); - - datamap_cache.push_back({datamap, name, offset, fieldType, dmap->dataDesc[i].fieldSize}); - return datamap_cache.back(); - } - } - } - - datamap_cache.push_back({datamap, name, 0, FIELD_VOID, 0}); - return datamap_cache.back(); - } - - void ParseCustomOutput(CBaseEntity *entity, const char *name, const char *value) { - std::string namestr = name; - boost::algorithm::to_lower(namestr); - // DevMsg("Add custom output %d %s %s\n", entity, namestr.c_str(), value); - entity->AddCustomOutput(namestr.c_str(), value); - variant_t variant; - variant.SetString(AllocPooledString(value)); - SetCustomVariable(entity, namestr.c_str(), variant); - - if (FStrEq(name, "modules")) { - std::string str(value); - boost::tokenizer> tokens(str, boost::char_separator(",")); - - for (auto &token : tokens) { - AddModuleByName(entity,token.c_str()); - } - } - if (entity->GetClassname() == PStr<"$entity_spawn_detector">() && FStrEq(name, "name")) { - bool found = false; - for (auto &pair : entity_listeners) { - if (pair.second == entity) { - pair.first = AllocPooledString(value); - found = true; - break; - } - } - if (!found) { - entity_listeners.push_back({AllocPooledString(value), entity}); - } - } - } - - void FireFormatInput(CLogicCase *entity, CBaseEntity *activator, CBaseEntity *caller) - { - std::string fmtstr = STRING(entity->m_nCase[15]); - unsigned int pos = 0; - unsigned int index = 1; - while ((pos = fmtstr.find('%', pos)) != std::string::npos ) { - if (pos != fmtstr.size() - 1 && fmtstr[pos + 1] == '%') { - fmtstr.erase(pos, 1); - pos++; - } - else - { - string_t str = entity->m_nCase[index - 1]; - fmtstr.replace(pos, 1, STRING(str)); - index++; - pos += strlen(STRING(str)); - } - } - - variant_t variant1; - variant1.SetString(AllocPooledString(fmtstr.c_str())); - entity->m_OnDefault->FireOutput(variant1, activator, entity); - } - - enum GetInputType { - ANY, - VARIABLE, - KEYVALUE, - DATAMAP, - SENDPROP - }; + void *stringTSendProxy = nullptr; CStandardSendProxies* sendproxies = nullptr; - bool SetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable) { - bool found = false; - - if (type == ANY) { - found = SetEntityVariable(entity, DATAMAP, name, variable) || - SetEntityVariable(entity, SENDPROP, name, variable) || - SetEntityVariable(entity, KEYVALUE, name, variable) || - SetEntityVariable(entity, VARIABLE, name, variable); - } - else if (type == VARIABLE) { - SetCustomVariable(entity, name, variable); - found = true; - } - else if (type == KEYVALUE) { - std::string nameNoArray = name; - int vecOffset; - ReadVectorIndexFromString(nameNoArray, vecOffset); - if (vecOffset != -1) { - Vector vec; - variant_t variant; - entity->ReadKeyField(nameNoArray.c_str(), &variant); - variable.Vector3D(vec); - variable.Convert(FIELD_FLOAT); - vec[vecOffset] = variable.Float(); - variable.SetVector3D(vec); - } - else { - entity->KeyValue(nameNoArray.c_str(), variable.String()); - } - found = false; - } - else if (type == DATAMAP) { - auto &entry = GetDataMapOffset(entity->GetDataDescMap(), name); - - if (entry.offset > 0) { - if (entry.fieldType == FIELD_CHARACTER && entry.size > 1) { - V_strncpy(((char*)entity) + entry.offset, variable.String(), entry.size); - } - else { - variable.Convert(entry.fieldType); - variable.SetOther(((char*)entity) + entry.offset); - } - found = true; - } - } - else if (type == SENDPROP) { - auto &entry = GetSendPropOffset(entity->GetServerClass(), name); - - if (entry.offset > 0) { - - int offset = entry.offset; - auto propType = entry.prop->GetType(); - if (propType == DPT_Int) { - - if (entry.prop->m_nBits == 21 && (entry.prop->GetFlags() & SPROP_UNSIGNED)) { - variable.Convert(FIELD_EHANDLE); - *(CHandle*)(((char*)entity) + offset) = variable.Entity(); - } - else { - variable.Convert(FIELD_INTEGER); - auto proxyfn = entry.prop->GetProxyFn(); - if (proxyfn == sendproxies->m_Int8ToInt32 || proxyfn == sendproxies->m_UInt8ToInt32) { - *(char*)(((char*)entity) + offset) = variable.Int(); - } - else if (proxyfn == sendproxies->m_Int16ToInt32 || proxyfn == sendproxies->m_UInt16ToInt32) { - *(short*)(((short*)entity) + offset) = variable.Int(); - } - else { - *(int*)(((char*)entity) + offset) = variable.Int(); - } - } - } - else if (propType == DPT_Float || entry.isVecAxis) { - variable.Convert(FIELD_FLOAT); - *(float*)(((char*)entity) + offset) = variable.Float(); - } - else if (propType == DPT_String) { - *(string_t*)(((char*)entity) + offset) = AllocPooledString(variable.String()); - } - else if (propType == DPT_Vector) { - variable.Convert(FIELD_VECTOR); - Vector tmpVec; - variable.Vector3D(tmpVec); - *(Vector*)(((char*)entity) + offset) = tmpVec; - } - entity->NetworkStateChanged(); - found = true; - } - } - return found; - } - - bool GetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable) { - bool found = false; - - if (type == ANY) { - found = GetEntityVariable(entity, VARIABLE, name, variable) || - GetEntityVariable(entity, DATAMAP, name, variable) || - GetEntityVariable(entity, SENDPROP, name, variable) || - GetEntityVariable(entity, KEYVALUE, name, variable); - } - else if (type == VARIABLE) { - std::string nameNoArray = name; - int vecOffset; - ReadVectorIndexFromString(nameNoArray, vecOffset); - if (entity->GetCustomVariableByText(nameNoArray.c_str(), variable)) { - found = true; - ConvertToVectorIndex(variable, vecOffset); - } - } - else if (type == KEYVALUE) { - if (name[0] == '$') { - return GetEntityVariable(entity, VARIABLE, name + 1, variable); - } - std::string nameNoArray = name; - int vecOffset; - ReadVectorIndexFromString(nameNoArray, vecOffset); - found = entity->ReadKeyField(nameNoArray.c_str(), &variable); - ConvertToVectorIndex(variable, vecOffset); - } - else if (type == DATAMAP) { - auto &entry = GetDataMapOffset(entity->GetDataDescMap(), name); - if (entry.offset > 0) { - if (entry.fieldType == FIELD_CHARACTER && entry.size > 1) { - variable.SetString(AllocPooledString(((char*)entity) + entry.offset)); - } - else { - variable.Set(entry.fieldType, ((char*)entity) + entry.offset); - } - if (entry.fieldType == FIELD_POSITION_VECTOR) { - variable.Convert(FIELD_VECTOR); - } - else if (entry.fieldType == FIELD_CLASSPTR) { - variable.Convert(FIELD_EHANDLE); - } - else if ((entry.fieldType == FIELD_CHARACTER && entry.size == 1) || (entry.fieldType == FIELD_SHORT)) { - variable.Convert(FIELD_INTEGER); - } - - found = true; - } - } - else if (type == SENDPROP) { - auto &entry = GetSendPropOffset(entity->GetServerClass(), name); - - if (entry.offset > 0) { - int offset = entry.offset; - auto propType = entry.prop->GetType(); - if (propType == DPT_Int) { - if (entry.prop->m_nBits == 21 && (entry.prop->GetFlags() & SPROP_UNSIGNED)) { - variable.Set(FIELD_EHANDLE, (CHandle*)(((char*)entity) + offset)); - } - else { - auto proxyfn = entry.prop->GetProxyFn(); - if (proxyfn == sendproxies->m_Int8ToInt32 || proxyfn == sendproxies->m_UInt8ToInt32) { - variable.SetInt(*(char*)(((char*)entity) + offset)); - } - else if (proxyfn == sendproxies->m_Int16ToInt32 || proxyfn == sendproxies->m_UInt16ToInt32) { - variable.SetInt(*(short*)(((char*)entity) + offset)); - } - else { - variable.SetInt(*(int*)(((char*)entity) + offset)); - } - } - } - else if (propType == DPT_Float || entry.isVecAxis) { - variable.SetFloat(*(float*)(((char*)entity) + offset)); - } - else if (propType == DPT_String) { - variable.SetString(*(string_t*)(((char*)entity) + offset)); - } - else if (propType == DPT_Vector) { - variable.SetVector3D(*(Vector*)(((char*)entity) + offset)); - } - found = true; - } - } - return found; - } - - void FireGetInput(CBaseEntity *entity, GetInputType type, const char *name, CBaseEntity *activator, CBaseEntity *caller, variant_t &value) { - char param_tokenized[256] = ""; - V_strncpy(param_tokenized, value.String(), sizeof(param_tokenized)); - char *targetstr = strtok(param_tokenized,"|"); - char *action = strtok(NULL,"|"); - char *defvalue = strtok(NULL,"|"); + void GetSendPropInfo(SendProp *prop, PropCacheEntry &entry, int offset) { + if (prop == nullptr) return; - variant_t variable; - - if (targetstr != nullptr && action != nullptr) { - - bool found = GetEntityVariable(entity, type, name, variable); - - if (!found && defvalue != nullptr) { - variable.SetString(AllocPooledString(defvalue)); - } - - if (found || defvalue != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, entity, activator, caller)) != nullptr ;) { - target->AcceptInput(action, activator, entity, variable, 0); - } - } - } - } - - DETOUR_DECL_MEMBER(void, CTFPlayer_InputIgnitePlayer, inputdata_t &inputdata) - { - CTFPlayer *activator = inputdata.pActivator != nullptr && inputdata.pActivator->IsPlayer() ? ToTFPlayer(inputdata.pActivator) : reinterpret_cast(this); - reinterpret_cast(this)->m_Shared->Burn(activator, nullptr, 10.0f); - } - - THINK_FUNC_DECL(RotatingFollowEntity) - { - auto rotating = reinterpret_cast(this); - auto data = GetExtraFuncRotatingData(rotating); - if (data->m_hRotateTarget == nullptr) - return; - - auto lookat = rotating->GetCustomVariable<"lookat">(); - Vector targetVec; - if (FStrEq(lookat, PStr<"origin">())) { - targetVec = data->m_hRotateTarget->GetAbsOrigin(); - } - else if (FStrEq(lookat, PStr<"center">())) { - targetVec = data->m_hRotateTarget->WorldSpaceCenter(); - } - else { - targetVec = data->m_hRotateTarget->EyePosition(); - } - - targetVec -= rotating->GetAbsOrigin(); - targetVec += data->m_hRotateTarget->GetAbsVelocity() * gpGlobals->frametime; - float projectileSpeed = rotating->GetCustomVariableFloat<"projectilespeed">(); - if (projectileSpeed != 0) { - targetVec += (targetVec.Length() / projectileSpeed) * data->m_hRotateTarget->GetAbsVelocity(); + entry.offset = offset; + if (prop->GetType() == DPT_Array) { + entry.offset += prop->GetArrayProp()->GetOffset(); + entry.elementStride = prop->GetElementStride(); + entry.arraySize = prop->GetNumElements(); } - - QAngle angToTarget; - VectorAngles(targetVec, angToTarget); - - float aimOffset = rotating->GetCustomVariableFloat<"aimoffset">(); - if (aimOffset != 0) { - angToTarget.x -= Vector(targetVec.x, targetVec.y, 0.0f).Length() * aimOffset; + else if (prop->GetDataTable() != nullptr) { + entry.arraySize = prop->GetDataTable()->GetNumProps(); + if (entry.arraySize > 1) { + entry.elementStride = prop->GetDataTable()->GetProp(1)->GetOffset() - prop->GetDataTable()->GetProp(0)->GetOffset(); + } } - float angleDiff; - float angle = 0; - QAngle moveAngle = rotating->m_vecMoveAng; - QAngle angles = rotating->GetAbsAngles(); - - float limit = rotating->GetCustomVariableFloat<"limit">(); - if (limit != 0.0f) { - angToTarget.x = clamp(AngleNormalize(angToTarget.x), -limit, limit); - angToTarget.y = clamp(AngleNormalize(angToTarget.y), -limit, limit); - } - if (moveAngle == QAngle(1,0,0)) { - angleDiff = AngleDiff(angles.x, angToTarget.x); - angle = rotating->GetLocalAngles().x; - } - else { - angleDiff = AngleDiff(angles.y, angToTarget.y); - angle = rotating->GetLocalAngles().y; + if (prop->GetArrayProp() != nullptr) { + prop = prop->GetArrayProp(); } - float speed = rotating->m_flMaxSpeed; - if (abs(angleDiff) < rotating->m_flMaxSpeed/66) { - speed = 0; - if (moveAngle == QAngle(1,0,0)) { - rotating->SetAbsAngles(QAngle(angToTarget.x, angles.y, angles.z)); + auto propType = prop->GetType(); + if (propType == DPT_Int) { + + if (prop->m_nBits == 21 && (prop->GetFlags() & SPROP_UNSIGNED)) { + entry.fieldType = FIELD_EHANDLE; } else { - rotating->SetAbsAngles(QAngle(angles.x, angToTarget.y, angles.z)); - } - } - - if (angleDiff > 0) { - speed *= -1.0f; - } - - if (speed != rotating->m_flTargetSpeed) { - rotating->m_bReversed = angleDiff > 0; - rotating->SetTargetSpeed(abs(speed)); - } - - rotating->SetNextThink(gpGlobals->curtime + 0.01f, "RotatingFollowEntity"); - } - - class RotatorModule : public EntityModule - { - public: - CHandle m_hRotateTarget; - }; - - THINK_FUNC_DECL(RotatorModuleTick) - { - auto data = this->GetEntityModule("rotator"); - if (data == nullptr || data->m_hRotateTarget == nullptr) - return; - - auto lookat = this->GetCustomVariable<"lookat">("eyes"); - Vector targetVec; - auto aimEntity = data->m_hRotateTarget; - if (FStrEq(lookat, PStr<"origin">())) { - targetVec = data->m_hRotateTarget->GetAbsOrigin(); - } - else if (FStrEq(lookat, PStr<"center">())) { - targetVec = data->m_hRotateTarget->WorldSpaceCenter(); - } - else if (FStrEq(lookat, PStr<"aim">())) { - Vector fwd; - Vector dest; - AngleVectors(data->m_hRotateTarget->EyeAngles(), &fwd); - VectorMA(data->m_hRotateTarget->EyePosition(), 8192.0f, fwd, dest); - trace_t tr; - UTIL_TraceLine(data->m_hRotateTarget->EyePosition(), dest, MASK_SHOT, data->m_hRotateTarget, COLLISION_GROUP_NONE, &tr); - - targetVec = tr.endpos; - aimEntity = tr.m_pEnt; - } - else { - targetVec = data->m_hRotateTarget->EyePosition(); - } - - targetVec -= this->GetAbsOrigin(); - if (aimEntity != nullptr) { - targetVec += aimEntity->GetAbsVelocity() * gpGlobals->frametime; - } - float projectileSpeed = this->GetCustomVariableFloat<"projectilespeed">(); - if (projectileSpeed != 0) { - targetVec += (targetVec.Length() / projectileSpeed) * data->m_hRotateTarget->GetAbsVelocity(); - } - - QAngle angToTarget; - VectorAngles(targetVec, angToTarget); - - float aimOffset = this->GetCustomVariableFloat<"aimoffset">(); - if (aimOffset != 0) { - angToTarget.x -= Vector(targetVec.x, targetVec.y, 0.0f).Length() * aimOffset; - } - - QAngle angles = this->GetAbsAngles(); - - bool velocitymode = this->GetCustomVariableFloat<"velocitymode">(); - float speedx = this->GetCustomVariableFloat<"rotationspeedx">(); - float speedy = this->GetCustomVariableFloat<"rotationspeedy">(); - if (!velocitymode) { - speedx *= gpGlobals->frametime; - speedy *= gpGlobals->frametime; - } - angToTarget.x = ApproachAngle(angToTarget.x, angles.x, speedx); - angToTarget.y = ApproachAngle(angToTarget.y, angles.y, speedy); - - float limitx = this->GetCustomVariableFloat<"rotationlimitx">(); - if (limitx != 0.0f) { - angToTarget.x = clamp(AngleNormalize(angToTarget.x), -limitx, limitx); - } - - float limity = this->GetCustomVariableFloat<"rotationlimity">(); - if (limity != 0.0f) { - angToTarget.y = clamp(AngleNormalize(angToTarget.y), -limity, limity); - } - - if (!velocitymode) { - this->SetAbsAngles(QAngle(angToTarget.x, angToTarget.y, angles.z)); - } - else { - angToTarget = QAngle(angToTarget.x - angles.x, angToTarget.y - angles.y, 0.0f); - this->SetLocalAngularVelocity(angToTarget); - } - - this->SetNextThink(gpGlobals->curtime + 0.01f, "RotatorModuleTick"); - } - - class ForwardVelocityModule : public EntityModule - { - public: - ForwardVelocityModule(CBaseEntity *entity); - }; - - THINK_FUNC_DECL(ForwardVelocityTick) - { - if (this->GetEntityModule("forwardvelocity") == nullptr) - return; - - Vector fwd; - AngleVectors(this->GetAbsAngles(), &fwd); - - fwd *= this->GetCustomVariableFloat<"forwardspeed">(); - - if (this->GetCustomVariableFloat<"directmode">() != 0) { - this->SetAbsOrigin(this->GetAbsOrigin() + fwd * gpGlobals->frametime); - } - else { - IPhysicsObject *pPhysicsObject = this->VPhysicsGetObject(); - if (pPhysicsObject) { - pPhysicsObject->SetVelocity(&fwd, nullptr); - } - else { - this->SetAbsVelocity(fwd); - } - } - this->SetNextThink(gpGlobals->curtime + 0.01f, "ForwardVelocityTick"); - } - - ForwardVelocityModule::ForwardVelocityModule(CBaseEntity *entity) - { - if (entity->GetNextThink("ForwardVelocityTick") < gpGlobals->curtime) { - THINK_FUNC_SET(entity, ForwardVelocityTick, gpGlobals->curtime + 0.01); - } - } - - class DroppedWeaponModule : public EntityModule - { - public: - DroppedWeaponModule(CBaseEntity *entity) : EntityModule(entity) {} - - CHandle m_hWeaponSpawner; - int ammo = -1; - int clip = -1; - float energy = FLT_MIN; - float charge = FLT_MIN; - }; - - class FakeParentModule : public EntityModule - { - public: - FakeParentModule(CBaseEntity *entity) : EntityModule(entity) {} - CHandle m_hParent; - bool m_bParentSet = false; - }; - - class MathVectorModule : public EntityModule - { - public: - MathVectorModule(CBaseEntity *entity) : EntityModule(entity) {} - union { - Vector value; - QAngle valueAng; - }; - }; - - THINK_FUNC_DECL(FakeParentModuleTick) - { - auto data = this->GetEntityModule("fakeparent"); - if (data == nullptr || data->m_hParent == nullptr) return; - - if (data->m_hParent == nullptr && data->m_bParentSet) { - variant_t variant; - this->FireCustomOutput<"onfakeparentkilled">(this, this, variant); - data->m_bParentSet = false; - } - - if (data->m_hParent == nullptr) return; - - Vector pos; - QAngle ang; - CBaseEntity *parent =data->m_hParent; - - auto bone = this->GetCustomVariable<"bone">(); - auto attachment = this->GetCustomVariable<"attachment">(); - bool posonly = this->GetCustomVariableFloat<"positiononly">(); - bool rotationonly = this->GetCustomVariableFloat<"rotationonly">(); - Vector offset = this->GetCustomVariableVector<"fakeparentoffset">(); - QAngle offsetangle = this->GetCustomVariableAngle<"fakeparentrotation">(); - matrix3x4_t transform; - - if (bone != nullptr) { - CBaseAnimating *anim = rtti_cast(parent); - anim->GetBoneTransform(anim->LookupBone(bone), transform); - } - else if (attachment != nullptr){ - CBaseAnimating *anim = rtti_cast(parent); - anim->GetAttachment(anim->LookupAttachment(attachment), transform); - } - else{ - transform = parent->EntityToWorldTransform(); - } - - if (!rotationonly) { - VectorTransform(offset, transform, pos); - this->SetAbsOrigin(pos); - } - - if (!posonly) { - MatrixAngles(transform, ang); - ang += offsetangle; - this->SetAbsAngles(ang); - } - - this->SetNextThink(gpGlobals->curtime + 0.01f, "FakeParentModuleTick"); - } - - class AimFollowModule : public EntityModule - { - public: - AimFollowModule(CBaseEntity *entity) : EntityModule(entity) {} - CHandle m_hParent; - }; - - THINK_FUNC_DECL(AimFollowModuleTick) - { - auto data = this->GetEntityModule("aimfollow"); - if (data == nullptr || data->m_hParent == nullptr) return; - - - Vector fwd; - Vector dest; - AngleVectors(data->m_hParent->EyeAngles(), &fwd); - VectorMA(data->m_hParent->EyePosition(), 8192.0f, fwd, dest); - trace_t tr; - CTraceFilterSkipTwoEntities filter(data->m_hParent, this, COLLISION_GROUP_NONE); - UTIL_TraceLine(data->m_hParent->EyePosition(), dest, MASK_SHOT, &filter, &tr); - - Vector targetVec = tr.endpos; - - this->SetAbsOrigin(targetVec); - bool rotationfollow = this->GetCustomVariableFloat<"rotationfollow">(); - if (rotationfollow) { - this->SetAbsAngles(data->m_hParent->EyeAngles()); - } - this->SetNextThink(gpGlobals->curtime + 0.01f, "AimFollowModuleTick"); - } - - class FakePropModule : public EntityModule, public AutoList - { - public: - FakePropModule() {} - FakePropModule(CBaseEntity *entity) : entity(entity) {} - - CBaseEntity *entity = nullptr; - std::unordered_map> props; - }; - - void AddModuleByName(CBaseEntity *entity, const char *name) - { - if (FStrEq(name, "rotator")) { - entity->AddEntityModule("rotator", new RotatorModule()); - } - else if (FStrEq(name, "forwardvelocity")) { - entity->AddEntityModule("forwardvelocity", new ForwardVelocityModule(entity)); - } - else if (FStrEq(name, "fakeparent")) { - entity->AddEntityModule("fakeparent", new FakeParentModule(entity)); - } - else if (FStrEq(name, "aimfollow")) { - entity->AddEntityModule("aimfollow", new AimFollowModule(entity)); - } - } - - PooledString logic_case_classname("logic_case"); - PooledString tf_gamerules_classname("tf_gamerules"); - PooledString player_classname("player"); - PooledString point_viewcontrol_classname("point_viewcontrol"); - PooledString weapon_spawner_classname("$weapon_spawner"); - - bool allow_create_dropped_weapon = false; - bool HandleCustomInput(CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID) - { - if (ent->GetClassname() == logic_case_classname) { - CLogicCase *logic_case = static_cast(ent); - if (strnicmp(szInputName, "FormatInput", strlen("$FormatInput")) == 0) { - int num = strtol(szInputName + strlen("$FormatInput"), nullptr, 10); - if (num > 0 && num < 16) { - logic_case->m_nCase[num - 1] = AllocPooledString(Value.String()); - FireFormatInput(logic_case, pActivator, pCaller); - - return true; - } - } - else if (strnicmp(szInputName, "FormatInputNoFire", strlen("FormatInputNoFire")) == 0) { - int num = strtol(szInputName + strlen("FormatInputNoFire"), nullptr, 10); - if (num > 0 && num < 16) { - logic_case->m_nCase[num - 1] = AllocPooledString(Value.String()); - return true; - } - } - else if (FStrEq(szInputName, "FormatString")) { - logic_case->m_nCase[15] = AllocPooledString(Value.String()); - FireFormatInput(logic_case, pActivator, pCaller); - return true; - } - else if (FStrEq(szInputName, "FormatStringNoFire")) { - logic_case->m_nCase[15] = AllocPooledString(Value.String()); - return true; - } - else if (FStrEq(szInputName, "Format")) { - FireFormatInput(logic_case, pActivator, pCaller); - return true; - } - else if (FStrEq(szInputName, "TestSigsegv")) { - ent->m_OnUser1->FireOutput(Value, pActivator, pCaller); - return true; - } - else if (FStrEq(szInputName, "ToInt")) { - variant_t convert; - convert.SetInt(strtol(Value.String(), nullptr, 10)); - logic_case->m_OnDefault->FireOutput(convert, pActivator, ent); - return true; - } - else if (FStrEq(szInputName, "ToFloat")) { - variant_t convert; - convert.SetFloat(strtof(Value.String(), nullptr)); - logic_case->m_OnDefault->FireOutput(convert, pActivator, ent); - return true; - } - else if (FStrEq(szInputName, "CallerToActivator")) { - logic_case->m_OnDefault->FireOutput(Value, pCaller, ent); - return true; - } - else if (FStrEq(szInputName, "GetKeyValueFromActivator")) { - variant_t variant; - pActivator->ReadKeyField(Value.String(), &variant); - logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); - return true; - } - else if (FStrEq(szInputName, "GetConVar")) { - ConVarRef cvref(Value.String()); - if (cvref.IsValid() && cvref.IsFlagSet(FCVAR_REPLICATED) && !cvref.IsFlagSet(FCVAR_PROTECTED)) { - variant_t variant; - variant.SetFloat(cvref.GetFloat()); - logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); - } - return true; - } - else if (FStrEq(szInputName, "GetConVarString")) { - ConVarRef cvref(Value.String()); - if (cvref.IsValid() && cvref.IsFlagSet(FCVAR_REPLICATED) && !cvref.IsFlagSet(FCVAR_PROTECTED)) { - variant_t variant; - variant.SetString(AllocPooledString(cvref.GetString())); - logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); - } - return true; - } - else if (FStrEq(szInputName, "DisplayMenu")) { - - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { - if (target != nullptr && target->IsPlayer() && !ToTFPlayer(target)->IsBot()) { - CaseMenuHandler *handler = new CaseMenuHandler(ToTFPlayer(target), logic_case); - IBaseMenu *menu = menus->GetDefaultStyle()->CreateMenu(handler); - - int i; - for (i = 1; i < 16; i++) { - string_t str = logic_case->m_nCase[i - 1]; - const char *name = STRING(str); - if (strlen(name) != 0) { - bool enabled = name[0] != '!'; - ItemDrawInfo info1(enabled ? name : name + 1, enabled ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); - menu->AppendItem("it", info1); - } - else { - break; - } - } - if (i < 11) { - menu->SetPagination(MENU_NO_PAGINATION); - } - - variant_t variant; - ent->ReadKeyField("Case16", &variant); - - char param_tokenized[256]; - V_strncpy(param_tokenized, variant.String(), sizeof(param_tokenized)); - - char *name = strtok(param_tokenized,"|"); - char *timeout = strtok(NULL,"|"); - - menu->SetDefaultTitle(name); - - char *flag; - while ((flag = strtok(NULL,"|")) != nullptr) { - if (FStrEq(flag, "Cancel")) { - menu->SetMenuOptionFlags(menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXIT); - } - } - - menu->Display(ENTINDEX(target), timeout == nullptr ? 0 : atoi(timeout)); - } - } - return true; - } - else if (FStrEq(szInputName, "HideMenu")) { - auto target = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr && target->IsPlayer()) { - menus->GetDefaultStyle()->CancelClientMenu(ENTINDEX(target), false); - } - } - else if (FStrEq(szInputName, "BitTest")) { - Value.Convert(FIELD_INTEGER); - int val = Value.Int(); - for (int i = 1; i <= 16; i++) { - string_t str = logic_case->m_nCase[i - 1]; - - if (val & atoi(STRING(str))) { - logic_case->FireCase(i, pActivator); - } - else { - break; - } - } - } - else if (FStrEq(szInputName, "BitTestAll")) { - Value.Convert(FIELD_INTEGER); - int val = Value.Int(); - for (int i = 1; i <= 16; i++) { - string_t str = logic_case->m_nCase[i - 1]; - - int test = atoi(STRING(str)); - if ((val & test) == test) { - logic_case->FireCase(i, pActivator); - } - else { - break; - } - } - } - } - else if (ent->GetClassname() == tf_gamerules_classname) { - if (stricmp(szInputName, "StopVO") == 0) { - TFGameRules()->BroadcastSound(SOUND_FROM_LOCAL_PLAYER, Value.String(), SND_STOP); - return true; - } - else if (stricmp(szInputName, "StopVORed") == 0) { - TFGameRules()->BroadcastSound(TF_TEAM_RED, Value.String(), SND_STOP); - return true; - } - else if (stricmp(szInputName, "StopVOBlue") == 0) { - TFGameRules()->BroadcastSound(TF_TEAM_BLUE, Value.String(), SND_STOP); - return true; - } - else if (stricmp(szInputName, "SetBossHealthPercentage") == 0) { - Value.Convert(FIELD_FLOAT); - float val = Value.Float(); - if (g_pMonsterResource.GetRef() != nullptr) - g_pMonsterResource->m_iBossHealthPercentageByte = (int) (val * 255.0f); - return true; - } - else if (stricmp(szInputName, "SetBossState") == 0) { - Value.Convert(FIELD_INTEGER); - int val = Value.Int(); - if (g_pMonsterResource.GetRef() != nullptr) - g_pMonsterResource->m_iBossState = val; - return true; - } - else if (stricmp(szInputName, "AddCurrencyGlobal") == 0) { - Value.Convert(FIELD_INTEGER); - int val = atoi(Value.Int()); - TFGameRules()->DistributeCurrencyAmount(val, nullptr, true, true, false); - return true; - } - } - else if (ent->GetClassname() == player_classname) { - if (stricmp(szInputName, "AllowClassAnimations") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - player->GetPlayerClass()->m_bUseClassAnimations = Value.Bool(); - } - return true; - } - else if (stricmp(szInputName, "SwitchClass") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - - if (player != nullptr) { - // Disable setup to allow class changing during waves in mvm - static ConVarRef endless("tf_mvm_endless_force_on"); - endless.SetValue(true); - - int index = strtol(Value.String(), nullptr, 10); - if (index > 0 && index < 10) { - player->HandleCommand_JoinClass(g_aRawPlayerClassNames[index]); - } - else { - player->HandleCommand_JoinClass(Value.String()); - } - endless.SetValue(false); - } - return true; - } - else if (stricmp(szInputName, "SwitchClassInPlace") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - - if (player != nullptr) { - // Disable setup to allow class changing during waves in mvm - static ConVarRef endless("tf_mvm_endless_force_on"); - endless.SetValue(true); - - Vector pos = player->GetAbsOrigin(); - QAngle ang = player->GetAbsAngles(); - Vector vel = player->GetAbsVelocity(); - - int index = strtol(Value.String(), nullptr, 10); - int oldState = player->m_Shared->GetState(); - player->m_Shared->SetState(TF_STATE_DYING); - if (index > 0 && index < 10) { - player->HandleCommand_JoinClass(g_aRawPlayerClassNames[index]); - } - else { - player->HandleCommand_JoinClass(Value.String()); - } - player->m_Shared->SetState(oldState); - endless.SetValue(false); - player->ForceRespawn(); - player->Teleport(&pos, &ang, &vel); - - } - return true; - } - else if (stricmp(szInputName, "ForceRespawn") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - if (player->GetTeamNumber() >= TF_TEAM_RED && player->GetPlayerClass() != nullptr && player->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED) { - player->ForceRespawn(); - } - else { - player->m_bAllowInstantSpawn = true; - } - } - return true; - } - else if (stricmp(szInputName, "ForceRespawnDead") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr && !player->IsAlive()) { - if (player->GetTeamNumber() >= TF_TEAM_RED && player->GetPlayerClass() != nullptr && player->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED) { - player->ForceRespawn(); - } - else { - player->m_bAllowInstantSpawn = true; - } - } - return true; - } - else if (stricmp(szInputName, "DisplayTextCenter") == 0) { - using namespace std::string_literals; - std::string text{Value.String()}; - text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); - text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); - text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, - (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); - gamehelpers->TextMsg(ENTINDEX(ent), TEXTMSG_DEST_CENTER, text.c_str()); - return true; - } - else if (stricmp(szInputName, "DisplayTextChat") == 0) { - using namespace std::string_literals; - std::string text{"\x01"s + Value.String() + "\x01"s}; - text = std::regex_replace(text, std::regex{"\\{reset\\}", std::regex_constants::icase}, "\x01"); - text = std::regex_replace(text, std::regex{"\\{blue\\}", std::regex_constants::icase}, "\x07" "99ccff"); - text = std::regex_replace(text, std::regex{"\\{red\\}", std::regex_constants::icase}, "\x07" "ff3f3f"); - text = std::regex_replace(text, std::regex{"\\{green\\}", std::regex_constants::icase}, "\x07" "99ff99"); - text = std::regex_replace(text, std::regex{"\\{darkgreen\\}", std::regex_constants::icase}, "\x07" "40ff40"); - text = std::regex_replace(text, std::regex{"\\{yellow\\}", std::regex_constants::icase}, "\x07" "ffb200"); - text = std::regex_replace(text, std::regex{"\\{grey\\}", std::regex_constants::icase}, "\x07" "cccccc"); - text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); - text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); - text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, - (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); - auto pos{text.find("{")}; - while(pos != std::string::npos){ - if(text.substr(pos).length() > 7){ - text[pos] = '\x07'; - text.erase(pos+7, 1); - pos = text.find("{"); - } else break; - } - gamehelpers->TextMsg(ENTINDEX(ent), TEXTMSG_DEST_CHAT , text.c_str()); - return true; - } - else if (stricmp(szInputName, "DisplayTextHint") == 0) { - using namespace std::string_literals; - std::string text{Value.String()}; - text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); - text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); - text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, - (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); - gamehelpers->HintTextMsg(ENTINDEX(ent), text.c_str()); - return true; - } - else if (stricmp(szInputName, "Suicide") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - player->CommitSuicide(false, true); - } - return true; - } - else if (stricmp(szInputName, "ChangeAttributes") == 0) { - CTFBot *bot = ToTFBot(ent); - if (bot != nullptr) { - auto *attrib = bot->GetEventChangeAttributes(Value.String()); - if (attrib != nullptr){ - bot->OnEventChangeAttributes(attrib); - } - } - return true; - } - else if (stricmp(szInputName, "RollCommonSpell") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); - - if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return true; - - CTFSpellBook *spellbook = rtti_cast(weapon); - spellbook->RollNewSpell(0); - - return true; - } - else if (stricmp(szInputName, "SetSpell") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); - - if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return true; - - const char *str = Value.String(); - int index = strtol(str, nullptr, 10); - for (int i = 0; i < ARRAYSIZE(SPELL_TYPE); i++) { - if (FStrEq(str, SPELL_TYPE[i])) { - index = i; - } - } - - CTFSpellBook *spellbook = rtti_cast(weapon); - spellbook->m_iSelectedSpellIndex = index; - spellbook->m_iSpellCharges = 1; - - return true; - } - else if (stricmp(szInputName, "AddSpell") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - - CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); - - if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return true; - - const char *str = Value.String(); - int index = strtol(str, nullptr, 10); - for (int i = 0; i < ARRAYSIZE(SPELL_TYPE); i++) { - if (FStrEq(str, SPELL_TYPE[i])) { - index = i; - } - } - - CTFSpellBook *spellbook = rtti_cast(weapon); - if (spellbook->m_iSelectedSpellIndex != index) { - spellbook->m_iSpellCharges = 0; - } - spellbook->m_iSelectedSpellIndex = index; - spellbook->m_iSpellCharges += 1; - - return true; - } - else if (stricmp(szInputName, "AddCond") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - int index = 0; - float duration = -1.0f; - sscanf(Value.String(), "%d %f", &index, &duration); - if (player != nullptr) { - player->m_Shared->AddCond((ETFCond)index, duration); - } - return true; - } - else if (stricmp(szInputName, "RemoveCond") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - int index = strtol(Value.String(), nullptr, 10); - if (player != nullptr) { - player->m_Shared->RemoveCond((ETFCond)index); - } - return true; - } - else if (stricmp(szInputName, "AddPlayerAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - char param_tokenized[256]; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - - char *attr = strtok(param_tokenized,"|"); - char *value = strtok(NULL,"|"); - - if (player != nullptr && value != nullptr) { - player->AddCustomAttribute(attr, atof(value), -1.0f); - } - return true; - } - else if (stricmp(szInputName, "RemovePlayerAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - player->RemoveCustomAttribute(Value.String()); - } - return true; - } - else if (stricmp(szInputName, "GetPlayerAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - char param_tokenized[256] = ""; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - char *attrName = strtok(param_tokenized,"|"); - char *targetstr = strtok(NULL,"|"); - char *action = strtok(NULL,"|"); - char *defvalue = strtok(NULL,"|"); - CEconItemAttribute * attr = player->GetAttributeList()->GetAttributeByName(attrName); - variant_t variable; - bool found = false; - if (attr != nullptr) { - char buf[256]; - attr->GetStaticData()->ConvertValueToString(*attr->GetValuePtr(), buf, sizeof(buf)); - variable.SetString(AllocPooledString(buf)); - found = true; - } - else { - variable.SetString(AllocPooledString(defvalue)); - } - - if (targetstr != nullptr && action != nullptr) { - if (found || defvalue != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { - target->AcceptInput(action, pActivator, ent, variable, 0); - } - } - } - } - return true; - } - else if (stricmp(szInputName, "GetItemAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - if (player != nullptr) { - char param_tokenized[256] = ""; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - char *itemSlot = strtok(param_tokenized,"|"); - char *attrName = strtok(NULL,"|"); - char *targetstr = strtok(NULL,"|"); - char *action = strtok(NULL,"|"); - char *defvalue = strtok(NULL,"|"); - - bool found = false; - - variant_t variable; - if (itemSlot != nullptr && attrName != nullptr) { - int slot = 0; - CEconEntity *item = nullptr; - if (StringToIntStrict(itemSlot, slot)) { - if (slot != -1) { - item = GetEconEntityAtLoadoutSlot(player, slot); - } - else { - item = player->GetActiveTFWeapon(); - } - } - else { - ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ - if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), itemSlot)) { - item = entity; - } - }); - } - - if (item != nullptr) { - CEconItemAttribute * attr = item->GetItem()->GetAttributeList().GetAttributeByName(attrName); - if (attr != nullptr) { - char buf[256]; - attr->GetStaticData()->ConvertValueToString(*attr->GetValuePtr(), buf, sizeof(buf)); - variable.SetString(AllocPooledString(buf)); - found = true; - } - } - } - - if (!found) { - variable.SetString(AllocPooledString(defvalue)); - } - - if (targetstr != nullptr && action != nullptr) { - if (found || defvalue != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { - target->AcceptInput(action, pActivator, ent, variable, 0); - } - } - } - } - return true; - } - else if (strnicmp(szInputName, "GetKey$", strlen("GetKey$")) == 0) { - FireGetInput(ent, KEYVALUE, szInputName + strlen("GetKey$"), pActivator, pCaller, Value); - return true; - } - else if (stricmp(szInputName, "AddItemAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - char param_tokenized[256]; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - - char *attr = strtok(param_tokenized,"|"); - char *value = strtok(NULL,"|"); - char *slot = strtok(NULL,"|"); - - if (player != nullptr && value != nullptr) { - CEconEntity *item = nullptr; - if (slot != nullptr) { - ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ - if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), slot)) { - item = entity; - } - }); - if (item == nullptr) - item = GetEconEntityAtLoadoutSlot(player, atoi(slot)); - } - else { - item = player->GetActiveTFWeapon(); - } - if (item != nullptr) { - CEconItemAttributeDefinition *attr_def = GetItemSchema()->GetAttributeDefinitionByName(attr); - if (attr_def == nullptr) { - int idx = -1; - if (StringToIntStrict(attr, idx)) { - attr_def = GetItemSchema()->GetAttributeDefinition(idx); - } - } - item->GetItem()->GetAttributeList().AddStringAttribute(attr_def, value); - - } - } - return true; - } - else if (stricmp(szInputName, "RemoveItemAttribute") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - char param_tokenized[256]; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - - char *attr = strtok(param_tokenized,"|"); - char *slot = strtok(NULL,"|"); - - if (player != nullptr) { - CEconEntity *item = nullptr; - if (slot != nullptr) { - ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ - if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), Value.String())) { - item = entity; - } - }); - if (item == nullptr) - item = GetEconEntityAtLoadoutSlot(player, atoi(slot)); - } - else { - item = player->GetActiveTFWeapon(); - } - if (item != nullptr) { - CEconItemAttributeDefinition *attr_def = GetItemSchema()->GetAttributeDefinitionByName(attr); - if (attr_def == nullptr) { - int idx = -1; - if (StringToIntStrict(attr, idx)) { - attr_def = GetItemSchema()->GetAttributeDefinition(idx); - } - } - item->GetItem()->GetAttributeList().RemoveAttribute(attr_def); - - } - } - return true; - } - else if (stricmp(szInputName, "PlaySoundToSelf") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - CRecipientFilter filter; - filter.AddRecipient(player); - - if (!enginesound->PrecacheSound(Value.String(), true)) - CBaseEntity::PrecacheScriptSound(Value.String()); - - EmitSound_t params; - params.m_pSoundName = Value.String(); - params.m_flSoundTime = 0.0f; - params.m_pflSoundDuration = nullptr; - params.m_bWarnOnDirectWaveReference = true; - CBaseEntity::EmitSound(filter, ENTINDEX(player), params); - return true; - } - else if (stricmp(szInputName, "IgnitePlayerDuration") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - Value.Convert(FIELD_FLOAT); - CTFPlayer *activator = pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : player; - player->m_Shared->Burn(activator, nullptr, Value.Float()); - return true; - } - else if (stricmp(szInputName, "WeaponSwitchSlot") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - Value.Convert(FIELD_INTEGER); - player->Weapon_Switch(player->Weapon_GetSlot(Value.Int())); - return true; - } - else if (stricmp(szInputName, "WeaponStripSlot") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - Value.Convert(FIELD_INTEGER); - int slot = Value.Int(); - CBaseCombatWeapon *weapon = player->GetActiveTFWeapon(); - if (slot != -1) { - weapon = player->Weapon_GetSlot(slot); - } - if (weapon != nullptr) - weapon->Remove(); - return true; - } - else if (stricmp(szInputName, "RemoveItem") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ - if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), Value.String())) { - if (entity->MyCombatWeaponPointer() != nullptr) { - player->Weapon_Detach(entity->MyCombatWeaponPointer()); - } - entity->Remove(); - } - }); - return true; - } - else if (stricmp(szInputName, "GiveItem") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - GiveItemByName(player, Value.String()); - return true; - } - else if (stricmp(szInputName, "DropItem") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - Value.Convert(FIELD_INTEGER); - int slot = Value.Int(); - CBaseCombatWeapon *weapon = player->GetActiveTFWeapon(); - if (slot != -1) { - weapon = player->Weapon_GetSlot(slot); - } - - if (weapon != nullptr) { - CEconItemView *item_view = weapon->GetItem(); - - allow_create_dropped_weapon = true; - auto dropped = CTFDroppedWeapon::Create(player, player->EyePosition(), vec3_angle, weapon->GetWorldModel(), item_view); - if (dropped != nullptr) - dropped->InitDroppedWeapon(player, static_cast(weapon), false, false); - - allow_create_dropped_weapon = false; - } - } - else if (stricmp(szInputName, "SetCurrency") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - player->RemoveCurrency(player->GetCurrency() - atoi(Value.String())); - } - else if (stricmp(szInputName, "AddCurrency") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - player->RemoveCurrency(atoi(Value.String()) * -1); - } - else if (stricmp(szInputName, "RemoveCurrency") == 0) { - CTFPlayer *player = ToTFPlayer(ent); - player->RemoveCurrency(atoi(Value.String())); - } - else if (strnicmp(szInputName, "CurrencyOutput", 15) == 0) { - CTFPlayer *player = ToTFPlayer(ent); - int cost = atoi(szInputName + 15); - if(player->GetCurrency() >= cost){ - char param_tokenized[2048] = ""; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - const char *separator = strchr(param_tokenized, ',') != nullptr ? "," : "|"; - if(strcmp(param_tokenized, "") != 0){ - char *target = strtok(param_tokenized,separator); - char *action = NULL; - char *value = NULL; - if(target != NULL) - action = strtok(NULL,separator); - if(action != NULL) - value = strtok(NULL,separator); - if(value != NULL){ - CEventQueue &que = g_EventQueue; - variant_t actualvalue; - string_t stringvalue = AllocPooledString(value); - actualvalue.SetString(stringvalue); - que.AddEvent(STRING(AllocPooledString(target)), STRING(AllocPooledString(action)), actualvalue, 0.0, player, player, -1); - } - } - player->RemoveCurrency(cost); - } - return true; - } - else if (strnicmp(szInputName, "CurrencyInvertOutput", 21) == 0) { - CTFPlayer *player = ToTFPlayer(ent); - int cost = atoi(szInputName + 21); - if(player->GetCurrency() < cost){ - char param_tokenized[2048] = ""; - const char *separator = strchr(param_tokenized, ',') != nullptr ? "," : "|"; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - if(strcmp(param_tokenized, "") != 0){ - char *target = strtok(param_tokenized,separator); - char *action = NULL; - char *value = NULL; - if(target != NULL) - action = strtok(NULL,separator); - if(action != NULL) - value = strtok(NULL,separator); - if(value != NULL){ - CEventQueue &que = g_EventQueue; - variant_t actualvalue; - string_t stringvalue = AllocPooledString(value); - actualvalue.SetString(stringvalue); - que.AddEvent(STRING(AllocPooledString(target)), STRING(AllocPooledString(action)), actualvalue, 0.0, player, player, -1); - } - } - //player->RemoveCurrency(cost); - } - return true; - } - else if (stricmp(szInputName, "RefillAmmo") == 0) { - CTFPlayer* player = ToTFPlayer(ent); - for(int i = 0; i < 7; ++i){ - player->SetAmmoCount(player->GetMaxAmmo(i), i); - } - return true; - } - else if(stricmp(szInputName, "Regenerate") == 0){ - CTFPlayer* player = ToTFPlayer(ent); - player->Regenerate(true); - return true; - } - else if(stricmp(szInputName, "BotCommand") == 0){ - CTFBot* bot = ToTFBot(ent); - if (bot != nullptr) - bot->MyNextBotPointer()->OnCommandString(Value.String()); - return true; - } - else if(stricmp(szInputName, "ResetInventory") == 0){ - CTFPlayer* player = ToTFPlayer(ent); - - player->GiveDefaultItemsNoAmmo(); - - } - else if(stricmp(szInputName, "PlaySequence") == 0){ - CTFPlayer* player = ToTFPlayer(ent); - player->PlaySpecificSequence(Value.String()); - - return true; - } - else if(stricmp(szInputName, "AwardExtraItem") == 0){ - CTFPlayer* player = ToTFPlayer(ent); - std::string str = Value.String(); - Mod::Pop::PopMgr_Extensions::AwardExtraItem(player, str); - return true; - } - else if(stricmp(szInputName, "StripExtraItem") == 0){ - CTFPlayer* player = ToTFPlayer(ent); - std::string str = Value.String(); - Mod::Pop::PopMgr_Extensions::StripExtraItem(player, str); - return true; - } -#ifdef GCC11 - else if(stricmp(szInputName, "TauntFromItem") == 0){ - CTFPlayer* player{ToTFPlayer(ent)}; - auto view{CEconItemView::Create()}; - const std::string_view input{Value.String()}; - auto index{vi::from_str(Value.String())}; - const auto v{vi::split_str(input, "|")}; - const auto do_taunt{[&view, &player](int index) -> void { - view->Init(index); - player->PlayTauntSceneFromItem(view); - CEconItemView::Destroy(view); - }}; - if(index && (v.size() < 2)){ - do_taunt(*index); - } else { - if(v.size() > 1){ - index = {vi::from_str(v[0])}; - const auto value{v[1]}; - if(index && (value.length() > 0)){ - std::string_view no_op{value}; - no_op.remove_prefix(1); - const auto remove_op{vi::from_str(no_op)}; - auto original{vi::from_str(value)}; - - const std::unordered_map> ops{ - {'+', [&player, &remove_op]{ - player->m_flTauntAttackTime += *remove_op; - } - }, - {'-', [&player, &remove_op]{ - player->m_flTauntAttackTime -= *remove_op; - } - }, - {'*', [&player, &remove_op]{ - player->m_flTauntAttackTime = - gpGlobals->curtime + - (player->m_flTauntAttackTime - - gpGlobals->curtime) * - *remove_op; - } - } - }; - if(value[0] == 'i'){ - do_taunt(*index); - player->m_flTauntAttackTime = 0.1f; - original = {}; - } - for(const auto& [op, func] : ops){ - if((value[0] == op) && remove_op){ - do_taunt(*index); - func(); - original = {}; - break; - } - } - if(original){ - do_taunt(*index); - player->m_flTauntAttackTime = - gpGlobals->curtime + *original; - } - } - } - } - } - else if(stricmp(szInputName, "TauntIndexConcept") == 0){ - CTFPlayer* player{ToTFPlayer(ent)}; - const std::string_view input{Value.String()}; - auto v{vi::split_str(input, "|")}; - if(v.size() > 1){ - auto index{ - vi::from_str(v[0]) - }; - auto taunt_concept{ - vi::from_str(v[1]) - }; - if(index && taunt_concept) - player->Taunt(*index, *taunt_concept); - } - } - -#endif - } - else if (ent->GetClassname() == point_viewcontrol_classname) { - auto camera = static_cast(ent); - if (stricmp(szInputName, "EnableAll") == 0) { - ForEachTFPlayer([&](CTFPlayer *player) { - if (player->IsBot()) - return; - else { - - camera->m_hPlayer = player; - camera->Enable(); - camera->m_spawnflags |= 512; - } - }); - return true; - } - else if (stricmp(szInputName, "DisableAll") == 0) { - ForEachTFPlayer([&](CTFPlayer *player) { - if (player->IsBot()) - return; - else { - camera->m_hPlayer = player; - camera->Disable(); - player->m_takedamage = player->IsObserver() ? 0 : 2; - camera->m_spawnflags &= ~(512); - } - }); - return true; - } - else if (stricmp(szInputName, "SetTarget") == 0) { - camera->m_hTarget = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - return true; - } - } - else if (ent->GetClassname() == trigger_detector_class) { - if (stricmp(szInputName, "targettest") == 0) { - auto data = GetExtraTriggerDetectorData(ent); - if (data->m_hLastTarget != nullptr) { - ent->FireCustomOutput<"targettestpass">(data->m_hLastTarget, ent, Value); - } - else { - ent->FireCustomOutput<"targettestfail">(nullptr, ent, Value); - } - return true; - } - } - else if (ent->GetClassname() == weapon_spawner_classname) { - if (stricmp(szInputName, "DropWeapon") == 0) { - - auto data = GetExtraData(ent); - auto name = ent->GetCustomVariable<"item">(); - auto item_def = GetItemSchema()->GetItemDefinitionByName(name); - - if (item_def != nullptr) { - auto item = CEconItemView::Create(); - item->Init(item_def->m_iItemDefIndex, item_def->m_iItemQuality, 9999, 0); - item->m_iItemID = (RandomInt(INT_MIN, INT_MAX) << 16) + ENTINDEX(ent); - Mod::Pop::PopMgr_Extensions::AddCustomWeaponAttributes(name, item); - auto &vars = GetCustomVariables(ent); - for (auto &var : vars) { - auto attr_def = GetItemSchema()->GetAttributeDefinitionByName(STRING(var.key)); - if (attr_def != nullptr) { - item->GetAttributeList().AddStringAttribute(attr_def, var.value.String()); - } - } - auto weapon = CTFDroppedWeapon::Create(nullptr, ent->EyePosition(), vec3_angle, item->GetPlayerDisplayModel(1, 2), item); - if (weapon != nullptr) { - if (weapon->VPhysicsGetObject() != nullptr) { - weapon->VPhysicsGetObject()->SetMass(25.0f); - - if (ent->GetCustomVariableFloat<"nomotion">() != 0) { - weapon->VPhysicsGetObject()->EnableMotion(false); - } - } - auto weapondata = weapon->GetOrCreateEntityModule("droppedweapon"); - weapondata->m_hWeaponSpawner = ent; - weapondata->ammo = ent->GetCustomVariableFloat<"ammo">(-1); - weapondata->clip = ent->GetCustomVariableFloat<"clip">(-1); - weapondata->energy = ent->GetCustomVariableFloat<"energy">(FLT_MIN); - weapondata->charge = ent->GetCustomVariableFloat<"charge">(FLT_MAX); - - data->m_SpawnedWeapons.push_back(weapon); - } - CEconItemView::Destroy(item); - } - return true; - } - else if (stricmp(szInputName, "RemoveDroppedWeapons") == 0) { - auto data = GetExtraWeaponSpawnerData(ent); - for (auto weapon : data->m_SpawnedWeapons) { - if (weapon != nullptr) { - weapon->Remove(); - } - } - data->m_SpawnedWeapons.clear(); - return true; - } - } - else if (ent->GetClassname() == PStr<"prop_vehicle_driveable">()) { - if (stricmp(szInputName, "EnterVehicle") == 0) { - auto target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - auto vehicle = rtti_cast(ent); - if (ToTFPlayer(target) != nullptr && vehicle != nullptr) { - - Vector delta = target->GetAbsOrigin() - ent->GetAbsOrigin(); - - QAngle angToTarget; - VectorAngles(delta, angToTarget); - ToTFPlayer(target)->SnapEyeAngles(angToTarget); - - CBaseServerVehicle *serverVehicle = vehicle->m_pServerVehicle; - serverVehicle->HandlePassengerEntry(ToTFPlayer(target), true); - } - } - if (stricmp(szInputName, "ExitVehicle") == 0) { - auto vehicle = rtti_cast(ent); - if (vehicle != nullptr && vehicle->m_hPlayer != nullptr) { - CBaseServerVehicle *serverVehicle = vehicle->m_pServerVehicle; - serverVehicle->HandlePassengerExit(vehicle->m_hPlayer); - } - } - } - else if (ent->GetClassname() == PStr<"$math_vector">()) { - auto mathVector = ent->GetOrCreateEntityModule("math_vector"); - auto vecValue = ent->GetCustomVariableVector<"value">(); - if (stricmp(szInputName, "Set") == 0) { - Value.Convert(FIELD_VECTOR); - ent->SetCustomVariable("value", Value); - return true; - } - else if (stricmp(szInputName, "Add") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - vec += vecValue; - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "AddScalar") == 0) { - Value.Convert(FIELD_FLOAT); - Vector vec = vecValue + Vector(Value.Float(), Value.Float(), Value.Float()); - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "Subtract") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - vec -= vecValue; - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "SubtractScalar") == 0) { - Value.Convert(FIELD_FLOAT); - Vector vec = vecValue - Vector(Value.Float(), Value.Float(), Value.Float()); - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "Multiply") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - vec *= vecValue; - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "MultiplyScalar") == 0) { - Value.Convert(FIELD_FLOAT); - Vector vec = vecValue * Value.Float(); - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "Divide") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - vec /= vecValue; - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "DivideScalar") == 0) { - Value.Convert(FIELD_FLOAT); - Vector vec = vecValue / Value.Float(); - Value.SetVector3D(vec); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "DotProduct") == 0) { - if (Value.Convert(FIELD_VECTOR)) { - Vector vec; - Value.Vector3D(vec); - Value.SetFloat(DotProduct(vec, vecValue)); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - } - return true; - } - else if (stricmp(szInputName, "CrossProduct") == 0) { - if (Value.Convert(FIELD_VECTOR)) { - Vector vec; - Value.Vector3D(vec); - Vector out; - CrossProduct(vec, vecValue, out); - Value.SetVector3D(out); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - } - return true; - } - else if (stricmp(szInputName, "Distance") == 0) { - if (Value.Convert(FIELD_VECTOR)) { - Vector vec; - Value.Vector3D(vec); - Value.SetFloat(vec.DistTo(vecValue)); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - } - return true; - } - else if (stricmp(szInputName, "DistanceToEntity") == 0) { - auto target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - Value.SetFloat(vecValue.DistTo(target->GetAbsOrigin())); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - } - return true; - } - else if (stricmp(szInputName, "RotateVector") == 0) { - Value.Convert(FIELD_VECTOR); - QAngle ang; - Value.Vector3D(*reinterpret_cast(&ang)); - Vector out; - VectorRotate(vecValue, ang, out); - Value.SetVector3D(out); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "Length") == 0) { - Value.SetFloat(vecValue.Length()); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "ToAngles") == 0) { - QAngle out; - VectorAngles(vecValue, out); - Value.SetVector3D(*reinterpret_cast(&out)); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "Normalize") == 0) { - Vector out = vecValue.Normalized(); - Value.SetVector3D(out); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "ToForwardVector") == 0) { - Vector out; - AngleVectors(*reinterpret_cast(&vecValue), &out); - Value.SetVector3D(out); - ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "GetX") == 0) { - Value.SetFloat(vecValue.x); - ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "GetY") == 0) { - Value.SetFloat(vecValue.y); - ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "GetZ") == 0) { - Value.SetFloat(vecValue.z); - ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); - return true; - } - } - if (stricmp(szInputName, "FireUserAsActivator1") == 0) { - ent->m_OnUser1->FireOutput(Value, ent, ent); - return true; - } - else if (stricmp(szInputName, "FireUserAsActivator2") == 0) { - ent->m_OnUser2->FireOutput(Value, ent, ent); - return true; - } - else if (stricmp(szInputName, "FireUserAsActivator3") == 0) { - ent->m_OnUser3->FireOutput(Value, ent, ent); - return true; - } - else if (stricmp(szInputName, "FireUserAsActivator4") == 0) { - ent->m_OnUser4->FireOutput(Value, ent, ent); - return true; - } - else if (stricmp(szInputName, "FireUser5") == 0) { - ent->FireCustomOutput<"onuser5">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "FireUser6") == 0) { - ent->FireCustomOutput<"onuser6">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "FireUser7") == 0) { - ent->FireCustomOutput<"onuser7">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "FireUser8") == 0) { - ent->FireCustomOutput<"onuser8">(pActivator, ent, Value); - return true; - } - else if (stricmp(szInputName, "TakeDamage") == 0) { - Value.Convert(FIELD_INTEGER); - int damage = Value.Int(); - CBaseEntity *attacker = ent; - - CTakeDamageInfo info(attacker, attacker, nullptr, vec3_origin, ent->GetAbsOrigin(), damage, DMG_PREVENT_PHYSICS_FORCE, 0 ); - ent->TakeDamage(info); - return true; - } - else if (stricmp(szInputName, "AddHealth") == 0) { - Value.Convert(FIELD_INTEGER); - CBaseEntity *attacker = ent; - ent->TakeHealth(Value.Int(), DMG_GENERIC); - return true; - } - else if (stricmp(szInputName, "TakeDamageFromActivator") == 0) { - Value.Convert(FIELD_INTEGER); - int damage = Value.Int(); - CBaseEntity *attacker = pActivator; - - CTakeDamageInfo info(attacker, attacker, nullptr, vec3_origin, ent->GetAbsOrigin(), damage, DMG_PREVENT_PHYSICS_FORCE, 0 ); - ent->TakeDamage(info); - return true; - } - else if (stricmp(szInputName, "SetModelOverride") == 0) { - int replace_model = CBaseEntity::PrecacheModel(Value.String()); - if (replace_model != -1) { - for (int i = 0; i < MAX_VISION_MODES; ++i) { - ent->SetModelIndexOverride(i, replace_model); - } - } - return true; - } - else if (stricmp(szInputName, "SetModel") == 0) { - CBaseEntity::PrecacheModel(Value.String()); - ent->SetModel(Value.String()); - return true; - } - else if (stricmp(szInputName, "SetModelSpecial") == 0) { - int replace_model = CBaseEntity::PrecacheModel(Value.String()); - if (replace_model != -1) { - ent->SetModelIndex(replace_model); - } - return true; - } - else if (stricmp(szInputName, "SetOwner") == 0) { - auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - if (owner != nullptr) { - ent->SetOwnerEntity(owner); - } - return true; - } - else if (stricmp(szInputName, "GetKeyValue") == 0) { - variant_t variant; - ent->ReadKeyField(Value.String(), &variant); - ent->m_OnUser1->FireOutput(variant, pActivator, ent); - return true; - } - else if (stricmp(szInputName, "InheritOwner") == 0) { - auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - if (owner != nullptr) { - ent->SetOwnerEntity(owner->GetOwnerEntity()); - } - return true; - } - else if (stricmp(szInputName, "InheritParent") == 0) { - auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - if (owner != nullptr) { - ent->SetParent(owner->GetMoveParent(), -1); - } - return true; - } - else if (stricmp(szInputName, "MoveType") == 0) { - variant_t variant; - int val1=0; - int val2=MOVECOLLIDE_DEFAULT; - - sscanf(Value.String(), "%d,%d", &val1, &val2); - ent->SetMoveType((MoveType_t)val1, (MoveCollide_t)val2); - return true; - } - else if (stricmp(szInputName, "PlaySound") == 0) { - - if (!enginesound->PrecacheSound(Value.String(), true)) - CBaseEntity::PrecacheScriptSound(Value.String()); - - ent->EmitSound(Value.String()); - return true; - } - else if (stricmp(szInputName, "StopSound") == 0) { - ent->StopSound(Value.String()); - return true; - } - else if (stricmp(szInputName, "SetLocalOrigin") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - ent->SetLocalOrigin(vec); - return true; - } - else if (stricmp(szInputName, "SetLocalAngles") == 0) { - Value.Convert(FIELD_VECTOR); - QAngle vec; - Value.Vector3D(*reinterpret_cast(&vec)); - ent->SetLocalAngles(vec); - return true; + auto proxyfn = prop->GetProxyFn(); + if (proxyfn == sendproxies->m_Int8ToInt32 || proxyfn == sendproxies->m_UInt8ToInt32) { + entry.fieldType = FIELD_CHARACTER; + } + else if (proxyfn == sendproxies->m_Int16ToInt32 || proxyfn == sendproxies->m_UInt16ToInt32) { + entry.fieldType = FIELD_SHORT; + } + else { + entry.fieldType = FIELD_INTEGER; + } + } } - else if (stricmp(szInputName, "SetLocalVelocity") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - ent->SetLocalVelocity(vec); - return true; + else if (propType == DPT_Float) { + entry.fieldType = FIELD_FLOAT; } - else if (stricmp(szInputName, "TeleportToEntity") == 0) { - auto target = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - Vector targetpos = target->GetAbsOrigin(); - ent->Teleport(&targetpos, nullptr, nullptr); + else if (propType == DPT_String) { + auto proxyfn = prop->GetProxyFn(); + if (proxyfn == stringTSendProxy) { + entry.fieldType = FIELD_STRING; + } + else { + entry.fieldType = FIELD_CHARACTER; } - return true; - } - else if (stricmp(szInputName, "MoveRelative") == 0) { - Value.Convert(FIELD_VECTOR); - Vector vec; - Value.Vector3D(vec); - vec = vec + ent->GetLocalOrigin(); - ent->SetLocalOrigin(vec); - return true; } - else if (stricmp(szInputName, "RotateRelative") == 0) { - Value.Convert(FIELD_VECTOR); - QAngle vec; - Value.Vector3D(*reinterpret_cast(&vec)); - vec = vec + ent->GetLocalAngles(); - ent->SetLocalAngles(vec); - return true; + else if (propType == DPT_Vector || propType == DPT_VectorXY) { + entry.fieldType = FIELD_VECTOR; } - else if (stricmp(szInputName, "TestEntity") == 0) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { - auto filter = rtti_cast(ent); - if (filter != nullptr && filter->PassesFilter(pCaller, target)) { - filter->m_OnPass->FireOutput(Value, pActivator, target); - } + } + + PropCacheEntry &GetSendPropOffset(ServerClass *serverClass, std::string &name) { + size_t classIndex = 0; + for (; classIndex < send_prop_cache_classes.size(); classIndex++) { + if (send_prop_cache_classes[classIndex] == serverClass) { + break; } - return true; } - else if (stricmp(szInputName, "StartTouchEntity") == 0) { - auto filter = rtti_cast(ent); - if (filter != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { - filter->StartTouch(target); - } - } - return true; + if (classIndex >= send_prop_cache_classes.size()) { + send_prop_cache_classes.push_back(serverClass); + send_prop_cache.emplace_back(); } - else if (stricmp(szInputName, "EndTouchEntity") == 0) { - auto filter = rtti_cast(ent); - if (filter != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { - filter->EndTouch(target); - } + auto &pair = send_prop_cache[classIndex]; + auto &names = pair.first; + + int nameCount = names.size(); + for (size_t i = 0; i < nameCount; i++ ) { + if (names[i] == name) { + return pair.second[i]; } - return true; } - else if (stricmp(szInputName, "RotateTowards") == 0) { - auto rotating = rtti_cast(ent); - if (rotating != nullptr) { - CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - auto data = GetExtraFuncRotatingData(rotating); - data->m_hRotateTarget = target; - if (rotating->GetNextThink("RotatingFollowEntity") < gpGlobals->curtime) { - THINK_FUNC_SET(rotating, RotatingFollowEntity, gpGlobals->curtime + 0.1); - } - } + int offset = 0; + SendProp *prop = nullptr; + FindSendProp(offset,serverClass->m_pTable, name.c_str(), prop); + + PropCacheEntry entry; + GetSendPropInfo(prop, entry, offset); + + names.push_back(name); + pair.second.push_back(entry); + return pair.second.back(); + } + + void WriteProp(CBaseEntity *entity, PropCacheEntry &entry, variant_t &variant, int arrayPos, int vecAxis) + { + if (entry.offset > 0) { + int offset = entry.offset + arrayPos * entry.elementStride; + fieldtype_t fieldType = entry.fieldType; + if (vecAxis != -1) { + fieldType = FIELD_FLOAT; + offset += vecAxis * sizeof(float); } - auto data = ent->GetEntityModule("rotator"); - if (data != nullptr) { - CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - data->m_hRotateTarget = target; - if (ent->GetNextThink("RotatingFollowEntity") < gpGlobals->curtime) { - Msg("install rotator\n"); - THINK_FUNC_SET(ent, RotatorModuleTick, gpGlobals->curtime + 0.1); - } - } + if (fieldType == FIELD_CHARACTER && entry.arraySize > 1) { + V_strncpy(((char*)entity) + offset, variant.String(), entry.arraySize); } - return true; - } - else if (stricmp(szInputName, "StopRotateTowards") == 0) { - auto data = ent->GetEntityModule("rotator"); - if (data != nullptr) { - data->m_hRotateTarget = nullptr; + else { + variant.Convert(fieldType); + variant.SetOther(((char*)entity) + offset); } - return true; } - else if (stricmp(szInputName, "SetForwardVelocity") == 0) { - Vector fwd; - AngleVectors(ent->GetAbsAngles(), &fwd); - fwd *= strtof(Value.String(), nullptr); + } + + void ReadProp(CBaseEntity *entity, PropCacheEntry &entry, variant_t &variant, int arrayPos, int vecAxis) + { + if (entry.offset > 0) { + int offset = entry.offset + arrayPos * entry.elementStride; + fieldtype_t fieldType = entry.fieldType; + if (vecAxis != -1) { + fieldType = FIELD_FLOAT; + offset += vecAxis * sizeof(float); + } - IPhysicsObject *pPhysicsObject = ent->VPhysicsGetObject(); - if (pPhysicsObject) { - pPhysicsObject->SetVelocity(&fwd, nullptr); + if (fieldType == FIELD_CHARACTER && entry.arraySize > 1) { + variant.SetString(AllocPooledString(((char*)entity) + offset)); } else { - ent->SetAbsVelocity(fwd); + variant.Set(fieldType, ((char*)entity) + offset); + } + if (fieldType == FIELD_POSITION_VECTOR) { + variant.Convert(FIELD_VECTOR); + } + else if (fieldType == FIELD_CLASSPTR) { + variant.Convert(FIELD_EHANDLE); + } + else if ((fieldType == FIELD_CHARACTER && entry.arraySize == 1) || (fieldType == FIELD_SHORT)) { + variant.Convert(FIELD_INTEGER); } - - return true; } - else if (stricmp(szInputName, "FaceEntity") == 0) { - CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - Vector delta = target->GetAbsOrigin() - ent->GetAbsOrigin(); - - QAngle angToTarget; - VectorAngles(delta, angToTarget); - ent->SetAbsAngles(angToTarget); - if (ToTFPlayer(ent) != nullptr) { - ToTFPlayer(ent)->SnapEyeAngles(angToTarget); - } + } + + void GetDataMapInfo(typedescription_t &desc, PropCacheEntry &entry) { + entry.fieldType = desc.fieldType; + entry.offset = desc.fieldOffset[ TD_OFFSET_NORMAL ]; + + entry.arraySize = (int)desc.fieldSize; + entry.elementStride = (desc.fieldSizeInBytes / desc.fieldSize); + } + + PropCacheEntry &GetDataMapOffset(datamap_t *datamap, std::string &name) { + + size_t classIndex = 0; + for (; classIndex < datamap_cache_classes.size(); classIndex++) { + if (datamap_cache_classes[classIndex] == datamap) { + break; } - - return true; } - else if (stricmp(szInputName, "SetFakeParent") == 0) { - auto data = ent->GetEntityModule("fakeparent"); - if (data != nullptr) { - CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - data->m_hParent = target; - data->m_bParentSet = true; - if (ent->GetNextThink("FakeParentModuleTick") < gpGlobals->curtime) { - THINK_FUNC_SET(ent, FakeParentModuleTick, gpGlobals->curtime + 0.01); - } - } + if (classIndex >= datamap_cache_classes.size()) { + datamap_cache_classes.push_back(datamap); + datamap_cache.emplace_back(); + } + auto &pair = datamap_cache[classIndex]; + auto &names = pair.first; + + int nameCount = names.size(); + for (size_t i = 0; i < nameCount; i++ ) { + if (names[i] == name) { + return pair.second[i]; } - - return true; } - else if (stricmp(szInputName, "SetAimFollow") == 0) { - auto data = ent->GetEntityModule("aimfollow"); - if (data != nullptr) { - CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); - if (target != nullptr) { - data->m_hParent = target; - if (ent->GetNextThink("AimFollowModuleTick") < gpGlobals->curtime) { - THINK_FUNC_SET(ent, AimFollowModuleTick, gpGlobals->curtime + 0.01); - } + + for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if (dmap->dataDesc[i].fieldName != nullptr && (strcmp(dmap->dataDesc[i].fieldName, name.c_str()) == 0 || + ( (dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY)) && strcmp(dmap->dataDesc[i].externalName, name.c_str()) == 0))) { + PropCacheEntry entry; + GetDataMapInfo(dmap->dataDesc[i], entry); + + names.push_back(name); + pair.second.push_back(entry); + return pair.second.back(); } } - - return true; } - else if (stricmp(szInputName, "ClearFakeParent") == 0) { - auto data = ent->GetEntityModule("fakeparent"); - if (data != nullptr) { - data->m_hParent = nullptr; - data->m_bParentSet = false; + + names.push_back(name); + pair.second.push_back({0, FIELD_VOID, 1, 0}); + return pair.second.back(); + } + + void ParseCustomOutput(CBaseEntity *entity, const char *name, const char *value) { + std::string namestr = name; + boost::algorithm::to_lower(namestr); + // DevMsg("Add custom output %d %s %s\n", entity, namestr.c_str(), value); + entity->AddCustomOutput(namestr.c_str(), value); + variant_t variant; + variant.SetString(AllocPooledString(value)); + SetCustomVariable(entity, namestr, variant); + + if (FStrEq(name, "modules")) { + std::string str(value); + boost::tokenizer> tokens(str, boost::char_separator(",")); + + for (auto &token : tokens) { + AddModuleByName(entity,token.c_str()); } - - return true; } - else if (strnicmp(szInputName, "SetVar$", strlen("SetVar$")) == 0) { - SetEntityVariable(ent, VARIABLE, szInputName + strlen("SetVar$"), Value); - return true; + if (entity->GetClassname() == PStr<"$entity_spawn_detector">() && FStrEq(name, "name")) { + bool found = false; + for (auto &pair : entity_listeners) { + if (pair.second == entity) { + pair.first = AllocPooledString(value); + found = true; + break; + } + } + if (!found) { + entity_listeners.push_back({AllocPooledString(value), entity}); + } } - else if (strnicmp(szInputName, "GetVar$", strlen("GetVar$")) == 0) { - FireGetInput(ent, VARIABLE, szInputName + strlen("GetVar$"), pActivator, pCaller, Value); - return true; + } + + bool SetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable) { + + std::string nameNoArray = name; + int arrayPos; + int vecAxis; + ReadArrayIndexFromString(nameNoArray, arrayPos, vecAxis); + return SetEntityVariable(entity, type, nameNoArray, variable, arrayPos, vecAxis); + } + + bool SetEntityVariable(CBaseEntity *entity, GetInputType type, std::string &name, variant_t &variable, int arrayPos, int vecAxis) { + bool found = false; + + if (type == ANY) { + found = SetEntityVariable(entity, VARIABLE_NO_CREATE, name, variable, arrayPos, vecAxis) || + SetEntityVariable(entity, DATAMAP_REFRESH, name, variable, arrayPos, vecAxis) || + SetEntityVariable(entity, SENDPROP, name, variable, arrayPos, vecAxis) || + SetEntityVariable(entity, KEYVALUE, name, variable, arrayPos, vecAxis) || + SetEntityVariable(entity, VARIABLE_NO_FIND, name, variable, arrayPos, vecAxis); } - else if (strnicmp(szInputName, "SetKey$", strlen("SetKey$")) == 0) { - SetEntityVariable(ent, KEYVALUE, szInputName + strlen("SetKey$"), Value); - return true; + else if (type == VARIABLE || type == VARIABLE_NO_CREATE || type == VARIABLE_NO_FIND) { + found = SetCustomVariable(entity, name, variable, type != VARIABLE_NO_CREATE, type != VARIABLE_NO_FIND, vecAxis); } - else if (strnicmp(szInputName, "GetKey$", strlen("GetKey$")) == 0) { - FireGetInput(ent, KEYVALUE, szInputName + strlen("GetKey$"), pActivator, pCaller, Value); - return true; + else if (type == KEYVALUE) { + + if (vecAxis != -1) { + Vector vec; + variant_t variant; + entity->ReadKeyField(name.c_str(), &variant); + variable.Vector3D(vec); + variable.Convert(FIELD_FLOAT); + vec[vecAxis] = variable.Float(); + variable.SetVector3D(vec); + } + + found = entity->KeyValue(name.c_str(), variable.String()); } - else if (strnicmp(szInputName, "SetData$", strlen("SetData$")) == 0) { - SetEntityVariable(ent, DATAMAP, szInputName + strlen("SetData$"), Value); - return true; + else if (type == DATAMAP || type == DATAMAP_REFRESH) { + auto &entry = GetDataMapOffset(entity->GetDataDescMap(), name); + + if (entry.offset > 0) { + WriteProp(entity, entry, variable, arrayPos, vecAxis); + + // Sometimes datamap shares the property with sendprops. In this case, it is better to tell the network state changed + if (type == DATAMAP_REFRESH) { + entity->NetworkStateChanged(); + } + found = true; + } } - else if (strnicmp(szInputName, "GetData$", strlen("GetData$")) == 0) { - FireGetInput(ent, DATAMAP, szInputName + strlen("GetData$"), pActivator, pCaller, Value); - return true; + else if (type == SENDPROP) { + auto &entry = GetSendPropOffset(entity->GetServerClass(), name); + + if (entry.offset > 0) { + WriteProp(entity, entry, variable, arrayPos, vecAxis); + entity->NetworkStateChanged(); + found = true; + } } - else if (strnicmp(szInputName, "SetProp$", strlen("SetProp$")) == 0) { - SetEntityVariable(ent, SENDPROP, szInputName + strlen("SetProp$"), Value); - return true; + return found; + } + + bool GetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable) { + + std::string nameNoArray = name; + int arrayPos; + int vecAxis; + ReadArrayIndexFromString(nameNoArray, arrayPos, vecAxis); + return GetEntityVariable(entity, type, nameNoArray, variable, arrayPos, vecAxis); + } + + bool GetEntityVariable(CBaseEntity *entity, GetInputType type, std::string &name, variant_t &variable, int arrayPos, int vecAxis) { + bool found = false; + + if (type == ANY) { + found = GetEntityVariable(entity, VARIABLE, name, variable, arrayPos, vecAxis) || + GetEntityVariable(entity, DATAMAP, name, variable, arrayPos, vecAxis) || + GetEntityVariable(entity, SENDPROP, name, variable, arrayPos, vecAxis);// || + //GetEntityVariable(entity, KEYVALUE, name, variable, arrayPos, vecAxis); } - else if (strnicmp(szInputName, "GetProp$", strlen("GetProp$")) == 0) { - FireGetInput(ent, SENDPROP, szInputName + strlen("GetProp$"), pActivator, pCaller, Value); - return true; + else if (type == VARIABLE) { + if (entity->GetCustomVariableByText(name.c_str(), variable)) { + found = true; + ConvertToVectorIndex(variable, vecAxis); + } } - else if (strnicmp(szInputName, "SetClientProp$", strlen("SetClientProp$")) == 0) { - auto mod = ent->GetOrCreateEntityModule("fakeprop"); - mod->props[szInputName + strlen("SetClientProp$")] = {Value, Value}; - return true; + else if (type == KEYVALUE) { + if (name[0] == '$') { + std::string varName = name.substr(1); + return GetEntityVariable(entity, VARIABLE, varName, variable, arrayPos, vecAxis); + } + auto &entry = GetDataMapOffset(entity->GetDataDescMap(), name); + if (entry.offset > 0) { + ReadProp(entity, entry, variable, arrayPos, vecAxis); + found = true; + } + //found = entity->ReadKeyField(name.c_str(), &variable); + //ConvertToVectorIndex(variable, vecAxis); } - else if (strnicmp(szInputName, "ResetClientProp$", strlen("ResetClientProp$")) == 0) { - auto mod = ent->GetOrCreateEntityModule("fakeprop"); - mod->props.erase(szInputName + strlen("ResetClientProp$")); - return true; + else if (type == DATAMAP) { + auto &entry = GetDataMapOffset(entity->GetDataDescMap(), name); + if (entry.offset > 0) { + ReadProp(entity, entry, variable, arrayPos, vecAxis); + found = true; + } } - else if (stricmp(szInputName, "GetEntIndex") == 0) { - char param_tokenized[256] = ""; - V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); - char *targetstr = strtok(param_tokenized,"|"); - char *action = strtok(NULL,"|"); - - variant_t variable; - variable.SetInt(ent->entindex()); - if (targetstr != nullptr && action != nullptr) { - for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { - target->AcceptInput(action, pActivator, ent, variable, 0); - } + else if (type == SENDPROP) { + auto &entry = GetSendPropOffset(entity->GetServerClass(), name); + if (entry.offset > 0) { + ReadProp(entity, entry, variable, arrayPos, vecAxis); + found = true; } - return true; } - else if (stricmp(szInputName, "AddModule") == 0) { - AddModuleByName(ent, Value.String()); - return true; + return found; + } + + DETOUR_DECL_MEMBER(void, CTFPlayer_InputIgnitePlayer, inputdata_t &inputdata) + { + CTFPlayer *activator = inputdata.pActivator != nullptr && inputdata.pActivator->IsPlayer() ? ToTFPlayer(inputdata.pActivator) : reinterpret_cast(this); + reinterpret_cast(this)->m_Shared->Burn(activator, nullptr, 10.0f); + } + + void AddModuleByName(CBaseEntity *entity, const char *name) + { + if (FStrEq(name, "rotator")) { + entity->AddEntityModule("rotator", new RotatorModule()); } - else if (stricmp(szInputName, "RemoveModule") == 0) { - ent->RemoveEntityModule(Value.String()); - return true; + else if (FStrEq(name, "forwardvelocity")) { + entity->AddEntityModule("forwardvelocity", new ForwardVelocityModule(entity)); } - else if (stricmp(szInputName, "RemoveOutput") == 0) { - const char *name = Value.String(); - auto datamap = ent->GetDataDescMap(); - for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { - // search through all the readable fields in the data description, looking for a match - for (int i = 0; i < dmap->dataNumFields; i++) { - if ((dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT) && stricmp(dmap->dataDesc[i].externalName, name) == 0) { - ((CBaseEntityOutput*)(((char*)ent) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]))->DeleteAllElements(); - return true; + else if (FStrEq(name, "fakeparent")) { + entity->AddEntityModule("fakeparent", new FakeParentModule(entity)); + } + else if (FStrEq(name, "aimfollow")) { + entity->AddEntityModule("aimfollow", new AimFollowModule(entity)); + } + } + + PooledString logic_case_classname("logic_case"); + PooledString tf_gamerules_classname("tf_gamerules"); + PooledString player_classname("player"); + PooledString point_viewcontrol_classname("point_viewcontrol"); + PooledString weapon_spawner_classname("$weapon_spawner"); + + inline bool CompareCaseInsensitiveStringView(std::string_view &view1, std::string_view &view2) + { + return view1.size() == view2.size() && stricmp(view1.data(), view2.data()) == 0; + } + + inline bool CompareCaseInsensitiveStringViewBeginsWith(std::string_view &view1, std::string_view &view2) + { + return view1.size() >= view2.size() && strnicmp(view1.data(), view2.data(), view2.size()) == 0; + } + + bool allow_create_dropped_weapon = false; + CustomInputFunction *GetCustomInput(CBaseEntity *ent, const char *szInputName) + { + { + char inputNameLowerBuf[1024]; + StrLowerCopy(szInputName, inputNameLowerBuf); + std::string_view inputNameLower(inputNameLowerBuf); + + for (auto &filter : InputFilter::List()) { + if (filter->Test(ent)) { + for (auto &input : filter->inputs) { + if ((!input.prefix && CompareCaseInsensitiveStringView(inputNameLower, input.name)) || + (input.prefix && CompareCaseInsensitiveStringViewBeginsWith(inputNameLower, input.name))) { + return &input.func; + } } } } - ent->RemoveCustomOutput(name+1); - return true; - } - else if (stricmp(szInputName, "CancelPending") == 0) { - g_EventQueue.GetRef().CancelEvents(ent); - return true; + return nullptr; } - return false; + + return nullptr; } DETOUR_DECL_MEMBER(bool, CBaseEntity_AcceptInput, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID) @@ -2438,8 +592,12 @@ namespace Mod::Etc::Mapentity_Additions eval.Evaluate(str + 3, ent, pActivator, pCaller, var2); } } - if (szInputName[0] == '$' && HandleCustomInput(ent, szInputName + 1, pActivator, pCaller, Value, outputID)) { - return true; + if (szInputName[0] == '$') { + auto func = GetCustomInput(ent, szInputName + 1); + if (func != nullptr) { + (*func)(ent, szInputName + 1, pActivator, pCaller, Value); + return true; + } } return DETOUR_MEMBER_CALL(CBaseEntity_AcceptInput)(szInputName, pActivator, pCaller, Value, outputID); @@ -3434,6 +1592,49 @@ namespace Mod::Etc::Mapentity_Additions } } + DETOUR_DECL_MEMBER(bool, CTFGameRules_RoundCleanupShouldIgnore, CBaseEntity *entity) + { + if (entity->GetClassname() == PStr<"$script_manager">()) return true; + + return DETOUR_MEMBER_CALL(CTFGameRules_RoundCleanupShouldIgnore)(entity); + } + + DETOUR_DECL_MEMBER(bool, CTFGameRules_ShouldCreateEntity, const char *classname) + { + if (strcmp(classname, "$script_manager") == 0) return false; + + return DETOUR_MEMBER_CALL(CTFGameRules_ShouldCreateEntity)(classname); + } + + + VHOOK_DECL(void, CMathCounter_Activate) + { + auto entity = reinterpret_cast(this); + if (strcmp(entity->GetClassname(), "$script_manager") == 0) { + auto mod = entity->GetOrCreateEntityModule("script"); + + auto filesVar = entity->GetCustomVariable<"scriptfile">(); + if (filesVar != nullptr) { + std::string files(filesVar); + boost::tokenizer> tokens(files, boost::char_separator(",")); + + for (auto &token : tokens) { + mod->DoFile(token.c_str(), true); + } + } + + auto script = entity->GetCustomVariable<"script">(); + if (script != nullptr) { + mod->DoString(script, true); + } + + mod->Activate(); + } + + return VHOOK_CALL(CMathCounter_Activate)(); + } + + void ClearFakeProp() { while (!FakePropModule::List().empty() ) { @@ -3470,6 +1671,9 @@ namespace Mod::Etc::Mapentity_Additions MOD_ADD_DETOUR_MEMBER(CBaseEntity_PassesDamageFilter, "CBaseEntity::PassesDamageFilter"); MOD_ADD_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks"); MOD_ADD_DETOUR_MEMBER(CTankSpawner_Spawn, "CTankSpawner::Spawn"); + MOD_ADD_DETOUR_MEMBER(CTFGameRules_RoundCleanupShouldIgnore, "CTFGameRules::RoundCleanupShouldIgnore"); + MOD_ADD_DETOUR_MEMBER(CTFGameRules_ShouldCreateEntity, "CTFGameRules::ShouldCreateEntity"); + MOD_ADD_VHOOK(CMathCounter_Activate, TypeName(), "CBaseEntity::Activate"); // Execute -1 delay events immediately @@ -3491,6 +1695,7 @@ namespace Mod::Etc::Mapentity_Additions virtual bool OnLoad() override { + stringTSendProxy = AddrManager::GetAddr("SendProxy_StringT_To_String"); ActivateLoadedInput(); if (servertools->GetEntityFactoryDictionary()->FindFactory("$filter_keyvalue") == nullptr) { servertools->GetEntityFactoryDictionary()->InstallFactory(servertools->GetEntityFactoryDictionary()->FindFactory("filter_base"), "$filter_keyvalue"); @@ -3505,12 +1710,18 @@ namespace Mod::Etc::Mapentity_Additions servertools->GetEntityFactoryDictionary()->InstallFactory(servertools->GetEntityFactoryDictionary()->FindFactory("point_teleport"), "$weapon_spawner"); servertools->GetEntityFactoryDictionary()->InstallFactory(servertools->GetEntityFactoryDictionary()->FindFactory("math_counter"), "$math_vector"); servertools->GetEntityFactoryDictionary()->InstallFactory(servertools->GetEntityFactoryDictionary()->FindFactory("math_counter"), "$entity_spawn_detector"); + servertools->GetEntityFactoryDictionary()->InstallFactory(servertools->GetEntityFactoryDictionary()->FindFactory("math_counter"), "$script_manager"); } return true; } virtual bool ShouldReceiveCallbacks() const override { return this->IsEnabled(); } + virtual void OnEnable() override + { + sendproxies = gamedll->GetStandardSendProxies(); + } + virtual void LevelInitPreEntity() override { sendproxies = gamedll->GetStandardSendProxies(); diff --git a/src/mod/etc/mapentity_additions.h b/src/mod/etc/mapentity_additions.h new file mode 100644 index 00000000..444bf03a --- /dev/null +++ b/src/mod/etc/mapentity_additions.h @@ -0,0 +1,159 @@ +#ifndef _INCLUDE_SIGSEGV_MOD_ETC_MAPENTITY_ADDITIONS_H_ +#define _INCLUDE_SIGSEGV_MOD_ETC_MAPENTITY_ADDITIONS_H_ + +#include "util/lua.h" + +namespace Mod::Etc::Mapentity_Additions +{ + struct PropCacheEntry + { + int offset = 0; + fieldtype_t fieldType = FIELD_VOID; + int arraySize = 1; + int elementStride = 0; + }; + + enum GetInputType { + ANY, + VARIABLE, + KEYVALUE, + DATAMAP, + SENDPROP, + VARIABLE_NO_FIND, + VARIABLE_NO_CREATE, + DATAMAP_REFRESH, + }; + + void WriteProp(CBaseEntity *entity, PropCacheEntry &entry, variant_t &variant, int arrayPos, int vecAxis); + void ReadProp(CBaseEntity *entity, PropCacheEntry &entry, variant_t &variant, int arrayPos, int vecAxis); + + void GetSendPropInfo(SendProp *prop, PropCacheEntry &entry, int offset); + void GetDataMapInfo(typedescription_t &desc, PropCacheEntry &entry); + + PropCacheEntry &GetSendPropOffset(ServerClass *serverClass, std::string &name); + PropCacheEntry &GetDataMapOffset(datamap_t *datamap, std::string &name); + + bool SetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable); + bool SetEntityVariable(CBaseEntity *entity, GetInputType type, std::string &name, variant_t &variable, int arrayPos, int vecAxis); + + bool GetEntityVariable(CBaseEntity *entity, GetInputType type, const char *name, variant_t &variable); + bool GetEntityVariable(CBaseEntity *entity, GetInputType type, std::string &name, variant_t &variable, int arrayPos, int vecAxis); + + extern bool allow_create_dropped_weapon; + + class DroppedWeaponModule : public EntityModule + { + public: + DroppedWeaponModule(CBaseEntity *entity) : EntityModule(entity) {} + + CHandle m_hWeaponSpawner; + int ammo = -1; + int clip = -1; + float energy = FLT_MIN; + float charge = FLT_MIN; + }; + + class FakeParentModule : public EntityModule + { + public: + FakeParentModule(CBaseEntity *entity) : EntityModule(entity) {} + CHandle m_hParent; + bool m_bParentSet = false; + }; + + class MathVectorModule : public EntityModule + { + public: + MathVectorModule(CBaseEntity *entity) : EntityModule(entity) {} + union { + Vector value; + QAngle valueAng; + }; + }; + + class RotatorModule : public EntityModule + { + public: + CHandle m_hRotateTarget; + }; + + class FakePropModule : public EntityModule, public AutoList + { + public: + FakePropModule() {} + FakePropModule(CBaseEntity *entity) : entity(entity) {} + + CBaseEntity *entity = nullptr; + std::unordered_map> props; + }; + + class ForwardVelocityModule : public EntityModule + { + public: + ForwardVelocityModule(CBaseEntity *entity); + }; + + class AimFollowModule : public EntityModule + { + public: + AimFollowModule(CBaseEntity *entity) : EntityModule(entity) {} + CHandle m_hParent; + }; + + class ScriptModule : public EntityModule, public Util::Lua::LuaState + { + public: + ScriptModule(CBaseEntity *entity) : EntityModule(entity), owner(entity) {} + + virtual void TimerAdded() override; + CBaseEntity *owner; + bool popInit = false; + }; + + + using CustomInputFunction = void (*)(CBaseEntity *, const char *, CBaseEntity *, CBaseEntity *, variant_t &); + CustomInputFunction *GetCustomInput(CBaseEntity *ent, const char *szInputName); + + class CustomInput { + public: + CustomInput(std::string_view name, bool prefix, CustomInputFunction func) : name(name), prefix(prefix), func(func) {} + std::string_view name; + bool prefix; + CustomInputFunction func; + }; + + class InputFilter : public AutoList { + public: + InputFilter(std::vector inputs) : + inputs(inputs) + {} + + virtual bool Test(CBaseEntity *ent) {return true;} + + void AddFunction(std::string_view name, bool prefix, CustomInputFunction func) { + inputs.push_back({name, prefix, func}); + } + std::vector inputs; + }; + + class ClassnameFilter : public InputFilter { + public: + ClassnameFilter(const char *classname, std::vector inputs) :InputFilter(inputs), classname(PooledString(classname)) {} + + virtual bool Test(CBaseEntity *ent) {return ent->GetClassname() == classname;} + + PooledString classname; + }; + + template + class RTTIFilter : public InputFilter { + public: + RTTIFilter(std::vector inputs) : InputFilter(inputs) {} + + virtual bool Test(CBaseEntity *ent) {return rtti_cast(ent);} + }; + + void AddModuleByName(CBaseEntity *entity, const char *name); +} + +#endif \ No newline at end of file diff --git a/src/mod/etc/mapentity_inputs.cpp b/src/mod/etc/mapentity_inputs.cpp new file mode 100644 index 00000000..d984d799 --- /dev/null +++ b/src/mod/etc/mapentity_inputs.cpp @@ -0,0 +1,1869 @@ +#include "mod.h" +#include "stub/baseentity.h" +#include "stub/entities.h" +#include "stub/gamerules.h" +#include "stub/populators.h" +#include "stub/tfbot.h" +#include "stub/nextbot_cc.h" +#include "stub/tf_shareddefs.h" +#include "stub/misc.h" +#include "stub/strings.h" +#include "stub/server.h" +#include "stub/objects.h" +#include "stub/extraentitydata.h" +#include "mod/pop/common.h" +#include "util/pooled_string.h" +#include "util/scope.h" +#include "util/iterate.h" +#include "util/misc.h" +#include "util/clientmsg.h" +//#include +//#include +#include +#include +#include +#include +#include "stub/sendprop.h" +#include "mod/pop/popmgr_extensions.h" +#include "util/vi.h" +#include "util/expression_eval.h" +#include "mod/etc/mapentity_additions.h" + +namespace Mod::Etc::Mapentity_Additions +{ + + class CaseMenuHandler : public IMenuHandler + { + public: + + CaseMenuHandler(CTFPlayer * pPlayer, CLogicCase *pProvider) : IMenuHandler() { + this->player = pPlayer; + this->provider = pProvider; + } + + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) { + + if (provider == nullptr) + return; + + const char *info = menu->GetItemInfo(item, nullptr); + + provider->FireCase(item + 1, player); + variant_t variant; + provider->FireCustomOutput<"onselect">(player, provider, variant); + } + + virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) + { + if (provider == nullptr) + return; + + variant_t variant; + provider->m_OnDefault->FireOutput(variant, player, provider); + } + + virtual void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) + { + menu->Destroy(false); + } + + void OnMenuDestroy(IBaseMenu *menu) { + DevMsg("Menu destroy\n"); + delete this; + } + + CHandle player; + CHandle provider; + }; + + void FireFormatInput(CLogicCase *entity, CBaseEntity *activator, CBaseEntity *caller) + { + std::string fmtstr = STRING(entity->m_nCase[15]); + unsigned int pos = 0; + unsigned int index = 1; + while ((pos = fmtstr.find('%', pos)) != std::string::npos ) { + if (pos != fmtstr.size() - 1 && fmtstr[pos + 1] == '%') { + fmtstr.erase(pos, 1); + pos++; + } + else + { + string_t str = entity->m_nCase[index - 1]; + fmtstr.replace(pos, 1, STRING(str)); + index++; + pos += strlen(STRING(str)); + } + } + + variant_t variant1; + variant1.SetString(AllocPooledString(fmtstr.c_str())); + entity->m_OnDefault->FireOutput(variant1, activator, entity); + } + + THINK_FUNC_DECL(RotatingFollowEntity) + { + auto rotating = reinterpret_cast(this); + auto data = GetExtraFuncRotatingData(rotating); + if (data->m_hRotateTarget == nullptr) + return; + + auto lookat = rotating->GetCustomVariable<"lookat">(); + Vector targetVec; + if (FStrEq(lookat, PStr<"origin">())) { + targetVec = data->m_hRotateTarget->GetAbsOrigin(); + } + else if (FStrEq(lookat, PStr<"center">())) { + targetVec = data->m_hRotateTarget->WorldSpaceCenter(); + } + else { + targetVec = data->m_hRotateTarget->EyePosition(); + } + + targetVec -= rotating->GetAbsOrigin(); + targetVec += data->m_hRotateTarget->GetAbsVelocity() * gpGlobals->frametime; + float projectileSpeed = rotating->GetCustomVariableFloat<"projectilespeed">(); + if (projectileSpeed != 0) { + targetVec += (targetVec.Length() / projectileSpeed) * data->m_hRotateTarget->GetAbsVelocity(); + } + + QAngle angToTarget; + VectorAngles(targetVec, angToTarget); + + float aimOffset = rotating->GetCustomVariableFloat<"aimoffset">(); + if (aimOffset != 0) { + angToTarget.x -= Vector(targetVec.x, targetVec.y, 0.0f).Length() * aimOffset; + } + + float angleDiff; + float angle = 0; + QAngle moveAngle = rotating->m_vecMoveAng; + QAngle angles = rotating->GetAbsAngles(); + + float limit = rotating->GetCustomVariableFloat<"limit">(); + if (limit != 0.0f) { + angToTarget.x = clamp(AngleNormalize(angToTarget.x), -limit, limit); + angToTarget.y = clamp(AngleNormalize(angToTarget.y), -limit, limit); + } + if (moveAngle == QAngle(1,0,0)) { + angleDiff = AngleDiff(angles.x, angToTarget.x); + angle = rotating->GetLocalAngles().x; + } + else { + angleDiff = AngleDiff(angles.y, angToTarget.y); + angle = rotating->GetLocalAngles().y; + } + + float speed = rotating->m_flMaxSpeed; + if (abs(angleDiff) < rotating->m_flMaxSpeed/66) { + speed = 0; + if (moveAngle == QAngle(1,0,0)) { + rotating->SetAbsAngles(QAngle(angToTarget.x, angles.y, angles.z)); + } + else { + rotating->SetAbsAngles(QAngle(angles.x, angToTarget.y, angles.z)); + } + } + + if (angleDiff > 0) { + speed *= -1.0f; + } + + if (speed != rotating->m_flTargetSpeed) { + rotating->m_bReversed = angleDiff > 0; + rotating->SetTargetSpeed(abs(speed)); + } + + rotating->SetNextThink(gpGlobals->curtime + 0.01f, "RotatingFollowEntity"); + } + + THINK_FUNC_DECL(RotatorModuleTick) + { + auto data = this->GetEntityModule("rotator"); + if (data == nullptr || data->m_hRotateTarget == nullptr) + return; + + auto lookat = this->GetCustomVariable<"lookat">("eyes"); + Vector targetVec; + auto aimEntity = data->m_hRotateTarget; + if (FStrEq(lookat, PStr<"origin">())) { + targetVec = data->m_hRotateTarget->GetAbsOrigin(); + } + else if (FStrEq(lookat, PStr<"center">())) { + targetVec = data->m_hRotateTarget->WorldSpaceCenter(); + } + else if (FStrEq(lookat, PStr<"aim">())) { + Vector fwd; + Vector dest; + AngleVectors(data->m_hRotateTarget->EyeAngles(), &fwd); + VectorMA(data->m_hRotateTarget->EyePosition(), 8192.0f, fwd, dest); + trace_t tr; + UTIL_TraceLine(data->m_hRotateTarget->EyePosition(), dest, MASK_SHOT, data->m_hRotateTarget, COLLISION_GROUP_NONE, &tr); + + targetVec = tr.endpos; + aimEntity = tr.m_pEnt; + } + else { + targetVec = data->m_hRotateTarget->EyePosition(); + } + + targetVec -= this->GetAbsOrigin(); + if (aimEntity != nullptr) { + targetVec += aimEntity->GetAbsVelocity() * gpGlobals->frametime; + } + float projectileSpeed = this->GetCustomVariableFloat<"projectilespeed">(); + if (projectileSpeed != 0) { + targetVec += (targetVec.Length() / projectileSpeed) * data->m_hRotateTarget->GetAbsVelocity(); + } + + QAngle angToTarget; + VectorAngles(targetVec, angToTarget); + + float aimOffset = this->GetCustomVariableFloat<"aimoffset">(); + if (aimOffset != 0) { + angToTarget.x -= Vector(targetVec.x, targetVec.y, 0.0f).Length() * aimOffset; + } + + QAngle angles = this->GetAbsAngles(); + + bool velocitymode = this->GetCustomVariableFloat<"velocitymode">(); + float speedx = this->GetCustomVariableFloat<"rotationspeedx">(); + float speedy = this->GetCustomVariableFloat<"rotationspeedy">(); + if (!velocitymode) { + speedx *= gpGlobals->frametime; + speedy *= gpGlobals->frametime; + } + angToTarget.x = ApproachAngle(angToTarget.x, angles.x, speedx); + angToTarget.y = ApproachAngle(angToTarget.y, angles.y, speedy); + + float limitx = this->GetCustomVariableFloat<"rotationlimitx">(); + if (limitx != 0.0f) { + angToTarget.x = clamp(AngleNormalize(angToTarget.x), -limitx, limitx); + } + + float limity = this->GetCustomVariableFloat<"rotationlimity">(); + if (limity != 0.0f) { + angToTarget.y = clamp(AngleNormalize(angToTarget.y), -limity, limity); + } + + if (!velocitymode) { + this->SetAbsAngles(QAngle(angToTarget.x, angToTarget.y, angles.z)); + } + else { + angToTarget = QAngle(angToTarget.x - angles.x, angToTarget.y - angles.y, 0.0f); + this->SetLocalAngularVelocity(angToTarget); + } + + this->SetNextThink(gpGlobals->curtime + 0.01f, "RotatorModuleTick"); + } + + THINK_FUNC_DECL(ForwardVelocityTick) + { + if (this->GetEntityModule("forwardvelocity") == nullptr) + return; + + Vector fwd; + AngleVectors(this->GetAbsAngles(), &fwd); + + fwd *= this->GetCustomVariableFloat<"forwardspeed">(); + + if (this->GetCustomVariableFloat<"directmode">() != 0) { + this->SetAbsOrigin(this->GetAbsOrigin() + fwd * gpGlobals->frametime); + } + else { + IPhysicsObject *pPhysicsObject = this->VPhysicsGetObject(); + if (pPhysicsObject) { + pPhysicsObject->SetVelocity(&fwd, nullptr); + } + else { + this->SetAbsVelocity(fwd); + } + } + this->SetNextThink(gpGlobals->curtime + 0.01f, "ForwardVelocityTick"); + } + + ForwardVelocityModule::ForwardVelocityModule(CBaseEntity *entity) + { + if (entity->GetNextThink("ForwardVelocityTick") < gpGlobals->curtime) { + THINK_FUNC_SET(entity, ForwardVelocityTick, gpGlobals->curtime + 0.01); + } + } + + THINK_FUNC_DECL(FakeParentModuleTick) + { + auto data = this->GetEntityModule("fakeparent"); + if (data == nullptr || data->m_hParent == nullptr) return; + + if (data->m_hParent == nullptr && data->m_bParentSet) { + variant_t variant; + this->FireCustomOutput<"onfakeparentkilled">(this, this, variant); + data->m_bParentSet = false; + } + + if (data->m_hParent == nullptr) return; + + Vector pos; + QAngle ang; + CBaseEntity *parent =data->m_hParent; + + auto bone = this->GetCustomVariable<"bone">(); + auto attachment = this->GetCustomVariable<"attachment">(); + bool posonly = this->GetCustomVariableFloat<"positiononly">(); + bool rotationonly = this->GetCustomVariableFloat<"rotationonly">(); + Vector offset = this->GetCustomVariableVector<"fakeparentoffset">(); + QAngle offsetangle = this->GetCustomVariableAngle<"fakeparentrotation">(); + matrix3x4_t transform; + + if (bone != nullptr) { + CBaseAnimating *anim = rtti_cast(parent); + anim->GetBoneTransform(anim->LookupBone(bone), transform); + } + else if (attachment != nullptr){ + CBaseAnimating *anim = rtti_cast(parent); + anim->GetAttachment(anim->LookupAttachment(attachment), transform); + } + else{ + transform = parent->EntityToWorldTransform(); + } + + if (!rotationonly) { + VectorTransform(offset, transform, pos); + this->SetAbsOrigin(pos); + } + + if (!posonly) { + MatrixAngles(transform, ang); + ang += offsetangle; + this->SetAbsAngles(ang); + } + + this->SetNextThink(gpGlobals->curtime + 0.01f, "FakeParentModuleTick"); + } + + THINK_FUNC_DECL(AimFollowModuleTick) + { + auto data = this->GetEntityModule("aimfollow"); + if (data == nullptr || data->m_hParent == nullptr) return; + + + Vector fwd; + Vector dest; + AngleVectors(data->m_hParent->EyeAngles(), &fwd); + VectorMA(data->m_hParent->EyePosition(), 8192.0f, fwd, dest); + trace_t tr; + CTraceFilterSkipTwoEntities filter(data->m_hParent, this, COLLISION_GROUP_NONE); + UTIL_TraceLine(data->m_hParent->EyePosition(), dest, MASK_SHOT, &filter, &tr); + + Vector targetVec = tr.endpos; + + this->SetAbsOrigin(targetVec); + bool rotationfollow = this->GetCustomVariableFloat<"rotationfollow">(); + if (rotationfollow) { + this->SetAbsAngles(data->m_hParent->EyeAngles()); + } + this->SetNextThink(gpGlobals->curtime + 0.01f, "AimFollowModuleTick"); + } + + THINK_FUNC_DECL(ScriptModuleTick) + { + auto data = this->GetEntityModule("script"); + if (data == nullptr) return; + data->UpdateTimers(); + + if (data->HasTimers()) { + this->SetNextThink(gpGlobals->curtime + 0.01f, "ScriptModuleTick"); + } + + } + + void ScriptModule::TimerAdded() { + THINK_FUNC_SET(owner, ScriptModuleTick, gpGlobals->curtime); + } + + void FireGetInput(CBaseEntity *entity, GetInputType type, const char *name, CBaseEntity *activator, CBaseEntity *caller, variant_t &value) { + char param_tokenized[256] = ""; + V_strncpy(param_tokenized, value.String(), sizeof(param_tokenized)); + char *targetstr = strtok(param_tokenized,"|"); + char *action = strtok(NULL,"|"); + char *defvalue = strtok(NULL,"|"); + + variant_t variable; + + if (targetstr != nullptr && action != nullptr) { + + bool found = GetEntityVariable(entity, type, name, variable); + + if (!found && defvalue != nullptr) { + variable.SetString(AllocPooledString(defvalue)); + } + + if (found || defvalue != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, entity, activator, caller)) != nullptr ;) { + target->AcceptInput(action, activator, entity, variable, 0); + } + } + } + } + + ClassnameFilter logic_case_filter("logic_case", { + {"FormatInputNoFire"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + int num = strtol(szInputName + strlen("FormatInputNoFire"), nullptr, 10); + if (num > 0 && num < 16) { + logic_case->m_nCase[num - 1] = AllocPooledString(Value.String()); + } + }}, + {"FormatInput"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + int num = strtol(szInputName + strlen("FormatInput"), nullptr, 10); + if (num > 0 && num < 16) { + logic_case->m_nCase[num - 1] = AllocPooledString(Value.String()); + FireFormatInput(logic_case, pActivator, pCaller); + } + }}, + {"FormatString"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + logic_case->m_nCase[15] = AllocPooledString(Value.String()); + FireFormatInput(logic_case, pActivator, pCaller); + }}, + {"FormatStringNoFire"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + logic_case->m_nCase[15] = AllocPooledString(Value.String()); + }}, + {"Format"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + FireFormatInput(logic_case, pActivator, pCaller); + }}, + {"TestSigsegv"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->m_OnUser1->FireOutput(Value, pActivator, pCaller); + }}, + {"ToInt"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + variant_t convert; + convert.SetInt(strtol(Value.String(), nullptr, 10)); + logic_case->m_OnDefault->FireOutput(convert, pActivator, ent); + }}, + {"ToFloat"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + variant_t convert; + convert.SetFloat(strtof(Value.String(), nullptr)); + logic_case->m_OnDefault->FireOutput(convert, pActivator, ent); + }}, + {"CallerToActivator"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + logic_case->m_OnDefault->FireOutput(Value, pCaller, ent); + }}, + {"GetKeyValueFromActivator"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + variant_t variant; + pActivator->ReadKeyField(Value.String(), &variant); + logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); + }}, + {"GetConVar"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + ConVarRef cvref(Value.String()); + if (cvref.IsValid() && cvref.IsFlagSet(FCVAR_REPLICATED) && !cvref.IsFlagSet(FCVAR_PROTECTED)) { + variant_t variant; + variant.SetFloat(cvref.GetFloat()); + logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); + } + }}, + {"GetConVarString"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + ConVarRef cvref(Value.String()); + if (cvref.IsValid() && cvref.IsFlagSet(FCVAR_REPLICATED) && !cvref.IsFlagSet(FCVAR_PROTECTED)) { + variant_t variant; + variant.SetString(AllocPooledString(cvref.GetString())); + logic_case->m_OnDefault->FireOutput(variant, pActivator, ent); + } + }}, + {"DisplayMenu"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { + if (target != nullptr && target->IsPlayer() && !ToTFPlayer(target)->IsBot()) { + CaseMenuHandler *handler = new CaseMenuHandler(ToTFPlayer(target), logic_case); + IBaseMenu *menu = menus->GetDefaultStyle()->CreateMenu(handler); + + int i; + for (i = 1; i < 16; i++) { + string_t str = logic_case->m_nCase[i - 1]; + const char *name = STRING(str); + if (strlen(name) != 0) { + bool enabled = name[0] != '!'; + ItemDrawInfo info1(enabled ? name : name + 1, enabled ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu->AppendItem("it", info1); + } + else { + break; + } + } + if (i < 11) { + menu->SetPagination(MENU_NO_PAGINATION); + } + + variant_t variant; + ent->ReadKeyField("Case16", &variant); + + char param_tokenized[256]; + V_strncpy(param_tokenized, variant.String(), sizeof(param_tokenized)); + + char *name = strtok(param_tokenized,"|"); + char *timeout = strtok(NULL,"|"); + + menu->SetDefaultTitle(name); + + char *flag; + while ((flag = strtok(NULL,"|")) != nullptr) { + if (FStrEq(flag, "Cancel")) { + menu->SetMenuOptionFlags(menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXIT); + } + } + + menu->Display(ENTINDEX(target), timeout == nullptr ? 0 : atoi(timeout)); + } + } + }}, + {"HideMenu"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto target = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr && target->IsPlayer()) { + menus->GetDefaultStyle()->CancelClientMenu(ENTINDEX(target), false); + } + }}, + {"BitTest"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + Value.Convert(FIELD_INTEGER); + int val = Value.Int(); + for (int i = 1; i <= 16; i++) { + string_t str = logic_case->m_nCase[i - 1]; + + if (val & atoi(STRING(str))) { + logic_case->FireCase(i, pActivator); + } + else { + break; + } + } + }}, + {"BitTestAll"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CLogicCase *logic_case = static_cast(ent); + Value.Convert(FIELD_INTEGER); + int val = Value.Int(); + for (int i = 1; i <= 16; i++) { + string_t str = logic_case->m_nCase[i - 1]; + + int test = atoi(STRING(str)); + if ((val & test) == test) { + logic_case->FireCase(i, pActivator); + } + else { + break; + } + } + }} + }); + ClassnameFilter tf_gamerules_filter("tf_gamerules", { + {"StopVO"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + TFGameRules()->BroadcastSound(SOUND_FROM_LOCAL_PLAYER, Value.String(), SND_STOP); + }}, + {"StopVORed"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + TFGameRules()->BroadcastSound(TF_TEAM_RED, Value.String(), SND_STOP); + }}, + {"StopVOBlue"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + TFGameRules()->BroadcastSound(TF_TEAM_BLUE, Value.String(), SND_STOP); + }}, + {"SetBossHealthPercentage"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_FLOAT); + float val = Value.Float(); + if (g_pMonsterResource.GetRef() != nullptr) + g_pMonsterResource->m_iBossHealthPercentageByte = (int) (val * 255.0f); + }}, + {"SetBossState"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_INTEGER); + int val = Value.Int(); + if (g_pMonsterResource.GetRef() != nullptr) + g_pMonsterResource->m_iBossState = val; + }}, + {"AddCurrencyGlobal"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_INTEGER); + int val = Value.Int(); + TFGameRules()->DistributeCurrencyAmount(val, nullptr, true, true, false); + }} + }); + ClassnameFilter player_filter("player", { + {"AllowClassAnimations"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + player->GetPlayerClass()->m_bUseClassAnimations = Value.Bool(); + } + }}, + {"SwitchClass"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + + if (player != nullptr) { + // Disable setup to allow class changing during waves in mvm + static ConVarRef endless("tf_mvm_endless_force_on"); + endless.SetValue(true); + + int index = strtol(Value.String(), nullptr, 10); + if (index > 0 && index < 10) { + player->HandleCommand_JoinClass(g_aRawPlayerClassNames[index]); + } + else { + player->HandleCommand_JoinClass(Value.String()); + } + endless.SetValue(false); + } + }}, + {"SwitchClassInPlace"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + + if (player != nullptr) { + // Disable setup to allow class changing during waves in mvm + static ConVarRef endless("tf_mvm_endless_force_on"); + endless.SetValue(true); + + Vector pos = player->GetAbsOrigin(); + QAngle ang = player->GetAbsAngles(); + Vector vel = player->GetAbsVelocity(); + + int index = strtol(Value.String(), nullptr, 10); + int oldState = player->m_Shared->GetState(); + player->m_Shared->SetState(TF_STATE_DYING); + if (index > 0 && index < 10) { + player->HandleCommand_JoinClass(g_aRawPlayerClassNames[index]); + } + else { + player->HandleCommand_JoinClass(Value.String()); + } + player->m_Shared->SetState(oldState); + endless.SetValue(false); + player->ForceRespawn(); + player->Teleport(&pos, &ang, &vel); + + } + }}, + {"ForceRespawn"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + if (player->GetTeamNumber() >= TF_TEAM_RED && player->GetPlayerClass() != nullptr && player->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED) { + player->ForceRespawn(); + } + else { + player->m_bAllowInstantSpawn = true; + } + } + }}, + {"ForceRespawnDead"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr && !player->IsAlive()) { + if (player->GetTeamNumber() >= TF_TEAM_RED && player->GetPlayerClass() != nullptr && player->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED) { + player->ForceRespawn(); + } + else { + player->m_bAllowInstantSpawn = true; + } + } + }}, + {"DisplayTextCenter"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + using namespace std::string_literals; + std::string text{Value.String()}; + text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); + text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); + text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, + (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); + gamehelpers->TextMsg(ENTINDEX(ent), TEXTMSG_DEST_CENTER, text.c_str()); + }}, + {"DisplayTextChat"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + using namespace std::string_literals; + std::string text{"\x01"s + Value.String() + "\x01"s}; + text = std::regex_replace(text, std::regex{"\\{reset\\}", std::regex_constants::icase}, "\x01"); + text = std::regex_replace(text, std::regex{"\\{blue\\}", std::regex_constants::icase}, "\x07" "99ccff"); + text = std::regex_replace(text, std::regex{"\\{red\\}", std::regex_constants::icase}, "\x07" "ff3f3f"); + text = std::regex_replace(text, std::regex{"\\{green\\}", std::regex_constants::icase}, "\x07" "99ff99"); + text = std::regex_replace(text, std::regex{"\\{darkgreen\\}", std::regex_constants::icase}, "\x07" "40ff40"); + text = std::regex_replace(text, std::regex{"\\{yellow\\}", std::regex_constants::icase}, "\x07" "ffb200"); + text = std::regex_replace(text, std::regex{"\\{grey\\}", std::regex_constants::icase}, "\x07" "cccccc"); + text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); + text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); + text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, + (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); + auto pos{text.find("{")}; + while(pos != std::string::npos){ + if(text.substr(pos).length() > 7){ + text[pos] = '\x07'; + text.erase(pos+7, 1); + pos = text.find("{"); + } else break; + } + gamehelpers->TextMsg(ENTINDEX(ent), TEXTMSG_DEST_CHAT , text.c_str()); + }}, + {"DisplayTextHint"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + using namespace std::string_literals; + std::string text{Value.String()}; + text = std::regex_replace(text, std::regex{"\\{newline\\}", std::regex_constants::icase}, "\n"); + text = std::regex_replace(text, std::regex{"\\{player\\}", std::regex_constants::icase}, ToTFPlayer(ent)->GetPlayerName()); + text = std::regex_replace(text, std::regex{"\\{activator\\}", std::regex_constants::icase}, + (pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : ToTFPlayer(ent))->GetPlayerName()); + gamehelpers->HintTextMsg(ENTINDEX(ent), text.c_str()); + }}, + {"Suicide"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + player->CommitSuicide(false, true); + } + }}, + {"ChangeAttributes"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFBot *bot = ToTFBot(ent); + if (bot != nullptr) { + auto *attrib = bot->GetEventChangeAttributes(Value.String()); + if (attrib != nullptr){ + bot->OnEventChangeAttributes(attrib); + } + } + }}, + {"RollCommonSpell"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); + + if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return; + + CTFSpellBook *spellbook = rtti_cast(weapon); + spellbook->RollNewSpell(0); + }}, + {"SetSpell"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); + + if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return; + + const char *str = Value.String(); + int index = strtol(str, nullptr, 10); + for (size_t i = 0; i < ARRAYSIZE(SPELL_TYPE); i++) { + if (FStrEq(str, SPELL_TYPE[i])) { + index = i; + } + } + + CTFSpellBook *spellbook = rtti_cast(weapon); + spellbook->m_iSelectedSpellIndex = index; + spellbook->m_iSpellCharges = 1; + }}, + {"AddSpell"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + + CBaseEntity *weapon = player->GetEntityForLoadoutSlot(LOADOUT_POSITION_ACTION); + + if (weapon == nullptr || !FStrEq(weapon->GetClassname(), "tf_weapon_spellbook")) return; + + const char *str = Value.String(); + int index = strtol(str, nullptr, 10); + for (size_t i = 0; i < ARRAYSIZE(SPELL_TYPE); i++) { + if (FStrEq(str, SPELL_TYPE[i])) { + index = i; + } + } + + CTFSpellBook *spellbook = rtti_cast(weapon); + if (spellbook->m_iSelectedSpellIndex != index) { + spellbook->m_iSpellCharges = 0; + } + spellbook->m_iSelectedSpellIndex = index; + spellbook->m_iSpellCharges += 1; + }}, + {"AddCond"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + int index = 0; + float duration = -1.0f; + sscanf(Value.String(), "%d %f", &index, &duration); + if (player != nullptr) { + player->m_Shared->AddCond((ETFCond)index, duration); + } + }}, + {"RemoveCond"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + int index = strtol(Value.String(), nullptr, 10); + if (player != nullptr) { + player->m_Shared->RemoveCond((ETFCond)index); + } + }}, + {"AddPlayerAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + char param_tokenized[256]; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + + char *attr = strtok(param_tokenized,"|"); + char *value = strtok(NULL,"|"); + + if (player != nullptr && value != nullptr) { + player->AddCustomAttribute(attr, atof(value), -1.0f); + } + }}, + {"RemovePlayerAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + player->RemoveCustomAttribute(Value.String()); + } + }}, + {"GetPlayerAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + char param_tokenized[256] = ""; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + char *attrName = strtok(param_tokenized,"|"); + char *targetstr = strtok(NULL,"|"); + char *action = strtok(NULL,"|"); + char *defvalue = strtok(NULL,"|"); + CEconItemAttribute * attr = player->GetAttributeList()->GetAttributeByName(attrName); + variant_t variable; + bool found = false; + if (attr != nullptr) { + char buf[256]; + attr->GetStaticData()->ConvertValueToString(*attr->GetValuePtr(), buf, sizeof(buf)); + variable.SetString(AllocPooledString(buf)); + found = true; + } + else { + variable.SetString(AllocPooledString(defvalue)); + } + + if (targetstr != nullptr && action != nullptr) { + if (found || defvalue != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { + target->AcceptInput(action, pActivator, ent, variable, 0); + } + } + } + } + }}, + {"GetItemAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + if (player != nullptr) { + char param_tokenized[256] = ""; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + char *itemSlot = strtok(param_tokenized,"|"); + char *attrName = strtok(NULL,"|"); + char *targetstr = strtok(NULL,"|"); + char *action = strtok(NULL,"|"); + char *defvalue = strtok(NULL,"|"); + + bool found = false; + + variant_t variable; + if (itemSlot != nullptr && attrName != nullptr) { + int slot = 0; + CEconEntity *item = nullptr; + if (StringToIntStrict(itemSlot, slot)) { + if (slot != -1) { + item = GetEconEntityAtLoadoutSlot(player, slot); + } + else { + item = player->GetActiveTFWeapon(); + } + } + else { + ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ + if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), itemSlot)) { + item = entity; + } + }); + } + + if (item != nullptr) { + CEconItemAttribute * attr = item->GetItem()->GetAttributeList().GetAttributeByName(attrName); + if (attr != nullptr) { + char buf[256]; + attr->GetStaticData()->ConvertValueToString(*attr->GetValuePtr(), buf, sizeof(buf)); + variable.SetString(AllocPooledString(buf)); + found = true; + } + } + } + + if (!found) { + variable.SetString(AllocPooledString(defvalue)); + } + + if (targetstr != nullptr && action != nullptr) { + if (found || defvalue != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { + target->AcceptInput(action, pActivator, ent, variable, 0); + } + } + } + } + }}, + {"AddItemAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + char param_tokenized[256]; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + + char *attr = strtok(param_tokenized,"|"); + char *value = strtok(NULL,"|"); + char *slot = strtok(NULL,"|"); + + if (player != nullptr && value != nullptr) { + CEconEntity *item = nullptr; + if (slot != nullptr) { + ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ + if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), slot)) { + item = entity; + } + }); + if (item == nullptr) + item = GetEconEntityAtLoadoutSlot(player, atoi(slot)); + } + else { + item = player->GetActiveTFWeapon(); + } + if (item != nullptr) { + CEconItemAttributeDefinition *attr_def = GetItemSchema()->GetAttributeDefinitionByName(attr); + if (attr_def == nullptr) { + int idx = -1; + if (StringToIntStrict(attr, idx)) { + attr_def = GetItemSchema()->GetAttributeDefinition(idx); + } + } + item->GetItem()->GetAttributeList().AddStringAttribute(attr_def, value); + + } + } + }}, + {"RemoveItemAttribute"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + char param_tokenized[256]; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + + char *attr = strtok(param_tokenized,"|"); + char *slot = strtok(NULL,"|"); + + if (player != nullptr) { + CEconEntity *item = nullptr; + if (slot != nullptr) { + ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ + if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), Value.String())) { + item = entity; + } + }); + if (item == nullptr) + item = GetEconEntityAtLoadoutSlot(player, atoi(slot)); + } + else { + item = player->GetActiveTFWeapon(); + } + if (item != nullptr) { + CEconItemAttributeDefinition *attr_def = GetItemSchema()->GetAttributeDefinitionByName(attr); + if (attr_def == nullptr) { + int idx = -1; + if (StringToIntStrict(attr, idx)) { + attr_def = GetItemSchema()->GetAttributeDefinition(idx); + } + } + item->GetItem()->GetAttributeList().RemoveAttribute(attr_def); + + } + } + }}, + {"PlaySoundToSelf"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + CRecipientFilter filter; + filter.AddRecipient(player); + + if (!enginesound->PrecacheSound(Value.String(), true)) + CBaseEntity::PrecacheScriptSound(Value.String()); + + EmitSound_t params; + params.m_pSoundName = Value.String(); + params.m_flSoundTime = 0.0f; + params.m_pflSoundDuration = nullptr; + params.m_bWarnOnDirectWaveReference = true; + CBaseEntity::EmitSound(filter, ENTINDEX(player), params); + }}, + {"IgnitePlayerDuration"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + Value.Convert(FIELD_FLOAT); + CTFPlayer *activator = pActivator != nullptr && pActivator->IsPlayer() ? ToTFPlayer(pActivator) : player; + player->m_Shared->Burn(activator, nullptr, Value.Float()); + }}, + {"WeaponSwitchSlot"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + Value.Convert(FIELD_INTEGER); + player->Weapon_Switch(player->Weapon_GetSlot(Value.Int())); + }}, + {"WeaponStripSlot"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + Value.Convert(FIELD_INTEGER); + int slot = Value.Int(); + CBaseCombatWeapon *weapon = player->GetActiveTFWeapon(); + if (slot != -1) { + weapon = player->Weapon_GetSlot(slot); + } + if (weapon != nullptr) + weapon->Remove(); + }}, + {"RemoveItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ + if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), Value.String())) { + if (entity->MyCombatWeaponPointer() != nullptr) { + player->Weapon_Detach(entity->MyCombatWeaponPointer()); + } + entity->Remove(); + } + }); + }}, + {"GiveItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + GiveItemByName(player, Value.String()); + }}, + {"DropItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + Value.Convert(FIELD_INTEGER); + int slot = Value.Int(); + CBaseCombatWeapon *weapon = player->GetActiveTFWeapon(); + if (slot != -1) { + weapon = player->Weapon_GetSlot(slot); + } + + if (weapon != nullptr) { + CEconItemView *item_view = weapon->GetItem(); + + allow_create_dropped_weapon = true; + auto dropped = CTFDroppedWeapon::Create(player, player->EyePosition(), vec3_angle, weapon->GetWorldModel(), item_view); + if (dropped != nullptr) + dropped->InitDroppedWeapon(player, static_cast(weapon), false, false); + + allow_create_dropped_weapon = false; + } + }}, + {"SetCurrency"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + player->RemoveCurrency(player->GetCurrency() - atoi(Value.String())); + }}, + {"AddCurrency"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + player->RemoveCurrency(atoi(Value.String()) * -1); + }}, + {"RemoveCurrency"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + player->RemoveCurrency(atoi(Value.String())); + }}, + {"CurrencyOutput"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + int cost = atoi(szInputName + 15); + if(player->GetCurrency() >= cost){ + char param_tokenized[2048] = ""; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + const char *separator = strchr(param_tokenized, ',') != nullptr ? "," : "|"; + if(strcmp(param_tokenized, "") != 0){ + char *target = strtok(param_tokenized,separator); + char *action = NULL; + char *value = NULL; + if(target != NULL) + action = strtok(NULL,separator); + if(action != NULL) + value = strtok(NULL,separator); + if(value != NULL){ + CEventQueue &que = g_EventQueue; + variant_t actualvalue; + string_t stringvalue = AllocPooledString(value); + actualvalue.SetString(stringvalue); + que.AddEvent(STRING(AllocPooledString(target)), STRING(AllocPooledString(action)), actualvalue, 0.0, player, player, -1); + } + } + player->RemoveCurrency(cost); + } + }}, + {"CurrencyInvertOutput"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer *player = ToTFPlayer(ent); + int cost = atoi(szInputName + 21); + if(player->GetCurrency() < cost){ + char param_tokenized[2048] = ""; + const char *separator = strchr(param_tokenized, ',') != nullptr ? "," : "|"; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + if(strcmp(param_tokenized, "") != 0){ + char *target = strtok(param_tokenized,separator); + char *action = NULL; + char *value = NULL; + if(target != NULL) + action = strtok(NULL,separator); + if(action != NULL) + value = strtok(NULL,separator); + if(value != NULL){ + CEventQueue &que = g_EventQueue; + variant_t actualvalue; + string_t stringvalue = AllocPooledString(value); + actualvalue.SetString(stringvalue); + que.AddEvent(STRING(AllocPooledString(target)), STRING(AllocPooledString(action)), actualvalue, 0.0, player, player, -1); + } + } + //player->RemoveCurrency(cost); + } + }}, + {"RefillAmmo"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + for(int i = 0; i < 7; ++i){ + player->SetAmmoCount(player->GetMaxAmmo(i), i); + } + }}, + {"Regenerate"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + player->Regenerate(true); + }}, + {"BotCommand"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFBot* bot = ToTFBot(ent); + if (bot != nullptr) + bot->MyNextBotPointer()->OnCommandString(Value.String()); + }}, + {"ResetInventory"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + + player->GiveDefaultItemsNoAmmo(); + }}, + {"PlaySequence"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + player->PlaySpecificSequence(Value.String()); + }}, + {"AwardExtraItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + std::string str = Value.String(); + Mod::Pop::PopMgr_Extensions::AwardExtraItem(player, str); + }}, + +#ifdef GCC11 + {"TauntFromItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + + CTFPlayer* player{ToTFPlayer(ent)}; + auto view{CEconItemView::Create()}; + const std::string_view input{Value.String()}; + auto index{vi::from_str(Value.String())}; + const auto v{vi::split_str(input, "|")}; + const auto do_taunt{[&view, &player](int index) -> void { + view->Init(index); + player->PlayTauntSceneFromItem(view); + CEconItemView::Destroy(view); + }}; + if(index && (v.size() < 2)){ + do_taunt(*index); + } else { + if(v.size() > 1){ + index = {vi::from_str(v[0])}; + const auto value{v[1]}; + if(index && (value.length() > 0)){ + std::string_view no_op{value}; + no_op.remove_prefix(1); + const auto remove_op{vi::from_str(no_op)}; + auto original{vi::from_str(value)}; + + const std::unordered_map> ops{ + {'+', [&player, &remove_op]{ + player->m_flTauntAttackTime += *remove_op; + } + }, + {'-', [&player, &remove_op]{ + player->m_flTauntAttackTime -= *remove_op; + } + }, + {'*', [&player, &remove_op]{ + player->m_flTauntAttackTime = + gpGlobals->curtime + + (player->m_flTauntAttackTime - + gpGlobals->curtime) * + *remove_op; + } + } + }; + if(value[0] == 'i'){ + do_taunt(*index); + player->m_flTauntAttackTime = 0.1f; + original = {}; + } + for(const auto& [op, func] : ops){ + if((value[0] == op) && remove_op){ + do_taunt(*index); + func(); + original = {}; + break; + } + } + if(original){ + do_taunt(*index); + player->m_flTauntAttackTime = + gpGlobals->curtime + *original; + } + } + } + } + + }}, + {"TauntIndexConcept"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player{ToTFPlayer(ent)}; + const std::string_view input{Value.String()}; + auto v{vi::split_str(input, "|")}; + if(v.size() > 1){ + auto index{ + vi::from_str(v[0]) + }; + auto taunt_concept{ + vi::from_str(v[1]) + }; + if(index && taunt_concept) + player->Taunt(*index, *taunt_concept); + } + + }}, +#endif + {"StripExtraItem"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CTFPlayer* player = ToTFPlayer(ent); + std::string str = Value.String(); + Mod::Pop::PopMgr_Extensions::StripExtraItem(player, str); + }} + }); + + ClassnameFilter point_viewcontrol_filter("point_viewcontrol", { + {"EnableAll"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto camera = static_cast(ent); + ForEachTFPlayer([&](CTFPlayer *player) { + if (player->IsBot()) + return; + else { + + camera->m_hPlayer = player; + camera->Enable(); + camera->m_spawnflags |= 512; + } + }); + }}, + {"DisableAll"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto camera = static_cast(ent); + ForEachTFPlayer([&](CTFPlayer *player) { + if (player->IsBot()) + return; + else { + camera->m_hPlayer = player; + camera->Disable(); + player->m_takedamage = player->IsObserver() ? 0 : 2; + camera->m_spawnflags &= ~(512); + } + }); + }}, + {"SetTarget"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto camera = static_cast(ent); + camera->m_hTarget = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + }} + }); + + ClassnameFilter trigger_detector_filter("$trigger_detector", { + {"targettest"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto data = GetExtraTriggerDetectorData(ent); + if (data->m_hLastTarget != nullptr) { + ent->FireCustomOutput<"targettestpass">(data->m_hLastTarget, ent, Value); + } + else { + ent->FireCustomOutput<"targettestfail">(nullptr, ent, Value); + } + }} + }); + + ClassnameFilter weapon_spawner_filter("$weapon_spawner", { + {"DropWeapon"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto data = GetExtraData(ent); + auto name = ent->GetCustomVariable<"item">(); + auto item_def = GetItemSchema()->GetItemDefinitionByName(name); + + if (item_def != nullptr) { + auto item = CEconItemView::Create(); + item->Init(item_def->m_iItemDefIndex, item_def->m_iItemQuality, 9999, 0); + item->m_iItemID = (RandomInt(INT_MIN, INT_MAX) << 16) + ENTINDEX(ent); + Mod::Pop::PopMgr_Extensions::AddCustomWeaponAttributes(name, item); + auto &vars = GetCustomVariables(ent); + for (auto &var : vars) { + auto attr_def = GetItemSchema()->GetAttributeDefinitionByName(STRING(var.key)); + if (attr_def != nullptr) { + item->GetAttributeList().AddStringAttribute(attr_def, var.value.String()); + } + } + auto weapon = CTFDroppedWeapon::Create(nullptr, ent->EyePosition(), vec3_angle, item->GetPlayerDisplayModel(1, 2), item); + if (weapon != nullptr) { + if (weapon->VPhysicsGetObject() != nullptr) { + weapon->VPhysicsGetObject()->SetMass(25.0f); + + if (ent->GetCustomVariableFloat<"nomotion">() != 0) { + weapon->VPhysicsGetObject()->EnableMotion(false); + } + } + auto weapondata = weapon->GetOrCreateEntityModule("droppedweapon"); + weapondata->m_hWeaponSpawner = ent; + weapondata->ammo = ent->GetCustomVariableFloat<"ammo">(-1); + weapondata->clip = ent->GetCustomVariableFloat<"clip">(-1); + weapondata->energy = ent->GetCustomVariableFloat<"energy">(FLT_MIN); + weapondata->charge = ent->GetCustomVariableFloat<"charge">(FLT_MAX); + + data->m_SpawnedWeapons.push_back(weapon); + } + CEconItemView::Destroy(item); + } + }}, + {"RemoveDroppedWeapons"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + + auto data = GetExtraWeaponSpawnerData(ent); + for (auto weapon : data->m_SpawnedWeapons) { + if (weapon != nullptr) { + weapon->Remove(); + } + } + data->m_SpawnedWeapons.clear(); + }} + }); + ClassnameFilter prop_vehicle_driveable_filter("prop_vehicle_driveable", { + {"EnterVehicle"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + auto vehicle = rtti_cast(ent); + if (ToTFPlayer(target) != nullptr && vehicle != nullptr) { + + Vector delta = target->GetAbsOrigin() - ent->GetAbsOrigin(); + + QAngle angToTarget; + VectorAngles(delta, angToTarget); + ToTFPlayer(target)->SnapEyeAngles(angToTarget); + + CBaseServerVehicle *serverVehicle = vehicle->m_pServerVehicle; + serverVehicle->HandlePassengerEntry(ToTFPlayer(target), true); + } + }}, + {"ExitVehicle"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto vehicle = rtti_cast(ent); + if (vehicle != nullptr && vehicle->m_hPlayer != nullptr) { + CBaseServerVehicle *serverVehicle = vehicle->m_pServerVehicle; + serverVehicle->HandlePassengerExit(vehicle->m_hPlayer); + } + }} + }); + ClassnameFilter script_manager_filter("$script_manager", { + {"ExecuteScript"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto scriptManager = ent->GetOrCreateEntityModule("script"); + scriptManager->DoString(Value.String(), true); + scriptManager->popInit = true; + }}, + {"ExecuteScriptFile"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto scriptManager = ent->GetOrCreateEntityModule("script"); + scriptManager->DoFile(Value.String(), true); + scriptManager->popInit = true; + }}, + {"Reset"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->AddEntityModule( "script", new ScriptModule(ent)); + }}, + {""sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto scriptManager = ent->GetOrCreateEntityModule("script"); + auto state = scriptManager->GetState(); + lua_getglobal(state, szInputName); + Util::Lua::LFromVariant(state, Value); + Util::Lua::LEntityAlloc(state, pActivator); + Util::Lua::LEntityAlloc(state, pCaller); + scriptManager->Call(3, 0); + lua_settop(state, 0); + }} + }); + ClassnameFilter math_vector_filter("$math_vector", { + {"Set"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + ent->SetCustomVariable("value", Value); + }}, + {"Add"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + vec += vecValue; + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"AddScalar"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_FLOAT); + Vector vec = vecValue + Vector(Value.Float(), Value.Float(), Value.Float()); + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"Subtract"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + vec -= vecValue; + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"SubtractScalar"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_FLOAT); + Vector vec = vecValue - Vector(Value.Float(), Value.Float(), Value.Float()); + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"Multiply"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + vec *= vecValue; + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"MultiplyScalar"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_FLOAT); + Vector vec = vecValue * Value.Float(); + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"Divide"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + vec /= vecValue; + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"DivideScalar"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_FLOAT); + Vector vec = vecValue / Value.Float(); + Value.SetVector3D(vec); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"DotProduct"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + if (Value.Convert(FIELD_VECTOR)) { + Vector vec; + Value.Vector3D(vec); + Value.SetFloat(DotProduct(vec, vecValue)); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + } + }}, + {"CrossProduct"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + if (Value.Convert(FIELD_VECTOR)) { + Vector vec; + Value.Vector3D(vec); + Vector out; + CrossProduct(vec, vecValue, out); + Value.SetVector3D(out); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + } + }}, + {"Distance"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + if (Value.Convert(FIELD_VECTOR)) { + Vector vec; + Value.Vector3D(vec); + Value.SetFloat(vec.DistTo(vecValue)); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + } + }}, + {"DistanceToEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + auto target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + Value.SetFloat(vecValue.DistTo(target->GetAbsOrigin())); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + } + }}, + {"DistanceToEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.Convert(FIELD_VECTOR); + QAngle ang; + Value.Vector3D(*reinterpret_cast(&ang)); + Vector out; + VectorRotate(vecValue, ang, out); + Value.SetVector3D(out); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"DistanceToEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.SetFloat(vecValue.Length()); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"DistanceToEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + QAngle out; + VectorAngles(vecValue, out); + Value.SetVector3D(*reinterpret_cast(&out)); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"ToForwardVector"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Vector out; + AngleVectors(*reinterpret_cast(&vecValue), &out); + Value.SetVector3D(out); + ent->FireCustomOutput<"outvalue">(pActivator, ent, Value); + }}, + {"GetX"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.SetFloat(vecValue.x); + ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); + }}, + {"GetY"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.SetFloat(vecValue.y); + ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); + }}, + {"GetZ"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mathVector = ent->GetOrCreateEntityModule("math_vector"); + auto vecValue = ent->GetCustomVariableVector<"value">(); + Value.SetFloat(vecValue.z); + ent->FireCustomOutput<"getvalue">(pActivator, ent, Value); + }} + }); + + InputFilter input_filter({ + {"FireUserAsActivator1"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->m_OnUser1->FireOutput(Value, ent, ent); + }}, + {"FireUserAsActivator2"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->m_OnUser2->FireOutput(Value, ent, ent); + }}, + {"FireUserAsActivator3"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->m_OnUser3->FireOutput(Value, ent, ent); + }}, + {"FireUserAsActivator4"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->m_OnUser4->FireOutput(Value, ent, ent); + }}, + {"FireUser5"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->FireCustomOutput<"onuser5">(pActivator, ent, Value); + }}, + {"FireUser6"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->FireCustomOutput<"onuser6">(pActivator, ent, Value); + }}, + {"FireUser7"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->FireCustomOutput<"onuser7">(pActivator, ent, Value); + }}, + {"FireUser8"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->FireCustomOutput<"onuser8">(pActivator, ent, Value); + }}, + {"TakeDamage"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_INTEGER); + int damage = Value.Int(); + CBaseEntity *attacker = ent; + + CTakeDamageInfo info(attacker, attacker, nullptr, vec3_origin, ent->GetAbsOrigin(), damage, DMG_PREVENT_PHYSICS_FORCE, 0 ); + ent->TakeDamage(info); + }}, + {"AddHealth"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_INTEGER); + CBaseEntity *attacker = ent; + ent->TakeHealth(Value.Int(), DMG_GENERIC); + }}, + {"TakeDamageFromActivator"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_INTEGER); + int damage = Value.Int(); + CBaseEntity *attacker = pActivator; + + CTakeDamageInfo info(attacker, attacker, nullptr, vec3_origin, ent->GetAbsOrigin(), damage, DMG_PREVENT_PHYSICS_FORCE, 0 ); + ent->TakeDamage(info); + }}, + {"SetModelOverride"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + int replace_model = CBaseEntity::PrecacheModel(Value.String()); + if (replace_model != -1) { + for (int i = 0; i < MAX_VISION_MODES; ++i) { + ent->SetModelIndexOverride(i, replace_model); + } + } + }}, + {"SetModel"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CBaseEntity::PrecacheModel(Value.String()); + ent->SetModel(Value.String()); + }}, + {"SetModelSpecial"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + int replace_model = CBaseEntity::PrecacheModel(Value.String()); + if (replace_model != -1) { + ent->SetModelIndex(replace_model); + } + }}, + {"SetOwner"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + if (owner != nullptr) { + ent->SetOwnerEntity(owner); + } + }}, + {"GetKeyValue"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + variant_t variant; + ent->ReadKeyField(Value.String(), &variant); + ent->m_OnUser1->FireOutput(variant, pActivator, ent); + }}, + {"InheritOwner"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + if (owner != nullptr) { + ent->SetOwnerEntity(owner->GetOwnerEntity()); + } + }}, + {"InheritParent"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto owner = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + if (owner != nullptr) { + ent->SetParent(owner->GetMoveParent(), -1); + } + }}, + {"MoveType"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + variant_t variant; + int val1=0; + int val2=MOVECOLLIDE_DEFAULT; + + sscanf(Value.String(), "%d,%d", &val1, &val2); + ent->SetMoveType((MoveType_t)val1, (MoveCollide_t)val2); + }}, + {"PlaySound"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + + if (!enginesound->PrecacheSound(Value.String(), true)) + CBaseEntity::PrecacheScriptSound(Value.String()); + + ent->EmitSound(Value.String()); + }}, + {"StopSound"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->StopSound(Value.String()); + }}, + {"SetLocalOrigin"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + ent->SetLocalOrigin(vec); + }}, + {"SetLocalAngles"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_VECTOR); + QAngle vec; + Value.Vector3D(*reinterpret_cast(&vec)); + ent->SetLocalAngles(vec); + }}, + {"SetLocalVelocity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + ent->SetLocalVelocity(vec); + }}, + {"TeleportToEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto target = servertools->FindEntityByName(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + Vector targetpos = target->GetAbsOrigin(); + ent->Teleport(&targetpos, nullptr, nullptr); + } + }}, + {"MoveRelative"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_VECTOR); + Vector vec; + Value.Vector3D(vec); + vec = vec + ent->GetLocalOrigin(); + ent->SetLocalOrigin(vec); + }}, + {"RotateRelative"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Value.Convert(FIELD_VECTOR); + QAngle vec; + Value.Vector3D(*reinterpret_cast(&vec)); + vec = vec + ent->GetLocalAngles(); + ent->SetLocalAngles(vec); + }}, + {"TestEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { + auto filter = rtti_cast(ent); + if (filter != nullptr && filter->PassesFilter(pCaller, target)) { + filter->m_OnPass->FireOutput(Value, pActivator, target); + } + } + }}, + {"StartTouchEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto filter = rtti_cast(ent); + if (filter != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { + filter->StartTouch(target); + } + } + }}, + {"EndTouchEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto filter = rtti_cast(ent); + if (filter != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, Value.String(), ent, pActivator, pCaller)) != nullptr ;) { + filter->EndTouch(target); + } + } + }}, + {"RotateTowards"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto rotating = rtti_cast(ent); + if (rotating != nullptr) { + CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + auto data = GetExtraFuncRotatingData(rotating); + data->m_hRotateTarget = target; + + if (rotating->GetNextThink("RotatingFollowEntity") < gpGlobals->curtime) { + THINK_FUNC_SET(rotating, RotatingFollowEntity, gpGlobals->curtime + 0.1); + } + } + } + auto data = ent->GetEntityModule("rotator"); + if (data != nullptr) { + CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + data->m_hRotateTarget = target; + + if (ent->GetNextThink("RotatingFollowEntity") < gpGlobals->curtime) { + Msg("install rotator\n"); + THINK_FUNC_SET(ent, RotatorModuleTick, gpGlobals->curtime + 0.1); + } + } + } + }}, + {"StopRotateTowards"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto data = ent->GetEntityModule("rotator"); + if (data != nullptr) { + data->m_hRotateTarget = nullptr; + } + }}, + {"SetForwardVelocity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + Vector fwd; + AngleVectors(ent->GetAbsAngles(), &fwd); + fwd *= strtof(Value.String(), nullptr); + + IPhysicsObject *pPhysicsObject = ent->VPhysicsGetObject(); + if (pPhysicsObject) { + pPhysicsObject->SetVelocity(&fwd, nullptr); + } + else { + ent->SetAbsVelocity(fwd); + } + }}, + {"FaceEntity"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + Vector delta = target->GetAbsOrigin() - ent->GetAbsOrigin(); + + QAngle angToTarget; + VectorAngles(delta, angToTarget); + ent->SetAbsAngles(angToTarget); + if (ToTFPlayer(ent) != nullptr) { + ToTFPlayer(ent)->SnapEyeAngles(angToTarget); + } + } + }}, + {"SetFakeParent"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto data = ent->GetEntityModule("fakeparent"); + if (data != nullptr) { + CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + data->m_hParent = target; + data->m_bParentSet = true; + if (ent->GetNextThink("FakeParentModuleTick") < gpGlobals->curtime) { + THINK_FUNC_SET(ent, FakeParentModuleTick, gpGlobals->curtime + 0.01); + } + } + } + }}, + {"SetAimFollow"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + + auto data = ent->GetEntityModule("aimfollow"); + if (data != nullptr) { + CBaseEntity *target = servertools->FindEntityGeneric(nullptr, Value.String(), ent, pActivator, pCaller); + if (target != nullptr) { + data->m_hParent = target; + if (ent->GetNextThink("AimFollowModuleTick") < gpGlobals->curtime) { + THINK_FUNC_SET(ent, AimFollowModuleTick, gpGlobals->curtime + 0.01); + } + } + } + }}, + {"ClearFakeParent"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto data = ent->GetEntityModule("fakeparent"); + if (data != nullptr) { + data->m_hParent = nullptr; + data->m_bParentSet = false; + } + }}, + {"SetVar$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + SetEntityVariable(ent, VARIABLE, szInputName + strlen("SetVar$"), Value); + }}, + {"GetVar$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + FireGetInput(ent, VARIABLE, szInputName + strlen("GetVar$"), pActivator, pCaller, Value); + }}, + {"SetKey$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + SetEntityVariable(ent, KEYVALUE, szInputName + strlen("SetKey$"), Value); + }}, + {"GetKey$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + FireGetInput(ent, KEYVALUE, szInputName + strlen("GetKey$"), pActivator, pCaller, Value); + }}, + {"SetData$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + SetEntityVariable(ent, DATAMAP, szInputName + strlen("SetData$"), Value); + }}, + {"GetData$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + FireGetInput(ent, DATAMAP, szInputName + strlen("GetData$"), pActivator, pCaller, Value); + }}, + {"SetProp$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + SetEntityVariable(ent, SENDPROP, szInputName + strlen("SetProp$"), Value); + }}, + {"GetProp$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + FireGetInput(ent, SENDPROP, szInputName + strlen("GetProp$"), pActivator, pCaller, Value); + }}, + {"SetClientProp$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mod = ent->GetOrCreateEntityModule("fakeprop"); + mod->props[szInputName + strlen("SetClientProp$")] = {Value, Value}; + }}, + {"ResetClientProp$"sv, true, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + auto mod = ent->GetOrCreateEntityModule("fakeprop"); + mod->props.erase(szInputName + strlen("ResetClientProp$")); + }}, + {"GetEntIndex"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + char param_tokenized[256] = ""; + V_strncpy(param_tokenized, Value.String(), sizeof(param_tokenized)); + char *targetstr = strtok(param_tokenized,"|"); + char *action = strtok(NULL,"|"); + + variant_t variable; + variable.SetInt(ent->entindex()); + if (targetstr != nullptr && action != nullptr) { + for (CBaseEntity *target = nullptr; (target = servertools->FindEntityGeneric(target, targetstr, ent, pActivator, pCaller)) != nullptr ;) { + target->AcceptInput(action, pActivator, ent, variable, 0); + } + } + }}, + {"AddModule"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + AddModuleByName(ent, Value.String()); + }}, + {"RemoveModule"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + ent->RemoveEntityModule(Value.String()); + }}, + {"RemoveOutput"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + const char *name = Value.String(); + auto datamap = ent->GetDataDescMap(); + for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if ((dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT) && stricmp(dmap->dataDesc[i].externalName, name) == 0) { + ((CBaseEntityOutput*)(((char*)ent) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]))->DeleteAllElements(); + return; + } + } + } + ent->RemoveCustomOutput(name+1); + }}, + {"CancelPending"sv, false, [](CBaseEntity *ent, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t &Value){ + g_EventQueue.GetRef().CancelEvents(ent); + }} + }); +} \ No newline at end of file diff --git a/src/mod/mvm/robot_limit.cpp b/src/mod/mvm/robot_limit.cpp index de1e4a94..06a93d7e 100644 --- a/src/mod/mvm/robot_limit.cpp +++ b/src/mod/mvm/robot_limit.cpp @@ -12,6 +12,11 @@ namespace Mod::Pop::WaveSpawn_Extensions bool IsEnabled(); } +namespace Mod::Etc::Extra_Player_Slots +{ + void SetForceCreateAtSlot(int slot); +} + namespace Mod::MvM::Robot_Limit { void CheckForMaxInvadersAndKickExtras(CUtlVector& mvm_bots); @@ -51,9 +56,59 @@ namespace Mod::MvM::Robot_Limit }); - - + bool has_extra_bot_mission = false; + + void ScanMissionsForExtraBots() + { + has_extra_bot_mission = false; + static ConVarRef sig_etc_extra_player_slots_allow_bots("sig_etc_extra_player_slots_allow_bots"); + if (sig_etc_extra_player_slots_allow_bots.GetBool()) { + has_extra_bot_mission = true; + return; + } + + FileFindHandle_t missionHandle; + const char *map = STRING(gpGlobals->mapname); + char poppathfind[256]; + snprintf(poppathfind, sizeof(poppathfind), "scripts/population/%s_*.pop", map); + for (const char *missionName = filesystem->FindFirstEx(poppathfind, "GAME", &missionHandle); + missionName != nullptr; missionName = filesystem->FindNext(missionHandle)) { + + char poppath[256]; + snprintf(poppath, sizeof(poppath), "%s%s","scripts/population/", missionName); + KeyValues *kv = new KeyValues("kv"); + kv->UsesConditionals(false); + if (kv->LoadFromFile(filesystem, poppath)) { + FOR_EACH_SUBKEY(kv, subkey) { + + if (FStrEq(subkey->GetName(), "AllowBotExtraSlots") && subkey->GetBool() ) { + has_extra_bot_mission = true; + //Msg("Found extra bot mission\n"); + break; + } + } + kv->deleteThis(); + if (has_extra_bot_mission) break; + } + } + filesystem->FindClose(missionHandle); + } + int GetMvMInvaderLimit() { return cvar_override.GetInt(); } + + // Clients may crash when a bot at extra slot joined after they spawned, so all the extra slots get occupied at once as soon as a player joins. + // Those extra slots must stay unused to respect the max robot limit + int GetMaxAllowedSlot() + { + static ConVarRef sig_etc_extra_player_slots_allow_bots("sig_etc_extra_player_slots_allow_bots"); + if (!sig_etc_extra_player_slots_allow_bots.GetBool()) return 33; + + static ConVarRef tv_enable("tv_enable"); + static ConVarRef visible_max_players("sv_visiblemaxplayers"); + int specs = MAX(0, Mod::Pop::PopMgr_Extensions::GetMaxSpectators()); + + return GetMvMInvaderLimit() + visible_max_players.GetInt() + specs + (tv_enable.GetBool() ? 1 : 0); + } // reimplement the MvM bots-over-quota logic, with some changes: @@ -80,13 +135,13 @@ namespace Mod::MvM::Robot_Limit // Kick bots over the slot 33 - for (int i = 0; i < mvm_bots.Count(); i++) { + /*for (int i = 0; i < mvm_bots.Count(); i++) { if (ENTINDEX(mvm_bots[i]) > 33 && ENTINDEX(mvm_bots[i]) > GetMvMInvaderLimit() + visible_max_players.GetInt() + specs + (tv_enable.GetBool() ? 1 : 0)) { engine->ServerCommand(CFmtStr("kickid %d\n", mvm_bots[i]->GetUserID())); mvm_bots.Remove(i); i--; } - } + }*/ if (mvm_bots.Count() < GetMvMInvaderLimit()) return; @@ -99,7 +154,7 @@ namespace Mod::MvM::Robot_Limit for (auto bot : mvm_bots) { if (need_to_kick <= 0) break; - if (bot->GetTeamNumber() == TEAM_SPECTATOR) { + if (bot->GetTeamNumber() == TEAM_SPECTATOR && ENTINDEX(bot) <= 33) { bots_to_kick.AddToTail(bot); --need_to_kick; } @@ -110,7 +165,7 @@ namespace Mod::MvM::Robot_Limit for (auto bot : mvm_bots) { if (need_to_kick <= 0) break; - if (bot->GetTeamNumber() == TF_TEAM_RED) { + if (bot->GetTeamNumber() == TF_TEAM_RED && ENTINDEX(bot) <= 33) { bots_to_kick.AddToTail(bot); --need_to_kick; } @@ -120,7 +175,7 @@ namespace Mod::MvM::Robot_Limit for (auto bot : mvm_bots) { if (need_to_kick <= 0) break; - if (!bot->IsAlive()) { + if (!bot->IsAlive() && ENTINDEX(bot) <= 33) { bots_to_kick.AddToTail(bot); --need_to_kick; } @@ -129,7 +184,7 @@ namespace Mod::MvM::Robot_Limit for (auto bot : mvm_bots) { if (need_to_kick <= 0) break; - if (bot->GetTeamNumber() == TF_TEAM_BLUE) { + if (bot->GetTeamNumber() == TF_TEAM_BLUE && ENTINDEX(bot) <= 33) { bots_to_kick.AddToTail(bot); --need_to_kick; } @@ -152,13 +207,14 @@ namespace Mod::MvM::Robot_Limit int CollectMvMBots(CUtlVector *mvm_bots, bool collect_red) { mvm_bots->RemoveAll(); int count_red = 0; - for (int i = 1; i <= gpGlobals->maxClients; ++i) { + int maxAllowedSlot = GetMaxAllowedSlot(); + for (int i = 1; i <= maxAllowedSlot; ++i) { CTFBot *bot = ToTFBot(UTIL_PlayerByIndex(i)); if (bot == nullptr) continue; if (ENTINDEX(bot) == 0) continue; if (!bot->IsBot()) continue; if (!bot->IsConnected()) continue; - + if (bot->GetTeamNumber() == TF_TEAM_RED) { if (collect_red && bot->m_Shared->InCond(TF_COND_REPROGRAMMED)) { count_red += 1; @@ -237,6 +293,20 @@ namespace Mod::MvM::Robot_Limit ++num_bots; } + + // To prevent client crashes when 33+ slots are used, create all bots at extra slots at once. They will still be ignored if robot limit is not high enough + if (has_extra_bot_mission) { + for (int i = 33; i < gpGlobals->maxClients; i++) { + if (UTIL_PlayerByIndex(i + 1) == nullptr) { + Mod::Etc::Extra_Player_Slots::SetForceCreateAtSlot(i); + CTFBot *bot = NextBotCreatePlayerBot("TFBot", false); + Mod::Etc::Extra_Player_Slots::SetForceCreateAtSlot(-1); + if (bot != nullptr) { + bot->ChangeTeam(TEAM_SPECTATOR, false, true, false); + } + } + } + } popmgr->m_bAllocatedBots = true; } @@ -343,7 +413,7 @@ namespace Mod::MvM::Robot_Limit slots = old_slots; } - class CMod : public IMod + class CMod : public IMod, public IModCallbackListener { public: CMod() : IMod("MvM:Robot_Limit") @@ -364,6 +434,14 @@ namespace Mod::MvM::Robot_Limit MOD_ADD_DETOUR_MEMBER(CGameServer_SetHibernating, "CGameServer::SetHibernating"); } + + virtual bool ShouldReceiveCallbacks() const override { return this->IsEnabled(); } + + virtual void LevelInitPreEntity() override + { + ScanMissionsForExtraBots(); + } + }; CMod s_Mod; diff --git a/src/mod/perf/input_optimize.cpp b/src/mod/perf/input_optimize.cpp index faccd371..b44f5902 100644 --- a/src/mod/perf/input_optimize.cpp +++ b/src/mod/perf/input_optimize.cpp @@ -230,7 +230,7 @@ namespace Mod::Perf::Input_Optimize CMod() : IMod("Perf::Input_Optimize") { MOD_ADD_DETOUR_MEMBER_PRIORITY(CBaseEntity_AcceptInput, "CBaseEntity::AcceptInput", LOWEST); - MOD_ADD_DETOUR_MEMBER(CBaseEntityOutput_FireOutput, "CBaseEntityOutput::FireOutput"); + MOD_ADD_DETOUR_MEMBER_PRIORITY(CBaseEntityOutput_FireOutput, "CBaseEntityOutput::FireOutput", LOWEST); MOD_ADD_DETOUR_MEMBER(CTFPlayer_ClientCommand, "CTFPlayer::ClientCommand"); //MOD_ADD_DETOUR_MEMBER(CEventQueue_ServiceEvents, "CEventQueue::ServiceEvents"); diff --git a/src/mod/pop/ecattr_extensions.cpp b/src/mod/pop/ecattr_extensions.cpp index b6b381f6..24cefc97 100644 --- a/src/mod/pop/ecattr_extensions.cpp +++ b/src/mod/pop/ecattr_extensions.cpp @@ -161,6 +161,9 @@ namespace Mod::Pop::ECAttr_Extensions std::vector strip_item; + float desired_attack_range = -1; + float move_behind_enemy = 0; + CRC32_t spray_file; }; @@ -862,6 +865,10 @@ namespace Mod::Pop::ECAttr_Extensions CBaseEntity::PrecacheScriptSound(kv->GetString()); } else if (FStrEq(name, "StripItem")) { data.strip_item.push_back(kv->GetString()); + } else if (FStrEq(name, "DesiredAttackRange")) { + data.desired_attack_range = kv->GetFloat(); + } else if (FStrEq(name, "MoveBehindEnemy")) { + data.move_behind_enemy = kv->GetFloat(); } else { found = false; } @@ -2407,6 +2414,87 @@ namespace Mod::Pop::ECAttr_Extensions DETOUR_STATIC_CALL(TE_TFParticleEffect)(recipement, value, name, vector, angles, entity, attach); } + DETOUR_DECL_MEMBER(float, CTFBot_GetDesiredAttackRange) + { + auto bot = reinterpret_cast(this); + + if (TFGameRules()->IsMannVsMachineMode()) { + auto *data = GetDataForBot(bot); + if (data != nullptr && data->desired_attack_range != -1) { + return data->desired_attack_range; + } + } + + auto result = DETOUR_MEMBER_CALL(CTFBot_GetDesiredAttackRange)(); + + return result; + } + + DETOUR_DECL_MEMBER(ActionResult, CTFBotAttack_Update, CTFBot *me, float interval) + { + auto result = DETOUR_MEMBER_CALL(CTFBotAttack_Update)(me, interval); + + if (!TFGameRules()->IsMannVsMachineMode()) return result; + auto *data = GetDataForBot(me); + + if (data == nullptr) return result; + + float circleStrafeRange = data->move_behind_enemy; + if (circleStrafeRange < 1) return result; + if (circleStrafeRange == 1) { + circleStrafeRange = 250; + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(false); + + + if (threat == nullptr || threat->IsObsolete()) return result; + + auto playerThreat = ToTFPlayer(threat->GetEntity()); + + if (playerThreat != nullptr && threat->IsVisibleInFOVNow()) + { + Vector toPlayerThreat = playerThreat->GetAbsOrigin() - me->GetAbsOrigin(); + + float threatRange = toPlayerThreat.NormalizeInPlace(); + + Vector playerThreatForward; + playerThreat->EyeVectors( &playerThreatForward ); + + bool isBehindVictim = DotProduct( playerThreatForward, toPlayerThreat ) > 0.7071f; + if ( threatRange < circleStrafeRange ) + { + // we're close - aim our stab attack + me->GetBodyInterface()->AimHeadTowards( playerThreat, IBody::LookAtPriorityType::CRITICAL, 0.1f, NULL, "Aiming my stab!" ); + + if ( !isBehindVictim ) + { + // circle around our victim to get behind them + Vector myForward; + me->EyeVectors( &myForward ); + + Vector cross; + CrossProduct( playerThreatForward, myForward, cross ); + + if ( cross.z < 0.0f ) + { + me->PressRightButton(); + } + else + { + me->PressLeftButton(); + } + + // don't continue to close in if we're already very close so we don't bump them and give ourselves away + // if ( threatRange < 100.0f ) + // { + // isMovingTowardVictim = false; + // } + } + } + } + return result; + } class CMod : public IMod, public IModCallbackListener, public IFrameUpdatePostEntityThinkListener { public: @@ -2475,6 +2563,9 @@ namespace Mod::Pop::ECAttr_Extensions MOD_ADD_DETOUR_STATIC(DispatchParticleEffect, "DispatchParticleEffect [overload 3]"); MOD_ADD_DETOUR_STATIC(TE_TFParticleEffect, "TE_TFParticleEffect"); MOD_ADD_DETOUR_MEMBER(CTFPlayer_GetSceneSoundToken, "CTFPlayer::GetSceneSoundToken"); + + MOD_ADD_DETOUR_MEMBER(CTFBot_GetDesiredAttackRange, "CTFBot::GetDesiredAttackRange"); + MOD_ADD_DETOUR_MEMBER(CTFBotAttack_Update, "CTFBotAttack::Update"); } virtual bool OnLoad() override diff --git a/src/mod/pop/popmgr_extensions.cpp b/src/mod/pop/popmgr_extensions.cpp index c3d6a0e4..cb9be1d2 100644 --- a/src/mod/pop/popmgr_extensions.cpp +++ b/src/mod/pop/popmgr_extensions.cpp @@ -24,6 +24,8 @@ #include "stub/nextbot_cc.h" #include "util/clientmsg.h" #include "util/admin.h" +#include "mod/etc/mapentity_additions.h" + WARN_IGNORE__REORDER() #include <../server/vote_controller.h> WARN_RESTORE() @@ -655,7 +657,8 @@ namespace Mod::Pop::PopMgr_Extensions m_AllowBotsExtraSlots ("sig_etc_extra_player_slots_allow_bots"), m_AutoWeaponStrip ("sig_auto_weapon_strip"), m_RemoveOffhandViewmodel ("sig_etc_entity_limit_manager_viewmodel"), - m_RemoveBotExpressions ("sig_etc_entity_limit_manager_remove_expressions") + m_RemoveBotExpressions ("sig_etc_entity_limit_manager_remove_expressions"), + m_ExtraBotSlotsNoDeathcam ("sig_etc_extra_player_slots_no_death_cam") { this->Reset(); @@ -806,6 +809,7 @@ namespace Mod::Pop::PopMgr_Extensions this->m_AutoWeaponStrip.Reset(); this->m_RemoveOffhandViewmodel.Reset(); this->m_RemoveBotExpressions.Reset(); + this->m_ExtraBotSlotsNoDeathcam.Reset(); this->m_CustomUpgradesFile.Reset(); this->m_TextPrintSpeed.Reset(); @@ -881,6 +885,8 @@ namespace Mod::Pop::PopMgr_Extensions this->m_ParticleOverride.clear(); this->m_BuildingPointTemplates.clear(); + this->m_Scripts.clear(); + this->m_ScriptFiles.clear(); } bool m_bGiantsDropRareSpells; @@ -1029,6 +1035,7 @@ namespace Mod::Pop::PopMgr_Extensions CPopOverride_ConVar m_AutoWeaponStrip; CPopOverride_ConVar m_RemoveOffhandViewmodel; CPopOverride_ConVar m_RemoveBotExpressions; + CPopOverride_ConVar m_ExtraBotSlotsNoDeathcam; @@ -1099,6 +1106,10 @@ namespace Mod::Pop::PopMgr_Extensions std::unordered_map m_ParticleOverride; std::vector m_BuildingPointTemplates; + + std::vector m_Scripts; + std::vector m_ScriptFiles; + CHandle m_ScriptManager; }; PopState state{}; @@ -2006,12 +2017,26 @@ namespace Mod::Pop::PopMgr_Extensions DETOUR_DECL_MEMBER(void, CMannVsMachineStats_RoundEvent_WaveEnd, bool success) { DevMsg("Wave end team win %d\n", TFGameRules()->GetWinningTeam()); + bool origSuccess = success; if (state.m_bReverseWinConditions && last_game_was_win) { DevMsg("State_Enter %d\n", rc_CTeamplayRoundBasedRules_State_Enter); - DETOUR_MEMBER_CALL(CMannVsMachineStats_RoundEvent_WaveEnd)(true); - - return; + success = true; + } + + if (state.m_ScriptManager != nullptr) { + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + + if (scriptManager->CheckGlobal("OnWaveInit")) { + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount + (origSuccess ? 1 : 0)); + scriptManager->Call(1, 0); + } + + if (scriptManager->CheckGlobal(success ? "OnWaveSuccess" : "OnWaveFail")) { + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount + (origSuccess ? 1 : 0)); + scriptManager->Call(1, 0); + } } + DETOUR_MEMBER_CALL(CMannVsMachineStats_RoundEvent_WaveEnd)(success); } @@ -4018,7 +4043,16 @@ namespace Mod::Pop::PopMgr_Extensions state.m_PlayersByWaveStart.insert(player); } }); + DETOUR_MEMBER_CALL(CPopulationManager_StartCurrentWave)(); + + if (state.m_ScriptManager != nullptr) { + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + if (scriptManager->CheckGlobal("OnWaveStart")) { + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount); + scriptManager->Call(1, 0); + } + } } DETOUR_DECL_MEMBER(bool, CTFPlayer_IsReadyToSpawn) @@ -4384,6 +4418,48 @@ namespace Mod::Pop::PopMgr_Extensions } } } + + DETOUR_DECL_MEMBER(bool, CTFBotSpawner_Spawn, const Vector& where, CUtlVector> *ents) + { + auto result = DETOUR_MEMBER_CALL(CTFBotSpawner_Spawn)(where, ents); + if (result && ents != nullptr && state.m_ScriptManager != nullptr) { + auto player = ToTFBot(ents->Tail()); + if (player != nullptr) { + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + if (scriptManager->CheckGlobal("OnWaveSpawnBot")) { + Util::Lua::LEntityAlloc(scriptManager->GetState(), player); + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount); + lua_newtable(scriptManager->GetState()); + int idx = 1; + for (auto &tag : player->m_Tags) { + lua_pushstring(scriptManager->GetState(), tag.Get()); + lua_rawseti(scriptManager->GetState(), -2, idx); + idx++; + } + scriptManager->Call(3, 0); + } + } + } + return result; + } + + DETOUR_DECL_MEMBER(bool, CTankSpawner_Spawn, const Vector& where, CUtlVector> *ents) + { + auto result = DETOUR_MEMBER_CALL(CTankSpawner_Spawn)(where, ents); + if (result && ents != nullptr && state.m_ScriptManager != nullptr) { + auto entity = ents->Tail(); + if (entity != nullptr) { + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + if (scriptManager->CheckGlobal("OnWaveSpawnTank")) { + Util::Lua::LEntityAlloc(scriptManager->GetState(), entity); + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount); + scriptManager->Call(2, 0); + } + } + } + return result; + } + // DETOUR_DECL_STATIC(void, MessageWriteString,const char *name) // { // DevMsg("MessageWriteString %s\n",name); @@ -5295,6 +5371,10 @@ namespace Mod::Pop::PopMgr_Extensions state.m_BoughtLoadoutItems.clear(); state.m_BoughtLoadoutItemsCheckpoint.clear(); Mod::Etc::Mapentity_Additions::ClearFakeProp(); + + if (state.m_ScriptManager != nullptr) { + state.m_ScriptManager->Remove(); + } DETOUR_MEMBER_CALL(CPopulationManager_ResetMap)(); } @@ -5402,6 +5482,26 @@ namespace Mod::Pop::PopMgr_Extensions } state.m_SpawnTemplates.clear(); + if (state.m_ScriptManager == nullptr && (!state.m_Scripts.empty() || !state.m_ScriptFiles.empty())) { + state.m_ScriptManager = CreateEntityByName("$script_manager"); + DispatchSpawn(state.m_ScriptManager); + state.m_ScriptManager->SetName(AllocPooledString("popscript")); + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + for (auto &script : state.m_ScriptFiles) { + scriptManager->DoFile(script.c_str(), true); + } + for (auto &script : state.m_Scripts) { + scriptManager->DoString(script.c_str(), true); + } + state.m_ScriptManager->Activate(); + } + if (state.m_ScriptManager != nullptr) { + auto scriptManager = state.m_ScriptManager->GetOrCreateEntityModule("script"); + if (scriptManager->CheckGlobal("OnWaveReset")) { + lua_pushinteger(scriptManager->GetState(), TFObjectiveResource()->m_nMannVsMachineWaveCount); + scriptManager->Call(1, 0); + } + } TogglePatches(); // THINK_FUNC_SET(g_pPopulationManager, DoSprayDecal, gpGlobals->curtime+1.0f); return ret; @@ -5817,6 +5917,12 @@ namespace Mod::Pop::PopMgr_Extensions state.m_RemoveOffhandViewmodel.Set(subkey->GetBool()); } else if (FStrEq(name, "RemoveBotExpressions")) { state.m_RemoveBotExpressions.Set(subkey->GetBool()); + } else if (FStrEq(name, "ExtraBotSlotsNoDeathcam")) { + state.m_ExtraBotSlotsNoDeathcam.Set(subkey->GetBool()); + } else if (FStrEq(name, "LuaScript")) { + state.m_Scripts.push_back(subkey->GetString()); + } else if (FStrEq(name, "LuaScriptFile")) { + state.m_ScriptFiles.push_back(subkey->GetString()); } else if (FStrEq(name, "CustomNavFile")) { char strippedFile[128]; V_StripExtension(subkey->GetString(), strippedFile, sizeof(strippedFile)); @@ -6139,6 +6245,8 @@ namespace Mod::Pop::PopMgr_Extensions //MOD_ADD_DETOUR_MEMBER(CBaseCombatWeapon_UpdateTransmitState, "CBaseCombatWeapon::UpdateTransmitState"); //MOD_ADD_DETOUR_MEMBER(CTFWeaponBase_Deploy, "CTFWeaponBase::Deploy"); //MOD_ADD_DETOUR_MEMBER(CTFWeaponBase_Holster, "CTFWeaponBase::Holster"); + MOD_ADD_DETOUR_MEMBER(CTFBotSpawner_Spawn, "CTFBotSpawner::Spawn"); + MOD_ADD_DETOUR_MEMBER(CTankSpawner_Spawn, "CTankSpawner::Spawn"); diff --git a/src/mod/pop/tfbot_extensions.cpp b/src/mod/pop/tfbot_extensions.cpp index 8ee8e3f2..bc5bed48 100644 --- a/src/mod/pop/tfbot_extensions.cpp +++ b/src/mod/pop/tfbot_extensions.cpp @@ -19,6 +19,7 @@ #include "stub/trace.h" #include "util/clientmsg.h" #include "mod/pop/popmgr_extensions.h" +#include "stub/team.h" #include @@ -267,6 +268,8 @@ namespace Mod::Pop::TFBot_Extensions bool no_formation = false; bool no_idle_sound = false; + + bool prefer_extra_slots = false; }; std::unordered_map spawners; @@ -608,6 +611,8 @@ namespace Mod::Pop::TFBot_Extensions spawners[spawner].neutral = subkey->GetBool(); } else if (FStrEq(name, "NoIdleSound")) { spawners[spawner].no_idle_sound = subkey->GetBool(); + } else if (FStrEq(name, "PreferExtraSlots")) { + spawners[spawner].prefer_extra_slots = subkey->GetBool(); //#ifdef ENABLE_BROKEN_STUFF } else if (FStrEq(name, "DropWeapon")) { spawners[spawner].drop_weapon = subkey->GetBool(); @@ -736,7 +741,7 @@ namespace Mod::Pop::TFBot_Extensions } // clock_t start_time_spawn; - void OnBotSpawn(CTFBotSpawner *spawner, CUtlVector> *ents) { + void OnBotSpawn(CTFBotSpawner *spawner, CUtlVector> *ents, SpawnerData &data) { // clock_t endn = clock() ; @@ -754,146 +759,154 @@ namespace Mod::Pop::TFBot_Extensions // } if (ents != nullptr && !ents->IsEmpty()) { - auto it = spawners.find(spawner); - if (it != spawners.end()) { - SpawnerData& data = (*it).second; - CTFBot *bot_leader = ToTFBot(ents->Head()); - CTFBot *bot = ToTFBot(ents->Tail()); - if (bot != nullptr) { - spawner_of_bot[bot] = spawner; - bots_data[bot] = &data; - - // DevMsg("CTFBotSpawner %08x: found %u AddCond's\n", (uintptr_t)spawner, data.addconds.size()); - ApplyAddCond(bot, data.addconds, delayed_addconds); - ApplyPendingTask(bot, data.periodic_tasks, pending_periodic_tasks); - - for (auto templ : data.templ) { - templ.SpawnTemplate(bot); - //if (Point_Templates().find(templ) != Point_Templates().end()) - // Point_Templates()[templ].SpawnTemplate(bot); - } + CTFBot *bot_leader = ToTFBot(ents->Head()); + CTFBot *bot = ToTFBot(ents->Tail()); + if (bot != nullptr) { + spawner_of_bot[bot] = spawner; + bots_data[bot] = &data; + + // DevMsg("CTFBotSpawner %08x: found %u AddCond's\n", (uintptr_t)spawner, data.addconds.size()); + ApplyAddCond(bot, data.addconds, delayed_addconds); + ApplyPendingTask(bot, data.periodic_tasks, pending_periodic_tasks); + + for (auto templ : data.templ) { + templ.SpawnTemplate(bot); + //if (Point_Templates().find(templ) != Point_Templates().end()) + // Point_Templates()[templ].SpawnTemplate(bot); + } - if (data.force_romevision_cosmetics) { - for (int i = 0; i < 2; i++) { - //CEconItemView *item_view= CEconItemView::Create(); - //item_view->Init(152, 6, 9999, 0); + if (data.force_romevision_cosmetics) { + for (int i = 0; i < 2; i++) { + //CEconItemView *item_view= CEconItemView::Create(); + //item_view->Init(152, 6, 9999, 0); + + CEconWearable *wearable = static_cast(ItemGeneration()->SpawnItem(152, Vector(0,0,0), QAngle(0,0,0), 6, 9999, "tf_wearable")); + if (wearable) { - CEconWearable *wearable = static_cast(ItemGeneration()->SpawnItem(152, Vector(0,0,0), QAngle(0,0,0), 6, 9999, "tf_wearable")); - if (wearable) { - - wearable->m_bValidatedAttachedEntity = true; - wearable->GiveTo(bot); - DevMsg("Created wearable %d\n",bot->GetPlayerClass()->GetClassIndex()*2 + i); - bot->EquipWearable(wearable); - const char *path = ROMEVISON_MODELS[bot->GetPlayerClass()->GetClassIndex()*2 + i]; - int model_index = CBaseEntity::PrecacheModel(path); - wearable->SetModelIndex(model_index); - for (int j = 0; j < MAX_VISION_MODES; ++j) { - wearable->SetModelIndexOverride(j, model_index); - } + wearable->m_bValidatedAttachedEntity = true; + wearable->GiveTo(bot); + DevMsg("Created wearable %d\n",bot->GetPlayerClass()->GetClassIndex()*2 + i); + bot->EquipWearable(wearable); + const char *path = ROMEVISON_MODELS[bot->GetPlayerClass()->GetClassIndex()*2 + i]; + int model_index = CBaseEntity::PrecacheModel(path); + wearable->SetModelIndex(model_index); + for (int j = 0; j < MAX_VISION_MODES; ++j) { + wearable->SetModelIndexOverride(j, model_index); } } } + } - //Replenish clip, if clip bonus is being applied - for (int i = 0; i < bot->WeaponCount(); ++i) { - CBaseCombatWeapon *weapon = bot->GetWeapon(i); - if (weapon == nullptr) continue; - - int fire_when_full = 0; - CALL_ATTRIB_HOOK_INT_ON_OTHER(weapon, fire_when_full, auto_fires_full_clip); - - if (fire_when_full == 0) - weapon->m_iClip1 = weapon->GetMaxClip1(); - } + //Replenish clip, if clip bonus is being applied + for (int i = 0; i < bot->WeaponCount(); ++i) { + CBaseCombatWeapon *weapon = bot->GetWeapon(i); + if (weapon == nullptr) continue; - DevMsg("Dests %d\n",Teleport_Destination().size()); - if (!(bot->m_nBotAttrs & CTFBot::AttributeType::ATTR_TELEPORT_TO_HINT) && !Teleport_Destination().empty()) { - bool done = false; - CBaseEntity *destination = nullptr; + int fire_when_full = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER(weapon, fire_when_full, auto_fires_full_clip); - if (Teleport_Destination().find("small") != Teleport_Destination().end() && !bot_leader->IsMiniBoss()) { - destination = Teleport_Destination().find("small")->second; - } - else if (Teleport_Destination().find("giants") != Teleport_Destination().end() && bot_leader->IsMiniBoss()) { - destination = Teleport_Destination().find("giants")->second; - } - else if (Teleport_Destination().find("all") != Teleport_Destination().end()) { - destination = Teleport_Destination().find("all")->second; - } - else { - ForEachEntityByClassname("info_player_teamspawn", [&](CBaseEntity *ent){ - if (done) - return; + if (fire_when_full == 0) + weapon->m_iClip1 = weapon->GetMaxClip1(); + } + + DevMsg("Dests %d\n",Teleport_Destination().size()); + if (!(bot->m_nBotAttrs & CTFBot::AttributeType::ATTR_TELEPORT_TO_HINT) && !Teleport_Destination().empty()) { + bool done = false; + CBaseEntity *destination = nullptr; + + if (Teleport_Destination().find("small") != Teleport_Destination().end() && !bot_leader->IsMiniBoss()) { + destination = Teleport_Destination().find("small")->second; + } + else if (Teleport_Destination().find("giants") != Teleport_Destination().end() && bot_leader->IsMiniBoss()) { + destination = Teleport_Destination().find("giants")->second; + } + else if (Teleport_Destination().find("all") != Teleport_Destination().end()) { + destination = Teleport_Destination().find("all")->second; + } + else { + ForEachEntityByClassname("info_player_teamspawn", [&](CBaseEntity *ent){ + if (done) + return; - auto vec = ent->WorldSpaceCenter(); - - auto area = TheNavMesh->GetNearestNavArea(vec); + auto vec = ent->WorldSpaceCenter(); + + auto area = TheNavMesh->GetNearestNavArea(vec); - if (area != nullptr) { - vec = area->GetCenter(); - } + if (area != nullptr) { + vec = area->GetCenter(); + } - float dist = vec.DistToSqr(bot->GetAbsOrigin()); - DevMsg("Dist %f %s\n",dist, ent->GetEntityName()); - if (dist < 1000) { - auto dest = Teleport_Destination().find(STRING(ent->GetEntityName())); - if(dest != Teleport_Destination().end() && dest->second != nullptr){ - destination = dest->second; - done = true; - } + float dist = vec.DistToSqr(bot->GetAbsOrigin()); + DevMsg("Dist %f %s\n",dist, ent->GetEntityName()); + if (dist < 1000) { + auto dest = Teleport_Destination().find(STRING(ent->GetEntityName())); + if(dest != Teleport_Destination().end() && dest->second != nullptr){ + destination = dest->second; + done = true; } - }); - } - if (destination != nullptr) - { - auto vec = destination->WorldSpaceCenter(); - vec.z += destination->CollisionProp()->OBBMaxs().z; - bool is_space_to_spawn = IsSpaceToSpawnHere(vec); - if (!is_space_to_spawn) - vec.z += 50.0f; - if (is_space_to_spawn || IsSpaceToSpawnHere(vec)){ - bot->Teleport(&(vec),&(destination->GetAbsAngles()),&(bot->GetAbsVelocity())); - bot->EmitSound("MVM.Robot_Teleporter_Deliver"); - bot->m_Shared->AddCond(TF_COND_INVULNERABLE_CARD_EFFECT,1.5f); } + }); + } + if (destination != nullptr) + { + auto vec = destination->WorldSpaceCenter(); + vec.z += destination->CollisionProp()->OBBMaxs().z; + bool is_space_to_spawn = IsSpaceToSpawnHere(vec); + if (!is_space_to_spawn) + vec.z += 50.0f; + if (is_space_to_spawn || IsSpaceToSpawnHere(vec)){ + bot->Teleport(&(vec),&(destination->GetAbsAngles()),&(bot->GetAbsVelocity())); + bot->EmitSound("MVM.Robot_Teleporter_Deliver"); + bot->m_Shared->AddCond(TF_COND_INVULNERABLE_CARD_EFFECT,1.5f); } - } + + } - if (bot->GetPlayerClass()->GetClassIndex() != TF_CLASS_ENGINEER && (bot->m_nBotAttrs & CTFBot::AttributeType::ATTR_TELEPORT_TO_HINT)) - TeleportToHint(bot, data.action != ACTION_Default); + if (bot->GetPlayerClass()->GetClassIndex() != TF_CLASS_ENGINEER && (bot->m_nBotAttrs & CTFBot::AttributeType::ATTR_TELEPORT_TO_HINT)) + TeleportToHint(bot, data.action != ACTION_Default); - if (data.action == ACTION_BotSpyInfiltrate) { - SpyInitAction(bot); - } - - //DevMsg("Client get pre %s\n", bot->GetPlayerName()); - //DevMsg("Client setting user info changed\n"); + if (data.action == ACTION_BotSpyInfiltrate) { + SpyInitAction(bot); + } + + //DevMsg("Client get pre %s\n", bot->GetPlayerName()); + //DevMsg("Client setting user info changed\n"); - //reinterpret_cast(sv)->UserInfoChanged(client->GetPlayerSlot()); - //DevMsg("Client success\n"); - - //player_info_t *pi = (player_info_t*) sv->GetUserInfoTable()->GetStringUserData( ENTINDEX(bot), NULL ); - //spawned_bots_first_tick.push_back(bot); + //reinterpret_cast(sv)->UserInfoChanged(client->GetPlayerSlot()); + //DevMsg("Client success\n"); + + //player_info_t *pi = (player_info_t*) sv->GetUserInfoTable()->GetStringUserData( ENTINDEX(bot), NULL ); + //spawned_bots_first_tick.push_back(bot); - if (data.neutral) { - bot->SetTeamNumber(TEAM_SPECTATOR); + if (data.neutral) { + bot->SetTeamNumber(TEAM_SPECTATOR); - ForEachTFPlayerEconEntity(bot, [&](CEconEntity *entity) { - entity->ChangeTeam(TEAM_SPECTATOR); - }); + ForEachTFPlayerEconEntity(bot, [&](CEconEntity *entity) { + entity->ChangeTeam(TEAM_SPECTATOR); + }); + } + + if (data.prefer_extra_slots) { + // Swap the team vector contents back + auto team = TFTeamMgr()->GetTeam(TEAM_SPECTATOR); + auto &vec = team->m_aPlayers.Get(); + int count = vec.Count(); + for (int i = 0; i < count/2; i++) { + auto swap = vec[count - 1 - i]; + vec[count - 1 - i] = vec[i]; + vec[i] = swap; } + } - /*for (int i = 0; i < bot->WeaponCount(); i++) { - CBaseCombatWeapon *weapon = bot->GetWeapon(i); - if (weapon != nullptr) { - bot->Weapon_Switch(weapon); + /*for (int i = 0; i < bot->WeaponCount(); i++) { + CBaseCombatWeapon *weapon = bot->GetWeapon(i); + if (weapon != nullptr) { + bot->Weapon_Switch(weapon); - //DevMsg("Is active %d %d %d\n", weapon == player->GetActiveWeapon(), weapon->GetEffects(), weapon->GetRenderMode()); - } - }*/ - } + //DevMsg("Is active %d %d %d\n", weapon == player->GetActiveWeapon(), weapon->GetEffects(), weapon->GetRenderMode()); + } + }*/ } } @@ -907,9 +920,25 @@ namespace Mod::Pop::TFBot_Extensions { auto spawner = reinterpret_cast(this); current_spawner = spawner; + + auto it = spawners.find(spawner); + if (it != spawners.end()) { + SpawnerData &data = (*it).second; + if (data.prefer_extra_slots) { + // Swap the team vector contents to use extra slots instead of normal + auto team = TFTeamMgr()->GetTeam(TEAM_SPECTATOR); + auto &vec = team->m_aPlayers.Get(); + int count = vec.Count(); + for (int i = 0; i < count/2; i++) { + auto swap = vec[count - 1 - i]; + vec[count - 1 - i] = vec[i]; + vec[i] = swap; + } + } + } auto result = DETOUR_MEMBER_CALL(CTFBotSpawner_Spawn)(where, ents); - if (result) { - OnBotSpawn(spawner,ents); + if (result && it != spawners.end()) { + OnBotSpawn(spawner,ents, (*it).second); } return result; } @@ -1807,11 +1836,17 @@ namespace Mod::Pop::TFBot_Extensions return DETOUR_MEMBER_CALL(CBaseCombatCharacter_Weapon_Detach)(weapon); } + RefCount rc_CBasePlayer_ChangeTeam; + DETOUR_DECL_MEMBER(void, CBasePlayer_ChangeTeam, int iTeamNum, bool b1, bool b2, bool b3) + { + auto player = reinterpret_cast(this); + DETOUR_MEMBER_CALL(CBasePlayer_ChangeTeam)(iTeamNum, b1, b2, b3); + SCOPED_INCREMENT_IF(rc_CBasePlayer_ChangeTeam, player->IsFakeClient() || player->IsHLTV()); + } + DETOUR_DECL_MEMBER(void, CQuestItemTracker_FireGameEvent, IGameEvent* event) { - if (rc_CTFBotSpawner_Spawn) { - return; - } + if (rc_CBasePlayer_ChangeTeam) return; DETOUR_MEMBER_CALL(CQuestItemTracker_FireGameEvent)(event); } @@ -2002,6 +2037,7 @@ namespace Mod::Pop::TFBot_Extensions MOD_ADD_DETOUR_MEMBER(CBaseCombatCharacter_Weapon_Detach, "CBaseCombatCharacter::Weapon_Detach"); // Fix crash related to bot change teams? + MOD_ADD_DETOUR_MEMBER(CBasePlayer_ChangeTeam, "CBasePlayer::ChangeTeam [int, bool, bool, bool]"); MOD_ADD_DETOUR_MEMBER(CQuestItemTracker_FireGameEvent, "CQuestItemTracker::FireGameEvent"); //MOD_ADD_DETOUR_MEMBER(CTFBot_AddItem, "CTFBot::AddItem"); diff --git a/src/mod/util/client_cmds.cpp b/src/mod/util/client_cmds.cpp index cfdc776f..6faaf01f 100644 --- a/src/mod/util/client_cmds.cpp +++ b/src/mod/util/client_cmds.cpp @@ -1693,7 +1693,7 @@ namespace Mod::Util::Client_Cmds }; - DETOUR_DECL_MEMBER(bool, CTFPlayer_ClientCommand, const CCommand& args) + DETOUR_DECL_MEMBER(bool, CTFPlayer_ClientCommand, const CCommand& args) { auto player = reinterpret_cast(this); if (player != nullptr) { diff --git a/src/sdk2013/takedamageinfo.h b/src/sdk2013/takedamageinfo.h new file mode 100644 index 00000000..c1ab8129 --- /dev/null +++ b/src/sdk2013/takedamageinfo.h @@ -0,0 +1,421 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TAKEDAMAGEINFO_H +#define TAKEDAMAGEINFO_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "networkvar.h" // todo: change this when DECLARE_CLASS is moved into a better location. + +// Used to initialize m_flBaseDamage to something that we know pretty much for sure +// hasn't been modified by a user. +#define BASEDAMAGE_NOT_SPECIFIED FLT_MAX + +class CBaseEntity; + + +class CTakeDamageInfo +{ +public: + + enum ECritType + { + CRIT_NONE = 0, + CRIT_MINI, + CRIT_FULL, + }; + + DECLARE_CLASS_NOBASE( CTakeDamageInfo ); + + CTakeDamageInfo(); + CTakeDamageInfo( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType, int iKillType = 0 ); + CTakeDamageInfo( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, float flDamage, int bitsDamageType, int iKillType = 0 ); + CTakeDamageInfo( CBaseEntity *pInflictor, CBaseEntity *pAttacker, const Vector &damageForce, const Vector &damagePosition, float flDamage, int bitsDamageType, int iKillType = 0, Vector *reportedPosition = NULL ); + CTakeDamageInfo( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, const Vector &damageForce, const Vector &damagePosition, float flDamage, int bitsDamageType, int iKillType = 0, Vector *reportedPosition = NULL ); + + + // Inflictor is the weapon or rocket (or player) that is dealing the damage. + CBaseEntity* GetInflictor() const; + void SetInflictor( CBaseEntity *pInflictor ); + + // Weapon is the weapon that did the attack. + // For hitscan weapons, it'll be the same as the inflictor. For projectile weapons, the projectile + // is the inflictor, and this contains the weapon that created the projectile. + CBaseEntity* GetWeapon() const; + void SetWeapon( CBaseEntity *pWeapon ); + + // Attacker is the character who originated the attack (like a player or an AI). + CBaseEntity* GetAttacker() const; + void SetAttacker( CBaseEntity *pAttacker ); + + float GetDamage() const; + void SetDamage( float flDamage ); + float GetMaxDamage() const; + void SetMaxDamage( float flMaxDamage ); + void ScaleDamage( float flScaleAmount ); + void AddDamage( float flAddAmount ); + void SubtractDamage( float flSubtractAmount ); + float GetDamageBonus() const; + CBaseEntity *GetDamageBonusProvider() const; + void SetDamageBonus( float flBonus, CBaseEntity *pProvider = NULL ); + + float GetBaseDamage() const; + bool BaseDamageIsValid() const; + + Vector GetDamageForce() const; + void SetDamageForce( const Vector &damageForce ); + void ScaleDamageForce( float flScaleAmount ); + float GetDamageForForceCalc() const; + void SetDamageForForceCalc( const float flScaleAmount ); + + Vector GetDamagePosition() const; + void SetDamagePosition( const Vector &damagePosition ); + + Vector GetReportedPosition() const; + void SetReportedPosition( const Vector &reportedPosition ); + + int GetDamageType() const; + void SetDamageType( int bitsDamageType ); + void AddDamageType( int bitsDamageType ); + int GetDamageCustom( void ) const; + void SetDamageCustom( int iDamageCustom ); + int GetDamageStats( void ) const; + void SetDamageStats( int iDamageStats ); + void SetForceFriendlyFire( bool bValue ) { m_bForceFriendlyFire = bValue; } + bool IsForceFriendlyFire( void ) const { return m_bForceFriendlyFire; } + + int GetAmmoType() const; + void SetAmmoType( int iAmmoType ); + const char * GetAmmoName() const; + + int GetPlayerPenetrationCount() const { return m_iPlayerPenetrationCount; } + void SetPlayerPenetrationCount( int iPlayerPenetrationCount ) { m_iPlayerPenetrationCount = iPlayerPenetrationCount; } + + int GetDamagedOtherPlayers() const { return m_iDamagedOtherPlayers; } + void SetDamagedOtherPlayers( int iVal ) { m_iDamagedOtherPlayers = iVal; } + + void Set( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType, int iKillType = 0 ); + void Set( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, float flDamage, int bitsDamageType, int iKillType = 0 ); + void Set( CBaseEntity *pInflictor, CBaseEntity *pAttacker, const Vector &damageForce, const Vector &damagePosition, float flDamage, int bitsDamageType, int iKillType = 0, Vector *reportedPosition = NULL ); + void Set( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, const Vector &damageForce, const Vector &damagePosition, float flDamage, int bitsDamageType, int iKillType = 0, Vector *reportedPosition = NULL ); + + void AdjustPlayerDamageInflictedForSkillLevel(); + void AdjustPlayerDamageTakenForSkillLevel(); + + // Given a damage type (composed of the #defines above), fill out a string with the appropriate text. + // For designer debug output. + static void DebugGetDamageTypeString(unsigned int DamageType, char *outbuf, int outbuflength ); + + void SetCritType( ECritType eType ); + + ECritType GetCritType() const { return m_eCritType; } + +//private: + void CopyDamageToBaseDamage(); + +protected: + void Init( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, const Vector &damageForce, const Vector &damagePosition, const Vector &reportedPosition, float flDamage, int bitsDamageType, int iKillType ); + + Vector m_vecDamageForce; + Vector m_vecDamagePosition; + Vector m_vecReportedPosition; // Position players are told damage is coming from + EHANDLE m_hInflictor; + EHANDLE m_hAttacker; + EHANDLE m_hWeapon; + float m_flDamage; + float m_flMaxDamage; + float m_flBaseDamage; // The damage amount before skill leve adjustments are made. Used to get uniform damage forces. + int m_bitsDamageType; + int m_iDamageCustom; + int m_iDamageStats; + int m_iAmmoType; // AmmoType of the weapon used to cause this damage, if any + int m_iDamagedOtherPlayers; + int m_iPlayerPenetrationCount; + float m_flDamageBonus; // Anything that increases damage (crit) - store the delta + EHANDLE m_hDamageBonusProvider; // Who gave us the ability to do extra damage? + bool m_bForceFriendlyFire; // Ideally this would be a dmg type, but we can't add more + + // AlliedModders - This member only exists after the 2015 SDK update. + float m_flDamageForForce; + + ECritType m_eCritType; + + DECLARE_SIMPLE_DATADESC(); +}; + +//----------------------------------------------------------------------------- +// Purpose: Multi damage. Used to collect multiple damages in the same frame (i.e. shotgun pellets) +//----------------------------------------------------------------------------- +class CMultiDamage : public CTakeDamageInfo +{ + DECLARE_CLASS( CMultiDamage, CTakeDamageInfo ); +public: + CMultiDamage(); + + bool IsClear( void ) { return (m_hTarget == NULL); } + CBaseEntity *GetTarget() const; + void SetTarget( CBaseEntity *pTarget ); + + void Init( CBaseEntity *pTarget, CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, const Vector &damageForce, const Vector &damagePosition, const Vector &reportedPosition, float flDamage, int bitsDamageType, int iKillType ); + +protected: + EHANDLE m_hTarget; + + DECLARE_SIMPLE_DATADESC(); +}; + +extern CMultiDamage g_MultiDamage; + +// Multidamage accessors +void ClearMultiDamage( void ); +void ApplyMultiDamage( void ); +void AddMultiDamage( const CTakeDamageInfo &info, CBaseEntity *pEntity ); + +//----------------------------------------------------------------------------- +// Purpose: Utility functions for physics damage force calculation +//----------------------------------------------------------------------------- +float ImpulseScale( float flTargetMass, float flDesiredSpeed ); +void CalculateExplosiveDamageForce( CTakeDamageInfo *info, const Vector &vecDir, const Vector &vecForceOrigin, float flScale = 1.0 ); +void CalculateBulletDamageForce( CTakeDamageInfo *info, int iBulletType, const Vector &vecBulletDir, const Vector &vecForceOrigin, float flScale = 1.0 ); +void CalculateMeleeDamageForce( CTakeDamageInfo *info, const Vector &vecMeleeDir, const Vector &vecForceOrigin, float flScale = 1.0 ); +void GuessDamageForce( CTakeDamageInfo *info, const Vector &vecForceDir, const Vector &vecForceOrigin, float flScale = 1.0 ); + + +// -------------------------------------------------------------------------------------------------- // +// Inlines. +// -------------------------------------------------------------------------------------------------- // + +inline CBaseEntity* CTakeDamageInfo::GetInflictor() const +{ + return m_hInflictor; +} + + +inline void CTakeDamageInfo::SetInflictor( CBaseEntity *pInflictor ) +{ + m_hInflictor = pInflictor; +} + + +inline CBaseEntity* CTakeDamageInfo::GetAttacker() const +{ + return m_hAttacker; +} + + +inline void CTakeDamageInfo::SetAttacker( CBaseEntity *pAttacker ) +{ + m_hAttacker = pAttacker; +} + +inline CBaseEntity* CTakeDamageInfo::GetWeapon() const +{ + return m_hWeapon; +} + + +inline void CTakeDamageInfo::SetWeapon( CBaseEntity *pWeapon ) +{ + m_hWeapon = pWeapon; +} + + +inline float CTakeDamageInfo::GetDamage() const +{ + return m_flDamage; +} + +inline void CTakeDamageInfo::SetDamage( float flDamage ) +{ + m_flDamage = flDamage; +} + +inline float CTakeDamageInfo::GetMaxDamage() const +{ + return m_flMaxDamage; +} + +inline void CTakeDamageInfo::SetMaxDamage( float flMaxDamage ) +{ + m_flMaxDamage = flMaxDamage; +} + +inline void CTakeDamageInfo::ScaleDamage( float flScaleAmount ) +{ + m_flDamage *= flScaleAmount; +} + +inline void CTakeDamageInfo::AddDamage( float flAddAmount ) +{ + m_flDamage += flAddAmount; +} + +inline void CTakeDamageInfo::SubtractDamage( float flSubtractAmount ) +{ + m_flDamage -= flSubtractAmount; +} + +inline float CTakeDamageInfo::GetDamageBonus() const +{ + return m_flDamageBonus; +} + +inline CBaseEntity *CTakeDamageInfo::GetDamageBonusProvider() const +{ + return m_hDamageBonusProvider; +} + +inline void CTakeDamageInfo::SetDamageBonus( float flBonus, CBaseEntity *pProvider /*= NULL*/ ) +{ + m_flDamageBonus = flBonus; + m_hDamageBonusProvider = pProvider; +} + +inline float CTakeDamageInfo::GetBaseDamage() const +{ + if( BaseDamageIsValid() ) + return m_flBaseDamage; + + // No one ever specified a base damage, so just return damage. + return m_flDamage; +} + +inline bool CTakeDamageInfo::BaseDamageIsValid() const +{ + return (m_flBaseDamage != BASEDAMAGE_NOT_SPECIFIED); +} + +inline Vector CTakeDamageInfo::GetDamageForce() const +{ + return m_vecDamageForce; +} + +inline void CTakeDamageInfo::SetDamageForce( const Vector &damageForce ) +{ + m_vecDamageForce = damageForce; +} + +inline void CTakeDamageInfo::ScaleDamageForce( float flScaleAmount ) +{ + m_vecDamageForce *= flScaleAmount; +} + +inline float CTakeDamageInfo::GetDamageForForceCalc() const +{ + return m_flDamageForForce; +} + +inline void CTakeDamageInfo::SetDamageForForceCalc( float flDamage ) +{ + m_flDamageForForce = flDamage; +} + +inline Vector CTakeDamageInfo::GetDamagePosition() const +{ + return m_vecDamagePosition; +} + + +inline void CTakeDamageInfo::SetDamagePosition( const Vector &damagePosition ) +{ + m_vecDamagePosition = damagePosition; +} + +inline Vector CTakeDamageInfo::GetReportedPosition() const +{ + return m_vecReportedPosition; +} + + +inline void CTakeDamageInfo::SetReportedPosition( const Vector &reportedPosition ) +{ + m_vecReportedPosition = reportedPosition; +} + + +inline void CTakeDamageInfo::SetDamageType( int bitsDamageType ) +{ + m_bitsDamageType = bitsDamageType; +} + +inline int CTakeDamageInfo::GetDamageType() const +{ + return m_bitsDamageType; +} + +inline void CTakeDamageInfo::AddDamageType( int bitsDamageType ) +{ + m_bitsDamageType |= bitsDamageType; +} + +inline int CTakeDamageInfo::GetDamageCustom() const +{ + return m_iDamageCustom; +} + +inline void CTakeDamageInfo::SetDamageCustom( int iDamageCustom ) +{ + m_iDamageCustom = iDamageCustom; +} + +inline int CTakeDamageInfo::GetDamageStats() const +{ + return m_iDamageCustom; +} + +inline void CTakeDamageInfo::SetDamageStats( int iDamageCustom ) +{ + m_iDamageCustom = iDamageCustom; +} + +inline int CTakeDamageInfo::GetAmmoType() const +{ + return m_iAmmoType; +} + +inline void CTakeDamageInfo::SetAmmoType( int iAmmoType ) +{ + m_iAmmoType = iAmmoType; +} + +inline void CTakeDamageInfo::CopyDamageToBaseDamage() +{ + m_flBaseDamage = m_flDamage; +} + +inline void CTakeDamageInfo::SetCritType( ECritType eType ) +{ + if ( eType == CRIT_NONE ) + { + // always let CRIT_NONE override the current setting + m_eCritType = eType; + } + else + { + // don't let CRIT_MINI override CRIT_FULL + m_eCritType = ( eType > m_eCritType ) ? eType : m_eCritType; + } +} + +// -------------------------------------------------------------------------------------------------- // +// Inlines. +// -------------------------------------------------------------------------------------------------- // +inline CBaseEntity *CMultiDamage::GetTarget() const +{ + return m_hTarget; +} + +inline void CMultiDamage::SetTarget( CBaseEntity *pTarget ) +{ + m_hTarget = pTarget; +} + + +#endif // TAKEDAMAGEINFO_H diff --git a/src/stub/baseentity.cpp b/src/stub/baseentity.cpp index ec7e6890..09e4bdbe 100644 --- a/src/stub/baseentity.cpp +++ b/src/stub/baseentity.cpp @@ -84,6 +84,7 @@ MemberFuncThunk< CBaseEntity *, bool, const char *, variant_t * MemberFuncThunk< CBaseEntity *, IPhysicsObject * > CBaseEntity::ft_VPhysicsInitStatic ("CBaseEntity::VPhysicsInitStatic"); MemberFuncThunk< CBaseEntity *, void *,int > CBaseEntity::ft_GetDataObject ("CBaseEntity::GetDataObject"); MemberFuncThunk< CBaseEntity *, int, int > CBaseEntity::ft_SetTransmitState ("CBaseEntity::SetTransmitState"); +MemberFuncThunk< CBaseEntity *, void, const char *, variant_t, CBaseEntity *, CBaseEntity *, float> CBaseEntity::ft_FireNamedOutput ("CBaseEntity::FireNamedOutput"); MemberVFuncThunk< CBaseEntity *, Vector > CBaseEntity::vt_EyePosition (TypeName(), "CBaseEntity::EyePosition"); MemberVFuncThunk< CBaseEntity *, const QAngle& > CBaseEntity::vt_EyeAngles (TypeName(), "CBaseEntity::EyeAngles"); diff --git a/src/stub/baseentity.h b/src/stub/baseentity.h index bb4e443d..a8264771 100644 --- a/src/stub/baseentity.h +++ b/src/stub/baseentity.h @@ -209,7 +209,7 @@ class CBaseEntity : public IServerEntity template bool GetCustomVariableVariant(variant_t &value); bool GetCustomVariableByText(const char *key, variant_t &value); - void SetCustomVariable(const char *key, variant_t &value); + bool SetCustomVariable(const char *key, variant_t &value, bool create = true, bool find = true); // Alert! Custom outputs must be defined in lowercase template @@ -299,6 +299,7 @@ class CBaseEntity : public IServerEntity IPhysicsObject *VPhysicsInitStatic() { return ft_VPhysicsInitStatic (this); } void *GetDataObject(int type) { return ft_GetDataObject (this, type); } int SetTransmitState(int state) { return ft_SetTransmitState (this, state); } + void FireNamedOutput(const char *pszOutput, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller, float flDelay) { return ft_FireNamedOutput (this, pszOutput, variant, pActivator, pCaller, flDelay); } Vector EyePosition() { return vt_EyePosition (this); } const QAngle& EyeAngles() { return vt_EyeAngles (this); } @@ -345,7 +346,7 @@ class CBaseEntity : public IServerEntity void StartTouch(CBaseEntity *entity) { return vt_StartTouch (this, entity); } void EndTouch(CBaseEntity *entity) { return vt_EndTouch (this, entity); } void PostClientActive() { return vt_PostClientActive (this); } - + /* static */ static CBaseEntity *Create(const char *szName, const Vector& vecOrigin, const QAngle& vecAngles, CBaseEntity *pOwner = nullptr) { return ft_Create (szName, vecOrigin, vecAngles, pOwner); } static CBaseEntity *CreateNoSpawn(const char *szName, const Vector& vecOrigin, const QAngle& vecAngles, CBaseEntity *pOwner = nullptr) { return ft_CreateNoSpawn (szName, vecOrigin, vecAngles, pOwner); } @@ -454,6 +455,8 @@ class CBaseEntity : public IServerEntity static MemberFuncThunk< CBaseEntity *, IPhysicsObject *> ft_VPhysicsInitStatic; static MemberFuncThunk< CBaseEntity *, void *,int> ft_GetDataObject; static MemberFuncThunk< CBaseEntity *, int, int> ft_SetTransmitState; + static MemberFuncThunk< CBaseEntity *, void, const char *, variant_t, CBaseEntity *, CBaseEntity *, float> ft_FireNamedOutput; + static MemberVFuncThunk< CBaseEntity *, Vector> vt_EyePosition; static MemberVFuncThunk< CBaseEntity *, const QAngle&> vt_EyeAngles; diff --git a/src/stub/baseplayer.cpp b/src/stub/baseplayer.cpp index c734526e..86f17f3e 100644 --- a/src/stub/baseplayer.cpp +++ b/src/stub/baseplayer.cpp @@ -90,6 +90,7 @@ IMPL_SENDPROP(CPlayerLocalData, CBasePlayer, m_Local, IMPL_SENDPROP(int, CBasePlayer, m_nTickBase, CBasePlayer); IMPL_SENDPROP(float, CBasePlayer, m_flMaxspeed, CBasePlayer); IMPL_SENDPROP(CUtlVector>, CBasePlayer, m_hMyWearables, CBasePlayer); +IMPL_SENDPROP(CHandle, CBasePlayer, m_hObserverTarget, CBasePlayer); MemberFuncThunk CBasePlayer::ft_EyeVectors ("CBasePlayer::EyeVectors"); MemberFuncThunk CBasePlayer::ft_GetSteamID ("CBasePlayer::GetSteamID"); diff --git a/src/stub/baseplayer.h b/src/stub/baseplayer.h index 0485b30d..0fe0bd1c 100644 --- a/src/stub/baseplayer.h +++ b/src/stub/baseplayer.h @@ -152,6 +152,7 @@ class CBasePlayer : public CBaseCombatCharacter DECL_DATAMAP (CHandle, m_hViewEntity); DECL_DATAMAP (CHandle, m_hVehicle); DECL_DATAMAP(int, m_nButtons); + DECL_SENDPROP(CHandle, m_hObserverTarget); private: diff --git a/src/stub/entities.cpp b/src/stub/entities.cpp index 73e7294e..cfdbf047 100644 --- a/src/stub/entities.cpp +++ b/src/stub/entities.cpp @@ -324,4 +324,6 @@ MemberVFuncThunk CBaseServerVehicle::vt_HandlePassengerExit(TypeName(), "CBaseServerVehicle::HandlePassengerExit"); MemberVFuncThunk CBaseServerVehicle::vt_HandlePassengerEntry(TypeName(), "CBaseServerVehicle::HandlePassengerEntry"); MemberVFuncThunk CBaseServerVehicle::vt_GetDriver(TypeName(), "CBaseServerVehicle::GetDriver"); -MemberVFuncThunk CBaseServerVehicle::vt_GetVehicleEnt(TypeName(), "CBaseServerVehicle::GetVehicleEnt"); \ No newline at end of file +MemberVFuncThunk CBaseServerVehicle::vt_GetVehicleEnt(TypeName(), "CBaseServerVehicle::GetVehicleEnt"); + +GlobalThunk g_WorldEntity("g_WorldEntity"); \ No newline at end of file diff --git a/src/stub/entities.h b/src/stub/entities.h index 44bb20df..6e08e288 100644 --- a/src/stub/entities.h +++ b/src/stub/entities.h @@ -582,6 +582,18 @@ class CPropVehicleDriveable : public CPropVehicle }; + +class CMathCounter : public CLogicalEntity +{ + +}; + +extern GlobalThunk g_WorldEntity; + +inline CBaseEntity* GetWorldEntity() { + return g_WorldEntity; +} + // 20151007a // CTFPlayer::Event_Killed diff --git a/src/stub/extraentitydata.h b/src/stub/extraentitydata.h index 7656e7ac..b55c4c17 100644 --- a/src/stub/extraentitydata.h +++ b/src/stub/extraentitydata.h @@ -480,20 +480,24 @@ inline bool CBaseEntity::GetCustomVariableByText(const char *key, variant_t &val return false; } -inline void CBaseEntity::SetCustomVariable(const char *key, variant_t &value) +inline bool CBaseEntity::SetCustomVariable(const char *key, variant_t &value, bool create, bool find) { auto &list = GetExtraData(this)->GetCustomVariables(); bool found = false; - for (auto &var : list) { - if (STRING(var.key) == key || stricmp(key, STRING(var.key)) == 0) { - var.value = value; - found = true; - break; + if (find) { + for (auto &var : list) { + if (STRING(var.key) == key || stricmp(key, STRING(var.key)) == 0) { + var.value = value; + found = true; + return true; + } } } - if (!found) { + if (!found && create) { list.emplace_back(key, value); + return true; } + return false; } inline void CBaseEntity::AddCustomOutput(const char *key, const char *value) diff --git a/src/stub/misc.cpp b/src/stub/misc.cpp index cdc5e48b..0db87daf 100644 --- a/src/stub/misc.cpp +++ b/src/stub/misc.cpp @@ -42,6 +42,10 @@ CTakeDamageInfo::CTakeDamageInfo(CBaseEntity *pInflictor, CBaseEntity *pAttacker ft_CTakeDamageInfo_ctor5(this, pInflictor, pAttacker, pWeapon, damageForce, damagePosition, flDamage, bitsDamageType, iKillType, reportedPosition); } +CTakeDamageInfo::CTakeDamageInfo() +{ + ft_CTakeDamageInfo_ctor5(this, nullptr, nullptr, nullptr, vec3_origin, vec3_origin, 0, 0, 0, nullptr); +} static MemberFuncThunk ft_CTraceFilterSimple_ctor("CTraceFilterSimple::CTraceFilterSimple [C1]"); CTraceFilterSimple::CTraceFilterSimple(const IHandleEntity *passedict, int collisionGroup, ShouldHitFunc_t pExtraShouldHitFunc) @@ -99,10 +103,23 @@ static StaticFuncThunk ft_VariantSet("variant_t::Set"); -void variant_t::Set(fieldtype_t type, void *data) { ft_VariantSet(this, type, data); } +void variant_t::Set(fieldtype_t type, void *data) { + if (type == FIELD_CUSTOM) { + iVal = *(int *)data; + fieldType = FIELD_CUSTOM; + return; + } + ft_VariantSet(this, type, data); } static MemberFuncThunk ft_VariantSetOther("variant_t::SetOther"); -void variant_t::SetOther(void *data) { ft_VariantSetOther(this, data); } +void variant_t::SetOther(void *data) { + + if (this->fieldType == FIELD_CUSTOM) { + *((int *)data) = iVal; + return; + } + + ft_VariantSetOther(this, data); } void variant_t::SetEntity( CBaseEntity *val ) { @@ -129,6 +146,16 @@ bool variant_t::Convert(fieldtype_t newType) { case FIELD_FLOAT: SetFloat( (float) iVal ); return true; case FIELD_CHARACTER: case FIELD_SHORT: case FIELD_INTEGER: fieldType = newType; return true; case FIELD_BOOLEAN: SetBool( iVal != 0 ); return true; + case FIELD_EHANDLE: case FIELD_CLASSPTR: { + + CBaseEntity *ent = nullptr; + if ( iszVal != NULL_STRING ) { + ent = CHandle::FromIndex(iVal); + } + SetEntity(ent); + fieldType = newType; + return true; + } } break; } @@ -182,10 +209,8 @@ bool variant_t::Convert(fieldtype_t newType) { } case FIELD_EHANDLE: case FIELD_CLASSPTR: { - // convert the string to an entity by locating it by classname CBaseEntity *ent = nullptr; if ( iszVal != NULL_STRING ) { - // FIXME: do we need to pass an activator in here? ent = servertools->FindEntityByName(nullptr, STRING(iszVal)); } SetEntity(ent); @@ -209,6 +234,14 @@ bool variant_t::Convert(fieldtype_t newType) { } return true; } + + case FIELD_INTEGER: { + // take the entities targetname as the string + if ( eVal != nullptr ) { + SetInt( eVal.ToInt() ); + } + return true; + } } break; } diff --git a/src/stub/tfbot.cpp b/src/stub/tfbot.cpp index 67393f56..b92c2406 100644 --- a/src/stub/tfbot.cpp +++ b/src/stub/tfbot.cpp @@ -74,11 +74,16 @@ struct CExtract_CTFBot_m_nMission : public IExtract #if defined _LINUX static constexpr uint8_t s_Buf_CTFBot_m_Tags[] = { - 0x55, // +0000 push ebp + /*0x55, // +0000 push ebp 0x89, 0xe5, // +0001 mov ebp,esp 0x53, // +0003 push ebx 0x8b, 0x5d, 0x08, // +0004 mov ebx,[ebp+this] - 0x8b, 0x83, 0xd8, 0x2b, 0x00, 0x00, // +0007 mov eax,[ebx+0xVVVVVVVV] + 0x8b, 0x83, 0xd8, 0x2b, 0x00, 0x00, // +0007 mov eax,[ebx+0xVVVVVVVV] */ + 0x55, + 0x89, 0xE5, + 0x8B, 0x45, 0x08, + 0xC7, 0x80, 0x90, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; struct CExtract_CTFBot_m_Tags : public IExtract *> @@ -91,7 +96,7 @@ struct CExtract_CTFBot_m_Tags : public IExtract *> { buf.CopyFrom(s_Buf_CTFBot_m_Tags); - mask.SetRange(0x07 + 2, 4, 0x00); + mask.SetRange(0x06 + 2, 4, 0x00); return true; } @@ -99,7 +104,7 @@ struct CExtract_CTFBot_m_Tags : public IExtract *> virtual const char *GetFuncName() const override { return "CTFBot::ClearTags"; } virtual uint32_t GetFuncOffMin() const override { return 0x0000; } virtual uint32_t GetFuncOffMax() const override { return 0x0000; } - virtual uint32_t GetExtractOffset() const override { return 0x0007 + 2; } + virtual uint32_t GetExtractOffset() const override { return 0x0006 + 2; } virtual T AdjustValue(T val) const { return reinterpret_cast((uintptr_t)val - 0xc); } }; @@ -197,9 +202,9 @@ MemberFuncThunk CTFBot::SuspectedSpyInfo_t:: IMPL_EXTRACT(CTFBot::MissionType, CTFBot, m_nMission, new CExtract_CTFBot_m_nMission()); #if !defined _WINDOWS -#if TOOLCHAIN_FIXES +//#if TOOLCHAIN_FIXES IMPL_EXTRACT(CUtlVector, CTFBot, m_Tags, new CExtract_CTFBot_m_Tags()); -#endif +//#endif IMPL_EXTRACT(CTFBot::AttributeType, CTFBot, m_nBotAttrs, new CExtract_CTFBot_m_nBotAttrs()); IMPL_RELATIVE(CTFBot::WeaponRestriction, CTFBot, m_iWeaponRestrictionFlags, m_nBotAttrs, -0x04); IMPL_RELATIVE(CHandle, CTFBot, m_enemySentry, m_nBotAttrs, 0x20); diff --git a/src/stub/tfbot.h b/src/stub/tfbot.h index 084352b6..df3dc86c 100644 --- a/src/stub/tfbot.h +++ b/src/stub/tfbot.h @@ -372,9 +372,9 @@ class CTFBot : public NextBotPlayer } #endif -#if TOOLCHAIN_FIXES +//#if TOOLCHAIN_FIXES DECL_EXTRACT(CUtlVector, m_Tags); -#endif +//#endif DECL_EXTRACT(AttributeType, m_nBotAttrs); DECL_RELATIVE(WeaponRestriction, m_iWeaponRestrictionFlags); DECL_RELATIVE(CHandle, m_enemySentry); diff --git a/src/stub/tfplayer.cpp b/src/stub/tfplayer.cpp index 448db0ad..6f1dbb2a 100644 --- a/src/stub/tfplayer.cpp +++ b/src/stub/tfplayer.cpp @@ -393,26 +393,32 @@ CEconItemView *CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot(CTFPlayer *pla return ft_GetEconItemViewByLoadoutSlot(player, slot, ent); } -CEconEntity *GiveItemByName(CTFPlayer *player, const char *item_name, bool no_remove, bool force_give) +CEconEntity *CreateItemByName(CTFPlayer *player, const char *item_name) { auto item_def = GetItemSchema()->GetItemDefinitionByName(item_name); if (item_def != nullptr) { const char *classname = TranslateWeaponEntForClass_improved(item_def->GetItemClass(), player->GetPlayerClass()->GetClassIndex()); CEconEntity *entity = static_cast(ItemGeneration()->SpawnItem(item_def->m_iItemDefIndex, player->WorldSpaceCenter(), vec3_angle, 1, 6, classname)); - DispatchSpawn(entity); if (entity != nullptr) { Mod::Pop::PopMgr_Extensions::AddCustomWeaponAttributes(item_name, entity->GetItem()); - if (!GiveItemToPlayer(player, entity, no_remove, force_give, item_name)) { - entity->Remove(); - entity = nullptr; - } + DispatchSpawn(entity); } return entity; } return nullptr; } +CEconEntity *GiveItemByName(CTFPlayer *player, const char *item_name, bool no_remove, bool force_give) +{ + auto entity = CreateItemByName(player, item_name); + if (entity != nullptr && !GiveItemToPlayer(player, entity, no_remove, force_give, item_name)) { + entity->Remove(); + entity = nullptr; + } + return entity; +} + bool GiveItemToPlayer(CTFPlayer *player, CEconEntity *entity, bool no_remove, bool force_give, const char *item_name) { if (entity == nullptr) diff --git a/src/stub/tfplayer.h b/src/stub/tfplayer.h index deaee8d5..e88c54b4 100644 --- a/src/stub/tfplayer.h +++ b/src/stub/tfplayer.h @@ -576,6 +576,7 @@ template<> inline int CollectPlayers(CUtlVector *playerV extern StaticFuncThunk ft_TE_PlayerAnimEvent; inline void TE_PlayerAnimEvent(CBasePlayer *player, int anim, int data) { ft_TE_PlayerAnimEvent(player, anim, data); } +CEconEntity *CreateItemByName(CTFPlayer *player, const char *item_name); CEconEntity *GiveItemByName(CTFPlayer *player, const char *item_name, bool no_remove = false, bool force_give = true); bool GiveItemToPlayer(CTFPlayer *player, CEconEntity *entity, bool no_remove, bool force_give, const char *item_name); diff --git a/src/stub/trace.cpp b/src/stub/trace.cpp index d460039a..fa67b82e 100644 --- a/src/stub/trace.cpp +++ b/src/stub/trace.cpp @@ -1,5 +1,6 @@ #include "stub/trace.h" #include "stub/gamerules.h" +#include "stub/entities.h" CFlaggedEntitiesEnum::CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask ) @@ -205,4 +206,14 @@ bool CTraceFilterSkipTwoEntities::ShouldHitEntity( IHandleEntity *pHandleEntity, return false; return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); +} + +bool CGameTrace::DidHitWorld() const +{ + return m_pEnt == GetWorldEntity(); +} + +bool CGameTrace::DidHitNonWorldEntity() const +{ + return m_pEnt != NULL && !DidHitWorld(); } \ No newline at end of file diff --git a/src/util/admin.cpp b/src/util/admin.cpp index dd63bf08..55888ad1 100644 --- a/src/util/admin.cpp +++ b/src/util/admin.cpp @@ -114,6 +114,24 @@ void SendConsoleMessageToAdmins(const char *fmt, ...) vsnprintf(buf, sizeof(buf), fmt, arglist); va_end(arglist); + for (int i = 1; i <= gpGlobals->maxClients; ++i) { + CBasePlayer *player = UTIL_PlayerByIndex(i); + if (player == nullptr) continue; + if (player->IsFakeClient()) continue; + if (!PlayerIsSMAdmin(player)) continue; + engine->ClientPrintf(player->edict(), buf); + } +} + +void SendWarningConsoleMessageToAdmins(const char *fmt, ...) +{ + va_list arglist; + va_start(arglist, fmt); + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, arglist); + va_end(arglist); + Warning("%s", buf); + for (int i = 1; i <= gpGlobals->maxClients; ++i) { CBasePlayer *player = UTIL_PlayerByIndex(i); if (player == nullptr) continue; diff --git a/src/util/admin.h b/src/util/admin.h index 051e4afe..4612a0b6 100644 --- a/src/util/admin.h +++ b/src/util/admin.h @@ -23,5 +23,6 @@ bool PlayerHasSMAdminFlags_All(CBasePlayer *player, FlagBits flag_mask); bool PlayerHasSMAdminFlags_Any(CBasePlayer *player, FlagBits flag_mask); void SendConsoleMessageToAdmins(const char *fmt, ...); +void SendWarningConsoleMessageToAdmins(const char *fmt, ...); #endif diff --git a/src/util/lua.cpp b/src/util/lua.cpp new file mode 100644 index 00000000..4c6f5866 --- /dev/null +++ b/src/util/lua.cpp @@ -0,0 +1,3016 @@ + +#include "util/misc.h" +#include "stub/misc.h" +#include "stub/baseentity.h" +#include "stub/econ.h" +#include "stub/tfweaponbase.h" +#include "mod/pop/common.h" +#include "util/iterate.h" +#include "util/lua.h" +#include "util/admin.h" +#include "util/clientmsg.h" +#include "mod/etc/mapentity_additions.h" +#include "mod.h" + +namespace Util::Lua +{ + LuaState *cur_state = nullptr; + + int vector_meta = LUA_NOREF; + const void *vector_meta_ptr = nullptr; + + int entity_meta = LUA_NOREF; + const void *entity_meta_ptr = nullptr; + + int prop_meta = LUA_NOREF; + int entity_table_store = LUA_NOREF; + + static void stackDump (lua_State *L) { + int i; + int top = lua_gettop(L); + for (i = 1; i <= top; i++) { /* repeat for each level */ + int t = lua_type(L, i); + printf("%d: ", i); + switch (t) { + + case LUA_TSTRING: /* strings */ + printf("`%s'", lua_tostring(L, i)); + break; + + case LUA_TBOOLEAN: /* booleans */ + printf(lua_toboolean(L, i) ? "true" : "false"); + break; + + case LUA_TNUMBER: /* numbers */ + printf("%g", lua_tonumber(L, i)); + break; + + default: /* other values */ + printf("%s", lua_typename(L, t)); + break; + + } + printf("\n"); /* put a separator */ + } + printf("\n"); /* end the listing */ + } + + int LPrint(lua_State *l) + { + int args = lua_gettop(l); + std::string str = ""; + for (int i = 1; i <= args; i++) { + const char *msg = nullptr; + auto type = lua_type(l, i); + if (type == LUA_TSTRING || type == LUA_TNUMBER) { + msg = lua_tostring(l, i); + } + else { + lua_getglobal(l, "tostring"); + //lua_pushvalue(l, -1); + lua_pushvalue(l,i); + lua_pcall(l, 1, -2, 0); + msg = lua_tostring(l, -1); + } + str += msg != nullptr ? msg : ""; + str += '\t'; + } + SendConsoleMessageToAdmins("%s\n", str.c_str()); + Msg("%s\n", str.c_str()); + return 0; + } + + inline QAngle *LAngleAlloc(lua_State *l) + { + QAngle *angles = (QAngle *)lua_newuserdata(l, sizeof(Vector)); + lua_rawgeti(l, LUA_REGISTRYINDEX, vector_meta); + lua_setmetatable(l, -2); + return angles; + } + + inline Vector *LVectorAlloc(lua_State *l) + { + Vector *vec = (Vector *)lua_newuserdata(l, sizeof(Vector)); + lua_rawgeti(l, LUA_REGISTRYINDEX, vector_meta); + lua_setmetatable(l, -2); + return vec; + } + + int LVectorNew(lua_State *l) + { + if (lua_type(l, 1) == LUA_TSTRING) { + auto vec = LVectorAlloc(l); + UTIL_StringToVectorAlt(*vec, lua_tostring(l, 1)); + } + else { + float x = luaL_checknumber(l, 1); + float y = luaL_checknumber(l, 2); + float z = luaL_checknumber(l, 3); + + LVectorAlloc(l)->Init(x, y, z); + } + return 1; + } + + // Faster version of userdata check that compares metadata pointers + inline void *LGetUserdata(lua_State *l, int index, const void *metatablePtr) + { + if (lua_getmetatable(l, index)) { + auto ourMetatablePtr = lua_topointer(l, -1); + lua_pop(l,1); + if (metatablePtr == ourMetatablePtr) { + void *ud = lua_touserdata(l, index); + if (ud != nullptr) { + return ud; + } + } + } + return nullptr; + } + + // Faster version of userdata check that compares metadata pointers + inline void *LCheckUserdata(lua_State *l, int index, const void *metatablePtr, const char *name) + { + auto ud = LGetUserdata(l, index, metatablePtr); + if (ud == nullptr) { + luaL_error(l, "Invalid argument %d: expected '%s'", index, name); + } + return ud; + } + + inline Vector *LVectorGetCheck(lua_State *l, int index) + { + return (Vector *)LCheckUserdata(l, index, vector_meta_ptr, "vector"); + } + + inline Vector *LVectorGetNoCheck(lua_State *l, int index) + { + return (Vector *)LGetUserdata(l, index, vector_meta_ptr); + } + + inline QAngle *LAngleGetCheck(lua_State *l, int index) + { + return (QAngle *)LCheckUserdata(l, index, vector_meta_ptr, "vector"); + } + + inline QAngle *LAngleGetNoCheck(lua_State *l, int index) + { + return (QAngle *)LGetUserdata(l, index, vector_meta_ptr); + } + + int LVectorGet(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + int index = luaL_checkinteger(l, 2); + + luaL_argcheck(l, index >= 1 && index <= 3, 2, "index out of 1-3 range"); + + lua_pushnumber(l, (*vec)[index - 1]); + return 1; + } + + int LVectorGetX(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + lua_pushnumber(l, vec->x); + return 1; + } + + int LVectorGetY(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + lua_pushnumber(l, vec->y); + return 1; + } + + int LVectorGetZ(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + lua_pushnumber(l, vec->z); + return 1; + } + + int LVectorSet(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + int index = luaL_checkinteger(l, 2); + float value = luaL_checknumber(l, 3); + + luaL_argcheck(l, index >= 1 && index <= 3, 2, "index out of 1-3 range"); + + (*vec)[index - 1] = value; + return 0; + } + + int LVectorSetX(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + float value = luaL_checknumber(l, 2); + + vec->x = value; + return 0; + } + + int LVectorSetY(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + float value = luaL_checknumber(l, 2); + + vec->x = value; + return 0; + } + + int LVectorSetZ(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + float value = luaL_checknumber(l, 2); + + vec->x = value; + return 0; + } + + int LVectorIndex(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + int index = -1; + + // Read index from offset + if (lua_isnumber(l, 2)) { + index = luaL_checkinteger(l, 2); + luaL_argcheck(l, index >= 1 && index <= 3, 2, "index out of 1-3 range"); + } + // Read index from x/y/z + else { + const char *str = luaL_checkstring(l, 2); + if (str != nullptr && str[0] != '\0' && str[1] == '\0') { + switch(str[0]) { + case 'x': index = 1; break; + case 'y': index = 2; break; + case 'z': index = 3; break; + } + } + } + if (index != -1) { + lua_pushnumber(l, (*vec)[index - 1]); + } + // Get function + else { + lua_getmetatable(l, 1); + lua_pushvalue(l, 2); + lua_rawget(l, 3); + } + return 1; + } + + int LVectorSetIndex(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + int index = -1; + if (lua_isnumber(l, 2)) { + index = luaL_checkinteger(l, 2); + luaL_argcheck(l, index >= 1 && index <= 3, 2, "index out of 1-3 range"); + } + else { + const char *str = luaL_checkstring(l, 2); + if (str != nullptr && str[0] != '\0' && str[1] == '\0') { + switch(str[0]) { + case 'x': index = 1; break; + case 'y': index = 2; break; + case 'z': index = 3; break; + } + } + } + if (index != -1) { + float value = luaL_checknumber(l, 3); + + (*vec)[index - 1] = value; + } + return 0; + } + + int LVectorLength(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + + lua_pushnumber(l, vec->Length()); + return 1; + } + + int LVectorEquals(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *vec2 = LVectorGetCheck(l, 2); + + lua_pushboolean(l, vec == vec2); + return 1; + } + + int LVectorDistance(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *vec2 = LVectorGetCheck(l, 2); + + lua_pushnumber(l, vec->DistTo(*vec2)); + return 1; + } + + int LVectorDot(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *vec2 = LVectorGetCheck(l, 2); + + lua_pushnumber(l, vec->Dot(*vec2)); + return 1; + } + + int LVectorCross(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *vec2 = LVectorGetCheck(l, 2); + + Vector *out = LVectorAlloc(l); + CrossProduct(*vec, *vec2, *out); + return 1; + } + + int LVectorRotate(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + QAngle *angle = LAngleGetCheck(l, 2); + + Vector *out = LVectorAlloc(l); + VectorRotate(*vec, *angle, *out); + return 1; + } + + int LVectorToAngles(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + + QAngle *out = LAngleAlloc(l); + VectorAngles(*vec, *out); + return 1; + } + + int LVectorNormalize(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + + Vector *out = LVectorAlloc(l); + *out = *vec; + VectorNormalize(*out); + return 1; + } + + int LVectorGetForward(lua_State *l) + { + QAngle *angle = LAngleGetCheck(l, 1); + + Vector *out1 = LVectorAlloc(l); + AngleVectors(*angle, out1); + return 1; + } + + int LVectorGetAngleVectors(lua_State *l) + { + QAngle *angle = LAngleGetCheck(l, 1); + + Vector *out1 = LVectorAlloc(l); + Vector *out2 = LVectorAlloc(l); + Vector *out3 = LVectorAlloc(l); + AngleVectors(*angle, out1, out2, out3); + return 3; + } + + int LVectorAdd(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *out = LVectorAlloc(l); + if (lua_isnumber(l, 2)) { + float num = lua_tonumber(l, 2); + out->Init(vec->x+num,vec->y+num,vec->z+num); + } + else { + Vector *vec2 = LVectorGetCheck(l, 2); + *out = *vec + *vec2; + } + return 1; + } + + int LVectorSubtract(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *out = LVectorAlloc(l); + if (lua_isnumber(l, 2)) { + float num = lua_tonumber(l, 2); + out->Init(vec->x-num,vec->y-num,vec->z-num); + } + else { + Vector *vec2 = LVectorGetCheck(l, 2); + *out = *vec - *vec2; + } + return 1; + } + + int LVectorMultiply(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *out = LVectorAlloc(l); + if (lua_isnumber(l, 2)) { + float num = lua_tonumber(l, 2); + out->Init(vec->x*num,vec->y*num,vec->z*num); + } + else { + Vector *vec2 = LVectorGetCheck(l, 2); + *out = *vec * *vec2; + } + return 1; + } + + int LVectorDivide(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *out = LVectorAlloc(l); + if (lua_isnumber(l, 2)) { + float num = lua_tonumber(l, 2); + out->Init(vec->x/num,vec->y/num,vec->z/num); + } + else { + Vector *vec2 = LVectorGetCheck(l, 2); + *out = *vec / *vec2; + } + return 1; + } + + int LVectorCopy(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + Vector *vec2 = LVectorGetCheck(l, 2); + *vec = *vec2; + return 0; + } + + int LVectorCopyUnpacked(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + float x = luaL_checknumber(l, 2); + float y = luaL_checknumber(l, 3); + float z = luaL_checknumber(l, 4); + + LVectorAlloc(l)->Init(x, y, z); + return 0; + } + + int LVectorToString(lua_State *l) + { + Vector *vec = LVectorGetCheck(l, 1); + + lua_pushfstring(l, "%f %f %f", vec->x, vec->y, vec->z); + return 1; + } + + inline const EHANDLE *LEntityAlloc(lua_State *l, const EHANDLE oldhandle) + { + EHANDLE *handle = (EHANDLE *)lua_newuserdata(l, sizeof(EHANDLE)); + *handle = oldhandle; + //luaL_getmetatable(l, "entity"); + lua_rawgeti(l, LUA_REGISTRYINDEX, entity_meta); + lua_setmetatable(l, -2); + return handle; + } + + const EHANDLE invalid_handle; + //!! Returns invalid handle ref if entity is null + const EHANDLE *LEntityAlloc(lua_State *l, CBaseEntity *entity) + { + if (entity == nullptr) { + lua_pushnil(l); + return &invalid_handle; + } + EHANDLE *handle = (EHANDLE *)lua_newuserdata(l, sizeof(EHANDLE)); + handle->Set(entity); + //luaL_getmetatable(l, "entity"); + lua_rawgeti(l, LUA_REGISTRYINDEX, entity_meta); + lua_setmetatable(l, -2); + return handle; + } + + inline CBaseEntity *LEntityFindByIndex(int index) + { + if (index > 2048) + return EHANDLE::FromIndex(index); + else + return UTIL_EntityByIndex(index); + } + + int LEntityNew(lua_State *l) + { + if (lua_type(l, 1) == LUA_TNUMBER) { + auto entity = LEntityFindByIndex(lua_tointeger(l, 1)); + auto handle = LEntityAlloc(l,entity); + } + else if (lua_isstring(l, 1)) { + auto entity = CreateEntityByName(lua_tostring(l, 1)); + auto handle = LEntityAlloc(l,entity); + if (entity != nullptr && (lua_gettop(l) < 2 || lua_toboolean(l,2)) ) { + DispatchSpawn(entity); + } + if (entity != nullptr && (lua_gettop(l) < 3 || lua_toboolean(l,3)) ) { + entity->Activate(); + } + } + else { + luaL_error(l, "Expected classname string or entity index"); + } + return 1; + } + + // Returns entity handle, throws an error if variable is not nil or entity + inline const EHANDLE *LEntityGetCheck(lua_State *l, int index) + { + if (lua_type(l, index) == LUA_TNIL ) return &invalid_handle; + + return (const EHANDLE *)LCheckUserdata(l, index, entity_meta_ptr, "entity"); + } + + // Returns entity, or null if the variable is not a valid entity + inline CBaseEntity *LEntityGetOptional(lua_State *l, int index) + { + auto *handle = (const EHANDLE *)LGetUserdata(l, index, entity_meta_ptr); + + return handle != nullptr ? handle->Get() : nullptr; + } + + // Returns entity and throws an error if variable is not an entity or the entity is invalid + inline CBaseEntity *LEntityGetNonNull(lua_State *l, int index) + { + auto ud = (const EHANDLE *)LCheckUserdata(l, index, entity_meta_ptr, "entity"); + auto entity = ud->Get(); + luaL_argcheck(l, entity != nullptr, index, "entity handle is invalid"); + return entity; + } + + // Returns player entity and throws an error if variable is not an entity, the entity is invalid or not a player + CTFPlayer *LPlayerGetNonNull(lua_State *l, int index) + { + auto ud = (const EHANDLE *)LCheckUserdata(l, index, entity_meta_ptr, "entity"); + auto entity = ud->Get(); + luaL_argcheck(l, entity != nullptr, index, "entity handle is invalid"); + auto player = ToTFPlayer(entity); + luaL_argcheck(l, player != nullptr, index, "entity is not a player"); + return player; + } + + + int LEntityEquals(lua_State *l) + { + auto *handlel = LEntityGetCheck(l, 1); + auto *handler = LEntityGetCheck(l, 2); + + lua_pushboolean(l, *handlel == *handler || handlel->Get() == handler->Get()); + return 1; + } + + int LIsValid(lua_State *l) + { + lua_pushboolean(l, LEntityGetOptional(l, 1) != nullptr); + return 1; + } + + int LEntityIsValid(lua_State *l) + { + auto *handle = LEntityGetCheck(l, 1); + + lua_pushboolean(l, handle->Get() != nullptr); + return 1; + } + + int LEntityGetHandleIndex(lua_State *l) + { + auto *handle = LEntityGetCheck(l, 1); + + lua_pushinteger(l, handle->ToInt()); + return 1; + } + + int LEntityGetNetworkIndex(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + + lua_pushinteger(l, entity->edict() != nullptr ? entity->entindex() : -1); + return 1; + } + + int LEntitySpawn(lua_State *l) + { + servertools->DispatchSpawn(LEntityGetNonNull(l, 1)); + return 0; + } + + int LEntityActivate(lua_State *l) + { + LEntityGetNonNull(l, 1)->Activate(); + return 0; + } + + int LEntityRemove(lua_State *l) + { + LEntityGetNonNull(l, 1)->Remove(); + return 0; + } + + int LEntityGetName(lua_State *l) + { + lua_pushstring(l, STRING(LEntityGetNonNull(l, 1)->GetEntityName())); + return 1; + } + + int LEntitySetName(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + char nameLower[1024]; + StrLowerCopy(name, nameLower); + + entity->SetName(AllocPooledString(nameLower)); + return 0; + } + + int LEntityGetPlayerName(lua_State *l) + { + auto entity = LPlayerGetNonNull(l, 1); + lua_pushstring(l, entity->GetPlayerName()); + return 1; + } + + int LEntityIsAlive(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->IsAlive()); + return 1; + } + + int LEntityIsPlayer(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->IsPlayer()); + return 1; + } + + int LEntityIsNPC(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && !entity->IsPlayer() && entity->MyNextBotPointer() != nullptr); + return 1; + } + + int LEntityIsBot(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, ToTFBot(entity) != nullptr); + return 1; + } + + int LEntityIsRealPlayer(lua_State *l) + { + auto entity = ToTFPlayer(LEntityGetOptional(l, 1)); + lua_pushboolean(l, entity != nullptr && !entity->IsFakeClient() && !entity->IsHLTV()); + return 1; + } + + int LEntityIsWeapon(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->IsBaseCombatWeapon()); + return 1; + } + + int LEntityIsObject(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->IsBaseObject()); + return 1; + } + + int LEntityIsCombatCharacter(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->MyCombatCharacterPointer() != nullptr); + return 1; + } + + int LEntityIsWearable(lua_State *l) + { + auto entity = LEntityGetOptional(l, 1); + lua_pushboolean(l, entity != nullptr && entity->IsWearable()); + return 1; + } + + int LEntityGetClassname(lua_State *l) + { + auto *handle = LEntityGetCheck(l, 1); + auto entity = handle->Get(); + lua_pushstring(l, entity->GetClassname()); + return 1; + } + + enum CallbackType + { + ON_REMOVE, + ON_SPAWN, + ON_ACTIVATE, + ON_DAMAGE_RECEIVED_PRE, + ON_DAMAGE_RECEIVED_POST, + ON_INPUT, + ON_OUTPUT, + ON_KEY_PRESSED, + ON_KEY_RELEASED, + ON_DEATH, + CALLBACK_TYPE_COUNT + }; + + class EntityCallback + { + public: + EntityCallback(LuaState *state, int func) : state(state), func(func) {}; + + ~EntityCallback() { + if (state != nullptr) { + luaL_unref(state->GetState(), LUA_REGISTRYINDEX, func); + } + }; + LuaState *state; + int func; + }; + + struct EntityTableStorageEntry + { + LuaState *state; + int value; + }; + + class LuaEntityModule : public EntityModule + { + public: + LuaEntityModule(CBaseEntity *entity) : EntityModule(entity), owner(entity) {}; + + virtual ~LuaEntityModule() { + if (owner != nullptr && owner->IsMarkedForDeletion()) { + FireCallback(ON_REMOVE); + } + for (auto state : states) { + state->EntityDeleted(owner); + } + //for (auto &entry : tableStore) { + // luaL_unref(entry.state->GetState(), LUA_REGISTRYINDEX, entry.value); + //} + } + + // LuaState removed, remove associated callbacks + void LuaStateRemoved(LuaState *state) { + for (int callbackType = 0; callbackType < CALLBACK_TYPE_COUNT; callbackType++) { + RemoveIf(callbacks[callbackType], [&](auto &pair) { + if (pair.state == state) { + + // Set to null to prevent destructor trying to unref function + pair.state = nullptr; + return true; + } + return false; + }); + } + states.erase(state); + //std::remove_if(tableStore.begin(), tableStore.end(), [&](auto &entry) { + // return entry.state == state; + //}); + } + + void AddCallback(CallbackType type, LuaState *state, int func) { + if (type < 0 && type >= CALLBACK_TYPE_COUNT) return; + + callbacks[type].emplace_back(state, func); + states.insert(state); + state->EntityCallbackAdded(owner); + } + + void RemoveCallback(CallbackType type, LuaState *state, int func) { + RemoveFirstElement(callbacks[type], [&](auto &pair) { + return pair.state == state && func == pair.func; + }); + } + + // bool RemoveTableStorageEntry(LuaState *state, int value) { + // if (tableStore.empty()) return false; + + // if (RemoveFirstElement(tableStore, [&](auto &entry){ + // return entry.state == state && entry.value == value; + // })) { + // luaL_unref(state->GetState(), LUA_REGISTRYINDEX, value); + // } + + // return false; + // } + + // void SetTableStorageEntry(LuaState *state, int value) { + // for(auto &entry : tableStore) { + // if (entry.state == state && entry.value == value) { + // return; + // } + // } + // tableStore.push_back({state, value}); + // } + + void FireCallback(CallbackType type, std::function *extrafunc = nullptr, int extraargs = 0, std::function *extraretfunc = nullptr, int ret = 0) { + for (auto &pair : callbacks[type]) { + auto l = pair.state->GetState(); + lua_rawgeti(l, LUA_REGISTRYINDEX, pair.func); + LEntityAlloc(l, owner); + if (extrafunc != nullptr) + (*extrafunc)(pair.state); + pair.state->Call(1 + extraargs, ret); + if (extraretfunc != nullptr) + (*extraretfunc)(pair.state); + + lua_settop(l, 0); + } + } + + std::vector callbacks[CALLBACK_TYPE_COUNT]; + std::unordered_set states; + CBaseEntity *owner; + //std::vector tableStore; + }; + + int LEntityAddCallback(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + int type = luaL_checkinteger(l, 2); + luaL_argcheck(l, type >= 0 && type < CALLBACK_TYPE_COUNT, 2, "type out of range"); + luaL_checktype(l, 3, LUA_TFUNCTION); + int func = luaL_ref(l, LUA_REGISTRYINDEX); + + entity->GetOrCreateEntityModule("luaentity")->AddCallback((CallbackType)type, cur_state, func); + lua_pushinteger(l, func); + return 1; + } + + int LEntityRemoveCallback(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + int type = luaL_checkinteger(l, 2); + luaL_argcheck(l, type >= 0 && type < CALLBACK_TYPE_COUNT, 2, "type out of range"); + int func = luaL_checkinteger(l, 3); + + auto module = entity->GetEntityModule("luaentity"); + if (module != nullptr) { + module->RemoveCallback((CallbackType)type, cur_state, func); + } + return 0; + } + + void LFromVariant(lua_State *l, variant_t &variant) + { + auto fieldType = variant.FieldType(); + switch (fieldType) { + case FIELD_VOID: lua_pushnil(l); return; + case FIELD_BOOLEAN: lua_pushboolean(l,variant.Bool()); return; + case FIELD_CHARACTER: case FIELD_SHORT: case FIELD_INTEGER: variant.Convert(FIELD_INTEGER); lua_pushinteger(l,variant.Int()); return; + case FIELD_FLOAT: lua_pushnumber(l,variant.Float()); return; + case FIELD_STRING: lua_pushstring(l,variant.String()); return; + /*case FIELD_CUSTOM: { + int ref; + variant.SetOther(&ref); + lua_rawgeti(l, LUA_REGISTRYINDEX, ref); + return; + }*/ + case FIELD_POSITION_VECTOR: case FIELD_VECTOR: { + variant.Convert(FIELD_VECTOR); + auto vec = LVectorAlloc(l); + variant.Vector3D(*vec); + return; + } + case FIELD_EHANDLE: case FIELD_CLASSPTR: variant.Convert(FIELD_EHANDLE); LEntityAlloc(l, variant.Entity()); return; + default: lua_pushnil(l); return; + } + + } + + void LToVariant(lua_State *l, int index, variant_t &variant) + { + int type = lua_type(l, index); + if (type == LUA_TNIL) { + + } + else if (type == LUA_TBOOLEAN) { + variant.SetBool(lua_toboolean(l, index)); + } + else if (type == LUA_TNUMBER) { + double number = lua_tonumber(l, index); + int inumber = (int) number; + if (number == inumber) { + variant.SetInt(lua_tointeger(l, index)); + } + else { + variant.SetFloat(lua_tointeger(l, index)); + } + } + else if (type != LUA_TUSERDATA && type != LUA_TLIGHTUSERDATA) { + variant.SetString(AllocPooledString(lua_tostring(l,index))); + } + else if (luaL_testudata(l, index, "vector")) { + variant.SetVector3D(*(Vector*)lua_touserdata(l, index)); + } + else if (luaL_testudata(l, index, "entity")) { + variant.SetEntity(*(EHANDLE*)lua_touserdata(l, index)); + } + /*else if (allowTables) { + lua_pushvalue(l, index); + int ref = luaL_ref(l, LUA_REGISTRYINDEX); + variant.Set(FIELD_CUSTOM, &ref); + }*/ + } + + int LEntityAcceptInput(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + const char *name = luaL_checkstring(l, 2); + variant_t variant; + if (lua_gettop(l) > 2) + LToVariant(l, 3, variant); + + CBaseEntity *activator = lua_gettop(l) > 3 ? LEntityGetCheck(l, 4)->Get() : nullptr; + CBaseEntity *caller = lua_gettop(l) > 4 ? LEntityGetCheck(l, 5)->Get() : nullptr; + + lua_pushboolean(l, entity->AcceptInput(name, activator, caller, variant, -1)); + return 1; + } + + int LEntityGetPlayerItemBySlot(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + int slot = luaL_checkinteger(l, 2); + if (player != nullptr) { + LEntityAlloc(l, GetEconEntityAtLoadoutSlot(player, slot)); + return 1; + } + lua_pushnil(l); + return 1; + } + + int LEntityGetPlayerItemByName(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + const char *name = luaL_checkstring(l, 2); + if (player != nullptr) { + CBaseEntity *ret = nullptr; + ForEachTFPlayerEconEntity(player, [&](CEconEntity *entity){ + if (entity->GetItem() != nullptr && FStrEq(GetItemName(entity->GetItem()), name)) { + ret = entity; + return false; + } + return true; + }); + LEntityAlloc(l, ret); + return 1; + } + lua_pushnil(l); + return 1; + } + + int LEntityGetItemAttribute(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + + if (entity == nullptr) { + luaL_error(l, "Entity in not valid"); + lua_pushnil(l); + return 1; + } + + CAttributeList *list = nullptr; + if (entity->IsPlayer()) { + list = ToTFPlayer(entity)->GetAttributeList(); + } + else { + CEconEntity *econEntity = rtti_cast(entity); + if (econEntity != nullptr) { + list = &econEntity->GetItem()->GetAttributeList(); + } + } + if (list == nullptr) { + luaL_error(l, "Entity (%s) is not a player or item", entity->GetClassname()); + lua_pushnil(l); + return 1; + } + CEconItemAttribute * attr = list->GetAttributeByName(name); + if (attr != nullptr) { + if (!attr->GetStaticData()->IsType()) { + lua_pushnumber(l,attr->GetValuePtr()->m_Float); + } + else { + char buf[256]; + attr->GetStaticData()->ConvertValueToString(*attr->GetValuePtr(), buf, sizeof(buf)); + lua_pushstring(l,buf); + } + } + else { + lua_pushnil(l); + } + return 1; + } + + int LEntitySetItemAttribute(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + auto value = lua_tostring(l, 3); + + CAttributeList *list = nullptr; + if (entity->IsPlayer()) { + list = ToTFPlayer(entity)->GetAttributeList(); + } + else { + CEconEntity *econEntity = rtti_cast(entity); + if (econEntity != nullptr) { + list = &econEntity->GetItem()->GetAttributeList(); + } + } + if (list == nullptr) { + luaL_error(l, "Entity (%s) is not a player or item", entity->GetClassname()); + return 0; + } + auto def = GetItemSchema()->GetAttributeDefinitionByName(name); + if (def != nullptr) { + if (value == nullptr) { + list->RemoveAttribute(def); + } + else { + list->AddStringAttribute(def, value); + } + } + else { + luaL_error(l, "Invalid attribute name: %s", name); + } + return 0; + } + + int LEntityToString(lua_State *l) + { + auto *handle = LEntityGetCheck(l, 1); + auto entity = handle->Get(); + + if (entity != nullptr) { + lua_pushfstring(l, "Entity handle id: %d , classname: %s , targetname: %s , net id: %d", handle->ToInt(), entity->GetClassname(), STRING(entity->GetEntityName()), entity->entindex()); + } + else { + lua_pushfstring(l, "Invalid entity handle id: %d", handle->ToInt()); + } + + return 1; + } + + void FindSendProp(int& off, SendTable *s_table, std::function onfound) + { + for (int i = 0; i < s_table->GetNumProps(); ++i) { + SendProp *s_prop = s_table->GetProp(i); + //Msg("Prop %d %s %d %d %d\n", s_prop, s_prop->GetName(), s_prop->GetDataTable(), s_prop->GetDataTable() != nullptr && s_prop->GetDataTable()->GetNumProps() > 1, s_prop->GetDataTable() != nullptr && s_prop->GetDataTable()->GetNumProps() > 1 && strcmp(s_prop->GetDataTable()->GetProp(0)->GetName(), "000") == 0); + if (s_prop->IsInsideArray()) + continue; + + if (s_prop->GetDataTable() == nullptr || (s_prop->GetDataTable() != nullptr && s_prop->GetDataTable()->GetNumProps() > 1 && strcmp(s_prop->GetDataTable()->GetProp(0)->GetName(), "000") == 0)) { + onfound(s_prop, off + s_prop->GetOffset()); + } + else if (s_prop->GetDataTable() != nullptr) { + off += s_prop->GetOffset(); + FindSendProp(off, s_prop->GetDataTable(), onfound); + off -= s_prop->GetOffset(); + } + } + } + + void *stringTSendProxy = nullptr; + CStandardSendProxies* sendproxies = nullptr; + int LEntityDumpProperties(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + + int off = 0; + + lua_newtable(l); + FindSendProp(off, entity->GetServerClass()->m_pTable, [&](SendProp *prop, int offset){ + Mod::Etc::Mapentity_Additions::PropCacheEntry entry; + Mod::Etc::Mapentity_Additions::GetSendPropInfo(prop, entry, offset); + if (entry.arraySize > 1) { + lua_newtable(l); + } + for (int i = 0; i < entry.arraySize; i++) { + variant_t variant; + Mod::Etc::Mapentity_Additions::ReadProp(entity, entry, variant, i, -1); + //Msg("Sendprop %s = %s %d %d\n", prop->GetName(), variant.String(), entry.offset, entry.arraySize); + + LFromVariant(l, variant); + if (entry.arraySize == 1) { + lua_setfield(l, -2, prop->GetName()); + } + else { + lua_rawseti(l, -2, i + 1); + } + } + if (entry.arraySize > 1) { + lua_setfield(l, -2, prop->GetName()); + } + }); + + for (datamap_t *dmap = entity->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if (dmap->dataDesc[i].fieldName != nullptr) { + Mod::Etc::Mapentity_Additions::PropCacheEntry entry; + Mod::Etc::Mapentity_Additions::GetDataMapInfo(dmap->dataDesc[i], entry); + if (entry.arraySize > 1) { + lua_newtable(l); + } + for (int j = 0; j < entry.arraySize; j++) { + variant_t variant; + Mod::Etc::Mapentity_Additions::ReadProp(entity, entry, variant, j, -1); + //Msg("Datamap %s = %s %d\n", dmap->dataDesc[i].fieldName, variant.String(), entry.offset); + + LFromVariant(l, variant); + if (entry.arraySize == 1) { + lua_setfield(l, -2, dmap->dataDesc[i].fieldName); + } + else { + lua_rawseti(l, -2, j + 1); + } + } + if (entry.arraySize > 1) { + lua_setfield(l, -2, dmap->dataDesc[i].fieldName); + } + } + } + } + + auto vars = GetCustomVariables(entity); + for (auto var : vars) { + LFromVariant(l, var.value); + lua_setfield(l, -2, STRING(var.key)); + } + + return 1; + } + + + int LEntityDumpInputs(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + lua_newtable(l); + int j = 0; + for (datamap_t *dmap = entity->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if (dmap->dataDesc[i].externalName != nullptr && (dmap->dataDesc[i].flags & FTYPEDESC_INPUT)) { + lua_pushstring(l, dmap->dataDesc[i].externalName); + lua_rawseti(l, -2, ++j); + } + } + } + for (auto &filter : Mod::Etc::Mapentity_Additions::InputFilter::List()) { + if (filter->Test(entity)) { + for (auto &input : filter->inputs) { + lua_pushfstring(l, "$%d%s",input.name, input.prefix ? "" : ""); + lua_rawseti(l, -2, ++j); + //if ((!input.prefix && CompareCaseInsensitiveStringView(inputNameLower, input.name)) || + // (input.prefix && CompareCaseInsensitiveStringViewBeginsWith(inputNameLower, input.name))) { + // return &input.func; + //} + } + } + } + return 1; + } + struct InputCacheEntry { + inputfunc_t inputptr = nullptr; + Mod::Etc::Mapentity_Additions::CustomInputFunction *custominputptr = nullptr; + }; + + /*bool CheckInput(CBaseEntity *entity) { + + auto datamap = entity->GetDataDescMap(); + for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if (dmap->dataDesc[i].fieldName != nullptr && strcmp(dmap->dataDesc[i].fieldName, nameNoArray.c_str()) == 0) { + fieldtype_t fieldType = isVecAxis ? FIELD_FLOAT : dmap->dataDesc[i].fieldType; + int offset = dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] + extraOffset; + + offset += clamp(arrayPos, 0, (int)dmap->dataDesc[i].fieldSize) * (dmap->dataDesc[i].fieldSizeInBytes / dmap->dataDesc[i].fieldSize); + + datamap_cache.push_back({datamap, name, offset, fieldType, dmap->dataDesc[i].fieldSize}); + return datamap_cache.back(); + } + } + } + }*/ + std::vector input_cache_classes; + std::vector, std::vector>> input_cache; + + struct ArrayProp + { + CBaseEntity *entity; + Mod::Etc::Mapentity_Additions::PropCacheEntry entry; + }; + + ArrayProp *LPropAlloc(lua_State *l, CBaseEntity *entity, Mod::Etc::Mapentity_Additions::PropCacheEntry &entry) + { + ArrayProp *prop = (ArrayProp *)lua_newuserdata(l, sizeof(ArrayProp)); + prop->entity = entity; + prop->entry = entry; + //luaL_getmetatable(l, "entity"); + lua_rawgeti(l, LUA_REGISTRYINDEX, prop_meta); + lua_setmetatable(l, -2); + return prop; + } + + int LPropGet(lua_State *l) + { + auto prop = (ArrayProp *) lua_touserdata(l, 1); + int index = lua_tointeger(l, 2); + luaL_argcheck(l, prop != nullptr, 1, "Userdata is null"); + luaL_argcheck(l, index > 0 && index <= prop->entry.arraySize, 2, "index out of range"); + + variant_t variant; + Mod::Etc::Mapentity_Additions::ReadProp(prop->entity, prop->entry, variant, index, -1); + LFromVariant(l, variant); + return 1; + } + + int LPropGetN(lua_State *l) + { + auto prop = (ArrayProp *) lua_touserdata(l, 1); + luaL_argcheck(l, prop != nullptr, 1, "Userdata is null"); + + lua_pushinteger(l, prop->entry.arraySize); + return 1; + } + + int LPropSet(lua_State *l) + { + auto prop = (ArrayProp *) lua_touserdata(l, 1); + int index = lua_tointeger(l, 2); + luaL_argcheck(l, prop != nullptr, 1, "Userdata is null"); + luaL_argcheck(l, index > 0 && index <= prop->entry.arraySize, 2, "index out of range"); + + variant_t variant; + LToVariant(l, 3, variant); + Mod::Etc::Mapentity_Additions::WriteProp(prop->entity, prop->entry, variant, index, -1); + return 0; + } + + inline InputCacheEntry &LGetInputCacheEntry(CBaseEntity *entity, std::string &name) + { + auto datamap = entity->GetDataDescMap(); + size_t classIndex = 0; + for (; classIndex < input_cache_classes.size(); classIndex++) { + if (input_cache_classes[classIndex] == datamap) { + break; + } + } + if (classIndex >= input_cache_classes.size()) { + input_cache_classes.push_back(datamap); + input_cache.emplace_back(); + } + auto &pair = input_cache[classIndex]; + auto &names = pair.first; + + size_t nameCount = names.size(); + for (size_t i = 0; i < nameCount; i++ ) { + if (names[i] == name) { + return pair.second[i]; + } + } + for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if (dmap->dataDesc[i].fieldName != nullptr && (dmap->dataDesc[i].flags & FTYPEDESC_INPUT) && (strcmp(dmap->dataDesc[i].externalName, name.c_str()) == 0)) { + names.push_back(name); + pair.second.push_back({dmap->dataDesc[i].inputFunc, nullptr}); + return pair.second.back(); + } + } + } + auto func = Mod::Etc::Mapentity_Additions::GetCustomInput(entity, name.c_str()); + if (func != nullptr) { + names.push_back(name); + pair.second.push_back({nullptr, func}); + return pair.second.back(); + } + + names.push_back(name); + pair.second.push_back({nullptr, nullptr}); + return pair.second.back(); + } + + int LEntityCallInputNormal(lua_State *l) + { + auto func = MakePtrToMemberFunc((void *)(uintptr_t) lua_tointeger(l, lua_upvalueindex(1))); + auto entity = LEntityGetNonNull(l, 1); + inputdata_t data; + LToVariant(l, 2, data.value); + data.pActivator = LEntityGetOptional(l, 3); + data.pCaller = LEntityGetOptional(l, 4); + data.nOutputID = -1; + (entity->*func)(data); + + return 0; + } + + int LEntityCallInputCustom(lua_State *l) + { + auto func = (Mod::Etc::Mapentity_Additions::CustomInputFunction *)(uintptr_t) lua_tointeger(l, lua_upvalueindex(1)); + auto entity = LEntityGetNonNull(l, 1); + variant_t value; + LToVariant(l, 2, value); + (*func)(entity, lua_tostring(l, lua_upvalueindex(2)), LEntityGetOptional(l,3), LEntityGetOptional(l,4), value); + return 0; + } + + int LEntityGetProp(lua_State *l) + { + // Get function + lua_getmetatable(l, 1); + lua_pushvalue(l, 2); + lua_rawget(l, 3); + if (!lua_isnil(l,4)) return 1; + + auto entity = LEntityGetNonNull(l, 1); + + // Table access first + lua_rawgeti(l, LUA_REGISTRYINDEX, entity_table_store); + if (lua_rawgetp(l, -1, entity) != LUA_TNIL) { + lua_pushvalue(l, 2); + if (lua_rawget(l, -2) != LUA_TNIL) { + return 1; + } + } + + const char *varName = luaL_checkstring(l, 2); + + if (varName == nullptr) { + lua_pushnil(l); + return 1; + } + + std::string varNameStr(varName); + + variant_t variant; + + if (entity->GetCustomVariableByText(varName, variant)) { + LFromVariant(l, variant); + return 1; + } + auto *entry = &Mod::Etc::Mapentity_Additions::GetDataMapOffset(entity->GetDataDescMap(), varNameStr); + if (entry == nullptr && entry->offset <= 0) { + entry = &Mod::Etc::Mapentity_Additions::GetSendPropOffset(entity->GetServerClass(), varNameStr); + } + if (entry != nullptr && entry->offset > 0) { + if (entry->arraySize > 1) { + LPropAlloc(l, entity, *entry); + } + else{ + Mod::Etc::Mapentity_Additions::ReadProp(entity, *entry, variant, 0, -1); + LFromVariant(l, variant); + } + return 1; + } + + auto &input = LGetInputCacheEntry(entity, varNameStr); + if (input.inputptr != nullptr) { + lua_pushinteger(l, (uintptr_t) GetAddrOfMemberFunc(input.inputptr)); + lua_pushcclosure(l, LEntityCallInputNormal,1); + return 1; + } + else if (input.custominputptr != nullptr) { + lua_pushinteger(l, (uintptr_t) input.custominputptr); + lua_pushstring(l, varNameStr.c_str()); + lua_pushcclosure(l, LEntityCallInputCustom,2); + return 1; + } + lua_pushnil(l); + return 1; + } + + int LEntitySetProp(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + + // Tables must be saved in special way + if (lua_type(l, 3) == LUA_TTABLE) { + lua_rawgeti(l, LUA_REGISTRYINDEX, entity_table_store); + if (lua_rawgetp(l, -1, entity) == LUA_TNIL) { + auto mod = entity->GetOrCreateEntityModule("luaentity"); + lua_newtable(l); + lua_pushvalue(l,2); + lua_pushvalue(l,3); + lua_rawset(l, -3); + + lua_rawsetp(l, -3, entity); + return 0; + } + lua_pushvalue(l,2); + lua_pushvalue(l,3); + lua_rawset(l, -3); + return 0; + } + else { + lua_rawgeti(l, LUA_REGISTRYINDEX, entity_table_store); + if (lua_rawgetp(l, -1, entity) != LUA_TNIL) { + lua_pushvalue(l,2); + lua_pushnil(l); + lua_rawset(l, -3); + } + } + + const char *varName = luaL_checkstring(l, 2); + + if (varName == nullptr) { + lua_pushnil(l); + return 1; + } + + std::string varNameStr(varName); + /*auto mod = entity->GetEntityModule("luaentity"); + if (mod != nullptr) { + variant_t variantPre; + entity->GetCustomVariableByText(varName, variantPre); + if (variantPre.FieldType() == FIELD_CUSTOM) { + int ref; + variantPre.SetOther(&ref); + + } + }*/ + + variant_t variant; + LToVariant(l, 3, variant); + SetEntityVariable(entity, Mod::Etc::Mapentity_Additions::ANY, varNameStr, variant, 0, -1); + + /*if (variant.FieldType() == FIELD_CUSTOM) { + if (mod == nullptr) { + entity->AddEntityModule("luaentity", mod = new LuaEntityModule(entity)); + } + int ref; + variant.SetOther(&ref); + mod->SetTableStorageEntry(cur_state, ref); + }*/ + + return 0; + } + + struct EntityCreateCallback + { + LuaState *state; + string_t classname; + bool wildcard = false; + int func; + }; + std::vector entity_create_callbacks; + + int LEntityAddCreateCallback(lua_State *l) + { + const char *name = luaL_checkstring(l, 1); + luaL_argcheck(l, strlen(name) > 0, 1, "entity classname empty"); + luaL_checktype(l,2, LUA_TFUNCTION); + int func = luaL_ref(l, LUA_REGISTRYINDEX); + + entity_create_callbacks.push_back({cur_state, AllocPooledString(name), name[strlen(name) - 1] == '*', func}); + lua_pushinteger(l, func); + return 1; + } + + int LEntityRemoveCreateCallback(lua_State *l) + { + int func = luaL_checkinteger(l, 1); + RemoveIf(entity_create_callbacks, [&](auto &callback){ + if (callback.state == cur_state && callback.func == func) { + luaL_unref(l, LUA_REGISTRYINDEX, func); + return true ; + } + return false ; + }); + + return 0; + } + + void DamageInfoToTable(lua_State *l, CTakeDamageInfo &info) { + lua_newtable(l); + LEntityAlloc(l, info.GetAttacker()); + lua_setfield(l, -2, "Attacker"); + LEntityAlloc(l, info.GetInflictor()); + lua_setfield(l, -2, "Inflictor"); + LEntityAlloc(l, info.GetWeapon()); + lua_setfield(l, -2, "Weapon"); + lua_pushnumber(l, info.GetDamage()); + lua_setfield(l, -2, "Damage"); + lua_pushinteger(l, info.GetDamageType()); + lua_setfield(l, -2, "DamageType"); + lua_pushinteger(l, info.GetDamageCustom()); + lua_setfield(l, -2, "DamageCustom"); + lua_pushinteger(l, info.GetCritType()); + lua_setfield(l, -2, "CritType"); + }; + + void TableToDamageInfo(lua_State *l, int idx, CTakeDamageInfo &info) { + lua_pushvalue(l, idx); + lua_getfield(l, -1, "Attacker"); + info.SetAttacker(LEntityGetOptional(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "Inflictor"); + info.SetInflictor(LEntityGetOptional(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "Weapon"); + info.SetWeapon(LEntityGetOptional(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "Damage"); + info.SetDamage(lua_tonumber(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "DamageType"); + info.SetDamageType(lua_tointeger(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "DamageCustom"); + info.SetDamageCustom(lua_tointeger(l, -1)); + lua_pop(l, 1); + + lua_getfield(l, -1, "CritType"); + info.SetCritType((CTakeDamageInfo::ECritType)lua_tointeger(l, -1)); + lua_pop(l, 1); + }; + + int LEntityTakeDamage(lua_State *l) + { + CTakeDamageInfo info; + auto entity = LEntityGetNonNull(l, 1); + luaL_checktype(l, 2, LUA_TTABLE); + TableToDamageInfo(l, 2, info); + lua_pushinteger(l, entity->TakeDamage(info)); + return 1; + } + + int LEntityAddCond(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + int condition = luaL_checkinteger(l, 2); + float duration = luaL_optnumber(l, 3, -1); + auto provider = lua_gettop(l) > 3 ? ToTFPlayer(LEntityGetOptional(l, 4)) : nullptr; + player->m_Shared->AddCond((ETFCond)condition, duration, provider); + return 0; + } + + int LEntityRemoveCond(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + int condition = luaL_checkinteger(l, 2); + player->m_Shared->RemoveCond((ETFCond)condition); + return 0; + } + + int LEntityGetCondProvider(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + int condition = luaL_checkinteger(l, 2); + LEntityAlloc(l, player->m_Shared->GetConditionProvider((ETFCond)condition)); + return 1; + } + + int LEntityInCond(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + int condition = luaL_checkinteger(l, 2); + lua_pushinteger(l, player->m_Shared->InCond((ETFCond)condition)); + return 1; + } + + int LEntityStunPlayer(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + float duration = luaL_checknumber(l, 2); + float amount = luaL_checknumber(l, 3); + int flags = luaL_optnumber(l, 4, 1); + auto stunner = lua_gettop(l) > 4 ? ToTFPlayer(LEntityGetOptional(l, 5)) : nullptr; + player->m_Shared->StunPlayer(duration, amount, flags, stunner); + return 0; + } + + int LEntityGetAbsOrigin(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto *vec = LVectorAlloc(l); + *vec = entity->GetAbsOrigin(); + return 1; + } + + int LEntitySetAbsOrigin(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto vec = LVectorGetCheck(l, 2); + entity->SetAbsOrigin(*vec); + return 0; + } + + int LEntityGetAbsAngles(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto *vec = LAngleAlloc(l); + *vec = entity->GetAbsAngles(); + return 1; + } + + int LEntitySetAbsAngles(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto vec = LAngleGetCheck(l, 2); + entity->SetAbsAngles(*vec); + return 0; + } + + int LEntityTeleport(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto pos = LVectorGetNoCheck(l, 2); + auto ang = LAngleGetNoCheck(l, 3); + auto vel = LVectorGetNoCheck(l, 4); + entity->Teleport(pos, ang, vel); + return 0; + } + + int LEntityCreateItem(lua_State *l) + { + auto player = LPlayerGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + auto entity = CreateItemByName(player, name); + auto noRemove = lua_toboolean(l, 4); + auto forceGive = lua_gettop(l) < 5 || lua_toboolean(l, 5); + if (entity != nullptr && lua_gettop(l) > 2) { + auto view = entity->GetItem(); + lua_pushnil(l); + while (lua_next(l, 3) != 0) { + if (lua_type(l, -2) == LUA_TSTRING) { + auto attrdef = GetItemSchema()->GetAttributeDefinitionByName(lua_tostring(l, -2)); + if (attrdef != nullptr) { + view->GetAttributeList().AddStringAttribute(attrdef, lua_tostring(l, -1)); + } + } + lua_pop(l, 1); + } + } + if (entity != nullptr && !GiveItemToPlayer(player, entity, noRemove, forceGive, name)) { + entity->Remove(); + entity = nullptr; + } + if (entity != nullptr) { + LEntityAlloc(l, entity); + } + else { + lua_pushnil(l); + } + return 1; + } + + int LEntityFireOutput(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + variant_t value; + LToVariant(l, 3, value); + auto activator = LEntityGetOptional(l, 4); + auto delay = luaL_optnumber(l, 5, 0); + entity->FireNamedOutput(name,value, activator, entity, delay); + return 0; + } + + int LEntitySetFakeProp(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + variant_t value; + LToVariant(l, 3, value); + + auto mod = entity->GetOrCreateEntityModule("fakeprop"); + mod->props[name] = {value, value}; + return 0; + } + + int LEntityResetFakeProp(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + variant_t value; + LToVariant(l, 3, value); + + auto mod = entity->GetOrCreateEntityModule("fakeprop"); + mod->props.erase(name); + return 0; + } + + int LEntityGetFakeProp(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + auto name = luaL_checkstring(l, 2); + + auto mod = entity->GetOrCreateEntityModule("fakeprop"); + LFromVariant(l, mod->props[name].first); + return 1; + } + + int LEntityAddEffects(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + entity->AddEffects(luaL_checknumber(l, 2)); + return 0; + } + + int LEntityRemoveEffects(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + entity->RemoveEffects(luaL_checknumber(l, 2)); + return 0; + } + + int LEntityIsEffectActive(lua_State *l) + { + auto entity = LEntityGetNonNull(l, 1); + lua_pushboolean(l, entity->IsEffectActive(luaL_checknumber(l, 2))); + + return 1; + } + + int LFindEntityByName(lua_State *l) + { + const char *name = luaL_checkstring(l, 1); + auto prevEntity = lua_gettop(l) < 2 ? nullptr : LEntityGetCheck(l, 2)->Get(); + LEntityAlloc(l, servertools->FindEntityByName(prevEntity, name)); + return 1; + } + + int LFindEntityByClassname(lua_State *l) + { + const char *name = luaL_checkstring(l, 1); + auto prevEntity = lua_gettop(l) < 2 ? nullptr : LEntityGetCheck(l, 2)->Get(); + LEntityAlloc(l, servertools->FindEntityByClassname(prevEntity, name)); + return 1; + } + + int LFindAllEntityByName(lua_State *l) + { + const char *name = luaL_checkstring(l, 1); + lua_newtable(l); + int idx = 1; + for(CBaseEntity *entity = nullptr ; (entity = servertools->FindEntityByName(entity, name)) != nullptr; ) { + LEntityAlloc(l, entity); + lua_rawseti (l, -2, idx); + idx++; + } + return 1; + } + + int LFindAllEntityByClassname(lua_State *l) + { + const char *name = luaL_checkstring(l, 1); + lua_newtable(l); + int idx = 1; + for(CBaseEntity *entity = nullptr ; (entity = servertools->FindEntityByClassname(entity, name)) != nullptr; ) { + LEntityAlloc(l, entity); + lua_rawseti (l, -2, idx); + idx++; + } + return 1; + } + + int LFindAllEntity(lua_State *l) + { + lua_createtable(l, gEntList->m_iNumEnts, 0); + int idx = 1; + for (const CEntInfo *pInfo = gEntList->FirstEntInfo(); pInfo != nullptr; pInfo = pInfo->m_pNext) { + CBaseEntity *entity = (CBaseEntity *) pInfo->m_pEntity; + if (entity != nullptr) { + LEntityAlloc(l, entity); + lua_rawseti (l, -2, idx); + idx++; + } + } + return 1; + } + + class CLuaEnumerator : public IPartitionEnumerator + { + public: + CLuaEnumerator(lua_State *l) : l(l) {}; + + // This gets called by the enumeration methods with each element + // that passes the test. + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { + CBaseEntity *pEntity = static_cast(pHandleEntity)->GetBaseEntity(); + if ( pEntity ) + { + LEntityAlloc(l, pEntity); + lua_rawseti (l, -2, idx); + idx++; + } + return ITERATION_CONTINUE; + } + lua_State *l; + int idx = 1; + }; + + int LFindAllEntityInBox(lua_State *l) + { + auto min = LVectorGetCheck(l, 1); + auto max = LVectorGetCheck(l, 2); + + CLuaEnumerator iter = CLuaEnumerator(l); + + lua_newtable(l); + partition->EnumerateElementsInBox(PARTITION_ENGINE_NON_STATIC_EDICTS, *min, *max, false, &iter); + return 1; + } + + int LFindAllEntityInSphere(lua_State *l) + { + auto center = LVectorGetCheck(l, 1); + auto radius = luaL_checknumber(l, 2); + + CLuaEnumerator iter = CLuaEnumerator(l); + + lua_newtable(l); + partition->EnumerateElementsInSphere(PARTITION_ENGINE_NON_STATIC_EDICTS, *center, radius, false, &iter); + return 1; + } + + int LEntityFirst(lua_State *l) + { + LEntityAlloc(l, servertools->FirstEntity()); + return 1; + } + + int LEntityNext(lua_State *l) + { + LEntityAlloc(l, servertools->NextEntity(LEntityGetCheck(l,1)->Get())); + return 1; + } + + void LCreateTimer(lua_State *l) + { + + float delay = luaL_checknumber(l, 1); + + luaL_checktype(l, 2, LUA_TFUNCTION); + lua_pushvalue(l,2); + int func = luaL_ref(l, LUA_REGISTRYINDEX); + int repeats = lua_gettop(l) >= 3 ? luaL_checknumber(l,3) : 1; + + lua_pushvalue(l,4); + int param = lua_gettop(l) >= 4 ? luaL_ref(l, LUA_REGISTRYINDEX) : 0; + + lua_pushnumber(l, cur_state->AddTimer(delay, repeats, func, param)); + } + + int LTimerSimple(lua_State *l) + { + float delay = luaL_checknumber(l, 1); + luaL_checktype(l, 2, LUA_TFUNCTION); + lua_pushvalue(l,2); + int func = luaL_ref(l, LUA_REGISTRYINDEX); + + lua_pushnumber(l, cur_state->AddTimer(delay, 1, func, 0)); + return 1; + } + + int LTimerCreate(lua_State *l) + { + LCreateTimer(l); + return 1; + } + + int LTimerStop(lua_State *l) + { + int id = luaL_checkinteger(l, 1); + if (!cur_state->StopTimer(id)) { + luaL_error(l, "Timer with id %d is not valid\n", id); + } + return 0; + } + + class LuaTraceFilter : public CTraceFilterSimple + { + public: + LuaTraceFilter(lua_State *l, IHandleEntity *entity, int collisionGroup) : CTraceFilterSimple( entity, collisionGroup) + { + if (lua_istable(l, -1)) { + int len = lua_rawlen(l, -1); + for(int i = 1; i <= len; i++) { + lua_rawgeti(l, -1, i); + auto entity = LEntityGetOptional(l, -1); + + if (entity != nullptr) { + list.push_back(entity); + } + lua_pop(l, 1); + } + } + this->hasFunction = lua_isfunction(l, -1); + } + virtual bool ShouldHitEntity(IHandleEntity *pHandleEntity, int contentsMask) { + if (CTraceFilterSimple::ShouldHitEntity(pHandleEntity, contentsMask)) { + if (!list.empty() && std::find(list.begin(), list.end(), pHandleEntity) != list.end()) { + return false; + } + if (hasFunction) { + LEntityAlloc(l, EntityFromEntityHandle(pHandleEntity)); + lua_call(l, 1, 1); + bool hit = lua_toboolean(l, -1); + lua_pop(l, 1); + return hit; + } + } + return false; + } + std::vector list; + lua_State *l; + bool hasFunction; + }; + + int LTraceLine(lua_State *l) + { + lua_getfield(l, 1, "start"); + auto startptrentity = LEntityGetOptional(l, -1); + auto startptrvector = LVectorGetNoCheck(l, -1); + Vector start(0,0,0); + if (startptrentity != nullptr) { + start = startptrentity->EyePosition(); + } + else if (startptrvector != nullptr) { + start = *startptrvector; + } + + lua_getfield(l, 1, "endpos"); + auto endptr = LVectorGetNoCheck(l, -1); + Vector end(0,0,0); + if (endptr != nullptr) { + end = *endptr; + } + else { + QAngle angles(0,0,0); + lua_getfield(l, 1, "distance"); + float distance = lua_isnil(l, -1) ? 8192 : lua_tonumber(l, -1); + + lua_getfield(l, 1, "angles"); + auto anglesptr = LAngleGetNoCheck(l, -1); + if (anglesptr != nullptr) { + angles = *anglesptr; + } + Vector fwd; + AngleVectors(angles, &fwd); + end = start + fwd * distance; + } + + lua_getfield(l, 1, "mask"); + int mask = lua_isnil(l, -1) ? MASK_SOLID : lua_tointeger(l, -1); + lua_getfield(l, 1, "collisiongroup"); + int collisiongroup = lua_isnil(l, -1) ? COLLISION_GROUP_NONE : lua_tointeger(l, -1); + + //lua_getfield(l, 1, "filter"); + // + // + + trace_t trace; + lua_getfield(l, 1, "mins"); + auto *minsptr = LVectorGetNoCheck(l, -1); + lua_getfield(l, 1, "maxs"); + auto *maxsptr = LVectorGetNoCheck(l, -1); + + + lua_getfield(l, 1, "filter"); + CBaseEntity *filterEntity = lua_isnil(l, -1) ? startptrentity : LEntityGetOptional(l, -1); + LuaTraceFilter filter(l, filterEntity, collisiongroup); + if (minsptr == nullptr || maxsptr == nullptr) { + UTIL_TraceLine(start, end, mask, &filter, &trace); + } + else { + UTIL_TraceHull(start, end, *minsptr, *maxsptr, mask, &filter, &trace); + } + + + lua_newtable(l); + LEntityAlloc(l, trace.m_pEnt); + lua_setfield(l, -2, "Entity"); + lua_pushnumber(l, trace.fraction); + lua_setfield(l, -2, "Fraction"); + lua_pushnumber(l, trace.fractionleftsolid); + lua_setfield(l, -2, "FractionLeftSolid"); + lua_pushboolean(l, trace.DidHit()); + lua_setfield(l, -2, "Hit"); + lua_pushinteger(l, trace.hitbox); + lua_setfield(l, -2, "HitBox"); + lua_pushinteger(l, trace.hitgroup); + lua_setfield(l, -2, "HitGroup"); + lua_pushboolean(l, trace.surface.flags & SURF_NODRAW); + lua_setfield(l, -2, "HitNoDraw"); + lua_pushboolean(l, trace.DidHitNonWorldEntity()); + lua_setfield(l, -2, "HitNonWorld"); + auto normal = LVectorAlloc(l); + *normal = trace.plane.normal; + lua_setfield(l, -2, "HitNormal"); + auto hitpos = LVectorAlloc(l); + *hitpos = trace.endpos; + lua_setfield(l, -2, "HitPos"); + lua_pushboolean(l, trace.surface.flags & SURF_SKY); + lua_setfield(l, -2, "HitSky"); + lua_pushstring(l, trace.surface.name); + lua_setfield(l, -2, "HitTexture"); + lua_pushboolean(l, trace.DidHitWorld()); + lua_setfield(l, -2, "HitWorld"); + auto normal2 = LVectorAlloc(l); + *normal2 = (trace.endpos - trace.startpos).Normalized(); + lua_setfield(l, -2, "Normal"); + auto start2 = LVectorAlloc(l); + *start2 = trace.startpos; + lua_setfield(l, -2, "StartPos"); + lua_pushboolean(l, trace.startsolid); + lua_setfield(l, -2, "StartSolid"); + lua_pushboolean(l, trace.allsolid); + lua_setfield(l, -2, "AllSolid"); + lua_pushinteger(l, trace.surface.flags); + lua_setfield(l, -2, "SurfaceFlags"); + lua_pushinteger(l, trace.dispFlags); + lua_setfield(l, -2, "DispFlags"); + lua_pushinteger(l, trace.contents); + lua_setfield(l, -2, "Contents"); + lua_pushinteger(l, trace.surface.surfaceProps); + lua_setfield(l, -2, "SurfaceProps"); + lua_pushinteger(l, trace.physicsbone); + lua_setfield(l, -2, "PhysicsBone"); + return 1; + } + + int LUtilPrintToConsoleAll(lua_State *l) + { + int args = lua_gettop(l); + std::string str; + for (int i = 1; i <= args; i++) { + const char *msg = lua_tostring(l, i); + if (msg != nullptr) { + str += msg; + str += '\t'; + } + } + ClientMsgAll("%s\n", str.c_str()); + return 0; + } + + int LUtilPrintToConsole(lua_State *l) + { + auto player = ToTFPlayer(LEntityGetCheck(l, 1)->Get()); + if (player != nullptr) { + luaL_error(l, "Entity is not a valid player"); + } + int args = lua_gettop(l); + std::string str; + for (int i = 2; i <= args; i++) { + const char *msg = lua_tostring(l, i); + if (msg != nullptr) { + str += msg; + str += '\t'; + } + } + ClientMsg(player,"%s\n", str.c_str()); + return 0; + } + + int LUtilPrintToChatAll(lua_State *l) + { + int args = lua_gettop(l); + std::string str; + for (int i = 1; i <= args; i++) { + const char *msg = lua_tostring(l, i); + if (msg != nullptr) { + str += msg; + str += '\t'; + } + } + PrintToChatAll(str.c_str()); + return 0; + } + + int LUtilPrintToChat(lua_State *l) + { + auto player = ToTFPlayer(LEntityGetCheck(l, 1)->Get()); + if (player != nullptr) { + luaL_error(l, "Entity is not a valid player"); + } + int args = lua_gettop(l); + std::string str; + for (int i = 1; i <= args; i++) { + const char *msg = lua_tostring(l, i); + if (msg != nullptr) { + str += msg; + str += '\t'; + } + } + PrintToChat(str.c_str(), player); + return 0; + } + + int LCurTime(lua_State *l) + { + lua_pushnumber(l, gpGlobals->curtime); + return 1; + } + + int LTickCount(lua_State *l) + { + lua_pushinteger(l, gpGlobals->tickcount); + return 1; + } + + int LGetMapName(lua_State *l) + { + lua_pushstring(l, STRING(gpGlobals->mapname)); + return 1; + } + + static const struct luaL_Reg vectorlib_f [] = { + {"__call", LVectorNew}, + {nullptr, nullptr}, + }; + + static const struct luaL_Reg vectorlib_m [] = { + {"Length", LVectorLength}, + {"Distance", LVectorDistance}, + {"Cross", LVectorCross}, + {"Dot", LVectorDot}, + {"ToAngles", LVectorToAngles}, + {"GetForward", LVectorGetForward}, + {"GetAngleVectors", LVectorGetAngleVectors}, + {"Normalize", LVectorNormalize}, + {"Rotate", LVectorRotate}, + {"Set", LVectorCopy}, + {"SetUnpacked", LVectorCopyUnpacked}, + {"__add", LVectorAdd}, + {"__sub", LVectorSubtract}, + {"__mul", LVectorMultiply}, + {"__div", LVectorDivide}, + {"__eq", LVectorEquals}, + {"__tostring", LVectorToString}, + {"__index", LVectorIndex}, + {"__newindex", LVectorSetIndex}, + {nullptr, nullptr}, + }; + + static const struct luaL_Reg entitylib_f [] = { + {"__call", LEntityNew}, + {"FindByName", LFindEntityByName}, + {"FindByClass", LFindEntityByClassname}, + {"FindAllByName", LFindAllEntityByName}, + {"FindAllByClass", LFindAllEntityByClassname}, + {"FindInBox", LFindAllEntityInBox}, + {"FindInSphere", LFindAllEntityInSphere}, + {"GetAll", LFindAllEntity}, + {"Create", LEntityNew}, + {"GetFirstEntity", LEntityFirst}, + {"GetNextEntity", LEntityNext}, + {"AddCreateCallback", LEntityAddCreateCallback}, + {"RemoveCreateCallback", LEntityRemoveCreateCallback}, + {nullptr, nullptr}, + }; + + static const struct luaL_Reg entitylib_m [] = { + {"IsValid", LEntityIsValid}, + {"DispatchSpawn", LEntitySpawn}, + {"Activate", LEntityActivate}, + {"Remove", LEntityRemove}, + {"AcceptInput", LEntityAcceptInput}, + {"GetNetIndex", LEntityGetNetworkIndex}, + {"GetHandleIndex", LEntityGetHandleIndex}, + {"GetPlayerItemBySlot", LEntityGetPlayerItemBySlot}, + {"GetPlayerItemByName", LEntityGetPlayerItemByName}, + {"GetAttributeValue", LEntityGetItemAttribute}, + {"SetAttributeValue", LEntitySetItemAttribute}, + {"PrintToChat", LUtilPrintToChat}, + {"PrintToConsole", LUtilPrintToConsole}, + {"GetName", LEntityGetName}, + {"SetName", LEntitySetName}, + {"GetPlayerName", LEntityGetPlayerName}, + {"IsAlive", LEntityIsAlive}, + {"IsPlayer", LEntityIsPlayer}, + {"IsNPC", LEntityIsNPC}, + {"IsBot", LEntityIsBot}, + {"IsRealPlayer", LEntityIsRealPlayer}, + {"IsObject", LEntityIsObject}, + {"IsWeapon", LEntityIsWeapon}, + {"IsCombatCharacter", LEntityIsCombatCharacter}, + {"IsWearable", LEntityIsWearable}, + {"AddCallback", LEntityAddCallback}, + {"RemoveCallback", LEntityRemoveCallback}, + {"DumpProperties", LEntityDumpProperties}, + {"DumpInputs", LEntityDumpInputs}, + {"TakeDamage", LEntityTakeDamage}, + {"AddCond", LEntityAddCond}, + {"RemoveCond", LEntityRemoveCond}, + {"InCond", LEntityInCond}, + {"GetConditionProvider", LEntityGetCondProvider}, + {"StunPlayer", LEntityStunPlayer}, + {"GetAbsOrigin", LEntityGetAbsOrigin}, + {"SetAbsOrigin", LEntitySetAbsOrigin}, + {"GetAbsAngles", LEntityGetAbsAngles}, + {"SetAbsAngles", LEntitySetAbsAngles}, + {"Teleport", LEntityTeleport}, + {"GiveItem", LEntityCreateItem}, + {"FireOutput", LEntityFireOutput}, + {"SetFakeSendProp", LEntitySetFakeProp}, + {"ResetFakeSendProp", LEntityResetFakeProp}, + {"GetFakeSendProp", LEntityGetFakeProp}, + {"AddEffects", LEntityAddEffects}, + {"RemoveEffects", LEntityRemoveEffects}, + {"IsEffectActive", LEntityIsEffectActive}, + {"__eq", LEntityEquals}, + {"__tostring", LEntityToString}, + {"__index", LEntityGetProp}, + {"__newindex", LEntitySetProp}, + + {nullptr, nullptr}, + }; + + static const struct luaL_Reg timerlib_f [] = { + + {"Create", LTimerCreate}, + {"Simple", LTimerSimple}, + {"Stop", LTimerStop}, + {nullptr, nullptr}, + }; + + static const struct luaL_Reg utillib_f [] = { + + {"Trace", LTraceLine}, + {"PrintToConsoleAll", LUtilPrintToConsoleAll}, + {"PrintToConsole", LUtilPrintToConsole}, + {"PrintToChatAll", LUtilPrintToChatAll}, + {"PrintToChat", LUtilPrintToChat}, + {nullptr, nullptr}, + }; + + static const struct luaL_Reg proplib_m [] = { + {"__index", LPropGet}, + {"__newindex", LPropSet}, + {"__len", LPropGetN}, + {nullptr, nullptr}, + }; + + THINK_FUNC_DECL(ScriptModuleTick) + { + + this->SetNextThink(gpGlobals->curtime + 6.0f, "ScriptModuleTick"); + } + + LuaState::LuaState() + { + + l = luaL_newstate(); + luaL_openlibs(l); + + // Blacklist some stuff + lua_pushnil(l); + lua_setglobal(l, "io"); + lua_pushnil(l); + lua_setglobal(l, "package"); + lua_pushnil(l); + lua_setglobal(l, "loadfile"); + lua_pushnil(l); + lua_setglobal(l, "dofile"); + lua_pushnil(l); + lua_setglobal(l, "load"); + lua_pushnil(l); + lua_setglobal(l, "require"); + + lua_getglobal(l, "os"); + lua_pushnil(l); + lua_setfield(l, -2, "execute"); + lua_pushnil(l); + lua_setfield(l, -2, "getenv"); + lua_pushnil(l); + lua_setfield(l, -2, "remove"); + lua_pushnil(l); + lua_setfield(l, -2, "rename"); + lua_pushnil(l); + lua_setfield(l, -2, "setlocale"); + lua_pushnil(l); + lua_setfield(l, -2, "tmpname"); + lua_pushnil(l); + lua_setfield(l, -2, "exit"); + + // Override print + lua_register(l, "print", LPrint); + + /* Vector */ + luaL_newmetatable(l, "vector"); + luaL_setfuncs(l, vectorlib_m, 0); + m_pVectorMeta = vector_meta_ptr = lua_topointer(l, -1); + m_iVectorMeta = vector_meta = luaL_ref(l, LUA_REGISTRYINDEX); + + luaL_newmetatable(l, "entity"); + luaL_setfuncs(l, entitylib_m, 0); + m_pEntityMeta = entity_meta_ptr = lua_topointer(l, -1); + m_iEntityMeta = entity_meta = luaL_ref(l, LUA_REGISTRYINDEX); + + luaL_newmetatable(l, "prop"); + luaL_setfuncs(l, proplib_m, 0); + m_iPropMeta = prop_meta = luaL_ref(l, LUA_REGISTRYINDEX); + //lua_rawsetp(l, LUA_REGISTRYINDEX, (void*)entity_meta); + + //lua_pushstring(l, "__index"); + //lua_pushvalue(l, -2); + //lua_settable(l, -3); + + //lua_pushcfunction(l, LVectorSetIndex); + //lua_settable(l, -3); + + //lua_pushstring(l, ""); + //lua_pushcfunction(l, LVectorIndex); + //lua_settable(l, -3); + + lua_register(l, "Vector", LVectorNew); + lua_register(l, "Entity", LEntityNew); + lua_register(l, "Timer", LTimerCreate); + lua_register(l, "StopTimer", LTimerStop); + lua_register(l, "CurTime", LCurTime); + lua_register(l, "GetMapName", LGetMapName); + lua_register(l, "TickCount", LTickCount); + lua_register(l, "IsValid", LIsValid); + + + luaL_newlib(l, entitylib_f); + //lua_newtable(l); + //luaL_setfuncs(l, entitylib_f, 0); + lua_setglobal(l, "ents"); + + luaL_newlib(l, timerlib_f); + //lua_newtable(l); + //luaL_setfuncs(l, timerlib_f, 0); + lua_setglobal(l, "timer"); + + luaL_newlib(l, utillib_f); + //lua_newtable(l); + //luaL_setfuncs(l, utillib_f, 0); + lua_setglobal(l, "util"); + + lua_newtable(l); + m_iEntityTableStorage = entity_table_store = luaL_ref(l, LUA_REGISTRYINDEX); + + if (filesystem != nullptr) + DoFile("scripts/globals.lua", true); + } + + LuaState::~LuaState() { + for (auto entity : callbackEntities) { + auto luaModule = entity->GetEntityModule("luaentity"); + if (luaModule != nullptr) { + luaModule->LuaStateRemoved(this); + } + } + RemoveIf(entity_create_callbacks, [&](auto &callback){ + return callback.state == this; + }); + lua_close(l); + } + + double script_exec_time = 0.0; + double script_exec_time_tick = 0.0; + bool script_exec_time_guard = false; + void LuaState::DoString(const char *str, bool execute) { + VPROF_BUDGET("LuaState::DoString", "Lua"); + CFastTimer timer; + timer.Start(); + SwitchState(); + { + TIME_SCOPE2(lol); + int err = luaL_loadstring(l, str); + if (err) { + const char *errbuf = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errbuf); + } + else if (execute) { + err = lua_pcall(l, 0, LUA_MULTRET, 0); + + if (err) { + const char *errbuf = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errbuf); + } + } + } + lua_settop(l,0); + timer.End(); + script_exec_time += timer.GetDuration().GetSeconds(); + script_exec_time_tick += timer.GetDuration().GetSeconds(); + } + + void LuaState::DoFile(const char *path, bool execute) { + VPROF_BUDGET("LuaState::DoFile", "Lua"); + CFastTimer timer; + timer.Start(); + SwitchState(); + + FileHandle_t f = filesystem->Open(path, "rb", "GAME"); + if (f == nullptr) { + SendWarningConsoleMessageToAdmins("Cannot find lua script file %s\n", path); + return; + } + int fileSize = filesystem->Size(f); + if (fileSize > 200000) { + SendWarningConsoleMessageToAdmins("Lua script file %s is too large\n", path); + return; + } + uint bufSize = ((IFileSystem *)filesystem)->GetOptimalReadSize(f, fileSize + 2); + char *buffer = (char*)((IFileSystem *)filesystem)->AllocOptimalReadBuffer(f, bufSize); + // read into local buffer + bool bRetOK = (((IFileSystem *)filesystem)->ReadEx(buffer, bufSize, fileSize, f) != 0); + + if (bRetOK) { + TIME_SCOPE2(lol); + int err = luaL_loadbuffer(l, buffer, bufSize, path); + if (err) { + const char *errbuf = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errbuf); + } + else if (execute) { + err = lua_pcall(l, 0, LUA_MULTRET, 0); + + if (err) { + const char *errbuf = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errbuf); + } + } + } + else { + SendWarningConsoleMessageToAdmins("Fail reading buffer of lua script file %s\n", path); + return; + } + lua_settop(l,0); + timer.End(); + script_exec_time += timer.GetDuration().GetSeconds(); + script_exec_time_tick += timer.GetDuration().GetSeconds(); + } + + void LuaState::SwitchState() + { + cur_state = this; + vector_meta = m_iVectorMeta; + vector_meta_ptr = m_pVectorMeta; + entity_meta = m_iEntityMeta; + entity_meta_ptr = m_pEntityMeta; + entity_table_store = m_iEntityTableStorage; + prop_meta = m_iPropMeta; + } + + void LuaState::CallGlobal(const char *str, int numargs) { + VPROF_BUDGET("LuaState::CallGlobal", "Lua"); + CFastTimer timer; + timer.Start(); + + SwitchState(); + { + TIME_SCOPE2(lol); + lua_getglobal(l, str); + int err = lua_pcall(l, numargs, 0, 0); + if (err) { + const char *errstr = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errstr); + } + } + stackDump(l); + lua_settop(l,0); + + timer.End(); + script_exec_time += timer.GetDuration().GetSeconds(); + script_exec_time_tick += timer.GetDuration().GetSeconds(); + } + + + int LuaState::Call(int numargs, int numret) { + VPROF_BUDGET("LuaState::Call", "Lua"); + TIME_SCOPE2(call); + CFastTimer timer; + timer.Start(); + SwitchState(); + + int err = lua_pcall(l, numargs, numret, 0); + if (err) { + const char *errstr = lua_tostring(l, -1); + SendWarningConsoleMessageToAdmins("%s\n", errstr); + } + + timer.End(); + script_exec_time += timer.GetDuration().GetSeconds(); + script_exec_time_tick += timer.GetDuration().GetSeconds(); + return err; + } + + bool LuaState::CheckGlobal(const char* name) { + int type = lua_getglobal(l, name); + if (type != LUA_TNIL) { + return true; + } + lua_pop(l, -1); + return false; + } + void LuaState::UpdateTimers() { + + VPROF_BUDGET("LuaState::UpdateTimers", "Lua"); + SwitchState(); + + for (auto it = timers.begin(); it != timers.end();) { + auto &timer = *it; + + if (timer.m_flNextCallTime < gpGlobals->curtime) { + lua_rawgeti(l, LUA_REGISTRYINDEX, timer.m_iRefFunc); + if (timer.m_iRefParam != 0) + lua_rawgeti(l, LUA_REGISTRYINDEX, timer.m_iRefParam); + + int err = Call(timer.m_iRefParam != 0 ? 1 : 0, 1); + bool stop = false; + if (timer.m_iRepeats > 0) { + if (--timer.m_iRepeats <= 0) { + stop = true; + } + } + if (!lua_isnil(l, -1) && !lua_toboolean(l, -1)) { + stop = true; + } + if (!stop) { + lua_settop(l,0); + timer.m_flNextCallTime = gpGlobals->curtime + timer.m_flDelay; + } + else { + timer.Destroy(l); + it = timers.erase(it); + lua_settop(l,0); + + if (timers.empty()) + AllTimersRemoved(); + continue; + } + } + it++; + } + } + + int LuaState::AddTimer(float delay, int repeats, int reffunc, int refparam) { + m_iNextTimerID++; + if (timers.empty()) + TimerAdded(); + timers.emplace_back(m_iNextTimerID, delay, repeats, reffunc, refparam); + return m_iNextTimerID; + } + + bool LuaState::StopTimer(int id) { + for (auto it = timers.begin(); it != timers.end(); it++) { + if (it->m_iID == id) { + it->Destroy(l); + timers.erase(it); + + if (timers.empty()) + AllTimersRemoved(); + + return true; + } + } + return false; + } + + void LuaState::EntityDeleted(CBaseEntity *entity) { + callbackEntities.erase(entity); + + lua_rawgeti(l, LUA_REGISTRYINDEX, m_iEntityTableStorage); + lua_pushnil(l); + lua_rawsetp(l, -2, entity); + lua_pop(l, 1); + } + + void LuaState::EntityCallbackAdded(CBaseEntity *entity) { + callbackEntities.insert(entity); + } + + void LuaState::Activate() { + // Tell scripts about every connected player so far + ForEachTFPlayer([&](CTFPlayer *player){ + if (this->CheckGlobal("OnPlayerConnected")) { + LEntityAlloc(l, player); + this->Call(1, 0); + } + }); + } + + void LuaTimer::Destroy(lua_State *l) { + luaL_unref(l, LUA_REGISTRYINDEX, m_iRefFunc); + luaL_unref(l, LUA_REGISTRYINDEX, m_iRefParam); + } + + LuaState *state = nullptr; + CON_COMMAND_F(sig_lua_test, "", FCVAR_NONE) + { + if (state == nullptr) state = new LuaState(); + state->DoString(args[1], true); + } + CON_COMMAND_F(sig_lua_file, "", FCVAR_NONE) + { + if (state == nullptr) state = new LuaState(); + state->DoFile(args[1], true); + } + CON_COMMAND_F(sig_lua_call, "", FCVAR_NONE) + { + if (state == nullptr) state = new LuaState(); + state->CallGlobal(args[1], 0); + } + + DETOUR_DECL_MEMBER(void, CBaseEntity_Activate) + { + auto entity = reinterpret_cast(this); + DETOUR_MEMBER_CALL(CBaseEntity_Activate)(); + auto mod = entity->GetEntityModule("luaentity"); + if (mod != nullptr) { + mod->FireCallback(ON_ACTIVATE); + } + } + + DETOUR_DECL_STATIC(void, DispatchSpawn, CBaseEntity *entity) + { + DETOUR_STATIC_CALL(DispatchSpawn)(entity); + auto mod = entity->GetEntityModule("luaentity"); + if (mod != nullptr && !entity->IsPlayer()) { + mod->FireCallback(ON_SPAWN); + } + } + + DETOUR_DECL_STATIC(CBaseEntity *, CreateEntityByName, const char *className, int iForceEdictIndex) + { + auto entity = DETOUR_STATIC_CALL(CreateEntityByName)(className, iForceEdictIndex); + if (entity != nullptr && !entity_create_callbacks.empty()) { + for(auto &callback : entity_create_callbacks) { + if (entity->GetClassnameString() == callback.classname || (callback.wildcard && NamesMatchCaseSensitve(STRING(callback.classname), entity->GetClassnameString()))) { + auto l = callback.state->GetState(); + lua_rawgeti(l, LUA_REGISTRYINDEX, callback.func); + LEntityAlloc(l, entity); + lua_pushstring(l, className); + callback.state->Call(2, 0); + } + } + } + return entity; + } + + RefCount rc_CBaseEntity_TakeDamage; + + DETOUR_DECL_MEMBER(int, CBaseEntity_TakeDamage, CTakeDamageInfo &info) + { + SCOPED_INCREMENT(rc_CBaseEntity_TakeDamage); + + CBaseEntity *entity = reinterpret_cast(this); + + bool overriden = false; + auto mod = entity->GetEntityModule("luaentity"); + int damage = 0; + + int health_pre = entity->GetHealth(); + if (mod != nullptr) { + CTakeDamageInfo infooverride = info; + for (auto &pair : mod->callbacks[ON_DAMAGE_RECEIVED_PRE]) { + auto l = pair.state->GetState(); + DamageInfoToTable(l, infooverride); + lua_pushvalue(l, -1); + lua_rawgeti(l, LUA_REGISTRYINDEX, pair.func); + LEntityAlloc(l, entity); + lua_pushvalue(l, -3); + pair.state->Call(2, 1); + if (lua_toboolean(l, -1)) { + overriden = true; + TableToDamageInfo(l, -2, infooverride); + } + + lua_settop(l, 0); + } + if(overriden) { + damage = DETOUR_MEMBER_CALL(CBaseEntity_TakeDamage)(infooverride); + } + } + + if (!overriden) { + damage = DETOUR_MEMBER_CALL(CBaseEntity_TakeDamage)(info); + } + + if (mod != nullptr) { + for (auto &pair : mod->callbacks[ON_DAMAGE_RECEIVED_POST]) { + auto l = pair.state->GetState(); + lua_rawgeti(l, LUA_REGISTRYINDEX, pair.func); + LEntityAlloc(l, entity); + DamageInfoToTable(l, info); + lua_pushnumber(l, health_pre); + pair.state->Call(3, 0); + + lua_settop(l, 0); + } + } + + return damage; + } + + DETOUR_DECL_MEMBER(bool, CBaseEntity_AcceptInput, const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID) + { + CBaseEntity *entity = reinterpret_cast(this); + auto mod = entity->GetEntityModule("luaentity"); + if (mod != nullptr) { + std::function func = [&](LuaState *state){ + lua_pushstring(state->GetState(), szInputName); + LFromVariant(state->GetState(), Value); + LEntityAlloc(state->GetState(), pActivator); + LEntityAlloc(state->GetState(), pCaller); + }; + bool stop = false; + std::function funcReturn = [&](LuaState *state){ + stop = lua_toboolean(state->GetState(), -1); + }; + mod->FireCallback(ON_INPUT, &func, 4, &funcReturn, 1); + if (stop) { + return true; + } + } + return DETOUR_MEMBER_CALL(CBaseEntity_AcceptInput)(szInputName, pActivator, pCaller, Value, outputID); + } + + + DETOUR_DECL_MEMBER(void, CBaseEntityOutput_FireOutput, variant_t Value, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay) + { + + CBaseEntityOutput *output = reinterpret_cast(this); + + auto mod = pCaller != nullptr ? pCaller->GetEntityModule("luaentity") : nullptr; + if (mod != nullptr) { + + auto datamap = pCaller->GetDataDescMap(); + const char *name = ""; + bool found = false; + for (datamap_t *dmap = datamap; dmap != NULL; dmap = dmap->baseMap) { + // search through all the readable fields in the data description, looking for a match + for (int i = 0; i < dmap->dataNumFields; i++) { + if ((dmap->dataDesc[i].flags & FTYPEDESC_OUTPUT) && ((CBaseEntityOutput*)(((char*)pCaller) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ])) == output) { + name = dmap->dataDesc[i].externalName; + found = true; + break; + } + } + if (found) break; + } + + std::function func = [&](LuaState *state){ + lua_pushstring(state->GetState(), name); + LFromVariant(state->GetState(), Value); + LEntityAlloc(state->GetState(), pActivator); + }; + bool stop = false; + std::function funcReturn = [&](LuaState *state){ + stop = lua_toboolean(state->GetState(), -1); + }; + mod->FireCallback(ON_OUTPUT, &func, 3, &funcReturn, 1); + if (stop) { + return; + } + } + return DETOUR_MEMBER_CALL(CBaseEntityOutput_FireOutput)(Value, pActivator, pCaller, fDelay); + } + + DETOUR_DECL_MEMBER(void, CServerGameClients_ClientPutInServer, edict_t *edict, const char *playername) + { + DETOUR_MEMBER_CALL(CServerGameClients_ClientPutInServer)(edict, playername); + for(auto state : LuaState::List()) { + + if (state->CheckGlobal("OnPlayerConnected")) { + LEntityAlloc(state->GetState(), GetContainingEntity(edict)); + state->Call(1, 0); + } + } + } + + DETOUR_DECL_MEMBER(void, CTFPlayer_UpdateOnRemove) + { + auto player = reinterpret_cast(this); + DETOUR_MEMBER_CALL(CTFPlayer_UpdateOnRemove)(); + for(auto state : LuaState::List()) { + + if (state->CheckGlobal("OnPlayerDisconnected")) { + LEntityAlloc(state->GetState(), player); + state->Call(1, 0); + } + } + } + + DETOUR_DECL_MEMBER(void, CTFPlayer_PlayerRunCommand, CUserCmd* cmd, IMoveHelper* moveHelper) + { + CTFPlayer* player = reinterpret_cast(this); + int prebuttons = player->m_nButtons; + if (prebuttons != cmd->buttons) { + auto mod = player->GetEntityModule("luaentity"); + if (mod != nullptr) { + for (int i = 0; i < 32; i++) { + int mask = 1 << i; + if ((cmd->buttons & mask) != (prebuttons & mask)) { + std::function func = [&](LuaState *state){ + lua_pushinteger(state->GetState(), mask); + }; + //bool stop = false; + //std::function funcReturn = [&](LuaState *state){ + // stop = lua_toboolean(state->GetState(), -1); + //}; + bool release = (cmd->buttons & mask) && !(prebuttons & mask); + mod->FireCallback(release ? ON_KEY_PRESSED : ON_KEY_RELEASED, &func, 1); + //if (stop) { + // cmd->buttons = release ? cmd->buttons | mask : cmd->buttons & ~(mask); + //} + } + } + } + } + DETOUR_MEMBER_CALL(CBasePlayer_PlayerRunCommand)(cmd, moveHelper); + } + + bool profile[34] = {}; + DETOUR_DECL_MEMBER(bool, CTFPlayer_ClientCommand, const CCommand& args) + { + auto player = reinterpret_cast(this); + if (player != nullptr && ENTINDEX(player) < 34) { + if (FStrEq(args[0], "sig_lua_prof_start")) { + profile[ENTINDEX(player)] = 1; + return true; + } + if (FStrEq(args[0], "sig_lua_prof_end")) { + profile[ENTINDEX(player)] = 0; + return true; + } + } + + return DETOUR_MEMBER_CALL(CTFPlayer_ClientCommand)(args); + } + + DETOUR_DECL_MEMBER(void, CBaseEntity_Event_Killed, CTakeDamageInfo &info) + { + CBaseEntity *entity = reinterpret_cast(this); + + bool overriden = false; + auto mod = entity->GetEntityModule("luaentity"); + + if (mod != nullptr) { + CTakeDamageInfo infooverride = info; + for (auto &pair : mod->callbacks[ON_DEATH]) { + auto l = pair.state->GetState(); + DamageInfoToTable(l, infooverride); + lua_pushvalue(l, -1); + lua_rawgeti(l, LUA_REGISTRYINDEX, pair.func); + LEntityAlloc(l, entity); + lua_pushvalue(l, -3); + pair.state->Call(2, 1); + if (lua_toboolean(l, -1)) { + overriden = true; + TableToDamageInfo(l, -2, infooverride); + } + + lua_settop(l, 0); + } + if(overriden) { + DETOUR_MEMBER_CALL(CBaseEntity_Event_Killed)(infooverride); + return; + } + } + DETOUR_MEMBER_CALL(CBaseEntity_Event_Killed)(info); + } + + DETOUR_DECL_MEMBER(void, CBaseCombatCharacter_Event_Killed, CTakeDamageInfo &info) + { + CBaseEntity *entity = reinterpret_cast(this); + + bool overriden = false; + auto mod = entity->GetEntityModule("luaentity"); + + if (mod != nullptr) { + CTakeDamageInfo infooverride = info; + for (auto &pair : mod->callbacks[ON_DEATH]) { + auto l = pair.state->GetState(); + DamageInfoToTable(l, infooverride); + lua_pushvalue(l, -1); + lua_rawgeti(l, LUA_REGISTRYINDEX, pair.func); + LEntityAlloc(l, entity); + lua_pushvalue(l, -3); + pair.state->Call(2, 1); + if (lua_toboolean(l, -1)) { + overriden = true; + TableToDamageInfo(l, -2, infooverride); + } + + lua_settop(l, 0); + } + if(overriden) { + DETOUR_MEMBER_CALL(CBaseCombatCharacter_Event_Killed)(infooverride); + return; + } + } + DETOUR_MEMBER_CALL(CBaseCombatCharacter_Event_Killed)(info); + } + + DETOUR_DECL_MEMBER(void, CTFPlayer_Spawn) + { + DETOUR_MEMBER_CALL(CTFPlayer_Spawn)(); + CTFPlayer *player = reinterpret_cast(this); + auto mod = player->GetEntityModule("luaentity"); + if (mod != nullptr) { + mod->FireCallback(ON_SPAWN); + } + + } + + class CMod : public IMod, public IModCallbackListener, public IFrameUpdatePostEntityThinkListener + { + public: + CMod() : IMod("Util:Lua") + { + MOD_ADD_DETOUR_MEMBER_PRIORITY(CBaseEntity_AcceptInput, "CBaseEntity::AcceptInput", HIGH); + MOD_ADD_DETOUR_MEMBER_PRIORITY(CBaseEntityOutput_FireOutput, "CBaseEntityOutput::FireOutput", HIGH); + MOD_ADD_DETOUR_MEMBER_PRIORITY(CBaseEntity_TakeDamage, "CBaseEntity::TakeDamage", HIGH); + MOD_ADD_DETOUR_MEMBER(CBaseEntity_Activate, "CBaseEntity::Activate"); + MOD_ADD_DETOUR_STATIC(DispatchSpawn, "DispatchSpawn"); + MOD_ADD_DETOUR_STATIC(CreateEntityByName, "CreateEntityByName"); + MOD_ADD_DETOUR_MEMBER(CServerGameClients_ClientPutInServer, "CServerGameClients::ClientPutInServer"); + MOD_ADD_DETOUR_MEMBER(CTFPlayer_UpdateOnRemove, "CTFPlayer::UpdateOnRemove"); + MOD_ADD_DETOUR_MEMBER(CTFPlayer_PlayerRunCommand, "CTFPlayer::PlayerRunCommand"); + MOD_ADD_DETOUR_MEMBER(CTFPlayer_ClientCommand, "CTFPlayer::ClientCommand"); + MOD_ADD_DETOUR_MEMBER(CBaseEntity_Event_Killed, "CBaseEntity::Event_Killed"); + MOD_ADD_DETOUR_MEMBER(CBaseCombatCharacter_Event_Killed, "CBaseCombatCharacter::Event_Killed"); + MOD_ADD_DETOUR_MEMBER(CTFPlayer_Spawn, "CTFPlayer::Spawn"); + + } + + virtual void PreLoad() override + { + stringTSendProxy = AddrManager::GetAddr("SendProxy_StringT_To_String"); + } + + virtual bool ShouldReceiveCallbacks() const override { return this->IsEnabled(); } + + virtual bool EnableByDefault() { return true; } + + virtual void OnEnable() override + { + sendproxies = gamedll->GetStandardSendProxies(); + } + + virtual void LevelInitPreEntity() override + { + if (state != nullptr) { delete state; state = nullptr;} + + sendproxies = gamedll->GetStandardSendProxies(); + } + + virtual void FrameUpdatePostEntityThink() override + { + static double script_exec_time_tick_max = 0.0; + if (script_exec_time_tick > script_exec_time_tick_max ) { + script_exec_time_tick_max = script_exec_time_tick; + } + script_exec_time_tick = 0.0; + if (gpGlobals->tickcount % 66 == 0) { + for (int i = 0; i < 34; i++) { + if (profile[i]) { + auto player = UTIL_PlayerByIndex(i); + if (player != nullptr) { + ClientMsg(player, "Lua script execution time: [avg: %.9fs (%d%%)| max: %.9fs (%d%%)]\n", script_exec_time / 66, (int)(script_exec_time * 100), script_exec_time_tick_max, (int)((script_exec_time_tick_max / 0.015) * 100) ); + } + } + } + script_exec_time = 0; + script_exec_time_tick_max = 0; + } + + if (state != nullptr) + state->UpdateTimers(); + + if (!LuaState::List().empty()) + { + for(auto state : LuaState::List()) { + + if (state->CheckGlobal("OnGameTick")) { + state->Call(0, 0); + } + } + } + } + }; + CMod s_Mod; +} \ No newline at end of file diff --git a/src/util/lua.h b/src/util/lua.h new file mode 100644 index 00000000..6de7957d --- /dev/null +++ b/src/util/lua.h @@ -0,0 +1,83 @@ +#ifndef _INCLUDE_SIGSEGV_UTIL_LUA_H_ +#define _INCLUDE_SIGSEGV_UTIL_LUA_H_ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} + +namespace Util::Lua +{ + + class LuaTimer + { + public: + LuaTimer(int id, float delay, int repeats, int reffunc, int refparam) : m_iID(id), m_flNextCallTime(gpGlobals->curtime + delay), m_flDelay(delay), m_iRepeats(repeats), m_iRefFunc(reffunc), m_iRefParam(refparam) {} + + void Destroy(lua_State *l); + + int m_iID; + float m_flNextCallTime; + float m_flDelay; + int m_iRepeats; + int m_iRefFunc; + int m_iRefParam; + }; + + class LuaState : public AutoList + { + public: + LuaState(); + + virtual ~LuaState(); + + lua_State *GetState() { + return l; + } + + void SwitchState(); + + void DoString(const char *str, bool execute); + + void DoFile(const char *str, bool execute); + + void CallGlobal(const char *str, int numargs); + + bool CheckGlobal(const char *str); + + int Call(int numargs, int numret); + + void UpdateTimers(); + + virtual void TimerAdded() {} + virtual void AllTimersRemoved() {} + + int AddTimer(float delay, int repeats, int reffunc, int refparam); + bool StopTimer(int id); + + bool HasTimers() { return !timers.empty(); } + + void EntityDeleted(CBaseEntity *entity); + void EntityCallbackAdded(CBaseEntity *entity); + + void Activate(); + private: + lua_State *l; + + int m_iVectorMeta = LUA_NOREF; + const void *m_pVectorMeta = nullptr; + int m_iEntityMeta = LUA_NOREF; + const void *m_pEntityMeta = nullptr; + int m_iEntityTableStorage = LUA_NOREF; + int m_iPropMeta = LUA_NOREF; + + std::vector timers; + int m_iNextTimerID = 0; + std::unordered_set callbackEntities; + }; + + void LFromVariant(lua_State *l, variant_t &variant); + const EHANDLE *LEntityAlloc(lua_State *l, CBaseEntity *entity); +} +#endif \ No newline at end of file diff --git a/src/util/misc.h b/src/util/misc.h index 773a4bd9..cc0dda7e 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -731,6 +731,60 @@ inline const char *FindCaseInsensitiveReverse(const char *string, const char* ne return nullptr; } +template +inline bool RemoveFirstElement(Container &container, UnaryPredicate p) +{ + for (auto it = container.begin(); it != container.end(); it++) { + if (p(*it)) { + container.erase(it); + return true; + } + } + return false; +} + +template +inline void RemoveIf(Container &container, UnaryPredicate p) +{ + for (auto it = container.begin(); it != container.end();) { + if (p(*it)) { + it = container.erase(it); + } + else { + it++; + } + } +} + +enum IterateAction +{ + IT_CONTINUE, + IT_BREAK, + IT_REMOVE, + IT_REMOVE_BREAK, +}; + +template +inline void Iterate(Container &container, Func f) +{ + for (auto it = container.begin(); it != container.end();) { + IterateAction action = f(*it); + if (action == IT_CONTINUE) { + it++; + } + else if (action == IT_REMOVE) { + it = container.erase(it); + } + else if (action == IT_REMOVE_BREAK) { + container.erase(it); + return; + } + else { + return; + } + } +} + template std::string CFmtStdStr(ARGS&&... args) {