DEADSOFTWARE

0a4facfdbdc7a0fe1c91cd362962b5fc7e3dadc1
[d2df-sdl.git] / src / game / g_netmsg.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_netmsg;
18 interface
20 uses e_msg, g_net, g_triggers, Classes, SysUtils, md5;
22 const
23 NET_MSG_INFO = 100;
25 NET_MSG_CHAT = 101;
26 NET_MSG_SND = 102;
27 NET_MSG_GFX = 103;
28 NET_MSG_GEVENT = 104;
29 NET_MSG_SCORE = 105;
30 NET_MSG_COOP = 106;
31 NET_MSG_FLAG = 107;
32 NET_MSG_REQFST = 108;
33 NET_MSG_GSET = 109;
35 NET_MSG_PLR = 111;
36 NET_MSG_PLRPOS = 112;
37 NET_MSG_PLRSTA = 113;
38 NET_MSG_PLRDEL = 114;
39 NET_MSG_PLRDMG = 115;
40 NET_MSG_PLRDIE = 116;
41 NET_MSG_PLRFIRE= 117;
42 NET_MSG_PLRSET = 119;
43 NET_MSG_CHEAT = 120;
45 NET_MSG_ISPAWN = 121;
46 NET_MSG_IDEL = 122;
48 NET_MSG_MSPAWN = 131;
49 NET_MSG_MPOS = 132;
50 NET_MSG_MSTATE = 133;
51 NET_MSG_MSHOT = 134;
52 NET_MSG_MDEL = 135;
54 NET_MSG_PSTATE = 141;
55 NET_MSG_PTEX = 142;
57 NET_MSG_TSOUND = 151;
58 NET_MSG_TMUSIC = 152;
60 NET_MSG_SHDEL = 161;
61 NET_MSG_SHADD = 162;
62 NET_MSG_SHPOS = 163;
64 NET_MSG_RCON_AUTH = 191;
65 NET_MSG_RCON_CMD = 192;
66 NET_MSG_TIME_SYNC = 194;
67 NET_MSG_VOTE_EVENT = 195;
69 {
70 NET_MSG_MAP_REQUEST = 201;
71 NET_MSG_MAP_RESPONSE = 202;
72 NET_MSG_RES_REQUEST = 203;
73 NET_MSG_RES_RESPONSE = 204;
74 }
76 NET_CHAT_SYSTEM = 0;
77 NET_CHAT_PLAYER = 1;
78 NET_CHAT_TEAM = 2;
80 NET_RCON_NOAUTH = 0;
81 NET_RCON_PWGOOD = 1;
82 NET_RCON_PWBAD = 2;
84 NET_GFX_SPARK = 1;
85 NET_GFX_TELE = 2;
86 NET_GFX_RESPAWN = 3;
87 NET_GFX_FIRE = 4;
88 NET_GFX_EXPLODE = 5;
89 NET_GFX_BFGEXPL = 6;
90 NET_GFX_BFGHIT = 7;
91 NET_GFX_SHELL1 = 8;
92 NET_GFX_SHELL2 = 9;
93 NET_GFX_SHELL3 = 10;
95 NET_EV_MAPSTART = 1;
96 NET_EV_MAPEND = 2;
97 NET_EV_CHANGE_TEAM = 3;
98 NET_EV_PLAYER_KICK = 4;
99 NET_EV_PLAYER_BAN = 5;
100 NET_EV_LMS_WARMUP = 6;
101 NET_EV_LMS_SURVIVOR = 7;
102 NET_EV_RCON = 8;
103 NET_EV_BIGTEXT = 9;
104 NET_EV_SCORE = 10;
105 NET_EV_SCORE_MSG = 11;
106 NET_EV_LMS_START = 12;
107 NET_EV_LMS_WIN = 13;
108 NET_EV_TLMS_WIN = 14;
109 NET_EV_LMS_LOSE = 15;
110 NET_EV_LMS_DRAW = 16;
111 NET_EV_KILLCOMBO = 17;
112 NET_EV_PLAYER_TOUCH = 18;
113 NET_EV_SECRET = 19;
114 NET_EV_INTER_READY = 20;
115 NET_EV_LMS_NOSPAWN = 21;
117 NET_VE_STARTED = 1;
118 NET_VE_PASSED = 2;
119 NET_VE_FAILED = 3;
120 NET_VE_VOTE = 4;
121 NET_VE_REVOKE = 5;
122 NET_VE_INPROGRESS = 6;
124 NET_FLAG_GET = 1;
125 NET_FLAG_DROP = 2;
126 NET_FLAG_CAP = 3;
127 NET_FLAG_RETURN = 4;
129 NET_CHEAT_SUICIDE = 1;
130 NET_CHEAT_SPECTATE = 2;
131 NET_CHEAT_READY = 3;
132 NET_CHEAT_DROPFLAG = 4;
134 NET_MAX_DIFFTIME = 5000 div 36;
136 // HOST MESSAGES
138 procedure MH_ProcessFirstSpawn (C: pTNetClient);
140 procedure MH_RECV_Info(C: pTNetClient; var M: TMsg);
141 procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg);
142 procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
143 function MH_RECV_PlayerPos(C: pTNetClient; var M: TMsg): Word;
144 procedure MH_RECV_PlayerSettings(C: pTNetClient; var M: TMsg);
145 procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg);
146 procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg);
147 procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg);
148 //procedure MH_RECV_MapRequest(C: pTNetClient; var M: TMsg);
149 //procedure MH_RECV_ResRequest(C: pTNetClient; var M: TMsg);
150 procedure MH_RECV_Vote(C: pTNetClient; var M: TMsg);
152 // GAME
153 procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
154 procedure MH_SEND_Info(ID: Byte);
155 procedure MH_SEND_Chat(Txt: string; Mode: Byte; ID: Integer = NET_EVERYONE);
156 procedure MH_SEND_Effect(X, Y: Integer; Ang: SmallInt; Kind: Byte; ID: Integer = NET_EVERYONE);
157 procedure MH_SEND_Sound(X, Y: Integer; Name: string; Pos: Boolean = True; ID: Integer = NET_EVERYONE);
158 procedure MH_SEND_CreateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
159 procedure MH_SEND_UpdateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
160 procedure MH_SEND_DeleteShot(Proj: LongInt; X, Y: LongInt; Loud: Boolean = True; ID: Integer = NET_EVERYONE);
161 procedure MH_SEND_GameStats(ID: Integer = NET_EVERYONE);
162 procedure MH_SEND_CoopStats(ID: Integer = NET_EVERYONE);
163 procedure MH_SEND_GameEvent(EvType: Byte; EvNum: Integer = 0; EvStr: string = 'N'; ID: Integer = NET_EVERYONE);
164 procedure MH_SEND_FlagEvent(EvType: Byte; Flag: Byte; PID: Word; Quiet: Boolean = False; ID: Integer = NET_EVERYONE);
165 procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE);
166 // PLAYER
167 procedure MH_SEND_PlayerCreate(PID: Word; ID: Integer = NET_EVERYONE);
168 procedure MH_SEND_PlayerPos(Reliable: Boolean; PID: Word; ID: Integer = NET_EVERYONE);
169 procedure MH_SEND_PlayerStats(PID: Word; ID: Integer = NET_EVERYONE);
170 procedure MH_SEND_PlayerDelete(PID: Word; ID: Integer = NET_EVERYONE);
171 procedure MH_SEND_PlayerDamage(PID: Word; Kind: Byte; Attacker, Value: Word; VX, VY: Integer; ID: Integer = NET_EVERYONE);
172 procedure MH_SEND_PlayerFire(PID: Word; Weapon: Byte; X, Y, AX, AY: Integer; ShotID: Integer = -1; ID: Integer = NET_EVERYONE);
173 procedure MH_SEND_PlayerDeath(PID: Word; KillType, DeathType: Byte; Attacker: Word; ID: Integer = NET_EVERYONE);
174 procedure MH_SEND_PlayerSettings(PID: Word; Mdl: string = ''; ID: Integer = NET_EVERYONE);
175 // ITEM
176 procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
177 procedure MH_SEND_ItemDestroy(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
178 // PANEL
179 procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE);
180 procedure MH_SEND_PanelState(PGUID: Integer; ID: Integer = NET_EVERYONE);
181 // MONSTER
182 procedure MH_SEND_MonsterSpawn(UID: Word; ID: Integer = NET_EVERYONE);
183 procedure MH_SEND_MonsterPos(UID: Word; ID: Integer = NET_EVERYONE);
184 procedure MH_SEND_MonsterState(UID: Word; ForcedAnim: Byte = 255; ID: Integer = NET_EVERYONE);
185 procedure MH_SEND_MonsterShot(UID: Word; X, Y, VX, VY: Integer; ID: Integer = NET_EVERYONE);
186 procedure MH_SEND_MonsterDelete(UID: Word; ID: Integer = NET_EVERYONE);
187 // TRIGGER
188 procedure MH_SEND_TriggerSound(var T: TTrigger; ID: Integer = NET_EVERYONE);
189 procedure MH_SEND_TriggerMusic(ID: Integer = NET_EVERYONE);
190 // MISC
191 procedure MH_SEND_TimeSync(Time: LongWord; ID: Integer = NET_EVERYONE);
192 procedure MH_SEND_VoteEvent(EvType: Byte;
193 StrArg1: string = 'a'; StrArg2: string = 'b';
194 IntArg1: SmallInt = 0; IntArg2: SmallInt = 0;
195 ID: Integer = NET_EVERYONE);
197 // CLIENT MESSAGES //
199 // GAME
200 procedure MC_RECV_Chat(var M: TMsg);
201 procedure MC_RECV_Effect(var M: TMsg);
202 procedure MC_RECV_Sound(var M: TMsg);
203 procedure MC_RECV_GameStats(var M: TMsg);
204 procedure MC_RECV_CoopStats(var M: TMsg);
205 procedure MC_RECV_GameEvent(var M: TMsg);
206 procedure MC_RECV_FlagEvent(var M: TMsg);
207 procedure MC_RECV_GameSettings(var M: TMsg);
208 // PLAYER
209 function MC_RECV_PlayerCreate(var M: TMsg): Word;
210 function MC_RECV_PlayerPos(var M: TMsg): Word;
211 function MC_RECV_PlayerStats(var M: TMsg): Word;
212 function MC_RECV_PlayerDelete(var M: TMsg): Word;
213 function MC_RECV_PlayerDamage(var M: TMsg): Word;
214 function MC_RECV_PlayerDeath(var M: TMsg): Word;
215 function MC_RECV_PlayerFire(var M: TMsg): Word;
216 procedure MC_RECV_PlayerSettings(var M: TMsg);
217 // ITEM
218 procedure MC_RECV_ItemSpawn(var M: TMsg);
219 procedure MC_RECV_ItemDestroy(var M: TMsg);
220 // PANEL
221 procedure MC_RECV_PanelTexture(var M: TMsg);
222 procedure MC_RECV_PanelState(var M: TMsg);
223 // MONSTER
224 procedure MC_RECV_MonsterSpawn(var M: TMsg);
225 procedure MC_RECV_MonsterPos(var M: TMsg);
226 procedure MC_RECV_MonsterState(var M: TMsg);
227 procedure MC_RECV_MonsterShot(var M: TMsg);
228 procedure MC_RECV_MonsterDelete(var M: TMsg);
229 // SHOT
230 procedure MC_RECV_CreateShot(var M: TMsg);
231 procedure MC_RECV_UpdateShot(var M: TMsg);
232 procedure MC_RECV_DeleteShot(var M: TMsg);
233 // TRIGGER
234 procedure MC_RECV_TriggerSound(var M: TMsg);
235 procedure MC_RECV_TriggerMusic(var M: TMsg);
236 // MISC
237 procedure MC_RECV_TimeSync(var M: TMsg);
238 procedure MC_RECV_VoteEvent(var M: TMsg);
239 // SERVICE
240 procedure MC_SEND_Info(Password: string);
241 procedure MC_SEND_Chat(Txt: string; Mode: Byte);
242 procedure MC_SEND_PlayerPos();
243 procedure MC_SEND_FullStateRequest();
244 procedure MC_SEND_PlayerSettings();
245 procedure MC_SEND_CheatRequest(Kind: Byte);
246 procedure MC_SEND_RCONPassword(Password: string);
247 procedure MC_SEND_RCONCommand(Cmd: string);
248 procedure MC_SEND_Vote(Start: Boolean = False; Command: string = 'a');
249 // DOWNLOAD
250 //procedure MC_SEND_MapRequest();
251 //procedure MC_SEND_ResRequest(const resName: AnsiString);
254 type
255 TExternalResourceInfo = record
256 Name: string[255];
257 md5: TMD5Digest;
258 end;
260 TResDataMsg = record
261 MsgId: Byte;
262 FileSize: Integer;
263 FileData: AByte;
264 end;
266 TMapDataMsg = record
267 MsgId: Byte;
268 FileSize: Integer;
269 FileData: AByte;
270 ExternalResources: array of TExternalResourceInfo;
271 end;
273 function IsValidFileName(const S: String): Boolean;
274 function IsValidFilePath(const S: String): Boolean;
277 implementation
279 uses
280 Math, ENet, e_input, e_graphics, e_log,
281 g_textures, g_gfx, g_sound, g_console, g_basic, g_options, g_main,
282 g_game, g_player, g_map, g_panel, g_items, g_weapons, g_phys, g_gui,
283 g_language, g_monsters, g_netmaster, utils, wadreader, MAPDEF;
285 const
286 NET_KEY_LEFT = 1 shl 0;
287 NET_KEY_RIGHT = 1 shl 1;
288 NET_KEY_UP = 1 shl 2;
289 NET_KEY_DOWN = 1 shl 3;
290 NET_KEY_JUMP = 1 shl 4;
291 NET_KEY_FIRE = 1 shl 5;
292 NET_KEY_OPEN = 1 shl 6;
293 NET_KEY_CHAT = 1 shl 7;
294 NET_KEY_FORCEDIR = 1 shl 8;
296 //var
297 //kBytePrev: Word = 0;
298 //kDirPrev: TDirection = D_LEFT;
299 //HostGameTime: Word = 0;
302 function IsValidFileName(const S: String): Boolean;
303 const
304 Forbidden: set of Char = ['<', '>', '|', '"', ':', '*', '?'];
305 var
306 I: Integer;
307 begin
308 Result := S <> '';
309 for I := 1 to Length(S) do
310 Result := Result and (not(S[I] in Forbidden));
311 end;
313 function IsValidFilePath(const S: String): Boolean;
314 var
315 I: Integer;
316 begin
317 Result := False;
318 if not IsValidFileName(S) then exit;
319 if FileExists(S) then exit;
320 I := LastDelimiter('\/', S);
321 if (I > 0) then
322 if (not DirectoryExists(Copy(S, 1, I-1))) then
323 exit;
324 Result := True;
325 end;
328 // HOST MESSAGES //
331 // GAME
333 procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg);
334 var
335 Txt: string;
336 Mode: Byte;
337 PID: Word;
338 Pl: TPlayer;
339 begin
340 PID := C^.Player;
341 Pl := g_Player_Get(PID);
343 Txt := M.ReadString();
344 Mode := M.ReadByte();
345 if (Mode = NET_CHAT_SYSTEM) then
346 Mode := NET_CHAT_PLAYER; // prevent sending system messages from clients
347 if (Mode = NET_CHAT_TEAM) and (not gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
348 Mode := NET_CHAT_PLAYER; // revert to player chat in non-team game
350 if Pl = nil then
351 MH_SEND_Chat(Txt, Mode)
352 else
353 MH_SEND_Chat(Pl.Name + ': ' + Txt, Mode, IfThen(Mode = NET_CHAT_TEAM, Pl.Team, NET_EVERYONE));
354 end;
356 procedure MH_RECV_Info(C: pTNetClient; var M: TMsg);
357 var
358 Ver, PName, Model, Pw: string;
359 R, G, B, T: Byte;
360 PID: Word;
361 Color: TRGB;
362 I: Integer;
363 begin
364 Ver := M.ReadString();
365 Pw := M.ReadString();
366 PName := M.ReadString();
367 Model := M.ReadString();
368 R := M.ReadByte();
369 G := M.ReadByte();
370 B := M.ReadByte();
371 T := M.ReadByte();
373 if Ver <> GAME_VERSION then
374 begin
375 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
376 _lc[I_NET_DISC_VERSION]);
377 enet_peer_disconnect(C^.Peer, NET_DISC_VERSION);
378 Exit;
379 end;
381 if g_Net_IsHostBanned(C^.Peer^.address.host) then
382 begin
383 if g_Net_IsHostBanned(C^.Peer^.address.host, True) then
384 begin
385 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
386 _lc[I_NET_DISC_BAN]);
387 enet_peer_disconnect(C^.Peer, NET_DISC_BAN);
388 end
389 else
390 begin
391 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
392 _lc[I_NET_DISC_BAN]);
393 enet_peer_disconnect(C^.Peer, NET_DISC_TEMPBAN);
394 end;
395 Exit;
396 end;
398 if NetPassword <> '' then
399 if AnsiLowerCase(NetPassword) <> AnsiLowerCase(Pw) then
400 begin
401 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
402 _lc[I_NET_DISC_PASSWORD]);
403 enet_peer_disconnect(C^.Peer, NET_DISC_PASSWORD);
404 Exit;
405 end;
407 Color.R := R;
408 Color.B := B;
409 Color.G := G;
411 PID := g_Player_Create(Model, Color, T, False);
412 with g_Player_Get(PID) do
413 begin
414 Name := PName;
415 Reset(True);
416 end;
418 C^.Player := PID;
419 C^.WaitForFirstSpawn := false;
421 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True);
422 e_WriteLog('NET: Client ' + PName + ' [' + IntToStr(C^.ID) +
423 '] connected. Assigned player #' + IntToStr(PID) + '.', TMsgType.Notify);
425 MH_SEND_Info(C^.ID);
427 with g_Player_Get(PID) do
428 begin
429 Name := PName;
430 FClientID := C^.ID;
431 // round in progress, don't spawn
432 e_LogWritefln('*** client #%u (cid #%u) authenticated...', [C.ID, C.Player]);
433 //e_LogWritefln('spawning player with pid #%u...', [PID]);
434 //Respawn(gGameSettings.GameType = GT_SINGLE);
435 //k8: no, do not spawn a player yet, wait for "request full state" packet
436 Lives := 0;
437 Spectate;
438 FNoRespawn := True;
439 // `FWantsInGame` seems to mean "spawn the player on the next occasion".
440 // that is, if we'll set it to `true`, the player can be spawned after
441 // warmup time ran out, for example, regardless of the real player state.
442 // also, this seems to work only for the initial connection. further
443 // map changes could initiate resource downloading, but the player will
444 // be spawned immediately.
445 // the proper solution will require another player state, "ephemeral".
446 // the player should start any map in "ephemeral" state, and turned into
447 // real mobj only when they sent a special "i am ready" packet. this packet
448 // must be sent after receiving the full state, so the player will get a full
449 // map view before going into game.
450 FWantsInGame := false;
451 C^.WaitForFirstSpawn := true;
452 end;
454 //if not C^.WaitForFirstSpawn then
455 begin
456 for I := Low(NetClients) to High(NetClients) do
457 begin
458 if NetClients[I].ID = C^.ID then Continue;
459 MH_SEND_PlayerCreate(PID, NetClients[I].ID);
460 MH_SEND_PlayerPos(True, PID, NetClients[I].ID);
461 MH_SEND_PlayerStats(PID, NetClients[I].ID);
462 end;
463 end;
465 if gState in [STATE_INTERCUSTOM, STATE_FOLD] then
466 MH_SEND_GameEvent(NET_EV_MAPEND, 0, 'N', C^.ID);
468 if NetUseMaster then
469 begin
470 //g_Net_Slist_Update;
471 g_Net_Slist_Pulse();
472 end;
473 end;
476 procedure MH_ProcessFirstSpawn (C: pTNetClient);
477 var
478 plr: TPlayer;
479 begin
480 if not C.WaitForFirstSpawn then exit;
481 plr := g_Player_Get(C^.Player);
482 if not assigned(plr) then exit;
483 g_Net_Slist_ServerPlayerComes();
484 e_LogWritefln('*** client #%u (cid #%u) first spawn', [C.ID, C.Player]);
485 C.WaitForFirstSpawn := false;
486 plr.FNoRespawn := false;
487 plr.FWantsInGame := true; // TODO: look into this later
489 if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then
490 begin
491 plr.Spectate;
492 MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, 0, 'N', C.ID);
493 end
494 else
495 begin
496 plr.Respawn(False);
497 if gLMSRespawn > LMS_RESPAWN_NONE then
498 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime, 'N', C.ID);
499 end;
500 end;
503 procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
504 begin
505 //e_LogWritefln('*** client #%u (cid #%u) full state request', [C.ID, C.Player]);
506 if gGameOn then
507 begin
508 MH_SEND_Everything((C^.State = NET_STATE_AUTH), C^.ID)
509 end
510 else
511 begin
512 C^.RequestedFullUpdate := True;
513 end;
514 end;
516 // PLAYER
518 function MH_RECV_PlayerPos(C: pTNetClient; var M: TMsg): Word;
519 var
520 Dir, i: Byte;
521 WeaponAct: Byte;
522 WeaponSelect: Word;
523 PID: Word;
524 kByte: Word;
525 Pl: TPlayer;
526 GT: LongWord;
527 begin
528 Result := 0;
529 if not gGameOn then Exit;
531 GT := M.ReadLongWord();
532 PID := C^.Player;
533 Pl := g_Player_Get(PID);
534 if Pl = nil then
535 Exit;
537 if (GT > gTime + NET_MAX_DIFFTIME) or (GT < Pl.NetTime) then Exit;
539 with Pl do
540 begin
541 NetTime := GT;
542 kByte := M.ReadWord();
543 Dir := M.ReadByte();
544 WeaponAct := M.ReadByte();
545 WeaponSelect := M.ReadWord();
546 //e_WriteLog(Format('R:ws=%d', [WeaponSelect]), MSG_WARNING);
547 if Direction <> TDirection(Dir) then
548 JustTeleported := False;
550 SetDirection(TDirection(Dir));
551 ReleaseKeys;
553 if kByte = NET_KEY_CHAT then
554 begin
555 PressKey(KEY_CHAT, 10000);
556 Exit;
557 end;
559 if LongBool(kByte and NET_KEY_LEFT) then PressKey(KEY_LEFT, 10000);
560 if LongBool(kByte and NET_KEY_RIGHT) then PressKey(KEY_RIGHT, 10000);
561 if LongBool(kByte and NET_KEY_UP) then PressKey(KEY_UP, 10000);
562 if LongBool(kByte and NET_KEY_DOWN) then PressKey(KEY_DOWN, 10000);
563 if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000);
564 if LongBool(kByte and NET_KEY_FIRE) then PressKey(KEY_FIRE, 10000);
565 if LongBool(kByte and NET_KEY_OPEN) then PressKey(KEY_OPEN, 10000);
567 for i := 0 to 7 do
568 begin
569 if (WeaponAct and Byte(1 shl i)) <> 0 then
570 begin
571 //e_WriteLog(Format(' R:wn=%d', [i]), MSG_WARNING);
572 ProcessWeaponAction(i);
573 end;
574 end;
576 for i := 0 to 15 do
577 begin
578 if (WeaponSelect and Word(1 shl i)) <> 0 then
579 begin
580 //e_WriteLog(Format(' R:wn=%d', [i]), MSG_WARNING);
581 QueueWeaponSwitch(i);
582 end;
583 end;
584 end;
586 // MH_SEND_PlayerPos(False, PID, C^.ID);
587 end;
589 procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg);
590 var
591 CheatKind: Byte;
592 Pl: TPlayer;
593 begin
594 Pl := g_Player_Get(C^.Player);
595 if Pl = nil then Exit;
597 CheatKind := M.ReadByte();
599 case CheatKind of
600 NET_CHEAT_SUICIDE:
601 Pl.Damage(SUICIDE_DAMAGE, Pl.UID, 0, 0, HIT_SELF);
602 NET_CHEAT_SPECTATE:
603 begin
604 if Pl.FSpectator then
605 begin
606 if (gGameSettings.MaxLives = 0) or (gLMSRespawn > LMS_RESPAWN_NONE) then
607 Pl.Respawn(False)
608 else
609 MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, Pl.UID);
610 end
611 else
612 Pl.Spectate;
613 end;
614 NET_CHEAT_READY:
615 begin
616 if gState <> STATE_INTERCUSTOM then Exit;
617 Pl.FReady := not Pl.FReady;
618 if Pl.FReady then
619 begin
620 MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'Y');
621 Inc(gInterReadyCount);
622 end
623 else
624 begin
625 MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'N');
626 Dec(gInterReadyCount);
627 end;
628 end;
629 NET_CHEAT_DROPFLAG:
630 Pl.TryDropFlag();
631 end;
632 end;
634 procedure MH_RECV_PlayerSettings(C: pTNetClient; var M: TMsg);
635 var
636 TmpName: string;
637 TmpModel: string;
638 TmpColor: TRGB;
639 TmpTeam: Byte;
640 Pl: TPlayer;
641 begin
642 TmpName := M.ReadString();
643 TmpModel := M.ReadString();
644 TmpColor.R := M.ReadByte();
645 TmpColor.G := M.ReadByte();
646 TmpColor.B := M.ReadByte();
647 TmpTeam := M.ReadByte();
649 Pl := g_Player_Get(C^.Player);
650 if Pl = nil then Exit;
652 if (gGameSettings.GameMode in [GM_TDM, GM_CTF]) and (Pl.Team <> TmpTeam) then
653 Pl.SwitchTeam
654 else
655 Pl.SetColor(TmpColor);
657 if Pl.Name <> TmpName then
658 begin
659 g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True);
660 Pl.Name := TmpName;
661 end;
663 if TmpModel <> Pl.Model.Name then
664 Pl.SetModel(TmpModel);
666 MH_SEND_PlayerSettings(Pl.UID, TmpModel);
667 end;
669 // RCON
671 procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg);
672 var
673 Pwd: string;
674 begin
675 Pwd := M.ReadString();
676 if not NetAllowRCON then Exit;
677 if Pwd = NetRCONPassword then
678 begin
679 C^.RCONAuth := True;
680 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_PWGOOD, 'N', C^.ID);
681 end
682 else
683 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_PWBAD, 'N', C^.ID);
684 end;
686 procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg);
687 var
688 Cmd: string;
689 begin
690 Cmd := M.ReadString();
691 if not NetAllowRCON then Exit;
692 if not C^.RCONAuth then
693 begin
694 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_NOAUTH, 'N', C^.ID);
695 Exit;
696 end;
697 g_Console_Process(Cmd);
698 end;
700 // MISC
702 procedure MH_RECV_Vote(C: pTNetClient; var M: TMsg);
703 var
704 Start: Boolean;
705 Name, Command: string;
706 Need: Integer;
707 Pl: TPlayer;
708 begin
709 Start := M.ReadByte() <> 0;
710 Command := M.ReadString();
712 Pl := g_Player_Get(C^.Player);
713 if Pl = nil then Exit;
714 Name := Pl.Name;
716 if Start then
717 begin
718 if not g_Console_CommandBlacklisted(Command) then
719 g_Game_StartVote(Command, Name);
720 end
721 else if gVoteInProgress then
722 begin
723 if (gPlayer1 <> nil) or (gPlayer2 <> nil) then
724 Need := Floor((NetClientCount+1)/2.0) + 1
725 else
726 Need := Floor(NetClientCount/2.0) + 1;
727 if C^.Voted then
728 begin
729 Dec(gVoteCount);
730 C^.Voted := False;
731 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_REVOKED], [Name, gVoteCount, Need]), True);
732 MH_SEND_VoteEvent(NET_VE_REVOKE, Name, 'a', gVoteCount, Need);
733 end
734 else
735 begin
736 Inc(gVoteCount);
737 C^.Voted := True;
738 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Name, gVoteCount, Need]), True);
739 MH_SEND_VoteEvent(NET_VE_VOTE, Name, 'a', gVoteCount, Need);
740 g_Game_CheckVote;
741 end;
742 end;
743 end;
745 // GAME (SEND)
747 procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
749 function sendItemRespawn (it: PItem): Boolean;
750 begin
751 result := false; // don't stop
752 MH_SEND_ItemSpawn(True, it.myid, ID);
753 end;
755 function sendMonSpawn (mon: TMonster): Boolean;
756 begin
757 result := false; // don't stop
758 MH_SEND_MonsterSpawn(mon.UID, ID);
759 end;
761 function sendPanelState (pan: TPanel): Boolean;
762 begin
763 result := false; // don't stop
764 MH_SEND_PanelState(pan.guid, ID); // anyway, to sync mplats
765 if (pan.CanChangeTexture) then MH_SEND_PanelTexture(pan.guid, pan.LastAnimLoop, ID);
766 end;
768 var
769 I: Integer;
770 begin
771 if (ID >= 0) and (ID < length(NetClients)) then
772 begin
773 e_LogWritefln('*** client #%u (cid #%u) will get everything', [ID, NetClients[ID].Player]);
774 MH_ProcessFirstSpawn(@NetClients[ID]);
775 end;
777 if gPlayers <> nil then
778 begin
779 for I := Low(gPlayers) to High(gPlayers) do
780 begin
781 if gPlayers[I] <> nil then
782 begin
783 if CreatePlayers then MH_SEND_PlayerCreate(gPlayers[I].UID, ID);
784 MH_SEND_PlayerPos(True, gPlayers[I].UID, ID);
785 MH_SEND_PlayerStats(gPlayers[I].UID, ID);
787 if (gPlayers[I].Flag <> FLAG_NONE) and (gGameSettings.GameMode = GM_CTF) then
788 begin
789 MH_SEND_FlagEvent(FLAG_STATE_CAPTURED, gPlayers[I].Flag, gPlayers[I].UID, True, ID);
790 end;
791 end;
792 end;
793 end;
795 g_Items_ForEachAlive(sendItemRespawn, true); // backwards
796 g_Mons_ForEach(sendMonSpawn);
797 g_Map_ForEachPanel(sendPanelState);
799 if gTriggers <> nil then
800 begin
801 for I := Low(gTriggers) to High(gTriggers) do
802 begin
803 if gTriggers[I].TriggerType = TRIGGER_SOUND then
804 begin
805 MH_SEND_TriggerSound(gTriggers[I], ID);
806 end;
807 end;
808 end;
810 if Shots <> nil then
811 begin
812 for I := Low(Shots) to High(Shots) do
813 begin
814 if Shots[i].ShotType in [6, 7, 8] then
815 begin
816 MH_SEND_CreateShot(i, ID);
817 end;
818 end;
819 end;
821 MH_SEND_TriggerMusic(ID);
823 MH_SEND_GameStats(ID);
824 MH_SEND_CoopStats(ID);
826 if gGameSettings.GameMode = GM_CTF then
827 begin
828 if gFlags[FLAG_RED].State <> FLAG_STATE_CAPTURED then MH_SEND_FlagEvent(gFlags[FLAG_RED].State, FLAG_RED, 0, True, ID);
829 if gFlags[FLAG_BLUE].State <> FLAG_STATE_CAPTURED then MH_SEND_FlagEvent(gFlags[FLAG_BLUE].State, FLAG_BLUE, 0, True, ID);
830 end;
832 if CreatePlayers and (ID >= 0) then NetClients[ID].State := NET_STATE_GAME;
834 g_Net_Flush();
835 end;
837 procedure MH_SEND_Info(ID: Byte);
838 begin
839 NetOut.Clear();
841 NetOut.Write(Byte(NET_MSG_INFO));
842 NetOut.Write(ID);
843 NetOut.Write(NetClients[ID].Player);
844 NetOut.Write(ExtractFileName(gGameSettings.WAD));
845 NetOut.Write(g_ExtractFileName(gMapInfo.Map));
846 NetOut.Write(gWADHash);
847 NetOut.Write(gGameSettings.GameMode);
848 NetOut.Write(gGameSettings.GoalLimit);
849 NetOut.Write(gGameSettings.TimeLimit);
850 NetOut.Write(gGameSettings.MaxLives);
851 NetOut.Write(gGameSettings.Options);
852 NetOut.Write(gTime);
854 g_Net_Host_Send(ID, True, NET_CHAN_SERVICE);
855 end;
857 procedure MH_SEND_Chat(Txt: string; Mode: Byte; ID: Integer = NET_EVERYONE);
858 var
859 Name: string;
860 i: Integer;
861 Team: Byte;
862 begin
863 if (Mode = NET_CHAT_TEAM) and (not gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
864 Mode := NET_CHAT_PLAYER;
866 Team := 0;
867 if (Mode = NET_CHAT_TEAM) then
868 begin
869 for i := Low(gPlayers) to High(gPlayers) do
870 if (gPlayers[i] <> nil) and (gPlayers[i].FClientID >= 0) and
871 (gPlayers[i].Team = ID) then
872 begin
873 NetOut.Write(Byte(NET_MSG_CHAT));
874 NetOut.Write(Txt);
875 NetOut.Write(Mode);
876 g_Net_Host_Send(gPlayers[i].FClientID, True, NET_CHAN_CHAT);
877 end;
878 Team := ID;
879 ID := NET_EVERYONE;
880 end
881 else
882 begin
883 NetOut.Write(Byte(NET_MSG_CHAT));
884 NetOut.Write(Txt);
885 NetOut.Write(Mode);
886 g_Net_Host_Send(ID, True, NET_CHAN_CHAT);
887 end;
889 if Mode = NET_CHAT_SYSTEM then
890 Exit;
892 if ID = NET_EVERYONE then
893 begin
894 if Mode = NET_CHAT_PLAYER then
895 begin
896 g_Console_Add(Txt, True);
897 e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
898 g_Game_ChatSound(b_Text_Unformat(Txt));
899 end
900 else
901 if Mode = NET_CHAT_TEAM then
902 if gPlayer1 <> nil then
903 begin
904 if (gPlayer1.Team = TEAM_RED) and (Team = TEAM_RED) then
905 begin
906 g_Console_Add(#18'[Team] '#2 + Txt, True);
907 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
908 g_Game_ChatSound(b_Text_Unformat(Txt));
909 end
910 else if (gPlayer1.Team = TEAM_BLUE) and (Team = TEAM_BLUE) then
911 begin
912 g_Console_Add(#20'[Team] '#2 + Txt, True);
913 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
914 g_Game_ChatSound(b_Text_Unformat(Txt));
915 end;
916 end;
917 end
918 else
919 begin
920 Name := g_Net_ClientName_ByID(ID);
921 g_Console_Add('-> ' + Name + ': ' + Txt, True);
922 e_WriteLog('[Tell ' + Name + '] ' + b_Text_Unformat(Txt), TMsgType.Notify);
923 g_Game_ChatSound(b_Text_Unformat(Txt), False);
924 end;
925 end;
927 procedure MH_SEND_Effect(X, Y: Integer; Ang: SmallInt; Kind: Byte; ID: Integer = NET_EVERYONE);
928 begin
929 NetOut.Write(Byte(NET_MSG_GFX));
930 NetOut.Write(Kind);
931 NetOut.Write(X);
932 NetOut.Write(Y);
933 NetOut.Write(Ang);
935 g_Net_Host_Send(ID, False, NET_CHAN_GAME);
936 end;
938 procedure MH_SEND_Sound(X, Y: Integer; Name: string; Pos: Boolean = True; ID: Integer = NET_EVERYONE);
939 begin
940 NetOut.Write(Byte(NET_MSG_SND));
941 NetOut.Write(Name);
942 if Pos then
943 begin
944 NetOut.Write(Byte(1));
945 NetOut.Write(X);
946 NetOut.Write(Y);
947 end
948 else
949 NetOut.Write(Byte(0));
951 g_Net_Host_Send(ID, False, NET_CHAN_GAME);
952 end;
954 procedure MH_SEND_CreateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
955 begin
956 if (Shots = nil) or (Proj < 0) or (Proj > High(Shots)) then Exit;
958 NetOut.Write(Byte(NET_MSG_SHADD));
959 NetOut.Write(Proj);
960 NetOut.Write(Shots[Proj].ShotType);
961 NetOut.Write(Shots[Proj].Target);
962 NetOut.Write(Shots[Proj].SpawnerUID);
963 NetOut.Write(Shots[Proj].Timeout);
964 NetOut.Write(Shots[Proj].Obj.X);
965 NetOut.Write(Shots[Proj].Obj.Y);
966 NetOut.Write(Shots[Proj].Obj.Vel.X);
967 NetOut.Write(Shots[Proj].Obj.Vel.Y);
969 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
970 end;
972 procedure MH_SEND_UpdateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
973 begin
974 if (Shots = nil) or (Proj < 0) or (Proj > High(Shots)) then Exit;
976 NetOut.Write(Byte(NET_MSG_SHPOS));
977 NetOut.Write(Proj);
978 NetOut.Write(Shots[Proj].Obj.X);
979 NetOut.Write(Shots[Proj].Obj.Y);
980 NetOut.Write(Shots[Proj].Obj.Vel.X);
981 NetOut.Write(Shots[Proj].Obj.Vel.Y);
983 g_Net_Host_Send(ID, False, NET_CHAN_SHOTS);
984 end;
986 procedure MH_Send_DeleteShot(Proj: LongInt; X, Y: LongInt; Loud: Boolean = True; ID: Integer = NET_EVERYONE);
987 begin
988 NetOut.Write(Byte(NET_MSG_SHDEL));
989 NetOut.Write(Proj);
990 NetOut.Write(Byte(Loud));
991 NetOut.Write(X);
992 NetOut.Write(Y);
994 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
995 end;
997 procedure MH_SEND_GameStats(ID: Integer = NET_EVERYONE);
998 begin
999 NetOut.Write(Byte(NET_MSG_SCORE));
1000 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1001 begin
1002 NetOut.Write(gTeamStat[TEAM_RED].Goals);
1003 NetOut.Write(gTeamStat[TEAM_BLUE].Goals);
1004 end
1005 else
1006 if gGameSettings.GameMode = GM_COOP then
1007 begin
1008 NetOut.Write(gCoopMonstersKilled);
1009 NetOut.Write(gCoopSecretsFound);
1010 end;
1012 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1013 end;
1015 procedure MH_SEND_CoopStats(ID: Integer = NET_EVERYONE);
1016 begin
1017 NetOut.Write(Byte(NET_MSG_COOP));
1018 NetOut.Write(gTotalMonsters);
1019 NetOut.Write(gSecretsCount);
1020 NetOut.Write(gCoopTotalMonstersKilled);
1021 NetOut.Write(gCoopTotalSecretsFound);
1022 NetOut.Write(gCoopTotalMonsters);
1023 NetOut.Write(gCoopTotalSecrets);
1024 end;
1026 procedure MH_SEND_GameEvent(EvType: Byte; EvNum: Integer = 0; EvStr: string = 'N'; ID: Integer = NET_EVERYONE);
1027 begin
1028 NetOut.Write(Byte(NET_MSG_GEVENT));
1029 NetOut.Write(EvType);
1030 NetOut.Write(EvNum);
1031 NetOut.Write(EvStr);
1032 NetOut.Write(Byte(gLastMap));
1033 NetOut.Write(gTime);
1034 if (EvType = NET_EV_MAPSTART) and isWadPath(EvStr) then
1035 begin
1036 NetOut.Write(Byte(1));
1037 NetOut.Write(gWADHash);
1038 end else
1039 NetOut.Write(Byte(0));
1041 g_Net_Host_Send(ID, True, NET_CHAN_SERVICE);
1042 end;
1044 procedure MH_SEND_FlagEvent(EvType: Byte; Flag: Byte; PID: Word; Quiet: Boolean = False; ID: Integer = NET_EVERYONE);
1045 begin
1046 NetOut.Write(Byte(NET_MSG_FLAG));
1047 NetOut.Write(EvType);
1048 NetOut.Write(Flag);
1049 NetOut.Write(Byte(Quiet));
1050 NetOut.Write(PID);
1051 NetOut.Write(gFlags[Flag].State);
1052 NetOut.Write(gFlags[Flag].CaptureTime);
1053 NetOut.Write(gFlags[Flag].Obj.X);
1054 NetOut.Write(gFlags[Flag].Obj.Y);
1055 NetOut.Write(gFlags[Flag].Obj.Vel.X);
1056 NetOut.Write(gFlags[Flag].Obj.Vel.Y);
1058 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1059 end;
1061 procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE);
1062 begin
1063 NetOut.Write(Byte(NET_MSG_GSET));
1064 NetOut.Write(gGameSettings.GameMode);
1065 NetOut.Write(gGameSettings.GoalLimit);
1066 NetOut.Write(gGameSettings.TimeLimit);
1067 NetOut.Write(gGameSettings.MaxLives);
1068 NetOut.Write(gGameSettings.Options);
1070 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1071 end;
1073 // PLAYER (SEND)
1075 procedure MH_SEND_PlayerCreate(PID: Word; ID: Integer = NET_EVERYONE);
1076 var
1077 P: TPlayer;
1078 begin
1079 P := g_Player_Get(PID);
1080 if P = nil then Exit;
1082 NetOut.Write(Byte(NET_MSG_PLR));
1083 NetOut.Write(PID);
1084 NetOut.Write(P.Name);
1086 NetOut.Write(P.FActualModelName);
1087 NetOut.Write(P.FColor.R);
1088 NetOut.Write(P.FColor.G);
1089 NetOut.Write(P.FColor.B);
1090 NetOut.Write(P.Team);
1092 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT)
1093 end;
1095 procedure MH_SEND_PlayerPos(Reliable: Boolean; PID: Word; ID: Integer = NET_EVERYONE);
1096 var
1097 kByte: Word;
1098 Pl: TPlayer;
1099 begin
1100 Pl := g_Player_Get(PID);
1101 if Pl = nil then Exit;
1102 if Pl.FDummy then Exit;
1104 NetOut.Write(Byte(NET_MSG_PLRPOS));
1105 NetOut.Write(gTime);
1106 NetOut.Write(PID);
1108 kByte := 0;
1110 with Pl do
1111 begin
1112 NetOut.Write(FPing);
1113 NetOut.Write(FLoss);
1114 if IsKeyPressed(KEY_CHAT) then
1115 kByte := NET_KEY_CHAT
1116 else
1117 begin
1118 if IsKeyPressed(KEY_LEFT) then kByte := kByte or NET_KEY_LEFT;
1119 if IsKeyPressed(KEY_RIGHT) then kByte := kByte or NET_KEY_RIGHT;
1120 if IsKeyPressed(KEY_UP) then kByte := kByte or NET_KEY_UP;
1121 if IsKeyPressed(KEY_DOWN) then kByte := kByte or NET_KEY_DOWN;
1122 if IsKeyPressed(KEY_JUMP) then kByte := kByte or NET_KEY_JUMP;
1123 end;
1125 if JustTeleported then kByte := kByte or NET_KEY_FORCEDIR;
1127 NetOut.Write(kByte);
1128 if Direction = TDirection.D_LEFT then NetOut.Write(Byte(0)) else NetOut.Write(Byte(1));
1129 NetOut.Write(GameX);
1130 NetOut.Write(GameY);
1131 NetOut.Write(GameVelX);
1132 NetOut.Write(GameVelY);
1133 NetOut.Write(GameAccelX);
1134 NetOut.Write(GameAccelY);
1135 end;
1137 g_Net_Host_Send(ID, Reliable, NET_CHAN_PLAYERPOS);
1138 end;
1140 procedure MH_SEND_PlayerStats(PID: Word; ID: Integer = NET_EVERYONE);
1141 var
1142 P: TPlayer;
1143 I: Integer;
1144 begin
1145 P := g_Player_Get(PID);
1146 if P = nil then Exit;
1148 NetOut.Write(Byte(NET_MSG_PLRSTA));
1149 NetOut.Write(PID);
1151 with P do
1152 begin
1153 NetOut.Write(Byte(alive));
1154 NetOut.Write(Byte(GodMode));
1155 NetOut.Write(Health);
1156 NetOut.Write(Armor);
1157 NetOut.Write(Air);
1158 NetOut.Write(JetFuel);
1159 NetOut.Write(Lives);
1160 NetOut.Write(Team);
1162 for I := WP_FIRST to WP_LAST do
1163 NetOut.Write(Byte(FWeapon[I]));
1165 for I := A_BULLETS to A_HIGH do
1166 NetOut.Write(FAmmo[I]);
1168 for I := A_BULLETS to A_HIGH do
1169 NetOut.Write(FMaxAmmo[I]);
1171 for I := MR_SUIT to MR_MAX do
1172 NetOut.Write(LongWord(FMegaRulez[I]));
1174 NetOut.Write(Byte(R_ITEM_BACKPACK in FRulez));
1175 NetOut.Write(Byte(R_KEY_RED in FRulez));
1176 NetOut.Write(Byte(R_KEY_GREEN in FRulez));
1177 NetOut.Write(Byte(R_KEY_BLUE in FRulez));
1178 NetOut.Write(Byte(R_BERSERK in FRulez));
1180 NetOut.Write(Frags);
1181 NetOut.Write(Death);
1183 NetOut.Write(CurrWeap);
1185 NetOut.Write(Byte(FSpectator));
1186 NetOut.Write(Byte(FGhost));
1187 NetOut.Write(Byte(FPhysics));
1188 NetOut.Write(Byte(FNoRespawn));
1189 NetOut.Write(Byte(FJetpack));
1190 NetOut.Write(FFireTime);
1191 NetOut.Write(Byte(FFlaming));
1192 NetOut.Write(FSpawnInvul);
1193 end;
1195 g_Net_Host_Send(ID, True, NET_CHAN_PLAYER);
1196 end;
1198 procedure MH_SEND_PlayerDamage(PID: Word; Kind: Byte; Attacker, Value: Word; VX, VY: Integer; ID: Integer = NET_EVERYONE);
1199 begin
1200 NetOut.Write(Byte(NET_MSG_PLRDMG));
1201 NetOut.Write(PID);
1202 NetOut.Write(Kind);
1203 NetOut.Write(Attacker);
1204 NetOut.Write(Value);
1205 NetOut.Write(VX);
1206 NetOut.Write(VY);
1208 g_Net_Host_Send(ID, False, NET_CHAN_PLAYER);
1209 end;
1211 procedure MH_SEND_PlayerDeath(PID: Word; KillType, DeathType: Byte; Attacker: Word; ID: Integer = NET_EVERYONE);
1212 begin
1213 NetOut.Write(Byte(NET_MSG_PLRDIE));
1214 NetOut.Write(PID);
1215 NetOut.Write(KillType);
1216 NetOut.Write(DeathType);
1217 NetOut.Write(Attacker);
1219 g_Net_Host_Send(ID, True, NET_CHAN_PLAYER);
1220 end;
1222 procedure MH_SEND_PlayerFire(PID: Word; Weapon: Byte; X, Y, AX, AY: Integer; ShotID: Integer = -1; ID: Integer = NET_EVERYONE);
1223 begin
1224 NetOut.Write(Byte(NET_MSG_PLRFIRE));
1225 NetOut.Write(PID);
1226 NetOut.Write(Weapon);
1227 NetOut.Write(X);
1228 NetOut.Write(Y);
1229 NetOut.Write(AX);
1230 NetOut.Write(AY);
1231 NetOut.Write(ShotID);
1233 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
1234 end;
1236 procedure MH_SEND_PlayerDelete(PID: Word; ID: Integer = NET_EVERYONE);
1237 begin
1238 NetOut.Write(Byte(NET_MSG_PLRDEL));
1239 NetOut.Write(PID);
1241 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1242 end;
1244 procedure MH_SEND_PlayerSettings(PID: Word; Mdl: string = ''; ID: Integer = NET_EVERYONE);
1245 var
1246 Pl: TPlayer;
1247 begin
1248 Pl := g_Player_Get(PID);
1249 if Pl = nil then Exit;
1251 NetOut.Write(Byte(NET_MSG_PLRSET));
1252 NetOut.Write(PID);
1253 NetOut.Write(Pl.Name);
1254 if Mdl = '' then
1255 NetOut.Write(Pl.Model.Name)
1256 else
1257 NetOut.Write(Mdl);
1258 NetOut.Write(Pl.FColor.R);
1259 NetOut.Write(Pl.FColor.G);
1260 NetOut.Write(Pl.FColor.B);
1261 NetOut.Write(Pl.Team);
1263 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1264 end;
1266 // ITEM (SEND)
1268 procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
1269 var
1270 it: PItem;
1271 tt: Byte;
1272 begin
1273 it := g_Items_ByIdx(IID);
1275 NetOut.Write(Byte(NET_MSG_ISPAWN));
1276 NetOut.Write(IID);
1277 NetOut.Write(Byte(Quiet));
1278 tt := it.ItemType;
1279 if it.dropped then tt := tt or $80;
1280 NetOut.Write(tt);
1281 NetOut.Write(Byte(it.Fall));
1282 NetOut.Write(Byte(it.Respawnable));
1283 NetOut.Write(it.Obj.X);
1284 NetOut.Write(it.Obj.Y);
1285 NetOut.Write(it.Obj.Vel.X);
1286 NetOut.Write(it.Obj.Vel.Y);
1288 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1289 end;
1291 procedure MH_SEND_ItemDestroy(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
1292 begin
1293 NetOut.Write(Byte(NET_MSG_IDEL));
1294 NetOut.Write(IID);
1295 NetOut.Write(Byte(Quiet));
1297 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1298 end;
1300 // PANEL
1302 procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE);
1303 var
1304 TP: TPanel;
1305 begin
1306 TP := g_Map_PanelByGUID(PGUID);
1307 if (TP = nil) then exit;
1309 with TP do
1310 begin
1311 NetOut.Write(Byte(NET_MSG_PTEX));
1312 NetOut.Write(LongWord(PGUID));
1313 NetOut.Write(FCurTexture);
1314 NetOut.Write(FCurFrame);
1315 NetOut.Write(FCurFrameCount);
1316 NetOut.Write(AnimLoop);
1317 end;
1319 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1320 end;
1322 procedure MH_SEND_PanelState(PGUID: Integer; ID: Integer = NET_EVERYONE);
1323 var
1324 TP: TPanel;
1325 mpflags: Byte = 0;
1326 begin
1327 TP := g_Map_PanelByGUID(PGUID);
1328 if (TP = nil) then exit;
1330 NetOut.Write(Byte(NET_MSG_PSTATE));
1331 NetOut.Write(LongWord(PGUID));
1332 NetOut.Write(Byte(TP.Enabled));
1333 NetOut.Write(TP.LiftType);
1334 NetOut.Write(TP.X);
1335 NetOut.Write(TP.Y);
1336 NetOut.Write(Word(TP.Width));
1337 NetOut.Write(Word(TP.Height));
1338 // mplats
1339 NetOut.Write(LongInt(TP.movingSpeedX));
1340 NetOut.Write(LongInt(TP.movingSpeedY));
1341 NetOut.Write(LongInt(TP.movingStartX));
1342 NetOut.Write(LongInt(TP.movingStartY));
1343 NetOut.Write(LongInt(TP.movingEndX));
1344 NetOut.Write(LongInt(TP.movingEndY));
1345 NetOut.Write(LongInt(TP.sizeSpeedX));
1346 NetOut.Write(LongInt(TP.sizeSpeedY));
1347 NetOut.Write(LongInt(TP.sizeEndX));
1348 NetOut.Write(LongInt(TP.sizeEndY));
1349 if TP.movingActive then mpflags := mpflags or 1;
1350 if TP.moveOnce then mpflags := mpflags or 2;
1351 NetOut.Write(Byte(mpflags));
1353 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1354 end;
1356 // TRIGGER
1358 procedure MH_SEND_TriggerSound(var T: TTrigger; ID: Integer = NET_EVERYONE);
1359 begin
1360 if gTriggers = nil then Exit;
1361 if T.Sound = nil then Exit;
1363 NetOut.Write(Byte(NET_MSG_TSOUND));
1364 NetOut.Write(T.ClientID);
1365 NetOut.Write(Byte(T.Sound.IsPlaying));
1366 NetOut.Write(LongWord(T.Sound.GetPosition));
1367 NetOut.Write(T.SoundPlayCount);
1369 g_Net_Host_Send(ID, True);
1370 end;
1372 procedure MH_SEND_TriggerMusic(ID: Integer = NET_EVERYONE);
1373 begin
1374 NetOut.Write(Byte(NET_MSG_TMUSIC));
1375 NetOut.Write(gMusic.Name);
1376 NetOut.Write(Byte(gMusic.IsPlaying));
1377 NetOut.Write(LongWord(gMusic.GetPosition));
1378 NetOut.Write(Byte(gMusic.SpecPause or gMusic.IsPaused));
1380 g_Net_Host_Send(ID, True);
1381 end;
1383 // MONSTER
1385 procedure MH_SEND_MonsterSpawn(UID: Word; ID: Integer = NET_EVERYONE);
1386 var
1387 M: TMonster;
1388 begin
1389 M := g_Monsters_ByUID(UID);
1390 if M = nil then
1391 Exit;
1393 with M do
1394 begin
1395 NetOut.Write(Byte(NET_MSG_MSPAWN));
1396 NetOut.Write(UID);
1397 NetOut.Write(MonsterType);
1398 NetOut.Write(MonsterState);
1399 NetOut.Write(MonsterAnim);
1400 NetOut.Write(MonsterTargetUID);
1401 NetOut.Write(MonsterTargetTime);
1402 NetOut.Write(MonsterBehaviour);
1403 NetOut.Write(MonsterSleep);
1404 NetOut.Write(MonsterHealth);
1405 NetOut.Write(MonsterAmmo);
1406 NetOut.Write(GameX);
1407 NetOut.Write(GameY);
1408 NetOut.Write(GameVelX);
1409 NetOut.Write(GameVelY);
1410 NetOut.Write(Byte(GameDirection));
1411 end;
1413 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1414 end;
1416 procedure MH_SEND_MonsterPos(UID: Word; ID: Integer = NET_EVERYONE);
1417 var
1418 M: TMonster;
1419 begin
1420 M := g_Monsters_ByUID(UID);
1421 if M = nil then Exit;
1423 NetOut.Write(Byte(NET_MSG_MPOS));
1424 NetOut.Write(UID);
1426 with M do
1427 begin
1428 NetOut.Write(GameX);
1429 NetOut.Write(GameY);
1430 NetOut.Write(GameVelX);
1431 NetOut.Write(GameVelY);
1432 NetOut.Write(Byte(GameDirection));
1433 end;
1435 g_Net_Host_Send(ID, False, NET_CHAN_MONSTERPOS);
1436 end;
1438 procedure MH_SEND_MonsterState(UID: Word; ForcedAnim: Byte = 255; ID: Integer = NET_EVERYONE);
1439 var
1440 M: TMonster;
1441 begin
1442 M := g_Monsters_ByUID(UID);
1443 if M = nil then Exit;
1445 NetOut.Write(Byte(NET_MSG_MSTATE));
1446 NetOut.Write(UID);
1448 with M do
1449 begin
1450 NetOut.Write(MonsterState);
1451 NetOut.Write(ForcedAnim);
1452 NetOut.Write(MonsterTargetUID);
1453 NetOut.Write(MonsterTargetTime);
1454 NetOut.Write(MonsterSleep);
1455 NetOut.Write(MonsterHealth);
1456 NetOut.Write(MonsterAmmo);
1457 NetOut.Write(MonsterPain);
1458 NetOut.Write(Byte(AnimIsReverse));
1459 NetOut.Write(FFireTime);
1460 end;
1462 g_Net_Host_Send(ID, True, NET_CHAN_MONSTER);
1463 end;
1465 procedure MH_SEND_MonsterShot(UID: Word; X, Y, VX, VY: Integer; ID: Integer = NET_EVERYONE);
1466 begin
1467 NetOut.Write(Byte(NET_MSG_MSHOT));
1468 NetOut.Write(UID);
1469 NetOut.Write(X);
1470 NetOut.Write(Y);
1471 NetOut.Write(VX);
1472 NetOut.Write(VY);
1474 g_Net_Host_Send(ID, True, NET_CHAN_MONSTER);
1475 end;
1477 procedure MH_SEND_MonsterDelete(UID: Word; ID: Integer = NET_EVERYONE);
1478 var
1479 M: TMonster;
1480 begin
1481 M := g_Monsters_ByUID(UID);
1482 if M = nil then Exit;
1484 NetOut.Write(Byte(NET_MSG_MDEL));
1485 NetOut.Write(UID);
1487 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1488 end;
1490 // MISC
1492 procedure MH_SEND_TimeSync(Time: LongWord; ID: Integer = NET_EVERYONE);
1493 begin
1494 NetOut.Write(Byte(NET_MSG_TIME_SYNC));
1495 NetOut.Write(Time);
1497 g_Net_Host_Send(ID, False, NET_CHAN_SERVICE);
1498 end;
1500 procedure MH_SEND_VoteEvent(EvType: Byte;
1501 StrArg1: string = 'a'; StrArg2: string = 'b';
1502 IntArg1: SmallInt = 0; IntArg2: SmallInt = 0;
1503 ID: Integer = NET_EVERYONE);
1504 begin
1505 NetOut.Write(Byte(NET_MSG_VOTE_EVENT));
1506 NetOut.Write(EvType);
1507 NetOut.Write(IntArg1);
1508 NetOut.Write(IntArg2);
1509 NetOut.Write(StrArg1);
1510 NetOut.Write(StrArg2);
1512 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1513 end;
1515 // CLIENT MESSAGES //
1517 // GAME
1519 procedure MC_RECV_Chat(var M: TMsg);
1520 var
1521 Txt: string;
1522 Mode: Byte;
1523 begin
1524 Txt := M.ReadString();
1525 Mode := M.ReadByte();
1527 if Mode <> NET_CHAT_SYSTEM then
1528 begin
1529 if NetDeafLevel = 0 then
1530 begin
1531 if Mode = NET_CHAT_PLAYER then
1532 begin
1533 g_Console_Add(Txt, True);
1534 e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1535 g_Game_ChatSound(b_Text_Unformat(Txt));
1536 end else
1537 if (Mode = NET_CHAT_TEAM) and (gPlayer1 <> nil) then
1538 begin
1539 if gPlayer1.Team = TEAM_RED then
1540 g_Console_Add(b_Text_Format('\r[Team] ') + Txt, True);
1541 if gPlayer1.Team = TEAM_BLUE then
1542 g_Console_Add(b_Text_Format('\b[Team] ') + Txt, True);
1543 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1544 g_Game_ChatSound(b_Text_Unformat(Txt));
1545 end;
1546 end;
1547 end else if (NetDeafLevel < 2) then
1548 g_Console_Add(Txt, True);
1549 end;
1551 procedure MC_RECV_Effect(var M: TMsg);
1552 var
1553 Kind: Byte;
1554 X, Y: Integer;
1555 Ang: SmallInt;
1556 Anim: TAnimation;
1557 ID: LongWord;
1558 begin
1559 if not gGameOn then Exit;
1560 Kind := M.ReadByte();
1561 X := M.ReadLongInt();
1562 Y := M.ReadLongInt();
1563 Ang := M.ReadSmallInt();
1565 case Kind of
1566 NET_GFX_SPARK:
1567 g_GFX_Spark(X, Y, 2 + Random(2), Ang, 0, 0);
1569 NET_GFX_TELE:
1570 begin
1571 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
1572 begin
1573 Anim := TAnimation.Create(ID, False, 3);
1574 g_GFX_OnceAnim(X, Y, Anim);
1575 Anim.Free();
1576 end;
1577 if Ang = 1 then
1578 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', X, Y);
1579 end;
1581 NET_GFX_EXPLODE:
1582 begin
1583 if g_Frames_Get(ID, 'FRAMES_EXPLODE_ROCKET') then
1584 begin
1585 Anim := TAnimation.Create(ID, False, 6);
1586 Anim.Blending := False;
1587 g_GFX_OnceAnim(X-64, Y-64, Anim);
1588 Anim.Free();
1589 end;
1590 if Ang = 1 then
1591 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', X, Y);
1592 end;
1594 NET_GFX_BFGEXPL:
1595 begin
1596 if g_Frames_Get(ID, 'FRAMES_EXPLODE_BFG') then
1597 begin
1598 Anim := TAnimation.Create(ID, False, 6);
1599 Anim.Blending := False;
1600 g_GFX_OnceAnim(X-64, Y-64, Anim);
1601 Anim.Free();
1602 end;
1603 if Ang = 1 then
1604 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', X, Y);
1605 end;
1607 NET_GFX_BFGHIT:
1608 begin
1609 if g_Frames_Get(ID, 'FRAMES_BFGHIT') then
1610 begin
1611 Anim := TAnimation.Create(ID, False, 4);
1612 g_GFX_OnceAnim(X-32, Y-32, Anim);
1613 Anim.Free();
1614 end;
1615 end;
1617 NET_GFX_FIRE:
1618 begin
1619 if g_Frames_Get(ID, 'FRAMES_FIRE') then
1620 begin
1621 Anim := TAnimation.Create(ID, False, 4);
1622 g_GFX_OnceAnim(X, Y, Anim);
1623 Anim.Free();
1624 end;
1625 if Ang = 1 then
1626 g_Sound_PlayExAt('SOUND_FIRE', X, Y);
1627 end;
1629 NET_GFX_RESPAWN:
1630 begin
1631 if g_Frames_Get(ID, 'FRAMES_ITEM_RESPAWN') then
1632 begin
1633 Anim := TAnimation.Create(ID, False, 4);
1634 g_GFX_OnceAnim(X, Y, Anim);
1635 Anim.Free();
1636 end;
1637 if Ang = 1 then
1638 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
1639 end;
1641 NET_GFX_SHELL1:
1642 g_Player_CreateShell(X, Y, 0, -2, SHELL_BULLET);
1644 NET_GFX_SHELL2:
1645 g_Player_CreateShell(X, Y, 0, -2, SHELL_SHELL);
1647 NET_GFX_SHELL3:
1648 begin
1649 g_Player_CreateShell(X, Y, 0, -2, SHELL_SHELL);
1650 g_Player_CreateShell(X, Y, 0, -2, SHELL_SHELL);
1651 end;
1652 end;
1653 end;
1655 procedure MC_RECV_Sound(var M: TMsg);
1656 var
1657 Name: string;
1658 X, Y: Integer;
1659 Pos: Boolean;
1660 begin
1661 Name := M.ReadString();
1662 Pos := M.ReadByte() <> 0;
1663 if Pos then
1664 begin
1665 X := M.ReadLongInt();
1666 Y := M.ReadLongInt();
1667 g_Sound_PlayExAt(Name, X, Y);
1668 end
1669 else
1670 g_Sound_PlayEx(Name);
1671 end;
1673 procedure MC_RECV_CreateShot(var M: TMsg);
1674 var
1675 I, X, Y, XV, YV: Integer;
1676 Timeout: LongWord;
1677 Target, Spawner: Word;
1678 ShType: Byte;
1679 begin
1680 I := M.ReadLongInt();
1681 ShType := M.ReadByte();
1682 Target := M.ReadWord();
1683 Spawner := M.ReadWord();
1684 Timeout := M.ReadLongWord();
1685 X := M.ReadLongInt();
1686 Y := M.ReadLongInt();
1687 XV := M.ReadLongInt();
1688 YV := M.ReadLongInt();
1690 I := g_Weapon_CreateShot(I, ShType, Spawner, Target, X, Y, XV, YV);
1691 if (Shots <> nil) and (I <= High(Shots)) then
1692 begin
1693 Shots[I].Timeout := Timeout;
1694 //Shots[I].Target := Target; // TODO: find a use for Target later
1695 end;
1696 end;
1698 procedure MC_RECV_UpdateShot(var M: TMsg);
1699 var
1700 I, TX, TY, TXV, TYV: Integer;
1701 begin
1702 I := M.ReadLongInt();
1703 TX := M.ReadLongInt();
1704 TY := M.ReadLongInt();
1705 TXV := M.ReadLongInt();
1706 TYV := M.ReadLongInt();
1708 if (Shots <> nil) and (I <= High(Shots)) then
1709 with (Shots[i]) do
1710 begin
1711 Obj.X := TX;
1712 Obj.Y := TY;
1713 Obj.Vel.X := TXV;
1714 Obj.Vel.Y := TYV;
1715 end;
1716 end;
1718 procedure MC_RECV_DeleteShot(var M: TMsg);
1719 var
1720 I, X, Y: Integer;
1721 L: Boolean;
1722 begin
1723 if not gGameOn then Exit;
1724 I := M.ReadLongInt();
1725 L := (M.ReadByte() <> 0);
1726 X := M.ReadLongInt();
1727 Y := M.ReadLongInt();
1729 g_Weapon_DestroyShot(I, X, Y, L);
1730 end;
1732 procedure MC_RECV_GameStats(var M: TMsg);
1733 begin
1734 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1735 begin
1736 gTeamStat[TEAM_RED].Goals := M.ReadSmallInt();
1737 gTeamStat[TEAM_BLUE].Goals := M.ReadSmallInt();
1738 end
1739 else
1740 if gGameSettings.GameMode = GM_COOP then
1741 begin
1742 gCoopMonstersKilled := M.ReadWord();
1743 gCoopSecretsFound := M.ReadWord();
1744 end;
1745 end;
1747 procedure MC_RECV_CoopStats(var M: TMsg);
1748 begin
1749 gTotalMonsters := M.ReadLongInt();
1750 gSecretsCount := M.ReadLongInt();
1751 gCoopTotalMonstersKilled := M.ReadWord();
1752 gCoopTotalSecretsFound := M.ReadWord();
1753 gCoopTotalMonsters := M.ReadWord();
1754 gCoopTotalSecrets := M.ReadWord();
1755 end;
1757 procedure MC_RECV_GameEvent(var M: TMsg);
1758 var
1759 EvType: Byte;
1760 EvNum: Integer;
1761 EvStr: string;
1762 EvTime: LongWord;
1763 BHash: Boolean;
1764 EvHash: TMD5Digest;
1765 pl: TPlayer;
1766 i1, i2: TStrings_Locale;
1767 pln: String;
1768 cnt: Byte;
1769 goodCmd: Boolean = true;
1770 begin
1771 FillChar(EvHash, Sizeof(EvHash), 0);
1772 EvType := M.ReadByte();
1773 EvNum := M.ReadLongInt();
1774 EvStr := M.ReadString();
1775 gLastMap := M.ReadByte() <> 0;
1776 if gLastMap and (gGameSettings.GameMode = GM_COOP) then gStatsOff := True;
1777 gStatsPressed := True;
1778 EvTime := M.ReadLongWord();
1779 BHash := M.ReadByte() <> 0;
1780 if BHash then
1781 EvHash := M.ReadMD5();
1783 gTime := EvTime;
1785 if (g_Res_received_map_start <> 0) then
1786 begin
1787 if (g_Res_received_map_start < 0) then exit;
1788 goodCmd := false;
1789 case EvType of
1790 NET_EV_MAPSTART: goodCmd := true;
1791 NET_EV_MAPEND: goodCmd := true;
1792 NET_EV_PLAYER_KICK: goodCmd := true;
1793 NET_EV_PLAYER_BAN: goodCmd := true;
1794 NET_EV_LMS_WARMUP: goodCmd := true;
1795 end;
1796 if not goodCmd then exit;
1797 end;
1799 case EvType of
1800 NET_EV_MAPSTART:
1801 begin
1802 if (g_Res_received_map_start <> 0) then
1803 begin
1804 g_Res_received_map_start := -1;
1805 end
1806 else
1807 begin
1808 gGameOn := False;
1809 g_Game_ClearLoading();
1810 g_Game_StopAllSounds(True);
1812 gSwitchGameMode := Byte(EvNum);
1813 gGameSettings.GameMode := gSwitchGameMode;
1815 gWADHash := EvHash;
1816 if not g_Game_StartMap(false{asMegawad}, EvStr, True) then
1817 begin
1818 if not isWadPath(EvStr) then
1819 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [gGameSettings.WAD + ':\' + EvStr]))
1820 else
1821 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [EvStr]));
1822 Exit;
1823 end;
1825 MC_SEND_FullStateRequest;
1826 end;
1827 end;
1829 NET_EV_MAPEND:
1830 begin
1831 gLMSRespawn := LMS_RESPAWN_NONE;
1832 gLMSRespawnTime := 0;
1833 if (g_Res_received_map_start <> 0) then
1834 begin
1835 g_Res_received_map_start := -1;
1836 end
1837 else
1838 begin
1839 gMissionFailed := EvNum <> 0;
1840 gExit := EXIT_ENDLEVELCUSTOM;
1841 end;
1842 end;
1844 NET_EV_RCON:
1845 begin
1846 case EvNum of
1847 NET_RCON_NOAUTH:
1848 g_Console_Add(_lc[I_NET_RCON_NOAUTH], True);
1849 NET_RCON_PWGOOD:
1850 g_Console_Add(_lc[I_NET_RCON_PWD_VALID], True);
1851 NET_RCON_PWBAD:
1852 g_Console_Add(_lc[I_NET_RCON_PWD_INVALID], True);
1853 end;
1854 end;
1856 NET_EV_CHANGE_TEAM:
1857 begin
1858 if NetDeafLevel < 2 then
1859 begin
1860 if EvNum = TEAM_RED then
1861 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [EvStr]), True);
1862 if EvNum = TEAM_BLUE then
1863 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [EvStr]), True);
1864 end;
1865 end;
1867 NET_EV_PLAYER_KICK:
1868 begin
1869 g_Console_Add(Format(_lc[I_PLAYER_KICK], [EvStr]), True);
1870 if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
1871 end;
1873 NET_EV_PLAYER_BAN:
1874 begin
1875 g_Console_Add(Format(_lc[I_PLAYER_BAN], [EvStr]), True);
1876 if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
1877 end;
1879 NET_EV_LMS_WARMUP:
1880 begin
1881 if EvNum > 0 then
1882 begin
1883 gLMSRespawn := LMS_RESPAWN_WARMUP;
1884 gLMSRespawnTime := gTime + EvNum;
1885 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [EvNum div 1000]), True);
1886 end
1887 else if gPlayer1 = nil then
1888 begin
1889 g_Console_Add(_lc[I_PLAYER_SPECT4], True);
1890 end;
1891 end;
1893 NET_EV_LMS_SURVIVOR:
1894 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True);
1896 NET_EV_BIGTEXT:
1897 if NetDeafLevel < 2 then g_Game_Message(AnsiUpperCase(EvStr), Word(EvNum));
1899 NET_EV_SCORE:
1900 begin
1901 pl := g_Player_Get(EvNum and $FFFF);
1902 if pl = nil then
1903 pln := '?'
1904 else
1905 pln := pl.Name;
1906 cnt := (EvNum shr 16) and $FF;
1907 if Pos('w', EvStr) = 0 then
1908 begin
1909 // Default score
1910 if Pos('t', EvStr) = 0 then
1911 begin
1912 // Player +/- score
1913 if Pos('-', EvStr) = 0 then
1914 begin
1915 if Pos('e', EvStr) = 0 then
1916 i1 := I_PLAYER_SCORE_ADD_OWN
1917 else
1918 i1 := I_PLAYER_SCORE_ADD_ENEMY;
1919 end else
1920 begin
1921 if Pos('e', EvStr) = 0 then
1922 i1 := I_PLAYER_SCORE_SUB_OWN
1923 else
1924 i1 := I_PLAYER_SCORE_SUB_ENEMY;
1925 end;
1926 // Which team
1927 if Pos('r', EvStr) > 0 then
1928 i2 := I_PLAYER_SCORE_TO_RED
1929 else
1930 i2 := I_PLAYER_SCORE_TO_BLUE;
1931 g_Console_Add(Format(_lc[i1], [pln, cnt, _lc[i2]]), True);
1932 end else
1933 begin
1934 // Team +/- score
1935 if Pos('-', EvStr) = 0 then
1936 i1 := I_PLAYER_SCORE_ADD_TEAM
1937 else
1938 i1 := I_PLAYER_SCORE_SUB_TEAM;
1939 // Which team
1940 if Pos('r', EvStr) > 0 then
1941 i2 := I_PLAYER_SCORE_RED
1942 else
1943 i2 := I_PLAYER_SCORE_BLUE;
1944 g_Console_Add(Format(_lc[i1], [_lc[i2], cnt]), True);
1945 end;
1946 end else
1947 begin
1948 // Game Win
1949 if Pos('e', EvStr) = 0 then
1950 i1 := I_PLAYER_SCORE_WIN_OWN
1951 else
1952 i1 := I_PLAYER_SCORE_WIN_ENEMY;
1953 // Which team
1954 if Pos('r', EvStr) > 0 then
1955 i2 := I_PLAYER_SCORE_TO_RED
1956 else
1957 i2 := I_PLAYER_SCORE_TO_BLUE;
1958 g_Console_Add(Format(_lc[i1], [pln, _lc[i2]]), True);
1959 end;
1960 end;
1962 NET_EV_SCORE_MSG:
1963 begin
1964 if EvNum = TEAM_RED then
1965 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1966 if EvNum = TEAM_BLUE then
1967 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1968 if EvNum = -TEAM_RED then
1969 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1970 if EvNum = -TEAM_BLUE then
1971 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1972 end;
1974 NET_EV_LMS_START:
1975 begin
1976 g_Player_RemoveAllCorpses;
1977 gLMSRespawn := LMS_RESPAWN_NONE;
1978 g_Game_Message(_lc[I_MESSAGE_LMS_START], 144);
1979 end;
1981 NET_EV_LMS_WIN:
1982 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(EvStr)]), 144);
1984 NET_EV_TLMS_WIN:
1985 begin
1986 if EvNum = TEAM_RED then
1987 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
1988 if EvNum = TEAM_BLUE then
1989 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
1990 end;
1992 NET_EV_LMS_LOSE:
1993 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
1995 NET_EV_LMS_DRAW:
1996 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
1998 NET_EV_LMS_NOSPAWN:
1999 g_Console_Add(_lc[I_PLAYER_SPECT4], True);
2001 NET_EV_KILLCOMBO:
2002 g_Game_Announce_KillCombo(EvNum);
2004 NET_EV_PLAYER_TOUCH:
2005 begin
2006 pl := g_Player_Get(EvNum);
2007 if pl <> nil then
2008 pl.Touch();
2009 end;
2011 NET_EV_SECRET:
2012 begin
2013 pl := g_Player_Get(EvNum);
2014 if pl <> nil then
2015 begin
2016 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [pl.Name]), True);
2017 g_Sound_PlayEx('SOUND_GAME_SECRET');
2018 end;
2019 end;
2021 NET_EV_INTER_READY:
2022 begin
2023 pl := g_Player_Get(EvNum);
2024 if pl <> nil then pl.FReady := (EvStr = 'Y');
2025 end;
2026 end;
2027 end;
2029 procedure MC_RECV_FlagEvent(var M: TMsg);
2030 var
2031 PID: Word;
2032 Pl: TPlayer;
2033 EvType: Byte;
2034 Fl, a: Byte;
2035 Quiet: Boolean;
2036 s, ts: string;
2037 begin
2038 EvType := M.ReadByte();
2039 Fl := M.ReadByte();
2041 if Fl = FLAG_NONE then Exit;
2043 Quiet := (M.ReadByte() <> 0);
2044 PID := M.ReadWord();
2046 gFlags[Fl].State := M.ReadByte();
2047 gFlags[Fl].CaptureTime := M.ReadLongWord();
2048 gFlags[Fl].Obj.X := M.ReadLongInt();
2049 gFlags[Fl].Obj.Y := M.ReadLongInt();
2050 gFlags[Fl].Obj.Vel.X := M.ReadLongInt();
2051 gFlags[Fl].Obj.Vel.Y := M.ReadLongInt();
2053 Pl := g_Player_Get(PID);
2054 if (Pl = nil) and
2055 (EvType <> FLAG_STATE_NORMAL) and
2056 (EvType <> FLAG_STATE_DROPPED) and
2057 (EvType <> FLAG_STATE_RETURNED) then
2058 Exit;
2060 case EvType of
2061 FLAG_STATE_NORMAL:
2062 begin
2063 if Quiet or (Pl = nil) then Exit;
2065 if Fl = FLAG_RED then
2066 s := _lc[I_PLAYER_FLAG_RED]
2067 else
2068 s := _lc[I_PLAYER_FLAG_BLUE];
2070 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2072 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2073 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2074 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2075 a := 0
2076 else
2077 a := 1;
2079 if not sound_ret_flag[a].IsPlaying() then
2080 sound_ret_flag[a].Play();
2081 end;
2083 FLAG_STATE_CAPTURED:
2084 begin
2085 if (Pl <> nil) then Pl.SetFlag(Fl);
2087 if Quiet then Exit;
2089 if Fl = FLAG_RED then
2090 s := _lc[I_PLAYER_FLAG_RED]
2091 else
2092 s := _lc[I_PLAYER_FLAG_BLUE];
2094 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [Pl.Name, s]), True);
2095 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
2097 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2098 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2099 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2100 a := 0
2101 else
2102 a := 1;
2104 if not sound_get_flag[a].IsPlaying() then
2105 sound_get_flag[a].Play();
2106 end;
2108 FLAG_STATE_DROPPED:
2109 begin
2110 if (Pl <> nil) then Pl.SetFlag(FLAG_NONE);
2112 if Quiet or (Pl = nil) then Exit;
2114 if Fl = FLAG_RED then
2115 s := _lc[I_PLAYER_FLAG_RED]
2116 else
2117 s := _lc[I_PLAYER_FLAG_BLUE];
2119 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [Pl.Name, s]), True);
2120 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
2122 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2123 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2124 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2125 a := 0
2126 else
2127 a := 1;
2129 if not sound_lost_flag[a].IsPlaying() then
2130 sound_lost_flag[a].Play();
2131 end;
2133 FLAG_STATE_SCORED:
2134 begin
2135 g_Map_ResetFlag(FLAG_RED);
2136 g_Map_ResetFlag(FLAG_BLUE);
2137 if Quiet or (Pl = nil) then Exit;
2138 Pl.SetFlag(FLAG_NONE);
2140 if Fl = FLAG_RED then
2141 s := _lc[I_PLAYER_FLAG_RED]
2142 else
2143 s := _lc[I_PLAYER_FLAG_BLUE];
2145 ts := Format('%.4d', [gFlags[Fl].CaptureTime]);
2146 Insert('.', ts, Length(ts) + 1 - 3);
2147 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [Pl.Name, s, ts]), True);
2148 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
2150 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2151 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2152 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2153 a := 0
2154 else
2155 a := 1;
2157 if not sound_cap_flag[a].IsPlaying() then
2158 sound_cap_flag[a].Play();
2159 end;
2161 FLAG_STATE_RETURNED:
2162 begin
2163 g_Map_ResetFlag(Fl);
2164 if Quiet then Exit;
2166 if Fl = FLAG_RED then
2167 s := _lc[I_PLAYER_FLAG_RED]
2168 else
2169 s := _lc[I_PLAYER_FLAG_BLUE];
2171 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2173 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2174 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2175 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2176 a := 0
2177 else
2178 a := 1;
2180 if not sound_ret_flag[a].IsPlaying() then
2181 sound_ret_flag[a].Play();
2182 end;
2183 end;
2184 end;
2186 procedure MC_RECV_GameSettings(var M: TMsg);
2187 begin
2188 gGameSettings.GameMode := M.ReadByte();
2189 gGameSettings.GoalLimit := M.ReadWord();
2190 gGameSettings.TimeLimit := M.ReadWord();
2191 gGameSettings.MaxLives := M.ReadByte();
2192 gGameSettings.Options := M.ReadLongWord();
2193 end;
2195 // PLAYER
2197 function MC_RECV_PlayerCreate(var M: TMsg): Word;
2198 var
2199 PID, DID: Word;
2200 PName, Model: string;
2201 Color: TRGB;
2202 T: Byte;
2203 Pl: TPlayer;
2204 begin
2205 PID := M.ReadWord();
2206 Pl := g_Player_Get(PID);
2208 PName := M.ReadString();
2209 Model := M.ReadString();
2210 Color.R := M.ReadByte();
2211 Color.G := M.ReadByte();
2212 Color.B := M.ReadByte();
2213 T := M.ReadByte();
2215 Result := 0;
2216 if (PID <> NetPlrUID1) and (PID <> NetPlrUID2) then
2217 begin
2218 if (Pl <> nil) then Exit;
2219 DID := g_Player_Create(Model, Color, T, False);
2220 with g_Player_Get(DID) do
2221 begin
2222 UID := PID;
2223 Name := PName;
2224 Reset(True);
2225 end;
2226 end
2227 else
2228 begin
2229 if (PID = NetPlrUID1) and (gPlayer1 <> nil) then begin
2230 gPlayer1.UID := PID;
2231 gPlayer1.Model.SetColor(Color.R, Color.G, Color.B);
2232 gPlayer1.ChangeTeam(T);
2233 end;
2234 if (PID = NetPlrUID2) and (gPlayer2 <> nil) then begin
2235 gPlayer2.UID := PID;
2236 gPlayer2.Model.SetColor(Color.R, Color.G, Color.B);
2237 gPlayer2.ChangeTeam(T);
2238 end;
2239 end;
2241 if NetDeafLevel < 3 then
2242 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True);
2243 e_WriteLog('NET: Player ' + PName + ' [' + IntToStr(PID) + '] added.', TMsgType.Notify);
2244 Result := PID;
2245 end;
2247 function MC_RECV_PlayerPos(var M: TMsg): Word;
2248 var
2249 GT: LongWord;
2250 PID: Word;
2251 kByte: Word;
2252 Pl: TPlayer;
2253 Dir: Byte;
2254 TmpX, TmpY: Integer;
2255 begin
2256 Result := 0;
2258 GT := M.ReadLongWord();
2259 if GT < gTime - NET_MAX_DIFFTIME then
2260 begin
2261 gTime := GT;
2262 Exit;
2263 end;
2264 gTime := GT;
2266 PID := M.ReadWord();
2267 Pl := g_Player_Get(PID);
2269 if Pl = nil then Exit;
2271 Result := PID;
2273 with Pl do
2274 begin
2275 FPing := M.ReadWord();
2276 FLoss := M.ReadByte();
2277 kByte := M.ReadWord();
2278 Dir := M.ReadByte();
2280 TmpX := M.ReadLongInt();
2281 TmpY := M.ReadLongInt();
2283 ReleaseKeys;
2285 if LongBool(kByte and NET_KEY_CHAT) then
2286 PressKey(KEY_CHAT, 10000)
2287 else
2288 begin
2289 if LongBool(kByte and NET_KEY_LEFT) then PressKey(KEY_LEFT, 10000);
2290 if LongBool(kByte and NET_KEY_RIGHT) then PressKey(KEY_RIGHT, 10000);
2291 if LongBool(kByte and NET_KEY_UP) then PressKey(KEY_UP, 10000);
2292 if LongBool(kByte and NET_KEY_DOWN) then PressKey(KEY_DOWN, 10000);
2293 if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000);
2294 end;
2296 JustTeleported := LongBool(kByte and NET_KEY_FORCEDIR);
2298 if ((Pl <> gPlayer1) and (Pl <> gPlayer2)) or JustTeleported then
2299 SetDirection(TDirection(Dir));
2301 GameVelX := M.ReadLongInt();
2302 GameVelY := M.ReadLongInt();
2303 GameAccelX := M.ReadLongInt();
2304 GameAccelY := M.ReadLongInt();
2305 SetLerp(TmpX, TmpY);
2306 if NetForcePlayerUpdate then Update();
2307 end;
2308 end;
2310 function MC_RECV_PlayerStats(var M: TMsg): Word;
2311 var
2312 PID: Word;
2313 Pl: TPlayer;
2314 I, OldFire: Integer;
2315 OldJet, Flam: Boolean;
2316 NewTeam: Byte;
2317 begin
2318 PID := M.ReadWord();
2319 Pl := g_Player_Get(PID);
2320 Result := 0;
2321 if Pl = nil then
2322 Exit;
2324 with Pl do
2325 begin
2326 alive := (M.ReadByte() <> 0);
2327 GodMode := (M.ReadByte() <> 0);
2328 Health := M.ReadLongInt();
2329 Armor := M.ReadLongInt();
2330 Air := M.ReadLongInt();
2331 JetFuel := M.ReadLongInt();
2332 Lives := M.ReadByte();
2333 NewTeam := M.ReadByte();
2335 for I := WP_FIRST to WP_LAST do
2336 FWeapon[I] := (M.ReadByte() <> 0);
2338 for I := A_BULLETS to A_HIGH do
2339 FAmmo[I] := M.ReadWord();
2341 for I := A_BULLETS to A_HIGH do
2342 FMaxAmmo[I] := M.ReadWord();
2344 for I := MR_SUIT to MR_MAX do
2345 FMegaRulez[I] := M.ReadLongWord();
2347 FRulez := [];
2348 if (M.ReadByte() <> 0) then
2349 FRulez := FRulez + [R_ITEM_BACKPACK];
2350 if (M.ReadByte() <> 0) then
2351 FRulez := FRulez + [R_KEY_RED];
2352 if (M.ReadByte() <> 0) then
2353 FRulez := FRulez + [R_KEY_GREEN];
2354 if (M.ReadByte() <> 0) then
2355 FRulez := FRulez + [R_KEY_BLUE];
2356 if (M.ReadByte() <> 0) then
2357 FRulez := FRulez + [R_BERSERK];
2359 Frags := M.ReadLongInt();
2360 Death := M.ReadLongInt();
2362 SetWeapon(M.ReadByte());
2364 FSpectator := M.ReadByte() <> 0;
2365 if FSpectator then
2366 begin
2367 if UID = NetPlrUID1 then
2368 begin
2369 gSpectLatchPID1 := UID;
2370 gPlayer1 := nil;
2371 end;
2372 if UID = NetPlrUID2 then
2373 begin
2374 gSpectLatchPID2 := UID;
2375 gPlayer2 := nil;
2376 end;
2377 end
2378 else
2379 begin
2380 if (gPlayer1 = nil) and (gSpectLatchPID1 > 0) and (UID = gSpectLatchPID1) then
2381 begin
2382 gPlayer1 := Pl;
2383 gSpectLatchPID1 := 0;
2384 end;
2385 if (gPlayer2 = nil) and (gSpectLatchPID2 > 0) and (UID = gSpectLatchPID2) then
2386 begin
2387 gPlayer2 := Pl;
2388 gSpectLatchPID2 := 0;
2389 end;
2390 end;
2391 FGhost := M.ReadByte() <> 0;
2392 FPhysics := M.ReadByte() <> 0;
2393 FNoRespawn := M.ReadByte() <> 0;
2394 OldJet := FJetpack;
2395 FJetpack := M.ReadByte() <> 0;
2396 OldFire := FFireTime;
2397 FFireTime := M.ReadLongInt();
2398 if (OldFire <= 0) and (FFireTime > 0) then
2399 g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y);
2400 Flam := M.ReadByte() <> 0;
2401 FSpawnInvul := M.ReadLongInt();
2402 if OldJet and not FJetpack then
2403 JetpackOff
2404 else if not OldJet and FJetpack then
2405 JetpackOn;
2406 if FFlaming and not Flam then
2407 FlamerOff;
2408 if Team <> NewTeam then
2409 Pl.ChangeTeam(NewTeam);
2410 end;
2412 Result := PID;
2413 end;
2415 function MC_RECV_PlayerDamage(var M: TMsg): Word;
2416 var
2417 PID: Word;
2418 Pl: TPlayer;
2419 Kind: Byte;
2420 Attacker, Value: Word;
2421 VX, VY: Integer;
2422 begin
2423 Result := 0;
2424 if not gGameOn then Exit;
2425 PID := M.ReadWord();
2426 Pl := g_Player_Get(PID);
2427 if Pl = nil then Exit;
2429 Kind := M.ReadByte();
2430 Attacker := M.ReadWord();
2431 Value := M.ReadWord();
2432 VX := M.ReadWord();
2433 VY := M.ReadWord();
2435 with Pl do
2436 Damage(Value, Attacker, VX, VY, Kind);
2438 Result := PID;
2439 end;
2441 function MC_RECV_PlayerDeath(var M: TMsg): Word;
2442 var
2443 PID: Word;
2444 Pl: TPlayer;
2445 KillType, DeathType: Byte;
2446 Attacker: Word;
2447 begin
2448 Result := 0;
2449 if not gGameOn then Exit;
2450 PID := M.ReadWord();
2451 Pl := g_Player_Get(PID);
2452 if Pl = nil then Exit;
2454 KillType := M.ReadByte();
2455 DeathType := M.ReadByte();
2456 Attacker := M.ReadWord();
2458 with Pl do
2459 begin
2460 Kill(KillType, Attacker, DeathType);
2461 SoftReset;
2462 end;
2463 end;
2465 function MC_RECV_PlayerDelete(var M: TMsg): Word;
2466 var
2467 PID: Word;
2468 Pl: TPlayer;
2469 begin
2470 PID := M.ReadWord();
2471 Pl := g_Player_Get(PID);
2472 Result := 0;
2473 if Pl = nil then Exit;
2475 if NetDeafLevel < 3 then
2476 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
2477 e_WriteLog('NET: Player ' + Pl.Name + ' [' + IntToStr(PID) + '] removed.', TMsgType.Notify);
2479 g_Player_Remove(PID);
2481 Result := PID;
2482 end;
2484 function MC_RECV_PlayerFire(var M: TMsg): Word;
2485 var
2486 PID: Word;
2487 Weap: Byte;
2488 Pl: TPlayer;
2489 X, Y, AX, AY: Integer;
2490 SHID: Integer;
2491 begin
2492 Result := 0;
2493 if not gGameOn then Exit;
2494 PID := M.ReadWord();
2495 Pl := g_Player_Get(PID);
2496 if Pl = nil then Exit;
2498 Weap := M.ReadByte();
2499 X := M.ReadLongInt();
2500 Y := M.ReadLongInt();
2501 AX := M.ReadLongInt();
2502 AY := M.ReadLongInt();
2503 SHID := M.ReadLongInt();
2505 with Pl do
2506 if alive then NetFire(Weap, X, Y, AX, AY, SHID);
2507 end;
2509 procedure MC_RECV_PlayerSettings(var M: TMsg);
2510 var
2511 TmpName: string;
2512 TmpModel: string;
2513 TmpColor: TRGB;
2514 TmpTeam: Byte;
2515 Pl: TPlayer;
2516 PID: Word;
2517 begin
2518 PID := M.ReadWord();
2519 Pl := g_Player_Get(PID);
2520 if Pl = nil then Exit;
2522 TmpName := M.ReadString();
2523 TmpModel := M.ReadString();
2524 TmpColor.R := M.ReadByte();
2525 TmpColor.G := M.ReadByte();
2526 TmpColor.B := M.ReadByte();
2527 TmpTeam := M.ReadByte();
2529 if (gGameSettings.GameMode in [GM_TDM, GM_CTF]) and (Pl.Team <> TmpTeam) then
2530 begin
2531 Pl.ChangeTeam(TmpTeam);
2532 if gPlayer1 = Pl then
2533 gPlayer1Settings.Team := TmpTeam;
2534 if gPlayer2 = Pl then
2535 gPlayer2Settings.Team := TmpTeam;
2536 end else
2537 Pl.SetColor(TmpColor);
2539 if Pl.Name <> TmpName then
2540 begin
2541 if NetDeafLevel < 3 then
2542 g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True);
2543 Pl.Name := TmpName;
2544 end;
2546 if TmpModel <> Pl.Model.Name then
2547 Pl.SetModel(TmpModel);
2548 end;
2550 // ITEM
2552 procedure MC_RECV_ItemSpawn(var M: TMsg);
2553 var
2554 ID: Word;
2555 AID: DWord;
2556 X, Y, VX, VY: Integer;
2557 T: Byte;
2558 Quiet, Fall{, Resp}: Boolean;
2559 Anim: TAnimation;
2560 it: PItem;
2561 begin
2562 if not gGameOn then Exit;
2563 ID := M.ReadWord();
2564 Quiet := M.ReadByte() <> 0;
2565 T := M.ReadByte();
2566 Fall := M.ReadByte() <> 0;
2567 {Resp :=} M.ReadByte();
2568 X := M.ReadLongInt();
2569 Y := M.ReadLongInt();
2570 VX := M.ReadLongInt();
2571 VY := M.ReadLongInt();
2573 g_Items_Create(X, Y, T and $7F, Fall, False, False, ID);
2574 if ((T and $80) <> 0) then g_Items_SetDrop(ID);
2576 it := g_Items_ByIdx(ID);
2577 it.Obj.Vel.X := VX;
2578 it.Obj.Vel.Y := VY;
2580 if not Quiet then
2581 begin
2582 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
2583 if g_Frames_Get(AID, 'FRAMES_ITEM_RESPAWN') then
2584 begin
2585 Anim := TAnimation.Create(AID, False, 4);
2586 g_GFX_OnceAnim(X+(it.Obj.Rect.Width div 2)-16, Y+(it.Obj.Rect.Height div 2)-16, Anim);
2587 Anim.Free();
2588 end;
2589 end;
2590 end;
2592 procedure MC_RECV_ItemDestroy(var M: TMsg);
2593 var
2594 ID: Word;
2595 Quiet: Boolean;
2596 begin
2597 if not gGameOn then Exit;
2598 ID := M.ReadWord();
2599 Quiet := M.ReadByte() <> 0;
2601 if not g_Items_ValidId(ID) then exit;
2603 if not Quiet then g_Items_EmitPickupSound(ID);
2605 g_Items_Remove(ID);
2606 end;
2608 // PANEL
2610 procedure MC_RECV_PanelTexture(var M: TMsg);
2611 var
2612 TP: TPanel;
2613 PGUID: Integer;
2614 Tex, Fr: Integer;
2615 Loop, Cnt: Byte;
2616 begin
2617 if not gGameOn then Exit;
2619 PGUID := Integer(M.ReadLongWord());
2620 Tex := M.ReadLongInt();
2621 Fr := M.ReadLongInt();
2622 Cnt := M.ReadByte();
2623 Loop := M.ReadByte();
2625 TP := g_Map_PanelByGUID(PGUID);
2626 if (TP <> nil) then
2627 begin
2628 // switch texture
2629 TP.SetTexture(Tex, Loop);
2630 TP.SetFrame(Fr, Cnt);
2631 end;
2632 end;
2634 procedure MC_RECV_PanelState(var M: TMsg);
2635 var
2636 PGUID: Integer;
2637 E: Boolean;
2638 Lift: Byte;
2639 X, Y, W, H: Integer;
2640 TP: TPanel;
2641 speedX, speedY, startX, startY, endX, endY: Integer;
2642 sizeSpX, sizeSpY, sizeEX, sizeEY: Integer;
2643 mpflags: Byte;
2644 begin
2645 if not gGameOn then Exit;
2647 PGUID := Integer(M.ReadLongWord());
2648 E := (M.ReadByte() <> 0);
2649 Lift := M.ReadByte();
2650 X := M.ReadLongInt();
2651 Y := M.ReadLongInt();
2652 W := M.ReadWord();
2653 H := M.ReadWord();
2654 // mplats
2655 speedX := M.ReadLongInt();
2656 speedY := M.ReadLongInt();
2657 startX := M.ReadLongInt();
2658 startY := M.ReadLongInt();
2659 endX := M.ReadLongInt();
2660 endY := M.ReadLongInt();
2661 sizeSpX := M.ReadLongInt();
2662 sizeSpY := M.ReadLongInt();
2663 sizeEX := M.ReadLongInt();
2664 sizeEY := M.ReadLongInt();
2665 mpflags := M.ReadByte(); // bit0: TP.movingActive; bit1: TP.moveOnce
2667 TP := g_Map_PanelByGUID(PGUID);
2668 if (TP = nil) then exit;
2670 // update lifts state
2671 if TP.isGLift then g_Map_SetLiftGUID(PGUID, Lift);
2673 // update enabled/disabled state for all panels
2674 if E then g_Map_EnableWallGUID(PGUID) else g_Map_DisableWallGUID(PGUID);
2676 // update panel position, as it can be moved (mplat)
2677 TP.X := X;
2678 TP.Y := Y;
2679 TP.Width := W;
2680 TP.Height := H;
2681 // update mplat state
2682 TP.movingSpeedX := speedX;
2683 TP.movingSpeedY := speedY;
2684 TP.movingStartX := startX;
2685 TP.movingStartY := startY;
2686 TP.movingEndX := endX;
2687 TP.movingEndY := endY;
2688 TP.sizeSpeedX := sizeSpX;
2689 TP.sizeSpeedY := sizeSpY;
2690 TP.sizeEndX := sizeEX;
2691 TP.sizeEndY := sizeEY;
2692 TP.movingActive := ((mpflags and 1) <> 0);
2693 TP.moveOnce := ((mpflags and 2) <> 0);
2694 // notify panel of it's position/size change, so it can fix other internal structures
2695 TP.positionChanged();
2696 end;
2698 // TRIGGERS
2700 procedure MC_RECV_TriggerSound(var M: TMsg);
2701 var
2702 SPlaying: Boolean;
2703 SPos, SID: LongWord;
2704 SCount: LongInt;
2705 I: Integer;
2706 begin
2707 if not gGameOn then Exit;
2708 if gTriggers = nil then Exit;
2710 SID := M.ReadLongWord();
2711 SPlaying := M.ReadByte() <> 0;
2712 SPos := M.ReadLongWord();
2713 SCount := M.ReadLongInt();
2715 for I := Low(gTriggers) to High(gTriggers) do
2716 if gTriggers[I].TriggerType = TRIGGER_SOUND then
2717 if gTriggers[I].ClientID = SID then
2718 with gTriggers[I] do
2719 begin
2720 if Sound <> nil then
2721 begin
2722 if SPlaying then
2723 begin
2724 if tgcLocal then
2725 Sound.PlayVolumeAt(X+(Width div 2), Y+(Height div 2), tgcVolume/255.0)
2726 else
2727 Sound.PlayPanVolume((tgcPan-127.0)/128.0, tgcVolume/255.0);
2728 Sound.SetPosition(SPos);
2729 end
2730 else
2731 if Sound.IsPlaying then Sound.Stop;
2732 end;
2734 SoundPlayCount := SCount;
2735 end;
2736 end;
2738 procedure MC_RECV_TriggerMusic(var M: TMsg);
2739 var
2740 MName: string;
2741 MPlaying: Boolean;
2742 MPos: LongWord;
2743 MPaused: Boolean;
2744 begin
2745 if not gGameOn then Exit;
2747 MName := M.ReadString();
2748 MPlaying := M.ReadByte() <> 0;
2749 MPos := M.ReadLongWord();
2750 MPaused := M.ReadByte() <> 0;
2751 MPos := MPos+1; //k8: stfu, fpc!
2753 if MPlaying then
2754 begin
2755 gMusic.SetByName(MName);
2756 gMusic.Play(True);
2757 // gMusic.SetPosition(MPos);
2758 gMusic.SpecPause := MPaused;
2759 end
2760 else
2761 if gMusic.IsPlaying then gMusic.Stop;
2762 end;
2764 // MONSTERS
2766 procedure MC_RECV_MonsterSpawn(var M: TMsg);
2767 var
2768 ID: Word;
2769 MType, MState, MDir, MAnim, MBehav: Byte;
2770 X, Y, VX, VY, MTargTime, MHealth, MAmmo, MSleep: Integer;
2771 MTarg: Word;
2772 Mon: TMonster;
2773 begin
2774 ID := M.ReadWord();
2775 Mon := g_Monsters_ByUID(ID);
2776 if Mon <> nil then
2777 Exit;
2779 MType := M.ReadByte();
2780 MState := M.ReadByte();
2781 MAnim := M.ReadByte();
2782 MTarg := M.ReadWord();
2783 MTargTime := M.ReadLongInt();
2784 MBehav := M.ReadByte();
2785 MSleep := M.ReadLongInt();
2786 MHealth := M.ReadLongInt();
2787 MAmmo := M.ReadLongInt();
2789 X := M.ReadLongInt();
2790 Y := M.ReadLongInt();
2791 VX := M.ReadLongInt();
2792 VY := M.ReadLongInt();
2793 MDir := M.ReadByte();
2795 g_Monsters_Create(MType, X, Y, TDirection(MDir), False, ID);
2796 Mon := g_Monsters_ByUID(ID);
2797 if Mon = nil then
2798 Exit;
2800 with Mon do
2801 begin
2803 MonsterAnim := MAnim;
2804 MonsterTargetUID := MTarg;
2805 MonsterTargetTime := MTargTime;
2806 MonsterBehaviour := MBehav;
2807 MonsterSleep := MSleep;
2808 MonsterAmmo := MAmmo;
2809 SetHealth(MHealth);
2811 SetState(MState);
2813 setPosition(X, Y); // this will call positionChanged();
2814 GameVelX := VX;
2815 GameVelY := VY;
2816 end;
2817 end;
2819 procedure MC_RECV_MonsterPos(var M: TMsg);
2820 var
2821 Mon: TMonster;
2822 ID: Word;
2823 X, Y: Integer;
2824 begin
2825 ID := M.ReadWord();
2826 Mon := g_Monsters_ByUID(ID);
2827 if Mon = nil then
2828 Exit;
2830 with Mon do
2831 begin
2832 X := M.ReadLongInt();
2833 Y := M.ReadLongInt();
2834 Mon.setPosition(X, Y); // this will call `positionChanged()`
2835 GameVelX := M.ReadLongInt();
2836 GameVelY := M.ReadLongInt();
2837 GameDirection := TDirection(M.ReadByte());
2838 end;
2839 end;
2841 procedure MC_RECV_MonsterState(var M: TMsg);
2842 var
2843 ID, OldFire: Integer;
2844 MState, MFAnm: Byte;
2845 Mon: TMonster;
2846 AnimRevert: Boolean;
2847 begin
2848 ID := M.ReadWord();
2849 Mon := g_Monsters_ByUID(ID);
2850 if Mon = nil then Exit;
2852 MState := M.ReadByte();
2853 MFAnm := M.ReadByte();
2855 with Mon do
2856 begin
2857 MonsterTargetUID := M.ReadWord();
2858 MonsterTargetTime := M.ReadLongInt();
2859 MonsterSleep := M.ReadLongInt();
2860 MonsterHealth := M.ReadLongInt();
2861 MonsterAmmo := M.ReadLongInt();
2862 MonsterPain := M.ReadLongInt();
2863 AnimRevert := M.ReadByte() <> 0;
2864 OldFire := FFireTime;
2865 FFireTime := M.ReadLongInt();
2866 if (OldFire <= 0) and (FFireTime > 0) then
2867 g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y);
2868 RevertAnim(AnimRevert);
2870 if MonsterState <> MState then
2871 begin
2872 if (MState = MONSTATE_GO) and (MonsterState = MONSTATE_SLEEP) then WakeUpSound();
2873 if (MState = MONSTATE_DIE) then DieSound();
2874 if (MState = MONSTATE_PAIN) then MakeBloodSimple(Min(200, MonsterPain));
2875 if (MState = MONSTATE_ATTACK) then kick(nil);
2876 if (MState = MONSTATE_DEAD) then SetDeadAnim();
2878 SetState(MState, MFAnm);
2879 end;
2880 end;
2881 end;
2883 procedure MC_RECV_MonsterShot(var M: TMsg);
2884 var
2885 ID: Integer;
2886 Mon: TMonster;
2887 X, Y, VX, VY: Integer;
2888 begin
2889 ID := M.ReadWord();
2891 Mon := g_Monsters_ByUID(ID);
2892 if Mon = nil then Exit;
2894 X := M.ReadLongInt();
2895 Y := M.ReadLongInt();
2896 VX := M.ReadLongInt();
2897 VY := M.ReadLongInt();
2899 Mon.ClientAttack(X, Y, VX, VY);
2900 end;
2902 procedure MC_RECV_MonsterDelete(var M: TMsg);
2903 var
2904 ID: Integer;
2905 Mon: TMonster;
2906 begin
2907 ID := M.ReadWord();
2908 Mon := g_Monsters_ByUID(ID);
2909 if Mon = nil then Exit;
2910 Mon.SetState(5);
2911 Mon.MonsterRemoved := True;
2912 end;
2914 procedure MC_RECV_TimeSync(var M: TMsg);
2915 var
2916 Time: LongWord;
2917 begin
2918 Time := M.ReadLongWord();
2920 if gState = STATE_INTERCUSTOM then
2921 gServInterTime := Min(Time, 255);
2922 end;
2924 procedure MC_RECV_VoteEvent(var M: TMsg);
2925 var
2926 EvID: Byte;
2927 Str1, Str2: string;
2928 Int1, Int2: SmallInt;
2929 begin
2930 EvID := M.ReadByte();
2931 Int1 := M.ReadSmallInt();
2932 Int2 := M.ReadSmallInt();
2933 Str1 := M.ReadString();
2934 Str2 := M.ReadString();
2936 if NetDeafLevel < 2 then
2937 case EvID of
2938 NET_VE_STARTED:
2939 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_STARTED], [Str1, Str2, Int1]), True);
2940 NET_VE_PASSED:
2941 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_PASSED], [Str1]), True);
2942 NET_VE_FAILED:
2943 g_Console_Add(_lc[I_MESSAGE_VOTE_FAILED], True);
2944 NET_VE_VOTE:
2945 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Str1, Int1, Int2]), True);
2946 NET_VE_INPROGRESS:
2947 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_INPROGRESS], [Str1]), True);
2948 end;
2949 end;
2951 // CLIENT SEND
2953 procedure MC_SEND_Info(Password: string);
2954 begin
2955 NetOut.Clear();
2957 NetOut.Write(Byte(NET_MSG_INFO));
2958 NetOut.Write(GAME_VERSION);
2959 NetOut.Write(Password);
2960 NetOut.Write(gPlayer1Settings.Name);
2961 NetOut.Write(gPlayer1Settings.Model);
2962 NetOut.Write(gPlayer1Settings.Color.R);
2963 NetOut.Write(gPlayer1Settings.Color.G);
2964 NetOut.Write(gPlayer1Settings.Color.B);
2965 NetOut.Write(gPlayer1Settings.Team);
2967 g_Net_Client_Send(True, NET_CHAN_SERVICE);
2968 end;
2970 procedure MC_SEND_Chat(Txt: string; Mode: Byte);
2971 begin
2972 NetOut.Write(Byte(NET_MSG_CHAT));
2973 NetOut.Write(Txt);
2974 NetOut.Write(Mode);
2976 g_Net_Client_Send(True, NET_CHAN_CHAT);
2977 end;
2979 procedure MC_SEND_PlayerPos();
2980 var
2981 kByte: Word;
2982 Predict: Boolean;
2983 strafeDir: Byte;
2984 WeaponAct: Byte = 0;
2985 WeaponSelect: Word = 0;
2986 i: Integer;
2987 begin
2988 if not gGameOn then Exit;
2989 if gPlayers = nil then Exit;
2990 if gPlayer1 = nil then Exit;
2992 kByte := 0;
2993 Predict := NetPredictSelf; // and (not NetGotKeys);
2995 if (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
2996 begin
2997 strafeDir := P1MoveButton shr 4;
2998 P1MoveButton := P1MoveButton and $0F;
3000 if gPlayerAction[0, ACTION_MOVELEFT] and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
3001 P1MoveButton := 1
3002 else if (not gPlayerAction[0, ACTION_MOVELEFT]) and gPlayerAction[0, ACTION_MOVERIGHT] then
3003 P1MoveButton := 2
3004 else if (not gPlayerAction[0, ACTION_MOVELEFT]) and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
3005 P1MoveButton := 0;
3007 // strafing
3008 if gPlayerAction[0, ACTION_STRAFE] then
3009 begin
3010 // new strafe mechanics
3011 if (strafeDir = 0) then
3012 strafeDir := P1MoveButton; // start strafing
3013 // now set direction according to strafe (reversed)
3014 if (strafeDir = 2) then
3015 gPlayer1.SetDirection(TDirection.D_LEFT)
3016 else if (strafeDir = 1) then
3017 gPlayer1.SetDirection(TDirection.D_RIGHT)
3018 end
3019 else
3020 begin
3021 strafeDir := 0; // not strafing anymore
3022 if (P1MoveButton = 2) and gPlayerAction[0, ACTION_MOVELEFT] then
3023 gPlayer1.SetDirection(TDirection.D_LEFT)
3024 else if (P1MoveButton = 1) and gPlayerAction[0, ACTION_MOVERIGHT] then
3025 gPlayer1.SetDirection(TDirection.D_RIGHT)
3026 else if P1MoveButton <> 0 then
3027 gPlayer1.SetDirection(TDirection(P1MoveButton-1));
3028 end;
3030 gPlayer1.ReleaseKeys;
3031 if P1MoveButton = 1 then
3032 begin
3033 kByte := kByte or NET_KEY_LEFT;
3034 if Predict then gPlayer1.PressKey(KEY_LEFT, 10000);
3035 end;
3036 if P1MoveButton = 2 then
3037 begin
3038 kByte := kByte or NET_KEY_RIGHT;
3039 if Predict then gPlayer1.PressKey(KEY_RIGHT, 10000);
3040 end;
3041 if gPlayerAction[0, ACTION_LOOKUP] then
3042 begin
3043 kByte := kByte or NET_KEY_UP;
3044 gPlayer1.PressKey(KEY_UP, 10000);
3045 end;
3046 if gPlayerAction[0, ACTION_LOOKDOWN] then
3047 begin
3048 kByte := kByte or NET_KEY_DOWN;
3049 gPlayer1.PressKey(KEY_DOWN, 10000);
3050 end;
3051 if gPlayerAction[0, ACTION_JUMP] then
3052 begin
3053 kByte := kByte or NET_KEY_JUMP;
3054 // gPlayer1.PressKey(KEY_JUMP, 10000); // TODO: Make a prediction option
3055 end;
3056 if gPlayerAction[0, ACTION_ATTACK] then kByte := kByte or NET_KEY_FIRE;
3057 if gPlayerAction[0, ACTION_ACTIVATE] then kByte := kByte or NET_KEY_OPEN;
3059 for i := WP_FACT to WP_LACT do
3060 begin
3061 if gWeaponAction[0, i] then
3062 begin
3063 WeaponAct := WeaponAct or Byte(1 shl i);
3064 gWeaponAction[0, i] := False
3065 end
3066 end;
3068 for i := WP_FIRST to WP_LAST do
3069 begin
3070 if gSelectWeapon[0, i] then
3071 begin
3072 WeaponSelect := WeaponSelect or Word(1 shl i);
3073 gSelectWeapon[0, i] := False
3074 end
3075 end;
3077 // fix movebutton state
3078 P1MoveButton := P1MoveButton or (strafeDir shl 4);
3079 end
3080 else
3081 kByte := NET_KEY_CHAT;
3083 NetOut.Write(Byte(NET_MSG_PLRPOS));
3084 NetOut.Write(gTime);
3085 NetOut.Write(kByte);
3086 NetOut.Write(Byte(gPlayer1.Direction));
3087 NetOut.Write(WeaponAct);
3088 NetOut.Write(WeaponSelect);
3089 //e_WriteLog(Format('S:ws=%d', [WeaponSelect]), MSG_WARNING);
3090 g_Net_Client_Send(True, NET_CHAN_PLAYERPOS);
3092 //kBytePrev := kByte;
3093 //kDirPrev := gPlayer1.Direction;
3094 end;
3096 procedure MC_SEND_Vote(Start: Boolean = False; Command: string = 'a');
3097 begin
3098 NetOut.Write(Byte(NET_MSG_VOTE_EVENT));
3099 NetOut.Write(Byte(Start));
3100 NetOut.Write(Command);
3101 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3102 end;
3104 procedure MC_SEND_PlayerSettings();
3105 begin
3106 NetOut.Write(Byte(NET_MSG_PLRSET));
3107 NetOut.Write(gPlayer1Settings.Name);
3108 NetOut.Write(gPlayer1Settings.Model);
3109 NetOut.Write(gPlayer1Settings.Color.R);
3110 NetOut.Write(gPlayer1Settings.Color.G);
3111 NetOut.Write(gPlayer1Settings.Color.B);
3112 NetOut.Write(gPlayer1Settings.Team);
3114 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3115 end;
3117 procedure MC_SEND_FullStateRequest();
3118 begin
3119 NetOut.Write(Byte(NET_MSG_REQFST));
3121 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3122 end;
3124 procedure MC_SEND_CheatRequest(Kind: Byte);
3125 begin
3126 NetOut.Write(Byte(NET_MSG_CHEAT));
3127 NetOut.Write(Kind);
3129 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3130 end;
3131 procedure MC_SEND_RCONPassword(Password: string);
3132 begin
3133 NetOut.Write(Byte(NET_MSG_RCON_AUTH));
3134 NetOut.Write(Password);
3136 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3137 end;
3138 procedure MC_SEND_RCONCommand(Cmd: string);
3139 begin
3140 NetOut.Write(Byte(NET_MSG_RCON_CMD));
3141 NetOut.Write(Cmd);
3143 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3144 end;
3147 end.