2
2
using CounterStrikeSharp . API . Core ;
3
3
using CounterStrikeSharp . API . Modules . Utils ;
4
4
using CounterStrikeSharp . API . Modules . Cvars ;
5
+ using System . Text . Json ;
5
6
6
7
namespace MatchZy ;
7
8
@@ -26,6 +27,11 @@ public void HandleCoachCommand(CCSPlayerController? player, string side)
26
27
ReplyToUserCommand ( player , "Coach command can only be used in match mode!" ) ;
27
28
return ;
28
29
}
30
+ if ( IsWingmanMode ( ) )
31
+ {
32
+ ReplyToUserCommand ( player , "Coach command cannot be used in wingman!" ) ;
33
+ return ;
34
+ }
29
35
30
36
side = side . Trim ( ) . ToLower ( ) ;
31
37
@@ -73,13 +79,21 @@ public void HandleCoaches()
73
79
coachKillTimer ? . Kill ( ) ;
74
80
coachKillTimer = null ;
75
81
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
+
77
92
int freezeTime = ConVar . Find ( "mp_freezetime" ) ! . GetPrimitiveValue < int > ( ) ;
78
93
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 ) ;
82
95
96
+ Random random = new ( ) ;
83
97
foreach ( CCSPlayerController coach in coaches )
84
98
{
85
99
if ( ! IsPlayerValid ( coach ) ) continue ;
@@ -94,64 +108,72 @@ public void HandleCoaches()
94
108
coach . ActionTrackingServices ! . MatchStats . Assists = 0 ;
95
109
coach . ActionTrackingServices ! . MatchStats . Damage = 0 ;
96
110
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 ] ;
98
117
Position coachPosition = new ( coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , coach . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
99
118
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
+
109
122
// Elevating coach before dropping the C4 to prevent it going inside the ground.
110
123
AddTimer ( 0.05f , ( ) =>
111
124
{
112
125
coach ! . PlayerPawn . Value ! . Teleport ( new Vector ( coachPosition . PlayerPosition . X , coachPosition . PlayerPosition . Y , coachPosition . PlayerPosition . Z + 20.0f ) , coachPosition . PlayerAngle , new Vector ( 0 , 0 , 0 ) ) ;
113
126
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 ) ) ;
115
128
} ) ;
116
-
129
+
117
130
}
118
131
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.
120
138
121
- // foreach (var key in playerData.Keys)
122
- // {
123
- foreach ( var player in playerEntities )
139
+ foreach ( CCSPlayerController player in players )
124
140
{
125
- if ( ! IsPlayerValid ( player ) ) continue ;
126
- // CCSPlayerController player = playerData[key];
141
+ if ( ! IsPlayerValid ( player ) || coaches . Contains ( player ) ) continue ;
142
+
127
143
List < Position > teamPositions = spawnsData [ player . TeamNum ] ;
128
144
Position playerPosition = new ( player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsOrigin , player . PlayerPawn . Value ! . CBodyComponent ! . SceneNode ! . AbsRotation ) ;
129
145
bool isCompetitiveSpawn = false ;
130
146
foreach ( Position position in teamPositions )
131
147
{
132
148
if ( position . Equals ( playerPosition ) )
133
149
{
150
+ occupiedSpawns . Add ( position ) ;
134
151
isCompetitiveSpawn = true ;
135
152
break ;
136
153
}
137
154
}
138
- // Player is already on a competitive spawn, no need to swap.
139
155
if ( isCompetitiveSpawn ) continue ;
140
156
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
+ }
144
160
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 ;
151
164
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
+ }
155
177
}
156
178
}
157
179
@@ -204,29 +226,62 @@ private void KillCoaches()
204
226
{
205
227
if ( isPaused || IsTacticalTimeoutActive ( ) ) return ;
206
228
HashSet < CCSPlayerController > coaches = GetAllCoaches ( ) ;
207
- if ( coaches . Count == 0 ) return ;
229
+ if ( IsWingmanMode ( ) || coaches . Count == 0 ) return ;
208
230
string suicidePenalty = GetConvarStringValue ( ConVar . Find ( "mp_suicide_penalty" ) ) ;
209
- string deathDropGunEnabled = GetConvarStringValue ( ConVar . Find ( "mp_death_drop_gun" ) ) ;
210
231
string specFreezeTime = GetConvarStringValue ( ConVar . Find ( "spec_freeze_time" ) ) ;
211
232
string specFreezeTimeLock = GetConvarStringValue ( ConVar . Find ( "spec_freeze_time_lock" ) ) ;
212
233
string specFreezeDeathanim = GetConvarStringValue ( ConVar . Find ( "spec_freeze_deathanim_time" ) ) ;
213
234
Server . ExecuteCommand ( "mp_suicide_penalty 0;spec_freeze_time 0; spec_freeze_time_lock 0; spec_freeze_deathanim_time 0;" ) ;
214
235
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
217
254
{
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 )
219
261
{
220
- if ( ! IsPlayerValid ( coach ) ) continue ;
221
- if ( isPaused || IsTacticalTimeoutActive ( ) ) continue ;
262
+ byte team = byte . Parse ( entry . Key ) ;
263
+ List < Position > positionList = new ( ) ;
222
264
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 ;
228
279
}
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
+ }
231
286
}
232
- }
287
+ }
0 commit comments