DEADSOFTWARE

39a4023d68a194a0eb30cc458f9572466a3383ed
[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;
34 NET_MSG_FLAGPOS= 110;
36 NET_MSG_PLR = 111;
37 NET_MSG_PLRPOS = 112;
38 NET_MSG_PLRSTA = 113;
39 NET_MSG_PLRDEL = 114;
40 NET_MSG_PLRDMG = 115;
41 NET_MSG_PLRDIE = 116;
42 NET_MSG_PLRFIRE= 117;
43 NET_MSG_PLRSET = 119;
44 NET_MSG_CHEAT = 120;
46 NET_MSG_ISPAWN = 121;
47 NET_MSG_IDEL = 122;
48 NET_MSG_IPOS = 123;
50 NET_MSG_MSPAWN = 131;
51 NET_MSG_MPOS = 132;
52 NET_MSG_MSTATE = 133;
53 NET_MSG_MSHOT = 134;
54 NET_MSG_MDEL = 135;
56 NET_MSG_PSTATE = 141;
57 NET_MSG_PTEX = 142;
59 NET_MSG_TSOUND = 151;
60 NET_MSG_TMUSIC = 152;
62 NET_MSG_SHDEL = 161;
63 NET_MSG_SHADD = 162;
64 NET_MSG_SHPOS = 163;
66 NET_MSG_RCON_AUTH = 191;
67 NET_MSG_RCON_CMD = 192;
68 NET_MSG_TIME_SYNC = 194;
69 NET_MSG_VOTE_EVENT = 195;
71 {
72 NET_MSG_MAP_REQUEST = 201;
73 NET_MSG_MAP_RESPONSE = 202;
74 NET_MSG_RES_REQUEST = 203;
75 NET_MSG_RES_RESPONSE = 204;
76 }
78 NET_CHAT_SYSTEM = 0;
79 NET_CHAT_PLAYER = 1;
80 NET_CHAT_TEAM = 2;
82 NET_RCON_NOAUTH = 0;
83 NET_RCON_PWGOOD = 1;
84 NET_RCON_PWBAD = 2;
86 NET_GFX_SPARK = 1;
87 NET_GFX_TELE = 2;
88 NET_GFX_RESPAWN = 3;
89 NET_GFX_FIRE = 4;
90 NET_GFX_EXPLODE = 5;
91 NET_GFX_BFGEXPL = 6;
92 NET_GFX_BFGHIT = 7;
93 NET_GFX_SHELL1 = 8;
94 NET_GFX_SHELL2 = 9;
95 NET_GFX_SHELL3 = 10;
97 NET_EV_MAPSTART = 1;
98 NET_EV_MAPEND = 2;
99 NET_EV_CHANGE_TEAM = 3;
100 NET_EV_PLAYER_KICK = 4;
101 NET_EV_PLAYER_BAN = 5;
102 NET_EV_LMS_WARMUP = 6;
103 NET_EV_LMS_SURVIVOR = 7;
104 NET_EV_RCON = 8;
105 NET_EV_BIGTEXT = 9;
106 NET_EV_SCORE = 10;
107 NET_EV_SCORE_MSG = 11;
108 NET_EV_LMS_START = 12;
109 NET_EV_LMS_WIN = 13;
110 NET_EV_TLMS_WIN = 14;
111 NET_EV_LMS_LOSE = 15;
112 NET_EV_LMS_DRAW = 16;
113 NET_EV_KILLCOMBO = 17;
114 NET_EV_PLAYER_TOUCH = 18;
115 NET_EV_SECRET = 19;
116 NET_EV_INTER_READY = 20;
117 NET_EV_LMS_NOSPAWN = 21;
119 NET_VE_STARTED = 1;
120 NET_VE_PASSED = 2;
121 NET_VE_FAILED = 3;
122 NET_VE_VOTE = 4;
123 NET_VE_REVOKE = 5;
124 NET_VE_INPROGRESS = 6;
126 NET_FLAG_GET = 1;
127 NET_FLAG_DROP = 2;
128 NET_FLAG_CAP = 3;
129 NET_FLAG_RETURN = 4;
131 NET_CHEAT_SUICIDE = 1;
132 NET_CHEAT_SPECTATE = 2;
133 NET_CHEAT_READY = 3;
134 NET_CHEAT_DROPFLAG = 4;
136 NET_MAX_DIFFTIME = 5000 div 36;
138 // HOST MESSAGES
140 procedure MH_MalformedPacket(C: pTNetClient);
141 procedure MH_ProcessFirstSpawn (C: pTNetClient);
143 procedure MH_RECV_Info(C: pTNetClient; var M: TMsg);
144 procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg);
145 procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
146 function MH_RECV_PlayerPos(C: pTNetClient; var M: TMsg): Word;
147 procedure MH_RECV_PlayerSettings(C: pTNetClient; var M: TMsg);
148 procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg);
149 procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg);
150 procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg);
151 //procedure MH_RECV_MapRequest(C: pTNetClient; var M: TMsg);
152 //procedure MH_RECV_ResRequest(C: pTNetClient; var M: TMsg);
153 procedure MH_RECV_Vote(C: pTNetClient; var M: TMsg);
155 // GAME
156 procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
157 procedure MH_SEND_Info(ID: Byte);
158 procedure MH_SEND_Chat(Txt: string; Mode: Byte; ID: Integer = NET_EVERYONE);
159 procedure MH_SEND_Effect(X, Y: Integer; Ang: SmallInt; Kind: Byte; ID: Integer = NET_EVERYONE);
160 procedure MH_SEND_Sound(X, Y: Integer; Name: string; Pos: Boolean = True; ID: Integer = NET_EVERYONE);
161 procedure MH_SEND_CreateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
162 procedure MH_SEND_UpdateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
163 procedure MH_SEND_DeleteShot(Proj: LongInt; X, Y: LongInt; Loud: Boolean = True; ID: Integer = NET_EVERYONE);
164 procedure MH_SEND_GameStats(ID: Integer = NET_EVERYONE);
165 procedure MH_SEND_CoopStats(ID: Integer = NET_EVERYONE);
166 procedure MH_SEND_GameEvent(EvType: Byte; EvNum: Integer = 0; EvStr: string = 'N'; ID: Integer = NET_EVERYONE);
167 procedure MH_SEND_FlagEvent(EvType: Byte; Flag: Byte; PID: Word; Quiet: Boolean = False; ID: Integer = NET_EVERYONE);
168 procedure MH_SEND_FlagPos(Flag: Byte; ID: Integer = NET_EVERYONE);
169 procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE);
170 // PLAYER
171 procedure MH_SEND_PlayerCreate(PID: Word; ID: Integer = NET_EVERYONE);
172 procedure MH_SEND_PlayerPos(Reliable: Boolean; PID: Word; ID: Integer = NET_EVERYONE);
173 procedure MH_SEND_PlayerStats(PID: Word; ID: Integer = NET_EVERYONE);
174 procedure MH_SEND_PlayerDelete(PID: Word; ID: Integer = NET_EVERYONE);
175 procedure MH_SEND_PlayerDamage(PID: Word; Kind: Byte; Attacker, Value: Word; VX, VY: Integer; ID: Integer = NET_EVERYONE);
176 procedure MH_SEND_PlayerFire(PID: Word; Weapon: Byte; X, Y, AX, AY: Integer; ShotID: Integer = -1; ID: Integer = NET_EVERYONE);
177 procedure MH_SEND_PlayerDeath(PID: Word; KillType, DeathType: Byte; Attacker: Word; ID: Integer = NET_EVERYONE);
178 procedure MH_SEND_PlayerSettings(PID: Word; Mdl: string = ''; ID: Integer = NET_EVERYONE);
179 // ITEM
180 procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
181 procedure MH_SEND_ItemDestroy(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
182 procedure MH_SEND_ItemPos(IID: Word; ID: Integer = NET_EVERYONE);
183 // PANEL
184 procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE);
185 procedure MH_SEND_PanelState(PGUID: Integer; ID: Integer = NET_EVERYONE);
186 // MONSTER
187 procedure MH_SEND_MonsterSpawn(UID: Word; ID: Integer = NET_EVERYONE);
188 procedure MH_SEND_MonsterPos(UID: Word; ID: Integer = NET_EVERYONE);
189 procedure MH_SEND_MonsterState(UID: Word; ForcedAnim: Byte = 255; ID: Integer = NET_EVERYONE);
190 procedure MH_SEND_MonsterShot(UID: Word; X, Y, VX, VY: Integer; ID: Integer = NET_EVERYONE);
191 procedure MH_SEND_MonsterDelete(UID: Word; ID: Integer = NET_EVERYONE);
192 // TRIGGER
193 procedure MH_SEND_TriggerSound(var T: TTrigger; ID: Integer = NET_EVERYONE);
194 procedure MH_SEND_TriggerMusic(ID: Integer = NET_EVERYONE);
195 // MISC
196 procedure MH_SEND_TimeSync(Time: LongWord; ID: Integer = NET_EVERYONE);
197 procedure MH_SEND_VoteEvent(EvType: Byte;
198 StrArg1: string = 'a'; StrArg2: string = 'b';
199 IntArg1: SmallInt = 0; IntArg2: SmallInt = 0;
200 ID: Integer = NET_EVERYONE);
202 // CLIENT MESSAGES //
204 // GAME
205 procedure MC_RECV_Chat(var M: TMsg);
206 procedure MC_RECV_Effect(var M: TMsg);
207 procedure MC_RECV_Sound(var M: TMsg);
208 procedure MC_RECV_GameStats(var M: TMsg);
209 procedure MC_RECV_CoopStats(var M: TMsg);
210 procedure MC_RECV_GameEvent(var M: TMsg);
211 procedure MC_RECV_FlagEvent(var M: TMsg);
212 procedure MC_RECV_FlagPos(var M: TMsg);
213 procedure MC_RECV_GameSettings(var M: TMsg);
214 // PLAYER
215 function MC_RECV_PlayerCreate(var M: TMsg): Word;
216 function MC_RECV_PlayerPos(var M: TMsg): Word;
217 function MC_RECV_PlayerStats(var M: TMsg): Word;
218 function MC_RECV_PlayerDelete(var M: TMsg): Word;
219 function MC_RECV_PlayerDamage(var M: TMsg): Word;
220 function MC_RECV_PlayerDeath(var M: TMsg): Word;
221 function MC_RECV_PlayerFire(var M: TMsg): Word;
222 procedure MC_RECV_PlayerSettings(var M: TMsg);
223 // ITEM
224 procedure MC_RECV_ItemSpawn(var M: TMsg);
225 procedure MC_RECV_ItemDestroy(var M: TMsg);
226 procedure MC_RECV_ItemPos(var M: TMsg);
227 // PANEL
228 procedure MC_RECV_PanelTexture(var M: TMsg);
229 procedure MC_RECV_PanelState(var M: TMsg);
230 // MONSTER
231 procedure MC_RECV_MonsterSpawn(var M: TMsg);
232 procedure MC_RECV_MonsterPos(var M: TMsg);
233 procedure MC_RECV_MonsterState(var M: TMsg);
234 procedure MC_RECV_MonsterShot(var M: TMsg);
235 procedure MC_RECV_MonsterDelete(var M: TMsg);
236 // SHOT
237 procedure MC_RECV_CreateShot(var M: TMsg);
238 procedure MC_RECV_UpdateShot(var M: TMsg);
239 procedure MC_RECV_DeleteShot(var M: TMsg);
240 // TRIGGER
241 procedure MC_RECV_TriggerSound(var M: TMsg);
242 procedure MC_RECV_TriggerMusic(var M: TMsg);
243 // MISC
244 procedure MC_RECV_TimeSync(var M: TMsg);
245 procedure MC_RECV_VoteEvent(var M: TMsg);
246 // SERVICE
247 procedure MC_SEND_Info(Password: string);
248 procedure MC_SEND_Chat(Txt: string; Mode: Byte);
249 procedure MC_SEND_PlayerPos();
250 procedure MC_SEND_FullStateRequest();
251 procedure MC_SEND_PlayerSettings();
252 procedure MC_SEND_CheatRequest(Kind: Byte);
253 procedure MC_SEND_RCONPassword(Password: string);
254 procedure MC_SEND_RCONCommand(Cmd: string);
255 procedure MC_SEND_Vote(Start: Boolean = False; Command: string = 'a');
256 // DOWNLOAD
257 //procedure MC_SEND_MapRequest();
258 //procedure MC_SEND_ResRequest(const resName: AnsiString);
261 type
262 TExternalResourceInfo = record
263 Name: string[255];
264 md5: TMD5Digest;
265 end;
267 TResDataMsg = record
268 MsgId: Byte;
269 FileSize: Integer;
270 FileData: AByte;
271 end;
273 TMapDataMsg = record
274 MsgId: Byte;
275 FileSize: Integer;
276 FileData: AByte;
277 ExternalResources: array of TExternalResourceInfo;
278 end;
280 function IsValidFileName(const S: String): Boolean;
281 function IsValidFilePath(const S: String): Boolean;
284 implementation
286 uses
287 {$IFDEF ENABLE_MENU}
288 g_gui,
289 {$ENDIF}
290 {$IFDEF ENABLE_GFX}
291 g_gfx,
292 {$ENDIF}
293 {$IFDEF ENABLE_SHELLS}
294 g_shells,
295 {$ENDIF}
296 Math, ENet, e_input, e_log, g_base, g_basic,
297 g_textures, g_sound, g_console, g_options,
298 g_game, g_player, g_map, g_panel, g_items, g_weapons, g_phys,
299 g_language, g_monsters, g_netmaster, utils, wadreader, MAPDEF
302 const
303 NET_KEY_LEFT = 1 shl 0;
304 NET_KEY_RIGHT = 1 shl 1;
305 NET_KEY_UP = 1 shl 2;
306 NET_KEY_DOWN = 1 shl 3;
307 NET_KEY_JUMP = 1 shl 4;
308 NET_KEY_FIRE = 1 shl 5;
309 NET_KEY_OPEN = 1 shl 6;
310 NET_KEY_CHAT = 1 shl 7;
311 NET_KEY_FORCEDIR = 1 shl 8;
313 //var
314 //kBytePrev: Word = 0;
315 //kDirPrev: TDirection = D_LEFT;
316 //HostGameTime: Word = 0;
319 function IsValidFileName(const S: String): Boolean;
320 const
321 Forbidden: set of Char = ['<', '>', '|', '"', ':', '*', '?'];
322 var
323 I: Integer;
324 begin
325 Result := S <> '';
326 for I := 1 to Length(S) do
327 Result := Result and (not(S[I] in Forbidden));
328 end;
330 function IsValidFilePath(const S: String): Boolean;
331 var
332 I: Integer;
333 begin
334 Result := False;
335 if not IsValidFileName(S) then exit;
336 if FileExists(S) then exit;
337 I := LastDelimiter('\/', S);
338 if (I > 0) then
339 if (not DirectoryExists(Copy(S, 1, I-1))) then
340 exit;
341 Result := True;
342 end;
345 // HOST MESSAGES //
348 // GAME
350 procedure MH_MalformedPacket(C: pTNetClient);
351 begin
352 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
353 _lc[I_NET_DISC_PROTOCOL]);
354 g_Net_Host_Kick(C^.ID, NET_DISC_PROTOCOL);
355 end;
357 procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg);
358 var
359 Txt: string;
360 Mode: Byte;
361 PID: Word;
362 Pl: TPlayer;
363 Err: Boolean;
364 begin
365 PID := C^.Player;
366 Pl := g_Player_Get(PID);
368 Err := False;
369 try
370 Txt := M.ReadString();
371 Mode := M.ReadByte();
372 except
373 Err := True;
374 end;
376 if Err then begin MH_MalformedPacket(C); Exit; end;
378 if (Mode = NET_CHAT_SYSTEM) then
379 Mode := NET_CHAT_PLAYER; // prevent sending system messages from clients
380 if (Mode = NET_CHAT_TEAM) and (not gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
381 Mode := NET_CHAT_PLAYER; // revert to player chat in non-team game
383 if Pl = nil then
384 MH_SEND_Chat(Txt, Mode)
385 else
386 MH_SEND_Chat(Pl.Name + ': ' + Txt, Mode, IfThen(Mode = NET_CHAT_TEAM, Pl.Team, NET_EVERYONE));
387 end;
389 procedure MH_RECV_Info(C: pTNetClient; var M: TMsg);
390 var
391 Ver, PName, Model, Pw: string;
392 R, G, B, T: Byte;
393 WeapSwitch: Byte;
394 TmpPrefArray: Array [WP_FIRST .. WP_LAST + 1] of Byte;
395 SwitchEmpty: Byte;
396 SkipF: Byte;
397 PID: Word;
398 Color: TRGB;
399 I: Integer;
400 Err: Boolean;
401 begin
402 Err := False;
403 try
404 Ver := M.ReadString();
405 Pw := M.ReadString();
406 PName := M.ReadString();
407 Model := M.ReadString();
408 R := M.ReadByte();
409 G := M.ReadByte();
410 B := M.ReadByte();
411 T := M.ReadByte();
412 WeapSwitch := M.ReadByte();
413 for I := WP_FIRST to WP_LAST + 1 do
414 TmpPrefArray[I] := M.ReadByte();
415 SwitchEmpty := M.ReadByte();
416 SkipF := M.ReadByte();
417 except
418 Err := True;
419 end;
421 if Err then begin MH_MalformedPacket(C); Exit; end;
423 if Ver <> GAME_VERSION then
424 begin
425 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
426 _lc[I_NET_DISC_VERSION]);
427 g_Net_Host_Kick(C^.ID, NET_DISC_VERSION);
428 Exit;
429 end;
431 if g_Net_IsHostBanned(C^.Peer^.address.host) then
432 begin
433 if g_Net_IsHostBanned(C^.Peer^.address.host, True) then
434 begin
435 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
436 _lc[I_NET_DISC_BAN]);
437 g_Net_Host_Kick(C^.ID, NET_DISC_BAN);
438 end
439 else
440 begin
441 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
442 _lc[I_NET_DISC_BAN]);
443 g_Net_Host_Kick(C^.ID, NET_DISC_TEMPBAN);
444 end;
445 Exit;
446 end;
448 if NetPassword <> '' then
449 if AnsiLowerCase(NetPassword) <> AnsiLowerCase(Pw) then
450 begin
451 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
452 _lc[I_NET_DISC_PASSWORD]);
453 g_Net_Host_Kick(C^.ID, NET_DISC_PASSWORD);
454 Exit;
455 end;
457 if (C^.Player <> 0) then
458 begin
459 // already received info
460 g_Net_Penalize(C, 'client info spam');
461 Exit;
462 end;
464 Color.R := R;
465 Color.B := B;
466 Color.G := G;
468 PID := g_Player_Create(Model, Color, T, False);
469 with g_Player_Get(PID) do
470 begin
471 Name := PName;
472 WeapSwitchMode := WeapSwitch;
473 SetWeaponPrefs(TmpPrefArray);
474 SwitchToEmpty := SwitchEmpty;
475 SkipFist := SkipF;
476 if (g_Force_Model_Get() <> 0) then
477 SetModel(g_Forced_Model_GetName());
478 Reset(True);
479 end;
481 C^.Player := PID;
482 C^.WaitForFirstSpawn := false;
483 C^.AuthTime := 0;
485 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True);
486 e_WriteLog('NET: Client ' + PName + ' [' + IntToStr(C^.ID) +
487 '] connected. Assigned player #' + IntToStr(PID) + '.', TMsgType.Notify);
489 MH_SEND_Info(C^.ID);
491 with g_Player_Get(PID) do
492 begin
493 Name := PName;
494 FClientID := C^.ID;
495 // round in progress, don't spawn
496 e_LogWritefln('*** client #%u (cid #%u) authenticated...', [C.ID, C.Player]);
497 //e_LogWritefln('spawning player with pid #%u...', [PID]);
498 //Respawn(gGameSettings.GameType = GT_SINGLE);
499 //k8: no, do not spawn a player yet, wait for "request full state" packet
500 Lives := 0;
501 Spectate;
502 FNoRespawn := True;
503 // `FWantsInGame` seems to mean "spawn the player on the next occasion".
504 // that is, if we'll set it to `true`, the player can be spawned after
505 // warmup time ran out, for example, regardless of the real player state.
506 // also, this seems to work only for the initial connection. further
507 // map changes could initiate resource downloading, but the player will
508 // be spawned immediately.
509 // the proper solution will require another player state, "ephemeral".
510 // the player should start any map in "ephemeral" state, and turned into
511 // real mobj only when they sent a special "i am ready" packet. this packet
512 // must be sent after receiving the full state, so the player will get a full
513 // map view before going into game.
514 FWantsInGame := false;
515 C^.WaitForFirstSpawn := true;
516 end;
518 //if not C^.WaitForFirstSpawn then
519 begin
520 for I := Low(NetClients) to High(NetClients) do
521 begin
522 if NetClients[I].ID = C^.ID then Continue;
523 MH_SEND_PlayerCreate(PID, NetClients[I].ID);
524 MH_SEND_PlayerPos(True, PID, NetClients[I].ID);
525 MH_SEND_PlayerStats(PID, NetClients[I].ID);
526 end;
527 end;
529 if gState in [STATE_INTERCUSTOM, STATE_FOLD] then
530 MH_SEND_GameEvent(NET_EV_MAPEND, 0, 'N', C^.ID);
532 if NetUseMaster then
533 begin
534 //g_Net_Slist_Update;
535 g_Net_Slist_Pulse();
536 end;
537 end;
540 procedure MH_ProcessFirstSpawn (C: pTNetClient);
541 var
542 plr: TPlayer;
543 begin
544 if not C.WaitForFirstSpawn then exit;
545 plr := g_Player_Get(C^.Player);
546 if not assigned(plr) then exit;
547 g_Net_Slist_ServerPlayerComes();
548 e_LogWritefln('*** client #%u (cid #%u) first spawn', [C.ID, C.Player]);
549 C.WaitForFirstSpawn := false;
550 plr.FNoRespawn := false;
551 plr.FWantsInGame := true; // TODO: look into this later
553 if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then
554 begin
555 plr.Spectate;
556 MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, 0, 'N', C.ID);
557 end
558 else
559 begin
560 plr.Respawn(False);
561 if gLMSRespawn > LMS_RESPAWN_NONE then
562 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime, 'N', C.ID);
563 end;
564 end;
567 procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
568 begin
569 //e_LogWritefln('*** client #%u (cid #%u) full state request', [C.ID, C.Player]);
571 if C^.FullUpdateSent then
572 begin
573 // FullStateRequest spam?
574 g_Net_Penalize(C, 'duplicate full state request');
575 exit;
576 end;
578 if gGameOn then
579 begin
580 MH_SEND_Everything((C^.State = NET_STATE_AUTH), C^.ID)
581 end
582 else
583 begin
584 C^.RequestedFullUpdate := True;
585 end;
586 end;
588 // PLAYER
590 function MH_RECV_PlayerPos(C: pTNetClient; var M: TMsg): Word;
591 var
592 Dir, i: Byte;
593 WeaponAct: Byte;
594 WeaponSelect: Word;
595 PID: Word;
596 kByte: Word;
597 Pl: TPlayer;
598 GT: LongWord;
599 Err: Boolean;
600 begin
601 Result := 0;
602 Err := False;
603 if not gGameOn then Exit;
605 try
606 GT := M.ReadLongWord();
607 except
608 Err := True;
609 end;
611 if Err then begin MH_MalformedPacket(C); Exit; end;
613 PID := C^.Player;
614 Pl := g_Player_Get(PID);
615 if Pl = nil then
616 Exit;
618 if (GT > gTime + NET_MAX_DIFFTIME) or (GT < Pl.NetTime) then Exit;
620 with Pl do
621 begin
622 NetTime := GT;
623 try
624 kByte := M.ReadWord();
625 Dir := M.ReadByte();
626 WeaponAct := M.ReadByte();
627 WeaponSelect := M.ReadWord();
628 except
629 Err := True;
630 end;
632 if Err then begin MH_MalformedPacket(C); Exit; end;
634 //e_WriteLog(Format('R:ws=%d', [WeaponSelect]), MSG_WARNING);
635 if Direction <> TDirection(Dir) then
636 JustTeleported := False;
638 SetDirection(TDirection(Dir));
639 ReleaseKeys;
641 if kByte = NET_KEY_CHAT then
642 begin
643 PressKey(KEY_CHAT, 10000);
644 Exit;
645 end;
647 if LongBool(kByte and NET_KEY_LEFT) then PressKey(KEY_LEFT, 10000);
648 if LongBool(kByte and NET_KEY_RIGHT) then PressKey(KEY_RIGHT, 10000);
649 if LongBool(kByte and NET_KEY_UP) then PressKey(KEY_UP, 10000);
650 if LongBool(kByte and NET_KEY_DOWN) then PressKey(KEY_DOWN, 10000);
651 if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000);
652 if LongBool(kByte and NET_KEY_FIRE) then PressKey(KEY_FIRE, 10000);
653 if LongBool(kByte and NET_KEY_OPEN) then PressKey(KEY_OPEN, 10000);
655 for i := 0 to 7 do
656 begin
657 if (WeaponAct and Byte(1 shl i)) <> 0 then
658 begin
659 //e_WriteLog(Format(' R:wn=%d', [i]), MSG_WARNING);
660 ProcessWeaponAction(i);
661 end;
662 end;
664 for i := 0 to 15 do
665 begin
666 if (WeaponSelect and Word(1 shl i)) <> 0 then
667 begin
668 //e_WriteLog(Format(' R:wn=%d', [i]), MSG_WARNING);
669 QueueWeaponSwitch(i);
670 end;
671 end;
672 end;
674 // MH_SEND_PlayerPos(False, PID, C^.ID);
675 end;
677 procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg);
678 var
679 CheatKind: Byte;
680 Pl: TPlayer;
681 Err: Boolean;
682 begin
683 Err := False;
684 Pl := g_Player_Get(C^.Player);
685 if Pl = nil then Exit;
687 try
688 CheatKind := M.ReadByte();
689 except
690 Err := True;
691 end;
693 if Err then begin MH_MalformedPacket(C); Exit; end;
695 case CheatKind of
696 NET_CHEAT_SUICIDE:
697 Pl.Damage(SUICIDE_DAMAGE, Pl.UID, 0, 0, HIT_SELF);
698 NET_CHEAT_SPECTATE:
699 begin
700 if Pl.FSpectator then
701 begin
702 if (gGameSettings.MaxLives = 0) or (gLMSRespawn > LMS_RESPAWN_NONE) then
703 Pl.Respawn(False)
704 else
705 MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, Pl.UID);
706 end
707 else
708 Pl.Spectate;
709 end;
710 NET_CHEAT_READY:
711 begin
712 if gState <> STATE_INTERCUSTOM then Exit;
713 Pl.FReady := not Pl.FReady;
714 if Pl.FReady then
715 begin
716 MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'Y');
717 Inc(gInterReadyCount);
718 end
719 else
720 begin
721 MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'N');
722 Dec(gInterReadyCount);
723 end;
724 end;
725 NET_CHEAT_DROPFLAG:
726 Pl.TryDropFlag();
727 end;
728 end;
730 procedure MH_RECV_PlayerSettings(C: pTNetClient; var M: TMsg);
731 var
732 TmpName: string;
733 TmpModel: string;
734 TmpColor: TRGB;
735 TmpTeam: Byte;
736 TmpWeapSwitch: Byte;
737 TmpPrefArray: Array [WP_FIRST .. WP_LAST + 1] of Byte;
738 TmpSwEmpty: Byte;
739 TmpSkipF: Byte;
740 I: Integer;
741 Pl: TPlayer;
742 Err: Boolean;
743 begin
744 Err := False;
745 try
746 TmpName := M.ReadString();
747 TmpModel := M.ReadString();
748 TmpColor.R := M.ReadByte();
749 TmpColor.G := M.ReadByte();
750 TmpColor.B := M.ReadByte();
751 TmpTeam := M.ReadByte();
752 TmpWeapSwitch := M.ReadByte();
753 for I := WP_FIRST to WP_LAST + 1 do
754 TmpPrefArray[I] := M.ReadByte();
755 TmpSwEmpty := M.ReadByte();
756 TmpSkipF := M.ReadByte();
757 except
758 Err := True;
759 end;
761 if Err then begin MH_MalformedPacket(C); Exit; end;
763 Pl := g_Player_Get(C^.Player);
764 if Pl = nil then Exit;
766 if (gGameSettings.GameMode in [GM_TDM, GM_CTF]) and (Pl.Team <> TmpTeam) then
767 Pl.SwitchTeam
768 else
769 Pl.SetColor(TmpColor);
771 if Pl.Name <> TmpName then
772 begin
773 g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True);
774 Pl.Name := TmpName;
775 end;
777 if (g_Force_Model_Get() <> 0) then
778 TmpModel := g_Forced_Model_GetName();
779 if TmpModel <> Pl.Model.GetName() then
780 Pl.SetModel(TmpModel);
782 if (TmpWeapSwitch <> Pl.WeapSwitchMode) then
783 Pl.WeapSwitchMode := TmpWeapSwitch;
785 Pl.SetWeaponPrefs(TmpPrefArray);
786 if (TmpSwEmpty <> Pl.SwitchToEmpty) then
787 Pl.SwitchToEmpty := TmpSwEmpty;
789 if (TmpSkipF <> Pl.SkipFist) then
790 Pl.SkipFist := TmpSkipF;
792 MH_SEND_PlayerSettings(Pl.UID, TmpModel);
793 end;
795 // RCON
797 procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg);
798 var
799 Pwd: string;
800 Err: Boolean;
801 begin
802 Err := False;
803 try
804 Pwd := M.ReadString();
805 except
806 Err := True;
807 end;
808 if Err then begin MH_MalformedPacket(C); Exit; end;
809 if not NetAllowRCON then Exit;
810 if Pwd = NetRCONPassword then
811 begin
812 C^.RCONAuth := True;
813 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_PWGOOD, 'N', C^.ID);
814 end
815 else
816 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_PWBAD, 'N', C^.ID);
817 end;
819 procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg);
820 var
821 Cmd: string;
822 Err: Boolean;
823 begin
824 Err := False;
825 try
826 Cmd := M.ReadString();
827 except
828 Err := True;
829 end;
830 if Err then begin MH_MalformedPacket(C); Exit; end;
831 if not NetAllowRCON then Exit;
832 if not C^.RCONAuth then
833 begin
834 MH_SEND_GameEvent(NET_EV_RCON, NET_RCON_NOAUTH, 'N', C^.ID);
835 Exit;
836 end;
837 g_Console_Process(Cmd);
838 end;
840 // MISC
842 procedure MH_RECV_Vote(C: pTNetClient; var M: TMsg);
843 var
844 Start: Boolean;
845 Name, Command: string;
846 Need: Integer;
847 Pl: TPlayer;
848 Err: Boolean;
849 begin
850 Err := False;
851 try
852 Start := M.ReadByte() <> 0;
853 Command := M.ReadString();
854 except
855 Err := True;
856 end;
858 if Err then begin MH_MalformedPacket(C); Exit; end;
860 Pl := g_Player_Get(C^.Player);
861 if Pl = nil then Exit;
862 Name := Pl.Name;
864 if Start then
865 begin
866 if not g_Console_CommandBlacklisted(Command) then
867 g_Game_StartVote(Command, Name);
868 end
869 else if gVoteInProgress then
870 begin
871 if (gPlayer1 <> nil) or (gPlayer2 <> nil) then
872 Need := Floor((NetClientCount+1)/2.0) + 1
873 else
874 Need := Floor(NetClientCount/2.0) + 1;
875 if C^.Voted then
876 begin
877 Dec(gVoteCount);
878 C^.Voted := False;
879 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_REVOKED], [Name, gVoteCount, Need]), True);
880 MH_SEND_VoteEvent(NET_VE_REVOKE, Name, 'a', gVoteCount, Need);
881 end
882 else
883 begin
884 Inc(gVoteCount);
885 C^.Voted := True;
886 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Name, gVoteCount, Need]), True);
887 MH_SEND_VoteEvent(NET_VE_VOTE, Name, 'a', gVoteCount, Need);
888 g_Game_CheckVote;
889 end;
890 end;
891 end;
893 // GAME (SEND)
895 procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
897 function sendItemRespawn (it: PItem): Boolean;
898 begin
899 result := false; // don't stop
900 MH_SEND_ItemSpawn(True, it.myid, ID);
901 end;
903 function sendMonSpawn (mon: TMonster): Boolean;
904 begin
905 result := false; // don't stop
906 MH_SEND_MonsterSpawn(mon.UID, ID);
907 end;
909 function sendPanelState (pan: TPanel): Boolean;
910 begin
911 result := false; // don't stop
912 MH_SEND_PanelState(pan.guid, ID); // anyway, to sync mplats
913 if (pan.CanChangeTexture) then MH_SEND_PanelTexture(pan.guid, pan.LastAnimLoop, ID);
914 end;
916 var
917 I: Integer;
918 begin
919 if (ID < 0) or (ID >= Length(NetClients)) then
920 exit; // bogus client, this shouldn't happen
922 NetClients[ID].FullUpdateSent := True;
924 e_LogWritefln('*** client #%u (cid #%u) will get everything', [ID, NetClients[ID].Player]);
926 MH_ProcessFirstSpawn(@NetClients[ID]);
928 if gPlayers <> nil then
929 begin
930 for I := Low(gPlayers) to High(gPlayers) do
931 begin
932 if gPlayers[I] <> nil then
933 begin
934 if CreatePlayers then MH_SEND_PlayerCreate(gPlayers[I].UID, ID);
935 MH_SEND_PlayerPos(True, gPlayers[I].UID, ID);
936 MH_SEND_PlayerStats(gPlayers[I].UID, ID);
938 if (gPlayers[I].Flag <> FLAG_NONE) and (gGameSettings.GameMode = GM_CTF) then
939 begin
940 MH_SEND_FlagEvent(FLAG_STATE_CAPTURED, gPlayers[I].Flag, gPlayers[I].UID, True, ID);
941 end;
942 end;
943 end;
944 end;
946 g_Items_ForEachAlive(sendItemRespawn, true); // backwards
947 g_Mons_ForEach(sendMonSpawn);
948 g_Map_ForEachPanel(sendPanelState);
950 if gTriggers <> nil then
951 begin
952 for I := Low(gTriggers) to High(gTriggers) do
953 begin
954 if gTriggers[I].TriggerType = TRIGGER_SOUND then
955 begin
956 MH_SEND_TriggerSound(gTriggers[I], ID);
957 end;
958 end;
959 end;
961 if Shots <> nil then
962 begin
963 for I := Low(Shots) to High(Shots) do
964 begin
965 if Shots[i].ShotType in [6, 7, 8] then
966 begin
967 MH_SEND_CreateShot(i, ID);
968 end;
969 end;
970 end;
972 MH_SEND_TriggerMusic(ID);
974 MH_SEND_GameStats(ID);
975 MH_SEND_CoopStats(ID);
977 if gGameSettings.GameMode = GM_CTF then
978 begin
979 if gFlags[FLAG_RED].State <> FLAG_STATE_CAPTURED then MH_SEND_FlagEvent(gFlags[FLAG_RED].State, FLAG_RED, 0, True, ID);
980 if gFlags[FLAG_BLUE].State <> FLAG_STATE_CAPTURED then MH_SEND_FlagEvent(gFlags[FLAG_BLUE].State, FLAG_BLUE, 0, True, ID);
981 end;
983 if CreatePlayers and (ID >= 0) then NetClients[ID].State := NET_STATE_GAME;
985 g_Net_Flush();
986 end;
988 procedure MH_SEND_Info(ID: Byte);
989 begin
990 NetOut.Clear();
992 NetOut.Write(Byte(NET_MSG_INFO));
993 NetOut.Write(ID);
994 NetOut.Write(NetClients[ID].Player);
995 NetOut.Write(ExtractFileName(gGameSettings.WAD));
996 NetOut.Write(g_ExtractFileName(gMapInfo.Map));
997 NetOut.Write(gWADHash);
998 NetOut.Write(gGameSettings.GameMode);
999 NetOut.Write(gGameSettings.ScoreLimit);
1000 NetOut.Write(gGameSettings.TimeLimit);
1001 NetOut.Write(gGameSettings.MaxLives);
1002 NetOut.Write(gGameSettings.Options);
1003 NetOut.Write(gTime);
1005 g_Net_Host_Send(ID, True, NET_CHAN_SERVICE);
1006 end;
1008 procedure MH_SEND_Chat(Txt: string; Mode: Byte; ID: Integer = NET_EVERYONE);
1009 var
1010 Name: string;
1011 i: Integer;
1012 Team: Byte;
1013 begin
1014 if (Mode = NET_CHAT_TEAM) and (not gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1015 Mode := NET_CHAT_PLAYER;
1017 Team := 0;
1018 if (Mode = NET_CHAT_TEAM) then
1019 begin
1020 for i := Low(gPlayers) to High(gPlayers) do
1021 if (gPlayers[i] <> nil) and (gPlayers[i].FClientID >= 0) and
1022 (gPlayers[i].Team = ID) then
1023 begin
1024 NetOut.Write(Byte(NET_MSG_CHAT));
1025 NetOut.Write(Txt);
1026 NetOut.Write(Mode);
1027 g_Net_Host_Send(gPlayers[i].FClientID, True, NET_CHAN_CHAT);
1028 end;
1029 Team := ID;
1030 ID := NET_EVERYONE;
1031 end
1032 else
1033 begin
1034 NetOut.Write(Byte(NET_MSG_CHAT));
1035 NetOut.Write(Txt);
1036 NetOut.Write(Mode);
1037 g_Net_Host_Send(ID, True, NET_CHAN_CHAT);
1038 end;
1040 if Mode = NET_CHAT_SYSTEM then
1041 Exit;
1043 if ID = NET_EVERYONE then
1044 begin
1045 if Mode = NET_CHAT_PLAYER then
1046 begin
1047 g_Console_Add(Txt, True);
1048 e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1049 g_Game_ChatSound(b_Text_Unformat(Txt));
1050 end
1051 else
1052 if Mode = NET_CHAT_TEAM then
1053 if gPlayer1 <> nil then
1054 begin
1055 if (gPlayer1.Team = TEAM_RED) and (Team = TEAM_RED) then
1056 begin
1057 g_Console_Add(#18'[Team] '#2 + Txt, True);
1058 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1059 g_Game_ChatSound(b_Text_Unformat(Txt));
1060 end
1061 else if (gPlayer1.Team = TEAM_BLUE) and (Team = TEAM_BLUE) then
1062 begin
1063 g_Console_Add(#20'[Team] '#2 + Txt, True);
1064 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1065 g_Game_ChatSound(b_Text_Unformat(Txt));
1066 end;
1067 end;
1068 end
1069 else
1070 begin
1071 Name := g_Net_ClientName_ByID(ID);
1072 g_Console_Add('-> ' + Name + ': ' + Txt, True);
1073 e_WriteLog('[Tell ' + Name + '] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1074 g_Game_ChatSound(b_Text_Unformat(Txt), False);
1075 end;
1076 end;
1078 procedure MH_SEND_Effect(X, Y: Integer; Ang: SmallInt; Kind: Byte; ID: Integer = NET_EVERYONE);
1079 begin
1080 NetOut.Write(Byte(NET_MSG_GFX));
1081 NetOut.Write(Kind);
1082 NetOut.Write(X);
1083 NetOut.Write(Y);
1084 NetOut.Write(Ang);
1086 g_Net_Host_Send(ID, False, NET_CHAN_GAME);
1087 end;
1089 procedure MH_SEND_Sound(X, Y: Integer; Name: string; Pos: Boolean = True; ID: Integer = NET_EVERYONE);
1090 begin
1091 NetOut.Write(Byte(NET_MSG_SND));
1092 NetOut.Write(Name);
1093 if Pos then
1094 begin
1095 NetOut.Write(Byte(1));
1096 NetOut.Write(X);
1097 NetOut.Write(Y);
1098 end
1099 else
1100 NetOut.Write(Byte(0));
1102 g_Net_Host_Send(ID, False, NET_CHAN_GAME);
1103 end;
1105 procedure MH_SEND_CreateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
1106 begin
1107 if (Shots = nil) or (Proj < 0) or (Proj > High(Shots)) then Exit;
1109 NetOut.Write(Byte(NET_MSG_SHADD));
1110 NetOut.Write(Proj);
1111 NetOut.Write(Shots[Proj].ShotType);
1112 NetOut.Write(Shots[Proj].Target);
1113 NetOut.Write(Shots[Proj].SpawnerUID);
1114 NetOut.Write(Shots[Proj].Timeout);
1115 NetOut.Write(Shots[Proj].Obj.X);
1116 NetOut.Write(Shots[Proj].Obj.Y);
1117 NetOut.Write(Shots[Proj].Obj.Vel.X);
1118 NetOut.Write(Shots[Proj].Obj.Vel.Y);
1120 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
1121 end;
1123 procedure MH_SEND_UpdateShot(Proj: LongInt; ID: Integer = NET_EVERYONE);
1124 begin
1125 if (Shots = nil) or (Proj < 0) or (Proj > High(Shots)) then Exit;
1127 NetOut.Write(Byte(NET_MSG_SHPOS));
1128 NetOut.Write(Proj);
1129 NetOut.Write(Shots[Proj].Obj.X);
1130 NetOut.Write(Shots[Proj].Obj.Y);
1131 NetOut.Write(Shots[Proj].Obj.Vel.X);
1132 NetOut.Write(Shots[Proj].Obj.Vel.Y);
1134 g_Net_Host_Send(ID, False, NET_CHAN_SHOTS);
1135 end;
1137 procedure MH_Send_DeleteShot(Proj: LongInt; X, Y: LongInt; Loud: Boolean = True; ID: Integer = NET_EVERYONE);
1138 begin
1139 NetOut.Write(Byte(NET_MSG_SHDEL));
1140 NetOut.Write(Proj);
1141 NetOut.Write(Byte(Loud));
1142 NetOut.Write(X);
1143 NetOut.Write(Y);
1145 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
1146 end;
1148 procedure MH_SEND_GameStats(ID: Integer = NET_EVERYONE);
1149 begin
1150 NetOut.Write(Byte(NET_MSG_SCORE));
1151 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1152 begin
1153 NetOut.Write(gTeamStat[TEAM_RED].Score);
1154 NetOut.Write(gTeamStat[TEAM_BLUE].Score);
1155 end
1156 else
1157 if gGameSettings.GameMode = GM_COOP then
1158 begin
1159 NetOut.Write(gCoopMonstersKilled);
1160 NetOut.Write(gCoopSecretsFound);
1161 end;
1163 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1164 end;
1166 procedure MH_SEND_CoopStats(ID: Integer = NET_EVERYONE);
1167 begin
1168 NetOut.Write(Byte(NET_MSG_COOP));
1169 NetOut.Write(gTotalMonsters);
1170 NetOut.Write(gSecretsCount);
1171 NetOut.Write(gCoopTotalMonstersKilled);
1172 NetOut.Write(gCoopTotalSecretsFound);
1173 NetOut.Write(gCoopTotalMonsters);
1174 NetOut.Write(gCoopTotalSecrets);
1175 end;
1177 procedure MH_SEND_GameEvent(EvType: Byte; EvNum: Integer = 0; EvStr: string = 'N'; ID: Integer = NET_EVERYONE);
1178 begin
1179 NetOut.Write(Byte(NET_MSG_GEVENT));
1180 NetOut.Write(EvType);
1181 NetOut.Write(EvNum);
1182 NetOut.Write(EvStr);
1183 NetOut.Write(Byte(gLastMap));
1184 NetOut.Write(gTime);
1185 if (EvType = NET_EV_MAPSTART) and isWadPath(EvStr) then
1186 begin
1187 NetOut.Write(Byte(1));
1188 NetOut.Write(gWADHash);
1189 end else
1190 NetOut.Write(Byte(0));
1192 g_Net_Host_Send(ID, True, NET_CHAN_SERVICE);
1193 end;
1195 procedure MH_SEND_FlagEvent(EvType: Byte; Flag: Byte; PID: Word; Quiet: Boolean = False; ID: Integer = NET_EVERYONE);
1196 begin
1197 NetOut.Write(Byte(NET_MSG_FLAG));
1198 NetOut.Write(EvType);
1199 NetOut.Write(Flag);
1200 NetOut.Write(Byte(Quiet));
1201 NetOut.Write(PID);
1202 NetOut.Write(gFlags[Flag].State);
1203 NetOut.Write(gFlags[Flag].CaptureTime);
1204 NetOut.Write(gFlags[Flag].Obj.X);
1205 NetOut.Write(gFlags[Flag].Obj.Y);
1206 NetOut.Write(gFlags[Flag].Obj.Vel.X);
1207 NetOut.Write(gFlags[Flag].Obj.Vel.Y);
1208 NetOut.Write(Byte(gFlags[Flag].Direction));
1210 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1211 end;
1213 procedure MH_SEND_FlagPos(Flag: Byte; ID: Integer = NET_EVERYONE);
1214 begin
1215 NetOut.Write(Byte(NET_MSG_FLAGPOS));
1216 NetOut.Write(Flag);
1217 NetOut.Write(gFlags[Flag].Obj.X);
1218 NetOut.Write(gFlags[Flag].Obj.Y);
1219 NetOut.Write(gFlags[Flag].Obj.Vel.X);
1220 NetOut.Write(gFlags[Flag].Obj.Vel.Y);
1222 g_Net_Host_Send(ID, False, NET_CHAN_IMPORTANT);
1223 end;
1225 procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE);
1226 begin
1227 NetOut.Write(Byte(NET_MSG_GSET));
1228 NetOut.Write(gGameSettings.GameMode);
1229 NetOut.Write(gGameSettings.ScoreLimit);
1230 NetOut.Write(gGameSettings.TimeLimit);
1231 NetOut.Write(gGameSettings.MaxLives);
1232 NetOut.Write(gGameSettings.Options);
1234 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1235 end;
1237 // PLAYER (SEND)
1239 procedure MH_SEND_PlayerCreate(PID: Word; ID: Integer = NET_EVERYONE);
1240 var
1241 P: TPlayer;
1242 begin
1243 P := g_Player_Get(PID);
1244 if P = nil then Exit;
1246 NetOut.Write(Byte(NET_MSG_PLR));
1247 NetOut.Write(PID);
1248 NetOut.Write(P.Name);
1250 NetOut.Write(P.FActualModelName);
1251 NetOut.Write(P.FColor.R);
1252 NetOut.Write(P.FColor.G);
1253 NetOut.Write(P.FColor.B);
1254 NetOut.Write(P.Team);
1256 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT)
1257 end;
1259 procedure MH_SEND_PlayerPos(Reliable: Boolean; PID: Word; ID: Integer = NET_EVERYONE);
1260 var
1261 kByte: Word;
1262 Pl: TPlayer;
1263 begin
1264 Pl := g_Player_Get(PID);
1265 if Pl = nil then Exit;
1266 if Pl.FDummy then Exit;
1268 NetOut.Write(Byte(NET_MSG_PLRPOS));
1269 NetOut.Write(gTime);
1270 NetOut.Write(PID);
1272 kByte := 0;
1274 with Pl do
1275 begin
1276 NetOut.Write(FPing);
1277 NetOut.Write(FLoss);
1278 if IsKeyPressed(KEY_CHAT) then
1279 kByte := NET_KEY_CHAT
1280 else
1281 begin
1282 if IsKeyPressed(KEY_LEFT) then kByte := kByte or NET_KEY_LEFT;
1283 if IsKeyPressed(KEY_RIGHT) then kByte := kByte or NET_KEY_RIGHT;
1284 if IsKeyPressed(KEY_UP) then kByte := kByte or NET_KEY_UP;
1285 if IsKeyPressed(KEY_DOWN) then kByte := kByte or NET_KEY_DOWN;
1286 if IsKeyPressed(KEY_JUMP) then kByte := kByte or NET_KEY_JUMP;
1287 end;
1289 if JustTeleported then kByte := kByte or NET_KEY_FORCEDIR;
1291 NetOut.Write(kByte);
1292 if Direction = TDirection.D_LEFT then NetOut.Write(Byte(0)) else NetOut.Write(Byte(1));
1293 NetOut.Write(GameX);
1294 NetOut.Write(GameY);
1295 NetOut.Write(GameVelX);
1296 NetOut.Write(GameVelY);
1297 NetOut.Write(GameAccelX);
1298 NetOut.Write(GameAccelY);
1299 end;
1301 g_Net_Host_Send(ID, Reliable, NET_CHAN_PLAYERPOS);
1302 end;
1304 procedure MH_SEND_PlayerStats(PID: Word; ID: Integer = NET_EVERYONE);
1305 var
1306 P: TPlayer;
1307 I: Integer;
1308 begin
1309 P := g_Player_Get(PID);
1310 if P = nil then Exit;
1312 NetOut.Write(Byte(NET_MSG_PLRSTA));
1313 NetOut.Write(PID);
1315 with P do
1316 begin
1317 NetOut.Write(Byte(alive));
1318 NetOut.Write(Byte(GodMode));
1319 NetOut.Write(Health);
1320 NetOut.Write(Armor);
1321 NetOut.Write(Air);
1322 NetOut.Write(JetFuel);
1323 NetOut.Write(Lives);
1324 NetOut.Write(Team);
1326 for I := WP_FIRST to WP_LAST do
1327 NetOut.Write(Byte(FWeapon[I]));
1329 for I := A_BULLETS to A_HIGH do
1330 NetOut.Write(FAmmo[I]);
1332 for I := A_BULLETS to A_HIGH do
1333 NetOut.Write(FMaxAmmo[I]);
1335 for I := MR_SUIT to MR_MAX do
1336 NetOut.Write(LongWord(FMegaRulez[I]));
1338 NetOut.Write(Byte(R_ITEM_BACKPACK in FRulez));
1339 NetOut.Write(Byte(R_KEY_RED in FRulez));
1340 NetOut.Write(Byte(R_KEY_GREEN in FRulez));
1341 NetOut.Write(Byte(R_KEY_BLUE in FRulez));
1342 NetOut.Write(Byte(R_BERSERK in FRulez));
1344 NetOut.Write(Frags);
1345 NetOut.Write(Death);
1347 NetOut.Write(CurrWeap);
1349 NetOut.Write(Byte(FSpectator));
1350 NetOut.Write(Byte(FGhost));
1351 NetOut.Write(Byte(FPhysics));
1352 NetOut.Write(Byte(FNoRespawn));
1353 NetOut.Write(Byte(FJetpack));
1354 NetOut.Write(FFireTime);
1355 NetOut.Write(Byte(FFlaming));
1356 NetOut.Write(FSpawnInvul);
1357 end;
1359 g_Net_Host_Send(ID, True, NET_CHAN_PLAYER);
1360 end;
1362 procedure MH_SEND_PlayerDamage(PID: Word; Kind: Byte; Attacker, Value: Word; VX, VY: Integer; ID: Integer = NET_EVERYONE);
1363 begin
1364 NetOut.Write(Byte(NET_MSG_PLRDMG));
1365 NetOut.Write(PID);
1366 NetOut.Write(Kind);
1367 NetOut.Write(Attacker);
1368 NetOut.Write(Value);
1369 NetOut.Write(VX);
1370 NetOut.Write(VY);
1372 g_Net_Host_Send(ID, False, NET_CHAN_PLAYER);
1373 end;
1375 procedure MH_SEND_PlayerDeath(PID: Word; KillType, DeathType: Byte; Attacker: Word; ID: Integer = NET_EVERYONE);
1376 begin
1377 NetOut.Write(Byte(NET_MSG_PLRDIE));
1378 NetOut.Write(PID);
1379 NetOut.Write(KillType);
1380 NetOut.Write(DeathType);
1381 NetOut.Write(Attacker);
1383 g_Net_Host_Send(ID, True, NET_CHAN_PLAYER);
1384 end;
1386 procedure MH_SEND_PlayerFire(PID: Word; Weapon: Byte; X, Y, AX, AY: Integer; ShotID: Integer = -1; ID: Integer = NET_EVERYONE);
1387 begin
1388 NetOut.Write(Byte(NET_MSG_PLRFIRE));
1389 NetOut.Write(PID);
1390 NetOut.Write(Weapon);
1391 NetOut.Write(X);
1392 NetOut.Write(Y);
1393 NetOut.Write(AX);
1394 NetOut.Write(AY);
1395 NetOut.Write(ShotID);
1397 g_Net_Host_Send(ID, True, NET_CHAN_SHOTS);
1398 end;
1400 procedure MH_SEND_PlayerDelete(PID: Word; ID: Integer = NET_EVERYONE);
1401 begin
1402 NetOut.Write(Byte(NET_MSG_PLRDEL));
1403 NetOut.Write(PID);
1405 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1406 end;
1408 procedure MH_SEND_PlayerSettings(PID: Word; Mdl: string = ''; ID: Integer = NET_EVERYONE);
1409 var
1410 Pl: TPlayer;
1411 begin
1412 Pl := g_Player_Get(PID);
1413 if Pl = nil then Exit;
1415 NetOut.Write(Byte(NET_MSG_PLRSET));
1416 NetOut.Write(PID);
1417 NetOut.Write(Pl.Name);
1418 if Mdl = '' then
1419 NetOut.Write(Pl.Model.GetName())
1420 else
1421 NetOut.Write(Mdl);
1422 NetOut.Write(Pl.FColor.R);
1423 NetOut.Write(Pl.FColor.G);
1424 NetOut.Write(Pl.FColor.B);
1425 NetOut.Write(Pl.Team);
1427 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1428 end;
1430 // ITEM (SEND)
1432 procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
1433 var
1434 it: PItem;
1435 tt: Byte;
1436 begin
1437 it := g_Items_ByIdx(IID);
1439 NetOut.Write(Byte(NET_MSG_ISPAWN));
1440 NetOut.Write(IID);
1441 NetOut.Write(Byte(Quiet));
1442 tt := it.ItemType;
1443 if it.dropped then tt := tt or $80;
1444 NetOut.Write(tt);
1445 NetOut.Write(Byte(it.Fall));
1446 NetOut.Write(Byte(it.Respawnable));
1447 NetOut.Write(it.Obj.X);
1448 NetOut.Write(it.Obj.Y);
1449 NetOut.Write(it.Obj.Vel.X);
1450 NetOut.Write(it.Obj.Vel.Y);
1452 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1453 end;
1455 procedure MH_SEND_ItemDestroy(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
1456 begin
1457 NetOut.Write(Byte(NET_MSG_IDEL));
1458 NetOut.Write(IID);
1459 NetOut.Write(Byte(Quiet));
1461 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1462 end;
1464 procedure MH_SEND_ItemPos(IID: Word; ID: Integer = NET_EVERYONE);
1465 var
1466 it: PItem;
1467 begin
1468 it := g_Items_ByIdx(IID);
1470 NetOut.Write(Byte(NET_MSG_IPOS));
1471 NetOut.Write(IID);
1472 NetOut.Write(it.Obj.X);
1473 NetOut.Write(it.Obj.Y);
1474 NetOut.Write(it.Obj.Vel.X);
1475 NetOut.Write(it.Obj.Vel.Y);
1477 g_Net_Host_Send(ID, False, NET_CHAN_LARGEDATA);
1478 end;
1480 // PANEL
1482 procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE);
1483 var
1484 TP: TPanel;
1485 begin
1486 TP := g_Map_PanelByGUID(PGUID);
1487 if (TP = nil) then exit;
1489 with TP do
1490 begin
1491 NetOut.Write(Byte(NET_MSG_PTEX));
1492 NetOut.Write(LongWord(PGUID));
1493 NetOut.Write(FCurTexture);
1494 NetOut.Write(FCurFrame);
1495 NetOut.Write(FCurFrameCount);
1496 NetOut.Write(AnimLoop);
1497 end;
1499 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1500 end;
1502 procedure MH_SEND_PanelState(PGUID: Integer; ID: Integer = NET_EVERYONE);
1503 var
1504 TP: TPanel;
1505 mpflags: Byte = 0;
1506 begin
1507 TP := g_Map_PanelByGUID(PGUID);
1508 if (TP = nil) then exit;
1510 NetOut.Write(Byte(NET_MSG_PSTATE));
1511 NetOut.Write(LongWord(PGUID));
1512 NetOut.Write(Byte(TP.Enabled));
1513 NetOut.Write(TP.LiftType);
1514 NetOut.Write(TP.X);
1515 NetOut.Write(TP.Y);
1516 NetOut.Write(Word(TP.Width));
1517 NetOut.Write(Word(TP.Height));
1518 // mplats
1519 NetOut.Write(LongInt(TP.movingSpeedX));
1520 NetOut.Write(LongInt(TP.movingSpeedY));
1521 NetOut.Write(LongInt(TP.movingStartX));
1522 NetOut.Write(LongInt(TP.movingStartY));
1523 NetOut.Write(LongInt(TP.movingEndX));
1524 NetOut.Write(LongInt(TP.movingEndY));
1525 NetOut.Write(LongInt(TP.sizeSpeedX));
1526 NetOut.Write(LongInt(TP.sizeSpeedY));
1527 NetOut.Write(LongInt(TP.sizeEndX));
1528 NetOut.Write(LongInt(TP.sizeEndY));
1529 if TP.movingActive then mpflags := mpflags or 1;
1530 if TP.moveOnce then mpflags := mpflags or 2;
1531 NetOut.Write(Byte(mpflags));
1533 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1534 end;
1536 // TRIGGER
1538 procedure MH_SEND_TriggerSound(var T: TTrigger; ID: Integer = NET_EVERYONE);
1539 begin
1540 if gTriggers = nil then Exit;
1541 if T.Sound = nil then Exit;
1543 NetOut.Write(Byte(NET_MSG_TSOUND));
1544 NetOut.Write(T.ClientID);
1545 NetOut.Write(Byte(T.Sound.IsPlaying));
1546 NetOut.Write(LongWord(T.Sound.GetPosition));
1547 NetOut.Write(T.SoundPlayCount);
1549 g_Net_Host_Send(ID, True);
1550 end;
1552 procedure MH_SEND_TriggerMusic(ID: Integer = NET_EVERYONE);
1553 begin
1554 NetOut.Write(Byte(NET_MSG_TMUSIC));
1555 NetOut.Write(gMusic.Name);
1556 NetOut.Write(Byte(gMusic.IsPlaying));
1557 NetOut.Write(LongWord(gMusic.GetPosition));
1558 NetOut.Write(Byte(gMusic.SpecPause or gMusic.IsPaused));
1560 g_Net_Host_Send(ID, True);
1561 end;
1563 // MONSTER
1565 procedure MH_SEND_MonsterSpawn(UID: Word; ID: Integer = NET_EVERYONE);
1566 var
1567 M: TMonster;
1568 begin
1569 M := g_Monsters_ByUID(UID);
1570 if M = nil then
1571 Exit;
1573 with M do
1574 begin
1575 NetOut.Write(Byte(NET_MSG_MSPAWN));
1576 NetOut.Write(UID);
1577 NetOut.Write(MonsterType);
1578 NetOut.Write(MonsterState);
1579 NetOut.Write(MonsterAnim);
1580 NetOut.Write(MonsterTargetUID);
1581 NetOut.Write(MonsterTargetTime);
1582 NetOut.Write(MonsterBehaviour);
1583 NetOut.Write(MonsterSleep);
1584 NetOut.Write(MonsterHealth);
1585 NetOut.Write(MonsterAmmo);
1586 NetOut.Write(GameX);
1587 NetOut.Write(GameY);
1588 NetOut.Write(GameVelX);
1589 NetOut.Write(GameVelY);
1590 NetOut.Write(Byte(GameDirection));
1591 end;
1593 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1594 end;
1596 procedure MH_SEND_MonsterPos(UID: Word; ID: Integer = NET_EVERYONE);
1597 var
1598 M: TMonster;
1599 begin
1600 M := g_Monsters_ByUID(UID);
1601 if M = nil then Exit;
1603 NetOut.Write(Byte(NET_MSG_MPOS));
1604 NetOut.Write(UID);
1606 with M do
1607 begin
1608 NetOut.Write(GameX);
1609 NetOut.Write(GameY);
1610 NetOut.Write(GameVelX);
1611 NetOut.Write(GameVelY);
1612 NetOut.Write(Byte(GameDirection));
1613 end;
1615 g_Net_Host_Send(ID, False, NET_CHAN_MONSTERPOS);
1616 end;
1618 procedure MH_SEND_MonsterState(UID: Word; ForcedAnim: Byte = 255; ID: Integer = NET_EVERYONE);
1619 var
1620 M: TMonster;
1621 begin
1622 M := g_Monsters_ByUID(UID);
1623 if M = nil then Exit;
1625 NetOut.Write(Byte(NET_MSG_MSTATE));
1626 NetOut.Write(UID);
1628 with M do
1629 begin
1630 NetOut.Write(MonsterState);
1631 NetOut.Write(ForcedAnim);
1632 NetOut.Write(MonsterTargetUID);
1633 NetOut.Write(MonsterTargetTime);
1634 NetOut.Write(MonsterSleep);
1635 NetOut.Write(MonsterHealth);
1636 NetOut.Write(MonsterAmmo);
1637 NetOut.Write(MonsterPain);
1638 NetOut.Write(Byte(AnimIsReverse));
1639 NetOut.Write(FFireTime);
1640 end;
1642 g_Net_Host_Send(ID, True, NET_CHAN_MONSTER);
1643 end;
1645 procedure MH_SEND_MonsterShot(UID: Word; X, Y, VX, VY: Integer; ID: Integer = NET_EVERYONE);
1646 begin
1647 NetOut.Write(Byte(NET_MSG_MSHOT));
1648 NetOut.Write(UID);
1649 NetOut.Write(X);
1650 NetOut.Write(Y);
1651 NetOut.Write(VX);
1652 NetOut.Write(VY);
1654 g_Net_Host_Send(ID, True, NET_CHAN_MONSTER);
1655 end;
1657 procedure MH_SEND_MonsterDelete(UID: Word; ID: Integer = NET_EVERYONE);
1658 var
1659 M: TMonster;
1660 begin
1661 M := g_Monsters_ByUID(UID);
1662 if M = nil then Exit;
1664 NetOut.Write(Byte(NET_MSG_MDEL));
1665 NetOut.Write(UID);
1667 g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
1668 end;
1670 // MISC
1672 procedure MH_SEND_TimeSync(Time: LongWord; ID: Integer = NET_EVERYONE);
1673 begin
1674 NetOut.Write(Byte(NET_MSG_TIME_SYNC));
1675 NetOut.Write(Time);
1677 g_Net_Host_Send(ID, False, NET_CHAN_SERVICE);
1678 end;
1680 procedure MH_SEND_VoteEvent(EvType: Byte;
1681 StrArg1: string = 'a'; StrArg2: string = 'b';
1682 IntArg1: SmallInt = 0; IntArg2: SmallInt = 0;
1683 ID: Integer = NET_EVERYONE);
1684 begin
1685 NetOut.Write(Byte(NET_MSG_VOTE_EVENT));
1686 NetOut.Write(EvType);
1687 NetOut.Write(IntArg1);
1688 NetOut.Write(IntArg2);
1689 NetOut.Write(StrArg1);
1690 NetOut.Write(StrArg2);
1692 g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT);
1693 end;
1695 // CLIENT MESSAGES //
1697 // GAME
1699 procedure MC_RECV_Chat(var M: TMsg);
1700 var
1701 Txt: string;
1702 Mode: Byte;
1703 begin
1704 Txt := M.ReadString();
1705 Mode := M.ReadByte();
1707 if Mode <> NET_CHAT_SYSTEM then
1708 begin
1709 if NetDeafLevel = 0 then
1710 begin
1711 if Mode = NET_CHAT_PLAYER then
1712 begin
1713 g_Console_Add(Txt, True);
1714 e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1715 g_Game_ChatSound(b_Text_Unformat(Txt));
1716 end else
1717 if (Mode = NET_CHAT_TEAM) and (gPlayer1 <> nil) then
1718 begin
1719 if gPlayer1.Team = TEAM_RED then
1720 g_Console_Add(b_Text_Format('\r[Team] ') + Txt, True);
1721 if gPlayer1.Team = TEAM_BLUE then
1722 g_Console_Add(b_Text_Format('\b[Team] ') + Txt, True);
1723 e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify);
1724 g_Game_ChatSound(b_Text_Unformat(Txt));
1725 end;
1726 end;
1727 end else if (NetDeafLevel < 2) then
1728 g_Console_Add(Txt, True);
1729 end;
1731 procedure MC_RECV_Effect(var M: TMsg);
1732 var
1733 Kind: Byte;
1734 X, Y: Integer;
1735 Ang: SmallInt;
1736 begin
1737 if not gGameOn then Exit;
1738 Kind := M.ReadByte();
1739 X := M.ReadLongInt();
1740 Y := M.ReadLongInt();
1741 Ang := M.ReadSmallInt();
1743 case Kind of
1744 NET_GFX_SPARK:
1745 begin
1746 {$IFDEF ENABLE_GFX}
1747 g_GFX_Spark(X, Y, 2 + Random(2), Ang, 0, 0);
1748 {$ENDIF}
1749 end;
1750 NET_GFX_TELE:
1751 begin
1752 {$IFDEF ENABLE_GFX}
1753 g_GFX_QueueEffect(R_GFX_TELEPORT_FAST, X, Y);
1754 {$ENDIF}
1755 if Ang = 1 then
1756 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', X, Y);
1757 end;
1758 NET_GFX_EXPLODE:
1759 begin
1760 {$IFDEF ENABLE_GFX}
1761 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, X - 64, Y - 64);
1762 {$ENDIF}
1763 if Ang = 1 then
1764 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', X, Y);
1765 end;
1766 NET_GFX_BFGEXPL:
1767 begin
1768 {$IFDEF ENABLE_GFX}
1769 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, X - 64, Y - 64);
1770 {$ENDIF}
1771 if Ang = 1 then
1772 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', X, Y);
1773 end;
1774 NET_GFX_BFGHIT:
1775 begin
1776 {$IFDEF ENABLE_GFX}
1777 g_GFX_QueueEffect(R_GFX_BFG_HIT, X - 32, Y - 32);
1778 {$ENDIF}
1779 end;
1780 NET_GFX_FIRE:
1781 begin
1782 {$IFDEF ENABLE_GFX}
1783 g_GFX_QueueEffect(R_GFX_FIRE, X, Y);
1784 {$ENDIF}
1785 if Ang = 1 then
1786 g_Sound_PlayExAt('SOUND_FIRE', X, Y);
1787 end;
1788 NET_GFX_RESPAWN:
1789 begin
1790 {$IFDEF ENABLE_GFX}
1791 g_GFX_QueueEffect(R_GFX_ITEM_RESPAWN, X, Y);
1792 {$ENDIF}
1793 if Ang = 1 then
1794 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
1795 end;
1796 NET_GFX_SHELL1:
1797 begin
1798 {$IFDEF ENABLE_SHELLS}
1799 g_Shells_Create(X, Y, 0, -2, SHELL_BULLET);
1800 {$ENDIF}
1801 end;
1802 NET_GFX_SHELL2:
1803 begin
1804 {$IFDEF ENABLE_SHELLS}
1805 g_Shells_Create(X, Y, 0, -2, SHELL_SHELL);
1806 {$ENDIF}
1807 end;
1808 NET_GFX_SHELL3:
1809 begin
1810 {$IFDEF ENABLE_SHELLS}
1811 g_Shells_Create(X, Y, 0, -2, SHELL_SHELL);
1812 g_Shells_Create(X, Y, 0, -2, SHELL_SHELL);
1813 {$ENDIF}
1814 end;
1815 end;
1816 end;
1818 procedure MC_RECV_Sound(var M: TMsg);
1819 var
1820 Name: string;
1821 X, Y: Integer;
1822 Pos: Boolean;
1823 begin
1824 Name := M.ReadString();
1825 Pos := M.ReadByte() <> 0;
1826 if Pos then
1827 begin
1828 X := M.ReadLongInt();
1829 Y := M.ReadLongInt();
1830 g_Sound_PlayExAt(Name, X, Y);
1831 end
1832 else
1833 g_Sound_PlayEx(Name);
1834 end;
1836 procedure MC_RECV_CreateShot(var M: TMsg);
1837 var
1838 I, X, Y, XV, YV: Integer;
1839 Timeout: LongWord;
1840 Target, Spawner: Word;
1841 ShType: Byte;
1842 begin
1843 I := M.ReadLongInt();
1844 ShType := M.ReadByte();
1845 Target := M.ReadWord();
1846 Spawner := M.ReadWord();
1847 Timeout := M.ReadLongWord();
1848 X := M.ReadLongInt();
1849 Y := M.ReadLongInt();
1850 XV := M.ReadLongInt();
1851 YV := M.ReadLongInt();
1853 I := g_Weapon_CreateShot(I, ShType, Spawner, Target, X, Y, XV, YV);
1854 if (Shots <> nil) and (I <= High(Shots)) then
1855 begin
1856 Shots[I].Timeout := Timeout;
1857 //Shots[I].Target := Target; // TODO: find a use for Target later
1858 end;
1859 end;
1861 procedure MC_RECV_UpdateShot(var M: TMsg);
1862 var
1863 I, TX, TY, TXV, TYV: Integer;
1864 begin
1865 I := M.ReadLongInt();
1866 TX := M.ReadLongInt();
1867 TY := M.ReadLongInt();
1868 TXV := M.ReadLongInt();
1869 TYV := M.ReadLongInt();
1871 if (Shots <> nil) and (I <= High(Shots)) then
1872 with (Shots[i]) do
1873 begin
1874 Obj.X := TX;
1875 Obj.Y := TY;
1876 Obj.Vel.X := TXV;
1877 Obj.Vel.Y := TYV;
1878 end;
1879 end;
1881 procedure MC_RECV_DeleteShot(var M: TMsg);
1882 var
1883 I, X, Y: Integer;
1884 L: Boolean;
1885 begin
1886 if not gGameOn then Exit;
1887 I := M.ReadLongInt();
1888 L := (M.ReadByte() <> 0);
1889 X := M.ReadLongInt();
1890 Y := M.ReadLongInt();
1892 g_Weapon_DestroyShot(I, X, Y, L);
1893 end;
1895 procedure MC_RECV_GameStats(var M: TMsg);
1896 begin
1897 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1898 begin
1899 gTeamStat[TEAM_RED].Score := M.ReadSmallInt();
1900 gTeamStat[TEAM_BLUE].Score := M.ReadSmallInt();
1901 end
1902 else
1903 if gGameSettings.GameMode = GM_COOP then
1904 begin
1905 gCoopMonstersKilled := M.ReadWord();
1906 gCoopSecretsFound := M.ReadWord();
1907 end;
1908 end;
1910 procedure MC_RECV_CoopStats(var M: TMsg);
1911 begin
1912 gTotalMonsters := M.ReadLongInt();
1913 gSecretsCount := M.ReadLongInt();
1914 gCoopTotalMonstersKilled := M.ReadWord();
1915 gCoopTotalSecretsFound := M.ReadWord();
1916 gCoopTotalMonsters := M.ReadWord();
1917 gCoopTotalSecrets := M.ReadWord();
1918 end;
1920 procedure MC_RECV_GameEvent(var M: TMsg);
1921 var
1922 EvType: Byte;
1923 EvNum: Integer;
1924 EvStr: string;
1925 EvTime: LongWord;
1926 BHash: Boolean;
1927 EvHash: TMD5Digest;
1928 pl: TPlayer;
1929 i1, i2: TStrings_Locale;
1930 pln: String;
1931 cnt: Byte;
1932 goodCmd: Boolean = true;
1933 begin
1934 FillChar(EvHash, Sizeof(EvHash), 0);
1935 EvType := M.ReadByte();
1936 EvNum := M.ReadLongInt();
1937 EvStr := M.ReadString();
1938 gLastMap := M.ReadByte() <> 0;
1939 if gLastMap and (gGameSettings.GameMode = GM_COOP) then gStatsOff := True;
1940 gStatsPressed := True;
1941 EvTime := M.ReadLongWord();
1942 BHash := M.ReadByte() <> 0;
1943 if BHash then
1944 EvHash := M.ReadMD5();
1946 gTime := EvTime;
1948 if (g_Res_received_map_start <> 0) then
1949 begin
1950 if (g_Res_received_map_start < 0) then exit;
1951 goodCmd := false;
1952 case EvType of
1953 NET_EV_MAPSTART: goodCmd := true;
1954 NET_EV_MAPEND: goodCmd := true;
1955 NET_EV_PLAYER_KICK: goodCmd := true;
1956 NET_EV_PLAYER_BAN: goodCmd := true;
1957 NET_EV_LMS_WARMUP: goodCmd := true;
1958 end;
1959 if not goodCmd then exit;
1960 end;
1962 case EvType of
1963 NET_EV_MAPSTART:
1964 begin
1965 if (g_Res_received_map_start <> 0) then
1966 begin
1967 g_Res_received_map_start := -1;
1968 end
1969 else
1970 begin
1971 gGameOn := False;
1972 g_Game_ClearLoading();
1973 g_Game_StopAllSounds(True);
1975 gSwitchGameMode := Byte(EvNum);
1976 gGameSettings.GameMode := gSwitchGameMode;
1978 gWADHash := EvHash;
1979 if not g_Game_StartMap(false{asMegawad}, EvStr, True) then
1980 begin
1981 if not isWadPath(EvStr) then
1982 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [gGameSettings.WAD + ':\' + EvStr]))
1983 else
1984 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [EvStr]));
1985 Exit;
1986 end;
1988 MC_SEND_FullStateRequest;
1989 end;
1990 end;
1992 NET_EV_MAPEND:
1993 begin
1994 gLMSRespawn := LMS_RESPAWN_NONE;
1995 gLMSRespawnTime := 0;
1996 if (g_Res_received_map_start <> 0) then
1997 begin
1998 g_Res_received_map_start := -1;
1999 end
2000 else
2001 begin
2002 gMissionFailed := EvNum <> 0;
2003 gExit := EXIT_ENDLEVELCUSTOM;
2004 end;
2005 end;
2007 NET_EV_RCON:
2008 begin
2009 case EvNum of
2010 NET_RCON_NOAUTH:
2011 g_Console_Add(_lc[I_NET_RCON_NOAUTH], True);
2012 NET_RCON_PWGOOD:
2013 g_Console_Add(_lc[I_NET_RCON_PWD_VALID], True);
2014 NET_RCON_PWBAD:
2015 g_Console_Add(_lc[I_NET_RCON_PWD_INVALID], True);
2016 end;
2017 end;
2019 NET_EV_CHANGE_TEAM:
2020 begin
2021 if NetDeafLevel < 2 then
2022 begin
2023 if EvNum = TEAM_RED then
2024 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [EvStr]), True);
2025 if EvNum = TEAM_BLUE then
2026 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [EvStr]), True);
2027 end;
2028 end;
2030 NET_EV_PLAYER_KICK:
2031 begin
2032 g_Console_Add(Format(_lc[I_PLAYER_KICK], [EvStr]), True);
2033 if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
2034 end;
2036 NET_EV_PLAYER_BAN:
2037 begin
2038 g_Console_Add(Format(_lc[I_PLAYER_BAN], [EvStr]), True);
2039 if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
2040 end;
2042 NET_EV_LMS_WARMUP:
2043 begin
2044 if EvNum > 0 then
2045 begin
2046 gLMSRespawn := LMS_RESPAWN_WARMUP;
2047 gLMSRespawnTime := gTime + EvNum;
2048 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [EvNum div 1000]), True);
2049 end
2050 else if gPlayer1 = nil then
2051 begin
2052 g_Console_Add(_lc[I_PLAYER_SPECT4], True);
2053 end;
2054 end;
2056 NET_EV_LMS_SURVIVOR:
2057 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True);
2059 NET_EV_BIGTEXT:
2060 if NetDeafLevel < 2 then g_Game_Message(AnsiUpperCase(EvStr), Word(EvNum));
2062 NET_EV_SCORE:
2063 begin
2064 pl := g_Player_Get(EvNum and $FFFF);
2065 if pl = nil then
2066 pln := '?'
2067 else
2068 pln := pl.Name;
2069 cnt := (EvNum shr 16) and $FF;
2070 if Pos('w', EvStr) = 0 then
2071 begin
2072 // Default score
2073 if Pos('t', EvStr) = 0 then
2074 begin
2075 // Player +/- score
2076 if Pos('-', EvStr) = 0 then
2077 begin
2078 if Pos('e', EvStr) = 0 then
2079 i1 := I_PLAYER_SCORE_ADD_OWN
2080 else
2081 i1 := I_PLAYER_SCORE_ADD_ENEMY;
2082 end else
2083 begin
2084 if Pos('e', EvStr) = 0 then
2085 i1 := I_PLAYER_SCORE_SUB_OWN
2086 else
2087 i1 := I_PLAYER_SCORE_SUB_ENEMY;
2088 end;
2089 // Which team
2090 if Pos('r', EvStr) > 0 then
2091 i2 := I_PLAYER_SCORE_TO_RED
2092 else
2093 i2 := I_PLAYER_SCORE_TO_BLUE;
2094 g_Console_Add(Format(_lc[i1], [pln, cnt, _lc[i2]]), True);
2095 end else
2096 begin
2097 // Team +/- score
2098 if Pos('-', EvStr) = 0 then
2099 i1 := I_PLAYER_SCORE_ADD_TEAM
2100 else
2101 i1 := I_PLAYER_SCORE_SUB_TEAM;
2102 // Which team
2103 if Pos('r', EvStr) > 0 then
2104 i2 := I_PLAYER_SCORE_RED
2105 else
2106 i2 := I_PLAYER_SCORE_BLUE;
2107 g_Console_Add(Format(_lc[i1], [_lc[i2], cnt]), True);
2108 end;
2109 end else
2110 begin
2111 // Game Win
2112 if Pos('e', EvStr) = 0 then
2113 i1 := I_PLAYER_SCORE_WIN_OWN
2114 else
2115 i1 := I_PLAYER_SCORE_WIN_ENEMY;
2116 // Which team
2117 if Pos('r', EvStr) > 0 then
2118 i2 := I_PLAYER_SCORE_TO_RED
2119 else
2120 i2 := I_PLAYER_SCORE_TO_BLUE;
2121 g_Console_Add(Format(_lc[i1], [pln, _lc[i2]]), True);
2122 end;
2123 end;
2125 NET_EV_SCORE_MSG:
2126 begin
2127 if EvNum = TEAM_RED then
2128 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
2129 if EvNum = TEAM_BLUE then
2130 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
2131 if EvNum = -TEAM_RED then
2132 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
2133 if EvNum = -TEAM_BLUE then
2134 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
2135 end;
2137 NET_EV_LMS_START:
2138 begin
2139 g_Player_RemoveAllCorpses;
2140 gLMSRespawn := LMS_RESPAWN_NONE;
2141 g_Game_Message(_lc[I_MESSAGE_LMS_START], 144);
2142 end;
2144 NET_EV_LMS_WIN:
2145 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(EvStr)]), 144);
2147 NET_EV_TLMS_WIN:
2148 begin
2149 if EvNum = TEAM_RED then
2150 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
2151 if EvNum = TEAM_BLUE then
2152 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
2153 end;
2155 NET_EV_LMS_LOSE:
2156 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
2158 NET_EV_LMS_DRAW:
2159 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2161 NET_EV_LMS_NOSPAWN:
2162 g_Console_Add(_lc[I_PLAYER_SPECT4], True);
2164 NET_EV_KILLCOMBO:
2165 g_Game_Announce_KillCombo(EvNum);
2167 NET_EV_PLAYER_TOUCH:
2168 begin
2169 pl := g_Player_Get(EvNum);
2170 if pl <> nil then
2171 pl.Touch();
2172 end;
2174 NET_EV_SECRET:
2175 begin
2176 pl := g_Player_Get(EvNum);
2177 if pl <> nil then
2178 begin
2179 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [pl.Name]), True);
2180 g_Sound_PlayEx('SOUND_GAME_SECRET');
2181 end;
2182 end;
2184 NET_EV_INTER_READY:
2185 begin
2186 pl := g_Player_Get(EvNum);
2187 if pl <> nil then pl.FReady := (EvStr = 'Y');
2188 end;
2189 end;
2190 end;
2192 procedure MC_RECV_FlagPos(var M: TMsg);
2193 var
2194 Fl: Byte;
2195 begin
2196 Fl := M.ReadByte();
2197 if Fl = FLAG_NONE then Exit;
2198 gFlags[Fl].Obj.X := M.ReadLongInt();
2199 gFlags[Fl].Obj.Y := M.ReadLongInt();
2200 gFlags[Fl].Obj.Vel.X := M.ReadLongInt();
2201 gFlags[Fl].Obj.Vel.Y := M.ReadLongInt();
2202 end;
2204 procedure MC_RECV_FlagEvent(var M: TMsg);
2205 var
2206 PID: Word;
2207 Pl: TPlayer;
2208 EvType: Byte;
2209 Fl, a: Byte;
2210 Quiet: Boolean;
2211 s, ts: string;
2212 begin
2213 EvType := M.ReadByte();
2214 Fl := M.ReadByte();
2216 if Fl = FLAG_NONE then Exit;
2218 Quiet := (M.ReadByte() <> 0);
2219 PID := M.ReadWord();
2221 gFlags[Fl].State := M.ReadByte();
2222 gFlags[Fl].CaptureTime := M.ReadLongWord();
2223 gFlags[Fl].Obj.X := M.ReadLongInt();
2224 gFlags[Fl].Obj.Y := M.ReadLongInt();
2225 gFlags[Fl].Obj.Vel.X := M.ReadLongInt();
2226 gFlags[Fl].Obj.Vel.Y := M.ReadLongInt();
2227 gFlags[Fl].Direction := TDirection(M.ReadByte());
2229 Pl := g_Player_Get(PID);
2230 if (Pl = nil) and
2231 (EvType <> FLAG_STATE_NORMAL) and
2232 (EvType <> FLAG_STATE_DROPPED) and
2233 (EvType <> FLAG_STATE_RETURNED) then
2234 Exit;
2236 case EvType of
2237 FLAG_STATE_NORMAL:
2238 begin
2239 if Quiet or (Pl = nil) then Exit;
2241 if Fl = FLAG_RED then
2242 s := _lc[I_PLAYER_FLAG_RED]
2243 else
2244 s := _lc[I_PLAYER_FLAG_BLUE];
2246 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2248 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2249 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2250 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2251 a := 0
2252 else
2253 a := 1;
2255 if not sound_ret_flag[a].IsPlaying() then
2256 sound_ret_flag[a].Play();
2257 end;
2259 FLAG_STATE_CAPTURED:
2260 begin
2261 if (Pl <> nil) then Pl.SetFlag(Fl);
2263 if Quiet then Exit;
2265 if Fl = FLAG_RED then
2266 s := _lc[I_PLAYER_FLAG_RED]
2267 else
2268 s := _lc[I_PLAYER_FLAG_BLUE];
2270 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [Pl.Name, s]), True);
2271 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
2273 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2274 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2275 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2276 a := 0
2277 else
2278 a := 1;
2280 if not sound_get_flag[a].IsPlaying() then
2281 sound_get_flag[a].Play();
2282 end;
2284 FLAG_STATE_DROPPED:
2285 begin
2286 if (Pl <> nil) then Pl.SetFlag(FLAG_NONE);
2288 if Quiet or (Pl = nil) then Exit;
2290 if Fl = FLAG_RED then
2291 s := _lc[I_PLAYER_FLAG_RED]
2292 else
2293 s := _lc[I_PLAYER_FLAG_BLUE];
2295 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [Pl.Name, s]), True);
2296 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
2298 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2299 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2300 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2301 a := 0
2302 else
2303 a := 1;
2305 if not sound_lost_flag[a].IsPlaying() then
2306 sound_lost_flag[a].Play();
2307 end;
2309 FLAG_STATE_SCORED:
2310 begin
2311 g_Map_ResetFlag(FLAG_RED);
2312 g_Map_ResetFlag(FLAG_BLUE);
2313 if Quiet or (Pl = nil) then Exit;
2314 Pl.SetFlag(FLAG_NONE);
2316 if Fl = FLAG_RED then
2317 s := _lc[I_PLAYER_FLAG_RED]
2318 else
2319 s := _lc[I_PLAYER_FLAG_BLUE];
2321 ts := Format('%.4d', [gFlags[Fl].CaptureTime]);
2322 Insert('.', ts, Length(ts) + 1 - 3);
2323 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [Pl.Name, s, ts]), True);
2324 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
2326 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2327 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2328 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2329 a := 0
2330 else
2331 a := 1;
2333 if not sound_cap_flag[a].IsPlaying() then
2334 sound_cap_flag[a].Play();
2335 end;
2337 FLAG_STATE_RETURNED:
2338 begin
2339 g_Map_ResetFlag(Fl);
2340 if Quiet then Exit;
2342 if Fl = FLAG_RED then
2343 s := _lc[I_PLAYER_FLAG_RED]
2344 else
2345 s := _lc[I_PLAYER_FLAG_BLUE];
2347 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
2349 if ((Pl = gPlayer1) or (Pl = gPlayer2)
2350 or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
2351 or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
2352 a := 0
2353 else
2354 a := 1;
2356 if not sound_ret_flag[a].IsPlaying() then
2357 sound_ret_flag[a].Play();
2358 end;
2359 end;
2360 end;
2362 procedure MC_RECV_GameSettings(var M: TMsg);
2363 begin
2364 gGameSettings.GameMode := M.ReadByte();
2365 gGameSettings.ScoreLimit := M.ReadWord();
2366 gGameSettings.TimeLimit := M.ReadWord();
2367 gGameSettings.MaxLives := M.ReadByte();
2368 gGameSettings.Options := M.ReadLongWord();
2369 end;
2371 // PLAYER
2373 function MC_RECV_PlayerCreate(var M: TMsg): Word;
2374 var
2375 PID, DID: Word;
2376 PName, Model: string;
2377 Color: TRGB;
2378 T: Byte;
2379 Pl: TPlayer;
2380 begin
2381 PID := M.ReadWord();
2382 Pl := g_Player_Get(PID);
2384 PName := M.ReadString();
2385 Model := M.ReadString();
2386 Color.R := M.ReadByte();
2387 Color.G := M.ReadByte();
2388 Color.B := M.ReadByte();
2389 T := M.ReadByte();
2391 Result := 0;
2392 if (PID <> NetPlrUID1) and (PID <> NetPlrUID2) then
2393 begin
2394 if (Pl <> nil) then Exit;
2395 if (g_Force_Model_Get() <> 0) then
2396 Model := g_Forced_Model_GetName();
2397 DID := g_Player_Create(Model, Color, T, False);
2398 with g_Player_Get(DID) do
2399 begin
2400 UID := PID;
2401 Name := PName;
2402 Reset(True);
2403 end;
2404 end
2405 else
2406 begin
2407 if (PID = NetPlrUID1) and (gPlayer1 <> nil) then begin
2408 gPlayer1.UID := PID;
2409 gPlayer1.Model.SetColor(Color.R, Color.G, Color.B);
2410 gPlayer1.ChangeTeam(T);
2411 end;
2412 if (PID = NetPlrUID2) and (gPlayer2 <> nil) then begin
2413 gPlayer2.UID := PID;
2414 gPlayer2.Model.SetColor(Color.R, Color.G, Color.B);
2415 gPlayer2.ChangeTeam(T);
2416 end;
2417 end;
2419 if NetDeafLevel < 3 then
2420 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True);
2421 e_WriteLog('NET: Player ' + PName + ' [' + IntToStr(PID) + '] added.', TMsgType.Notify);
2422 Result := PID;
2423 end;
2425 function MC_RECV_PlayerPos(var M: TMsg): Word;
2426 var
2427 GT: LongWord;
2428 PID: Word;
2429 kByte: Word;
2430 Pl: TPlayer;
2431 Dir: Byte;
2432 TmpX, TmpY: Integer;
2433 begin
2434 Result := 0;
2436 GT := M.ReadLongWord();
2437 if GT < gTime - NET_MAX_DIFFTIME then
2438 begin
2439 gTime := GT;
2440 Exit;
2441 end;
2442 gTime := GT;
2444 PID := M.ReadWord();
2445 Pl := g_Player_Get(PID);
2447 if Pl = nil then Exit;
2449 Result := PID;
2451 with Pl do
2452 begin
2453 FPing := M.ReadWord();
2454 FLoss := M.ReadByte();
2455 kByte := M.ReadWord();
2456 Dir := M.ReadByte();
2458 TmpX := M.ReadLongInt();
2459 TmpY := M.ReadLongInt();
2461 ReleaseKeys;
2463 if LongBool(kByte and NET_KEY_CHAT) then
2464 PressKey(KEY_CHAT, 10000)
2465 else
2466 begin
2467 if LongBool(kByte and NET_KEY_LEFT) then PressKey(KEY_LEFT, 10000);
2468 if LongBool(kByte and NET_KEY_RIGHT) then PressKey(KEY_RIGHT, 10000);
2469 if LongBool(kByte and NET_KEY_UP) then PressKey(KEY_UP, 10000);
2470 if LongBool(kByte and NET_KEY_DOWN) then PressKey(KEY_DOWN, 10000);
2471 if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000);
2472 end;
2474 JustTeleported := LongBool(kByte and NET_KEY_FORCEDIR);
2476 if ((Pl <> gPlayer1) and (Pl <> gPlayer2)) or JustTeleported then
2477 SetDirection(TDirection(Dir));
2479 GameVelX := M.ReadLongInt();
2480 GameVelY := M.ReadLongInt();
2481 GameAccelX := M.ReadLongInt();
2482 GameAccelY := M.ReadLongInt();
2483 SetLerp(TmpX, TmpY);
2484 if NetForcePlayerUpdate then Update();
2485 end;
2486 end;
2488 function MC_RECV_PlayerStats(var M: TMsg): Word;
2489 var
2490 PID: Word;
2491 Pl: TPlayer;
2492 I, OldFire: Integer;
2493 OldJet, Flam: Boolean;
2494 NewTeam: Byte;
2495 begin
2496 PID := M.ReadWord();
2497 Pl := g_Player_Get(PID);
2498 Result := 0;
2499 if Pl = nil then
2500 Exit;
2502 with Pl do
2503 begin
2504 alive := (M.ReadByte() <> 0);
2505 GodMode := (M.ReadByte() <> 0);
2506 Health := M.ReadLongInt();
2507 Armor := M.ReadLongInt();
2508 Air := M.ReadLongInt();
2509 JetFuel := M.ReadLongInt();
2510 Lives := M.ReadByte();
2511 NewTeam := M.ReadByte();
2513 for I := WP_FIRST to WP_LAST do
2514 FWeapon[I] := (M.ReadByte() <> 0);
2516 for I := A_BULLETS to A_HIGH do
2517 FAmmo[I] := M.ReadWord();
2519 for I := A_BULLETS to A_HIGH do
2520 FMaxAmmo[I] := M.ReadWord();
2522 for I := MR_SUIT to MR_MAX do
2523 FMegaRulez[I] := M.ReadLongWord();
2525 FRulez := [];
2526 if (M.ReadByte() <> 0) then
2527 FRulez := FRulez + [R_ITEM_BACKPACK];
2528 if (M.ReadByte() <> 0) then
2529 FRulez := FRulez + [R_KEY_RED];
2530 if (M.ReadByte() <> 0) then
2531 FRulez := FRulez + [R_KEY_GREEN];
2532 if (M.ReadByte() <> 0) then
2533 FRulez := FRulez + [R_KEY_BLUE];
2534 if (M.ReadByte() <> 0) then
2535 FRulez := FRulez + [R_BERSERK];
2537 Frags := M.ReadLongInt();
2538 Death := M.ReadLongInt();
2540 SetWeapon(M.ReadByte());
2542 FSpectator := M.ReadByte() <> 0;
2543 if FSpectator then
2544 begin
2545 if UID = NetPlrUID1 then
2546 begin
2547 gSpectLatchPID1 := UID;
2548 gPlayer1 := nil;
2549 end;
2550 if UID = NetPlrUID2 then
2551 begin
2552 gSpectLatchPID2 := UID;
2553 gPlayer2 := nil;
2554 end;
2555 end
2556 else
2557 begin
2558 if (gPlayer1 = nil) and (gSpectLatchPID1 > 0) and (UID = gSpectLatchPID1) then
2559 begin
2560 gPlayer1 := Pl;
2561 gSpectLatchPID1 := 0;
2562 end;
2563 if (gPlayer2 = nil) and (gSpectLatchPID2 > 0) and (UID = gSpectLatchPID2) then
2564 begin
2565 gPlayer2 := Pl;
2566 gSpectLatchPID2 := 0;
2567 end;
2568 end;
2569 FGhost := M.ReadByte() <> 0;
2570 FPhysics := M.ReadByte() <> 0;
2571 FNoRespawn := M.ReadByte() <> 0;
2572 OldJet := FJetpack;
2573 FJetpack := M.ReadByte() <> 0;
2574 OldFire := FFireTime;
2575 FFireTime := M.ReadLongInt();
2576 if (OldFire <= 0) and (FFireTime > 0) then
2577 g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y);
2578 Flam := M.ReadByte() <> 0;
2579 FSpawnInvul := M.ReadLongInt();
2580 if OldJet and not FJetpack then
2581 JetpackOff
2582 else if not OldJet and FJetpack then
2583 JetpackOn;
2584 if FFlaming and not Flam then
2585 FlamerOff;
2586 if Team <> NewTeam then
2587 Pl.ChangeTeam(NewTeam);
2588 end;
2590 Result := PID;
2591 end;
2593 function MC_RECV_PlayerDamage(var M: TMsg): Word;
2594 var
2595 PID: Word;
2596 Pl: TPlayer;
2597 Kind: Byte;
2598 Attacker, Value: Word;
2599 VX, VY: Integer;
2600 begin
2601 Result := 0;
2602 if not gGameOn then Exit;
2603 PID := M.ReadWord();
2604 Pl := g_Player_Get(PID);
2605 if Pl = nil then Exit;
2607 Kind := M.ReadByte();
2608 Attacker := M.ReadWord();
2609 Value := M.ReadWord();
2610 VX := M.ReadWord();
2611 VY := M.ReadWord();
2613 with Pl do
2614 Damage(Value, Attacker, VX, VY, Kind);
2616 Result := PID;
2617 end;
2619 function MC_RECV_PlayerDeath(var M: TMsg): Word;
2620 var
2621 PID: Word;
2622 Pl: TPlayer;
2623 KillType, DeathType: Byte;
2624 Attacker: Word;
2625 begin
2626 Result := 0;
2627 if not gGameOn then Exit;
2628 PID := M.ReadWord();
2629 Pl := g_Player_Get(PID);
2630 if Pl = nil then Exit;
2632 KillType := M.ReadByte();
2633 DeathType := M.ReadByte();
2634 Attacker := M.ReadWord();
2636 with Pl do
2637 begin
2638 Kill(KillType, Attacker, DeathType);
2639 SoftReset;
2640 end;
2641 end;
2643 function MC_RECV_PlayerDelete(var M: TMsg): Word;
2644 var
2645 PID: Word;
2646 Pl: TPlayer;
2647 begin
2648 PID := M.ReadWord();
2649 Pl := g_Player_Get(PID);
2650 Result := 0;
2651 if Pl = nil then Exit;
2653 if NetDeafLevel < 3 then
2654 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
2655 e_WriteLog('NET: Player ' + Pl.Name + ' [' + IntToStr(PID) + '] removed.', TMsgType.Notify);
2657 g_Player_Remove(PID);
2659 Result := PID;
2660 end;
2662 function MC_RECV_PlayerFire(var M: TMsg): Word;
2663 var
2664 PID: Word;
2665 Weap: Byte;
2666 Pl: TPlayer;
2667 X, Y, AX, AY: Integer;
2668 SHID: Integer;
2669 begin
2670 Result := 0;
2671 if not gGameOn then Exit;
2672 PID := M.ReadWord();
2673 Pl := g_Player_Get(PID);
2674 if Pl = nil then Exit;
2676 Weap := M.ReadByte();
2677 X := M.ReadLongInt();
2678 Y := M.ReadLongInt();
2679 AX := M.ReadLongInt();
2680 AY := M.ReadLongInt();
2681 SHID := M.ReadLongInt();
2683 with Pl do
2684 if alive then NetFire(Weap, X, Y, AX, AY, SHID);
2685 end;
2687 procedure MC_RECV_PlayerSettings(var M: TMsg);
2688 var
2689 TmpName: string;
2690 TmpModel: string;
2691 CheckModel: string;
2692 TmpColor: TRGB;
2693 TmpTeam: Byte;
2694 Pl: TPlayer;
2695 PID: Word;
2696 begin
2697 PID := M.ReadWord();
2698 Pl := g_Player_Get(PID);
2699 if Pl = nil then Exit;
2701 TmpName := M.ReadString();
2702 TmpModel := M.ReadString();
2703 TmpColor.R := M.ReadByte();
2704 TmpColor.G := M.ReadByte();
2705 TmpColor.B := M.ReadByte();
2706 TmpTeam := M.ReadByte();
2708 if (gGameSettings.GameMode in [GM_TDM, GM_CTF]) and (Pl.Team <> TmpTeam) then
2709 begin
2710 Pl.ChangeTeam(TmpTeam);
2711 if gPlayer1 = Pl then
2712 gPlayer1Settings.Team := TmpTeam;
2713 if gPlayer2 = Pl then
2714 gPlayer2Settings.Team := TmpTeam;
2715 end else
2716 Pl.SetColor(TmpColor);
2718 if Pl.Name <> TmpName then
2719 begin
2720 if NetDeafLevel < 3 then
2721 g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True);
2722 Pl.Name := TmpName;
2723 end;
2725 if (g_Force_Model_Get() <> 0) then
2726 TmpModel := g_Forced_Model_GetName();
2727 if TmpModel <> Pl.Model.GetName() then
2728 Pl.SetModel(TmpModel);
2729 end;
2731 // ITEM
2733 procedure MC_RECV_ItemSpawn(var M: TMsg);
2734 var
2735 ID: Word;
2736 X, Y, VX, VY: Integer;
2737 T: Byte;
2738 Quiet, Fall{, Resp}: Boolean;
2739 it: PItem;
2740 begin
2741 if not gGameOn then Exit;
2742 ID := M.ReadWord();
2743 Quiet := M.ReadByte() <> 0;
2744 T := M.ReadByte();
2745 Fall := M.ReadByte() <> 0;
2746 {Resp :=} M.ReadByte();
2747 X := M.ReadLongInt();
2748 Y := M.ReadLongInt();
2749 VX := M.ReadLongInt();
2750 VY := M.ReadLongInt();
2752 g_Items_Create(X, Y, T and $7F, Fall, False, False, ID);
2753 if ((T and $80) <> 0) then g_Items_SetDrop(ID);
2755 it := g_Items_ByIdx(ID);
2756 it.Obj.Vel.X := VX;
2757 it.Obj.Vel.Y := VY;
2759 if not Quiet then
2760 begin
2761 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
2762 {$IFDEF ENABLE_GFX}
2763 g_GFX_QueueEffect(R_GFX_ITEM_RESPAWN, X+(it.Obj.Rect.Width div 2)-16, Y+(it.Obj.Rect.Height div 2)-16);
2764 {$ENDIF}
2765 end;
2766 end;
2768 procedure MC_RECV_ItemDestroy(var M: TMsg);
2769 var
2770 ID: Word;
2771 Quiet: Boolean;
2772 begin
2773 if not gGameOn then Exit;
2774 ID := M.ReadWord();
2775 Quiet := M.ReadByte() <> 0;
2777 if not g_Items_ValidId(ID) then exit;
2779 if not Quiet then g_Items_EmitPickupSound(ID);
2781 g_Items_Remove(ID);
2782 end;
2784 procedure MC_RECV_ItemPos(var M: TMsg);
2785 var
2786 ID: Word;
2787 X, Y, VX, VY: Integer;
2788 it: PItem;
2789 begin
2790 if not gGameOn then Exit;
2792 ID := M.ReadWord();
2793 X := M.ReadLongInt();
2794 Y := M.ReadLongInt();
2795 VX := M.ReadLongInt();
2796 VY := M.ReadLongInt();
2798 if g_Items_ValidId(ID) then
2799 begin
2800 it := g_Items_ByIdx(ID);
2801 it.Obj.X := X;
2802 it.Obj.Y := Y;
2803 it.Obj.Vel.X := VX;
2804 it.Obj.Vel.Y := VY;
2805 it.positionChanged();
2806 end;
2807 end;
2809 // PANEL
2811 procedure MC_RECV_PanelTexture(var M: TMsg);
2812 var
2813 TP: TPanel;
2814 PGUID: Integer;
2815 Tex, Fr: Integer;
2816 Loop, Cnt: Byte;
2817 begin
2818 if not gGameOn then Exit;
2820 PGUID := Integer(M.ReadLongWord());
2821 Tex := M.ReadLongInt();
2822 Fr := M.ReadLongInt();
2823 Cnt := M.ReadByte();
2824 Loop := M.ReadByte();
2826 TP := g_Map_PanelByGUID(PGUID);
2827 if (TP <> nil) then
2828 begin
2829 // switch texture
2830 TP.SetTexture(Tex, Loop);
2831 TP.SetFrame(Fr, Cnt);
2832 end;
2833 end;
2835 procedure MC_RECV_PanelState(var M: TMsg);
2836 var
2837 PGUID: Integer;
2838 E: Boolean;
2839 Lift: Byte;
2840 X, Y, W, H: Integer;
2841 TP: TPanel;
2842 speedX, speedY, startX, startY, endX, endY: Integer;
2843 sizeSpX, sizeSpY, sizeEX, sizeEY: Integer;
2844 mpflags: Byte;
2845 begin
2846 if not gGameOn then Exit;
2848 PGUID := Integer(M.ReadLongWord());
2849 E := (M.ReadByte() <> 0);
2850 Lift := M.ReadByte();
2851 X := M.ReadLongInt();
2852 Y := M.ReadLongInt();
2853 W := M.ReadWord();
2854 H := M.ReadWord();
2855 // mplats
2856 speedX := M.ReadLongInt();
2857 speedY := M.ReadLongInt();
2858 startX := M.ReadLongInt();
2859 startY := M.ReadLongInt();
2860 endX := M.ReadLongInt();
2861 endY := M.ReadLongInt();
2862 sizeSpX := M.ReadLongInt();
2863 sizeSpY := M.ReadLongInt();
2864 sizeEX := M.ReadLongInt();
2865 sizeEY := M.ReadLongInt();
2866 mpflags := M.ReadByte(); // bit0: TP.movingActive; bit1: TP.moveOnce
2868 TP := g_Map_PanelByGUID(PGUID);
2869 if (TP = nil) then exit;
2871 // update lifts state
2872 if TP.isGLift then g_Map_SetLiftGUID(PGUID, Lift);
2874 // update enabled/disabled state for all panels
2875 if E then g_Map_EnableWallGUID(PGUID) else g_Map_DisableWallGUID(PGUID);
2877 // update panel position, as it can be moved (mplat)
2878 TP.X := X;
2879 TP.Y := Y;
2880 TP.Width := W;
2881 TP.Height := H;
2882 // update mplat state
2883 TP.movingSpeedX := speedX;
2884 TP.movingSpeedY := speedY;
2885 TP.movingStartX := startX;
2886 TP.movingStartY := startY;
2887 TP.movingEndX := endX;
2888 TP.movingEndY := endY;
2889 TP.sizeSpeedX := sizeSpX;
2890 TP.sizeSpeedY := sizeSpY;
2891 TP.sizeEndX := sizeEX;
2892 TP.sizeEndY := sizeEY;
2893 TP.movingActive := ((mpflags and 1) <> 0);
2894 TP.moveOnce := ((mpflags and 2) <> 0);
2895 // notify panel of it's position/size change, so it can fix other internal structures
2896 TP.positionChanged();
2897 end;
2899 // TRIGGERS
2901 procedure MC_RECV_TriggerSound(var M: TMsg);
2902 var
2903 SPlaying: Boolean;
2904 SPos, SID: LongWord;
2905 SCount: LongInt;
2906 I: Integer;
2907 begin
2908 if not gGameOn then Exit;
2909 if gTriggers = nil then Exit;
2911 SID := M.ReadLongWord();
2912 SPlaying := M.ReadByte() <> 0;
2913 SPos := M.ReadLongWord();
2914 SCount := M.ReadLongInt();
2916 for I := Low(gTriggers) to High(gTriggers) do
2917 if gTriggers[I].TriggerType = TRIGGER_SOUND then
2918 if gTriggers[I].ClientID = SID then
2919 with gTriggers[I] do
2920 begin
2921 if Sound <> nil then
2922 begin
2923 if SPlaying then
2924 begin
2925 if tgcLocal then
2926 Sound.PlayVolumeAt(X+(Width div 2), Y+(Height div 2), tgcVolume/255.0)
2927 else
2928 Sound.PlayPanVolume((tgcPan-127.0)/128.0, tgcVolume/255.0);
2929 Sound.SetPosition(SPos);
2930 end
2931 else
2932 if Sound.IsPlaying then Sound.Stop;
2933 end;
2935 SoundPlayCount := SCount;
2936 end;
2937 end;
2939 procedure MC_RECV_TriggerMusic(var M: TMsg);
2940 var
2941 MName: string;
2942 MPlaying: Boolean;
2943 MPos: LongWord;
2944 MPaused: Boolean;
2945 begin
2946 if not gGameOn then Exit;
2948 MName := M.ReadString();
2949 MPlaying := M.ReadByte() <> 0;
2950 MPos := M.ReadLongWord();
2951 MPaused := M.ReadByte() <> 0;
2952 MPos := MPos+1; //k8: stfu, fpc!
2954 if MPlaying then
2955 begin
2956 gMusic.SetByName(MName);
2957 gMusic.Play(True);
2958 // gMusic.SetPosition(MPos);
2959 gMusic.SpecPause := MPaused;
2960 end
2961 else
2962 if gMusic.IsPlaying then gMusic.Stop;
2963 end;
2965 // MONSTERS
2967 procedure MC_RECV_MonsterSpawn(var M: TMsg);
2968 var
2969 ID: Word;
2970 MType, MState, MDir, MAnim, MBehav: Byte;
2971 X, Y, VX, VY, MTargTime, MHealth, MAmmo, MSleep: Integer;
2972 MTarg: Word;
2973 Mon: TMonster;
2974 begin
2975 ID := M.ReadWord();
2976 Mon := g_Monsters_ByUID(ID);
2977 if Mon <> nil then
2978 Exit;
2980 MType := M.ReadByte();
2981 MState := M.ReadByte();
2982 MAnim := M.ReadByte();
2983 MTarg := M.ReadWord();
2984 MTargTime := M.ReadLongInt();
2985 MBehav := M.ReadByte();
2986 MSleep := M.ReadLongInt();
2987 MHealth := M.ReadLongInt();
2988 MAmmo := M.ReadLongInt();
2990 X := M.ReadLongInt();
2991 Y := M.ReadLongInt();
2992 VX := M.ReadLongInt();
2993 VY := M.ReadLongInt();
2994 MDir := M.ReadByte();
2996 g_Monsters_Create(MType, X, Y, TDirection(MDir), False, ID);
2997 Mon := g_Monsters_ByUID(ID);
2998 if Mon = nil then
2999 Exit;
3001 with Mon do
3002 begin
3004 MonsterAnim := MAnim;
3005 MonsterTargetUID := MTarg;
3006 MonsterTargetTime := MTargTime;
3007 MonsterBehaviour := MBehav;
3008 MonsterSleep := MSleep;
3009 MonsterAmmo := MAmmo;
3010 SetHealth(MHealth);
3012 SetState(MState);
3014 setPosition(X, Y); // this will call positionChanged();
3015 GameVelX := VX;
3016 GameVelY := VY;
3017 end;
3018 end;
3020 procedure MC_RECV_MonsterPos(var M: TMsg);
3021 var
3022 Mon: TMonster;
3023 ID: Word;
3024 X, Y: Integer;
3025 begin
3026 ID := M.ReadWord();
3027 Mon := g_Monsters_ByUID(ID);
3028 if Mon = nil then
3029 Exit;
3031 with Mon do
3032 begin
3033 X := M.ReadLongInt();
3034 Y := M.ReadLongInt();
3035 Mon.setPosition(X, Y); // this will call `positionChanged()`
3036 GameVelX := M.ReadLongInt();
3037 GameVelY := M.ReadLongInt();
3038 GameDirection := TDirection(M.ReadByte());
3039 end;
3040 end;
3042 procedure MC_RECV_MonsterState(var M: TMsg);
3043 var
3044 ID, OldFire: Integer;
3045 MState, MFAnm: Byte;
3046 Mon: TMonster;
3047 AnimRevert: Boolean;
3048 begin
3049 ID := M.ReadWord();
3050 Mon := g_Monsters_ByUID(ID);
3051 if Mon = nil then Exit;
3053 MState := M.ReadByte();
3054 MFAnm := M.ReadByte();
3056 with Mon do
3057 begin
3058 MonsterTargetUID := M.ReadWord();
3059 MonsterTargetTime := M.ReadLongInt();
3060 MonsterSleep := M.ReadLongInt();
3061 MonsterHealth := M.ReadLongInt();
3062 MonsterAmmo := M.ReadLongInt();
3063 MonsterPain := M.ReadLongInt();
3064 AnimRevert := M.ReadByte() <> 0;
3065 OldFire := FFireTime;
3066 FFireTime := M.ReadLongInt();
3067 if (OldFire <= 0) and (FFireTime > 0) then
3068 g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y);
3069 RevertAnim(AnimRevert);
3071 if MonsterState <> MState then
3072 begin
3073 if (MState = MONSTATE_GO) and (MonsterState = MONSTATE_SLEEP) then WakeUpSound();
3074 if (MState = MONSTATE_DIE) then DieSound();
3075 if (MState = MONSTATE_PAIN) then MakeBloodSimple(Min(200, MonsterPain));
3076 if (MState = MONSTATE_ATTACK) then kick(nil);
3077 if (MState = MONSTATE_DEAD) then SetDeadAnim();
3079 SetState(MState, MFAnm);
3080 end;
3081 end;
3082 end;
3084 procedure MC_RECV_MonsterShot(var M: TMsg);
3085 var
3086 ID: Integer;
3087 Mon: TMonster;
3088 X, Y, VX, VY: Integer;
3089 begin
3090 ID := M.ReadWord();
3092 Mon := g_Monsters_ByUID(ID);
3093 if Mon = nil then Exit;
3095 X := M.ReadLongInt();
3096 Y := M.ReadLongInt();
3097 VX := M.ReadLongInt();
3098 VY := M.ReadLongInt();
3100 Mon.ClientAttack(X, Y, VX, VY);
3101 end;
3103 procedure MC_RECV_MonsterDelete(var M: TMsg);
3104 var
3105 ID: Integer;
3106 Mon: TMonster;
3107 begin
3108 ID := M.ReadWord();
3109 Mon := g_Monsters_ByUID(ID);
3110 if Mon = nil then Exit;
3111 Mon.SetState(5);
3112 Mon.MonsterRemoved := True;
3113 end;
3115 procedure MC_RECV_TimeSync(var M: TMsg);
3116 var
3117 Time: LongWord;
3118 begin
3119 Time := M.ReadLongWord();
3121 if gState = STATE_INTERCUSTOM then
3122 gServInterTime := Min(Time, 255);
3123 end;
3125 procedure MC_RECV_VoteEvent(var M: TMsg);
3126 var
3127 EvID: Byte;
3128 Str1, Str2: string;
3129 Int1, Int2: SmallInt;
3130 begin
3131 EvID := M.ReadByte();
3132 Int1 := M.ReadSmallInt();
3133 Int2 := M.ReadSmallInt();
3134 Str1 := M.ReadString();
3135 Str2 := M.ReadString();
3137 if NetDeafLevel < 2 then
3138 case EvID of
3139 NET_VE_STARTED:
3140 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_STARTED], [Str1, Str2, Int1]), True);
3141 NET_VE_PASSED:
3142 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_PASSED], [Str1]), True);
3143 NET_VE_FAILED:
3144 g_Console_Add(_lc[I_MESSAGE_VOTE_FAILED], True);
3145 NET_VE_VOTE:
3146 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Str1, Int1, Int2]), True);
3147 NET_VE_INPROGRESS:
3148 g_Console_Add(Format(_lc[I_MESSAGE_VOTE_INPROGRESS], [Str1]), True);
3149 end;
3150 end;
3152 // CLIENT SEND
3154 procedure MC_SEND_Info(Password: string);
3155 var i: Integer;
3156 begin
3157 NetOut.Clear();
3159 NetOut.Write(Byte(NET_MSG_INFO));
3160 NetOut.Write(GAME_VERSION);
3161 NetOut.Write(Password);
3162 NetOut.Write(gPlayer1Settings.Name);
3163 NetOut.Write(gPlayer1Settings.Model);
3164 NetOut.Write(gPlayer1Settings.Color.R);
3165 NetOut.Write(gPlayer1Settings.Color.G);
3166 NetOut.Write(gPlayer1Settings.Color.B);
3167 NetOut.Write(gPlayer1Settings.Team);
3168 NetOut.Write(gPlayer1Settings.WeaponSwitch);
3169 for i := WP_FIRST to WP_LAST + 1 do
3170 NetOut.Write(gPlayer1Settings.WeaponPreferences[i]);
3171 NetOut.Write(gPlayer1Settings.SwitchToEmpty);
3172 NetOut.Write(gPlayer1Settings.SkipFist);
3174 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3175 end;
3177 procedure MC_SEND_Chat(Txt: string; Mode: Byte);
3178 begin
3179 NetOut.Write(Byte(NET_MSG_CHAT));
3180 NetOut.Write(Txt);
3181 NetOut.Write(Mode);
3183 g_Net_Client_Send(True, NET_CHAN_CHAT);
3184 end;
3186 procedure MC_SEND_PlayerPos();
3187 var
3188 kByte: Word;
3189 Predict: Boolean;
3190 strafeDir: Byte;
3191 WeaponAct: Byte = 0;
3192 WeaponSelect: Word = 0;
3193 i: Integer;
3194 begin
3195 if not gGameOn then Exit;
3196 if gPlayers = nil then Exit;
3197 if gPlayer1 = nil then Exit;
3199 kByte := 0;
3200 Predict := NetPredictSelf; // and (not NetGotKeys);
3202 {$IFDEF DISABLE_MENU}
3203 if (not gConsoleShow) and (not gChatShow) then
3204 {$ELSE}
3205 if (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
3206 {$ENDIF}
3207 begin
3208 strafeDir := P1MoveButton shr 4;
3209 P1MoveButton := P1MoveButton and $0F;
3211 if gPlayerAction[0, ACTION_MOVELEFT] and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
3212 P1MoveButton := 1
3213 else if (not gPlayerAction[0, ACTION_MOVELEFT]) and gPlayerAction[0, ACTION_MOVERIGHT] then
3214 P1MoveButton := 2
3215 else if (not gPlayerAction[0, ACTION_MOVELEFT]) and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
3216 P1MoveButton := 0;
3218 // strafing
3219 if gPlayerAction[0, ACTION_STRAFE] then
3220 begin
3221 // new strafe mechanics
3222 if (strafeDir = 0) then
3223 strafeDir := P1MoveButton; // start strafing
3224 // now set direction according to strafe (reversed)
3225 if (strafeDir = 2) then
3226 gPlayer1.SetDirection(TDirection.D_LEFT)
3227 else if (strafeDir = 1) then
3228 gPlayer1.SetDirection(TDirection.D_RIGHT)
3229 end
3230 else
3231 begin
3232 strafeDir := 0; // not strafing anymore
3233 if (P1MoveButton = 2) and gPlayerAction[0, ACTION_MOVELEFT] then
3234 gPlayer1.SetDirection(TDirection.D_LEFT)
3235 else if (P1MoveButton = 1) and gPlayerAction[0, ACTION_MOVERIGHT] then
3236 gPlayer1.SetDirection(TDirection.D_RIGHT)
3237 else if P1MoveButton <> 0 then
3238 gPlayer1.SetDirection(TDirection(P1MoveButton-1));
3239 end;
3241 gPlayer1.ReleaseKeys;
3242 if P1MoveButton = 1 then
3243 begin
3244 kByte := kByte or NET_KEY_LEFT;
3245 if Predict then gPlayer1.PressKey(KEY_LEFT, 10000);
3246 end;
3247 if P1MoveButton = 2 then
3248 begin
3249 kByte := kByte or NET_KEY_RIGHT;
3250 if Predict then gPlayer1.PressKey(KEY_RIGHT, 10000);
3251 end;
3252 if gPlayerAction[0, ACTION_LOOKUP] then
3253 begin
3254 kByte := kByte or NET_KEY_UP;
3255 gPlayer1.PressKey(KEY_UP, 10000);
3256 end;
3257 if gPlayerAction[0, ACTION_LOOKDOWN] then
3258 begin
3259 kByte := kByte or NET_KEY_DOWN;
3260 gPlayer1.PressKey(KEY_DOWN, 10000);
3261 end;
3262 if gPlayerAction[0, ACTION_JUMP] then
3263 begin
3264 kByte := kByte or NET_KEY_JUMP;
3265 // gPlayer1.PressKey(KEY_JUMP, 10000); // TODO: Make a prediction option
3266 end;
3267 if gPlayerAction[0, ACTION_ATTACK] then kByte := kByte or NET_KEY_FIRE;
3268 if gPlayerAction[0, ACTION_ACTIVATE] then kByte := kByte or NET_KEY_OPEN;
3270 for i := WP_FACT to WP_LACT do
3271 begin
3272 if gWeaponAction[0, i] then
3273 begin
3274 WeaponAct := WeaponAct or Byte(1 shl i);
3275 gWeaponAction[0, i] := False
3276 end
3277 end;
3279 for i := WP_FIRST to WP_LAST do
3280 begin
3281 if gSelectWeapon[0, i] then
3282 begin
3283 WeaponSelect := WeaponSelect or Word(1 shl i);
3284 gSelectWeapon[0, i] := False
3285 end
3286 end;
3288 // fix movebutton state
3289 P1MoveButton := P1MoveButton or (strafeDir shl 4);
3290 end
3291 else
3292 kByte := NET_KEY_CHAT;
3294 NetOut.Write(Byte(NET_MSG_PLRPOS));
3295 NetOut.Write(gTime);
3296 NetOut.Write(kByte);
3297 NetOut.Write(Byte(gPlayer1.Direction));
3298 NetOut.Write(WeaponAct);
3299 NetOut.Write(WeaponSelect);
3300 //e_WriteLog(Format('S:ws=%d', [WeaponSelect]), MSG_WARNING);
3301 g_Net_Client_Send(True, NET_CHAN_PLAYERPOS);
3303 //kBytePrev := kByte;
3304 //kDirPrev := gPlayer1.Direction;
3305 end;
3307 procedure MC_SEND_Vote(Start: Boolean = False; Command: string = 'a');
3308 begin
3309 NetOut.Write(Byte(NET_MSG_VOTE_EVENT));
3310 NetOut.Write(Byte(Start));
3311 NetOut.Write(Command);
3312 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3313 end;
3315 procedure MC_SEND_PlayerSettings();
3316 var i: Integer;
3317 begin
3318 NetOut.Write(Byte(NET_MSG_PLRSET));
3319 NetOut.Write(gPlayer1Settings.Name);
3320 NetOut.Write(gPlayer1Settings.Model);
3321 NetOut.Write(gPlayer1Settings.Color.R);
3322 NetOut.Write(gPlayer1Settings.Color.G);
3323 NetOut.Write(gPlayer1Settings.Color.B);
3324 NetOut.Write(gPlayer1Settings.Team);
3325 NetOut.Write(gPlayer1Settings.WeaponSwitch);
3326 for i := WP_FIRST to WP_LAST + 1 do
3327 NetOut.Write(gPlayer1Settings.WeaponPreferences[i]);
3328 NetOut.Write(gPlayer1Settings.SwitchToEmpty);
3329 NetOut.Write(gPlayer1Settings.SkipFist);
3331 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3332 end;
3334 procedure MC_SEND_FullStateRequest();
3335 begin
3336 NetOut.Write(Byte(NET_MSG_REQFST));
3338 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3339 end;
3341 procedure MC_SEND_CheatRequest(Kind: Byte);
3342 begin
3343 NetOut.Write(Byte(NET_MSG_CHEAT));
3344 NetOut.Write(Kind);
3346 g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
3347 end;
3348 procedure MC_SEND_RCONPassword(Password: string);
3349 begin
3350 NetOut.Write(Byte(NET_MSG_RCON_AUTH));
3351 NetOut.Write(Password);
3353 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3354 end;
3355 procedure MC_SEND_RCONCommand(Cmd: string);
3356 begin
3357 NetOut.Write(Byte(NET_MSG_RCON_CMD));
3358 NetOut.Write(Cmd);
3360 g_Net_Client_Send(True, NET_CHAN_SERVICE);
3361 end;
3364 end.