1 // _________ __ __
2 // / _____// |_____________ _/ |______ ____ __ __ ______
3 // \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
4 // / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
5 // /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
6 // \/ \/ \//_____/ \/
7 // ______________________ ______________________
8 // T H E W A R B E G I N S
9 // Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name network.cpp - The network. */
12 //
13 // (c) Copyright 2000-2008 by Lutz Sammer, Andreas Arens, and Jimmy Salmon
14 //
15 // This program is free software; you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation; only version 2 of the License.
18 //
19 // This program is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program; if not, write to the Free Software
26 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 // 02111-1307, USA.
28 //
29
30 //@{
31
32 //----------------------------------------------------------------------------
33 // Documentation
34 //----------------------------------------------------------------------------
35
36 /**
37 ** @page NetworkModule Module - Network
38 **
39 ** @section Basics How does it work.
40 **
41 ** Stratagus uses an UDP peer to peer protocol (p2p). The default port
42 ** is 6660.
43 **
44 ** @subsection udp_vs_tcp UDP vs. TCP
45 **
46 ** UDP is a connectionless protocol. This means it does not perform
47 ** retransmission of data and therefore provides very few error recovery
48 ** services. UDP instead offers a direct way to send and receive
49 ** datagrams (packets) over the network; it is used primarily for
50 ** broadcasting messages.
51 **
52 ** TCP, on the other hand, provides a connection-based, reliable data
53 ** stream. TCP guarantees delivery of data and also guarantees that
54 ** packets will be delivered in the same order in which they were sent.
55 **
56 ** TCP is a simple and effective way of transmitting data. For making sure
57 ** that a client and server can talk to each other it is very good.
58 ** However, it carries with it a lot of overhead and extra network lag.
59 **
60 ** UDP needs less overhead and has a smaller lag. Which is very important
61 ** for real time games. The disadvantages includes:
62 **
63 ** @li You won't have an individual socket for each client.
64 ** @li Given that clients don't need to open a unique socket in order to
65 ** transmit data there is the very real possibility that a client
66 ** who is not logged into the game will start sending all kinds of
67 ** garbage to your server in some kind of attack. It becomes much
68 ** more difficult to stop them at this point.
69 ** @li Likewise, you won't have a clear disconnect/leave game message
70 ** unless you write one yourself.
71 ** @li Some data may not reach the other machine, so you may have to send
72 ** important stuff many times.
73 ** @li Some data may arrive in the wrong order. Imagine that you get
74 ** package 3 before package 1. Even a package can come duplicate.
75 ** @li UDP is connectionless and therefore has problems with firewalls.
76 **
77 ** I have chosen UDP. Additional support for the TCP protocol is welcome.
78 **
79 ** @subsection sc_vs_p2p server/client vs. peer to peer
80 **
81 ** @li server to client
82 **
83 ** The player input is send to the server. The server collects the input
84 ** of all players and than send the commands to all clients.
85 **
86 ** @li peer to peer (p2p)
87 **
88 ** The player input is direct send to all others clients in game.
89 **
90 ** p2p has the advantage of a smaller lag, but needs a higher bandwidth
91 ** by the clients.
92 **
93 ** p2p has been chosen for in-game.
94 ** s/c for the preparing room.
95 **
96 ** @subsection bandwidth bandwidth
97 **
98 ** I wanted to support up to 8 players with 28.8kbit modems.
99 **
100 ** Most modems have a bandwidth of 28.8K bits/second (both directions) to
101 ** 56K bits/second (33.6K uplink) It takes actually 10 bits to send 1 byte.
102 ** This makes calculating how many bytes you are sending easy however, as
103 ** you just need to divide 28800 bits/second by 10 and end up with 2880
104 ** bytes per second.
105 **
106 ** We want to send many packets, more updated per second and big packets,
107 ** less protocol overhead.
108 **
109 ** If we do an update 6 times per second, leaving approximately 480 bytes
110 ** per update in an ideal environment.
111 **
112 ** For the TCP/IP protocol we need following:
113 ** IP Header 20 bytes
114 ** UDP Header 8 bytes
115 **
116 ** With ~9 bytes per command and up to N(=9) commands there are 20+8+1+9*N(=120) bytes
117 ** per packet. Sending it to 7 other players, gives 840 bytes per update.
118 ** This means we could do 6 updates (each 166ms) per second (6*840=5040 bytes/s).
119 **
120 ** @subsection a_packet Network packet
121 **
122 ** @li [IP Header - 20 bytes]
123 ** @li [UDP Header - 8 bytes]
124 ** @li [Header Data:Type - 1 byte]
125 ** if Type is one of the InitConfigMessage
126 ** @li [Header Data:SubType - 1 byte]
127 ** @li [Data - depend of subtype (may be 0 byte)]
128 ** else
129 ** @li [Header Data:Types - N-1 bytes] (for N commands)
130 ** @li [Header Data:Cycle - 1 byte]
131 ** @li [Data:Commands - Sum of Xi bytes for the N Commands]
132 **
133 **
134 ** @subsection internals Putting it together
135 **
136 ** All computers in play must run absolute syncron.
137 ** Only user commands are send over the network to the other computers.
138 ** To reduce network traffic, commands are sent/executed every gameCyclesPerUpdate
139 ** gameCycles. The command needs some time to reach the other clients (lag),
140 ** so the command is not executed immediately on the local computer,
141 ** but a delay (NetworkLag NetUpdates) later. Commands are stored
142 ** in a circular array indexed by update time. Once each other players commands
143 ** are received for a specified gameNetCycle, all commands of this gameNetCycle
144 ** Each gameNetCycle, a package must be send. if there is no user command,
145 ** a "dummy" sync package is send (which checks that all players are still in sync).
146 ** If there are missing packages, the game is paused and old commands
147 ** are resend to all clients.
148 **
149 ** @section missing What features are missing
150 **
151 ** @li The recover from lost packets can be improved, as the player knows
152 ** which packets is missing.
153 **
154 ** @li The UDP protocol isn't good for firewalls, we need also support
155 ** for the TCP protocol.
156 **
157 ** @li Add a server/client protocol, which allows more players per game.
158 **
159 ** @li Lag (latency) and bandwidth should be automatic detected during game setup
160 ** and later during game automatic adapted.
161 **
162 ** @li Also it would be nice, if we support viewing clients. This means
163 ** other people can view the game in progress.
164 **
165 ** @li The current protocol only uses single cast, for local LAN we
166 ** should also support broadcast and multicast.
167 **
168 ** @li Proxy and relays should be supported, to improve the playable
169 ** over the internet.
170 **
171 ** @li We can sort the command by importants, currently all commands are
172 ** send in order, only chat messages are send if there are free slots.
173 **
174 ** @li password protection the login process (optional), to prevent that
175 ** the wrong player join an open network game.
176 **
177 ** @li add meta server support, I have planned to use bnetd and its protocol.
178 **
179 ** @section api API How should it be used.
180 **
181 ** ::InitNetwork1()
182 ** Open port. Must be called by Lua
183 **
184 ** ::ExitNetwork1()
185 ** Close port. Called internally and by Lua
186 **
187 ** ::NetworkOnGameStart()
188 ** Initialize msg stuff for ingame communication.
189 **
190 ** ::NetworkEvent()
191 ** Manage network event (preparation room + ingame).
192 **
193 ** ::NetworkRecover()
194 ** Do stuff to have again NetworkInSync == true
195 **
196 ** ::NetworkCommands()
197 ** Network Updates : exec current command, and send commands to other players
198 **
199 ** ::NetworkFildes
200 ** UDP Socket for communication.
201 **
202 ** ::NetworkInSync
203 ** false when commands of the next gameNetCycle of the other player are not ready.
204 **
205 ** ::NetworkSendCommand()
206 ** Send a normal unit order.
207 **
208 ** ::NetworkSendExtendedCommand()
209 ** Send special command (diplomacy, ...)
210 **
211 ** ::NetworkSendChatMessage()
212 ** Send a chat message to others player
213 **
214 ** ::NetworkQuitGame()
215 ** Warn other users that we leave.
216 */
217
218 //----------------------------------------------------------------------------
219 // Includes
220 //----------------------------------------------------------------------------
221
222 #include "stratagus.h"
223
224 #include <stddef.h>
225 #include <list>
226
227 #include "network.h"
228
229 #include "actions.h"
230 #include "commands.h"
231 #include "map/map.h"
232 #include "net_lowlevel.h"
233 #include "net_message.h"
234 #include "netconnect.h"
235 #include "parameters.h"
236 #include "player.h"
237 #include "replay.h"
238 #include "sound.h"
239 #include "translate.h"
240 #include "ui/interface.h"
241 #include "unit/unit.h"
242 #include "unit/unit_manager.h"
243 #include "unit/unittype.h"
244 #include "video.h"
245
246 #include <deque>
247
248 //----------------------------------------------------------------------------
249 // Declaration
250 //----------------------------------------------------------------------------
251
252 /**
253 ** Network command input/output queue.
254 */
255 class CNetworkCommandQueue
256 {
257 public:
CNetworkCommandQueue()258 CNetworkCommandQueue() : Time(0), Type(0) {}
Clear()259 void Clear() { this->Time = this->Type = 0; Data.clear(); }
260
operator ==(const CNetworkCommandQueue & rhs) const261 bool operator == (const CNetworkCommandQueue &rhs) const
262 {
263 return Time == rhs.Time && Type == rhs.Type && Data == rhs.Data;
264 }
operator !=(const CNetworkCommandQueue & rhs) const265 bool operator != (const CNetworkCommandQueue &rhs) const { return !(*this == rhs); }
266 public:
267 unsigned long Time; /// time to execute
268 unsigned char Type; /// Command Type
269 std::vector<unsigned char> Data; /// command content (network format)
270 };
271
272 //----------------------------------------------------------------------------
273 // Variables
274 //----------------------------------------------------------------------------
275
276 /* static */ CNetworkParameter CNetworkParameter::Instance;
277
CNetworkParameter()278 CNetworkParameter::CNetworkParameter()
279 {
280 localHost = "127.0.0.1";
281 localPort = defaultPort;
282 gameCyclesPerUpdate = 1;
283 NetworkLag = 10;
284 timeoutInS = 45;
285 }
286
FixValues()287 void CNetworkParameter::FixValues()
288 {
289 gameCyclesPerUpdate = std::max(gameCyclesPerUpdate, 1u);
290 NetworkLag = std::max(NetworkLag, 2u * gameCyclesPerUpdate);
291 }
292
293 bool NetworkInSync = true; /// Network is in sync
294
295 CUDPSocket NetworkFildes; /// Network file descriptor
296
297 static unsigned long NetworkLastFrame[PlayerMax]; /// Last frame received packet
298 static unsigned long NetworkLastCycle[PlayerMax]; /// Last cycle received packet
299
300 static int NetworkSyncSeeds[256]; /// Network sync seeds.
301 static int NetworkSyncHashs[256]; /// Network sync hashs.
302 static CNetworkCommandQueue NetworkIn[256][PlayerMax][MaxNetworkCommands]; /// Per-player network packet input queue
303 static std::deque<CNetworkCommandQueue> CommandsIn; /// Network command input queue
304 static std::deque<CNetworkCommandQueue> MsgCommandsIn; /// Network message input queue
305
306
307 #ifdef DEBUG
308 class CNetworkStat
309 {
310 public:
CNetworkStat()311 CNetworkStat() :
312 resentPacketCount(0)
313 {}
314
print() const315 void print() const
316 {
317 DebugPrint("resent: %d packets\n" _C_ resentPacketCount);
318 }
319
320 public:
321 unsigned int resentPacketCount;
322 };
323
printStatistic(const CUDPSocket::CStatistic & statistic)324 static void printStatistic(const CUDPSocket::CStatistic &statistic)
325 {
326 DebugPrint("Sent: %d packets %d bytes (max %d bytes).\n"
327 _C_ statistic.sentPacketsCount _C_ statistic.sentBytesCount
328 _C_ statistic.biggestSentPacketSize);
329 DebugPrint("Received: %d packets %d bytes (max %d bytes).\n" _C_
330 statistic.receivedPacketsCount _C_ statistic.receivedBytesCount
331 _C_ statistic.biggestReceivedPacketSize);
332 DebugPrint("Received: %d error(s).\n" _C_ statistic.receivedErrorCount);
333 }
334
335 static CNetworkStat NetworkStat;
336 #endif
337
338 static int PlayerQuit[PlayerMax]; /// Player quit
339
340 //----------------------------------------------------------------------------
341 // Mid-Level api functions
342 //----------------------------------------------------------------------------
343
344 /**
345 ** Send message to all clients.
346 **
347 ** @param packet Packet to send.
348 ** @param numcommands Number of commands.
349 */
NetworkBroadcast(const CNetworkPacket & packet,int numcommands,int player=255)350 static void NetworkBroadcast(const CNetworkPacket &packet, int numcommands, int player = 255)
351 {
352 const unsigned int size = packet.Size(numcommands);
353 unsigned char *buf = new unsigned char[size];
354 packet.Serialize(buf, numcommands);
355
356 // Send to all clients.
357 if (NetConnectType == 1) { // server
358 for (int i = 0; i < HostsCount; ++i) {
359 const CHost host(Hosts[i].Host, Hosts[i].Port);
360 if (Hosts[i].PlyNr == player) {
361 continue;
362 }
363 NetworkFildes.Send(host, buf, size);
364 }
365 } else { // client
366 const CHost host(Hosts[HostsCount - 1].Host, Hosts[HostsCount - 1].Port);
367 NetworkFildes.Send(host, buf, size);
368 }
369 delete[] buf;
370 }
371
372 /**
373 ** Network send packet. Build it from queue and broadcast.
374 **
375 ** @param ncq Outgoing network queue start.
376 */
NetworkSendPacket(const CNetworkCommandQueue (& ncq)[MaxNetworkCommands])377 static void NetworkSendPacket(const CNetworkCommandQueue(&ncq)[MaxNetworkCommands])
378 {
379 CNetworkPacket packet;
380
381 // Build packet of up to MaxNetworkCommands messages.
382 int numcommands = 0;
383 packet.Header.Cycle = ncq[0].Time & 0xFF;
384 packet.Header.OrigPlayer = ThisPlayer->Index;
385 int i;
386 for (i = 0; i < MaxNetworkCommands && ncq[i].Type != MessageNone; ++i) {
387 packet.Header.Type[i] = ncq[i].Type;
388 packet.Command[i] = ncq[i].Data;
389 ++numcommands;
390 }
391 for (; i < MaxNetworkCommands; ++i) {
392 packet.Header.Type[i] = MessageNone;
393 }
394 NetworkBroadcast(packet, numcommands);
395 }
396
397 //----------------------------------------------------------------------------
398 // API init..
399 //----------------------------------------------------------------------------
400
401 /**
402 ** Initialize network port.
403 */
InitNetwork1()404 void InitNetwork1()
405 {
406 CNetworkParameter::Instance.FixValues();
407
408 NetInit(); // machine dependent setup
409
410 // Our communication port
411 const int port = CNetworkParameter::Instance.localPort;
412 const char *NetworkAddr = nullptr; // FIXME : bad use
413 const CHost host(NetworkAddr, port);
414 NetworkFildes.Open(host);
415 if (NetworkFildes.IsValid() == false) {
416 fprintf(stderr, "NETWORK: No free port %d available, aborting\n", port);
417 NetExit(); // machine dependent network exit
418 return;
419 }
420 #ifdef DEBUG
421 const std::string hostStr = host.toString();
422 DebugPrint("My host:port %s\n" _C_ hostStr.c_str());
423 #endif
424
425 #if 0 // FIXME: need a working interface check
426 unsigned long ips[10];
427 int networkNumInterfaces = NetSocketAddr(NetworkFildes, ips, 10);
428 if (networkNumInterfaces) {
429 DebugPrint("Num IP: %d\n" _C_ networkNumInterfaces);
430 for (int i = 0; i < networkNumInterfaces; ++i) {
431 DebugPrint("IP: %d.%d.%d.%d\n" _C_ NIPQUAD(ntohl(ips[i])));
432 }
433 } else {
434 fprintf(stderr, "NETWORK: Not connected to any external IPV4-network, aborting\n");
435 ExitNetwork1();
436 return;
437 }
438 #endif
439 }
440
441 /**
442 ** Cleanup network.
443 */
ExitNetwork1()444 void ExitNetwork1()
445 {
446 if (!IsNetworkGame()) { // No network running
447 return;
448 }
449
450 #ifdef DEBUG
451 printStatistic(NetworkFildes.getStatistic());
452 NetworkFildes.clearStatistic();
453 NetworkStat.print();
454 #endif
455
456 NetworkFildes.Close();
457 NetExit(); // machine dependent setup
458
459 NetworkInSync = true;
460 NetPlayers = 0;
461 HostsCount = 0;
462 }
463
464 /**
465 ** Game will start now.
466 */
NetworkOnStartGame()467 void NetworkOnStartGame()
468 {
469 ThisPlayer->SetName(Parameters::Instance.LocalPlayerName);
470 for (int i = 0; i < HostsCount; ++i) {
471 Players[Hosts[i].PlyNr].SetName(Hosts[i].PlyName);
472 }
473 DebugPrint("Updates %d, Lag %d, Hosts %d\n" _C_
474 CNetworkParameter::Instance.gameCyclesPerUpdate _C_
475 CNetworkParameter::Instance.NetworkLag _C_ HostsCount);
476
477 NetworkInSync = true;
478 CommandsIn.clear();
479 MsgCommandsIn.clear();
480 // Prepare first time without syncs.
481 for (int i = 0; i != 256; ++i) {
482 for (int p = 0; p != PlayerMax; ++p) {
483 for (int j = 0; j != MaxNetworkCommands; ++j) {
484 NetworkIn[i][p][j].Clear();
485 }
486 }
487 }
488 CNetworkCommandSync nc;
489 //nc.syncHash = SyncHash;
490 //nc.syncSeed = SyncRandSeed;
491
492 for (unsigned int i = 0; i <= CNetworkParameter::Instance.NetworkLag; i += CNetworkParameter::Instance.gameCyclesPerUpdate) {
493 for (int n = 0; n < HostsCount; ++n) {
494 CNetworkCommandQueue(&ncqs)[MaxNetworkCommands] = NetworkIn[i][Hosts[n].PlyNr];
495
496 ncqs[0].Time = i;
497 ncqs[0].Type = MessageSync;
498 ncqs[0].Data.resize(nc.Size());
499 nc.Serialize(&ncqs[0].Data[0]);
500 ncqs[1].Time = i;
501 ncqs[1].Type = MessageNone;
502 }
503 }
504 memset(NetworkSyncSeeds, 0, sizeof(NetworkSyncSeeds));
505 memset(NetworkSyncHashs, 0, sizeof(NetworkSyncHashs));
506 memset(PlayerQuit, 0, sizeof(PlayerQuit));
507 memset(NetworkLastFrame, 0, sizeof(NetworkLastFrame));
508 memset(NetworkLastCycle, 0, sizeof(NetworkLastCycle));
509 }
510
511 //----------------------------------------------------------------------------
512 // Commands input
513 //----------------------------------------------------------------------------
514
515 /**
516 ** Prepare send of command message.
517 **
518 ** Convert arguments into network format and place it into output queue.
519 **
520 ** @param command Command (Move,Attack,...).
521 ** @param unit Unit that receive the command.
522 ** @param x optional X map position.
523 ** @param y optional y map position.
524 ** @param dest optional destination unit.
525 ** @param type optional unit-type argument.
526 ** @param status Append command or flush old commands.
527 **
528 ** @warning Destination and unit-type shares the same network slot.
529 */
NetworkSendCommand(int command,const CUnit & unit,int x,int y,const CUnit * dest,const CUnitType * type,int status)530 void NetworkSendCommand(int command, const CUnit &unit, int x, int y,
531 const CUnit *dest, const CUnitType *type, int status)
532 {
533 CNetworkCommandQueue ncq;
534
535 ncq.Time = GameCycle;
536 ncq.Type = command;
537 if (status) {
538 ncq.Type |= 0x80;
539 }
540 CNetworkCommand nc;
541 nc.Unit = UnitNumber(unit);
542 nc.X = x;
543 nc.Y = y;
544 Assert(!dest || !type); // Both together isn't allowed
545 if (dest) {
546 nc.Dest = UnitNumber(*dest);
547 } else if (type) {
548 nc.Dest = type->Slot;
549 } else {
550 nc.Dest = 0xFFFF; // -1
551 }
552 ncq.Data.resize(nc.Size());
553 nc.Serialize(&ncq.Data[0]);
554 // Check for duplicate command in queue
555 if (std::find(CommandsIn.begin(), CommandsIn.end(), ncq) != CommandsIn.end()) {
556 return;
557 }
558 CommandsIn.push_back(ncq);
559 }
560
561 /**
562 ** Prepare send of extended command message.
563 **
564 ** Convert arguments into network format and place it into output queue.
565 **
566 ** @param command Command (Move,Attack,...).
567 ** @param arg1 optional argument #1
568 ** @param arg2 optional argument #2
569 ** @param arg3 optional argument #3
570 ** @param arg4 optional argument #4
571 ** @param status Append command or flush old commands.
572 */
NetworkSendExtendedCommand(int command,int arg1,int arg2,int arg3,int arg4,int status)573 void NetworkSendExtendedCommand(int command, int arg1, int arg2, int arg3,
574 int arg4, int status)
575 {
576 CNetworkCommandQueue ncq;
577
578 ncq.Time = GameCycle;
579 ncq.Type = MessageExtendedCommand;
580 if (status) {
581 ncq.Type |= 0x80;
582 }
583 CNetworkExtendedCommand nec;
584 nec.ExtendedType = command;
585 nec.Arg1 = arg1;
586 nec.Arg2 = arg2;
587 nec.Arg3 = arg3;
588 nec.Arg4 = arg4;
589 ncq.Data.resize(nec.Size());
590 nec.Serialize(&ncq.Data[0]);
591 CommandsIn.push_back(ncq);
592 }
593
594 /**
595 ** Sends my selections to teammates
596 **
597 ** @param units Units to send
598 ** @param count Number of units to send
599 */
NetworkSendSelection(CUnit ** units,int count)600 void NetworkSendSelection(CUnit **units, int count)
601 {
602 // Check if we have any teammates to send to
603 bool hasteammates = false;
604 for (int i = 0; i < HostsCount; ++i) {
605 if (Players[Hosts[i].PlyNr].Team == ThisPlayer->Team) {
606 hasteammates = true;
607 break;
608 }
609 }
610 if (!hasteammates) {
611 return;
612 }
613 // Build and send packets to cover all units.
614 CNetworkSelection ns;
615
616 for (int i = 0; i != count; ++i) {
617 ns.Units.push_back(UnitNumber(*units[i]));
618 }
619 CNetworkCommandQueue ncq;
620 ncq.Time = GameCycle;
621 ncq.Type = MessageSelection;
622
623 ncq.Data.resize(ns.Size());
624 ns.Serialize(&ncq.Data[0]);
625 CommandsIn.push_back(ncq);
626 }
627
628 /**
629 ** Send chat message. (Message is sent with low priority)
630 **
631 ** @param msg Text message to send.
632 */
NetworkSendChatMessage(const std::string & msg)633 void NetworkSendChatMessage(const std::string &msg)
634 {
635 if (!IsNetworkGame()) {
636 return;
637 }
638 CNetworkChat nc;
639 nc.Text = msg;
640 CNetworkCommandQueue ncq;
641 ncq.Type = MessageChat;
642 ncq.Data.resize(nc.Size());
643 nc.Serialize(&ncq.Data[0]);
644 MsgCommandsIn.push_back(ncq);
645 }
646
647 /**
648 ** Remove a player from the game.
649 **
650 ** @param player Player number
651 */
NetworkRemovePlayer(int player)652 static void NetworkRemovePlayer(int player)
653 {
654 // Remove player from Hosts and clear NetworkIn
655 for (int i = 0; i < HostsCount; ++i) {
656 if (Hosts[i].PlyNr == player) {
657 Hosts[i] = Hosts[HostsCount - 1];
658 --HostsCount;
659 break;
660 }
661 }
662 for (int i = 0; i < 256; ++i) {
663 for (int c = 0; c < MaxNetworkCommands; ++c) {
664 NetworkIn[i][player][c].Time = 0;
665 }
666 }
667 }
668
IsNetworkCommandReady(int hostIndex,unsigned long gameNetCycle)669 static bool IsNetworkCommandReady(int hostIndex, unsigned long gameNetCycle)
670 {
671 const int ply = Hosts[hostIndex].PlyNr;
672 const CNetworkCommandQueue &ncq = NetworkIn[gameNetCycle & 0xFF][ply][0];
673
674 if (ncq.Time != gameNetCycle) {
675 return false;
676 }
677 return true;
678 }
679
IsNetworkCommandReady(unsigned long gameNetCycle)680 static bool IsNetworkCommandReady(unsigned long gameNetCycle)
681 {
682 // Check if all next messages are available.
683 for (int i = 0; i < HostsCount; ++i) {
684 if (IsNetworkCommandReady(i, gameNetCycle) == false) {
685 return false;
686 }
687 }
688
689 return true;
690 }
691
ParseResendCommand(const CNetworkPacket & packet)692 static void ParseResendCommand(const CNetworkPacket &packet)
693 {
694 // Destination cycle (time to execute).
695 unsigned long n = ((GameCycle + 128) & ~0xFF) | packet.Header.Cycle;
696 if (n > GameCycle + 128) {
697 n -= 0x100;
698 }
699 const unsigned long gameNetCycle = n;
700 // FIXME: not necessary to send this packet multiple times!!!!
701 // other side sends re-send until it gets an answer.
702 if (n != NetworkIn[gameNetCycle & 0xFF][ThisPlayer->Index][0].Time) {
703 // Asking for a cycle we haven't gotten to yet, ignore for now
704 return;
705 }
706 NetworkSendPacket(NetworkIn[gameNetCycle & 0xFF][ThisPlayer->Index]);
707 // Check if a player quit this cycle
708 for (int j = 0; j < HostsCount; ++j) {
709 for (int c = 0; c < MaxNetworkCommands; ++c) {
710 const CNetworkCommandQueue *ncq;
711 ncq = &NetworkIn[gameNetCycle & 0xFF][Hosts[j].PlyNr][c];
712 if (ncq->Time && ncq->Type == MessageQuit) {
713 CNetworkPacket np;
714 np.Header.Cycle = ncq->Time & 0xFF;
715 np.Header.Type[0] = MessageQuit;
716 np.Command[0] = ncq->Data;
717 for (int k = 1; k < MaxNetworkCommands; ++k) {
718 np.Header.Type[k] = MessageNone;
719 }
720 NetworkBroadcast(np, 1);
721 break;
722 }
723 }
724 }
725 }
726
IsAValidCommand_Command(const CNetworkPacket & packet,int index,const int player)727 static bool IsAValidCommand_Command(const CNetworkPacket &packet, int index, const int player)
728 {
729 CNetworkCommand nc;
730 nc.Deserialize(&packet.Command[index][0]);
731 const unsigned int slot = nc.Unit;
732 const CUnit *unit = slot < UnitManager.GetUsedSlotCount() ? &UnitManager.GetSlotUnit(slot) : nullptr;
733
734 if (unit && (unit->Player->Index == player
735 || Players[player].IsTeamed(*unit) || unit->Player->Type == PlayerNeutral)) {
736 return true;
737 } else {
738 return false;
739 }
740 }
741
IsAValidCommand_Dismiss(const CNetworkPacket & packet,int index,const int player)742 static bool IsAValidCommand_Dismiss(const CNetworkPacket &packet, int index, const int player)
743 {
744 CNetworkCommand nc;
745 nc.Deserialize(&packet.Command[index][0]);
746 const unsigned int slot = nc.Unit;
747 const CUnit *unit = slot < UnitManager.GetUsedSlotCount() ? &UnitManager.GetSlotUnit(slot) : nullptr;
748
749 if (unit && unit->Type->ClicksToExplode) {
750 return true;
751 }
752 return IsAValidCommand_Command(packet, index, player);
753 }
754
IsAValidCommand(const CNetworkPacket & packet,int index,const int player)755 static bool IsAValidCommand(const CNetworkPacket &packet, int index, const int player)
756 {
757 switch (packet.Header.Type[index] & 0x7F) {
758 case MessageExtendedCommand: // FIXME: ensure the sender is part of the command
759 case MessageSync: // Sync does not matter
760 case MessageSelection: // FIXME: ensure it's from the right player
761 case MessageQuit: // FIXME: ensure it's from the right player
762 case MessageResend: // FIXME: ensure it's from the right player
763 case MessageChat: // FIXME: ensure it's from the right player
764 return true;
765 case MessageCommandDismiss: return IsAValidCommand_Dismiss(packet, index, player);
766 default: return IsAValidCommand_Command(packet, index, player);
767 }
768 // FIXME: not all values in nc have been validated
769 }
770
NetworkParseInGameEvent(const unsigned char * buf,int len,const CHost & host)771 static void NetworkParseInGameEvent(const unsigned char *buf, int len, const CHost &host)
772 {
773 CNetworkPacket packet;
774 int commands;
775 packet.Deserialize(buf, len, &commands);
776
777 int player = packet.Header.OrigPlayer;
778 if (player == 255) {
779 const int index = FindHostIndexBy(host);
780 if (index == -1 || PlayerQuit[Hosts[index].PlyNr]) {
781 #ifdef DEBUG
782 const std::string hostStr = host.toString();
783 DebugPrint("Not a host in play: %s\n" _C_ hostStr.c_str());
784 #endif
785 return;
786 }
787 player = Hosts[index].PlyNr;
788 }
789 if (NetConnectType == 1) {
790 if (player != 255) {
791 NetworkBroadcast(packet, commands, player);
792 }
793 }
794 if (commands < 0) {
795 DebugPrint("Bad packet read\n");
796 return;
797 }
798 NetworkLastCycle[player] = packet.Header.Cycle;
799 // Parse the packet commands.
800 for (int i = 0; i != commands; ++i) {
801 // Handle some messages.
802 if (packet.Header.Type[i] == MessageQuit) {
803 CNetworkCommandQuit nc;
804 nc.Deserialize(&packet.Command[i][0]);
805 const int playerNum = nc.player;
806
807 if (playerNum >= 0 && playerNum < NumPlayers) {
808 PlayerQuit[playerNum] = 1;
809 }
810 }
811 if (packet.Header.Type[i] == MessageResend) {
812 ParseResendCommand(packet);
813 return;
814 }
815 // Receive statistic
816 NetworkLastFrame[player] = FrameCounter;
817
818 bool validCommand = IsAValidCommand(packet, i, player);
819 // Place in network in
820 if (validCommand) {
821 // Destination cycle (time to execute).
822 unsigned long n = ((GameCycle + 128) & ~0xFF) | packet.Header.Cycle;
823 if (n > GameCycle + 128) {
824 n -= 0x100;
825 }
826 NetworkIn[packet.Header.Cycle][player][i].Time = n;
827 NetworkIn[packet.Header.Cycle][player][i].Type = packet.Header.Type[i];
828 NetworkIn[packet.Header.Cycle][player][i].Data = packet.Command[i];
829 } else {
830 SetMessage(_("%s sent bad command"), Players[player].Name.c_str());
831 DebugPrint("%s sent bad command: 0x%x\n" _C_ Players[player].Name.c_str()
832 _C_ packet.Header.Type[i] & 0x7F);
833 }
834 }
835 for (int i = commands; i != MaxNetworkCommands; ++i) {
836 NetworkIn[packet.Header.Cycle][player][i].Time = 0;
837 }
838 // Waiting for this time slot
839 if (!NetworkInSync) {
840 const int networkUpdates = CNetworkParameter::Instance.gameCyclesPerUpdate;
841 unsigned long n = ((GameCycle / networkUpdates) + 1) * networkUpdates;
842 if (IsNetworkCommandReady(n) == true) {
843 NetworkInSync = true;
844 }
845 }
846 }
847
848 /**
849 ** Called if message for the network is ready.
850 ** (by WaitEventsOneFrame)
851 */
NetworkEvent()852 void NetworkEvent()
853 {
854 if (!IsNetworkGame()) {
855 NetworkInSync = true;
856 return;
857 }
858 // Read the packet.
859 unsigned char buf[1024];
860 CHost host;
861 int len = NetworkFildes.Recv(&buf, sizeof(buf), &host);
862 if (len < 0) {
863 DebugPrint("Server/Client gone?\n");
864 // just hope for an automatic recover right now..
865 NetworkInSync = false;
866 return;
867 }
868
869 // Setup messages
870 if (NetConnectRunning) {
871 if (NetworkParseSetupEvent(buf, len, host)) {
872 return;
873 }
874 }
875 const unsigned char msgtype = buf[0];
876 if (msgtype == MessageInit_FromClient || msgtype == MessageInit_FromServer) {
877 return;
878 }
879 NetworkParseInGameEvent(buf, len, host);
880 }
881
882 /**
883 ** Quit the game.
884 */
NetworkQuitGame()885 void NetworkQuitGame()
886 {
887 if (!ThisPlayer || IsNetworkGame() == false) {
888 return;
889 }
890 const int gameCyclesPerUpdate = CNetworkParameter::Instance.gameCyclesPerUpdate;
891 const int NetworkLag = CNetworkParameter::Instance.NetworkLag;
892 const int n = (GameCycle + gameCyclesPerUpdate) / gameCyclesPerUpdate * gameCyclesPerUpdate + NetworkLag;
893 CNetworkCommandQueue(&ncqs)[MaxNetworkCommands] = NetworkIn[n & 0xFF][ThisPlayer->Index];
894 CNetworkCommandQuit nc;
895 nc.player = ThisPlayer->Index;
896 ncqs[0].Type = MessageQuit;
897 ncqs[0].Time = n;
898 ncqs[0].Data.resize(nc.Size());
899 nc.Serialize(&ncqs[0].Data[0]);
900 for (int i = 1; i < MaxNetworkCommands; ++i) {
901 ncqs[i].Type = MessageNone;
902 ncqs[i].Data.clear();
903 }
904 NetworkSendPacket(ncqs);
905 }
906
NetworkExecCommand_Sync(const CNetworkCommandQueue & ncq)907 static void NetworkExecCommand_Sync(const CNetworkCommandQueue &ncq)
908 {
909 Assert((ncq.Type & 0x7F) == MessageSync);
910
911 CNetworkCommandSync nc;
912 nc.Deserialize(&ncq.Data[0]);
913 const unsigned long gameNetCycle = GameCycle;
914 const int syncSeed = nc.syncSeed;
915 const int syncHash = nc.syncHash;
916
917 if (syncSeed != NetworkSyncSeeds[gameNetCycle & 0xFF]
918 || syncHash != NetworkSyncHashs[gameNetCycle & 0xFF]) {
919 SetMessage("%s", _("Network out of sync"));
920 //Wyrmgus start
921 fprintf(stderr, "Network out of sync %x!=%x! %d!=%d! Cycle %lu\n", syncSeed, NetworkSyncSeeds[gameNetCycle & 0xFF], syncHash, NetworkSyncHashs[gameNetCycle & 0xFF], GameCycle);
922 //Wyrmgus end
923 DebugPrint("\nNetwork out of sync %x!=%x! %d!=%d! Cycle %lu\n\n" _C_
924 syncSeed _C_ NetworkSyncSeeds[gameNetCycle & 0xFF] _C_
925 syncHash _C_ NetworkSyncHashs[gameNetCycle & 0xFF] _C_ GameCycle);
926 }
927 }
928
NetworkExecCommand_Selection(const CNetworkCommandQueue & ncq)929 static void NetworkExecCommand_Selection(const CNetworkCommandQueue &ncq)
930 {
931 Assert((ncq.Type & 0x7F) == MessageSelection);
932
933 CNetworkSelection ns;
934
935 ns.Deserialize(&ncq.Data[0]);
936 if (Players[ns.player].Team != ThisPlayer->Team) {
937 return;
938 }
939 std::vector<CUnit *> units;
940
941 for (size_t i = 0; i != ns.Units.size(); ++i) {
942 units.push_back(&UnitManager.GetSlotUnit(ns.Units[i]));
943 }
944 ChangeTeamSelectedUnits(Players[ns.player], units);
945 }
946
NetworkExecCommand_Chat(const CNetworkCommandQueue & ncq)947 static void NetworkExecCommand_Chat(const CNetworkCommandQueue &ncq)
948 {
949 Assert((ncq.Type & 0x7F) == MessageChat);
950
951 CNetworkChat nc;
952 nc.Deserialize(&ncq.Data[0]);
953
954 SetMessage("%s", nc.Text.c_str());
955 PlayGameSound(GameSounds.ChatMessage.Sound, MaxSampleVolume);
956 CommandLog("chat", NoUnitP, FlushCommands, -1, -1, NoUnitP, nc.Text.c_str(), -1);
957 }
958
NetworkExecCommand_Quit(const CNetworkCommandQueue & ncq)959 static void NetworkExecCommand_Quit(const CNetworkCommandQueue &ncq)
960 {
961 Assert((ncq.Type & 0x7F) == MessageQuit);
962 CNetworkCommandQuit nc;
963
964 nc.Deserialize(&ncq.Data[0]);
965 NetworkRemovePlayer(nc.player);
966 CommandLog("quit", NoUnitP, FlushCommands, nc.player, -1, NoUnitP, nullptr, -1);
967 CommandQuit(nc.player);
968 }
969
NetworkExecCommand_ExtendedCommand(const CNetworkCommandQueue & ncq)970 static void NetworkExecCommand_ExtendedCommand(const CNetworkCommandQueue &ncq)
971 {
972 Assert((ncq.Type & 0x7F) == MessageExtendedCommand);
973 CNetworkExtendedCommand nec;
974
975 nec.Deserialize(&ncq.Data[0]);
976 ExecExtendedCommand(nec.ExtendedType, (ncq.Type & 0x80) >> 7,
977 nec.Arg1, nec.Arg2, nec.Arg3, nec.Arg4);
978 }
979
NetworkExecCommand_Command(const CNetworkCommandQueue & ncq)980 static void NetworkExecCommand_Command(const CNetworkCommandQueue &ncq)
981 {
982 CNetworkCommand nc;
983
984 nc.Deserialize(&ncq.Data[0]);
985 ExecCommand(ncq.Type, nc.Unit, nc.X, nc.Y, nc.Dest);
986 }
987
988 /**
989 ** Execute a network command.
990 **
991 ** @param ncq Network command from queue
992 */
NetworkExecCommand(const CNetworkCommandQueue & ncq)993 static void NetworkExecCommand(const CNetworkCommandQueue &ncq)
994 {
995 switch (ncq.Type & 0x7F) {
996 case MessageSync: NetworkExecCommand_Sync(ncq); break;
997 case MessageSelection: NetworkExecCommand_Selection(ncq); break;
998 case MessageChat: NetworkExecCommand_Chat(ncq); break;
999 case MessageQuit: NetworkExecCommand_Quit(ncq); break;
1000 case MessageExtendedCommand: NetworkExecCommand_ExtendedCommand(ncq); break;
1001 case MessageNone:
1002 // Nothing to Do, This Message Should Never be Executed
1003 Assert(0);
1004 break;
1005 default: NetworkExecCommand_Command(ncq); break;
1006 }
1007 }
1008
1009 /**
1010 ** Network send commands.
1011 */
NetworkSendCommands(unsigned long gameNetCycle)1012 static void NetworkSendCommands(unsigned long gameNetCycle)
1013 {
1014 // No command available, send sync.
1015 int numcommands = 0;
1016 CNetworkCommandQueue(&ncq)[MaxNetworkCommands] = NetworkIn[gameNetCycle & 0xFF][ThisPlayer->Index];
1017 ncq[0].Clear();
1018 if (CommandsIn.empty() && MsgCommandsIn.empty()) {
1019 CNetworkCommandSync nc;
1020 ncq[0].Type = MessageSync;
1021 nc.syncHash = SyncHash;
1022 nc.syncSeed = SyncRandSeed;
1023 ncq[0].Data.resize(nc.Size());
1024 nc.Serialize(&ncq[0].Data[0]);
1025 ncq[0].Time = gameNetCycle;
1026 numcommands = 1;
1027 } else {
1028 while (!CommandsIn.empty() && numcommands < MaxNetworkCommands) {
1029 const CNetworkCommandQueue &incommand = CommandsIn.front();
1030 #ifdef DEBUG
1031 if (incommand.Type != MessageExtendedCommand) {
1032 CNetworkCommand nc;
1033 nc.Deserialize(&incommand.Data[0]);
1034
1035 const CUnit &unit = UnitManager.GetSlotUnit(nc.Unit);
1036 // FIXME: we can send destoyed units over network :(
1037 if (unit.Destroyed) {
1038 DebugPrint("Sending destroyed unit %d over network!!!!!!\n" _C_ nc.Unit);
1039 }
1040 }
1041 #endif
1042 ncq[numcommands] = incommand;
1043 ncq[numcommands].Time = gameNetCycle;
1044 ++numcommands;
1045 CommandsIn.pop_front();
1046 }
1047 while (!MsgCommandsIn.empty() && numcommands < MaxNetworkCommands) {
1048 const CNetworkCommandQueue &incommand = MsgCommandsIn.front();
1049 ncq[numcommands] = incommand;
1050 ncq[numcommands].Time = gameNetCycle;
1051 ++numcommands;
1052 MsgCommandsIn.pop_front();
1053 }
1054 }
1055 if (numcommands != MaxNetworkCommands) {
1056 ncq[numcommands].Type = MessageNone;
1057 }
1058 NetworkSyncSeeds[gameNetCycle & 0xFF] = SyncRandSeed;
1059 NetworkSyncHashs[gameNetCycle & 0xFF] = SyncHash;
1060 NetworkSendPacket(ncq);
1061 }
1062
1063 /**
1064 ** Network execute commands.
1065 */
NetworkExecCommands(unsigned long gameNetCycle)1066 static void NetworkExecCommands(unsigned long gameNetCycle)
1067 {
1068 // Must execute commands on all computers in the same order.
1069 for (int i = 0; i < NumPlayers; ++i) {
1070 const CNetworkCommandQueue *ncqs = NetworkIn[gameNetCycle & 0xFF][i];
1071 for (int c = 0; c < MaxNetworkCommands; ++c) {
1072 const CNetworkCommandQueue &ncq = ncqs[c];
1073 if (ncq.Type == MessageNone) {
1074 break;
1075 }
1076 if (ncq.Time && ncq.Time == gameNetCycle) {
1077 NetworkExecCommand(ncq);
1078 }
1079 }
1080 }
1081 }
1082
1083 /**
1084 ** Handle network commands.
1085 */
NetworkCommands()1086 void NetworkCommands()
1087 {
1088 if (!IsNetworkGame()) {
1089 return;
1090 }
1091 if ((GameCycle % CNetworkParameter::Instance.gameCyclesPerUpdate) != 0) {
1092 return;
1093 }
1094 const unsigned long gameNetCycle = GameCycle;
1095 // Send messages to all clients (other players)
1096 NetworkSendCommands(gameNetCycle + CNetworkParameter::Instance.NetworkLag);
1097 NetworkExecCommands(gameNetCycle);
1098 NetworkInSync = IsNetworkCommandReady(gameNetCycle + CNetworkParameter::Instance.gameCyclesPerUpdate);
1099 }
1100
CheckPlayerThatTimeOut(int hostIndex)1101 static void CheckPlayerThatTimeOut(int hostIndex)
1102 {
1103 const int playerIndex = Hosts[hostIndex].PlyNr;
1104 const unsigned long lastFrame = NetworkLastFrame[playerIndex];
1105 if (!lastFrame) {
1106 return;
1107 }
1108 const int framesPerSecond = FRAMES_PER_SECOND * VideoSyncSpeed / 100;
1109 const int secs = (FrameCounter - lastFrame) / framesPerSecond;
1110 // FIXME: display a menu while we wait
1111 const int timeoutInS = CNetworkParameter::Instance.timeoutInS;
1112 if (3 <= secs && secs < timeoutInS && FrameCounter % framesPerSecond == 0) {
1113 SetMessage(_("Waiting for player \"%s\": %d:%02d"), Hosts[hostIndex].PlyName,
1114 (timeoutInS - secs) / 60, (timeoutInS - secs) % 60);
1115 }
1116 if (secs >= timeoutInS) {
1117 const unsigned int nextGameNetCycle = GameCycle / CNetworkParameter::Instance.gameCyclesPerUpdate + 1;
1118 CNetworkCommandQuit nc;
1119 nc.player = playerIndex;
1120 CNetworkCommandQueue *ncq = &NetworkIn[nextGameNetCycle & 0xFF][playerIndex][0];
1121 ncq->Time = nextGameNetCycle * CNetworkParameter::Instance.gameCyclesPerUpdate;
1122 ncq->Type = MessageQuit;
1123 ncq->Data.resize(nc.Size());
1124 nc.Serialize(&ncq->Data[0]);
1125 PlayerQuit[playerIndex] = 1;
1126 SetMessage("%s", _("Timed out"));
1127
1128 CNetworkPacket np;
1129 np.Header.Cycle = ncq->Time & 0xFF;
1130 np.Header.Type[0] = ncq->Type;
1131 np.Header.Type[1] = MessageNone;
1132
1133 NetworkBroadcast(np, 1);
1134 }
1135 }
1136
1137 /**
1138 ** Network resend commands, we have a missing packet send to all clients
1139 ** what packet we are missing.
1140 **
1141 ** @todo
1142 ** We need only send to the clients, that have not delivered the packet.
1143 */
NetworkResendCommands()1144 static void NetworkResendCommands()
1145 {
1146 #ifdef DEBUG
1147 ++NetworkStat.resentPacketCount;
1148 #endif
1149
1150 const int networkUpdates = CNetworkParameter::Instance.gameCyclesPerUpdate;
1151 const int nextGameCycle = ((GameCycle / networkUpdates) + 1) * networkUpdates;
1152 // Build packet
1153 CNetworkPacket packet;
1154 packet.Header.Type[0] = MessageResend;
1155 packet.Header.Type[1] = MessageNone;
1156 packet.Header.Cycle = uint8_t(nextGameCycle & 0xFF);
1157
1158 NetworkBroadcast(packet, 1);
1159 }
1160
1161 /**
1162 ** Recover network.
1163 */
NetworkRecover()1164 void NetworkRecover()
1165 {
1166 if (HostsCount == 0) {
1167 NetworkInSync = true;
1168 return;
1169 }
1170 if (FrameCounter % CNetworkParameter::Instance.gameCyclesPerUpdate != 0) {
1171 return;
1172 }
1173 for (int i = 0; i != HostsCount; ++i) {
1174 CheckPlayerThatTimeOut(i);
1175 }
1176 NetworkResendCommands();
1177 const unsigned int nextGameNetCycle = GameCycle / CNetworkParameter::Instance.gameCyclesPerUpdate + 1;
1178 NetworkInSync = IsNetworkCommandReady(nextGameNetCycle);
1179 }
1180
1181 //@}
1182