diff --git a/game/jbmod.fgd b/game/jbmod.fgd index ec176c8bc34..cae403d4a0b 100644 --- a/game/jbmod.fgd +++ b/game/jbmod.fgd @@ -10,13 +10,6 @@ "A bridge entity that sets the active JBMod game mode and runs the corresponding mode script." [ gamemode(string) : "Game Mode Name" : "" : "The name of the game mode (e.g. 'sandbox'). This will automatically load 'scripts/vscripts/gamemodes/.nut'." - - spawnflags(flags) = - [ - 1: "Start Disabled" : 0 - ] - - input Enable(void) : "Apply the game mode settings and run the mode script." ] diff --git a/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut b/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut new file mode 100644 index 00000000000..2fedb0bf53b --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/cstrike.nut @@ -0,0 +1,4 @@ +printl( "Loading Counter-Strike..." ); + +RegisterEntityClass( "info_player_counterterrorist", "info_player_start" ); +RegisterEntityClass( "info_player_terrorist", "info_player_start" ); diff --git a/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut b/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut new file mode 100644 index 00000000000..fe5c3af8d74 --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/deathmatch.nut @@ -0,0 +1,5 @@ +printl( "Loading Deathmatch..." ); + +RegisterEntityClass( "info_player_deathmatch", "info_player_start" ); +RegisterEntityClass( "info_player_combine", "info_player_start" ); +RegisterEntityClass( "info_player_rebel", "info_player_start" ); diff --git a/game/jbmod/scripts/vscripts/gamemodes/default.nut b/game/jbmod/scripts/vscripts/gamemodes/default.nut new file mode 100644 index 00000000000..bb1c769b42d --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/default.nut @@ -0,0 +1,23 @@ +// Default gamemode selector +// This script runs if no jbmod_logic_gamemode is found in the map. + +// TODO: Once we have mounting place, we can also match by folder/vpk +// This is just a demo for now + +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_" ) + IncludeScript( "gamemodes/cstrike.nut" ); +else if ( mapname.slice( 0, 4 ) == "dod_" ) + IncludeScript( "gamemodes/dod.nut" ); +else if ( mapname.slice( 0, 6 ) == "arena_" || mapname.slice( 0, 3 ) == "cp_" || mapname.slice( 0, 4 ) == "ctf_" || mapname.slice( 0, 5 ) == "koth_" || mapname.slice( 0, 3 ) == "pl_" ) + IncludeScript( "gamemodes/tf.nut" ); diff --git a/game/jbmod/scripts/vscripts/gamemodes/dod.nut b/game/jbmod/scripts/vscripts/gamemodes/dod.nut new file mode 100644 index 00000000000..23b27676428 --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/dod.nut @@ -0,0 +1,4 @@ +printl( "Loading Day of Defeat..." ); + +RegisterEntityClass( "info_player_allies", "info_player_start" ); +RegisterEntityClass( "info_player_axis", "info_player_start" ); diff --git a/game/jbmod/scripts/vscripts/gamemodes/tf.nut b/game/jbmod/scripts/vscripts/gamemodes/tf.nut new file mode 100644 index 00000000000..714ff61640d --- /dev/null +++ b/game/jbmod/scripts/vscripts/gamemodes/tf.nut @@ -0,0 +1,3 @@ +printl( "Loading Team Fortress..." ); + +RegisterEntityClass( "info_player_teamspawn", "info_player_start" ); diff --git a/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut b/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut new file mode 100644 index 00000000000..6ab360b1664 --- /dev/null +++ b/game/jbmod/scripts/vscripts/weapons/weapon_leafblower.nut @@ -0,0 +1,56 @@ +local FORCE = 500.0 +local FORCE_MULTIPLIER = 4.0 +local RANGE = 512.0 + +function PrimaryAttack() +{ + if (SERVER) // No prediction for now + { + self.EmitSound( "Weapon_Crossbow.BoltFly" ) + Blow(FORCE) + } +} + +function SecondaryAttack() +{ + if (SERVER) // No prediction for now + { + self.EmitSound( "Weapon_PhysCannon.Launch" ) + Blow( FORCE * FORCE_MULTIPLIER ) + } +} + +function Blow(force) +{ + if ( owner == null ) return + + local shootPos = owner.Weapon_ShootPosition() + local forward = owner.GetForwardVector() + + local trace = { + start = shootPos, + end = shootPos + forward * 16384, + hullmin = Vector( -8, -8, -8 ), + hullmax = Vector( 8, 8, 8 ), + mask = 0x4600400b, + ignore = owner + } + + TraceHull( trace ) + + if ( !("enthit" in trace) || trace.enthit == null ) + return + + local ent = trace.enthit + if ( !ent.IsPhysicsObject() ) + return + + local delta = owner.GetCenter() - ent.GetCenter() + local dist = delta.Length() + local ratio = 1.0 - (dist / RANGE) + if (ratio < 0) ratio = 0 + if (ratio > 1) ratio = 1 + + local appliedForce = forward * force * ratio + ent.ApplyForceCenter( appliedForce ) +} diff --git a/game/jbmod/scripts/weapon_leafblower.txt b/game/jbmod/scripts/weapon_leafblower.txt new file mode 100644 index 00000000000..9a2ba5beb7e --- /dev/null +++ b/game/jbmod/scripts/weapon_leafblower.txt @@ -0,0 +1,59 @@ +// Leafblower + +WeaponData +{ + // Weapon data is loaded by both the Game and Client DLLs. + "classname" "weapon_scripted" + "vscript" "weapons/weapon_leafblower" + + "printname" "#jbmod_Leafblower" + "viewmodel" "models/weapons/v_pistol.mdl" + "playermodel" "models/weapons/w_pistol.mdl" + "anim_prefix" "pistol" + "bucket" "0" + "bucket_position" "4" + + "clip_size" "-1" + "clip2_size" "-1" + "default_clip" "-1" + "default_clip2" "-1" + + "primary_ammo" "None" + "secondary_ammo" "None" + + "weight" "0" + "item_flags" "0" + + // Weapon Sprite data is loaded by the Client DLL. + TextureData + { + "weapon" + { + "font" "WeaponIcons" + "character" "d" + } + "weapon_s" + { + "font" "WeaponIconsSelected" + "character" "d" + } + "weapon_small" + { + "font" "WeaponIconsSmall" + "character" "d" + } + "crosshair" + { + "font" "Crosshairs" + "character" "Q" + } + "autoaim" + { + "file" "sprites/crosshairs" + "x" "0" + "y" "48" + "width" "24" + "height" "24" + } + } +} \ No newline at end of file diff --git a/src/game/client/c_baseanimating.cpp b/src/game/client/c_baseanimating.cpp index 2e059aaaff0..76ff534bd7b 100644 --- a/src/game/client/c_baseanimating.cpp +++ b/src/game/client/c_baseanimating.cpp @@ -253,6 +253,12 @@ BEGIN_PREDICTION_DATA( C_BaseAnimating ) END_PREDICTION_DATA() +#ifdef JBMOD +BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, CBaseEntity, "Animating models" ) + +END_SCRIPTDESC(); +#endif // JBMOD + LINK_ENTITY_TO_CLASS( client_ragdoll, C_ClientRagdoll ); BEGIN_DATADESC( C_ClientRagdoll ) diff --git a/src/game/client/c_baseanimating.h b/src/game/client/c_baseanimating.h index 987213cf655..9896485e0a8 100644 --- a/src/game/client/c_baseanimating.h +++ b/src/game/client/c_baseanimating.h @@ -95,6 +95,9 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); DECLARE_INTERPOLATION(); +#ifdef JBMOD + DECLARE_ENT_SCRIPTDESC(); +#endif enum { diff --git a/src/game/client/c_basecombatweapon.cpp b/src/game/client/c_basecombatweapon.cpp index 6a0cedff381..30cb5744b0d 100644 --- a/src/game/client/c_basecombatweapon.cpp +++ b/src/game/client/c_basecombatweapon.cpp @@ -20,6 +20,12 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef JBMOD +BEGIN_ENT_SCRIPTDESC( C_BaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM, "Base Combat Weapon" ) + +END_SCRIPTDESC(); +#endif // JBMOD + //----------------------------------------------------------------------------- // Purpose: Gets the local client's active weapon, if any. //----------------------------------------------------------------------------- diff --git a/src/game/client/c_baseentity.cpp b/src/game/client/c_baseentity.cpp index 6b65e3d908d..8b3dda56c09 100644 --- a/src/game/client/c_baseentity.cpp +++ b/src/game/client/c_baseentity.cpp @@ -40,6 +40,7 @@ #include "cdll_bounded_cvars.h" #include "inetchannelinfo.h" #include "proto_version.h" +#include "gamestringpool.h" #ifdef TF_CLIENT_DLL #include "c_tf_player.h" @@ -568,6 +569,12 @@ BEGIN_PREDICTION_DATA_NO_BASE( C_BaseEntity ) #endif END_PREDICTION_DATA() +#ifdef JBMOD +BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities" ) + +END_SCRIPTDESC(); +#endif // JBMOD + //----------------------------------------------------------------------------- // Helper functions. //----------------------------------------------------------------------------- @@ -6479,6 +6486,25 @@ int C_BaseEntity::GetCreationTick() const return m_nCreationTick; } +#ifdef JBMOD +HSCRIPT C_BaseEntity::GetScriptInstance() +{ + if ( !m_hScriptInstance ) + { + if ( m_iszScriptId == NULL_STRING ) + { + char *szName = (char *)stackalloc( 1024 ); + g_pScriptVM->GenerateUniqueKey( GetClassname(), szName, 1024 ); + m_iszScriptId = AllocPooledString( szName ); + } + + m_hScriptInstance = g_pScriptVM->RegisterInstance( GetScriptDesc(), this ); + g_pScriptVM->SetInstanceUniqeId( m_hScriptInstance, STRING(m_iszScriptId) ); + } + return m_hScriptInstance; +} +#endif // JBMOD + //------------------------------------------------------------------------------ void CC_CL_Find_Ent( const CCommand& args ) { diff --git a/src/game/client/c_baseentity.h b/src/game/client/c_baseentity.h index 8ca3e375e99..22111158dbb 100644 --- a/src/game/client/c_baseentity.h +++ b/src/game/client/c_baseentity.h @@ -35,6 +35,7 @@ #include "particle_property.h" #include "toolframework/itoolentity.h" #include "tier0/threadtools.h" +#include "vscript_client.h" class C_Team; class IPhysicsObject; @@ -184,6 +185,9 @@ class C_BaseEntity : public IClientEntity DECLARE_DATADESC(); DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); +#ifdef JBMOD + DECLARE_ENT_SCRIPTDESC(); +#endif C_BaseEntity(); virtual ~C_BaseEntity(); @@ -1709,6 +1713,14 @@ class C_BaseEntity : public IClientEntity color32 m_PreviousRenderColor; #endif +#ifdef JBMOD +public: + HSCRIPT GetScriptInstance(); +protected: + HSCRIPT m_hScriptInstance; + string_t m_iszScriptId; +#endif // JBMOD + private: bool m_bOldShouldDraw; }; diff --git a/src/game/client/classmap.cpp b/src/game/client/classmap.cpp index a6790a0fe0c..266435a768c 100644 --- a/src/game/client/classmap.cpp +++ b/src/game/client/classmap.cpp @@ -44,6 +44,7 @@ class CClassMap : public IClassMap virtual const char *Lookup( const char *classname ); virtual C_BaseEntity *CreateEntity( const char *mapname ); virtual int GetClassSize( const char *classname ); + virtual DISPATCHFUNCTION FindFactory( const char *classname ); private: CUtlDict< classentry_t, unsigned short > m_ClassDict; @@ -135,3 +136,23 @@ int CClassMap::GetClassSize( const char *classname ) return -1; } + +DISPATCHFUNCTION CClassMap::FindFactory( const char *classname ) +{ + int c = m_ClassDict.Count(); + int i; + + for ( i = 0; i < c; i++ ) + { + classentry_t *lookup = &m_ClassDict[ i ]; + if ( !lookup ) + continue; + + if ( Q_strcmp( lookup->GetMapName(), classname ) ) + continue; + + return lookup->factory; + } + + return NULL; +} diff --git a/src/game/client/client_jbmod.vpc b/src/game/client/client_jbmod.vpc index 7bf9c66456e..1d4fa4da637 100644 --- a/src/game/client/client_jbmod.vpc +++ b/src/game/client/client_jbmod.vpc @@ -185,6 +185,8 @@ $Project "Client (JBMod)" $File "$SRCDIR\game\shared\jbmod\weapon_slam.h" $File "$SRCDIR\game\shared\jbmod\weapon_smg1.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_stunstick.cpp" + $File "$SRCDIR\game\shared\jbmod\weapon_scripted.cpp" + $File "$SRCDIR\game\shared\jbmod\weapon_scripted.h" } $Folder "UI" diff --git a/src/game/client/iclassmap.h b/src/game/client/iclassmap.h index 78ac84c9838..08c7d5fe11d 100644 --- a/src/game/client/iclassmap.h +++ b/src/game/client/iclassmap.h @@ -23,6 +23,7 @@ abstract_class IClassMap virtual char const *Lookup( const char *classname ) = 0; virtual C_BaseEntity *CreateEntity( const char *mapname ) = 0; virtual int GetClassSize( const char *classname ) = 0; + virtual DISPATCHFUNCTION FindFactory( const char *classname ) = 0; }; extern IClassMap& GetClassMap(); diff --git a/src/game/client/vscript_client.cpp b/src/game/client/vscript_client.cpp index 444e2d9c80d..00dbc676dee 100644 --- a/src/game/client/vscript_client.cpp +++ b/src/game/client/vscript_client.cpp @@ -25,6 +25,7 @@ #include "usermessages.h" #include "hud_macros.h" +#include "iclassmap.h" #if defined( PORTAL2_PUZZLEMAKER ) #include "matchmaking/imatchframework.h" @@ -47,6 +48,22 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); #endif // VMPROFILE +void ScriptRegisterEntityClass( const char *pszNewClassname, const char *pszBaseClassname ) +{ + DISPATCHFUNCTION factory = GetClassMap().FindFactory( pszBaseClassname ); + if ( !factory ) + { + Warning( "ScriptRegisterEntityClass: Could not find base factory for '%s' on client\n", pszBaseClassname ); + return; + } + + if ( !GetClassMap().FindFactory( pszNewClassname ) ) + { + int size = GetClassMap().GetClassSize( pszBaseClassname ); + GetClassMap().Add( pszNewClassname, pszNewClassname, size, factory ); + } +} + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -166,6 +183,7 @@ bool VScriptClientInit() if( g_pScriptVM ) { Log_Msg( LOG_VScript, "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptRegisterEntityClass, "RegisterEntityClass", "Registers a new entity classname mapped to an existing base class" ); ScriptRegisterFunction( g_pScriptVM, GetMapName, "Get the name of the map."); ScriptRegisterFunction( g_pScriptVM, Time, "Get the current server time" ); ScriptRegisterFunction( g_pScriptVM, DoIncludeScript, "Execute a script (internal)" ); @@ -174,6 +192,8 @@ bool VScriptClientInit() ScriptRegisterFunction( g_pScriptVM, RequestMapRating, "Pops up the map rating dialog for user input" ); #endif // PORTAL2_PUZZLEMAKER + g_pScriptVM->RegisterAllClasses(); + if ( GameRules() ) { GameRules()->RegisterScriptFunctions(); @@ -183,6 +203,9 @@ bool VScriptClientInit() g_pScriptVM->RegisterInstance( &g_ScriptPanorama, "Panorama" ); #endif + g_pScriptVM->SetValue( "CLIENT", true ); + g_pScriptVM->SetValue( "SERVER", false ); + if ( scriptLanguage == SL_SQUIRREL ) { g_pScriptVM->Run( g_Script_vscript_client ); diff --git a/src/game/server/basecombatweapon.cpp b/src/game/server/basecombatweapon.cpp index 5a0192f9122..17ce84c24e2 100644 --- a/src/game/server/basecombatweapon.cpp +++ b/src/game/server/basecombatweapon.cpp @@ -87,6 +87,7 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM, "Base Co DEFINE_SCRIPTFUNC( SetClip2, "Set current ammo in clip2" ) DEFINE_SCRIPTFUNC( GetPrimaryAmmoCount, "Current primary ammo count if no clip is used or to give a player if they pick up this weapon legacy style (not TF)" ) DEFINE_SCRIPTFUNC( GetSecondaryAmmoCount, "Current secondary ammo count if no clip is used or to give a player if they pick up this weapon legacy style (not TF)" ) + DEFINE_SCRIPTFUNC( SendWeaponAnim, "Sends an animation to the weapon's view model" ) END_SCRIPTDESC(); ConVar weapon_showproficiency( "weapon_showproficiency", "0" ); diff --git a/src/game/server/baseentity.cpp b/src/game/server/baseentity.cpp index 357122bf03b..626d0811cea 100644 --- a/src/game/server/baseentity.cpp +++ b/src/game/server/baseentity.cpp @@ -1640,6 +1640,19 @@ void CBaseEntity::SetPhysAngularVelocity( const Vector &angularImpulse ) } } +void CBaseEntity::ApplyForceCenter( const Vector &force ) +{ + if ( VPhysicsGetObject() ) + { + VPhysicsGetObject()->ApplyForceCenter( force ); + } +} + +bool CBaseEntity::IsPhysicsObject() +{ + return VPhysicsGetObject() != NULL; +} + //----------------------------------------------------------------------------- // Purpose: Scale damage done and call OnTakeDamage //----------------------------------------------------------------------------- @@ -2365,6 +2378,9 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC( GetPhysAngularVelocity, "" ) DEFINE_SCRIPTFUNC( SetPhysVelocity, "" ) DEFINE_SCRIPTFUNC( SetPhysAngularVelocity, "" ) + DEFINE_SCRIPTFUNC( ApplyForceCenter, "" ) + DEFINE_SCRIPTFUNC( IsPhysicsObject, "Whether the entity has a physics object" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveType, "GetMoveType", "") DEFINE_SCRIPTFUNC_NAMED( ScriptSetMoveType, "SetMoveType", "" ) diff --git a/src/game/server/baseentity.h b/src/game/server/baseentity.h index afed3bfa4b6..38e66359696 100644 --- a/src/game/server/baseentity.h +++ b/src/game/server/baseentity.h @@ -1106,6 +1106,9 @@ class CBaseEntity : public IServerEntity static void PhysicsRemoveGround( CBaseEntity *other, groundlink_t *link ); static void PhysicsRemoveGroundList( CBaseEntity *ent ); + void ApplyForceCenter( const Vector &force ); + bool IsPhysicsObject(); + void StartGroundContact( CBaseEntity *ground ); void EndGroundContact( CBaseEntity *ground ); diff --git a/src/game/server/jbmod/jbmod_logic_gamemode.cpp b/src/game/server/jbmod/jbmod_logic_gamemode.cpp index 6d352fd4ed0..48cf269e462 100644 --- a/src/game/server/jbmod/jbmod_logic_gamemode.cpp +++ b/src/game/server/jbmod/jbmod_logic_gamemode.cpp @@ -22,8 +22,6 @@ BEGIN_DATADESC( CJBModLogicGamemode ) DEFINE_KEYFIELD( m_iszGameMode, FIELD_STRING, "gamemode" ), - - DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), END_DATADESC() LINK_ENTITY_TO_CLASS( jbmod_logic_gamemode, CJBModLogicGamemode ); @@ -37,14 +35,6 @@ void CJBModLogicGamemode::Activate() { BaseClass::Activate(); - if ( !HasSpawnFlags( SF_GAMEMODE_START_DISABLED ) ) - { - ApplySettings(); - } -} - -void CJBModLogicGamemode::InputEnable( inputdata_t &inputdata ) -{ ApplySettings(); } @@ -57,6 +47,9 @@ void CJBModLogicGamemode::ApplySettings() if ( pszGameMode && *pszGameMode ) { + if ( !Q_stricmp( JBModRules()->m_szGameMode, pszGameMode ) ) + return; + Q_strncpy( JBModRules()->m_szGameMode, pszGameMode, 64 ); char szScriptPath[256]; diff --git a/src/game/server/jbmod/jbmod_player.cpp b/src/game/server/jbmod/jbmod_player.cpp index 49d550b64bd..542708456fc 100644 --- a/src/game/server/jbmod/jbmod_player.cpp +++ b/src/game/server/jbmod/jbmod_player.cpp @@ -44,17 +44,6 @@ void DropPrimedFragGrenade( CJBMod_Player *pPlayer, CBaseCombatWeapon *pGrenade LINK_ENTITY_TO_CLASS( player, CJBMod_Player ); -LINK_ENTITY_TO_CLASS( info_player_combine, CPointEntity ); -LINK_ENTITY_TO_CLASS( info_player_rebel, CPointEntity ); - -// CSS spawn entities -LINK_ENTITY_TO_CLASS( info_player_terrorist, CPointEntity ); -LINK_ENTITY_TO_CLASS( info_player_counterterrorist, CPointEntity ); - -// DOD spawn entities -LINK_ENTITY_TO_CLASS( info_player_allies, CPointEntity ); -LINK_ENTITY_TO_CLASS( info_player_axis, CPointEntity ); - // specific to the local player BEGIN_SEND_TABLE_NOBASE( CJBMod_Player, DT_JBModLocalPlayerExclusive ) // send a hi-res origin to the local player for use in prediction diff --git a/src/game/server/jbmod/vscript_init.cpp b/src/game/server/jbmod/vscript_init.cpp new file mode 100644 index 00000000000..cd615b9fe30 --- /dev/null +++ b/src/game/server/jbmod/vscript_init.cpp @@ -0,0 +1,81 @@ +// 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. + +#include "cbase.h" +#include "vscript_shared.h" +#include "igamesystem.h" +#include "mapentities_shared.h" +#include "jbmod/jbmod_gamerules.h" +#include "vscript_server.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CServerScriptGameSystem : public CAutoGameSystem +{ +public: + CServerScriptGameSystem() : CAutoGameSystem( "CServerScriptGameSystem" ) {} + + virtual void LevelInitPreEntity() + { + // VScript may not be loaded yet (both are using CAutoGameSystem::LevelInitPreEntity) + if ( !g_pScriptVM ) + VScriptServerInit(); + + const char *pMapData = engine->GetMapEntitiesString(); + if ( !pMapData ) + return; + + char szWorkBuffer[2048]; + bool bFoundGamemode = false; + char szGamemode[256]; + szGamemode[0] = '\0'; + + while ( pMapData ) + { + pMapData = MapEntity_SkipToNextEntity( pMapData, szWorkBuffer ); + + char szClassname[MAX_PATH]; + if ( MapEntity_ExtractValue( szWorkBuffer, "classname", szClassname ) ) + { + if ( !Q_stricmp( szClassname, "jbmod_logic_gamemode" ) ) + { + if ( MapEntity_ExtractValue( szWorkBuffer, "gamemode", szGamemode ) ) + { + bFoundGamemode = true; + break; + } + } + } + } + + if ( bFoundGamemode && szGamemode[0] ) + { + if ( JBModRules() ) + { + Q_strncpy( JBModRules()->m_szGameMode, szGamemode, sizeof( JBModRules()->m_szGameMode ) ); + } + + char szScriptFile[256]; + Q_snprintf( szScriptFile, sizeof( szScriptFile ), "gamemodes/%s", szGamemode ); + VScriptRunScript( szScriptFile, true ); + } + else + { + VScriptRunScript( "gamemodes/default", true ); + } + } +}; + +static CServerScriptGameSystem g_ServerScriptGameSystem; diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 881d2a472ae..081f483b34c 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -7811,6 +7811,7 @@ BEGIN_ENT_SCRIPTDESC( CBasePlayer, CBaseCombatCharacter, "The player entity." ) DEFINE_SCRIPTFUNC( GetForceLocalDraw, "Gets the state of whether the player is being forced by SetForceLocalDraw to be drawn" ) DEFINE_SCRIPTFUNC( GetScriptOverlayMaterial, "Gets the current view overlay material" ) DEFINE_SCRIPTFUNC( SetScriptOverlayMaterial, "Sets a view overlay material" ) + DEFINE_SCRIPTFUNC( Weapon_ShootPosition, "Gets the point from which weapons are fired" ) END_SCRIPTDESC(); void CStripWeapons::InputStripWeapons(inputdata_t &data) diff --git a/src/game/server/server_jbmod.vpc b/src/game/server/server_jbmod.vpc index 96c1dea4d46..a50b2e8dbae 100644 --- a/src/game/server/server_jbmod.vpc +++ b/src/game/server/server_jbmod.vpc @@ -366,6 +366,7 @@ $Project "Server (JBMod)" $File "$SRCDIR\game\shared\jbmod\jbmod_player_shared.h" $File "$SRCDIR\game\shared\jbmod\jbmod_weapon_parse.cpp" $File "$SRCDIR\game\shared\jbmod\jbmod_weapon_parse.h" + $File "jbmod\vscript_init.cpp" $Folder "Bots" { @@ -464,6 +465,8 @@ $Project "Server (JBMod)" $File "$SRCDIR\game\shared\jbmod\weapon_slam.h" $File "$SRCDIR\game\shared\jbmod\weapon_smg1.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_stunstick.cpp" + $File "$SRCDIR\game\shared\jbmod\weapon_scripted.cpp" + $File "$SRCDIR\game\shared\jbmod\weapon_scripted.h" } } } diff --git a/src/game/server/subs.cpp b/src/game/server/subs.cpp index b2bf003f444..3d10d124f07 100644 --- a/src/game/server/subs.cpp +++ b/src/game/server/subs.cpp @@ -60,7 +60,9 @@ END_DATADESC() // These are the new entry points to entities. +#ifndef JBMOD LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +#endif LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); diff --git a/src/game/server/team_spawnpoint.cpp b/src/game/server/team_spawnpoint.cpp index ab3699e469c..ab9e8e08d1e 100644 --- a/src/game/server/team_spawnpoint.cpp +++ b/src/game/server/team_spawnpoint.cpp @@ -16,7 +16,9 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifndef JBMOD LINK_ENTITY_TO_CLASS( info_player_teamspawn, CTeamSpawnPoint ); +#endif BEGIN_DATADESC( CTeamSpawnPoint ) diff --git a/src/game/server/vscript_server.cpp b/src/game/server/vscript_server.cpp index c1b2eae8c13..547b36b8a5c 100644 --- a/src/game/server/vscript_server.cpp +++ b/src/game/server/vscript_server.cpp @@ -76,6 +76,21 @@ ConVar script_break_in_native_debugger_on_error( "script_break_in_native_debugge #define VSCRIPT_CONVAR_ALLOWLIST_NAME "cfg/vscript_convar_allowlist.txt" +void ScriptRegisterEntityClass( const char *pszNewClassname, const char *pszBaseClassname ) +{ + IEntityFactory *pFactory = EntityFactoryDictionary()->FindFactory( pszBaseClassname ); + if ( !pFactory ) + { + Warning( "ScriptRegisterEntityClass: Could not find base factory for '%s'\n", pszBaseClassname ); + return; + } + + if ( !EntityFactoryDictionary()->FindFactory( pszNewClassname ) ) + { + EntityFactoryDictionary()->InstallFactory( pFactory, pszNewClassname ); + } +} + /// Exposes convars to script class CScriptConvarAccessor : public CAutoGameSystem { @@ -2499,6 +2514,7 @@ bool VScriptServerInit() Log_Msg( LOG_VScript, "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); g_pScriptVM->SetErrorCallback( &VScriptServerScriptErrorFunc ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptRegisterEntityClass, "RegisterEntityClass", "Registers a new entity classname mapped to an existing base class" ); ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_ShowMessageAll, "ShowMessage", "Print a hud message on all clients" ); ScriptRegisterFunction( g_pScriptVM, SendToConsole, "Send a string to the console as a command" ); ScriptRegisterFunction( g_pScriptVM, SendToServerConsole, "Send a string that gets executed on the server as a ServerCommand. Respects sv_allow_point_servercommand." ); @@ -3497,6 +3513,9 @@ DECLARE_SCRIPT_CONST_NAMED( Server, "ConstantNamingConvention", "Constants are n REGISTER_SCRIPT_CONST_TABLE( Server ) #endif g_pScriptVM->SetValue( "Constants", vConstantsTable ); + + g_pScriptVM->SetValue( "CLIENT", false ); + g_pScriptVM->SetValue( "SERVER", true ); if ( scriptLanguage == SL_SQUIRREL ) { @@ -3969,7 +3988,8 @@ class CVScriptGameSystem : public CAutoGameSystemPerFrame // diagnostics that detects such loops and warns the developer. m_bAllowEntityCreationInScripts = true; - VScriptServerInit(); + if ( !g_pScriptVM ) + VScriptServerInit(); } virtual void LevelInitPostEntity( void ) diff --git a/src/game/server/vscript_server.h b/src/game/server/vscript_server.h index cc74ea79adb..73ab4c4e552 100644 --- a/src/game/server/vscript_server.h +++ b/src/game/server/vscript_server.h @@ -22,6 +22,9 @@ class ISaveRestoreBlockHandler; bool VScriptServerReplaceClosures( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing = false ); ISaveRestoreBlockHandler *GetVScriptSaveRestoreBlockHandler(); +bool VScriptServerInit(); +void VScriptServerTerm(); + class CBaseEntityScriptInstanceHelper : public IScriptInstanceHelper { diff --git a/src/game/shared/basecombatweapon_shared.h b/src/game/shared/basecombatweapon_shared.h index 4ca7cd03d32..01dab43a76d 100644 --- a/src/game/shared/basecombatweapon_shared.h +++ b/src/game/shared/basecombatweapon_shared.h @@ -157,7 +157,7 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM DECLARE_CLASS( CBaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM ); DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); -#ifdef GAME_DLL +#if defined(GAME_DLL) || defined(JBMOD) DECLARE_ENT_SCRIPTDESC(); #endif @@ -647,8 +647,9 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM IPhysicsConstraint *GetConstraint() { return m_pConstraint; } -private: +protected: WEAPON_FILE_INFO_HANDLE m_hWeaponFileInfo; +private: IPhysicsConstraint *m_pConstraint; int m_iAltFireHudHintCount; // How many times has this weapon displayed its alt-fire HUD hint? diff --git a/src/game/shared/jbmod/jbmod_weapon_parse.cpp b/src/game/shared/jbmod/jbmod_weapon_parse.cpp index ca2f5738ff9..4b7574dd44f 100644 --- a/src/game/shared/jbmod/jbmod_weapon_parse.cpp +++ b/src/game/shared/jbmod/jbmod_weapon_parse.cpp @@ -14,19 +14,29 @@ FileWeaponInfo_t* CreateWeaponInfo() return new CJBModSWeaponInfo; } - - CJBModSWeaponInfo::CJBModSWeaponInfo() { m_iPlayerDamage = 0; } - void CJBModSWeaponInfo::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) { BaseClass::Parse( pKeyValuesData, szWeaponName ); m_iPlayerDamage = pKeyValuesData->GetInt( "damage", 0 ); + +#ifndef CLIENT_DLL + const char *pszClassname = pKeyValuesData->GetString( "classname", "" ); + if ( pszClassname && szWeaponName && Q_stricmp( pszClassname, "weapon_scripted" ) == 0 && Q_stricmp( szWeaponName, "weapon_scripted" ) != 0 ) + { + IEntityFactory *pFactory = EntityFactoryDictionary()->FindFactory( "weapon_scripted" ); + if ( pFactory && !EntityFactoryDictionary()->FindFactory( szWeaponName ) ) + { + EntityFactoryDictionary()->InstallFactory( pFactory, szWeaponName ); + DevMsg( "CWeaponScripted: Registered alias '%s'\n", szWeaponName ); + } + } +#endif } diff --git a/src/game/shared/jbmod/weapon_scripted.cpp b/src/game/shared/jbmod/weapon_scripted.cpp new file mode 100644 index 00000000000..7e4c677324b --- /dev/null +++ b/src/game/shared/jbmod/weapon_scripted.cpp @@ -0,0 +1,253 @@ +// Copyright 2025 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. + +#include "cbase.h" +#include "weapon_scripted.h" +#include "filesystem.h" +#include "weapon_parse.h" +#include "vscript/ivscript.h" +#include "vscript_shared.h" +#include "ammodef.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponScripted, DT_WeaponScripted ) + +BEGIN_NETWORK_TABLE( CWeaponScripted, DT_WeaponScripted ) +#ifdef CLIENT_DLL + RecvPropString( RECVINFO( m_szScriptedWeaponName ) ), +#else + SendPropString( SENDINFO( m_szScriptedWeaponName ) ), +#endif +END_NETWORK_TABLE() + +#if defined( CLIENT_DLL ) +BEGIN_PREDICTION_DATA( CWeaponScripted ) + DEFINE_PRED_FIELD( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flNextSecondaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ) +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_scripted, CWeaponScripted ); + +#if !defined( CLIENT_DLL ) +BEGIN_DATADESC( CWeaponScripted ) +END_DATADESC() +#endif + +BEGIN_ENT_SCRIPTDESC( CWeaponScripted, CBaseCombatWeapon, "JBMod Scripted Weapon" ) +END_SCRIPTDESC(); + +CWeaponScripted::CWeaponScripted() +{ + m_szVScriptName[0] = '\0'; + m_szScriptedWeaponName.GetForModify()[0] = '\0'; + m_bScriptLoaded = false; + m_ScriptScope = NULL; +} + +void CWeaponScripted::Precache( void ) +{ +#ifndef CLIENT_DLL + BaseClass::Precache(); + Q_strncpy( m_szScriptedWeaponName.GetForModify(), GetClassname(), MAX_WEAPON_STRING ); + + FileWeaponInfo_t *pInfo = GetFileWeaponInfoFromHandle( m_hWeaponFileInfo ); + if ( pInfo && pInfo->szAmmo1[0] == 0 && pInfo->iMaxClip1 == -1 ) + { + pInfo->m_bMeleeWeapon = true; + } +#else + const char *pszWeaponName = m_szScriptedWeaponName; + if ( !pszWeaponName || !pszWeaponName[0] ) + return; + + m_iPrimaryAmmoType = m_iSecondaryAmmoType = -1; + if ( ReadWeaponDataFromFileForSlot( filesystem, pszWeaponName, &m_hWeaponFileInfo, GetEncryptionKey() ) ) + { + if ( GetWpnData().szAmmo1[0] ) + { + m_iPrimaryAmmoType = GetAmmoDef()->Index( GetWpnData().szAmmo1 ); + } + if ( GetWpnData().szAmmo2[0] ) + { + m_iSecondaryAmmoType = GetAmmoDef()->Index( GetWpnData().szAmmo2 ); + } + + FileWeaponInfo_t *pInfo = GetFileWeaponInfoFromHandle( GetWeaponFileInfoHandle() ); + if ( pInfo && m_iPrimaryAmmoType == -1 && pInfo->iMaxClip1 == -1 ) + { + pInfo->m_bMeleeWeapon = true; + } + + gWR.LoadWeaponSprites( GetWeaponFileInfoHandle() ); + + m_iViewModelIndex = 0; + m_iWorldModelIndex = 0; + if ( GetViewModel() && GetViewModel()[0] ) + { + m_iViewModelIndex = CBaseEntity::PrecacheModel( GetViewModel() ); + } + if ( GetWorldModel() && GetWorldModel()[0] ) + { + m_iWorldModelIndex = CBaseEntity::PrecacheModel( GetWorldModel() ); + } + + for ( int i = 0; i < NUM_SHOOT_SOUND_TYPES; ++i ) + { + const char *shootsound = GetShootSound( i ); + if ( shootsound && shootsound[0] ) + { + CBaseEntity::PrecacheScriptSound( shootsound ); + } + } + } + else + { + Warning( "CWeaponScripted: Error reading weapon data file for: %s\n", pszWeaponName ); + } +#endif + + const FileWeaponInfo_t &info = GetWpnData(); + if ( info.bParsedScript ) + { + char szFullName[512]; + Q_snprintf( szFullName, sizeof(szFullName), "scripts/%s", info.szClassName ); + + KeyValues *pKV = ReadEncryptedKVFile( filesystem, szFullName, NULL ); + if ( pKV ) + { + const char *pszVScript = pKV->GetString( "vscript", NULL ); + if ( pszVScript && *pszVScript ) + { + Q_strncpy( m_szVScriptName, pszVScript, sizeof(m_szVScriptName) ); + DevMsg( "CWeaponScripted: Found vscript '%s' for weapon '%s'\n", m_szVScriptName, info.szClassName ); + } + pKV->deleteThis(); + } + } +} + +void CWeaponScripted::Spawn( void ) +{ +#ifndef CLIENT_DLL + Q_strncpy( m_szScriptedWeaponName.GetForModify(), GetClassname(), MAX_WEAPON_STRING ); +#endif + BaseClass::Spawn(); + + LoadVScript(); +} + +#if defined( CLIENT_DLL ) +void CWeaponScripted::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ( type == DATA_UPDATE_CREATED ) + { + Precache(); + LoadVScript(); + } +} +#endif + +void CWeaponScripted::LoadVScript( void ) +{ + if ( m_bScriptLoaded || !g_pScriptVM ) + return; + + if ( !m_szVScriptName[0] ) + { + return; + } + + if ( !ValidateScriptScope() ) + { + Warning( "CWeaponScripted: Failed to create script scope for '%s'\n", GetClassname() ); + return; + } + + HSCRIPT hScript = VScriptCompileScript( m_szVScriptName, true ); + if ( !hScript ) + { + Warning( "CWeaponScripted: Failed to compile script '%s'\n", m_szVScriptName ); + return; + } + + if ( g_pScriptVM->Run( hScript, m_ScriptScope ) == SCRIPT_ERROR ) + { + Warning( "CWeaponScripted: Failed to run script '%s'\n", m_szVScriptName ); + return; + } + + m_bScriptLoaded = true; + DevMsg( "CWeaponScripted [%s]: Loaded vscript '%s' for weapon '%s'\n", + (IsClient() ? "Client" : "Server"), m_szVScriptName, GetClassname() ); +} + +bool CWeaponScripted::CallScriptFunction( const char *pszFunc ) +{ + if ( !m_bScriptLoaded || !g_pScriptVM || !m_ScriptScope ) + return false; + + HSCRIPT hFunc = g_pScriptVM->LookupFunction( pszFunc, m_ScriptScope ); + if ( !hFunc ) + return false; + + g_pScriptVM->SetValue( m_ScriptScope, "self", this->GetScriptInstance() ); + + CBaseEntity *pOwner = GetOwner(); + if ( pOwner ) + { + g_pScriptVM->SetValue( m_ScriptScope, "owner", pOwner->GetScriptInstance() ); + } + + g_pScriptVM->Call( hFunc, m_ScriptScope ); + g_pScriptVM->ReleaseFunction( hFunc ); + return true; +} + +bool CWeaponScripted::ValidateScriptScope( void ) +{ + if ( !g_pScriptVM ) + return false; + + if ( m_ScriptScope ) + return true; + + m_ScriptScope = g_pScriptVM->CreateScope( GetClassname(), NULL ); + if ( !m_ScriptScope ) + return false; + + return true; +} + +void CWeaponScripted::PrimaryAttack( void ) +{ + CallScriptFunction( "PrimaryAttack" ); +} + +void CWeaponScripted::SecondaryAttack( void ) +{ + CallScriptFunction( "SecondaryAttack" ); +} + +void CWeaponScripted::ItemPostFrame( void ) +{ + BaseClass::ItemPostFrame(); + + CallScriptFunction( "Think" ); +} diff --git a/src/game/shared/jbmod/weapon_scripted.h b/src/game/shared/jbmod/weapon_scripted.h new file mode 100644 index 00000000000..921d5d648b0 --- /dev/null +++ b/src/game/shared/jbmod/weapon_scripted.h @@ -0,0 +1,62 @@ +// Copyright 2025 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. + +#ifndef WEAPON_SCRIPTED_H +#define WEAPON_SCRIPTED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "weapon_jbmodbasehlmpcombatweapon.h" +#include "vscript/ivscript.h" +#include "vscript_shared.h" + +#if defined( CLIENT_DLL ) +#define CWeaponScripted C_WeaponScripted +#endif + +class CWeaponScripted : public CBaseJBModCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponScripted, CBaseJBModCombatWeapon ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); +#if !defined( CLIENT_DLL ) + DECLARE_DATADESC(); +#endif + DECLARE_ENT_SCRIPTDESC(); + + CWeaponScripted(); + + virtual void Precache( void ); + virtual void Spawn( void ); +#if defined( CLIENT_DLL ) + virtual void OnDataChanged( DataUpdateType_t type ); +#endif + virtual void PrimaryAttack( void ); + virtual void SecondaryAttack( void ); + virtual void ItemPostFrame( void ); + + void LoadVScript( void ); + bool CallScriptFunction( const char *pszFunc ); + bool ValidateScriptScope( void ); + +private: + char m_szVScriptName[MAX_WEAPON_STRING]; + CNetworkString( m_szScriptedWeaponName, MAX_WEAPON_STRING ); + bool m_bScriptLoaded; + HSCRIPT m_ScriptScope; +}; + +#endif // WEAPON_SCRIPTED_H diff --git a/src/game/shared/vscript_shared.cpp b/src/game/shared/vscript_shared.cpp index d1e13884638..1fccf11d4e4 100644 --- a/src/game/shared/vscript_shared.cpp +++ b/src/game/shared/vscript_shared.cpp @@ -106,7 +106,8 @@ HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) if( !bResult ) { - Log_Warning( LOG_VScript, "Script not found (%s) \n", scriptPath.operator const char *() ); + if ( bWarnMissing ) + Log_Warning( LOG_VScript, "Script not found (%s) \n", scriptPath.operator const char *() ); Assert( "Error running script" ); } diff --git a/src/game/shared/weapon_parse.cpp b/src/game/shared/weapon_parse.cpp index 368ba80248c..872fb656153 100644 --- a/src/game/shared/weapon_parse.cpp +++ b/src/game/shared/weapon_parse.cpp @@ -191,6 +191,29 @@ void PrecacheFileWeaponInfoDatabase( IFileSystem *pFilesystem, const unsigned ch } } manifest->deleteThis(); + +#ifdef JBMOD + FileFindHandle_t findHandle; + const char *pFilename = pFilesystem->FindFirstEx( "scripts/weapon_*.txt", "GAME", &findHandle ); + while ( pFilename ) + { + char fileBase[512]; + Q_FileBase( pFilename, fileBase, sizeof(fileBase) ); + + WEAPON_FILE_INFO_HANDLE tmp; +#ifdef CLIENT_DLL + if ( ReadWeaponDataFromFileForSlot( pFilesystem, fileBase, &tmp, pICEKey ) ) + { + gWR.LoadWeaponSprites( tmp ); + } +#else + ReadWeaponDataFromFileForSlot( pFilesystem, fileBase, &tmp, pICEKey ); +#endif + + pFilename = pFilesystem->FindNext( findHandle ); + } + pFilesystem->FindClose( findHandle ); +#endif // JBMOD } KeyValues* ReadEncryptedKVFile( IFileSystem *pFilesystem, const char *szFilenameWithoutExtension, const unsigned char *pICEKey, bool bForceReadEncryptedFile /*= false*/ )