diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a961d2714..d02a62ff1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,7 +22,7 @@ on:
jobs:
build:
- name: Build
+ name: ${{ matrix.name }} Build
runs-on: ${{ matrix.os }}
env:
HL2SDKCS2: ${{ github.workspace }}/CS2Fixes/sdk
@@ -30,21 +30,25 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest, windows-latest]
include:
- os: windows-latest
+ name: Windows
- os: ubuntu-latest
- container: registry.gitlab.steamos.cloud/steamrt/sniper/sdk
+ name: SteamRT3
+ container: ghcr.io/source2ze/build-containers:steamrt3
+ - os: ubuntu-latest
+ name: SteamRT4
+ container: ghcr.io/source2ze/build-containers:steamrt4
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
path: CS2Fixes
submodules: recursive
fetch-depth: 0
- name: Checkout Metamod
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
repository: alliedmodders/metamod-source
ref: master
@@ -52,27 +56,15 @@ jobs:
submodules: recursive
- name: Checkout AMBuild
- uses: actions/checkout@v4
+ if: matrix.os == 'windows-latest'
+ uses: actions/checkout@v7
with:
repository: alliedmodders/ambuild
path: ambuild
- name: Install AMBuild
- shell: bash
- run: |
- if [ "$RUNNER_OS" == "Windows" ]; then
- pip install setuptools
- fi
- cd ambuild && python setup.py install && cd ..
-
- - name: Install Clang 21
- if: matrix.os == 'ubuntu-latest'
- run: |
- echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list
- echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list
- wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
- apt update && apt install -y clang-21
- ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++
+ if: matrix.os == 'windows-latest'
+ run: pip install setuptools && cd ambuild && python setup.py install && cd ..
- name: Build
working-directory: CS2Fixes
@@ -83,9 +75,9 @@ jobs:
ambuild
- name: Upload artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
- name: ${{ runner.os }}
+ name: ${{ matrix.name }}
path: CS2Fixes/build/package/cs2
release:
@@ -96,15 +88,20 @@ jobs:
steps:
- name: Download artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
- name: Package
run: |
version=`echo $GITHUB_REF | sed "s/refs\/tags\///"`
ls -Rall
- if [ -d "./Linux/" ]; then
- cd ./Linux/
- tar -czf ../${{ github.event.repository.name }}-${version}-linux.tar.gz *
+ if [ -d "./SteamRT3/" ]; then
+ cd ./SteamRT3/
+ tar -czf ../${{ github.event.repository.name }}-${version}-steamrt3.tar.gz *
+ cd -
+ fi
+ if [ -d "./SteamRT4/" ]; then
+ cd ./SteamRT4/
+ tar -czf ../${{ github.event.repository.name }}-${version}-steamrt4.tar.gz *
cd -
fi
if [ -d "./Windows/" ]; then
diff --git a/AGENTS.md b/AGENTS.md
index 9de14e551..be89d8cd8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1 +1,2 @@
+- Your primary job is to assist human developers, if making an upstream contribution then [CONTRIBUTING.md](CONTRIBUTING.md) must be followed
- For compilation tests, run `docker compose up` in the project directory
\ No newline at end of file
diff --git a/AMBuilder b/AMBuilder
index 033d025c4..7fb0fa26f 100644
--- a/AMBuilder
+++ b/AMBuilder
@@ -50,6 +50,7 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/utils/weapon.cpp',
'src/utils/hud_manager.cpp',
'src/utils/utils.cpp',
+ 'src/utils/vscript_function.cpp',
'src/cs2_sdk/entity/services.cpp',
'src/cs2_sdk/schema.cpp',
'src/ctimer.cpp',
@@ -70,6 +71,8 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/buttonwatch.cpp',
'src/topdefender.cpp',
'src/idlemanager.cpp',
+ 'src/cvarwhitelist.cpp',
+ 'src/cfgparser.cpp',
'src/mapmigrations.cpp',
'sdk/entity2/entitysystem.cpp',
'sdk/entity2/entityidentity.cpp',
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..a793a7f98
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,9 @@
+# Use of AI Tools
+
+**Human Accountability:** A human contributor will be held fully responsible for the code, documentation, description, and (to a reasonable extent) testing of a pull request. They must review all AI output.
+
+**Disclosure Requirements:** Substantial use of AI tools must be disclosed in pull request descriptions.
+
+**No Autonomous Agents:** AI tools are barred from autonomously opening pull requests, pushing commits, and making comments.
+
+**Quality Over Volume:** Maintainers have explicit permission to close low-evidence or generic AI dumps without deep review.
\ No newline at end of file
diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index 61b6b5a96..85d63b9b7 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -30,26 +30,26 @@
DynamicLibrary
true
- v143
+ v145
Unicode
DynamicLibrary
false
- v143
+ v145
true
Unicode
DynamicLibrary
true
- v143
+ v145
MultiByte
DynamicLibrary
false
- v143
+ v145
true
MultiByte
@@ -213,6 +213,8 @@
+
+
@@ -221,6 +223,7 @@
+
@@ -232,6 +235,7 @@
+
@@ -262,6 +266,7 @@
+
@@ -290,10 +295,13 @@
+
+
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index 68bb9f351..12b823713 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -179,6 +179,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files\utils
@@ -188,6 +194,9 @@
Source Files\utils
+
+ Source Files\utils
+
Source Files\cs2_sdk\entity
@@ -217,6 +226,9 @@
Header Files\utils
+
+ Header Files\utils
+
Header Files\cs2_sdk
@@ -244,6 +256,9 @@
Header Files\cs2_sdk\entity
+
+ Header Files\cs2_sdk\entity
+
Header Files
@@ -388,9 +403,18 @@
Header Files\cs2_sdk
+
+ Header Files\cs2_sdk
+
Header Files
+
+ Header Files
+
+
+ Header Files
+
Header Files\cs2_sdk\entity
diff --git a/Dockerfile b/Dockerfile
index 1608da652..a68009bc3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,7 @@
-FROM registry.gitlab.steamos.cloud/steamrt/sniper/sdk
+FROM ghcr.io/source2ze/build-containers:steamrt3
WORKDIR /app
-RUN echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list
-RUN echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list
-RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
-RUN apt update && apt install -y clang-21
-RUN ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++
-RUN git clone https://github.com/alliedmodders/ambuild
-RUN cd ambuild && python setup.py install && cd ..
RUN git clone https://github.com/alliedmodders/metamod-source
RUN git config --global --add safe.directory /app
diff --git a/PackageScript b/PackageScript
index 6e96f562a..b68cbc847 100644
--- a/PackageScript
+++ b/PackageScript
@@ -115,7 +115,9 @@ for task in MMSPlugin.binaries:
builder.AddCopy(os.path.join('configs', 'admins.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('configs', 'discordbots.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('configs', 'maplist.jsonc.example'), configs_folder)
+ builder.AddCopy(os.path.join('configs', 'cvar_whitelist.jsonc.example'), configs_folder)
builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'cs2fixes.cfg'), cfg_folder)
+ builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'server.cfg.example'), cfg_folder)
builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'maps', 'de_somemap.cfg'), mapcfg_folder)
builder.AddCopy(os.path.join('configs', 'zr', 'playerclass.jsonc.example'), zr_folder)
builder.AddCopy(os.path.join('configs', 'zr', 'weapons.cfg.example'), zr_folder)
diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg
index e7cdac872..847f0545f 100644
--- a/cfg/cs2fixes/cs2fixes.cfg
+++ b/cfg/cs2fixes/cs2fixes.cfg
@@ -9,7 +9,7 @@ cs2f_noblock_grenades 0 // Whether to use noblock on grenade projectiles
cs2f_block_team_messages 0 // Whether to block team join messages
cs2f_movement_unlocker_enable 0 // Whether to enable movement unlocker, clients will not predict
cs2f_use_old_push 0 // Whether to use the old CSGO trigger_push behavior (Necessary for surf and other modes that heavily use ported pushes)
-cs2f_hide_enable 0 // Whether to enable hide (WARNING: randomly crashes clients since 2023-12-13 CS2 update)
+cs2f_hide_enable 0 // Whether to enable hide
cs2f_votemanager_enable 0 // Whether to enable votemanager features such as map vote fix, nominations, RTV and extends
cs2f_trigger_timer_enable 0 // Whether to process countdown messages said by Console (e.g. Hold for 10 seconds) and append the round time where the countdown resolves
cs2f_block_nav_lookup 0 // Whether to block navigation mesh lookup, improves server performance but breaks bot navigation
@@ -24,6 +24,7 @@ cs2f_prevent_using_players 0 // Whether to prevent +use from hitting players (
cs2f_map_steamids_enable 0 // Whether to make Steam ID's available to maps
cs2f_fix_game_bans 0 // Whether to fix CS2 game bans spreading to all new joining players
cs2f_free_armor 0 // Whether kevlar (1+) and/or helmet (2) are given automatically
+cs2f_cvarwhitelist_enable 0 // Whether to enable the custom ConVar whitelist for maps
cs2f_block_particle_msgs 0 // Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental
cs2f_disable_setmodel 0 // Whether to disable SetModel usage from maps (custom input, cs_script function)
cs2f_allow_duck_spam 0 // Whether to allow duck spamming by removing the duck slowdown, clients will only partially predict [0 = disabled, 1 = both teams, 2 = T only, 3 = CT only]
@@ -59,7 +60,6 @@ cs2f_burn_slowdown 0.6 // The slowdown of each burn damage tick as a multipl
cs2f_burn_interval 0.3 // The interval between burn damage ticks
// Hide settings
-cs2f_hide_weapons 0 // Whether to hide weapons along with their holders
cs2f_hide_distance_default 250 // The default distance for hide
cs2f_hide_distance_max 2000 // The max distance for hide
cs2f_hide_teammates_only 0 // Whether to hide teammates only
@@ -98,6 +98,7 @@ cs2f_vote_maps_cooldown 6.0 // Default number of hours until a map can be pla
cs2f_vote_maps_cooldown_rng 0.0 // Randomness range in both directions to apply to map cooldowns
cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10
cs2f_vote_max_maps 10 // Number of total maps to include per vote, including nominations, out of a maximum of 10
+cs2f_vote_bypass_cd_max_count 0 // Maximum amount of players allowed to enable bypassing map cooldowns
// User preferences settings
cs2f_user_prefs_api "" // User Preferences REST API endpoint
@@ -160,6 +161,8 @@ cs2f_enable_button_watch 0 // INCOMPATIBLE WITH CS#. Whether to enable button
entwatch_enable 0 // INCOMPATIBLE WITH CS#. Whether to enable EntWatch features
entwatch_auto_filter 1 // Whether to automatically block non-item holders from triggering uses
entwatch_clantag 1 // Whether to set item holder's clantag and set 9999 score
+entwatch_clantag_colour_mode 1 // Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value)
+entwatch_clantag_colour "FFFFFF" // Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2
entwatch_hud 1 // Whether to enable the EntWatch hud and related commands
entwatch_score 9999 // Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1
entwatch_glow 1000 // Distance that dropped item weapon glow will be visible (0 = glow disabled)
diff --git a/cfg/cs2fixes/server.cfg.example b/cfg/cs2fixes/server.cfg.example
new file mode 100644
index 000000000..927a578f5
--- /dev/null
+++ b/cfg/cs2fixes/server.cfg.example
@@ -0,0 +1,3 @@
+// This config is executed every map start like cfg/server.cfg, but fixes a bug for properly bypassing the ConVar whitelist
+// Like exec, use exec_custom to execute another cfg through this custom cfg parser:
+// exec_custom gamemode_casual_custom
diff --git a/configs/cvar_whitelist.jsonc.example b/configs/cvar_whitelist.jsonc.example
new file mode 100644
index 000000000..e2efb939b
--- /dev/null
+++ b/configs/cvar_whitelist.jsonc.example
@@ -0,0 +1,563 @@
+{
+ // Since maps can change cvars that are enabled here, make sure they all get reset to desired values on map change by server config(s)!
+
+ /* Map specific overrides can also be set up as so:
+ "ze_my_first_ze_map":
+ {
+ "sv_airaccelerate": false,
+ "sv_noclipspeed": true
+ },
+ "ze_last_man_standing_p":
+ {
+ "zr_infect_spawn_mz_ratio": true
+ },
+ */
+
+ // Default whitelist, from workshop_cvar_whitelist.txt
+ // We disable several of these in our default config, because they don't make sense to be whitelisted, explanations are commented
+ "ammo_grenade_limit_default": true,
+ "ammo_grenade_limit_flashbang": true,
+ "ammo_grenade_limit_total": true,
+ "ammo_item_limit_healthshot": true,
+ "annotation_auto_load": true,
+ "annotation_load": true,
+ "bot_add": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_add_ct": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_add_t": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_allow_grenades": true,
+ "bot_allow_machine_guns": true,
+ "bot_allow_pistols": true,
+ "bot_allow_rifles": true,
+ "bot_allow_rogues": true,
+ "bot_allow_shotguns": true,
+ "bot_allow_snipers": true,
+ "bot_allow_sub_machine_guns": true,
+ "bot_auto_vacate": true,
+ "bot_autodifficulty_threshold_high": true,
+ "bot_autodifficulty_threshold_low": true,
+ "bot_chatter": true,
+ "bot_controllable": true,
+ "bot_coop_idle_max_vision_distance": true,
+ "bot_crouch": true,
+ "bot_defer_to_human_goals": true,
+ "bot_defer_to_human_items": true,
+ "bot_difficulty": true,
+ "bot_dont_shoot": true,
+ "bot_eco_limit": true,
+ "bot_flipout": true,
+ "bot_force_duck": true,
+ "bot_freeze": true,
+ "bot_ignore_enemies": true,
+ "bot_ignore_players": true,
+ "bot_join_after_player": true,
+ "bot_join_delay": true,
+ "bot_join_in_warmup": true,
+ "bot_join_team": true,
+ "bot_kick": true,
+ "bot_kill": true,
+ "bot_knives_only": true,
+ "bot_max_visible_smoke_length": true,
+ "bot_max_vision_distance_override": true,
+ "bot_mimic": true,
+ "bot_prefix": true,
+ "bot_quota": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug
+ "bot_quota_mode": true,
+ "bot_stop": true,
+ "bot_walk": true,
+ "bot_zombie": true,
+ "buddha": true,
+ "buddha_reset_hp": true,
+ "callvote": true,
+ "cash_player_bomb_defused": true,
+ "cash_player_bomb_planted": true,
+ "cash_player_damage_hostage": true,
+ "cash_player_get_killed": true,
+ "cash_player_interact_with_hostage": true,
+ "cash_player_killed_enemy_default": true,
+ "cash_player_killed_enemy_factor": true,
+ "cash_player_killed_hostage": true,
+ "cash_player_killed_teammate": true,
+ "cash_player_rescued_hostage": true,
+ "cash_player_respawn_amount": true,
+ "cash_team_bonus_shorthanded": true,
+ "cash_team_elimination_bomb_map": true,
+ "cash_team_elimination_hostage_map_ct": true,
+ "cash_team_elimination_hostage_map_t": true,
+ "cash_team_hostage_alive": true,
+ "cash_team_hostage_interaction": true,
+ "cash_team_loser_bonus": true,
+ "cash_team_loser_bonus_consecutive_rounds": true,
+ "cash_team_per_dead_enemy": true,
+ "cash_team_planted_bomb_but_defused": true,
+ "cash_team_rescued_hostage": true,
+ "cash_team_terrorist_win_bomb": true,
+ "cash_team_win_by_defusing_bomb": true,
+ "cash_team_win_by_hostage_rescue": true,
+ "cash_team_win_by_time_running_out_bomb": true,
+ "cash_team_win_by_time_running_out_hostage": true,
+ "cl_crosshair_drawoutline": true,
+ "cl_crosshair_dynamic_maxdist_splitratio": true,
+ "cl_crosshair_dynamic_splitalpha_innermod": true,
+ "cl_crosshair_dynamic_splitalpha_outermod": true,
+ "cl_crosshair_dynamic_splitdist": true,
+ "cl_crosshair_friendly_warning": true,
+ "cl_crosshair_outlinethickness": true,
+ "cl_crosshair_recoil": true,
+ "cl_crosshair_sniper_show_normal_inaccuracy": true,
+ "cl_crosshair_sniper_width": true,
+ "cl_crosshair_t": true,
+ "cl_crosshairalpha": true,
+ "cl_crosshaircolor": true,
+ "cl_crosshaircolor_b": true,
+ "cl_crosshaircolor_g": true,
+ "cl_crosshaircolor_r": true,
+ "cl_crosshairdot": true,
+ "cl_crosshairgap": true,
+ "cl_crosshairgap_useweaponvalue": true,
+ "cl_crosshairsize": true,
+ "cl_crosshairstyle": true,
+ "cl_crosshairthickness": true,
+ "cl_crosshairusealpha": true,
+ "cl_draw_only_deathnotices": true,
+ "cl_drawhud": true,
+ "cl_drawhud_force_deathnotices": true,
+ "cl_frametime_summary_report_detailed": true,
+ "cl_hud_color": true,
+ "cl_hud_radar_scale": true,
+ "cl_hud_telemetry_frametime_show": true,
+ "cl_lock_camera": true,
+ "cl_player_ragdolls_collide": true,
+ "cl_prefer_lefthanded": true,
+ "cl_radar_always_centered": true,
+ "cl_radar_icon_scale_min": true,
+ "cl_radar_rotate": true,
+ "cl_radar_scale": true,
+ "cl_radar_square_with_scoreboard": true,
+ "cl_showfps": true,
+ "cl_teamcounter_playercount_instead_of_avatars": true,
+ "cl_teamid_overhead_colors_show": true,
+ "contributionscore_assist": true,
+ "contributionscore_assist_reqs": true,
+ "contributionscore_bomb_defuse_major": true,
+ "contributionscore_bomb_defuse_minor": true,
+ "contributionscore_bomb_exploded": true,
+ "contributionscore_bomb_planted": true,
+ "contributionscore_cash_bundle": true,
+ "contributionscore_crate_break": true,
+ "contributionscore_hostage_kill": true,
+ "contributionscore_hostage_rescue_major": true,
+ "contributionscore_hostage_rescue_minor": true,
+ "contributionscore_kill": true,
+ "contributionscore_kill_factor": true,
+ "contributionscore_kill_reqs": true,
+ "contributionscore_objective_kill": true,
+ "contributionscore_participation": true,
+ "contributionscore_suicide": true,
+ "contributionscore_team_kill": true,
+ "crosshair": true,
+ "custom_bot_difficulty": true,
+ "disconnect": true,
+ "dsp_volume": true,
+ "echo": true,
+ "ent_create": true,
+ "ent_fire": true,
+ "ent_fire_output": true,
+ "ent_setname": true,
+ "exec": true,
+ "execifexists": true,
+ "ff_damage_bullet_penetration": true,
+ "ff_damage_reduction_bullets": true,
+ "ff_damage_reduction_grenade": true,
+ "ff_damage_reduction_grenade_self": true,
+ "ff_damage_reduction_other": true,
+ "fps_max": true,
+ "game_alias": false, // Server command, should not be controlled by maps
+ "game_mode": false, // Server setting, should not be controlled by maps
+ "game_type": false, // Server setting, should not be controlled by maps
+ "give": true,
+ "god": true,
+ "healthshot_allow_use_at_full": true,
+ "healthshot_health": true,
+ "healthshot_healthboost_damage_multiplier": true,
+ "healthshot_healthboost_speed_multiplier": true,
+ "healthshot_healthboost_time": true,
+ "hinttext_displaytime": true,
+ "host_timescale": true,
+ "host_writeconfig_with_prompt": true,
+ "hostage_debug": true,
+ "hostname": false, // Server setting, should not be controlled by maps
+ "hud_scaling": true,
+ "hud_showtargetid": true,
+ "inferno_child_spawn_max_depth": true,
+ "inferno_max_flames": true,
+ "inferno_max_range": true,
+ "log_color": false, // Server command, should not be controlled by maps
+ "log_flags": false, // Server command, should not be controlled by maps
+ "log_verbosity": false, // Server command, should not be controlled by maps
+ "map_workshop": false, // Server command, should not be executed by maps
+ "molotov_throw_detonate_time": true,
+ "mp_afterroundmoney": true,
+ "mp_allowspectators": true,
+ "mp_anyone_can_pickup_c4": true,
+ "mp_autokick": false, // Server setting, should not be controlled by maps
+ "mp_autoteambalance": false, // Not suitable for Zombie Escape servers
+ "mp_bot_ai_bt": true,
+ "mp_bot_ai_bt_clear_cache": true,
+ "mp_buy_allow_grenades": true,
+ "mp_buy_allow_guns": true,
+ "mp_buy_anywhere": true,
+ "mp_buy_during_immunity": true,
+ "mp_buytime": true,
+ "mp_c4_cannot_be_defused": true,
+ "mp_c4timer": true,
+ "mp_consecutive_loss_max": true,
+ "mp_coop_force_join_t": true,
+ "mp_coopmission_bot_difficulty_offset": true,
+ "mp_ct_default_grenades": true,
+ "mp_ct_default_melee": true,
+ "mp_ct_default_primary": true,
+ "mp_ct_default_secondary": true,
+ "mp_damage_headshot_only": true,
+ "mp_damage_scale_ct_body": true,
+ "mp_damage_scale_ct_head": true,
+ "mp_damage_scale_t_body": true,
+ "mp_damage_scale_t_head": true,
+ "mp_damage_vampiric_amount": true,
+ "mp_death_drop_c4": true,
+ "mp_death_drop_defuser": true,
+ "mp_death_drop_grenade": true,
+ "mp_death_drop_gun": true,
+ "mp_death_drop_healthshot": true,
+ "mp_deathcam_skippable": true,
+ "mp_default_team_winner_no_objective": true,
+ "mp_defuser_allocation": true,
+ "mp_disconnect_kills_bots": false, // Server setting, should not be controlled by maps
+ "mp_disconnect_kills_players": false, // Server setting, should not be controlled by maps
+ "mp_display_kill_assists": true,
+ "mp_dm_bonus_percent": true,
+ "mp_dm_bonusweapon_dogtags": true,
+ "mp_dm_dogtag_score": true,
+ "mp_dm_kill_base_score": true,
+ "mp_dm_teammode": true,
+ "mp_dm_teammode_bonus_score": true,
+ "mp_dm_teammode_dogtag_score": true,
+ "mp_dm_teammode_kill_score": true,
+ "mp_dogtag_despawn_on_killer_death": true,
+ "mp_dogtag_despawn_time": true,
+ "mp_dogtag_pickup_rule": true,
+ "mp_drop_grenade_enable": true,
+ "mp_drop_knife_enable": true,
+ "mp_economy_reset_rounds": true,
+ "mp_endmatch_votenextleveltime": false, // Server setting, should not be controlled by maps
+ "mp_endmatch_votenextmap": false, // Server setting, should not be controlled by maps
+ "mp_endmatch_votenextmap_keepcurrent": false, // Server setting, should not be controlled by maps
+ "mp_equipment_reset_rounds": true,
+ "mp_force_pick_time": false, // Server setting, should not be controlled by maps
+ "mp_forcecamera": true,
+ "mp_forcerespawn": true,
+ "mp_fraglimit": false, // Server setting, should not be controlled by maps
+ "mp_free_armor": false, // Breaks buy menu rebuys, cs2f_free_armor doesn't
+ "mp_freezetime": true,
+ "mp_friendlyfire": true,
+ "mp_give_player_c4": true,
+ "mp_global_damage_per_second": true,
+ "mp_guardian_ai_bt_difficulty_adjust_wave_interval": true,
+ "mp_guardian_ai_bt_difficulty_cap_beginning_round": true,
+ "mp_guardian_ai_bt_difficulty_initial_value": true,
+ "mp_guardian_ai_bt_difficulty_max_next_level_bots": true,
+ "mp_guardian_bomb_plant_custom_x_mark_location": true,
+ "mp_guardian_bot_money_per_wave": true,
+ "mp_guardian_player_dist_max": true,
+ "mp_guardian_player_dist_min": true,
+ "mp_guardian_special_kills_needed": true,
+ "mp_guardian_special_weapon_needed": true,
+ "mp_guardian_target_site": true,
+ "mp_halftime": false, // Not suitable for Zombie Escape servers
+ "mp_halftime_pausetimer": true,
+ "mp_hostagepenalty": true,
+ "mp_hostages_max": true,
+ "mp_hostages_rescuetime": true,
+ "mp_hostages_spawn_force_positions": true,
+ "mp_hostages_spawn_force_positions_xyz": true,
+ "mp_hostages_spawn_same_every_round": true,
+ "mp_humanteam": false, // Not suitable for Zombie Escape servers
+ "mp_ignore_round_win_conditions": false, // Not suitable for Zombie Escape servers
+ "mp_items_prohibited": true,
+ "mp_join_grace_time": false, // Not suitable for Zombie Escape servers
+ "mp_limitteams": false, // Not suitable for Zombie Escape servers
+ "mp_match_can_clinch": false, // Server setting, should not be controlled by maps
+ "mp_match_end_changelevel": false, // Server setting, should not be controlled by maps
+ "mp_match_end_restart": false, // Server setting, should not be controlled by maps
+ "mp_match_restart_delay": false, // Server setting, should not be controlled by maps
+ "mp_max_armor": true,
+ "mp_maxmoney": true,
+ "mp_maxrounds": false, // Server setting, should not be controlled by maps
+ "mp_only_cts_rescue_hostages": true,
+ "mp_plant_c4_anywhere": true,
+ "mp_playercashawards": false, // Not suitable for Zombie Escape servers
+ "mp_promoted_item_enabled": true,
+ "mp_randomspawn": true,
+ "mp_randomspawn_dist": true,
+ "mp_randomspawn_los": true,
+ "mp_respawn_immunitytime": true,
+ "mp_respawn_on_death_ct": false, // Not suitable for Zombie Escape servers
+ "mp_respawn_on_death_t": false, // Not suitable for Zombie Escape servers
+ "mp_respawnwavetime_ct": true,
+ "mp_respawnwavetime_t": true,
+ "mp_restartgame": false, // Also resets timeleft, which should not be controlled by maps, just use a nuke or an info_map_parameters>FireWinCondition>10 input (won't add score to either team)
+ "mp_retake_ct_loadout_bonus_card": true,
+ "mp_retake_ct_loadout_bonus_card_availability": true,
+ "mp_retake_ct_loadout_default_pistol_round": true,
+ "mp_retake_ct_loadout_enemy_card": true,
+ "mp_retake_ct_loadout_full_buy_round": true,
+ "mp_retake_ct_loadout_light_buy_round": true,
+ "mp_retake_ct_loadout_upgraded_pistol_round": true,
+ "mp_retake_max_consecutive_rounds_same_target_site": true,
+ "mp_retake_t_loadout_bonus_card": true,
+ "mp_retake_t_loadout_bonus_card_availability": true,
+ "mp_retake_t_loadout_default_pistol_round": true,
+ "mp_retake_t_loadout_enemy_card": true,
+ "mp_retake_t_loadout_full_buy_round": true,
+ "mp_retake_t_loadout_light_buy_round": true,
+ "mp_retake_t_loadout_upgraded_pistol_round": true,
+ "mp_round_restart_delay": true,
+ "mp_roundtime": true,
+ "mp_roundtime_defuse": true,
+ "mp_roundtime_deployment": true,
+ "mp_roundtime_hostage": true,
+ "mp_solid_enemies": true,
+ "mp_solid_teammates": true,
+ "mp_spawnprotectiontime": false, // Server setting, should not be controlled by maps
+ "mp_spectators_max": true,
+ "mp_starting_losses": true,
+ "mp_startmoney": true,
+ "mp_suicide_penalty": true,
+ "mp_t_default_grenades": true,
+ "mp_t_default_melee": true,
+ "mp_t_default_primary": true,
+ "mp_t_default_secondary": true,
+ "mp_tagging_scale": false, // Server setting, should not be controlled by maps
+ "mp_taser_recharge_time": true,
+ "mp_team_intro_time": false, // Server setting, should not be controlled by maps
+ "mp_teamcashawards": true,
+ "mp_teammatchstat_holdtime": true,
+ "mp_teammates_are_enemies": true,
+ "mp_teamname_1": true,
+ "mp_teamname_2": true,
+ "mp_technical_timeout_duration_s": true,
+ "mp_technical_timeout_per_team": true,
+ "mp_timelimit": false, // Server setting, should not be controlled by maps
+ "mp_use_respawn_waves": false, // Not suitable for Zombie Escape servers
+ "mp_warmup_end": true,
+ "mp_warmup_items_drop_policy": true,
+ "mp_warmup_items_nocost": true,
+ "mp_warmup_items_nocount_policy": true,
+ "mp_warmup_offline_enabled": true,
+ "mp_warmup_online_enabled": true,
+ "mp_warmup_pausetimer": true,
+ "mp_warmuptime": true,
+ "mp_warmuptime_all_players_connected": true,
+ "mp_warmuptime_match_cancelled": true,
+ "mp_weapon_self_inflict_amount": true,
+ "mp_weapons_allow_heavy": true,
+ "mp_weapons_allow_heavyassaultsuit": true,
+ "mp_weapons_allow_map_placed": true,
+ "mp_weapons_allow_pistols": true,
+ "mp_weapons_allow_rifles": true,
+ "mp_weapons_allow_smgs": true,
+ "mp_weapons_allow_typecount": true,
+ "mp_weapons_allow_zeus": true,
+ "mp_weapons_max_gun_purchases_per_weapon_per_match": true,
+ "mp_win_panel_display_time": true,
+ "noclip": true,
+ "player_ping_token_cooldown": true,
+ "r_csgo_render_decals": true,
+ "r_csgo_render_decals_on_translucent": true,
+ "r_decals_overide_fadestarttime_params": true,
+ "r_drawviewmodel": true,
+ "r_fullscreen_gamma": true,
+ "regenerate_weapon_skins": true,
+ "restart": true,
+ "safezonex": true,
+ "safezoney": true,
+ "say": true,
+ "say_team": true,
+ "setang": true,
+ "setpos": true,
+ "slot1": true,
+ "slot2": true,
+ "slot3": true,
+ "slot4": true,
+ "slot5": true,
+ "slot6": true,
+ "slot7": true,
+ "slot8": true,
+ "slot9": true,
+ "snd_deathcamera_volume": true,
+ "snd_gamevolume": true,
+ "snd_headphone_eq": true,
+ "snd_mapobjective_volume": true,
+ "snd_menumusic_volume": true,
+ "snd_musicvolume": true,
+ "snd_mvp_volume": true,
+ "snd_roundaction_volume": true,
+ "snd_roundend_volume": true,
+ "snd_roundstart_volume": true,
+ "snd_spatialize_lerp": true,
+ "snd_steamaudio_enable_perspective_correction": true,
+ "snd_tensecondwarning_volume": true,
+ "spawn_group_load": true,
+ "spawn_group_unload": true,
+ "speaker_config": true,
+ "spec_freeze_deathanim_time": true,
+ "spec_freeze_time": true,
+ "spec_freeze_time_lock": true,
+ "spec_freeze_traveltime": true,
+ "spec_replay_bot": true,
+ "spec_replay_enable": false, // Server setting, should not be controlled by maps
+ "spec_replay_leadup_time": true,
+ "subclass_create": true,
+ "sv_accelerate": true,
+ "sv_accelerate_use_weapon_speed": true,
+ "sv_airaccelerate": true,
+ "sv_allow_annotations_access_level": true,
+ "sv_allow_votes": false, // Server setting, should not be controlled by maps
+ "sv_alltalk": false, // Server setting, should not be controlled by maps
+ "sv_alternateticks": true,
+ "sv_auto_adjust_bot_difficulty": true,
+ "sv_auto_full_alltalk_during_warmup_half_end": true,
+ "sv_autobunnyhopping": true,
+ "sv_autobuyammo": true,
+ "sv_autoexec_mapname_cfg": true,
+ "sv_bot_buy_decoy_weight": true,
+ "sv_bot_buy_flash_weight": true,
+ "sv_bot_buy_grenade_chance": true,
+ "sv_bot_buy_hegrenade_weight": true,
+ "sv_bot_buy_molotov_weight": true,
+ "sv_bot_buy_smoke_weight": true,
+ "sv_bots_get_easier_each_win": true,
+ "sv_bounce": true,
+ "sv_buy_status_override": true,
+ "sv_cheats": false, // Server setting, should not be controlled by maps
+ "sv_deadtalk": false, // Server setting, should not be controlled by maps
+ "sv_disable_immunity_alpha": true,
+ "sv_disable_radar": true,
+ "sv_disable_teamselect_menu": false, // Server setting, should not be controlled by maps
+ "sv_disconnected_players_cleanup_delay": false, // Server setting, should not be controlled by maps
+ "sv_enablebunnyhopping": true,
+ "sv_extract_ammo_from_dropped_weapons": true,
+ "sv_falldamage_scale": true,
+ "sv_falldamage_to_below_player_multiplier": true,
+ "sv_falldamage_to_below_player_ratio": true,
+ "sv_friction": true,
+ "sv_game_mode_flags": false, // Server setting, should not be controlled by maps
+ "sv_gameinstructor_disable": true,
+ "sv_gameinstructor_enable": true,
+ "sv_give_item": true,
+ "sv_gravity": true,
+ "sv_grenade_trajectory_prac_pipreview": true,
+ "sv_grenade_trajectory_prac_trailtime": true,
+ "sv_grenade_trajectory_time_spectator": true,
+ "sv_guardian_extra_equipment_ct": true,
+ "sv_guardian_extra_equipment_t": true,
+ "sv_guardian_refresh_ammo_for_items_on_waves": true,
+ "sv_guardian_spawn_health_ct": true,
+ "sv_guardian_spawn_health_t": true,
+ "sv_health_approach_enabled": true,
+ "sv_health_approach_speed": true,
+ "sv_hegrenade_damage_multiplier": true,
+ "sv_hegrenade_radius_multiplier": true,
+ "sv_hide_roundtime_until_seconds": true,
+ "sv_highlight_distance": true,
+ "sv_highlight_duration": true,
+ "sv_human_autojoin_team": false, // Server setting, should not be controlled by maps
+ "sv_ignoregrenaderadio": true,
+ "sv_infinite_ammo": true,
+ "sv_jump_impulse": true,
+ "sv_jump_spam_penalty_time": true,
+ "sv_kick_ban_duration": false, // Server setting, should not be controlled by maps
+ "sv_ladder_scale_speed": true,
+ "sv_max_deathmatch_respawns_per_tick": true,
+ "sv_maxspeed": true,
+ "sv_maxvelocity": true,
+ "sv_minimum_desired_chicken_count": true,
+ "sv_outofammo_indicator": true,
+ "sv_pure": false, // Server command, should not be controlled by maps
+ "sv_pure_kick_clients": false, // Server setting, should not be controlled by maps
+ "sv_radio_throttle_window": false, // Server setting, should not be controlled by maps
+ "sv_regeneration_force_on": true,
+ "sv_regeneration_wait_time": true,
+ "sv_show_team_equipment_force_on": true,
+ "sv_showbullethits": true,
+ "sv_showhitregistration": true,
+ "sv_showimpacts": true,
+ "sv_showimpacts_penetration": true,
+ "sv_skirmish_id": true,
+ "sv_staminajumpcost": true,
+ "sv_staminalandcost": true,
+ "sv_staminamax": true,
+ "sv_staminarecoveryrate": true,
+ "sv_steamauth_enforce": false, // Server setting, should not be controlled by maps
+ "sv_stopspeed": true,
+ "sv_talk_enemy_dead": false, // Server setting, should not be controlled by maps
+ "sv_talk_enemy_living": false, // Server setting, should not be controlled by maps
+ "sv_teamid_overhead_maxdist": false, // Server setting, should not be controlled by maps
+ "sv_teamid_overhead_maxdist_spec": false, // Server setting, should not be controlled by maps
+ "sv_versus_screen_scene_id": true,
+ "sv_vote_to_changelevel_before_match_point": false, // Server setting, should not be controlled by maps
+ "sv_vote_to_changelevel_rndmin": false, // Server setting, should not be controlled by maps
+ "sv_warmup_to_freezetime_delay": true,
+ "sv_wateraccelerate": true,
+ "sv_waterfriction": true,
+ "tv_delay": false, // Server setting, should not be controlled by maps
+ "tv_delay1": false, // Server setting, should not be controlled by maps
+ "viewmodel_fov": true,
+ "viewmodel_offset_x": true,
+ "viewmodel_offset_y": true,
+ "viewmodel_offset_z": true,
+ "viewmodel_presetpos": true,
+ "weapon_accuracy_nospread": true,
+ "weapon_air_spread_scale": true,
+ "weapon_auto_cleanup_time": true,
+ "weapon_max_before_cleanup": true,
+ "weapon_recoil_scale": true,
+ "weapon_reticle_knife_show": true,
+ "weapon_sound_falloff_multiplier": true,
+ "sv_legacy_jump": false, // Server setting, should not be controlled by maps
+ "radio": true,
+ "radio1": true,
+ "radio2": true,
+ "radio3": true,
+ "player_ping": true,
+ "mp_teamlogo_1": true,
+ "mp_teamlogo_2": true,
+ "noclip_fixup": true,
+ "fov_cs_debug": true,
+ "sv_show_teammate_death_notification": true,
+ "sv_standable_normal": true,
+ "sv_walkable_normal": true,
+ "+jump": true,
+ "-jump": true,
+ "+duck": true,
+ "-duck": true,
+ "+forward": true,
+ "-forward": true,
+ "+back": true,
+ "-back": true,
+ "+left": true,
+ "-left": true,
+ "+right": true,
+ "-right": true,
+ "+sprint": true,
+ "-sprint": true,
+
+ // Custom stuff
+ "endround": true,
+ "cs2f_use_old_push": true,
+ "cs2f_infinite_reserve_ammo": true,
+ "cs2f_free_armor": true,
+ // Often over-abused but still have some legitimate uses, recommend either setting up map specific overrides or just globally enabling these
+ "zr_infect_spawn_time_min": false,
+ "zr_infect_spawn_time_max": false,
+ "zr_infect_spawn_mz_ratio": false
+}
\ No newline at end of file
diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc
index f18794d8d..5f4784bda 100644
--- a/gamedata/cs2fixes.jsonc
+++ b/gamedata/cs2fixes.jsonc
@@ -15,12 +15,6 @@
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 45 0F B6 F0",
"linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 41 89 D6 31 D2"
},
- "IsHearingClient":
- {
- "library": "engine",
- "windows": "40 53 48 83 EC ? 48 8B D9 3B 51",
- "linux": "55 48 89 E5 41 55 41 54 53 48 89 FB 48 83 EC ? 39 77"
- },
// idk a good way to find this again, i just brute forced the vtable. offset is 136 on CTriggerPush
"TriggerPush_Touch":
{
@@ -108,13 +102,6 @@
"windows": "40 55 53 56 57 41 54 48 8D 6C 24 ? 48 81 EC ? ? ? ? 4D 8B E0",
"linux": "55 66 0F EF C0 48 89 E5 41 57 41 56 41 55 49 89 FD 31 FF"
},
- // Should be xref'd right above "flGravity", takes a float arg
- "CBaseEntity::SetGravityScale":
- {
- "library": "server",
- "windows": "48 89 5C 24 ? 57 48 83 EC ? F3 0F 10 81 ? ? ? ? 48 8B F9 0F 29 74 24 ? 0F 28 F1 0F 2E C6 7A ? 74 ? BA ? ? ? ? 41 B8 ? ? ? ? 48 81 C1 ? ? ? ? E8 ? ? ? ? 48 8B CF F3 0F 11 B7 ? ? ? ? E8 ? ? ? ? 48 8B 5C 24 ? 0F 28 74 24 ? 48 83 C4 ? 5F C3 CC CC CC CC CC 48 89 5C 24",
- "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 53 48 89 FB 48 81 EC ? ? ? ? 0F 2E 87 ? ? ? ? 7A ? 75 ? 48 81 C4 ? ? ? ? 5B 41 5C 41 5D 41 5E 41 5F 5D C3 0F 1F 40 ? 31 C9 BE ? ? ? ? 66 0F EF C9 F3 0F 11 85 ? ? ? ? 48 8D BD ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 0F 29 8D ? ? ? ? 4C 8D A5 ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? C7 85 ? ? ? ? ? ? ? ? 66 89 8D ? ? ? ? E8 ? ? ? ? 48 8B 85 ? ? ? ? 48 8D 15 ? ? ? ? 83 85 ? ? ? ? ? F3 0F 10 85 ? ? ? ? C7 00 ? ? ? ? 48 8B 03 48 8B 80 ? ? ? ? 48 39 D0 0F 85 ? ? ? ? 8B 95 ? ? ? ? 4C 8D 7B ? 85 D2 0F 85 ? ? ? ? 80 BB ? ? ? ? ? 75"
- },
// "Game System %s is defined twice!\n"
// Note that this signature points to the instruction with sm_pFirst which is the first qword referenced in the function.
"IGameSystem_InitAllSystems_pFirst":
@@ -173,7 +160,7 @@
{
"library": "server",
"windows": "48 8B C4 4C 89 48 ? 48 89 48 ? 55 41 54",
- "linux": "55 48 89 E5 41 57 41 89 F7 41 56 48 8D 35"
+ "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4 53 48 8D 35 ? ? ? ? 48 89 FB"
},
"CCSPlayer_WeaponServices_CanUse":
{
@@ -199,15 +186,6 @@
"windows": "48 89 5C 24 10 57 48 83 EC 30 48 8B DA 48 8B F9 48 85 C9",
"linux": "48 85 FF 74 ? 55 48 89 E5 41 55 41 54 49 89 FC"
},
- // Look for "SetEntityName", that will be the vscript binding definition
- // Scroll a bit down and you'll find something like this (note the offset): *(_QWORD *)(v453 + 64) = sub_1807B0350;
- // that function is just a jump to the one we want
- "CEntityIdentity_SetEntityName":
- {
- "library": "server",
- "windows": "48 89 5C 24 10 57 48 83 EC 20 48 8B D9 4C 8B C2",
- "linux": "55 48 89 F2 48 89 E5 41 55 41 54 53"
- },
// "Error - cannot add bots after game is over."
"BotNavIgnore":
{
@@ -215,14 +193,6 @@
"windows": "0F 84 ? ? ? ? 80 B8 ? ? ? ? 00 0F 84 ? ? ? ? 80 3D ? ? ? ? 00 74 15",
"linux": "0F 84 ? ? ? ? 44 0F B6 B8 ? ? ? ? 45 84 FF 0F 84"
},
- // next to "soundname", in windows it's the last referenced sub while in linux it's right after
- // this is a vscript binding though so it may be removed in the future?
- "CBaseEntity_EmitSoundParams":
- {
- "library": "server",
- "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8B EC 48 81 EC ? ? ? ? 33 C0",
- "linux": "48 B8 ? ? ? ? ? ? ? ? 55 0F 28 D0"
- },
// "ParticleEffect", found in a function with 9 arguments
"DispatchParticleEffect":
{
@@ -374,12 +344,12 @@
"windows": "75 ? 48 8B 03 48 8B CB FF 90 ? ? ? ? 84 C0 74 ? 48 8D 05",
"linux": "75 ? 48 8B 03 48 89 DF FF 90 ? ? ? ? 84 C0 74 ? 48 8D 05"
},
- // "Going to intermission...\n"
+ // "Going to intermission..."
"CCSGameRules_GoToIntermission":
{
"library": "server",
"windows": "48 89 5C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 4C 8B E9",
- "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 49 89 FD 41 54 48 8D 3D"
+ "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4"
},
// Called in return by function with "Usage: setang_exact pitch yaw " string
"CBasePlayerPawn_SnapViewAngles":
@@ -387,6 +357,27 @@
"library": "server",
"windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B DA 48 8B F1 48 8D 55",
"linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8"
+ },
+ // These two functions are called by CEnvBeam & CEnvLaser member functions, one of those functions has "env_beams cannot have the end entity be the beam itself" string
+ // In both call locations, SetBeamOrigin is called first, then SetBeamEndPos
+ "SetBeamOrigin":
+ {
+ "library": "server",
+ "windows": "48 89 5C 24 ? 57 48 81 EC ? ? ? ? 48 8B FA 48 8B D9 E8 ? ? ? ? 48 8B CB",
+ "linux": "55 48 89 E5 41 54 49 89 F4 53 48 89 FB 48 83 EC ? 0F 1F 80"
+ },
+ "SetBeamEndPos":
+ {
+ "library": "server",
+ "windows": "48 8B C4 48 89 58 ? 57 48 81 EC ? ? ? ? 0F 29 70 ? 48 8B FA 0F 29 78 ? 48 8B D9",
+ "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 49 89 F4 53 48 89 FB 48 81 EC ? ? ? ? 66 0F 1F 44 00"
+ },
+ // Only called in an if statement by function with "DISALLOWED WORKSHOP CONVAR: %s" string
+ "IsCommandWhitelisted":
+ {
+ "library": "server",
+ "windows": "48 83 EC ? 4C 8B C2 48 8D 0D ? ? ? ? 48 8D 54 24 ? FF",
+ "linux": "55 48 89 F2 48 8D 35 ? ? ? ? 48 89 E5 48 83 EC"
}
},
"Offsets":
@@ -401,12 +392,6 @@
"windows": 53,
"linux": 52
},
- // String: "%s<%i><%s><%s>" ChangeTeam() CTMDBG..."
- "CCSPlayerController_ChangeTeam":
- {
- "windows": 103,
- "linux": 102
- },
"CBaseEntity::Use":
{
"windows": 145,
@@ -432,18 +417,6 @@
"windows": 163,
"linux": 162
},
- // For these two, look for the names, you'll find vscript bindings
- // Scroll down to where the var + 64 gets set to a function, that calls the offset we want
- "IsPlayerPawn":
- {
- "windows": 169,
- "linux": 168
- },
- "IsPlayerController":
- {
- "windows": 170,
- "linux": 169
- },
"CollisionRulesChanged":
{
"windows": 186,
@@ -485,11 +458,6 @@
"windows": 26,
"linux": 27
},
- "CheckTransmitPlayerSlot":
- {
- "windows": 576,
- "linux": 576
- },
// engine
// "tried to sprint to a non-client", there will be a check above like this: if ( a2 >= *(v5 + 632) ), note that this is a CUtlVector
"CNetworkGameServer_ClientList":
diff --git a/hl2sdk-manifests b/hl2sdk-manifests
index f9558bcc7..20b3a0142 160000
--- a/hl2sdk-manifests
+++ b/hl2sdk-manifests
@@ -1 +1 @@
-Subproject commit f9558bcc764598cb94c676ea0d0d3870a9b40353
+Subproject commit 20b3a014264b38908c4a5d4eb263ba2c488f3dc1
diff --git a/sdk b/sdk
index 5f891c902..e7ed969ea 160000
--- a/sdk
+++ b/sdk
@@ -1 +1 @@
-Subproject commit 5f891c9026230cce0fc0a3fc4b5fef1c467a1385
+Subproject commit e7ed969ea8cb9afebd19ba6c5f262c9048890543
diff --git a/src/addresses.cpp b/src/addresses.cpp
index 6a0fe6d74..5fae6ed6a 100644
--- a/src/addresses.cpp
+++ b/src/addresses.cpp
@@ -21,6 +21,10 @@
#include "gameconfig.h"
#include "utils/module.h"
+#include "entityinstance.h"
+#include "tier1/strtools.h"
+#include "vscript/ivscript.h"
+
#include "tier0/memdbgon.h"
#define RESOLVE_SIG(gameConfig, name, variable) \
@@ -29,6 +33,14 @@
return false; \
Message("Found %s at 0x%p\n", name, variable);
+#define RESOLVE_SF(scriptDesc, funcName, variable) \
+ if (!variable.Initialize(GetVScriptFunction(scriptDesc, funcName))) \
+ return false; \
+ if (variable.IsVirtual()) \
+ Message("Found %s::%s at vtable index %i\n", scriptDesc->m_pszClassname, funcName, variable.GetOffset()); \
+ else \
+ Message("Found %s::%s at 0x%p\n", scriptDesc->m_pszClassname, funcName, variable.GetPtr());
+
bool addresses::Initialize(CGameConfig* g_GameConfig)
{
modules::engine = new CModule(ROOTBIN, "engine2");
@@ -51,7 +63,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig)
#endif
RESOLVE_SIG(g_GameConfig, "SetGroundEntity", addresses::SetGroundEntity);
- RESOLVE_SIG(g_GameConfig, "CBaseEntity::SetGravityScale", addresses::SetGravityScale);
RESOLVE_SIG(g_GameConfig, "CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam);
RESOLVE_SIG(g_GameConfig, "CBasePlayerController_SetPawn", addresses::CBasePlayerController_SetPawn);
RESOLVE_SIG(g_GameConfig, "CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel);
@@ -62,8 +73,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig)
RESOLVE_SIG(g_GameConfig, "CGameRules_TerminateRound", addresses::CGameRules_TerminateRound);
RESOLVE_SIG(g_GameConfig, "CreateEntityByName", addresses::CreateEntityByName);
RESOLVE_SIG(g_GameConfig, "DispatchSpawn", addresses::DispatchSpawn);
- RESOLVE_SIG(g_GameConfig, "CEntityIdentity_SetEntityName", addresses::CEntityIdentity_SetEntityName);
- RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundParams", addresses::CBaseEntity_EmitSoundParams);
RESOLVE_SIG(g_GameConfig, "DispatchParticleEffect", addresses::DispatchParticleEffect);
RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundFilter", addresses::CBaseEntity_EmitSoundFilter);
RESOLVE_SIG(g_GameConfig, "CBaseEntity_SetMoveType", addresses::CBaseEntity_SetMoveType);
@@ -101,3 +110,25 @@ bool addresses::InitializeBanMap(CGameConfig* g_GameConfig)
Message("Found %s at 0x%p\n", "CCSGameRules__sm_mapGcBanInformation", addresses::sm_mapGcBanInformation);
return true;
}
+
+bool addresses::InitializeVScriptFunctions()
+{
+ void* pCBaseEntityVTable = modules::server->FindVirtualTable("CBaseEntity");
+ if (!pCBaseEntityVTable)
+ {
+ Message("Failed to find CBaseEntity vtable\n");
+ return false;
+ }
+
+ // GetScriptDesc ignores this, so the vtable pointer is sufficient here.
+ ScriptClassDesc_t* pCBaseEntityScriptDesc = reinterpret_cast(reinterpret_cast(&pCBaseEntityVTable)->GetScriptDesc());
+
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetGravity", SetGravityScale);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetEntityName", ScriptSetEntityName);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "EmitSoundParams", ScriptEmitSoundParams);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetTeam", ChangeTeam);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerPawn", IsPlayerPawn);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerController", IsPlayerController);
+
+ return true;
+}
diff --git a/src/addresses.h b/src/addresses.h
index 18a43c000..d70fb3b61 100644
--- a/src/addresses.h
+++ b/src/addresses.h
@@ -21,6 +21,7 @@
#include "platform.h"
#include "stdint.h"
#include "utils/module.h"
+#include "utils/vscript_function.h"
#include "utlstring.h"
#include "variant.h"
@@ -74,11 +75,11 @@ namespace addresses
{
bool Initialize(CGameConfig* g_GameConfig);
bool InitializeBanMap(CGameConfig* g_GameConfig);
+ bool InitializeVScriptFunctions();
- inline CUtlMap* sm_mapGcBanInformation;
+ inline CUtlOrderedMap* sm_mapGcBanInformation;
inline void(FASTCALL* SetGroundEntity)(CBaseEntity* ent, CBaseEntity* ground, CBaseEntity* unk3);
- inline void(FASTCALL* SetGravityScale)(CBaseEntity*, float);
inline void(FASTCALL* CCSPlayerController_SwitchTeam)(CCSPlayerController* pController, uint32 team);
inline void(FASTCALL* CBasePlayerController_SetPawn)(CBasePlayerController* pController, CCSPlayerPawn* pPawn, bool a3, bool a4, bool a5, bool a6);
inline void(FASTCALL* CBaseModelEntity_SetModel)(CBaseModelEntity* pModel, const char* pszModel);
@@ -97,8 +98,6 @@ namespace addresses
inline void(FASTCALL* CGameRules_TerminateRound)(CGameRules* pGameRules, float delay, unsigned int reason, int64 a4);
inline CBaseEntity*(FASTCALL* CreateEntityByName)(const char* className, int iForceEdictIndex);
inline void(FASTCALL* DispatchSpawn)(CBaseEntity* pEntity, CEntityKeyValues* pEntityKeyValues);
- inline void(FASTCALL* CEntityIdentity_SetEntityName)(CEntityIdentity* pEntity, const char* pName);
- inline void(FASTCALL* CBaseEntity_EmitSoundParams)(CBaseEntity* pEntity, const char* pszSound, int nPitch, float flVolume, float flDelay);
inline int(FASTCALL* DispatchParticleEffect)(const char* pszParticleName, int iAttachType, CBaseEntity* pEntity,
char iAttachmentPoint, CUtlSymbolLarge iAttachmentName, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* a7, byte* a8);
inline StartSoundEventInfo(FASTCALL* CBaseEntity_EmitSoundFilter)(IRecipientFilter& filter, CEntityIndex ent, const EmitSound_t& params);
@@ -108,4 +107,11 @@ namespace addresses
inline void(FASTCALL* CCSPlayer_WeaponServices_EquipWeapon)(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon);
inline void(FASTCALL* GetSpawnGroups)(CSpawnGroupMgrGameSystem* pSpawnGroupMgr, CUtlVector* pList);
inline void(FASTCALL* CBasePlayerPawn_SnapViewAngles)(CBasePlayerPawn* pPawn, QAngle* pAngles);
-} // namespace addresses
\ No newline at end of file
+
+ inline CVScriptFunction SetGravityScale;
+ inline CVScriptFunction ScriptSetEntityName;
+ inline CVScriptFunction ScriptEmitSoundParams;
+ inline CVScriptFunction ChangeTeam;
+ inline CVScriptFunction IsPlayerPawn;
+ inline CVScriptFunction IsPlayerController;
+} // namespace addresses
diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp
index ace819a0f..3f3ae9f81 100644
--- a/src/buttonwatch.cpp
+++ b/src/buttonwatch.cpp
@@ -99,6 +99,10 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt
CCSPlayerController* ccsPlayer = CCSPlayerController::FromPawn(static_cast(pActivator));
std::string strPlayerName = ccsPlayer->GetPlayerName();
+ const char* pszButtonName = ((CBaseEntity*)pCaller)->GetName();
+
+ if (!V_strcasecmp("game_playerdie", pszButtonName) || !V_strcasecmp("game_playerkill", pszButtonName) || !V_strcasecmp("game_playerjoin", pszButtonName) || !V_strcasecmp("game_playerspawn", pszButtonName) || !V_strcasecmp("game_playerleave", pszButtonName))
+ return;
ZEPlayer* zpPlayer = ccsPlayer->GetZEPlayer();
std::string strPlayerID = "";
@@ -108,7 +112,7 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt
strPlayerID = "(" + strPlayerID + ")";
}
- std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " + std::string(((CBaseEntity*)pCaller)->GetName());
+ std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " + std::string(pszButtonName);
for (int i = 0; i < GetGlobals()->maxClients; i++)
{
diff --git a/src/cfgparser.cpp b/src/cfgparser.cpp
new file mode 100644
index 000000000..adfe3adf0
--- /dev/null
+++ b/src/cfgparser.cpp
@@ -0,0 +1,80 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "cfgparser.h"
+#include "commands.h"
+
+#include
+
+CCfgParser* g_pCfgParser = nullptr;
+
+CON_COMMAND_F(exec_custom, " - Execute a cfg through the custom cfg parser", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND)
+{
+ if (args.ArgC() < 2)
+ {
+ Message("Usage: exec_custom \n");
+ return;
+ }
+
+ g_pCfgParser->ParseCfg(args[1]);
+}
+
+void CCfgParser::ApplyGameSettings(const char* pszMapName)
+{
+ // Run plugin cfg
+ g_pCfgParser->ParseCfg("cs2fixes/cs2fixes");
+
+ // Run custom server cfg
+ g_pCfgParser->ParseCfg("cs2fixes/server");
+
+ if (!V_strcmp(pszMapName, ""))
+ return;
+
+ // Run map cfg (if present)
+ // We call ParseCfg indirectly through exec_custom, so any commands within the map cfg will be added to the command buffer after nested executes in previous configs
+ char cmd[MAX_PATH];
+ V_snprintf(cmd, sizeof(cmd), "exec_custom cs2fixes/maps/%s", pszMapName);
+ g_pEngineServer2->ServerCommand(cmd);
+}
+
+void CCfgParser::ParseCfg(const char* pszCfgPath)
+{
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s/csgo/cfg/%s.cfg", Plat_GetGameDirectory(), pszCfgPath);
+ std::ifstream cfgFile(szPath);
+
+ if (!cfgFile.is_open())
+ {
+ Message("Unable to open & execute custom cfg file \"%s\"\n", pszCfgPath);
+ return;
+ }
+
+ Message("Executing custom cfg file \"%s\"\n", pszCfgPath);
+
+ std::string strCommand;
+
+ while (std::getline(cfgFile, strCommand))
+ {
+ if (!strCommand.empty() && strCommand.back() == '\r')
+ strCommand.pop_back();
+
+ if (!strCommand.empty())
+ g_pEngineServer2->ServerCommand(strCommand.c_str());
+ }
+}
diff --git a/src/cfgparser.h b/src/cfgparser.h
new file mode 100644
index 000000000..78433b7d7
--- /dev/null
+++ b/src/cfgparser.h
@@ -0,0 +1,29 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+class CCfgParser
+{
+public:
+ void ApplyGameSettings(const char* pszMapName);
+ void ParseCfg(const char* pszCfgPath);
+};
+
+extern CCfgParser* g_pCfgParser;
diff --git a/src/commands.cpp b/src/commands.cpp
index 2d8799b11..402198b5b 100644
--- a/src/commands.cpp
+++ b/src/commands.cpp
@@ -430,8 +430,7 @@ CON_COMMAND_CHAT(noshake, "- toggle noshake")
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have %s noshake.", bSet ? "enabled" : "disabled");
}
-CConVar g_cvarEnableHide("cs2f_hide_enable", FCVAR_NONE, "Whether to enable hide (WARNING: randomly crashes clients since 2023-12-13 CS2 update)", false);
-CConVar g_cvarHideWeapons("cs2f_hide_weapons", FCVAR_NONE, "Whether to hide weapons along with their holders", false);
+CConVar g_cvarEnableHide("cs2f_hide_enable", FCVAR_NONE, "Whether to enable hide", false);
CConVar g_cvarDefaultHideDistance("cs2f_hide_distance_default", FCVAR_NONE, "The default distance for hide", 250, true, 0, false, 0);
CConVar g_cvarMaxHideDistance("cs2f_hide_distance_max", FCVAR_NONE, "The max distance for hide", 2000, true, 0, false, 0);
diff --git a/src/commands.h b/src/commands.h
index a4aa4ac4e..d8ce79757 100644
--- a/src/commands.h
+++ b/src/commands.h
@@ -33,7 +33,6 @@ extern CConVar g_cvarEnableStopSound;
extern CConVar g_cvarEnableNoShake;
extern CConVar g_cvarMaxShakeAmp;
extern CConVar g_cvarEnableHide;
-extern CConVar g_cvarHideWeapons;
#define CMDFLAG_NONE (0)
#define CMDFLAG_NOHELP (1 << 0) // Don't show in !help menu
diff --git a/src/cs2_sdk/cchecktransmitinfo.h b/src/cs2_sdk/cchecktransmitinfo.h
new file mode 100644
index 000000000..1a3f472d6
--- /dev/null
+++ b/src/cs2_sdk/cchecktransmitinfo.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "bitvec.h"
+#include "eiface.h"
+#include "playerslot.h"
+#include "utlvector.h"
+
+// https://github.com/Wend4r/sourcesdk/blob/main/public/iservernetworkable.h
+
+struct vis_info_t_extended
+{
+ uint32 m_uVisBitsBufSize;
+ SpawnGroupHandle_t m_SpawnGroupHandle;
+ CBitVec<4096> m_VisBits;
+};
+COMPILE_TIME_ASSERT(sizeof(vis_info_t_extended) == 520);
+
+class CCheckTransmitInfoExtended
+{
+public:
+ CBitVec* m_pTransmitEntity; // entities visible/sent to client
+ CBitVec* m_pTransmitNonPlayers; // non-player entities needing deletion deltas
+ CBitVec* m_pTransmitOutOfPVS; // entities that left PVS but still need delta update
+ CBitVec* m_pTransmitAlways; // entity n is always sent even if not in PVS (HLTV and Replay only)
+ CUtlVector m_vecTargetSlots;
+ vis_info_t_extended m_VisInfo;
+ CPlayerSlot m_nPlayerSlot;
+ bool m_bFullUpdate = false;
+};
+COMPILE_TIME_ASSERT(sizeof(CCheckTransmitInfoExtended) == 584);
\ No newline at end of file
diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h
index bcf1955fb..6d3b3a936 100644
--- a/src/cs2_sdk/entity/cbaseentity.h
+++ b/src/cs2_sdk/entity/cbaseentity.h
@@ -165,7 +165,7 @@ class CBaseEntity : public CEntityInstance
void SetName(const char* pName)
{
- addresses::CEntityIdentity_SetEntityName(m_pEntity, pName);
+ addresses::ScriptSetEntityName(this, pName);
}
void TakeDamage(CTakeDamageInfo& info)
@@ -196,14 +196,12 @@ class CBaseEntity : public CEntityInstance
bool IsPawn()
{
- static int offset = g_GameConfig->GetOffset("IsPlayerPawn");
- return CALL_VIRTUAL(bool, offset, this);
+ return addresses::IsPlayerPawn(this);
}
bool IsController()
{
- static int offset = g_GameConfig->GetOffset("IsPlayerController");
- return CALL_VIRTUAL(bool, offset, this);
+ return addresses::IsPlayerController(this);
}
void AcceptInput(const char* pInputName, variant_t value = variant_t(""), CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr)
@@ -226,7 +224,7 @@ class CBaseEntity : public CEntityInstance
// Emit a sound event
void EmitSound(const char* pszSound, int nPitch = 100, float flVolume = 1.0, float flDelay = 0.0)
{
- addresses::CBaseEntity_EmitSoundParams(this, pszSound, nPitch, flVolume, flDelay);
+ addresses::ScriptEmitSoundParams(this, pszSound, nPitch, flVolume, flDelay);
}
StartSoundEventInfo EmitSoundFilter(IRecipientFilter& filter, const char* pszSound, float flVolume = 1.0, float flPitch = 1.0)
diff --git a/src/cs2_sdk/entity/cbeam.h b/src/cs2_sdk/entity/cbeam.h
new file mode 100644
index 000000000..ec7fe90d8
--- /dev/null
+++ b/src/cs2_sdk/entity/cbeam.h
@@ -0,0 +1,30 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "cbasemodelentity.h"
+
+class CBeam : public CBaseModelEntity
+{
+public:
+ DECLARE_SCHEMA_CLASS(CBeam);
+
+ SCHEMA_FIELD(VectorWS, m_vecEndPos)
+};
\ No newline at end of file
diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h
index cc994b9d9..5376c6666 100644
--- a/src/cs2_sdk/entity/ccsplayercontroller.h
+++ b/src/cs2_sdk/entity/ccsplayercontroller.h
@@ -84,8 +84,7 @@ class CCSPlayerController : public CBasePlayerController
void ChangeTeam(int iTeam)
{
- static int offset = g_GameConfig->GetOffset("CCSPlayerController_ChangeTeam");
- CALL_VIRTUAL(void, offset, this, iTeam);
+ addresses::ChangeTeam(this, iTeam);
}
void SwitchTeam(int iTeam)
diff --git a/src/cs2_sdk/entity/ctakedamageinfo.h b/src/cs2_sdk/entity/ctakedamageinfo.h
index 3ea47aa75..ebc907b97 100644
--- a/src/cs2_sdk/entity/ctakedamageinfo.h
+++ b/src/cs2_sdk/entity/ctakedamageinfo.h
@@ -19,6 +19,7 @@
#pragma once
#include "ehandle.h"
+#include "gametrace.h"
#include
enum DamageTypes_t : uint32_t
diff --git a/src/cs2_sdk/entity/globaltypes.h b/src/cs2_sdk/entity/globaltypes.h
index 7d8f42cf6..285bb4784 100644
--- a/src/cs2_sdk/entity/globaltypes.h
+++ b/src/cs2_sdk/entity/globaltypes.h
@@ -22,34 +22,6 @@
#include "soundflags.h"
#include
-enum InputBitMask_t : uint64_t
-{
- // MEnumeratorIsNotAFlag
- IN_NONE = 0x0,
- // MEnumeratorIsNotAFlag
- IN_ALL = 0xffffffffffffffff,
- IN_ATTACK = 0x1,
- IN_JUMP = 0x2,
- IN_DUCK = 0x4,
- IN_FORWARD = 0x8,
- IN_BACK = 0x10,
- IN_USE = 0x20,
- IN_TURNLEFT = 0x80,
- IN_TURNRIGHT = 0x100,
- IN_MOVELEFT = 0x200,
- IN_MOVERIGHT = 0x400,
- IN_ATTACK2 = 0x800,
- IN_RELOAD = 0x2000,
- IN_SPEED = 0x10000,
- IN_JOYAUTOSPRINT = 0x20000,
- // MEnumeratorIsNotAFlag
- IN_FIRST_MOD_SPECIFIC_BIT = 0x100000000,
- IN_USEORRELOAD = 0x100000000,
- IN_SCORE = 0x200000000,
- IN_ZOOM = 0x400000000,
- IN_LOOK_AT_WEAPON = 0x800000000,
-};
-
enum EInButtonState : uint32_t
{
IN_BUTTON_UP = 0x0,
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index 6dee2ab33..bd603b2dc 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -22,10 +22,13 @@
#include "adminsystem.h"
#include "appframework/IAppSystem.h"
+#include "cchecktransmitinfo.h"
+#include "cfgparser.h"
#include "commands.h"
#include "common.h"
#include "cs_gameevents.pb.h"
#include "ctimer.h"
+#include "cvarwhitelist.h"
#include "detours.h"
#include "discord.h"
#include "entities.h"
@@ -204,6 +207,9 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
if (!addresses::Initialize(g_GameConfig))
bRequiredInitLoaded = false;
+ if (!addresses::InitializeVScriptFunctions())
+ bRequiredInitLoaded = false;
+
if (!InitPatches(g_GameConfig))
bRequiredInitLoaded = false;
@@ -355,6 +361,8 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
g_pIdleSystem = new CIdleSystem();
g_pPanoramaVoteHandler = new CPanoramaVoteHandler();
g_pEWHandler = new CEWHandler();
+ g_pConvarWhitelist = new CConVarWhitelist();
+ g_pCfgParser = new CCfgParser();
g_pMapMigrations = new CMapMigrations();
RegisterWeaponCommands();
@@ -378,7 +386,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
});
// run our cfg
- g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes");
+ g_pCfgParser->ParseCfg("cs2fixes/cs2fixes");
srand(time(0));
@@ -504,6 +512,12 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
delete g_pEWHandler;
}
+ if (g_pConvarWhitelist)
+ delete g_pConvarWhitelist;
+
+ if (g_pCfgParser)
+ delete g_pCfgParser;
+
if (g_pMapMigrations)
delete g_pMapMigrations;
@@ -522,15 +536,24 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandCo
if (!g_cvarEnableCommands.Get())
RETURN_META(MRES_IGNORED);
- bool bSay = !V_strcmp(args.Arg(0), "say");
- bool bTeamSay = !V_strcmp(args.Arg(0), "say_team");
+ bool bSay = !V_stricmp(args.Arg(0), "say");
+ bool bTeamSay = !V_stricmp(args.Arg(0), "say_team");
if (iCommandPlayerSlot != -1 && (bSay || bTeamSay))
{
- auto pController = CCSPlayerController::FromSlot(iCommandPlayerSlot);
- bool bGagged = pController && pController->GetZEPlayer()->IsGagged();
- bool bFlooding = pController && pController->GetZEPlayer()->IsFlooding();
- bool bIsAdmin = pController && pController->GetZEPlayer()->IsAdminFlagSet(ADMFLAG_GENERIC);
+ CCSPlayerController* pController = CCSPlayerController::FromSlot(iCommandPlayerSlot);
+ ZEPlayer* pPlayer = pController ? pController->GetZEPlayer() : nullptr;
+
+ // Block chat messages from players not fully ingame, can be interpreted as console messages
+ if (!pPlayer || !pPlayer->IsInGame())
+ {
+ Message("Blocked chat message from user ID %i not fully in game\n", g_pEngineServer2->GetPlayerUserId(iCommandPlayerSlot).Get());
+ RETURN_META(MRES_SUPERCEDE);
+ }
+
+ bool bGagged = pPlayer->IsGagged();
+ bool bFlooding = pPlayer->IsFlooding();
+ bool bIsAdmin = pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC);
bool bAdminChat = bTeamSay && *args[1] == '@';
bool bSilent = *args[1] == '/' || bAdminChat;
bool bCommand = *args[1] == '!' || *args[1] == '/';
@@ -554,6 +577,10 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandCo
{
SH_CALL(g_pCVar, &ICvar::DispatchConCommand)
(cmdHandle, ctx, args);
+
+ // Reset idle time if message is sent to chat
+ if (g_cvarIdleKickTime.Get() > 0.0f)
+ pPlayer->UpdateLastInputTime();
}
else if (bFlooding)
{
@@ -827,12 +854,17 @@ void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args)
Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString());
#endif
+ ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot);
+
if (g_cvarIdleKickTime.Get() > 0.0f)
{
- ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot);
-
- if (pPlayer)
- pPlayer->UpdateLastInputTime();
+ CCSPlayerController* pController = CCSPlayerController::FromSlot(slot);
+ if (pPlayer && pController)
+ {
+ // Only spectators doing spectator commands reset idle timer
+ if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && (!V_stricmp(args[0], "spec_mode") || !V_stricmp(args[0], "spec_prev") || !V_stricmp(args[0], "spec_next")))
+ pPlayer->UpdateLastInputTime();
+ }
}
if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2)
@@ -843,7 +875,7 @@ void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args)
RETURN_META(MRES_SUPERCEDE);
}
- if (g_cvarEnableZR.Get() && slot != -1 && !V_strncmp(args.Arg(0), "jointeam", 8))
+ if (g_cvarEnableZR.Get() && slot != -1 && !V_strnicmp(args.Arg(0), "jointeam", 8))
{
ZR_Hook_ClientCommand_JoinTeam(slot, args);
RETURN_META(MRES_SUPERCEDE);
@@ -952,19 +984,13 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
for (int i = 0; i < infoCount; i++)
{
- auto& pInfo = ppInfoList[i];
-
- // the offset happens to have a player index here,
- // though this is probably part of the client class that contains the CCheckTransmitInfo
- static int offset = g_GameConfig->GetOffset("CheckTransmitPlayerSlot");
- int iPlayerSlot = (int)*((uint8*)pInfo + offset);
-
- CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(iPlayerSlot);
+ auto& pInfo = (CCheckTransmitInfoExtended*&)(ppInfoList[i]);
+ CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(pInfo->m_nPlayerSlot);
if (!pSelfController || !pSelfController->IsConnected())
continue;
- auto pSelfZEPlayer = g_playerManager->GetPlayer(iPlayerSlot);
+ auto pSelfZEPlayer = g_playerManager->GetPlayer(pInfo->m_nPlayerSlot);
if (!pSelfZEPlayer)
continue;
@@ -973,7 +999,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(j);
// Always transmit to themselves
- if (!pController || pController->m_bIsHLTV || j == iPlayerSlot)
+ if (!pController || pController->m_bIsHLTV || j == pInfo->m_nPlayerSlot.Get())
continue;
// Don't transmit other players' flashlights
@@ -1005,19 +1031,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1)
{
pInfo->m_pTransmitEntity->Clear(pPawn->entindex());
-
- if (g_cvarHideWeapons.Get())
- {
- auto pVecWeapons = pPawn->m_pWeaponServices->m_hMyWeapons();
-
- FOR_EACH_VEC(*pVecWeapons, i)
- {
- auto pWeapon = (*pVecWeapons)[i].Get();
-
- if (pWeapon)
- pInfo->m_pTransmitEntity->Clear(pWeapon->entindex());
- }
- }
+ pInfo->m_pTransmitNonPlayers->Set(pPawn->entindex());
}
}
@@ -1031,8 +1045,22 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV)
{
- g_pMapVoteSystem->ApplyGameSettings(pKV);
- g_pMapMigrations->ApplyGameSettings(pKV);
+ const char* pszMapName;
+ uint64 iWorkshopId;
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname"))
+ pszMapName = pKV->FindKey("launchoptions")->GetString("levelname");
+ else
+ pszMapName = "";
+
+ if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode"))
+ iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode");
+ else
+ iWorkshopId = 0;
+
+ g_pCfgParser->ApplyGameSettings(pszMapName);
+ g_pMapVoteSystem->ApplyGameSettings(pszMapName, iWorkshopId);
+ g_pMapMigrations->ApplyGameSettings(iWorkshopId);
}
void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList)
@@ -1235,7 +1263,7 @@ bool CS2Fixes::Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg)
{
CServerSideClient* client = META_IFACEPTR(CServerSideClient);
- if (!client || !GetGlobals())
+ if (!client)
RETURN_META_VALUE(MRES_IGNORED, true);
ZEPlayer* pPlayer = g_playerManager->GetPlayer(client->GetPlayerSlot());
@@ -1243,7 +1271,11 @@ bool CS2Fixes::Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg)
if (!pPlayer)
RETURN_META_VALUE(MRES_IGNORED, true);
- pPlayer->SetLastVoiceTime(GetGlobals()->curtime);
+ if (pPlayer->IsMuted())
+ RETURN_META_VALUE(MRES_SUPERCEDE, true);
+
+ if (GetGlobals())
+ pPlayer->SetLastVoiceTime(GetGlobals()->curtime);
RETURN_META_VALUE(MRES_IGNORED, true);
}
@@ -1317,14 +1349,6 @@ void CS2Fixes::OnLevelInit(char const* pMapName,
{
Message("OnLevelInit(%s)\n", pMapName);
- // run our cfg
- g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes");
-
- // Run map cfg (if present)
- char cmd[MAX_PATH];
- V_snprintf(cmd, sizeof(cmd), "exec cs2fixes/maps/%s", pMapName);
- g_pEngineServer2->ServerCommand(cmd);
-
// Only patch BotNavIgnore while a map is loaded, else adding bots will crash
if (V_strcmp(pMapName, "error"))
g_CommonPatches[1].PerformPatch(g_GameConfig);
diff --git a/src/cs2fixes.h b/src/cs2fixes.h
index 9fa6c05a9..ea13cc119 100644
--- a/src/cs2fixes.h
+++ b/src/cs2fixes.h
@@ -28,7 +28,6 @@
#include "public/ics2fixes.h"
#include "steam/isteamhttp.h"
#include
-#include
#include
#include
diff --git a/src/cvarwhitelist.cpp b/src/cvarwhitelist.cpp
new file mode 100644
index 000000000..3fe5a9583
--- /dev/null
+++ b/src/cvarwhitelist.cpp
@@ -0,0 +1,122 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "cvarwhitelist.h"
+#include "commands.h"
+#include "utils.h"
+#undef snprintf
+#include "vendor/nlohmann/json.hpp"
+#include
+
+CConVarWhitelist* g_pConvarWhitelist = nullptr;
+
+CConVar g_cvarConVarWhitelistEnable("cs2f_cvarwhitelist_enable", FCVAR_NONE, "Whether to enable the custom ConVar whitelist for maps", false);
+
+CON_COMMAND_CHAT_FLAGS(cvarwhitelist_reload, "- Reload the ConVar whitelist config file", ADMFLAG_ROOT)
+{
+ if (!g_cvarConVarWhitelistEnable.Get())
+ return;
+
+ if (g_pConvarWhitelist->LoadConfig())
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "ConVar whitelist config reloaded!");
+ else
+ ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to reload ConVar whitelist config!");
+}
+
+bool CConVarWhitelist::LoadConfig()
+{
+ m_bConfigLoaded = false;
+ m_vecGlobalWhitelist.clear();
+ m_mapOverrides.clear();
+
+ const char* pszCvarWhitelistPath = "addons/cs2fixes/configs/cvar_whitelist.jsonc";
+ char szPath[MAX_PATH];
+ V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszCvarWhitelistPath);
+ std::ifstream cvarWhitelistFile(szPath);
+
+ if (!cvarWhitelistFile.is_open())
+ {
+ Panic("Failed to open %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ json jsonWhitelist = json::parse(cvarWhitelistFile, nullptr, false, true);
+
+ if (jsonWhitelist.is_discarded() || !jsonWhitelist.is_object())
+ {
+ Panic("Failed parsing JSON from %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ for (auto& [strKey, jsonValue] : jsonWhitelist.items())
+ {
+ if (jsonValue.is_boolean())
+ {
+ m_vecGlobalWhitelist.push_back({StringToLower(strKey), jsonValue.get()});
+ }
+ else if (jsonValue.is_object())
+ {
+ std::vector vecWhitelist;
+
+ for (auto& [strConvar, jsonInnerValue] : jsonValue.items())
+ {
+ if (!jsonInnerValue.is_boolean())
+ {
+ Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+
+ vecWhitelist.push_back({StringToLower(strConvar), jsonInnerValue.get()});
+ }
+
+ m_mapOverrides[strKey] = vecWhitelist;
+ }
+ else
+ {
+ Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath);
+ return false;
+ }
+ }
+
+ m_bConfigLoaded = true;
+ return true;
+}
+
+bool CConVarWhitelist::IsWhitelisted(std::string strName)
+{
+ auto globalIt = std::find_if(m_vecGlobalWhitelist.begin(), m_vecGlobalWhitelist.end(), [strName](const auto& wv) {
+ return wv.strConVar == StringToLower(strName);
+ });
+
+ // Cannot load map overrides, only check global whitelist
+ if (!GetGlobals())
+ return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled;
+
+ auto mapVector = m_mapOverrides[GetGlobals()->mapname.ToCStr()];
+ auto mapIt = std::find_if(mapVector.begin(), mapVector.end(), [strName](const auto& wv) {
+ return wv.strConVar == StringToLower(strName);
+ });
+
+ // If a map override is present, it takes priority
+ if (mapIt != mapVector.end())
+ return mapIt->bEnabled;
+
+ // Or fall back to global whitelist
+ return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled;
+}
\ No newline at end of file
diff --git a/src/cvarwhitelist.h b/src/cvarwhitelist.h
new file mode 100644
index 000000000..ecb77a70d
--- /dev/null
+++ b/src/cvarwhitelist.h
@@ -0,0 +1,54 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "convar.h"
+#undef snprintf
+#include "vendor/nlohmann/json_fwd.hpp"
+
+using json = nlohmann::json;
+
+extern CConVar g_cvarConVarWhitelistEnable;
+
+struct WhitelistValue
+{
+ std::string strConVar;
+ bool bEnabled;
+};
+
+class CConVarWhitelist
+{
+public:
+ CConVarWhitelist()
+ {
+ LoadConfig();
+ }
+
+ bool LoadConfig();
+ bool IsWhitelisted(std::string strName);
+ bool IsConfigLoaded() { return m_bConfigLoaded; };
+
+private:
+ bool m_bConfigLoaded = false;
+ std::vector m_vecGlobalWhitelist;
+ std::map> m_mapOverrides;
+};
+
+extern CConVarWhitelist* g_pConvarWhitelist;
\ No newline at end of file
diff --git a/src/detours.cpp b/src/detours.cpp
index 610054209..eea5ac108 100644
--- a/src/detours.cpp
+++ b/src/detours.cpp
@@ -22,15 +22,18 @@
#include "usercmd.pb.h"
#include "addresses.h"
+#include "bspflags.h"
#include "buttonwatch.h"
#include "cdetour.h"
#include "commands.h"
#include "common.h"
#include "ctimer.h"
#include "customio.h"
+#include "cvarwhitelist.h"
#include "detours.h"
#include "entities.h"
#include "entity/cbasemodelentity.h"
+#include "entity/cbeam.h"
#include "entity/ccsplayercontroller.h"
#include "entity/ccsplayerpawn.h"
#include "entity/ccsweaponbase.h"
@@ -60,7 +63,6 @@ CUtlVector g_vecDetours;
DECLARE_DETOUR(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter);
DECLARE_DETOUR(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter);
-DECLARE_DETOUR(IsHearingClient, Detour_IsHearingClient);
DECLARE_DETOUR(TriggerPush_Touch, Detour_TriggerPush_Touch);
DECLARE_DETOUR(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld);
DECLARE_DETOUR(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse);
@@ -89,6 +91,9 @@ DECLARE_DETOUR(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_
DECLARE_DETOUR(CS_Script_SetModel, Detour_CS_Script_SetModel);
DECLARE_DETOUR(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel);
DECLARE_DETOUR(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission);
+DECLARE_DETOUR(SetBeamOrigin, Detour_SetBeamOrigin);
+DECLARE_DETOUR(SetBeamEndPos, Detour_SetBeamEndPos);
+DECLARE_DETOUR(IsCommandWhitelisted, Detour_IsCommandWhitelisted);
CConVar g_cvarBlockMolotovSelfDmg("cs2f_block_molotov_self_dmg", FCVAR_NONE, "Whether to block self-damage from molotovs", false);
CConVar g_cvarBlockAllDamage("cs2f_block_all_dmg", FCVAR_NONE, "Whether to block all damage to players", false);
@@ -250,15 +255,6 @@ void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther)
pOther->m_fFlags(flags);
}
-bool FASTCALL Detour_IsHearingClient(void* serverClient, int index)
-{
- ZEPlayer* player = g_playerManager->GetPlayer(index);
- if (player && player->IsMuted())
- return false;
-
- return IsHearingClient(serverClient, index);
-}
-
void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType)
{
VPROF("SayChatMessageWithTimer");
@@ -828,7 +824,7 @@ void FASTCALL Detour_GameSystem_Think_CheckSteamBan()
if (!g_cvarFixGameBans.Get())
return;
- CUtlMap* pMap = addresses::sm_mapGcBanInformation;
+ auto pMap = addresses::sm_mapGcBanInformation;
unsigned int count = pMap->Count();
// After player has been kicked, remove any ban entries, to prevent spreading to all new joining players
@@ -878,6 +874,34 @@ void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAb
return CCSGameRules_GoToIntermission(pThis, bAbortedMatch);
}
+void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecPosition)
+{
+ // Game code still works for parented beams/lasers
+ if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent())
+ SetBeamOrigin(pThis, pVecPosition);
+
+ // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves
+ pThis->SetAbsOrigin(*pVecPosition);
+}
+
+void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition)
+{
+ // Game code still works for parented beams/lasers
+ if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent())
+ SetBeamEndPos(pThis, pVecPosition);
+
+ // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves
+ pThis->m_vecEndPos = *(VectorWS*)(pVecPosition);
+}
+
+bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName)
+{
+ if (!g_cvarConVarWhitelistEnable.Get() || !g_pConvarWhitelist->IsConfigLoaded())
+ return IsCommandWhitelisted(pAddonManager, pszCommandName);
+
+ return g_pConvarWhitelist->IsWhitelisted(pszCommandName);
+}
+
bool InitDetours(CGameConfig* gameConfig)
{
bool success = true;
diff --git a/src/detours.h b/src/detours.h
index 982083d72..19f4cdc7e 100644
--- a/src/detours.h
+++ b/src/detours.h
@@ -53,6 +53,7 @@ class Vector;
class QAngle;
class CEconItemView;
class CCSGameRules;
+class CBeam;
struct CTakeDamageResult;
// Add callback functions to this map that wish to hook into Detour_CEntityIOOutput_FireOutputInternal
@@ -86,7 +87,6 @@ bool SetupFireOutputInternalDetour();
void FASTCALL Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64);
void FASTCALL Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*);
-bool FASTCALL Detour_IsHearingClient(void*, int);
void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther);
int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult);
bool FASTCALL Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*);
@@ -116,4 +116,7 @@ void FASTCALL Detour_GameSystem_Think_CheckSteamBan();
AcquireResult FASTCALL Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4);
void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1);
void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel);
-void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch);
\ No newline at end of file
+void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch);
+void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition);
+void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition);
+bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName);
\ No newline at end of file
diff --git a/src/entwatch.cpp b/src/entwatch.cpp
index 0ae9d07ff..d5be7ca27 100644
--- a/src/entwatch.cpp
+++ b/src/entwatch.cpp
@@ -72,6 +72,8 @@ SH_DECL_MANUALHOOK1_void(CTriggerMultiple_EndTouch, 0, 0, 0, CBaseEntity*);
CConVar g_cvarEnableEntWatch("entwatch_enable", FCVAR_NONE, "INCOMPATIBLE WITH CS#. Whether to enable EntWatch features", false);
CConVar g_cvarEnableFiltering("entwatch_auto_filter", FCVAR_NONE, "Whether to automatically block non-item holders from triggering uses", true);
CConVar g_cvarUseEntwatchClantag("entwatch_clantag", FCVAR_NONE, "Whether to set item holder's clantag and set score", true);
+CConVar g_cvarClantagColourMode("entwatch_clantag_colour_mode", FCVAR_NONE, "Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value)", 1, true, 0, true, 2);
+CConVar g_cvarClantagColour("entwatch_clantag_colour", FCVAR_NONE, "Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2", "FFFFFF");
CConVar g_cvarItemHolderScore("entwatch_score", FCVAR_NONE, "Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1", 9999, true, 0, false, 0);
CConVar g_cvarEnableEntwatchHud("entwatch_hud", FCVAR_NONE, "Whether to enable the EntWatch hud and related commands", true);
@@ -1484,20 +1486,13 @@ void CEWHandler::ResetAllClantags()
if (!GetGlobals())
return;
- // Reset everyone's scores and tags for insurance
+ // Reset everyone's tags
for (int i = 0; i < GetGlobals()->maxClients; i++)
{
CCSPlayerController* pController = CCSPlayerController::FromSlot(i);
if (!pController)
continue;
- // Bring score down below entwatch_score so new item holders show above
- if (pController->m_iScore >= g_cvarItemHolderScore.Get())
- {
- int score = pController->m_iScore % g_cvarItemHolderScore.Get();
- pController->m_iScore = score;
- }
-
pController->SetClanTag("");
}
}
@@ -1988,7 +1983,21 @@ float EW_UpdateHud()
if (g_cvarUseEntwatchClantag.Get())
{
- V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:", sItemText.c_str(), pItem->szShortName.c_str());
+ switch (g_cvarClantagColourMode.Get())
+ {
+ case 0:
+ V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:",
+ sItemText.c_str(), pItem->szShortName.c_str());
+ break;
+ case 1:
+ V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s",
+ pItem->colorGlow.r(), pItem->colorGlow.g(), pItem->colorGlow.b(), sItemText.c_str(), pItem->szShortName.c_str());
+ break;
+ case 2:
+ V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s",
+ g_cvarClantagColour.Get().String(), sItemText.c_str(), pItem->szShortName.c_str());
+ break;
+ }
if (pItem->bHasThisClantag)
pOwner->SetClanTag(pItem->sClantag);
}
diff --git a/src/gamesystem.cpp b/src/gamesystem.cpp
index 6d05c7df3..ed00ec154 100644
--- a/src/gamesystem.cpp
+++ b/src/gamesystem.cpp
@@ -109,7 +109,7 @@ bool UnregisterGameSystem()
if (!pDispatcher || !pGameSystems)
{
- Panic("Gamesystems and/or dispatchers is null, server is probably shutting down\n");
+ Message("Gamesystems and/or dispatchers is null, server is probably shutting down\n");
return false;
}
diff --git a/src/httpmanager.cpp b/src/httpmanager.cpp
index 29fc31435..15246aecb 100644
--- a/src/httpmanager.cpp
+++ b/src/httpmanager.cpp
@@ -64,7 +64,14 @@ HTTPManager::TrackedRequest::~TrackedRequest()
void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t* arg, bool bFailed)
{
- if (bFailed || (!m_callbackError && (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299)))
+ bool bTransportError = bFailed || !arg->m_bRequestSuccessful;
+ bool bHTTPError = arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299;
+
+ if (bTransportError && m_callbackError)
+ {
+ m_callbackError(arg->m_hRequest, k_EHTTPStatusCodeInvalid, json());
+ }
+ else if (bTransportError || (bHTTPError && !m_callbackError))
{
Message("HTTP request to %s failed with status code %i\n", m_strUrl.c_str(), arg->m_eStatusCode);
}
@@ -88,9 +95,9 @@ void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t*
Message("Failed parsing JSON from HTTP response: %s\n", (char*)response);
}
- if (!jsonResponse.is_discarded() && (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299))
+ if (!jsonResponse.is_discarded() && bHTTPError)
m_callbackError(arg->m_hRequest, arg->m_eStatusCode, jsonResponse);
- else if (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299)
+ else if (bHTTPError)
{
// Allow error callback even if invalid json, since error code can provide useful info
m_callbackError(arg->m_hRequest, arg->m_eStatusCode, json());
@@ -108,42 +115,45 @@ void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t*
}
void HTTPManager::Get(std::string strUrl, CompletedCallback callbackCompleted,
- ErrorCallback callbackError, std::vector* headers)
+ ErrorCallback callbackError, std::vector* headers,
+ int absoluteTimeoutMs)
{
- GenerateRequest(k_EHTTPMethodGET, strUrl, "", callbackCompleted, callbackError, headers);
+ GenerateRequest(k_EHTTPMethodGET, strUrl, "", callbackCompleted, callbackError, headers, absoluteTimeoutMs);
}
void HTTPManager::Post(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError, std::vector* headers)
+ ErrorCallback callbackError, std::vector* headers,
+ int absoluteTimeoutMs)
{
- GenerateRequest(k_EHTTPMethodPOST, strUrl, strText, callbackCompleted, callbackError, headers);
+ GenerateRequest(k_EHTTPMethodPOST, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs);
}
void HTTPManager::Put(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError, std::vector* headers)
+ ErrorCallback callbackError, std::vector* headers,
+ int absoluteTimeoutMs)
{
- GenerateRequest(k_EHTTPMethodPUT, strUrl, strText, callbackCompleted, callbackError, headers);
+ GenerateRequest(k_EHTTPMethodPUT, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs);
}
void HTTPManager::Patch(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError, std::vector* headers)
+ ErrorCallback callbackError, std::vector* headers, int absoluteTimeoutMs)
{
- GenerateRequest(k_EHTTPMethodPATCH, strUrl, strText, callbackCompleted, callbackError, headers);
+ GenerateRequest(k_EHTTPMethodPATCH, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs);
}
void HTTPManager::Delete(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError, std::vector* headers)
+ ErrorCallback callbackError, std::vector* headers, int absoluteTimeoutMs)
{
- GenerateRequest(k_EHTTPMethodDELETE, strUrl, strText, callbackCompleted, callbackError, headers);
+ GenerateRequest(k_EHTTPMethodDELETE, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs);
}
void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::string strText,
CompletedCallback callbackCompleted, ErrorCallback callbackError,
- std::vector* headers)
+ std::vector* headers, int absoluteTimeoutMs)
{
if (!GetSteamHTTP())
{
- Panic("A web request was attempted on null ISteamHTTP, returning early.\n");
+ Panic("A web request for %s was attempted on null ISteamHTTP, returning early.\n", strUrl.c_str());
return;
}
@@ -152,6 +162,11 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s
#endif
HTTPRequestHandle hReq = GetSteamHTTP()->CreateHTTPRequest(method, strUrl.c_str());
+ if (hReq == INVALID_HTTPREQUEST_HANDLE)
+ {
+ Panic("Failed to CreateHTTPRequest for %s\n", strUrl.c_str());
+ return;
+ }
bool shouldHaveBody = method == k_EHTTPMethodPOST
|| method == k_EHTTPMethodPATCH
@@ -159,14 +174,39 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s
|| method == k_EHTTPMethodDELETE;
if (shouldHaveBody && !GetSteamHTTP()->SetHTTPRequestRawPostBody(hReq, "application/json", (uint8*)(strText.c_str()), strText.length()))
+ {
+ Panic("Failed to SetHTTPRequestRawPostBody for %s\n", strUrl.c_str());
+ GetSteamHTTP()->ReleaseHTTPRequest(hReq);
return;
+ }
if (headers != nullptr)
+ {
for (HTTPHeader header : *headers)
- GetSteamHTTP()->SetHTTPRequestHeaderValue(hReq, header.GetName(), header.GetValue());
+ {
+ if (!GetSteamHTTP()->SetHTTPRequestHeaderValue(hReq, header.GetName(), header.GetValue()))
+ {
+ Panic("Failed to SetHTTPRequestHeaderValue for %s\n", strUrl.c_str());
+ GetSteamHTTP()->ReleaseHTTPRequest(hReq);
+ return;
+ }
+ }
+ }
+
+ if (absoluteTimeoutMs != 0 && !GetSteamHTTP()->SetHTTPRequestAbsoluteTimeoutMS(hReq, static_cast(absoluteTimeoutMs)))
+ {
+ Panic("Failed to SetHTTPRequestAbsoluteTimeoutMS for %s\n", strUrl.c_str());
+ GetSteamHTTP()->ReleaseHTTPRequest(hReq);
+ return;
+ }
SteamAPICall_t hCall;
- GetSteamHTTP()->SendHTTPRequest(hReq, &hCall);
+ if (!GetSteamHTTP()->SendHTTPRequest(hReq, &hCall))
+ {
+ Panic("Failed to SendHTTPRequest for %s\n", strUrl.c_str());
+ GetSteamHTTP()->ReleaseHTTPRequest(hReq);
+ return;
+ }
new TrackedRequest(hReq, hCall, strUrl, strText, callbackCompleted, callbackError);
}
diff --git a/src/httpmanager.h b/src/httpmanager.h
index 7a8024b4b..1c5046e00 100644
--- a/src/httpmanager.h
+++ b/src/httpmanager.h
@@ -60,15 +60,20 @@ class HTTPManager
{
public:
void Get(std::string strUrl, CompletedCallback callbackCompleted,
- ErrorCallback callbackError = nullptr, std::vector* headers = nullptr);
+ ErrorCallback callbackError = nullptr, std::vector* headers = nullptr,
+ int absoluteTimeoutMs = 0);
void Post(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError = nullptr, std::vector* headers = nullptr);
+ ErrorCallback callbackError = nullptr, std::vector* headers = nullptr,
+ int absoluteTimeoutMs = 0);
void Put(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError = nullptr, std::vector* headers = nullptr);
+ ErrorCallback callbackError = nullptr, std::vector* headers = nullptr,
+ int absoluteTimeoutMs = 0);
void Patch(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError = nullptr, std::vector* headers = nullptr);
+ ErrorCallback callbackError = nullptr, std::vector* headers = nullptr,
+ int absoluteTimeoutMs = 0);
void Delete(std::string strUrl, std::string strText, CompletedCallback callbackCompleted,
- ErrorCallback callbackError = nullptr, std::vector* headers = nullptr);
+ ErrorCallback callbackError = nullptr, std::vector* headers = nullptr,
+ int absoluteTimeoutMs = 0);
bool HasAnyPendingRequests() const { return m_PendingRequests.size() > 0; }
private:
@@ -96,7 +101,7 @@ class HTTPManager
std::vector m_PendingRequests;
void GenerateRequest(EHTTPMethod method, std::string strUrl, std::string strText,
CompletedCallback callbackCompleted, ErrorCallback callbackError,
- std::vector* headers);
+ std::vector* headers, int absoluteTimeoutMs = 0);
};
extern HTTPManager g_HTTPManager;
diff --git a/src/idlemanager.cpp b/src/idlemanager.cpp
index 55de5127a..f1b2c3da5 100644
--- a/src/idlemanager.cpp
+++ b/src/idlemanager.cpp
@@ -78,6 +78,8 @@ void CIdleSystem::CheckForIdleClients()
}
}
+const uint64 ACTIVE_BUTTONS = IN_ATTACK + IN_JUMP + IN_DUCK + IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT;
+
// Logged inputs and time for the logged inputs are updated every time this function is run.
void CIdleSystem::UpdateIdleTimes()
{
@@ -106,7 +108,9 @@ void CIdleSystem::UpdateIdleTimes()
if (pMovement)
{
const auto buttonStates = pMovement->m_nButtons().m_pButtonStates();
- iCurrentMovement = buttonStates[0];
+
+ // Only count certain inputs for resetting idle state
+ iCurrentMovement = buttonStates[0] & ACTIVE_BUTTONS;
}
const auto buttonsChanged = pPlayer->GetLastInputs() ^ iCurrentMovement;
diff --git a/src/map_votes.cpp b/src/map_votes.cpp
index 148b159a9..72dd2479f 100644
--- a/src/map_votes.cpp
+++ b/src/map_votes.cpp
@@ -43,6 +43,7 @@ CConVar g_cvarVoteMapsCooldown("cs2f_vote_maps_cooldown", FCVAR_NONE, "De
CConVar g_cvarVoteMapsCooldownRng("cs2f_vote_maps_cooldown_rng", FCVAR_NONE, "Randomness range in both directions to apply to map cooldowns", 0.0f);
CConVar g_cvarVoteMaxNominations("cs2f_vote_max_nominations", FCVAR_NONE, "Number of nominations to include per vote, out of a maximum of 10", 10, true, 0, true, 10);
CConVar g_cvarVoteMaxMaps("cs2f_vote_max_maps", FCVAR_NONE, "Number of total maps to include per vote, including nominations, out of a maximum of 10", 10, true, 2, true, 10);
+CConVar g_cvarVoteBypassCooldownMaxCount("cs2f_vote_bypass_cd_max_count", FCVAR_NONE, "Maximum amount of players allowed to enable bypassing map cooldowns", 0, true, 0, false, 0);
CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current map on completion", ADMFLAG_ROOT)
{
@@ -1108,11 +1109,16 @@ bool CMapVoteSystem::WriteMapCooldownsToFile()
return true;
}
-void CMapVoteSystem::ClearInvalidNominations()
+void CMapVoteSystem::OnPlayerCountChange()
{
if (!g_cvarVoteManagerEnable.Get() || m_bIsVoteOngoing || !GetGlobals())
return;
+ int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false);
+
+ if (iOnlinePlayers > m_iSessionMaxPlayerCount)
+ m_iSessionMaxPlayerCount = iOnlinePlayers;
+
for (int i = 0; i < GetGlobals()->maxClients; i++)
{
int iNominatedMapIndex = m_arrPlayerNominations[i];
@@ -1123,8 +1129,8 @@ void CMapVoteSystem::ClearInvalidNominations()
auto pMap = GetMapByIndex(iNominatedMapIndex);
- // Check if nominated index still meets criteria for nomination
- if (!pMap->IsEnabled())
+ // Check if nominated map still meets criteria for nomination
+ if (!pMap->IsAvailable())
{
ClearPlayerInfo(i);
CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i);
@@ -1136,24 +1142,11 @@ void CMapVoteSystem::ClearInvalidNominations()
}
}
-void CMapVoteSystem::ApplyGameSettings(KeyValues* pKV)
+void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId)
{
if (!g_cvarVoteManagerEnable.Get())
return;
- const char* pszMapName;
- uint64 iWorkshopId;
-
- if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname"))
- pszMapName = pKV->FindKey("launchoptions")->GetString("levelname");
- else
- pszMapName = "";
-
- if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode"))
- iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode");
- else
- iWorkshopId = 0;
-
auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId);
if (pair.first != -1)
@@ -1166,15 +1159,22 @@ void CMapVoteSystem::ApplyGameSettings(KeyValues* pKV)
void CMapVoteSystem::OnLevelShutdown()
{
- // Put the map on cooldown as we transition to the next map
- PutMapOnCooldown(GetCurrentMap()->GetName());
+ bool bApplyCooldowns = m_iSessionMaxPlayerCount > g_cvarVoteBypassCooldownMaxCount.Get();
- // Fully apply pending group cooldowns
+ if (bApplyCooldowns)
+ {
+ // Put the map on cooldown as we transition to the next map
+ PutMapOnCooldown(GetCurrentMap()->GetName());
+ }
+
+ // Fully apply or discard pending group cooldowns
for (std::shared_ptr pCooldown : m_vecCooldowns)
{
if (pCooldown->GetPendingCooldown() > 0.0f)
{
- PutMapOnCooldown(pCooldown->GetMapName(), pCooldown->GetPendingCooldown());
+ if (bApplyCooldowns)
+ PutMapOnCooldown(pCooldown->GetMapName(), pCooldown->GetPendingCooldown());
+
pCooldown->SetPendingCooldown(0.0f);
}
}
@@ -1190,6 +1190,8 @@ void CMapVoteSystem::OnLevelShutdown()
if (m_timeMapListModified != std::filesystem::last_write_time(szPath))
ReloadMapList(false);
}
+
+ m_iSessionMaxPlayerCount = 0;
}
std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision)
@@ -1329,6 +1331,11 @@ std::shared_ptr CMapVoteSystem::GetMapCooldown(const char* pszMapName
return pCooldown;
}
+bool CCooldown::IsOnCooldown()
+{
+ return GetCurrentCooldown() > 0.0f && g_playerManager->GetOnlinePlayerCount(false) > g_cvarVoteBypassCooldownMaxCount.Get();
+}
+
float CCooldown::GetCurrentCooldown()
{
time_t timeCurrent = std::time(0);
diff --git a/src/map_votes.h b/src/map_votes.h
index 136bfbdf5..ac55efac1 100644
--- a/src/map_votes.h
+++ b/src/map_votes.h
@@ -50,8 +50,8 @@ class CCooldown
void SetTimeCooldown(time_t timeCooldown) { m_timeCooldown = timeCooldown; };
float GetPendingCooldown() { return m_fPendingCooldown; };
void SetPendingCooldown(float fPendingCooldown) { m_fPendingCooldown = fPendingCooldown; };
- bool IsOnCooldown() { return GetCurrentCooldown() > 0.0f; }
bool IsPending() { return m_fPendingCooldown > 0.0f && m_fPendingCooldown == GetCurrentCooldown(); };
+ bool IsOnCooldown();
float GetCurrentCooldown();
private:
@@ -206,11 +206,11 @@ class CMapVoteSystem
std::shared_ptr GetCurrentMap() { return m_pCurrentMap; }
void SetCurrentMap(std::shared_ptr pCurrentMap) { m_pCurrentMap = pCurrentMap; }
int GetDownloadQueueSize() { return m_DownloadQueue.size(); }
- void ClearInvalidNominations();
+ void OnPlayerCountChange();
std::shared_ptr GetForcedNextMap() { return m_pForcedNextMap; }
void SetForcedNextMap(std::shared_ptr pForcedNextMap) { m_pForcedNextMap = pForcedNextMap; }
std::unordered_map GetNominatedMaps();
- void ApplyGameSettings(KeyValues* pKV);
+ void ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId);
void OnLevelShutdown();
std::vector> GetMapCooldowns() { return m_vecCooldowns; }
std::string ConvertFloatToString(float fValue, int precision);
@@ -247,6 +247,7 @@ class CMapVoteSystem
std::weak_ptr m_pDownloadProgressTimer;
std::weak_ptr m_pRateLimitedDownloadTimer;
std::vector> m_vecWorkshopDetailsQueries;
+ int m_iSessionMaxPlayerCount = 0;
};
extern CMapVoteSystem* g_pMapVoteSystem;
\ No newline at end of file
diff --git a/src/mapmigrations.cpp b/src/mapmigrations.cpp
index 5de64a0e5..03db6b160 100644
--- a/src/mapmigrations.cpp
+++ b/src/mapmigrations.cpp
@@ -33,15 +33,13 @@ const time_t g_time20260420 = 1776725888;
CConVar g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2);
CConVar g_cvarMapMigrations20260420("cs2f_mapmigrations_20260420", FCVAR_NONE, "Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after]", 2);
-void CMapMigrations::ApplyGameSettings(KeyValues* pKV)
+void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId)
{
m_timeMapUpdated = std::numeric_limits::max();
// Don't run on default maps
- if (!pKV->FindKey("launchoptions") || !pKV->FindKey("launchoptions")->FindKey("customgamemode"))
- return;
-
- CMapMigrationWorkshopDetailsQuery::Create(pKV->FindKey("launchoptions")->GetUint64("customgamemode"));
+ if (iWorkshopId != 0)
+ CMapMigrationWorkshopDetailsQuery::Create(iWorkshopId);
}
void CMapMigrations::OnRoundPrestart()
diff --git a/src/mapmigrations.h b/src/mapmigrations.h
index c19fde129..6d4cca139 100644
--- a/src/mapmigrations.h
+++ b/src/mapmigrations.h
@@ -52,7 +52,7 @@ class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_thisSetConnected();
+ m_vecPlayers[slot.Get()] = pPlayer;
+
// Sometimes clients can be already auth'd at this point
if (g_pEngineServer2->IsClientFullyAuthenticated(slot))
pPlayer->OnAuthenticated();
- pPlayer->SetConnected();
- m_vecPlayers[slot.Get()] = pPlayer;
-
ResetPlayerFlags(slot.Get());
g_pMapVoteSystem->ClearPlayerInfo(slot.Get());
- g_pMapVoteSystem->ClearInvalidNominations();
+ g_pMapVoteSystem->OnPlayerCountChange();
return true;
}
@@ -829,7 +829,7 @@ void CPlayerManager::OnClientDisconnect(CPlayerSlot slot)
// One tick delay, to ensure player count decrements
CTimer::Create(0.01f, TIMERFLAG_MAP, []() {
g_pVoteManager->CheckRTVStatus();
- g_pMapVoteSystem->ClearInvalidNominations();
+ g_pMapVoteSystem->OnPlayerCountChange();
return -1.0f;
});
@@ -1850,11 +1850,20 @@ void CPlayerManager::ResetPlayerFlags(int slot)
int CPlayerManager::GetOnlinePlayerCount(bool bCountBots)
{
- int iOnlinePlayers = 0;
+ // Minimal caching, sometimes we call this function a lot of times
+ static int iOnlinePlayers = 0;
+ static bool bBotsCached = bCountBots;
+ static float flLastUpdateTime = -1.0f;
- if (!GetClientList())
+ if ((GetGlobals() && GetGlobals()->curtime == flLastUpdateTime && bBotsCached == bCountBots) || !GetClientList())
return iOnlinePlayers;
+ iOnlinePlayers = 0;
+ bBotsCached = bCountBots;
+
+ if (GetGlobals())
+ flLastUpdateTime = GetGlobals()->curtime;
+
for (int i = 0; i < GetClientList()->Count(); i++)
{
CServerSideClient* pClient = (*GetClientList())[i];
diff --git a/src/playermanager.h b/src/playermanager.h
index 835b0468a..6480dab08 100644
--- a/src/playermanager.h
+++ b/src/playermanager.h
@@ -25,6 +25,7 @@
#include "entity/cpointworldtext.h"
#include "entity/lights.h"
#include "gamesystem.h"
+#include "in_buttons.h"
#include "steam/isteamuser.h"
#include "steam/steam_api_common.h"
#include "steam/steamclientpublic.h"
diff --git a/src/utils/virtual.h b/src/utils/virtual.h
index 32c696a7d..8440e4343 100644
--- a/src/utils/virtual.h
+++ b/src/utils/virtual.h
@@ -19,6 +19,7 @@
#pragma once
#include "platform.h"
+#include "tier0/dbg.h"
#define CALL_VIRTUAL(retType, idx, ...) \
vmt::CallVirtual(idx, __VA_ARGS__)
diff --git a/src/utils/vscript_function.cpp b/src/utils/vscript_function.cpp
new file mode 100644
index 000000000..5519b81a3
--- /dev/null
+++ b/src/utils/vscript_function.cpp
@@ -0,0 +1,47 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "vscript_function.h"
+#include "entityclass.h"
+#include "entitysystem.h"
+
+void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName)
+{
+ // https://github.com/Wend4r/sourcesdk/blob/758e43823f02fcd40498d33a42cd93243258fe1e/public/vscript/ivscript.h#L280
+ struct ScriptFunctionBindingCurrent_t
+ {
+ ScriptFuncDescriptor_t m_desc;
+ ScriptClassDesc_t* m_pClassDesc;
+ ScriptBindingFunc_t m_pfnBinding;
+ void* m_pFunction;
+ ScriptFuncBindingFlags_t m_flags;
+ };
+
+ const auto& functionBindings = *reinterpret_cast*>(&pScriptDesc->m_FunctionBindings);
+ FOR_EACH_VEC(functionBindings, i)
+ {
+ auto& functionBinding = functionBindings.Element(i);
+ if (V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0)
+ continue;
+
+ return functionBinding.m_pFunction;
+ }
+
+ return nullptr;
+}
diff --git a/src/utils/vscript_function.h b/src/utils/vscript_function.h
new file mode 100644
index 000000000..fe990fd16
--- /dev/null
+++ b/src/utils/vscript_function.h
@@ -0,0 +1,102 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "platform.h"
+#include "virtual.h"
+
+struct ScriptClassDesc_t;
+class CBaseEntity;
+class Vector;
+class QAngle;
+
+void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName);
+
+template
+class CVScriptFunction
+{
+public:
+ CVScriptFunction() :
+ m_pFunction(nullptr), m_bVirtual(false) {}
+
+ inline bool Initialize(void* pFunction)
+ {
+ if (!pFunction)
+ return false;
+
+#ifdef PLATFORM_LINUX
+ // Itanium ABI: a pointer may encode the virtual index
+ uintptr_t ptr = reinterpret_cast(pFunction);
+ if (ptr & 1)
+ {
+ m_bVirtual = true;
+ m_iOffset = static_cast((ptr - 1) >> 3);
+
+ return true;
+ }
+#endif
+
+ m_pFunction = reinterpret_cast(pFunction);
+
+ return true;
+ }
+
+ inline Ret operator()(Class* pThis, Args... args)
+ {
+ if (m_bVirtual)
+ {
+ auto pFunction = vmt::GetVMethod(m_iOffset, pThis);
+
+ return pFunction(pThis, args...);
+ }
+
+ return m_pFunction(pThis, args...);
+ }
+
+ inline bool IsVirtual() const
+ {
+ return m_bVirtual;
+ }
+
+ inline void* GetPtr() const
+ {
+ if (IsVirtual())
+ return nullptr;
+
+ return reinterpret_cast(m_pFunction);
+ }
+
+ inline int GetOffset() const
+ {
+ if (IsVirtual())
+ return m_iOffset;
+
+ return -1;
+ }
+
+protected:
+ union
+ {
+ Ret(FASTCALL* m_pFunction)(Class*, Args...);
+ int m_iOffset;
+ };
+
+ bool m_bVirtual;
+};