22using CounterStrikeSharp . API . Core ;
33using CounterStrikeSharp . API . Modules . Utils ;
44using CounterStrikeSharp . API . Modules . Cvars ;
5+ using System . Text . Json ;
56
67namespace MatchZy ;
78
@@ -26,6 +27,11 @@ public void HandleCoachCommand(CCSPlayerController? player, string side)
2627 ReplyToUserCommand ( player , "Coach command can only be used in match mode!" ) ;
2728 return ;
2829 }
30+ if ( IsWingmanMode ( ) )
31+ {
32+ ReplyToUserCommand ( player , "Coach command cannot be used in wingman!" ) ;
33+ return ;
34+ }
2935
3036 side = side . Trim ( ) . ToLower ( ) ;
3137
@@ -73,13 +79,21 @@ public void HandleCoaches()
7379 coachKillTimer ? . Kill ( ) ;
7480 coachKillTimer = null ;
7581 HashSet < CCSPlayerController > coaches = GetAllCoaches ( ) ;
76- if ( coaches . Count == 0 ) return ;
82+ if ( IsWingmanMode ( ) || coaches . Count == 0 ) return ;
83+ if ( spawnsData . Values . Any ( list => list . Count == 0 ) ) GetSpawns ( ) ;
84+ if ( coachSpawns . Count == 0 ||
85+ coachSpawns [ ( byte ) CsTeam . CounterTerrorist ] . Count == 0 ||
86+ coachSpawns [ ( byte ) CsTeam . Terrorist ] . Count == 0 )
87+ {
88+ Log ( $ "[HandleCoaches] No coach spawns found, player positions will not be swapped!") ;
89+ return ;
90+ }
91+
7792 int freezeTime = ConVar . Find ( "mp_freezetime" ) ! . GetPrimitiveValue < int > ( ) ;
7893 freezeTime = freezeTime > 2 ? freezeTime : 2 ;
79- coachKillTimer ??= AddTimer ( freezeTime - 1.5f , KillCoaches ) ;
80- HashSet < CCSPlayerController > competitiveSpawnCoaches = new ( ) ;
81- if ( spawnsData . Values . Any ( list => list . Count == 0 ) ) GetSpawns ( ) ;
94+ coachKillTimer ??= AddTimer ( freezeTime - 1f , KillCoaches ) ;
8295
96+ Random random = new ( ) ;
8397 foreach ( CCSPlayerController coach in coaches )
8498 {
8599 if ( ! IsPlayerValid ( coach ) ) continue ;
@@ -94,64 +108,72 @@ public void HandleCoaches()
94108 coach . ActionTrackingServices ! . MatchStats . Assists = 0 ;
95109 coach . ActionTrackingServices ! . MatchStats . Damage = 0 ;
96110
97- List < Position > teamPositions = spawnsData [ coach . TeamNum ] ;
111+ SetPlayerInvisible ( player : coach , setWeaponsInvisible : false ) ;
112+ // Stopping the coaches from moving, so that they don't block the players.
113+ coach . PlayerPawn . Value ! . MoveType = MoveType_t . MOVETYPE_NONE ;
114+ coach . PlayerPawn . Value ! . ActualMoveType = MoveType_t . MOVETYPE_NONE ;
115+
116+ List < Position > coachTeamSpawns = coachSpawns [ coach . TeamNum ] ;
98117 Position coachPosition = new ( coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
99118
100- foreach ( Position position in teamPositions )
101- {
102- if ( position . Equals ( coachPosition ) )
103- {
104- competitiveSpawnCoaches . Add ( coach ) ;
105- break ;
106- }
107- }
108- SetPlayerInvisible ( player : coach , setWeaponsInvisible : false ) ;
119+ // Picking a random position for the coach (from coachSpawns) to teleport them.
120+ Position newPosition = coachTeamSpawns [ random . Next ( 0 , coachTeamSpawns . Count ) ] ;
121+
109122 // Elevating coach before dropping the C4 to prevent it going inside the ground.
110123 AddTimer ( 0.05f , ( ) =>
111124 {
112125 coach ! . PlayerPawn . Value ! . Teleport ( new Vector ( coachPosition . PlayerPosition . X , coachPosition . PlayerPosition . Y , coachPosition . PlayerPosition . Z + 20.0f ) , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
113126 HandleCoachWeapons ( coach ) ;
114- coach ! . PlayerPawn . Value . Teleport ( coachPosition . PlayerPosition , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
127+ coach ! . PlayerPawn . Value . Teleport ( newPosition . PlayerPosition , newPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
115128 } ) ;
116-
129+
117130 }
118131
119- var playerEntities = Utilities . FindAllEntitiesByDesignerName < CCSPlayerController > ( "cs_player_controller" ) ;
132+ List < CCSPlayerController > players = Utilities . GetPlayers ( ) ;
133+ HashSet < Position > occupiedSpawns = new ( ) ;
134+ HashSet < CCSPlayerController > incorrectSpawnedPlayers = new ( ) ;
135+
136+ // We will loop on the players 2 times, first loop is to get all the players who are on a non-competitive spawn, and to get all the non-occupied competitive spawn.
137+ // In the next loop, we will teleport the non-competitive spawned players to an available competitive spawn.
120138
121- // foreach (var key in playerData.Keys)
122- // {
123- foreach ( var player in playerEntities )
139+ foreach ( CCSPlayerController player in players )
124140 {
125- if ( ! IsPlayerValid ( player ) ) continue ;
126- // CCSPlayerController player = playerData[key];
141+ if ( ! IsPlayerValid ( player ) || coaches . Contains ( player ) ) continue ;
142+
127143 List < Position > teamPositions = spawnsData [ player . TeamNum ] ;
128144 Position playerPosition = new ( player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
129145 bool isCompetitiveSpawn = false ;
130146 foreach ( Position position in teamPositions )
131147 {
132148 if ( position . Equals ( playerPosition ) )
133149 {
150+ occupiedSpawns . Add ( position ) ;
134151 isCompetitiveSpawn = true ;
135152 break ;
136153 }
137154 }
138- // Player is already on a competitive spawn, no need to swap.
139155 if ( isCompetitiveSpawn ) continue ;
140156
141- CCSPlayerController ? coach = competitiveSpawnCoaches . FirstOrDefault ( ( CCSPlayerController coach ) => coach . Team == player . Team ) ;
142- if ( coach is null ) continue ;
143- competitiveSpawnCoaches . Remove ( coach ) ;
157+ // The player is not on a competitive spawn, we will put them on one in the next loop.
158+ incorrectSpawnedPlayers . Add ( player ) ;
159+ }
144160
145- Position coachPosition = new ( coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
146- AddTimer ( 0.1f , ( ) =>
147- {
148- coach ! . PlayerPawn . Value ! . Teleport ( new Vector ( playerPosition . PlayerPosition . X , playerPosition . PlayerPosition . Y , playerPosition . PlayerPosition . Z ) , playerPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
149- player ! . PlayerPawn . Value . Teleport ( coachPosition . PlayerPosition , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
150- } ) ;
161+ foreach ( CCSPlayerController player in incorrectSpawnedPlayers )
162+ {
163+ if ( ! IsPlayerValid ( player ) || coaches . Contains ( player ) ) continue ;
151164
152- // Stopping the coaches from moving, so that they don't block the players.
153- coach . PlayerPawn . Value ! . MoveType = MoveType_t . MOVETYPE_NONE ;
154- coach . PlayerPawn . Value ! . ActualMoveType = MoveType_t . MOVETYPE_NONE ;
165+ List < Position > teamPositions = spawnsData [ player . TeamNum ] ;
166+ Position playerPosition = new ( player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
167+ foreach ( Position position in teamPositions )
168+ {
169+ if ( occupiedSpawns . Contains ( position ) ) continue ;
170+ occupiedSpawns . Add ( position ) ;
171+ AddTimer ( 0.1f , ( ) =>
172+ {
173+ player ! . PlayerPawn . Value . Teleport ( position . PlayerPosition , position . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
174+ } ) ;
175+ break ;
176+ }
155177 }
156178 }
157179
@@ -204,29 +226,62 @@ private void KillCoaches()
204226 {
205227 if ( isPaused || IsTacticalTimeoutActive ( ) ) return ;
206228 HashSet < CCSPlayerController > coaches = GetAllCoaches ( ) ;
207- if ( coaches . Count == 0 ) return ;
229+ if ( IsWingmanMode ( ) || coaches . Count == 0 ) return ;
208230 string suicidePenalty = GetConvarStringValue ( ConVar . Find ( "mp_suicide_penalty" ) ) ;
209- string deathDropGunEnabled = GetConvarStringValue ( ConVar . Find ( "mp_death_drop_gun" ) ) ;
210231 string specFreezeTime = GetConvarStringValue ( ConVar . Find ( "spec_freeze_time" ) ) ;
211232 string specFreezeTimeLock = GetConvarStringValue ( ConVar . Find ( "spec_freeze_time_lock" ) ) ;
212233 string specFreezeDeathanim = GetConvarStringValue ( ConVar . Find ( "spec_freeze_deathanim_time" ) ) ;
213234 Server . ExecuteCommand ( "mp_suicide_penalty 0;spec_freeze_time 0; spec_freeze_time_lock 0; spec_freeze_deathanim_time 0;" ) ;
214235
215- // Adding timer to make sure above commands are executed successfully.
216- AddTimer ( 0.5f , ( ) =>
236+ foreach ( var coach in coaches )
237+ {
238+ if ( ! IsPlayerValid ( coach ) ) continue ;
239+ if ( isPaused || IsTacticalTimeoutActive ( ) ) continue ;
240+
241+ Position coachPosition = new ( coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
242+ coach ! . PlayerPawn . Value ! . Teleport ( new Vector ( coachPosition . PlayerPosition . X , coachPosition . PlayerPosition . Y , coachPosition . PlayerPosition . Z + 20.0f ) , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
243+ // Dropping the C4 if it was picked up or passed to the coach.
244+ DropWeaponByDesignerName ( coach , "weapon_c4" ) ;
245+ coach . PlayerPawn . Value ! . CommitSuicide ( explode : false , force : true ) ;
246+ }
247+ Server . ExecuteCommand ( $ "mp_suicide_penalty { suicidePenalty } ; spec_freeze_time { specFreezeTime } ; spec_freeze_time_lock { specFreezeTimeLock } ; spec_freeze_deathanim_time { specFreezeDeathanim } ;") ;
248+ }
249+
250+ private void GetCoachSpawns ( )
251+ {
252+ coachSpawns = GetEmptySpawnsData ( ) ;
253+ try
217254 {
218- foreach ( var coach in coaches )
255+ string spawnsConfigPath = Path . Combine ( ModuleDirectory , "spawns" , "coach" , $ "{ Server . MapName } .json") ;
256+ string spawnsConfig = File . ReadAllText ( spawnsConfigPath ) ;
257+
258+ var jsonDictionary = JsonSerializer . Deserialize < Dictionary < string , List < Dictionary < string , string > > > > ( spawnsConfig ) ;
259+ if ( jsonDictionary is null ) return ;
260+ foreach ( var entry in jsonDictionary )
219261 {
220- if ( ! IsPlayerValid ( coach ) ) continue ;
221- if ( isPaused || IsTacticalTimeoutActive ( ) ) continue ;
262+ byte team = byte . Parse ( entry . Key ) ;
263+ List < Position > positionList = new ( ) ;
222264
223- Position coachPosition = new ( coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
224- coach ! . PlayerPawn . Value ! . Teleport ( new Vector ( coachPosition . PlayerPosition . X , coachPosition . PlayerPosition . Y , coachPosition . PlayerPosition . Z + 20.0f ) , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
225- // Dropping the C4 if it was picked up or passed to the coach.
226- DropWeaponByDesignerName ( coach , "weapon_c4" ) ;
227- coach . PlayerPawn . Value ! . CommitSuicide ( explode : false , force : true ) ;
265+ foreach ( var positionData in entry . Value )
266+ {
267+ string [ ] vectorArray = positionData [ "Vector" ] . Split ( ' ' ) ;
268+ string [ ] angleArray = positionData [ "QAngle" ] . Split ( ' ' ) ;
269+
270+ // Parse position and angle
271+ Vector vector = new ( float . Parse ( vectorArray [ 0 ] ) , float . Parse ( vectorArray [ 1 ] ) , float . Parse ( vectorArray [ 2 ] ) ) ;
272+ QAngle qAngle = new ( float . Parse ( angleArray [ 0 ] ) , float . Parse ( angleArray [ 1 ] ) , float . Parse ( angleArray [ 2 ] ) ) ;
273+
274+ Position position = new ( vector , qAngle ) ;
275+
276+ positionList . Add ( position ) ;
277+ }
278+ coachSpawns [ team ] = positionList ;
228279 }
229- Server . ExecuteCommand ( $ "mp_suicide_penalty { suicidePenalty } ; spec_freeze_time { specFreezeTime } ; spec_freeze_time_lock { specFreezeTimeLock } ; spec_freeze_deathanim_time { specFreezeDeathanim } ;") ;
230- } ) ;
280+ Log ( $ "[GetCoachSpawns] Loaded { coachSpawns . Count } coach spawns") ;
281+ }
282+ catch ( Exception ex )
283+ {
284+ Log ( $ "[GetCoachSpawns - FATAL] Error getting coach spawns. [ERROR]: { ex . Message } ") ;
285+ }
231286 }
232- }
287+ }
0 commit comments