Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
  3.  * It is copyright by its individual contributors, as recorded in the
  4.  * project's Git history.  See COPYING.txt at the top level for license
  5.  * terms and a link to the Git history.
  6.  */
  7. /*
  8.  *
  9.  * Routines for managing UDP-protocol network play.
  10.  *
  11.  */
  12.  
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <stdlib.h>
  16. #include <random>
  17.  
  18. #include "pstypes.h"
  19. #include "window.h"
  20. #include "strutil.h"
  21. #include "args.h"
  22. #include "timer.h"
  23. #include "newmenu.h"
  24. #include "key.h"
  25. #include "gauges.h"
  26. #include "object.h"
  27. #include "dxxerror.h"
  28. #include "laser.h"
  29. #include "gamesave.h"
  30. #include "gamemine.h"
  31. #include "player.h"
  32. #include "gameseq.h"
  33. #include "fireball.h"
  34. #include "net_udp.h"
  35. #include "game.h"
  36. #include "multi.h"
  37. #include "endlevel.h"
  38. #include "palette.h"
  39. #include "cntrlcen.h"
  40. #include "powerup.h"
  41. #include "menu.h"
  42. #include "gameseg.h"
  43. #include "sounds.h"
  44. #include "text.h"
  45. #include "kmatrix.h"
  46. #include "newdemo.h"
  47. #include "multibot.h"
  48. #include "state.h"
  49. #include "wall.h"
  50. #include "bm.h"
  51. #include "effects.h"
  52. #include "physics.h"
  53. #include "hudmsg.h"
  54. #include "switch.h"
  55. #include "textures.h"
  56. #include "automap.h"
  57. #include "event.h"
  58. #include "playsave.h"
  59. #include "gamefont.h"
  60. #include "rbaudio.h"
  61. #include "config.h"
  62. #include "vers_id.h"
  63. #include "u_mem.h"
  64.  
  65. #include "dxxsconf.h"
  66. #include "compiler-cf_assert.h"
  67. #include "compiler-range_for.h"
  68. #include "d_range.h"
  69. #include "partial_range.h"
  70. #include <array>
  71. #include <utility>
  72.  
  73. #if defined(DXX_BUILD_DESCENT_I)
  74. #define UDP_REQ_ID "D1XR" // ID string for a request packet
  75. #elif defined(DXX_BUILD_DESCENT_II)
  76. #define UDP_REQ_ID "D2XR" // ID string for a request packet
  77. #endif
  78.  
  79. namespace {
  80.  
  81. // player position packet structure
  82. struct UDP_frame_info : prohibit_void_ptr<UDP_frame_info>
  83. {
  84.         ubyte                           type;
  85.         ubyte                           Player_num;
  86.         ubyte                           connected;
  87.         quaternionpos                   qpp;
  88. };
  89.  
  90. enum class join_netgame_status_code : unsigned
  91. {
  92.         game_in_disallowed_state,
  93.         game_has_capacity,
  94.         game_is_full,
  95.         game_refuses_players,
  96. };
  97.  
  98. }
  99.  
  100. // Prototypes
  101. static void net_udp_init();
  102. static void net_udp_close();
  103. static void net_udp_listen();
  104. namespace dsx {
  105. static int net_udp_show_game_info();
  106. static int net_udp_do_join_game();
  107. }
  108. static void net_udp_flush();
  109. namespace dsx {
  110. static void net_udp_update_netgame(void);
  111. static void net_udp_send_objects(void);
  112. static void net_udp_send_rejoin_sync(unsigned player_num);
  113. static void net_udp_do_refuse_stuff (UDP_sequence_packet *their);
  114. static void net_udp_read_sync_packet(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr);
  115. }
  116. static void net_udp_ping_frame(fix64 time);
  117. static void net_udp_process_ping(const uint8_t *data, const _sockaddr &sender_addr);
  118. static void net_udp_process_pong(const uint8_t *data, const _sockaddr &sender_addr);
  119. static void net_udp_read_endlevel_packet(const uint8_t *data, const _sockaddr &sender_addr);
  120. static void net_udp_send_mdata(int needack, fix64 time);
  121. static void net_udp_process_mdata (uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr, int needack);
  122. static void net_udp_send_pdata();
  123. static void net_udp_process_pdata (const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr);
  124. static void net_udp_read_pdata_packet(UDP_frame_info *pd);
  125. static void net_udp_timeout_check(fix64 time);
  126. static int net_udp_get_new_player_num ();
  127. static void net_udp_noloss_got_ack(const uint8_t *data, uint_fast32_t data_len);
  128. static void net_udp_noloss_init_mdata_queue(void);
  129. static void net_udp_noloss_clear_mdata_trace(ubyte player_num);
  130. static void net_udp_noloss_process_queue(fix64 time);
  131. namespace dsx {
  132. static void net_udp_send_extras ();
  133. }
  134. static void net_udp_broadcast_game_info(ubyte info_upid);
  135. namespace dsx {
  136. static void net_udp_process_game_info(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &game_addr, int lite_info, uint16_t TrackerGameID = 0);
  137. }
  138. static int net_udp_start_game(void);
  139.  
  140. // Variables
  141. static int UDP_num_sendto, UDP_len_sendto, UDP_num_recvfrom, UDP_len_recvfrom;
  142. static UDP_mdata_info           UDP_MData;
  143. static UDP_sequence_packet UDP_Seq;
  144. static unsigned UDP_mdata_queue_highest;
  145. static std::array<UDP_mdata_store, UDP_MDATA_STOR_QUEUE_SIZE> UDP_mdata_queue;
  146. static std::array<UDP_mdata_check, MAX_PLAYERS> UDP_mdata_trace;
  147. static UDP_sequence_packet UDP_sync_player; // For rejoin object syncing
  148. static std::array<UDP_netgame_info_lite, UDP_MAX_NETGAMES> Active_udp_games;
  149. static unsigned num_active_udp_games;
  150. static int num_active_udp_changed;
  151. static uint16_t UDP_MyPort;
  152. static sockaddr_in GBcast; // global Broadcast address clients and hosts will use for lite_info exchange over LAN
  153. #define UDP_BCAST_ADDR "255.255.255.255"
  154. #if DXX_USE_IPv6
  155. #define UDP_MCASTv6_ADDR "ff02::1"
  156. static sockaddr_in6 GMcast_v6; // same for IPv6-only
  157. #define dispatch_sockaddr_from  from.sin6
  158. #else
  159. #define dispatch_sockaddr_from  from.sin
  160. #endif
  161. #if DXX_USE_TRACKER
  162. static _sockaddr TrackerSocket;
  163. enum class TrackerAckState : uint8_t
  164. {
  165.         TACK_NOCONNECTION,   // No connection with tracker (yet);
  166.         TACK_INTERNAL   = 1, // Got ACK on TrackerSocket
  167.         TACK_EXTERNAL   = 2, // Got ACK on our game sopcket
  168.         TACK_SEQCOMPL   = 3, // We had enough time to get all acks. If we missed something now, tell the user
  169. };
  170. static TrackerAckState TrackerAckStatus;
  171. static fix64 TrackerAckTime;
  172. static int udp_tracker_init();
  173. static int udp_tracker_unregister();
  174. namespace dsx {
  175. static int udp_tracker_register();
  176. static int udp_tracker_reqgames();
  177. }
  178. static int udp_tracker_process_game( ubyte *data, int data_len, const _sockaddr &sender_addr );
  179. static void udp_tracker_process_ack( ubyte *data, int data_len, const _sockaddr &sender_addr );
  180. static void udp_tracker_verify_ack_timeout();
  181. static void udp_tracker_request_holepunch( uint16_t TrackerGameID );
  182. static void udp_tracker_process_holepunch(uint8_t *data, unsigned data_len, const _sockaddr &sender_addr );
  183. #endif
  184.  
  185. static fix64 StartAbortMenuTime;
  186.  
  187. #ifndef _WIN32
  188. constexpr std::integral_constant<int, -1> INVALID_SOCKET{};
  189. #endif
  190.  
  191. namespace dcx {
  192.  
  193. namespace {
  194.  
  195. constexpr std::integral_constant<uint32_t, 0xfffffffe> network_checksum_marker_object{};
  196.  
  197. class RAIIsocket
  198. {
  199. #ifndef _WIN32
  200.         typedef int SOCKET;
  201.         int closesocket(SOCKET fd)
  202.         {
  203.                 return close(fd);
  204.         }
  205. #endif
  206.         SOCKET s = INVALID_SOCKET;
  207. public:
  208.         constexpr RAIIsocket() = default;
  209.         RAIIsocket(int domain, int type, int protocol) : s(socket(domain, type, protocol))
  210.         {
  211.         }
  212.         RAIIsocket(const RAIIsocket &) = delete;
  213.         RAIIsocket &operator=(const RAIIsocket &) = delete;
  214.         RAIIsocket &operator=(RAIIsocket &&) = delete;
  215.         ~RAIIsocket()
  216.         {
  217.                 reset();
  218.         }
  219.         /* This should be a move-assignment operator=, but early versions of
  220.          * gcc-4.9 mishandle synthesizing std::array<T, N>::operator=(array &&)
  221.          * when the contained type is movable but not copyable.  Debian
  222.          * Jessie's newest gcc is still affected (18 months after the fix
  223.          * was published upstream), so use awkward syntax here to avoid the
  224.          * problem.
  225.          *
  226.          * https://github.com/dxx-rebirth/dxx-rebirth/issues/289
  227.          * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66501
  228.          */
  229.         void move(RAIIsocket &&r)
  230.         {
  231.                 std::swap(s, r.s);
  232.         }
  233.         void reset()
  234.         {
  235.                 if (s != INVALID_SOCKET)
  236.                         closesocket(std::exchange(s, INVALID_SOCKET));
  237.         }
  238.         explicit operator bool() const { return s != INVALID_SOCKET; }
  239.         explicit operator bool() { return static_cast<bool>(*const_cast<const RAIIsocket *>(this)); }
  240.         operator SOCKET() { return s; }
  241.         template <typename T> bool operator<(T) const = delete;
  242.         template <typename T> bool operator<=(T) const = delete;
  243.         template <typename T> bool operator>(T) const = delete;
  244.         template <typename T> bool operator>=(T) const = delete;
  245.         template <typename T> bool operator==(T) const = delete;
  246.         template <typename T> bool operator!=(T) const = delete;
  247. };
  248.  
  249. #ifdef DXX_HAVE_GETADDRINFO
  250. class RAIIaddrinfo
  251. {
  252.         struct deleter
  253.         {
  254.                 void operator()(addrinfo *p) const
  255.                 {
  256.                         freeaddrinfo(p);
  257.                 }
  258.         };
  259.         std::unique_ptr<addrinfo, deleter> result;
  260. public:
  261.         int getaddrinfo(const char *node, const char *service, const addrinfo *hints)
  262.         {
  263.                 addrinfo *p = nullptr;
  264.                 int r = ::getaddrinfo(node, service, hints, &p);
  265.                 result.reset(p);
  266.                 return r;
  267.         }
  268.         addrinfo *get() { return result.get(); }
  269.         addrinfo *operator->() { return result.operator->(); }
  270. };
  271. #endif
  272.  
  273. class start_poll_menu_items
  274. {
  275.         /* The host must play */
  276.         unsigned playercount = 1;
  277. public:
  278.         std::array<newmenu_item, MAX_PLAYERS + 4> m;
  279.         unsigned get_player_count() const
  280.         {
  281.                 return playercount;
  282.         }
  283.         void set_player_count(const unsigned c)
  284.         {
  285.                 playercount = c;
  286.         }
  287. };
  288.  
  289. static const char *dxx_ntop(const _sockaddr &sa, std::array<char, _sockaddr::presentation_buffer_size> &dbuf)
  290. {
  291. #ifdef WIN32
  292. #ifdef DXX_HAVE_INET_NTOP
  293.         /*
  294.          * Windows and inet_ntop: copy the in_addr/in6_addr to local
  295.          * variables because the Microsoft prototype lacks a const
  296.          * qualifier.
  297.          */
  298.         union {
  299.                 in_addr ia;
  300. #if DXX_USE_IPv6
  301.                 in6_addr ia6;
  302. #endif
  303.         };
  304.         const auto addr =
  305. #if DXX_USE_IPv6
  306.                 (sa.sa.sa_family == AF_INET6)
  307.                 ? &(ia6 = sa.sin6.sin6_addr)
  308.                 :
  309. #endif
  310.                 static_cast<void *>(&(ia = sa.sin.sin_addr));
  311. #else
  312.         /*
  313.          * Windows and not inet_ntop: only inet_ntoa available.
  314.          *
  315.          * SConf check_inet_ntop_present enforces that Windows without
  316.          * inet_ntop cannot enable IPv6, so the IPv4 branch must be correct
  317.          * here.
  318.          *
  319.          * The reverse is not true.  Windows with inet_ntop might not enable
  320.          * IPv6.
  321.          */
  322. #if DXX_USE_IPv6
  323. #error "IPv6 requires inet_ntop; SConf should prevent this path"
  324. #endif
  325.         dbuf.back() = 0;
  326.         /*
  327.          * Copy the formatted string to the local buffer `dbuf` to guard
  328.          * against concurrent uses of `dxx_ntop`.
  329.          */
  330.         return reinterpret_cast<const char *>(memcpy(dbuf.data(), inet_ntoa(sa.sin.sin_addr), dbuf.size() - 1));
  331. #endif
  332. #else
  333.         /*
  334.          * Not Windows; assume inet_ntop present.  Non-Windows platforms
  335.          * declare inet_ntop with a const qualifier, so take a pointer to
  336.          * the underlying data.
  337.          */
  338.         const auto addr =
  339. #if DXX_USE_IPv6
  340.                 (sa.sa.sa_family == AF_INET6)
  341.                 ? &sa.sin6.sin6_addr
  342.                 :
  343. #endif
  344.                 static_cast<const void *>(&sa.sin.sin_addr);
  345. #endif
  346. #if !defined(WIN32) || defined(DXX_HAVE_INET_NTOP)
  347.         if (const auto r = inet_ntop(sa.sa.sa_family, addr, dbuf.data(), dbuf.size()))
  348.                 return r;
  349.         return "address";
  350. #endif
  351. }
  352.  
  353. uint8_t get_effective_netgame_status(const d_level_unique_control_center_state &LevelUniqueControlCenterState)
  354. {
  355.         if (Network_status == NETSTAT_ENDLEVEL)
  356.                 return NETSTAT_ENDLEVEL;
  357.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  358.                 return NETSTAT_ENDLEVEL;
  359.         if (Netgame.PlayTimeAllowed.count())
  360.         {
  361.                 const auto TicksPlayTimeRemaining = Netgame.PlayTimeAllowed - ThisLevelTime;
  362.                 if (TicksPlayTimeRemaining.count() < i2f(30))
  363.                         return NETSTAT_ENDLEVEL;
  364.         }
  365.         return Netgame.game_status;
  366. }
  367.  
  368. }
  369.  
  370. }
  371.  
  372. static std::array<RAIIsocket, 2> UDP_Socket;
  373.  
  374. static void clear_UDP_Socket()
  375. {
  376.         /* This would be simply `UDP_Socket = {}`, but the contained type
  377.          * has a deleted move-assignment operator= to compensate for a
  378.          * gcc-4.9 bug.  See the comment in RAIIsocket for details.
  379.          */
  380.         range_for (auto &i, UDP_Socket)
  381.                 i.reset();
  382. }
  383.  
  384. static bool operator==(const _sockaddr &l, const _sockaddr &r)
  385. {
  386.         return !memcmp(&l, &r, sizeof(l));
  387. }
  388.  
  389. static bool operator!=(const _sockaddr &l, const _sockaddr &r)
  390. {
  391.         return !(l == r);
  392. }
  393.  
  394. template <std::size_t N>
  395. static void copy_from_ntstring(uint8_t *const buf, uint_fast32_t &len, const ntstring<N> &in)
  396. {
  397.         len += in.copy_out(0, reinterpret_cast<char *>(&buf[len]), N);
  398. }
  399.  
  400. template <std::size_t N>
  401. static void copy_to_ntstring(const uint8_t *const buf, uint_fast32_t &len, ntstring<N> &out)
  402. {
  403.         uint_fast32_t c = out.copy_if(reinterpret_cast<const char *>(&buf[len]), N);
  404.         if (c < N)
  405.                 ++ c;
  406.         len += c;
  407. }
  408.  
  409. static void net_udp_prepare_request_game_info(std::array<uint8_t, UPID_GAME_INFO_REQ_SIZE> &buf, int lite)
  410. {
  411.         buf[0] = lite ? UPID_GAME_INFO_LITE_REQ : UPID_GAME_INFO_REQ;
  412.         memcpy(&buf[1], UDP_REQ_ID, 4);
  413.         PUT_INTEL_SHORT(&buf[5], DXX_VERSION_MAJORi);
  414.         PUT_INTEL_SHORT(&buf[7], DXX_VERSION_MINORi);
  415.         PUT_INTEL_SHORT(&buf[9], DXX_VERSION_MICROi);
  416.         PUT_INTEL_SHORT(&buf[11], MULTI_PROTO_VERSION);
  417. }
  418.  
  419. static void reset_UDP_MyPort()
  420. {
  421.         UDP_MyPort = CGameArg.MplUdpMyPort >= 1024 ? CGameArg.MplUdpMyPort : UDP_PORT_DEFAULT;
  422. }
  423.  
  424. static bool convert_text_portstring(const std::array<char, 6> &portstring, uint16_t &outport, bool allow_privileged, bool silent)
  425. {
  426.         char *porterror;
  427.         unsigned long myport = strtoul(&portstring[0], &porterror, 10);
  428.         if (*porterror || static_cast<uint16_t>(myport) != myport || (!allow_privileged && myport < 1024))
  429.         {
  430.                 if (!silent)
  431.                         nm_messagebox(TXT_ERROR, 1, TXT_OK, "Illegal port \"%s\"", portstring.data());
  432.                 return false;
  433.         }
  434.         else
  435.                 outport = myport;
  436.         return true;
  437. }
  438.  
  439. namespace {
  440.  
  441. #if DXX_USE_IPv6
  442. /* Returns true if kernel allows specifying sizeof(sockaddr_in6) for
  443.  * size of a sockaddr_in.  Saves a compare+jump in application code to
  444.  * pass sizeof(sockaddr_in6) and let kernel sort it out.
  445.  */
  446. static constexpr bool kernel_accepts_extra_sockaddr_bytes()
  447. {
  448. #if defined(__linux__)
  449.         /* Known to work */
  450.         return true;
  451. #else
  452.         /* Default case: not known */
  453.         return false;
  454. #endif
  455. }
  456. #endif
  457.  
  458.         /* Forward to static function to eliminate this pointer */
  459. template <typename F>
  460. class passthrough_static_apply : F
  461. {
  462. public:
  463. #define apply_passthrough()     this->F::apply(std::forward<Args>(args)...)
  464.         template <typename... Args>
  465.         __attribute_always_inline()
  466.                 auto operator()(Args &&... args) const
  467.                 {
  468.                         return apply_passthrough();
  469.                 }
  470. #undef apply_passthrough
  471. };
  472.  
  473. template <typename F>
  474. class sockaddr_dispatch_t : F
  475. {
  476. public:
  477. #define apply_sockaddr()        this->F::operator()(reinterpret_cast<sockaddr &>(from), fromlen, std::forward<Args>(args)...)
  478.         template <typename... Args>
  479.                 auto operator()(sockaddr_in &from, socklen_t &fromlen, Args &&... args) const
  480.                 {
  481.                         fromlen = sizeof(from);
  482.                         return apply_sockaddr();
  483.                 }
  484. #if DXX_USE_IPv6
  485.         template <typename... Args>
  486.                 auto operator()(sockaddr_in6 &from, socklen_t &fromlen, Args &&... args) const
  487.                 {
  488.                         fromlen = sizeof(from);
  489.                         return apply_sockaddr();
  490.                 }
  491. #endif
  492.         template <typename... Args>
  493.                 __attribute_always_inline()
  494.                 auto operator()(_sockaddr &from, socklen_t &fromlen, Args &&... args) const
  495.                 {
  496.                         return this->sockaddr_dispatch_t<F>::operator()<Args...>(dispatch_sockaddr_from, fromlen, std::forward<Args>(args)...);
  497.                 }
  498. #undef apply_sockaddr
  499. };
  500.  
  501. template <typename F>
  502. class csockaddr_dispatch_t : F
  503. {
  504. public:
  505. #define apply_sockaddr()        this->F::operator()(to, tolen, std::forward<Args>(args)...)
  506.         template <typename... Args>
  507.                 auto operator()(const sockaddr &to, socklen_t tolen, Args &&... args) const
  508.                 {
  509.                         return apply_sockaddr();
  510.                 }
  511. #undef apply_sockaddr
  512. #define apply_sockaddr(to)      this->F::operator()(reinterpret_cast<const sockaddr &>(to), sizeof(to), std::forward<Args>(args)...)
  513.         template <typename... Args>
  514.                 auto operator()(const sockaddr_in &to, Args &&... args) const
  515.                 {
  516.                         return apply_sockaddr(to);
  517.                 }
  518. #if DXX_USE_IPv6
  519.         template <typename... Args>
  520.                 auto operator()(const sockaddr_in6 &to, Args &&... args) const
  521.                 {
  522.                         return apply_sockaddr(to);
  523.                 }
  524. #endif
  525.         template <typename... Args>
  526.                 auto operator()(const _sockaddr &to, Args &&... args) const
  527.                 {
  528. #if DXX_USE_IPv6
  529.                         if (kernel_accepts_extra_sockaddr_bytes() || to.sin6.sin6_family == AF_INET6)
  530.                                 return apply_sockaddr(to.sin6);
  531. #endif
  532.                         return apply_sockaddr(to.sin);
  533.                 }
  534. #undef apply_sockaddr
  535. };
  536.  
  537. template <typename F>
  538. class socket_array_dispatch_t : F
  539. {
  540. public:
  541. #define apply_array(B,L)        this->F::operator()(to, tolen, sock, B, L, std::forward<Args>(args)...)
  542.         template <typename T, typename... Args>
  543.                 auto operator()(const sockaddr &to, socklen_t tolen, int sock, T *buf, uint_fast32_t buflen, Args &&... args) const
  544.                 {
  545.                         return apply_array(buf, buflen);
  546.                 }
  547.         template <std::size_t N, typename... Args>
  548.                 auto operator()(const sockaddr &to, socklen_t tolen, int sock, std::array<uint8_t, N> &buf, Args &&... args) const
  549.                 {
  550.                         return apply_array(buf.data(), buf.size());
  551.                 }
  552. #undef apply_array
  553. };
  554.  
  555. /* General UDP functions - START */
  556. class dxx_sendto_t
  557. {
  558. public:
  559.         __attribute_always_inline()
  560.         ssize_t operator()(const sockaddr &to, socklen_t tolen, int sockfd, const void *msg, size_t len, int flags) const
  561.         {
  562.                 /* Fix argument order */
  563.                 return apply(sockfd, msg, len, flags, to, tolen);
  564.         }
  565.         static ssize_t apply(int sockfd, const void *msg, size_t len, int flags, const sockaddr &to, socklen_t tolen);
  566. };
  567.  
  568. class dxx_recvfrom_t
  569. {
  570. public:
  571.         __attribute_always_inline()
  572.         ssize_t operator()(sockaddr &from, socklen_t &fromlen, int sockfd, void *msg, size_t len, int flags) const
  573.         {
  574.                 /* Fix argument order */
  575.                 return apply(sockfd, msg, len, flags, from, fromlen);
  576.         }
  577.         static ssize_t apply(int sockfd, void *msg, size_t len, int flags, sockaddr &from, socklen_t &fromlen);
  578. };
  579.  
  580. ssize_t dxx_sendto_t::apply(int sockfd, const void *msg, size_t len, int flags, const sockaddr &to, socklen_t tolen)
  581. {
  582.         ssize_t rv = sendto(sockfd, reinterpret_cast<const char *>(msg), len, flags, &to, tolen);
  583.  
  584.         UDP_num_sendto++;
  585.         if (rv > 0)
  586.                 UDP_len_sendto += rv;
  587.  
  588.         return rv;
  589. }
  590.  
  591. ssize_t dxx_recvfrom_t::apply(int sockfd, void *buf, size_t len, int flags, sockaddr &from, socklen_t &fromlen)
  592. {
  593.         ssize_t rv = recvfrom(sockfd, reinterpret_cast<char *>(buf), len, flags, &from, &fromlen);
  594.  
  595.         UDP_num_recvfrom++;
  596.         UDP_len_recvfrom += rv;
  597.  
  598.         return rv;
  599. }
  600.  
  601. constexpr csockaddr_dispatch_t<socket_array_dispatch_t<dxx_sendto_t>> dxx_sendto{};
  602. constexpr sockaddr_dispatch_t<dxx_recvfrom_t> dxx_recvfrom{};
  603.  
  604. }
  605.  
  606. static void udp_traffic_stat()
  607. {
  608.         static fix64 last_traf_time = 0;
  609.  
  610.         if (timer_query() >= last_traf_time + F1_0)
  611.         {
  612.                 last_traf_time = timer_query();
  613.                 con_printf(CON_DEBUG, "P#%u TRAFFIC - OUT: %fKB/s %iPPS IN: %fKB/s %iPPS",Player_num, static_cast<float>(UDP_len_sendto)/1024, UDP_num_sendto, static_cast<float>(UDP_len_recvfrom)/1024, UDP_num_recvfrom);
  614.                 UDP_num_sendto = UDP_len_sendto = UDP_num_recvfrom = UDP_len_recvfrom = 0;
  615.         }
  616. }
  617.  
  618. namespace {
  619.  
  620. class udp_dns_filladdr_t
  621. {
  622. public:
  623.         static int apply(sockaddr &addr, socklen_t addrlen, int ai_family, const char *host, uint16_t port, bool numeric_only, bool silent);
  624. };
  625.  
  626. // Resolve address
  627. int udp_dns_filladdr_t::apply(sockaddr &addr, socklen_t addrlen, int ai_family, const char *host, uint16_t port, bool numeric_only, bool silent)
  628. {
  629. #ifdef DXX_HAVE_GETADDRINFO
  630.         // Variables
  631.         addrinfo hints{};
  632.         char sPort[6];
  633.  
  634.         // Build the port
  635.         snprintf(sPort, 6, "%hu", port);
  636.        
  637.         // Uncomment the following if we want ONLY what we compile for
  638.         hints.ai_family = ai_family;
  639.         // We are always UDP
  640.         hints.ai_socktype = SOCK_DGRAM;
  641. #ifdef AI_NUMERICSERV
  642.         hints.ai_flags |= AI_NUMERICSERV;
  643. #endif
  644. #if DXX_USE_IPv6
  645.         hints.ai_flags |= AI_V4MAPPED | AI_ALL;
  646. #endif
  647.         // Numeric address only?
  648.         if (numeric_only)
  649.                 hints.ai_flags |= AI_NUMERICHOST;
  650.        
  651.         // Resolve the domain name
  652.         RAIIaddrinfo result;
  653.         if (result.getaddrinfo(host, sPort, &hints) != 0)
  654.         {
  655.                 con_printf( CON_URGENT, "udp_dns_filladdr (getaddrinfo) failed for host %s", host );
  656.                 if (!silent)
  657.                         nm_messagebox( TXT_ERROR, 1, TXT_OK, "Could not resolve address\n%s", host );
  658.                 addr.sa_family = AF_UNSPEC;
  659.                 return -1;
  660.         }
  661.        
  662.         if (result->ai_addrlen > addrlen)
  663.         {
  664.                 con_printf(CON_URGENT, "Address too big for host %s", host);
  665.                 if (!silent)
  666.                         nm_messagebox(TXT_ERROR, 1, TXT_OK, "Address too big for host\n%s", host);
  667.                 addr.sa_family = AF_UNSPEC;
  668.                 return -1;
  669.         }
  670.         // Now copy it over
  671.         memcpy(&addr, result->ai_addr, addrlen = result->ai_addrlen);
  672.        
  673.         /* WARNING:  NERDY CONTENT
  674.          *
  675.          * The above works, since result->ai_addr contains the socket family,
  676.          * which is copied into our struct.  Our struct will be read for sendto
  677.          * and recvfrom, using the sockaddr.sa_family member.  If we are IPv6,
  678.          * this already has enough space to read into.  If we are IPv4, we will
  679.          * not be able to get any IPv6 connections anyway, so we will be safe
  680.          * from an overflow.  The more you know, 'cause knowledge is power!
  681.          *
  682.          * -- Matt
  683.          */
  684.        
  685.         // Free memory
  686. #else
  687.         (void)numeric_only;
  688.         sockaddr_in &sai = reinterpret_cast<sockaddr_in &>(addr);
  689.         if (addrlen < sizeof(sai))
  690.                 return -1;
  691.         const auto he = gethostbyname(host);
  692.         if (!he)
  693.         {
  694.                 con_printf(CON_URGENT, "udp_dns_filladdr (gethostbyname) failed for host %s", host);
  695.                 if (!silent)
  696.                         nm_messagebox(TXT_ERROR, 1, TXT_OK, "Could not resolve IPv4 address\n%s", host);
  697.                 addr.sa_family = AF_UNSPEC;
  698.                 return -1;
  699.         }
  700.         sai = {};
  701.         sai.sin_family = ai_family;
  702.         sai.sin_port = htons(port);
  703.         sai.sin_addr = *reinterpret_cast<const in_addr *>(he->h_addr);
  704. #endif
  705.         return 0;
  706. }
  707.  
  708. template <typename F>
  709. class sockaddr_resolve_family_dispatch_t : sockaddr_dispatch_t<F>
  710. {
  711. public:
  712. #define apply_sockaddr(fromlen,family)  this->sockaddr_dispatch_t<F>::operator()(from, fromlen, family, std::forward<Args>(args)...)
  713.         template <typename... Args>
  714.                 auto operator()(sockaddr_in &from, Args &&... args) const
  715.                 {
  716.                         socklen_t fromlen;
  717.                         return apply_sockaddr(fromlen, AF_INET);
  718.                 }
  719. #if DXX_USE_IPv6
  720.         template <typename... Args>
  721.                 auto operator()(sockaddr_in6 &from, Args &&... args) const
  722.                 {
  723.                         socklen_t fromlen;
  724.                         return apply_sockaddr(fromlen, AF_INET6);
  725.                 }
  726. #endif
  727.         template <typename... Args>
  728.                 auto operator()(_sockaddr &from, Args &&... args) const
  729.                 {
  730.                         return this->operator()(dispatch_sockaddr_from, std::forward<Args>(args)...);
  731.                 }
  732. #undef apply_sockaddr
  733. };
  734.  
  735. constexpr sockaddr_resolve_family_dispatch_t<passthrough_static_apply<udp_dns_filladdr_t>> udp_dns_filladdr{};
  736.  
  737. }
  738.  
  739. static void udp_init_broadcast_addresses()
  740. {
  741.         udp_dns_filladdr(GBcast, UDP_BCAST_ADDR, UDP_PORT_DEFAULT, true, true);
  742. #if DXX_USE_IPv6
  743.         udp_dns_filladdr(GMcast_v6, UDP_MCASTv6_ADDR, UDP_PORT_DEFAULT, true, true);
  744. #endif
  745. }
  746.  
  747. // Open socket
  748. static int udp_open_socket(RAIIsocket &sock, int port)
  749. {
  750.         int bcast = 1;
  751.  
  752.         // close stale socket
  753.         struct _sockaddr sAddr;   // my address information
  754.  
  755.         sock.move(RAIIsocket(sAddr.address_family(), SOCK_DGRAM, 0));
  756.         if (!sock)
  757.         {
  758.                 con_printf(CON_URGENT,"udp_open_socket: socket creation failed (port %i)", port);
  759.                 nm_messagebox(TXT_ERROR,1,TXT_OK,"Port: %i\nCould not create socket.", port);
  760.                 return -1;
  761.         }
  762.         sAddr = {};
  763.         sAddr.sa.sa_family = sAddr.address_family();
  764. #if DXX_USE_IPv6
  765.         sAddr.sin6.sin6_port = htons (port); // short, network byte order
  766.         sAddr.sin6.sin6_addr = IN6ADDR_ANY_INIT; // automatically fill with my IP
  767. #else
  768.         sAddr.sin.sin_port = htons (port); // short, network byte order
  769.         sAddr.sin.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
  770. #endif
  771.        
  772.         if (bind(sock, &sAddr.sa, sizeof(sAddr)) < 0)
  773.         {      
  774.                 con_printf(CON_URGENT,"udp_open_socket: bind name to socket failed (port %i)", port);
  775.                 nm_messagebox(TXT_ERROR,1,TXT_OK,"Port: %i\nCould not bind name to socket.", port);
  776.                 sock.reset();
  777.                 return -1;
  778.         }
  779. #ifdef _WIN32
  780.         setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&bcast), sizeof(bcast));
  781. #else
  782.         setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast));
  783. #endif
  784.         return 0;
  785. }
  786.  
  787. #ifndef MSG_DONTWAIT
  788. static int udp_general_packet_ready(RAIIsocket &sock)
  789. {
  790.         fd_set set;
  791.         struct timeval tv;
  792.  
  793.         FD_ZERO(&set);
  794.         FD_SET(sock, &set);
  795.         tv.tv_sec = tv.tv_usec = 0;
  796.         if (select(sock + 1, &set, NULL, NULL, &tv) > 0)
  797.                 return 1;
  798.         else
  799.                 return 0;
  800. }
  801. #endif
  802.  
  803. // Gets some text. Returns 0 if nothing on there.
  804. static int udp_receive_packet(RAIIsocket &sock, ubyte *text, int len, struct _sockaddr *sender_addr)
  805. {
  806.         if (!sock)
  807.                 return -1;
  808. #ifndef MSG_DONTWAIT
  809.         if (!udp_general_packet_ready(sock))
  810.                 return 0;
  811. #endif
  812.         ssize_t msglen;
  813.                 socklen_t clen;
  814.                 int flags = 0;
  815. #ifdef MSG_DONTWAIT
  816.                 flags |= MSG_DONTWAIT;
  817. #endif
  818.                 msglen = dxx_recvfrom(*sender_addr, clen, sock, text, len, flags);
  819.  
  820.                 if (msglen < 0)
  821.                         return 0;
  822.  
  823.                 if ((msglen >= 0) && (msglen < len))
  824.                         text[msglen] = 0;
  825.         return msglen;
  826. }
  827. /* General UDP functions - END */
  828.  
  829.  
  830. namespace {
  831.  
  832. class net_udp_request_game_info_t
  833. {
  834. public:
  835.         static void apply(const sockaddr &to, socklen_t tolen, int lite);
  836. };
  837.  
  838. class net_udp_send_game_info_t
  839. {
  840. public:
  841.         static void apply(const sockaddr &target_addr, socklen_t targetlen, const _sockaddr *sender_addr, ubyte info_upid);
  842. };
  843.  
  844. void net_udp_request_game_info_t::apply(const sockaddr &game_addr, socklen_t addrlen, int lite)
  845. {
  846.         std::array<uint8_t, UPID_GAME_INFO_REQ_SIZE> buf;
  847.         net_udp_prepare_request_game_info(buf, lite);
  848.         dxx_sendto(game_addr, addrlen, UDP_Socket[0], buf, 0);
  849. }
  850.  
  851. constexpr csockaddr_dispatch_t<passthrough_static_apply<net_udp_request_game_info_t>> net_udp_request_game_info{};
  852. constexpr csockaddr_dispatch_t<passthrough_static_apply<net_udp_send_game_info_t>> net_udp_send_game_info{};
  853.  
  854. struct direct_join
  855. {
  856.         struct _sockaddr host_addr;
  857.         int connecting;
  858.         fix64 start_time, last_time;
  859. #if DXX_USE_TRACKER
  860.         uint16_t gameid;
  861. #endif
  862. };
  863.  
  864. struct manual_join_user_inputs
  865. {
  866.         std::array<char, 6> hostportbuf, myportbuf;
  867.         std::array<char, 128> addrbuf;
  868. };
  869.  
  870. struct manual_join : direct_join, manual_join_user_inputs
  871. {
  872.         static manual_join_user_inputs s_last_inputs;
  873.         std::array<newmenu_item, 7> m;
  874. };
  875.  
  876. struct list_join : direct_join
  877. {
  878.         enum {
  879.                 entries = ((UDP_NETGAMES_PPAGE + 5) * 2) + 1,
  880.         };
  881.         std::array<newmenu_item, entries> m;
  882.         std::array<std::array<char, 92>, entries> ljtext;
  883. };
  884.  
  885. manual_join_user_inputs manual_join::s_last_inputs;
  886.  
  887. }
  888.  
  889. // Connect to a game host and get full info. Eventually we join!
  890. static int net_udp_game_connect(direct_join *const dj)
  891. {
  892.         // Get full game info so we can show it.
  893.  
  894.         // Timeout after 10 seconds
  895.         if (timer_query() >= dj->start_time + (F1_0*10))
  896.         {
  897.                 dj->connecting = 0;
  898.                 std::array<char, _sockaddr::presentation_buffer_size> dbuf;
  899.                 const auto port =
  900. #if DXX_USE_IPv6
  901.                         dj->host_addr.sa.sa_family == AF_INET6
  902.                         ? dj->host_addr.sin6.sin6_port
  903.                         :
  904. #endif
  905.                         dj->host_addr.sin.sin_port;
  906.                 nm_messagebox(TXT_ERROR, 1, TXT_OK,
  907. "No response by host.\n\n\
  908. Possible reasons:\n\
  909. * No game on %s (anymore)\n\
  910. * Host port %hu is not open\n\
  911. * Game is hosted on a different port\n\
  912. * Host uses a game version\n\
  913.  I do not understand", dxx_ntop(dj->host_addr, dbuf), ntohs(port));
  914.                 return 0;
  915.         }
  916.        
  917.         if (Netgame.protocol.udp.valid == -1)
  918.         {
  919.                 nm_messagebox(TXT_ERROR,1,TXT_OK,"Version mismatch! Cannot join Game.\n\nHost game version: %i.%i.%i\nHost game protocol: %i\n(%s)\n\nYour game version: " DXX_VERSION_STR "\nYour game protocol: %i\n(%s)",Netgame.protocol.udp.program_iver[0],Netgame.protocol.udp.program_iver[1],Netgame.protocol.udp.program_iver[2],Netgame.protocol.udp.program_iver[3],(Netgame.protocol.udp.program_iver[3]==0?"RELEASE VERSION":"DEVELOPMENT BUILD, BETA, etc."), MULTI_PROTO_VERSION, (MULTI_PROTO_VERSION==0?"RELEASE VERSION":"DEVELOPMENT BUILD, BETA, etc."));
  920.                 dj->connecting = 0;
  921.                 return 0;
  922.         }
  923.        
  924.         if (timer_query() >= dj->last_time + F1_0)
  925.         {
  926.                 net_udp_request_game_info(dj->host_addr, 0);
  927. #if DXX_USE_TRACKER
  928.                 if (dj->gameid)
  929.                         if (timer_query() >= dj->start_time + (F1_0*4))
  930.                                 udp_tracker_request_holepunch(dj->gameid);
  931. #endif
  932.                 dj->last_time = timer_query();
  933.         }
  934.         timer_delay2(5);
  935.         net_udp_listen();
  936.  
  937.         if (Netgame.protocol.udp.valid != 1)
  938.                 return 0;               // still trying to connect
  939.  
  940.         if (dj->connecting == 1)
  941.         {
  942.                 if (!net_udp_show_game_info()) // show info menu and check if we join
  943.                 {
  944.                         dj->connecting = 0;
  945.                         return 0;
  946.                 }
  947.                 else
  948.                 {
  949.                         // Get full game info again as it could have changed since we entered the info menu.
  950.                         dj->connecting = 2;
  951.                         Netgame.protocol.udp.valid = 0;
  952.                         dj->start_time = timer_query();
  953.  
  954.                         return 0;
  955.                 }
  956.         }
  957.                
  958.         dj->connecting = 0;
  959.  
  960.         return net_udp_do_join_game();
  961. }
  962.  
  963. static int manual_join_game_handler(newmenu *const menu, const d_event &event, manual_join *const dj)
  964. {
  965.         newmenu_item *items = newmenu_get_items(menu);
  966.  
  967.         switch (event.type)
  968.         {
  969.                 case EVENT_KEY_COMMAND:
  970.                         if (dj->connecting && event_key_get(event) == KEY_ESC)
  971.                         {
  972.                                 dj->connecting = 0;
  973.                                 nm_set_item_text(items[6], "");
  974.                                 return 1;
  975.                         }
  976.                         break;
  977.                        
  978.                 case EVENT_IDLE:
  979.                         if (dj->connecting)
  980.                         {
  981.                                 if (net_udp_game_connect(dj))
  982.                                         return -2;      // Success!
  983.                                 else if (!dj->connecting)
  984.                                         nm_set_item_text(items[6], "");
  985.                         }
  986.                         break;
  987.  
  988.                 case EVENT_NEWMENU_SELECTED:
  989.                 {
  990.                         int sockres = -1;
  991.  
  992.                         net_udp_init(); // yes, redundant call but since the menu does not know any better it would allow any IP entry as long as Netgame-entry looks okay... my head hurts...
  993.                         if (!convert_text_portstring(dj->myportbuf, UDP_MyPort, false, false))
  994.                                 return 1;
  995.                         sockres = udp_open_socket(UDP_Socket[0], UDP_MyPort);
  996.                         if (sockres != 0)
  997.                         {
  998.                                 return 1;
  999.                         }
  1000.                         uint16_t hostport;
  1001.                         if (!convert_text_portstring(dj->hostportbuf, hostport, true, false))
  1002.                                 return 1;
  1003.                         // Resolve address
  1004.                         if (udp_dns_filladdr(dj->host_addr, &dj->addrbuf[0], hostport, false, false) < 0)
  1005.                         {
  1006.                                 return 1;
  1007.                         }
  1008.                         else
  1009.                         {
  1010.                                 dj->s_last_inputs = *dj;
  1011.                                 multi_new_game();
  1012.                                 N_players = 0;
  1013.                                 change_playernum_to(1);
  1014.                                 dj->start_time = timer_query();
  1015.                                 dj->last_time = 0;
  1016.                                
  1017.                                 Netgame.players[0].protocol.udp.addr = dj->host_addr;
  1018.                                
  1019.                                 dj->connecting = 1;
  1020.                                 nm_set_item_text(items[6], "Connecting...");
  1021.                                 return 1;
  1022.                         }
  1023.  
  1024.                         break;
  1025.                 }
  1026.                        
  1027.                 case EVENT_WINDOW_CLOSE:
  1028.                         if (!Game_wind) // they cancelled
  1029.                                 net_udp_close();
  1030.                         std::default_delete<manual_join>()(dj);
  1031.                         break;
  1032.                        
  1033.                 default:
  1034.                         break;
  1035.         }
  1036.        
  1037.         return 0;
  1038. }
  1039.  
  1040. void net_udp_manual_join_game()
  1041. {
  1042.         int nitems = 0;
  1043.  
  1044.         auto dj = std::make_unique<manual_join>();
  1045.         net_udp_init();
  1046.  
  1047.         reset_UDP_MyPort();
  1048.  
  1049.         if (dj->s_last_inputs.addrbuf[0])
  1050.                 dj->addrbuf = dj->s_last_inputs.addrbuf;
  1051.         else
  1052.                 snprintf(&dj->addrbuf[0], dj->addrbuf.size(), "%s", CGameArg.MplUdpHostAddr.c_str());
  1053.         if (dj->s_last_inputs.hostportbuf[0])
  1054.                 dj->hostportbuf = dj->s_last_inputs.hostportbuf;
  1055.         else
  1056.                 snprintf(&dj->hostportbuf[0], dj->hostportbuf.size(), "%hu", CGameArg.MplUdpHostPort ? CGameArg.MplUdpHostPort : UDP_PORT_DEFAULT);
  1057.         if (dj->s_last_inputs.myportbuf[0])
  1058.                 dj->myportbuf = dj->s_last_inputs.myportbuf;
  1059.         else
  1060.                 snprintf(&dj->myportbuf[0], dj->myportbuf.size(), "%hu", UDP_MyPort);
  1061.  
  1062.         nitems = 0;
  1063.         auto &m = dj->m;
  1064.         nm_set_item_text(m[nitems++],"GAME ADDRESS OR HOSTNAME:");
  1065.         nm_set_item_input(m[nitems++],dj->addrbuf);
  1066.         nm_set_item_text(m[nitems++],"GAME PORT:");
  1067.         nm_set_item_input(m[nitems++], dj->hostportbuf);
  1068.         nm_set_item_text(m[nitems++],"MY PORT:");
  1069.         nm_set_item_input(m[nitems++], dj->myportbuf);
  1070.         nm_set_item_text(m[nitems++],"");
  1071.  
  1072.         newmenu_do1(nullptr, "ENTER GAME ADDRESS", nitems, &m[0], manual_join_game_handler, dj.release(), 0);
  1073. }
  1074.  
  1075. static void copy_truncate_string(const grs_font &cv_font, const font_x_scaled_float strbound, std::array<char, 25> &out, const ntstring<25> &in)
  1076. {
  1077.         size_t k = 0, x = 0;
  1078.         char thold[2];
  1079.         thold[1] = 0;
  1080.         const std::size_t outsize = out.size();
  1081.         range_for (const char c, in)
  1082.         {
  1083.                 if (unlikely(c == '\t'))
  1084.                         continue;
  1085.                 if (unlikely(!c))
  1086.                         break;
  1087.                 thold[0] = c;
  1088.                 int tx;
  1089.                 gr_get_string_size(cv_font, thold, &tx, nullptr, nullptr);
  1090.                 if ((x += tx) >= strbound)
  1091.                 {
  1092.                         const std::size_t outbound = outsize - 4;
  1093.                         if (k > outbound)
  1094.                                 k = outbound;
  1095.                         out[k] = out[k + 1] = out[k + 2] = '.';
  1096.                         k += 3;
  1097.                         break;
  1098.                 }
  1099.                 out[k++] = c;
  1100.                 if (k >= outsize - 1)
  1101.                         break;
  1102.         }
  1103.         out[k] = 0;
  1104. }
  1105.  
  1106. static int net_udp_list_join_poll(newmenu *menu, const d_event &event, list_join *const dj)
  1107. {
  1108.         // Polling loop for Join Game menu
  1109.         int newpage = 0;
  1110.         static int NLPage = 0;
  1111.         newmenu_item *menus = newmenu_get_items(menu);
  1112.         switch (event.type)
  1113.         {
  1114.                 case EVENT_WINDOW_ACTIVATED:
  1115.                 {
  1116.                         Netgame.protocol.udp.valid = 0;
  1117.                         Active_udp_games = {};
  1118.                         num_active_udp_changed = 1;
  1119.                         num_active_udp_games = 0;
  1120.                         net_udp_request_game_info(GBcast, 1);
  1121. #if DXX_USE_IPv6
  1122.                         net_udp_request_game_info(GMcast_v6, 1);
  1123. #endif
  1124. #if DXX_USE_TRACKER
  1125.                         udp_tracker_reqgames();
  1126. #endif
  1127.                         if (!dj->connecting) // fallback/failsafe!
  1128.                                 nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
  1129.                         break;
  1130.                 }
  1131.                 case EVENT_IDLE:
  1132.                         if (dj->connecting)
  1133.                         {
  1134.                                 if (net_udp_game_connect(dj))
  1135.                                         return -2;      // Success!
  1136.                                 if (!dj->connecting) // connect wasn't successful - get rid of the message.
  1137.                                         nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
  1138.                         }
  1139.                         break;
  1140.                 case EVENT_KEY_COMMAND:
  1141.                 {
  1142.                         int key = event_key_get(event);
  1143.                         if (key == KEY_PAGEUP)
  1144.                         {
  1145.                                 NLPage--;
  1146.                                 newpage++;
  1147.                                 if (NLPage < 0)
  1148.                                         NLPage = UDP_NETGAMES_PAGES-1;
  1149.                                 key = 0;
  1150.                                 break;
  1151.                         }
  1152.                         if (key == KEY_PAGEDOWN)
  1153.                         {
  1154.                                 NLPage++;
  1155.                                 newpage++;
  1156.                                 if (NLPage >= UDP_NETGAMES_PAGES)
  1157.                                         NLPage = 0;
  1158.                                 key = 0;
  1159.                                 break;
  1160.                         }
  1161.                         if( key == KEY_F4 )
  1162.                         {
  1163.                                 // Empty the list
  1164.                                 Active_udp_games = {};
  1165.                                 num_active_udp_changed = 1;
  1166.                                 num_active_udp_games = 0;
  1167.                                
  1168.                                 // Request LAN games
  1169.                                 net_udp_request_game_info(GBcast, 1);
  1170. #if DXX_USE_IPv6
  1171.                                 net_udp_request_game_info(GMcast_v6, 1);
  1172. #endif
  1173. #if DXX_USE_TRACKER
  1174.                                 udp_tracker_reqgames();
  1175. #endif
  1176.                                 // All done
  1177.                                 break;
  1178.                         }
  1179. #if DXX_USE_TRACKER
  1180.                         if (key == KEY_F5)
  1181.                         {
  1182.                                 Active_udp_games = {};
  1183.                                 num_active_udp_changed = 1;
  1184.                                 num_active_udp_games = 0;
  1185.                                 net_udp_request_game_info(GBcast, 1);
  1186.  
  1187. #if DXX_USE_IPv6
  1188.                                 net_udp_request_game_info(GMcast_v6, 1);
  1189. #endif
  1190.                                 break;
  1191.                         }
  1192.  
  1193.                         if( key == KEY_F6 )
  1194.                         {
  1195.                                 // Zero the list
  1196.                                 Active_udp_games = {};
  1197.                                 num_active_udp_changed = 1;
  1198.                                 num_active_udp_games = 0;
  1199.                                
  1200.                                 // Request from the tracker
  1201.                                 udp_tracker_reqgames();
  1202.                                
  1203.                                 // Break off
  1204.                                 break;
  1205.                         }
  1206. #endif
  1207.                         if (key == KEY_ESC)
  1208.                         {
  1209.                                 if (dj->connecting)
  1210.                                 {
  1211.                                         dj->connecting = 0;
  1212.                                         nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\t");
  1213.                                         return 1;
  1214.                                 }
  1215.                                 break;
  1216.                         }
  1217.                         break;
  1218.                 }
  1219.                 case EVENT_NEWMENU_SELECTED:
  1220.                 {
  1221.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  1222.                         if (((citem+(NLPage*UDP_NETGAMES_PPAGE)) >= 4) && (((citem+(NLPage*UDP_NETGAMES_PPAGE))-3) <= num_active_udp_games))
  1223.                         {
  1224.                                 multi_new_game();
  1225.                                 N_players = 0;
  1226.                                 change_playernum_to(1);
  1227.                                 dj->start_time = timer_query();
  1228.                                 dj->last_time = 0;
  1229.                                 dj->host_addr = Active_udp_games[(citem+(NLPage*UDP_NETGAMES_PPAGE))-4].game_addr;
  1230.                                 Netgame.players[0].protocol.udp.addr = dj->host_addr;
  1231.                                 dj->connecting = 1;
  1232. #if DXX_USE_TRACKER
  1233.                                 dj->gameid = Active_udp_games[(citem+(NLPage*UDP_NETGAMES_PPAGE))-4].TrackerGameID;
  1234. #endif
  1235.                                 nm_set_item_text(menus[UDP_NETGAMES_PPAGE+4], "\tConnecting. Please wait...");
  1236.                                 return 1;
  1237.                         }
  1238.                         else
  1239.                         {
  1240.                                 nm_messagebox(TXT_SORRY, 1, TXT_OK, TXT_INVALID_CHOICE);
  1241.                                 return -1; // invalid game selected - stay in the menu
  1242.                         }
  1243.                         break;
  1244.                 }
  1245.                 case EVENT_WINDOW_CLOSE:
  1246.                 {
  1247.                         std::default_delete<list_join>()(dj);
  1248.                         if (!Game_wind)
  1249.                         {
  1250.                                 net_udp_close();
  1251.                                 Network_status = NETSTAT_MENU;  // they cancelled
  1252.                         }
  1253.                         return 0;
  1254.                 }
  1255.                 default:
  1256.                         break;
  1257.         }
  1258.  
  1259.         net_udp_listen();
  1260.  
  1261.         if (!num_active_udp_changed && !newpage)
  1262.                 return 0;
  1263.  
  1264.         num_active_udp_changed = 0;
  1265.  
  1266.         // Copy the active games data into the menu options
  1267.         for (int i = 0; i < UDP_NETGAMES_PPAGE; i++)
  1268.         {
  1269.                 const auto &augi = Active_udp_games[(i + (NLPage * UDP_NETGAMES_PPAGE))];
  1270.                 int game_status = augi.game_status;
  1271.                 int nplayers = 0;
  1272.                 char levelname[8];
  1273.  
  1274.                 if ((i+(NLPage*UDP_NETGAMES_PPAGE)) >= num_active_udp_games)
  1275.                 {
  1276.                         auto &p = dj->ljtext[i];
  1277.                         snprintf(&p[0], p.size(), "%d.                                                                      ", (i + (NLPage * UDP_NETGAMES_PPAGE)) + 1);
  1278.                         continue;
  1279.                 }
  1280.  
  1281.                 // These next two loops protect against menu skewing
  1282.                 // if missiontitle or gamename contain a tab
  1283.  
  1284.                 const auto &&fspacx = FSPACX();
  1285.                 const auto &cv_font = *grd_curcanv->cv_font;
  1286.                 std::array<char, 25> MissName, GameName;
  1287.                 const auto &&fspacx55 = fspacx(55);
  1288.                 copy_truncate_string(cv_font, fspacx55, MissName, augi.mission_title);
  1289.                 copy_truncate_string(cv_font, fspacx55, GameName, augi.game_name);
  1290.  
  1291.                 nplayers = augi.numconnected;
  1292.  
  1293.                 const int levelnum = augi.levelnum;
  1294.                 if (levelnum < 0)
  1295.                 {
  1296.                         cf_assert(-levelnum < MAX_SECRET_LEVELS_PER_MISSION);
  1297.                         snprintf(levelname, sizeof(levelname), "S%d", -levelnum);
  1298.                 }
  1299.                 else
  1300.                 {
  1301.                         cf_assert(levelnum < MAX_LEVELS_PER_MISSION);
  1302.                         snprintf(levelname, sizeof(levelname), "%d", levelnum);
  1303.                 }
  1304.  
  1305.                 const char *status;
  1306.                 if (game_status == NETSTAT_STARTING)
  1307.                         status = "FORMING ";
  1308.                 else if (game_status == NETSTAT_PLAYING)
  1309.                 {
  1310.                         if (augi.RefusePlayers)
  1311.                                 status = "RESTRICT";
  1312.                         else if (augi.game_flag.closed)
  1313.                                 status = "CLOSED  ";
  1314.                         else
  1315.                                 status = "OPEN    ";
  1316.                 }
  1317.                 else
  1318.                         status = "BETWEEN ";
  1319.                
  1320.                 unsigned gamemode = augi.gamemode;
  1321.                 auto &p = dj->ljtext[i];
  1322.                 snprintf(&p[0], p.size(), "%d.\t%.24s \t%.7s \t%3u/%u \t%.24s \t %s \t%s", (i + (NLPage * UDP_NETGAMES_PPAGE)) + 1, GameName.data(), (gamemode < std::size(GMNamesShrt)) ? GMNamesShrt[gamemode] : "INVALID", nplayers, augi.max_numplayers, MissName.data(), levelname, status);
  1323.         }
  1324.         return 0;
  1325. }
  1326.  
  1327. void net_udp_list_join_game()
  1328. {
  1329.         auto dj = std::make_unique<list_join>();
  1330.  
  1331.         net_udp_init();
  1332.         const auto gamemyport = CGameArg.MplUdpMyPort;
  1333.         if (udp_open_socket(UDP_Socket[0], gamemyport >= 1024 ? gamemyport : UDP_PORT_DEFAULT) < 0)
  1334.                 return;
  1335.  
  1336.         if (gamemyport >= 1024 && gamemyport != UDP_PORT_DEFAULT)
  1337.                 if (udp_open_socket(UDP_Socket[1], UDP_PORT_DEFAULT) < 0)
  1338.                         nm_messagebox(TXT_WARNING, 1, TXT_OK, "Cannot open default port!\nYou can only scan for games\nmanually.");
  1339.  
  1340.         // prepare broadcast address to discover games
  1341.         udp_init_broadcast_addresses();
  1342.  
  1343.         change_playernum_to(1);
  1344.         N_players = 0;
  1345.         Network_send_objects = 0;
  1346.         Network_sending_extras=0;
  1347.         Network_rejoined=0;
  1348.  
  1349.         Network_status = NETSTAT_BROWSING; // We are looking at a game menu
  1350.  
  1351.         net_udp_flush();
  1352.         net_udp_listen();  // Throw out old info
  1353.  
  1354.         num_active_udp_games = 0;
  1355.  
  1356.         Active_udp_games = {};
  1357.  
  1358.         gr_set_fontcolor(*grd_curcanv, BM_XRGB(15, 15, 23),-1);
  1359.  
  1360.         auto &m = dj->m;
  1361. #if DXX_USE_TRACKER
  1362.         nm_set_item_text(m[0], "\tF4/F5/F6: (Re)Scan for all/LAN/Tracker Games." );
  1363. #else
  1364.         nm_set_item_text(m[0], "\tF4: (Re)Scan for LAN Games." );
  1365. #endif
  1366.         nm_set_item_text(m[1], "\tPgUp/PgDn: Flip Pages." );
  1367.         nm_set_item_text(m[2], " " );
  1368.         nm_set_item_text(m[3],  "\tGAME \tMODE \t#PLYRS \tMISSION \tLEV \tSTATUS");
  1369.  
  1370.         for (int i = 0; i < UDP_NETGAMES_PPAGE; i++) {
  1371.                 auto &p = dj->ljtext[i];
  1372.                 nm_set_item_menu(m[i + 4], &p[0]);
  1373.                 snprintf(&p[0], p.size(), "%d.                                                                      ", i + 1);
  1374.         }
  1375.         nm_set_item_text(m[UDP_NETGAMES_PPAGE+4], "\t" );
  1376.  
  1377.         num_active_udp_changed = 1;
  1378.         newmenu_dotiny("NETGAMES", nullptr, UDP_NETGAMES_PPAGE + 5, &m[0], 1, net_udp_list_join_poll, dj.release());
  1379. }
  1380.  
  1381. static void net_udp_send_sequence_packet(UDP_sequence_packet seq, const _sockaddr &recv_addr)
  1382. {
  1383.         std::array<uint8_t, UPID_SEQUENCE_SIZE> buf;
  1384.         int len = 0;
  1385.         buf[0] = seq.type;                                              len++;
  1386.         memcpy(&buf[len], seq.player.callsign.buffer(), CALLSIGN_LEN+1);                len += CALLSIGN_LEN+1;
  1387.         buf[len] = seq.player.connected;                                len++;
  1388.         buf[len] = seq.player.rank;                                     len++;
  1389.         dxx_sendto(recv_addr, UDP_Socket[0], buf, 0);
  1390. }
  1391.  
  1392. static void net_udp_receive_sequence_packet(ubyte *data, UDP_sequence_packet *seq, const _sockaddr &sender_addr)
  1393. {
  1394.         int len = 0;
  1395.        
  1396.         seq->type = data[0];                                            len++;
  1397.         memcpy(seq->player.callsign.buffer(), &(data[len]), CALLSIGN_LEN+1);    len += CALLSIGN_LEN+1;
  1398.         seq->player.connected = data[len];                              len++;
  1399.         memcpy (&(seq->player.rank),&(data[len]),1);                    len++;
  1400.        
  1401.         if (multi_i_am_master())
  1402.                 seq->player.protocol.udp.addr = sender_addr;
  1403. }
  1404.  
  1405. void net_udp_init()
  1406. {
  1407.         // So you want to play a netgame, eh?  Let's a get a few things straight
  1408.  
  1409. #ifdef _WIN32
  1410. {
  1411.         WORD wVersionRequested;
  1412.         WSADATA wsaData;
  1413.         wVersionRequested = MAKEWORD(2, 2);
  1414.         WSACleanup();
  1415.         if (WSAStartup( wVersionRequested, &wsaData))
  1416.                 nm_messagebox( TXT_ERROR, 1, TXT_OK, "Cannot init Winsock!"); // no break here... game will fail at socket creation anyways...
  1417. }
  1418. #endif
  1419.  
  1420.         clear_UDP_Socket();
  1421.  
  1422.         Netgame = {};
  1423.         UDP_Seq = {};
  1424.         UDP_MData = {};
  1425.         net_udp_noloss_init_mdata_queue();
  1426.         UDP_Seq.type = UPID_REQUEST;
  1427.         UDP_Seq.player.callsign = InterfaceUniqueState.PilotName;
  1428.  
  1429.         UDP_Seq.player.rank=GetMyNetRanking(); 
  1430.  
  1431.         multi_new_game();
  1432.         net_udp_flush();
  1433.  
  1434. #if DXX_USE_TRACKER
  1435.         // Initialize the tracker info
  1436.         udp_tracker_init();
  1437. #endif
  1438. }
  1439.  
  1440. void net_udp_close()
  1441. {
  1442.         clear_UDP_Socket();
  1443. #ifdef _WIN32
  1444.         WSACleanup();
  1445. #endif
  1446. }
  1447.  
  1448. // Same as above but used when player pressed ESC during kmatrix (host also does the packets for playing clients)
  1449. int net_udp_kmatrix_poll2( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
  1450. {
  1451.         int rval = 0;
  1452.  
  1453.         // Polling loop for End-of-level menu
  1454.         if (event.type == EVENT_WINDOW_CREATED)
  1455.         {
  1456.                 StartAbortMenuTime=timer_query();
  1457.                 return 0;
  1458.         }
  1459.         if (event.type != EVENT_WINDOW_DRAW)
  1460.                 return 0;
  1461.         if (timer_query() > (StartAbortMenuTime+(F1_0*3)))
  1462.                 rval = -2;
  1463.  
  1464.         net_udp_do_frame(0, 1);
  1465.        
  1466.         return rval;
  1467. }
  1468.  
  1469. namespace dsx {
  1470. int net_udp_endlevel(int *secret)
  1471. {
  1472.         // Do whatever needs to be done between levels
  1473. #if defined(DXX_BUILD_DESCENT_II)
  1474.         if (EMULATING_D1)
  1475. #endif
  1476.         {
  1477.                 // We do not really check if a player has actually found a secret level... yeah, I am too lazy! So just go there and pretend we did!
  1478.                 range_for (const auto i, unchecked_partial_range(Secret_level_table.get(), N_secret_levels))
  1479.                 {
  1480.                         if (Current_level_num == i)
  1481.                         {
  1482.                                 *secret = 1;
  1483.                                 break;
  1484.                         }
  1485.                 }
  1486.         }
  1487. #if defined(DXX_BUILD_DESCENT_II)
  1488.         else
  1489.                 *secret = 0;
  1490. #endif
  1491.  
  1492.         Network_status = NETSTAT_ENDLEVEL; // We are between levels
  1493.         net_udp_listen();
  1494.         net_udp_send_endlevel_packet();
  1495.  
  1496.         range_for (auto &i, partial_range(Netgame.players, N_players))
  1497.         {
  1498.                 i.LastPacketTime = timer_query();
  1499.         }
  1500.    
  1501.         net_udp_send_endlevel_packet();
  1502.         net_udp_send_endlevel_packet();
  1503.  
  1504.         net_udp_update_netgame();
  1505.  
  1506.         return(0);
  1507. }
  1508. }
  1509.  
  1510. static join_netgame_status_code net_udp_can_join_netgame(const netgame_info *const game)
  1511. {
  1512.         // Can this player rejoin a netgame in progress?
  1513.         if (game->game_status == NETSTAT_STARTING)
  1514.                 return join_netgame_status_code::game_has_capacity;
  1515.  
  1516.         if (game->game_status != NETSTAT_PLAYING)
  1517.                 return join_netgame_status_code::game_in_disallowed_state;
  1518.  
  1519.         // Game is in progress, figure out if this guy can re-join it
  1520.  
  1521.         const unsigned num_players = game->numplayers;
  1522.  
  1523.         if (!(game->game_flag.closed)) {
  1524.                 // Look for player that is not connected
  1525.                
  1526.                 if (game->numconnected==game->max_numplayers)
  1527.                         return join_netgame_status_code::game_is_full;
  1528.                
  1529.                 if (game->RefusePlayers)
  1530.                         return join_netgame_status_code::game_refuses_players;
  1531.                
  1532.                 if (num_players < game->max_numplayers)
  1533.                         return join_netgame_status_code::game_has_capacity;
  1534.  
  1535.                 if (game->numconnected<num_players)
  1536.                         return join_netgame_status_code::game_has_capacity;
  1537.         }
  1538.  
  1539.         // Search to see if we were already in this closed netgame in progress
  1540.  
  1541.         auto &plr = get_local_player();
  1542.         for (const auto i : xrange(num_players))
  1543.         {
  1544.                 if (plr.callsign == game->players[i].callsign && i == game->protocol.udp.your_index)
  1545.                         return join_netgame_status_code::game_has_capacity;
  1546.         }
  1547.         return join_netgame_status_code::game_in_disallowed_state;
  1548. }
  1549.  
  1550. // do UDP stuff to disconnect a player. Should ONLY be called from multi_disconnect_player()
  1551. void net_udp_disconnect_player(int playernum)
  1552. {
  1553.         // A player has disconnected from the net game, take whatever steps are
  1554.         // necessary
  1555.  
  1556.         if (playernum == Player_num)
  1557.         {
  1558.                 Int3(); // Weird, see Rob
  1559.                 return;
  1560.         }
  1561.  
  1562.         if (VerifyPlayerJoined==playernum)
  1563.                 VerifyPlayerJoined=-1;
  1564.  
  1565.         net_udp_noloss_clear_mdata_trace(playernum);
  1566. }
  1567.  
  1568. namespace dsx {
  1569. static void net_udp_new_player(UDP_sequence_packet *const their)
  1570. {
  1571.         auto &Objects = LevelUniqueObjectState.Objects;
  1572.         auto &vmobjptr = Objects.vmptr;
  1573.         unsigned pnum = their->player.connected;
  1574.  
  1575.         Assert(pnum < Netgame.max_numplayers);
  1576.        
  1577.         if (Newdemo_state == ND_STATE_RECORDING) {
  1578.                 int new_player;
  1579.  
  1580.                 if (pnum == N_players)
  1581.                         new_player = 1;
  1582.                 else
  1583.                         new_player = 0;
  1584.                 newdemo_record_multi_connect(pnum, new_player, their->player.callsign);
  1585.         }
  1586.  
  1587.         auto &plr = *vmplayerptr(pnum);
  1588.         plr.callsign = their->player.callsign;
  1589.         Netgame.players[pnum].callsign = their->player.callsign;
  1590.         Netgame.players[pnum].protocol.udp.addr = their->player.protocol.udp.addr;
  1591.  
  1592.         ClipRank (&their->player.rank);
  1593.         Netgame.players[pnum].rank=their->player.rank;
  1594.  
  1595.         plr.connected = CONNECT_PLAYING;
  1596.         kill_matrix[pnum] = {};
  1597.         auto &objp = *vmobjptr(plr.objnum);
  1598.         auto &player_info = objp.ctype.player_info;
  1599.         player_info.net_killed_total = 0;
  1600.         player_info.net_kills_total = 0;
  1601.         player_info.mission.score = 0;
  1602.         player_info.powerup_flags = {};
  1603.         player_info.KillGoalCount = 0;
  1604.  
  1605.         if (pnum == N_players)
  1606.         {
  1607.                 N_players++;
  1608.                 Netgame.numplayers = N_players;
  1609.         }
  1610.  
  1611.         digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
  1612.  
  1613.         ClipRank (&their->player.rank);
  1614.  
  1615.         const auto &&rankstr = GetRankStringWithSpace(their->player.rank);
  1616.         HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign), TXT_JOINING);
  1617.        
  1618.         multi_make_ghost_player(pnum);
  1619.  
  1620.         multi_send_score();
  1621. #if defined(DXX_BUILD_DESCENT_II)
  1622.         multi_sort_kill_list();
  1623. #endif
  1624.  
  1625.         net_udp_noloss_clear_mdata_trace(pnum);
  1626. }
  1627. }
  1628.  
  1629. static void net_udp_welcome_player(UDP_sequence_packet *their)
  1630. {
  1631.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1632.         auto &Objects = LevelUniqueObjectState.Objects;
  1633.         auto &vmobjptr = Objects.vmptr;
  1634.         // Add a player to a game already in progress
  1635.         WaitForRefuseAnswer=0;
  1636.  
  1637.         // Don't accept new players if we're ending this level.  Its safe to
  1638.         // ignore since they'll request again later
  1639.  
  1640.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  1641.         {
  1642.                 net_udp_dump_player(their->player.protocol.udp.addr, DUMP_ENDLEVEL);
  1643.                 return;
  1644.         }
  1645.  
  1646.         if (Network_send_objects || Network_sending_extras)
  1647.         {
  1648.                 // Ignore silently, we're already responding to someone and we can't
  1649.                 // do more than one person at a time.  If we don't dump them they will
  1650.                 // re-request in a few seconds.
  1651.                 return;
  1652.         }
  1653.  
  1654.         // Joining a running game will need quite a few packets on the mdata-queue, so let players only join if we have enough space.
  1655.         if (Netgame.PacketLossPrevention)
  1656.                 if ((UDP_MDATA_STOR_QUEUE_SIZE - UDP_mdata_queue_highest) < UDP_MDATA_STOR_MIN_FREE_2JOIN)
  1657.                         return;
  1658.  
  1659.         if (their->player.connected != Current_level_num)
  1660.         {
  1661.                 net_udp_dump_player(their->player.protocol.udp.addr, DUMP_LEVEL);
  1662.                 return;
  1663.         }
  1664.  
  1665.         unsigned player_num = UINT_MAX;
  1666.         UDP_sync_player = {};
  1667.         Network_player_added = 0;
  1668.  
  1669.         for (unsigned i = 0; i < N_players; i++)
  1670.         {
  1671.                 if (vcplayerptr(i)->callsign == their->player.callsign &&
  1672.                         their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
  1673.                 {
  1674.                         player_num = i;
  1675.                         break;
  1676.                 }
  1677.         }
  1678.  
  1679.         if (player_num == UINT_MAX)
  1680.         {
  1681.                 // Player is new to this game
  1682.  
  1683.                 if ( !(Netgame.game_flag.closed) && (N_players < Netgame.max_numplayers))
  1684.                 {
  1685.                         // Add player in an open slot, game not full yet
  1686.  
  1687.                         player_num = N_players;
  1688.                         Network_player_added = 1;
  1689.                 }
  1690.                 else if (Netgame.game_flag.closed)
  1691.                 {
  1692.                         // Slots are open but game is closed
  1693.                         net_udp_dump_player(their->player.protocol.udp.addr, DUMP_CLOSED);
  1694.                         return;
  1695.                 }
  1696.                 else
  1697.                 {
  1698.                         // Slots are full but game is open, see if anyone is
  1699.                         // disconnected and replace the oldest player with this new one
  1700.                
  1701.                         int oldest_player = -1;
  1702.                         fix64 oldest_time = timer_query();
  1703.                         int activeplayers = 0;
  1704.  
  1705.                         Assert(N_players == Netgame.max_numplayers);
  1706.  
  1707.                         range_for (auto &i, partial_const_range(Netgame.players, Netgame.numplayers))
  1708.                                 if (i.connected)
  1709.                                         activeplayers++;
  1710.  
  1711.                         if (activeplayers == Netgame.max_numplayers)
  1712.                         {
  1713.                                 // Game is full.
  1714.                                 net_udp_dump_player(their->player.protocol.udp.addr, DUMP_FULL);
  1715.                                 return;
  1716.                         }
  1717.  
  1718.                         for (unsigned i = 0; i < N_players; i++)
  1719.                         {
  1720.                                 if (!vcplayerptr(i)->connected && Netgame.players[i].LastPacketTime < oldest_time)
  1721.                                 {
  1722.                                         oldest_time = Netgame.players[i].LastPacketTime;
  1723.                                         oldest_player = i;
  1724.                                 }
  1725.                         }
  1726.  
  1727.                         if (oldest_player == -1)
  1728.                         {
  1729.                                 // Everyone is still connected
  1730.                                 net_udp_dump_player(their->player.protocol.udp.addr, DUMP_FULL);
  1731.                                 return;
  1732.                         }
  1733.                         else
  1734.                         {
  1735.                                 // Found a slot!
  1736.  
  1737.                                 player_num = oldest_player;
  1738.                                 Network_player_added = 1;
  1739.                         }
  1740.                 }
  1741.         }
  1742.         else
  1743.         {
  1744.                 // Player is reconnecting
  1745.                
  1746.                 auto &plr = *vcplayerptr(player_num);
  1747.                 if (plr.connected)
  1748.                 {
  1749.                         return;
  1750.                 }
  1751.  
  1752.                 if (Newdemo_state == ND_STATE_RECORDING)
  1753.                         newdemo_record_multi_reconnect(player_num);
  1754.  
  1755.                 Network_player_added = 0;
  1756.  
  1757.                 digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
  1758.  
  1759.                 const auto &&rankstr = GetRankStringWithSpace(Netgame.players[player_num].rank);
  1760.                 HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(plr.callsign), TXT_REJOIN);
  1761.  
  1762.                 multi_send_score();
  1763.  
  1764.                 net_udp_noloss_clear_mdata_trace(player_num);
  1765.         }
  1766.  
  1767.         auto &obj = *vmobjptr(vcplayerptr(player_num)->objnum);
  1768.         auto &player_info = obj.ctype.player_info;
  1769.         player_info.KillGoalCount = 0;
  1770.  
  1771.         // Send updated Objects data to the new/returning player
  1772.  
  1773.        
  1774.         UDP_sync_player = *their;
  1775.         UDP_sync_player.player.connected = player_num;
  1776.         Network_send_objects = 1;
  1777.         Network_send_objnum = -1;
  1778.         Netgame.players[player_num].LastPacketTime = timer_query();
  1779.  
  1780.         net_udp_send_objects();
  1781. }
  1782.  
  1783. int net_udp_objnum_is_past(objnum_t objnum)
  1784. {
  1785.         // determine whether or not a given object number has already been sent
  1786.         // to a re-joining player.
  1787.        
  1788.         int player_num = UDP_sync_player.player.connected;
  1789.         int obj_mode = !((object_owner[objnum] == -1) || (object_owner[objnum] == player_num));
  1790.  
  1791.         if (!Network_send_objects)
  1792.                 return 0; // We're not sending objects to a new player
  1793.  
  1794.         if (obj_mode > Network_send_object_mode)
  1795.                 return 0;
  1796.         else if (obj_mode < Network_send_object_mode)
  1797.                 return 1;
  1798.         else if (objnum < Network_send_objnum)
  1799.                 return 1;
  1800.         else
  1801.                 return 0;
  1802. }
  1803.  
  1804. namespace dsx {
  1805.  
  1806. #if defined(DXX_BUILD_DESCENT_I)
  1807. static void net_udp_send_door_updates(void)
  1808. {
  1809.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1810.         auto &vcwallptridx = Walls.vcptridx;
  1811.         // Send door status when new player joins
  1812.         range_for (const auto &&p, vcwallptridx)
  1813.         {
  1814.                 auto &w = *p;
  1815.                 if ((w.type == WALL_DOOR && (w.state == WALL_DOOR_OPENING || w.state == WALL_DOOR_WAITING)) || (w.type == WALL_BLASTABLE && (w.flags & WALL_BLASTED)))
  1816.                         multi_send_door_open(w.segnum, w.sidenum,0);
  1817.                 else if (w.type == WALL_BLASTABLE && w.hps != WALL_HPS)
  1818.                         multi_send_hostage_door_status(p);
  1819.         }
  1820.  
  1821. }
  1822. #elif defined(DXX_BUILD_DESCENT_II)
  1823. static void net_udp_send_door_updates(const playernum_t pnum)
  1824. {
  1825.         // Send door status when new player joins
  1826.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1827.         auto &vcwallptridx = Walls.vcptridx;
  1828.         range_for (const auto &&p, vcwallptridx)
  1829.         {
  1830.                 auto &w = *p;
  1831.                 if ((w.type == WALL_DOOR && (w.state == WALL_DOOR_OPENING || w.state == WALL_DOOR_WAITING || w.state == WALL_DOOR_OPEN)) || (w.type == WALL_BLASTABLE && (w.flags & WALL_BLASTED)))
  1832.                         multi_send_door_open_specific(pnum,w.segnum, w.sidenum,w.flags);
  1833.                 else if (w.type == WALL_BLASTABLE && w.hps != WALL_HPS)
  1834.                         multi_send_hostage_door_status(p);
  1835.                 else
  1836.                         multi_send_wall_status_specific(pnum,p,w.type,w.flags,w.state);
  1837.         }
  1838. }
  1839. #endif
  1840.  
  1841. }
  1842.  
  1843. static void net_udp_process_monitor_vector(uint32_t vector)
  1844. {
  1845.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  1846.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  1847.         if (!vector)
  1848.                 return;
  1849.         range_for (unique_segment &seg, vmsegptr)
  1850.         {
  1851.                 int tm, ec, bm;
  1852.                 range_for (auto &j, seg.sides)
  1853.                 {
  1854.                         if ( ((tm = j.tmap_num2) != 0) &&
  1855.                                 (ec = TmapInfo[tm & 0x3fff].eclip_num) != eclip_none &&
  1856.                                 (bm = Effects[ec].dest_bm_num) != ~0u)
  1857.                         {
  1858.                                 if (vector & 1)
  1859.                                 {
  1860.                                         j.tmap_num2 = bm | (tm&0xc000);
  1861.                                 }
  1862.                                 if (!(vector >>= 1))
  1863.                                         return;
  1864.                         }
  1865.                 }
  1866.         }
  1867. }
  1868.  
  1869. namespace {
  1870.  
  1871. class blown_bitmap_array
  1872. {
  1873.         typedef int T;
  1874.         using array_t = std::array<T, 32>;
  1875.         typedef array_t::const_iterator const_iterator;
  1876.         array_t a;
  1877.         array_t::iterator e = a.begin();
  1878. public:
  1879.         bool exists(T t) const
  1880.         {
  1881.                 const_iterator ce = e;
  1882.                 return std::find(a.begin(), ce, t) != ce;
  1883.         }
  1884.         void insert_unique(T t)
  1885.         {
  1886.                 if (exists(t))
  1887.                         return;
  1888.                 if (e == a.end())
  1889.                 {
  1890.                         LevelError("too many blown bitmaps; ignoring bitmap %i.", t);
  1891.                         return;
  1892.                 }
  1893.                 *e = t;
  1894.                 ++e;
  1895.         }
  1896. };
  1897.  
  1898. }
  1899.  
  1900. static unsigned net_udp_create_monitor_vector(void)
  1901. {
  1902.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  1903.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  1904.         blown_bitmap_array blown_bitmaps;
  1905.         constexpr size_t max_textures = Textures.size();
  1906.         range_for (auto &i, partial_const_range(Effects, Num_effects))
  1907.         {
  1908.                 if (i.dest_bm_num < max_textures)
  1909.                 {
  1910.                         blown_bitmaps.insert_unique(i.dest_bm_num);
  1911.                 }
  1912.         }
  1913.         unsigned monitor_num = 0;
  1914.         unsigned vector = 0;
  1915.         range_for (const auto &&seg, vcsegptridx)
  1916.         {
  1917.                 range_for (auto &j, seg->unique_segment::sides)
  1918.                 {
  1919.                         const unsigned tm2 = j.tmap_num2;
  1920.                         if (!tm2)
  1921.                                 continue;
  1922.                         const unsigned masked_tm2 = tm2 & 0x3fff;
  1923.                         const unsigned ec = TmapInfo[masked_tm2].eclip_num;
  1924.                         {
  1925.                                 if (ec != eclip_none &&
  1926.                                         Effects[ec].dest_bm_num != ~0u)
  1927.                                 {
  1928.                                 }
  1929.                                 else if (blown_bitmaps.exists(masked_tm2))
  1930.                                 {
  1931.                                         if (monitor_num >= 8 * sizeof(vector))
  1932.                                         {
  1933.                                                 LevelError("too many blown monitors; ignoring segment %hu.", seg.get_unchecked_index());
  1934.                                                 return vector;
  1935.                                         }
  1936.                                                         vector |= (1 << monitor_num);
  1937.                                 }
  1938.                                 else
  1939.                                         continue;
  1940.                                 monitor_num++;
  1941.                         }
  1942.                 }
  1943.         }
  1944.         return(vector);
  1945. }
  1946.  
  1947. static void net_udp_stop_resync(UDP_sequence_packet *their)
  1948. {
  1949.         if (UDP_sync_player.player.protocol.udp.addr == their->player.protocol.udp.addr &&
  1950.                 (!d_stricmp(UDP_sync_player.player.callsign, their->player.callsign)) )
  1951.         {
  1952.                 Network_send_objects = 0;
  1953.                 Network_sending_extras=0;
  1954.                 Network_rejoined=0;
  1955.                 Player_joining_extras=-1;
  1956.                 Network_send_objnum = -1;
  1957.         }
  1958. }
  1959.  
  1960. namespace dsx {
  1961. void net_udp_send_objects(void)
  1962. {
  1963.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1964.         auto &Objects = LevelUniqueObjectState.Objects;
  1965.         auto &vmobjptr = Objects.vmptr;
  1966.         sbyte owner, player_num = UDP_sync_player.player.connected;
  1967.         static int obj_count = 0;
  1968.         int loc = 0, remote_objnum = 0, obj_count_frame = 0;
  1969.         static fix64 last_send_time = 0;
  1970.        
  1971.         if (last_send_time + (F1_0/50) > timer_query())
  1972.                 return;
  1973.         last_send_time = timer_query();
  1974.  
  1975.         // Send clear objects array trigger and send player num
  1976.  
  1977.         Assert(Network_send_objects != 0);
  1978.         Assert(player_num >= 0);
  1979.         Assert(player_num < Netgame.max_numplayers);
  1980.  
  1981.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  1982.         {
  1983.                 // Endlevel started before we finished sending the goods, we'll
  1984.                 // have to stop and try again after the level.
  1985.                 net_udp_dump_player(UDP_sync_player.player.protocol.udp.addr, DUMP_ENDLEVEL);
  1986.                 Network_send_objects = 0;
  1987.                 return;
  1988.         }
  1989.  
  1990.         std::array<uint8_t, UPID_MAX_SIZE> object_buffer;
  1991.         object_buffer = {};
  1992.         object_buffer[0] = UPID_OBJECT_DATA;
  1993.         loc = 5;
  1994.  
  1995.         if (Network_send_objnum == -1)
  1996.         {
  1997.                 obj_count = 0;
  1998.                 Network_send_object_mode = 0;
  1999.                 PUT_INTEL_INT(&object_buffer[loc], -1);                       loc += 4;
  2000.                 object_buffer[loc] = player_num;                            loc += 1;
  2001.                 /* Placeholder for remote_objnum, not used here */          loc += 4;
  2002.                 Network_send_objnum = 0;
  2003.                 obj_count_frame = 1;
  2004.         }
  2005.        
  2006.         objnum_t i;
  2007.         for (i = Network_send_objnum; i <= Highest_object_index; i++)
  2008.         {
  2009.                 const auto &&objp = vmobjptr(i);
  2010.                 if ((objp->type != OBJ_POWERUP) && (objp->type != OBJ_PLAYER) &&
  2011.                                 (objp->type != OBJ_CNTRLCEN) && (objp->type != OBJ_GHOST) &&
  2012.                                 (objp->type != OBJ_ROBOT) && (objp->type != OBJ_HOSTAGE)
  2013. #if defined(DXX_BUILD_DESCENT_II)
  2014.                                 && !(objp->type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::PMINE_ID)
  2015. #endif
  2016.                                 )
  2017.                         continue;
  2018.                 if ((Network_send_object_mode == 0) && ((object_owner[i] != -1) && (object_owner[i] != player_num)))
  2019.                         continue;
  2020.                 if ((Network_send_object_mode == 1) && ((object_owner[i] == -1) || (object_owner[i] == player_num)))
  2021.                         continue;
  2022.  
  2023.                 if ( loc + sizeof(object_rw) + 9 > UPID_MAX_SIZE-1 )
  2024.                         break; // Not enough room for another object
  2025.  
  2026.                 obj_count_frame++;
  2027.                 obj_count++;
  2028.  
  2029.                 remote_objnum = objnum_local_to_remote(i, &owner);
  2030.                 Assert(owner == object_owner[i]);
  2031.  
  2032.                 PUT_INTEL_INT(&object_buffer[loc], i);                        loc += 4;
  2033.                 object_buffer[loc] = owner;                                 loc += 1;
  2034.                 PUT_INTEL_INT(&object_buffer[loc], remote_objnum);            loc += 4;
  2035.                 // use object_rw to send objects for now. if object sometime contains some day contains something useful the client should know about, we should use it. but by now it's also easier to use object_rw because then we also do not need fix64 timer values.
  2036.                 multi_object_to_object_rw(vmobjptr(i), reinterpret_cast<object_rw *>(&object_buffer[loc]));
  2037.                 if constexpr (words_bigendian)
  2038.                         object_rw_swap(reinterpret_cast<object_rw *>(&object_buffer[loc]), 1);
  2039.                 loc += sizeof(object_rw);
  2040.         }
  2041.  
  2042.         if (obj_count_frame) // Send any objects we've buffered
  2043.         {
  2044.                 Network_send_objnum = i;
  2045.                 PUT_INTEL_INT(&object_buffer[1], obj_count_frame);
  2046.  
  2047.                 Assert(loc <= UPID_MAX_SIZE);
  2048.  
  2049.                 dxx_sendto(UDP_sync_player.player.protocol.udp.addr, UDP_Socket[0], &object_buffer[0], loc, 0);
  2050.         }
  2051.  
  2052.         if (i > Highest_object_index)
  2053.         {
  2054.                 if (Network_send_object_mode == 0)
  2055.                 {
  2056.                         Network_send_objnum = 0;
  2057.                         Network_send_object_mode = 1; // go to next mode
  2058.                 }
  2059.                 else
  2060.                 {
  2061.                         Assert(Network_send_object_mode == 1);
  2062.  
  2063.                         // Send count so other side can make sure he got them all
  2064.                         object_buffer[0] = UPID_OBJECT_DATA;
  2065.                         PUT_INTEL_INT(&object_buffer[1], 1);
  2066.                         PUT_INTEL_INT(&object_buffer[5], network_checksum_marker_object);
  2067.                         object_buffer[9] = player_num;
  2068.                         PUT_INTEL_INT(&object_buffer[10], obj_count);
  2069.                         dxx_sendto(UDP_sync_player.player.protocol.udp.addr, UDP_Socket[0], &object_buffer[0], 14, 0);
  2070.  
  2071.                         // Send sync packet which tells the player who he is and to start!
  2072.                         net_udp_send_rejoin_sync(player_num);
  2073.  
  2074.                         // Turn off send object mode
  2075.                         Network_send_objnum = -1;
  2076.                         Network_send_objects = 0;
  2077.                         obj_count = 0;
  2078.  
  2079. #if defined(DXX_BUILD_DESCENT_I)
  2080.                         Network_sending_extras=3; // start to send extras
  2081. #elif defined(DXX_BUILD_DESCENT_II)
  2082.                         Network_sending_extras=9; // start to send extras
  2083. #endif
  2084.                         VerifyPlayerJoined = Player_joining_extras = player_num;
  2085.  
  2086.                         return;
  2087.                 } // mode == 1;
  2088.         } // i > Highest_object_index
  2089. }
  2090. }
  2091.  
  2092. static int net_udp_verify_objects(int remote, int local)
  2093. {
  2094.         auto &Objects = LevelUniqueObjectState.Objects;
  2095.         auto &vcobjptr = Objects.vcptr;
  2096.         int nplayers = 0;
  2097.  
  2098.         if ((remote-local) > 10)
  2099.                 return(2);
  2100.  
  2101.         range_for (const auto &&objp, vcobjptr)
  2102.         {
  2103.                 if (objp->type == OBJ_PLAYER || objp->type == OBJ_GHOST)
  2104.                         nplayers++;
  2105.         }
  2106.  
  2107.         if (Netgame.max_numplayers<=nplayers)
  2108.                 return(0);
  2109.  
  2110.         return(1);
  2111. }
  2112.  
  2113. static void net_udp_read_object_packet( ubyte *data )
  2114. {
  2115.         auto &Objects = LevelUniqueObjectState.Objects;
  2116.         auto &vmobjptridx = Objects.vmptridx;
  2117.         // Object from another net player we need to sync with
  2118.         sbyte obj_owner;
  2119.         static int mode = 0, object_count = 0, my_pnum = 0;
  2120.         int remote_objnum = 0, nobj = 0, loc = 5;
  2121.        
  2122.         nobj = GET_INTEL_INT(data + 1);
  2123.  
  2124.         for (int i = 0; i < nobj; i++)
  2125.         {
  2126.                 const unsigned uobjnum = GET_INTEL_INT(data + loc);
  2127.                 objnum_t objnum = uobjnum;                         loc += 4;
  2128.                 obj_owner = data[loc];                                      loc += 1;
  2129.                 remote_objnum = GET_INTEL_INT(data + loc);                  loc += 4;
  2130.  
  2131.                 if (objnum == object_none)
  2132.                 {
  2133.                         // Clear object array
  2134.                         init_objects();
  2135.                         Network_rejoined = 1;
  2136.                         my_pnum = obj_owner;
  2137.                         change_playernum_to(my_pnum);
  2138.                         mode = 1;
  2139.                         object_count = 0;
  2140.                 }
  2141.                 else if (uobjnum == network_checksum_marker_object)
  2142.                 {
  2143.                         // Special debug checksum marker for entire send
  2144.                         if (mode == 1)
  2145.                         {
  2146.                                 special_reset_objects(LevelUniqueObjectState);
  2147.                                 mode = 0;
  2148.                         }
  2149.                         if (remote_objnum != object_count) {
  2150.                                 Int3();
  2151.                         }
  2152.                         if (net_udp_verify_objects(remote_objnum, object_count))
  2153.                         {
  2154.                                 // Failed to sync up
  2155.                                 nm_messagebox(NULL, 1, TXT_OK, TXT_NET_SYNC_FAILED);
  2156.                                 Network_status = NETSTAT_MENU;                          
  2157.                                 return;
  2158.                         }
  2159.                 }
  2160.                 else
  2161.                 {
  2162.                         object_count++;
  2163.                         if ((obj_owner == my_pnum) || (obj_owner == -1))
  2164.                         {
  2165.                                 if (mode != 1)
  2166.                                         Int3(); // SEE ROB
  2167.                                 objnum = remote_objnum;
  2168.                         }
  2169.                         else {
  2170.                                 if (mode == 1)
  2171.                                 {
  2172.                                         special_reset_objects(LevelUniqueObjectState);
  2173.                                         mode = 0;
  2174.                                 }
  2175.                                 objnum = obj_allocate(LevelUniqueObjectState);
  2176.                         }
  2177.                         if (objnum != object_none) {
  2178.                                 auto obj = vmobjptridx(objnum);
  2179.                                 if (obj->type != OBJ_NONE)
  2180.                                 {
  2181.                                         obj_unlink(Objects.vmptr, Segments.vmptr, obj);
  2182.                                         Assert(obj->segnum == segment_none);
  2183.                                 }
  2184.                                 Assert(objnum < MAX_OBJECTS);
  2185.                                 if constexpr (words_bigendian)
  2186.                                         object_rw_swap(reinterpret_cast<object_rw *>(&data[loc]), 1);
  2187.                                 multi_object_rw_to_object(reinterpret_cast<object_rw *>(&data[loc]), obj);
  2188.                                 loc += sizeof(object_rw);
  2189.                                 auto segnum = obj->segnum;
  2190.                                 obj->attached_obj = object_none;
  2191.                                 if (segnum != segment_none)
  2192.                                 {
  2193.                                         obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
  2194.                                 }
  2195.                                 if (obj_owner == my_pnum)
  2196.                                         map_objnum_local_to_local(objnum);
  2197.                                 else if (obj_owner != -1)
  2198.                                         map_objnum_local_to_remote(objnum, remote_objnum, obj_owner);
  2199.                                 else
  2200.                                         object_owner[objnum] = -1;
  2201.                         }
  2202.                 } // For a standard onbject
  2203.         } // For each object in packet
  2204. }
  2205.  
  2206. namespace dsx {
  2207.  
  2208. void net_udp_send_rejoin_sync(const unsigned player_num)
  2209. {
  2210.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  2211.         auto &Objects = LevelUniqueObjectState.Objects;
  2212.         auto &vcobjptr = Objects.vcptr;
  2213.         vmplayerptr(player_num)->connected = CONNECT_PLAYING; // connect the new guy
  2214.         Netgame.players[player_num].LastPacketTime = timer_query();
  2215.  
  2216.         if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
  2217.         {
  2218.                 // Endlevel started before we finished sending the goods, we'll
  2219.                 // have to stop and try again after the level.
  2220.  
  2221.                 net_udp_dump_player(UDP_sync_player.player.protocol.udp.addr, DUMP_ENDLEVEL);
  2222.  
  2223.                 Network_send_objects = 0;
  2224.                 Network_sending_extras=0;
  2225.                 return;
  2226.         }
  2227.  
  2228.         if (Network_player_added)
  2229.         {
  2230.                 UDP_sync_player.type = UPID_ADDPLAYER;
  2231.                 UDP_sync_player.player.connected = player_num;
  2232.                 net_udp_new_player(&UDP_sync_player);
  2233.  
  2234.                 for (unsigned i = 0; i < N_players; ++i)
  2235.                 {
  2236.                         if (i != player_num && i != Player_num && vcplayerptr(i)->connected)
  2237.                                 net_udp_send_sequence_packet( UDP_sync_player, Netgame.players[i].protocol.udp.addr);
  2238.                 }
  2239.         }
  2240.  
  2241.         // Send sync packet to the new guy
  2242.  
  2243.         net_udp_update_netgame();
  2244.  
  2245.         // Fill in the kill list
  2246.         Netgame.kills = kill_matrix;
  2247.         for (unsigned j = 0; j < MAX_PLAYERS; ++j)
  2248.         {
  2249.                 auto &objp = *vcobjptr(vcplayerptr(j)->objnum);
  2250.                 auto &player_info = objp.ctype.player_info;
  2251.                 Netgame.killed[j] = player_info.net_killed_total;
  2252.                 Netgame.player_kills[j] = player_info.net_kills_total;
  2253.                 Netgame.player_score[j] = player_info.mission.score;
  2254.         }
  2255.  
  2256.         Netgame.level_time = get_local_player().time_level;
  2257.         Netgame.monitor_vector = net_udp_create_monitor_vector();
  2258.  
  2259.         net_udp_send_game_info(UDP_sync_player.player.protocol.udp.addr, &UDP_sync_player.player.protocol.udp.addr, UPID_SYNC);
  2260. #if defined(DXX_BUILD_DESCENT_I)
  2261.         net_udp_send_door_updates();
  2262. #endif
  2263.  
  2264.         return;
  2265. }
  2266. }
  2267.  
  2268. static void net_udp_resend_sync_due_to_packet_loss()
  2269. {
  2270.         auto &Objects = LevelUniqueObjectState.Objects;
  2271.         auto &vcobjptr = Objects.vcptr;
  2272.         if (!multi_i_am_master())
  2273.                 return;
  2274.  
  2275.         net_udp_update_netgame();
  2276.  
  2277.         // Fill in the kill list
  2278.         Netgame.kills = kill_matrix;
  2279.         for (unsigned j = 0; j < MAX_PLAYERS; ++j)
  2280.         {
  2281.                 auto &objp = *vcobjptr(vcplayerptr(j)->objnum);
  2282.                 auto &player_info = objp.ctype.player_info;
  2283.                 Netgame.killed[j] = player_info.net_killed_total;
  2284.                 Netgame.player_kills[j] = player_info.net_kills_total;
  2285.                 Netgame.player_score[j] = player_info.mission.score;
  2286.         }
  2287.  
  2288.         Netgame.level_time = get_local_player().time_level;
  2289.         Netgame.monitor_vector = net_udp_create_monitor_vector();
  2290.  
  2291.         net_udp_send_game_info(UDP_sync_player.player.protocol.udp.addr, &UDP_sync_player.player.protocol.udp.addr, UPID_SYNC);
  2292. }
  2293.  
  2294. static void net_udp_add_player(UDP_sequence_packet *p)
  2295. {
  2296.         auto &Objects = LevelUniqueObjectState.Objects;
  2297.         auto &vmobjptr = Objects.vmptr;
  2298.         range_for (auto &i, partial_range(Netgame.players, N_players))
  2299.         {
  2300.                 if (i.protocol.udp.addr == p->player.protocol.udp.addr)
  2301.                 {
  2302.                         i.LastPacketTime = timer_query();
  2303.                         return;         // already got them
  2304.                 }
  2305.         }
  2306.  
  2307.         if ( N_players >= MAX_PLAYERS )
  2308.         {
  2309.                 return;         // too many of em
  2310.         }
  2311.  
  2312.         ClipRank (&p->player.rank);
  2313.         Netgame.players[N_players].callsign = p->player.callsign;
  2314.         Netgame.players[N_players].protocol.udp.addr = p->player.protocol.udp.addr;
  2315.         Netgame.players[N_players].rank=p->player.rank;
  2316.         Netgame.players[N_players].connected = CONNECT_PLAYING;
  2317.         auto &obj = *vmobjptr(vcplayerptr(N_players)->objnum);
  2318.         auto &player_info = obj.ctype.player_info;
  2319.         player_info.KillGoalCount = 0;
  2320.         vmplayerptr(N_players)->connected = CONNECT_PLAYING;
  2321.         Netgame.players[N_players].LastPacketTime = timer_query();
  2322.         N_players++;
  2323.         Netgame.numplayers = N_players;
  2324.  
  2325.         net_udp_send_netgame_update();
  2326. }
  2327.  
  2328. // One of the players decided not to join the game
  2329.  
  2330. static void net_udp_remove_player(UDP_sequence_packet *p)
  2331. {
  2332.         int pn;
  2333.        
  2334.         pn = -1;
  2335.         for (int i=0; i<N_players; i++ )
  2336.         {
  2337.                 if (Netgame.players[i].protocol.udp.addr == p->player.protocol.udp.addr)
  2338.                 {
  2339.                         pn = i;
  2340.                         break;
  2341.                 }
  2342.         }
  2343.        
  2344.         if (pn < 0 )
  2345.                 return;
  2346.  
  2347.         for (int i=pn; i<N_players-1; i++ )
  2348.         {
  2349.                 Netgame.players[i].callsign = Netgame.players[i+1].callsign;
  2350.                 Netgame.players[i].protocol.udp.addr = Netgame.players[i+1].protocol.udp.addr;
  2351.                 Netgame.players[i].rank=Netgame.players[i+1].rank;
  2352.                 ClipRank (&Netgame.players[i].rank);
  2353.         }
  2354.                
  2355.         N_players--;
  2356.         Netgame.numplayers = N_players;
  2357.  
  2358.         net_udp_send_netgame_update();
  2359. }
  2360.  
  2361. void net_udp_dump_player(const _sockaddr &dump_addr, int why)
  2362. {
  2363.         // Inform player that he was not chosen for the netgame
  2364.         std::array<uint8_t, UPID_DUMP_SIZE> buf;
  2365.         buf[0] = UPID_DUMP;
  2366.         buf[1] = why;
  2367.         dxx_sendto(dump_addr, UDP_Socket[0], buf, 0);
  2368.         if (multi_i_am_master())
  2369.                 for (playernum_t i = 1; i < N_players; i++)
  2370.                         if (dump_addr == Netgame.players[i].protocol.udp.addr)
  2371.                                 multi_disconnect_player(i);
  2372. }
  2373.  
  2374. namespace dsx {
  2375. void net_udp_update_netgame(void)
  2376. {
  2377.         auto &Objects = LevelUniqueObjectState.Objects;
  2378.         auto &vcobjptr = Objects.vcptr;
  2379.         // Update the netgame struct with current game variables
  2380.         Netgame.numconnected=0;
  2381.         range_for (auto &i, partial_const_range(Players, N_players))
  2382.                 if (i.connected)
  2383.                         Netgame.numconnected++;
  2384.  
  2385. #if defined(DXX_BUILD_DESCENT_II)
  2386. // This is great: D2 1.0 and 1.1 ignore upper part of the game_flags field of
  2387. //      the lite_info struct when you're sitting on the join netgame screen.  We can
  2388. //      "sneak" Hoard information into this field.  This is better than sending
  2389. //      another packet that could be lost in transit.
  2390.  
  2391.         if (HoardEquipped())
  2392.         {
  2393.                 if (game_mode_hoard())
  2394.                 {
  2395.                         Netgame.game_flag.hoard = 1;
  2396.                         Netgame.game_flag.team_hoard = !!(Game_mode & GM_TEAM);
  2397.                 }
  2398.                 else
  2399.                 {
  2400.                         Netgame.game_flag.hoard = 0;
  2401.                         Netgame.game_flag.team_hoard = 0;
  2402.                 }
  2403.         }
  2404. #endif
  2405.         if (Network_status == NETSTAT_STARTING)
  2406.                 return;
  2407.  
  2408.         Netgame.numplayers = N_players;
  2409.         Netgame.game_status = Network_status;
  2410.  
  2411.         Netgame.kills = kill_matrix;
  2412.         for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  2413.         {
  2414.                 auto &plr = *vcplayerptr(i);
  2415.                 Netgame.players[i].connected = plr.connected;
  2416.                 auto &objp = *vcobjptr(plr.objnum);
  2417.                 auto &player_info = objp.ctype.player_info;
  2418.                 Netgame.killed[i] = player_info.net_killed_total;
  2419.                 Netgame.player_kills[i] = player_info.net_kills_total;
  2420. #if defined(DXX_BUILD_DESCENT_II)
  2421.                 Netgame.player_score[i] = player_info.mission.score;
  2422. #endif
  2423.                 Netgame.net_player_flags[i] = player_info.powerup_flags;
  2424.         }
  2425.         Netgame.team_kills = team_kills;
  2426.         Netgame.levelnum = Current_level_num;
  2427. }
  2428. }
  2429.  
  2430. /* Send an updated endlevel status to everyone (if we are host) or host (if we are client)  */
  2431. void net_udp_send_endlevel_packet(void)
  2432. {
  2433.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  2434.         auto &Objects = LevelUniqueObjectState.Objects;
  2435.         auto &vcobjptr = Objects.vcptr;
  2436.         auto &vmobjptr = Objects.vmptr;
  2437.         int len = 0;
  2438.  
  2439.         if (multi_i_am_master())
  2440.         {
  2441.                 std::array<uint8_t, 2 + (5 * Players.size()) + sizeof(kill_matrix)> buf;
  2442.                 buf[len] = UPID_ENDLEVEL_H;                                                                                     len++;
  2443.                 buf[len] = LevelUniqueControlCenterState.Countdown_seconds_left;                                len++;
  2444.  
  2445.                 range_for (auto &i, Players)
  2446.                 {
  2447.                         buf[len] = i.connected;                                                         len++;
  2448.                         auto &objp = *vcobjptr(i.objnum);
  2449.                         auto &player_info = objp.ctype.player_info;
  2450.                         PUT_INTEL_SHORT(&buf[len], player_info.net_kills_total);
  2451.                         len += 2;
  2452.                         PUT_INTEL_SHORT(&buf[len], player_info.net_killed_total);
  2453.                         len += 2;
  2454.                 }
  2455.  
  2456.                 range_for (auto &i, kill_matrix)
  2457.                 {
  2458.                         range_for (auto &j, i)
  2459.                         {
  2460.                                 PUT_INTEL_SHORT(&buf[len], j);                          len += 2;
  2461.                         }
  2462.                 }
  2463.  
  2464.                 for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  2465.                         if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED)
  2466.                                 dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
  2467.         }
  2468.         else
  2469.         {
  2470.                 std::array<uint8_t, 8 + sizeof(kill_matrix[0])> buf;
  2471.                 buf[len] = UPID_ENDLEVEL_C;                                                                                     len++;
  2472.                 buf[len] = Player_num;                                                                                          len++;
  2473.                 buf[len] = get_local_player().connected;                                                        len++;
  2474.                 buf[len] = LevelUniqueControlCenterState.Countdown_seconds_left;                                len++;
  2475.                 auto &player_info = get_local_plrobj().ctype.player_info;
  2476.                 PUT_INTEL_SHORT(&buf[len], player_info.net_kills_total);
  2477.                 len += 2;
  2478.                 PUT_INTEL_SHORT(&buf[len], player_info.net_killed_total);
  2479.                 len += 2;
  2480.  
  2481.                 range_for (auto &i, kill_matrix[Player_num])
  2482.                 {
  2483.                         PUT_INTEL_SHORT(&buf[len], i);
  2484.                         len += 2;
  2485.                 }
  2486.  
  2487.                 dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, 0);
  2488.         }
  2489. }
  2490.  
  2491. static void net_udp_send_version_deny(const _sockaddr &sender_addr)
  2492. {
  2493.         std::array<uint8_t, UPID_VERSION_DENY_SIZE> buf;
  2494.         buf[0] = UPID_VERSION_DENY;
  2495.         PUT_INTEL_SHORT(&buf[1], DXX_VERSION_MAJORi);
  2496.         PUT_INTEL_SHORT(&buf[3], DXX_VERSION_MINORi);
  2497.         PUT_INTEL_SHORT(&buf[5], DXX_VERSION_MICROi);
  2498.         PUT_INTEL_SHORT(&buf[7], MULTI_PROTO_VERSION);
  2499.         dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
  2500. }
  2501.  
  2502. static void net_udp_process_version_deny(ubyte *data, const _sockaddr &)
  2503. {
  2504.         Netgame.protocol.udp.program_iver[0] = GET_INTEL_SHORT(&data[1]);
  2505.         Netgame.protocol.udp.program_iver[1] = GET_INTEL_SHORT(&data[3]);
  2506.         Netgame.protocol.udp.program_iver[2] = GET_INTEL_SHORT(&data[5]);
  2507.         Netgame.protocol.udp.program_iver[3] = GET_INTEL_SHORT(&data[7]);
  2508.         Netgame.protocol.udp.valid = -1;
  2509. }
  2510.  
  2511. // Check request for game info. Return 1 if sucessful; -1 if version mismatch; 0 if wrong game or some other error - do not process
  2512. static int net_udp_check_game_info_request(ubyte *data, int lite)
  2513. {
  2514.         short sender_iver[4] = { 0, 0, 0, 0 };
  2515.         char sender_id[4] = "";
  2516.  
  2517.         memcpy(&sender_id, &(data[1]), 4);
  2518.         sender_iver[0] = GET_INTEL_SHORT(&(data[5]));
  2519.         sender_iver[1] = GET_INTEL_SHORT(&(data[7]));
  2520.         sender_iver[2] = GET_INTEL_SHORT(&(data[9]));
  2521.         if (!lite)
  2522.                 sender_iver[3] = GET_INTEL_SHORT(&(data[11]));
  2523.        
  2524.         if (memcmp(&sender_id, UDP_REQ_ID, 4))
  2525.                 return 0;
  2526.        
  2527.         if ((sender_iver[0] != DXX_VERSION_MAJORi) || (sender_iver[1] != DXX_VERSION_MINORi) || (sender_iver[2] != DXX_VERSION_MICROi) || (!lite && sender_iver[3] != MULTI_PROTO_VERSION))
  2528.                 return -1;
  2529.                
  2530.         return 1;
  2531. }
  2532.  
  2533. namespace {
  2534.  
  2535. struct game_info_light
  2536. {
  2537.         std::array<uint8_t, UPID_GAME_INFO_LITE_SIZE_MAX> buf;
  2538. };
  2539.  
  2540. struct game_info_heavy
  2541. {
  2542.         std::array<uint8_t, UPID_GAME_INFO_SIZE_MAX> buf;
  2543. };
  2544.  
  2545. static uint_fast32_t net_udp_prepare_light_game_info(game_info_light &info)
  2546. {
  2547.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  2548.         uint_fast32_t len = 0;
  2549.         uint8_t *const buf = info.buf.data();
  2550.                 buf[0] = UPID_GAME_INFO_LITE;                                                           len++;                          // 1
  2551.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MAJORi);                                                 len += 2;                       // 3
  2552.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MINORi);                                                 len += 2;                       // 5
  2553.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MICROi);                                                 len += 2;                       // 7
  2554.                 PUT_INTEL_INT(buf + len, Netgame.protocol.udp.GameID);                          len += 4;                       // 11
  2555.                 PUT_INTEL_INT(buf + len, Netgame.levelnum);                                     len += 4;
  2556.                 buf[len] = Netgame.gamemode;                                                    len++;
  2557.                 buf[len] = Netgame.RefusePlayers;                                               len++;
  2558.                 buf[len] = Netgame.difficulty;                                                  len++;
  2559.         const auto tmpvar = get_effective_netgame_status(LevelUniqueControlCenterState);
  2560.                 buf[len] = tmpvar;                                                              len++;
  2561.                 buf[len] = Netgame.numconnected;                                                len++;
  2562.                 buf[len] = Netgame.max_numplayers;                                              len++;
  2563.                 buf[len] = pack_game_flags(&Netgame.game_flag).value;                                                   len++;
  2564.                 copy_from_ntstring(buf, len, Netgame.game_name);
  2565.                 copy_from_ntstring(buf, len, Netgame.mission_title);
  2566.                 copy_from_ntstring(buf, len, Netgame.mission_name);
  2567.         return len;
  2568. }
  2569.  
  2570. static uint_fast32_t net_udp_prepare_heavy_game_info(const _sockaddr *addr, ubyte info_upid, game_info_heavy &info)
  2571. {
  2572.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  2573.         uint8_t *const buf = info.buf.data();
  2574.         uint_fast32_t len = 0;
  2575.  
  2576.                 buf[0] = info_upid;                                                             len++;
  2577.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MAJORi);                                                 len += 2;
  2578.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MINORi);                                                 len += 2;
  2579.                 PUT_INTEL_SHORT(buf + len, DXX_VERSION_MICROi);                                                 len += 2;
  2580.                 ubyte &your_index = buf[len++];
  2581.                 your_index = MULTI_PNUM_UNDEF;
  2582.                 for (int i = 0; i < Netgame.players.size(); i++)
  2583.                 {
  2584.                         memcpy(&buf[len], Netgame.players[i].callsign.buffer(), CALLSIGN_LEN+1);        len += CALLSIGN_LEN+1;
  2585.                         buf[len] = Netgame.players[i].connected;                                len++;
  2586.                         buf[len] = Netgame.players[i].rank;                                     len++;
  2587.                         if (addr && *addr == Netgame.players[i].protocol.udp.addr)
  2588.                                 your_index = i;
  2589.                 }
  2590.                 PUT_INTEL_INT(buf + len, Netgame.levelnum);                                     len += 4;
  2591.                 buf[len] = Netgame.gamemode;                                                    len++;
  2592.                 buf[len] = Netgame.RefusePlayers;                                               len++;
  2593.                 buf[len] = Netgame.difficulty;                                                  len++;
  2594.         const auto tmpvar = get_effective_netgame_status(LevelUniqueControlCenterState);
  2595.                 buf[len] = tmpvar;                                                              len++;
  2596.                 buf[len] = Netgame.numplayers;                                                  len++;
  2597.                 buf[len] = Netgame.max_numplayers;                                              len++;
  2598.                 buf[len] = Netgame.numconnected;                                                len++;
  2599.                 buf[len] = pack_game_flags(&Netgame.game_flag).value;                                                   len++;
  2600.                 buf[len] = Netgame.team_vector;                                                 len++;
  2601.                 PUT_INTEL_INT(buf + len, Netgame.AllowedItems);                                 len += 4;
  2602.                 /* In cooperative games, never shuffle. */
  2603.                 PUT_INTEL_INT(&buf[len], (Game_mode & GM_MULTI_COOP) ? 0 : Netgame.ShufflePowerupSeed);                 len += 4;
  2604.                 buf[len] = Netgame.SecludedSpawns;                      len += 1;
  2605. #if defined(DXX_BUILD_DESCENT_I)
  2606.                 buf[len] = Netgame.SpawnGrantedItems.mask;                      len += 1;
  2607.                 buf[len] = Netgame.DuplicatePowerups.get_packed_field();                        len += 1;
  2608. #elif defined(DXX_BUILD_DESCENT_II)
  2609.                 PUT_INTEL_SHORT(buf + len, Netgame.SpawnGrantedItems.mask);                     len += 2;
  2610.                 PUT_INTEL_SHORT(buf + len, Netgame.DuplicatePowerups.get_packed_field());                       len += 2;
  2611.                 buf[len++] = Netgame.Allow_marker_view;
  2612.                 buf[len++] = Netgame.AlwaysLighting;
  2613.                 buf[len++] = Netgame.ThiefModifierFlags;
  2614.                 buf[len++] = Netgame.AllowGuidebot;
  2615. #endif
  2616.                 buf[len++] = Netgame.ShowEnemyNames;
  2617.                 buf[len++] = Netgame.BrightPlayers;
  2618.                 buf[len++] = Netgame.InvulAppear;
  2619.                 range_for (const auto &i, Netgame.team_name)
  2620.                 {
  2621.                         memcpy(&buf[len], static_cast<const char *>(i), (CALLSIGN_LEN+1));
  2622.                         len += CALLSIGN_LEN + 1;
  2623.                 }
  2624.                 range_for (auto &i, Netgame.locations)
  2625.                 {
  2626.                         PUT_INTEL_INT(buf + len, i);                            len += 4;
  2627.                 }
  2628.                 range_for (auto &i, Netgame.kills)
  2629.                 {
  2630.                         range_for (auto &j, i)
  2631.                         {
  2632.                                 PUT_INTEL_SHORT(buf + len, j);          len += 2;
  2633.                         }
  2634.                 }
  2635.                 PUT_INTEL_SHORT(buf + len, Netgame.segments_checksum);                  len += 2;
  2636.                 PUT_INTEL_SHORT(buf + len, Netgame.team_kills[0]);                              len += 2;
  2637.                 PUT_INTEL_SHORT(buf + len, Netgame.team_kills[1]);                              len += 2;
  2638.                 range_for (auto &i, Netgame.killed)
  2639.                 {
  2640.                         PUT_INTEL_SHORT(buf + len, i);                          len += 2;
  2641.                 }
  2642.                 range_for (auto &i, Netgame.player_kills)
  2643.                 {
  2644.                         PUT_INTEL_SHORT(buf + len, i);                  len += 2;
  2645.                 }
  2646.                 PUT_INTEL_INT(buf + len, Netgame.KillGoal);                                     len += 4;
  2647.                 PUT_INTEL_INT(buf + len, Netgame.PlayTimeAllowed.count());                              len += 4;
  2648.                 PUT_INTEL_INT(buf + len, Netgame.level_time);                                   len += 4;
  2649.                 PUT_INTEL_INT(buf + len, Netgame.control_invul_time);                           len += 4;
  2650.                 PUT_INTEL_INT(buf + len, Netgame.monitor_vector);                               len += 4;
  2651.                 range_for (auto &i, Netgame.player_score)
  2652.                 {
  2653.                         PUT_INTEL_INT(buf + len, i);                    len += 4;
  2654.                 }
  2655.                 range_for (auto &i, Netgame.net_player_flags)
  2656.                 {
  2657.                         buf[len] = static_cast<uint8_t>(i.get_player_flags());
  2658.                         len++;
  2659.                 }
  2660.                 PUT_INTEL_SHORT(buf + len, Netgame.PacketsPerSec);                              len += 2;
  2661.                 buf[len] = Netgame.PacketLossPrevention;                                        len++;
  2662.                 buf[len] = Netgame.NoFriendlyFire;                                              len++;
  2663.                 buf[len] = Netgame.MouselookFlags;                                              len++;
  2664.                 copy_from_ntstring(buf, len, Netgame.game_name);
  2665.                 copy_from_ntstring(buf, len, Netgame.mission_title);
  2666.                 copy_from_ntstring(buf, len, Netgame.mission_name);
  2667.         return len;
  2668. }
  2669.  
  2670. void net_udp_send_game_info_t::apply(const sockaddr &sender_addr, socklen_t senderlen, const _sockaddr *player_address, ubyte info_upid)
  2671. {
  2672.         // Send game info to someone who requested it
  2673.         net_udp_update_netgame(); // Update the values in the netgame struct
  2674.         union {
  2675.                 game_info_light light;
  2676.                 game_info_heavy heavy;
  2677.         };
  2678.         std::size_t len;
  2679.         const uint8_t *info;
  2680.         if (info_upid == UPID_GAME_INFO_LITE)
  2681.         {
  2682.                 len = net_udp_prepare_light_game_info(light);
  2683.                 info = light.buf.data();
  2684.         }
  2685.         else
  2686.         {
  2687.                 len = net_udp_prepare_heavy_game_info(player_address, info_upid, heavy);
  2688.                 info = heavy.buf.data();
  2689.         }
  2690.         dxx_sendto(sender_addr, senderlen, UDP_Socket[0], info, len, 0);
  2691. }
  2692.  
  2693. }
  2694.  
  2695. static unsigned MouselookMPFlag(const unsigned game_mode)
  2696. {
  2697.         return (game_mode & GM_MULTI_COOP) ? MouselookMode::MPCoop : MouselookMode::MPAnarchy;
  2698. }
  2699.  
  2700. static void net_udp_broadcast_game_info(ubyte info_upid)
  2701. {
  2702.         net_udp_send_game_info(GBcast, nullptr, info_upid);
  2703. #if DXX_USE_IPv6
  2704.         net_udp_send_game_info(GMcast_v6, nullptr, info_upid);
  2705. #endif
  2706. }
  2707.  
  2708. /* Send game info to all players in this game. Also send lite_info for people watching the netlist */
  2709. void net_udp_send_netgame_update()
  2710. {
  2711.         for (unsigned i = 1; i < N_players; ++i)
  2712.         {
  2713.                 if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
  2714.                         continue;
  2715.                 const auto &addr = Netgame.players[i].protocol.udp.addr;
  2716.                 net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
  2717.         }
  2718.         net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
  2719. }
  2720.  
  2721. static unsigned net_udp_send_request(void)
  2722. {
  2723.         // Send a request to join a game 'Netgame'.  Returns 0 if we can join this
  2724.         // game, non-zero if there is some problem.
  2725.         auto b = Netgame.players.begin();
  2726.         auto e = Netgame.players.end();
  2727.         auto i = std::find_if(b, e, [](const netplayer_info &ni) { return ni.connected != 0; });
  2728.         if (i == e)
  2729.         {
  2730.                 Assert(false);
  2731.                 return std::distance(b, i);
  2732.         }
  2733.         UDP_Seq.type = UPID_REQUEST;
  2734.         UDP_Seq.player.connected = Current_level_num;
  2735.  
  2736.         net_udp_send_sequence_packet(UDP_Seq, Netgame.players[0].protocol.udp.addr);
  2737.         return std::distance(b, i);
  2738. }
  2739.  
  2740. namespace dsx {
  2741. static void net_udp_process_game_info(const uint8_t *data, uint_fast32_t, const _sockaddr &game_addr, int lite_info, uint16_t TrackerGameID)
  2742. {
  2743.         uint_fast32_t len = 0;
  2744.         if (lite_info)
  2745.         {
  2746.                 UDP_netgame_info_lite recv_game;
  2747.                
  2748.                 recv_game.game_addr = game_addr;
  2749.                                                                                                 len++; // skip UPID byte
  2750.                 recv_game.program_iver[0] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
  2751.                 recv_game.program_iver[1] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
  2752.                 recv_game.program_iver[2] = GET_INTEL_SHORT(&(data[len]));                      len += 2;
  2753.                
  2754.                 if ((recv_game.program_iver[0] != DXX_VERSION_MAJORi) || (recv_game.program_iver[1] != DXX_VERSION_MINORi) || (recv_game.program_iver[2] != DXX_VERSION_MICROi))
  2755.                         return;
  2756.  
  2757.                 recv_game.GameID = GET_INTEL_INT(&(data[len]));                                 len += 4;
  2758.                 recv_game.levelnum = GET_INTEL_INT(&(data[len]));                               len += 4;
  2759.                 recv_game.gamemode = data[len];                                                 len++;
  2760.                 recv_game.RefusePlayers = data[len];                                            len++;
  2761.                 recv_game.difficulty = data[len];                                               len++;
  2762.                 recv_game.game_status = data[len];                                              len++;
  2763.                 recv_game.numconnected = data[len];                                             len++;
  2764.                 recv_game.max_numplayers = data[len];                                           len++;
  2765.                 packed_game_flags p;
  2766.                 p.value = data[len];
  2767.                 recv_game.game_flag = unpack_game_flags(&p);                                            len++;
  2768.                 copy_to_ntstring(data, len, recv_game.game_name);
  2769.                 copy_to_ntstring(data, len, recv_game.mission_title);
  2770.                 copy_to_ntstring(data, len, recv_game.mission_name);
  2771.                 recv_game.TrackerGameID = TrackerGameID;
  2772.        
  2773.                 num_active_udp_changed = 1;
  2774.                
  2775.                 auto r = partial_range(Active_udp_games, num_active_udp_games);
  2776.                 auto i = std::find_if(r.begin(), r.end(), [&recv_game](const UDP_netgame_info_lite &g) { return !d_stricmp(g.game_name.data(), recv_game.game_name.data()) && g.GameID == recv_game.GameID; });
  2777.                 if (i == Active_udp_games.end())
  2778.                 {
  2779.                         return;
  2780.                 }
  2781.                
  2782.                 *i = std::move(recv_game);
  2783. #if defined(DXX_BUILD_DESCENT_II)
  2784.                 // See if this is really a Hoard game
  2785.                 // If so, adjust all the data accordingly
  2786.                 if (HoardEquipped())
  2787.                 {
  2788.                         if (i->game_flag.hoard)
  2789.                         {
  2790.                                 i->gamemode=NETGAME_HOARD;
  2791.                                 i->game_status=NETSTAT_PLAYING;
  2792.                                
  2793.                                 if (i->game_flag.team_hoard)
  2794.                                         i->gamemode=NETGAME_TEAM_HOARD;
  2795.                                 if (i->game_flag.endlevel)
  2796.                                         i->game_status=NETSTAT_ENDLEVEL;
  2797.                                 if (i->game_flag.forming)
  2798.                                         i->game_status=NETSTAT_STARTING;
  2799.                         }
  2800.                 }
  2801. #endif
  2802.                 if (i == r.end())
  2803.                 {
  2804.                         if (i->numconnected)
  2805.                                 num_active_udp_games++;
  2806.                 }
  2807.                 else if (!i->numconnected)
  2808.                 {
  2809.                         // Delete this game
  2810.                         std::move(std::next(i), r.end(), i);
  2811.                         num_active_udp_games--;
  2812.                 }
  2813.         }
  2814.         else
  2815.         {
  2816.                 Netgame.players[0].protocol.udp.addr = game_addr;
  2817.  
  2818.                                                                                                 len++; // skip UPID byte
  2819.                 Netgame.protocol.udp.program_iver[0] = GET_INTEL_SHORT(&(data[len]));           len += 2;
  2820.                 Netgame.protocol.udp.program_iver[1] = GET_INTEL_SHORT(&(data[len]));           len += 2;
  2821.                 Netgame.protocol.udp.program_iver[2] = GET_INTEL_SHORT(&(data[len]));           len += 2;
  2822.                 Netgame.protocol.udp.your_index = data[len]; ++len;
  2823.                 range_for (auto &i, Netgame.players)
  2824.                 {
  2825.                         i.callsign.copy_lower(reinterpret_cast<const char *>(&data[len]), CALLSIGN_LEN);
  2826.                         len += CALLSIGN_LEN+1;
  2827.                         i.connected = data[len];                                len++;
  2828.                         i.rank = data[len];                                     len++;
  2829.                 }
  2830.                 Netgame.levelnum = GET_INTEL_INT(&(data[len]));                                 len += 4;
  2831.                 Netgame.gamemode = data[len];                                                   len++;
  2832.                 Netgame.RefusePlayers = data[len];                                              len++;
  2833.                 Netgame.difficulty = cast_clamp_difficulty(data[len]);
  2834.                 len++;
  2835.                 Netgame.game_status = data[len];                                                len++;
  2836.                 Netgame.numplayers = data[len];                                                 len++;
  2837.                 Netgame.max_numplayers = data[len];                                             len++;
  2838.                 Netgame.numconnected = data[len];                                               len++;
  2839.                 packed_game_flags p;
  2840.                 p.value = data[len];
  2841.                 Netgame.game_flag = unpack_game_flags(&p);                                              len++;
  2842.                 Netgame.team_vector = data[len];                                                len++;
  2843.                 Netgame.AllowedItems = GET_INTEL_INT(&(data[len]));                             len += 4;
  2844.                 Netgame.ShufflePowerupSeed = GET_INTEL_INT(&(data[len]));               len += 4;
  2845.                 Netgame.SecludedSpawns = data[len];             len += 1;
  2846. #if defined(DXX_BUILD_DESCENT_I)
  2847.                 Netgame.SpawnGrantedItems = data[len];          len += 1;
  2848.                 Netgame.DuplicatePowerups.set_packed_field(data[len]);                  len += 1;
  2849. #elif defined(DXX_BUILD_DESCENT_II)
  2850.                 Netgame.SpawnGrantedItems = GET_INTEL_SHORT(&(data[len]));              len += 2;
  2851.                 Netgame.DuplicatePowerups.set_packed_field(GET_INTEL_SHORT(&data[len])); len += 2;
  2852.                 if (unlikely(map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) > MAX_SUPER_LASER_LEVEL))
  2853.                         /* Bogus input - reject whole entry */
  2854.                         Netgame.SpawnGrantedItems = 0;
  2855.                 Netgame.Allow_marker_view = data[len++];
  2856.                 Netgame.AlwaysLighting = data[len++];
  2857.                 Netgame.ThiefModifierFlags = data[len++];
  2858.                 Netgame.AllowGuidebot = data[len++];
  2859. #endif
  2860.                 Netgame.ShowEnemyNames = data[len];                             len += 1;
  2861.                 Netgame.BrightPlayers = data[len];                              len += 1;
  2862.                 Netgame.InvulAppear = data[len];                                len += 1;
  2863.                 range_for (auto &i, Netgame.team_name)
  2864.                 {
  2865.                         i.copy(reinterpret_cast<const char *>(&data[len]), (CALLSIGN_LEN+1));
  2866.                         len += CALLSIGN_LEN + 1;
  2867.                 }
  2868.                 range_for (auto &i, Netgame.locations)
  2869.                 {
  2870.                         i = GET_INTEL_INT(&(data[len]));                        len += 4;
  2871.                 }
  2872.                 range_for (auto &i, Netgame.kills)
  2873.                 {
  2874.                         range_for (auto &j, i)
  2875.                         {
  2876.                                 j = GET_INTEL_SHORT(&(data[len]));              len += 2;
  2877.                         }
  2878.                 }
  2879.                 Netgame.segments_checksum = GET_INTEL_SHORT(&(data[len]));                      len += 2;
  2880.                 Netgame.team_kills[0] = GET_INTEL_SHORT(&(data[len]));                          len += 2;      
  2881.                 Netgame.team_kills[1] = GET_INTEL_SHORT(&(data[len]));                          len += 2;
  2882.                 range_for (auto &i, Netgame.killed)
  2883.                 {
  2884.                         i = GET_INTEL_SHORT(&(data[len]));                      len += 2;
  2885.                 }
  2886.                 range_for (auto &i, Netgame.player_kills)
  2887.                 {
  2888.                         i = GET_INTEL_SHORT(&(data[len]));              len += 2;
  2889.                 }
  2890.                 Netgame.KillGoal = GET_INTEL_INT(&(data[len]));                                 len += 4;
  2891.                 Netgame.PlayTimeAllowed = d_time_fix(GET_INTEL_INT(&data[len]));
  2892.                 len += 4;
  2893.                 Netgame.level_time = GET_INTEL_INT(&(data[len]));                               len += 4;
  2894.                 Netgame.control_invul_time = GET_INTEL_INT(&(data[len]));                       len += 4;
  2895.                 Netgame.monitor_vector = GET_INTEL_INT(&(data[len]));                           len += 4;
  2896.                 range_for (auto &i, Netgame.player_score)
  2897.                 {
  2898.                         i = GET_INTEL_INT(&(data[len]));                        len += 4;
  2899.                 }
  2900.                 range_for (auto &i, Netgame.net_player_flags)
  2901.                 {
  2902.                         i = player_flags(data[len]);
  2903.                         len++;
  2904.                 }
  2905.                 Netgame.PacketsPerSec = GET_INTEL_SHORT(&(data[len]));                          len += 2;
  2906.                 Netgame.PacketLossPrevention = data[len];                                       len++;
  2907.                 Netgame.NoFriendlyFire = data[len];                                             len++;
  2908.                 Netgame.MouselookFlags = data[len];                                             len++;
  2909.                 copy_to_ntstring(data, len, Netgame.game_name);
  2910.                 copy_to_ntstring(data, len, Netgame.mission_title);
  2911.                 copy_to_ntstring(data, len, Netgame.mission_name);
  2912.  
  2913.                 Netgame.protocol.udp.valid = 1; // This game is valid! YAY!
  2914.         }
  2915. }
  2916. }
  2917.  
  2918. static void net_udp_process_dump(ubyte *data, int, const _sockaddr &sender_addr)
  2919. {
  2920.         // Our request for join was denied.  Tell the user why.
  2921.         if (sender_addr != Netgame.players[0].protocol.udp.addr)
  2922.                 return;
  2923.  
  2924.         switch (data[1])
  2925.         {
  2926.                 case DUMP_PKTTIMEOUT:
  2927.                 case DUMP_KICKED:
  2928.                         if (Game_wind)
  2929.                                 window_set_visible(Game_wind, 0);
  2930.                         if (data[1] == DUMP_PKTTIMEOUT)
  2931.                                 nm_messagebox(NULL, 1, TXT_OK, "You were removed from the game.\nYou failed receiving important\npackets. Sorry.");
  2932.                         if (data[1] == DUMP_KICKED)
  2933.                                 nm_messagebox(NULL, 1, TXT_OK, "You were kicked by Host!");
  2934.                         if (Game_wind)
  2935.                                 window_set_visible(Game_wind, 1);
  2936.                         multi_quit_game = 1;
  2937.                         game_leave_menus();
  2938.                         break;
  2939.                 default:
  2940.                         if (data[1] > DUMP_LEVEL) // invalid dump... heh
  2941.                                 break;
  2942.                         Network_status = NETSTAT_MENU; // stop us from sending before message
  2943.                         nm_messagebox_str(NULL, TXT_OK, NET_DUMP_STRINGS(data[1]));
  2944.                         Network_status = NETSTAT_MENU;
  2945.                         multi_reset_stuff();
  2946.                         break;
  2947.         }
  2948. }
  2949.  
  2950. static void net_udp_process_request(UDP_sequence_packet *their)
  2951. {
  2952.         // Player is ready to receieve a sync packet
  2953.         for (unsigned i = 0; i < N_players; ++i)
  2954.                 if (their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr && !d_stricmp(their->player.callsign, Netgame.players[i].callsign))
  2955.                 {
  2956.                         vmplayerptr(i)->connected = CONNECT_PLAYING;
  2957.                         Netgame.players[i].LastPacketTime = timer_query();
  2958.                         break;
  2959.                 }
  2960. }
  2961.  
  2962. static void net_udp_process_packet(ubyte *data, const _sockaddr &sender_addr, int length )
  2963. {
  2964.         UDP_sequence_packet their{};
  2965.  
  2966.         switch (data[0])
  2967.         {
  2968.                 case UPID_VERSION_DENY:
  2969.                         if (multi_i_am_master() || length != UPID_VERSION_DENY_SIZE)
  2970.                                 break;
  2971.                         net_udp_process_version_deny(data, sender_addr);
  2972.                         break;
  2973.                 case UPID_GAME_INFO_REQ:
  2974.                 {
  2975.                         int result = 0;
  2976.                         static fix64 last_full_req_time = 0;
  2977.                         if (!multi_i_am_master() || length != UPID_GAME_INFO_REQ_SIZE)
  2978.                                 break;
  2979.                         if (timer_query() < last_full_req_time+(F1_0/2)) // answer 2 times per second max
  2980.                                 break;
  2981.                         last_full_req_time = timer_query();
  2982.                         result = net_udp_check_game_info_request(data, 0);
  2983.                         if (result == -1)
  2984.                                 net_udp_send_version_deny(sender_addr);
  2985.                         else if (result == 1)
  2986.                                 net_udp_send_game_info(sender_addr, &sender_addr, UPID_GAME_INFO);
  2987.                         break;
  2988.                 }
  2989.                 case UPID_GAME_INFO:
  2990.                         if (multi_i_am_master() || length > UPID_GAME_INFO_SIZE_MAX)
  2991.                                 break;
  2992.                         net_udp_process_game_info(data, length, sender_addr, 0);
  2993.                         break;
  2994.                 case UPID_GAME_INFO_LITE_REQ:
  2995.                 {
  2996.                         static fix64 last_lite_req_time = 0;
  2997.                         if (!multi_i_am_master() || length != UPID_GAME_INFO_LITE_REQ_SIZE)
  2998.                                 break;
  2999.                         if (timer_query() < last_lite_req_time+(F1_0/8))// answer 8 times per second max
  3000.                                 break;
  3001.                         last_lite_req_time = timer_query();
  3002.                         if (net_udp_check_game_info_request(data, 1) == 1)
  3003.                                 net_udp_send_game_info(sender_addr, &sender_addr, UPID_GAME_INFO_LITE);
  3004.                         break;
  3005.                 }
  3006.                 case UPID_GAME_INFO_LITE:
  3007.                         if (multi_i_am_master() || length > UPID_GAME_INFO_LITE_SIZE_MAX)
  3008.                                 break;
  3009.                         net_udp_process_game_info(data, length, sender_addr, 1);
  3010.                         break;
  3011.                 case UPID_DUMP:
  3012.                         if (multi_i_am_master() || Netgame.players[0].protocol.udp.addr != sender_addr || length != UPID_DUMP_SIZE)
  3013.                                 break;
  3014.                         if ((Network_status == NETSTAT_WAITING) || (Network_status == NETSTAT_PLAYING))
  3015.                                 net_udp_process_dump(data, length, sender_addr);
  3016.                         break;
  3017.                 case UPID_ADDPLAYER:
  3018.                         if (multi_i_am_master() || Netgame.players[0].protocol.udp.addr != sender_addr || length != UPID_SEQUENCE_SIZE)
  3019.                                 break;
  3020.                         net_udp_receive_sequence_packet(data, &their, sender_addr);
  3021.                         net_udp_new_player(&their);
  3022.                         break;
  3023.                 case UPID_REQUEST:
  3024.                         if (!multi_i_am_master() || length != UPID_SEQUENCE_SIZE)
  3025.                                 break;
  3026.                         net_udp_receive_sequence_packet(data, &their, sender_addr);
  3027.                         if (Network_status == NETSTAT_STARTING)
  3028.                         {
  3029.                                 // Someone wants to join our game!
  3030.                                 net_udp_add_player(&their);
  3031.                         }
  3032.                         else if (Network_status == NETSTAT_WAITING)
  3033.                         {
  3034.                                 // Someone is ready to recieve a sync packet
  3035.                                 net_udp_process_request(&their);
  3036.                         }
  3037.                         else if (Network_status == NETSTAT_PLAYING)
  3038.                         {
  3039.                                 // Someone wants to join a game in progress!
  3040.                                 if (Netgame.RefusePlayers)
  3041.                                         net_udp_do_refuse_stuff (&their);
  3042.                                 else
  3043.                                         net_udp_welcome_player(&their);
  3044.                         }
  3045.                         break;
  3046.                 case UPID_QUIT_JOINING:
  3047.                         if (!multi_i_am_master() || length != UPID_SEQUENCE_SIZE)
  3048.                                 break;
  3049.                         net_udp_receive_sequence_packet(data, &their, sender_addr);
  3050.                         if (Network_status == NETSTAT_STARTING)
  3051.                                 net_udp_remove_player( &their );
  3052.                         else if ((Network_status == NETSTAT_PLAYING) && (Network_send_objects))
  3053.                                 net_udp_stop_resync( &their );
  3054.                         break;
  3055.                 case UPID_SYNC:
  3056.                         if (multi_i_am_master() || length > UPID_GAME_INFO_SIZE_MAX || Network_status != NETSTAT_WAITING)
  3057.                                 break;
  3058.                         net_udp_read_sync_packet(data, length, sender_addr);
  3059.                         break;
  3060.                 case UPID_OBJECT_DATA:
  3061.                         if (multi_i_am_master() || length > UPID_MAX_SIZE || Network_status != NETSTAT_WAITING)
  3062.                                 break;
  3063.                         net_udp_read_object_packet(data);
  3064.                         break;
  3065.                 case UPID_PING:
  3066.                         if (multi_i_am_master() || length != UPID_PING_SIZE)
  3067.                                 break;
  3068.                         net_udp_process_ping(data, sender_addr);
  3069.                         break;
  3070.                 case UPID_PONG:
  3071.                         if (!multi_i_am_master() || length != UPID_PONG_SIZE)
  3072.                                 break;
  3073.                         net_udp_process_pong(data, sender_addr);
  3074.                         break;
  3075.                 case UPID_ENDLEVEL_H:
  3076.                         if ((!multi_i_am_master()) && ((Network_status == NETSTAT_ENDLEVEL) || (Network_status == NETSTAT_PLAYING)))
  3077.                                 net_udp_read_endlevel_packet(data, sender_addr);
  3078.                         break;
  3079.                 case UPID_ENDLEVEL_C:
  3080.                         if ((multi_i_am_master()) && ((Network_status == NETSTAT_ENDLEVEL) || (Network_status == NETSTAT_PLAYING)))
  3081.                                 net_udp_read_endlevel_packet(data, sender_addr);
  3082.                         break;
  3083.                 case UPID_PDATA:
  3084.                         net_udp_process_pdata( data, length, sender_addr );
  3085.                         break;
  3086.                 case UPID_MDATA_PNORM:
  3087.                         net_udp_process_mdata( data, length, sender_addr, 0 );
  3088.                         break;
  3089.                 case UPID_MDATA_PNEEDACK:
  3090.                         net_udp_process_mdata( data, length, sender_addr, 1 );
  3091.                         break;
  3092.                 case UPID_MDATA_ACK:
  3093.                         net_udp_noloss_got_ack(data, length);
  3094.                         break;
  3095. #if DXX_USE_TRACKER
  3096.                 case UPID_TRACKER_GAMEINFO:
  3097.                         udp_tracker_process_game( data, length, sender_addr );
  3098.                         break;
  3099.                 case UPID_TRACKER_ACK:
  3100.                         if (!multi_i_am_master())
  3101.                                 break;
  3102.                         udp_tracker_process_ack( data, length, sender_addr );
  3103.                         break;
  3104.                 case UPID_TRACKER_HOLEPUNCH:
  3105.                         udp_tracker_process_holepunch( data, length, sender_addr );
  3106.                         break;
  3107. #endif
  3108.                 default:
  3109.                         con_printf(CON_DEBUG, "unknown packet type received - type %i", data[0]);
  3110.                         break;
  3111.         }
  3112. }
  3113.  
  3114. // Packet for end of level syncing
  3115. void net_udp_read_endlevel_packet(const uint8_t *data, const _sockaddr &sender_addr)
  3116. {
  3117.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  3118.         auto &Objects = LevelUniqueObjectState.Objects;
  3119.         auto &vmobjptr = Objects.vmptr;
  3120.         int len = 0;
  3121.         ubyte tmpvar = 0;
  3122.        
  3123.         if (multi_i_am_master())
  3124.         {
  3125.                 playernum_t pnum = data[1];
  3126.  
  3127.                 if (Netgame.players[pnum].protocol.udp.addr != sender_addr)
  3128.                         return;
  3129.  
  3130.                 len += 2;
  3131.  
  3132.                 if (static_cast<int>(data[len]) == CONNECT_DISCONNECTED)
  3133.                         multi_disconnect_player(pnum);
  3134.                 vmplayerptr(pnum)->connected = data[len];                                       len++;
  3135.                 tmpvar = data[len];                                                     len++;
  3136.                 if (Network_status != NETSTAT_PLAYING && vcplayerptr(pnum)->connected == CONNECT_PLAYING && tmpvar < LevelUniqueControlCenterState.Countdown_seconds_left)
  3137.                         LevelUniqueControlCenterState.Countdown_seconds_left = tmpvar;
  3138.                 auto &objp = *vmobjptr(vcplayerptr(pnum)->objnum);
  3139.                 auto &player_info = objp.ctype.player_info;
  3140.                 player_info.net_kills_total = GET_INTEL_SHORT(&data[len]);
  3141.                 len += 2;
  3142.                 player_info.net_killed_total = GET_INTEL_SHORT(&data[len]);
  3143.                 len += 2;
  3144.  
  3145.                 range_for (auto &i, kill_matrix[pnum])
  3146.                 {
  3147.                         i = GET_INTEL_SHORT(&(data[len]));              len += 2;
  3148.                 }
  3149.                 if (vcplayerptr(pnum)->connected)
  3150.                         Netgame.players[pnum].LastPacketTime = timer_query();
  3151.         }
  3152.         else
  3153.         {
  3154.                 if (Netgame.players[0].protocol.udp.addr != sender_addr)
  3155.                         return;
  3156.  
  3157.                 len++;
  3158.  
  3159.                 tmpvar = data[len];                                                     len++;
  3160.                 if (Network_status != NETSTAT_PLAYING && tmpvar < LevelUniqueControlCenterState.Countdown_seconds_left)
  3161.                         LevelUniqueControlCenterState.Countdown_seconds_left = tmpvar;
  3162.  
  3163.                 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
  3164.                 {
  3165.                         if (i == Player_num)
  3166.                         {
  3167.                                 len += 5;
  3168.                                 continue;
  3169.                         }
  3170.  
  3171.                         if (static_cast<int>(data[len]) == CONNECT_DISCONNECTED)
  3172.                                 multi_disconnect_player(i);
  3173.                         auto &objp = *vmobjptr(vcplayerptr(i)->objnum);
  3174.                         auto &player_info = objp.ctype.player_info;
  3175.                         vmplayerptr(i)->connected = data[len];                          len++;
  3176.                         player_info.net_kills_total = GET_INTEL_SHORT(&data[len]);
  3177.                         len += 2;
  3178.                         player_info.net_killed_total = GET_INTEL_SHORT(&data[len]);
  3179.                         len += 2;
  3180.  
  3181.                         if (vcplayerptr(i)->connected)
  3182.                                 Netgame.players[i].LastPacketTime = timer_query();
  3183.                 }
  3184.  
  3185.                 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
  3186.                 {
  3187.                         for (playernum_t j = 0; j < MAX_PLAYERS; j++)
  3188.                         {
  3189.                                 if (i != Player_num)
  3190.                                 {
  3191.                                         kill_matrix[i][j] = GET_INTEL_SHORT(&(data[len]));
  3192.                                 }
  3193.                                                                                         len += 2;
  3194.                         }
  3195.                 }
  3196.         }
  3197. }
  3198.  
  3199. /*
  3200.  * Polling loop waiting for sync packet to start game after having sent request
  3201.  */
  3202. static int net_udp_sync_poll( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
  3203. {
  3204.         static fix64 t1 = 0;
  3205.         int rval = 0;
  3206.  
  3207.         if (event.type != EVENT_WINDOW_DRAW)
  3208.                 return 0;
  3209.         net_udp_listen();
  3210.  
  3211.         // Leave if Host disconnects
  3212.         if (Netgame.players[0].connected == CONNECT_DISCONNECTED)
  3213.                 rval = -2;
  3214.  
  3215.         if (Network_status != NETSTAT_WAITING)  // Status changed to playing, exit the menu
  3216.                 rval = -2;
  3217.  
  3218.         if (Network_status != NETSTAT_MENU && !Network_rejoined && (timer_query() > t1+F1_0*2))
  3219.         {
  3220.                 // Poll time expired, re-send request
  3221.                
  3222.                 t1 = timer_query();
  3223.  
  3224.                 auto i = net_udp_send_request();
  3225.                 if (i >= MAX_PLAYERS)
  3226.                         rval = -2;
  3227.         }
  3228.        
  3229.         return rval;
  3230. }
  3231.  
  3232. static int net_udp_start_poll(newmenu *, const d_event &event, start_poll_menu_items *const items)
  3233. {
  3234.         if (event.type != EVENT_WINDOW_DRAW)
  3235.                 return 0;
  3236.         Assert(Network_status == NETSTAT_STARTING);
  3237.  
  3238.         auto &menus = items->m;
  3239.         const unsigned nitems = menus.size();
  3240.         menus[0].value = 1;
  3241.         range_for (auto &i, partial_range(menus, N_players, nitems))
  3242.                 i.value = 0;
  3243.  
  3244.         const auto predicate = [](const newmenu_item &i) {
  3245.                 return i.value;
  3246.         };
  3247.         const auto nm = std::count_if(menus.begin(), std::next(menus.begin(), nitems), predicate);
  3248.         if ( nm > Netgame.max_numplayers ) {
  3249.                 nm_messagebox( TXT_ERROR, 1, TXT_OK, "%s %d %s", TXT_SORRY_ONLY, Netgame.max_numplayers, TXT_NETPLAYERS_IN );
  3250.                 // Turn off the last player highlighted
  3251.                 for (int i = N_players; i > 0; i--)
  3252.                         if (menus[i].value == 1)
  3253.                         {
  3254.                                 menus[i].value = 0;
  3255.                                 break;
  3256.                         }
  3257.         }
  3258.  
  3259.         net_udp_listen();
  3260.  
  3261.         for (int i=0; i<N_players; i++ ) // fill this in always in case players change but not their numbers
  3262.         {
  3263.                 const auto &&rankstr = GetRankStringWithSpace(Netgame.players[i].rank);
  3264.                 snprintf(menus[i].text, 45, "%d. %s%s%-20s", i+1, rankstr.first, rankstr.second, static_cast<const char *>(Netgame.players[i].callsign));
  3265.         }
  3266.  
  3267.         const unsigned players_last_poll = items->get_player_count();
  3268.         if (players_last_poll == Netgame.numplayers)
  3269.                 return 0;
  3270.         items->set_player_count(Netgame.numplayers);
  3271.         // A new player
  3272.         if (players_last_poll < Netgame.numplayers)
  3273.         {
  3274.                 digi_play_sample (SOUND_HUD_MESSAGE,F1_0);
  3275.                 if (N_players <= Netgame.max_numplayers)
  3276.                         menus[N_players-1].value = 1;
  3277.         }
  3278.         else    // One got removed...
  3279.         {
  3280.                 digi_play_sample (SOUND_HUD_KILL,F1_0);
  3281.  
  3282.                 const auto j = std::min(N_players, static_cast<unsigned>(Netgame.max_numplayers));
  3283.                 /* Reset all the user's choices, since there is insufficient
  3284.                  * integration to move the checks based on the position of the
  3285.                  * departed player(s).  Without this reset, names would move up
  3286.                  * one line, but checkboxes would not.
  3287.                  */
  3288.                 range_for (auto &i, partial_range(menus, j))
  3289.                         i.value = 1;
  3290.                 range_for (auto &i, partial_range(menus, j, N_players))
  3291.                         i.value = 0;
  3292.                 range_for (auto &i, partial_range(menus, N_players, players_last_poll))
  3293.                 {
  3294.                         /* The default format string is "%d. "
  3295.                          * For single digit numbers, [3] is the first character
  3296.                          * after the space.  For double digit numbers, [3] is the
  3297.                          * space.  Users cannot see the trailing space or its
  3298.                          * absence, so always overwrite [3].  This would break if
  3299.                          * the menu allowed more than 99 players.
  3300.                          */
  3301.                         i.text[3] = 0;
  3302.                         i.value = 0;
  3303.                 }
  3304.         }
  3305.         return 0;
  3306. }
  3307.  
  3308. #if DXX_USE_TRACKER
  3309. #define DXX_UDP_MENU_TRACKER_OPTION(VERB)       \
  3310.         DXX_MENUITEM(VERB, CHECK, "Track this game on", opt_tracker, Netgame.Tracker) \
  3311.         DXX_MENUITEM(VERB, TEXT, tracker_addr_txt, opt_tracker_addr)    \
  3312.         DXX_MENUITEM(VERB, CHECK, "Enable tracker NAT hole punch", opt_tracker_nathp, TrackerNATWarned) \
  3313.  
  3314. #else
  3315. #define DXX_UDP_MENU_TRACKER_OPTION(VERB)
  3316. #endif
  3317.  
  3318. #if defined(DXX_BUILD_DESCENT_I)
  3319. #define D2X_UDP_MENU_OPTIONS(VERB)      \
  3320.  
  3321. #elif defined(DXX_BUILD_DESCENT_II)
  3322. #define D2X_UDP_MENU_OPTIONS(VERB)      \
  3323.         DXX_MENUITEM(VERB, CHECK, "Allow Marker camera views", opt_marker_view, Netgame.Allow_marker_view)      \
  3324.         DXX_MENUITEM(VERB, CHECK, "Indestructible lights", opt_light, Netgame.AlwaysLighting)   \
  3325.         DXX_MENUITEM(VERB, CHECK, "Remove Thief at level start", opt_thief_presence, thief_absent)      \
  3326.         DXX_MENUITEM(VERB, CHECK, "Prevent Thief Stealing Energy Weapons", opt_thief_steal_energy, thief_cannot_steal_energy_weapons)   \
  3327.         DXX_MENUITEM(VERB, CHECK, "Allow Guidebot (coop only; experimental)", opt_guidebot_enabled, Netgame.AllowGuidebot)      \
  3328.  
  3329. #endif
  3330.  
  3331. constexpr std::integral_constant<unsigned, F1_0 * 60> reactor_invul_time_mini_scale{};
  3332. constexpr std::integral_constant<unsigned, 5 * reactor_invul_time_mini_scale> reactor_invul_time_scale{};
  3333.  
  3334. #if defined(DXX_BUILD_DESCENT_I)
  3335. #define D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
  3336.  
  3337. #elif defined(DXX_BUILD_DESCENT_II)
  3338. #define D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
  3339.         DXX_MENUITEM(VERB, SLIDER, extraAccessory, opt_extra_accessory, accessory, 0, (1 << packed_netduplicate_items::accessory_width) - 1)    \
  3340.  
  3341. #endif
  3342.  
  3343. #define DXX_DUPLICATE_POWERUP_OPTIONS(VERB)                                \
  3344.         DXX_MENUITEM(VERB, SLIDER, extraPrimary, opt_extra_primary, primary, 0, (1 << packed_netduplicate_items::primary_width) - 1)    \
  3345.         DXX_MENUITEM(VERB, SLIDER, extraSecondary, opt_extra_secondary, secondary, 0, (1 << packed_netduplicate_items::secondary_width) - 1)    \
  3346.         D2X_DUPLICATE_POWERUP_OPTIONS(VERB)                                
  3347.  
  3348. #define DXX_UDP_MENU_OPTIONS(VERB)                                          \
  3349.         DXX_MENUITEM(VERB, TEXT, "Game Options", game_label)                         \
  3350.         DXX_MENUITEM(VERB, SLIDER, get_annotated_difficulty_string(Netgame.difficulty), opt_difficulty, difficulty, Difficulty_0, Difficulty_4) \
  3351.         DXX_MENUITEM(VERB, SCALE_SLIDER, srinvul, opt_cinvul, Netgame.control_invul_time, 0, 10, reactor_invul_time_scale)      \
  3352.         DXX_MENUITEM(VERB, SLIDER, PlayText, opt_playtime, PlayTimeAllowed, 0, 12)      \
  3353.         DXX_MENUITEM(VERB, SLIDER, KillText, opt_killgoal, Netgame.KillGoal, 0, 20)     \
  3354.         DXX_MENUITEM(VERB, TEXT, "", blank_1)                                     \
  3355.         DXX_MENUITEM(VERB, TEXT, "Duplicate Powerups", duplicate_label)           \
  3356.         DXX_DUPLICATE_POWERUP_OPTIONS(VERB)                                           \
  3357.         DXX_MENUITEM(VERB, TEXT, "", blank_5)                                     \
  3358.         DXX_MENUITEM(VERB, TEXT, "Spawn Options", spawn_label)                     \
  3359.         DXX_MENUITEM(VERB, SLIDER, SecludedSpawnText, opt_secluded_spawns, Netgame.SecludedSpawns, 0, MAX_PLAYERS - 1)  \
  3360.         DXX_MENUITEM(VERB, SLIDER, SpawnInvulnerableText, opt_start_invul, Netgame.InvulAppear, 0, 8)   \
  3361.         DXX_MENUITEM(VERB, TEXT, "", blank_2)                                     \
  3362.         DXX_MENUITEM(VERB, TEXT, "Object Options", powerup_label)                       \
  3363.         DXX_MENUITEM(VERB, CHECK, "Shuffle powerups in anarchy games", opt_shuffle_powerups, Netgame.ShufflePowerupSeed)        \
  3364.         DXX_MENUITEM(VERB, MENU, "Set Objects allowed...", opt_setpower)                 \
  3365.         DXX_MENUITEM(VERB, MENU, "Set Objects granted at spawn...", opt_setgrant)       \
  3366.         DXX_MENUITEM(VERB, TEXT, "", blank_3)                                     \
  3367.         DXX_MENUITEM(VERB, TEXT, "Misc. Options", misc_label)                       \
  3368.         DXX_MENUITEM(VERB, CHECK, TXT_SHOW_ON_MAP, opt_show_on_map, Netgame.game_flag.show_on_map)      \
  3369.         D2X_UDP_MENU_OPTIONS(VERB)                                              \
  3370.         DXX_MENUITEM(VERB, CHECK, "Bright player ships", opt_bright, Netgame.BrightPlayers)     \
  3371.         DXX_MENUITEM(VERB, CHECK, "Show enemy names on HUD", opt_show_names, Netgame.ShowEnemyNames)    \
  3372.         DXX_MENUITEM(VERB, CHECK, "No friendly fire (Team, Coop)", opt_ffire, Netgame.NoFriendlyFire)   \
  3373.         DXX_MENUITEM(VERB, FCHECK, (Game_mode & GM_MULTI_COOP) ? "Allow coop mouselook" : "Allow anarchy mouselook", opt_mouselook, Netgame.MouselookFlags, MouselookMPFlag(Game_mode)) \
  3374.         DXX_MENUITEM(VERB, TEXT, "", blank_4)                                     \
  3375.         DXX_MENUITEM_AUTOSAVE_LABEL_INPUT(VERB) \
  3376.         DXX_MENUITEM(VERB, TEXT, "", blank_6)                                     \
  3377.         DXX_MENUITEM(VERB, TEXT, "Network Options", network_label)                     \
  3378.         DXX_MENUITEM(VERB, TEXT, "Packets per second (" DXX_STRINGIZE_PPS(MIN_PPS) " - " DXX_STRINGIZE_PPS(MAX_PPS) ")", opt_label_pps) \
  3379.         DXX_MENUITEM(VERB, INPUT, packstring, opt_packets)      \
  3380.         DXX_MENUITEM(VERB, TEXT, "Network port", opt_label_port)        \
  3381.         DXX_MENUITEM(VERB, INPUT, portstring, opt_port) \
  3382.         DXX_UDP_MENU_TRACKER_OPTION(VERB)
  3383.  
  3384. #define DXX_STRINGIZE_PPS2(X)   #X
  3385. #define DXX_STRINGIZE_PPS(X)    DXX_STRINGIZE_PPS2(X)
  3386.  
  3387. static void net_udp_set_power (void)
  3388. {
  3389.         newmenu_item m[multi_allow_powerup_text.size()];
  3390.         for (int i = 0; i < multi_allow_powerup_text.size(); i++)
  3391.         {
  3392.                 nm_set_item_checkbox(m[i], multi_allow_powerup_text[i], (Netgame.AllowedItems >> i) & 1);
  3393.         }
  3394.  
  3395.         newmenu_do1( NULL, "Objects to allow", MULTI_ALLOW_POWERUP_MAX, m, unused_newmenu_subfunction, unused_newmenu_userdata, 0 );
  3396.  
  3397.         Netgame.AllowedItems &= ~NETFLAG_DOPOWERUP;
  3398.         for (int i = 0; i < multi_allow_powerup_text.size(); i++)
  3399.                 if (m[i].value)
  3400.                         Netgame.AllowedItems |= (1 << i);
  3401. }
  3402.  
  3403. #if defined(DXX_BUILD_DESCENT_I)
  3404. #define D2X_GRANT_POWERUP_MENU(VERB)
  3405. #elif defined(DXX_BUILD_DESCENT_II)
  3406. #define D2X_GRANT_POWERUP_MENU(VERB)    \
  3407.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_GAUSS, opt_gauss, menu_bit_wrapper(flags, NETGRANT_GAUSS))      \
  3408.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_HELIX, opt_helix, menu_bit_wrapper(flags, NETGRANT_HELIX))      \
  3409.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_PHOENIX, opt_phoenix, menu_bit_wrapper(flags, NETGRANT_PHOENIX))        \
  3410.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_OMEGA, opt_omega, menu_bit_wrapper(flags, NETGRANT_OMEGA))      \
  3411.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_AFTERBURNER, opt_afterburner, menu_bit_wrapper(flags, NETGRANT_AFTERBURNER))    \
  3412.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_AMMORACK, opt_ammo_rack, menu_bit_wrapper(flags, NETGRANT_AMMORACK))    \
  3413.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_CONVERTER, opt_converter, menu_bit_wrapper(flags, NETGRANT_CONVERTER))  \
  3414.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_HEADLIGHT, opt_headlight, menu_bit_wrapper(flags, NETGRANT_HEADLIGHT))  \
  3415.  
  3416. #endif
  3417.  
  3418. #define DXX_GRANT_POWERUP_MENU(VERB)    \
  3419.         DXX_MENUITEM(VERB, NUMBER, "Laser level", opt_laser_level, menu_number_bias_wrapper<1>(laser_level), LASER_LEVEL_1 + 1, DXX_MAXIMUM_LASER_LEVEL + 1)    \
  3420.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_QUAD, opt_quad_lasers, menu_bit_wrapper(flags, NETGRANT_QUAD))  \
  3421.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_VULCAN, opt_vulcan, menu_bit_wrapper(flags, NETGRANT_VULCAN))   \
  3422.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_SPREAD, opt_spreadfire, menu_bit_wrapper(flags, NETGRANT_SPREAD))       \
  3423.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_PLASMA, opt_plasma, menu_bit_wrapper(flags, NETGRANT_PLASMA))   \
  3424.         DXX_MENUITEM(VERB, CHECK, NETFLAG_LABEL_FUSION, opt_fusion, menu_bit_wrapper(flags, NETGRANT_FUSION))   \
  3425.         D2X_GRANT_POWERUP_MENU(VERB)
  3426.  
  3427. namespace {
  3428.  
  3429. class more_game_options_menu_items
  3430. {
  3431.         char packstring[sizeof("99")];
  3432.         std::array<char, sizeof("65535")> portstring;
  3433.         char srinvul[sizeof("Reactor life: 50 min")];
  3434.         char PlayText[sizeof("Max time: 50 min")];
  3435.         char SpawnInvulnerableText[sizeof("Invul. Time: 0.0 sec")];
  3436.         char SecludedSpawnText[sizeof("Use 0 Furthest Sites")];
  3437.         char KillText[sizeof("Kill goal: 000 kills")];
  3438.         char extraPrimary[sizeof("Primaries: 0")];
  3439.         char extraSecondary[sizeof("Secondaries: 0")];
  3440. #if defined(DXX_BUILD_DESCENT_II)
  3441.         char extraAccessory[sizeof("Accessories: 0")];
  3442. #endif
  3443. #if DXX_USE_TRACKER
  3444.         char tracker_addr_txt[sizeof("65535") + 28];
  3445. #endif
  3446.         human_readable_mmss_time<decltype(d_gameplay_options::AutosaveInterval)::rep> AutosaveInterval;
  3447.         using menu_array = std::array<newmenu_item, DXX_UDP_MENU_OPTIONS(COUNT)>;
  3448.         menu_array m;
  3449.         static const char *get_annotated_difficulty_string(const Difficulty_level_type d)
  3450.         {
  3451.                 static const std::array<char[20], 5> text{{
  3452.                         "Difficulty: Trainee",
  3453.                         "Difficulty: Rookie",
  3454.                         "Difficulty: Hotshot",
  3455.                         "Difficulty: Ace",
  3456.                         "Difficulty: Insane"
  3457.                 }};
  3458.                 switch (d)
  3459.                 {
  3460.                         case Difficulty_0:
  3461.                         case Difficulty_1:
  3462.                         case Difficulty_2:
  3463.                         case Difficulty_3:
  3464.                         case Difficulty_4:
  3465.                                 return text[d];
  3466.                         default:
  3467.                                 return &text[3][16];
  3468.                 }
  3469.         }
  3470.         static int handler(newmenu *, const d_event &event, more_game_options_menu_items *items);
  3471. public:
  3472.         menu_array &get_menu_items()
  3473.         {
  3474.                 return m;
  3475.         }
  3476.         void update_difficulty_string(const Difficulty_level_type difficulty)
  3477.         {
  3478.                 /* Cast away const because newmenu_item uses `char *text` even
  3479.                  * for fields where text is treated as `const char *`.
  3480.                  */
  3481.                 m[opt_difficulty].text = const_cast<char *>(get_annotated_difficulty_string(difficulty));
  3482.         }
  3483.         void update_extra_primary_string(unsigned primary)
  3484.         {
  3485.                 snprintf(extraPrimary, sizeof(extraPrimary), "Primaries: %u", primary);
  3486.         }
  3487.         void update_extra_secondary_string(unsigned secondary)
  3488.         {
  3489.                 snprintf(extraSecondary, sizeof(extraSecondary), "Secondaries: %u", secondary);
  3490.         }
  3491. #if defined(DXX_BUILD_DESCENT_II)
  3492.         void update_extra_accessory_string(unsigned accessory)
  3493.         {
  3494.                 snprintf(extraAccessory, sizeof(extraAccessory), "Accessories: %u", accessory);
  3495.         }
  3496. #endif
  3497.         void update_packstring()
  3498.         {
  3499.                 snprintf(packstring, sizeof(packstring), "%u", Netgame.PacketsPerSec);
  3500.         }
  3501.         void update_portstring()
  3502.         {
  3503.                 snprintf(&portstring[0], portstring.size(), "%hu", UDP_MyPort);
  3504.         }
  3505.         void update_reactor_life_string(unsigned t)
  3506.         {
  3507.                 snprintf(srinvul, sizeof(srinvul), "%s: %u %s", TXT_REACTOR_LIFE, t, TXT_MINUTES_ABBREV);
  3508.         }
  3509.         void update_max_play_time_string()
  3510.         {
  3511.                 snprintf(PlayText, sizeof(PlayText), "Max time: %d %s", Netgame.PlayTimeAllowed.count() / (F1_0 * 60), TXT_MINUTES_ABBREV);
  3512.         }
  3513.         void update_spawn_invuln_string()
  3514.         {
  3515.                 snprintf(SpawnInvulnerableText, sizeof(SpawnInvulnerableText), "Invul. Time: %1.1f sec", static_cast<float>(Netgame.InvulAppear) / 2);
  3516.         }
  3517.         void update_secluded_spawn_string()
  3518.         {
  3519.                 const unsigned SecludedSpawns = Netgame.SecludedSpawns;
  3520.                 cf_assert(SecludedSpawns < MAX_PLAYERS);
  3521.                 snprintf(SecludedSpawnText, sizeof(SecludedSpawnText), "Use %u Furthest Sites", SecludedSpawns + 1);
  3522.         }
  3523.         void update_kill_goal_string()
  3524.         {
  3525.                 snprintf(KillText, sizeof(KillText), "Kill Goal: %3d", Netgame.KillGoal * 5);
  3526.         }
  3527.         enum
  3528.         {
  3529.                 DXX_UDP_MENU_OPTIONS(ENUM)
  3530.         };
  3531.         more_game_options_menu_items()
  3532.         {
  3533.                 const auto difficulty = Netgame.difficulty;
  3534.                 update_difficulty_string(difficulty);
  3535.                 update_packstring();
  3536.                 update_portstring();
  3537.                 update_reactor_life_string(Netgame.control_invul_time / reactor_invul_time_mini_scale);
  3538.                 update_max_play_time_string();
  3539.                 update_spawn_invuln_string();
  3540.                 update_secluded_spawn_string();
  3541.                 update_kill_goal_string();
  3542.                 auto primary = Netgame.DuplicatePowerups.get_primary_count();
  3543.                 auto secondary = Netgame.DuplicatePowerups.get_secondary_count();
  3544. #if defined(DXX_BUILD_DESCENT_II)
  3545.                 auto accessory = Netgame.DuplicatePowerups.get_accessory_count();
  3546.                 const auto thief_absent = Netgame.ThiefModifierFlags & ThiefModifier::Absent;
  3547.                 const auto thief_cannot_steal_energy_weapons = Netgame.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons;
  3548.                 update_extra_accessory_string(accessory);
  3549. #endif
  3550.                 update_extra_primary_string(primary);
  3551.                 update_extra_secondary_string(secondary);
  3552. #if DXX_USE_TRACKER
  3553.                 const unsigned TrackerNATWarned = Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::UserEnabledHP;
  3554. #endif
  3555.                 const unsigned PlayTimeAllowed = std::chrono::duration_cast<std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>>(Netgame.PlayTimeAllowed).count();
  3556.                 format_human_readable_time(AutosaveInterval, Netgame.MPGameplayOptions.AutosaveInterval);
  3557.                 DXX_UDP_MENU_OPTIONS(ADD);
  3558. #if DXX_USE_TRACKER
  3559.                 const auto &tracker_addr = CGameArg.MplTrackerAddr;
  3560.                 if (tracker_addr.empty())
  3561.                 {
  3562.                         nm_set_item_text(m[opt_tracker], "Tracker use disabled");
  3563.                         nm_set_item_text(m[opt_tracker_addr], "<Tracker address not set>");
  3564.                 }
  3565.                 else
  3566.                 {
  3567.                         snprintf(tracker_addr_txt, sizeof(tracker_addr_txt), "%s:%u", tracker_addr.c_str(), CGameArg.MplTrackerPort);
  3568.                 }
  3569. #endif
  3570.         }
  3571.         void read() const
  3572.         {
  3573.                 unsigned primary, secondary;
  3574. #if defined(DXX_BUILD_DESCENT_II)
  3575.                 unsigned accessory;
  3576.                 uint8_t thief_absent;
  3577.                 uint8_t thief_cannot_steal_energy_weapons;
  3578. #endif
  3579.                 uint8_t difficulty;
  3580. #if DXX_USE_TRACKER
  3581.                 unsigned TrackerNATWarned;
  3582. #endif
  3583.                 unsigned PlayTimeAllowed;
  3584.                 DXX_UDP_MENU_OPTIONS(READ);
  3585.                 Netgame.difficulty = cast_clamp_difficulty(difficulty);
  3586.                 Netgame.PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(PlayTimeAllowed);
  3587.                 auto &items = Netgame.DuplicatePowerups;
  3588.                 items.set_primary_count(primary);
  3589.                 items.set_secondary_count(secondary);
  3590. #if defined(DXX_BUILD_DESCENT_II)
  3591.                 items.set_accessory_count(accessory);
  3592.                 Netgame.ThiefModifierFlags =
  3593.                         (thief_absent ? ThiefModifier::Absent : 0) |
  3594.                         (thief_cannot_steal_energy_weapons ? ThiefModifier::NoEnergyWeapons : 0);
  3595. #endif
  3596.                 char *p;
  3597.                 auto pps = strtol(packstring, &p, 10);
  3598.                 if (!*p)
  3599.                         Netgame.PacketsPerSec = pps;
  3600. #if DXX_USE_TRACKER
  3601.                 Netgame.TrackerNATWarned = TrackerNATWarned ? TrackerNATHolePunchWarn::UserEnabledHP : TrackerNATHolePunchWarn::UserRejectedHP;
  3602. #endif
  3603.                 convert_text_portstring(portstring, UDP_MyPort, false, false);
  3604.                 parse_human_readable_time(Netgame.MPGameplayOptions.AutosaveInterval, AutosaveInterval);
  3605.         }
  3606.         static void net_udp_more_game_options();
  3607. };
  3608.  
  3609. class grant_powerup_menu_items
  3610. {
  3611. public:
  3612.         enum
  3613.         {
  3614.                 DXX_GRANT_POWERUP_MENU(ENUM)
  3615.         };
  3616.         std::array<newmenu_item, DXX_GRANT_POWERUP_MENU(COUNT)> m;
  3617.         grant_powerup_menu_items(const unsigned laser_level, const packed_spawn_granted_items p)
  3618.         {
  3619.                 auto &flags = p.mask;
  3620.                 DXX_GRANT_POWERUP_MENU(ADD);
  3621.         }
  3622.         void read(packed_spawn_granted_items &p) const
  3623.         {
  3624.                 unsigned laser_level, flags = 0;
  3625.                 DXX_GRANT_POWERUP_MENU(READ);
  3626.                 p.mask = laser_level | flags;
  3627.         }
  3628. };
  3629.  
  3630. }
  3631.  
  3632. static void net_udp_set_grant_power()
  3633. {
  3634.         const auto SpawnGrantedItems = Netgame.SpawnGrantedItems;
  3635.         grant_powerup_menu_items menu{map_granted_flags_to_laser_level(SpawnGrantedItems), SpawnGrantedItems};
  3636.         newmenu_do(nullptr, "Powerups granted at player spawn", menu.m, unused_newmenu_subfunction, unused_newmenu_userdata);
  3637.         menu.read(Netgame.SpawnGrantedItems);
  3638. }
  3639.  
  3640. void more_game_options_menu_items::net_udp_more_game_options()
  3641. {
  3642.         more_game_options_menu_items menu;
  3643.         newmenu_do(nullptr, "Advanced netgame options", menu.get_menu_items(), handler, &menu);
  3644.         menu.read();
  3645.         if (Netgame.PacketsPerSec>MAX_PPS)
  3646.         {
  3647.                 Netgame.PacketsPerSec=MAX_PPS;
  3648.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "Packet value out of range\nSetting value to %i",MAX_PPS);
  3649.         }
  3650.         else if (Netgame.PacketsPerSec < MIN_PPS)
  3651.         {
  3652.                 Netgame.PacketsPerSec=MIN_PPS;
  3653.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "Packet value out of range\nSetting value to %i", MIN_PPS);
  3654.         }
  3655.         GameUniqueState.Difficulty_level = Netgame.difficulty;
  3656. }
  3657.  
  3658. int more_game_options_menu_items::handler(newmenu *, const d_event &event, more_game_options_menu_items *items)
  3659. {
  3660.         switch (event.type)
  3661.         {
  3662.                 case EVENT_NEWMENU_CHANGED:
  3663.                 {
  3664.                         auto &citem = static_cast<const d_change_event &>(event).citem;
  3665.                         auto &menus = items->get_menu_items();
  3666.                         if (citem == opt_difficulty)
  3667.                         {
  3668.                                 Netgame.difficulty = cast_clamp_difficulty(menus[opt_difficulty].value);
  3669.                                 items->update_difficulty_string(Netgame.difficulty);
  3670.                         }
  3671.                         else if (citem == opt_cinvul)
  3672.                                 items->update_reactor_life_string(menus[opt_cinvul].value * (reactor_invul_time_scale / reactor_invul_time_mini_scale));
  3673.                         else if (citem == opt_playtime)
  3674.                         {
  3675.                                 if (Game_mode & GM_MULTI_COOP)
  3676.                                 {
  3677.                                         nm_messagebox ("Sorry",1,TXT_OK,"You can't change those for coop!");
  3678.                                         menus[opt_playtime].value=0;
  3679.                                         return 0;
  3680.                                 }
  3681.                                
  3682.                                 Netgame.PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(menus[opt_playtime].value);
  3683.                                 items->update_max_play_time_string();
  3684.                         }
  3685.                         else if (citem == opt_killgoal)
  3686.                         {
  3687.                                 if (Game_mode & GM_MULTI_COOP)
  3688.                                 {
  3689.                                         nm_messagebox ("Sorry",1,TXT_OK,"You can't change those for coop!");
  3690.                                         menus[opt_killgoal].value=0;
  3691.                                         return 0;
  3692.                                 }
  3693.                                
  3694.                                 Netgame.KillGoal=menus[opt_killgoal].value;
  3695.                                 items->update_kill_goal_string();
  3696.                         }
  3697.                         else if(citem == opt_extra_primary)
  3698.                         {
  3699.                                 auto primary = menus[opt_extra_primary].value;
  3700.                                 items->update_extra_primary_string(primary);
  3701.                         }
  3702.                         else if(citem == opt_extra_secondary)
  3703.                         {
  3704.                                 auto secondary = menus[opt_extra_secondary].value;
  3705.                                 items->update_extra_secondary_string(secondary);
  3706.                         }
  3707. #if defined(DXX_BUILD_DESCENT_II)
  3708.                         else if(citem == opt_extra_accessory)
  3709.                         {
  3710.                                 auto accessory = menus[opt_extra_accessory].value;
  3711.                                 items->update_extra_accessory_string(accessory);
  3712.                         }
  3713. #endif
  3714.                         else if (citem == opt_start_invul)
  3715.                         {
  3716.                                 Netgame.InvulAppear = menus[opt_start_invul].value;
  3717.                                 items->update_spawn_invuln_string();
  3718.                         }
  3719.                         else if (citem == opt_secluded_spawns)
  3720.                         {
  3721.                                 Netgame.SecludedSpawns = menus[opt_secluded_spawns].value;
  3722.                                 items->update_secluded_spawn_string();
  3723.                         }
  3724.                         break;
  3725.                 }
  3726.                 case EVENT_NEWMENU_SELECTED:
  3727.                 {
  3728.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  3729.                         if (citem == opt_setpower)
  3730.                                 net_udp_set_power();
  3731.                         else if (citem == opt_setgrant)
  3732.                                 net_udp_set_grant_power();
  3733.                         else
  3734.                                 break;
  3735.                         return 1;
  3736.                 }
  3737.                 default:
  3738.                         break;
  3739.         }
  3740.         return 0;
  3741. }
  3742.  
  3743. namespace {
  3744.  
  3745. struct param_opt
  3746. {
  3747.         enum {
  3748.                 name = 2,
  3749.                 label_level,
  3750.                 level,
  3751.         };
  3752.         int start_game, mode, mode_end, moreopts;
  3753.         int closed, refuse, maxnet, anarchy, team_anarchy, robot_anarchy, coop, bounty;
  3754. #if defined(DXX_BUILD_DESCENT_II)
  3755.         int capture, hoard, team_hoard;
  3756. #endif
  3757.         char slevel[sizeof("S100")] = "1";
  3758.         char srmaxnet[sizeof("Maximum players: 99")];
  3759.         std::array<newmenu_item, 22> m;
  3760.         void update_netgame_max_players()
  3761.         {
  3762.                 Netgame.max_numplayers = m[maxnet].value + 2;
  3763.                 update_max_players_string();
  3764.         }
  3765.         void update_max_players_string()
  3766.         {
  3767.                 const unsigned max_numplayers = Netgame.max_numplayers;
  3768.                 cf_assert(max_numplayers < MAX_PLAYERS);
  3769.                 snprintf(srmaxnet, sizeof(srmaxnet), "Maximum players: %u", max_numplayers);
  3770.         }
  3771. };
  3772.  
  3773. }
  3774.  
  3775. namespace dsx {
  3776. static int net_udp_game_param_handler( newmenu *menu,const d_event &event, param_opt *opt )
  3777. {
  3778.         newmenu_item *menus = newmenu_get_items(menu);
  3779.         switch (event.type)
  3780.         {
  3781.                 case EVENT_NEWMENU_CHANGED:
  3782.                 {
  3783.                         auto &citem = static_cast<const d_change_event &>(event).citem;
  3784. #if defined(DXX_BUILD_DESCENT_I)
  3785.                         if (citem == opt->team_anarchy)
  3786.                         {
  3787.                                 menus[opt->closed].value = 1;
  3788.                                 menus[opt->closed-1].value = 0;
  3789.                                 menus[opt->closed+1].value = 0;
  3790.                         }
  3791. #elif defined(DXX_BUILD_DESCENT_II)
  3792.                         if (((HoardEquipped() && (citem == opt->team_hoard)) || ((citem == opt->team_anarchy) || (citem == opt->capture))) && !menus[opt->closed].value && !menus[opt->refuse].value)
  3793.                         {
  3794.                                 menus[opt->refuse].value = 1;
  3795.                                 menus[opt->refuse-1].value = 0;
  3796.                                 menus[opt->refuse-2].value = 0;
  3797.                         }
  3798. #endif
  3799.                        
  3800.                         if (menus[opt->coop].value)
  3801.                         {
  3802.                                 Netgame.game_flag.show_on_map = 1;
  3803.  
  3804.                                 Netgame.PlayTimeAllowed = {};
  3805.                                 Netgame.KillGoal = 0;
  3806.                         }
  3807.                         if (citem == opt->level)
  3808.                         {
  3809.                                 auto &slevel = opt->slevel;
  3810. #if defined(DXX_BUILD_DESCENT_I)
  3811.                                 if (tolower(static_cast<unsigned>(*slevel)) == 's')
  3812.                                         Netgame.levelnum = -atoi(slevel+1);
  3813.                                 else
  3814. #endif
  3815.                                         Netgame.levelnum = atoi(slevel);
  3816.                         }
  3817.                        
  3818.                         if (citem == opt->maxnet)
  3819.                         {
  3820.                                 opt->update_netgame_max_players();
  3821.                         }
  3822.  
  3823.                         if ((citem >= opt->mode) && (citem <= opt->mode_end))
  3824.                         {
  3825.                                 if ( menus[opt->anarchy].value )
  3826.                                         Netgame.gamemode = NETGAME_ANARCHY;
  3827.                                
  3828.                                 else if (menus[opt->team_anarchy].value) {
  3829.                                         Netgame.gamemode = NETGAME_TEAM_ANARCHY;
  3830.                                 }
  3831. #if defined(DXX_BUILD_DESCENT_II)
  3832.                                 else if (menus[opt->capture].value)
  3833.                                         Netgame.gamemode = NETGAME_CAPTURE_FLAG;
  3834.                                 else if (HoardEquipped() && menus[opt->hoard].value)
  3835.                                         Netgame.gamemode = NETGAME_HOARD;
  3836.                                 else if (HoardEquipped() && menus[opt->team_hoard].value)
  3837.                                         Netgame.gamemode = NETGAME_TEAM_HOARD;
  3838. #endif
  3839.                                 else if( menus[opt->bounty].value )
  3840.                                         Netgame.gamemode = NETGAME_BOUNTY;
  3841.                                 else if (ANARCHY_ONLY_MISSION) {
  3842.                                         int i = 0;
  3843.                                         nm_messagebox(NULL, 1, TXT_OK, TXT_ANARCHY_ONLY_MISSION);
  3844.                                         for (i = opt->mode; i <= opt->mode_end; i++)
  3845.                                                 menus[i].value = 0;
  3846.                                         menus[opt->anarchy].value = 1;
  3847.                                         return 0;
  3848.                                 }
  3849.                                 else if ( menus[opt->robot_anarchy].value )
  3850.                                         Netgame.gamemode = NETGAME_ROBOT_ANARCHY;
  3851.                                 else if ( menus[opt->coop].value )
  3852.                                         Netgame.gamemode = NETGAME_COOPERATIVE;
  3853.                                 else Int3(); // Invalid mode -- see Rob
  3854.                         }
  3855.  
  3856.                         Netgame.game_flag.closed = menus[opt->closed].value;
  3857.                         Netgame.RefusePlayers=menus[opt->refuse].value;
  3858.                         break;
  3859.                 }
  3860.                 case EVENT_NEWMENU_SELECTED:
  3861.                 {
  3862.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  3863. #if defined(DXX_BUILD_DESCENT_I)
  3864.                         if ((Netgame.levelnum < Last_secret_level) || (Netgame.levelnum > Last_level) || (Netgame.levelnum == 0))
  3865. #elif defined(DXX_BUILD_DESCENT_II)
  3866.                         if ((Netgame.levelnum < 1) || (Netgame.levelnum > Last_level))
  3867. #endif
  3868.                         {
  3869.                                 auto &slevel = opt->slevel;
  3870.                                 nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_LEVEL_OUT_RANGE );
  3871.                                 strcpy(slevel, "1");
  3872.                                 return 1;
  3873.                         }
  3874.  
  3875.                         if (citem==opt->moreopts)
  3876.                         {
  3877.                                 if ( menus[opt->coop].value )
  3878.                                         Game_mode=GM_MULTI_COOP;
  3879.                                 more_game_options_menu_items::net_udp_more_game_options();
  3880.                                 Game_mode=0;
  3881.                                 return 1;
  3882.                         }
  3883.                         if (citem==opt->start_game)
  3884.                                 return !net_udp_start_game();
  3885.                         return 1;
  3886.                 }
  3887.                 default:
  3888.                         break;
  3889.         }
  3890.        
  3891.         return 0;
  3892. }
  3893. }
  3894.  
  3895. namespace dsx {
  3896. window_event_result net_udp_setup_game()
  3897. {
  3898.         int optnum;
  3899.         param_opt opt;
  3900.         auto &m = opt.m;
  3901.         char level_text[32];
  3902.  
  3903.         net_udp_init();
  3904.  
  3905.         multi_new_game();
  3906.  
  3907.         change_playernum_to(0);
  3908.  
  3909.         const auto &self = get_local_player();
  3910.         {
  3911.                 range_for (auto &i, Players)
  3912.                         if (&i != &self)
  3913.                                 i.callsign = {};
  3914.         }
  3915.  
  3916.         Netgame.max_numplayers = MAX_PLAYERS;
  3917.         Netgame.KillGoal=0;
  3918.         Netgame.PlayTimeAllowed = {};
  3919. #if defined(DXX_BUILD_DESCENT_I)
  3920.         Netgame.RefusePlayers=0;
  3921. #elif defined(DXX_BUILD_DESCENT_II)
  3922.         Netgame.Allow_marker_view=1;
  3923.         Netgame.ThiefModifierFlags = 0;
  3924. #endif
  3925.         Netgame.difficulty=PlayerCfg.DefaultDifficulty;
  3926.         Netgame.PacketsPerSec=DEFAULT_PPS;
  3927.         snprintf(Netgame.game_name.data(), Netgame.game_name.size(), "%s%s", static_cast<const char *>(InterfaceUniqueState.PilotName), TXT_S_GAME);
  3928.         reset_UDP_MyPort();
  3929.         Netgame.ShufflePowerupSeed = 0;
  3930.         Netgame.BrightPlayers = 1;
  3931.         Netgame.InvulAppear = 4;
  3932.         Netgame.SecludedSpawns = MAX_PLAYERS - 1;
  3933.         Netgame.AllowedItems = NETFLAG_DOPOWERUP;
  3934.         Netgame.PacketLossPrevention = 1;
  3935.         Netgame.NoFriendlyFire = 0;
  3936.         Netgame.MouselookFlags = 0;
  3937.  
  3938. #if DXX_USE_TRACKER
  3939.         Netgame.Tracker = 1;
  3940. #endif
  3941.  
  3942.         read_netgame_profile(&Netgame);
  3943.  
  3944. #if defined(DXX_BUILD_DESCENT_II)
  3945.         if (!HoardEquipped() && (Netgame.gamemode == NETGAME_HOARD || Netgame.gamemode == NETGAME_TEAM_HOARD)) // did we restore a hoard mode but don't have hoard installed right now? then fall back to anarchy!
  3946.                 Netgame.gamemode = NETGAME_ANARCHY;
  3947. #endif
  3948.  
  3949.         Netgame.mission_name.copy_if(&*Current_mission->filename, Netgame.mission_name.size());
  3950.         Netgame.mission_title = Current_mission_longname;
  3951.  
  3952.         Netgame.levelnum = 1;
  3953.  
  3954.         optnum = 0;
  3955.         opt.start_game=optnum;
  3956.         nm_set_item_menu(  m[optnum], "Start Game"); optnum++;
  3957.         nm_set_item_text(m[optnum], TXT_DESCRIPTION); optnum++;
  3958.  
  3959.         nm_set_item_input(m[optnum], Netgame.game_name); optnum++;
  3960.  
  3961. #define DXX_LEVEL_FORMAT_LEADER "%s (1-%d"
  3962. #define DXX_LEVEL_FORMAT_TRAILER        ")"
  3963. #if defined(DXX_BUILD_DESCENT_I)
  3964.         if (Last_secret_level == -1)
  3965.                 /* Exactly one secret level */
  3966.                 snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER ", S1" DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level);
  3967.         else if (Last_secret_level)
  3968.                 /* More than one secret level */
  3969.                 snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER ", S1-S%d" DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level, -Last_secret_level);
  3970.         else
  3971.                 /* No secret levels */
  3972. #endif
  3973.                 snprintf(level_text, sizeof(level_text), DXX_LEVEL_FORMAT_LEADER DXX_LEVEL_FORMAT_TRAILER, TXT_LEVEL_, Last_level);
  3974. #undef DXX_LEVEL_FORMAT_TRAILER
  3975. #undef DXX_LEVEL_FORMAT_LEADER
  3976.  
  3977.         nm_set_item_text(m[optnum], level_text); optnum++;
  3978.  
  3979.         nm_set_item_input(m[optnum], opt.slevel); optnum++;
  3980.         nm_set_item_text(m[optnum], TXT_OPTIONS); optnum++;
  3981.  
  3982.         opt.mode = optnum;
  3983.         nm_set_item_radio(m[optnum], TXT_ANARCHY,(Netgame.gamemode == NETGAME_ANARCHY),0); opt.anarchy=optnum; optnum++;
  3984.         nm_set_item_radio(m[optnum], TXT_TEAM_ANARCHY,(Netgame.gamemode == NETGAME_TEAM_ANARCHY),0); opt.team_anarchy=optnum; optnum++;
  3985.         nm_set_item_radio(m[optnum], TXT_ANARCHY_W_ROBOTS,(Netgame.gamemode == NETGAME_ROBOT_ANARCHY),0); opt.robot_anarchy=optnum; optnum++;
  3986.         nm_set_item_radio(m[optnum], TXT_COOPERATIVE,(Netgame.gamemode == NETGAME_COOPERATIVE),0); opt.coop=optnum; optnum++;
  3987. #if defined(DXX_BUILD_DESCENT_II)
  3988.         nm_set_item_radio(m[optnum], "Capture the flag",(Netgame.gamemode == NETGAME_CAPTURE_FLAG),0); opt.capture=optnum; optnum++;
  3989.  
  3990.         if (HoardEquipped())
  3991.         {
  3992.                 nm_set_item_radio(m[optnum], "Hoard",(Netgame.gamemode == NETGAME_HOARD),0); opt.hoard=optnum; optnum++;
  3993.                 nm_set_item_radio(m[optnum], "Team Hoard",(Netgame.gamemode == NETGAME_TEAM_HOARD),0); opt.team_hoard=optnum; optnum++;
  3994.         }
  3995.         else
  3996.         {
  3997.                 opt.hoard = opt.team_hoard = 0; // NOTE: Make sure if you use these, use them in connection with HoardEquipped() only!
  3998.         }
  3999. #endif
  4000.         nm_set_item_radio(m[optnum], "Bounty", ( Netgame.gamemode & NETGAME_BOUNTY ), 0); opt.mode_end=opt.bounty=optnum; optnum++;
  4001.  
  4002.         nm_set_item_text(m[optnum], ""); optnum++;
  4003.  
  4004.         nm_set_item_radio(m[optnum], "Open game",(!Netgame.RefusePlayers && !Netgame.game_flag.closed),1); optnum++;
  4005.         opt.closed = optnum;
  4006.         nm_set_item_radio(m[optnum], TXT_CLOSED_GAME,Netgame.game_flag.closed,1); optnum++;
  4007.         opt.refuse = optnum;
  4008.         nm_set_item_radio(m[optnum], "Restricted Game              ",Netgame.RefusePlayers,1); optnum++;
  4009.  
  4010.         opt.maxnet = optnum;
  4011.         opt.update_max_players_string();
  4012.         nm_set_item_slider(m[optnum], opt.srmaxnet, Netgame.max_numplayers - 2, 0, Netgame.max_numplayers - 2); optnum++;
  4013.        
  4014.         opt.moreopts=optnum;
  4015.         nm_set_item_menu(  m[optnum], "Advanced Options"); optnum++;
  4016.  
  4017.         Assert(optnum <= 20);
  4018.  
  4019. #if DXX_USE_TRACKER
  4020.         if (Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::Unset)
  4021.         {
  4022.                 const unsigned choice = nm_messagebox_str("NAT Hole Punch", nm_messagebox_tie("Yes, let Internet users join", "No, I will configure my router"),
  4023. "Rebirth now supports automatic\n"
  4024. "NAT hole punch through the\n"
  4025. "tracker.\n\n"
  4026. "This allows Internet users to\n"
  4027. "join your game, even if you do\n"
  4028. "not configure your router for\n"
  4029. "hosting.\n\n"
  4030. "Do you want to use this feature?");
  4031.                 if (choice <= 1)
  4032.                         Netgame.TrackerNATWarned = static_cast<TrackerNATHolePunchWarn>(choice + 1);
  4033.         }
  4034. #endif
  4035.  
  4036.         const int i = newmenu_do1(nullptr, TXT_NETGAME_SETUP, optnum, m.data(), net_udp_game_param_handler, &opt, opt.start_game);
  4037.  
  4038.         if (i < 0)
  4039.                 net_udp_close();
  4040.  
  4041.         write_netgame_profile(&Netgame);
  4042. #if DXX_USE_TRACKER
  4043.         /* Force off _after_ writing profile, so that command line does not
  4044.          * change ngp file.
  4045.          */
  4046.         if (CGameArg.MplTrackerAddr.empty())
  4047.                 Netgame.Tracker = 0;
  4048. #endif
  4049.  
  4050.         return (i >= 0) ? window_event_result::close : window_event_result::handled;
  4051. }
  4052. }
  4053.  
  4054. namespace dsx {
  4055. static void net_udp_set_game_mode(const int gamemode)
  4056. {
  4057.         Show_kill_list = 1;
  4058.  
  4059.         if ( gamemode == NETGAME_ANARCHY )
  4060.                 Game_mode = GM_NETWORK;
  4061.         else if ( gamemode == NETGAME_ROBOT_ANARCHY )
  4062.                 Game_mode = GM_NETWORK | GM_MULTI_ROBOTS;
  4063.         else if ( gamemode == NETGAME_COOPERATIVE )
  4064.                 Game_mode = GM_NETWORK | GM_MULTI_COOP | GM_MULTI_ROBOTS;
  4065. #if defined(DXX_BUILD_DESCENT_II)
  4066.         else if (gamemode == NETGAME_CAPTURE_FLAG)
  4067.                 {
  4068.                  Game_mode = GM_NETWORK | GM_TEAM | GM_CAPTURE;
  4069.                  Show_kill_list=3;
  4070.                 }
  4071.  
  4072.         else if (HoardEquipped() && gamemode == NETGAME_HOARD)
  4073.                  Game_mode = GM_NETWORK | GM_HOARD;
  4074.         else if (HoardEquipped() && gamemode == NETGAME_TEAM_HOARD)
  4075.                  {
  4076.                   Game_mode = GM_NETWORK | GM_HOARD | GM_TEAM;
  4077.                   Show_kill_list=3;
  4078.                  }
  4079. #endif
  4080.         else if( gamemode == NETGAME_BOUNTY )
  4081.                 Game_mode = GM_NETWORK | GM_BOUNTY;
  4082.         else if ( gamemode == NETGAME_TEAM_ANARCHY )
  4083.         {
  4084.                 Game_mode = GM_NETWORK | GM_TEAM;
  4085.                 Show_kill_list = 3;
  4086.         }
  4087.         else
  4088.                 Int3();
  4089. }
  4090. }
  4091.  
  4092. namespace dsx {
  4093. void net_udp_read_sync_packet(const uint8_t * data, uint_fast32_t data_len, const _sockaddr &sender_addr)
  4094. {
  4095.         auto &Objects = LevelUniqueObjectState.Objects;
  4096.         auto &vmobjptr = Objects.vmptr;
  4097.         auto &vmobjptridx = Objects.vmptridx;
  4098.         if (data)
  4099.         {
  4100.                 net_udp_process_game_info(data, data_len, sender_addr, 0);
  4101.         }
  4102.  
  4103.         N_players = Netgame.numplayers;
  4104.         GameUniqueState.Difficulty_level = Netgame.difficulty;
  4105.         Network_status = Netgame.game_status;
  4106.  
  4107.         if (Netgame.segments_checksum != my_segments_checksum)
  4108.         {
  4109.                 Network_status = NETSTAT_MENU;
  4110.                 net_udp_close();
  4111.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_NETLEVEL_NMATCH);
  4112.                 throw multi::level_checksum_mismatch();
  4113.         }
  4114.  
  4115.         // Discover my player number
  4116.  
  4117.         auto &temp_callsign = InterfaceUniqueState.PilotName;
  4118.        
  4119.         Player_num = MULTI_PNUM_UNDEF;
  4120.  
  4121.         for (unsigned i = 0; i < N_players; ++i)
  4122.         {
  4123.                 if (i == Netgame.protocol.udp.your_index && Netgame.players[i].callsign == temp_callsign)
  4124.                 {
  4125.                         if (Player_num!=MULTI_PNUM_UNDEF) {
  4126.                                 Int3(); // Hey, we've found ourselves twice
  4127.                                 Network_status = NETSTAT_MENU;
  4128.                                 return;
  4129.                         }
  4130.                         change_playernum_to(i);
  4131.                 }
  4132.                 auto &plr = *vmplayerptr(i);
  4133.                 plr.callsign = Netgame.players[i].callsign;
  4134.                 plr.connected = Netgame.players[i].connected;
  4135.                 auto &objp = *vmobjptr(plr.objnum);
  4136.                 auto &player_info = objp.ctype.player_info;
  4137.                 player_info.net_kills_total = Netgame.player_kills[i];
  4138.                 player_info.net_killed_total = Netgame.killed[i];
  4139.                 if ((Network_rejoined) || (i != Player_num))
  4140.                         player_info.mission.score = Netgame.player_score[i];
  4141.         }
  4142.         kill_matrix = Netgame.kills;
  4143.  
  4144.         if (Player_num >= MAX_PLAYERS)
  4145.         {
  4146.                 Network_status = NETSTAT_MENU;
  4147.                 throw multi::local_player_not_playing();
  4148.         }
  4149.  
  4150. #if defined(DXX_BUILD_DESCENT_I)
  4151.         {
  4152.                 auto &player_info = get_local_plrobj().ctype.player_info;
  4153.                 PlayerCfg.NetlifeKills -= player_info.net_kills_total;
  4154.                 PlayerCfg.NetlifeKilled -= player_info.net_killed_total;
  4155.         }
  4156. #endif
  4157.  
  4158.         auto &plr = get_local_player();
  4159.         if (Network_rejoined)
  4160.         {
  4161.                 net_udp_process_monitor_vector(Netgame.monitor_vector);
  4162.                 plr.time_level = Netgame.level_time;
  4163.         }
  4164.  
  4165.         team_kills = Netgame.team_kills;
  4166.         plr.connected = CONNECT_PLAYING;
  4167.         Netgame.players[Player_num].connected = CONNECT_PLAYING;
  4168.         Netgame.players[Player_num].rank=GetMyNetRanking();
  4169.  
  4170.         if (!Network_rejoined)
  4171.         {
  4172.                 for (unsigned i = 0; i < NumNetPlayerPositions; ++i)
  4173.                 {
  4174.                         const auto &&o = vmobjptridx(vcplayerptr(i)->objnum);
  4175.                         const auto &p = Player_init[Netgame.locations[i]];
  4176.                         o->pos = p.pos;
  4177.                         o->orient = p.orient;
  4178.                         obj_relink(vmobjptr, vmsegptr, o, vmsegptridx(p.segnum));
  4179.                 }
  4180.         }
  4181.  
  4182.         get_local_plrobj().type = OBJ_PLAYER;
  4183.  
  4184.         Network_status = NETSTAT_PLAYING;
  4185.         multi_sort_kill_list();
  4186. }
  4187. }
  4188.  
  4189. static int net_udp_send_sync(void)
  4190. {
  4191.         int np;
  4192.  
  4193.         // Check if there are enough starting positions
  4194.         if (NumNetPlayerPositions < Netgame.max_numplayers)
  4195.         {
  4196.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "Not enough start positions\n(set %d got %d)\nNetgame aborted", Netgame.max_numplayers, NumNetPlayerPositions);
  4197.                 // Tell everyone we're bailing
  4198.                 Netgame.numplayers = 0;
  4199.                 for (unsigned i = 1; i < N_players; ++i)
  4200.                 {
  4201.                         if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
  4202.                                 continue;
  4203.                         const auto &addr = Netgame.players[i].protocol.udp.addr;
  4204.                         net_udp_dump_player(addr, DUMP_ABORTED);
  4205.                         net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
  4206.                 }
  4207.                 net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
  4208.                 return -1;
  4209.         }
  4210.  
  4211.         // Randomize their starting locations...
  4212.         d_srand(static_cast<fix>(timer_query()));
  4213.         for (unsigned i=0; i<NumNetPlayerPositions; i++ )
  4214.         {
  4215.                 if (vcplayerptr(i)->connected)
  4216.                         vmplayerptr(i)->connected = CONNECT_PLAYING; // Get rid of endlevel connect statuses
  4217.  
  4218.                 if (Game_mode & GM_MULTI_COOP)
  4219.                         Netgame.locations[i] = i;
  4220.                 else {
  4221.                         do
  4222.                         {
  4223.                                 np = d_rand() % NumNetPlayerPositions;
  4224.                                 range_for (auto &j, partial_const_range(Netgame.locations, i))
  4225.                                 {
  4226.                                         if (j==np)  
  4227.                                         {
  4228.                                                 np =-1;
  4229.                                                 break;
  4230.                                         }
  4231.                                 }
  4232.                         } while (np<0);
  4233.                         // np is a location that is not used anywhere else..
  4234.                         Netgame.locations[i]=np;
  4235.                 }
  4236.         }
  4237.  
  4238.         // Push current data into the sync packet
  4239.  
  4240.         net_udp_update_netgame();
  4241.         Netgame.game_status = NETSTAT_PLAYING;
  4242.         Netgame.segments_checksum = my_segments_checksum;
  4243.  
  4244.         for (unsigned i = 0; i < N_players; ++i)
  4245.         {
  4246.                 if (!vcplayerptr(i)->connected || i == Player_num)
  4247.                         continue;
  4248.                 const auto &addr = Netgame.players[i].protocol.udp.addr;
  4249.                 net_udp_send_game_info(addr, &addr, UPID_SYNC);
  4250.         }
  4251.  
  4252.         net_udp_read_sync_packet(NULL, 0, Netgame.players[0].protocol.udp.addr); // Read it myself, as if I had sent it
  4253.         return 0;
  4254. }
  4255.  
  4256. static int net_udp_select_teams()
  4257. {
  4258.         newmenu_item m[MAX_PLAYERS+4];
  4259.         int choice, opt, opt_team_b;
  4260.         ubyte team_vector = 0;
  4261.         int pnums[MAX_PLAYERS+2];
  4262.  
  4263.         // One-time initialization
  4264.  
  4265.         for (int i = N_players/2; i < N_players; i++) // Put first half of players on team A
  4266.         {
  4267.                 team_vector |= (1 << i);
  4268.         }
  4269.  
  4270.         std::array<callsign_t, 2> team_names;
  4271.         team_names[0].copy(TXT_BLUE, ~0ul);
  4272.         team_names[1].copy(TXT_RED, ~0ul);
  4273.  
  4274.         // Here comes da menu
  4275. menu:
  4276.         nm_set_item_input(m[0], team_names[0].buffer());
  4277.  
  4278.         opt = 1;
  4279.         for (int i = 0; i < N_players; i++)
  4280.         {
  4281.                 if (!(team_vector & (1 << i)))
  4282.                 {
  4283.                         nm_set_item_menu( m[opt], Netgame.players[i].callsign); pnums[opt] = i; opt++;
  4284.                 }
  4285.         }
  4286.         opt_team_b = opt;
  4287.         nm_set_item_input(m[opt], team_names[1].buffer());
  4288.         opt++;
  4289.         for (int i = 0; i < N_players; i++)
  4290.         {
  4291.                 if (team_vector & (1 << i))
  4292.                 {
  4293.                         nm_set_item_menu( m[opt], Netgame.players[i].callsign); pnums[opt] = i; opt++;
  4294.                 }
  4295.         }
  4296.         nm_set_item_text(m[opt], ""); opt++;
  4297.         nm_set_item_menu( m[opt], TXT_ACCEPT); opt++;
  4298.  
  4299.         Assert(opt <= MAX_PLAYERS+4);
  4300.        
  4301.         choice = newmenu_do(NULL, TXT_TEAM_SELECTION, opt, m, unused_newmenu_subfunction, unused_newmenu_userdata);
  4302.  
  4303.         if (choice == opt-1)
  4304.         {
  4305. #if 0 // no need to wait for other players
  4306.                 if ((opt-2-opt_team_b < 2) || (opt_team_b == 1))
  4307.                 {
  4308.                         nm_messagebox(NULL, 1, TXT_OK, TXT_TEAM_MUST_ONE);
  4309.                         #ifdef RELEASE
  4310.                         goto menu;
  4311.                         #endif
  4312.                 }
  4313. #endif
  4314.                 Netgame.team_vector = team_vector;
  4315.                 Netgame.team_name = team_names;
  4316.                 return 1;
  4317.         }
  4318.  
  4319.         else if ((choice > 0) && (choice < opt_team_b)) {
  4320.                 team_vector |= (1 << pnums[choice]);
  4321.         }
  4322.         else if ((choice > opt_team_b) && (choice < opt-2)) {
  4323.                 team_vector &= ~(1 << pnums[choice]);
  4324.         }
  4325.         else if (choice == -1)
  4326.                 return 0;
  4327.         goto menu;
  4328. }
  4329.  
  4330. namespace dsx {
  4331. static int net_udp_select_players()
  4332. {
  4333.         int j;
  4334.         char text[MAX_PLAYERS+4][45];
  4335.         char title[50];
  4336.         unsigned save_nplayers;              //how may people would like to join
  4337.  
  4338.         if (Netgame.ShufflePowerupSeed)
  4339.         {
  4340.                 unsigned seed = 0;
  4341.                 try {
  4342.                         seed = std::random_device()();
  4343.                         if (!seed)
  4344.                                 /* random_device can return any number, including zero.
  4345.                                  * Rebirth treats zero specially, interpreting it as a
  4346.                                  * request not to shuffle.  Prevent a zero from
  4347.                                  * random_device being interpreted as a request not to
  4348.                                  * shuffle.
  4349.                                  */
  4350.                                 seed = 1;
  4351.                 } catch (const std::exception &e) {
  4352.                         con_printf(CON_URGENT, "Failed to generate random number: %s", e.what());
  4353.                         /* Fall out without setting `seed`, so that the option is
  4354.                          * disabled until the user notices the message and
  4355.                          * resolves the problem.
  4356.                          */
  4357.                 }
  4358.                 Netgame.ShufflePowerupSeed = seed;
  4359.         }
  4360.  
  4361.         net_udp_add_player( &UDP_Seq );
  4362.         start_poll_menu_items spd;
  4363.                
  4364.         for (int i=0; i< MAX_PLAYERS+4; i++ ) {
  4365.                 snprintf(text[i], sizeof(text[i]), "%d. ", i + 1);
  4366.                 nm_set_item_checkbox(spd.m[i], text[i], 0);
  4367.         }
  4368.  
  4369.         spd.m[0].value = 1;                         // Assume server will play...
  4370.  
  4371.         const auto &&rankstr = GetRankStringWithSpace(Netgame.players[Player_num].rank);
  4372.         snprintf( text[0], sizeof(text[0]), "%d. %s%s%-20s", 1, rankstr.first, rankstr.second, static_cast<const char *>(get_local_player().callsign));
  4373.  
  4374.         snprintf(title, sizeof(title), "%s %d %s", TXT_TEAM_SELECT, Netgame.max_numplayers, TXT_TEAM_PRESS_ENTER);
  4375.  
  4376. #if DXX_USE_TRACKER
  4377.         if( Netgame.Tracker )
  4378.         {
  4379.                 TrackerAckStatus = TrackerAckState::TACK_NOCONNECTION;
  4380.                 TrackerAckTime = timer_query();
  4381.                 udp_tracker_register();
  4382.         }
  4383. #endif
  4384.  
  4385. GetPlayersAgain:
  4386.         j = newmenu_do1(nullptr, title, spd.m.size(), spd.m.data(), net_udp_start_poll, &spd, 1);
  4387.  
  4388.         save_nplayers = N_players;
  4389.  
  4390.         if (j<0)
  4391.         {
  4392.                 // Aborted!
  4393.                 // Dump all players and go back to menu mode
  4394. abort:
  4395.                 // Tell everyone we're bailing
  4396.                 Netgame.numplayers = 0;
  4397.                 for (unsigned i = 1; i < save_nplayers; ++i)
  4398.                 {
  4399.                         if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
  4400.                                 continue;
  4401.                         const auto &addr = Netgame.players[i].protocol.udp.addr;
  4402.                         net_udp_dump_player(addr, DUMP_ABORTED);
  4403.                         net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
  4404.                 }
  4405.                 net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
  4406.                 Netgame.numplayers = save_nplayers;
  4407.  
  4408.                 Network_status = NETSTAT_MENU;
  4409. #if DXX_USE_TRACKER
  4410.                 if( Netgame.Tracker )
  4411.                         udp_tracker_unregister();
  4412. #endif
  4413.                 return(0);
  4414.         }
  4415.         // Count number of players chosen
  4416.  
  4417.         N_players = 0;
  4418.         range_for (auto &i, partial_const_range(spd.m, save_nplayers))
  4419.         {
  4420.                 if (i.value)
  4421.                         N_players++;
  4422.         }
  4423.        
  4424.         if ( N_players > Netgame.max_numplayers) {
  4425.                 nm_messagebox( TXT_ERROR, 1, TXT_OK, "%s %d %s", TXT_SORRY_ONLY, Netgame.max_numplayers, TXT_NETPLAYERS_IN );
  4426.                 N_players = save_nplayers;
  4427.                 goto GetPlayersAgain;
  4428.         }
  4429.  
  4430. // Let host join without Client available. Let's see if our players like that
  4431. #if 0 //def RELEASE
  4432.         if ( N_players < 2 )    {
  4433.                 nm_messagebox( TXT_ERROR, 1, TXT_OK, TXT_TEAM_ATLEAST_TWO );
  4434.                 N_players = save_nplayers;
  4435.                 goto GetPlayersAgain;
  4436.         }
  4437. #endif
  4438.  
  4439. #if 0 //def RELEASE
  4440.         if ( (Netgame.gamemode == NETGAME_TEAM_ANARCHY || Netgame.gamemode == NETGAME_CAPTURE_FLAG || Netgame.gamemode == NETGAME_TEAM_HOARD) && (N_players < 2) )
  4441.         {
  4442.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "You must select at least two\nplayers to start a team game" );
  4443.                 N_players = save_nplayers;
  4444.                 goto GetPlayersAgain;
  4445.         }
  4446. #endif
  4447.  
  4448.         // Remove players that aren't marked.
  4449.         N_players = 0;
  4450.         for (int i=0; i<save_nplayers; i++ )
  4451.         {
  4452.                 if (spd.m[i].value)
  4453.                 {
  4454.                         if (i > N_players)
  4455.                         {
  4456.                                 Netgame.players[N_players].callsign = Netgame.players[i].callsign;
  4457.                                 Netgame.players[N_players].rank=Netgame.players[i].rank;
  4458.                                 ClipRank (&Netgame.players[N_players].rank);
  4459.                         }
  4460.                         vmplayerptr(N_players)->connected = CONNECT_PLAYING;
  4461.                         N_players++;
  4462.                 }
  4463.                 else
  4464.                 {
  4465.                         net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_DORK);
  4466.                 }
  4467.         }
  4468.  
  4469.         range_for (auto &i, partial_range(Netgame.players, N_players, Netgame.players.size()))
  4470.         {
  4471.                 i.callsign = {};
  4472.                 i.rank=0;
  4473.         }
  4474.  
  4475. #if defined(DXX_BUILD_DESCENT_I)
  4476.         if (Netgame.gamemode == NETGAME_TEAM_ANARCHY)
  4477. #elif defined(DXX_BUILD_DESCENT_II)
  4478.         if (Netgame.gamemode == NETGAME_TEAM_ANARCHY ||
  4479.             Netgame.gamemode == NETGAME_CAPTURE_FLAG ||
  4480.                  Netgame.gamemode == NETGAME_TEAM_HOARD)
  4481. #endif
  4482.                  if (!net_udp_select_teams())
  4483.                         goto abort;
  4484.         return(1);
  4485. }
  4486. }
  4487.  
  4488. static int net_udp_start_game(void)
  4489. {
  4490.         int i;
  4491.  
  4492.         i = udp_open_socket(UDP_Socket[0], UDP_MyPort);
  4493.  
  4494.         if (i != 0)
  4495.                 return 0;
  4496.        
  4497.         if (UDP_MyPort != UDP_PORT_DEFAULT)
  4498.                 i = udp_open_socket(UDP_Socket[1], UDP_PORT_DEFAULT); // Default port open for Broadcasts
  4499.  
  4500.         if (i != 0)
  4501.                 return 0;
  4502.  
  4503.         // prepare broadcast address to announce our game
  4504.         udp_init_broadcast_addresses();
  4505.         d_srand(static_cast<fix>(timer_query()));
  4506.         Netgame.protocol.udp.GameID=d_rand();
  4507.  
  4508.         N_players = 0;
  4509.         Netgame.game_status = NETSTAT_STARTING;
  4510.         Netgame.numplayers = 0;
  4511.  
  4512.         Network_status = NETSTAT_STARTING;
  4513.  
  4514.         net_udp_set_game_mode(Netgame.gamemode);
  4515.  
  4516.         Netgame.protocol.udp.your_index = 0; // I am Host. I need to know that y'know? For syncing later.
  4517.        
  4518.         if (!net_udp_select_players()
  4519.                 || StartNewLevel(Netgame.levelnum) == window_event_result::close)
  4520.         {
  4521.                 Game_mode = GM_GAME_OVER;
  4522.                 return 0;       // see if we want to tweak the game we setup
  4523.         }
  4524.         state_set_next_autosave(GameUniqueState, Netgame.MPGameplayOptions.AutosaveInterval);
  4525.         net_udp_broadcast_game_info(UPID_GAME_INFO_LITE); // game started. broadcast our current status to everyone who wants to know
  4526.  
  4527.         return 1;       // don't keep params menu or mission listbox (may want to join a game next time)
  4528. }
  4529.  
  4530. static int net_udp_wait_for_sync(void)
  4531. {
  4532.         char text[60];
  4533.         int choice=0;
  4534.        
  4535.         Network_status = NETSTAT_WAITING;
  4536.  
  4537.         std::array<newmenu_item, 2> m{{
  4538.                 nm_item_text(text),
  4539.                 nm_item_text(TXT_NET_LEAVE),
  4540.         }};
  4541.         auto i = net_udp_send_request();
  4542.  
  4543.         if (i >= MAX_PLAYERS)
  4544.                 return(-1);
  4545.  
  4546.         snprintf(text, sizeof(text), "%s\n'%s' %s", TXT_NET_WAITING, static_cast<const char *>(Netgame.players[i].callsign), TXT_NET_TO_ENTER );
  4547.  
  4548.         while (choice > -1)
  4549.         {
  4550.                 timer_update();
  4551.                 choice=newmenu_do( NULL, TXT_WAIT, m, net_udp_sync_poll, unused_newmenu_userdata );
  4552.         }
  4553.  
  4554.         if (Network_status != NETSTAT_PLAYING)
  4555.         {
  4556.                 UDP_sequence_packet me{};
  4557.                 me.type = UPID_QUIT_JOINING;
  4558.                 me.player.callsign = get_local_player().callsign;
  4559.                 net_udp_send_sequence_packet(me, Netgame.players[0].protocol.udp.addr);
  4560.                 N_players = 0;
  4561.                 Game_mode = GM_GAME_OVER;
  4562.                 return(-1);     // they cancelled
  4563.         }
  4564.         return(0);
  4565. }
  4566.  
  4567. static int net_udp_request_poll( newmenu *,const d_event &event, const unused_newmenu_userdata_t *)
  4568. {
  4569.         // Polling loop for waiting-for-requests menu
  4570.         int num_ready = 0;
  4571.  
  4572.         if (event.type != EVENT_WINDOW_DRAW)
  4573.                 return 0;
  4574.         net_udp_listen();
  4575.         net_udp_timeout_check(timer_query());
  4576.  
  4577.         range_for (auto &i, partial_const_range(Players, N_players))
  4578.         {
  4579.                 if ((i.connected == CONNECT_PLAYING) || (i.connected == CONNECT_DISCONNECTED))
  4580.                         num_ready++;
  4581.         }
  4582.  
  4583.         if (num_ready == N_players) // All players have checked in or are disconnected
  4584.         {
  4585.                 return -2;
  4586.         }
  4587.        
  4588.         return 0;
  4589. }
  4590.  
  4591. static int net_udp_wait_for_requests(void)
  4592. {
  4593.         // Wait for other players to load the level before we send the sync
  4594.         int choice;
  4595.         std::array<newmenu_item, 1> m{{
  4596.                 nm_item_text(TXT_NET_LEAVE),
  4597.         }};
  4598.         Network_status = NETSTAT_WAITING;
  4599.         net_udp_flush();
  4600.  
  4601.         get_local_player().connected = CONNECT_PLAYING;
  4602.  
  4603. menu:
  4604.         choice = newmenu_do(NULL, TXT_WAIT, m, net_udp_request_poll, unused_newmenu_userdata);
  4605.  
  4606.         if (choice == -1)
  4607.         {
  4608.                 // User aborted
  4609.                 choice = nm_messagebox(NULL, 3, TXT_YES, TXT_NO, TXT_START_NOWAIT, TXT_QUITTING_NOW);
  4610.                 if (choice == 2) {
  4611.                         N_players = 1;
  4612.                         return 0;
  4613.                 }
  4614.                 if (choice != 0)
  4615.                         goto menu;
  4616.                
  4617.                 // User confirmed abort
  4618.                
  4619.                 for (unsigned i = 0; i < N_players; ++i)
  4620.                 {
  4621.                         if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED && i != Player_num)
  4622.                         {
  4623.                                 net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_ABORTED);
  4624.                         }
  4625.                 }
  4626.                 return -1;
  4627.         }
  4628.         else if (choice != -2)
  4629.                 goto menu;
  4630.  
  4631.         return 0;
  4632. }
  4633.  
  4634. /* Do required syncing after each level, before starting new one */
  4635. window_event_result net_udp_level_sync()
  4636. {
  4637.         int result = 0;
  4638.  
  4639.         UDP_MData = {};
  4640.         net_udp_noloss_init_mdata_queue();
  4641.  
  4642.         net_udp_flush(); // Flush any old packets
  4643.  
  4644.         if (N_players == 0)
  4645.                 result = net_udp_wait_for_sync();
  4646.         else if (multi_i_am_master())
  4647.         {
  4648.                 result = net_udp_wait_for_requests();
  4649.                 if (!result)
  4650.                         result = net_udp_send_sync();
  4651.         }
  4652.         else
  4653.                 result = net_udp_wait_for_sync();
  4654.  
  4655.         if (result)
  4656.         {
  4657.                 get_local_player().connected = CONNECT_DISCONNECTED;
  4658.                 net_udp_send_endlevel_packet();
  4659.                 show_menus();
  4660.                 net_udp_close();
  4661.                 return window_event_result::close;
  4662.         }
  4663.         return window_event_result::handled;
  4664. }
  4665.  
  4666. namespace dsx {
  4667. int net_udp_do_join_game()
  4668. {
  4669.  
  4670.         if (Netgame.game_status == NETSTAT_ENDLEVEL)
  4671.         {
  4672.                 nm_messagebox(TXT_SORRY, 1, TXT_OK, TXT_NET_GAME_BETWEEN2);
  4673.                 return 0;
  4674.         }
  4675.  
  4676.         // Check for valid mission name
  4677.         {
  4678.                 mission_entry_predicate mission_predicate;
  4679.                 mission_predicate.filesystem_name = Netgame.mission_name;
  4680. #if defined(DXX_BUILD_DESCENT_II)
  4681.                 /* FIXME: This should be set to true and the version set
  4682.                  * accordingly.  However, currently the host does not provide
  4683.                  * the mission version to the guests.
  4684.                  */
  4685.                 mission_predicate.check_version = false;
  4686. #endif
  4687.         if (const auto errstr = load_mission_by_name(mission_predicate, mission_name_type::guess))
  4688.         {
  4689.                 nm_messagebox(nullptr, 1, TXT_OK, "%s\n\n%s", TXT_MISSION_NOT_FOUND, errstr);
  4690.                 return 0;
  4691.         }
  4692.         }
  4693.  
  4694. #if defined(DXX_BUILD_DESCENT_II)
  4695.         if (is_D2_OEM)
  4696.         {
  4697.                 if (Netgame.levelnum>8)
  4698.                 {
  4699.                         nm_messagebox(NULL, 1, TXT_OK, "This OEM version only supports\nthe first 8 levels!");
  4700.                         return 0;
  4701.                 }
  4702.         }
  4703.  
  4704.         if (is_MAC_SHARE)
  4705.         {
  4706.                 if (Netgame.levelnum > 4)
  4707.                 {
  4708.                         nm_messagebox(NULL, 1, TXT_OK, "This SHAREWARE version only supports\nthe first 4 levels!");
  4709.                         return 0;
  4710.                 }
  4711.         }
  4712.  
  4713.         if ( !HoardEquipped() && (Netgame.gamemode == NETGAME_HOARD || Netgame.gamemode == NETGAME_TEAM_HOARD) )
  4714.         {
  4715.                 nm_messagebox(TXT_SORRY, 1, TXT_OK, "HOARD(.ham) not installed. You can't join.");
  4716.                 return 0;
  4717.         }
  4718.  
  4719.         Network_status = NETSTAT_BROWSING; // We are looking at a game menu
  4720. #endif
  4721.  
  4722.         if (net_udp_can_join_netgame(&Netgame) == join_netgame_status_code::game_in_disallowed_state)
  4723.         {
  4724.                 nm_messagebox(TXT_SORRY, 1, TXT_OK, Netgame.numplayers == Netgame.max_numplayers ? TXT_GAME_FULL : TXT_IN_PROGRESS);
  4725.                 return 0;
  4726.         }
  4727.  
  4728.         // Choice is valid, prepare to join in
  4729.         GameUniqueState.Difficulty_level = Netgame.difficulty;
  4730.         change_playernum_to(1);
  4731.  
  4732.         net_udp_set_game_mode(Netgame.gamemode);
  4733.  
  4734.         return StartNewLevel(Netgame.levelnum) == window_event_result::handled;     // look ma, we're in a game!!! (If level syncing didn't fail -kreatordxx)
  4735. }
  4736. }
  4737.  
  4738. namespace dsx {
  4739. void net_udp_leave_game()
  4740. {
  4741.         int nsave;
  4742.  
  4743.         net_udp_do_frame(1, 1);
  4744.  
  4745.         if (multi_i_am_master())
  4746.         {
  4747.                 while (Network_sending_extras>1 && Player_joining_extras!=-1)
  4748.                 {
  4749.                         timer_update();
  4750.                         net_udp_send_extras();
  4751.                 }
  4752.  
  4753.                 Netgame.numplayers = 0;
  4754.                 nsave=N_players;
  4755.                 N_players=0;
  4756.                 for (unsigned i = 1; i < nsave; ++i)
  4757.                 {
  4758.                         if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
  4759.                                 continue;
  4760.                         const auto &addr = Netgame.players[i].protocol.udp.addr;
  4761.                         net_udp_send_game_info(addr, &addr, UPID_GAME_INFO);
  4762.                 }
  4763.                 net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
  4764.                 N_players=nsave;
  4765. #if DXX_USE_TRACKER
  4766.                 if( Netgame.Tracker )
  4767.                         udp_tracker_unregister();
  4768. #endif
  4769.         }
  4770.  
  4771.         get_local_player().connected = CONNECT_DISCONNECTED;
  4772.         change_playernum_to(0);
  4773. #if defined(DXX_BUILD_DESCENT_II)
  4774.         write_player_file();
  4775. #endif
  4776.  
  4777.         net_udp_flush();
  4778.         net_udp_close();
  4779. }
  4780. }
  4781.  
  4782. static void net_udp_flush(RAIIsocket &s)
  4783. {
  4784.         if (!s)
  4785.                 return;
  4786.         unsigned i = 0;
  4787.         struct _sockaddr sender_addr;
  4788.         std::array<uint8_t, UPID_MAX_SIZE> packet;
  4789.         while (udp_receive_packet(s, packet.data(), packet.size(), &sender_addr) > 0)
  4790.                 ++i;
  4791.         if (i)
  4792.                 con_printf(CON_VERBOSE, "Flushed %u UDP packets from socket %i", i, static_cast<int>(s));
  4793. }
  4794.  
  4795. void net_udp_flush()
  4796. {
  4797.         range_for (auto &s, UDP_Socket)
  4798.                 net_udp_flush(s);
  4799. }
  4800.  
  4801. static void net_udp_listen(RAIIsocket &sock)
  4802. {
  4803.         if (!sock)
  4804.                 return;
  4805.         struct _sockaddr sender_addr;
  4806.         std::array<uint8_t, UPID_MAX_SIZE> packet;
  4807.         for (;;)
  4808.         {
  4809.                 const int size = udp_receive_packet(sock, packet.data(), packet.size(), &sender_addr);
  4810.                 if (!(size > 0))
  4811.                         break;
  4812.                 net_udp_process_packet(packet.data(), sender_addr, size);
  4813.         }
  4814. }
  4815.  
  4816. void net_udp_listen()
  4817. {
  4818.         range_for (auto &s, UDP_Socket)
  4819.                 net_udp_listen(s);
  4820. }
  4821.  
  4822. void net_udp_send_data(const uint8_t *const ptr, const unsigned len, const int priority)
  4823. {
  4824. #if DXX_HAVE_POISON_VALGRIND
  4825.         VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len);
  4826. #endif
  4827.         char check;
  4828.  
  4829.         if ((UDP_MData.mbuf_size+len) > UPID_MDATA_BUF_SIZE )
  4830.         {
  4831.                 check = ptr[0];
  4832.                 net_udp_send_mdata(0, timer_query());
  4833.                 if (UDP_MData.mbuf_size != 0)
  4834.                         Int3();
  4835.                 Assert(check == ptr[0]);
  4836.                 (void)check;
  4837.         }
  4838.  
  4839.         Assert(UDP_MData.mbuf_size+len <= UPID_MDATA_BUF_SIZE);
  4840.  
  4841.         memcpy( &UDP_MData.mbuf[UDP_MData.mbuf_size], ptr, len );
  4842.         UDP_MData.mbuf_size += len;
  4843.  
  4844.         if (priority)
  4845.                 net_udp_send_mdata((priority==2)?1:0, timer_query());
  4846. }
  4847.  
  4848. void net_udp_timeout_check(fix64 time)
  4849. {
  4850.         static fix64 last_timeout_time = 0;
  4851.         if (time>=last_timeout_time+F1_0)
  4852.         {
  4853.                 // Check for player timeouts
  4854.                 for (playernum_t i = 0; i < N_players; i++)
  4855.                 {
  4856.                         if ((i != Player_num) && (vcplayerptr(i)->connected != CONNECT_DISCONNECTED))
  4857.                         {
  4858.                                 if ((Netgame.players[i].LastPacketTime == 0) || (Netgame.players[i].LastPacketTime > time))
  4859.                                 {
  4860.                                         Netgame.players[i].LastPacketTime = time;
  4861.                                 }
  4862.                                 else if ((time - Netgame.players[i].LastPacketTime) > UDP_TIMEOUT)
  4863.                                 {
  4864.                                         multi_disconnect_player(i);
  4865.                                 }
  4866.                         }
  4867.                 }
  4868.                 last_timeout_time = time;
  4869.         }
  4870. }
  4871.  
  4872. namespace dsx {
  4873. void net_udp_do_frame(int force, int listen)
  4874. {
  4875.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  4876.         static fix64 last_pdata_time = 0, last_mdata_time = 16, last_endlevel_time = 32, last_bcast_time = 48, last_resync_time = 64;
  4877.  
  4878.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  4879.                 return;
  4880.  
  4881.         const fix64 time = timer_update();
  4882.  
  4883.         if (WaitForRefuseAnswer && time>(RefuseTimeLimit+(F1_0*12)))
  4884.                 WaitForRefuseAnswer=0;
  4885.  
  4886.         // Send positional update either in the regular PPS interval OR if forced
  4887.         if (force || (time >= (last_pdata_time+(F1_0/Netgame.PacketsPerSec))))
  4888.         {
  4889.                 last_pdata_time = time;
  4890.                 net_udp_send_pdata();
  4891. #if defined(DXX_BUILD_DESCENT_II)
  4892.                 multi_send_thief_frame();
  4893. #endif
  4894.         }
  4895.        
  4896.         if (force || (time >= (last_mdata_time+(F1_0/10))))
  4897.         {
  4898.                 last_mdata_time = time;
  4899.                 multi_send_robot_frame(0);
  4900.                 net_udp_send_mdata(0, time);
  4901.         }
  4902.  
  4903.         net_udp_noloss_process_queue(time);
  4904.  
  4905.         if (VerifyPlayerJoined!=-1 && time >= last_resync_time+F1_0)
  4906.         {
  4907.                 last_resync_time = time;
  4908.                 net_udp_resend_sync_due_to_packet_loss(); // This will resend to UDP_sync_player
  4909.         }
  4910.  
  4911.         if (time >= last_endlevel_time + F1_0 && LevelUniqueControlCenterState.Control_center_destroyed)
  4912.         {
  4913.                 last_endlevel_time = time;
  4914.                 net_udp_send_endlevel_packet();
  4915.         }
  4916.  
  4917.         // broadcast lite_info every 10 seconds
  4918.         if (multi_i_am_master() && time>=last_bcast_time+(F1_0*10))
  4919.         {
  4920.                 last_bcast_time = time;
  4921.                 net_udp_broadcast_game_info(UPID_GAME_INFO_LITE);
  4922. #if DXX_USE_TRACKER
  4923.                 if ( Netgame.Tracker )
  4924.                         udp_tracker_register();
  4925. #endif
  4926.         }
  4927.  
  4928.         net_udp_ping_frame(time);
  4929. #if DXX_USE_TRACKER
  4930.         udp_tracker_verify_ack_timeout();
  4931. #endif
  4932.  
  4933.         if (listen)
  4934.         {
  4935.                 net_udp_timeout_check(time);
  4936.                 net_udp_listen();
  4937.                 if (Network_send_objects)
  4938.                         net_udp_send_objects();
  4939.                 if (Network_sending_extras && VerifyPlayerJoined==-1)
  4940.                         net_udp_send_extras();
  4941.         }
  4942.  
  4943.         udp_traffic_stat();
  4944. }
  4945. }
  4946.  
  4947. /* CODE FOR PACKET LOSS PREVENTION - START */
  4948. /* This code tries to make sure that packets with opcode UPID_MDATA_PNEEDACK aren't lost and sent and received in order. */
  4949. /*
  4950.  * Adds a packet to our queue. Should be called when an IMPORTANT mdata packet is created.
  4951.  * player_ack is an array which should contain 0 for each player that needs to send an ACK signal.
  4952.  */
  4953. static void net_udp_noloss_add_queue_pkt(fix64 time, const ubyte *data, ushort data_size, ubyte pnum, ubyte player_ack[MAX_PLAYERS])
  4954. {
  4955.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  4956.                 return;
  4957.  
  4958.         if (!Netgame.PacketLossPrevention)
  4959.                 return;
  4960.  
  4961.         if (UDP_mdata_queue_highest == UDP_MDATA_STOR_QUEUE_SIZE) // The list is full. That should not happen. But if it does, we must do something.
  4962.         {
  4963.                 con_printf(CON_VERBOSE, "P#%u: MData store list is full!", Player_num);
  4964.                 if (multi_i_am_master()) // I am host. I will kick everyone who did not ACK the first packet and then remove it.
  4965.                 {
  4966.                         for ( int i=1; i<N_players; i++ )
  4967.                                 if (UDP_mdata_queue[0].player_ack[i] == 0)
  4968.                                         net_udp_dump_player(Netgame.players[i].protocol.udp.addr, DUMP_PKTTIMEOUT);
  4969.                         std::move(std::next(UDP_mdata_queue.begin()), UDP_mdata_queue.end(), UDP_mdata_queue.begin());
  4970.                         UDP_mdata_queue[UDP_MDATA_STOR_QUEUE_SIZE - 1] = {};
  4971.                         UDP_mdata_queue_highest--;
  4972.                 }
  4973.                 else // I am just a client. I gotta go.
  4974.                 {
  4975.                         Netgame.PacketLossPrevention = 0; // Disable PLP - otherwise we get stuck in an infinite loop here. NOTE: We could as well clean the whole queue to continue protect our disconnect signal bit it's not that important - we just wanna leave.
  4976.                         if (Game_wind)
  4977.                                 window_set_visible(Game_wind, 0);
  4978.                         nm_messagebox(NULL, 1, TXT_OK, "You left the game. You failed\nsending important packets.\nSorry.");
  4979.                         if (Game_wind)
  4980.                                 window_set_visible(Game_wind, 1);
  4981.                         multi_quit_game = 1;
  4982.                         game_leave_menus();
  4983.                 }
  4984.                 Assert(UDP_mdata_queue_highest == (UDP_MDATA_STOR_QUEUE_SIZE - 1));
  4985.         }
  4986.  
  4987.         con_printf(CON_VERBOSE, "P#%u: Adding MData pkt_num [%i,%i,%i,%i,%i,%i,%i,%i], type %i from P#%i to MData store list", Player_num, UDP_mdata_trace[0].pkt_num_tosend,UDP_mdata_trace[1].pkt_num_tosend,UDP_mdata_trace[2].pkt_num_tosend,UDP_mdata_trace[3].pkt_num_tosend,UDP_mdata_trace[4].pkt_num_tosend,UDP_mdata_trace[5].pkt_num_tosend,UDP_mdata_trace[6].pkt_num_tosend,UDP_mdata_trace[7].pkt_num_tosend, data[0], pnum);
  4988.         UDP_mdata_queue[UDP_mdata_queue_highest].used = 1;
  4989.         UDP_mdata_queue[UDP_mdata_queue_highest].pkt_initial_timestamp = time;
  4990.         for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  4991.         {
  4992.                 if (i == Player_num || player_ack[i] || vcplayerptr(i)->connected == CONNECT_DISCONNECTED) // if player me, is not playing or does not require an ACK, do not add timestamp or increment pkt_num
  4993.                         continue;
  4994.                
  4995.                 UDP_mdata_queue[UDP_mdata_queue_highest].pkt_timestamp[i] = time;
  4996.                 UDP_mdata_queue[UDP_mdata_queue_highest].pkt_num[i] = UDP_mdata_trace[i].pkt_num_tosend;
  4997.                 UDP_mdata_trace[i].pkt_num_tosend++;
  4998.                 if (UDP_mdata_trace[i].pkt_num_tosend > UDP_MDATA_PKT_NUM_MAX)
  4999.                         UDP_mdata_trace[i].pkt_num_tosend = UDP_MDATA_PKT_NUM_MIN;
  5000.         }
  5001.         UDP_mdata_queue[UDP_mdata_queue_highest].Player_num = pnum;
  5002.         memcpy( &UDP_mdata_queue[UDP_mdata_queue_highest].player_ack, player_ack, sizeof(ubyte)*MAX_PLAYERS);
  5003.         memcpy( &UDP_mdata_queue[UDP_mdata_queue_highest].data, data, sizeof(char)*data_size );
  5004.         UDP_mdata_queue[UDP_mdata_queue_highest].data_size = data_size;
  5005.         UDP_mdata_queue_highest++;
  5006. }
  5007.  
  5008. /*
  5009.  * We have received a MDATA packet. Send ACK response to sender!
  5010.  * Make sure this packet has the expected packet number so we get them all in order. If not, reject it and await further packets.
  5011.  * Also check in our UDP_mdata_trace list, if we got this packet already. If yes, return 0 so do not process it!
  5012.  */
  5013. static int net_udp_noloss_validate_mdata(uint32_t pkt_num, ubyte sender_pnum, const _sockaddr &sender_addr)
  5014. {
  5015.         ubyte pkt_sender_pnum = sender_pnum;
  5016.         int len = 0;
  5017.  
  5018.         // If we are a client, we get all our packets from the host.
  5019.         if (!multi_i_am_master())
  5020.                 sender_pnum = 0;
  5021.  
  5022.         // Check if this comes from a valid IP
  5023.         if (sender_addr != Netgame.players[sender_pnum].protocol.udp.addr)
  5024.                 return 0;
  5025.  
  5026.         // Prepare the ACK (but do not send, yet)
  5027.         std::array<uint8_t, 7> buf;
  5028.         buf[len] = UPID_MDATA_ACK;                                                                                      len++;
  5029.         buf[len] = Player_num;                                                                                          len++;
  5030.         buf[len] = pkt_sender_pnum;                                                                                     len++;
  5031.         PUT_INTEL_INT(&buf[len], pkt_num);                                                                              len += 4;
  5032.  
  5033.         // Make sure this is the packet we are expecting!
  5034.         if (UDP_mdata_trace[sender_pnum].pkt_num_torecv != pkt_num)
  5035.         {
  5036.                 range_for (auto &i, partial_const_range(UDP_mdata_trace[sender_pnum].pkt_num, static_cast<uint32_t>(UDP_MDATA_STOR_QUEUE_SIZE)))
  5037.                 {
  5038.                         if (pkt_num == i) // We got this packet already - need to REsend ACK
  5039.                         {
  5040.                                 con_printf(CON_VERBOSE, "P#%u: Resending MData ACK for pkt %i we already got by pnum %i",Player_num, pkt_num, sender_pnum);
  5041.                                 dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
  5042.                                 return 0;
  5043.                         }
  5044.                 }
  5045.                 con_printf(CON_VERBOSE, "P#%u: Rejecting MData pkt %i - expected %i by pnum %i",Player_num, pkt_num, UDP_mdata_trace[sender_pnum].pkt_num_torecv, sender_pnum);
  5046.                 return 0; // Not the right packet and we haven't gotten it, yet either. So bail out and wait for the right one.
  5047.         }
  5048.  
  5049.         con_printf(CON_VERBOSE, "P#%u: Sending MData ACK for pkt %i by pnum %i",Player_num, pkt_num, sender_pnum);
  5050.         dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
  5051.  
  5052.         UDP_mdata_trace[sender_pnum].cur_slot++;
  5053.         if (UDP_mdata_trace[sender_pnum].cur_slot >= UDP_MDATA_STOR_QUEUE_SIZE)
  5054.                 UDP_mdata_trace[sender_pnum].cur_slot = 0;
  5055.         UDP_mdata_trace[sender_pnum].pkt_num[UDP_mdata_trace[sender_pnum].cur_slot] = pkt_num;
  5056.         UDP_mdata_trace[sender_pnum].pkt_num_torecv++;
  5057.         if (UDP_mdata_trace[sender_pnum].pkt_num_torecv > UDP_MDATA_PKT_NUM_MAX)
  5058.                 UDP_mdata_trace[sender_pnum].pkt_num_torecv = UDP_MDATA_PKT_NUM_MIN;
  5059.         return 1;
  5060. }
  5061.  
  5062. /* We got an ACK by a player. Set this player slot to positive! */
  5063. void net_udp_noloss_got_ack(const uint8_t *data, uint_fast32_t data_len)
  5064. {
  5065.         int len = 0;
  5066.         uint32_t pkt_num = 0;
  5067.         ubyte sender_pnum = 0, dest_pnum = 0;
  5068.  
  5069.         if (data_len != 7)
  5070.                 return;
  5071.  
  5072.                                                                                                                         len++;
  5073.         sender_pnum = data[len];                                                                                        len++;
  5074.         dest_pnum = data[len];                                                                                          len++;
  5075.         pkt_num = GET_INTEL_INT(&data[len]);                                                                            len += 4;
  5076.  
  5077.         for (int i = 0; i < UDP_mdata_queue_highest; i++)
  5078.         {
  5079.                 if ((pkt_num == UDP_mdata_queue[i].pkt_num[sender_pnum]) && (dest_pnum == UDP_mdata_queue[i].Player_num))
  5080.                 {
  5081.                         con_printf(CON_VERBOSE, "P#%u: Got MData ACK for pkt_num %i from pnum %i for pnum %i",Player_num, pkt_num, sender_pnum, dest_pnum);
  5082.                         UDP_mdata_queue[i].player_ack[sender_pnum] = 1;
  5083.                         break;
  5084.                 }
  5085.         }
  5086. }
  5087.  
  5088. /* Init/Free the queue. Call at start and end of a game or level. */
  5089. void net_udp_noloss_init_mdata_queue(void)
  5090. {
  5091.         UDP_mdata_queue_highest=0;
  5092.         con_printf(CON_VERBOSE, "P#%u: Clearing MData store/trace list",Player_num);
  5093.         UDP_mdata_queue = {};
  5094.         for (int i = 0; i < MAX_PLAYERS; i++)
  5095.                 net_udp_noloss_clear_mdata_trace(i);
  5096. }
  5097.  
  5098. /* Reset the trace list for given player when (dis)connect happens */
  5099. void net_udp_noloss_clear_mdata_trace(ubyte player_num)
  5100. {
  5101.         con_printf(CON_VERBOSE, "P#%u: Clearing trace list for %i",Player_num, player_num);
  5102.         UDP_mdata_trace[player_num].pkt_num = {};
  5103.         UDP_mdata_trace[player_num].cur_slot = 0;
  5104.         UDP_mdata_trace[player_num].pkt_num_torecv = UDP_MDATA_PKT_NUM_MIN;
  5105.         UDP_mdata_trace[player_num].pkt_num_tosend = UDP_MDATA_PKT_NUM_MIN;
  5106. }
  5107.  
  5108. /*
  5109.  * The main queue-process function.
  5110.  * Check if we can remove a packet from queue, and check if there are packets in queue which we need to re-send
  5111.  */
  5112. void net_udp_noloss_process_queue(fix64 time)
  5113. {
  5114.         int total_len = 0;
  5115.  
  5116.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  5117.                 return;
  5118.  
  5119.         if (!Netgame.PacketLossPrevention)
  5120.                 return;
  5121.  
  5122.         for (int queuec = 0; queuec < UDP_mdata_queue_highest; queuec++)
  5123.         {
  5124.                 int needack = 0;
  5125.  
  5126.                 // This might happen if we get out ACK's in the wrong order. So ignore that packet for now. It'll resolve itself.
  5127.                 if (!UDP_mdata_queue[queuec].used)
  5128.                         continue;
  5129.  
  5130.                 // Check if at least one connected player has not ACK'd the packet
  5131.                 for (unsigned plc = 0; plc < MAX_PLAYERS; ++plc)
  5132.                 {
  5133.                         // If player is not playing anymore, we can remove him from list. Also remove *me* (even if that should have been done already). Also make sure Clients do not send to anyone else than Host
  5134.                         if ((vcplayerptr(plc)->connected != CONNECT_PLAYING || plc == Player_num) || (!multi_i_am_master() && plc > 0))
  5135.                                 UDP_mdata_queue[queuec].player_ack[plc] = 1;
  5136.  
  5137.                         if (!UDP_mdata_queue[queuec].player_ack[plc])
  5138.                         {
  5139.                                 // Resend if enough time has passed.
  5140.                                 if (UDP_mdata_queue[queuec].pkt_timestamp[plc] + (F1_0/4) <= time)
  5141.                                 {
  5142.                                         ubyte buf[sizeof(UDP_mdata_info)];
  5143.                                         int len = 0;
  5144.                                        
  5145.                                         con_printf(CON_VERBOSE, "P#%u: Resending pkt_num %i from pnum %i to pnum %i",Player_num, UDP_mdata_queue[queuec].pkt_num[plc], UDP_mdata_queue[queuec].Player_num, plc);
  5146.                                        
  5147.                                         UDP_mdata_queue[queuec].pkt_timestamp[plc] = time;
  5148.                                         memset(&buf, 0, sizeof(UDP_mdata_info));
  5149.                                        
  5150.                                         // Prepare the packet and send it
  5151.                                         buf[len] = UPID_MDATA_PNEEDACK;                                                                                                 len++;
  5152.                                         buf[len] = UDP_mdata_queue[queuec].Player_num;                                                          len++;
  5153.                                         PUT_INTEL_INT(buf + len, UDP_mdata_queue[queuec].pkt_num[plc]);                                 len += 4;
  5154.                                         memcpy(&buf[len], UDP_mdata_queue[queuec].data.data(), sizeof(char)*UDP_mdata_queue[queuec].data_size);
  5155.                                                                                                                                                                                                 len += UDP_mdata_queue[queuec].data_size;
  5156.                                         dxx_sendto(Netgame.players[plc].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
  5157.                                         total_len += len;
  5158.                                 }
  5159.                                 needack++;
  5160.                         }
  5161.                 }
  5162.  
  5163.                 // Check if we can remove that packet due to to it had no resend's or Timeout
  5164.                 if (needack==0 || (UDP_mdata_queue[queuec].pkt_initial_timestamp + UDP_TIMEOUT <= time))
  5165.                 {
  5166.                         if (needack) // packet timed out but still not all have ack'd.
  5167.                         {
  5168.                                 if (multi_i_am_master()) // We are host, so we kick the remaining players.
  5169.                                 {
  5170.                                         for ( int plc=1; plc<N_players; plc++ )
  5171.                                                 if (UDP_mdata_queue[queuec].player_ack[plc] == 0)
  5172.                                                         net_udp_dump_player(Netgame.players[plc].protocol.udp.addr, DUMP_PKTTIMEOUT);
  5173.                                 }
  5174.                                 else // We are client, so we gotta go.
  5175.                                 {
  5176.                                         Netgame.PacketLossPrevention = 0; // Disable PLP - otherwise we get stuck in an infinite loop here. NOTE: We could as well clean the whole queue to continue protect our disconnect signal bit it's not that important - we just wanna leave.
  5177.                                         if (Game_wind)
  5178.                                                 window_set_visible(Game_wind, 0);
  5179.                                         nm_messagebox(NULL, 1, TXT_OK, "You left the game. You failed\nsending important packets.\nSorry.");
  5180.                                         if (Game_wind)
  5181.                                                 window_set_visible(Game_wind, 1);
  5182.                                         multi_quit_game = 1;
  5183.                                         game_leave_menus();
  5184.                                 }
  5185.                         }
  5186.                         con_printf(CON_VERBOSE, "P#%u: Removing stored pkt_num [%i,%i,%i,%i,%i,%i,%i,%i] - missing ACKs: %i",Player_num, UDP_mdata_queue[queuec].pkt_num[0],UDP_mdata_queue[queuec].pkt_num[1],UDP_mdata_queue[queuec].pkt_num[2],UDP_mdata_queue[queuec].pkt_num[3],UDP_mdata_queue[queuec].pkt_num[4],UDP_mdata_queue[queuec].pkt_num[5],UDP_mdata_queue[queuec].pkt_num[6],UDP_mdata_queue[queuec].pkt_num[7], needack); // Just *marked* for removal. The actual process happens further below.
  5187.                         UDP_mdata_queue[queuec].used = 0;
  5188.                 }
  5189.  
  5190.                 // Send up to half our max packet size
  5191.                 if (total_len >= (UPID_MAX_SIZE/2))
  5192.                         break;
  5193.         }
  5194.  
  5195.         // Now that we are done processing the queue, actually remove all unused packets from the top of the list.
  5196.         while (!UDP_mdata_queue[0].used && UDP_mdata_queue_highest > 0)
  5197.         {
  5198.                 std::move(std::next(UDP_mdata_queue.begin()), UDP_mdata_queue.end(), UDP_mdata_queue.begin());
  5199.                 UDP_mdata_queue[UDP_MDATA_STOR_QUEUE_SIZE - 1] = {};
  5200.                 UDP_mdata_queue_highest--;
  5201.         }
  5202. }
  5203. /* CODE FOR PACKET LOSS PREVENTION - END */
  5204.  
  5205. void net_udp_send_mdata_direct(const ubyte *data, int data_len, int pnum, int needack)
  5206. {
  5207.         ubyte buf[sizeof(UDP_mdata_info)];
  5208.         ubyte pack[MAX_PLAYERS];
  5209.         int len = 0;
  5210.        
  5211.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  5212.                 return;
  5213.  
  5214.         if (!(data_len > 0))
  5215.                 return;
  5216.  
  5217.         if (!multi_i_am_master() && pnum != 0)
  5218.                 Error("Client sent direct data to non-Host in net_udp_send_mdata_direct()!\n");
  5219.  
  5220.         if (!Netgame.PacketLossPrevention)
  5221.                 needack = 0;
  5222.  
  5223.         memset(&buf, 0, sizeof(UDP_mdata_info));
  5224.         memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
  5225.  
  5226.         pack[pnum] = 0;
  5227.  
  5228.         if (needack)
  5229.                 buf[len] = UPID_MDATA_PNEEDACK;
  5230.         else
  5231.                 buf[len] = UPID_MDATA_PNORM;
  5232.                                                                                                                 len++;
  5233.         buf[len] = Player_num;                                                                                  len++;
  5234.         if (needack)
  5235.         {
  5236.                 PUT_INTEL_INT(buf + len, UDP_mdata_trace[pnum].pkt_num_tosend);                                 len += 4;
  5237.         }
  5238.         memcpy(&buf[len], data, sizeof(char)*data_len);                                                         len += data_len;
  5239.  
  5240.         dxx_sendto(Netgame.players[pnum].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
  5241.  
  5242.         if (needack)
  5243.                 net_udp_noloss_add_queue_pkt(timer_query(), data, data_len, Player_num, pack);
  5244. }
  5245.  
  5246. void net_udp_send_mdata(int needack, fix64 time)
  5247. {
  5248.         ubyte buf[sizeof(UDP_mdata_info)];
  5249.         ubyte pack[MAX_PLAYERS];
  5250.         int len = 0;
  5251.        
  5252.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  5253.                 return;
  5254.  
  5255.         if (!(UDP_MData.mbuf_size > 0))
  5256.                 return;
  5257.  
  5258.         if (!Netgame.PacketLossPrevention)
  5259.                 needack = 0;
  5260.  
  5261.         memset(&buf, 0, sizeof(UDP_mdata_info));
  5262.         memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
  5263.  
  5264.         if (needack)
  5265.                 buf[len] = UPID_MDATA_PNEEDACK;
  5266.         else
  5267.                 buf[len] = UPID_MDATA_PNORM;
  5268.                                                                                                                 len++;
  5269.         buf[len] = Player_num;                                                                                  len++;
  5270.         if (needack)                                                                                            len += 4; // we place the pkt_num later since it changes per player
  5271.         memcpy(&buf[len], UDP_MData.mbuf.data(), sizeof(char)*UDP_MData.mbuf_size);
  5272.         len += UDP_MData.mbuf_size;
  5273.  
  5274.         if (multi_i_am_master())
  5275.         {
  5276.                 for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  5277.                 {
  5278.                         if (vcplayerptr(i)->connected == CONNECT_PLAYING)
  5279.                         {
  5280.                                 if (needack) // assign pkt_num
  5281.                                         PUT_INTEL_INT(buf + 2, UDP_mdata_trace[i].pkt_num_tosend);
  5282.                                 dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
  5283.                                 pack[i] = 0;
  5284.                         }
  5285.                 }
  5286.         }
  5287.         else
  5288.         {
  5289.                 if (needack) // assign pkt_num
  5290.                         PUT_INTEL_INT(buf + 2, UDP_mdata_trace[0].pkt_num_tosend);
  5291.                 dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, len, 0);
  5292.                 pack[0] = 0;
  5293.         }
  5294.        
  5295.         if (needack)
  5296.                 net_udp_noloss_add_queue_pkt(time, UDP_MData.mbuf.data(), UDP_MData.mbuf_size, Player_num, pack);
  5297.  
  5298.         // Clear UDP_MData except pkt_num. That one must not be deleted so we can clearly keep track of important packets.
  5299.         UDP_MData.type = 0;
  5300.         UDP_MData.Player_num = 0;
  5301.         UDP_MData.mbuf_size = 0;
  5302.         UDP_MData.mbuf = {};
  5303. }
  5304.  
  5305. void net_udp_process_mdata(uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr, int needack)
  5306. {
  5307.         int pnum = data[1], dataoffset = (needack?6:2);
  5308.  
  5309.         // Check if packet might be bogus
  5310.         if ((pnum < 0) || (data_len > sizeof(UDP_mdata_info)))
  5311.                 return;
  5312.  
  5313.         // Check if it came from valid IP
  5314.         if (multi_i_am_master())
  5315.         {
  5316.                 if (sender_addr != Netgame.players[pnum].protocol.udp.addr)
  5317.                 {
  5318.                         return;
  5319.                 }
  5320.         }
  5321.         else
  5322.         {
  5323.                 if (sender_addr != Netgame.players[0].protocol.udp.addr)
  5324.                 {
  5325.                         return;
  5326.                 }
  5327.         }
  5328.  
  5329.         // Add needack packet and check for possible redundancy
  5330.         if (needack)
  5331.         {
  5332.                 if (!net_udp_noloss_validate_mdata(GET_INTEL_INT(&data[2]), pnum, sender_addr))
  5333.                         return;
  5334.         }
  5335.  
  5336.         // send this to everyone else (if master)
  5337.         if (multi_i_am_master())
  5338.         {
  5339.                 ubyte pack[MAX_PLAYERS];
  5340.                 memset(&pack, 1, sizeof(ubyte)*MAX_PLAYERS);
  5341.                
  5342.                 for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  5343.                 {
  5344.                         if (i != pnum && vcplayerptr(i)->connected == CONNECT_PLAYING)
  5345.                         {
  5346.                                 if (needack)
  5347.                                 {
  5348.                                         pack[i] = 0;
  5349.                                         PUT_INTEL_INT(data + 2, UDP_mdata_trace[i].pkt_num_tosend);
  5350.                                 }
  5351.                                 dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], data, data_len, 0);
  5352.                                
  5353.                         }
  5354.                 }
  5355.  
  5356.                 if (needack)
  5357.                 {
  5358.                         net_udp_noloss_add_queue_pkt(timer_query(), data+dataoffset, data_len-dataoffset, pnum, pack);
  5359.                 }
  5360.         }
  5361.  
  5362.         // Check if we are in correct state to process the packet
  5363.         if (!((Network_status == NETSTAT_PLAYING)||(Network_status == NETSTAT_ENDLEVEL) || Network_status==NETSTAT_WAITING))
  5364.                 return;
  5365.  
  5366.         // Process
  5367.  
  5368.         multi_process_bigdata(pnum, data+dataoffset, data_len-dataoffset );
  5369. }
  5370.  
  5371. void net_udp_send_pdata()
  5372. {
  5373.         auto &Objects = LevelUniqueObjectState.Objects;
  5374.         auto &vmobjptr = Objects.vmptr;
  5375.         std::array<uint8_t, 3 + quaternionpos::packed_size::value> buf;
  5376.         int len = 0;
  5377.  
  5378.         if (!(Game_mode&GM_NETWORK) || !UDP_Socket[0])
  5379.                 return;
  5380.         auto &plr = get_local_player();
  5381.         if (plr.connected != CONNECT_PLAYING)
  5382.                 return;
  5383.         if ( !( Network_status == NETSTAT_PLAYING || Network_status == NETSTAT_ENDLEVEL ) )
  5384.                 return;
  5385.  
  5386.         buf[len] = UPID_PDATA;                                                                  len++;
  5387.         buf[len] = Player_num;                                                                  len++;
  5388.         buf[len] = plr.connected;                                               len++;
  5389.  
  5390.         quaternionpos qpp{};
  5391.         create_quaternionpos(qpp, vmobjptr(plr.objnum));
  5392.         PUT_INTEL_SHORT(&buf[len], qpp.orient.w);                                                       len += 2;
  5393.         PUT_INTEL_SHORT(&buf[len], qpp.orient.x);                                                       len += 2;
  5394.         PUT_INTEL_SHORT(&buf[len], qpp.orient.y);                                                       len += 2;
  5395.         PUT_INTEL_SHORT(&buf[len], qpp.orient.z);                                                       len += 2;
  5396.         PUT_INTEL_INT(&buf[len], qpp.pos.x);                                                    len += 4;
  5397.         PUT_INTEL_INT(&buf[len], qpp.pos.y);                                                    len += 4;
  5398.         PUT_INTEL_INT(&buf[len], qpp.pos.z);                                                    len += 4;
  5399.         PUT_INTEL_SHORT(&buf[len], qpp.segment);                                                        len += 2;
  5400.         PUT_INTEL_INT(&buf[len], qpp.vel.x);                                                    len += 4;
  5401.         PUT_INTEL_INT(&buf[len], qpp.vel.y);                                                    len += 4;
  5402.         PUT_INTEL_INT(&buf[len], qpp.vel.z);                                                    len += 4;
  5403.         PUT_INTEL_INT(&buf[len], qpp.rotvel.x);                                                 len += 4;
  5404.         PUT_INTEL_INT(&buf[len], qpp.rotvel.y);                                                 len += 4;
  5405.         PUT_INTEL_INT(&buf[len], qpp.rotvel.z);                                                 len += 4; // 46 + 3 = 49
  5406.  
  5407.         if (multi_i_am_master())
  5408.         {
  5409.                 for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  5410.                         if (vcplayerptr(i)->connected != CONNECT_DISCONNECTED)
  5411.                                 dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
  5412.         }
  5413.         else
  5414.         {
  5415.                 dxx_sendto(Netgame.players[0].protocol.udp.addr, UDP_Socket[0], buf, 0);
  5416.         }
  5417. }
  5418.  
  5419. void net_udp_process_pdata(const uint8_t *data, uint_fast32_t data_len, const _sockaddr &sender_addr)
  5420. {
  5421.         UDP_frame_info pd;
  5422.         int len = 0;
  5423.  
  5424.         if ( !( Game_mode & GM_NETWORK && ( Network_status == NETSTAT_PLAYING || Network_status == NETSTAT_ENDLEVEL ) ) )
  5425.                 return;
  5426.  
  5427.         len++;
  5428.  
  5429.         pd = {};
  5430.        
  5431.         if (data_len > sizeof(UDP_frame_info))
  5432.                 return;
  5433.         if (data_len != UPID_PDATA_SIZE)
  5434.                 return;
  5435.  
  5436.         if (sender_addr != Netgame.players[((multi_i_am_master())?(data[len]):(0))].protocol.udp.addr)
  5437.                 return;
  5438.  
  5439.         pd.Player_num = data[len];                                                              len++;
  5440.         pd.connected = data[len];                                                               len++;
  5441.         pd.qpp.orient.w = GET_INTEL_SHORT(&data[len]);                                  len += 2;
  5442.         pd.qpp.orient.x = GET_INTEL_SHORT(&data[len]);                                  len += 2;
  5443.         pd.qpp.orient.y = GET_INTEL_SHORT(&data[len]);                                  len += 2;
  5444.         pd.qpp.orient.z = GET_INTEL_SHORT(&data[len]);                                  len += 2;
  5445.         pd.qpp.pos.x = GET_INTEL_INT(&data[len]);                                               len += 4;
  5446.         pd.qpp.pos.y = GET_INTEL_INT(&data[len]);                                               len += 4;
  5447.         pd.qpp.pos.z = GET_INTEL_INT(&data[len]);                                               len += 4;
  5448.         pd.qpp.segment = GET_INTEL_SHORT(&data[len]);                                   len += 2;
  5449.         pd.qpp.vel.x = GET_INTEL_INT(&data[len]);                                               len += 4;
  5450.         pd.qpp.vel.y = GET_INTEL_INT(&data[len]);                                               len += 4;
  5451.         pd.qpp.vel.z = GET_INTEL_INT(&data[len]);                                               len += 4;
  5452.         pd.qpp.rotvel.x = GET_INTEL_INT(&data[len]);                                    len += 4;
  5453.         pd.qpp.rotvel.y = GET_INTEL_INT(&data[len]);                                    len += 4;
  5454.         pd.qpp.rotvel.z = GET_INTEL_INT(&data[len]);                                    len += 4;
  5455.        
  5456.         if (multi_i_am_master()) // I am host - must relay this packet to others!
  5457.         {
  5458.                 const unsigned ppn = pd.Player_num;
  5459.                 if (ppn > 0 && ppn <= N_players && vcplayerptr(ppn)->connected == CONNECT_PLAYING) // some checking whether this packet is legal
  5460.                 {
  5461.                         for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  5462.                         {
  5463.                                 // not to sender or disconnected/waiting players - right.
  5464.                                 if (i == ppn)
  5465.                                         continue;
  5466.                                 auto &iplr = *vcplayerptr(i);
  5467.                                 if (iplr.connected != CONNECT_DISCONNECTED && iplr.connected != CONNECT_WAITING)
  5468.                                         dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], data, data_len, 0);
  5469.                         }
  5470.                 }
  5471.         }
  5472.  
  5473.         net_udp_read_pdata_packet (&pd);
  5474. }
  5475.  
  5476. void net_udp_read_pdata_packet(UDP_frame_info *pd)
  5477. {
  5478.         auto &Objects = LevelUniqueObjectState.Objects;
  5479.         auto &vmobjptridx = Objects.vmptridx;
  5480.         const unsigned TheirPlayernum = pd->Player_num;
  5481.         auto &tplr = *vmplayerptr(TheirPlayernum);
  5482.         const auto TheirObjnum = tplr.objnum;
  5483.  
  5484.         if (multi_i_am_master())
  5485.         {
  5486.                 // latecoming player seems to successfully have synced
  5487.                 if ( VerifyPlayerJoined != -1 && TheirPlayernum == VerifyPlayerJoined )
  5488.                         VerifyPlayerJoined=-1;
  5489.                 // we say that guy is disconnected so we do not want him/her in game
  5490.                 if (tplr.connected == CONNECT_DISCONNECTED )
  5491.                         return;
  5492.         }
  5493.         else
  5494.         {
  5495.                 // only by reading pdata a client can know if a player reconnected. So do that here.
  5496.                 // NOTE: we might do this somewhere else - maybe with a sync packet like when adding a fresh player.
  5497.                 if (tplr.connected == CONNECT_DISCONNECTED && pd->connected == CONNECT_PLAYING )
  5498.                 {
  5499.                         tplr.connected = CONNECT_PLAYING;
  5500.  
  5501.                         if (Newdemo_state == ND_STATE_RECORDING)
  5502.                                 newdemo_record_multi_reconnect(TheirPlayernum);
  5503.  
  5504.                         digi_play_sample( SOUND_HUD_MESSAGE, F1_0);
  5505.                         ClipRank (&Netgame.players[TheirPlayernum].rank);
  5506.                        
  5507.                         const auto &&rankstr = GetRankStringWithSpace(Netgame.players[TheirPlayernum].rank);
  5508.                         HUD_init_message(HM_MULTI, "%s%s'%s' %s", rankstr.first, rankstr.second, static_cast<const char *>(vcplayerptr(TheirPlayernum)->callsign), TXT_REJOIN);
  5509.  
  5510.                         multi_send_score();
  5511.  
  5512.                         net_udp_noloss_clear_mdata_trace(TheirPlayernum);
  5513.                 }
  5514.         }
  5515.  
  5516.         if (vcplayerptr(TheirPlayernum)->connected != CONNECT_PLAYING || TheirPlayernum == Player_num)
  5517.                 return;
  5518.  
  5519.         if (!multi_quit_game && (TheirPlayernum >= N_players))
  5520.         {
  5521.                 if (Network_status!=NETSTAT_WAITING)
  5522.                 {
  5523.                         Int3(); // We missed an important packet!
  5524.                         multi_consistency_error(0);
  5525.                         return;
  5526.                 }
  5527.                 else
  5528.                         return;
  5529.         }
  5530.  
  5531.         const auto TheirObj = vmobjptridx(TheirObjnum);
  5532.         Netgame.players[TheirPlayernum].LastPacketTime = timer_query();
  5533.  
  5534.         // do not read the packet unless the level is loaded.
  5535.         if (vcplayerptr(Player_num)->connected == CONNECT_DISCONNECTED || vcplayerptr(Player_num)->connected == CONNECT_WAITING)
  5536.                 return;
  5537.         //------------ Read the player's ship's object info ----------------------
  5538.         extract_quaternionpos(TheirObj, pd->qpp);
  5539.         if (TheirObj->movement_type == MT_PHYSICS)
  5540.                 set_thrust_from_velocity(TheirObj);
  5541. }
  5542.  
  5543. #if defined(DXX_BUILD_DESCENT_II)
  5544. static void net_udp_send_smash_lights (const playernum_t pnum)
  5545.  {
  5546.   // send the lights that have been blown out
  5547.         range_for (const auto &&segp, vmsegptridx)
  5548.         {
  5549.                 if (segp->light_subtracted)
  5550.                         multi_send_light_specific(pnum, segp, segp->light_subtracted);
  5551.         }
  5552.  }
  5553.  
  5554. static void net_udp_send_fly_thru_triggers (const playernum_t pnum)
  5555.  {
  5556.   // send the fly thru triggers that have been disabled
  5557.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  5558.         auto &vctrgptridx = Triggers.vcptridx;
  5559.         range_for (const auto &&t, vctrgptridx)
  5560.         {
  5561.                 if (t->flags & trigger_behavior_flags::disabled)
  5562.                         multi_send_trigger_specific(pnum, t);
  5563.         }
  5564.  }
  5565.  
  5566. static void net_udp_send_player_flags()
  5567.  {
  5568.         for (playernum_t i=0;i<N_players;i++)
  5569.         multi_send_flags(i);
  5570.  }
  5571. #endif
  5572.  
  5573. // Send the ping list in regular intervals
  5574. void net_udp_ping_frame(fix64 time)
  5575. {
  5576.         static fix64 PingTime = 0;
  5577.        
  5578.         if ((PingTime + F1_0) < time)
  5579.         {
  5580.                 std::array<uint8_t, UPID_PING_SIZE> buf;
  5581.                 int len = 0;
  5582.                
  5583.                 memset(&buf, 0, sizeof(ubyte)*UPID_PING_SIZE);
  5584.                 buf[len] = UPID_PING;                                                   len++;
  5585.                 memcpy(&buf[len], &time, 8);                                            len += 8;
  5586.                 range_for (auto &i, partial_const_range(Netgame.players, 1u, MAX_PLAYERS))
  5587.                 {
  5588.                         PUT_INTEL_INT(&buf[len], i.ping);               len += 4;
  5589.                 }
  5590.                
  5591.                 for (unsigned i = 1; i < MAX_PLAYERS; ++i)
  5592.                 {
  5593.                         if (vcplayerptr(i)->connected == CONNECT_DISCONNECTED)
  5594.                                 continue;
  5595.                         dxx_sendto(Netgame.players[i].protocol.udp.addr, UDP_Socket[0], buf, 0);
  5596.                 }
  5597.                 PingTime = time;
  5598.         }
  5599. }
  5600.  
  5601. // Got a PING from host. Apply the pings to our players and respond to host.
  5602. void net_udp_process_ping(const uint8_t *data, const _sockaddr &sender_addr)
  5603. {
  5604.         fix64 host_ping_time = 0;
  5605.         std::array<uint8_t, UPID_PONG_SIZE> buf;
  5606.         int len = 0;
  5607.  
  5608.         if (Netgame.players[0].protocol.udp.addr != sender_addr)
  5609.                 return;
  5610.  
  5611.                                                                                 len++; // Skip UPID byte;
  5612.         memcpy(&host_ping_time, &data[len], 8);                                 len += 8;
  5613.         range_for (auto &i, partial_range(Netgame.players, 1u, MAX_PLAYERS))
  5614.         {
  5615.                 i.ping = GET_INTEL_INT(&(data[len]));           len += 4;
  5616.         }
  5617.        
  5618.         buf[0] = UPID_PONG;
  5619.         buf[1] = Player_num;
  5620.         memcpy(&buf[2], &host_ping_time, 8);
  5621.        
  5622.         dxx_sendto(sender_addr, UDP_Socket[0], buf, 0);
  5623. }
  5624.  
  5625. // Got a PONG from a client. Check the time and add it to our players.
  5626. void net_udp_process_pong(const uint8_t *data, const _sockaddr &sender_addr)
  5627. {
  5628.         const uint_fast32_t playernum = data[1];
  5629.         if (playernum >= MAX_PLAYERS || playernum < 1)
  5630.                 return;
  5631.         if (sender_addr != Netgame.players[playernum].protocol.udp.addr)
  5632.                 return;
  5633.         fix64 client_pong_time;
  5634.         memcpy(&client_pong_time, &data[2], 8);
  5635.         const fix64 delta64 = timer_update() - client_pong_time;
  5636.         const fix delta = static_cast<fix>(delta64);
  5637.         fix result;
  5638.         if (likely(delta64 == static_cast<fix64>(delta)))
  5639.         {
  5640.                 result = f2i(fixmul64(delta, i2f(1000)));
  5641.                 const fix lower_bound = 0;
  5642.                 const fix upper_bound = 9999;
  5643.                 if (unlikely(result < lower_bound))
  5644.                         result = lower_bound;
  5645.                 else if (unlikely(result > upper_bound))
  5646.                         result = upper_bound;
  5647.         }
  5648.         else
  5649.                 result = 0;
  5650.         Netgame.players[playernum].ping = result;
  5651. }
  5652.  
  5653. namespace dsx {
  5654. void net_udp_do_refuse_stuff (UDP_sequence_packet *their)
  5655. {
  5656.         int new_player_num;
  5657.        
  5658.         ClipRank (&their->player.rank);
  5659.                
  5660.         for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  5661.         {
  5662.                 if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
  5663.                 {
  5664.                         net_udp_welcome_player(their);
  5665.                         return;
  5666.                 }
  5667.         }
  5668.  
  5669.         if (!WaitForRefuseAnswer)
  5670.         {
  5671.                 for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  5672.                 {
  5673.                         if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
  5674.                         {
  5675.                                 net_udp_welcome_player(their);
  5676.                                 return;
  5677.                         }
  5678.                 }
  5679.        
  5680. #if defined(DXX_BUILD_DESCENT_I)
  5681.                 digi_play_sample (SOUND_CONTROL_CENTER_WARNING_SIREN,F1_0*2);
  5682. #elif defined(DXX_BUILD_DESCENT_II)
  5683.                 digi_play_sample (SOUND_HUD_JOIN_REQUEST,F1_0*2);
  5684. #endif
  5685.        
  5686.                 const auto &&rankstr = GetRankStringWithSpace(their->player.rank);
  5687.                 if (Game_mode & GM_TEAM)
  5688.                 {
  5689.                         HUD_init_message(HM_MULTI, "%s%s'%s' wants to join", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign));
  5690.                         HUD_init_message(HM_MULTI, "Alt-1 assigns to team %s. Alt-2 to team %s", static_cast<const char *>(Netgame.team_name[0]), static_cast<const char *>(Netgame.team_name[1]));
  5691.                 }
  5692.                 else
  5693.                 {
  5694.                         HUD_init_message(HM_MULTI, "%s%s'%s' wants to join (accept: F6)", rankstr.first, rankstr.second, static_cast<const char *>(their->player.callsign));
  5695.                 }
  5696.        
  5697.                 strcpy (RefusePlayerName,their->player.callsign);
  5698.                 RefuseTimeLimit=timer_query();
  5699.                 RefuseThisPlayer=0;
  5700.                 WaitForRefuseAnswer=1;
  5701.         }
  5702.         else
  5703.         {
  5704.                 for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  5705.                 {
  5706.                         if (!d_stricmp(vcplayerptr(i)->callsign, their->player.callsign) && their->player.protocol.udp.addr == Netgame.players[i].protocol.udp.addr)
  5707.                         {
  5708.                                 net_udp_welcome_player(their);
  5709.                                 return;
  5710.                         }
  5711.                 }
  5712.        
  5713.                 if (strcmp(their->player.callsign,RefusePlayerName))
  5714.                         return;
  5715.        
  5716.                 if (RefuseThisPlayer)
  5717.                 {
  5718.                         RefuseTimeLimit=0;
  5719.                         RefuseThisPlayer=0;
  5720.                         WaitForRefuseAnswer=0;
  5721.                         if (Game_mode & GM_TEAM)
  5722.                         {
  5723.                                 new_player_num=net_udp_get_new_player_num ();
  5724.        
  5725.                                 Assert (RefuseTeam==1 || RefuseTeam==2);        
  5726.                        
  5727.                                 if (RefuseTeam==1)      
  5728.                                         Netgame.team_vector &=(~(1<<new_player_num));
  5729.                                 else
  5730.                                         Netgame.team_vector |=(1<<new_player_num);
  5731.                                 net_udp_welcome_player(their);
  5732.                                 net_udp_send_netgame_update();
  5733.                         }
  5734.                         else
  5735.                         {
  5736.                                 net_udp_welcome_player(their);
  5737.                         }
  5738.                         return;
  5739.                 }
  5740.  
  5741.                 if ((timer_query()) > RefuseTimeLimit+REFUSE_INTERVAL)
  5742.                 {
  5743.                         RefuseTimeLimit=0;
  5744.                         RefuseThisPlayer=0;
  5745.                         WaitForRefuseAnswer=0;
  5746.                         if (!strcmp (their->player.callsign,RefusePlayerName))
  5747.                         {
  5748.                                 net_udp_dump_player(their->player.protocol.udp.addr, DUMP_DORK);
  5749.                         }
  5750.                         return;
  5751.                 }
  5752.         }
  5753. }
  5754. }
  5755.  
  5756. static int net_udp_get_new_player_num ()
  5757. {
  5758.         if ( N_players < Netgame.max_numplayers)
  5759.                 return (N_players);
  5760.  
  5761.         else
  5762.         {
  5763.                 // Slots are full but game is open, see if anyone is
  5764.                 // disconnected and replace the oldest player with this new one
  5765.  
  5766.                 int oldest_player = -1;
  5767.                 fix64 oldest_time = timer_query();
  5768.  
  5769.                 Assert(N_players == Netgame.max_numplayers);
  5770.  
  5771.                 for (unsigned i = 0; i < N_players; ++i)
  5772.                 {
  5773.                         if (!vcplayerptr(i)->connected && Netgame.players[i].LastPacketTime < oldest_time)
  5774.                         {
  5775.                                 oldest_time = Netgame.players[i].LastPacketTime;
  5776.                                 oldest_player = i;
  5777.                         }
  5778.                 }
  5779.                 return (oldest_player);
  5780.         }
  5781. }
  5782.  
  5783. namespace dsx {
  5784. void net_udp_send_extras ()
  5785. {
  5786.         static fix64 last_send_time = 0;
  5787.        
  5788.         if (last_send_time + (F1_0/50) > timer_query())
  5789.                 return;
  5790.         last_send_time = timer_query();
  5791.  
  5792.         Assert (Player_joining_extras>-1);
  5793.  
  5794. #if defined(DXX_BUILD_DESCENT_I)
  5795.         if (Network_sending_extras==3 && (Netgame.PlayTimeAllowed.count() || Netgame.KillGoal))
  5796. #elif defined(DXX_BUILD_DESCENT_II)
  5797.         if (Network_sending_extras==9)
  5798.                 net_udp_send_fly_thru_triggers(Player_joining_extras);
  5799.         if (Network_sending_extras==8)
  5800.                 net_udp_send_door_updates(Player_joining_extras);
  5801.         if (Network_sending_extras==7)
  5802.                 multi_send_markers();
  5803.         if (Network_sending_extras==6 && (Game_mode & GM_MULTI_ROBOTS))
  5804.                 multi_send_stolen_items();
  5805.         if (Network_sending_extras==5 && (Netgame.PlayTimeAllowed.count() || Netgame.KillGoal))
  5806. #endif
  5807.                 multi_send_kill_goal_counts();
  5808. #if defined(DXX_BUILD_DESCENT_II)
  5809.         if (Network_sending_extras==4)
  5810.                 net_udp_send_smash_lights(Player_joining_extras);
  5811.         if (Network_sending_extras==3)
  5812.                 net_udp_send_player_flags();    
  5813. #endif
  5814.         if (Network_sending_extras==2)
  5815.                 multi_send_player_inventory(1);
  5816.         if (Network_sending_extras==1 && Game_mode & GM_BOUNTY)
  5817.                 multi_send_bounty();
  5818.  
  5819.         Network_sending_extras--;
  5820.         if (!Network_sending_extras)
  5821.                 Player_joining_extras=-1;
  5822. }
  5823. }
  5824.  
  5825. static int show_game_info_handler(newmenu *, const d_event &event, netgame_info *netgame)
  5826. {
  5827.         switch (event.type)
  5828.         {
  5829.                 case EVENT_NEWMENU_SELECTED:
  5830.                 {
  5831.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  5832.                         if (citem != 1)
  5833.                                 return 0;
  5834.                         show_netgame_info(*netgame);
  5835.                         return 1;
  5836.                 }
  5837.                 default:
  5838.                         return 0;
  5839.         }
  5840. }
  5841.  
  5842. namespace dsx {
  5843. int net_udp_show_game_info()
  5844. {
  5845.         char rinfo[512];
  5846.         int c;
  5847.         netgame_info *netgame = &Netgame;
  5848.  
  5849. #if defined(DXX_BUILD_DESCENT_I)
  5850. #define DXX_SECRET_LEVEL_FORMAT "%s"
  5851. #define DXX_SECRET_LEVEL_PARAMETER      (netgame->levelnum >= 0 ? "" : "S"), \
  5852.         netgame->levelnum < 0 ? -netgame->levelnum :    /* else portion provided by invoker */
  5853. #elif defined(DXX_BUILD_DESCENT_II)
  5854. #define DXX_SECRET_LEVEL_FORMAT
  5855. #define DXX_SECRET_LEVEL_PARAMETER
  5856. #endif
  5857.         unsigned gamemode = netgame->gamemode;
  5858.         unsigned players;
  5859. #if defined(DXX_BUILD_DESCENT_I)
  5860.         players = netgame->numplayers;
  5861. #elif defined(DXX_BUILD_DESCENT_II)
  5862.         players = netgame->numconnected;
  5863. #endif
  5864. #define GAME_INFO_FORMAT_TEXT(F)        \
  5865.         F("\nConnected to\n\"%." DXX_STRINGIZE(NETGAME_NAME_LEN) "s\"\n", netgame->game_name.data())    \
  5866.         F("%." DXX_STRINGIZE(MISSION_NAME_LEN) "s", netgame->mission_title.data())      \
  5867.         F(" - Lvl " DXX_SECRET_LEVEL_FORMAT "%i", DXX_SECRET_LEVEL_PARAMETER netgame->levelnum) \
  5868.         F("\n\nDifficulty: %s", MENU_DIFFICULTY_TEXT(netgame->difficulty))      \
  5869.         F("\nGame Mode: %s", gamemode < GMNames.size() ? GMNames[gamemode] : "INVALID") \
  5870.         F("\nPlayers: %u/%i", players, netgame->max_numplayers)
  5871. #define EXPAND_FORMAT(A,B,...)  A
  5872. #define EXPAND_ARGUMENT(A,B,...)        , B, ## __VA_ARGS__
  5873.         snprintf(rinfo, std::size(rinfo), GAME_INFO_FORMAT_TEXT(EXPAND_FORMAT) GAME_INFO_FORMAT_TEXT(EXPAND_ARGUMENT));
  5874. #undef GAME_INFO_FORMAT_TEXT
  5875.  
  5876.         std::array<newmenu_item, 2> nm_message_items{{
  5877.                 nm_item_menu("JOIN GAME"),
  5878.                 nm_item_menu("GAME INFO"),
  5879.         }};
  5880.         c = newmenu_do("WELCOME", rinfo, nm_message_items, show_game_info_handler, netgame);
  5881.         if (c==0)
  5882.                 return 1;
  5883.         //else if (c==1)
  5884.         // handled in above callback
  5885.         else
  5886.                 return 0;
  5887. }
  5888. }
  5889.  
  5890. /* Tracker stuff, begin! */
  5891. #if DXX_USE_TRACKER
  5892.  
  5893. /* Tracker initialization */
  5894. static int udp_tracker_init()
  5895. {
  5896.         if (CGameArg.MplTrackerAddr.empty())
  5897.                 return 0;
  5898.  
  5899.         TrackerAckStatus = TrackerAckState::TACK_NOCONNECTION;
  5900.         TrackerAckTime = timer_query();
  5901.  
  5902.         const char *tracker_addr = CGameArg.MplTrackerAddr.c_str();
  5903.  
  5904.         // Fill the address
  5905.         if (udp_dns_filladdr(TrackerSocket, tracker_addr, CGameArg.MplTrackerPort, false, true) < 0)
  5906.                 return -1;
  5907.  
  5908.         // Yay
  5909.         return 0;
  5910. }
  5911.  
  5912. /* Compares sender to tracker. Returns 1 if address matches, Returns 2 is address and port matches. */
  5913. static int sender_is_tracker(const _sockaddr &sender, const _sockaddr &tracker)
  5914. {
  5915.         uint16_t sf, tf, sp, tp;
  5916.  
  5917.         sf = sender.sin.sin_family;
  5918.         tf = tracker.sin.sin_family;
  5919.  
  5920. #if DXX_USE_IPv6
  5921.         if (sf == AF_INET6)
  5922.         {
  5923.                 if (tf == AF_INET)
  5924.                 {
  5925.                         if (IN6_IS_ADDR_V4MAPPED(&sender.sin6.sin6_addr))
  5926.                         {
  5927.                                 if (memcmp(&sender.sin6.sin6_addr.s6_addr[12], &tracker.sin.sin_addr, sizeof(tracker.sin.sin_addr)))
  5928.                                         return 0;
  5929.                                 tp = tracker.sin.sin_port;
  5930.                         }
  5931.                         else
  5932.                                 return 0;
  5933.                 }
  5934.                 else if (tf == AF_INET6)
  5935.                 {
  5936.                         if (memcmp(&sender.sin6.sin6_addr, &tracker.sin6.sin6_addr, sizeof(sender.sin6.sin6_addr)))
  5937.                                 return 0;
  5938.                         tp = tracker.sin6.sin6_port;
  5939.                 }
  5940.                 else
  5941.                         return 0;
  5942.                 sp = sender.sin6.sin6_port;
  5943.         }
  5944.         else
  5945. #endif
  5946.         if (sf == AF_INET)
  5947.         {
  5948.                 if (sf != tf)
  5949.                         return 0;
  5950.                 if (memcmp(&sender.sin.sin_addr, &tracker.sin.sin_addr, sizeof(sender.sin.sin_addr)))
  5951.                         return 0;
  5952.                 sp = sender.sin.sin_port;
  5953.                 tp = tracker.sin.sin_port;
  5954.         }
  5955.         else
  5956.                 return 0;
  5957.  
  5958.         if (tp == sp)
  5959.                 return 2;
  5960.         else
  5961.                 return 1;
  5962. }
  5963.  
  5964. /* Unregister from the tracker */
  5965. static int udp_tracker_unregister()
  5966. {
  5967.         std::array<uint8_t, 1> pBuf;
  5968.  
  5969.         pBuf[0] = UPID_TRACKER_REMOVE;
  5970.  
  5971.         return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, 1, 0);
  5972. }
  5973.  
  5974. namespace dsx {
  5975. /* Register or update (i.e. keep alive) a game on the tracker */
  5976. static int udp_tracker_register()
  5977. {
  5978.         net_udp_update_netgame();
  5979.  
  5980.         game_info_light light;
  5981.         int len = 1, light_len = net_udp_prepare_light_game_info(light);
  5982.         std::array<uint8_t, 2 + sizeof("b=") + sizeof(UDP_REQ_ID) + sizeof("00000.00000.00000.00000,z=") + UPID_GAME_INFO_LITE_SIZE_MAX> pBuf = {};
  5983.  
  5984.         pBuf[0] = UPID_TRACKER_REGISTER;
  5985.         len += snprintf(reinterpret_cast<char *>(&pBuf[1]), sizeof(pBuf)-1, "b=" UDP_REQ_ID DXX_VERSION_STR ".%hu,z=", MULTI_PROTO_VERSION );
  5986.         memcpy(&pBuf[len], light.buf.data(), light_len);                len += light_len;
  5987.  
  5988.         return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, len, 0);
  5989. }
  5990.  
  5991. /* Ask the tracker to send us a list of games */
  5992. static int udp_tracker_reqgames()
  5993. {
  5994.         std::array<uint8_t, 2 + sizeof(UDP_REQ_ID) + sizeof("00000.00000.00000.00000")> pBuf = {};
  5995.         int len = 1;
  5996.  
  5997.         pBuf[0] = UPID_TRACKER_REQGAMES;
  5998.         len += snprintf(reinterpret_cast<char *>(&pBuf[1]), sizeof(pBuf)-1, UDP_REQ_ID DXX_VERSION_STR ".%hu", MULTI_PROTO_VERSION );
  5999.  
  6000.         return dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, len, 0);
  6001. }
  6002. }
  6003.  
  6004. /* The tracker has sent us a game.  Let's list it. */
  6005. static int udp_tracker_process_game( ubyte *data, int data_len, const _sockaddr &sender_addr )
  6006. {
  6007.         // Only accept data from the tracker we specified and only when we look at the netlist (i.e. NETSTAT_BROWSING)
  6008.         if (!sender_is_tracker(sender_addr, TrackerSocket) || (Network_status != NETSTAT_BROWSING))
  6009.                 return -1;
  6010.  
  6011.         char *p0 = NULL, *p1 = NULL, *p2 = NULL, *p3 = NULL;
  6012.         char sIP[47] = {};
  6013.         std::array<char, 6> sPort{};
  6014.         uint16_t iPort = 0, TrackerGameID = 0;
  6015.  
  6016.         // Get the IP
  6017.         if ((p0 = strstr(reinterpret_cast<char *>(data), "a=")) == NULL)
  6018.                 return -1;
  6019.         p0 +=2;
  6020.         if ((p1 = strstr(p0, "/")) == NULL)
  6021.                 return -1;
  6022.         if (p1-p0 < 1 || p1-p0 > sizeof(sIP))
  6023.                 return -1;
  6024.         memcpy(sIP, p0, p1-p0);
  6025.  
  6026.         // Get the port
  6027.         p1++;
  6028.         if ((p2 = strstr(p1, "c=")) == NULL)
  6029.                 return -1;
  6030.         if (p2-p1-1 < 1 || p2-p1-1 > sizeof(sPort))
  6031.                 return -1;
  6032.         memcpy(&sPort, p1, p2-p1-1);
  6033.         if (!convert_text_portstring(sPort, iPort, true, true))
  6034.                 return -1;
  6035.  
  6036.         // Get the DNS stuff
  6037.         struct _sockaddr sAddr;
  6038.         if(udp_dns_filladdr(sAddr, sIP, iPort, true, true) < 0)
  6039.                 return -1;
  6040.         if (data_len < p2-reinterpret_cast<char *>(data)+2)
  6041.                 return -1;
  6042.         TrackerGameID = GET_INTEL_SHORT(p2 + 2);
  6043.         if ((p3 = strstr(reinterpret_cast<char *>(data), "z=")) == NULL)
  6044.                 return -1;
  6045.  
  6046.         // Now process the actual lite_game packet contained.
  6047.         int iPos = (p3-p0+5);
  6048.         net_udp_process_game_info( &data[iPos], data_len - iPos, sAddr, 1, TrackerGameID );
  6049.  
  6050.         return 0;
  6051. }
  6052.  
  6053. /* Process ACK's from tracker. We will get up to 5, each internal and external */
  6054. static void udp_tracker_process_ack( ubyte *data, int data_len, const _sockaddr &sender_addr )
  6055. {
  6056.         if(!Netgame.Tracker)
  6057.                 return;
  6058.         if (data_len != 2)
  6059.                 return;
  6060.         int addr_check = sender_is_tracker(sender_addr, TrackerSocket);
  6061.  
  6062.         switch (data[1])
  6063.         {
  6064.                 case 0: // ack coming from the same socket we are already talking with the tracker
  6065.                         if (TrackerAckStatus == TrackerAckState::TACK_NOCONNECTION && addr_check == 2)
  6066.                         {
  6067.                                 TrackerAckStatus = TrackerAckState::TACK_INTERNAL;
  6068.                                 con_puts(CON_VERBOSE, "[Tracker] Got internal ACK. Your game is hosted!");
  6069.                         }
  6070.                         break;
  6071.                 case 1: // ack from another socket (same IP, different port) to see if we're reachable from the outside
  6072.                         if (TrackerAckStatus <= TrackerAckState::TACK_INTERNAL && addr_check)
  6073.                         {
  6074.                                 TrackerAckStatus = TrackerAckState::TACK_EXTERNAL;
  6075.                                 con_puts(CON_VERBOSE, "[Tracker] Got external ACK. Your game is hosted and game port is reachable!");
  6076.                         }
  6077.                         break;
  6078.         }
  6079. }
  6080.  
  6081. /* 10 seconds passed since we registered our game. If we have not received all ACK's, yet, tell user about that! */
  6082. static void udp_tracker_verify_ack_timeout()
  6083. {
  6084.         if (!Netgame.Tracker || !multi_i_am_master() || TrackerAckTime + F1_0*10 > timer_query() || TrackerAckStatus == TrackerAckState::TACK_SEQCOMPL)
  6085.                 return;
  6086.         if (TrackerAckStatus == TrackerAckState::TACK_NOCONNECTION)
  6087.         {
  6088.                 TrackerAckStatus = TrackerAckState::TACK_SEQCOMPL; // set this now or we'll run into an endless loop if nm_messagebox triggers.
  6089.                 if (Network_status == NETSTAT_PLAYING)
  6090.                         HUD_init_message(HM_MULTI, "No ACK from tracker. Please check game log.");
  6091.                 else
  6092.                         nm_messagebox(TXT_WARNING, 1, TXT_OK, "No ACK from tracker.\nPlease check game log.");
  6093.                 con_puts(CON_URGENT, "[Tracker] No response from game tracker. Tracker address may be invalid or Tracker may be offline or otherwise unreachable.");
  6094.         }
  6095.         else if (TrackerAckStatus == TrackerAckState::TACK_INTERNAL)
  6096.         {
  6097.                 con_puts(CON_NORMAL, "[Tracker] No external signal from game tracker.  Your game port does not seem to be reachable.");
  6098.                 con_puts(CON_NORMAL, Netgame.TrackerNATWarned == TrackerNATHolePunchWarn::UserEnabledHP ? "Clients will attempt hole-punching to join your game." : "Clients will only be able to join your game if specifically configured in your router.");
  6099.         }
  6100.         TrackerAckStatus = TrackerAckState::TACK_SEQCOMPL;
  6101. }
  6102.  
  6103. /* We don't seem to be able to connect to a game. Ask Tracker to send hole punch request to host. */
  6104. static void udp_tracker_request_holepunch( uint16_t TrackerGameID )
  6105. {
  6106.         std::array<uint8_t, 3> pBuf;
  6107.  
  6108.         pBuf[0] = UPID_TRACKER_HOLEPUNCH;
  6109.         PUT_INTEL_SHORT(&pBuf[1], TrackerGameID);
  6110.  
  6111.         con_printf(CON_VERBOSE, "[Tracker] Sending hole-punch request for game [%i] to tracker.", TrackerGameID);
  6112.         dxx_sendto(TrackerSocket, UDP_Socket[0], &pBuf, 3, 0);
  6113. }
  6114.  
  6115. /* Tracker sent us an address from a client requesting hole punching.
  6116.  * We'll simply reply with another hole punch packet and wait for them to request our game info properly. */
  6117. static void udp_tracker_process_holepunch(uint8_t *const data, const unsigned data_len, const _sockaddr &sender_addr )
  6118. {
  6119.         if (data_len == 1 && !multi_i_am_master())
  6120.         {
  6121.                 con_puts(CON_VERBOSE, "[Tracker] Received hole-punch pong from a host.");
  6122.                 return;
  6123.         }
  6124.         if (!Netgame.Tracker || !sender_is_tracker(sender_addr, TrackerSocket) || !multi_i_am_master())
  6125.                 return;
  6126.         if (Netgame.TrackerNATWarned != TrackerNATHolePunchWarn::UserEnabledHP)
  6127.         {
  6128.                 con_puts(CON_NORMAL, "Ignoring tracker hole-punch request because user disabled hole punch.");
  6129.                 return;
  6130.         }
  6131.         if (!data_len || data[data_len - 1])
  6132.                 return;
  6133.  
  6134.         auto &delimiter = "/";
  6135.  
  6136.         const auto p0 = strtok(reinterpret_cast<char *>(data), delimiter);
  6137.         if (!p0)
  6138.                 return;
  6139.         const auto sIP = p0 + 1;
  6140.         const auto pPort = strtok(NULL, delimiter);
  6141.         if (!pPort)
  6142.                 return;
  6143.         char *porterror;
  6144.         const auto myport = strtoul(pPort, &porterror, 10);
  6145.         if (*porterror)
  6146.                 return;
  6147.         const uint16_t iPort = myport;
  6148.         if (iPort != myport)
  6149.                 return;
  6150.  
  6151.         // Get the DNS stuff
  6152.         struct _sockaddr sAddr;
  6153.         if(udp_dns_filladdr(sAddr, sIP, iPort, true, true) < 0)
  6154.                 return;
  6155.  
  6156.         std::array<uint8_t, 1> pBuf;
  6157.         pBuf[0] = UPID_TRACKER_HOLEPUNCH;
  6158.         dxx_sendto(sAddr, UDP_Socket[0], &pBuf, 1, 0);
  6159. }
  6160. #endif /* USE_TRACKER */
  6161.