1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907
|
/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "network/multi.h"
#include "network/multiutil.h"
#include "network/multimsgs.h"
#include "ship/ship.h"
#include "io/timer.h"
#include "playerman/player.h"
#include "mission/missionparse.h"
#include "mission/missioncampaign.h"
#include "gamesequence/gamesequence.h"
#include "freespace.h"
#include "osapi/osapi.h"
#include "network/stand_gui.h"
#include "network/multi_xfer.h"
#include "network/multiui.h"
#include "network/multi_ingame.h"
#include "popup/popup.h"
#include "missionui/chatbox.h"
#include "model/modelreplace.h"
#include "network/multiteamselect.h"
#include "network/multi_data.h"
#include "network/multi_kick.h"
#include "network/multi_campaign.h"
#include "network/multi_voice.h"
#include "network/multi_team.h"
#include "network/multi_respawn.h"
#include "network/multi_pmsg.h"
#include "network/multi_endgame.h"
#include "missionui/missiondebrief.h"
#include "network/multi_pause.h"
#include "mission/missiongoals.h"
#include "network/multi_log.h"
#include "network/multi_rate.h"
#include "network/multi_lua.h"
#include "hud/hudescort.h"
#include "hud/hudmessage.h"
#include "globalincs/alphacolors.h"
#include "globalincs/pstypes.h"
#include "cfile/cfile.h"
#include "network/multi_fstracker.h"
#include "network/multi_sw.h"
#include "network/multi_portfwd.h"
#include "network/multi_turret_manager.h"
#include "pilotfile/pilotfile.h"
#include "debugconsole/console.h"
#include "network/psnet2.h"
#include "network/multi_mdns.h"
#include "cmdline/cmdline.h"
// Stupid windows workaround...
#ifdef MessageBox
#undef MessageBox
#endif
// ----------------------------------------------------------------------------------------
// Basic module scope defines
//
//
// timestamp defines
#define NETGAME_SEND_TIME 2 // time between sending netgame update packets
#define STATE_SEND_TIME 2 // time between sending netplayer state packets
#define GAMEINFO_SEND_TIME 3 // time between sending game information packets
#define PING_SEND_TIME 2 // time between player pings
#define BYTES_SENT_TIME 5 // every five seconds
// local network buffer stuff
#define MAX_NET_BUFFER (1024 * 16) // define and variable declaration for our local tcp buffer
#define NUM_REENTRANT_LEVELS 3
// time (in fixed seconds) to put up dialog about no contect from server
#define MULTI_SERVER_MAX_TIMEOUT (F1_0 * 4) // after this number of milliseoncds, stop client simulation
#define MULTI_SERVER_MAX_TIMEOUT_LARGE (F1_0 * 40) // done anytime not in mission
#define MULTI_SERVER_WAIT_TIME (F1_0 * 60) // wait 60 seconds to reconnect with the server
#define MULTI_SERVER_GONE 1
#define MULTI_SERVER_ALIVE 2
// define for when to show "slow network" icon
#define MULTI_SERVER_SLOW_PING_TIME 700 // when average ping time to server reaches this -- display hud icon
// update times for clients ships based on object update level
int Multi_client_update_intervals[MAX_OBJ_UPDATE_LEVELS] =
{
333, // Dialup, 3x a second
166, // Medium, 6x a second
80, // High, 12.5x a second
30, // LAN, 33x a second
};
int Multi_display_netinfo = 1;
// ----------------------------------------------------------------------------------------
// Multiplayer vars
//
//
// net player vars
net_player Net_players[MAX_PLAYERS]; // array of all netplayers in the game
net_player *Net_player; // pointer to console's net_player entry
// netgame vars
netgame_info Netgame; // netgame information
int Multi_mission_loaded = 0; // flag, so that we don't load the mission more than once client side
int Ingame_join_net_signature = -1; // signature for the player obj for use when joining ingame
int Multi_button_info_ok = 0; // flag saying it is ok to apply critical button info on a client machine
int Multi_button_info_id = 0; // identifier of the stored button info to be applying
// misc data
SCP_list<active_game> Active_games; // list of active games displayed on the Join screen
CFILE* Multi_chat_stream; // for streaming multiplayer chat strings to a file
int Multi_connection_speed; // connection speed of this machine.
int Multi_num_players_at_start = 0; // the # of players present (kept track of only on the server) at the very start of the mission
short Multi_id_num = 0; // for assigning player id #'s
// permanent server list
server_item* Game_server_head; // list of permanent game servers to be querying
// timestamp data
int Netgame_send_time = -1; // timestamp used to send netgame info to players before misison starts
time_t State_send_time = -1; // timestamp used to send state information to the host before a mission starts
int Gameinfo_send_time = -1; // timestamp used by master to send game information to clients
time_t Next_ping_time = -1; // when we should next ping all
int Multi_server_check_count = 0; // var to keep track of reentrancy when checking server status
int Next_bytes_time = -1; // bytes sent
// how often each player gets updated
UI_TIMESTAMP Multi_client_update_times[MAX_PLAYERS]; // client update packet timestamp
// local network buffer data
LOCAL ubyte net_buffer[NUM_REENTRANT_LEVELS][MAX_NET_BUFFER];
LOCAL ubyte Multi_read_count;
UI_TIMESTAMP Multi_restr_query_timestamp;
join_request Multi_restr_join_request;
net_addr Multi_restr_addr;
int Multi_join_restr_mode = -1;
LOCAL fix Multi_server_wait_start; // variable to hold start time when waiting to reestablish with server
// non API master tracker vars
char Multi_tracker_login[MULTI_TRACKER_STRING_LEN+1] = "";
char Multi_tracker_passwd[MULTI_TRACKER_STRING_LEN+1] = "";
char Multi_tracker_squad_name[MULTI_TRACKER_STRING_LEN+1] = "";
int Multi_tracker_id = -1;
char Multi_tracker_id_string[255];
// current file checksum
ushort Multi_current_file_checksum = 0;
int Multi_current_file_length = -1;
// -------------------------------------------------------------------------------------------------
// multi_init() is called only once, at game start-up. Get player address + port, initialize the
// network players list.
//
//
void multi_init()
{
int idx;
// read in config file
multi_options_read_config();
Assert( Net_player == NULL );
Multi_id_num = 0;
// clear out all netplayers
for(idx=0; idx<MAX_PLAYERS; idx++){
Net_players[idx].init();
Net_players[idx].reliable_socket = PSNET_INVALID_SOCKET;
}
// initialize the local netplayer
Net_player = &Net_players[0];
Net_player->tracker_player_id = Multi_tracker_id;
Net_player->m_player = Player;
Net_player->flags = 0;
Net_player->s_info.xfer_handle = -1;
Net_player->player_id = multi_get_new_id();
Net_player->client_cinfo_seq = 0;
Net_player->client_server_seq = 0;
// get our connection speed
Multi_connection_speed = multi_get_connection_speed();
// initialize other stuff
multi_log_init();
// load up common multiplayer icons
if (!Is_standalone)
multi_load_common_icons();
// delete mvalid.cfg if it exists
cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
}
// this is an important function which re-initializes any variables required in multiplayer games.
// Always make sure globals you add are re-initialized here !!!!
void multi_vars_init()
{
// initialize this variable right away. Used in game_level_init for init'ing the player
Next_ship_signature = SHIP_SIG_MIN;
Next_asteroid_signature = ASTEROID_SIG_MIN;
Next_non_perm_signature = NPERM_SIG_MIN;
Next_debris_signature = DEBRIS_SIG_MIN;
Next_waypoint_signature = WAYPOINT_SIG_MIN;
// server-client critical stuff
Multi_button_info_ok = 0;
Multi_button_info_id = 0;
// Ingame join stuff
Ingame_join_net_signature = -1;
// Netgame stuff
Netgame.game_state = NETGAME_STATE_FORMING;
// team select stuff
Multi_ts_inited = 0;
// load send stuff
Multi_mission_loaded = 0; // client side
// restricted game stuff
Multi_restr_query_timestamp = UI_TIMESTAMP::invalid();
// respawn stuff
Multi_server_check_count = 0;
// reentrant variable
Multi_read_count = 0;
// current file checksum
Multi_current_file_checksum = 0;
Multi_current_file_length = -1;
Active_games.clear();
Game_server_head = NULL;
// only the server should ever care about this
Multi_id_num = 0;
}
// -------------------------------------------------------------------------------------------------
// multi_level_init() is called whenever the player starts a multiplayer game
//
//
void multi_level_init()
{
int idx;
// NETLOG
ml_string(NOX("multi_level_init()"));
// initialize the Net_players array
for ( idx = 0; idx < MAX_PLAYERS; idx++) {
// close all sockets down just for good measure
psnet_rel_close_socket(Net_players[idx].reliable_socket);
Net_players[idx].init();
Net_players[idx].reliable_socket = PSNET_INVALID_SOCKET;
Net_players[idx].s_info.xfer_handle = -1;
Net_players[idx].p_info.team = 0;
}
// initialize the Players array
for (idx=0;idx<MAX_PLAYERS;idx++) {
if (Player == &Players[idx]) {
continue;
}
Players[idx].reset();
}
multi_vars_init();
// initialize the fake lag/loss system
#ifdef MULTI_USE_LAG
multi_lag_init();
#endif
// initialize the kick system
multi_kick_init();
// initialize all file xfer stuff
multi_xfer_init(multi_file_xfer_notify);
// close the chatbox (if one exists)
chatbox_close();
// reset the data xfer system
multi_data_reset();
// initialize the voice system
multi_voice_init();
// intialize the pause system
multi_pause_reset();
// initialize endgame stuff
multi_endgame_init();
// initialize respawning
multi_respawn_init();
// initialize all netgame timestamps
multi_reset_timestamps();
// flush psnet sockets
psnet_flush();
}
// multi_check_listen() calls low level psnet routine to see if we have a connection from a client we
// should accept.
void multi_check_listen()
{
int i;
net_addr addr;
PSNET_SOCKET_RELIABLE sock;
// call psnet routine which calls select to see if we need to check for a connect from a client
// by passing addr, we are telling check_for_listen to do the accept and return who it was from in
// addr. The
sock = psnet_rel_check_for_listen(&addr);
if (sock != PSNET_INVALID_SOCKET) {
// be sure that my address and the server address are set correctly.
if ( !psnet_same(&Psnet_my_addr, &Net_player->p_info.addr) ){
Net_player->p_info.addr = Psnet_my_addr;
}
if ( !psnet_same(&Psnet_my_addr, &(Netgame.server_addr)) ){
Netgame.server_addr = Psnet_my_addr;
}
// the connection was accepted in check_for_listen. Find the netplayer whose address we connected
// with and assign the socket descriptor.
// Updated to utilize psnet_same() for address comparison so the port is also taken into account.
// This allows multiple players using NAT to access a remote server simultneously.
for (i = 0; i < MAX_PLAYERS; i++ ) {
if ( (Net_players[i].flags & NETINFO_FLAG_CONNECTED) && (psnet_same(&addr, &(Net_players[i].p_info.addr))) ) {
// mark this flag so we know he's "fully" connected
Net_players[i].flags |= NETINFO_FLAG_RELIABLE_CONNECTED;
Net_players[i].reliable_socket = sock;
// send player information to the joiner
send_accept_player_data( &Net_players[i], (Net_players[i].flags & NETINFO_FLAG_INGAME_JOIN)?1:0 );
// send a netgame update so the new client has all the necessary settings
send_netgame_update_packet();
// if this is a team vs. team game, send an update
if(Netgame.type_flags & NG_TYPE_TEAM){
multi_team_send_update();
}
// NETLOG
ml_printf(NOX("Accepted TCP connection from %s"), Net_players[i].m_player == NULL ? NOX("Unknown") : Net_players[i].m_player->callsign);
break;
}
}
// if we didn't find a player, close the socket
if ( i == MAX_PLAYERS ) {
nprintf(("Network", "Got accept on my listen socket, but unknown player. Closing socket.\n"));
psnet_rel_close_socket(sock);
}
}
}
// returns true is server hasn't been heard from in N seconds. false otherwise
int multi_client_server_dead()
{
fix this_time, last_time, max;
// get the last time we have heard from the server. If greater than some default, then maybe
// display some icon on the HUD indicating slow network connection. if greater than some higher
// max, stop simulating on the client side until we hear from the server again.
this_time = timer_get_fixed_seconds();
last_time = Netgame.server->last_heard_time;
// check for wrap! must return 0
if ( last_time > this_time )
return 0;
this_time -= last_time;
// if in mission, use the smaller timeout value. Outside of mission, use a large one.
if ( MULTI_IN_MISSION ){
max = MULTI_SERVER_MAX_TIMEOUT;
} else {
max = MULTI_SERVER_MAX_TIMEOUT_LARGE;
}
if ( this_time > max){
return 1;
} else {
return 0;
}
}
void multi_process_incoming(); // prototype for function later in this module
// function to process network data in hopes of getting info back from server
int multi_client_wait_on_server()
{
int is_dead;
is_dead = multi_client_server_dead();
// if the server is back alive, tell our popup
if ( !is_dead ){
return MULTI_SERVER_ALIVE;
}
// on release version -- keep popup active for 60 seconds, then bail
#ifdef NDEBUG
fix this_time = timer_get_fixed_seconds();
// if the timer wrapped:
if ( this_time < Multi_server_wait_start ) {
Multi_server_wait_start = timer_get_fixed_seconds();
return FALSE;
}
// check to see if timeout expired
this_time -= Multi_server_wait_start;
if ( this_time > MULTI_SERVER_WAIT_TIME ){
return MULTI_SERVER_GONE;
}
#endif
return FALSE;
}
// function called by multiplayer clients to stop simulating when they have not heard from the server
// in a while.
void multi_client_check_server()
{
int rval;
Assert( MULTIPLAYER_CLIENT );
// this function can get called while in the popup code below. So we include this check as a
// reentrancy check.
if ( Multi_server_check_count )
return;
// make sure we have a valid server
if(Netgame.server == NULL){
return;
}
Multi_server_check_count++;
if(multi_client_server_dead()){
Netgame.flags |= NG_FLAG_SERVER_LOST;
} else {
Netgame.flags &= ~(NG_FLAG_SERVER_LOST);
}
if(Netgame.flags & NG_FLAG_SERVER_LOST) {
if(!(Game_mode & GM_IN_MISSION) && !popup_active()){
// need to start a popup
Multi_server_wait_start = timer_get_fixed_seconds();
rval = popup_till_condition( multi_client_wait_on_server, XSTR("Cancel",641), XSTR("Contact lost with server. Stopping simulation until contact reestablished. Press Cancel to exit game.",642) );
if ( !rval || (rval == MULTI_SERVER_GONE) ) {
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_CONTACT_LOST);
}
Netgame.flags &= ~(NG_FLAG_SERVER_LOST);
}
}
Multi_server_check_count--;
}
// -------------------------------------------------------------------------------------------------
// process_packet_normal() will determine what sort of packet it is, and send it to the appropriate spot.
// Prelimiary verification of the magic number and checksum are done here.
//
void process_packet_normal(ubyte* data, header *header_info, bool reliable)
{
// this is for helping to diagnose misaligned packets. The last sensible packet
// is usually the culprit that needs to be analyzed.
if (Cmdline_dump_packet_type) {
mprintf(("Game packet type of %d received.\n", data[0]));
}
switch ( data[0] ) {
case JOIN:
process_join_packet(data, header_info);
break;
case GAME_CHAT:
process_game_chat_packet( data, header_info );
break;
case NOTIFY_NEW_PLAYER:
process_new_player_packet(data, header_info);
break;
case HUD_MSG:
process_hud_message(data, header_info);
break;
case MISSION_MESSAGE:
process_mission_message_packet( data, header_info );
break;
case LEAVE_GAME:
process_leave_game_packet(data, header_info);
break;
case GAME_QUERY:
process_game_query(data, header_info);
break;
case GAME_ACTIVE:
process_game_active_packet(data, header_info);
break;
case GAME_INFO:
process_game_info_packet( data, header_info );
break;
case SECONDARY_FIRED_AI:
process_secondary_fired_packet(data, header_info, 0);
break;
case SECONDARY_FIRED_PLR:
process_secondary_fired_packet(data, header_info, 1);
break;
case COUNTERMEASURE_FIRED:
process_countermeasure_fired_packet( data, header_info );
break;
case FIRE_TURRET_WEAPON:
process_turret_fired_packet( data, header_info );
break;
case GAME_UPDATE:
process_netgame_update_packet( data, header_info );
break;
case UPDATE_DESCRIPT:
process_netgame_descript_packet( data, header_info );
break;
case NETPLAYER_UPDATE:
process_netplayer_update_packet( data, header_info );
break;
case ACCEPT :
process_accept_packet(data, header_info);
break;
case OBJECT_UPDATE:
multi_oo_process_update(data, header_info);
break;
case SHIP_KILL:
process_ship_kill_packet( data, header_info );
break;
case MISSILE_KILL:
process_weapon_kill_packet(data, header_info);
break;
case WING_CREATE:
process_wing_create_packet( data, header_info );
break;
case SHIP_CREATE:
process_ship_create_packet( data, header_info );
break;
case SHIP_DEPART:
process_ship_depart_packet( data, header_info );
break;
case MISSION_LOG_ENTRY:
process_mission_log_packet( data, header_info );
break;
case PING:
process_ping_packet(data, header_info);
break;
case PONG:
process_pong_packet(data, header_info);
break;
case XFER_PACKET:
Assert(header_info->id >= 0);
int np_index;
PSNET_SOCKET_RELIABLE sock;
sock = PSNET_INVALID_SOCKET;
// if I'm the server of the game, find out who this came from
if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER)){
np_index = find_player_index(header_info->id);
if(np_index >= 0){
sock = Net_players[np_index].reliable_socket;
}
}
// otherwise always use my own socket
else if(Net_player != NULL){
sock = Net_player->reliable_socket;
}
header_info->bytes_processed = multi_xfer_process_packet(data + HEADER_LENGTH, sock) + HEADER_LENGTH;
break;
case MISSION_REQUEST:
process_mission_request_packet(data,header_info);
break;
case MISSION_ITEM:
process_mission_item_packet(data,header_info);
break;
case MULTI_PAUSE_REQUEST:
process_multi_pause_packet(data, header_info);
break;
case INGAME_NAK:
process_ingame_nak(data, header_info);
break;
case SHIPS_INGAME_PACKET:
process_ingame_ships_packet(data, header_info);
break;
case WINGS_INGAME_PACKET:
process_ingame_wings_packet(data, header_info);
break;
case MISSION_END:
process_endgame_packet(data, header_info);
break;
case FORCE_MISSION_END:
process_force_end_mission_packet(data, header_info);
break;
case OBSERVER_UPDATE:
process_observer_update_packet(data, header_info);
break;
case NETPLAYER_SLOTS_P:
process_netplayer_slot_packet(data, header_info);
break;
case SHIP_STATUS_CHANGE:
process_ship_status_packet(data, header_info);
break;
case PLAYER_ORDER_PACKET:
process_player_order_packet(data, header_info);
break;
case INGAME_SHIP_UPDATE:
process_ingame_ship_update_packet(data, header_info);
break;
case INGAME_SHIP_REQUEST:
process_ingame_ship_request_packet(data, header_info);
break;
case FILE_SIG_INFO:
process_file_sig_packet(data, header_info);
break;
case RESPAWN_NOTICE:
multi_respawn_process_packet(data,header_info);
break;
case SUBSYSTEM_DESTROYED:
process_subsystem_destroyed_packet( data, header_info );
break;
case LOAD_MISSION_NOW :
process_netplayer_load_packet(data, header_info);
break;
case FILE_SIG_REQUEST :
process_file_sig_request(data, header_info);
break;
case JUMP_INTO_GAME:
process_jump_into_mission_packet(data, header_info);
break;
case CLIENT_REPAIR_INFO:
process_repair_info_packet(data,header_info);
break;
case MISSION_SYNC_DATA:
process_mission_sync_packet(data,header_info);
break;
case STORE_MISSION_STATS:
process_store_stats_packet(data, header_info);
break;
case DEBRIS_UPDATE:
process_debris_update_packet(data, header_info);
break;
case SHIP_WSTATE_CHANGE:
process_ship_weapon_change( data, header_info );
break;
case WSS_UPDATE_PACKET:
process_wss_update_packet(data, header_info);
break;
case WSS_REQUEST_PACKET:
process_wss_request_packet( data, header_info );
break;
case FIRING_INFO:
process_firing_info_packet( data, header_info );
break;
case CARGO_REVEALED:
process_cargo_revealed_packet( data, header_info);
break;
case CARGO_HIDDEN:
process_cargo_hidden_packet( data, header_info);
break;
case SUBSYS_CARGO_REVEALED:
process_subsystem_cargo_revealed_packet( data, header_info);
break;
case SUBSYS_CARGO_HIDDEN:
process_subsystem_cargo_hidden_packet( data, header_info);
break;
case MISSION_GOAL_INFO:
process_mission_goal_info_packet(data, header_info);
break;
case KICK_PLAYER:
process_player_kick_packet(data, header_info);
break;
case PLAYER_SETTINGS:
process_player_settings_packet(data, header_info);
break;
case DENY:
process_deny_packet(data, header_info);
break;
case POST_SYNC_DATA:
process_post_sync_data_packet(data, header_info);
break;
case WSS_SLOTS_DATA:
process_wss_slots_data_packet(data,header_info);
break;
case SHIELD_EXPLOSION:
process_shield_explosion_packet( data, header_info );
break;
case PLAYER_STATS:
process_player_stats_block_packet(data, header_info);
break;
case SLOT_UPDATE:
process_pslot_update_packet(data,header_info);
break;
case AI_INFO_UPDATE:
process_ai_info_update_packet( data, header_info );
break;
case CAMPAIGN_UPDATE :
multi_campaign_process_update(data,header_info);
break;
case CAMPAIGN_UPDATE_INGAME:
multi_campaign_process_ingame_start(data,header_info);
break;
case VOICE_PACKET :
multi_voice_process_packet(data,header_info);
break;
case TEAM_UPDATE :
multi_team_process_packet(data,header_info);
break;
case ASTEROID_INFO:
process_asteroid_info(data, header_info);
break;
case HOST_RESTR_QUERY:
process_host_restr_packet(data, header_info);
break;
case OPTIONS_UPDATE:
multi_options_process_packet(data,header_info);
break;
case SQUADMSG_PLAYER:
multi_msg_process_squadmsg_packet(data,header_info);
break;
case NETGAME_END_ERROR:
process_netgame_end_error_packet(data,header_info);
break;
case COUNTERMEASURE_SUCCESS:
process_countermeasure_success_packet( data, header_info );
break;
case CLIENT_UPDATE:
process_client_update_packet(data, header_info);
break;
case COUNTDOWN:
process_countdown_packet(data, header_info);
break;
case DEBRIEF_INFO:
process_debrief_info( data, header_info );
break;
case ACCEPT_PLAYER_DATA:
process_accept_player_data( data, header_info );
break;
case HOMING_WEAPON_UPDATE:
process_homing_weapon_info( data, header_info );
break;
case EMP_EFFECT:
process_emp_effect(data, header_info);
break;
case REINFORCEMENT_AVAIL:
process_reinforcement_avail( data, header_info );
break;
case PRIMARY_FIRED_NEW:
process_NEW_primary_fired_packet(data, header_info);
break;
case LINEAR_WEAPON_FIRED:
process_non_homing_fired_packet(data, header_info);
break;
case ANIMATION_TRIGGERED:
process_animation_triggered_packet(data, header_info);
break;
case COUNTERMEASURE_NEW:
process_NEW_countermeasure_fired_packet(data, header_info);
break;
case BEAM_FIRED:
process_beam_fired_packet(data, header_info);
break;
case SW_STD_QUERY:
process_sw_query_packet(data, header_info);
break;
case EVENT_UPDATE:
process_event_update_packet(data, header_info);
break;
case VARIABLE_UPDATE:
process_variable_update_packet(data, header_info);
break;
case OBJECT_UPDATE_NEW:
multi_oo_process_update(data, header_info);
break;
case WEAPON_DET:
process_weapon_detonate_packet(data, header_info);
break;
case FLAK_FIRED:
process_flak_fired_packet(data, header_info);
break;
case NETPLAYER_PAIN:
process_player_pain_packet(data, header_info);
break;
case LIGHTNING_PACKET:
process_lightning_packet(data, header_info);
break;
case BYTES_SENT:
process_bytes_recvd_packet(data, header_info);
break;
case TRANSFER_HOST:
process_host_captain_change_packet(data, header_info);
break;
case SELF_DESTRUCT:
process_self_destruct_packet(data, header_info);
break;
case SEXP:
process_sexp_packet(data, header_info);
break;
case TURRET_TRACK:
process_turret_tracking_packet(data, header_info);
break;
case LUA_DATA_PACKET:
process_lua_packet(data, header_info, reliable);
break;
default:
mprintf(("Received packet with unknown type %d\n", data[0] ));
header_info->bytes_processed = 10000;
break;
} // end switch
// Let's also dump the amount of data that we've processed so far.
if (Cmdline_dump_packet_type) {
mprintf(("Game packet ended. Total amount of data processed from packet is %d.\n", header_info->bytes_processed));
}
}
// Takes a bunch of messages, check them for validity,
// and pass them to multi_process_data.
// --------------------^
// this should be process_packet() I think, or with the new code
// process_tracker_packet() as defined in MultiTracker.[h,cpp]
void multi_process_bigdata(ubyte *data, int len, net_addr *from_addr, int reliable)
{
int bytes_processed;
int player_num;
header header_info;
ubyte *buf;
// the only packets we will process from an unknown player are GAME_QUERY, GAME_INFO, JOIN, PING, PONG, ACCEPT, and GAME_ACTIVE packets
player_num = find_player(from_addr);
// find the player who sent the message and mark the last_heard time for this player
// check to see if netplayer is null (it may be in cases such as getting lists of games from the tracker)
if(player_num >= 0){
Net_players[player_num].last_heard_time = timer_get_fixed_seconds();
}
// store fields that were passed along in the message
// store header information that was captured from the network-layer header
memcpy(header_info.addr, from_addr->addr, sizeof(header_info.addr));
header_info.port = from_addr->port;
if(player_num >= 0){
header_info.id = Net_players[player_num].player_id;
} else {
header_info.id = -1;
}
bytes_processed = 0;
// start off logging of packets by writing how many bytes of data we should get through.
if (Cmdline_dump_packet_type) {
mprintf(("Network packet with %d bytes of data received. ", len));
}
while( (bytes_processed >= 0) && (bytes_processed < len) ) {
buf = &(data[bytes_processed]);
const ubyte type = buf[0];
// if its coming from an unknown source, there are only certain packets we will actually process
if((player_num == -1) && !multi_is_valid_unknown_packet((ubyte)type)){
// So let's log it, because we should probably at least be vaguely aware of the buggy behavior.
mprintf(("Receiving unknown source packet of type %d that should have a source, aborting packet processing!\n", type));
return ;
}
// perform any special processing checks here
process_packet_normal(buf,&header_info, reliable != 0);
// MWA -- magic number was removed from header on 8/4/97. Replaced with bytes_processed
// variable which gets stuffed whenever a packet is processed.
bytes_processed += header_info.bytes_processed;
}
// if this is not reliable data and we have a valid player
if(Net_player != NULL){
if(!MULTIPLAYER_MASTER && !reliable && (Game_mode & GM_IN_MISSION)){
Net_player->cl_bytes_recvd += len;
}
}
}
// process all reliable socket details
void multi_process_reliable_details()
{
int idx;
int sock_status;
// run reliable sockets
psnet_rel_work();
// server operations
if ( MULTIPLAYER_MASTER ){
// listen for new reliable socket connections
multi_check_listen();
// check for any broken sockets and delete any players
for(idx=0; idx<MAX_PLAYERS; idx++){
// players who _should_ be validly connected
if((idx != MY_NET_PLAYER_NUM) && MULTI_CONNECTED(Net_players[idx])){
// if this guy's socket is broken or disconnected, kill him
sock_status = psnet_rel_get_status(Net_players[idx].reliable_socket);
if((sock_status == RNF_UNUSED) || (sock_status == RNF_BROKEN) || (sock_status == RNF_DISCONNECTED)){
ml_string("Shutting down rel socket because of disconnect!");
delete_player(idx);
}
// if we're still waiting for this guy to connect on his reliable socket and he's timed out, boot him
if(Net_players[idx].s_info.reliable_connect_time != -1){
// if he's connected
if(Net_players[idx].reliable_socket != PSNET_INVALID_SOCKET){
Net_players[idx].s_info.reliable_connect_time = -1;
}
// if he's timed out
else if(((time(nullptr) - Net_players[idx].s_info.reliable_connect_time) > MULTI_RELIABLE_CONNECT_WAIT) && (Net_players[idx].reliable_socket == PSNET_INVALID_SOCKET)){
ml_string("Player timed out while connecting on reliable socket!");
delete_player(idx);
}
}
}
}
}
// clients should detect broken sockets
else {
extern unsigned int Serverconn;
if(Serverconn != 0xffffffff){
int status = psnet_rel_get_status(Serverconn);
if(status == RNF_BROKEN){
mprintf(("CLIENT SOCKET DISCONNECTED\n"));
// quit the game
if(!multi_endgame_ending()){
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_CONTACT_LOST);
}
}
}
}
}
// multi_process_incoming reads incoming data off the unreliable and reliable ports and sends
// the data to process_big_data
void multi_process_incoming()
{
int size;
ubyte *data, *savep;
net_addr from_addr;
Assert( Multi_read_count < NUM_REENTRANT_LEVELS );
savep = net_buffer[Multi_read_count];
Multi_read_count++;
data = savep;
// get the other net players data
while( (size = psnet_get(data, &from_addr))>0 ) {
// ingame joiners will ignore UDP packets until they are have picked a ship and are in the mission
if( (Net_player->flags & NETINFO_FLAG_INGAME_JOIN) && (Net_player->state != NETPLAYER_STATE_INGAME_SHIP_SELECT) ){
nprintf(("Network","Tossing UDP like a good little ingame joiner...\n"));
}
// otherwise process incoming data normally
else {
multi_process_bigdata(data, size, &from_addr, 0);
}
} // end while
// read reliable sockets for data
data = savep;
int idx;
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
for (idx=0;idx<MAX_PLAYERS;idx++) {
if((Net_players[idx].flags & NETINFO_FLAG_CONNECTED) && (Net_player != NULL) && (Net_player->player_id != Net_players[idx].player_id)){
while( (size = psnet_rel_get(Net_players[idx].reliable_socket, data, MAX_NET_BUFFER)) > 0){
multi_process_bigdata(data, size, &Net_players[idx].p_info.addr, 1);
}
}
}
} else {
// if I'm not the master of the game, read reliable data from my connection with the server
if((Net_player->reliable_socket != PSNET_INVALID_SOCKET) && (Net_player->reliable_socket != 0)){
while( (size = psnet_rel_get(Net_player->reliable_socket,data, MAX_NET_BUFFER)) > 0){
multi_process_bigdata(data, size, &Netgame.server_addr, 1);
}
}
}
Multi_read_count--;
}
// -------------------------------------------------------------------------------------------------
// multi_do_frame() is called once per game loop do update all the multiplayer objects, and send
// the player data to all the other net players.
//
//
int eye_tog = 1;
DCF(eye_tog, "Toggles setting of the local player eyepoint on every frame (Multiplayer)")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("proper eye stuff is %s\n", eye_tog ? "ON" : "OFF");
return;
}
eye_tog = !eye_tog;
dc_printf("proper eye stuff is %s\n", eye_tog ? "ON" : "OFF");
}
void multi_do_frame()
{
PSNET_TOP_LAYER_PROCESS();
// always set the local player eye position/orientation here so we know its valid throughout all multiplayer
// function calls
if((Net_player != NULL) && eye_tog){
camid cid = player_get_cam();
if(cid.isValid())
{
camera *cam = cid.getCamera();
cam->get_info(&Net_player->s_info.eye_pos, &Net_player->s_info.eye_orient);
}
}
// send all buffered packets from the previous frame
multi_io_send_buffered_packets();
// datarate tracking
multi_rate_process();
// always process any pending endgame details
multi_endgame_process();
// process all reliable socket details, including :
// 1.) Listening for new pending reliable connections (server)
// 2.) Checking for broken sockets (server/client)
// 3.) Checking for clients who haven't fully connected
multi_process_reliable_details();
// get the other net players data
multi_process_incoming();
// process object update datarate stuff (for clients and server both)
multi_oo_rate_process();
// clients should check when last time they heard from sever was -- if too long, then
// pause the simulation so wait for it to possibly come back
if ( (MULTIPLAYER_CLIENT) && (Net_player->flags & NETINFO_FLAG_CONNECTED) ){
multi_client_check_server();
}
// everybody pings all the time
if((Next_ping_time < 0) || ((time(NULL) - Next_ping_time) > PING_SEND_TIME) ){
if( (Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
send_netplayer_update_packet();
}
// ping everyone
multi_ping_send_all();
Next_ping_time = time(NULL);
}
// if I am the master, and we are not yet actually playing the mission, send off netgame
// status to all other players in the game. If I am not the master of the game, and we
// are not in the game, then send out my netplayer status to the host
if ( (Net_player->flags & NETINFO_FLAG_CONNECTED) && !(Game_mode & GM_IN_MISSION)){
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
if ( (Netgame_send_time < 0) || ((time(NULL) - Netgame_send_time) > NETGAME_SEND_TIME) ) {
send_netgame_update_packet(nullptr, true);
Netgame_send_time = (int) time(NULL);
}
} else {
if ( (State_send_time < 0) || ((time(NULL) - State_send_time) > STATE_SEND_TIME) ){
// observers shouldn't send an update state packet
if ( !(Net_player->flags & NETINFO_FLAG_OBSERVER) ){
send_netplayer_update_packet();
}
State_send_time = time(NULL);
}
}
}
else if ( (Net_player->flags & NETINFO_FLAG_CONNECTED) && (Game_mode & GM_IN_MISSION) ) {
// if I am connected and am in the mission, do things that need to be done on a regular basis
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
if ( (Gameinfo_send_time < 0) || ((time(NULL) - Gameinfo_send_time) > GAMEINFO_SEND_TIME)){
send_game_info_packet();
Gameinfo_send_time = (int) time(NULL);
}
// for any potential respawns
multi_respawn_handle_invul_players();
multi_respawn_check_ai();
// for any potential ingame joiners
multi_handle_ingame_joiners();
} else {
// the clients need to do some processing of stuff as well
}
}
// check to see if we're waiting on confirmation for a restricted ingame join
if(Multi_restr_query_timestamp.isValid()){
// if it has elapsed, unset the ingame join flag
if(ui_timestamp_elapsed(Multi_restr_query_timestamp)){
Multi_restr_query_timestamp = UI_TIMESTAMP::invalid();
Netgame.flags &= ~(NG_FLAG_INGAME_JOINING);
}
}
// while in the mission, send my PlayerControls to the host so that he can process
// my movement
if ( Game_mode & GM_IN_MISSION ) {
if ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
if(Net_player->flags & NETINFO_FLAG_OBSERVER){
// if the rate limiting system says its ok
if(multi_oo_cirate_can_send()){
// send my observer position/object update
send_observer_update_packet();
}
} else if ( !(Player_ship->is_departing() ) ){
// if the rate limiting system says its ok
if(multi_oo_cirate_can_send()){
// use the new method
multi_oo_send_control_info();
}
}
// bytes received info
if( (Next_bytes_time < 0) || ((time(NULL) - Next_bytes_time) > BYTES_SENT_TIME) ){
if(Net_player != NULL){
send_bytes_recvd_packet(Net_player);
// reset bytes recvd
Net_player->cl_bytes_recvd = 0;
}
// reset timestamp
Next_bytes_time = (int) time(NULL);
}
} else {
// right before sending new positions, we should do any rollback shots and resimulation
multi_ship_record_do_rollback();
// sending new objects from here is dependent on having objects only created after
// the game is done moving the objects. I think that I can enforce this.
multi_oo_process();
// send updates for turret tracking.
Multi_Turret_Manager.send_queued_packets();
// evaluate whether the time limit has been reached or max kills has been reached
// Commented out by Sandeep 4/12/98, was causing problems with testing.
if( ((f2fl(Netgame.options.mission_time_limit) > 0.0f) && (Missiontime > Netgame.options.mission_time_limit)) ||
multi_kill_limit_reached() ) {
// make sure we don't do this more than once
if(Netgame.game_state == NETGAME_STATE_IN_MISSION){
multi_handle_end_mission_request();
}
}
}
}
// periodically send a client update packet to all clients
if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER)){
int idx;
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
if ( !Multi_client_update_times[idx].isValid() || ui_timestamp_elapsed_safe(Multi_client_update_times[idx], MILLISECONDS_PER_SECOND) ) {
send_client_update_packet(&Net_players[idx]);
Multi_client_update_times[idx] = ui_timestamp(Multi_client_update_intervals[Net_players[idx].p_info.options.obj_update_level]);
}
}
}
}
// process any kicked player details
multi_kick_process();
// do any file xfer details
multi_xfer_do();
// process any player data details (wav files, pilot pics, etc)
multi_data_do();
// do any voice details
multi_voice_process();
// process any player messaging details
multi_msg_process();
// process any tracker messages
multi_fs_tracker_process();
// Cyborg17 update the new frame recording system for accurate client shots, needs to go after most everything else in multi.
if (Game_mode & GM_IN_MISSION) {
multi_ship_record_increment_frame();
}
// if on the standalone, do any gui stuff
if (Game_mode & GM_STANDALONE_SERVER) {
std_do_gui_frame();
}
// if master then maybe do port forwarding setup/refresh/wait
if (Net_player->flags & NETINFO_FLAG_AM_MASTER) {
multi_port_forward_do();
// do mdns stuff here too
if ( !MULTI_IS_TRACKER_GAME && (Net_player->p_info.options.flags & MLO_FLAG_LOCAL_BROADCAST) ) {
multi_mdns_service_do();
}
}
}
// -------------------------------------------------------------------------------------------------
// multi_pause_do_frame() is called once per game loop do update all the multiplayer objects, and send
// the player data to all the other net players when the multiplayer game is paused. It only will do
// checking for a few specialized packets (MULTI_UNPAUSE, etc)
//
void multi_pause_do_frame()
{
PSNET_TOP_LAYER_PROCESS();
// always set the local player eye position/orientation here so we know its valid throughout all multiplayer
// function calls
// if((Net_player != NULL) && eye_tog){
// player_get_eye(&Net_player->s_info.eye_pos, &Net_player->s_info.eye_orient);
// }
// send all buffered packets from the previous frame
multi_io_send_buffered_packets();
// always process any pending endgame details
multi_endgame_process();
// process all reliable socket details, including :
// 1.) Listening for new pending reliable connections (server)
// 2.) Checking for broken sockets (server/client)
// 3.) Checking for clients who haven't fully connected
multi_process_reliable_details();
// these timestamps and handlers shoul be evaluated in the pause state
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
if ( (Gameinfo_send_time < 0) || ((time(NULL) - Gameinfo_send_time) > GAMEINFO_SEND_TIME) ){
send_game_info_packet();
Gameinfo_send_time = (int) time(NULL);
}
}
// everybody pings all the time
if((Next_ping_time < 0) || ((time(NULL) - Next_ping_time) > PING_SEND_TIME) ){
multi_ping_send_all();
Next_ping_time = time(NULL);
}
// periodically send a client update packet to all clients
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
int idx;
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
if ( !Multi_client_update_times[idx].isValid() || ui_timestamp_elapsed_safe(Multi_client_update_times[idx], MILLISECONDS_PER_SECOND) ) {
send_client_update_packet(&Net_players[idx]);
Multi_client_update_times[idx] = ui_timestamp(Multi_client_update_intervals[Net_players[idx].p_info.options.obj_update_level]);
}
}
}
// for any potential ingame joiners
multi_handle_ingame_joiners();
}
// do any file xfer details
multi_xfer_do();
// process any player data details (wav files, pilot pics, etc)
multi_data_do();
// get the other net players data
multi_process_incoming();
// do any voice details
multi_voice_process();
// process any player messaging details
multi_msg_process();
// process any kicked player details
multi_kick_process();
// process any pending endgame details
multi_endgame_process();
// process object update stuff (for clients and server both)
if(MULTIPLAYER_MASTER){
multi_oo_process();
}
// if on the standalone, do any gui stuff
if (Game_mode & GM_STANDALONE_SERVER) {
std_do_gui_frame();
}
// if master then maybe do port forwarding setup/refresh/wait
if (Net_player->flags & NETINFO_FLAG_AM_MASTER) {
multi_port_forward_do();
// do mdns stuff here too
if ( !MULTI_IS_TRACKER_GAME && (Net_player->p_info.options.flags & MLO_FLAG_LOCAL_BROADCAST) ) {
multi_mdns_service_do();
}
}
}
// --------------------------------------------------------------------------------
// standalone_main_init() the standalone equivalent of the main menu
//
extern int sock_inited;
float frame_time = (float)1.0/(float)30.0;
void standalone_main_init()
{
std_debug_set_standalone_state_string("Main Init");
Game_mode = (GM_STANDALONE_SERVER | GM_MULTIPLAYER);
// NETLOG
ml_string(NOX("Standalone server initializing"));
// read in config file
// multi_options_read_config();
#ifdef _WIN32
// if we failed to startup on our desired protocol, fail
if ( !psnet_is_active() ) {
if (Psnet_failure_code == WSAEADDRINUSE) {
os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, XSTR("You have selected TCP/IP for multiplayer FreeSpace, but the TCP socket is already in use. Check for another instance and/or use the \"-port <port_num>\" command line option to select an available port.", 1604));
}
else {
os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, XSTR("You have selected TCP/IP for multiplayer FreeSpace, but the TCP/IP protocol was not detected on your machine.", 362));
}
exit(1);
}
#endif // ifdef _WIN32
// clear out the Netgame structure and start filling in the values
// NOTE : these values are not incredibly important since they will be overwritten by the host when he joins
Netgame.init();
Netgame.game_state = NETGAME_STATE_FORMING; // game is currently starting up
Netgame.security = 0;
Netgame.server_addr = Psnet_my_addr;
The_mission.Reset( );
// reinitialize all systems
multi_level_init();
// intialize endgame stuff
multi_endgame_init();
// clear the file xfer system
multi_xfer_reset();
multi_xfer_force_dir(CF_TYPE_MULTI_CACHE);
// setup a blank pilot (this is a standalone usage only!)
Pilot.load_player(NULL);
// setup the netplayer for the standalone
Net_player = &Net_players[0];
Net_player->tracker_player_id = -1;
Net_player->flags |= (NETINFO_FLAG_AM_MASTER | NETINFO_FLAG_CONNECTED | NETINFO_FLAG_DO_NETWORKING | NETINFO_FLAG_MISSION_OK);
Net_player->state = NETPLAYER_STATE_WAITING;
Net_player->m_player = Player;
strcpy_s(Player->callsign, "server");
Net_player->p_info.addr = Psnet_my_addr;
Net_player->s_info.xfer_handle = -1;
Net_player->player_id = multi_get_new_id();
Netgame.server = Net_player;
// maybe flag the game as having a hacked ships.tbl
/*if(!Game_ships_tbl_valid){
Netgame.flags |= NG_FLAG_HACKED_SHIPS_TBL;
}
// maybe flag the game as having a hacked weapons.tbl
if(!Game_weapons_tbl_valid){
Netgame.flags |= NG_FLAG_HACKED_WEAPONS_TBL;
}*/
// hacked data
if(game_hacked_data()){
Netgame.flags |= NG_FLAG_HACKED_SHIPS_TBL;
Net_player->flags |= NETINFO_FLAG_HAXOR;
}
// setup debug flags
Netgame.debug_flags = 0;
// setup the default game name for the standalone
std_connect_set_gamename(NULL);
// set netgame default options
multi_options_set_netgame_defaults(&Netgame.options);
// set local netplayer default options
multi_options_set_local_defaults(&Net_player->p_info.options);
// set our object update level from the standalone default
Net_player->p_info.options.obj_update_level = Multi_options_g.std_datarate;
switch(Net_player->p_info.options.obj_update_level){
case OBJ_UPDATE_LOW:
nprintf(("Network","STANDALONE USING LOW UPDATES\n"));
break;
case OBJ_UPDATE_MEDIUM:
nprintf(("Network","STANDALONE USING MEDIUM UPDATES\n"));
break;
case OBJ_UPDATE_HIGH:
nprintf(("Network","STANDALONE USING HIGH UPDATE\n"));
break;
case OBJ_UPDATE_LAN:
nprintf(("Network","STANDALONE USING LAN UPDATE\n"));
break;
}
// clear out various things
animation::ModelAnimationParseHelper::parseTables();
psnet_flush();
game_flush();
virtual_pof_init();
ship_init();
// setup port forwarding
multi_port_forward_init();
// setup mdns
if ( !MULTI_IS_TRACKER_GAME ) {
multi_mdns_service_init();
}
// login to game tracker
std_tracker_login();
std_debug_set_standalone_state_string("Main Do");
std_set_standalone_fps((float)0);
std_multi_set_standalone_missiontime((float)0);
// load my missions and campaigns
multi_create_list_load_missions();
multi_create_list_load_campaigns();
// if this is a tracker game, validate missions
if(MULTI_IS_TRACKER_GAME){
multi_update_valid_missions();
}
}
// --------------------------------------------------------------------------------
// standalone_main_do()
//
// DESCRIPTION : the standalone server will wait in this state until the host of the game
// is "Waiting". That is, his state==NETPLAYER_STATE_WAITING, and he has finished
// doing everything and wants to play the game. Once this happens, we will jump
// into GS_STATE_MULTI_SERVER_WAIT
void standalone_main_do()
{
os_sleep(10); // since nothing will really be going on here, we can afford to give some time
// back to the operating system.
// kind of a do-nothing spin state.
// The standalone will eventually move into the GS_STATE_MULTI_MISSION_SYNC state when a host connects and
// attempts to start a game
// process/renew port mapping
multi_port_forward_do();
// handle mdns messages
if ( !MULTI_IS_TRACKER_GAME ) {
multi_mdns_service_do();
}
}
// --------------------------------------------------------------------------------
// standalone_main_close()
//
void standalone_main_close()
{
std_debug_set_standalone_state_string("Main Close");
// disconnect game from tracker
multi_fs_tracker_logout();
// remove port forwarding
multi_port_forward_close();
// stop mdns
multi_mdns_service_close();
}
void multi_standalone_reset_all()
{
int idx;
// NETLOG
ml_string(NOX("Standalone resetting"));
// shut all game stuff down
game_level_close();
// reinitialize the gui
std_reset_standalone_gui();
// close down all sockets
for(idx=0;idx<MAX_PLAYERS;idx++){
// 6/25/98 -- MWA -- call delete_player here to remove the player. This closes down the socket
// and marks the player as not connected anymore. It is probably cleaner to do this.
if ( &Net_players[idx] != Net_player ) {
delete_player( idx );
}
}
// make sure we go to the proper state.
if(gameseq_get_state() == GS_STATE_STANDALONE_MAIN){
standalone_main_init();
}
gameseq_post_event(GS_EVENT_STANDALONE_MAIN);
}
// --------------------------------------------------------------------------------
// multi_server_wait_init() do stuff like setting the status bits correctly
//
void multi_standalone_wait_init()
{
std_debug_set_standalone_state_string("Wait Do");
std_multi_add_goals(); // fill in the goals for the mission into the tree view
multi_reset_timestamps();
// create the bogus standalone object
multi_create_standalone_object();
}
// --------------------------------------------------------------------------------
// multi_server_wait_do_frame() wait for everyone to log in or the host to send commands
//
// DESCRIPTION : we will be in this state once the host of the game is waiting for everyone
// to be finished and ready to go, at which point, we will will tell everyone
// to enter the game, and we will start simulating ourselves. Note that most of
// this code is lifted from multi_wait_do_frame()
void multi_standalone_wait_do()
{
}
// --------------------------------------------------------------------------------
// multi_server_wait_close() cleanup
//
void multi_standalone_wait_close()
{
std_debug_set_standalone_state_string("Wait Close / Game Play");
// all players should reset sequencing
int idx;
for(idx=0;idx<MAX_PLAYERS;idx++){
if(Net_player->flags & NETINFO_FLAG_CONNECTED){
Net_players[idx].client_cinfo_seq = 0;
Net_players[idx].client_server_seq = 0;
}
}
}
// this is an artificial state which will push the standalone into the main state without it having to go through
// the init function (which would disconnect everyone and generally just screw things up)
// it will also eventually do tracker stats update
extern int Multi_debrief_server_framecount;
void multi_standalone_postgame_init()
{
std_debug_set_standalone_state_string("Postgame / Send Stats");
// NETLOG
ml_string(NOX("Standlone entering postgame"));
mission_goal_fail_incomplete();
// handle campaign stuff
if ( Game_mode & GM_CAMPAIGN_MODE ) {
// MUST store goals and events first - may be used to evaluate next mission
// store goals and events
mission_campaign_store_goals_and_events_and_variables();
// evaluate next mission
mission_campaign_eval_next_mission();
}
// always set my state to be "DEBRIEF_ACCEPT"
Net_player->state = NETPLAYER_STATE_DEBRIEF_ACCEPT;
// mark stats as not being store yet
Netgame.flags &= ~(NG_FLAG_STORED_MT_STATS);
Multi_debrief_server_framecount = 0;
// reset network timestamps
multi_reset_timestamps();
}
void multi_standalone_postgame_do()
{
// wait until everyone is in the debriefing
if((Netgame.game_state != NETGAME_STATE_DEBRIEF) && multi_netplayer_state_check(NETPLAYER_STATE_DEBRIEF, 1)){
Netgame.game_state = NETGAME_STATE_DEBRIEF;
send_netgame_update_packet();
debrief_multi_server_stuff();
}
// process server debriefing details
if(Netgame.game_state == NETGAME_STATE_DEBRIEF){
multi_debrief_server_process();
}
}
void multi_standalone_postgame_close()
{
// maybe store stats on tracker
if ( MULTI_IS_TRACKER_GAME && !(Netgame.flags & NG_FLAG_STORED_MT_STATS) ) {
if (multi_debrief_stats_accept_code() != 0) {
int stats_saved = multi_fs_std_tracker_store_stats();
if (stats_saved) {
Netgame.flags |= NG_FLAG_STORED_MT_STATS;
send_netgame_update_packet();
} else {
send_store_stats_packet(0);
}
if (Netgame.type_flags & NG_TYPE_SW) {
multi_sw_report(stats_saved);
}
}
}
}
void multi_reset_timestamps()
{
int i;
for ( i = 0 ; i < MAX_PLAYERS; i++ ){
Multi_client_update_times[i] = UI_TIMESTAMP::invalid();
}
Netgame_send_time = -1;
Gameinfo_send_time = -1;
Next_ping_time = -1;
State_send_time = -1;
Next_bytes_time = -1;
chatbox_reset_timestamps();
// do for all players so that ingame joiners work properly.
for (i = 0; i < MAX_PLAYERS; i++ ) {
Players[i].update_dumbfire_time = timestamp(0);
Players[i].update_lock_time = timestamp(0);
Net_players[i].s_info.voice_token_timestamp = UI_TIMESTAMP::invalid();
Net_players[i].s_info.player_collision_timestamp = TIMESTAMP::immediate();
}
// reset standalone gui timestamps (these are not game critical, so there is not much danger)
std_reset_timestamps();
}
// netgame debug flags for debug console stuff
DCF(netd, "change netgame debug flags (Mulitplayer)")
{
int value;
dc_stuff_int(&value);
// if we're the server, change flags
if ((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (value <= 7)) {
Netgame.debug_flags ^= (1 << value);
}
// display network flags
dc_printf("BITS\n");
}
// display any multiplayer/networking information here
void multi_display_netinfo()
{
int sx = gr_screen.center_offset_x + gr_screen.center_w - 200;
int sy = gr_screen.center_offset_y + 20;
int dy = gr_get_font_height() + 1;
int idx;
// not multiplayer
if(!(Game_mode & GM_MULTIPLAYER)){
return;
}
// HUD is turned off
if (!HUD_draw) {
return;
}
// message window is open
if (Player->flags & PLAYER_FLAGS_MSG_MODE) {
return;
}
gr_set_color_fast(&Color_normal);
// server or client
if(MULTIPLAYER_MASTER){
gr_string(sx, sy, "SERVER", GR_RESIZE_NONE); sy += dy;
for(idx=0; idx<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx]) && (Net_players[idx].m_player != NULL)){
if(Net_players[idx].sv_last_pl < 0){
gr_printf_no_resize(sx, sy, "%s: %d, %d pl", Net_players[idx].m_player->callsign, Net_players[idx].sv_bytes_sent, 0);
sy += dy;
} else {
gr_printf_no_resize(sx, sy, "%s: %d, %d pl", Net_players[idx].m_player->callsign, Net_players[idx].sv_bytes_sent, Net_players[idx].sv_last_pl);
sy += dy;
}
}
}
} else {
gr_string(sx, sy, "CLIENT", GR_RESIZE_NONE); sy += dy;
// display PL
if(Net_player != NULL){
if(Net_player->cl_last_pl < 0){
gr_printf_no_resize(sx, sy, "PL: %d %d pl\n", Net_player->cl_bytes_recvd, 0);
sy += dy;
} else {
gr_printf_no_resize(sx, sy, "PL: %d %d pl\n", Net_player->cl_bytes_recvd, Net_player->cl_last_pl);
sy += dy;
}
}
}
}
|