diff --git a/managed/src/SwiftlyS2.Core/Modules/Game/GameService.cs b/managed/src/SwiftlyS2.Core/Modules/Game/GameService.cs new file mode 100644 index 000000000..b5eeca800 --- /dev/null +++ b/managed/src/SwiftlyS2.Core/Modules/Game/GameService.cs @@ -0,0 +1,255 @@ +using SwiftlyS2.Shared.Misc; +using SwiftlyS2.Core.Schemas; +using SwiftlyS2.Shared.Players; +using SwiftlyS2.Shared.Natives; +using SwiftlyS2.Shared.Services; +using SwiftlyS2.Shared.EntitySystem; +using SwiftlyS2.Shared.SchemaDefinitions; + +namespace SwiftlyS2.Core.Services; + +internal class GameService : IGameService +{ + private readonly IEntitySystemService entitySystemService; + // m_bMapHasBombZone (hash: 0x6295CF65D3F4FD4D) + 0x02 + private static readonly Lazy MatchStructOffsetLazy = new(() => Schema.GetOffset(0x6295CF65D3F4FD4D) + 0x02, LazyThreadSafetyMode.None); + private static nint MatchStructOffset => MatchStructOffsetLazy.Value; + + public GameService( IEntitySystemService entitySystemService ) + { + this.entitySystemService = entitySystemService; + } + + public unsafe ref readonly CCSMatch MatchData => ref *GetCCSMatchPtr(); + + public unsafe void Reset() + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->ActualRoundsPlayed = 0; + match->NOvertimePlaying = 0; + match->CTScoreFirstHalf = 0; + match->CTScoreSecondHalf = 0; + match->CTScoreOvertime = 0; + match->CTScoreTotal = 0; + match->TerroristScoreFirstHalf = 0; + match->TerroristScoreSecondHalf = 0; + match->TerroristScoreOvertime = 0; + match->TerroristScoreTotal = 0; + match->Phase = (int)GamePhase.GAMEPHASE_PLAYING_STANDARD; + + gameRules.TotalRoundsPlayed = 0; + gameRules.OvertimePlaying = 0; + gameRules.GamePhaseEnum = GamePhase.GAMEPHASE_PLAYING_STANDARD; + gameRules.TotalRoundsPlayedUpdated(); + gameRules.OvertimePlayingUpdated(); + gameRules.GamePhaseUpdated(); + + UpdateTeamScores(); + } + + public unsafe void SetPhase( GamePhase phase ) + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->Phase = (int)phase; + + gameRules.GamePhaseEnum = phase; + gameRules.GamePhaseUpdated(); + } + + public unsafe void AddTerroristWins( int numWins ) + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->ActualRoundsPlayed += (short)numWins; + + gameRules.TotalRoundsPlayed = match->ActualRoundsPlayed; + gameRules.TotalRoundsPlayedUpdated(); + + AddTerroristScore(numWins); + } + + public unsafe void AddCTWins( int numWins ) + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->ActualRoundsPlayed += (short)numWins; + + gameRules.TotalRoundsPlayed = match->ActualRoundsPlayed; + gameRules.TotalRoundsPlayedUpdated(); + + AddCTScore(numWins); + } + + public unsafe void IncrementRound( int numRounds = 1 ) + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->ActualRoundsPlayed += (short)numRounds; + + gameRules.TotalRoundsPlayed = match->ActualRoundsPlayed; + gameRules.TotalRoundsPlayedUpdated(); + } + + public void AddTerroristBonusPoints( int points ) + { + AddTerroristScore(points); + } + + public void AddCTBonusPoints( int points ) + { + AddCTScore(points); + } + + public unsafe void AddTerroristScore( int score ) + { + var match = GetCCSMatchPtr(); + + match->TerroristScoreTotal += (short)score; + + if (match->NOvertimePlaying > 0) + { + match->TerroristScoreOvertime += (short)score; + } + else if (match->Phase == (int)GamePhase.GAMEPHASE_PLAYING_FIRST_HALF) + { + match->TerroristScoreFirstHalf += (short)score; + } + else if (match->Phase == (int)GamePhase.GAMEPHASE_PLAYING_SECOND_HALF) + { + match->TerroristScoreSecondHalf += (short)score; + } + + UpdateTeamScores(); + } + + public unsafe void AddCTScore( int score ) + { + var match = GetCCSMatchPtr(); + + match->CTScoreTotal += (short)score; + + if (match->NOvertimePlaying > 0) + { + match->CTScoreOvertime += (short)score; + } + else if (match->Phase == (int)GamePhase.GAMEPHASE_PLAYING_FIRST_HALF) + { + match->CTScoreFirstHalf += (short)score; + } + else if (match->Phase == (int)GamePhase.GAMEPHASE_PLAYING_SECOND_HALF) + { + match->CTScoreSecondHalf += (short)score; + } + + UpdateTeamScores(); + } + + public unsafe void GoToOvertime( int numOvertimesToAdd = 1 ) + { + var match = GetCCSMatchPtr(); + var gameRules = GetGameRules(); + + match->NOvertimePlaying += (short)numOvertimesToAdd; + + gameRules.OvertimePlaying = match->NOvertimePlaying; + gameRules.OvertimePlayingUpdated(); + } + + public unsafe void SwapTeamScores() + { + var match = GetCCSMatchPtr(); + + (match->TerroristScoreFirstHalf, match->CTScoreFirstHalf) = (match->CTScoreFirstHalf, match->TerroristScoreFirstHalf); + (match->TerroristScoreSecondHalf, match->CTScoreSecondHalf) = (match->CTScoreSecondHalf, match->TerroristScoreSecondHalf); + (match->TerroristScoreOvertime, match->CTScoreOvertime) = (match->CTScoreOvertime, match->TerroristScoreOvertime); + (match->TerroristScoreTotal, match->CTScoreTotal) = (match->CTScoreTotal, match->TerroristScoreTotal); + + UpdateTeamScores(); + } + + public unsafe int GetWinningTeam() + { + var match = GetCCSMatchPtr(); + var teams = entitySystemService.GetAllEntitiesByDesignerName("cs_team_manager"); + + foreach (var team in teams) + { + if (team.TeamNum == (int)Team.T && team.Surrendered) + { + return (int)Team.CT; + } + + if (team.TeamNum == (int)Team.CT && team.Surrendered) + { + return (int)Team.T; + } + } + + if (match->TerroristScoreTotal > match->CTScoreTotal) + { + return (int)Team.T; + } + + if (match->TerroristScoreTotal < match->CTScoreTotal) + { + return (int)Team.CT; + } + + return (int)Team.None; + } + + private unsafe void UpdateTeamScores() + { + var match = GetCCSMatchPtr(); + var teams = entitySystemService.GetAllEntitiesByDesignerName("cs_team_manager"); + + foreach (var team in teams) + { + switch (team.TeamNum) + { + case (int)Team.T: + UpdateTeamEntity(team, match->TerroristScoreTotal, match->TerroristScoreFirstHalf, match->TerroristScoreSecondHalf, match->TerroristScoreOvertime); + break; + + case (int)Team.CT: + UpdateTeamEntity(team, match->CTScoreTotal, match->CTScoreFirstHalf, match->CTScoreSecondHalf, match->CTScoreOvertime); + break; + } + } + } + + private CCSGameRules GetGameRules() + { + var gameRules = entitySystemService.GetGameRules(); + if (gameRules?.IsValid ?? false) + { + return gameRules; + } + throw new InvalidOperationException("GameRules not found"); + } + + private unsafe CCSMatch* GetCCSMatchPtr() + { + var gameRules = GetGameRules(); + return (CCSMatch*)(gameRules.Address + MatchStructOffset); + } + + private static void UpdateTeamEntity( CCSTeam team, int totalScore, int firstHalfScore, int secondHalfScore, int overtimeScore ) + { + team.Score = totalScore; + team.ScoreFirstHalf = firstHalfScore; + team.ScoreSecondHalf = secondHalfScore; + team.ScoreOvertime = overtimeScore; + team.ScoreUpdated(); + team.ScoreFirstHalfUpdated(); + team.ScoreSecondHalfUpdated(); + team.ScoreOvertimeUpdated(); + } +} \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Core/Modules/Plugins/SwiftlyCore.cs b/managed/src/SwiftlyS2.Core/Modules/Plugins/SwiftlyCore.cs index 177c6223c..886ae14e6 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Plugins/SwiftlyCore.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Plugins/SwiftlyCore.cs @@ -75,6 +75,7 @@ internal class SwiftlyCore : ISwiftlyCore, IDisposable public MenuManagerAPI MenuManagerAPI { get; init; } public CommandLineService CommandLineService { get; init; } public HelpersService Helpers { get; init; } + public GameService GameService { get; init; } public string ContextBasePath { get; init; } public string PluginDataDirectory { get; init; } public GameFileSystem GameFileSystem { get; init; } @@ -120,6 +121,7 @@ public SwiftlyCore( string contextId, string contextBaseDirectory, PluginMetadat .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(provider => provider.GetRequiredService()) .AddSingleton(provider => provider.GetRequiredService()) @@ -145,6 +147,7 @@ public SwiftlyCore( string contextId, string contextBaseDirectory, PluginMetadat .AddSingleton(provider => provider.GetRequiredService()) .AddSingleton(provider => provider.GetRequiredService()) .AddSingleton(provider => provider.GetRequiredService()) + .AddSingleton(provider => provider.GetRequiredService()) .AddSingleton(provider => provider.GetRequiredService()) .AddLogging(builder => builder.AddProvider(new SwiftlyLoggerProvider(id.Name))) @@ -176,6 +179,7 @@ public SwiftlyCore( string contextId, string contextBaseDirectory, PluginMetadat MenuManagerAPI = serviceProvider.GetRequiredService(); CommandLineService = serviceProvider.GetRequiredService(); Helpers = serviceProvider.GetRequiredService(); + GameService = serviceProvider.GetRequiredService(); Logger = LoggerFactory.CreateLogger(contextType); GameFileSystem = serviceProvider.GetRequiredService(); } @@ -220,10 +224,12 @@ public void Dispose() // [Obsolete("MenuManager will be deprecared at the release of SwiftlyS2. Please use MenuManagerAPI instead")] // IMenuManager ISwiftlyCore.Menus => MenuManager; IMenuManagerAPI ISwiftlyCore.MenusAPI => MenuManagerAPI; - string ISwiftlyCore.PluginPath => ContextBasePath; - string ISwiftlyCore.CSGODirectory => NativeEngineHelpers.GetCSGODirectoryPath(); - string ISwiftlyCore.GameDirectory => NativeEngineHelpers.GetGameDirectoryPath(); ICommandLine ISwiftlyCore.CommandLine => CommandLineService; IHelpers ISwiftlyCore.Helpers => Helpers; + IGameService ISwiftlyCore.Game => GameService; IGameFileSystem ISwiftlyCore.GameFileSystem => GameFileSystem; + string ISwiftlyCore.PluginPath => ContextBasePath; + string ISwiftlyCore.PluginDataDirectory => PluginDataDirectory; + string ISwiftlyCore.CSGODirectory => NativeEngineHelpers.GetCSGODirectoryPath(); + string ISwiftlyCore.GameDirectory => NativeEngineHelpers.GetGameDirectoryPath(); } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRules.cs b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRules.cs index a155c9db7..5b697ca87 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRules.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRules.cs @@ -1,12 +1,13 @@ -using SwiftlyS2.Shared.Misc; -using SwiftlyS2.Shared.Natives; -using SwiftlyS2.Shared.SchemaDefinitions; +using SwiftlyS2.Shared.Misc; using SwiftlyS2.Shared.Schemas; +using SwiftlyS2.Shared.Natives; namespace SwiftlyS2.Shared.SchemaDefinitions; public partial interface CCSGameRules { + public GamePhase GamePhaseEnum { get; set; } + /// /// Find the player that the controller is targetting /// @@ -21,5 +22,12 @@ public partial interface CCSGameRules /// The delay before ending the round public void TerminateRound( RoundEndReason reason, float delay ); - public GamePhase GamePhaseEnum { get; set; } + /// + /// Ends the current round with the specified reason after an optional delay + /// + /// The reason for ending the round + /// The delay before ending the round + /// The team id to end the round for + /// Unknown parameter + public void TerminateRound( RoundEndReason reason, float delay, uint teamId, uint unk01 = 0 ); } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRulesImpl.cs b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRulesImpl.cs index 06e813558..e99960f58 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRulesImpl.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Schemas/Extensions/CCSGameRulesImpl.cs @@ -1,26 +1,30 @@ -using SwiftlyS2.Core.Natives; using SwiftlyS2.Shared.Misc; -using SwiftlyS2.Shared.Natives; -using SwiftlyS2.Shared.SchemaDefinitions; +using SwiftlyS2.Core.Natives; using SwiftlyS2.Shared.Schemas; +using SwiftlyS2.Shared.SchemaDefinitions; +using EndReason = SwiftlyS2.Shared.Natives.RoundEndReason; namespace SwiftlyS2.Core.SchemaDefinitions; internal partial class CCSGameRulesImpl : CCSGameRules { + public GamePhase GamePhaseEnum { + get => (GamePhase)GamePhase; + set => GamePhase = (int)value; + } + public T? FindPickerEntity( CBasePlayerController controller ) where T : ISchemaClass { - CBaseEntity ent = new CBaseEntityImpl(GameFunctions.FindPickerEntity(Address, controller.Address)); - return ent.As(); + return ((CBaseEntity)new CBaseEntityImpl(GameFunctions.FindPickerEntity(Address, controller.Address))).As(); } - public void TerminateRound( RoundEndReason reason, float delay ) + public void TerminateRound( EndReason reason, float delay ) { - GameFunctions.TerminateRound(Address, (uint)reason, delay); + GameFunctions.TerminateRound(Address, (uint)reason, delay, 0, 0); } - public GamePhase GamePhaseEnum { - get => (GamePhase)GamePhase; - set => GamePhase = (int)value; + public void TerminateRound( EndReason reason, float delay, uint teamId, uint unk01 ) + { + GameFunctions.TerminateRound(Address, (uint)reason, delay, teamId, unk01); } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs index e4e185100..6ab1d7ec2 100644 --- a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs +++ b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs @@ -1,8 +1,7 @@ -using System.Buffers; using System.Text; +using System.Buffers; using Spectre.Console; using SwiftlyS2.Shared.Natives; - namespace SwiftlyS2.Core.Natives; internal static class GameFunctions @@ -16,8 +15,8 @@ internal static class GameFunctions public static unsafe delegate* unmanaged< nint, nint, float, void > pSetOrAddAttribute; public static unsafe delegate* unmanaged< int, nint, nint > pGetWeaponCSDataFromKey; public static unsafe delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void > pDispatchParticleEffect; - public static unsafe delegate* unmanaged< nint, uint, float, nint, byte, void > pTerminateRoundLinux; - public static unsafe delegate* unmanaged< nint, float, uint, nint, byte, void > pTerminateRoundWindows; + public static unsafe delegate* unmanaged< nint, uint, nint, uint, float, void > pTerminateRoundLinux; + public static unsafe delegate* unmanaged< nint, float, uint, nint, uint, void > pTerminateRoundWindows; public static unsafe delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void > pTeleport; public static int TeleportOffset => NativeOffsets.Fetch("CBaseEntity::Teleport"); public static int CommitSuicideOffset => NativeOffsets.Fetch("CBasePlayerPawn::CommitSuicide"); @@ -46,35 +45,37 @@ public static void Initialize() pDispatchParticleEffect = (delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void >)NativeSignatures.Fetch("DispatchParticleEffect"); if (IsWindows) { - pTerminateRoundWindows = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + pTerminateRoundWindows = (delegate* unmanaged< nint, float, uint, nint, uint, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); } else { - pTerminateRoundLinux = (delegate* unmanaged< nint, uint, float, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + pTerminateRoundLinux = (delegate* unmanaged< nint, uint, nint, uint, float, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); } pTeleport = (delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void >)((void**)NativeMemoryHelpers.GetVirtualTableAddress("server", "CBaseEntity"))[TeleportOffset]; } } - public unsafe static void* GetVirtualFunction( nint handle, int offset ) + public static unsafe void* GetVirtualFunction( nint handle, int offset ) { var ppVTable = (void***)handle; return *(*ppVTable + offset); } - public static void TerminateRound( nint gameRules, uint reason, float delay ) + public static void DispatchParticleEffect( string particleName, uint attachmentType, nint entity, byte attachmentPoint, CUtlSymbolLarge attachmentName, bool resetAllParticlesOnEntity, int splitScreenSlot, CRecipientFilter filter ) { try { unsafe { - if (IsWindows) - { - pTerminateRoundWindows(gameRules, delay, reason, 0, 0); - } - else + var pool = ArrayPool.Shared; + var nameLength = Encoding.UTF8.GetByteCount(particleName); + var nameBuffer = pool.Rent(nameLength + 1); + _ = Encoding.UTF8.GetBytes(particleName, nameBuffer); + nameBuffer[nameLength] = 0; + fixed (byte* pParticleName = nameBuffer) { - pTerminateRoundLinux(gameRules, reason, delay, 0, 0); + pDispatchParticleEffect((nint)pParticleName, attachmentType, entity, attachmentPoint, attachmentName, (byte)(resetAllParticlesOnEntity ? 1 : 0), splitScreenSlot, (nint)(&filter), IntPtr.Zero); + pool.Return(nameBuffer); } } } @@ -84,21 +85,19 @@ public static void TerminateRound( nint gameRules, uint reason, float delay ) } } - public static void DispatchParticleEffect( string particleName, uint attachmentType, nint entity, byte attachmentPoint, CUtlSymbolLarge attachmentName, bool resetAllParticlesOnEntity, int splitScreenSlot, CRecipientFilter filter ) + public static void TerminateRound( nint gameRules, uint reason, float delay, uint teamId, uint unk01 ) { try { unsafe { - var pool = ArrayPool.Shared; - var nameLength = Encoding.UTF8.GetByteCount(particleName); - var nameBuffer = pool.Rent(nameLength + 1); - _ = Encoding.UTF8.GetBytes(particleName, nameBuffer); - nameBuffer[nameLength] = 0; - fixed (byte* pParticleName = nameBuffer) + if (IsWindows) { - pDispatchParticleEffect((nint)pParticleName, attachmentType, entity, attachmentPoint, attachmentName, (byte)(resetAllParticlesOnEntity ? 1 : 0), splitScreenSlot, (nint)(&filter), IntPtr.Zero); - pool.Return(nameBuffer); + pTerminateRoundWindows(gameRules, delay, reason, teamId > 0 ? (nint)(&teamId) : 0, unk01); + } + else + { + pTerminateRoundLinux(gameRules, reason, teamId > 0 ? (nint)(&teamId) : 0, unk01, delay); } } } @@ -117,7 +116,7 @@ public static nint GetWeaponCSDataFromKey( int unknown, string key ) var pool = ArrayPool.Shared; var keyLength = Encoding.UTF8.GetByteCount(key); var keyBuffer = pool.Rent(keyLength + 1); - Encoding.UTF8.GetBytes(key, keyBuffer); + _ = Encoding.UTF8.GetBytes(key, keyBuffer); keyBuffer[keyLength] = 0; fixed (byte* pKey = keyBuffer) { @@ -166,7 +165,7 @@ public static nint GetSkeletonInstance( nint handle ) return 0; } - public unsafe static void PawnCommitSuicide( nint pPawn, bool bExplode, bool bForce ) + public static unsafe void PawnCommitSuicide( nint pPawn, bool bExplode, bool bForce ) { try { @@ -182,7 +181,7 @@ public unsafe static void PawnCommitSuicide( nint pPawn, bool bExplode, bool bFo } } - public unsafe static void SetPlayerControllerPawn( nint pController, nint pPawn, bool b1, bool b2, bool b3, bool b4 ) + public static unsafe void SetPlayerControllerPawn( nint pController, nint pPawn, bool b1, bool b2, bool b3, bool b4 ) { try { @@ -197,7 +196,7 @@ public unsafe static void SetPlayerControllerPawn( nint pController, nint pPawn, } } - public unsafe static void SetModel( nint pEntity, string model ) + public static unsafe void SetModel( nint pEntity, string model ) { try { @@ -206,7 +205,7 @@ public unsafe static void SetModel( nint pEntity, string model ) var pool = ArrayPool.Shared; var modelLength = Encoding.UTF8.GetByteCount(model); var modelBuffer = pool.Rent(modelLength + 1); - Encoding.UTF8.GetBytes(model, modelBuffer); + _ = Encoding.UTF8.GetBytes(model, modelBuffer); modelBuffer[modelLength] = 0; fixed (byte* pModel = modelBuffer) { @@ -221,12 +220,12 @@ public unsafe static void SetModel( nint pEntity, string model ) } } - public unsafe static void Teleport( - nint pEntity, - Vector* vecPosition, - QAngle* vecAngle, - Vector* vecVelocity - ) + public static unsafe void Teleport( + nint pEntity, + Vector* vecPosition, + QAngle* vecAngle, + Vector* vecVelocity + ) { try { @@ -241,7 +240,7 @@ public unsafe static void Teleport( } } - public unsafe static void TracePlayerBBox( + public static unsafe void TracePlayerBBox( Vector vecStart, Vector vecEnd, BBox_t bounds, @@ -262,7 +261,7 @@ public unsafe static void TracePlayerBBox( } } - public unsafe static void TraceShape( + public static unsafe void TraceShape( nint pEngineTrace, Ray_t* ray, Vector vecStart, @@ -284,7 +283,7 @@ public unsafe static void TraceShape( } } - public unsafe static void CTakeDamageInfoConstructor( + public static unsafe void CTakeDamageInfoConstructor( CTakeDamageInfo* pThis, nint pInflictor, nint pAttacker, @@ -310,7 +309,7 @@ public unsafe static void CTakeDamageInfoConstructor( } } - public unsafe static void CCSPlayer_ItemServices_RemoveWeapons( nint pThis ) + public static unsafe void CCSPlayer_ItemServices_RemoveWeapons( nint pThis ) { try { @@ -326,18 +325,18 @@ public unsafe static void CCSPlayer_ItemServices_RemoveWeapons( nint pThis ) } } - public unsafe static nint CCSPlayer_ItemServices_GiveNamedItem( nint pThis, string name ) + public static unsafe nint CCSPlayer_ItemServices_GiveNamedItem( nint pThis, string name ) { try { unsafe { - void*** ppVTable = (void***)pThis; + var ppVTable = (void***)pThis; var pGiveNamedItem = (delegate* unmanaged< nint, nint, nint >)ppVTable[0][GiveNamedItemOffset]; var pool = ArrayPool.Shared; var nameLength = Encoding.UTF8.GetByteCount(name); var nameBuffer = pool.Rent(nameLength + 1); - Encoding.UTF8.GetBytes(name, nameBuffer); + _ = Encoding.UTF8.GetBytes(name, nameBuffer); nameBuffer[nameLength] = 0; fixed (byte* pName = nameBuffer) { @@ -352,7 +351,7 @@ public unsafe static nint CCSPlayer_ItemServices_GiveNamedItem( nint pThis, stri } } - public unsafe static void CCSPlayer_ItemServices_DropActiveItem( nint pThis, Vector momentum ) + public static unsafe void CCSPlayer_ItemServices_DropActiveItem( nint pThis, Vector momentum ) { try { @@ -368,7 +367,7 @@ public unsafe static void CCSPlayer_ItemServices_DropActiveItem( nint pThis, Vec } } - public unsafe static void CCSPlayer_WeaponServices_DropWeapon( nint pThis, nint pWeapon ) + public static unsafe void CCSPlayer_WeaponServices_DropWeapon( nint pThis, nint pWeapon ) { try { @@ -384,7 +383,7 @@ public unsafe static void CCSPlayer_WeaponServices_DropWeapon( nint pThis, nint } } - public unsafe static void CCSPlayer_WeaponServices_SelectWeapon( nint pThis, nint pWeapon ) + public static unsafe void CCSPlayer_WeaponServices_SelectWeapon( nint pThis, nint pWeapon ) { try { @@ -400,7 +399,7 @@ public unsafe static void CCSPlayer_WeaponServices_SelectWeapon( nint pThis, nin } } - public unsafe static void CEntityResourceManifest_AddResource( nint pThis, string path ) + public static unsafe void CEntityResourceManifest_AddResource( nint pThis, string path ) { try { @@ -409,7 +408,7 @@ public unsafe static void CEntityResourceManifest_AddResource( nint pThis, strin var pool = ArrayPool.Shared; var pathLength = Encoding.UTF8.GetByteCount(path); var pathBuffer = pool.Rent(pathLength + 1); - Encoding.UTF8.GetBytes(path, pathBuffer); + _ = Encoding.UTF8.GetBytes(path, pathBuffer); pathBuffer[pathLength] = 0; var pAddResource = (delegate* unmanaged< nint, nint, void >)GetVirtualFunction(pThis, AddResourceOffset); fixed (byte* pPath = pathBuffer) @@ -425,7 +424,7 @@ public unsafe static void CEntityResourceManifest_AddResource( nint pThis, strin } } - public unsafe static void SetOrAddAttribute( nint handle, string name, float value ) + public static unsafe void SetOrAddAttribute( nint handle, string name, float value ) { try { @@ -434,7 +433,7 @@ public unsafe static void SetOrAddAttribute( nint handle, string name, float val var pool = ArrayPool.Shared; var nameLength = Encoding.UTF8.GetByteCount(name); var nameBuffer = pool.Rent(nameLength + 1); - Encoding.UTF8.GetBytes(name, nameBuffer); + _ = Encoding.UTF8.GetBytes(name, nameBuffer); nameBuffer[nameLength] = 0; fixed (byte* pName = nameBuffer) { @@ -449,7 +448,7 @@ public unsafe static void SetOrAddAttribute( nint handle, string name, float val } } - public unsafe static void CBaseEntity_CollisionRulesChanged( nint pThis ) + public static unsafe void CBaseEntity_CollisionRulesChanged( nint pThis ) { try { @@ -465,7 +464,7 @@ public unsafe static void CBaseEntity_CollisionRulesChanged( nint pThis ) } } - public unsafe static void CCSPlayerController_Respawn( nint pThis ) + public static unsafe void CCSPlayerController_Respawn( nint pThis ) { try { diff --git a/managed/src/SwiftlyS2.Shared/ISwiftlyCore.cs b/managed/src/SwiftlyS2.Shared/ISwiftlyCore.cs index 418c818a3..e651fbd1d 100644 --- a/managed/src/SwiftlyS2.Shared/ISwiftlyCore.cs +++ b/managed/src/SwiftlyS2.Shared/ISwiftlyCore.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using SwiftlyS2.Core.Services; using SwiftlyS2.Shared.CommandLine; using SwiftlyS2.Shared.Commands; using SwiftlyS2.Shared.ConsoleOutput; @@ -52,6 +51,11 @@ public interface ISwiftlyCore /// public IHelpers Helpers { get; } + /// + /// Game service. + /// + public IGameService Game { get; } + /// /// Command service. /// @@ -87,7 +91,6 @@ public interface ISwiftlyCore /// public IPlayerManagerService PlayerManager { get; } - /// /// Memory service. /// @@ -159,6 +162,11 @@ public interface ISwiftlyCore /// public ICommandLine CommandLine { get; } + /// + /// Game file system interface. + /// + public IGameFileSystem GameFileSystem { get; } + /// /// Gets the file path to the plugin directory. /// @@ -179,9 +187,4 @@ public interface ISwiftlyCore /// This directory is ensured to exist by the framework. /// public string PluginDataDirectory { get; } - - /// - /// Game file system interface. - /// - public IGameFileSystem GameFileSystem { get; } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Modules/Game/IGameService.cs b/managed/src/SwiftlyS2.Shared/Modules/Game/IGameService.cs new file mode 100644 index 000000000..d3c613ae4 --- /dev/null +++ b/managed/src/SwiftlyS2.Shared/Modules/Game/IGameService.cs @@ -0,0 +1,87 @@ +using SwiftlyS2.Shared.Misc; +using SwiftlyS2.Shared.Natives; + +namespace SwiftlyS2.Shared.Services; + +public interface IGameService +{ + /// + /// Gets a read-only reference to the current match data. + /// + ref readonly CCSMatch MatchData { get; } + + /// + /// Resets all match data to initial state. + /// + void Reset(); + + /// + /// Sets the current game phase. + /// + /// The game phase to set. + void SetPhase( GamePhase phase ); + + /// + /// Adds wins to the Terrorist team. + /// + /// Number of wins to add. + void AddTerroristWins( int numWins ); + + /// + /// Adds wins to the Counter-Terrorist team. + /// + /// Number of wins to add. + void AddCTWins( int numWins ); + + /// + /// Increments the round count. + /// + /// Number of rounds to increment. + void IncrementRound( int numRounds = 1 ); + + /// + /// Adds bonus points to the Terrorist team. + /// + /// Bonus points to add. + void AddTerroristBonusPoints( int points ); + + /// + /// Adds bonus points to the Counter-Terrorist team. + /// + /// Bonus points to add. + void AddCTBonusPoints( int points ); + + /// + /// Adds score to the Terrorist team. + /// + /// Score to add. + void AddTerroristScore( int score ); + + /// + /// Adds score to the Counter-Terrorist team. + /// + /// Score to add. + void AddCTScore( int score ); + + /// + /// Enters overtime mode. + /// + /// Number of overtime periods to add. + void GoToOvertime( int numOvertimesToAdd = 1 ); + + /// + /// Swaps the team scores between Terrorist and Counter-Terrorist. + /// + void SwapTeamScores(); + + // /// + // /// Updates the team score entities based on current match data. + // /// + // void UpdateTeamScores(); + + /// + /// Gets the winning team ID. + /// + /// Team ID of the winner, or 0 if tie. + int GetWinningTeam(); +} \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Natives/Structs/CCSMatch.cs b/managed/src/SwiftlyS2.Shared/Natives/Structs/CCSMatch.cs new file mode 100644 index 000000000..bedbb87e8 --- /dev/null +++ b/managed/src/SwiftlyS2.Shared/Natives/Structs/CCSMatch.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace SwiftlyS2.Shared.Natives; + +[StructLayout(LayoutKind.Sequential)] +public struct CCSMatch +{ + public short TotalScore; + public short ActualRoundsPlayed; + public short NOvertimePlaying; + public short CTScoreFirstHalf; + public short CTScoreSecondHalf; + public short CTScoreOvertime; + public short CTScoreTotal; + public short TerroristScoreFirstHalf; + public short TerroristScoreSecondHalf; + public short TerroristScoreOvertime; + public short TerroristScoreTotal; + public short Unknown; + public int Phase; + + /// + /// Returns a formatted string representation of the match data. + /// + public override readonly string ToString() + { + return $"Match [Round {ActualRoundsPlayed}] T: {TerroristScoreTotal} ({TerroristScoreFirstHalf}/{TerroristScoreSecondHalf}/{TerroristScoreOvertime}) vs CT: {CTScoreTotal} ({CTScoreFirstHalf}/{CTScoreSecondHalf}/{CTScoreOvertime}) | OT: {NOvertimePlaying} | Phase: {Phase}"; + } +} \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/SwiftlyCoreInjection.cs b/managed/src/SwiftlyS2.Shared/SwiftlyCoreInjection.cs index fb8b43732..6cbe5929d 100644 --- a/managed/src/SwiftlyS2.Shared/SwiftlyCoreInjection.cs +++ b/managed/src/SwiftlyS2.Shared/SwiftlyCoreInjection.cs @@ -1,48 +1,48 @@ +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using SwiftlyS2.Shared.Commands; namespace SwiftlyS2.Shared; -public static class SwiftlyCoreInjection { +public static class SwiftlyCoreInjection +{ + public static IServiceCollection AddSwiftly( this IServiceCollection self, ISwiftlyCore core, bool addLogger = true, bool addConfiguration = true ) + { + _ = self + .AddSingleton(core) + .AddSingleton(core.ConVar) + .AddSingleton(core.Command) + .AddSingleton(core.Database) + .AddSingleton(core.Engine) + .AddSingleton(core.EntitySystem) + .AddSingleton(core.Event) + .AddSingleton(core.GameData) + .AddSingleton(core.GameEvent) + .AddSingleton(core.Localizer) + .AddSingleton(core.Memory) + .AddSingleton(core.NetMessage) + .AddSingleton(core.Permission) + .AddSingleton(core.PlayerManager) + .AddSingleton(core.Profiler) + .AddSingleton(core.Scheduler) + .AddSingleton(core.Trace) + .AddSingleton(core.Translation); - public static IServiceCollection AddSwiftly(this IServiceCollection self, ISwiftlyCore core, bool addLogger = true, bool addConfiguration = true) - { - self - .AddSingleton(core) - .AddSingleton(core.ConVar) - .AddSingleton(core.Command) - .AddSingleton(core.Database) - .AddSingleton(core.Engine) - .AddSingleton(core.EntitySystem) - .AddSingleton(core.Event) - .AddSingleton(core.GameData) - .AddSingleton(core.GameEvent) - .AddSingleton(core.Localizer) - .AddSingleton(core.Memory) - .AddSingleton(core.NetMessage) - .AddSingleton(core.Permission) - .AddSingleton(core.PlayerManager) - .AddSingleton(core.Profiler) - .AddSingleton(core.Scheduler) - .AddSingleton(core.Trace) - .AddSingleton(core.Translation); + if (addLogger) + { + _ = self + .AddSingleton(core.LoggerFactory) + .AddSingleton(typeof(ILogger<>), typeof(Logger<>)); + } - if (addLogger) { - self - .AddSingleton(core.LoggerFactory) - .AddSingleton(typeof(ILogger<>), typeof(Logger<>)); - } + if (addConfiguration && core.Configuration.BasePathExists) + { + _ = self + .AddSingleton(core.Configuration) + .AddSingleton(core.Configuration.Manager) + .AddSingleton(provider => provider.GetRequiredService()); + } - if (addConfiguration && core.Configuration.BasePathExists) { - self - .AddSingleton(core.Configuration) - .AddSingleton(core.Configuration.Manager) - .AddSingleton(provider => provider.GetRequiredService()); + return self; } - - return self; - } - } \ No newline at end of file diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 6ef31563b..7ed34de44 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -642,7 +642,19 @@ public void GetIpCommand( ICommandContext context ) public void EndRoundCommand( ICommandContext _ ) { var gameRules = Core.EntitySystem.GetGameRules()!; - gameRules.TerminateRound(RoundEndReason.CTsWin, 10.0f); + // gameRules.TerminateRound(RoundEndReason.CTsWin, 10.0f); + // gameRules.AddTerroristWins(1, 7.0f); + // gameRules.AddCTWins(1, 7.0f); + Core.Game.AddCTWins(1); + gameRules.TerminateRound(RoundEndReason.AllHostageRescued, 7.0f); + } + + [Command("ss")] + public void SwapScoresCommand( ICommandContext _ ) + { + Core.PlayerManager.SendChat($"Before: {Core.Game.MatchData}"); + Core.Game.SwapTeamScores(); + Core.PlayerManager.SendChat($"After: {Core.Game.MatchData}"); } [Command("sizecheck")]