From c157efa55b02af82e7ae4242259864775f0d7256 Mon Sep 17 00:00:00 2001 From: ray <1041137+ray@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:09:48 -0400 Subject: [PATCH] Refactoring more logic into vscript - Started gamerules cleanup and refactored some of it into vscript - Ported rollerball from the v8 scripting beta Signed-off-by: ray <1041137+ray@users.noreply.github.com> --- game/jbmod/cfg/vscript_convar_allowlist.txt | 2 + game/jbmod/maps/graphs/jb_waterhole.ain | Bin 16 -> 16 bytes game/jbmod/maps/jb_waterhole.bsp | Bin 405980 -> 406232 bytes game/jbmod/scripts/client_precache.txt | 18 + .../jbmod/scripts/vscripts/gamemodes/base.nut | 205 ++++++ .../scripts/vscripts/gamemodes/cstrike.nut | 52 +- .../scripts/vscripts/gamemodes/deathmatch.nut | 352 +++++++++- .../scripts/vscripts/gamemodes/default.nut | 26 +- game/jbmod/scripts/vscripts/gamemodes/dod.nut | 52 +- .../scripts/vscripts/gamemodes/rollerball.nut | 229 +++++++ .../scripts/vscripts/gamemodes/sandbox.nut | 17 +- game/jbmod/scripts/vscripts/gamemodes/tf.nut | 40 +- .../vscripts/weapons/weapon_leafblower.nut | 14 + mapsrc/jb_waterhole.vmf | 106 ++- src/game/client/jbmod/c_jbmod_player.cpp | 33 +- src/game/client/jbmod/c_jbmod_player.h | 6 +- src/game/server/jbmod/jbmod_client.cpp | 124 ++-- src/game/server/jbmod/jbmod_player.cpp | 642 +++++++----------- src/game/server/jbmod/jbmod_player.h | 35 +- src/game/server/vscript_server.cpp | 58 +- src/game/shared/jbmod/jbmod_gamerules.cpp | 386 ++++++++--- src/game/shared/jbmod/jbmod_gamerules.h | 3 +- src/game/shared/jbmod/jbmod_player_shared.cpp | 91 --- src/game/shared/jbmod/jbmod_player_shared.h | 9 - 24 files changed, 1740 insertions(+), 760 deletions(-) create mode 100644 game/jbmod/scripts/client_precache.txt create mode 100644 game/jbmod/scripts/vscripts/gamemodes/base.nut create mode 100644 game/jbmod/scripts/vscripts/gamemodes/rollerball.nut diff --git a/game/jbmod/cfg/vscript_convar_allowlist.txt b/game/jbmod/cfg/vscript_convar_allowlist.txt index 12ebdc60c2a..e355de8c717 100644 --- a/game/jbmod/cfg/vscript_convar_allowlist.txt +++ b/game/jbmod/cfg/vscript_convar_allowlist.txt @@ -51,4 +51,6 @@ vscript_convar_allowlist // Other sv_alltalk allowed + sv_jbmod_weapon_respawn_time allowed + sv_jbmod_item_respawn_time allowed } diff --git a/game/jbmod/maps/graphs/jb_waterhole.ain b/game/jbmod/maps/graphs/jb_waterhole.ain index 0d7c8df1267990d426ca4b5b150f0a48d08722f4..3b24240dcf10dcd128068db86bd795f47358ed05 100644 GIT binary patch literal 16 PcmY#oU|>)KVi*7b1SkMZ literal 16 PcmY#oU|>)MVi*7b1RMZM diff --git a/game/jbmod/maps/jb_waterhole.bsp b/game/jbmod/maps/jb_waterhole.bsp index 485174b7238cdc3bf86ab03a39fc50499442445f..b95ad59c5884330c068ebea8238d0a6b69e7ea74 100644 GIT binary patch delta 22517 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 S+zizWZQD0jt8r*~<^K=CiOvK7 delta 26114 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 0 && !limitInWorld ) + return 0; + + return Convars.GetFloat( "sv_jbmod_weapon_respawn_time" ); +} + +function GetItemRespawnTime( classname ) +{ + return Convars.GetFloat( "sv_jbmod_item_respawn_time" ); +} + +function ShouldWeaponRespawn( classname ) +{ + return true; +} + +function CanPickupWeapon( player, classname, ownsWeapon ) +{ + if ( Convars.GetFloat( "mp_weaponstay" ) > 0 && ownsWeapon ) + return false; + + return true; +} + +//============================================================================= +// Connection / Disconnection +//============================================================================= +function OnPlayerConnect( player ) +{ + local name = player.GetPlayerName(); + if ( name == "" ) + name = ""; + ClientPrint( null, Constants.EHudNotify.HUD_PRINTNOTIFY, name + " has joined the game\n" ); + + // Show MOTD by default, override in your own gamemode if you don't want it + player.ShowMOTD(); +} + +function OnPlayerDisconnect( player ) +{ +} + +//============================================================================= +// Chat (return false to block) +//============================================================================= +function OnPlayerChat( player, text ) +{ + return text; +} + +//============================================================================= +// Round Lifecycle +//============================================================================= +function OnRoundStart() +{ +} + +function OnRoundEnd() +{ +} diff --git a/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut b/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut index 2fedb0bf53b..7507e62d1e6 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut @@ -1,4 +1,54 @@ -printl( "Loading Counter-Strike..." ); +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//============================================================================= +// Counter-Strike gamemode +// Very basic version to load maps, no gameplay logic +//============================================================================= + +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Spawn Point Registration +//============================================================================= RegisterEntityClass( "info_player_counterterrorist", "info_player_start" ); RegisterEntityClass( "info_player_terrorist", "info_player_start" ); + +//============================================================================= +// Preserve Entities +//============================================================================= +PreserveEntityClass( "info_player_counterterrorist" ); +PreserveEntityClass( "info_player_terrorist" ); + +//============================================================================= +// Team Constants +//============================================================================= +const TEAM_COUNTERTERRORIST = Constants.ETeam.TEAM_COMBINE; +const TEAM_TERRORIST = Constants.ETeam.TEAM_REBELS; + +//============================================================================= +// Spawn Point Selection +//============================================================================= +function GetSpawnPointClassname( player ) +{ + local team = player.GetTeam(); + if ( team == TEAM_COUNTERTERRORIST ) + return "info_player_counterterrorist"; + else if ( team == TEAM_TERRORIST ) + return "info_player_terrorist"; + return "info_player_deathmatch"; +} diff --git a/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut b/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut index fe5c3af8d74..65598341378 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut @@ -1,5 +1,353 @@ -printl( "Loading Deathmatch..." ); +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -RegisterEntityClass( "info_player_deathmatch", "info_player_start" ); +//============================================================================= +// Deathmatch gamemode +// Implements most HL2DM logic +//============================================================================= +SetGameDescription( "JBMod Deathmatch" ); + +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Spawn Point Registration +//============================================================================= RegisterEntityClass( "info_player_combine", "info_player_start" ); RegisterEntityClass( "info_player_rebel", "info_player_start" ); + +//============================================================================= +// Preserve Entities +//============================================================================= +PreserveEntityClass( "info_player_combine" ); +PreserveEntityClass( "info_player_rebel" ); + +//============================================================================= +// In deathmatch, players can be either combine or rebels +//============================================================================= +CitizenModels <- [ + "models/humans/group03/male_01.mdl", + "models/humans/group03/male_02.mdl", + "models/humans/group03/female_01.mdl", + "models/humans/group03/male_03.mdl", + "models/humans/group03/female_02.mdl", + "models/humans/group03/male_04.mdl", + "models/humans/group03/female_03.mdl", + "models/humans/group03/male_05.mdl", + "models/humans/group03/female_04.mdl", + "models/humans/group03/male_06.mdl", + "models/humans/group03/female_06.mdl", + "models/humans/group03/male_07.mdl", + "models/humans/group03/female_07.mdl", + "models/humans/group03/male_08.mdl", + "models/humans/group03/male_09.mdl", +]; + +CombineModels <- [ + "models/combine_soldier.mdl", + "models/combine_soldier_prisonguard.mdl", + "models/combine_super_soldier.mdl", + "models/police.mdl", +]; + +LastCitizenModel <- 0; +LastCombineModel <- 0; + +//============================================================================= +// Precache models and sounds +//============================================================================= +function OnPrecache() +{ + foreach ( m in CitizenModels ) + PrecacheModel( m ); + foreach ( m in CombineModels ) + PrecacheModel( m ); + + PrecacheScriptSound( "NPC_MetroPolice.Die" ); + PrecacheScriptSound( "NPC_CombineS.Die" ); + PrecacheScriptSound( "NPC_Citizen.die" ); +} + +//============================================================================= +// Only allow models defined above +//============================================================================= +function IsValidModel( model ) +{ + foreach ( m in CitizenModels ) + if ( m == model ) return true; + foreach ( m in CombineModels ) + if ( m == model ) return true; + return false; +} + +//============================================================================= +// Model Selection +//============================================================================= +function OnPlayerSetModel( player, model ) +{ + if ( IsTeamplay() ) + { + local team = player.GetTeam(); + if ( team == Constants.ETeam.TEAM_COMBINE ) + { + model = CombineModels[LastCombineModel % CombineModels.len()]; + LastCombineModel++; + } + else if ( team == Constants.ETeam.TEAM_REBELS ) + { + model = CitizenModels[LastCitizenModel % CitizenModels.len()]; + LastCitizenModel++; + } + } + + if ( !IsValidModel( model ) ) + model = "models/combine_soldier.mdl"; + + return model; +} + +//============================================================================= +// Model Change Notification +//============================================================================= +function OnPlayerModelChanged( player, model ) +{ + if ( IsTeamplay() ) + return; + + if ( !IsValidModel( model ) ) + player.SetPlayerModel( "models/combine_soldier.mdl" ); +} + +//============================================================================= +// Spawn Point Selection +//============================================================================= +function GetSpawnPointClassname( player ) +{ + if ( !IsTeamplay() ) + return "info_player_deathmatch"; + + local team = player.GetTeam(); + if ( team == Constants.ETeam.TEAM_COMBINE ) + return "info_player_combine"; + else if ( team == Constants.ETeam.TEAM_REBELS ) + return "info_player_rebel"; + return "info_player_deathmatch"; +} + +//============================================================================= +// Team Selection +//============================================================================= +function OnPlayerPickTeam( player ) +{ + if ( IsTeamplay() ) + { + // Auto-balance: assign to the smaller team + local combineCount = GetTeamPlayerCount( Constants.ETeam.TEAM_COMBINE ); + local rebelsCount = GetTeamPlayerCount( Constants.ETeam.TEAM_REBELS ); + + if ( combineCount > rebelsCount ) + return Constants.ETeam.TEAM_REBELS; + else if ( combineCount < rebelsCount ) + return Constants.ETeam.TEAM_COMBINE; + else + return (RandomInt( 0, 1 ) == 0) ? Constants.ETeam.TEAM_COMBINE : Constants.ETeam.TEAM_REBELS; + } + return Constants.ETeam.TEAM_UNASSIGNED; +} + +//============================================================================= +// Loadout +//============================================================================= +function IsCombineModel( player ) +{ + local model = player.GetModelName(); + return model.find( "police" ) != null || model.find( "combine" ) != null; +} +function OnPlayerSpawn( player ) +{ + player.EquipSuit(); + + player.GiveAmmo( 255, "Pistol" ); + player.GiveAmmo( 45, "SMG1" ); + player.GiveAmmo( 1, "grenade" ); + player.GiveAmmo( 6, "Buckshot" ); + player.GiveAmmo( 6, "357" ); + + if ( IsCombineModel( player ) ) + player.GiveItem( "weapon_stunstick" ); + else + player.GiveItem( "weapon_crowbar" ); + + player.GiveItem( "weapon_pistol" ); + player.GiveItem( "weapon_smg1" ); + player.GiveItem( "weapon_frag" ); + player.GiveItem( "weapon_physcannon" ); + + local defaultWpn = player.GetClientConVar( "cl_defaultweapon" ); + if ( defaultWpn != "" ) + player.SwitchToWeapon( defaultWpn ); + else + player.SwitchToWeapon( "weapon_physcannon" ); +} + +//============================================================================= +// Scoring +//============================================================================= +function OnPlayerKilled( victim, attacker ) +{ + if ( attacker == null ) + return; + + if ( attacker == victim ) + attacker.AddTeamScore( -1 ); + else + attacker.AddTeamScore( 1 ); +} + +//============================================================================= +// Impulse 101 / Give All Items +//============================================================================= +function OnGiveAllItems( player ) +{ + player.EquipSuit(); + + player.GiveAmmo( 255, "Pistol" ); + player.GiveAmmo( 255, "AR2" ); + player.GiveAmmo( 5, "AR2AltFire" ); + player.GiveAmmo( 255, "SMG1" ); + player.GiveAmmo( 1, "smg1_grenade" ); + player.GiveAmmo( 255, "Buckshot" ); + player.GiveAmmo( 32, "357" ); + player.GiveAmmo( 3, "rpg_round" ); + player.GiveAmmo( 16, "XBowBolt" ); + player.GiveAmmo( 1, "grenade" ); + player.GiveAmmo( 2, "slam" ); + + player.GiveItem( "weapon_crowbar" ); + player.GiveItem( "weapon_stunstick" ); + player.GiveItem( "weapon_pistol" ); + player.GiveItem( "weapon_357" ); + player.GiveItem( "weapon_smg1" ); + player.GiveItem( "weapon_ar2" ); + player.GiveItem( "weapon_shotgun" ); + player.GiveItem( "weapon_frag" ); + player.GiveItem( "weapon_crossbow" ); + player.GiveItem( "weapon_rpg" ); + player.GiveItem( "weapon_slam" ); + player.GiveItem( "weapon_physcannon" ); +} + +//============================================================================= +// Death Sound +//============================================================================= +function GetModelSoundPrefix( player ) +{ + local model = player.GetModelName(); + if ( model.find( "police" ) != null ) + return "NPC_MetroPolice"; + else if ( model.find( "combine" ) != null ) + return "NPC_CombineS"; + return "NPC_Citizen"; +} +function OnPlayerDeathSound( player ) +{ + return GetModelSoundPrefix( player ) + ".Die"; +} + +//============================================================================= +// Win Conditions +//============================================================================= +function CheckWinConditions() +{ + if ( IsGameOver() ) + return; + + // Time limit + if ( GetMapRemainingTime() < 0 ) + { + GoToIntermission(); + return; + } + + // Frag limit + local fragLimit = Convars.GetFloat( "mp_fraglimit" ); + if ( fragLimit <= 0 ) + return; + + if ( IsTeamplay() ) + { + if ( GetTeamScore( Constants.ETeam.TEAM_COMBINE ) >= fragLimit + || GetTeamScore( Constants.ETeam.TEAM_REBELS ) >= fragLimit ) + { + GoToIntermission(); + return; + } + } + else + { + for ( local i = 1; i <= GetMaxPlayers(); i++ ) + { + local player = GetPlayerByIndex( i ); + if ( player != null && player.GetFragCount() >= fragLimit ) + { + GoToIntermission(); + return; + } + } + } +} +function OnThink() +{ + CheckWinConditions(); +} + +//============================================================================= +// Respawn (wait for death animation, then respawn) +//============================================================================= +function OnPlayerRespawn( player ) +{ + return Time() > player.GetDeathTime() + Constants.Server.DEATH_ANIMATION_TIME; +} + +//============================================================================= +// Player Relationship +//============================================================================= +function GetPlayerRelationship( player, target ) +{ + if ( IsTeamplay() && player.GetTeam() == target.GetTeam() ) + return Constants.ERelationship.GR_TEAMMATE; + return Constants.ERelationship.GR_NOTTEAMMATE; +} + +//============================================================================= +// Player Connect +//============================================================================= +function OnPlayerConnect( player ) +{ + local name = player.GetPlayerName(); + if ( name == "" ) + name = ""; + ClientPrint( null, Constants.EHudNotify.HUD_PRINTNOTIFY, name + " has joined the game\n" ); + + // Show team info in teamplay + if ( IsTeamplay() ) + { + ClientPrint( player, Constants.EHudNotify.HUD_PRINTTALK, "You are on team " + GetTeamName( player.GetTeam() ) + "\n" ); + } + + // Show MOTD + player.ShowMOTD(); +} diff --git a/game/jbmod/scripts/vscripts/gamemodes/default.nut b/game/jbmod/scripts/vscripts/gamemodes/default.nut index bb1c769b42d..5ca3172a84c 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/default.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/default.nut @@ -1,18 +1,28 @@ -// Default gamemode selector -// This script runs if no jbmod_logic_gamemode is found in the map. +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// TODO: Once we have mounting place, we can also match by folder/vpk -// This is just a demo for now +//============================================================================= +// Default gamemode selector +// This script runs if no jbmod_logic_gamemode is found in the map, and tries +// to load a gamemode based on the map name prefix. +//============================================================================= local mapname = GetMapName(); local lastSlash = mapname.find( "/" ); if ( lastSlash != null ) mapname = mapname.slice( lastSlash + 1 ); -local dot = mapname.find( ".bsp" ); -if ( dot != null ) - mapname = mapname.slice( 0, dot ); - if ( mapname.slice( 0, 3 ) == "dm_" ) IncludeScript( "gamemodes/deathmatch.nut" ); else if ( mapname.slice( 0, 3 ) == "cs_" || mapname.slice( 0, 3 ) == "de_" ) diff --git a/game/jbmod/scripts/vscripts/gamemodes/dod.nut b/game/jbmod/scripts/vscripts/gamemodes/dod.nut index 23b27676428..35af3bd8ec4 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/dod.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/dod.nut @@ -1,4 +1,54 @@ -printl( "Loading Day of Defeat..." ); +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//============================================================================= +// Day of Defeat gamemode +// Very basic version to load maps, no gameplay logic +//============================================================================= + +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Spawn Point Registration +//============================================================================= RegisterEntityClass( "info_player_allies", "info_player_start" ); RegisterEntityClass( "info_player_axis", "info_player_start" ); + +//============================================================================= +// Preserve Entities +//============================================================================= +PreserveEntityClass( "info_player_allies" ); +PreserveEntityClass( "info_player_axis" ); + +//============================================================================= +// Team Constants +//============================================================================= +const TEAM_ALLIES = Constants.ETeam.TEAM_COMBINE; +const TEAM_AXIS = Constants.ETeam.TEAM_REBELS; + +//============================================================================= +// Spawn Point Selection +//============================================================================= +function GetSpawnPointClassname( player ) +{ + local team = player.GetTeam(); + if ( team == TEAM_ALLIES ) + return "info_player_allies"; + else if ( team == TEAM_AXIS ) + return "info_player_axis"; + return "info_player_deathmatch"; +} diff --git a/game/jbmod/scripts/vscripts/gamemodes/rollerball.nut b/game/jbmod/scripts/vscripts/gamemodes/rollerball.nut new file mode 100644 index 00000000000..9ee7ab223a4 --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/rollerball.nut @@ -0,0 +1,229 @@ +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//============================================================================= +// Rollerball gamemode +// Sample gamemode for testing non-standard player movement +//============================================================================= +SetGameDescription( "Rollerball" ); + +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Internal entity mapping +//============================================================================= +PlayerBalls <- {}; +JumpCooldowns <- {}; +SprintPower <- {}; +BallCounter <- 0; + +//============================================================================= +// Physics tuning +//============================================================================= +ROLL_FORCE <- 400.0; +ROLL_FORCE_BACK <- 320.0; +JUMP_FORCE <- 15000.0; +JUMP_COOLDOWN <- 0.8; + +//============================================================================= +// Sprint tuning +//============================================================================= +SPRINT_MULTIPLIER <- 2.0; +SPRINT_MAX <- 100.0; +SPRINT_DRAIN <- 25.0; +SPRINT_RECHARGE <- 15.0; + +//============================================================================= +// Precache rollermine +//============================================================================= +function OnPrecache() +{ + PrecacheModel( "models/roller.mdl" ); +} + +//============================================================================= +// Spawn: create a ball, parent the player to it +//============================================================================= +function OnPlayerSpawn( player ) +{ + player.RemoveAllItems(); + local idx = player.entindex(); + + // Clean up old ball if it exists (respawn case) + player.AcceptInput( "ClearParent", "", null, null ); + if ( idx in PlayerBalls && PlayerBalls[idx] != null && PlayerBalls[idx].IsValid() ) + { + PlayerBalls[idx].Destroy(); + delete PlayerBalls[idx]; + } + BallCounter++; + local ballName = "rollerball_" + idx + "_" + BallCounter; + + // Spawn a rollermine at the player's position + local ball = SpawnEntityFromTable( "prop_physics_override", { + model = "models/roller.mdl", + origin = player.GetOrigin() + Vector( 0, 0, 16 ), + targetname = ballName, + health = 0 + } ); + ball.AddEFlags( 128 ); // EFL_FORCE_CHECK_TRANSMIT + + // Track it + PlayerBalls[idx] <- ball; + SprintPower[idx] <- SPRINT_MAX; + + // Make the player invisible, non-solid + player.SetDrawEnabled( false ); + player.ShowViewModel( false ); + player.SetSolid( 0 ); // SOLID_NONE + player.SetMoveType( 0, 0 ); // MOVETYPE_NONE + + // Parent the player to the ball + player.SetOrigin( ball.GetOrigin() ); + player.AcceptInput( "SetParent", ballName, ball, ball ); +} + +//============================================================================= +// Block weapon pickups +//============================================================================= +function CanPickupWeapon( player, classname, ownsWeapon ) +{ + return false; +} + +//============================================================================= +// No fall damage +//============================================================================= +function GetFallDamage( player, fallSpeed ) +{ + return 0; +} + +//============================================================================= +// Immediate respawn +//============================================================================= +function OnPlayerRespawn( player ) +{ + return true; +} + +//============================================================================= +// Per-tick: read buttons and apply forces to the ball +//============================================================================= +function OnThink() +{ + local dt = FrameTime(); + + for ( local i = 1; i <= GetMaxPlayers(); i++ ) + { + local player = GetPlayerByIndex( i ); + if ( player == null ) + continue; + + local idx = player.entindex(); + + // If player is dead, clean up their ball immediately + if ( !player.IsAlive() ) + { + if ( idx in PlayerBalls && PlayerBalls[idx] != null && PlayerBalls[idx].IsValid() ) + { + player.AcceptInput( "ClearParent", "", null, null ); + PlayerBalls[idx].Destroy(); + } + if ( idx in PlayerBalls ) + delete PlayerBalls[idx]; + continue; + } + + if ( !(idx in PlayerBalls) || PlayerBalls[idx] == null || !PlayerBalls[idx].IsValid() ) + continue; + + local ball = PlayerBalls[idx]; + local buttons = player.GetButtons(); + local fwd = player.GetEyeForward(); + + // Sprint: boost force while holding IN_SPEED, drains power + local sprinting = false; + if ( (buttons & Constants.FButtons.IN_SPEED) && SprintPower[idx] > 0 ) + { + sprinting = true; + SprintPower[idx] <- SprintPower[idx] - SPRINT_DRAIN * dt; + if ( SprintPower[idx] < 0 ) + SprintPower[idx] <- 0.0; + } + else + { + // Recharge when not sprinting + SprintPower[idx] <- SprintPower[idx] + SPRINT_RECHARGE * dt; + if ( SprintPower[idx] > SPRINT_MAX ) + SprintPower[idx] <- SPRINT_MAX; + } + local forceMul = sprinting ? SPRINT_MULTIPLIER : 1.0; + + // Forward movement + if ( buttons & Constants.FButtons.IN_FORWARD ) + { + local f = ROLL_FORCE * forceMul; + ball.ApplyForceCenter( Vector( fwd.x * f, fwd.y * f, 0 ) ); + } + + // Backward movement + if ( buttons & Constants.FButtons.IN_BACK ) + { + local f = ROLL_FORCE_BACK * forceMul; + ball.ApplyForceCenter( Vector( fwd.x * -f, fwd.y * -f, 0 ) ); + } + + // Jump (spacebar) with cooldown + if ( buttons & Constants.FButtons.IN_JUMP ) + { + local now = Time(); + if ( !(idx in JumpCooldowns) || JumpCooldowns[idx] < now ) + { + ball.ApplyForceCenter( Vector( 0, 0, JUMP_FORCE ) ); + JumpCooldowns[idx] <- now + JUMP_COOLDOWN; + } + } + } +} + +//============================================================================= +// Clean up ball on disconnect +//============================================================================= +function OnPlayerDisconnect( player ) +{ + local idx = player.entindex(); + if ( idx in PlayerBalls && PlayerBalls[idx] != null && PlayerBalls[idx].IsValid() ) + PlayerBalls[idx].Destroy(); + if ( idx in PlayerBalls ) + delete PlayerBalls[idx]; + if ( idx in SprintPower ) + delete SprintPower[idx]; +} + +//============================================================================= +// Welcome message +//============================================================================= +function OnPlayerConnect( player ) +{ + local name = player.GetPlayerName(); + if ( name == "" ) + name = ""; + ClientPrint( null, Constants.EHudNotify.HUD_PRINTNOTIFY, name + " has joined the game\n" ); + + ClientPrint( player, Constants.EHudNotify.HUD_PRINTTALK, "Welcome to Rollerball! WASD to roll, Space to jump, Shift for a speed boost.\n" ); +} diff --git a/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut b/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut index e570c15bb3e..d9b73bfc35c 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut @@ -12,11 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -printl( "Initializing Sandbox..." ); +//============================================================================= +// Sandbox gamemode +// Starts with a physgun, will eventually be where spawn menus etc are +//============================================================================= SetGameDescription( "JBMod Sandbox" ); -Convars.SetValue( "sv_infinite_aux_power", 1 ); -Convars.SetValue( "mp_falldamage", 1 ); +Convars.SetDefault( "sv_infinite_aux_power", "1" ); +Convars.SetDefault( "mp_falldamage", "1" ); +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Spawn with suit + physgun +//============================================================================= function OnPlayerSpawn( player ) { player.EquipSuit(); diff --git a/game/jbmod/scripts/vscripts/gamemodes/tf.nut b/game/jbmod/scripts/vscripts/gamemodes/tf.nut index 714ff61640d..b315aff9dca 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/tf.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/tf.nut @@ -1,3 +1,41 @@ -printl( "Loading Team Fortress..." ); +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//============================================================================= +// Team Fortress gamemode +// Very basic version to load maps, no gameplay logic +//============================================================================= + +//============================================================================= +// Include base gamemode for default behavior +//============================================================================= +IncludeScript( "gamemodes/base.nut" ); + +//============================================================================= +// Spawn Point Registration +//============================================================================= RegisterEntityClass( "info_player_teamspawn", "info_player_start" ); + +//============================================================================= +// Preserve Entities +//============================================================================= +PreserveEntityClass( "info_player_teamspawn" ); + +//============================================================================= +// Spawn Point Selection +//============================================================================= +function GetSpawnPointClassname( player ) +{ + return "info_player_teamspawn"; +} diff --git a/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut b/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut index 6ab360b1664..79095f5d599 100644 --- a/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut +++ b/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut @@ -1,3 +1,17 @@ +// Copyright 2026 The JBMod Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + local FORCE = 500.0 local FORCE_MULTIPLIER = 4.0 local RANGE = 512.0 diff --git a/mapsrc/jb_waterhole.vmf b/mapsrc/jb_waterhole.vmf index 55163018e12..623e0d7d3d0 100644 --- a/mapsrc/jb_waterhole.vmf +++ b/mapsrc/jb_waterhole.vmf @@ -1,8 +1,8 @@ versioninfo { "editorversion" "400" - "editorbuild" "8870" - "mapversion" "37" + "editorbuild" "10427" + "mapversion" "38" "formatversion" "100" "prefab" "0" } @@ -15,11 +15,12 @@ viewsettings "bShowGrid" "1" "bShowLogicalGrid" "0" "nGridSpacing" "8" + "bShow3DGrid" "0" } world { "id" "0" - "mapversion" "37" + "mapversion" "38" "classname" "worldspawn" "detailmaterial" "detail/detailsprites" "detailvbsp" "detail.vbsp" @@ -1019,7 +1020,6 @@ entity editor { "color" "220 30 220" - "visgroupid" "3" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 0]" @@ -1034,8 +1034,6 @@ entity editor { "color" "0 255 0" - "visgroupid" "3" - "visgroupid" "2" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 500]" @@ -1051,7 +1049,6 @@ entity editor { "color" "0 0 255" - "visgroupid" "3" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 1000]" @@ -1067,7 +1064,6 @@ entity editor { "color" "220 30 220" - "visgroupid" "3" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 1500]" @@ -1083,7 +1079,6 @@ entity editor { "color" "220 30 220" - "visgroupid" "3" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 2000]" @@ -1091,24 +1086,73 @@ entity } entity { - "id" "1803" - "classname" "prop_vehicle_airboat" - "actionScale" "1" - "angles" "0 90 0" - "fademindist" "-1" - "fadescale" "1" - "lightmapresolutionx" "32" - "lightmapresolutiony" "32" - "model" "models/airboat.mdl" - "origin" "504 360 8" - "solid" "6" - "vehiclescript" "scripts/vehicles/airboat.txt" + "id" "1814" + "classname" "info_player_start" + "angles" "0 180 0" + "origin" "544 312 8" + editor + { + "color" "0 255 0" + "visgroupshown" "1" + "visgroupautoshown" "1" + "logicalpos" "[0 500]" + } +} +entity +{ + "id" "1820" + "classname" "info_player_start" + "angles" "0 180 0" + "origin" "544 232 8" + editor + { + "color" "0 255 0" + "visgroupshown" "1" + "visgroupautoshown" "1" + "logicalpos" "[0 500]" + } +} +entity +{ + "id" "1826" + "classname" "info_player_start" + "angles" "0 180 0" + "origin" "544 192 8" + editor + { + "color" "0 255 0" + "visgroupshown" "1" + "visgroupautoshown" "1" + "logicalpos" "[0 500]" + } +} +entity +{ + "id" "1838" + "classname" "info_player_start" + "angles" "0 180 0" + "origin" "544 152 8" + editor + { + "color" "0 255 0" + "visgroupshown" "1" + "visgroupautoshown" "1" + "logicalpos" "[0 500]" + } +} +entity +{ + "id" "1842" + "classname" "jbmod_logic_gamemode" + "gamemode" "rollerball" + "spawnflags" "0" + "origin" "0 0 0" editor { "color" "220 30 220" "visgroupshown" "1" "visgroupautoshown" "1" - "logicalpos" "[0 2500]" + "logicalpos" "[0 0]" } } cameras @@ -1116,21 +1160,13 @@ cameras "activecamera" "0" camera { - "position" "[443.354 108.04 121.019]" - "look" "[420.522 370.495 82.9345]" + "position" "[453.647 -10.2785 138.188]" + "look" "[430.815 252.176 100.104]" } } -cordons +cordon { + "mins" "(-1024 -1024 -1024)" + "maxs" "(1024 1024 1024)" "active" "0" - cordon - { - "name" "cordon" - "active" "1" - box - { - "mins" "(99999 99999 99999)" - "maxs" "(-99999 -99999 -99999)" - } - } } diff --git a/src/game/client/jbmod/c_jbmod_player.cpp b/src/game/client/jbmod/c_jbmod_player.cpp index b499ca3eec0..e73b6fa1645 100644 --- a/src/game/client/jbmod/c_jbmod_player.cpp +++ b/src/game/client/jbmod/c_jbmod_player.cpp @@ -52,7 +52,6 @@ IMPLEMENT_CLIENTCLASS_DT(C_JBMod_Player, DT_JBMod_Player, CJBMod_Player) RecvPropEHandle( RECVINFO( m_hRagdoll ) ), RecvPropInt( RECVINFO( m_iSpawnInterpCounter ) ), - RecvPropInt( RECVINFO( m_iPlayerSoundType) ), RecvPropBool( RECVINFO( m_fIsWalking ) ), END_RECV_TABLE() @@ -953,6 +952,38 @@ void C_JBMod_Player::CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNea return; } + C_BaseEntity *pParent = GetMoveParent(); + if ( pParent && m_lifeState == LIFE_ALIVE && !IsObserver() ) + { + BaseClass::CalcView( eyeOrigin, eyeAngles, zNear, zFar, fov ); + + Vector vOrigin = pParent->GetAbsOrigin(); + engine->GetViewAngles( eyeAngles ); + + Vector vForward; + AngleVectors( eyeAngles, &vForward ); + VectorNormalize( vForward ); + + eyeOrigin = vOrigin; + eyeOrigin.z += 40.0f; + VectorMA( eyeOrigin, -100.0f, vForward, eyeOrigin ); + + Vector WALL_MIN( -WALL_OFFSET, -WALL_OFFSET, -WALL_OFFSET ); + Vector WALL_MAX( WALL_OFFSET, WALL_OFFSET, WALL_OFFSET ); + + trace_t trace; + C_BaseEntity::PushEnableAbsRecomputations( false ); + UTIL_TraceHull( vOrigin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trace ); + C_BaseEntity::PopEnableAbsRecomputations(); + + if ( trace.fraction < 1.0f ) + { + eyeOrigin = trace.endpos; + } + + return; + } + BaseClass::CalcView( eyeOrigin, eyeAngles, zNear, zFar, fov ); } diff --git a/src/game/client/jbmod/c_jbmod_player.h b/src/game/client/jbmod/c_jbmod_player.h index 18dd9c19294..8294b4c674c 100644 --- a/src/game/client/jbmod/c_jbmod_player.h +++ b/src/game/client/jbmod/c_jbmod_player.h @@ -81,7 +81,6 @@ class C_JBMod_Player : public C_BaseHLPlayer virtual void CreateLightEffects( void ) {} virtual bool ShouldReceiveProjectedTextures( int flags ); virtual void PostDataUpdate( DataUpdateType_t updateType ); - virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ); virtual void PreThink( void ); virtual void DoImpactEffect( trace_t &tr, int nDamageType ); IRagdoll* GetRepresentativeRagdoll() const; @@ -108,8 +107,7 @@ class C_JBMod_Player : public C_BaseHLPlayer void Initialize( void ); int GetIDTarget() const; void UpdateIDTarget( void ); - void PrecacheFootStepSounds( void ); - const char *GetPlayerModelSoundPrefix( void ); + JBModPlayerState State_Get() const; @@ -153,8 +151,6 @@ class C_JBMod_Player : public C_BaseHLPlayer int m_iSpawnInterpCounter; int m_iSpawnInterpCounterCache; - int m_iPlayerSoundType; - void ReleaseFlashlight( void ); Beam_t *m_pFlashlightBeam; diff --git a/src/game/server/jbmod/jbmod_client.cpp b/src/game/server/jbmod/jbmod_client.cpp index 70cb6691e23..5dd1a03e872 100644 --- a/src/game/server/jbmod/jbmod_client.cpp +++ b/src/game/server/jbmod/jbmod_client.cpp @@ -5,13 +5,6 @@ // $NoKeywords: $ // //=============================================================================// -/* - -===== tf_client.cpp ======================================================== - - HL2 client/server game specific stuff - -*/ #include "cbase.h" #include "jbmod_player.h" @@ -25,6 +18,8 @@ #include "engine/IEngineSound.h" #include "team.h" #include "viewport_panel_names.h" +#include "vscript_shared.h" +#include "filesystem.h" #include "tier0/vprof.h" @@ -33,8 +28,6 @@ void Host_Say( edict_t *pEdict, bool teamonly ); -ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." ); - extern CBaseEntity* FindPickerEntityClass( CBasePlayer *pPlayer, char *classname ); extern bool g_fGameOver; @@ -43,38 +36,15 @@ void FinishClientPutInServer( CJBMod_Player *pPlayer ) pPlayer->InitialSpawn(); pPlayer->Spawn(); - - char sName[128]; - Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) ); - - // First parse the name and remove any %'s - for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + if ( g_pScriptVM ) { - // Replace it with a space - if ( *pApersand == '%' ) - *pApersand = ' '; - } - - // notify other clients of player joining the game - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "" ); - - if ( JBModRules()->IsTeamplay() == true ) - { - ClientPrint( pPlayer, HUD_PRINTTALK, "You are on team %s1\n", pPlayer->GetTeam()->GetName() ); + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerConnect" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL, pPlayer->GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + } } - - const ConVar *hostname = cvar->FindVar( "hostname" ); - const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY"; - - KeyValues *data = new KeyValues("data"); - data->SetString( "title", title ); // info panel title - data->SetString( "type", "1" ); // show userdata from stringtable entry - data->SetString( "msg", "motd" ); // use this stringtable entry - data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() ); - - pPlayer->ShowViewPortPanel( PANEL_INFO, true, data ); - - data->deleteThis(); } /* @@ -86,7 +56,6 @@ called each time a player is spawned into the game */ void ClientPutInServer( edict_t *pEdict, const char *playername ) { - // Allocate a CBaseTFPlayer for pev, and call spawn CJBMod_Player *pPlayer = CJBMod_Player::CreatePlayer( "player", pEdict ); pPlayer->SetPlayerName( playername ); } @@ -94,7 +63,7 @@ void ClientPutInServer( edict_t *pEdict, const char *playername ) void ClientActive( edict_t *pEdict, bool bLoadGame ) { - // Can't load games in CS! + // Can't load games! Assert( !bLoadGame ); CJBMod_Player *pPlayer = ToJBModPlayer( CBaseEntity::Instance( pEdict ) ); @@ -139,21 +108,39 @@ CBaseEntity* FindEntity( edict_t *pEdict, char *classname) //----------------------------------------------------------------------------- void ClientGamePrecache( void ) { - CBaseEntity::PrecacheModel("models/player.mdl"); - CBaseEntity::PrecacheModel( "models/gibs/agibs.mdl" ); - CBaseEntity::PrecacheModel ("models/weapons/v_hands.mdl"); - - CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowAmmo" ); - CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowHealth" ); - - CBaseEntity::PrecacheScriptSound( "FX_AntlionImpact.ShellImpact" ); - CBaseEntity::PrecacheScriptSound( "Missile.ShotDown" ); - CBaseEntity::PrecacheScriptSound( "Bullets.DefaultNearmiss" ); - CBaseEntity::PrecacheScriptSound( "Bullets.GunshipNearmiss" ); - CBaseEntity::PrecacheScriptSound( "Bullets.StriderNearmiss" ); - - CBaseEntity::PrecacheScriptSound( "Geiger.BeepHigh" ); - CBaseEntity::PrecacheScriptSound( "Geiger.BeepLow" ); + const char *pFilename = "scripts/client_precache.txt"; + KeyValues *pValues = new KeyValues( "ClientPrecache" ); + + if ( !pValues->LoadFromFile( filesystem, pFilename, "GAME" ) ) + { + Error( "Can't open %s for client precache info.", pFilename ); + pValues->deleteThis(); + return; + } + + for ( KeyValues *pData = pValues->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + const char *pszType = pData->GetName(); + const char *pszFile = pData->GetString(); + + if ( Q_strlen( pszType ) > 0 && Q_strlen( pszFile ) > 0 ) + { + if ( !Q_stricmp( pData->GetName(), "model" ) ) + { + CBaseEntity::PrecacheModel( pszFile ); + } + else if ( !Q_stricmp( pData->GetName(), "scriptsound" ) ) + { + CBaseEntity::PrecacheScriptSound( pszFile ); + } + else if ( !Q_stricmp( pData->GetName(), "particle" ) ) + { + PrecacheParticleSystem( pszFile ); + } + } + } + + pValues->deleteThis(); } @@ -164,14 +151,24 @@ void respawn( CBaseEntity *pEdict, bool fCopyCorpse ) if ( pPlayer ) { - if ( gpGlobals->curtime > pPlayer->GetDeathTime() + DEATH_ANIMATION_TIME ) - { - // respawn player - pPlayer->Spawn(); - } - else + if ( g_pScriptVM ) { - pPlayer->SetNextThink( gpGlobals->curtime + 0.1f ); + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerRespawn" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, pPlayer->GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_BOOLEAN ) + { + if ( (bool)result ) + pPlayer->Spawn(); + else + pPlayer->SetNextThink( gpGlobals->curtime + 0.1f ); + return; + } + } } } } @@ -190,7 +187,6 @@ void GameStartFrame( void ) //========================================================= void InstallGameRules() { - // vanilla deathmatch CreateGameRulesObject( "CJBModRules" ); } diff --git a/src/game/server/jbmod/jbmod_player.cpp b/src/game/server/jbmod/jbmod_player.cpp index 542708456fc..359f38ec932 100644 --- a/src/game/server/jbmod/jbmod_player.cpp +++ b/src/game/server/jbmod/jbmod_player.cpp @@ -15,6 +15,7 @@ #include "in_buttons.h" #include "jbmod_gamerules.h" #include "KeyValues.h" +#include "viewport_panel_names.h" #include "team.h" #include "weapon_jbmodbase.h" #include "grenade_satchel.h" @@ -29,11 +30,7 @@ #include "ilagcompensationmanager.h" #include "vscript_server.h" -int g_iLastCitizenModel = 0; -int g_iLastCombineModel = 0; -CBaseEntity *g_pLastCombineSpawn = NULL; -CBaseEntity *g_pLastRebelSpawn = NULL; extern CBaseEntity *g_pLastSpawn; ConVar jbmod_spawn_frag_fallback_radius( "jbmod_spawn_frag_fallback_radius", "48", FCVAR_NONE, "If no spawns are available, kill players with this radius to allow new players to spawn." ); @@ -87,7 +84,6 @@ IMPLEMENT_SERVERCLASS_ST(CJBMod_Player, DT_JBMod_Player) SendPropEHandle( SENDINFO( m_hRagdoll ) ), SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ), - SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ), SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), SendPropExclude( "DT_BaseFlex", "m_viewtarget" ), @@ -103,38 +99,31 @@ BEGIN_ENT_SCRIPTDESC( CJBMod_Player, CHL2_Player, "JBMod Player" ) DEFINE_SCRIPTFUNC_NAMED( ScriptEquipSuit, "EquipSuit", "Give the player the HEV suit." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGiveItem, "GiveItem", "Give the player a specific item by classname." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGiveAmmo, "GiveAmmo", "Give the player a specific amount of ammo (count, ammoName)." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSwitchToWeapon, "SwitchToWeapon", "Switch to a weapon the player owns by classname." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetClientConVar, "GetClientConVar", "Read a client ConVar value by name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveAllItems, "RemoveAllItems", "Strip all weapons and suit from the player." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFragCount, "GetFragCount", "Get the player's frag count." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddFrags, "AddFrags", "Add to the player's frag count." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddTeamScore, "AddTeamScore", "Add to this player's team score." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetPlayerModel, "SetPlayerModel", "Set player model." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptCommitSuicide, "CommitSuicide", "Kill the player." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptForceRespawn, "ForceRespawn", "Force the player to respawn immediately." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPlayerName, "GetPlayerName", "Get the player's name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsAlive, "IsAlive", "Returns true if the player is alive." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetDeathCount, "GetDeathCount", "Get the player's death count." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddDeaths, "AddDeaths", "Add to the player's death count." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetArmorValue, "GetArmorValue", "Get the player's armor value." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetArmorValue, "SetArmorValue", "Set the player's armor value." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptShowMOTD, "ShowMOTD", "Show the Message of the Day panel to this player." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetDeathTime, "GetDeathTime", "Get the time this player last died." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetMaxSpeed, "SetMaxSpeed", "Set the player's maximum movement speed." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetMaxHealth, "SetMaxHealth", "Set the player's maximum health." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetButtons, "GetButtons", "Get the player's currently pressed buttons (bitmask)." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeForward, "GetEyeForward", "Get the player's eye forward direction vector." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptShowViewModel, "ShowViewModel", "Show or hide the player's viewmodel." ) END_SCRIPTDESC(); -const char *g_ppszRandomCitizenModels[] = -{ - "models/humans/group03/male_01.mdl", - "models/humans/group03/male_02.mdl", - "models/humans/group03/female_01.mdl", - "models/humans/group03/male_03.mdl", - "models/humans/group03/female_02.mdl", - "models/humans/group03/male_04.mdl", - "models/humans/group03/female_03.mdl", - "models/humans/group03/male_05.mdl", - "models/humans/group03/female_04.mdl", - "models/humans/group03/male_06.mdl", - "models/humans/group03/female_06.mdl", - "models/humans/group03/male_07.mdl", - "models/humans/group03/female_07.mdl", - "models/humans/group03/male_08.mdl", - "models/humans/group03/male_09.mdl", -}; - -const char *g_ppszRandomCombineModels[] = -{ - "models/combine_soldier.mdl", - "models/combine_soldier_prisonguard.mdl", - "models/combine_super_soldier.mdl", - "models/police.mdl", -}; - - -#define MAX_COMBINE_MODELS 4 -#define MODEL_CHANGE_INTERVAL 5.0f +ConVar sv_model_change_interval( "sv_model_change_interval", "5.0", FCVAR_GAMEDLL, "Minimum seconds between player model changes." ); #define TEAM_CHANGE_INTERVAL 5.0f #define JBMODPLAYER_PHYSDAMAGE_SCALE 4.0f @@ -167,6 +156,16 @@ CJBMod_Player::~CJBMod_Player( void ) void CJBMod_Player::UpdateOnRemove( void ) { + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerDisconnect" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL, GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + } + } + if ( m_hRagdoll ) { UTIL_RemoveImmediate( m_hRagdoll ); @@ -180,101 +179,14 @@ void CJBMod_Player::Precache( void ) { BaseClass::Precache(); - PrecacheModel ( "sprites/glow01.vmt" ); - - //Precache Citizen models - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - int i; - - for ( i = 0; i < nHeads; ++i ) - PrecacheModel( g_ppszRandomCitizenModels[i] ); - - //Precache Combine Models - nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - for ( i = 0; i < nHeads; ++i ) - PrecacheModel( g_ppszRandomCombineModels[i] ); - - PrecacheFootStepSounds(); - - PrecacheScriptSound( "NPC_MetroPolice.Die" ); - PrecacheScriptSound( "NPC_CombineS.Die" ); - PrecacheScriptSound( "NPC_Citizen.die" ); -} - -void CJBMod_Player::GiveAllItems( void ) -{ - EquipSuit(); - - CBasePlayer::GiveAmmo( 255, "Pistol"); - CBasePlayer::GiveAmmo( 255, "AR2" ); - CBasePlayer::GiveAmmo( 5, "AR2AltFire" ); - CBasePlayer::GiveAmmo( 255, "SMG1"); - CBasePlayer::GiveAmmo( 1, "smg1_grenade"); - CBasePlayer::GiveAmmo( 255, "Buckshot"); - CBasePlayer::GiveAmmo( 32, "357" ); - CBasePlayer::GiveAmmo( 3, "rpg_round"); - CBasePlayer::GiveAmmo( 16, "XBowBolt"); - - CBasePlayer::GiveAmmo( 1, "grenade" ); - CBasePlayer::GiveAmmo( 2, "slam" ); - - GiveNamedItem( "weapon_crowbar" ); - GiveNamedItem( "weapon_stunstick" ); - GiveNamedItem( "weapon_pistol" ); - GiveNamedItem( "weapon_357" ); - - GiveNamedItem( "weapon_smg1" ); - GiveNamedItem( "weapon_ar2" ); - - GiveNamedItem( "weapon_shotgun" ); - GiveNamedItem( "weapon_frag" ); - - GiveNamedItem( "weapon_crossbow" ); - - GiveNamedItem( "weapon_rpg" ); - - GiveNamedItem( "weapon_slam" ); - - GiveNamedItem( "weapon_physcannon" ); - -} - -void CJBMod_Player::GiveDefaultItems( void ) -{ - EquipSuit(); - - CBasePlayer::GiveAmmo( 255, "Pistol"); - CBasePlayer::GiveAmmo( 45, "SMG1"); - CBasePlayer::GiveAmmo( 1, "grenade" ); - CBasePlayer::GiveAmmo( 6, "Buckshot"); - CBasePlayer::GiveAmmo( 6, "357" ); - - if ( GetPlayerModelType() == PLAYER_SOUNDS_METROPOLICE || GetPlayerModelType() == PLAYER_SOUNDS_COMBINESOLDIER ) - { - GiveNamedItem( "weapon_stunstick" ); - } - else if ( GetPlayerModelType() == PLAYER_SOUNDS_CITIZEN ) - { - GiveNamedItem( "weapon_crowbar" ); - } - - GiveNamedItem( "weapon_pistol" ); - GiveNamedItem( "weapon_smg1" ); - GiveNamedItem( "weapon_frag" ); - GiveNamedItem( "weapon_physcannon" ); - - const char *szDefaultWeaponName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_defaultweapon" ); - - CBaseCombatWeapon *pDefaultWeapon = Weapon_OwnsThisType( szDefaultWeaponName ); - - if ( pDefaultWeapon ) - { - Weapon_Switch( pDefaultWeapon ); - } - else + if ( g_pScriptVM ) { - Weapon_Switch( Weapon_OwnsThisType( "weapon_physcannon" ) ); + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPrecache" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL ); + g_pScriptVM->ReleaseFunction( hFunction ); + } } } @@ -282,46 +194,18 @@ void CJBMod_Player::PickDefaultSpawnTeam( void ) { if ( GetTeamNumber() == 0 ) { - if ( JBModRules()->IsTeamplay() == false ) + if ( g_pScriptVM ) { - if ( GetModelPtr() == NULL ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerPickTeam" ); + if ( hFunction ) { - const char *szModelName = NULL; - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); - if ( ValidatePlayerModel( szModelName ) == false ) - { - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel models/combine_soldier.mdl\n" ); - engine->ClientCommand ( edict(), szReturnString ); - } - - ChangeTeam( TEAM_UNASSIGNED ); - } - } - else - { - CTeam *pCombine = g_Teams[TEAM_COMBINE]; - CTeam *pRebels = g_Teams[TEAM_REBELS]; - - if ( pCombine == NULL || pRebels == NULL ) - { - ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); - } - else - { - if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() ) - { - ChangeTeam( TEAM_REBELS ); - } - else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() ) + if ( result.GetType() == FIELD_INTEGER ) { - ChangeTeam( TEAM_COMBINE ); - } - else - { - ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); + ChangeTeam( (int)result ); } } } @@ -347,10 +231,7 @@ void CJBMod_Player::Spawn(void) RemoveEffects( EF_NODRAW ); - if ( CallScriptOnPlayerSpawn() == false ) - { - GiveDefaultItems(); - } + CallScriptOnPlayerSpawn(); } SetNumAnimOverlays( 3 ); @@ -382,32 +263,14 @@ void CJBMod_Player::Spawn(void) m_bReady = false; } -bool CJBMod_Player::ValidatePlayerModel( const char *pModel ) +void CJBMod_Player::PlayerUse( void ) { - int iModels = ARRAYSIZE( g_ppszRandomCitizenModels ); - int i; - - for ( i = 0; i < iModels; ++i ) - { - if ( !Q_stricmp( g_ppszRandomCitizenModels[i], pModel ) ) - { - return true; - } - } - - iModels = ARRAYSIZE( g_ppszRandomCombineModels ); - - for ( i = 0; i < iModels; ++i ) - { - if ( !Q_stricmp( g_ppszRandomCombineModels[i], pModel ) ) - { - return true; - } - } + // Block +use when player is invisible (e.g. Rollerball mode) + if ( IsEffectActive( EF_NODRAW ) ) + return; - return false; + BaseClass::PlayerUse(); } - ConVar jbmod_allow_pickup( "jbmod_allow_pickup", "0", FCVAR_GAMEDLL ); void CJBMod_Player::PickupObject( CBaseEntity* pObject, bool bLimitMassAndSize ) @@ -418,145 +281,39 @@ void CJBMod_Player::PickupObject( CBaseEntity* pObject, bool bLimitMassAndSize ) return BaseClass::PickupObject( pObject, bLimitMassAndSize ); } -void CJBMod_Player::SetPlayerTeamModel( void ) +void CJBMod_Player::SetPlayerModel( void ) { - const char *szModelName = NULL; - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); - - int modelIndex = modelinfo->GetModelIndex( szModelName ); - - if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false ) - { - szModelName = "models/Combine_Soldier.mdl"; - m_iModelType = TEAM_COMBINE; - - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); - engine->ClientCommand ( edict(), szReturnString ); - } - - if ( GetTeamNumber() == TEAM_COMBINE ) + if ( g_pScriptVM ) { - if ( Q_stristr( szModelName, "models/human") ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerSetModel" ); + if ( hFunction ) { - int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; - szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; + const char *pRequestedModel = engine->GetClientConVarValue( entindex(), "cl_playermodel" ); + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance(), pRequestedModel ); + g_pScriptVM->ReleaseFunction( hFunction ); + + const char *pModelStr = (const char *)result; + if ( result.GetType() == FIELD_CSTRING ) + ScriptSetPlayerModel( pModelStr ); } - - m_iModelType = TEAM_COMBINE; } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - if ( !Q_stristr( szModelName, "models/human") ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - - g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; - szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; - } - - m_iModelType = TEAM_REBELS; - } - - SetModel( szModelName ); - SetupPlayerSoundsByModel( szModelName ); - - m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; } -void CJBMod_Player::SetPlayerModel( void ) +void CJBMod_Player::ScriptShowMOTD( void ) { - const char *szModelName = NULL; - const char *pszCurrentModelName = modelinfo->GetModelName( GetModel()); - - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); - - if ( ValidatePlayerModel( szModelName ) == false ) - { - char szReturnString[512]; - - if ( ValidatePlayerModel( pszCurrentModelName ) == false ) - { - pszCurrentModelName = "models/Combine_Soldier.mdl"; - } - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName ); - engine->ClientCommand ( edict(), szReturnString ); - - szModelName = pszCurrentModelName; - } - - if ( GetTeamNumber() == TEAM_COMBINE ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; - szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; - - m_iModelType = TEAM_COMBINE; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - - g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; - szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; - - m_iModelType = TEAM_REBELS; - } - else - { - if ( Q_strlen( szModelName ) == 0 ) - { - szModelName = g_ppszRandomCitizenModels[0]; - } - - if ( Q_stristr( szModelName, "models/human") ) - { - m_iModelType = TEAM_REBELS; - } - else - { - m_iModelType = TEAM_COMBINE; - } - } - - int modelIndex = modelinfo->GetModelIndex( szModelName ); - - if ( modelIndex == -1 ) - { - szModelName = "models/Combine_Soldier.mdl"; - m_iModelType = TEAM_COMBINE; - - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); - engine->ClientCommand ( edict(), szReturnString ); - } + const ConVar *hostname = cvar->FindVar( "hostname" ); + const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY"; - SetModel( szModelName ); - SetupPlayerSoundsByModel( szModelName ); + KeyValues *data = new KeyValues("data"); + data->SetString( "title", title ); + data->SetString( "type", "1" ); + data->SetString( "msg", "motd" ); + data->SetBool( "unload", true ); - m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; -} + ShowViewPortPanel( PANEL_INFO, true, data ); -void CJBMod_Player::SetupPlayerSoundsByModel( const char *pModelName ) -{ - if ( Q_stristr( pModelName, "models/human") ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN; - } - else if ( Q_stristr(pModelName, "police" ) ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_METROPOLICE; - } - else if ( Q_stristr(pModelName, "combine" ) ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_COMBINESOLDIER; - } + data->deleteThis(); } void CJBMod_Player::ResetAnimation( void ) @@ -722,7 +479,8 @@ bool CJBMod_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, co Activity CJBMod_Player::TranslateTeamActivity( Activity ActToTranslate ) { - if ( m_iModelType == TEAM_COMBINE ) + const char *pModelName = STRING( GetModelName() ); + if ( Q_stristr( pModelName, "combine" ) || Q_stristr( pModelName, "police" ) ) return ActToTranslate; if ( ActToTranslate == ACT_RUN ) @@ -961,14 +719,19 @@ bool CJBMod_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) void CJBMod_Player::ChangeTeam( int iTeam ) { -/* if ( GetNextTeamChangeTime() >= gpGlobals->curtime ) + if ( g_pScriptVM ) { - char szReturnString[128]; - Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch teams again.\n", (int)(GetNextTeamChangeTime() - gpGlobals->curtime) ); + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerChangeTeam" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance(), iTeam ); + g_pScriptVM->ReleaseFunction( hFunction ); - ClientPrint( this, HUD_PRINTTALK, szReturnString ); - return; - }*/ + if ( result.GetType() == FIELD_BOOLEAN && !(bool)result ) + return; + } + } bool bKill = false; @@ -990,14 +753,7 @@ void CJBMod_Player::ChangeTeam( int iTeam ) m_flNextTeamChangeTime = gpGlobals->curtime + TEAM_CHANGE_INTERVAL; - if ( JBModRules()->IsTeamplay() == true ) - { - SetPlayerTeamModel(); - } - else - { - SetPlayerModel(); - } + SetPlayerModel(); if ( iTeam == TEAM_SPECTATOR ) { @@ -1094,9 +850,14 @@ void CJBMod_Player::CheatImpulseCommands( int iImpulse ) { case 101: { - if( sv_cheats->GetBool() ) + if( sv_cheats->GetBool() && g_pScriptVM ) { - GiveAllItems(); + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnGiveAllItems" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL, GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + } } } break; @@ -1310,7 +1071,10 @@ void CJBMod_Player::Event_Killed( const CTakeDamageInfo &info ) // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW // because we still want to transmit to the clients in our PVS. - CreateRagdollEntity(); + if ( !IsEffectActive( EF_NODRAW ) ) + { + CreateRagdollEntity(); + } DetonateTripmines(); @@ -1326,16 +1090,16 @@ void CJBMod_Player::Event_Killed( const CTakeDamageInfo &info ) CBaseEntity *pAttacker = info.GetAttacker(); - if ( pAttacker ) + if ( g_pScriptVM ) { - int iScoreToAdd = 1; - - if ( pAttacker == this ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerKilled" ); + if ( hFunction ) { - iScoreToAdd = -1; + g_pScriptVM->Call( hFunction, NULL, true, NULL, + GetScriptInstance(), + ToHScript( pAttacker ) ); + g_pScriptVM->ReleaseFunction( hFunction ); } - - GetGlobalTeam( pAttacker->GetTeamNumber() )->AddScore( iScoreToAdd ); } FlashlightTurnOff(); @@ -1352,6 +1116,34 @@ int CJBMod_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo ) if ( gpGlobals->curtime < m_flSlamProtectTime && (inputInfo.GetDamageType() == DMG_BLAST ) ) return 0; + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerTakeDamage" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, + GetScriptInstance(), + ToHScript( inputInfo.GetAttacker() ), + inputInfo.GetDamage() ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_FLOAT || result.GetType() == FIELD_INTEGER ) + { + float flNewDamage = (float)result; + if ( flNewDamage < 0 ) + return 0; + + CTakeDamageInfo modifiedInfo = inputInfo; + modifiedInfo.SetDamage( flNewDamage ); + + m_vecTotalBulletForce += modifiedInfo.GetDamageForce(); + gamestats->Event_PlayerDamage( this, modifiedInfo ); + return BaseClass::OnTakeDamage( modifiedInfo ); + } + } + } + m_vecTotalBulletForce += inputInfo.GetDamageForce(); gamestats->Event_PlayerDamage( this, inputInfo ); @@ -1364,14 +1156,29 @@ void CJBMod_Player::DeathSound( const CTakeDamageInfo &info ) if ( m_hRagdoll && m_hRagdoll->GetBaseAnimating()->IsDissolving() ) return; - char szStepSound[128]; + const char *pSoundName = NULL; - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.Die", GetPlayerModelSoundPrefix() ); + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerDeathSound" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_CSTRING ) + pSoundName = (const char *)result; + } + } + + if ( !pSoundName || !pSoundName[0] ) + return; const char *pModelName = STRING( GetModelName() ); CSoundParameters params; - if ( GetParametersForSound( szStepSound, params, pModelName ) == false ) + if ( GetParametersForSound( pSoundName, params, pModelName ) == false ) return; Vector vecOrigin = GetAbsOrigin(); @@ -1398,23 +1205,19 @@ CBaseEntity* CJBMod_Player::EntSelectSpawnPoint( void ) edict_t *player = edict(); const char *pSpawnpointName = "info_player_deathmatch"; - if ( JBModRules()->IsTeamplay() == true ) + if ( g_pScriptVM ) { - if ( GetTeamNumber() == TEAM_COMBINE ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "GetSpawnPointClassname" ); + if ( hFunction ) { - pSpawnpointName = "info_player_combine"; - pLastSpawnPoint = g_pLastCombineSpawn; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - pSpawnpointName = "info_player_rebel"; - pLastSpawnPoint = g_pLastRebelSpawn; - } + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); - if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) - { - pSpawnpointName = "info_player_deathmatch"; - pLastSpawnPoint = g_pLastSpawn; + if ( result.GetType() == FIELD_CSTRING && (const char *)result != NULL && ((const char *)result)[0] ) + { + pSpawnpointName = (const char *)result; + } } } @@ -1469,60 +1272,8 @@ CBaseEntity* CJBMod_Player::EntSelectSpawnPoint( void ) goto ReturnSpot; } - // Incredibly terrible garbage way of getting CSS and DOD spawnpoints to work. - // These aren't set up for teamplay so it'll just plop you at a random spawn. - // Stops the game from immediately crashing on CSS maps. - // -nocaps 29.3.26 - - // cstrike spawns - if ( !pSpot ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_counterterrorist" ); - - if ( pSpot ) - goto ReturnSpot; - } - - if ( !pSpot ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_terrorist" ); - - if ( pSpot ) - goto ReturnSpot; - } - - // day of defeat spawns - if ( !pSpot ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_allies" ); - - if ( pSpot ) - goto ReturnSpot; - } - - if ( !pSpot ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_axis" ); - - if ( pSpot ) - goto ReturnSpot; - } - - ReturnSpot: - if ( JBModRules()->IsTeamplay() == true ) - { - if ( GetTeamNumber() == TEAM_COMBINE ) - { - g_pLastCombineSpawn = pSpot; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - g_pLastRebelSpawn = pSpot; - } - } - g_pLastSpawn = pSpot; m_flSlamProtectTime = gpGlobals->curtime + 0.5; @@ -1603,8 +1354,64 @@ bool CJBMod_Player::CallScriptOnPlayerSpawn( void ) return false; } +void CJBMod_Player::ScriptSwitchToWeapon( const char *szWeapon ) +{ + CBaseCombatWeapon *pWeapon = Weapon_OwnsThisType( szWeapon ); + if ( pWeapon ) + { + Weapon_Switch( pWeapon ); + } +} + +const char *CJBMod_Player::ScriptGetClientConVar( const char *szCvar ) +{ + if ( !szCvar || !szCvar[0] ) + return ""; + + return engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), szCvar ); +} + +void CJBMod_Player::ScriptAddTeamScore( int nScore ) +{ + CTeam *pTeam = GetTeam(); + if ( pTeam ) + { + pTeam->AddScore( nScore ); + } +} + +void CJBMod_Player::ScriptSetPlayerModel( const char *szModel ) +{ + if ( !szModel || !szModel[0] || !Q_stristr( szModel, ".mdl" ) ) + return; + + SetModel( szModel ); + m_flNextModelChangeTime = gpGlobals->curtime + sv_model_change_interval.GetFloat(); +} + void CJBMod_Player::CheckChatText( char *p, int bufsize ) { + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerChat" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, GetScriptInstance(), p ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_BOOLEAN && !(bool)result ) + { + p[0] = '\0'; + return; + } + else if ( result.GetType() == FIELD_CSTRING ) + { + Q_strncpy( p, (const char *)result, bufsize ); + } + } + } + //Look for escape sequences and replace char *buf = new char[bufsize]; @@ -1684,6 +1491,13 @@ CJBModPlayerStateInfo *CJBMod_Player::State_LookupInfo( JBModPlayerState state ) return NULL; } +const Vector &CJBMod_Player::ScriptGetEyeForward( void ) +{ + static Vector vecForward; + AngleVectors( pl.v_angle, &vecForward ); + return vecForward; +} + bool CJBMod_Player::StartObserverMode(int mode) { //we only want to go into observer mode if the player asked to, not on a death timeout diff --git a/src/game/server/jbmod/jbmod_player.h b/src/game/server/jbmod/jbmod_player.h index 0c0f7d45695..68267d4260e 100644 --- a/src/game/server/jbmod/jbmod_player.h +++ b/src/game/server/jbmod/jbmod_player.h @@ -71,8 +71,8 @@ class CJBMod_Player : public CHL2_Player virtual bool Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex = 0); virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon ); virtual void ChangeTeam( int iTeam ) OVERRIDE; + virtual void PlayerUse( void ); virtual void PickupObject ( CBaseEntity *pObject, bool bLimitMassAndSize ); - virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ); virtual void Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget = NULL, const Vector *pVelocity = NULL ); virtual void UpdateOnRemove( void ); virtual void DeathSound( const CTakeDamageInfo &info ); @@ -81,8 +81,6 @@ class CJBMod_Player : public CHL2_Player int FlashlightIsOn( void ); void FlashlightTurnOn( void ); void FlashlightTurnOff( void ); - void PrecacheFootStepSounds( void ); - bool ValidatePlayerModel( const char *pModel ); QAngle GetAnimEyeAngles( void ) { return m_angEyeAngles.Get(); } @@ -90,28 +88,43 @@ class CJBMod_Player : public CHL2_Player void CheatImpulseCommands( int iImpulse ); void CreateRagdollEntity( void ); - void GiveAllItems( void ); - void GiveDefaultItems( void ); void NoteWeaponFired( void ); void ResetAnimation( void ); void SetPlayerModel( void ); - void SetPlayerTeamModel( void ); Activity TranslateTeamActivity( Activity ActToTranslate ); float GetNextModelChangeTime( void ) { return m_flNextModelChangeTime; } float GetNextTeamChangeTime( void ) { return m_flNextTeamChangeTime; } void PickDefaultSpawnTeam( void ); - void SetupPlayerSoundsByModel( const char *pModelName ); bool CallScriptOnPlayerSpawn( void ); - const char *GetPlayerModelSoundPrefix( void ); void ScriptEquipSuit( void ) { EquipSuit(); } HSCRIPT ScriptGiveItem( const char *szItem ) { return ToHScript( GiveNamedItem( szItem ) ); } void ScriptGiveAmmo( int nCount, const char *szAmmoName ) { CBasePlayer::GiveAmmo( nCount, szAmmoName ); } - - int GetPlayerModelType( void ) { return m_iPlayerSoundType; } + void ScriptSwitchToWeapon( const char *szWeapon ); + const char *ScriptGetClientConVar( const char *szCvar ); + void ScriptRemoveAllItems( void ) { RemoveAllItems( true ); } + int ScriptGetFragCount( void ) { return FragCount(); } + void ScriptAddFrags( int nCount ) { IncrementFragCount( nCount ); } + void ScriptAddTeamScore( int nScore ); + void ScriptSetPlayerModel( const char *szModel ); + void ScriptCommitSuicide( void ) { CommitSuicide(); } + void ScriptForceRespawn( void ) { Spawn(); } + const char *ScriptGetPlayerName( void ) { return GetPlayerName(); } + bool ScriptIsAlive( void ) { return IsAlive(); } + int ScriptGetDeathCount( void ) { return DeathCount(); } + void ScriptAddDeaths( int nCount ) { IncrementDeathCount( nCount ); } + int ScriptGetArmorValue( void ) { return ArmorValue(); } + void ScriptSetArmorValue( int value ) { SetArmorValue( value ); } + void ScriptShowMOTD( void ); + float ScriptGetDeathTime( void ) { return GetDeathTime(); } + void ScriptSetMaxSpeed( float speed ) { SetMaxSpeed( speed ); } + void ScriptSetMaxHealth( int health ) { SetMaxHealth( health ); if ( m_iHealth > health ) m_iHealth = health; } + int ScriptGetButtons( void ) { return m_nButtons; } + const Vector &ScriptGetEyeForward( void ); + void ScriptShowViewModel( bool bShow ) { ShowViewModel( bShow ); } int GetMaxAmmo( int iAmmoIndex ) const; @@ -155,9 +168,7 @@ class CJBMod_Player : public CHL2_Player CPlayerAnimState m_PlayerAnimState; int m_iLastWeaponFireUsercmd; - int m_iModelType; CNetworkVar( int, m_iSpawnInterpCounter ); - CNetworkVar( int, m_iPlayerSoundType ); float m_flNextModelChangeTime; float m_flNextTeamChangeTime; diff --git a/src/game/server/vscript_server.cpp b/src/game/server/vscript_server.cpp index 547b36b8a5c..16bf5e43c61 100644 --- a/src/game/server/vscript_server.cpp +++ b/src/game/server/vscript_server.cpp @@ -38,6 +38,10 @@ #include "bot/tf_bot.h" #endif +#ifdef JBMOD +#include "jbmod/jbmod_gamerules.h" +#endif + #if defined( _WIN32 ) || defined( POSIX ) #include "vscript_server_nut.h" #endif @@ -101,6 +105,7 @@ class CScriptConvarAccessor : public CAutoGameSystem ScriptVariant_t GetStr( const char *cvar ); const char *GetClientConvarValue( const char *cvar, int entindex ); void SetValue( const char *cvar, ScriptVariant_t value ); + void SetDefault( const char *cvar, const char *value ); void LevelInitPreEntity() OVERRIDE; void LevelShutdownPostEntity() OVERRIDE; @@ -232,6 +237,28 @@ void CScriptConvarAccessor::SetValue( const char *cvar, ScriptVariant_t value ) } } +void CScriptConvarAccessor::SetDefault( const char *cvar, const char *value ) +{ + if ( !cvar || !*cvar || !value ) + return; + + if ( !IsConVarOnAllowList( cvar ) ) + { + DevMsg( "Convar %s was not in " VSCRIPT_CONVAR_ALLOWLIST_NAME "\n", cvar ); + return; + } + + ConVarRef cref( cvar ); + if ( cref.IsValid() && !cref.IsFlagSet( FCVAR_SCRIPT_NONO ) ) + { + if ( Q_strcmp( cref.GetString(), cref.GetDefault() ) == 0 ) + { + cref.SetValue( value ); + GameRules()->SaveConvar( cref ); + } + } +} + void CScriptConvarAccessor::LevelInitPreEntity() { m_AllowedConVars.RemoveAll(); @@ -275,6 +302,7 @@ DEFINE_SCRIPTFUNC( GetFloat, "GetFloat(name) : returns the convar as a float. Ma DEFINE_SCRIPTFUNC( GetStr, "GetStr(name) : returns the convar as a string. May return null if no such convar." ) DEFINE_SCRIPTFUNC( GetClientConvarValue, "GetClientConvarValue(name) : returns the convar value for the entindex as a string." ) DEFINE_SCRIPTFUNC( SetValue, "SetValue(name, value) : sets the value of the convar. The convar must be in " VSCRIPT_CONVAR_ALLOWLIST_NAME " to be set. Supported types are bool, int, float, string." ) +DEFINE_SCRIPTFUNC( SetDefault, "SetDefault(name, value) : sets the value of the convar only if it has not changed from its default." ) DEFINE_SCRIPTFUNC( IsConVarOnAllowList, "IsConVarOnAllowList(name) : checks if the convar is allowed to be used and is in " VSCRIPT_CONVAR_ALLOWLIST_NAME ". Please be nice with this and use it for *compatibility* if you need check support and NOT to force server owners to allow hostname to be set... or else this will simply lie and return true in future. ;-) You have been warned!" ) END_SCRIPTDESC() @@ -2650,7 +2678,6 @@ bool VScriptServerInit() #define DECLARE_SCRIPT_CONST_NAMED( type, name, x ) g_pScriptVM->SetValue( (HSCRIPT)vConstantsTable_##type, name, x ); #define DECLARE_SCRIPT_CONST( type, x ) DECLARE_SCRIPT_CONST_NAMED( type, #x, x ) #define REGISTER_SCRIPT_CONST_TABLE( x ) g_pScriptVM->SetValue( (HSCRIPT) vConstantsTable, #x, vConstantsTable_##x ); -#ifdef TF_DLL DECLARE_SCRIPT_CONST_TABLE( EScriptRecipientFilter ) DECLARE_SCRIPT_CONST( EScriptRecipientFilter, RECIPIENT_FILTER_DEFAULT ) DECLARE_SCRIPT_CONST( EScriptRecipientFilter, RECIPIENT_FILTER_PAS_ATTENUATION ) @@ -2661,6 +2688,7 @@ DECLARE_SCRIPT_CONST( EScriptRecipientFilter, RECIPIENT_FILTER_GLOBAL ) DECLARE_SCRIPT_CONST( EScriptRecipientFilter, RECIPIENT_FILTER_TEAM ) REGISTER_SCRIPT_CONST_TABLE( EScriptRecipientFilter ) +#ifdef TF_DLL DECLARE_SCRIPT_CONST_TABLE( ETFCond ) DECLARE_SCRIPT_CONST( ETFCond, TF_COND_INVALID ) DECLARE_SCRIPT_CONST( ETFCond, TF_COND_AIMING ) @@ -2905,6 +2933,7 @@ DECLARE_SCRIPT_CONST( FNavAttributeType, NAV_MESH_FUNC_COST ) DECLARE_SCRIPT_CONST( FNavAttributeType, NAV_MESH_HAS_ELEVATOR ) DECLARE_SCRIPT_CONST( FNavAttributeType, NAV_MESH_NAV_BLOCKER ) REGISTER_SCRIPT_CONST_TABLE( FNavAttributeType ) +#endif // TF_DLL DECLARE_SCRIPT_CONST_TABLE( FButtons ) DECLARE_SCRIPT_CONST( FButtons, IN_ATTACK ) @@ -2948,15 +2977,18 @@ DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_CROSSHAIR ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_VEHICLE_CROSSHAIR ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_INVEHICLE ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_BONUS_PROGRESS ) +#ifdef TF_DLL DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_BUILDING_STATUS ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_CLOAK_AND_FEIGN ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_PIPES_AND_CHARGE ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_METAL ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_TARGET_ID ) DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_MATCH_STATUS ) +#endif DECLARE_SCRIPT_CONST( FHideHUD, HIDEHUD_BITCOUNT ) REGISTER_SCRIPT_CONST_TABLE( FHideHUD ) +#ifdef TF_DLL DECLARE_SCRIPT_CONST_TABLE( FTaunts ) DECLARE_SCRIPT_CONST( FTaunts, TAUNT_BASE_WEAPON ) DECLARE_SCRIPT_CONST( FTaunts, TAUNT_MISC_ITEM ) @@ -2964,6 +2996,7 @@ DECLARE_SCRIPT_CONST( FTaunts, TAUNT_SHOW_ITEM ) DECLARE_SCRIPT_CONST( FTaunts, TAUNT_LONG ) DECLARE_SCRIPT_CONST( FTaunts, TAUNT_SPECIAL ) REGISTER_SCRIPT_CONST_TABLE( FTaunts ) +#endif DECLARE_SCRIPT_CONST_TABLE( EHitGroup ) DECLARE_SCRIPT_CONST( EHitGroup, HITGROUP_GENERIC ) @@ -3285,6 +3318,7 @@ DECLARE_SCRIPT_CONST( EMoveCollide, MOVECOLLIDE_COUNT ) DECLARE_SCRIPT_CONST( EMoveCollide, MOVECOLLIDE_MAX_BITS ) REGISTER_SCRIPT_CONST_TABLE( EMoveCollide ) +#ifdef TF_DLL // Unnamed enum DECLARE_SCRIPT_CONST_TABLE( ETFTeam ) DECLARE_SCRIPT_CONST( ETFTeam, TEAM_ANY ) @@ -3481,6 +3515,7 @@ DECLARE_SCRIPT_CONST( FTFNavAttributeType, TF_NAV_DOOR_ALWAYS_BLOCKS ) DECLARE_SCRIPT_CONST( FTFNavAttributeType, TF_NAV_UNBLOCKABLE ) DECLARE_SCRIPT_CONST( FTFNavAttributeType, TF_NAV_PERSISTENT_ATTRIBUTES ) REGISTER_SCRIPT_CONST_TABLE( FTFNavAttributeType ) +#endif // TF_DLL DECLARE_SCRIPT_CONST_TABLE( EHudNotify ) DECLARE_SCRIPT_CONST( EHudNotify, HUD_PRINTNOTIFY ) @@ -3489,9 +3524,11 @@ DECLARE_SCRIPT_CONST( EHudNotify, HUD_PRINTTALK ) DECLARE_SCRIPT_CONST( EHudNotify, HUD_PRINTCENTER ) REGISTER_SCRIPT_CONST_TABLE( EHudNotify ) +#ifdef TF_DLL DECLARE_SCRIPT_CONST_TABLE( EBotType ) DECLARE_SCRIPT_CONST( EBotType, TF_BOT_TYPE ) REGISTER_SCRIPT_CONST_TABLE( EBotType ) +#endif DECLARE_SCRIPT_CONST_TABLE( Math ) DECLARE_SCRIPT_CONST_NAMED( Math, "Epsilon", FLT_EPSILON) @@ -3505,13 +3542,30 @@ DECLARE_SCRIPT_CONST_NAMED( Math, "Sqrt3", 1.732050808f) DECLARE_SCRIPT_CONST_NAMED( Math, "GoldenRatio", 1.618033989f) REGISTER_SCRIPT_CONST_TABLE( Math ) +DECLARE_SCRIPT_CONST_TABLE( ETeam ) +DECLARE_SCRIPT_CONST( ETeam, TEAM_UNASSIGNED ) +DECLARE_SCRIPT_CONST( ETeam, TEAM_SPECTATOR ) +#ifdef JBMOD +DECLARE_SCRIPT_CONST( ETeam, TEAM_COMBINE ) +DECLARE_SCRIPT_CONST( ETeam, TEAM_REBELS ) +#endif +REGISTER_SCRIPT_CONST_TABLE( ETeam ) + +DECLARE_SCRIPT_CONST_TABLE( ERelationship ) +DECLARE_SCRIPT_CONST( ERelationship, GR_NOTTEAMMATE ) +DECLARE_SCRIPT_CONST( ERelationship, GR_TEAMMATE ) +DECLARE_SCRIPT_CONST( ERelationship, GR_ENEMY ) +DECLARE_SCRIPT_CONST( ERelationship, GR_ALLY ) +DECLARE_SCRIPT_CONST( ERelationship, GR_NEUTRAL ) +REGISTER_SCRIPT_CONST_TABLE( ERelationship ) + DECLARE_SCRIPT_CONST_TABLE( Server ) DECLARE_SCRIPT_CONST( Server, MAX_EDICTS ) DECLARE_SCRIPT_CONST( Server, MAX_PLAYERS ) DECLARE_SCRIPT_CONST( Server, DIST_EPSILON ) +DECLARE_SCRIPT_CONST( Server, DEATH_ANIMATION_TIME ) DECLARE_SCRIPT_CONST_NAMED( Server, "ConstantNamingConvention", "Constants are named as follows: F -> flags, E -> enums, (nothing) -> random values/constants" ) REGISTER_SCRIPT_CONST_TABLE( Server ) -#endif g_pScriptVM->SetValue( "Constants", vConstantsTable ); g_pScriptVM->SetValue( "CLIENT", false ); diff --git a/src/game/shared/jbmod/jbmod_gamerules.cpp b/src/game/shared/jbmod/jbmod_gamerules.cpp index 4927509761f..4099fb6a2eb 100644 --- a/src/game/shared/jbmod/jbmod_gamerules.cpp +++ b/src/game/shared/jbmod/jbmod_gamerules.cpp @@ -41,12 +41,11 @@ extern bool FindInList( const char **pStrings, const char *pToFind ); ConVar sv_jbmod_weapon_respawn_time( "sv_jbmod_weapon_respawn_time", "20", FCVAR_GAMEDLL | FCVAR_NOTIFY ); ConVar sv_jbmod_item_respawn_time( "sv_jbmod_item_respawn_time", "30", FCVAR_GAMEDLL | FCVAR_NOTIFY ); ConVar sv_report_client_settings("sv_report_client_settings", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar sv_fov_min( "sv_fov_min", "75", FCVAR_GAMEDLL, "Minimum allowed FOV for players." ); +ConVar sv_fov_max( "sv_fov_max", "90", FCVAR_GAMEDLL, "Maximum allowed FOV for players." ); extern ConVar mp_chattime; -extern CBaseEntity *g_pLastCombineSpawn; -extern CBaseEntity *g_pLastRebelSpawn; - #define WEAPON_MAX_DISTANCE_FROM_SPAWN 64 #endif @@ -87,11 +86,14 @@ static JBModViewVectors g_JBModViewVectors( Vector( 16, 16, 60 ) //VEC_CROUCH_TRACE_MAX (m_vCrouchTraceMax) ); +#ifndef CLIENT_DLL + static const char *s_PreserveEnts[] = { "ai_network", "ai_hint", "jbmod_gamerules", + "jbmod_logic_gamemode", "team_manager", "player_manager", "env_soundscape", @@ -110,8 +112,6 @@ static const char *s_PreserveEnts[] = "info_target", "info_node_hint", "info_player_deathmatch", - "info_player_combine", - "info_player_rebel", "info_map_parameters", "keyframe_rope", "move_rope", @@ -130,7 +130,22 @@ static const char *s_PreserveEnts[] = "", // END Marker }; +static CUtlVector s_ExtraPreserveEnts; + +static bool IsPreservedEntity( const char *pClassname ) +{ + if ( FindInList( s_PreserveEnts, pClassname ) ) + return true; + for ( int i = 0; i < s_ExtraPreserveEnts.Count(); i++ ) + { + if ( !Q_stricmp( s_ExtraPreserveEnts[i].Get(), pClassname ) ) + return true; + } + return false; +} + +#endif // CLIENT_DLL #ifdef CLIENT_DLL void RecvProxy_JBModRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) @@ -237,9 +252,6 @@ void CJBModRules::CreateStandardEntities( void ) BaseClass::CreateStandardEntities(); - g_pLastCombineSpawn = NULL; - g_pLastRebelSpawn = NULL; - #ifdef DBGFLAG_ASSERT CBaseEntity *pEnt = #endif @@ -255,19 +267,23 @@ void CJBModRules::CreateStandardEntities( void ) float CJBModRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) { #ifndef CLIENT_DLL - if ( weaponstay.GetInt() > 0 ) + if ( g_pScriptVM ) { - // make sure it's only certain weapons - if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "GetWeaponRespawnTime" ); + if ( hFunction ) { - return 0; // weapon respawns almost instantly + bool bLimitInWorld = ( pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD ) != 0; + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, pWeapon->GetClassname(), bLimitInWorld ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_FLOAT || result.GetType() == FIELD_INTEGER ) + return (float)result; } } - - return sv_jbmod_weapon_respawn_time.GetFloat(); #endif - return 0; // weapon respawns almost instantly + return 0; } @@ -312,41 +328,13 @@ void CJBModRules::Think( void ) return; } -// float flTimeLimit = mp_timelimit.GetFloat() * 60; - float flFragLimit = fraglimit.GetFloat(); - - if ( GetMapRemainingTime() < 0 ) - { - GoToIntermission(); - return; - } - - if ( flFragLimit ) + if ( g_pScriptVM ) { - if( IsTeamplay() == true ) + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnThink" ); + if ( hFunction ) { - CTeam *pCombine = g_Teams[TEAM_COMBINE]; - CTeam *pRebels = g_Teams[TEAM_REBELS]; - - if ( pCombine->GetScore() >= flFragLimit || pRebels->GetScore() >= flFragLimit ) - { - GoToIntermission(); - return; - } - } - else - { - // check if any player is over the frag limit - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) - { - GoToIntermission(); - return; - } - } + g_pScriptVM->Call( hFunction, NULL, true, NULL ); + g_pScriptVM->ReleaseFunction( hFunction ); } } @@ -382,6 +370,16 @@ void CJBModRules::GoToIntermission( void ) if ( g_fGameOver ) return; + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnRoundEnd" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL ); + g_pScriptVM->ReleaseFunction( hFunction ); + } + } + g_fGameOver = true; m_flIntermissionEndTime = gpGlobals->curtime + mp_chattime.GetInt(); @@ -596,7 +594,21 @@ QAngle CJBModRules::VecItemRespawnAngles( CItem *pItem ) //========================================================= float CJBModRules::FlItemRespawnTime( CItem *pItem ) { - return sv_jbmod_item_respawn_time.GetFloat(); + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "GetItemRespawnTime" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, pItem->GetClassname() ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_FLOAT || result.GetType() == FIELD_INTEGER ) + return (float)result; + } + } + + return 0.0f; } @@ -606,15 +618,48 @@ float CJBModRules::FlItemRespawnTime( CItem *pItem ) //========================================================= bool CJBModRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) { - if ( weaponstay.GetInt() > 0 ) + if ( g_pScriptVM ) { - if ( pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) ) - return false; + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "CanPickupWeapon" ); + if ( hFunction ) + { + bool bOwnsWeapon = pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) != NULL; + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, + pPlayer->GetScriptInstance(), + pItem->GetClassname(), + bOwnsWeapon ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_BOOLEAN ) + return (bool)result; + } } return BaseClass::CanHavePlayerItem( pPlayer, pItem ); } +float CJBModRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "GetFallDamage" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, + pPlayer->GetScriptInstance(), + pPlayer->m_Local.m_flFallVelocity ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_FLOAT || result.GetType() == FIELD_INTEGER ) + return (float)result; + } + } + + return BaseClass::FlPlayerFallDamage( pPlayer ); +} + #endif //========================================================= @@ -624,6 +669,20 @@ bool CJBModRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pI int CJBModRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) { #ifndef CLIENT_DLL + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "ShouldWeaponRespawn" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, pWeapon->GetClassname() ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_BOOLEAN ) + return (bool)result ? GR_WEAPON_RESPAWN_YES : GR_WEAPON_RESPAWN_NO; + } + } + if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) { return GR_WEAPON_RESPAWN_NO; @@ -652,6 +711,16 @@ void CJBModRules::ClientDisconnected( edict_t *pClient ) if ( pPlayer->IsInAVehicle() ) pPlayer->LeaveVehicle(); + + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerDisconnect" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL, pPlayer->GetScriptInstance() ); + g_pScriptVM->ReleaseFunction( hFunction ); + } + } } BaseClass::ClientDisconnected( pClient ); @@ -789,26 +858,13 @@ void CJBModRules::ClientSettingsChanged( CBasePlayer *pPlayer ) return; } - if ( JBModRules()->IsTeamplay() == false ) - { - pHL2Player->SetPlayerModel(); - - const char *pszCurrentModelName = modelinfo->GetModelName( pHL2Player->GetModel() ); - - char szReturnString[128]; - Q_snprintf( szReturnString, sizeof( szReturnString ), "Your player model is: %s\n", pszCurrentModelName ); - - ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); - } - else + if ( g_pScriptVM ) { - if ( Q_stristr( szModelName, "models/human") ) - { - pHL2Player->ChangeTeam( TEAM_REBELS ); - } - else + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnPlayerModelChanged" ); + if ( hFunction ) { - pHL2Player->ChangeTeam( TEAM_COMBINE ); + g_pScriptVM->Call( hFunction, NULL, true, NULL, pHL2Player->GetScriptInstance(), szModelName ); + g_pScriptVM->ReleaseFunction( hFunction ); } } } @@ -820,8 +876,10 @@ void CJBModRules::ClientSettingsChanged( CBasePlayer *pPlayer ) const char *pszFov = engine->GetClientConVarValue( pHL2Player->entindex(), "fov_desired" ); if ( pszFov ) { + static ConVarRef sv_fov_min( "sv_fov_min" ); + static ConVarRef sv_fov_max( "sv_fov_max" ); int iFov = atoi( pszFov ); - iFov = clamp( iFov, 75, 90 ); + iFov = clamp( iFov, sv_fov_min.GetInt(), sv_fov_max.GetInt() ); pHL2Player->SetDefaultFOV( iFov ); } @@ -833,14 +891,23 @@ void CJBModRules::ClientSettingsChanged( CBasePlayer *pPlayer ) int CJBModRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) { #ifndef CLIENT_DLL - // half life multiplay has a simple concept of Player Relationships. - // you are either on another player's team, or you are not. - if ( !pPlayer || !pTarget || !pTarget->IsPlayer() || IsTeamplay() == false ) + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) return GR_NOTTEAMMATE; - if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + if ( g_pScriptVM ) { - return GR_TEAMMATE; + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "GetPlayerRelationship" ); + if ( hFunction ) + { + ScriptVariant_t result; + g_pScriptVM->Call( hFunction, NULL, true, &result, + ToHScript( pPlayer ), + ToHScript( pTarget ) ); + g_pScriptVM->ReleaseFunction( hFunction ); + + if ( result.GetType() == FIELD_INTEGER ) + return (int)result; + } } #endif @@ -883,37 +950,6 @@ void CJBModRules::Precache( void ) CBaseEntity::PrecacheScriptSound( "AlyxEmp.Charge" ); } -#ifdef GAME_DLL -bool CJBModRules::IsOfficialMap( void ) -{ - static const char *s_OfficialMaps[] = - { - "devtest", - "dm_lockdown", - "dm_overwatch", - "dm_powerhouse", - "dm_resistance", - "dm_runoff", - "dm_steamlab", - "dm_underpass", - "halls3", - }; - - char szCurrentMap[MAX_MAP_NAME]; - Q_strncpy( szCurrentMap, STRING( gpGlobals->mapname ), sizeof( szCurrentMap ) ); - - for ( int i = 0; i < ARRAYSIZE( s_OfficialMaps ); ++i ) - { - if ( !Q_stricmp( s_OfficialMaps[i], szCurrentMap ) ) - { - return true; - } - } - - return BaseClass::IsOfficialMap(); -} -#endif - bool CJBModRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) { if ( collisionGroup0 > collisionGroup1 ) @@ -1075,6 +1111,16 @@ void CJBModRules::RestartGame() gameeventmanager->FireEvent( event ); } + + if ( g_pScriptVM ) + { + HSCRIPT hFunction = g_pScriptVM->LookupFunction( "OnRoundStart" ); + if ( hFunction ) + { + g_pScriptVM->Call( hFunction, NULL, true, NULL ); + g_pScriptVM->ReleaseFunction( hFunction ); + } + } } #ifdef GAME_DLL @@ -1103,7 +1149,7 @@ void CJBModRules::CleanUpMap() } } // remove entities that has to be restored on roundrestart (breakables etc) - else if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) + else if ( !IsPreservedEntity( pCur->GetClassname() ) ) { UTIL_Remove( pCur ); } @@ -1125,7 +1171,7 @@ void CJBModRules::CleanUpMap() virtual bool ShouldCreateEntity( const char *pClassname ) { // Don't recreate the preserved entities. - if ( !FindInList( s_PreserveEnts, pClassname ) ) + if ( !IsPreservedEntity( pClassname ) ) { return true; } @@ -1331,11 +1377,133 @@ static const char *ScriptGetGameMode() return ""; } +static void ScriptGoToIntermission() +{ + if ( JBModRules() ) + { + JBModRules()->GoToIntermission(); + } +} + +static bool ScriptIsGameOver() +{ + return g_fGameOver; +} + +static float ScriptGetMapRemainingTime() +{ + if ( JBModRules() ) + { + return JBModRules()->GetMapRemainingTime(); + } + return -1; +} + +static bool ScriptIsTeamplay() +{ + if ( JBModRules() ) + { + return JBModRules()->IsTeamplay(); + } + return false; +} + +static void ScriptPrecacheModel( const char *szModel ) +{ + if ( szModel && szModel[0] ) + { + CBaseEntity::PrecacheModel( szModel ); + } +} + +static void ScriptPrecacheSound( const char *szSound ) +{ + if ( szSound && szSound[0] ) + { + CBaseEntity::PrecacheScriptSound( szSound ); + } +} + +static HSCRIPT ScriptGetPlayerByIndex( int index ) +{ + CBasePlayer *pPlayer = UTIL_PlayerByIndex( index ); + if ( pPlayer ) + return pPlayer->GetScriptInstance(); + return NULL; +} + +static int ScriptGetMaxPlayers() +{ + return gpGlobals->maxClients; +} + +static int ScriptGetTeamScore( int team ) +{ + if ( team >= 0 && team < g_Teams.Count() ) + return g_Teams[team]->GetScore(); + return 0; +} + +static int ScriptGetTeamPlayerCount( int team ) +{ + if ( team >= 0 && team < g_Teams.Count() ) + return g_Teams[team]->GetNumPlayers(); + return 0; +} + +static const char *ScriptGetMapName() +{ + return STRING( gpGlobals->mapname ); +} + +static const char *ScriptGetTeamName( int team ) +{ + if ( team >= 0 && team < g_Teams.Count() ) + return g_Teams[team]->GetName(); + return ""; +} + +static void ScriptSetTeamName( int team, const char *pszName ) +{ + if ( team >= 0 && team < g_Teams.Count() && pszName ) + { + Q_strncpy( g_Teams[team]->m_szTeamname.GetForModify(), pszName, MAX_TEAM_NAME_LENGTH ); + } +} + +static void ScriptPreserveEntityClass( const char *pszClassname ) +{ + if ( !pszClassname || !pszClassname[0] ) + return; + + for ( int i = 0; i < s_ExtraPreserveEnts.Count(); i++ ) + { + if ( !Q_stricmp( s_ExtraPreserveEnts[i].Get(), pszClassname ) ) + return; + } + + s_ExtraPreserveEnts.AddToTail( CUtlString( pszClassname ) ); +} + void CJBModRules::RegisterScriptFunctions() { ScriptRegisterFunctionNamed( g_pScriptVM, ScriptSetGameDescription, "SetGameDescription", "Set the game description." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetGameDescription, "GetGameDescription", "Get the current game description." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetGameMode, "GetGameMode", "Get the current game mode name." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGoToIntermission, "GoToIntermission", "End the game and go to the scoreboard." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsGameOver, "IsGameOver", "Returns true if the game is in intermission." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetMapRemainingTime, "GetMapRemainingTime", "Returns seconds remaining on the map timer, or -1 if no limit." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsTeamplay, "IsTeamplay", "Returns true if teamplay is enabled." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheModel, "PrecacheModel", "Precache a model for use." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheSound, "PrecacheSound", "Precache a sound for use." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetPlayerByIndex, "GetPlayerByIndex", "Get a player entity by index (1-based)." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetMaxPlayers, "GetMaxPlayers", "Get the maximum number of players." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetTeamScore, "GetTeamScore", "Get the score for a team by index." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetTeamPlayerCount, "GetTeamPlayerCount", "Get the number of players on a team." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetMapName, "GetMapName", "Get the current map name." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetTeamName, "GetTeamName", "Get the name of a team by index." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptSetTeamName, "SetTeamName", "Set the name of a team by index." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPreserveEntityClass, "PreserveEntityClass", "Add an entity classname to the round-restart preserve list." ); } #endif diff --git a/src/game/shared/jbmod/jbmod_gamerules.h b/src/game/shared/jbmod/jbmod_gamerules.h index f64f189385c..e38ddbd68a1 100644 --- a/src/game/shared/jbmod/jbmod_gamerules.h +++ b/src/game/shared/jbmod/jbmod_gamerules.h @@ -129,6 +129,7 @@ class CJBModRules : public CTeamplayRules virtual float FlItemRespawnTime( CItem *pItem ); virtual bool CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ); virtual bool FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ); + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); void AddLevelDesignerPlacedObject( CBaseEntity *pEntity ); void RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ); @@ -139,8 +140,6 @@ class CJBModRules : public CTeamplayRules virtual void RegisterScriptFunctions(); #endif - bool IsOfficialMap( void ); - virtual void ClientDisconnected( edict_t *pClient ); bool CheckGameOver( void ); diff --git a/src/game/shared/jbmod/jbmod_player_shared.cpp b/src/game/shared/jbmod/jbmod_player_shared.cpp index 11167f61152..6d246783f54 100644 --- a/src/game/shared/jbmod/jbmod_player_shared.cpp +++ b/src/game/shared/jbmod/jbmod_player_shared.cpp @@ -18,37 +18,6 @@ #include "engine/IEngineSound.h" #include "SoundEmitterSystem/isoundemittersystembase.h" -extern ConVar sv_footsteps; - -const char *g_ppszPlayerSoundPrefixNames[PLAYER_SOUNDS_MAX] = -{ - "NPC_Citizen", - "NPC_CombineS", - "NPC_MetroPolice", -}; - -const char *CJBMod_Player::GetPlayerModelSoundPrefix( void ) -{ - return g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType]; -} - -void CJBMod_Player::PrecacheFootStepSounds( void ) -{ - int iFootstepSounds = ARRAYSIZE( g_ppszPlayerSoundPrefixNames ); - int i; - - for ( i = 0; i < iFootstepSounds; ++i ) - { - char szFootStepName[128]; - - Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[i] ); - PrecacheScriptSound( szFootStepName ); - - Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[i] ); - PrecacheScriptSound( szFootStepName ); - } -} - //----------------------------------------------------------------------------- // Consider the weapon's built-in accuracy, this character's proficiency with // the weapon, and the status of the target. Use this information to determine @@ -62,66 +31,6 @@ Vector CJBMod_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity * return VECTOR_CONE_15DEGREES; } -//----------------------------------------------------------------------------- -// Purpose: -// Input : step - -// fvol - -// force - force sound to play -//----------------------------------------------------------------------------- -void CJBMod_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) -{ - if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() ) - return; - -#if defined( CLIENT_DLL ) - // during prediction play footstep sounds only once - if ( !prediction->IsFirstTimePredicted() ) - return; -#endif - - if ( GetFlags() & FL_DUCKING ) - return; - - m_Local.m_nStepside = !m_Local.m_nStepside; - - char szStepSound[128]; - - if ( m_Local.m_nStepside ) - { - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); - } - else - { - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); - } - - CSoundParameters params; - if ( GetParametersForSound( szStepSound, params, NULL ) == false ) - return; - - CRecipientFilter filter; - filter.AddRecipientsByPAS( vecOrigin ); - -#ifndef CLIENT_DLL - // im MP, server removed all players in origins PVS, these players - // generate the footsteps clientside - if ( gpGlobals->maxClients > 1 ) - filter.RemoveRecipientsByPVS( vecOrigin ); -#endif - - EmitSound_t ep; - ep.m_nChannel = CHAN_BODY; - ep.m_pSoundName = params.soundname; - ep.m_flVolume = fvol; - ep.m_SoundLevel = params.soundlevel; - ep.m_nFlags = 0; - ep.m_nPitch = params.pitch; - ep.m_pOrigin = &vecOrigin; - - EmitSound( filter, entindex(), ep ); -} - - //========================== // ANIMATION CODE //========================== diff --git a/src/game/shared/jbmod/jbmod_player_shared.h b/src/game/shared/jbmod/jbmod_player_shared.h index 0d483b15b5f..026c292f4f6 100644 --- a/src/game/shared/jbmod/jbmod_player_shared.h +++ b/src/game/shared/jbmod/jbmod_player_shared.h @@ -12,15 +12,6 @@ #define JBMOD_PUSHAWAY_THINK_INTERVAL (1.0f / 20.0f) #include "studio.h" - -enum -{ - PLAYER_SOUNDS_CITIZEN = 0, - PLAYER_SOUNDS_COMBINESOLDIER, - PLAYER_SOUNDS_METROPOLICE, - PLAYER_SOUNDS_MAX, -}; - enum JBModPlayerState { // Happily running around in the game.