1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20 /*
21 *
22 * Multiplayer code for network play.
23 *
24 */
25
26 #include <bitset>
27 #include <stdexcept>
28 #include <random>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <ctype.h>
34 #include <inttypes.h>
35
36 #include "u_mem.h"
37 #include "strutil.h"
38 #include "game.h"
39 #include "multi.h"
40 #include "multiinternal.h"
41 #include "object.h"
42 #include "player.h"
43 #include "laser.h"
44 #include "gauges.h"
45 #include "gameseg.h"
46 #include "weapon.h"
47 #include "collide.h"
48 #include "dxxerror.h"
49 #include "fireball.h"
50 #include "newmenu.h"
51 #include "console.h"
52 #include "wall.h"
53 #include "cntrlcen.h"
54 #include "powerup.h"
55 #include "polyobj.h"
56 #include "bm.h"
57 #include "key.h"
58 #include "playsave.h"
59 #include "timer.h"
60 #include "digi.h"
61 #include "sounds.h"
62 #include "kconfig.h"
63 #include "hudmsg.h"
64 #include "newdemo.h"
65 #include "text.h"
66 #include "kmatrix.h"
67 #include "multibot.h"
68 #include "gameseq.h"
69 #include "physics.h"
70 #include "switch.h"
71 #include "textures.h"
72 #include "sounds.h"
73 #include "args.h"
74 #include "effects.h"
75 #include "iff.h"
76 #include "state.h"
77 #include "automap.h"
78 #include "event.h"
79 #if DXX_USE_UDP
80 #include "net_udp.h"
81 #endif
82 #include "d_array.h"
83 #include "d_enumerate.h"
84 #include "d_levelstate.h"
85 #include "d_range.h"
86 #include "d_underlying_value.h"
87 #include "d_zip.h"
88
89 #include "partial_range.h"
90 #include <utility>
91
92 #define array_snprintf(array,fmt,arg1,...) std::snprintf(array.data(), array.size(), fmt, arg1, ## __VA_ARGS__)
93
94 constexpr std::integral_constant<int8_t, -1> owner_none{};
95
96 namespace dsx {
97 namespace {
98 void multi_new_bounty_target_with_sound(playernum_t, const char *callsign);
99 static void multi_reset_object_texture(object_base &objp);
100 static void multi_process_data(playernum_t pnum, const ubyte *dat, uint_fast32_t type);
101 static void multi_update_objects_for_non_cooperative();
102 static void multi_restore_game(unsigned slot, unsigned id);
103 static void multi_save_game(unsigned slot, unsigned id, const d_game_unique_state::savegame_description &desc);
104 static void multi_add_lifetime_killed();
105 #if !(!defined(RELEASE) && defined(DXX_BUILD_DESCENT_II))
106 static void multi_add_lifetime_kills(int count);
107 #endif
108 }
109 }
110 namespace {
111 static void multi_send_heartbeat();
112 static void multi_send_ranking(netplayer_info::player_rank);
113 }
114 #if defined(DXX_BUILD_DESCENT_II)
115 namespace dsx {
116 namespace {
117 static void multi_do_capture_bonus(const playernum_t pnum);
118 static void multi_do_orb_bonus(const playernum_t pnum, const ubyte *buf);
119 static void multi_send_drop_flag(vmobjptridx_t objnum,int seed);
120 }
121 }
122 #endif
123 namespace {
124 static void multi_send_gmode_update();
125 }
126 namespace dcx {
127 namespace {
128 static int imulti_new_game; // to prep stuff for level only when starting new game
129 static int multi_message_index;
130 static std::array<std::array<objnum_t, MAX_OBJECTS>, MAX_PLAYERS> remote_to_local; // Remote object number for each local object
131 static std::array<uint16_t, MAX_OBJECTS> local_to_remote;
132 static std::array<unsigned, MAX_PLAYERS> sorted_kills;
133 static void multi_send_quit();
134 void multi_new_bounty_target(playernum_t, const char *callsign);
135 }
136 DEFINE_SERIAL_UDT_TO_MESSAGE(shortpos, s, (s.bytemat, s.xo, s.yo, s.zo, s.segment, s.velx, s.vely, s.velz));
137 }
138 namespace {
139 static playernum_t multi_who_is_master();
140 static void multi_show_player_list();
141 static void multi_send_message();
142 }
143
144 //
145 // Global variables
146 //
147
148 namespace dcx {
149
150 int multi_protocol=0; // set and determinate used protocol
151
152 //do we draw the kill list on the HUD?
153 show_kill_list_mode Show_kill_list = show_kill_list_mode::_1;
154 int Show_reticle_name = 1;
155 fix Show_kill_list_timer = 0;
156
157 }
158
159 #if defined(DXX_BUILD_DESCENT_II)
160 namespace dsx {
161 hoard_highest_record hoard_highest_record_stats;
162
163 char Multi_is_guided=0;
164 }
165 #endif
166
167 namespace dcx {
168
169 playernum_t Bounty_target;
170
171
172 std::array<msgsend_state, MAX_PLAYERS> multi_sending_message;
173 int multi_defining_message = 0;
174
175 std::array<sbyte, MAX_OBJECTS> object_owner; // Who created each object in my universe, -1 = loaded at start
176
177 unsigned Net_create_loc; // pointer into previous array
178 std::array<objnum_t, MAX_NET_CREATE_OBJECTS> Net_create_objnums; // For tracking object creation that will be sent to remote
179 int Network_status = 0;
180 ntstring<MAX_MESSAGE_LEN - 1> Network_message;
181 int Network_message_reciever=-1;
182 std::array<std::array<uint16_t, MAX_PLAYERS>, MAX_PLAYERS> kill_matrix;
183 std::array<int16_t, 2> team_kills;
184 int multi_quit_game = 0;
185
186 }
187
188 namespace dsx {
189
190 const GMNames_array GMNames = {{
191 "Anarchy",
192 "Team Anarchy",
193 "Robo Anarchy",
194 "Cooperative",
195 #if defined(DXX_BUILD_DESCENT_I)
196 "Unknown",
197 "Unknown",
198 "Unknown",
199 #elif defined(DXX_BUILD_DESCENT_II)
200 "Capture the Flag",
201 "Hoard",
202 "Team Hoard",
203 #endif
204 "Bounty"
205 }};
206 const std::array<char[8], MULTI_GAME_TYPE_COUNT> GMNamesShrt = {{
207 "ANRCHY",
208 "TEAM",
209 "ROBO",
210 "COOP",
211 #if defined(DXX_BUILD_DESCENT_I)
212 "UNKNOWN",
213 "UNKNOWN",
214 "UNKNOWN",
215 #elif defined(DXX_BUILD_DESCENT_II)
216 "FLAG",
217 "HOARD",
218 "TMHOARD",
219 #endif
220 "BOUNTY"
221 }};
222
223 }
224
225 namespace dcx {
226
227 // For rejoin object syncing (used here and all protocols - globally)
228
229 int Network_send_objects = 0; // Are we in the process of sending objects to a player?
230 int Network_send_object_mode = 0; // What type of objects are we sending, static or dynamic?
231 int Network_send_objnum = -1; // What object are we sending next?
232 int Network_rejoined = 0; // Did WE rejoin this game?
233 int Network_sending_extras=0;
234 int VerifyPlayerJoined=-1; // Player (num) to enter game before any ingame/extra stuff is being sent
235 int Player_joining_extras=-1; // This is so we know who to send 'latecomer' packets to.
236 int Network_player_added = 0; // Is this a new player or a returning player?
237
238 ushort my_segments_checksum = 0;
239
240
241 std::array<std::array<bitmap_index, N_PLAYER_SHIP_TEXTURES>, MAX_PLAYERS> multi_player_textures;
242
243 // Globals for protocol-bound Refuse-functions
244 char RefuseThisPlayer=0,WaitForRefuseAnswer=0,RefuseTeam,RefusePlayerName[12];
245 fix64 RefuseTimeLimit=0;
246
247 namespace {
248 constexpr int message_length[] = {
249 #define define_message_length(NAME,SIZE) (SIZE),
250 for_each_multiplayer_command(define_message_length)
251 };
252
253 }
254
255 }
256
257 namespace dsx {
258
259 netgame_info Netgame;
260 multi_level_inv MultiLevelInv;
261
262 }
263
264 namespace dcx {
265 const enumerated_array<char[16], 10, netplayer_info::player_rank> RankStrings{{{
266 "(unpatched)",
267 "Cadet",
268 "Ensign",
269 "Lieutenant",
270 "Lt.Commander",
271 "Commander",
272 "Captain",
273 "Vice Admiral",
274 "Admiral",
275 "Demigod"
276 }}};
277
build_rank_from_untrusted(const uint8_t untrusted)278 netplayer_info::player_rank build_rank_from_untrusted(const uint8_t untrusted)
279 {
280 switch (untrusted)
281 {
282 case static_cast<uint8_t>(netplayer_info::player_rank::None):
283 case static_cast<uint8_t>(netplayer_info::player_rank::Cadet):
284 case static_cast<uint8_t>(netplayer_info::player_rank::Ensign):
285 case static_cast<uint8_t>(netplayer_info::player_rank::Lieutenant):
286 case static_cast<uint8_t>(netplayer_info::player_rank::LtCommander):
287 case static_cast<uint8_t>(netplayer_info::player_rank::Commander):
288 case static_cast<uint8_t>(netplayer_info::player_rank::Captain):
289 case static_cast<uint8_t>(netplayer_info::player_rank::ViceAdmiral):
290 case static_cast<uint8_t>(netplayer_info::player_rank::Admiral):
291 case static_cast<uint8_t>(netplayer_info::player_rank::Demigod):
292 return netplayer_info::player_rank{untrusted};
293 default:
294 return netplayer_info::player_rank::None;
295 }
296 }
297 }
298
299 namespace dsx {
300 const multi_allow_powerup_text_array multi_allow_powerup_text = {{
301 #define define_netflag_string(NAME,STR) STR,
302 for_each_netflag_value(define_netflag_string)
303 }};
304 }
305
GetMyNetRanking()306 netplayer_info::player_rank GetMyNetRanking()
307 {
308 int rank, eff;
309
310 if (PlayerCfg.NetlifeKills + PlayerCfg.NetlifeKilled <= 0)
311 return netplayer_info::player_rank::Cadet;
312
313 rank=static_cast<int>((static_cast<float>(PlayerCfg.NetlifeKills)/3000.0)*8.0);
314
315 eff = static_cast<int>(
316 (
317 static_cast<float>(PlayerCfg.NetlifeKills) / (
318 static_cast<float>(PlayerCfg.NetlifeKilled) + static_cast<float>(PlayerCfg.NetlifeKills)
319 )
320 ) * 100.0
321 );
322
323 if (rank>8)
324 rank=8;
325
326 if (eff<0)
327 eff=0;
328
329 if (eff<60)
330 rank-=((59-eff)/10);
331
332 if (rank<0)
333 rank=0;
334 if (rank>8)
335 rank=8;
336
337 return static_cast<netplayer_info::player_rank>(rank + 1);
338 }
339
340 //
341 // Functions that replace what used to be macros
342 //
343
objnum_remote_to_local(uint16_t remote_objnum,int8_t owner)344 objnum_t objnum_remote_to_local(uint16_t remote_objnum, int8_t owner)
345 {
346 if (owner == owner_none)
347 return(remote_objnum);
348 // Map a remote object number from owner to a local object number
349 if ((owner >= N_players) || (owner < -1)) {
350 Int3(); // Illegal!
351 return(remote_objnum);
352 }
353
354 if (remote_objnum >= MAX_OBJECTS)
355 return(object_none);
356
357 auto result = remote_to_local[owner][remote_objnum];
358 return(result);
359 }
360
objnum_local_to_remote(objnum_t local_objnum)361 owned_remote_objnum objnum_local_to_remote(objnum_t local_objnum)
362 {
363 auto &Objects = LevelUniqueObjectState.Objects;
364 // Map a local object number to a remote + owner
365 if (local_objnum > Highest_object_index)
366 {
367 return {owner_none, 0xffff};
368 }
369 auto owner = object_owner[local_objnum];
370 if (owner == owner_none)
371 return {owner, local_objnum};
372 auto result = local_to_remote[local_objnum];
373 const char *emsg;
374 if (
375 ((owner >= N_players || owner < -1) && (emsg = "illegal object owner", true)) ||
376 (result >= MAX_OBJECTS && (emsg = "illegal object remote number", true)) // See Rob, object has no remote number!
377 )
378 throw std::runtime_error(emsg);
379 return {owner, result};
380 }
381
map_objnum_local_to_remote(const int local_objnum,const int remote_objnum,const int owner)382 void map_objnum_local_to_remote(const int local_objnum, const int remote_objnum, const int owner)
383 {
384 // Add a mapping from a network remote object number to a local one
385
386 Assert(local_objnum > -1);
387 Assert(local_objnum < MAX_OBJECTS);
388 Assert(remote_objnum > -1);
389 Assert(remote_objnum < MAX_OBJECTS);
390 Assert(owner > -1);
391 Assert(owner != Player_num);
392
393 object_owner[local_objnum] = owner;
394
395 remote_to_local[owner][remote_objnum] = local_objnum;
396 local_to_remote[local_objnum] = remote_objnum;
397
398 return;
399 }
400
map_objnum_local_to_local(objnum_t local_objnum)401 void map_objnum_local_to_local(objnum_t local_objnum)
402 {
403 // Add a mapping for our locally created objects
404 Assert(local_objnum < MAX_OBJECTS);
405
406 object_owner[local_objnum] = Player_num;
407 remote_to_local[Player_num][local_objnum] = local_objnum;
408 local_to_remote[local_objnum] = local_objnum;
409
410 return;
411 }
412
reset_network_objects()413 void reset_network_objects()
414 {
415 local_to_remote.fill(-1);
416 range_for (auto &i, remote_to_local)
417 i.fill(object_none);
418 object_owner.fill(-1);
419 }
420
421 namespace dsx {
422
423 namespace {
424
update_bounty_target()425 void update_bounty_target()
426 {
427 std::array<std::pair<playernum_t, const char *>, std::size(Players)> candidates{};
428 const auto b = candidates.begin();
429 auto iter = b;
430 for (auto &&[idx, plr] : enumerate(Players))
431 if (plr.connected)
432 *iter++ = {idx, plr.callsign};
433 const auto n = std::distance(b, iter);
434 if (!n)
435 return;
436 auto &choice = candidates[d_rand() % n];
437 multi_new_bounty_target_with_sound(choice.first, choice.second);
438 }
439
440 }
441
442 //
443 // Part 1 : functions whose main purpose in life is to divert the flow
444 // of execution to either network specific code based
445 // on the curretn Game_mode value.
446 //
447
448 // Show a score list to end of net players
multi_endlevel_score()449 kmatrix_result multi_endlevel_score()
450 {
451 auto &Objects = LevelUniqueObjectState.Objects;
452 auto &vmobjptr = Objects.vmptr;
453 int old_connect = 0;
454
455 // Save connect state and change to new connect state
456 if (Game_mode & GM_NETWORK)
457 {
458 auto &plr = get_local_player();
459 old_connect = plr.connected;
460 if (plr.connected != CONNECT_DIED_IN_MINE)
461 plr.connected = CONNECT_END_MENU;
462 Network_status = NETSTAT_ENDLEVEL;
463 }
464
465 // Do the actual screen we wish to show
466 const auto rval = kmatrix_view(static_cast<kmatrix_network>(Game_mode & GM_NETWORK), Controls);
467
468 // Restore connect state
469
470 if (Game_mode & GM_NETWORK)
471 {
472 get_local_player().connected = old_connect;
473 }
474
475 /* Door key flags should only be cleared in cooperative games, not
476 * in other games.
477 *
478 * The capture-the-flag marker can be cleared unconditionally, but
479 * would never have been set in a cooperative game.
480 *
481 * The kill goal count can be cleared unconditionally.
482 *
483 * For Descent 1, the only flags to clear are the door key flags.
484 * Use a no-op mask in non-cooperative games, since there are no
485 * flags to clear there.
486 *
487 * For Descent 2, clear door key flags or the capture-the-flag
488 * flag, depending on game type. This version has the advantage of
489 * making only one pass when in cooperative mode, where the previous
490 * version would make one pass if in a cooperative game, then make
491 * an unconditional pass to try to clear PLAYER_FLAGS_FLAG.
492 */
493 const auto clear_flags = (Game_mode & GM_MULTI_COOP)
494 // Reset keys
495 ? ~player_flags(PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY)
496 :
497 #if defined(DXX_BUILD_DESCENT_I)
498 /* Nothing to clear. Set a mask that has no effect when
499 * applied, so that the loop does not need to retest the
500 * conditional on each pass.
501 */
502 player_flags(~0u)
503 #elif defined(DXX_BUILD_DESCENT_II)
504 // Clear capture flag
505 ~player_flags(PLAYER_FLAGS_FLAG)
506 #endif
507 ;
508 range_for (auto &i, partial_const_range(Players, Netgame.max_numplayers))
509 {
510 auto &obj = *vmobjptr(i.objnum);
511 auto &player_info = obj.ctype.player_info;
512 player_info.powerup_flags &= clear_flags;
513 player_info.KillGoalCount = 0;
514 }
515 return rval;
516 }
517
518 }
519
get_team(const playernum_t pnum)520 int get_team(const playernum_t pnum)
521 {
522 if (Netgame.team_vector & (1 << pnum))
523 return 1;
524 else
525 return 0;
526 }
527
multi_new_game()528 void multi_new_game()
529 {
530 // Reset variables for a new net game
531 reset_globals_for_new_game();
532
533 LevelUniqueObjectState.accumulated_robots = 0;
534 LevelUniqueObjectState.total_hostages = 0;
535 GameUniqueState.accumulated_robots = 0;
536 GameUniqueState.total_hostages = 0;
537 for (uint_fast32_t i = 0; i < MAX_PLAYERS; i++)
538 init_player_stats_game(i);
539
540 kill_matrix = {}; // Clear kill matrix
541
542 for (playernum_t i = 0; i < MAX_PLAYERS; ++i)
543 {
544 sorted_kills[i] = i;
545 vmplayerptr(i)->connected = CONNECT_DISCONNECTED;
546 }
547 multi_sending_message.fill(msgsend_state::none);
548
549 robot_controlled.fill(-1);
550 robot_agitation = {};
551 robot_fired = {};
552
553 team_kills = {};
554 imulti_new_game=1;
555 multi_quit_game = 0;
556 Show_kill_list = show_kill_list_mode::_1;
557 game_disable_cheats();
558 }
559
560 namespace dsx {
561
multi_make_player_ghost(const playernum_t playernum)562 void multi_make_player_ghost(const playernum_t playernum)
563 {
564 auto &Objects = LevelUniqueObjectState.Objects;
565 auto &vmobjptridx = Objects.vmptridx;
566 if (playernum == Player_num || playernum >= MAX_PLAYERS)
567 {
568 Int3(); // Non-terminal, see Rob
569 return;
570 }
571 const auto &&obj = vmobjptridx(vcplayerptr(playernum)->objnum);
572 obj->type = OBJ_GHOST;
573 obj->render_type = RT_NONE;
574 obj->movement_source = object::movement_type::None;
575 multi_reset_player_object(obj);
576 multi_strip_robots(playernum);
577 }
578
multi_make_ghost_player(const playernum_t playernum)579 void multi_make_ghost_player(const playernum_t playernum)
580 {
581 auto &Objects = LevelUniqueObjectState.Objects;
582 auto &vmobjptridx = Objects.vmptridx;
583 if ((playernum == Player_num) || (playernum >= MAX_PLAYERS))
584 {
585 Int3(); // Non-terminal, see rob
586 return;
587 }
588 const auto &&obj = vmobjptridx(vcplayerptr(playernum)->objnum);
589 obj->type = OBJ_PLAYER;
590 obj->movement_source = object::movement_type::physics;
591 multi_reset_player_object(obj);
592 if (playernum != Player_num)
593 init_player_stats_new_ship(playernum);
594 }
595
596 }
597
multi_get_kill_list(playernum_array_t & plist)598 int multi_get_kill_list(playernum_array_t &plist)
599 {
600 // Returns the number of active net players and their
601 // sorted order of kills
602 int n = 0;
603
604 range_for (const auto i, partial_const_range(sorted_kills, N_players))
605 //if (Players[sorted_kills[i]].connected)
606 plist[n++] = i;
607
608 if (n == 0)
609 Int3(); // SEE ROB OR MATT
610
611 //memcpy(plist, sorted_kills, N_players*sizeof(int));
612
613 return(n);
614 }
615
616 namespace dsx {
617
multi_sort_kill_list()618 void multi_sort_kill_list()
619 {
620 auto &Objects = LevelUniqueObjectState.Objects;
621 auto &vcobjptr = Objects.vcptr;
622 // Sort the kills list each time a new kill is added
623 std::array<int, MAX_PLAYERS> kills;
624 for (playernum_t i = 0; i < MAX_PLAYERS; ++i)
625 {
626 auto &player_info = vcobjptr(vcplayerptr(i)->objnum)->ctype.player_info;
627 if (Game_mode & GM_MULTI_COOP)
628 {
629 kills[i] = player_info.mission.score;
630 }
631 #if defined(DXX_BUILD_DESCENT_II)
632 else
633 if (Show_kill_list == show_kill_list_mode::efficiency)
634 {
635 const auto kk = player_info.net_killed_total + player_info.net_kills_total;
636 // always draw the ones without any ratio last
637 kills[i] = kk <= 0
638 ? kk - 1
639 : static_cast<int>(
640 static_cast<float>(player_info.net_kills_total) / (
641 static_cast<float>(player_info.net_killed_total) + static_cast<float>(player_info.net_kills_total)
642 ) * 100.0
643 );
644 }
645 #endif
646 else
647 kills[i] = player_info.net_kills_total;
648 }
649
650 const auto predicate = [&](unsigned a, unsigned b) {
651 return kills[a] > kills[b];
652 };
653 const auto &range = partial_range(sorted_kills, N_players);
654 std::sort(range.begin(), range.end(), predicate);
655 }
656
657 namespace {
658
print_kill_goal_tables(fvcobjptr & vcobjptr)659 static void print_kill_goal_tables(fvcobjptr &vcobjptr)
660 {
661 const auto &local_player = get_local_player();
662 const auto pnum = Player_num;
663 con_printf(CON_NORMAL, "Kill goal statistics: player #%u \"%s\"", pnum, static_cast<const char *>(local_player.callsign));
664 for (auto &&[idx, i] : enumerate(Players))
665 {
666 if (!i.connected)
667 continue;
668 auto &plrobj = *vcobjptr(i.objnum);
669 auto &player_info = plrobj.ctype.player_info;
670 con_printf(CON_NORMAL, "\t#%" PRIuFAST32 " \"%s\"\tdeaths=%i\tkills=%i\tmatrix=%hu/%hu", idx, static_cast<const char *>(i.callsign), player_info.net_killed_total, player_info.net_kills_total, kill_matrix[pnum][idx], kill_matrix[idx][pnum]);
671 }
672 }
673
net_destroy_controlcen(object_array & Objects)674 static void net_destroy_controlcen(object_array &Objects)
675 {
676 print_kill_goal_tables(Objects.vcptr);
677 HUD_init_message_literal(HM_MULTI, "The control center has been destroyed!");
678 net_destroy_controlcen_object(obj_find_first_of_type(Objects.vmptridx, OBJ_CNTRLCEN));
679 }
680
681 }
682
683 }
684
685 namespace {
686
prepare_kill_name(const playernum_t pnum,char (& buf)[(CALLSIGN_LEN * 2)+4])687 static const char *prepare_kill_name(const playernum_t pnum, char (&buf)[(CALLSIGN_LEN*2)+4])
688 {
689 if (Game_mode & GM_TEAM)
690 {
691 snprintf(buf, sizeof(buf), "%s (%s)", static_cast<const char *>(vcplayerptr(pnum)->callsign), static_cast<const char *>(Netgame.team_name[get_team(pnum)]));
692 return buf;
693 }
694 else
695 return static_cast<const char *>(vcplayerptr(pnum)->callsign);
696 }
697
698 }
699
700 namespace dsx {
701
702 namespace {
703
multi_compute_kill(const imobjptridx_t killer,object & killed)704 static void multi_compute_kill(const imobjptridx_t killer, object &killed)
705 {
706 #if defined(DXX_BUILD_DESCENT_II)
707 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
708 #endif
709 auto &Objects = LevelUniqueObjectState.Objects;
710 auto &vmobjptr = Objects.vmptr;
711 // Figure out the results of a network kills and add it to the
712 // appropriate player's tally.
713
714 playernum_t killed_pnum, killer_pnum;
715
716 // Both object numbers are localized already!
717
718 const auto killed_type = killed.type;
719 if ((killed_type != OBJ_PLAYER) && (killed_type != OBJ_GHOST))
720 {
721 Int3(); // compute_kill passed non-player object!
722 return;
723 }
724
725 killed_pnum = get_player_id(killed);
726
727 Assert (killed_pnum < N_players);
728
729 char killed_buf[(CALLSIGN_LEN*2)+4];
730 const char *killed_name = prepare_kill_name(killed_pnum, killed_buf);
731
732 if (Newdemo_state == ND_STATE_RECORDING)
733 newdemo_record_multi_death(killed_pnum);
734
735 digi_play_sample( SOUND_HUD_KILL, F3_0 );
736
737 #if defined(DXX_BUILD_DESCENT_II)
738 if (LevelUniqueControlCenterState.Control_center_destroyed)
739 vmplayerptr(killed_pnum)->connected = CONNECT_DIED_IN_MINE;
740 #endif
741
742 if (killer == object_none)
743 return;
744 const auto killer_type = killer->type;
745 if (killer_type == OBJ_CNTRLCEN)
746 {
747 if (Game_mode & GM_TEAM)
748 -- team_kills[get_team(killed_pnum)];
749 ++ killed.ctype.player_info.net_killed_total;
750 -- killed.ctype.player_info.net_kills_total;
751 -- killed.ctype.player_info.KillGoalCount;
752
753 if (Newdemo_state == ND_STATE_RECORDING)
754 newdemo_record_multi_kill(killed_pnum, -1);
755
756 if (killed_pnum == Player_num)
757 {
758 HUD_init_message(HM_MULTI, "%s %s.", TXT_YOU_WERE, TXT_KILLED_BY_NONPLAY);
759 multi_add_lifetime_killed ();
760 }
761 else
762 HUD_init_message(HM_MULTI, "%s %s %s.", killed_name, TXT_WAS, TXT_KILLED_BY_NONPLAY );
763 return;
764 }
765
766 else if ((killer_type != OBJ_PLAYER) && (killer_type != OBJ_GHOST))
767 {
768 #if defined(DXX_BUILD_DESCENT_II)
769 if (killer_type == OBJ_WEAPON && get_weapon_id(killer) == weapon_id_type::PMINE_ID)
770 {
771 if (killed_pnum == Player_num)
772 HUD_init_message_literal(HM_MULTI, "You were killed by a mine!");
773 else
774 HUD_init_message(HM_MULTI, "%s was killed by a mine!",killed_name);
775 }
776 else
777 #endif
778 {
779 if (killed_pnum == Player_num)
780 {
781 HUD_init_message(HM_MULTI, "%s %s.", TXT_YOU_WERE, TXT_KILLED_BY_ROBOT);
782 multi_add_lifetime_killed();
783 }
784 else
785 HUD_init_message(HM_MULTI, "%s %s %s.", killed_name, TXT_WAS, TXT_KILLED_BY_ROBOT );
786 }
787 ++ killed.ctype.player_info.net_killed_total;
788 return;
789 }
790
791 killer_pnum = get_player_id(killer);
792
793 char killer_buf[(CALLSIGN_LEN*2)+4];
794 const char *killer_name = prepare_kill_name(killer_pnum, killer_buf);
795
796 // Beyond this point, it was definitely a player-player kill situation
797
798 if (killer_pnum >= N_players)
799 Int3(); // See rob, tracking down bug with kill HUD messages
800 if (killed_pnum >= N_players)
801 Int3(); // See rob, tracking down bug with kill HUD messages
802
803 kill_matrix[killer_pnum][killed_pnum] += 1;
804 if (killer_pnum == killed_pnum)
805 {
806 if (!game_mode_hoard())
807 {
808 if (Game_mode & GM_TEAM)
809 {
810 team_kills[get_team(killed_pnum)] -= 1;
811 }
812
813 ++ killed.ctype.player_info.net_killed_total;
814 -- killed.ctype.player_info.net_kills_total;
815 -- killed.ctype.player_info.KillGoalCount;
816
817 if (Newdemo_state == ND_STATE_RECORDING)
818 newdemo_record_multi_kill(killed_pnum, -1);
819 }
820 if (killer_pnum == Player_num)
821 {
822 HUD_init_message(HM_MULTI, "%s %s %s!", TXT_YOU, TXT_KILLED, TXT_YOURSELF );
823 multi_add_lifetime_killed();
824 }
825 else
826 HUD_init_message(HM_MULTI, "%s %s", killed_name, TXT_SUICIDE);
827
828 /* Bounty mode needs some lovin' */
829 if( Game_mode & GM_BOUNTY && killed_pnum == Bounty_target && multi_i_am_master() )
830 {
831 update_bounty_target();
832 }
833 }
834
835 else
836 {
837 short adjust = 1;
838 /* Dead stores to prevent bogus -Wmaybe-uninitialized warnings
839 * when the optimization level prevents the compiler from
840 * recognizing that these are written in a team game and only
841 * read in a team game.
842 */
843 unsigned killed_team = 0, killer_team = 0;
844 DXX_MAKE_VAR_UNDEFINED(killed_team);
845 DXX_MAKE_VAR_UNDEFINED(killer_team);
846 const auto is_team_game = Game_mode & GM_TEAM;
847 if (is_team_game)
848 {
849 killed_team = get_team(killed_pnum);
850 killer_team = get_team(killer_pnum);
851 if (killed_team == killer_team)
852 adjust = -1;
853 }
854 if (!game_mode_hoard())
855 {
856 if (is_team_game)
857 {
858 team_kills[killer_team] += adjust;
859 killer->ctype.player_info.net_kills_total += adjust;
860 killer->ctype.player_info.KillGoalCount += adjust;
861 }
862 else if( Game_mode & GM_BOUNTY )
863 {
864 /* Did the target die? Did the target get a kill? */
865 if( killed_pnum == Bounty_target || killer_pnum == Bounty_target )
866 {
867 /* Increment kill counts */
868 ++ killer->ctype.player_info.net_kills_total;
869 ++ killer->ctype.player_info.KillGoalCount;
870
871 /* If the target died, the new one is set! */
872 if( killed_pnum == Bounty_target )
873 multi_new_bounty_target_with_sound(killer_pnum, vcplayerptr(get_player_id(killer))->callsign);
874 }
875 }
876 else
877 {
878 ++ killer->ctype.player_info.net_kills_total;
879 ++ killer->ctype.player_info.KillGoalCount;
880 }
881
882 if (Newdemo_state == ND_STATE_RECORDING)
883 newdemo_record_multi_kill(killer_pnum, 1);
884 }
885
886 ++ killed.ctype.player_info.net_killed_total;
887 const char *name0, *name1;
888 if (killer_pnum == Player_num) {
889 if (Game_mode & GM_MULTI_COOP)
890 {
891 auto &player_info = get_local_plrobj().ctype.player_info;
892 const auto local_player_score = player_info.mission.score;
893 add_points_to_score(player_info, local_player_score >= 1000 ? -1000 : -local_player_score, Game_mode);
894 }
895 else
896 multi_add_lifetime_kills(adjust);
897 name0 = TXT_YOU;
898 name1 = killed_name;
899 }
900 else if (name0 = killer_name, killed_pnum == Player_num)
901 {
902 multi_add_lifetime_killed();
903 name1 = TXT_YOU;
904 }
905 else
906 name1 = killed_name;
907 HUD_init_message(HM_MULTI, "%s %s %s!", name0, TXT_KILLED, name1);
908 }
909
910 if (Netgame.KillGoal>0)
911 {
912 const auto TheGoal = Netgame.KillGoal * 5;
913 if (((Game_mode & GM_TEAM)
914 ? team_kills[get_team(killer_pnum)]
915 : killer->ctype.player_info.KillGoalCount
916 ) >= TheGoal)
917 {
918 if (killer_pnum==Player_num)
919 {
920 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
921 get_local_plrobj().shields = i2f(200);
922 }
923 else
924 {
925 char buf[(CALLSIGN_LEN*2)+4];
926 HUD_init_message(HM_MULTI, "%s has reached the kill goal!", prepare_kill_name(killer_pnum, buf));
927 }
928 net_destroy_controlcen(Objects);
929 }
930 }
931
932 multi_sort_kill_list();
933 multi_show_player_list();
934 #if defined(DXX_BUILD_DESCENT_II)
935 // clear the killed guys flags/headlights
936 killed.ctype.player_info.powerup_flags &= ~(PLAYER_FLAGS_HEADLIGHT_ON);
937 #endif
938 }
939
940 }
941
942 }
943
multi_do_frame()944 window_event_result multi_do_frame()
945 {
946 static d_time_fix lasttime;
947 static fix64 last_gmode_time = 0, last_inventory_time = 0, last_repo_time = 0;
948
949 if (!(Game_mode & GM_MULTI) || Newdemo_state == ND_STATE_PLAYBACK)
950 {
951 Int3();
952 return window_event_result::ignored;
953 }
954
955 if ((Game_mode & GM_NETWORK) && Netgame.PlayTimeAllowed.count() && lasttime != ThisLevelTime)
956 {
957 for (unsigned i = 0; i < N_players; ++i)
958 if (vcplayerptr(i)->connected)
959 {
960 if (i==Player_num)
961 {
962 multi_send_heartbeat();
963 lasttime = ThisLevelTime;
964 }
965 break;
966 }
967 }
968
969 // Send update about our game mode-specific variables every 2 secs (to keep in sync since delayed kills can invalidate these infos on Clients)
970 if (multi_i_am_master() && timer_query() >= last_gmode_time + (F1_0*2))
971 {
972 multi_send_gmode_update();
973 last_gmode_time = timer_query();
974 }
975
976 if (Network_status == NETSTAT_PLAYING)
977 {
978 // Send out inventory three times per second
979 if (timer_query() >= last_inventory_time + (F1_0/3))
980 {
981 multi_send_player_inventory(0);
982 last_inventory_time = timer_query();
983 }
984 // Repopulate the level if necessary
985 if (timer_query() >= last_repo_time + (F1_0/2))
986 {
987 MultiLevelInv_Repopulate((F1_0/2));
988 last_repo_time = timer_query();
989 }
990 }
991
992 multi_send_message(); // Send any waiting messages
993
994 if (Game_mode & GM_MULTI_ROBOTS)
995 {
996 multi_check_robot_timeout();
997 }
998
999 multi::dispatch->do_protocol_frame(0, 1);
1000
1001 return multi_quit_game ? window_event_result::close : window_event_result::handled;
1002 }
1003
_multi_send_data(const uint8_t * const buf,const unsigned len,const int priority)1004 void _multi_send_data(const uint8_t *const buf, const unsigned len, const int priority)
1005 {
1006 if (Game_mode & GM_NETWORK)
1007 {
1008 switch (multi_protocol)
1009 {
1010 #if DXX_USE_UDP
1011 case MULTI_PROTO_UDP:
1012 net_udp_send_data(buf, len, priority);
1013 break;
1014 #endif
1015 default:
1016 (void)buf; (void)len; (void)priority;
1017 Error("Protocol handling missing in multi_send_data\n");
1018 break;
1019 }
1020 }
1021 }
1022
1023 namespace {
1024
_multi_send_data_direct(const ubyte * buf,unsigned len,const playernum_t pnum,int priority)1025 static void _multi_send_data_direct(const ubyte *buf, unsigned len, const playernum_t pnum, int priority)
1026 {
1027 if (pnum >= MAX_PLAYERS)
1028 Error("multi_send_data_direct: Illegal player num: %u\n", pnum);
1029
1030 switch (multi_protocol)
1031 {
1032 #if DXX_USE_UDP
1033 case MULTI_PROTO_UDP:
1034 net_udp_send_mdata_direct(buf, len, pnum, priority);
1035 break;
1036 #endif
1037 default:
1038 (void)buf; (void)len; (void)priority;
1039 Error("Protocol handling missing in multi_send_data_direct\n");
1040 break;
1041 }
1042 }
1043
1044 template <multiplayer_command_t C>
multi_send_data_direct(uint8_t * const buf,const unsigned len,const playernum_t pnum,const int priority)1045 static void multi_send_data_direct(uint8_t *const buf, const unsigned len, const playernum_t pnum, const int priority)
1046 {
1047 buf[0] = C;
1048 unsigned expected = command_length<C>::value;
1049 #ifdef DXX_CONSTANT_TRUE
1050 if (DXX_CONSTANT_TRUE(len != expected))
1051 DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_multi_send_data, "wrong packet size");
1052 #endif
1053 if (len != expected)
1054 {
1055 Error("multi_send_data_direct: Packet type %i length: %i, expected: %i\n", C, len, expected);
1056 }
1057 _multi_send_data_direct(buf, len, pnum, priority);
1058 }
1059
1060 template <multiplayer_command_t C>
multi_send_data_direct(multi_command<C> & buf,const playernum_t pnum,const int priority)1061 static inline void multi_send_data_direct(multi_command<C> &buf, const playernum_t pnum, const int priority)
1062 {
1063 buf[0] = C;
1064 _multi_send_data_direct(buf.data(), buf.size(), pnum, priority);
1065 }
1066
1067 }
1068
1069 namespace dsx {
1070
multi_leave_game()1071 void multi_leave_game()
1072 {
1073 auto &Objects = LevelUniqueObjectState.Objects;
1074 auto &vmobjptridx = Objects.vmptridx;
1075
1076 if (!(Game_mode & GM_MULTI))
1077 return;
1078
1079 if (Game_mode & GM_NETWORK)
1080 {
1081 Net_create_loc = 0;
1082 const auto cobjp = vmobjptridx(get_local_player().objnum);
1083 multi_send_position(cobjp);
1084 auto &player_info = cobjp->ctype.player_info;
1085 if (!player_info.Player_eggs_dropped)
1086 {
1087 player_info.Player_eggs_dropped = true;
1088 drop_player_eggs(cobjp);
1089 }
1090 multi_send_player_deres(deres_drop);
1091 }
1092
1093 multi_send_quit();
1094 multi::dispatch->leave_game();
1095
1096 #if defined(DXX_BUILD_DESCENT_I)
1097 plyr_save_stats();
1098 #endif
1099
1100 multi_quit_game = 0; // quit complete
1101 }
1102
1103 }
1104
1105 namespace {
1106
multi_show_player_list()1107 void multi_show_player_list()
1108 {
1109 if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_COOP))
1110 return;
1111
1112 if (Show_kill_list != show_kill_list_mode::None)
1113 return;
1114
1115 Show_kill_list_timer = F1_0*5; // 5 second timer
1116 Show_kill_list = show_kill_list_mode::_1;
1117 }
1118
1119 }
1120
1121 //
1122 // Part 2 : functions that act on network messages and change the
1123 // the state of the game in some way.
1124 //
1125
multi_define_macro(const int key)1126 void multi_define_macro(const int key)
1127 {
1128 if (!(Game_mode & GM_MULTI))
1129 return;
1130
1131 switch (key & ~KEY_SHIFTED)
1132 {
1133 case KEY_F9:
1134 multi_defining_message = 1; break;
1135 case KEY_F10:
1136 multi_defining_message = 2; break;
1137 case KEY_F11:
1138 multi_defining_message = 3; break;
1139 case KEY_F12:
1140 multi_defining_message = 4; break;
1141 default:
1142 Int3();
1143 }
1144
1145 if (multi_defining_message) {
1146 key_toggle_repeat(1);
1147 multi_message_index = 0;
1148 Network_message = {};
1149 }
1150 }
1151
1152 namespace {
1153
multi_message_feedback(void)1154 static void multi_message_feedback(void)
1155 {
1156 char *colon;
1157 int found = 0;
1158 char feedback_result[200];
1159
1160 if (!(!(colon = strstr(Network_message.data(), ": ")) || colon == Network_message.data() || colon - Network_message.data() > CALLSIGN_LEN))
1161 {
1162 std::size_t feedlen = snprintf(feedback_result, sizeof(feedback_result), "%s ", TXT_MESSAGE_SENT_TO);
1163 if ((Game_mode & GM_TEAM) && (Network_message[0] == '1' || Network_message[0] == '2'))
1164 {
1165 snprintf(feedback_result + feedlen, sizeof(feedback_result) - feedlen, "%s '%s'", TXT_TEAM, static_cast<const char *>(Netgame.team_name[Network_message[0] - '1']));
1166 found = 1;
1167 }
1168 if (Game_mode & GM_TEAM)
1169 {
1170 range_for (auto &i, Netgame.team_name)
1171 {
1172 if (!d_strnicmp(i, Network_message.data(), colon - Network_message.data()))
1173 {
1174 const char *comma = found ? ", " : "";
1175 found++;
1176 const char *newline = (!(found % 4)) ? "\n" : "";
1177 size_t l = strlen(feedback_result);
1178 snprintf(feedback_result + l, sizeof(feedback_result) - l, "%s%s%s '%s'", comma, newline, TXT_TEAM, static_cast<const char *>(i));
1179 }
1180 }
1181 }
1182 const player *const local_player = &get_local_player();
1183 range_for (auto &i, partial_const_range(Players, N_players))
1184 {
1185 if (&i != local_player && i.connected && !d_strnicmp(static_cast<const char *>(i.callsign), Network_message.data(), colon - Network_message.data()))
1186 {
1187 const char *comma = found ? ", " : "";
1188 found++;
1189 const char *newline = (!(found % 4)) ? "\n" : "";
1190 size_t l = strlen(feedback_result);
1191 snprintf(feedback_result + l, sizeof(feedback_result) - l, "%s%s%s", comma, newline, static_cast<const char *>(i.callsign));
1192 }
1193 }
1194 if (!found)
1195 strcat(feedback_result, TXT_NOBODY);
1196 else
1197 strcat(feedback_result, ".");
1198
1199 digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
1200
1201 Assert(strlen(feedback_result) < 200);
1202
1203 HUD_init_message_literal(HM_MULTI, feedback_result);
1204 }
1205 }
1206
1207 }
1208
multi_send_macro(const int fkey)1209 void multi_send_macro(const int fkey)
1210 {
1211 if (! (Game_mode & GM_MULTI) )
1212 return;
1213
1214 unsigned key;
1215 switch (fkey)
1216 {
1217 case KEY_F9:
1218 key = 0; break;
1219 case KEY_F10:
1220 key = 1; break;
1221 case KEY_F11:
1222 key = 2; break;
1223 case KEY_F12:
1224 key = 3; break;
1225 default:
1226 Int3();
1227 return;
1228 }
1229
1230 if (!PlayerCfg.NetworkMessageMacro[key][0])
1231 {
1232 HUD_init_message_literal(HM_MULTI, TXT_NO_MACRO);
1233 return;
1234 }
1235
1236 Network_message = PlayerCfg.NetworkMessageMacro[key];
1237 Network_message_reciever = 100;
1238
1239 HUD_init_message(HM_MULTI, "%s '%s'", TXT_SENDING, Network_message.data());
1240 multi_message_feedback();
1241 }
1242
multi_send_message_start()1243 void multi_send_message_start()
1244 {
1245 if (Game_mode&GM_MULTI) {
1246 multi_sending_message[Player_num] = msgsend_state::typing;
1247 multi_send_msgsend_state(msgsend_state::typing);
1248 multi_message_index = 0;
1249 Network_message = {};
1250 key_toggle_repeat(1);
1251 }
1252 }
1253
1254 namespace dsx {
1255
1256 namespace {
1257
kick_player(const player & plr,netplayer_info & nplr)1258 static void kick_player(const player &plr, netplayer_info &nplr)
1259 {
1260 multi::dispatch->kick_player(nplr.protocol.udp.addr, DUMP_KICKED);
1261 HUD_init_message(HM_MULTI, "Dumping %s...", static_cast<const char *>(plr.callsign));
1262 multi_message_index = 0;
1263 multi_sending_message[Player_num] = msgsend_state::none;
1264 #if defined(DXX_BUILD_DESCENT_II)
1265 multi_send_msgsend_state(msgsend_state::none);
1266 #endif
1267 }
1268
multi_send_message_end(fvmobjptr & vmobjptr,control_info & Controls)1269 static void multi_send_message_end(fvmobjptr &vmobjptr, control_info &Controls)
1270 {
1271 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1272 #if defined(DXX_BUILD_DESCENT_I)
1273 multi_message_index = 0;
1274 multi_sending_message[Player_num] = msgsend_state::none;
1275 multi_send_msgsend_state(msgsend_state::none);
1276 key_toggle_repeat(0);
1277 #elif defined(DXX_BUILD_DESCENT_II)
1278 Network_message_reciever = 100;
1279 #endif
1280
1281 if (!d_strnicmp(Network_message.data(), "/Handicap: "))
1282 {
1283 constexpr auto minimum_allowed_shields = 10ul;
1284 constexpr auto maximum_allowed_shields = 100ul;
1285 auto &plr = get_local_player();
1286 const char *const callsign = plr.callsign;
1287 const auto requested_handicap = strtoul(&Network_message[11], 0, 10);
1288 const auto clamped_handicap_max = std::max(minimum_allowed_shields, requested_handicap);
1289 const auto clamped_handicap_min = std::min(maximum_allowed_shields, clamped_handicap_max);
1290 StartingShields = i2f(clamped_handicap_min);
1291 if (clamped_handicap_min != clamped_handicap_max)
1292 snprintf(Network_message.data(), Network_message.size(), "%s has tried to cheat!", callsign);
1293 else
1294 snprintf(Network_message.data(), Network_message.size(), "%s handicap is now %lu", callsign, clamped_handicap_min);
1295 HUD_init_message(HM_MULTI, "Telling others of your handicap of %lu!",clamped_handicap_min);
1296 }
1297 else if (!d_strnicmp(Network_message.data(), "/move: "))
1298 {
1299 if ((Game_mode & GM_NETWORK) && (Game_mode & GM_TEAM))
1300 {
1301 unsigned name_index=7;
1302 if (strlen(Network_message.data()) > 7)
1303 while (Network_message[name_index] == ' ')
1304 name_index++;
1305
1306 if (!multi_i_am_master())
1307 {
1308 HUD_init_message(HM_MULTI, "Only %s can move players!",static_cast<const char *>(Players[multi_who_is_master()].callsign));
1309 return;
1310 }
1311
1312 if (strlen(Network_message.data()) <= name_index)
1313 {
1314 HUD_init_message_literal(HM_MULTI, "You must specify a name to move");
1315 return;
1316 }
1317
1318 for (unsigned i = 0; i < N_players; ++i)
1319 if (vcplayerptr(i)->connected && !d_strnicmp(static_cast<const char *>(vcplayerptr(i)->callsign), &Network_message[name_index], strlen(Network_message.data()) - name_index))
1320 {
1321 #if defined(DXX_BUILD_DESCENT_II)
1322 if (game_mode_capture_flag() && (vmobjptr(vcplayerptr(i)->objnum)->ctype.player_info.powerup_flags & PLAYER_FLAGS_FLAG))
1323 {
1324 HUD_init_message_literal(HM_MULTI, "Can't move player because s/he has a flag!");
1325 return;
1326 }
1327 #endif
1328 if (Netgame.team_vector & (1<<i))
1329 Netgame.team_vector&=(~(1<<i));
1330 else
1331 Netgame.team_vector|=(1<<i);
1332
1333 range_for (auto &t, partial_const_range(Players, N_players))
1334 if (t.connected)
1335 multi_reset_object_texture(vmobjptr(t.objnum));
1336 reset_cockpit();
1337
1338 multi_send_gmode_update();
1339
1340 snprintf(Network_message.data(), Network_message.size(), "%s has changed teams!", static_cast<const char *>(vcplayerptr(i)->callsign));
1341 if (i==Player_num)
1342 {
1343 HUD_init_message_literal(HM_MULTI, "You have changed teams!");
1344 reset_cockpit();
1345 }
1346 else
1347 HUD_init_message(HM_MULTI, "Moving %s to other team.", static_cast<const char *>(vcplayerptr(i)->callsign));
1348 break;
1349 }
1350 }
1351 }
1352
1353 else if (!d_strnicmp(Network_message.data(), "/kick: ") && (Game_mode & GM_NETWORK))
1354 {
1355 unsigned name_index=7;
1356 if (strlen(Network_message.data()) > 7)
1357 while (Network_message[name_index] == ' ')
1358 name_index++;
1359
1360 if (!multi_i_am_master())
1361 {
1362 HUD_init_message(HM_MULTI, "Only %s can kick others out!", static_cast<const char *>(Players[multi_who_is_master()].callsign));
1363 multi_message_index = 0;
1364 multi_sending_message[Player_num] = msgsend_state::none;
1365 #if defined(DXX_BUILD_DESCENT_II)
1366 multi_send_msgsend_state(msgsend_state::none);
1367 #endif
1368 return;
1369 }
1370 if (strlen(Network_message.data()) <= name_index)
1371 {
1372 HUD_init_message_literal(HM_MULTI, "You must specify a name to kick");
1373 multi_message_index = 0;
1374 multi_sending_message[Player_num] = msgsend_state::none;
1375 #if defined(DXX_BUILD_DESCENT_II)
1376 multi_send_msgsend_state(msgsend_state::none);
1377 #endif
1378 return;
1379 }
1380
1381 if (Network_message[name_index] == '#' && isdigit(Network_message[name_index+1])) {
1382 playernum_array_t players;
1383 int listpos = Network_message[name_index+1] - '0';
1384
1385 if (Show_kill_list == show_kill_list_mode::_1 || Show_kill_list == show_kill_list_mode::efficiency)
1386 {
1387 if (listpos == 0 || listpos >= N_players) {
1388 HUD_init_message_literal(HM_MULTI, "Invalid player number for kick.");
1389 multi_message_index = 0;
1390 multi_sending_message[Player_num] = msgsend_state::none;
1391 #if defined(DXX_BUILD_DESCENT_II)
1392 multi_send_msgsend_state(msgsend_state::none);
1393 #endif
1394 return;
1395 }
1396 multi_get_kill_list(players);
1397 const auto i = players[listpos];
1398 if (i != Player_num && vcplayerptr(i)->connected)
1399 {
1400 kick_player(*vcplayerptr(i), Netgame.players[i]);
1401 return;
1402 }
1403 }
1404 else HUD_init_message_literal(HM_MULTI, "You cannot use # kicking with in team display.");
1405
1406
1407 multi_message_index = 0;
1408 multi_sending_message[Player_num] = msgsend_state::none;
1409 #if defined(DXX_BUILD_DESCENT_II)
1410 multi_send_msgsend_state(msgsend_state::none);
1411 #endif
1412 return;
1413 }
1414
1415 for (unsigned i = 0; i < N_players; i++)
1416 if (i != Player_num && vcplayerptr(i)->connected && !d_strnicmp(static_cast<const char *>(vcplayerptr(i)->callsign), &Network_message[name_index], strlen(Network_message.data()) - name_index))
1417 {
1418 kick_player(*vcplayerptr(i), Netgame.players[i]);
1419 return;
1420 }
1421 }
1422 else if (!d_stricmp (Network_message.data(), "/killreactor") && (Game_mode & GM_NETWORK) && !LevelUniqueControlCenterState.Control_center_destroyed)
1423 {
1424 if (!multi_i_am_master())
1425 HUD_init_message(HM_MULTI, "Only %s can kill the reactor this way!", static_cast<const char *>(Players[multi_who_is_master()].callsign));
1426 else
1427 {
1428 net_destroy_controlcen_object(object_none);
1429 multi_send_destroy_controlcen(object_none,Player_num);
1430 }
1431 multi_message_index = 0;
1432 multi_sending_message[Player_num] = msgsend_state::none;
1433 #if defined(DXX_BUILD_DESCENT_II)
1434 multi_send_msgsend_state(msgsend_state::none);
1435 #endif
1436 return;
1437 }
1438
1439 #if defined(DXX_BUILD_DESCENT_II)
1440 else
1441 #endif
1442 HUD_init_message(HM_MULTI, "%s '%s'", TXT_SENDING, Network_message.data());
1443
1444 multi_send_message();
1445 multi_message_feedback();
1446
1447 #if defined(DXX_BUILD_DESCENT_I)
1448 Network_message_reciever = 100;
1449 #elif defined(DXX_BUILD_DESCENT_II)
1450 multi_message_index = 0;
1451 multi_sending_message[Player_num] = msgsend_state::none;
1452 multi_send_msgsend_state(msgsend_state::none);
1453 key_toggle_repeat(0);
1454 #endif
1455 game_flush_inputs(Controls);
1456 }
1457
multi_define_macro_end(control_info & Controls)1458 static void multi_define_macro_end(control_info &Controls)
1459 {
1460 Assert( multi_defining_message > 0 );
1461
1462 PlayerCfg.NetworkMessageMacro[multi_defining_message-1] = Network_message;
1463 write_player_file();
1464
1465 multi_message_index = 0;
1466 multi_defining_message = 0;
1467 key_toggle_repeat(0);
1468 game_flush_inputs(Controls);
1469 }
1470
1471 }
1472
multi_message_input_sub(int key,control_info & Controls)1473 window_event_result multi_message_input_sub(int key, control_info &Controls)
1474 {
1475 auto &Objects = LevelUniqueObjectState.Objects;
1476 auto &vmobjptr = Objects.vmptr;
1477 switch( key )
1478 {
1479 case KEY_F8:
1480 case KEY_ESC:
1481 multi_sending_message[Player_num] = msgsend_state::none;
1482 multi_send_msgsend_state(msgsend_state::none);
1483 multi_defining_message = 0;
1484 key_toggle_repeat(0);
1485 game_flush_inputs(Controls);
1486 return window_event_result::handled;
1487 case KEY_LEFT:
1488 case KEY_BACKSP:
1489 case KEY_PAD4:
1490 if (multi_message_index > 0)
1491 multi_message_index--;
1492 Network_message[multi_message_index] = 0;
1493 return window_event_result::handled;
1494 case KEY_ENTER:
1495 if (multi_sending_message[Player_num] != msgsend_state::none)
1496 multi_send_message_end(vmobjptr, Controls);
1497 else if ( multi_defining_message )
1498 multi_define_macro_end(Controls);
1499 game_flush_inputs(Controls);
1500 return window_event_result::handled;
1501 default:
1502 {
1503 int ascii = key_ascii();
1504 if ( ascii < 255 ) {
1505 if (multi_message_index < MAX_MESSAGE_LEN-2 ) {
1506 Network_message[multi_message_index++] = ascii;
1507 Network_message[multi_message_index] = 0;
1508 }
1509 else if (multi_sending_message[Player_num] != msgsend_state::none)
1510 {
1511 int i;
1512 char * ptext;
1513 ptext = NULL;
1514 Network_message[multi_message_index++] = ascii;
1515 Network_message[multi_message_index] = 0;
1516 for (i=multi_message_index-1; i>=0; i-- ) {
1517 if ( Network_message[i]==32 ) {
1518 ptext = &Network_message[i+1];
1519 Network_message[i] = 0;
1520 break;
1521 }
1522 }
1523 multi_send_message_end(vmobjptr, Controls);
1524 if ( ptext ) {
1525 multi_sending_message[Player_num] = msgsend_state::typing;
1526 multi_send_msgsend_state(msgsend_state::typing);
1527 auto pcolon = strstr(Network_message.data(), ": " );
1528 if ( pcolon )
1529 strcpy( pcolon+1, ptext );
1530 else
1531 strcpy(Network_message.data(), ptext);
1532 multi_message_index = strlen( Network_message );
1533 }
1534 }
1535 }
1536 }
1537 }
1538 return window_event_result::ignored;
1539 }
1540
1541 namespace {
1542
multi_do_fire(fvmobjptridx & vmobjptridx,const playernum_t pnum,const uint8_t * const buf)1543 static void multi_do_fire(fvmobjptridx &vmobjptridx, const playernum_t pnum, const uint8_t *const buf)
1544 {
1545 sbyte flags;
1546 vms_vector shot_orientation;
1547
1548 // Act out the actual shooting
1549 const uint8_t untrusted_raw_weapon = buf[2];
1550
1551 flags = buf[4];
1552 icobjidx_t Network_laser_track = object_none;
1553 if (buf[0] == MULTI_FIRE_TRACK)
1554 {
1555 Network_laser_track = GET_INTEL_SHORT(buf + 18);
1556 Network_laser_track = objnum_remote_to_local(Network_laser_track, buf[20]);
1557 }
1558
1559 shot_orientation.x = static_cast<fix>(GET_INTEL_INT(buf + 6));
1560 shot_orientation.y = static_cast<fix>(GET_INTEL_INT(buf + 10));
1561 shot_orientation.z = static_cast<fix>(GET_INTEL_INT(buf + 14));
1562
1563 Assert (pnum < N_players);
1564
1565 const auto &&obj = vmobjptridx(vcplayerptr(pnum)->objnum);
1566 if (obj->type == OBJ_GHOST)
1567 multi_make_ghost_player(pnum);
1568
1569 if (untrusted_raw_weapon == FLARE_ADJUST)
1570 Laser_player_fire(obj, weapon_id_type::FLARE_ID, 6, 1, shot_orientation, object_none);
1571 else if (const uint8_t untrusted_missile_adjusted_weapon = untrusted_raw_weapon - MISSILE_ADJUST; untrusted_missile_adjusted_weapon < MAX_SECONDARY_WEAPONS)
1572 {
1573 const auto weapon = secondary_weapon_index_t{untrusted_missile_adjusted_weapon};
1574 const auto weapon_id = Secondary_weapon_to_weapon_info[weapon];
1575 const auto weapon_gun = Secondary_weapon_to_gun_num[weapon] + (flags & 1);
1576
1577 #if defined(DXX_BUILD_DESCENT_II)
1578 if (weapon == secondary_weapon_index_t::GUIDED_INDEX)
1579 {
1580 Multi_is_guided=1;
1581 }
1582 #endif
1583
1584 const auto &&objnum = Laser_player_fire(obj, weapon_id, weapon_gun, 1, shot_orientation, Network_laser_track);
1585 if (buf[0] == MULTI_FIRE_BOMB)
1586 {
1587 const auto remote_objnum = GET_INTEL_SHORT(buf + 18);
1588 map_objnum_local_to_remote(objnum, remote_objnum, pnum);
1589 }
1590 }
1591 else if (const uint8_t untrusted_weapon = untrusted_raw_weapon; untrusted_weapon < MAX_PRIMARY_WEAPONS)
1592 {
1593 const auto weapon = primary_weapon_index_t{untrusted_weapon};
1594 if (weapon == primary_weapon_index_t::FUSION_INDEX) {
1595 obj->ctype.player_info.Fusion_charge = flags << 12;
1596 }
1597 if (weapon == primary_weapon_index_t::LASER_INDEX)
1598 {
1599 auto &powerup_flags = obj->ctype.player_info.powerup_flags;
1600 if (flags & LASER_QUAD)
1601 powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
1602 else
1603 powerup_flags &= ~PLAYER_FLAGS_QUAD_LASERS;
1604 }
1605
1606 do_laser_firing(obj, weapon, laser_level{buf[3]}, flags, static_cast<int>(buf[5]), shot_orientation, Network_laser_track);
1607 }
1608 }
1609
1610 }
1611
1612 }
1613
1614 namespace {
1615
multi_do_message(const uint8_t * const cbuf)1616 static void multi_do_message(const uint8_t *const cbuf)
1617 {
1618 const auto buf = reinterpret_cast<const char *>(cbuf);
1619 const char *colon;
1620 const char *msgstart;
1621 int loc = 2;
1622
1623 if (((colon = strstr(buf+loc, ": ")) == NULL) || (colon-(buf+loc) < 1) || (colon-(buf+loc) > CALLSIGN_LEN))
1624 {
1625 msgstart = buf + 2;
1626 }
1627 else
1628 {
1629 if ( (!d_strnicmp(static_cast<const char *>(get_local_player().callsign), buf+loc, colon-(buf+loc))) ||
1630 ((Game_mode & GM_TEAM) && ( (get_team(Player_num) == atoi(buf+loc)-1) || !d_strnicmp(Netgame.team_name[get_team(Player_num)], buf+loc, colon-(buf+loc)))) )
1631 {
1632 msgstart = colon + 2;
1633 }
1634 else
1635 return;
1636 }
1637 const playernum_t pnum = buf[1];
1638 const auto color = get_player_or_team_color(pnum);
1639 char xrgb = BM_XRGB(player_rgb[color].r,player_rgb[color].g,player_rgb[color].b);
1640 digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
1641 HUD_init_message(HM_MULTI, "%c%c%s:%c%c %s", CC_COLOR, xrgb, static_cast<const char *>(vcplayerptr(pnum)->callsign), CC_COLOR, BM_XRGB(0, 31, 0), msgstart);
1642 multi_sending_message[pnum] = msgsend_state::none;
1643 }
1644
1645 }
1646
1647 namespace dsx {
1648
1649 namespace {
1650
multi_do_position(fvmobjptridx & vmobjptridx,const playernum_t pnum,const uint8_t * const buf)1651 static void multi_do_position(fvmobjptridx &vmobjptridx, const playernum_t pnum, const uint8_t *const buf)
1652 {
1653 const auto &&obj = vmobjptridx(vcplayerptr(pnum)->objnum);
1654 int count = 1;
1655
1656 quaternionpos qpp{};
1657 qpp.orient.w = GET_INTEL_SHORT(&buf[count]); count += 2;
1658 qpp.orient.x = GET_INTEL_SHORT(&buf[count]); count += 2;
1659 qpp.orient.y = GET_INTEL_SHORT(&buf[count]); count += 2;
1660 qpp.orient.z = GET_INTEL_SHORT(&buf[count]); count += 2;
1661 qpp.pos.x = GET_INTEL_INT(&buf[count]); count += 4;
1662 qpp.pos.y = GET_INTEL_INT(&buf[count]); count += 4;
1663 qpp.pos.z = GET_INTEL_INT(&buf[count]); count += 4;
1664 qpp.segment = GET_INTEL_SHORT(&buf[count]); count += 2;
1665 qpp.vel.x = GET_INTEL_INT(&buf[count]); count += 4;
1666 qpp.vel.y = GET_INTEL_INT(&buf[count]); count += 4;
1667 qpp.vel.z = GET_INTEL_INT(&buf[count]); count += 4;
1668 qpp.rotvel.x = GET_INTEL_INT(&buf[count]); count += 4;
1669 qpp.rotvel.y = GET_INTEL_INT(&buf[count]); count += 4;
1670 qpp.rotvel.z = GET_INTEL_INT(&buf[count]); count += 4;
1671 extract_quaternionpos(obj, qpp);
1672
1673 if (obj->movement_source == object::movement_type::physics)
1674 set_thrust_from_velocity(obj);
1675 }
1676
multi_do_reappear(const playernum_t pnum,const ubyte * buf)1677 static void multi_do_reappear(const playernum_t pnum, const ubyte *buf)
1678 {
1679 auto &Objects = LevelUniqueObjectState.Objects;
1680 auto &vcobjptr = Objects.vcptr;
1681 const objnum_t objnum = GET_INTEL_SHORT(&buf[2]);
1682
1683 const auto &&uobj = vcobjptr.check_untrusted(objnum);
1684 if (!uobj)
1685 return;
1686 auto &obj = **uobj;
1687 if (obj.type != OBJ_PLAYER && obj.type != OBJ_GHOST)
1688 {
1689 con_printf(CON_URGENT, "%s:%u: BUG: object %hu has type %u, expected %u or %u", __FILE__, __LINE__, objnum, obj.type, OBJ_PLAYER, OBJ_GHOST);
1690 return;
1691 }
1692 /* Override macro, call only the getter.
1693 *
1694 * This message is overloaded to be used on both players and ghosts,
1695 * so the standard check cannot be used. Instead, the correct check
1696 * is open-coded above.
1697 */
1698 if (pnum != (get_player_id)(obj))
1699 return;
1700
1701 multi_make_ghost_player(pnum);
1702 create_player_appearance_effect(Vclip, obj);
1703 }
1704
multi_do_player_deres(object_array & Objects,const playernum_t pnum,const uint8_t * const buf)1705 static void multi_do_player_deres(object_array &Objects, const playernum_t pnum, const uint8_t *const buf)
1706 {
1707 auto &vmobjptridx = Objects.vmptridx;
1708 auto &vmobjptr = Objects.vmptr;
1709 // Only call this for players, not robots. pnum is player number, not
1710 // Object number.
1711
1712 int count;
1713 char remote_created;
1714
1715 #ifdef NDEBUG
1716 if (pnum >= N_players)
1717 return;
1718 #else
1719 Assert(pnum < N_players);
1720 #endif
1721
1722 // If we are in the process of sending objects to a new player, reset that process
1723 if (Network_send_objects)
1724 {
1725 Network_send_objnum = -1;
1726 }
1727
1728 // Stuff the Players structure to prepare for the explosion
1729
1730 count = 3;
1731 #if defined(DXX_BUILD_DESCENT_I)
1732 #define GET_WEAPON_FLAGS(buf,count) buf[count++]
1733 #elif defined(DXX_BUILD_DESCENT_II)
1734 #define GET_WEAPON_FLAGS(buf,count) (count += sizeof(uint16_t), GET_INTEL_SHORT(buf + (count - sizeof(uint16_t))))
1735 #endif
1736 const auto &&objp = vmobjptridx(vcplayerptr(pnum)->objnum);
1737 auto &player_info = objp->ctype.player_info;
1738 player_info.primary_weapon_flags = GET_WEAPON_FLAGS(buf, count);
1739 player_info.laser_level = laser_level{buf[count]};
1740 count++;
1741 if (game_mode_hoard())
1742 player_info.hoard.orbs = buf[count];
1743 count++;
1744
1745 auto &secondary_ammo = player_info.secondary_ammo;
1746 secondary_ammo[HOMING_INDEX] = buf[count]; count++;
1747 secondary_ammo[CONCUSSION_INDEX] = buf[count];count++;
1748 secondary_ammo[SMART_INDEX] = buf[count]; count++;
1749 secondary_ammo[MEGA_INDEX] = buf[count]; count++;
1750 secondary_ammo[PROXIMITY_INDEX] = buf[count]; count++;
1751
1752 #if defined(DXX_BUILD_DESCENT_II)
1753 secondary_ammo[SMISSILE1_INDEX] = buf[count]; count++;
1754 secondary_ammo[GUIDED_INDEX] = buf[count]; count++;
1755 secondary_ammo[SMART_MINE_INDEX]= buf[count]; count++;
1756 secondary_ammo[SMISSILE4_INDEX] = buf[count]; count++;
1757 secondary_ammo[SMISSILE5_INDEX] = buf[count]; count++;
1758 #endif
1759
1760 player_info.vulcan_ammo = GET_INTEL_SHORT(buf + count); count += 2;
1761 player_info.powerup_flags = player_flags(GET_INTEL_INT(buf + count)); count += 4;
1762
1763 // objp->phys_info.velocity = *reinterpret_cast<vms_vector *>(buf+16); // 12 bytes
1764 // objp->pos = *reinterpret_cast<vms_vector *>(buf+28); // 12 bytes
1765
1766 remote_created = buf[count++]; // How many did the other guy create?
1767
1768 Net_create_loc = 0;
1769 drop_player_eggs(objp);
1770
1771 // Create mapping from remote to local numbering system
1772
1773 // We now handle this situation gracefully, Int3 not required
1774 // if (Net_create_loc != remote_created)
1775 // Int3(); // Probably out of object array space, see Rob
1776
1777 range_for (const auto i, partial_const_range(Net_create_objnums, std::min(Net_create_loc, static_cast<unsigned>(remote_created))))
1778 {
1779 short s;
1780
1781 s = GET_INTEL_SHORT(buf + count);
1782
1783 if ((s > 0) && (i > 0))
1784 map_objnum_local_to_remote(static_cast<short>(i), s, pnum);
1785 count += 2;
1786 }
1787 range_for (const auto i, partial_const_range(Net_create_objnums, remote_created, Net_create_loc))
1788 {
1789 vmobjptr(i)->flags |= OF_SHOULD_BE_DEAD;
1790 }
1791
1792 if (buf[2] == deres_explode)
1793 {
1794 explode_badass_player(objp);
1795
1796 objp->flags &= ~OF_SHOULD_BE_DEAD; //don't really kill player
1797 multi_make_player_ghost(pnum);
1798 }
1799 else
1800 {
1801 create_player_appearance_effect(Vclip, objp);
1802 }
1803
1804 player_info.powerup_flags &= ~(PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE);
1805 #if defined(DXX_BUILD_DESCENT_II)
1806 player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;
1807 #endif
1808 DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
1809 }
1810
1811 }
1812
1813 }
1814
1815 namespace {
1816
1817 /*
1818 * Process can compute a kill. If I am a Client this might be my own one (see multi_send_kill()) but with more specific data so I can compute my kill correctly.
1819 */
multi_do_kill(object_array & Objects,const uint8_t * const buf)1820 static void multi_do_kill(object_array &Objects, const uint8_t *const buf)
1821 {
1822 int count = 1;
1823 int type = static_cast<int>(buf[0]);
1824
1825 if (multi_i_am_master() && type != MULTI_KILL_CLIENT)
1826 return;
1827 if (!multi_i_am_master() && type != MULTI_KILL_HOST)
1828 return;
1829
1830 const playernum_t pnum = buf[1];
1831 if (pnum >= N_players)
1832 {
1833 Int3(); // Invalid player number killed
1834 return;
1835 }
1836
1837 // I am host, I know what's going on so take this packet, add game_mode related info which might be necessary for kill computation and send it to everyone so they can compute their kills correctly
1838 if (multi_i_am_master())
1839 {
1840 multi_command<MULTI_KILL_HOST> multibuf;
1841 std::memcpy(multibuf.data(), buf, 5);
1842 multibuf[5] = Netgame.team_vector;
1843 multibuf[6] = Bounty_target;
1844
1845 multi_send_data(multibuf, 2);
1846 }
1847
1848 const auto killed = vcplayerptr(pnum)->objnum;
1849 count += 1;
1850 objnum_t killer;
1851 killer = GET_INTEL_SHORT(buf + count);
1852 if (killer > 0)
1853 killer = objnum_remote_to_local(killer, buf[count+2]);
1854 if (!multi_i_am_master())
1855 {
1856 Netgame.team_vector = buf[5];
1857 Bounty_target = buf[6];
1858 }
1859
1860 multi_compute_kill(Objects.imptridx(killer), Objects.vmptridx(killed));
1861
1862 if (Game_mode & GM_BOUNTY && multi_i_am_master()) // update in case if needed... we could attach this to this packet but... meh...
1863 multi_send_bounty();
1864 }
1865
1866 }
1867
1868 namespace dsx {
1869
1870 namespace {
1871
1872 // Changed by MK on 10/20/94 to send NULL as object to net_destroy_controlcen if it got -1
1873 // which means not a controlcen object, but contained in another object
multi_do_controlcen_destroy(fimobjptridx & imobjptridx,const uint8_t * const buf)1874 static void multi_do_controlcen_destroy(fimobjptridx &imobjptridx, const uint8_t *const buf)
1875 {
1876 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1877 objnum_t objnum = GET_INTEL_SHORT(buf + 1);
1878 const playernum_t who = buf[3];
1879
1880 if (LevelUniqueControlCenterState.Control_center_destroyed != 1)
1881 {
1882 if ((who < N_players) && (who != Player_num)) {
1883 HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(vcplayerptr(who)->callsign), TXT_HAS_DEST_CONTROL);
1884 }
1885 else
1886 HUD_init_message_literal(HM_MULTI, who == Player_num ? TXT_YOU_DEST_CONTROL : TXT_CONTROL_DESTROYED);
1887
1888 net_destroy_controlcen_object(objnum == object_none ? object_none : imobjptridx(objnum));
1889 }
1890 }
1891
multi_do_escape(fvmobjptridx & vmobjptridx,const uint8_t * const buf)1892 static void multi_do_escape(fvmobjptridx &vmobjptridx, const uint8_t *const buf)
1893 {
1894 digi_play_sample(SOUND_HUD_MESSAGE, F1_0);
1895 const playernum_t pnum = buf[1];
1896 auto &plr = *vmplayerptr(pnum);
1897 const auto &&objnum = vmobjptridx(plr.objnum);
1898 #if defined(DXX_BUILD_DESCENT_II)
1899 digi_kill_sound_linked_to_object (objnum);
1900 #endif
1901
1902 const char *txt;
1903 int connected;
1904 #if defined(DXX_BUILD_DESCENT_I)
1905 if (buf[2] == static_cast<uint8_t>(multi_endlevel_type::secret))
1906 {
1907 txt = TXT_HAS_FOUND_SECRET;
1908 connected = CONNECT_FOUND_SECRET;
1909 }
1910 else
1911 #endif
1912 {
1913 txt = TXT_HAS_ESCAPED;
1914 connected = CONNECT_ESCAPE_TUNNEL;
1915 }
1916 HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(plr.callsign), txt);
1917 if (Game_mode & GM_NETWORK)
1918 plr.connected = connected;
1919 create_player_appearance_effect(Vclip, objnum);
1920 multi_make_player_ghost(buf[1]);
1921 }
1922
multi_do_remobj(fvmobjptr & vmobjptr,const uint8_t * const buf)1923 static void multi_do_remobj(fvmobjptr &vmobjptr, const uint8_t *const buf)
1924 {
1925 short objnum; // which object to remove
1926
1927 objnum = GET_INTEL_SHORT(buf + 1);
1928 // which remote list is it entered in
1929 auto obj_owner = buf[3];
1930
1931 Assert(objnum >= 0);
1932
1933 if (objnum < 1)
1934 return;
1935
1936 auto local_objnum = objnum_remote_to_local(objnum, obj_owner); // translate to local objnum
1937
1938 if (local_objnum == object_none)
1939 {
1940 return;
1941 }
1942
1943 auto &obj = *vmobjptr(local_objnum);
1944 if (obj.type != OBJ_POWERUP && obj.type != OBJ_HOSTAGE)
1945 {
1946 return;
1947 }
1948
1949 if (Network_send_objects && multi::dispatch->objnum_is_past(local_objnum))
1950 {
1951 Network_send_objnum = -1;
1952 }
1953
1954 obj.flags |= OF_SHOULD_BE_DEAD; // quick and painless
1955 }
1956
1957 }
1958
1959 }
1960
multi_disconnect_player(const playernum_t pnum)1961 void multi_disconnect_player(const playernum_t pnum)
1962 {
1963 if (!(Game_mode & GM_NETWORK))
1964 return;
1965 if (vcplayerptr(pnum)->connected == CONNECT_DISCONNECTED)
1966 return;
1967
1968 if (vcplayerptr(pnum)->connected == CONNECT_PLAYING)
1969 {
1970 digi_play_sample( SOUND_HUD_MESSAGE, F1_0 );
1971 HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(vcplayerptr(pnum)->callsign), TXT_HAS_LEFT_THE_GAME);
1972
1973 multi_sending_message[pnum] = msgsend_state::none;
1974
1975 if (Network_status == NETSTAT_PLAYING)
1976 {
1977 multi_make_player_ghost(pnum);
1978 multi_strip_robots(pnum);
1979 }
1980
1981 if (Newdemo_state == ND_STATE_RECORDING)
1982 newdemo_record_multi_disconnect(pnum);
1983
1984 // Bounty target left - select a new one
1985 if( Game_mode & GM_BOUNTY && pnum == Bounty_target && multi_i_am_master() )
1986 {
1987 update_bounty_target();
1988 /* Send this new data */
1989 multi_send_bounty();
1990 }
1991 }
1992
1993 vmplayerptr(pnum)->connected = CONNECT_DISCONNECTED;
1994 Netgame.players[pnum].connected = CONNECT_DISCONNECTED;
1995
1996 multi::dispatch->disconnect_player(pnum);
1997
1998 if (pnum == multi_who_is_master()) // Host has left - Quit game!
1999 {
2000 const auto g = Game_wind;
2001 if (g)
2002 g->set_visible(0);
2003 struct host_left_game : passive_messagebox
2004 {
2005 host_left_game() :
2006 passive_messagebox(menu_title{nullptr}, menu_subtitle{"Host left the game!"}, TXT_OK, grd_curscreen->sc_canvas)
2007 {
2008 }
2009 };
2010 run_blocking_newmenu<host_left_game>();
2011 if (g)
2012 g->set_visible(1);
2013 multi_quit_game = 1;
2014 game_leave_menus();
2015 return;
2016 }
2017
2018 int n = 0;
2019 range_for (auto &i, partial_const_range(Players, N_players))
2020 if (i.connected)
2021 if (++n > 1)
2022 break;
2023 if (n == 1)
2024 {
2025 HUD_init_message_literal(HM_MULTI, "You are the only person remaining in this netgame");
2026 }
2027 }
2028
2029 namespace {
2030
multi_do_quit(const uint8_t * const buf)2031 static void multi_do_quit(const uint8_t *const buf)
2032 {
2033 if (!(Game_mode & GM_NETWORK))
2034 return;
2035 multi_disconnect_player(static_cast<int>(buf[1]));
2036 }
2037
2038 }
2039
2040 namespace dsx {
2041
2042 namespace {
2043
multi_do_cloak(fvmobjptr & vmobjptr,const playernum_t pnum)2044 static void multi_do_cloak(fvmobjptr &vmobjptr, const playernum_t pnum)
2045 {
2046 Assert(pnum < N_players);
2047
2048 const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
2049 auto &player_info = objp->ctype.player_info;
2050 player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
2051 player_info.cloak_time = GameTime64;
2052 ai_do_cloak_stuff();
2053
2054 multi_strip_robots(pnum);
2055
2056 if (Newdemo_state == ND_STATE_RECORDING)
2057 newdemo_record_multi_cloak(pnum);
2058 }
2059
2060 }
2061
2062 }
2063
2064 namespace dcx {
2065
2066 namespace {
2067
deny_multi_save_game_duplicate_callsign(const partial_range_t<const player * > range)2068 static const char *deny_multi_save_game_duplicate_callsign(const partial_range_t<const player *> range)
2069 {
2070 const auto e = range.end();
2071 for (auto i = range.begin(); i != e; ++i)
2072 for (auto j = std::next(i); j != e; ++j)
2073 {
2074 if (i->callsign == j->callsign)
2075 return "Multiple players with same callsign!";
2076 }
2077 return nullptr;
2078 }
2079
multi_do_decloak(const playernum_t pnum)2080 static void multi_do_decloak(const playernum_t pnum)
2081 {
2082 if (Newdemo_state == ND_STATE_RECORDING)
2083 newdemo_record_multi_decloak(pnum);
2084 }
2085
2086 }
2087
2088 }
2089
2090 namespace dsx {
2091
2092 namespace {
2093
multi_do_door_open(fvmwallptr & vmwallptr,const uint8_t * const buf)2094 static void multi_do_door_open(fvmwallptr &vmwallptr, const uint8_t *const buf)
2095 {
2096 ubyte side;
2097 const segnum_t segnum = GET_INTEL_SHORT(&buf[1]);
2098 side = buf[3];
2099 #if defined(DXX_BUILD_DESCENT_II)
2100 ubyte flag= buf[4];
2101 #endif
2102
2103 if (side > 5)
2104 {
2105 Int3();
2106 return;
2107 }
2108
2109 const auto &&useg = vmsegptridx.check_untrusted(segnum);
2110 if (!useg)
2111 return;
2112 const auto &&seg = *useg;
2113 const auto wall_num = seg->shared_segment::sides[side].wall_num;
2114 if (wall_num == wall_none) { //Opening door on illegal wall
2115 Int3();
2116 return;
2117 }
2118
2119 auto &w = *vmwallptr(wall_num);
2120
2121 if (w.type == WALL_BLASTABLE)
2122 {
2123 if (!(w.flags & wall_flag::blasted))
2124 {
2125 wall_destroy(seg, side);
2126 }
2127 return;
2128 }
2129 else if (w.state != wall_state::opening)
2130 {
2131 wall_open_door(seg, side);
2132 }
2133 #if defined(DXX_BUILD_DESCENT_II)
2134 w.flags = wall_flags{flag};
2135 #endif
2136
2137 }
2138
multi_do_create_explosion(fvmobjptridx & vmobjptridx,const playernum_t pnum)2139 static void multi_do_create_explosion(fvmobjptridx &vmobjptridx, const playernum_t pnum)
2140 {
2141 create_small_fireball_on_object(vmobjptridx(vcplayerptr(pnum)->objnum), F1_0, 1);
2142 }
2143
multi_do_controlcen_fire(const ubyte * buf)2144 static void multi_do_controlcen_fire(const ubyte *buf)
2145 {
2146 auto &Objects = LevelUniqueObjectState.Objects;
2147 auto &vmobjptridx = Objects.vmptridx;
2148 vms_vector to_target;
2149 int gun_num;
2150 objnum_t objnum;
2151 int count = 1;
2152
2153 memcpy(&to_target, buf+count, 12); count += 12;
2154 if constexpr (words_bigendian)// swap the vector to_target
2155 {
2156 to_target.x = INTEL_INT(to_target.x);
2157 to_target.y = INTEL_INT(to_target.y);
2158 to_target.z = INTEL_INT(to_target.z);
2159 }
2160 gun_num = buf[count]; count += 1;
2161 objnum = GET_INTEL_SHORT(buf + count); count += 2;
2162
2163 const auto &&uobj = vmobjptridx.check_untrusted(objnum);
2164 if (!uobj)
2165 return;
2166 const auto &&objp = *uobj;
2167 Laser_create_new_easy(to_target, objp->ctype.reactor_info.gun_pos[gun_num], objp, weapon_id_type::CONTROLCEN_WEAPON_NUM, 1);
2168 }
2169
multi_do_create_powerup(fvmobjptr & vmobjptr,fvmsegptridx & vmsegptridx,const playernum_t pnum,const uint8_t * const buf)2170 static void multi_do_create_powerup(fvmobjptr &vmobjptr, fvmsegptridx &vmsegptridx, const playernum_t pnum, const uint8_t *const buf)
2171 {
2172 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2173 int count = 1;
2174 vms_vector new_pos;
2175 char powerup_type;
2176
2177 if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
2178 return;
2179
2180 count++;
2181 powerup_type = buf[count++];
2182 const auto &&useg = vmsegptridx.check_untrusted(GET_INTEL_SHORT(&buf[count]));
2183 if (!useg)
2184 return;
2185 const auto &&segnum = *useg;
2186 count += 2;
2187 objnum_t objnum = GET_INTEL_SHORT(buf + count); count += 2;
2188 memcpy(&new_pos, buf+count, sizeof(vms_vector)); count+=sizeof(vms_vector);
2189 if constexpr (words_bigendian)
2190 {
2191 new_pos.x = SWAPINT(new_pos.x);
2192 new_pos.y = SWAPINT(new_pos.y);
2193 new_pos.z = SWAPINT(new_pos.z);
2194 }
2195
2196 Net_create_loc = 0;
2197 const auto &&my_objnum = call_object_create_egg(vmobjptr(vcplayerptr(pnum)->objnum), 1, powerup_type);
2198
2199 if (my_objnum == object_none) {
2200 return;
2201 }
2202
2203 if (Network_send_objects && multi::dispatch->objnum_is_past(my_objnum))
2204 {
2205 Network_send_objnum = -1;
2206 }
2207
2208 my_objnum->pos = new_pos;
2209
2210 vm_vec_zero(my_objnum->mtype.phys_info.velocity);
2211
2212 obj_relink(vmobjptr, vmsegptr, my_objnum, segnum);
2213
2214 map_objnum_local_to_remote(my_objnum, objnum, pnum);
2215
2216 object_create_explosion(segnum, new_pos, i2f(5), VCLIP_POWERUP_DISAPPEARANCE);
2217 }
2218
multi_do_play_sound(object_array & Objects,const playernum_t pnum,const uint8_t * const buf)2219 static void multi_do_play_sound(object_array &Objects, const playernum_t pnum, const uint8_t *const buf)
2220 {
2221 auto &vcobjptridx = Objects.vcptridx;
2222 const auto &plr = *vcplayerptr(pnum);
2223 if (!plr.connected)
2224 return;
2225
2226 const unsigned sound_num = buf[2];
2227 const uint8_t once = buf[3];
2228 const fix volume = GET_INTEL_INT(&buf[4]);
2229
2230 assert(plr.objnum <= Highest_object_index);
2231 const auto objnum = plr.objnum;
2232 digi_link_sound_to_object(sound_num, vcobjptridx(objnum), 0, volume, static_cast<sound_stack>(once));
2233 }
2234
2235 }
2236
2237 }
2238
2239 namespace {
2240
multi_do_score(fvmobjptr & vmobjptr,const playernum_t pnum,const uint8_t * const buf)2241 static void multi_do_score(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
2242 {
2243 if (pnum >= N_players)
2244 {
2245 Int3(); // Non-terminal, see rob
2246 return;
2247 }
2248
2249 if (Newdemo_state == ND_STATE_RECORDING) {
2250 int score;
2251 score = GET_INTEL_INT(buf + 2);
2252 newdemo_record_multi_score(pnum, score);
2253 }
2254 auto &player_info = vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info;
2255 player_info.mission.score = GET_INTEL_INT(buf + 2);
2256 multi_sort_kill_list();
2257 }
2258
multi_do_trigger(const playernum_t pnum,const ubyte * buf)2259 static void multi_do_trigger(const playernum_t pnum, const ubyte *buf)
2260 {
2261 auto &Objects = LevelUniqueObjectState.Objects;
2262 auto &vmobjptr = Objects.vmptr;
2263 const std::underlying_type<trgnum_t>::type trigger = buf[2];
2264 if (pnum >= N_players || pnum == Player_num)
2265 {
2266 Int3(); // Got trigger from illegal playernum
2267 return;
2268 }
2269 auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
2270 if (trigger >= Triggers.get_count())
2271 {
2272 Int3(); // Illegal trigger number in multiplayer
2273 return;
2274 }
2275 check_trigger_sub(get_local_plrobj(), static_cast<trgnum_t>(trigger), pnum, 0);
2276 }
2277
2278 }
2279
2280 #if defined(DXX_BUILD_DESCENT_II)
2281 namespace dsx {
2282
2283 namespace {
2284
multi_do_effect_blowup(const playernum_t pnum,const ubyte * buf)2285 static void multi_do_effect_blowup(const playernum_t pnum, const ubyte *buf)
2286 {
2287 int side;
2288 vms_vector hitpnt;
2289
2290 if (pnum >= N_players || pnum == Player_num)
2291 return;
2292
2293 multi::dispatch->do_protocol_frame(1, 0); // force packets to be sent, ensuring this packet will be attached to following MULTI_TRIGGER
2294
2295 const auto &&useg = vmsegptridx.check_untrusted(GET_INTEL_SHORT(&buf[2]));
2296 if (!useg)
2297 return;
2298 side = buf[4];
2299 hitpnt.x = GET_INTEL_INT(buf + 5);
2300 hitpnt.y = GET_INTEL_INT(buf + 9);
2301 hitpnt.z = GET_INTEL_INT(buf + 13);
2302
2303 //create a dummy object which will be the weapon that hits
2304 //the monitor. the blowup code wants to know who the parent of the
2305 //laser is, so create a laser whose parent is the player
2306 laser_parent laser;
2307 laser.parent_type = OBJ_PLAYER;
2308 laser.parent_num = pnum;
2309
2310 auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
2311 check_effect_blowup(LevelSharedDestructibleLightState, Vclip, *useg, side, hitpnt, laser, 0, 1);
2312 }
2313
multi_do_drop_marker(object_array & Objects,fvmsegptridx & vmsegptridx,const playernum_t pnum,const uint8_t * const buf)2314 static void multi_do_drop_marker(object_array &Objects, fvmsegptridx &vmsegptridx, const playernum_t pnum, const uint8_t *const buf)
2315 {
2316 vms_vector position;
2317
2318 if (pnum==Player_num) // my marker? don't set it down cuz it might screw up the orientation
2319 return;
2320
2321 const auto mesnum = buf[2];
2322 const auto game_mode = Game_mode;
2323 const auto max_numplayers = Netgame.max_numplayers;
2324 if (mesnum >= MarkerState.get_markers_per_player(game_mode, max_numplayers))
2325 return;
2326
2327 position.x = GET_INTEL_INT(buf + 3);
2328 position.y = GET_INTEL_INT(buf + 7);
2329 position.z = GET_INTEL_INT(buf + 11);
2330
2331 const auto gmi = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, pnum, player_marker_index{mesnum});
2332 auto &marker_message = MarkerState.message[gmi];
2333 marker_message = {};
2334 for (const auto i : xrange(marker_message.size()))
2335 {
2336 const auto c = marker_message[i] = buf[15 + i];
2337 if (!c)
2338 break;
2339 }
2340
2341 auto &mo = MarkerState.imobjidx[gmi];
2342 if (mo != object_none)
2343 obj_delete(LevelUniqueObjectState, Segments, Objects.vmptridx(mo));
2344
2345 const auto &&plr_objp = Objects.vcptr(vcplayerptr(pnum)->objnum);
2346 mo = drop_marker_object(position, vmsegptridx(plr_objp->segnum), plr_objp->orient, gmi);
2347 }
2348
2349 }
2350
2351 }
2352 #endif
2353
2354 namespace {
2355
multi_do_hostage_door_status(fvmsegptridx & vmsegptridx,wall_array & Walls,const uint8_t * const buf)2356 static void multi_do_hostage_door_status(fvmsegptridx &vmsegptridx, wall_array &Walls, const uint8_t *const buf)
2357 {
2358 // Update hit point status of a door
2359
2360 int count = 1;
2361 fix hps;
2362
2363 const wallnum_t wallnum{GET_INTEL_SHORT(buf + count)};
2364 count += 2;
2365 hps = GET_INTEL_INT(buf + count); count += 4;
2366
2367 auto &vmwallptr = Walls.vmptr;
2368 auto &w = *vmwallptr(wallnum);
2369 if (hps < 0 || w.type != WALL_BLASTABLE)
2370 {
2371 Int3(); // Non-terminal, see Rob
2372 return;
2373 }
2374
2375 if (hps < w.hps)
2376 wall_damage(vmsegptridx(w.segnum), w.sidenum, w.hps - hps);
2377 }
2378
2379 }
2380
multi_reset_stuff()2381 void multi_reset_stuff()
2382 {
2383 auto &Objects = LevelUniqueObjectState.Objects;
2384 auto &vmobjptr = Objects.vmptr;
2385 // A generic, emergency function to solve problems that crop up
2386 // when a player exits quick-out from the game because of a
2387 // connection loss. Fixes several weird bugs!
2388
2389 dead_player_end();
2390 get_local_plrobj().ctype.player_info.homing_object_dist = -1; // Turn off homing sound.
2391 reset_rear_view();
2392 }
2393
2394 namespace dsx {
2395
multi_reset_player_object(object & objp)2396 void multi_reset_player_object(object &objp)
2397 {
2398 //Init physics for a non-console player
2399 Assert((objp.type == OBJ_PLAYER) || (objp.type == OBJ_GHOST));
2400
2401 vm_vec_zero(objp.mtype.phys_info.velocity);
2402 vm_vec_zero(objp.mtype.phys_info.thrust);
2403 vm_vec_zero(objp.mtype.phys_info.rotvel);
2404 vm_vec_zero(objp.mtype.phys_info.rotthrust);
2405 objp.mtype.phys_info.turnroll = 0;
2406 objp.mtype.phys_info.mass = Player_ship->mass;
2407 objp.mtype.phys_info.drag = Player_ship->drag;
2408 if (objp.type == OBJ_PLAYER)
2409 objp.mtype.phys_info.flags = PF_TURNROLL | PF_WIGGLE | PF_USES_THRUST;
2410 else
2411 objp.mtype.phys_info.flags &= ~(PF_TURNROLL | PF_LEVELLING | PF_WIGGLE);
2412
2413 //Init render info
2414
2415 objp.render_type = RT_POLYOBJ;
2416 objp.rtype.pobj_info.model_num = Player_ship->model_num; //what model is this?
2417 objp.rtype.pobj_info.subobj_flags = 0; //zero the flags
2418 objp.rtype.pobj_info.anim_angles = {};
2419
2420 // Clear misc
2421
2422 objp.flags = 0;
2423
2424 if (objp.type == OBJ_GHOST)
2425 objp.render_type = RT_NONE;
2426 //reset textures for this, if not player 0
2427 multi_reset_object_texture (objp);
2428 }
2429
2430 namespace {
2431
multi_reset_object_texture(object_base & objp)2432 static void multi_reset_object_texture(object_base &objp)
2433 {
2434 if (objp.type == OBJ_GHOST)
2435 return;
2436
2437 const auto player_id = get_player_id(objp);
2438 const auto id = (Game_mode & GM_TEAM)
2439 ? get_team(player_id)
2440 : player_id;
2441
2442 auto &pobj_info = objp.rtype.pobj_info;
2443 pobj_info.alt_textures = id;
2444 if (id)
2445 {
2446 auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
2447 auto &model = Polygon_models[pobj_info.model_num];
2448 const unsigned n_textures = model.n_textures;
2449 if (N_PLAYER_SHIP_TEXTURES < n_textures)
2450 Error("Too many player ship textures!\n");
2451
2452 const unsigned first_texture = model.first_texture;
2453 for (unsigned i = 0; i < n_textures; ++i)
2454 multi_player_textures[id - 1][i] = ObjBitmaps[ObjBitmapPtrs[first_texture + i]];
2455
2456 multi_player_textures[id-1][4] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(id-1)*2]];
2457 multi_player_textures[id-1][5] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(id-1)*2+1]];
2458 }
2459 }
2460
2461 }
2462
multi_process_bigdata(const playernum_t pnum,const uint8_t * const buf,const uint_fast32_t len)2463 void multi_process_bigdata(const playernum_t pnum, const uint8_t *const buf, const uint_fast32_t len)
2464 {
2465 // Takes a bunch of messages, check them for validity,
2466 // and pass them to multi_process_data.
2467
2468 uint_fast32_t bytes_processed = 0;
2469
2470 while( bytes_processed < len ) {
2471 const uint_fast32_t type = buf[bytes_processed];
2472
2473 if (type >= std::size(message_length))
2474 {
2475 con_printf(CON_DEBUG, "multi_process_bigdata: Invalid packet type %" PRIuFAST32 "!", type);
2476 return;
2477 }
2478 const uint_fast32_t sub_len = message_length[type];
2479
2480 Assert(sub_len > 0);
2481
2482 if ( (bytes_processed+sub_len) > len ) {
2483 con_printf(CON_DEBUG, "multi_process_bigdata: packet type %" PRIuFAST32 " too short (%" PRIuFAST32 " > %" PRIuFAST32 ")!", type, bytes_processed + sub_len, len);
2484 Int3();
2485 return;
2486 }
2487
2488 multi_process_data(pnum, &buf[bytes_processed], type);
2489 bytes_processed += sub_len;
2490 }
2491 }
2492
2493 //
2494 // Part 2 : Functions that send communication messages to inform the other
2495 // players of something we did.
2496 //
2497
multi_send_fire(int laser_gun,const laser_level level,int laser_flags,int laser_fired,objnum_t laser_track,const imobjptridx_t is_bomb_objnum)2498 void multi_send_fire(int laser_gun, const laser_level level, int laser_flags, int laser_fired, objnum_t laser_track, const imobjptridx_t is_bomb_objnum)
2499 {
2500 auto &Objects = LevelUniqueObjectState.Objects;
2501 auto &vmobjptr = Objects.vmptr;
2502 static fix64 last_fireup_time = 0;
2503
2504 // provoke positional update if possible (20 times per second max. matches vulcan, the fastest firing weapon)
2505 if (timer_query() >= (last_fireup_time+(F1_0/20)))
2506 {
2507 multi::dispatch->do_protocol_frame(1, 0);
2508 last_fireup_time = timer_query();
2509 }
2510
2511 uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
2512 multibuf[0] = static_cast<char>(MULTI_FIRE);
2513 if (is_bomb_objnum != object_none)
2514 {
2515 if (is_proximity_bomb_or_player_smart_mine(get_weapon_id(is_bomb_objnum)))
2516 multibuf[0] = static_cast<char>(MULTI_FIRE_BOMB);
2517 }
2518 else if (laser_track != object_none)
2519 {
2520 multibuf[0] = static_cast<char>(MULTI_FIRE_TRACK);
2521 }
2522 multibuf[1] = static_cast<char>(Player_num);
2523 multibuf[2] = static_cast<char>(laser_gun);
2524 multibuf[3] = static_cast<uint8_t>(level);
2525 multibuf[4] = static_cast<char>(laser_flags);
2526 multibuf[5] = static_cast<char>(laser_fired);
2527
2528 const auto &ownship = get_local_plrobj();
2529 PUT_INTEL_INT(multibuf+6 , ownship.orient.fvec.x);
2530 PUT_INTEL_INT(&multibuf[10], ownship.orient.fvec.y);
2531 PUT_INTEL_INT(&multibuf[14], ownship.orient.fvec.z);
2532
2533 /*
2534 * If we fire a bomb, it's persistent. Let others know of it's objnum so host can track it's behaviour over clients (host-authority functions, D2 chaff ability).
2535 * If we fire a tracking projectile, we should others let know abotu what we track but we have to pay attention it's mapped correctly.
2536 * If we fire something else, we make the packet as small as possible.
2537 */
2538 if (multibuf[0] == MULTI_FIRE_BOMB)
2539 {
2540 map_objnum_local_to_local(is_bomb_objnum);
2541 PUT_INTEL_SHORT(&multibuf[18], static_cast<objnum_t>(is_bomb_objnum));
2542 multi_send_data<MULTI_FIRE_BOMB>(multibuf, 20, 1);
2543 }
2544 else if (multibuf[0] == MULTI_FIRE_TRACK)
2545 {
2546 const auto &&[remote_owner, remote_laser_track] = objnum_local_to_remote(laser_track);
2547 PUT_INTEL_SHORT(&multibuf[18], remote_laser_track);
2548 multibuf[20] = remote_owner;
2549 multi_send_data<MULTI_FIRE_TRACK>(multibuf, 21, 1);
2550 }
2551 else
2552 multi_send_data<MULTI_FIRE>(multibuf, 18, 1);
2553 }
2554
multi_send_destroy_controlcen(const objnum_t objnum,const playernum_t player)2555 void multi_send_destroy_controlcen(const objnum_t objnum, const playernum_t player)
2556 {
2557 if (player == Player_num)
2558 HUD_init_message_literal(HM_MULTI, TXT_YOU_DEST_CONTROL);
2559 else if ((player > 0) && (player < N_players))
2560 HUD_init_message(HM_MULTI, "%s %s", static_cast<const char *>(vcplayerptr(player)->callsign), TXT_HAS_DEST_CONTROL);
2561 else
2562 HUD_init_message_literal(HM_MULTI, TXT_CONTROL_DESTROYED);
2563
2564 multi_command<MULTI_CONTROLCEN> multibuf;
2565 PUT_INTEL_SHORT(&multibuf[1], objnum);
2566 multibuf[3] = player;
2567 multi_send_data(multibuf, 2);
2568 }
2569
2570 #if defined(DXX_BUILD_DESCENT_II)
multi_send_drop_marker(const unsigned player,const vms_vector & position,const player_marker_index messagenum,const marker_message_text_t & text)2571 void multi_send_drop_marker(const unsigned player, const vms_vector &position, const player_marker_index messagenum, const marker_message_text_t &text)
2572 {
2573 uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
2574 multibuf[1]=static_cast<char>(player);
2575 multibuf[2] = static_cast<uint8_t>(messagenum);
2576 PUT_INTEL_INT(&multibuf[3], position.x);
2577 PUT_INTEL_INT(&multibuf[7], position.y);
2578 PUT_INTEL_INT(&multibuf[11], position.z);
2579 for (const auto i : xrange(text.size()))
2580 multibuf[15+i]=text[i];
2581 multi_send_data<MULTI_MARKER>(multibuf, 15 + text.size(), 2);
2582 }
2583
multi_send_markers()2584 void multi_send_markers()
2585 {
2586 auto &Objects = LevelUniqueObjectState.Objects;
2587 auto &vcobjptr = Objects.vcptr;
2588 // send marker positions/text to new player
2589
2590 const auto game_mode = Game_mode;
2591 const auto max_numplayers = Netgame.max_numplayers;
2592 auto &&player_marker_range = get_player_marker_range(MarkerState.get_markers_per_player(game_mode, max_numplayers));
2593 for (const playernum_t pnum : xrange(max_numplayers))
2594 {
2595 for (const auto pmi : player_marker_range)
2596 {
2597 const auto gmi = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, pnum, pmi);
2598 const auto mo = MarkerState.imobjidx[gmi];
2599 if (mo != object_none)
2600 multi_send_drop_marker(pnum, vcobjptr(mo)->pos, pmi, MarkerState.message[gmi]);
2601 }
2602 }
2603 }
2604 #endif
2605
2606 #if defined(DXX_BUILD_DESCENT_I)
multi_send_endlevel_start(const multi_endlevel_type secret)2607 void multi_send_endlevel_start(const multi_endlevel_type secret)
2608 #elif defined(DXX_BUILD_DESCENT_II)
2609 void multi_send_endlevel_start()
2610 #endif
2611 {
2612 multi_command<MULTI_ENDLEVEL_START> buf;
2613 buf[1] = Player_num;
2614 #if defined(DXX_BUILD_DESCENT_I)
2615 buf[2] = static_cast<uint8_t>(secret);
2616 #endif
2617
2618 multi_send_data(buf, 2);
2619 if (Game_mode & GM_NETWORK)
2620 {
2621 get_local_player().connected = CONNECT_ESCAPE_TUNNEL;
2622 multi::dispatch->send_endlevel_packet();
2623 }
2624 }
2625
multi_send_player_deres(deres_type_t type)2626 void multi_send_player_deres(deres_type_t type)
2627 {
2628 auto &Objects = LevelUniqueObjectState.Objects;
2629 auto &vmobjptr = Objects.vmptr;
2630 auto &vmobjptridx = Objects.vmptridx;
2631 int count = 0;
2632 if (Network_send_objects)
2633 {
2634 Network_send_objnum = -1;
2635 }
2636
2637 multi_send_position(vmobjptridx(get_local_player().objnum));
2638
2639 multi_command<MULTI_PLAYER_DERES> multibuf;
2640 count++;
2641 multibuf[count++] = Player_num;
2642 multibuf[count++] = type;
2643
2644 #if defined(DXX_BUILD_DESCENT_I)
2645 #define PUT_WEAPON_FLAGS(buf,count,value) (buf[count] = value, ++count)
2646 #elif defined(DXX_BUILD_DESCENT_II)
2647 #define PUT_WEAPON_FLAGS(buf,count,value) ((PUT_INTEL_SHORT(&buf[count], value)), count+=sizeof(uint16_t))
2648 #endif
2649 auto &player_info = get_local_plrobj().ctype.player_info;
2650 PUT_WEAPON_FLAGS(multibuf, count, player_info.primary_weapon_flags);
2651 multibuf[count++] = static_cast<char>(player_info.laser_level);
2652 multibuf[count++] = game_mode_hoard() ? static_cast<char>(player_info.hoard.orbs) : 0;
2653
2654 auto &secondary_ammo = player_info.secondary_ammo;
2655 multibuf[count++] = secondary_ammo[HOMING_INDEX];
2656 multibuf[count++] = secondary_ammo[CONCUSSION_INDEX];
2657 multibuf[count++] = secondary_ammo[SMART_INDEX];
2658 multibuf[count++] = secondary_ammo[MEGA_INDEX];
2659 multibuf[count++] = secondary_ammo[PROXIMITY_INDEX];
2660
2661 #if defined(DXX_BUILD_DESCENT_II)
2662 multibuf[count++] = secondary_ammo[SMISSILE1_INDEX];
2663 multibuf[count++] = secondary_ammo[GUIDED_INDEX];
2664 multibuf[count++] = secondary_ammo[SMART_MINE_INDEX];
2665 multibuf[count++] = secondary_ammo[SMISSILE4_INDEX];
2666 multibuf[count++] = secondary_ammo[SMISSILE5_INDEX];
2667 #endif
2668
2669 PUT_INTEL_SHORT(&multibuf[count], player_info.vulcan_ammo);
2670 count += 2;
2671 PUT_INTEL_INT(&multibuf[count], player_info.powerup_flags.get_player_flags());
2672 count += 4;
2673
2674 multibuf[count++] = Net_create_loc;
2675
2676 Assert(Net_create_loc <= MAX_NET_CREATE_OBJECTS);
2677
2678 memset(&multibuf[count], -1, MAX_NET_CREATE_OBJECTS*sizeof(short));
2679
2680 range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
2681 {
2682 if (i <= 0) {
2683 Int3(); // Illegal value in created egg object numbers
2684 count +=2;
2685 continue;
2686 }
2687
2688 PUT_INTEL_SHORT(&multibuf[count], i); count += 2;
2689
2690 // We created these objs so our local number = the network number
2691 map_objnum_local_to_local(i);
2692 }
2693
2694 Net_create_loc = 0;
2695
2696 if (count > message_length[MULTI_PLAYER_DERES])
2697 {
2698 Int3(); // See Rob
2699 }
2700
2701 multi_send_data(multibuf, 2);
2702 if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
2703 multi_send_decloak();
2704 multi_strip_robots(Player_num);
2705 }
2706
2707 }
2708
2709 namespace {
2710
multi_send_message()2711 void multi_send_message()
2712 {
2713 int loc = 0;
2714 if (Network_message_reciever != -1)
2715 {
2716 uint8_t multibuf[MAX_MULTI_MESSAGE_LEN+4];
2717 loc += 1;
2718 multibuf[loc] = static_cast<char>(Player_num); loc += 1;
2719 constexpr std::size_t bytes_to_copy = Network_message.size() - 1;
2720 memcpy(reinterpret_cast<char *>(&multibuf[loc]), Network_message.data(), bytes_to_copy);
2721 multibuf[loc + bytes_to_copy] = 0;
2722 loc += MAX_MESSAGE_LEN;
2723 multi_send_data<MULTI_MESSAGE>(multibuf, loc, 0);
2724 Network_message_reciever = -1;
2725 }
2726 }
2727
2728 }
2729
multi_send_reappear()2730 void multi_send_reappear()
2731 {
2732 auto &Objects = LevelUniqueObjectState.Objects;
2733 auto &vmobjptridx = Objects.vmptridx;
2734 auto &plr = get_local_player();
2735 multi_send_position(vmobjptridx(plr.objnum));
2736 multi_command<MULTI_REAPPEAR> multibuf;
2737 multibuf[1] = static_cast<char>(Player_num);
2738 PUT_INTEL_SHORT(&multibuf[2], plr.objnum);
2739
2740 multi_send_data(multibuf, 2);
2741 }
2742
2743 namespace dsx {
2744
multi_send_position(object & obj)2745 void multi_send_position(object &obj)
2746 {
2747 int count=1;
2748
2749 quaternionpos qpp{};
2750 create_quaternionpos(qpp, obj);
2751 multi_command<MULTI_POSITION> multibuf;
2752 PUT_INTEL_SHORT(&multibuf[count], qpp.orient.w); count += 2;
2753 PUT_INTEL_SHORT(&multibuf[count], qpp.orient.x); count += 2;
2754 PUT_INTEL_SHORT(&multibuf[count], qpp.orient.y); count += 2;
2755 PUT_INTEL_SHORT(&multibuf[count], qpp.orient.z); count += 2;
2756 PUT_INTEL_INT(&multibuf[count], qpp.pos.x); count += 4;
2757 PUT_INTEL_INT(&multibuf[count], qpp.pos.y); count += 4;
2758 PUT_INTEL_INT(&multibuf[count], qpp.pos.z); count += 4;
2759 PUT_INTEL_SHORT(&multibuf[count], qpp.segment); count += 2;
2760 PUT_INTEL_INT(&multibuf[count], qpp.vel.x); count += 4;
2761 PUT_INTEL_INT(&multibuf[count], qpp.vel.y); count += 4;
2762 PUT_INTEL_INT(&multibuf[count], qpp.vel.z); count += 4;
2763 PUT_INTEL_INT(&multibuf[count], qpp.rotvel.x); count += 4;
2764 PUT_INTEL_INT(&multibuf[count], qpp.rotvel.y); count += 4;
2765 PUT_INTEL_INT(&multibuf[count], qpp.rotvel.z); count += 4; // 46
2766
2767 // send twice while first has priority so the next one will be attached to the next bigdata packet
2768 multi_send_data(multibuf, 1);
2769 multi_send_data(multibuf, 0);
2770 }
2771
2772 /*
2773 * I was killed. If I am host, send this info to everyone and compute kill. If I am just a Client I'll only send the kill but not compute it for me. I (Client) will wait for Host to send me my kill back together with updated game_mode related variables which are important for me to compute consistent kill.
2774 */
multi_send_kill(const vmobjptridx_t objnum)2775 void multi_send_kill(const vmobjptridx_t objnum)
2776 {
2777 auto &Objects = LevelUniqueObjectState.Objects;
2778 auto &imobjptridx = Objects.imptridx;
2779 auto &vmobjptr = Objects.vmptr;
2780 // I died, tell the world.
2781 int count = 0;
2782
2783 Assert(get_player_id(objnum) == Player_num);
2784 const auto killer_objnum = get_local_plrobj().ctype.player_info.killer_objnum;
2785
2786 union {
2787 multi_command<MULTI_KILL_CLIENT> multibufc;
2788 multi_command<MULTI_KILL_HOST> multibufh;
2789 };
2790 count += 1;
2791 multibufh[count] = Player_num; count += 1;
2792
2793 if (killer_objnum != object_none)
2794 {
2795 const auto &&[remote_owner, remote_objnum] = objnum_local_to_remote(killer_objnum); // do it with variable since INTEL_SHORT won't work on return val from function.
2796 multibufh[count+2] = remote_owner;
2797 PUT_INTEL_SHORT(&multibufh[count], remote_objnum);
2798 }
2799 else
2800 {
2801 multibufh[count+2] = static_cast<char>(-1);
2802 PUT_INTEL_SHORT(&multibufh[count], static_cast<int16_t>(-1));
2803 }
2804 count += 3;
2805 // I am host - I know what's going on so attach game_mode related info which might be vital for correct kill computation
2806 if (multi_i_am_master())
2807 {
2808 multibufh[count] = Netgame.team_vector; count += 1;
2809 multibufh[count] = Bounty_target; count += 1;
2810 multi_compute_kill(imobjptridx(killer_objnum), objnum);
2811 multi_send_data(multibufh, 2);
2812 }
2813 else
2814 multi_send_data_direct(multibufc, multi_who_is_master(), 2); // I am just a client so I'll only send my kill but not compute it, yet. I'll get response from host so I can compute it correctly
2815
2816 multi_strip_robots(Player_num);
2817
2818 if (Game_mode & GM_BOUNTY && multi_i_am_master()) // update in case if needed... we could attach this to this packet but... meh...
2819 multi_send_bounty();
2820 }
2821
multi_send_remobj(const vmobjidx_t objnum)2822 void multi_send_remobj(const vmobjidx_t objnum)
2823 {
2824 // Tell the other guy to remove an object from his list
2825 const auto &&[obj_owner, remote_objnum] = objnum_local_to_remote(objnum);
2826 multi_command<MULTI_REMOVE_OBJECT> multibuf;
2827 PUT_INTEL_SHORT(&multibuf[1], remote_objnum); // Map to network objnums
2828
2829 multibuf[3] = obj_owner;
2830
2831 multi_send_data(multibuf, 2);
2832
2833 if (Network_send_objects && multi::dispatch->objnum_is_past(objnum))
2834 {
2835 Network_send_objnum = -1;
2836 }
2837 }
2838
2839 }
2840
2841 namespace dcx {
2842
2843 namespace {
2844
multi_send_quit()2845 void multi_send_quit()
2846 {
2847 // I am quitting the game, tell the other guy the bad news.
2848
2849 multi_command<MULTI_QUIT> multibuf;
2850 multibuf[1] = Player_num;
2851 multi_send_data(multibuf, 2);
2852 }
2853
2854 }
2855
multi_send_cloak()2856 void multi_send_cloak()
2857 {
2858 // Broadcast a change in our pflags (made to support cloaking)
2859
2860 multi_command<MULTI_CLOAK> multibuf;
2861 const auto pnum = Player_num;
2862 multibuf[1] = pnum;
2863
2864 multi_send_data(multibuf, 2);
2865
2866 multi_strip_robots(pnum);
2867 }
2868
multi_send_decloak()2869 void multi_send_decloak()
2870 {
2871 // Broadcast a change in our pflags (made to support cloaking)
2872
2873 multi_command<MULTI_DECLOAK> multibuf;
2874 multibuf[1] = Player_num;
2875
2876 multi_send_data(multibuf, 2);
2877 }
2878
2879 }
2880
2881 namespace dsx {
2882
multi_send_door_open(const vcsegidx_t segnum,const unsigned side,const wall_flags flag)2883 void multi_send_door_open(const vcsegidx_t segnum, const unsigned side, const wall_flags flag)
2884 {
2885 multi_command<MULTI_DOOR_OPEN> multibuf;
2886 // When we open a door make sure everyone else opens that door
2887 PUT_INTEL_SHORT(&multibuf[1], segnum );
2888 multibuf[3] = static_cast<int8_t>(side);
2889 #if defined(DXX_BUILD_DESCENT_I)
2890 (void)flag;
2891 #elif defined(DXX_BUILD_DESCENT_II)
2892 multibuf[4] = underlying_value(flag);
2893 #endif
2894 multi_send_data(multibuf, 2);
2895 }
2896
2897 #if defined(DXX_BUILD_DESCENT_II)
multi_send_door_open_specific(const playernum_t pnum,const vcsegidx_t segnum,const unsigned side,const wall_flags flag)2898 void multi_send_door_open_specific(const playernum_t pnum, const vcsegidx_t segnum, const unsigned side, const wall_flags flag)
2899 {
2900 // For sending doors only to a specific person (usually when they're joining)
2901
2902 Assert (Game_mode & GM_NETWORK);
2903 // Assert (pnum>-1 && pnum<N_players);
2904
2905 multi_command<MULTI_DOOR_OPEN> multibuf;
2906 PUT_INTEL_SHORT(&multibuf[1], segnum);
2907 multibuf[3] = static_cast<int8_t>(side);
2908 multibuf[4] = underlying_value(flag);
2909
2910 multi_send_data_direct(multibuf, pnum, 2);
2911 }
2912 #endif
2913
2914 }
2915
2916 //
2917 // Part 3 : Functions that change or prepare the game for multiplayer use.
2918 // Not including functions needed to syncronize or start the
2919 // particular type of multiplayer game. Includes preparing the
2920 // mines, player structures, etc.
2921
multi_send_create_explosion(const playernum_t pnum)2922 void multi_send_create_explosion(const playernum_t pnum)
2923 {
2924 // Send all data needed to create a remote explosion
2925
2926 int count = 0;
2927
2928 count += 1;
2929 multi_command<MULTI_CREATE_EXPLOSION> multibuf;
2930 multibuf[count] = static_cast<int8_t>(pnum); count += 1;
2931 // -----------
2932 // Total size = 2
2933 multi_send_data(multibuf, 0);
2934 }
2935
multi_send_controlcen_fire(const vms_vector & to_goal,int best_gun_num,objnum_t objnum)2936 void multi_send_controlcen_fire(const vms_vector &to_goal, int best_gun_num, objnum_t objnum)
2937 {
2938 int count = 0;
2939
2940 count += 1;
2941 multi_command<MULTI_CONTROLCEN_FIRE> multibuf;
2942 if constexpr (words_bigendian)
2943 {
2944 vms_vector swapped_vec;
2945 swapped_vec.x = INTEL_INT(static_cast<int>(to_goal.x));
2946 swapped_vec.y = INTEL_INT(static_cast<int>(to_goal.y));
2947 swapped_vec.z = INTEL_INT(static_cast<int>(to_goal.z));
2948 memcpy(&multibuf[count], &swapped_vec, 12);
2949 }
2950 else
2951 {
2952 memcpy(&multibuf[count], &to_goal, 12);
2953 }
2954 count += 12;
2955 multibuf[count] = static_cast<char>(best_gun_num); count += 1;
2956 PUT_INTEL_SHORT(&multibuf[count], objnum ); count += 2;
2957 // ------------
2958 // Total = 16
2959 multi_send_data(multibuf, 0);
2960 }
2961
2962 namespace dsx {
2963
multi_send_create_powerup(const powerup_type_t powerup_type,const vcsegidx_t segnum,const vcobjidx_t objnum,const vms_vector & pos)2964 void multi_send_create_powerup(const powerup_type_t powerup_type, const vcsegidx_t segnum, const vcobjidx_t objnum, const vms_vector &pos)
2965 {
2966 auto &Objects = LevelUniqueObjectState.Objects;
2967 auto &vmobjptridx = Objects.vmptridx;
2968 // Create a powerup on a remote machine, used for remote
2969 // placement of used powerups like missiles and cloaking
2970 // powerups.
2971
2972 int count = 0;
2973
2974 multi_send_position(vmobjptridx(get_local_player().objnum));
2975
2976 count += 1;
2977 multi_command<MULTI_CREATE_POWERUP> multibuf;
2978 multibuf[count] = Player_num; count += 1;
2979 multibuf[count] = powerup_type; count += 1;
2980 PUT_INTEL_SHORT(&multibuf[count], segnum ); count += 2;
2981 PUT_INTEL_SHORT(&multibuf[count], objnum ); count += 2;
2982 if constexpr (words_bigendian)
2983 {
2984 vms_vector swapped_vec;
2985 swapped_vec.x = INTEL_INT(static_cast<int>(pos.x));
2986 swapped_vec.y = INTEL_INT(static_cast<int>(pos.y));
2987 swapped_vec.z = INTEL_INT(static_cast<int>(pos.z));
2988 memcpy(&multibuf[count], &swapped_vec, 12);
2989 count += 12;
2990 }
2991 else
2992 {
2993 memcpy(&multibuf[count], &pos, sizeof(vms_vector));
2994 count += sizeof(vms_vector);
2995 }
2996 // -----------
2997 // Total = 19
2998 multi_send_data(multibuf, 2);
2999
3000 if (Network_send_objects && multi::dispatch->objnum_is_past(objnum))
3001 {
3002 Network_send_objnum = -1;
3003 }
3004
3005 map_objnum_local_to_local(objnum);
3006 }
3007
3008 }
3009
3010 namespace {
3011
multi_digi_play_sample(const int soundnum,const fix max_volume,const sound_stack once)3012 static void multi_digi_play_sample(const int soundnum, const fix max_volume, const sound_stack once)
3013 {
3014 auto &Objects = LevelUniqueObjectState.Objects;
3015 auto &vcobjptridx = Objects.vcptridx;
3016 if (Game_mode & GM_MULTI)
3017 multi_send_play_sound(soundnum, max_volume, once);
3018 digi_link_sound_to_object(soundnum, vcobjptridx(Viewer), 0, max_volume, once);
3019 }
3020
3021 }
3022
multi_digi_play_sample_once(int soundnum,fix max_volume)3023 void multi_digi_play_sample_once(int soundnum, fix max_volume)
3024 {
3025 multi_digi_play_sample(soundnum, max_volume, sound_stack::cancel_previous);
3026 }
3027
multi_digi_play_sample(int soundnum,fix max_volume)3028 void multi_digi_play_sample(int soundnum, fix max_volume)
3029 {
3030 multi_digi_play_sample(soundnum, max_volume, sound_stack::allow_stacking);
3031 }
3032
3033 namespace dsx {
3034
multi_digi_link_sound_to_pos(const int soundnum,const vcsegptridx_t segnum,const unsigned sidenum,const vms_vector & pos,const int forever,const fix max_volume)3035 void multi_digi_link_sound_to_pos(const int soundnum, const vcsegptridx_t segnum, const unsigned sidenum, const vms_vector &pos, const int forever, const fix max_volume)
3036 {
3037 if (Game_mode & GM_MULTI)
3038 multi_send_play_sound(soundnum, max_volume, sound_stack::allow_stacking);
3039 digi_link_sound_to_pos(soundnum, segnum, sidenum, pos, forever, max_volume);
3040 }
3041
3042 }
3043
multi_send_play_sound(const int sound_num,const fix volume,const sound_stack once)3044 void multi_send_play_sound(const int sound_num, const fix volume, const sound_stack once)
3045 {
3046 int count = 0;
3047 count += 1;
3048 multi_command<MULTI_PLAY_SOUND> multibuf;
3049 multibuf[count] = Player_num; count += 1;
3050 multibuf[count] = static_cast<char>(sound_num); count += 1;
3051 multibuf[count] = static_cast<uint8_t>(once); count += 1;
3052 PUT_INTEL_INT(&multibuf[count], volume); count += 4;
3053 // -----------
3054 // Total = 4
3055 multi_send_data(multibuf, 0);
3056 }
3057
multi_send_score()3058 void multi_send_score()
3059 {
3060 auto &Objects = LevelUniqueObjectState.Objects;
3061 auto &vmobjptr = Objects.vmptr;
3062 // Send my current score to all other players so it will remain
3063 // synced.
3064 int count = 0;
3065
3066 if (Game_mode & GM_MULTI_COOP) {
3067 multi_sort_kill_list();
3068 count += 1;
3069 multi_command<MULTI_SCORE> multibuf;
3070 multibuf[count] = Player_num; count += 1;
3071 auto &player_info = get_local_plrobj().ctype.player_info;
3072 PUT_INTEL_INT(&multibuf[count], player_info.mission.score); count += 4;
3073 multi_send_data(multibuf, 0);
3074 }
3075 }
3076
multi_send_trigger(const trgnum_t triggernum)3077 void multi_send_trigger(const trgnum_t triggernum)
3078 {
3079 // Send an event to trigger something in the mine
3080
3081 int count = 0;
3082
3083 count += 1;
3084 multi_command<MULTI_TRIGGER> multibuf;
3085 multibuf[count] = Player_num; count += 1;
3086 static_assert(sizeof(trgnum_t) == sizeof(uint8_t), "trigger number could be truncated");
3087 multibuf[count] = static_cast<uint8_t>(triggernum); count += 1;
3088
3089 multi_send_data(multibuf, 2);
3090 }
3091
3092 namespace dsx {
3093
3094 #if defined(DXX_BUILD_DESCENT_II)
multi_send_effect_blowup(const vcsegidx_t segnum,const unsigned side,const vms_vector & pnt)3095 void multi_send_effect_blowup(const vcsegidx_t segnum, const unsigned side, const vms_vector &pnt)
3096 {
3097 // We blew up something connected to a trigger. Send this blowup result to other players shortly before MULTI_TRIGGER.
3098 // NOTE: The reason this is now a separate packet is to make sure trigger-connected switches/monitors are in sync with MULTI_TRIGGER.
3099 // If a fire packet is late it might blow up a switch for some clients without the shooter actually registering this hit,
3100 // not sending MULTI_TRIGGER and making puzzles or progress impossible.
3101 int count = 0;
3102
3103 multi::dispatch->do_protocol_frame(1, 0); // force packets to be sent, ensuring this packet will be attached to following MULTI_TRIGGER
3104
3105 count += 1;
3106 multi_command<MULTI_EFFECT_BLOWUP> multibuf;
3107 multibuf[count] = Player_num; count += 1;
3108 PUT_INTEL_SHORT(&multibuf[count], segnum); count += 2;
3109 multibuf[count] = static_cast<int8_t>(side); count += 1;
3110 PUT_INTEL_INT(&multibuf[count], pnt.x); count += 4;
3111 PUT_INTEL_INT(&multibuf[count], pnt.y); count += 4;
3112 PUT_INTEL_INT(&multibuf[count], pnt.z); count += 4;
3113
3114 multi_send_data(multibuf, 0);
3115 }
3116 #endif
3117
multi_send_hostage_door_status(const vcwallptridx_t w)3118 void multi_send_hostage_door_status(const vcwallptridx_t w)
3119 {
3120 // Tell the other player what the hit point status of a hostage door
3121 // should be
3122
3123 int count = 0;
3124
3125 assert(w->type == WALL_BLASTABLE);
3126
3127 count += 1;
3128 multi_command<MULTI_HOSTAGE_DOOR> multibuf;
3129 PUT_INTEL_SHORT(&multibuf[count], underlying_value(wallnum_t{w}));
3130 count += 2;
3131 PUT_INTEL_INT(&multibuf[count], w->hps); count += 4;
3132
3133 multi_send_data(multibuf, 0);
3134 }
3135
3136 }
3137
multi_consistency_error(int reset)3138 void multi_consistency_error(int reset)
3139 {
3140 static int count = 0;
3141
3142 if (reset)
3143 count = 0;
3144
3145 if (++count < 10)
3146 return;
3147
3148 const auto g = Game_wind;
3149 if (g)
3150 g->set_visible(0);
3151 nm_messagebox_str(menu_title{nullptr}, nm_messagebox_tie(TXT_OK), menu_subtitle{TXT_CONSISTENCY_ERROR});
3152 if (g)
3153 g->set_visible(1);
3154 count = 0;
3155 multi_quit_game = 1;
3156 game_leave_menus();
3157 multi_reset_stuff();
3158 }
3159
grant_shift_helper(const packed_spawn_granted_items p,int s)3160 static constexpr unsigned grant_shift_helper(const packed_spawn_granted_items p, int s)
3161 {
3162 return s > 0 ? p.mask >> s : p.mask << -s;
3163 }
3164
3165 namespace dsx {
3166
map_granted_flags_to_player_flags(const packed_spawn_granted_items p)3167 player_flags map_granted_flags_to_player_flags(const packed_spawn_granted_items p)
3168 {
3169 auto &grant = p.mask;
3170 const auto None = PLAYER_FLAG::None;
3171 return player_flags(
3172 ((grant & NETGRANT_QUAD) ? PLAYER_FLAGS_QUAD_LASERS : None)
3173 #if defined(DXX_BUILD_DESCENT_II)
3174 | ((grant & NETGRANT_AFTERBURNER) ? PLAYER_FLAGS_AFTERBURNER : None)
3175 | ((grant & NETGRANT_AMMORACK) ? PLAYER_FLAGS_AMMO_RACK : None)
3176 | ((grant & NETGRANT_CONVERTER) ? PLAYER_FLAGS_CONVERTER : None)
3177 | ((grant & NETGRANT_HEADLIGHT) ? PLAYER_FLAGS_HEADLIGHT : None)
3178 #endif
3179 );
3180 }
3181
map_granted_flags_to_primary_weapon_flags(const packed_spawn_granted_items p)3182 uint_fast32_t map_granted_flags_to_primary_weapon_flags(const packed_spawn_granted_items p)
3183 {
3184 auto &grant = p.mask;
3185 return ((grant & NETGRANT_VULCAN) ? HAS_VULCAN_FLAG : 0)
3186 | ((grant & NETGRANT_SPREAD) ? HAS_SPREADFIRE_FLAG : 0)
3187 | ((grant & NETGRANT_PLASMA) ? HAS_PLASMA_FLAG : 0)
3188 | ((grant & NETGRANT_FUSION) ? HAS_FUSION_FLAG : 0)
3189 #if defined(DXX_BUILD_DESCENT_II)
3190 | ((grant & NETGRANT_GAUSS) ? HAS_GAUSS_FLAG : 0)
3191 | ((grant & NETGRANT_HELIX) ? HAS_HELIX_FLAG : 0)
3192 | ((grant & NETGRANT_PHOENIX) ? HAS_PHOENIX_FLAG : 0)
3193 | ((grant & NETGRANT_OMEGA) ? HAS_OMEGA_FLAG : 0)
3194 #endif
3195 ;
3196 }
3197
map_granted_flags_to_vulcan_ammo(const packed_spawn_granted_items p)3198 uint16_t map_granted_flags_to_vulcan_ammo(const packed_spawn_granted_items p)
3199 {
3200 auto &grant = p.mask;
3201 const auto amount = VULCAN_WEAPON_AMMO_AMOUNT;
3202 return
3203 #if defined(DXX_BUILD_DESCENT_II)
3204 (grant & NETGRANT_GAUSS ? amount : 0) +
3205 #endif
3206 (grant & NETGRANT_VULCAN ? amount : 0);
3207 }
3208
3209 namespace {
3210
map_granted_flags_to_netflag(const packed_spawn_granted_items grant)3211 static constexpr unsigned map_granted_flags_to_netflag(const packed_spawn_granted_items grant)
3212 {
3213 return (grant_shift_helper(grant, BIT_NETGRANT_QUAD - BIT_NETFLAG_DOQUAD) & (NETFLAG_DOQUAD | NETFLAG_DOVULCAN | NETFLAG_DOSPREAD | NETFLAG_DOPLASMA | NETFLAG_DOFUSION))
3214 #if defined(DXX_BUILD_DESCENT_II)
3215 | (grant_shift_helper(grant, BIT_NETGRANT_GAUSS - BIT_NETFLAG_DOGAUSS) & (NETFLAG_DOGAUSS | NETFLAG_DOHELIX | NETFLAG_DOPHOENIX | NETFLAG_DOOMEGA))
3216 | (grant_shift_helper(grant, BIT_NETGRANT_AFTERBURNER - BIT_NETFLAG_DOAFTERBURNER) & (NETFLAG_DOAFTERBURNER | NETFLAG_DOAMMORACK | NETFLAG_DOCONVERTER | NETFLAG_DOHEADLIGHT))
3217 #endif
3218 ;
3219 }
3220
3221 assert_equal(0, 0, "zero");
3222 assert_equal(map_granted_flags_to_netflag(NETGRANT_QUAD), NETFLAG_DOQUAD, "QUAD");
3223 assert_equal(map_granted_flags_to_netflag(NETGRANT_QUAD | NETGRANT_PLASMA), NETFLAG_DOQUAD | NETFLAG_DOPLASMA, "QUAD | PLASMA");
3224 #if defined(DXX_BUILD_DESCENT_II)
3225 assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS), NETFLAG_DOGAUSS, "GAUSS");
3226 assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_PLASMA), NETFLAG_DOGAUSS | NETFLAG_DOPLASMA, "GAUSS | PLASMA");
3227 assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_AFTERBURNER), NETFLAG_DOGAUSS | NETFLAG_DOAFTERBURNER, "GAUSS | AFTERBURNER");
3228 assert_equal(map_granted_flags_to_netflag(NETGRANT_GAUSS | NETGRANT_PLASMA | NETGRANT_AFTERBURNER), NETFLAG_DOGAUSS | NETFLAG_DOPLASMA | NETFLAG_DOAFTERBURNER, "GAUSS | PLASMA | AFTERBURNER");
3229 assert_equal(map_granted_flags_to_netflag(NETGRANT_PLASMA | NETGRANT_AFTERBURNER), NETFLAG_DOPLASMA | NETFLAG_DOAFTERBURNER, "PLASMA | AFTERBURNER");
3230 assert_equal(map_granted_flags_to_netflag(NETGRANT_AFTERBURNER), NETFLAG_DOAFTERBURNER, "AFTERBURNER");
3231 assert_equal(map_granted_flags_to_netflag(NETGRANT_HEADLIGHT), NETFLAG_DOHEADLIGHT, "HEADLIGHT");
3232 #endif
3233
3234 class update_item_state
3235 {
3236 std::bitset<MAX_OBJECTS> m_modified;
3237 public:
must_skip(const vcobjidx_t i) const3238 bool must_skip(const vcobjidx_t i) const
3239 {
3240 return m_modified.test(i);
3241 }
3242 void process_powerup(const d_vclip_array &Vclip, fvmsegptridx &, const object &, powerup_type_t);
3243 };
3244
3245 class powerup_shuffle_state
3246 {
3247 unsigned count = 0;
3248 unsigned seed;
3249 union {
3250 std::array<vmobjptridx_t, MAX_OBJECTS> ptrs;
3251 };
3252 public:
powerup_shuffle_state(const unsigned s)3253 powerup_shuffle_state(const unsigned s) :
3254 seed(s)
3255 {
3256 }
3257 void record_powerup(vmobjptridx_t);
3258 void shuffle() const;
3259 };
3260
process_powerup(const d_vclip_array & Vclip,fvmsegptridx & vmsegptridx,const object & o,const powerup_type_t id)3261 void update_item_state::process_powerup(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, const object &o, const powerup_type_t id)
3262 {
3263 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
3264 auto &Vertices = LevelSharedVertexState.get_vertices();
3265 uint_fast32_t count;
3266 switch (id)
3267 {
3268 case POW_LASER:
3269 case POW_QUAD_FIRE:
3270 case POW_VULCAN_WEAPON:
3271 case POW_VULCAN_AMMO:
3272 case POW_SPREADFIRE_WEAPON:
3273 case POW_PLASMA_WEAPON:
3274 case POW_FUSION_WEAPON:
3275 #if defined(DXX_BUILD_DESCENT_II)
3276 case POW_SUPER_LASER:
3277 case POW_GAUSS_WEAPON:
3278 case POW_HELIX_WEAPON:
3279 case POW_PHOENIX_WEAPON:
3280 case POW_OMEGA_WEAPON:
3281 #endif
3282 count = Netgame.DuplicatePowerups.get_primary_count();
3283 break;
3284 case POW_MISSILE_1:
3285 case POW_MISSILE_4:
3286 case POW_HOMING_AMMO_1:
3287 case POW_HOMING_AMMO_4:
3288 case POW_PROXIMITY_WEAPON:
3289 case POW_SMARTBOMB_WEAPON:
3290 case POW_MEGA_WEAPON:
3291 #if defined(DXX_BUILD_DESCENT_II)
3292 case POW_SMISSILE1_1:
3293 case POW_SMISSILE1_4:
3294 case POW_GUIDED_MISSILE_1:
3295 case POW_GUIDED_MISSILE_4:
3296 case POW_SMART_MINE:
3297 case POW_MERCURY_MISSILE_1:
3298 case POW_MERCURY_MISSILE_4:
3299 case POW_EARTHSHAKER_MISSILE:
3300 #endif
3301 count = Netgame.DuplicatePowerups.get_secondary_count();
3302 break;
3303 #if defined(DXX_BUILD_DESCENT_II)
3304 case POW_FULL_MAP:
3305 case POW_CONVERTER:
3306 case POW_AMMO_RACK:
3307 case POW_AFTERBURNER:
3308 case POW_HEADLIGHT:
3309 count = Netgame.DuplicatePowerups.get_accessory_count();
3310 break;
3311 #endif
3312 default:
3313 return;
3314 }
3315 if (!count)
3316 return;
3317 const auto &vc = Vclip[o.rtype.vclip_info.vclip_num];
3318 const auto vc_num_frames = vc.num_frames;
3319 const auto &&segp = vmsegptridx(o.segnum);
3320 const auto &seg_verts = segp->verts;
3321 auto &vcvertptr = Vertices.vcptr;
3322 for (uint_fast32_t i = count++; i; --i)
3323 {
3324 assert(o.movement_source == object::movement_type::None);
3325 assert(o.render_type == RT_POWERUP);
3326 const auto &&no = obj_create(OBJ_POWERUP, id, segp, vm_vec_avg(o.pos, vcvertptr(seg_verts[i % seg_verts.size()])), &vmd_identity_matrix, o.size, object::control_type::powerup, object::movement_type::None, RT_POWERUP);
3327 if (no == object_none)
3328 return;
3329 m_modified.set(no);
3330 no->rtype.vclip_info = o.rtype.vclip_info;
3331 no->rtype.vclip_info.framenum = (o.rtype.vclip_info.framenum + (i * vc_num_frames) / count) % vc_num_frames;
3332 no->ctype.powerup_info = o.ctype.powerup_info;
3333 }
3334 }
3335
3336 class accumulate_object_count
3337 {
3338 protected:
3339 using array_reference = std::array<uint32_t, MAX_POWERUP_TYPES> &;
3340 array_reference current;
accumulate_object_count(array_reference a)3341 accumulate_object_count(array_reference a) : current(a)
3342 {
3343 }
3344 };
3345
3346 template <typename F, typename M>
3347 class accumulate_flags_count : accumulate_object_count
3348 {
3349 const F &flags;
3350 public:
accumulate_flags_count(array_reference a,const F & f)3351 accumulate_flags_count(array_reference a, const F &f) :
3352 accumulate_object_count(a), flags(f)
3353 {
3354 }
process(const M mask,const unsigned id) const3355 void process(const M mask, const unsigned id) const
3356 {
3357 if (flags & mask)
3358 ++current[id];
3359 }
3360 };
3361
3362 }
3363
3364 /*
3365 * The place to do objects operations such as:
3366 * Robot deletion for non-robot games, Powerup duplication, AllowedItems, Initial powerup counting.
3367 * MUST be done before multi_level_sync() in case we join a running game and get updated objects there. We want the initial powerup setup for a level here!
3368 */
multi_prep_level_objects(const d_powerup_info_array & Powerup_info,const d_vclip_array & Vclip)3369 void multi_prep_level_objects(const d_powerup_info_array &Powerup_info, const d_vclip_array &Vclip)
3370 {
3371 auto &Objects = LevelUniqueObjectState.Objects;
3372 auto &vmobjptridx = Objects.vmptridx;
3373 if (!(Game_mode & GM_MULTI_COOP))
3374 {
3375 multi_update_objects_for_non_cooperative(); // Removes monsters from level
3376 }
3377
3378 constexpr unsigned MAX_ALLOWED_INVULNERABILITY = 3;
3379 constexpr unsigned MAX_ALLOWED_CLOAK = 3;
3380 const auto AllowedItems = Netgame.AllowedItems;
3381 const auto SpawnGrantedItems = map_granted_flags_to_netflag(Netgame.SpawnGrantedItems);
3382 unsigned inv_remaining = (AllowedItems & NETFLAG_DOINVUL) ? MAX_ALLOWED_INVULNERABILITY : 0;
3383 unsigned cloak_remaining = (AllowedItems & NETFLAG_DOCLOAK) ? MAX_ALLOWED_CLOAK : 0;
3384 update_item_state duplicates;
3385 range_for (const auto &&o, vmobjptridx)
3386 {
3387 if ((o->type == OBJ_HOSTAGE) && !(Game_mode & GM_MULTI_COOP))
3388 {
3389 const auto &&objnum = obj_create(OBJ_POWERUP, POW_SHIELD_BOOST, vmsegptridx(o->segnum), o->pos, &vmd_identity_matrix, Powerup_info[POW_SHIELD_BOOST].size, object::control_type::powerup, object::movement_type::physics, RT_POWERUP);
3390 obj_delete(LevelUniqueObjectState, Segments, o);
3391 if (objnum != object_none)
3392 {
3393 objnum->rtype.vclip_info.vclip_num = Powerup_info[POW_SHIELD_BOOST].vclip_num;
3394 objnum->rtype.vclip_info.frametime = Vclip[objnum->rtype.vclip_info.vclip_num].frame_time;
3395 objnum->rtype.vclip_info.framenum = 0;
3396 objnum->mtype.phys_info.drag = 512; //1024;
3397 objnum->mtype.phys_info.mass = F1_0;
3398 vm_vec_zero(objnum->mtype.phys_info.velocity);
3399 }
3400 continue;
3401 }
3402
3403 if (o->type == OBJ_POWERUP && !duplicates.must_skip(o))
3404 {
3405 switch (const auto id = get_powerup_id(o))
3406 {
3407 case POW_EXTRA_LIFE:
3408 set_powerup_id(Powerup_info, Vclip, o, POW_INVULNERABILITY);
3409 DXX_BOOST_FALLTHROUGH;
3410 case POW_INVULNERABILITY:
3411 if (inv_remaining)
3412 -- inv_remaining;
3413 else
3414 set_powerup_id(Powerup_info, Vclip, o, POW_SHIELD_BOOST);
3415 continue;
3416 case POW_CLOAK:
3417 if (cloak_remaining)
3418 -- cloak_remaining;
3419 else
3420 set_powerup_id(Powerup_info, Vclip, o, POW_SHIELD_BOOST);
3421 continue;
3422 default:
3423 if (!multi_powerup_is_allowed(id, AllowedItems, SpawnGrantedItems))
3424 bash_to_shield(Powerup_info, Vclip, o);
3425 else
3426 duplicates.process_powerup(Vclip, vmsegptridx, o, id);
3427 continue;
3428 }
3429 }
3430 }
3431
3432 // After everything is done, count initial level inventory.
3433 MultiLevelInv_InitializeCount();
3434 }
3435
multi_prep_level_player(void)3436 void multi_prep_level_player(void)
3437 {
3438 auto &Objects = LevelUniqueObjectState.Objects;
3439 auto &vmobjptr = Objects.vmptr;
3440 // Do any special stuff to the level required for games
3441 // before we begin playing in it.
3442
3443 // Player_num MUST be set before calling this procedure.
3444
3445 // This function must be called before checksuming the Object array,
3446 // since the resulting checksum with depend on the value of Player_num
3447 // at the time this is called.
3448
3449 Assert(Game_mode & GM_MULTI);
3450
3451 Assert(NumNetPlayerPositions > 0);
3452
3453 #if defined(DXX_BUILD_DESCENT_II)
3454 hoard_highest_record_stats = {};
3455 Drop_afterburner_blob_flag=0;
3456 #endif
3457 Bounty_target = 0;
3458
3459 multi_consistency_error(1);
3460
3461 multi_sending_message.fill(msgsend_state::none);
3462 if (imulti_new_game)
3463 for (uint_fast32_t i = 0; i != Players.size(); i++)
3464 init_player_stats_new_ship(i);
3465
3466 for (unsigned i = 0; i < NumNetPlayerPositions; i++)
3467 {
3468 const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
3469 if (i != Player_num)
3470 objp->control_source = object::control_type::remote;
3471 objp->movement_source = object::movement_type::physics;
3472 multi_reset_player_object(objp);
3473 Netgame.players[i].LastPacketTime = 0;
3474 }
3475
3476 robot_controlled.fill(-1);
3477 robot_agitation = {};
3478 robot_fired = {};
3479
3480 Viewer = ConsoleObject = &get_local_plrobj();
3481
3482 #if defined(DXX_BUILD_DESCENT_II)
3483 if (game_mode_hoard())
3484 init_hoard_data(Vclip);
3485
3486 if (game_mode_capture_flag() || game_mode_hoard())
3487 multi_apply_goal_textures();
3488 #endif
3489
3490 multi_sort_kill_list();
3491
3492 multi_show_player_list();
3493
3494 ConsoleObject->control_source = object::control_type::flying;
3495
3496 reset_player_object();
3497
3498 imulti_new_game=0;
3499 }
3500
3501 #if defined(DXX_BUILD_DESCENT_II)
3502 namespace {
3503
apply_segment_goal_texture(const d_level_unique_tmap_info_state & LevelUniqueTmapInfoState,unique_segment & seg,const texture1_value tex)3504 void apply_segment_goal_texture(const d_level_unique_tmap_info_state &LevelUniqueTmapInfoState, unique_segment &seg, const texture1_value tex)
3505 {
3506 auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
3507 const auto bright_light = i2f(100); //make static light bright
3508 seg.static_light = bright_light;
3509 if (static_cast<unsigned>(tex) < TmapInfo.size())
3510 range_for (auto &s, seg.sides)
3511 {
3512 s.tmap_num = tex;
3513 range_for (auto &uvl, s.uvls)
3514 uvl.l = bright_light; //max out
3515 }
3516 }
3517
find_goal_texture(const d_level_unique_tmap_info_state & LevelUniqueTmapInfoState,const tmapinfo_flag tmi_flag)3518 texture_index find_goal_texture(const d_level_unique_tmap_info_state &LevelUniqueTmapInfoState, const tmapinfo_flag tmi_flag)
3519 {
3520 auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
3521 const auto &&r = partial_const_range(TmapInfo, NumTextures);
3522 const auto &&predicate = [tmi_flag](const tmap_info &i) {
3523 return (i.flags & tmi_flag);
3524 };
3525 const auto begin = r.begin();
3526 const auto idx = std::distance(begin, std::find_if(begin, r.end(), predicate));
3527 return idx;
3528 }
3529
find_required_goal_texture(const d_level_unique_tmap_info_state & LevelUniqueTmapInfoState,const tmapinfo_flag tmi_flag)3530 const tmap_info &find_required_goal_texture(const d_level_unique_tmap_info_state &LevelUniqueTmapInfoState, const tmapinfo_flag tmi_flag)
3531 {
3532 auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
3533 const auto found_index = find_goal_texture(LevelUniqueTmapInfoState, tmi_flag);
3534 std::size_t r = found_index;
3535 if (r < TmapInfo.size())
3536 return TmapInfo[r];
3537 Int3(); // Hey, there is no goal texture for this PIG!!!!
3538 // Edit bitmaps.tbl and designate two textures to be RED and BLUE
3539 // goal textures
3540 throw std::runtime_error("PIG missing goal texture");
3541 }
3542
3543 }
3544
multi_apply_goal_textures()3545 void multi_apply_goal_textures()
3546 {
3547 texture1_value tex_blue, tex_red;
3548 if (game_mode_hoard())
3549 tex_blue = tex_red = build_texture1_value(find_goal_texture(LevelUniqueTmapInfoState, tmapinfo_flag::goal_hoard));
3550 else
3551 {
3552 tex_blue = build_texture1_value(find_goal_texture(LevelUniqueTmapInfoState, tmapinfo_flag::goal_blue));
3553 tex_red = build_texture1_value(find_goal_texture(LevelUniqueTmapInfoState, tmapinfo_flag::goal_red));
3554 }
3555 range_for (const auto &&seg, vmsegptr)
3556 {
3557 texture1_value tex;
3558 if (seg->special == segment_special::goal_blue)
3559 {
3560 tex = tex_blue;
3561 }
3562 else if (seg->special == segment_special::goal_red)
3563 {
3564 // Make both textures the same if Hoard mode
3565 tex = tex_red;
3566 }
3567 else
3568 continue;
3569 apply_segment_goal_texture(LevelUniqueTmapInfoState, seg, tex);
3570 }
3571 }
3572 #endif
3573
3574 namespace {
3575
object_allowed_in_anarchy(const object_base & objp)3576 static int object_allowed_in_anarchy(const object_base &objp)
3577 {
3578 if (objp.type == OBJ_NONE ||
3579 objp.type == OBJ_PLAYER ||
3580 objp.type == OBJ_POWERUP ||
3581 objp.type == OBJ_CNTRLCEN ||
3582 objp.type == OBJ_HOSTAGE)
3583 return 1;
3584 #if defined(DXX_BUILD_DESCENT_II)
3585 if (objp.type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::PMINE_ID)
3586 return 1;
3587 #endif
3588 return 0;
3589 }
3590
record_powerup(const vmobjptridx_t o)3591 void powerup_shuffle_state::record_powerup(const vmobjptridx_t o)
3592 {
3593 if (!seed)
3594 return;
3595 const auto id = get_powerup_id(o);
3596 switch (id)
3597 {
3598 /* record_powerup runs before object conversion or duplication,
3599 * so object types that anarchy converts still have their
3600 * original type when this switch runs. Therefore,
3601 * POW_EXTRA_LIFE and the key powerups must be handled here,
3602 * even though they are converted to other objects before play
3603 * begins. If they were not handled, no object could exchange
3604 * places with a converted object.
3605 */
3606 case POW_EXTRA_LIFE:
3607 case POW_ENERGY:
3608 case POW_SHIELD_BOOST:
3609 case POW_LASER:
3610 case POW_KEY_BLUE:
3611 case POW_KEY_RED:
3612 case POW_KEY_GOLD:
3613 case POW_MISSILE_1:
3614 case POW_MISSILE_4:
3615 case POW_QUAD_FIRE:
3616 case POW_VULCAN_WEAPON:
3617 case POW_SPREADFIRE_WEAPON:
3618 case POW_PLASMA_WEAPON:
3619 case POW_FUSION_WEAPON:
3620 case POW_PROXIMITY_WEAPON:
3621 case POW_HOMING_AMMO_1:
3622 case POW_HOMING_AMMO_4:
3623 case POW_SMARTBOMB_WEAPON:
3624 case POW_MEGA_WEAPON:
3625 case POW_VULCAN_AMMO:
3626 case POW_CLOAK:
3627 case POW_INVULNERABILITY:
3628 #if defined(DXX_BUILD_DESCENT_II)
3629 case POW_GAUSS_WEAPON:
3630 case POW_HELIX_WEAPON:
3631 case POW_PHOENIX_WEAPON:
3632 case POW_OMEGA_WEAPON:
3633
3634 case POW_SUPER_LASER:
3635 case POW_FULL_MAP:
3636 case POW_CONVERTER:
3637 case POW_AMMO_RACK:
3638 case POW_AFTERBURNER:
3639 case POW_HEADLIGHT:
3640
3641 case POW_SMISSILE1_1:
3642 case POW_SMISSILE1_4:
3643 case POW_GUIDED_MISSILE_1:
3644 case POW_GUIDED_MISSILE_4:
3645 case POW_SMART_MINE:
3646 case POW_MERCURY_MISSILE_1:
3647 case POW_MERCURY_MISSILE_4:
3648 case POW_EARTHSHAKER_MISSILE:
3649 #endif
3650 break;
3651 default:
3652 return;
3653 }
3654 if (count >= ptrs.size())
3655 return;
3656 ptrs[count++] = o;
3657 }
3658
shuffle() const3659 void powerup_shuffle_state::shuffle() const
3660 {
3661 auto &Objects = LevelUniqueObjectState.Objects;
3662 auto &vmobjptr = Objects.vmptr;
3663 if (!count)
3664 return;
3665 std::minstd_rand mr(seed);
3666 for (unsigned j = count; --j;)
3667 {
3668 const auto oi = std::uniform_int_distribution<unsigned>(0u, j)(mr);
3669 if (oi == j)
3670 /* Swapping an object with itself is a no-op. Skip the
3671 * work. Do not re-roll, both to avoid the potential for an
3672 * infinite loop on unlucky rolls and to ensure a uniform
3673 * distribution of swaps.
3674 */
3675 continue;
3676 const auto o0 = ptrs[j];
3677 const auto o1 = ptrs[oi];
3678 const auto os0 = o0->segnum;
3679 const auto os1 = o1->segnum;
3680 /* Disconnect both objects from their original segments. Swap
3681 * their positions. Link each object to the segment that the
3682 * other object previously used. This is necessary instead of
3683 * using std::swap on object::segnum, since the segment's linked
3684 * list of objects needs to be updated.
3685 */
3686 obj_unlink(vmobjptr, vmsegptr, *o0);
3687 obj_unlink(vmobjptr, vmsegptr, *o1);
3688 std::swap(o0->pos, o1->pos);
3689 obj_link_unchecked(vmobjptr, o0, vmsegptridx(os1));
3690 obj_link_unchecked(vmobjptr, o1, vmsegptridx(os0));
3691 }
3692 }
3693
multi_update_objects_for_non_cooperative()3694 void multi_update_objects_for_non_cooperative()
3695 {
3696 auto &Objects = LevelUniqueObjectState.Objects;
3697 auto &vmobjptridx = Objects.vmptridx;
3698 // Go through the object list and remove any objects not used in
3699 // 'Anarchy!' games.
3700
3701 const auto game_mode = Game_mode;
3702 /* Shuffle objects before object duplication runs. Otherwise,
3703 * duplication-eligible items would be duplicated, then scattered,
3704 * causing the original site to be a treasure trove of swapped
3705 * items. This way, duplicated items appear with their original.
3706 */
3707 powerup_shuffle_state powerup_shuffle(Netgame.ShufflePowerupSeed);
3708 range_for (const auto &&objp, vmobjptridx)
3709 {
3710 const auto obj_type = objp->type;
3711 if (obj_type == OBJ_PLAYER || obj_type == OBJ_GHOST)
3712 continue;
3713 else if (obj_type == OBJ_ROBOT && (game_mode & GM_MULTI_ROBOTS))
3714 continue;
3715 else if (obj_type == OBJ_POWERUP)
3716 {
3717 powerup_shuffle.record_powerup(objp);
3718 continue;
3719 }
3720 else if (!object_allowed_in_anarchy(objp) ) {
3721 #if defined(DXX_BUILD_DESCENT_II)
3722 // Before deleting object, if it's a robot, drop it's special powerup, if any
3723 if (obj_type == OBJ_ROBOT)
3724 if (objp->contains_count && (objp->contains_type == OBJ_POWERUP))
3725 object_create_robot_egg(objp);
3726 #endif
3727 obj_delete(LevelUniqueObjectState, Segments, objp);
3728 }
3729 }
3730 powerup_shuffle.shuffle();
3731 }
3732
3733 }
3734
3735 }
3736
3737 namespace {
3738
3739 // Returns the Player_num of Master/Host of this game
multi_who_is_master()3740 playernum_t multi_who_is_master()
3741 {
3742 return 0;
3743 }
3744
3745 }
3746
change_playernum_to(const playernum_t new_Player_num)3747 void change_playernum_to(const playernum_t new_Player_num)
3748 {
3749 if (Player_num < Players.size())
3750 {
3751 vmplayerptr(new_Player_num)->callsign = get_local_player().callsign;
3752 }
3753 Player_num = new_Player_num;
3754 }
3755
3756 namespace dsx {
3757
3758 #if defined(DXX_BUILD_DESCENT_I)
3759 static
3760 #endif
multi_all_players_alive(const fvcobjptr & vcobjptr,const partial_range_t<const player * > player_range)3761 int multi_all_players_alive(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range)
3762 {
3763 range_for (auto &plr, player_range)
3764 {
3765 const auto connected = plr.connected;
3766 if (connected == CONNECT_PLAYING)
3767 {
3768 if (vcobjptr(plr.objnum)->type == OBJ_GHOST) // player alive?
3769 return 0;
3770 }
3771 else if (connected != CONNECT_DISCONNECTED) // ... and actually playing?
3772 return 0;
3773 }
3774 return (1);
3775 }
3776
multi_common_deny_save_game(const fvcobjptr & vcobjptr,const partial_range_t<const player * > player_range)3777 const char *multi_common_deny_save_game(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range)
3778 {
3779 if (Network_status == NETSTAT_ENDLEVEL)
3780 return "Level is ending";
3781 if (!multi_all_players_alive(vcobjptr, player_range))
3782 return "All players must be alive and playing!";
3783 return deny_multi_save_game_duplicate_callsign(player_range);
3784 }
3785
multi_interactive_deny_save_game(const fvcobjptr & vcobjptr,const partial_range_t<const player * > player_range,const d_level_unique_control_center_state & LevelUniqueControlCenterState)3786 const char *multi_interactive_deny_save_game(const fvcobjptr &vcobjptr, const partial_range_t<const player *> player_range, const d_level_unique_control_center_state &LevelUniqueControlCenterState)
3787 {
3788 if (LevelUniqueControlCenterState.Control_center_destroyed)
3789 return "Countdown in progress";
3790 return multi_common_deny_save_game(vcobjptr, player_range);
3791 }
3792
multi_send_drop_weapon(const vmobjptridx_t objp,int seed)3793 void multi_send_drop_weapon(const vmobjptridx_t objp, int seed)
3794 {
3795 auto &Objects = LevelUniqueObjectState.Objects;
3796 auto &vmobjptridx = Objects.vmptridx;
3797 int count=0;
3798 int ammo_count;
3799
3800 multi_send_position(vmobjptridx(get_local_player().objnum));
3801 ammo_count = objp->ctype.powerup_info.count;
3802
3803 #if defined(DXX_BUILD_DESCENT_II)
3804 if (get_powerup_id(objp) == POW_OMEGA_WEAPON && ammo_count == F1_0)
3805 ammo_count = F1_0 - 1; //make fit in short
3806 #endif
3807
3808 Assert(ammo_count < F1_0); //make sure fits in short
3809
3810 count++;
3811 multi_command<MULTI_DROP_WEAPON> multibuf;
3812 multibuf[count++]=static_cast<char>(get_powerup_id(objp));
3813 PUT_INTEL_SHORT(&multibuf[count], objp); count += 2;
3814 PUT_INTEL_SHORT(&multibuf[count], static_cast<uint16_t>(ammo_count)); count += 2;
3815 PUT_INTEL_INT(&multibuf[count], seed);
3816 count += 4;
3817
3818 map_objnum_local_to_local(objp);
3819
3820 multi_send_data(multibuf, 2);
3821 }
3822
3823 namespace {
3824
multi_do_drop_weapon(fvmobjptr & vmobjptr,const playernum_t pnum,const uint8_t * const buf)3825 static void multi_do_drop_weapon(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
3826 {
3827 int ammo,remote_objnum,seed;
3828 const auto powerup_id = static_cast<powerup_type_t>(buf[1]);
3829 remote_objnum = GET_INTEL_SHORT(buf + 2);
3830 ammo = GET_INTEL_SHORT(buf + 4);
3831 seed = GET_INTEL_INT(buf + 6);
3832 const auto objnum = spit_powerup(Vclip, vmobjptr(vcplayerptr(pnum)->objnum), powerup_id, seed);
3833
3834 map_objnum_local_to_remote(objnum, remote_objnum, pnum);
3835
3836 if (objnum!=object_none)
3837 objnum->ctype.powerup_info.count = ammo;
3838 }
3839
3840 }
3841
3842 #if defined(DXX_BUILD_DESCENT_II)
3843 // We collected some ammo from a vulcan/gauss cannon powerup. Now we need to let everyone else know about its new ammo count.
multi_send_vulcan_weapon_ammo_adjust(const vmobjptridx_t objnum)3844 void multi_send_vulcan_weapon_ammo_adjust(const vmobjptridx_t objnum)
3845 {
3846 const auto &&[obj_owner, remote_objnum] = objnum_local_to_remote(objnum);
3847 multi_command<MULTI_VULWPN_AMMO_ADJ> multibuf;
3848 PUT_INTEL_SHORT(&multibuf[1], remote_objnum); // Map to network objnums
3849
3850 multibuf[3] = obj_owner;
3851
3852 const uint16_t ammo_count = objnum->ctype.powerup_info.count;
3853 PUT_INTEL_SHORT(&multibuf[4], ammo_count);
3854
3855 multi_send_data(multibuf, 2);
3856
3857 if (Network_send_objects && multi::dispatch->objnum_is_past(objnum))
3858 {
3859 Network_send_objnum = -1;
3860 }
3861 }
3862
3863 namespace {
3864
multi_do_vulcan_weapon_ammo_adjust(fvmobjptr & vmobjptr,const uint8_t * const buf)3865 static void multi_do_vulcan_weapon_ammo_adjust(fvmobjptr &vmobjptr, const uint8_t *const buf)
3866 {
3867 // which object to update
3868 const objnum_t objnum = GET_INTEL_SHORT(buf + 1);
3869 // which remote list is it entered in
3870 auto obj_owner = buf[3];
3871
3872 assert(objnum != object_none);
3873
3874 if (objnum < 1)
3875 return;
3876
3877 auto local_objnum = objnum_remote_to_local(objnum, obj_owner); // translate to local objnum
3878
3879 if (local_objnum == object_none)
3880 {
3881 return;
3882 }
3883
3884 const auto &&obj = vmobjptr(local_objnum);
3885 if (obj->type != OBJ_POWERUP)
3886 {
3887 return;
3888 }
3889
3890 if (Network_send_objects && multi::dispatch->objnum_is_past(local_objnum))
3891 {
3892 Network_send_objnum = -1;
3893 }
3894
3895 const auto ammo = GET_INTEL_SHORT(buf + 4);
3896 obj->ctype.powerup_info.count = ammo;
3897 }
3898
3899 struct multi_guided_info
3900 {
3901 uint8_t pnum;
3902 uint8_t release;
3903 shortpos sp;
3904 };
3905
3906 DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_GUIDED, multi_guided_info, g, (g.pnum, g.release, g.sp));
3907
3908 }
3909
multi_send_guided_info(const object_base & miss,const char done)3910 void multi_send_guided_info(const object_base &miss, const char done)
3911 {
3912 multi_guided_info gi;
3913 gi.pnum = static_cast<uint8_t>(Player_num);
3914 gi.release = done;
3915 create_shortpos_little(LevelSharedSegmentState, gi.sp, miss);
3916 multi_serialize_write(0, gi);
3917 }
3918
3919 namespace {
3920
multi_do_guided(d_level_unique_object_state & LevelUniqueObjectState,const playernum_t pnum,const uint8_t * const buf)3921 static void multi_do_guided(d_level_unique_object_state &LevelUniqueObjectState, const playernum_t pnum, const uint8_t *const buf)
3922 {
3923 multi_guided_info b;
3924 multi_serialize_read(buf, b);
3925 auto &Objects = LevelUniqueObjectState.Objects;
3926 auto &vmobjptr = Objects.vmptr;
3927
3928 if (b.release)
3929 {
3930 release_guided_missile(LevelUniqueObjectState, pnum);
3931 return;
3932 }
3933
3934 const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptridx, pnum);
3935 if (gimobj == nullptr)
3936 return;
3937 const vmobjptridx_t guided_missile = gimobj;
3938 extract_shortpos_little(guided_missile, &b.sp);
3939 update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, guided_missile);
3940 }
3941
3942 }
3943
multi_send_stolen_items()3944 void multi_send_stolen_items ()
3945 {
3946 multi_command<MULTI_STOLEN_ITEMS> multibuf;
3947 auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
3948 std::copy(Stolen_items.begin(), Stolen_items.end(), std::next(multibuf.begin()));
3949 multi_send_data(multibuf, 2);
3950 }
3951
3952 namespace {
3953
multi_do_stolen_items(const uint8_t * const buf)3954 static void multi_do_stolen_items(const uint8_t *const buf)
3955 {
3956 auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
3957 std::copy_n(buf + 1, Stolen_items.size(), Stolen_items.begin());
3958 }
3959
3960 }
3961
multi_send_wall_status_specific(const playernum_t pnum,wallnum_t wallnum,uint8_t type,const wall_flags flags,const wall_state state)3962 void multi_send_wall_status_specific(const playernum_t pnum, wallnum_t wallnum, uint8_t type, const wall_flags flags, const wall_state state)
3963 {
3964 // Send wall states a specific rejoining player
3965
3966 int count=0;
3967
3968 Assert (Game_mode & GM_NETWORK);
3969 //Assert (pnum>-1 && pnum<N_players);
3970
3971 count++;
3972 multi_command<MULTI_WALL_STATUS> multibuf;
3973 PUT_INTEL_SHORT(&multibuf[count], static_cast<uint16_t>(wallnum));
3974 count+=2;
3975 multibuf[count]=type; count++;
3976 multibuf[count] = underlying_value(flags); count++;
3977 multibuf[count] = underlying_value(state); count++;
3978
3979 multi_send_data_direct(multibuf, pnum, 2);
3980 }
3981
3982 namespace {
3983
multi_do_wall_status(fvmwallptr & vmwallptr,const uint8_t * const buf)3984 static void multi_do_wall_status(fvmwallptr &vmwallptr, const uint8_t *const buf)
3985 {
3986 ubyte flag,type,state;
3987
3988 const wallnum_t wallnum{GET_INTEL_SHORT(buf + 1)};
3989 type=buf[3];
3990 flag=buf[4];
3991 state=buf[5];
3992
3993 auto &w = *vmwallptr(wallnum);
3994 w.type = type;
3995 w.flags = wall_flags{flag};
3996 w.state = wall_state{state};
3997
3998 if (w.type == WALL_OPEN)
3999 {
4000 digi_kill_sound_linked_to_segment(w.segnum, w.sidenum, SOUND_FORCEFIELD_HUM);
4001 //digi_kill_sound_linked_to_segment(csegp-Segments,cside,SOUND_FORCEFIELD_HUM);
4002 }
4003 }
4004
4005 }
4006 #endif
4007
4008 }
4009
multi_send_kill_goal_counts()4010 void multi_send_kill_goal_counts()
4011 {
4012 auto &Objects = LevelUniqueObjectState.Objects;
4013 auto &vcobjptr = Objects.vcptr;
4014 int count=1;
4015
4016 multi_command<MULTI_KILLGOALS> multibuf;
4017 range_for (auto &i, Players)
4018 {
4019 auto &obj = *vcobjptr(i.objnum);
4020 auto &player_info = obj.ctype.player_info;
4021 multibuf[count] = player_info.KillGoalCount;
4022 count++;
4023 }
4024 multi_send_data(multibuf, 2);
4025 }
4026
4027 namespace {
4028
multi_do_kill_goal_counts(fvmobjptr & vmobjptr,const uint8_t * const buf)4029 static void multi_do_kill_goal_counts(fvmobjptr &vmobjptr, const uint8_t *const buf)
4030 {
4031 int count=1;
4032
4033 range_for (auto &i, Players)
4034 {
4035 auto &obj = *vmobjptr(i.objnum);
4036 auto &player_info = obj.ctype.player_info;
4037 player_info.KillGoalCount = buf[count];
4038 count++;
4039 }
4040 }
4041
multi_send_heartbeat()4042 void multi_send_heartbeat ()
4043 {
4044 if (!Netgame.PlayTimeAllowed.count())
4045 return;
4046
4047 multi_command<MULTI_HEARTBEAT> multibuf;
4048 PUT_INTEL_INT(&multibuf[1], ThisLevelTime.count());
4049 multi_send_data(multibuf, 0);
4050 }
4051
multi_do_heartbeat(const ubyte * buf)4052 static void multi_do_heartbeat (const ubyte *buf)
4053 {
4054 fix num;
4055
4056 num = GET_INTEL_INT(buf + 1);
4057
4058 ThisLevelTime = d_time_fix(num);
4059 }
4060
4061 }
4062
multi_check_for_killgoal_winner()4063 void multi_check_for_killgoal_winner ()
4064 {
4065 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
4066 auto &Objects = LevelUniqueObjectState.Objects;
4067 auto &vcobjptr = Objects.vcptr;
4068 if (LevelUniqueControlCenterState.Control_center_destroyed)
4069 return;
4070
4071 /* For historical compatibility, this routine has some quirks with
4072 * scoring:
4073 * - When two or more players have the same number of kills, this
4074 * routine always chooses the lowest index player. No opportunity
4075 * is provided for the players to score a tie-breaking kill, nor
4076 * is any other property (such as remaining shields) considered.
4077 * Historical versions had additional quirks relating to
4078 * zero/negative kills, but those quirks have been removed.
4079 */
4080 const auto &local_player = get_local_player();
4081 const player *bestplr = nullptr;
4082 int highest_kill_goal_count = 0;
4083 range_for (auto &i, partial_const_range(Players, N_players))
4084 {
4085 auto &obj = *vcobjptr(i.objnum);
4086 auto &player_info = obj.ctype.player_info;
4087 const auto KillGoalCount = player_info.KillGoalCount;
4088 if (highest_kill_goal_count < KillGoalCount)
4089 {
4090 highest_kill_goal_count = KillGoalCount;
4091 bestplr = &i;
4092 }
4093 }
4094 if (!bestplr)
4095 {
4096 /* No player has at least one kill */
4097 HUD_init_message_literal(HM_MULTI, "No one has scored any kills!");
4098 }
4099 else if (bestplr == &local_player)
4100 {
4101 HUD_init_message(HM_MULTI, "You have the best score at %d kills!", highest_kill_goal_count);
4102 }
4103 else
4104 HUD_init_message(HM_MULTI, "%s has the best score with %d kills!", static_cast<const char *>(bestplr->callsign), highest_kill_goal_count);
4105 net_destroy_controlcen(Objects);
4106 }
4107
4108 namespace dsx {
4109
4110 #if defined(DXX_BUILD_DESCENT_II)
4111 // Sync our seismic time with other players
multi_send_seismic(fix duration)4112 void multi_send_seismic(fix duration)
4113 {
4114 int count=1;
4115 multi_command<MULTI_SEISMIC> multibuf;
4116 PUT_INTEL_INT(&multibuf[count], duration); count += sizeof(duration);
4117 multi_send_data(multibuf, 2);
4118 }
4119
4120 namespace {
4121
multi_do_seismic(const ubyte * buf)4122 static void multi_do_seismic (const ubyte *buf)
4123 {
4124 const fix duration = GET_INTEL_INT(&buf[1]);
4125 LevelUniqueSeismicState.Seismic_disturbance_end_time = GameTime64 + duration;
4126 digi_play_sample (SOUND_SEISMIC_DISTURBANCE_START, F1_0);
4127 }
4128
4129 }
4130
multi_send_light_specific(const playernum_t pnum,const vcsegptridx_t segnum,const uint8_t val)4131 void multi_send_light_specific (const playernum_t pnum, const vcsegptridx_t segnum, const uint8_t val)
4132 {
4133 int count=1;
4134
4135 Assert (Game_mode & GM_NETWORK);
4136 // Assert (pnum>-1 && pnum<N_players);
4137
4138 multi_command<MULTI_LIGHT> multibuf;
4139 PUT_INTEL_SHORT(&multibuf[count], segnum);
4140 count += sizeof(uint16_t);
4141 multibuf[count] = val; count++;
4142
4143 range_for (auto &i, segnum->unique_segment::sides)
4144 {
4145 PUT_INTEL_SHORT(&multibuf[count], static_cast<uint16_t>(i.tmap_num2));
4146 count+=2;
4147 }
4148 multi_send_data_direct(multibuf, pnum, 2);
4149 }
4150
4151 namespace {
4152
multi_do_light(const ubyte * buf)4153 static void multi_do_light (const ubyte *buf)
4154 {
4155 int i;
4156 const auto sides = buf[3];
4157
4158 const segnum_t seg = GET_INTEL_SHORT(&buf[1]);
4159 const auto &&usegp = vmsegptridx.check_untrusted(seg);
4160 if (!usegp)
4161 return;
4162 const auto &&segp = *usegp;
4163 auto &side_array = segp->unique_segment::sides;
4164 for (i=0;i<6;i++)
4165 {
4166 if ((sides & (1<<i)))
4167 {
4168 auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
4169 subtract_light(LevelSharedDestructibleLightState, segp, i);
4170 const auto tmap_num2 = texture2_value{GET_INTEL_SHORT(&buf[4 + (2 * i)])};
4171 if (get_texture_index(tmap_num2) >= Textures.size())
4172 continue;
4173 side_array[i].tmap_num2 = tmap_num2;
4174 }
4175 }
4176 }
4177
multi_do_flags(fvmobjptr & vmobjptr,const playernum_t pnum,const uint8_t * const buf)4178 static void multi_do_flags(fvmobjptr &vmobjptr, const playernum_t pnum, const uint8_t *const buf)
4179 {
4180 uint flags;
4181
4182 flags = GET_INTEL_INT(buf + 2);
4183 if (pnum!=Player_num)
4184 vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags = player_flags(flags);
4185 }
4186
4187 }
4188
multi_send_flags(const playernum_t pnum)4189 void multi_send_flags (const playernum_t pnum)
4190 {
4191 auto &Objects = LevelUniqueObjectState.Objects;
4192 auto &vmobjptr = Objects.vmptr;
4193 multi_command<MULTI_FLAGS> multibuf;
4194 multibuf[1]=pnum;
4195 PUT_INTEL_INT(&multibuf[2], vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags.get_player_flags());
4196
4197 multi_send_data(multibuf, 2);
4198 }
4199
multi_send_drop_blobs(const playernum_t pnum)4200 void multi_send_drop_blobs (const playernum_t pnum)
4201 {
4202 multi_command<MULTI_DROP_BLOB> multibuf;
4203 multibuf[1]=pnum;
4204
4205 multi_send_data(multibuf, 0);
4206 }
4207
4208 namespace {
4209
multi_do_drop_blob(fvmobjptr & vmobjptr,const playernum_t pnum)4210 static void multi_do_drop_blob(fvmobjptr &vmobjptr, const playernum_t pnum)
4211 {
4212 drop_afterburner_blobs (vmobjptr(vcplayerptr(pnum)->objnum), 2, i2f(5) / 2, -1);
4213 }
4214
4215 }
4216
multi_send_sound_function(char whichfunc,char sound)4217 void multi_send_sound_function (char whichfunc, char sound)
4218 {
4219 int count=0;
4220
4221 count++;
4222 multi_command<MULTI_SOUND_FUNCTION> multibuf;
4223 multibuf[1]=Player_num; count++;
4224 multibuf[2]=whichfunc; count++;
4225 multibuf[3] = sound; count++; // this would probably work on the PC as well. Jason?
4226 multi_send_data(multibuf, 2);
4227 }
4228
4229 #define AFTERBURNER_LOOP_START 20098
4230 #define AFTERBURNER_LOOP_END 25776
4231
4232 namespace {
4233
multi_do_sound_function(const playernum_t pnum,const ubyte * buf)4234 static void multi_do_sound_function (const playernum_t pnum, const ubyte *buf)
4235 {
4236 auto &Objects = LevelUniqueObjectState.Objects;
4237 auto &vcobjptridx = Objects.vcptridx;
4238 // for afterburner
4239
4240 char whichfunc;
4241 int sound;
4242
4243 if (get_local_player().connected!=CONNECT_PLAYING)
4244 return;
4245
4246 whichfunc=buf[2];
4247 sound=buf[3];
4248
4249 const auto plobj = vcobjptridx(vcplayerptr(pnum)->objnum);
4250 if (whichfunc==0)
4251 digi_kill_sound_linked_to_object(plobj);
4252 else if (whichfunc==3)
4253 digi_link_sound_to_object3(sound, plobj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, AFTERBURNER_LOOP_START, AFTERBURNER_LOOP_END);
4254 }
4255
4256 }
4257
multi_send_capture_bonus(const playernum_t pnum)4258 void multi_send_capture_bonus (const playernum_t pnum)
4259 {
4260 multi_command<MULTI_CAPTURE_BONUS> multibuf;
4261 Assert (game_mode_capture_flag());
4262
4263 multibuf[1]=pnum;
4264
4265 multi_send_data(multibuf, 2);
4266 multi_do_capture_bonus (pnum);
4267 }
4268
multi_send_orb_bonus(const playernum_t pnum,const uint8_t hoard_orbs)4269 void multi_send_orb_bonus (const playernum_t pnum, const uint8_t hoard_orbs)
4270 {
4271 multi_command<MULTI_ORB_BONUS> multibuf;
4272 Assert (game_mode_hoard());
4273
4274 multibuf[1]=pnum;
4275 multibuf[2] = hoard_orbs;
4276
4277 multi_send_data(multibuf, 2);
4278 multi_do_orb_bonus (pnum, multibuf.data());
4279 }
4280
4281 namespace {
4282
multi_do_capture_bonus(const playernum_t pnum)4283 void multi_do_capture_bonus(const playernum_t pnum)
4284 {
4285 auto &Objects = LevelUniqueObjectState.Objects;
4286 auto &vmobjptr = Objects.vmptr;
4287 // Figure out the results of a network kills and add it to the
4288 // appropriate player's tally.
4289
4290 int TheGoal;
4291
4292 if (pnum==Player_num)
4293 HUD_init_message_literal(HM_MULTI, "You have Scored!");
4294 else
4295 HUD_init_message(HM_MULTI, "%s has Scored!", static_cast<const char *>(vcplayerptr(pnum)->callsign));
4296
4297 digi_play_sample(pnum == Player_num
4298 ? SOUND_HUD_YOU_GOT_GOAL
4299 : (get_team(pnum) == TEAM_RED
4300 ? SOUND_HUD_RED_GOT_GOAL
4301 : SOUND_HUD_BLUE_GOT_GOAL
4302 ), F1_0*2);
4303
4304
4305 team_kills[get_team(pnum)] += 5;
4306 auto &plr = *vcplayerptr(pnum);
4307 auto &player_info = vmobjptr(plr.objnum)->ctype.player_info;
4308 player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; // Clear capture flag
4309 player_info.net_kills_total += 5;
4310 player_info.KillGoalCount += 5;
4311
4312 if (Netgame.KillGoal>0)
4313 {
4314 TheGoal=Netgame.KillGoal*5;
4315
4316 if (player_info.KillGoalCount >= TheGoal)
4317 {
4318 if (pnum==Player_num)
4319 {
4320 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
4321 get_local_plrobj().shields = i2f(200);
4322 }
4323 else
4324 HUD_init_message(HM_MULTI, "%s has reached the kill goal!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
4325 net_destroy_controlcen(Objects);
4326 }
4327 }
4328
4329 multi_sort_kill_list();
4330 multi_show_player_list();
4331 }
4332
GetOrbBonus(char num)4333 static int GetOrbBonus (char num)
4334 {
4335 int bonus;
4336
4337 bonus=num*(num+1)/2;
4338 return (bonus);
4339 }
4340
multi_do_orb_bonus(const playernum_t pnum,const uint8_t * const buf)4341 void multi_do_orb_bonus(const playernum_t pnum, const uint8_t *const buf)
4342 {
4343 auto &Objects = LevelUniqueObjectState.Objects;
4344 auto &vmobjptr = Objects.vmptr;
4345 // Figure out the results of a network kills and add it to the
4346 // appropriate player's tally.
4347
4348 int TheGoal;
4349 int bonus=GetOrbBonus (buf[2]);
4350
4351 if (pnum==Player_num)
4352 HUD_init_message(HM_MULTI, "You have scored %d points!",bonus);
4353 else
4354 HUD_init_message(HM_MULTI, "%s has scored with %d orbs!",static_cast<const char *>(vcplayerptr(pnum)->callsign), buf[2]);
4355
4356 if (pnum==Player_num)
4357 digi_start_sound_queued (SOUND_HUD_YOU_GOT_GOAL,F1_0*2);
4358 else
4359 digi_play_sample((Game_mode & GM_TEAM)
4360 ? (get_team(pnum) == TEAM_RED
4361 ? SOUND_HUD_RED_GOT_GOAL
4362 : SOUND_HUD_BLUE_GOT_GOAL
4363 ) : SOUND_OPPONENT_HAS_SCORED, F1_0*2);
4364
4365 if (bonus > hoard_highest_record_stats.points)
4366 {
4367 hoard_highest_record_stats.player = pnum;
4368 hoard_highest_record_stats.points = bonus;
4369 if (pnum==Player_num)
4370 HUD_init_message(HM_MULTI, "You have the record with %d points!",bonus);
4371 else
4372 HUD_init_message(HM_MULTI, "%s has the record with %d points!",static_cast<const char *>(vcplayerptr(pnum)->callsign),bonus);
4373 digi_play_sample (SOUND_BUDDY_MET_GOAL,F1_0*2);
4374 }
4375
4376
4377 team_kills[get_team(pnum)] += bonus;
4378 auto &plr = *vcplayerptr(pnum);
4379 auto &player_info = vmobjptr(plr.objnum)->ctype.player_info;
4380 player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; // Clear orb flag
4381 player_info.net_kills_total += bonus;
4382 player_info.KillGoalCount += bonus;
4383
4384 team_kills[get_team(pnum)]%=1000;
4385 player_info.net_kills_total%=1000;
4386 player_info.KillGoalCount %= 1000;
4387
4388 if (Netgame.KillGoal>0)
4389 {
4390 TheGoal=Netgame.KillGoal*5;
4391
4392 if (player_info.KillGoalCount >= TheGoal)
4393 {
4394 if (pnum==Player_num)
4395 {
4396 HUD_init_message_literal(HM_MULTI, "You reached the kill goal!");
4397 get_local_plrobj().shields = i2f(200);
4398 }
4399 else
4400 HUD_init_message(HM_MULTI, "%s has reached the kill goal!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
4401 net_destroy_controlcen(Objects);
4402 }
4403 }
4404 multi_sort_kill_list();
4405 multi_show_player_list();
4406 }
4407
4408 }
4409
multi_send_got_flag(const playernum_t pnum)4410 void multi_send_got_flag (const playernum_t pnum)
4411 {
4412 multi_command<MULTI_GOT_FLAG> multibuf;
4413 multibuf[1]=pnum;
4414
4415 digi_start_sound_queued (SOUND_HUD_YOU_GOT_FLAG,F1_0*2);
4416
4417 multi_send_data(multibuf, 2);
4418 multi_send_flags (Player_num);
4419 }
4420
multi_send_got_orb(const playernum_t pnum)4421 void multi_send_got_orb (const playernum_t pnum)
4422 {
4423 multi_command<MULTI_GOT_ORB> multibuf;
4424 multibuf[1]=pnum;
4425
4426 digi_play_sample (SOUND_YOU_GOT_ORB,F1_0*2);
4427
4428 multi_send_data(multibuf, 2);
4429 multi_send_flags (Player_num);
4430 }
4431
4432 namespace {
4433
multi_do_got_flag(const playernum_t pnum)4434 static void multi_do_got_flag (const playernum_t pnum)
4435 {
4436 auto &Objects = LevelUniqueObjectState.Objects;
4437 auto &vmobjptr = Objects.vmptr;
4438 digi_start_sound_queued(pnum == Player_num
4439 ? SOUND_HUD_YOU_GOT_FLAG
4440 : (get_team(pnum) == TEAM_RED
4441 ? SOUND_HUD_RED_GOT_FLAG
4442 : SOUND_HUD_BLUE_GOT_FLAG
4443 ), F1_0*2);
4444 vmobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
4445 HUD_init_message(HM_MULTI, "%s picked up a flag!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
4446 }
4447
multi_do_got_orb(const playernum_t pnum)4448 static void multi_do_got_orb (const playernum_t pnum)
4449 {
4450 auto &Objects = LevelUniqueObjectState.Objects;
4451 auto &vmobjptr = Objects.vmptr;
4452 Assert (game_mode_hoard());
4453
4454 digi_play_sample((Game_mode & GM_TEAM) && get_team(pnum) == get_team(Player_num)
4455 ? SOUND_FRIEND_GOT_ORB
4456 : SOUND_OPPONENT_GOT_ORB, F1_0*2);
4457
4458 const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
4459 objp->ctype.player_info.powerup_flags |= PLAYER_FLAGS_FLAG;
4460 HUD_init_message(HM_MULTI, "%s picked up an orb!",static_cast<const char *>(vcplayerptr(pnum)->callsign));
4461 }
4462
DropOrb()4463 static void DropOrb ()
4464 {
4465 auto &Objects = LevelUniqueObjectState.Objects;
4466 auto &vmobjptr = Objects.vmptr;
4467 int seed;
4468
4469 if (!game_mode_hoard())
4470 Int3(); // How did we get here? Get Leighton!
4471
4472 auto &player_info = get_local_plrobj().ctype.player_info;
4473 auto &proximity = player_info.hoard.orbs;
4474 if (!proximity)
4475 {
4476 HUD_init_message_literal(HM_MULTI, "No orbs to drop!");
4477 return;
4478 }
4479
4480 seed = d_rand();
4481
4482 const auto &&objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), POW_HOARD_ORB, seed);
4483
4484 if (objnum == object_none)
4485 return;
4486
4487 HUD_init_message_literal(HM_MULTI, "Orb dropped!");
4488 digi_play_sample (SOUND_DROP_WEAPON,F1_0);
4489
4490 multi_send_drop_flag(objnum, seed);
4491 -- proximity;
4492
4493 // If empty, tell everyone to stop drawing the box around me
4494 if (!proximity)
4495 {
4496 player_info.powerup_flags &=~(PLAYER_FLAGS_FLAG);
4497 multi_send_flags (Player_num);
4498 }
4499 }
4500
4501 }
4502
DropFlag()4503 void DropFlag ()
4504 {
4505 auto &Objects = LevelUniqueObjectState.Objects;
4506 auto &vmobjptr = Objects.vmptr;
4507 int seed;
4508
4509 if (!game_mode_capture_flag() && !game_mode_hoard())
4510 return;
4511 if (game_mode_hoard())
4512 {
4513 DropOrb();
4514 return;
4515 }
4516
4517 auto &player_info = get_local_plrobj().ctype.player_info;
4518 if (!(player_info.powerup_flags & PLAYER_FLAGS_FLAG))
4519 {
4520 HUD_init_message_literal(HM_MULTI, "No flag to drop!");
4521 return;
4522 }
4523 seed = d_rand();
4524 const auto &&objnum = spit_powerup(Vclip, vmobjptr(ConsoleObject), get_team(Player_num) == TEAM_RED ? POW_FLAG_BLUE : POW_FLAG_RED, seed);
4525 if (objnum == object_none)
4526 {
4527 HUD_init_message_literal(HM_MULTI, "Failed to drop flag!");
4528 return;
4529 }
4530
4531 HUD_init_message_literal(HM_MULTI, "Flag dropped!");
4532 digi_play_sample (SOUND_DROP_WEAPON,F1_0);
4533
4534 if (game_mode_capture_flag())
4535 multi_send_drop_flag(objnum,seed);
4536
4537 player_info.powerup_flags &=~(PLAYER_FLAGS_FLAG);
4538 }
4539
4540 namespace {
4541
multi_send_drop_flag(const vmobjptridx_t objp,int seed)4542 void multi_send_drop_flag(const vmobjptridx_t objp, int seed)
4543 {
4544 multi_command<MULTI_DROP_FLAG> multibuf;
4545 int count=0;
4546 count++;
4547 multibuf[count++]=static_cast<char>(get_powerup_id(objp));
4548
4549 PUT_INTEL_SHORT(&multibuf[count], objp.get_unchecked_index());
4550 count += 2;
4551 PUT_INTEL_INT(&multibuf[count], seed);
4552
4553 map_objnum_local_to_local(objp);
4554
4555 multi_send_data(multibuf, 2);
4556 }
4557
multi_do_drop_flag(const playernum_t pnum,const ubyte * buf)4558 static void multi_do_drop_flag (const playernum_t pnum, const ubyte *buf)
4559 {
4560 auto &Objects = LevelUniqueObjectState.Objects;
4561 auto &vmobjptr = Objects.vmptr;
4562 int remote_objnum,seed;
4563 const auto powerup_id = static_cast<powerup_type_t>(buf[1]);
4564 remote_objnum = GET_INTEL_SHORT(buf + 2);
4565 seed = GET_INTEL_INT(buf + 6);
4566
4567 const auto &&objp = vmobjptr(vcplayerptr(pnum)->objnum);
4568
4569 imobjidx_t objnum = spit_powerup(Vclip, objp, powerup_id, seed);
4570
4571 map_objnum_local_to_remote(objnum, remote_objnum, pnum);
4572 if (!game_mode_hoard())
4573 objp->ctype.player_info.powerup_flags &= ~(PLAYER_FLAGS_FLAG);
4574 }
4575
4576 }
4577 #endif
4578
multi_powerup_is_allowed(const unsigned id,const unsigned AllowedItems)4579 uint_fast32_t multi_powerup_is_allowed(const unsigned id, const unsigned AllowedItems)
4580 {
4581 return multi_powerup_is_allowed(id, AllowedItems, map_granted_flags_to_netflag(Netgame.SpawnGrantedItems));
4582 }
4583
multi_powerup_is_allowed(const unsigned id,const unsigned BaseAllowedItems,const unsigned SpawnGrantedItems)4584 uint_fast32_t multi_powerup_is_allowed(const unsigned id, const unsigned BaseAllowedItems, const unsigned SpawnGrantedItems)
4585 {
4586 const auto AllowedItems = BaseAllowedItems & ~SpawnGrantedItems;
4587 switch (id)
4588 {
4589 case POW_KEY_BLUE:
4590 case POW_KEY_GOLD:
4591 case POW_KEY_RED:
4592 return Game_mode & GM_MULTI_COOP;
4593 case POW_INVULNERABILITY:
4594 return AllowedItems & NETFLAG_DOINVUL;
4595 case POW_CLOAK:
4596 return AllowedItems & NETFLAG_DOCLOAK;
4597 case POW_LASER:
4598 if (map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) >= MAX_LASER_LEVEL)
4599 return 0;
4600 return AllowedItems & NETFLAG_DOLASER;
4601 case POW_QUAD_FIRE:
4602 return AllowedItems & NETFLAG_DOQUAD;
4603 case POW_VULCAN_WEAPON:
4604 return AllowedItems & NETFLAG_DOVULCAN;
4605 case POW_SPREADFIRE_WEAPON:
4606 return AllowedItems & NETFLAG_DOSPREAD;
4607 case POW_PLASMA_WEAPON:
4608 return AllowedItems & NETFLAG_DOPLASMA;
4609 case POW_FUSION_WEAPON:
4610 return AllowedItems & NETFLAG_DOFUSION;
4611 case POW_HOMING_AMMO_1:
4612 case POW_HOMING_AMMO_4:
4613 return AllowedItems & NETFLAG_DOHOMING;
4614 case POW_PROXIMITY_WEAPON:
4615 return AllowedItems & NETFLAG_DOPROXIM;
4616 case POW_SMARTBOMB_WEAPON:
4617 return AllowedItems & NETFLAG_DOSMART;
4618 case POW_MEGA_WEAPON:
4619 return AllowedItems & NETFLAG_DOMEGA;
4620 case POW_VULCAN_AMMO:
4621 #if defined(DXX_BUILD_DESCENT_I)
4622 return BaseAllowedItems & NETFLAG_DOVULCAN;
4623 #elif defined(DXX_BUILD_DESCENT_II)
4624 return BaseAllowedItems & (NETFLAG_DOVULCAN | NETFLAG_DOGAUSS);
4625 #endif
4626 #if defined(DXX_BUILD_DESCENT_II)
4627 case POW_SUPER_LASER:
4628 if (map_granted_flags_to_laser_level(Netgame.SpawnGrantedItems) >= MAX_SUPER_LASER_LEVEL)
4629 return 0;
4630 return AllowedItems & NETFLAG_DOSUPERLASER;
4631 case POW_GAUSS_WEAPON:
4632 return AllowedItems & NETFLAG_DOGAUSS;
4633 case POW_HELIX_WEAPON:
4634 return AllowedItems & NETFLAG_DOHELIX;
4635 case POW_PHOENIX_WEAPON:
4636 return AllowedItems & NETFLAG_DOPHOENIX;
4637 case POW_OMEGA_WEAPON:
4638 return AllowedItems & NETFLAG_DOOMEGA;
4639 case POW_SMISSILE1_1:
4640 case POW_SMISSILE1_4:
4641 return AllowedItems & NETFLAG_DOFLASH;
4642 case POW_GUIDED_MISSILE_1:
4643 case POW_GUIDED_MISSILE_4:
4644 return AllowedItems & NETFLAG_DOGUIDED;
4645 case POW_SMART_MINE:
4646 return AllowedItems & NETFLAG_DOSMARTMINE;
4647 case POW_MERCURY_MISSILE_1:
4648 case POW_MERCURY_MISSILE_4:
4649 return AllowedItems & NETFLAG_DOMERCURY;
4650 case POW_EARTHSHAKER_MISSILE:
4651 return AllowedItems & NETFLAG_DOSHAKER;
4652 case POW_AFTERBURNER:
4653 return AllowedItems & NETFLAG_DOAFTERBURNER;
4654 case POW_CONVERTER:
4655 return AllowedItems & NETFLAG_DOCONVERTER;
4656 case POW_AMMO_RACK:
4657 return AllowedItems & NETFLAG_DOAMMORACK;
4658 case POW_HEADLIGHT:
4659 return AllowedItems & NETFLAG_DOHEADLIGHT;
4660 case POW_FLAG_BLUE:
4661 case POW_FLAG_RED:
4662 return game_mode_capture_flag();
4663 #endif
4664 default:
4665 return 1;
4666 }
4667 }
4668
4669 #if defined(DXX_BUILD_DESCENT_II)
multi_send_finish_game()4670 void multi_send_finish_game ()
4671 {
4672 multi_command<MULTI_FINISH_GAME> multibuf;
4673 multibuf[1]=Player_num;
4674
4675 multi_send_data(multibuf, 2);
4676 }
4677
4678 namespace {
4679
multi_do_finish_game(const uint8_t * const buf)4680 static void multi_do_finish_game(const uint8_t *const buf)
4681 {
4682 if (buf[0]!=MULTI_FINISH_GAME)
4683 return;
4684
4685 if (Current_level_num != Current_mission->last_level)
4686 return;
4687
4688 do_final_boss_hacks();
4689 }
4690
4691 }
4692
multi_send_trigger_specific(const playernum_t pnum,const trgnum_t trig)4693 void multi_send_trigger_specific(const playernum_t pnum, const trgnum_t trig)
4694 {
4695 multi_command<MULTI_START_TRIGGER> multibuf;
4696 static_assert(sizeof(trgnum_t) == sizeof(uint8_t), "trigger number could be truncated");
4697 multibuf[1] = static_cast<uint8_t>(trig);
4698
4699 multi_send_data_direct(multibuf, pnum, 2);
4700 }
4701
4702 namespace {
4703
multi_do_start_trigger(const uint8_t * const buf)4704 static void multi_do_start_trigger(const uint8_t *const buf)
4705 {
4706 auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
4707 auto &vmtrgptr = Triggers.vmptr;
4708 vmtrgptr(static_cast<trgnum_t>(buf[1]))->flags |= trigger_behavior_flags::disabled;
4709 }
4710
4711 }
4712 #endif
4713
4714 namespace {
multi_adjust_lifetime_ranking(int & k,const int count)4715 static void multi_adjust_lifetime_ranking(int &k, const int count)
4716 {
4717 if (!(Game_mode & GM_NETWORK))
4718 return;
4719
4720 const auto oldrank = GetMyNetRanking();
4721 k += count;
4722 const auto newrank = GetMyNetRanking();
4723 if (oldrank != newrank)
4724 {
4725 Netgame.players[Player_num].rank = newrank;
4726 multi_send_ranking(newrank);
4727 if (!PlayerCfg.NoRankings)
4728 {
4729 HUD_init_message(HM_MULTI, "You have been %smoted to %s!", newrank > oldrank ? "pro" : "de", RankStrings[newrank]);
4730 #if defined(DXX_BUILD_DESCENT_I)
4731 digi_play_sample (SOUND_CONTROL_CENTER_WARNING_SIREN,F1_0*2);
4732 #elif defined(DXX_BUILD_DESCENT_II)
4733 digi_play_sample (SOUND_BUDDY_MET_GOAL,F1_0*2);
4734 #endif
4735 }
4736 }
4737 }
4738 }
4739
4740 #if !(!defined(RELEASE) && defined(DXX_BUILD_DESCENT_II))
4741 namespace {
4742 #endif
multi_add_lifetime_kills(const int count)4743 void multi_add_lifetime_kills(const int count)
4744 {
4745 // This function adds a kill to lifetime stats of this player, and possibly
4746 // gives a promotion. If so, it will tell everyone else
4747
4748 multi_adjust_lifetime_ranking(PlayerCfg.NetlifeKills, count);
4749 }
4750 #if !(!defined(RELEASE) && defined(DXX_BUILD_DESCENT_II))
4751 }
4752 #endif
4753
4754 namespace {
multi_add_lifetime_killed()4755 void multi_add_lifetime_killed ()
4756 {
4757 // This function adds a "killed" to lifetime stats of this player, and possibly
4758 // gives a demotion. If so, it will tell everyone else
4759
4760 if (Game_mode & GM_MULTI_COOP)
4761 return;
4762
4763 multi_adjust_lifetime_ranking(PlayerCfg.NetlifeKilled, 1);
4764 }
4765 }
4766 }
4767
4768 namespace {
4769
multi_send_ranking(const netplayer_info::player_rank newrank)4770 void multi_send_ranking (const netplayer_info::player_rank newrank)
4771 {
4772 multi_command<MULTI_RANK> multibuf;
4773 multibuf[1]=static_cast<char>(Player_num);
4774 multibuf[2] = underlying_value(newrank);
4775
4776 multi_send_data(multibuf, 2);
4777 }
4778
multi_do_ranking(const playernum_t pnum,const ubyte * buf)4779 static void multi_do_ranking (const playernum_t pnum, const ubyte *buf)
4780 {
4781 const auto rank = build_rank_from_untrusted(buf[2]);
4782 if (rank == netplayer_info::player_rank::None)
4783 return;
4784 if (!RankStrings.valid_index(rank))
4785 return;
4786
4787 auto &netrank = Netgame.players[pnum].rank;
4788 if (netrank == rank)
4789 return;
4790 const auto rankstr = (netrank < rank) ? "pro" : "de";
4791 netrank = rank;
4792
4793 if (!PlayerCfg.NoRankings)
4794 HUD_init_message(HM_MULTI, "%s has been %smoted to %s!",static_cast<const char *>(vcplayerptr(pnum)->callsign), rankstr, RankStrings[rank]);
4795 }
4796
4797 }
4798
4799 namespace dcx {
4800
4801 // Decide if fire from "killer" is friendly. If yes return 1 (no harm to me) otherwise 0 (damage me)
multi_maybe_disable_friendly_fire(const object_base * const killer)4802 int multi_maybe_disable_friendly_fire(const object_base *const killer)
4803 {
4804 if (!(Game_mode & GM_NETWORK)) // no Multiplayer game -> always harm me!
4805 return 0;
4806 if (!Netgame.NoFriendlyFire) // friendly fire is activated -> harm me!
4807 return 0;
4808 if (!killer) // no actual killer -> harm me!
4809 return 0;
4810 if (killer->type != OBJ_PLAYER) // not a player -> harm me!
4811 return 0;
4812 if (auto is_coop = Game_mode & GM_MULTI_COOP) // coop mode -> don't harm me!
4813 return is_coop;
4814 else if (Game_mode & GM_TEAM) // team mode - find out if killer is in my team
4815 {
4816 if (get_team(Player_num) == get_team(get_player_id(*killer))) // in my team -> don't harm me!
4817 return 1;
4818 else // opposite team -> harm me!
4819 return 0;
4820 }
4821 return 0; // all other cases -> harm me!
4822 }
4823
4824 namespace {
4825
multi_new_bounty_target(playernum_t pnum,const char * const callsign)4826 void multi_new_bounty_target(playernum_t pnum, const char *const callsign)
4827 {
4828 /* If it's already the same, don't do it */
4829 if (Bounty_target == pnum)
4830 return;
4831 /* Set the target */
4832 Bounty_target = pnum;
4833 /* Send a message */
4834 HUD_init_message(HM_MULTI, "%c%c%s is the new target!", CC_COLOR, BM_XRGB(player_rgb[pnum].r, player_rgb[pnum].g, player_rgb[pnum].b), callsign);
4835 }
4836
4837 }
4838
4839 }
4840
4841 /* Bounty packer sender and handler */
multi_send_bounty(void)4842 void multi_send_bounty( void )
4843 {
4844 /* Test game mode */
4845 if( !( Game_mode & GM_BOUNTY ) )
4846 return;
4847 if ( !multi_i_am_master() )
4848 return;
4849
4850 multi_command<MULTI_DO_BOUNTY> multibuf;
4851 /* Add opcode, target ID and how often we re-assigned */
4852 multibuf[1] = static_cast<char>(Bounty_target);
4853
4854 /* Send data */
4855 multi_send_data(multibuf, 2);
4856 }
4857
4858 namespace dsx {
4859
4860 namespace {
4861
multi_do_bounty(const ubyte * buf)4862 static void multi_do_bounty( const ubyte *buf )
4863 {
4864 if ( multi_i_am_master() )
4865 return;
4866 const unsigned pnum = buf[1];
4867 multi_new_bounty_target_with_sound(pnum, vcplayerptr(pnum)->callsign);
4868 }
4869
multi_new_bounty_target_with_sound(const playernum_t pnum,const char * const callsign)4870 void multi_new_bounty_target_with_sound(const playernum_t pnum, const char *const callsign)
4871 {
4872 #if defined(DXX_BUILD_DESCENT_I)
4873 digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F1_0 * 3 );
4874 #elif defined(DXX_BUILD_DESCENT_II)
4875 digi_play_sample( SOUND_BUDDY_MET_GOAL, F1_0 * 2 );
4876 #endif
4877 multi_new_bounty_target(pnum, callsign);
4878 }
4879
multi_do_save_game(const uint8_t * const buf)4880 static void multi_do_save_game(const uint8_t *const buf)
4881 {
4882 int count = 1;
4883 ubyte slot;
4884 uint id;
4885 d_game_unique_state::savegame_description desc;
4886
4887 slot = buf[count]; count += 1;
4888 id = GET_INTEL_INT(buf+count); count += 4;
4889 memcpy(desc.data(), &buf[count], desc.size());
4890 desc.back() = 0;
4891
4892 multi_save_game(static_cast<unsigned>(slot), id, desc);
4893 }
4894
4895 }
4896
4897 }
4898
4899 namespace {
4900
multi_do_restore_game(const ubyte * buf)4901 static void multi_do_restore_game(const ubyte *buf)
4902 {
4903 int count = 1;
4904 ubyte slot;
4905 uint id;
4906
4907 slot = buf[count]; count += 1;
4908 id = GET_INTEL_INT(buf+count); count += 4;
4909
4910 multi_restore_game( slot, id );
4911 }
4912
4913 }
4914
4915 namespace dcx {
4916 namespace {
4917
multi_send_save_game(const d_game_unique_state::save_slot slot,const unsigned id,const d_game_unique_state::savegame_description & desc)4918 static void multi_send_save_game(const d_game_unique_state::save_slot slot, const unsigned id, const d_game_unique_state::savegame_description &desc)
4919 {
4920 int count = 0;
4921
4922 count += 1;
4923 multi_command<MULTI_SAVE_GAME> multibuf;
4924 multibuf[count] = static_cast<uint8_t>(slot); count += 1; // Save slot=0
4925 PUT_INTEL_INT(&multibuf[count], id ); count += 4; // Save id
4926 memcpy(&multibuf[count], desc.data(), desc.size());
4927
4928 multi_send_data(multibuf, 2);
4929 }
4930
multi_send_restore_game(ubyte slot,uint id)4931 static void multi_send_restore_game(ubyte slot, uint id)
4932 {
4933 int count = 0;
4934
4935 count += 1;
4936 multi_command<MULTI_RESTORE_GAME> multibuf;
4937 multibuf[count] = slot; count += 1; // Save slot=0
4938 PUT_INTEL_INT(&multibuf[count], id ); count += 4; // Save id
4939
4940 multi_send_data(multibuf, 2);
4941 }
4942
4943 }
4944 }
4945
4946 namespace dsx {
4947
multi_initiate_save_game()4948 void multi_initiate_save_game()
4949 {
4950 auto &Objects = LevelUniqueObjectState.Objects;
4951 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
4952 auto &vcobjptr = Objects.vcptr;
4953
4954 if (const auto reason = multi_i_am_master() ? multi_interactive_deny_save_game(vcobjptr, partial_range(Players, N_players), LevelUniqueControlCenterState) : "Only the host is allowed to save a game!")
4955 {
4956 HUD_init_message(HM_MULTI, "Cannot save: %s", reason);
4957 return;
4958 }
4959
4960 d_game_unique_state::savegame_file_path filename{};
4961 d_game_unique_state::savegame_description desc{};
4962 const auto slot = state_get_save_file(*grd_curcanv, filename, &desc, blind_save::no);
4963 if (!GameUniqueState.valid_save_slot(slot))
4964 return;
4965 const auto &&player_range = partial_const_range(Players, N_players);
4966 // Execute "alive" and "duplicate callsign" checks again in case things changed while host decided upon the savegame.
4967 if (const auto reason = multi_interactive_deny_save_game(vcobjptr, player_range, LevelUniqueControlCenterState))
4968 {
4969 HUD_init_message(HM_MULTI, "Cannot save: %s", reason);
4970 return;
4971 }
4972 multi_execute_save_game(slot, desc, player_range);
4973 }
4974
multi_execute_save_game(const d_game_unique_state::save_slot slot,const d_game_unique_state::savegame_description & desc,const partial_range_t<const player * > player_range)4975 void multi_execute_save_game(const d_game_unique_state::save_slot slot, const d_game_unique_state::savegame_description &desc, const partial_range_t<const player *> player_range)
4976 {
4977 // Make a unique game id
4978 fix game_id;
4979 game_id = static_cast<fix>(timer_query());
4980 game_id ^= N_players<<4;
4981 range_for (auto &i, player_range)
4982 {
4983 fix call2i;
4984 memcpy(&call2i, static_cast<const char *>(i.callsign), sizeof(fix));
4985 game_id ^= call2i;
4986 }
4987 if ( game_id == 0 )
4988 game_id = 1; // 0 is invalid
4989
4990 multi_send_save_game( slot, game_id, desc );
4991 multi_do_frame();
4992 multi_save_game(static_cast<unsigned>(slot), game_id, desc);
4993 }
4994
multi_initiate_restore_game()4995 void multi_initiate_restore_game()
4996 {
4997 auto &Objects = LevelUniqueObjectState.Objects;
4998 auto &vcobjptr = Objects.vcptr;
4999 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
5000
5001 if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
5002 return;
5003
5004 if (const auto reason = multi_i_am_master() ? multi_interactive_deny_save_game(vcobjptr, partial_const_range(Players, N_players), LevelUniqueControlCenterState) : "Only host is allowed to load a game!")
5005 {
5006 HUD_init_message(HM_MULTI, "Cannot load: %s", reason);
5007 return;
5008 }
5009 d_game_unique_state::savegame_file_path filename;
5010 const auto eslot = state_get_restore_file(*grd_curcanv, filename, blind_save::no);
5011 if (!GameUniqueState.valid_load_slot(eslot))
5012 return;
5013 /* Recheck the interactive conditions, but not the host status. If
5014 * this system was the host before, it must still be the host now.
5015 */
5016 if (const auto reason = multi_interactive_deny_save_game(vcobjptr, partial_const_range(Players, N_players), LevelUniqueControlCenterState))
5017 {
5018 HUD_init_message(HM_MULTI, "Cannot load: %s", reason);
5019 return;
5020 }
5021 state_game_id = state_get_game_id(filename);
5022 if (!state_game_id)
5023 return;
5024 const unsigned slot = static_cast<unsigned>(eslot);
5025 multi_send_restore_game(slot,state_game_id);
5026 multi_do_frame();
5027 multi_restore_game(slot,state_game_id);
5028 }
5029
5030 namespace {
5031
multi_save_game(const unsigned slot,const unsigned id,const d_game_unique_state::savegame_description & desc)5032 void multi_save_game(const unsigned slot, const unsigned id, const d_game_unique_state::savegame_description &desc)
5033 {
5034 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
5035 char filename[PATH_MAX];
5036
5037 if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
5038 return;
5039
5040 snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%s.mg%x"), static_cast<const char *>(get_local_player().callsign), slot);
5041 HUD_init_message(HM_MULTI, "Saving game #%d, '%s'", slot, desc.data());
5042 state_game_id = id;
5043 pause_game_world_time p;
5044 state_save_all_sub(filename, desc.data());
5045 }
5046
multi_restore_game(const unsigned slot,const unsigned id)5047 void multi_restore_game(const unsigned slot, const unsigned id)
5048 {
5049 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
5050 d_game_unique_state::savegame_file_path filename;
5051
5052 if (Network_status == NETSTAT_ENDLEVEL || LevelUniqueControlCenterState.Control_center_destroyed)
5053 return;
5054
5055 auto &plr = get_local_player();
5056 snprintf(filename.data(), filename.size(), PLAYER_DIRECTORY_STRING("%s.mg%x"), static_cast<const char *>(plr.callsign), slot);
5057
5058 for (unsigned i = 0, n = N_players; i < n; ++i)
5059 multi_strip_robots(i);
5060 if (multi_i_am_master()) // put all players to wait-state again so we can sync up properly
5061 range_for (auto &i, Players)
5062 if (i.connected == CONNECT_PLAYING && &i != &plr)
5063 i.connected = CONNECT_WAITING;
5064
5065 const auto thisid = state_get_game_id(filename);
5066 if (thisid!=id)
5067 {
5068 nm_messagebox_str(menu_title{nullptr}, nm_messagebox_tie(TXT_OK), menu_subtitle{"A multi-save game was restored\nthat you are missing or does not\nmatch that of the others.\nYou must rejoin if you wish to\ncontinue."});
5069 return;
5070 }
5071
5072 #if defined(DXX_BUILD_DESCENT_II)
5073 auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
5074 #endif
5075 state_restore_all_sub(
5076 #if defined(DXX_BUILD_DESCENT_II)
5077 LevelSharedDestructibleLightState, secret_restore::none,
5078 #endif
5079 filename.data());
5080 multi_send_score(); // send my restored scores. I sent 0 when I loaded the level anyways...
5081 }
5082
5083 }
5084
5085 }
5086
5087 namespace {
5088
multi_do_msgsend_state(const uint8_t * buf)5089 static void multi_do_msgsend_state(const uint8_t *buf)
5090 {
5091 multi_sending_message[static_cast<int>(buf[1])] = msgsend_state{buf[2]};
5092 }
5093
5094 }
5095
multi_send_msgsend_state(const msgsend_state state)5096 void multi_send_msgsend_state(const msgsend_state state)
5097 {
5098 multi_command<MULTI_TYPING_STATE> multibuf;
5099 multibuf[1] = Player_num;
5100 multibuf[2] = static_cast<char>(state);
5101
5102 multi_send_data(multibuf, 2);
5103 }
5104
5105 namespace {
5106
5107 // Specific variables related to our game mode we want the clients to know about
multi_send_gmode_update()5108 void multi_send_gmode_update()
5109 {
5110 if (!multi_i_am_master())
5111 return;
5112 if (!(Game_mode & GM_TEAM || Game_mode & GM_BOUNTY)) // expand if necessary
5113 return;
5114 multi_command<MULTI_GMODE_UPDATE> multibuf;
5115 multibuf[1] = Netgame.team_vector;
5116 multibuf[2] = Bounty_target;
5117
5118 multi_send_data(multibuf, 0);
5119 }
5120
multi_do_gmode_update(const ubyte * buf)5121 static void multi_do_gmode_update(const ubyte *buf)
5122 {
5123 auto &Objects = LevelUniqueObjectState.Objects;
5124 auto &vmobjptr = Objects.vmptr;
5125 if (multi_i_am_master())
5126 return;
5127 if (Game_mode & GM_TEAM)
5128 {
5129 if (buf[1] != Netgame.team_vector)
5130 {
5131 Netgame.team_vector = buf[1];
5132 range_for (auto &t, partial_const_range(Players, N_players))
5133 if (t.connected)
5134 multi_reset_object_texture (vmobjptr(t.objnum));
5135 reset_cockpit();
5136 }
5137 }
5138 if (Game_mode & GM_BOUNTY)
5139 {
5140 Bounty_target = buf[2]; // accept silently - message about change we SHOULD have gotten due to kill computation
5141 }
5142 }
5143
5144 }
5145
5146 /*
5147 * Send player inventory to all other players. Intended to be used for the host to repopulate the level with new powerups.
5148 * Could also be used to let host decide which powerups a client is allowed to collect and/or drop, anti-cheat functions (needs shield/energy update then and more frequent updates/triggers).
5149 */
5150 namespace dsx {
multi_send_player_inventory(int priority)5151 void multi_send_player_inventory(int priority)
5152 {
5153 auto &Objects = LevelUniqueObjectState.Objects;
5154 auto &vmobjptr = Objects.vmptr;
5155 multi_command<MULTI_PLAYER_INV> multibuf;
5156 int count = 0;
5157
5158 count++;
5159 multibuf[count++] = Player_num;
5160
5161 auto &player_info = get_local_plrobj().ctype.player_info;
5162 PUT_WEAPON_FLAGS(multibuf, count, player_info.primary_weapon_flags);
5163 multibuf[count++] = static_cast<char>(player_info.laser_level);
5164
5165 auto &secondary_ammo = player_info.secondary_ammo;
5166 multibuf[count++] = secondary_ammo[HOMING_INDEX];
5167 multibuf[count++] = secondary_ammo[CONCUSSION_INDEX];
5168 multibuf[count++] = secondary_ammo[SMART_INDEX];
5169 multibuf[count++] = secondary_ammo[MEGA_INDEX];
5170 multibuf[count++] = secondary_ammo[PROXIMITY_INDEX];
5171
5172 #if defined(DXX_BUILD_DESCENT_II)
5173 multibuf[count++] = secondary_ammo[SMISSILE1_INDEX];
5174 multibuf[count++] = secondary_ammo[GUIDED_INDEX];
5175 multibuf[count++] = secondary_ammo[SMART_MINE_INDEX];
5176 multibuf[count++] = secondary_ammo[SMISSILE4_INDEX];
5177 multibuf[count++] = secondary_ammo[SMISSILE5_INDEX];
5178 #endif
5179
5180 PUT_INTEL_SHORT(&multibuf[count], player_info.vulcan_ammo);
5181 count += 2;
5182 PUT_INTEL_INT(&multibuf[count], player_info.powerup_flags.get_player_flags());
5183 count += 4;
5184
5185 multi_send_data(multibuf, priority);
5186 }
5187 }
5188
5189 namespace dsx {
5190 namespace {
multi_do_player_inventory(const playernum_t pnum,const ubyte * buf)5191 static void multi_do_player_inventory(const playernum_t pnum, const ubyte *buf)
5192 {
5193 auto &Objects = LevelUniqueObjectState.Objects;
5194 auto &vmobjptridx = Objects.vmptridx;
5195 int count;
5196
5197 #ifdef NDEBUG
5198 if (pnum >= N_players || pnum == Player_num)
5199 return;
5200 #else
5201 Assert(pnum < N_players);
5202 Assert(pnum != Player_num);
5203 #endif
5204
5205 count = 2;
5206 #if defined(DXX_BUILD_DESCENT_I)
5207 #define GET_WEAPON_FLAGS(buf,count) buf[count++]
5208 #elif defined(DXX_BUILD_DESCENT_II)
5209 #define GET_WEAPON_FLAGS(buf,count) (count += sizeof(uint16_t), GET_INTEL_SHORT(buf + (count - sizeof(uint16_t))))
5210 #endif
5211 const auto &&objp = vmobjptridx(vcplayerptr(pnum)->objnum);
5212 auto &player_info = objp->ctype.player_info;
5213 player_info.primary_weapon_flags = GET_WEAPON_FLAGS(buf, count);
5214 player_info.laser_level = laser_level{buf[count]};
5215 count++;
5216
5217 auto &secondary_ammo = player_info.secondary_ammo;
5218 secondary_ammo[HOMING_INDEX] = buf[count]; count++;
5219 secondary_ammo[CONCUSSION_INDEX] = buf[count];count++;
5220 secondary_ammo[SMART_INDEX] = buf[count]; count++;
5221 secondary_ammo[MEGA_INDEX] = buf[count]; count++;
5222 secondary_ammo[PROXIMITY_INDEX] = buf[count]; count++;
5223
5224 #if defined(DXX_BUILD_DESCENT_II)
5225 secondary_ammo[SMISSILE1_INDEX] = buf[count]; count++;
5226 secondary_ammo[GUIDED_INDEX] = buf[count]; count++;
5227 secondary_ammo[SMART_MINE_INDEX]= buf[count]; count++;
5228 secondary_ammo[SMISSILE4_INDEX] = buf[count]; count++;
5229 secondary_ammo[SMISSILE5_INDEX] = buf[count]; count++;
5230 #endif
5231
5232 player_info.vulcan_ammo = GET_INTEL_SHORT(buf + count); count += 2;
5233 player_info.powerup_flags = player_flags(GET_INTEL_INT(buf + count)); count += 4;
5234 }
5235
5236 /*
5237 * Count the inventory of the level. Initial (start) or current (now).
5238 * In 'current', also consider player inventories (and the thief bot).
5239 * NOTE: We add actual ammo amount - we do not want to count in 'amount of powerups'. Makes it easier to keep track of overhead (proximities, vulcan ammo)
5240 */
MultiLevelInv_CountLevelPowerups()5241 static void MultiLevelInv_CountLevelPowerups()
5242 {
5243 auto &Objects = LevelUniqueObjectState.Objects;
5244 auto &vmobjptridx = Objects.vmptridx;
5245 if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_COOP))
5246 return;
5247 MultiLevelInv.Current = {};
5248
5249 range_for (const auto &&objp, vmobjptridx)
5250 {
5251 if (objp->type == OBJ_WEAPON) // keep live bombs in inventory so they will respawn after they're gone
5252 {
5253 auto wid = get_weapon_id(objp);
5254 if (wid == weapon_id_type::PROXIMITY_ID)
5255 MultiLevelInv.Current[POW_PROXIMITY_WEAPON]++;
5256 #if defined(DXX_BUILD_DESCENT_II)
5257 if (wid == weapon_id_type::SUPERPROX_ID)
5258 MultiLevelInv.Current[POW_SMART_MINE]++;
5259 #endif
5260 }
5261 if (objp->type != OBJ_POWERUP)
5262 continue;
5263 auto pid = get_powerup_id(objp);
5264 switch (pid)
5265 {
5266 case POW_VULCAN_WEAPON:
5267 #if defined(DXX_BUILD_DESCENT_II)
5268 case POW_GAUSS_WEAPON:
5269 #endif
5270 MultiLevelInv.Current[POW_VULCAN_AMMO] += objp->ctype.powerup_info.count; // add contained ammo so we do not lose this from level when used up
5271 /* fall through to increment Current[pid] */
5272 DXX_BOOST_FALLTHROUGH;
5273 case POW_LASER:
5274 case POW_QUAD_FIRE:
5275 case POW_SPREADFIRE_WEAPON:
5276 case POW_PLASMA_WEAPON:
5277 case POW_FUSION_WEAPON:
5278 case POW_MISSILE_1:
5279 case POW_HOMING_AMMO_1:
5280 case POW_SMARTBOMB_WEAPON:
5281 case POW_MEGA_WEAPON:
5282 case POW_CLOAK:
5283 case POW_INVULNERABILITY:
5284 #if defined(DXX_BUILD_DESCENT_II)
5285 case POW_SUPER_LASER:
5286 case POW_HELIX_WEAPON:
5287 case POW_PHOENIX_WEAPON:
5288 case POW_OMEGA_WEAPON:
5289 case POW_SMISSILE1_1:
5290 case POW_GUIDED_MISSILE_1:
5291 case POW_MERCURY_MISSILE_1:
5292 case POW_EARTHSHAKER_MISSILE:
5293 case POW_FULL_MAP:
5294 case POW_CONVERTER:
5295 case POW_AMMO_RACK:
5296 case POW_AFTERBURNER:
5297 case POW_HEADLIGHT:
5298 case POW_FLAG_BLUE:
5299 case POW_FLAG_RED:
5300 #endif
5301 MultiLevelInv.Current[pid]++;
5302 break;
5303 case POW_MISSILE_4:
5304 case POW_HOMING_AMMO_4:
5305 #if defined(DXX_BUILD_DESCENT_II)
5306 case POW_SMISSILE1_4:
5307 case POW_GUIDED_MISSILE_4:
5308 case POW_MERCURY_MISSILE_4:
5309 #endif
5310 MultiLevelInv.Current[pid-1] += 4;
5311 break;
5312 case POW_PROXIMITY_WEAPON:
5313 #if defined(DXX_BUILD_DESCENT_II)
5314 case POW_SMART_MINE:
5315 #endif
5316 MultiLevelInv.Current[pid] += 4; // count the actual bombs
5317 break;
5318 case POW_VULCAN_AMMO:
5319 MultiLevelInv.Current[pid] += VULCAN_AMMO_AMOUNT; // count the actual ammo
5320 break;
5321 default:
5322 break; // All other items either do not exist or we NEVER want to have them respawn.
5323 }
5324 }
5325 }
5326
MultiLevelInv_CountPlayerInventory()5327 static void MultiLevelInv_CountPlayerInventory()
5328 {
5329 auto &Objects = LevelUniqueObjectState.Objects;
5330 auto &vcobjptr = Objects.vcptr;
5331 auto &Current = MultiLevelInv.Current;
5332 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
5333 {
5334 if (vcplayerptr(i)->connected != CONNECT_PLAYING)
5335 continue;
5336 auto &obj = *vcobjptr(vcplayerptr(i)->objnum);
5337 if (obj.type == OBJ_GHOST) // Player is dead. Their items are dropped now.
5338 continue;
5339 auto &player_info = obj.ctype.player_info;
5340 // NOTE: We do not need to consider Granted Spawn Items here. These are replaced with shields and even if not, the repopulation function will take care of it.
5341 #if defined(DXX_BUILD_DESCENT_II)
5342 if (player_info.laser_level > MAX_LASER_LEVEL)
5343 {
5344 /*
5345 * We do not know exactly how many normal lasers the player collected before going super so assume they have all.
5346 * This loss possible is insignificant since we have super lasers and normal ones may respawn some time after this player dies.
5347 */
5348 Current[POW_LASER] += 4;
5349 Current[POW_SUPER_LASER] += static_cast<unsigned>(player_info.laser_level) - static_cast<unsigned>(MAX_LASER_LEVEL) + 1; // Laser levels start at 0!
5350 }
5351 else
5352 #endif
5353 {
5354 Current[POW_LASER] += static_cast<unsigned>(player_info.laser_level) + 1; // Laser levels start at 0!
5355 }
5356 accumulate_flags_count<player_flags, PLAYER_FLAG> powerup_flags(Current, player_info.powerup_flags);
5357 accumulate_flags_count<player_info::primary_weapon_flag_type, unsigned> primary_weapon_flags(Current, player_info.primary_weapon_flags);
5358 powerup_flags.process(PLAYER_FLAGS_QUAD_LASERS, POW_QUAD_FIRE);
5359 primary_weapon_flags.process(HAS_PRIMARY_FLAG(VULCAN_INDEX), POW_VULCAN_WEAPON);
5360 primary_weapon_flags.process(HAS_PRIMARY_FLAG(SPREADFIRE_INDEX), POW_SPREADFIRE_WEAPON);
5361 primary_weapon_flags.process(HAS_PRIMARY_FLAG(PLASMA_INDEX), POW_PLASMA_WEAPON);
5362 primary_weapon_flags.process(HAS_PRIMARY_FLAG(FUSION_INDEX), POW_FUSION_WEAPON);
5363 powerup_flags.process(PLAYER_FLAGS_CLOAKED, POW_CLOAK);
5364 powerup_flags.process(PLAYER_FLAGS_INVULNERABLE, POW_INVULNERABILITY);
5365 // NOTE: The following can probably be simplified.
5366 #if defined(DXX_BUILD_DESCENT_II)
5367 primary_weapon_flags.process(HAS_PRIMARY_FLAG(GAUSS_INDEX), POW_GAUSS_WEAPON);
5368 primary_weapon_flags.process(HAS_PRIMARY_FLAG(HELIX_INDEX), POW_HELIX_WEAPON);
5369 primary_weapon_flags.process(HAS_PRIMARY_FLAG(PHOENIX_INDEX), POW_PHOENIX_WEAPON);
5370 primary_weapon_flags.process(HAS_PRIMARY_FLAG(OMEGA_INDEX), POW_OMEGA_WEAPON);
5371 powerup_flags.process(PLAYER_FLAGS_MAP_ALL, POW_FULL_MAP);
5372 powerup_flags.process(PLAYER_FLAGS_CONVERTER, POW_CONVERTER);
5373 powerup_flags.process(PLAYER_FLAGS_AMMO_RACK, POW_AMMO_RACK);
5374 powerup_flags.process(PLAYER_FLAGS_AFTERBURNER, POW_AFTERBURNER);
5375 powerup_flags.process(PLAYER_FLAGS_HEADLIGHT, POW_HEADLIGHT);
5376 if ((Game_mode & GM_CAPTURE) && (player_info.powerup_flags & PLAYER_FLAGS_FLAG))
5377 {
5378 ++Current[(get_team(i) == TEAM_RED) ? POW_FLAG_BLUE : POW_FLAG_RED];
5379 }
5380 #endif
5381 Current[POW_VULCAN_AMMO] += player_info.vulcan_ammo;
5382 Current[POW_MISSILE_1] += player_info.secondary_ammo[CONCUSSION_INDEX];
5383 Current[POW_HOMING_AMMO_1] += player_info.secondary_ammo[HOMING_INDEX];
5384 Current[POW_PROXIMITY_WEAPON] += player_info.secondary_ammo[PROXIMITY_INDEX];
5385 Current[POW_SMARTBOMB_WEAPON] += player_info.secondary_ammo[SMART_INDEX];
5386 Current[POW_MEGA_WEAPON] += player_info.secondary_ammo[MEGA_INDEX];
5387 #if defined(DXX_BUILD_DESCENT_II)
5388 Current[POW_SMISSILE1_1] += player_info.secondary_ammo[SMISSILE1_INDEX];
5389 Current[POW_GUIDED_MISSILE_1] += player_info.secondary_ammo[GUIDED_INDEX];
5390 Current[POW_SMART_MINE] += player_info.secondary_ammo[SMART_MINE_INDEX];
5391 Current[POW_MERCURY_MISSILE_1] += player_info.secondary_ammo[SMISSILE4_INDEX];
5392 Current[POW_EARTHSHAKER_MISSILE] += player_info.secondary_ammo[SMISSILE5_INDEX];
5393 #endif
5394 }
5395 #if defined(DXX_BUILD_DESCENT_II)
5396 if (Game_mode & GM_MULTI_ROBOTS) // Add (possible) thief inventory
5397 {
5398 range_for (auto &i, LevelUniqueObjectState.ThiefState.Stolen_items)
5399 {
5400 if (i >= Current.size() || i == POW_ENERGY || i == POW_SHIELD_BOOST)
5401 continue;
5402 auto &c = Current[i];
5403 // NOTE: We don't need to consider vulcan ammo or 4pack items as the thief should not steal those items.
5404 if (i == POW_PROXIMITY_WEAPON || i == POW_SMART_MINE)
5405 c += 4;
5406 else
5407 ++c;
5408 }
5409 }
5410 #endif
5411 }
5412 }
5413 }
5414
MultiLevelInv_InitializeCount()5415 void MultiLevelInv_InitializeCount()
5416 {
5417 MultiLevelInv_CountLevelPowerups();
5418 MultiLevelInv.Initial = MultiLevelInv.Current;
5419 MultiLevelInv.RespawnTimer = {};
5420 }
5421
MultiLevelInv_Recount()5422 void MultiLevelInv_Recount()
5423 {
5424 MultiLevelInv_CountLevelPowerups();
5425 MultiLevelInv_CountPlayerInventory();
5426 }
5427
5428 // Takes a powerup type and checks if we are allowed to spawn it.
5429 namespace dsx {
MultiLevelInv_AllowSpawn(powerup_type_t powerup_type)5430 bool MultiLevelInv_AllowSpawn(powerup_type_t powerup_type)
5431 {
5432 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
5433 if ((Game_mode & GM_MULTI_COOP) || LevelUniqueControlCenterState.Control_center_destroyed || Network_status != NETSTAT_PLAYING)
5434 return 0;
5435
5436 int req_amount = 1; // required amount of item to drop a powerup.
5437
5438 if (powerup_type == POW_VULCAN_AMMO)
5439 req_amount = VULCAN_AMMO_AMOUNT;
5440 else if (powerup_type == POW_PROXIMITY_WEAPON
5441 #if defined(DXX_BUILD_DESCENT_II)
5442 || powerup_type == POW_SMART_MINE
5443 #endif
5444 )
5445 req_amount = 4;
5446
5447 if (MultiLevelInv.Initial[powerup_type] == 0 || MultiLevelInv.Current[powerup_type] > MultiLevelInv.Initial[powerup_type]) // Item does not exist in level or we have too many.
5448 return 0;
5449 else if (MultiLevelInv.Initial[powerup_type] - MultiLevelInv.Current[powerup_type] >= req_amount)
5450 return 1;
5451 return 0;
5452 }
5453 }
5454
5455 // Repopulate the level with missing items.
MultiLevelInv_Repopulate(fix frequency)5456 void MultiLevelInv_Repopulate(fix frequency)
5457 {
5458 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
5459 if (!multi_i_am_master() || (Game_mode & GM_MULTI_COOP) || LevelUniqueControlCenterState.Control_center_destroyed)
5460 return;
5461
5462 MultiLevelInv_Recount(); // recount current items
5463 for (unsigned i = 0; i < MAX_POWERUP_TYPES; i++)
5464 {
5465 if (MultiLevelInv_AllowSpawn(static_cast<powerup_type_t>(i)))
5466 MultiLevelInv.RespawnTimer[i] += frequency;
5467 else
5468 MultiLevelInv.RespawnTimer[i] = 0;
5469
5470 if (MultiLevelInv.RespawnTimer[i] >= F1_0*2)
5471 {
5472 con_printf(CON_VERBOSE, "MultiLevelInv_Repopulate type: %i - Init: %i Cur: %i", i, MultiLevelInv.Initial[i], MultiLevelInv.Current[i]);
5473 MultiLevelInv.RespawnTimer[i] = 0;
5474 maybe_drop_net_powerup(static_cast<powerup_type_t>(i), 0, 1);
5475 }
5476 }
5477 }
5478
5479 namespace dsx {
5480
5481 #if defined(DXX_BUILD_DESCENT_II)
5482 namespace {
5483
5484 ///
5485 /// CODE TO LOAD HOARD DATA
5486 ///
5487
5488 class hoard_resources_type
5489 {
5490 static constexpr auto invalid_bm_idx = std::integral_constant<int, -1>{};
5491 static constexpr auto invalid_snd_idx = std::integral_constant<unsigned, ~0u>{};
5492 public:
5493 int bm_idx = invalid_bm_idx;
5494 unsigned snd_idx = invalid_snd_idx;
5495 void reset();
~hoard_resources_type()5496 ~hoard_resources_type()
5497 {
5498 reset();
5499 }
5500 };
5501
5502 static hoard_resources_type hoard_resources;
5503
5504 }
5505
HoardEquipped()5506 int HoardEquipped()
5507 {
5508 static int checked=-1;
5509
5510 if (unlikely(checked == -1))
5511 {
5512 checked = PHYSFSX_exists("hoard.ham",1);
5513 }
5514 return (checked);
5515 }
5516
5517 std::array<grs_main_bitmap, 2> Orb_icons;
5518
reset()5519 void hoard_resources_type::reset()
5520 {
5521 if (bm_idx != invalid_bm_idx)
5522 d_free(GameBitmaps[std::exchange(bm_idx, invalid_bm_idx)].bm_mdata);
5523 if (snd_idx != invalid_snd_idx)
5524 {
5525 const auto idx = std::exchange(snd_idx, invalid_snd_idx);
5526 range_for (auto &i, partial_range(GameSounds, idx, idx + 4))
5527 d_free(i.data);
5528 }
5529 range_for (auto &i, Orb_icons)
5530 i.reset();
5531 }
5532
init_hoard_data(d_vclip_array & Vclip)5533 void init_hoard_data(d_vclip_array &Vclip)
5534 {
5535 auto &Effects = LevelUniqueEffectsClipState.Effects;
5536 hoard_resources.reset();
5537 static int orb_vclip;
5538 unsigned n_orb_frames,n_goal_frames;
5539 int orb_w,orb_h;
5540 palette_array_t palette;
5541 uint8_t *bitmap_data1;
5542 int save_pos;
5543 int bitmap_num = hoard_resources.bm_idx = Num_bitmap_files;
5544 auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
5545
5546 auto &&[ifile, physfserr] = PHYSFSX_openReadBuffered("hoard.ham");
5547 if (!ifile)
5548 Error("Failed to open <hoard.ham>: %s", PHYSFS_getErrorByCode(physfserr));
5549
5550 n_orb_frames = PHYSFSX_readShort(ifile);
5551 orb_w = PHYSFSX_readShort(ifile);
5552 orb_h = PHYSFSX_readShort(ifile);
5553 save_pos = PHYSFS_tell(ifile);
5554 PHYSFSX_fseek(ifile,sizeof(palette)+n_orb_frames*orb_w*orb_h,SEEK_CUR);
5555 n_goal_frames = PHYSFSX_readShort(ifile);
5556 PHYSFSX_fseek(ifile,save_pos,SEEK_SET);
5557
5558 //Allocate memory for bitmaps
5559 MALLOC( bitmap_data1, ubyte, n_orb_frames*orb_w*orb_h + n_goal_frames*64*64 );
5560
5561 //Create orb vclip
5562 orb_vclip = Num_vclips++;
5563 assert(Num_vclips <= Vclip.size());
5564 Vclip[orb_vclip].play_time = F1_0/2;
5565 Vclip[orb_vclip].num_frames = n_orb_frames;
5566 Vclip[orb_vclip].frame_time = Vclip[orb_vclip].play_time / Vclip[orb_vclip].num_frames;
5567 Vclip[orb_vclip].flags = 0;
5568 Vclip[orb_vclip].sound_num = -1;
5569 Vclip[orb_vclip].light_value = F1_0;
5570 range_for (auto &i, partial_range(Vclip[orb_vclip].frames, n_orb_frames))
5571 {
5572 i.index = bitmap_num;
5573 gr_init_bitmap(GameBitmaps[bitmap_num],bm_mode::linear,0,0,orb_w,orb_h,orb_w,bitmap_data1);
5574 gr_set_transparent(GameBitmaps[bitmap_num], 1);
5575 bitmap_data1 += orb_w*orb_h;
5576 bitmap_num++;
5577 Assert(bitmap_num < MAX_BITMAP_FILES);
5578 }
5579
5580 //Create obj powerup
5581 Powerup_info[POW_HOARD_ORB].vclip_num = orb_vclip;
5582 Powerup_info[POW_HOARD_ORB].hit_sound = -1; //Powerup_info[POW_SHIELD_BOOST].hit_sound;
5583 Powerup_info[POW_HOARD_ORB].size = Powerup_info[POW_SHIELD_BOOST].size;
5584 Powerup_info[POW_HOARD_ORB].light = Powerup_info[POW_SHIELD_BOOST].light;
5585
5586 //Create orb goal wall effect
5587 const auto goal_eclip = Num_effects++;
5588 assert(goal_eclip < Effects.size());
5589 Effects[goal_eclip] = Effects[94]; //copy from blue goal
5590 Effects[goal_eclip].changing_wall_texture = NumTextures;
5591 Effects[goal_eclip].vc.num_frames=n_goal_frames;
5592
5593 TmapInfo[NumTextures] = find_required_goal_texture(LevelUniqueTmapInfoState, tmapinfo_flag::goal_blue);
5594 TmapInfo[NumTextures].eclip_num = goal_eclip;
5595 TmapInfo[NumTextures].flags = static_cast<tmapinfo_flags>(tmapinfo_flag::goal_hoard);
5596 NumTextures++;
5597 Assert(NumTextures < MAX_TEXTURES);
5598 range_for (auto &i, partial_range(Effects[goal_eclip].vc.frames, n_goal_frames))
5599 {
5600 i.index = bitmap_num;
5601 gr_init_bitmap(GameBitmaps[bitmap_num],bm_mode::linear,0,0,64,64,64,bitmap_data1);
5602 bitmap_data1 += 64*64;
5603 bitmap_num++;
5604 Assert(bitmap_num < MAX_BITMAP_FILES);
5605 }
5606
5607 //Load and remap bitmap data for orb
5608 PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
5609 range_for (auto &i, partial_const_range(Vclip[orb_vclip].frames, n_orb_frames))
5610 {
5611 grs_bitmap *bm = &GameBitmaps[i.index];
5612 PHYSFS_read(ifile,bm->get_bitmap_data(),1,orb_w*orb_h);
5613 gr_remap_bitmap_good(*bm, palette, 255, -1);
5614 }
5615
5616 //Load and remap bitmap data for goal texture
5617 PHYSFSX_readShort(ifile); //skip frame count
5618 PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
5619 range_for (auto &i, partial_const_range(Effects[goal_eclip].vc.frames, n_goal_frames))
5620 {
5621 grs_bitmap *bm = &GameBitmaps[i.index];
5622 PHYSFS_read(ifile,bm->get_bitmap_data(),1,64*64);
5623 gr_remap_bitmap_good(*bm, palette, 255, -1);
5624 }
5625
5626 //Load and remap bitmap data for HUD icons
5627 range_for (auto &i, Orb_icons)
5628 {
5629 const unsigned icon_w = PHYSFSX_readShort(ifile);
5630 if (icon_w > 32)
5631 return;
5632 const unsigned icon_h = PHYSFSX_readShort(ifile);
5633 if (icon_h > 32)
5634 return;
5635 const unsigned extent = icon_w * icon_h;
5636 RAIIdmem<uint8_t[]> bitmap_data2;
5637 MALLOC(bitmap_data2, uint8_t[], extent);
5638 PHYSFS_read(ifile,&palette[0],sizeof(palette[0]),palette.size());
5639 PHYSFS_read(ifile, bitmap_data2.get(), 1, extent);
5640 gr_init_main_bitmap(i, bm_mode::linear, 0, 0, icon_w, icon_h, icon_w, std::move(bitmap_data2));
5641 gr_set_transparent(i, 1);
5642 gr_remap_bitmap_good(i, palette, 255, -1);
5643 }
5644
5645 //Load sounds for orb game
5646 hoard_resources.snd_idx = Num_sound_files;
5647 range_for (const unsigned i, xrange(4u))
5648 {
5649 int len;
5650
5651 len = PHYSFSX_readInt(ifile); //get 11k len
5652
5653 if (GameArg.SndDigiSampleRate == SAMPLE_RATE_22K) {
5654 PHYSFSX_fseek(ifile,len,SEEK_CUR); //skip over 11k sample
5655 len = PHYSFSX_readInt(ifile); //get 22k len
5656 }
5657
5658 GameSounds[Num_sound_files+i].length = len;
5659 MALLOC(GameSounds[Num_sound_files+i].data, ubyte, len);
5660 PHYSFS_read(ifile,GameSounds[Num_sound_files+i].data,1,len);
5661
5662 if (GameArg.SndDigiSampleRate == SAMPLE_RATE_11K) {
5663 len = PHYSFSX_readInt(ifile); //get 22k len
5664 PHYSFSX_fseek(ifile,len,SEEK_CUR); //skip over 22k sample
5665 }
5666
5667 Sounds[SOUND_YOU_GOT_ORB+i] = Num_sound_files+i;
5668 AltSounds[SOUND_YOU_GOT_ORB+i] = Sounds[SOUND_YOU_GOT_ORB+i];
5669 }
5670 }
5671
5672 #if DXX_USE_EDITOR
save_hoard_data(void)5673 void save_hoard_data(void)
5674 {
5675 grs_bitmap icon;
5676 unsigned nframes;
5677 palette_array_t palette;
5678 int iff_error;
5679 static const char sounds[][13] = {"selforb.raw","selforb.r22", //SOUND_YOU_GOT_ORB
5680 "teamorb.raw","teamorb.r22", //SOUND_FRIEND_GOT_ORB
5681 "enemyorb.raw","enemyorb.r22", //SOUND_OPPONENT_GOT_ORB
5682 "OPSCORE1.raw","OPSCORE1.r22"}; //SOUND_OPPONENT_HAS_SCORED
5683
5684 auto ofile = PHYSFSX_openWriteBuffered("hoard.ham").first;
5685 if (!ofile)
5686 return;
5687
5688 std::array<std::unique_ptr<grs_main_bitmap>, MAX_BITMAPS_PER_BRUSH> bm;
5689 iff_error = iff_read_animbrush("orb.abm",bm,&nframes,palette);
5690 Assert(iff_error == IFF_NO_ERROR);
5691 PHYSFS_writeULE16(ofile, nframes);
5692 PHYSFS_writeULE16(ofile, bm[0]->bm_w);
5693 PHYSFS_writeULE16(ofile, bm[0]->bm_h);
5694 PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
5695 range_for (auto &i, partial_const_range(bm, nframes))
5696 PHYSFS_write(ofile, i->bm_data, i->bm_w * i->bm_h, 1);
5697
5698 iff_error = iff_read_animbrush("orbgoal.abm",bm,&nframes,palette);
5699 Assert(iff_error == IFF_NO_ERROR);
5700 Assert(bm[0]->bm_w == 64 && bm[0]->bm_h == 64);
5701 PHYSFS_writeULE16(ofile, nframes);
5702 PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
5703 range_for (auto &i, partial_const_range(bm, nframes))
5704 PHYSFS_write(ofile, i->bm_data, i->bm_w * i->bm_h, 1);
5705
5706 range_for (const unsigned i, xrange(2u))
5707 {
5708 iff_error = iff_read_bitmap(i ? "orbb.bbm" : "orb.bbm", icon, &palette);
5709 Assert(iff_error == IFF_NO_ERROR);
5710 PHYSFS_writeULE16(ofile, icon.bm_w);
5711 PHYSFS_writeULE16(ofile, icon.bm_h);
5712 PHYSFS_write(ofile, &palette[0], sizeof(palette[0]), palette.size());
5713 PHYSFS_write(ofile, icon.bm_data, icon.bm_w*icon.bm_h, 1);
5714 }
5715 (void)iff_error;
5716
5717 range_for (auto &i, sounds)
5718 if (RAIIPHYSFS_File ifile{PHYSFS_openRead(i)})
5719 {
5720 int size;
5721 size = PHYSFS_fileLength(ifile);
5722 const auto buf = std::make_unique<uint8_t[]>(size);
5723 PHYSFS_read(ifile, buf, size, 1);
5724 PHYSFS_writeULE32(ofile, size);
5725 PHYSFS_write(ofile, buf, size, 1);
5726 }
5727 }
5728 #endif
5729 #endif
5730
5731 namespace {
5732
multi_process_data(const playernum_t pnum,const ubyte * buf,const uint_fast32_t type)5733 static void multi_process_data(const playernum_t pnum, const ubyte *buf, const uint_fast32_t type)
5734 {
5735 auto &Objects = LevelUniqueObjectState.Objects;
5736 auto &imobjptridx = Objects.imptridx;
5737 auto &vmobjptr = Objects.vmptr;
5738 auto &vmobjptridx = Objects.vmptridx;
5739 // Take an entire message (that has already been checked for validity,
5740 // if necessary) and act on it.
5741 auto &Walls = LevelUniqueWallSubsystemState.Walls;
5742 auto &vmwallptr = Walls.vmptr;
5743 switch (static_cast<multiplayer_command_t>(type))
5744 {
5745 case MULTI_POSITION:
5746 multi_do_position(vmobjptridx, pnum, buf);
5747 break;
5748 case MULTI_REAPPEAR:
5749 multi_do_reappear(pnum, buf); break;
5750 case MULTI_FIRE:
5751 case MULTI_FIRE_TRACK:
5752 case MULTI_FIRE_BOMB:
5753 multi_do_fire(vmobjptridx, pnum, buf);
5754 break;
5755 case MULTI_REMOVE_OBJECT:
5756 multi_do_remobj(vmobjptr, buf);
5757 break;
5758 case MULTI_PLAYER_DERES:
5759 multi_do_player_deres(Objects, pnum, buf);
5760 break;
5761 case MULTI_MESSAGE:
5762 multi_do_message(buf); break;
5763 case MULTI_QUIT:
5764 multi_do_quit(buf); break;
5765 case MULTI_CONTROLCEN:
5766 multi_do_controlcen_destroy(imobjptridx, buf);
5767 break;
5768 case MULTI_DROP_WEAPON:
5769 multi_do_drop_weapon(vmobjptr, pnum, buf);
5770 break;
5771 #if defined(DXX_BUILD_DESCENT_II)
5772 case MULTI_VULWPN_AMMO_ADJ:
5773 multi_do_vulcan_weapon_ammo_adjust(vmobjptr, buf);
5774 break;
5775 case MULTI_SOUND_FUNCTION:
5776 multi_do_sound_function(pnum, buf); break;
5777 case MULTI_MARKER:
5778 multi_do_drop_marker(Objects, vmsegptridx, pnum, buf);
5779 break;
5780 case MULTI_DROP_FLAG:
5781 multi_do_drop_flag(pnum, buf); break;
5782 case MULTI_GUIDED:
5783 multi_do_guided(LevelUniqueObjectState, pnum, buf);
5784 break;
5785 case MULTI_STOLEN_ITEMS:
5786 multi_do_stolen_items(buf); break;
5787 case MULTI_WALL_STATUS:
5788 multi_do_wall_status(vmwallptr, buf);
5789 break;
5790 case MULTI_SEISMIC:
5791 multi_do_seismic (buf); break;
5792 case MULTI_LIGHT:
5793 multi_do_light (buf); break;
5794 #endif
5795 case MULTI_ENDLEVEL_START:
5796 multi_do_escape(vmobjptridx, buf);
5797 break;
5798 case MULTI_CLOAK:
5799 multi_do_cloak(vmobjptr, pnum);
5800 break;
5801 case MULTI_DECLOAK:
5802 multi_do_decloak(pnum); break;
5803 case MULTI_DOOR_OPEN:
5804 multi_do_door_open(vmwallptr, buf);
5805 break;
5806 case MULTI_CREATE_EXPLOSION:
5807 multi_do_create_explosion(vmobjptridx, pnum);
5808 break;
5809 case MULTI_CONTROLCEN_FIRE:
5810 multi_do_controlcen_fire(buf); break;
5811 case MULTI_CREATE_POWERUP:
5812 multi_do_create_powerup(vmobjptr, vmsegptridx, pnum, buf);
5813 break;
5814 case MULTI_PLAY_SOUND:
5815 multi_do_play_sound(Objects, pnum, buf);
5816 break;
5817 #if defined(DXX_BUILD_DESCENT_II)
5818 case MULTI_CAPTURE_BONUS:
5819 multi_do_capture_bonus(pnum); break;
5820 case MULTI_ORB_BONUS:
5821 multi_do_orb_bonus(pnum, buf); break;
5822 case MULTI_GOT_FLAG:
5823 multi_do_got_flag(pnum); break;
5824 case MULTI_GOT_ORB:
5825 multi_do_got_orb(pnum); break;
5826 case MULTI_FINISH_GAME:
5827 multi_do_finish_game(buf); break; // do this one regardless of endsequence
5828 #endif
5829 case MULTI_RANK:
5830 multi_do_ranking (pnum, buf); break;
5831 case MULTI_ROBOT_CLAIM:
5832 multi_do_claim_robot(pnum, buf); break;
5833 case MULTI_ROBOT_POSITION:
5834 multi_do_robot_position(pnum, buf); break;
5835 case MULTI_ROBOT_EXPLODE:
5836 multi_do_robot_explode(buf); break;
5837 case MULTI_ROBOT_RELEASE:
5838 multi_do_release_robot(pnum, buf); break;
5839 case MULTI_ROBOT_FIRE:
5840 multi_do_robot_fire(buf); break;
5841 case MULTI_SCORE:
5842 multi_do_score(vmobjptr, pnum, buf);
5843 break;
5844 case MULTI_CREATE_ROBOT:
5845 multi_do_create_robot(Vclip, pnum, buf); break;
5846 case MULTI_TRIGGER:
5847 multi_do_trigger(pnum, buf); break;
5848 #if defined(DXX_BUILD_DESCENT_II)
5849 case MULTI_START_TRIGGER:
5850 multi_do_start_trigger(buf); break;
5851 case MULTI_EFFECT_BLOWUP:
5852 multi_do_effect_blowup(pnum, buf); break;
5853 case MULTI_FLAGS:
5854 multi_do_flags(vmobjptr, pnum, buf);
5855 break;
5856 case MULTI_DROP_BLOB:
5857 multi_do_drop_blob(vmobjptr, pnum);
5858 break;
5859 case MULTI_UPDATE_BUDDY_STATE:
5860 multi_recv_escort_goal(LevelUniqueObjectState.BuddyState, buf);
5861 break;
5862 #endif
5863 case MULTI_BOSS_TELEPORT:
5864 multi_do_boss_teleport(Vclip, pnum, buf); break;
5865 case MULTI_BOSS_CLOAK:
5866 multi_do_boss_cloak(buf); break;
5867 case MULTI_BOSS_START_GATE:
5868 multi_do_boss_start_gate(buf); break;
5869 case MULTI_BOSS_STOP_GATE:
5870 multi_do_boss_stop_gate(buf); break;
5871 case MULTI_BOSS_CREATE_ROBOT:
5872 multi_do_boss_create_robot(pnum, buf); break;
5873 case MULTI_CREATE_ROBOT_POWERUPS:
5874 multi_do_create_robot_powerups(pnum, buf); break;
5875 case MULTI_HOSTAGE_DOOR:
5876 multi_do_hostage_door_status(vmsegptridx, Walls, buf);
5877 break;
5878 case MULTI_SAVE_GAME:
5879 multi_do_save_game(buf); break;
5880 case MULTI_RESTORE_GAME:
5881 multi_do_restore_game(buf); break;
5882 case MULTI_HEARTBEAT:
5883 multi_do_heartbeat (buf); break;
5884 case MULTI_KILLGOALS:
5885 multi_do_kill_goal_counts(vmobjptr, buf);
5886 break;
5887 case MULTI_DO_BOUNTY:
5888 multi_do_bounty( buf ); break;
5889 case MULTI_TYPING_STATE:
5890 multi_do_msgsend_state( buf ); break;
5891 case MULTI_GMODE_UPDATE:
5892 multi_do_gmode_update( buf ); break;
5893 case MULTI_KILL_HOST:
5894 multi_do_kill(Objects, buf);
5895 break;
5896 case MULTI_KILL_CLIENT:
5897 multi_do_kill(Objects, buf);
5898 break;
5899 case MULTI_PLAYER_INV:
5900 multi_do_player_inventory( pnum, buf ); break;
5901 }
5902 }
5903
5904 }
5905
5906 // Following functions convert object to object_rw and back.
5907 // turn object to object_rw for sending
multi_object_to_object_rw(object & obj,object_rw * obj_rw)5908 void multi_object_to_object_rw(object &obj, object_rw *obj_rw)
5909 {
5910 /* Avoid leaking any uninitialized bytes onto the network. Some of
5911 * the unions are incompletely initialized for some branches.
5912 *
5913 * For poison enabled builds, poison any uninitialized fields.
5914 * Everything that the peer should read will be initialized by the
5915 * end of this function.
5916 */
5917 *obj_rw = {};
5918 DXX_POISON_DEFINED_VAR(*obj_rw, 0xfd);
5919 obj_rw->signature = static_cast<uint16_t>(obj.signature);
5920 obj_rw->type = obj.type;
5921 obj_rw->id = obj.id;
5922 obj_rw->control_source = static_cast<uint8_t>(obj.control_source);
5923 obj_rw->movement_source = static_cast<uint8_t>(obj.movement_source);
5924 obj_rw->render_type = obj.render_type;
5925 obj_rw->flags = obj.flags;
5926 obj_rw->segnum = obj.segnum;
5927 obj_rw->pos = obj.pos;
5928 obj_rw->orient = obj.orient;
5929 obj_rw->size = obj.size;
5930 obj_rw->shields = obj.shields;
5931 obj_rw->last_pos = obj.pos;
5932 obj_rw->contains_type = obj.contains_type;
5933 obj_rw->contains_id = obj.contains_id;
5934 obj_rw->contains_count= obj.contains_count;
5935 obj_rw->matcen_creator= obj.matcen_creator;
5936 obj_rw->lifeleft = obj.lifeleft;
5937
5938 switch (typename object::movement_type{obj_rw->movement_source})
5939 {
5940 case object::movement_type::None:
5941 obj_rw->mtype = {};
5942 break;
5943 case object::movement_type::physics:
5944 obj_rw->mtype.phys_info.velocity.x = obj.mtype.phys_info.velocity.x;
5945 obj_rw->mtype.phys_info.velocity.y = obj.mtype.phys_info.velocity.y;
5946 obj_rw->mtype.phys_info.velocity.z = obj.mtype.phys_info.velocity.z;
5947 obj_rw->mtype.phys_info.thrust.x = obj.mtype.phys_info.thrust.x;
5948 obj_rw->mtype.phys_info.thrust.y = obj.mtype.phys_info.thrust.y;
5949 obj_rw->mtype.phys_info.thrust.z = obj.mtype.phys_info.thrust.z;
5950 obj_rw->mtype.phys_info.mass = obj.mtype.phys_info.mass;
5951 obj_rw->mtype.phys_info.drag = obj.mtype.phys_info.drag;
5952 obj_rw->mtype.phys_info.rotvel.x = obj.mtype.phys_info.rotvel.x;
5953 obj_rw->mtype.phys_info.rotvel.y = obj.mtype.phys_info.rotvel.y;
5954 obj_rw->mtype.phys_info.rotvel.z = obj.mtype.phys_info.rotvel.z;
5955 obj_rw->mtype.phys_info.rotthrust.x = obj.mtype.phys_info.rotthrust.x;
5956 obj_rw->mtype.phys_info.rotthrust.y = obj.mtype.phys_info.rotthrust.y;
5957 obj_rw->mtype.phys_info.rotthrust.z = obj.mtype.phys_info.rotthrust.z;
5958 obj_rw->mtype.phys_info.turnroll = obj.mtype.phys_info.turnroll;
5959 obj_rw->mtype.phys_info.flags = obj.mtype.phys_info.flags;
5960 break;
5961
5962 case object::movement_type::spinning:
5963 obj_rw->mtype.spin_rate.x = obj.mtype.spin_rate.x;
5964 obj_rw->mtype.spin_rate.y = obj.mtype.spin_rate.y;
5965 obj_rw->mtype.spin_rate.z = obj.mtype.spin_rate.z;
5966 break;
5967 }
5968
5969 switch (typename object::control_type{obj_rw->control_source})
5970 {
5971 case object::control_type::weapon:
5972 obj_rw->ctype.laser_info.parent_type = obj.ctype.laser_info.parent_type;
5973 obj_rw->ctype.laser_info.parent_num = obj.ctype.laser_info.parent_num;
5974 obj_rw->ctype.laser_info.parent_signature = static_cast<uint16_t>(obj.ctype.laser_info.parent_signature);
5975 if (obj.ctype.laser_info.creation_time - GameTime64 < F1_0*(-18000))
5976 obj_rw->ctype.laser_info.creation_time = F1_0*(-18000);
5977 else
5978 obj_rw->ctype.laser_info.creation_time = obj.ctype.laser_info.creation_time - GameTime64;
5979 obj_rw->ctype.laser_info.last_hitobj = obj.ctype.laser_info.get_last_hitobj();
5980 obj_rw->ctype.laser_info.track_goal = obj.ctype.laser_info.track_goal;
5981 obj_rw->ctype.laser_info.multiplier = obj.ctype.laser_info.multiplier;
5982 break;
5983
5984 case object::control_type::explosion:
5985 obj_rw->ctype.expl_info.spawn_time = obj.ctype.expl_info.spawn_time;
5986 obj_rw->ctype.expl_info.delete_time = obj.ctype.expl_info.delete_time;
5987 obj_rw->ctype.expl_info.delete_objnum = obj.ctype.expl_info.delete_objnum;
5988 obj_rw->ctype.expl_info.attach_parent = obj.ctype.expl_info.attach_parent;
5989 obj_rw->ctype.expl_info.prev_attach = obj.ctype.expl_info.prev_attach;
5990 obj_rw->ctype.expl_info.next_attach = obj.ctype.expl_info.next_attach;
5991 break;
5992
5993 case object::control_type::ai:
5994 {
5995 int i;
5996 obj_rw->ctype.ai_info.behavior = static_cast<uint8_t>(obj.ctype.ai_info.behavior);
5997 for (i = 0; i < MAX_AI_FLAGS; i++)
5998 obj_rw->ctype.ai_info.flags[i] = obj.ctype.ai_info.flags[i];
5999 obj_rw->ctype.ai_info.hide_segment = obj.ctype.ai_info.hide_segment;
6000 obj_rw->ctype.ai_info.hide_index = obj.ctype.ai_info.hide_index;
6001 obj_rw->ctype.ai_info.path_length = obj.ctype.ai_info.path_length;
6002 obj_rw->ctype.ai_info.cur_path_index = obj.ctype.ai_info.cur_path_index;
6003 obj_rw->ctype.ai_info.danger_laser_num = obj.ctype.ai_info.danger_laser_num;
6004 if (obj.ctype.ai_info.danger_laser_num != object_none)
6005 obj_rw->ctype.ai_info.danger_laser_signature = static_cast<uint16_t>(obj.ctype.ai_info.danger_laser_signature);
6006 else
6007 obj_rw->ctype.ai_info.danger_laser_signature = 0;
6008 #if defined(DXX_BUILD_DESCENT_I)
6009 obj_rw->ctype.ai_info.follow_path_start_seg = segment_none;
6010 obj_rw->ctype.ai_info.follow_path_end_seg = segment_none;
6011 #elif defined(DXX_BUILD_DESCENT_II)
6012 obj_rw->ctype.ai_info.dying_sound_playing = obj.ctype.ai_info.dying_sound_playing;
6013 if (obj.ctype.ai_info.dying_start_time == 0) // if bot not dead, anything but 0 will kill it
6014 obj_rw->ctype.ai_info.dying_start_time = 0;
6015 else
6016 obj_rw->ctype.ai_info.dying_start_time = obj.ctype.ai_info.dying_start_time - GameTime64;
6017 #endif
6018 break;
6019 }
6020
6021 case object::control_type::light:
6022 obj_rw->ctype.light_info.intensity = obj.ctype.light_info.intensity;
6023 break;
6024
6025 case object::control_type::powerup:
6026 obj_rw->ctype.powerup_info.count = obj.ctype.powerup_info.count;
6027 #if defined(DXX_BUILD_DESCENT_II)
6028 if (obj.ctype.powerup_info.creation_time - GameTime64 < F1_0*(-18000))
6029 obj_rw->ctype.powerup_info.creation_time = F1_0*(-18000);
6030 else
6031 obj_rw->ctype.powerup_info.creation_time = obj.ctype.powerup_info.creation_time - GameTime64;
6032 obj_rw->ctype.powerup_info.flags = obj.ctype.powerup_info.flags;
6033 #endif
6034 break;
6035 case object::control_type::None:
6036 case object::control_type::flying:
6037 case object::control_type::slew:
6038 case object::control_type::flythrough:
6039 case object::control_type::repaircen:
6040 case object::control_type::morph:
6041 case object::control_type::debris:
6042 case object::control_type::remote:
6043 default:
6044 break;
6045 }
6046
6047 switch (obj_rw->render_type)
6048 {
6049 case RT_MORPH:
6050 case RT_POLYOBJ:
6051 case RT_NONE: // HACK below
6052 {
6053 int i;
6054 if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time.
6055 break;
6056 obj_rw->rtype.pobj_info.model_num = obj.rtype.pobj_info.model_num;
6057 for (i=0;i<MAX_SUBMODELS;i++)
6058 {
6059 obj_rw->rtype.pobj_info.anim_angles[i].p = obj.rtype.pobj_info.anim_angles[i].p;
6060 obj_rw->rtype.pobj_info.anim_angles[i].b = obj.rtype.pobj_info.anim_angles[i].b;
6061 obj_rw->rtype.pobj_info.anim_angles[i].h = obj.rtype.pobj_info.anim_angles[i].h;
6062 }
6063 obj_rw->rtype.pobj_info.subobj_flags = obj.rtype.pobj_info.subobj_flags;
6064 obj_rw->rtype.pobj_info.tmap_override = obj.rtype.pobj_info.tmap_override;
6065 obj_rw->rtype.pobj_info.alt_textures = obj.rtype.pobj_info.alt_textures;
6066 break;
6067 }
6068
6069 case RT_WEAPON_VCLIP:
6070 case RT_HOSTAGE:
6071 case RT_POWERUP:
6072 case RT_FIREBALL:
6073 obj_rw->rtype.vclip_info.vclip_num = obj.rtype.vclip_info.vclip_num;
6074 obj_rw->rtype.vclip_info.frametime = obj.rtype.vclip_info.frametime;
6075 obj_rw->rtype.vclip_info.framenum = obj.rtype.vclip_info.framenum;
6076 break;
6077
6078 case RT_LASER:
6079 break;
6080
6081 }
6082 }
6083
6084 // turn object_rw to object after receiving
multi_object_rw_to_object(object_rw * obj_rw,object & obj)6085 void multi_object_rw_to_object(object_rw *obj_rw, object &obj)
6086 {
6087 obj = {};
6088 DXX_POISON_VAR(obj, 0xfd);
6089 set_object_type(obj, obj_rw->type);
6090 if (obj.type == OBJ_NONE)
6091 return;
6092 obj.signature = object_signature_t{static_cast<uint16_t>(obj_rw->signature)};
6093 obj.id = obj_rw->id;
6094 /* obj->next,obj->prev handled by caller based on segment */
6095 obj.control_source = typename object::control_type{obj_rw->control_source};
6096 obj.movement_source = typename object::movement_type{obj_rw->movement_source};
6097 const auto render_type = obj_rw->render_type;
6098 if (valid_render_type(render_type))
6099 obj.render_type = render_type_t{render_type};
6100 else
6101 {
6102 con_printf(CON_URGENT, "peer sent bogus render type %#x for object %p; using none instead", render_type, &obj);
6103 obj.render_type = RT_NONE;
6104 }
6105 obj.flags = obj_rw->flags;
6106 obj.segnum = obj_rw->segnum;
6107 /* obj->attached_obj cleared by caller */
6108 obj.pos = obj_rw->pos;
6109 obj.orient = obj_rw->orient;
6110 obj.size = obj_rw->size;
6111 obj.shields = obj_rw->shields;
6112 obj.contains_type = obj_rw->contains_type;
6113 obj.contains_id = obj_rw->contains_id;
6114 obj.contains_count= obj_rw->contains_count;
6115 obj.matcen_creator= obj_rw->matcen_creator;
6116 obj.lifeleft = obj_rw->lifeleft;
6117
6118 switch (obj.movement_source)
6119 {
6120 case object::movement_type::None:
6121 break;
6122 case object::movement_type::physics:
6123 obj.mtype.phys_info.velocity.x = obj_rw->mtype.phys_info.velocity.x;
6124 obj.mtype.phys_info.velocity.y = obj_rw->mtype.phys_info.velocity.y;
6125 obj.mtype.phys_info.velocity.z = obj_rw->mtype.phys_info.velocity.z;
6126 obj.mtype.phys_info.thrust.x = obj_rw->mtype.phys_info.thrust.x;
6127 obj.mtype.phys_info.thrust.y = obj_rw->mtype.phys_info.thrust.y;
6128 obj.mtype.phys_info.thrust.z = obj_rw->mtype.phys_info.thrust.z;
6129 obj.mtype.phys_info.mass = obj_rw->mtype.phys_info.mass;
6130 obj.mtype.phys_info.drag = obj_rw->mtype.phys_info.drag;
6131 obj.mtype.phys_info.rotvel.x = obj_rw->mtype.phys_info.rotvel.x;
6132 obj.mtype.phys_info.rotvel.y = obj_rw->mtype.phys_info.rotvel.y;
6133 obj.mtype.phys_info.rotvel.z = obj_rw->mtype.phys_info.rotvel.z;
6134 obj.mtype.phys_info.rotthrust.x = obj_rw->mtype.phys_info.rotthrust.x;
6135 obj.mtype.phys_info.rotthrust.y = obj_rw->mtype.phys_info.rotthrust.y;
6136 obj.mtype.phys_info.rotthrust.z = obj_rw->mtype.phys_info.rotthrust.z;
6137 obj.mtype.phys_info.turnroll = obj_rw->mtype.phys_info.turnroll;
6138 obj.mtype.phys_info.flags = obj_rw->mtype.phys_info.flags;
6139 break;
6140
6141 case object::movement_type::spinning:
6142 obj.mtype.spin_rate.x = obj_rw->mtype.spin_rate.x;
6143 obj.mtype.spin_rate.y = obj_rw->mtype.spin_rate.y;
6144 obj.mtype.spin_rate.z = obj_rw->mtype.spin_rate.z;
6145 break;
6146 }
6147
6148 switch (obj.control_source)
6149 {
6150 case object::control_type::weapon:
6151 obj.ctype.laser_info.parent_type = obj_rw->ctype.laser_info.parent_type;
6152 obj.ctype.laser_info.parent_num = obj_rw->ctype.laser_info.parent_num;
6153 obj.ctype.laser_info.parent_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.laser_info.parent_signature)};
6154 obj.ctype.laser_info.creation_time = obj_rw->ctype.laser_info.creation_time;
6155 {
6156 const auto last_hitobj = obj_rw->ctype.laser_info.last_hitobj;
6157 obj.ctype.laser_info.reset_hitobj(last_hitobj);
6158 }
6159 obj.ctype.laser_info.track_goal = obj_rw->ctype.laser_info.track_goal;
6160 obj.ctype.laser_info.multiplier = obj_rw->ctype.laser_info.multiplier;
6161 #if defined(DXX_BUILD_DESCENT_II)
6162 obj.ctype.laser_info.last_afterburner_time = 0;
6163 #endif
6164 break;
6165
6166 case object::control_type::explosion:
6167 obj.ctype.expl_info.spawn_time = obj_rw->ctype.expl_info.spawn_time;
6168 obj.ctype.expl_info.delete_time = obj_rw->ctype.expl_info.delete_time;
6169 obj.ctype.expl_info.delete_objnum = obj_rw->ctype.expl_info.delete_objnum;
6170 obj.ctype.expl_info.attach_parent = obj_rw->ctype.expl_info.attach_parent;
6171 obj.ctype.expl_info.prev_attach = obj_rw->ctype.expl_info.prev_attach;
6172 obj.ctype.expl_info.next_attach = obj_rw->ctype.expl_info.next_attach;
6173 break;
6174
6175 case object::control_type::ai:
6176 {
6177 int i;
6178 obj.ctype.ai_info.behavior = static_cast<ai_behavior>(obj_rw->ctype.ai_info.behavior);
6179 for (i = 0; i < MAX_AI_FLAGS; i++)
6180 obj.ctype.ai_info.flags[i] = obj_rw->ctype.ai_info.flags[i];
6181 obj.ctype.ai_info.hide_segment = obj_rw->ctype.ai_info.hide_segment;
6182 obj.ctype.ai_info.hide_index = obj_rw->ctype.ai_info.hide_index;
6183 obj.ctype.ai_info.path_length = obj_rw->ctype.ai_info.path_length;
6184 obj.ctype.ai_info.cur_path_index = obj_rw->ctype.ai_info.cur_path_index;
6185 obj.ctype.ai_info.danger_laser_num = obj_rw->ctype.ai_info.danger_laser_num;
6186 if (obj.ctype.ai_info.danger_laser_num != object_none)
6187 obj.ctype.ai_info.danger_laser_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.ai_info.danger_laser_signature)};
6188 #if defined(DXX_BUILD_DESCENT_I)
6189 #elif defined(DXX_BUILD_DESCENT_II)
6190 obj.ctype.ai_info.dying_sound_playing = obj_rw->ctype.ai_info.dying_sound_playing;
6191 obj.ctype.ai_info.dying_start_time = obj_rw->ctype.ai_info.dying_start_time;
6192 #endif
6193 break;
6194 }
6195
6196 case object::control_type::light:
6197 obj.ctype.light_info.intensity = obj_rw->ctype.light_info.intensity;
6198 break;
6199
6200 case object::control_type::powerup:
6201 obj.ctype.powerup_info.count = obj_rw->ctype.powerup_info.count;
6202 #if defined(DXX_BUILD_DESCENT_I)
6203 obj.ctype.powerup_info.creation_time = 0;
6204 obj.ctype.powerup_info.flags = 0;
6205 #elif defined(DXX_BUILD_DESCENT_II)
6206 obj.ctype.powerup_info.creation_time = obj_rw->ctype.powerup_info.creation_time;
6207 obj.ctype.powerup_info.flags = obj_rw->ctype.powerup_info.flags;
6208 #endif
6209 break;
6210 case object::control_type::cntrlcen:
6211 {
6212 // gun points of reactor now part of the object but of course not saved in object_rw. Let's just recompute them.
6213 calc_controlcen_gun_point(obj);
6214 break;
6215 }
6216 case object::control_type::None:
6217 case object::control_type::flying:
6218 case object::control_type::slew:
6219 case object::control_type::flythrough:
6220 case object::control_type::repaircen:
6221 case object::control_type::morph:
6222 case object::control_type::debris:
6223 case object::control_type::remote:
6224 default:
6225 break;
6226 }
6227
6228 switch (obj.render_type)
6229 {
6230 case RT_MORPH:
6231 case RT_POLYOBJ:
6232 case RT_NONE: // HACK below
6233 {
6234 int i;
6235 if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time.
6236 break;
6237 obj.rtype.pobj_info.model_num = obj_rw->rtype.pobj_info.model_num;
6238 for (i=0;i<MAX_SUBMODELS;i++)
6239 {
6240 obj.rtype.pobj_info.anim_angles[i].p = obj_rw->rtype.pobj_info.anim_angles[i].p;
6241 obj.rtype.pobj_info.anim_angles[i].b = obj_rw->rtype.pobj_info.anim_angles[i].b;
6242 obj.rtype.pobj_info.anim_angles[i].h = obj_rw->rtype.pobj_info.anim_angles[i].h;
6243 }
6244 obj.rtype.pobj_info.subobj_flags = obj_rw->rtype.pobj_info.subobj_flags;
6245 obj.rtype.pobj_info.tmap_override = obj_rw->rtype.pobj_info.tmap_override;
6246 obj.rtype.pobj_info.alt_textures = obj_rw->rtype.pobj_info.alt_textures;
6247 break;
6248 }
6249
6250 case RT_WEAPON_VCLIP:
6251 case RT_HOSTAGE:
6252 case RT_POWERUP:
6253 case RT_FIREBALL:
6254 obj.rtype.vclip_info.vclip_num = obj_rw->rtype.vclip_info.vclip_num;
6255 obj.rtype.vclip_info.frametime = obj_rw->rtype.vclip_info.frametime;
6256 obj.rtype.vclip_info.framenum = obj_rw->rtype.vclip_info.framenum;
6257 break;
6258
6259 case RT_LASER:
6260 break;
6261
6262 }
6263 }
6264
show_netgame_info(const netgame_info & netgame)6265 void show_netgame_info(const netgame_info &netgame)
6266 {
6267 struct netgame_info_menu_items
6268 {
6269 enum netgame_menu_info_index
6270 {
6271 game_name,
6272 mission_name,
6273 level_number,
6274 game_mode,
6275 player_counts,
6276 blank_1,
6277 game_options_header,
6278 difficulty,
6279 reactor_life,
6280 max_time,
6281 kill_goal,
6282 blank_2,
6283 duplicate_powerups_header,
6284 duplicate_primaries,
6285 duplicate_secondaries,
6286 #if defined(DXX_BUILD_DESCENT_II)
6287 duplicate_accessories,
6288 #endif
6289 blank_3,
6290 spawn_site_header,
6291 spawn_count,
6292 spawn_invulnerable_time,
6293 blank_4,
6294 objects_allowed_header,
6295 allow_laser_upgrade,
6296 allow_quad_laser,
6297 allow_vulcan_cannon,
6298 allow_spreadfire_cannon,
6299 allow_plasma_cannon,
6300 allow_fusion_cannon,
6301 /* concussion missiles are always allowed, so there is no line
6302 * item for them */
6303 allow_homing_missiles,
6304 allow_proximity_bombs,
6305 allow_smart_missiles,
6306 allow_mega_missiles,
6307 #if defined(DXX_BUILD_DESCENT_II)
6308 allow_super_laser_upgrade,
6309 allow_gauss_cannon,
6310 allow_helix_cannon,
6311 allow_phoenix_cannon,
6312 allow_omega_cannon,
6313 allow_flash_missiles,
6314 allow_guided_missiles,
6315 allow_smart_mines,
6316 allow_mercury_missiles,
6317 allow_earthshaker_missiles,
6318 #endif
6319 allow_cloaking,
6320 allow_invulnerability,
6321 #if defined(DXX_BUILD_DESCENT_II)
6322 allow_afterburner,
6323 allow_ammo_rack,
6324 allow_energy_converter,
6325 allow_headlight,
6326 #endif
6327 blank_5,
6328 objects_granted_header,
6329 grant_laser_level,
6330 grant_quad_laser,
6331 grant_vulcan_cannon,
6332 grant_spreadfire_cannon,
6333 grant_plasma_cannon,
6334 grant_fusion_cannon,
6335 #if defined(DXX_BUILD_DESCENT_II)
6336 grant_gauss_cannon,
6337 grant_helix_cannon,
6338 grant_phoenix_cannon,
6339 grant_omega_cannon,
6340 grant_afterburner,
6341 grant_ammo_rack,
6342 grant_energy_converter,
6343 grant_headlight,
6344 #endif
6345 blank_6,
6346 misc_options_header,
6347 show_all_players_on_automap,
6348 #if defined(DXX_BUILD_DESCENT_II)
6349 allow_marker_camera,
6350 indestructible_lights,
6351 thief_permitted,
6352 thief_steals_energy,
6353 guidebot_enabled,
6354 #endif
6355 bright_player_ships,
6356 enemy_names_on_hud,
6357 friendly_fire,
6358 blank_7,
6359 network_options_header,
6360 packets_per_second,
6361 };
6362 enum
6363 {
6364 count_array_elements = static_cast<unsigned>(packets_per_second) + 1
6365 };
6366 enumerated_array<std::array<char, 50>, count_array_elements, netgame_menu_info_index> lines;
6367 enumerated_array<newmenu_item, count_array_elements, netgame_menu_info_index> menu_items;
6368 netgame_info_menu_items(const netgame_info &netgame)
6369 {
6370 for (auto &&[m, l] : zip(menu_items, lines))
6371 nm_set_item_text(m, l.data());
6372
6373 menu_items[blank_1].text =
6374 menu_items[blank_2].text =
6375 menu_items[blank_3].text =
6376 menu_items[blank_4].text =
6377 menu_items[blank_5].text =
6378 menu_items[blank_6].text =
6379 menu_items[blank_7].text =
6380 const_cast<char *>(" ");
6381
6382 menu_items[game_options_header].text = const_cast<char *>("Game Options:");
6383 menu_items[duplicate_powerups_header].text = const_cast<char *>("Duplicate Powerups:");
6384 menu_items[spawn_site_header].text = const_cast<char *>("Spawn Options:");
6385 menu_items[objects_allowed_header].text = const_cast<char *>("Objects Allowed:");
6386 menu_items[objects_granted_header].text = const_cast<char *>("Objects Granted At Spawn:");
6387 menu_items[misc_options_header].text = const_cast<char *>("Misc. Options:");
6388 menu_items[network_options_header].text = const_cast<char *>("Network Options:");
6389
6390 array_snprintf(lines[game_name], "Game Name\t %s", netgame.game_name.data());
6391 array_snprintf(lines[mission_name], "Mission Name\t %s", netgame.mission_title.data());
6392 array_snprintf(lines[level_number], "Level\t %s%i", (netgame.levelnum < 0) ? "S" : " ", abs(netgame.levelnum));
6393 const auto gamemode = underlying_value(netgame.gamemode);
6394 array_snprintf(lines[game_mode], "Game Mode\t %s", gamemode < GMNames.size() ? GMNames[gamemode] : "INVALID");
6395 array_snprintf(lines[player_counts], "Players\t %i/%i", netgame.numplayers, netgame.max_numplayers);
6396 array_snprintf(lines[difficulty], "Difficulty\t %s", MENU_DIFFICULTY_TEXT(netgame.difficulty));
6397 array_snprintf(lines[reactor_life], "Reactor Life\t %i %s", netgame.control_invul_time / F1_0 / 60, TXT_MINUTES_ABBREV);
6398 array_snprintf(lines[max_time], "Max Time\t %i %s", netgame.PlayTimeAllowed.count() / (F1_0 * 60), TXT_MINUTES_ABBREV);
6399 array_snprintf(lines[kill_goal], "Kill Goal\t %i", netgame.KillGoal * 5);
6400 array_snprintf(lines[duplicate_primaries], "Primaries\t %i", static_cast<int>(netgame.DuplicatePowerups.get_primary_count()));
6401 array_snprintf(lines[duplicate_secondaries], "Secondaries\t %i", static_cast<int>(netgame.DuplicatePowerups.get_secondary_count()));
6402 #if defined(DXX_BUILD_DESCENT_II)
6403 array_snprintf(lines[duplicate_accessories], "Accessories\t %i", static_cast<int>(netgame.DuplicatePowerups.get_accessory_count()));
6404 #endif
6405 array_snprintf(lines[spawn_count], "Use * Furthest Spawn Sites\t %i", netgame.SecludedSpawns+1);
6406 array_snprintf(lines[spawn_invulnerable_time], "Invulnerable Time\t %1.1f sec", static_cast<float>(netgame.InvulAppear) / 2);
6407 array_snprintf(lines[allow_laser_upgrade], "Laser Upgrade\t %s", (netgame.AllowedItems & NETFLAG_DOLASER)?TXT_YES:TXT_NO);
6408 array_snprintf(lines[allow_quad_laser], "Quad Lasers\t %s", (netgame.AllowedItems & NETFLAG_DOQUAD)?TXT_YES:TXT_NO);
6409 array_snprintf(lines[allow_vulcan_cannon], "Vulcan Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOVULCAN)?TXT_YES:TXT_NO);
6410 array_snprintf(lines[allow_spreadfire_cannon], "Spreadfire Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOSPREAD)?TXT_YES:TXT_NO);
6411 array_snprintf(lines[allow_plasma_cannon], "Plasma Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOPLASMA)?TXT_YES:TXT_NO);
6412 array_snprintf(lines[allow_fusion_cannon], "Fusion Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOFUSION)?TXT_YES:TXT_NO);
6413 array_snprintf(lines[allow_homing_missiles], "Homing Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOHOMING)?TXT_YES:TXT_NO);
6414 array_snprintf(lines[allow_proximity_bombs], "Proximity Bombs\t %s", (netgame.AllowedItems & NETFLAG_DOPROXIM)?TXT_YES:TXT_NO);
6415 array_snprintf(lines[allow_smart_missiles], "Smart Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOSMART)?TXT_YES:TXT_NO);
6416 array_snprintf(lines[allow_mega_missiles], "Mega Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOMEGA)?TXT_YES:TXT_NO);
6417 #if defined(DXX_BUILD_DESCENT_II)
6418 array_snprintf(lines[allow_super_laser_upgrade], "Super Lasers\t %s", (netgame.AllowedItems & NETFLAG_DOSUPERLASER)?TXT_YES:TXT_NO);
6419 array_snprintf(lines[allow_gauss_cannon], "Gauss Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOGAUSS)?TXT_YES:TXT_NO);
6420 array_snprintf(lines[allow_helix_cannon], "Helix Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOHELIX)?TXT_YES:TXT_NO);
6421 array_snprintf(lines[allow_phoenix_cannon], "Phoenix Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOPHOENIX)?TXT_YES:TXT_NO);
6422 array_snprintf(lines[allow_omega_cannon], "Omega Cannon\t %s", (netgame.AllowedItems & NETFLAG_DOOMEGA)?TXT_YES:TXT_NO);
6423 array_snprintf(lines[allow_flash_missiles], "Flash Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOFLASH)?TXT_YES:TXT_NO);
6424 array_snprintf(lines[allow_guided_missiles], "Guided Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOGUIDED)?TXT_YES:TXT_NO);
6425 array_snprintf(lines[allow_smart_mines], "Smart Mines\t %s", (netgame.AllowedItems & NETFLAG_DOSMARTMINE)?TXT_YES:TXT_NO);
6426 array_snprintf(lines[allow_mercury_missiles], "Mercury Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOMERCURY)?TXT_YES:TXT_NO);
6427 array_snprintf(lines[allow_earthshaker_missiles], "Earthshaker Missiles\t %s", (netgame.AllowedItems & NETFLAG_DOSHAKER)?TXT_YES:TXT_NO);
6428 #endif
6429 array_snprintf(lines[allow_cloaking], "Cloaking\t %s", (netgame.AllowedItems & NETFLAG_DOCLOAK)?TXT_YES:TXT_NO);
6430 array_snprintf(lines[allow_invulnerability], "Invulnerability\t %s", (netgame.AllowedItems & NETFLAG_DOINVUL)?TXT_YES:TXT_NO);
6431 #if defined(DXX_BUILD_DESCENT_II)
6432 array_snprintf(lines[allow_afterburner], "Afterburners\t %s", (netgame.AllowedItems & NETFLAG_DOAFTERBURNER)?TXT_YES:TXT_NO);
6433 array_snprintf(lines[allow_ammo_rack], "Ammo Rack\t %s", (netgame.AllowedItems & NETFLAG_DOAMMORACK)?TXT_YES:TXT_NO);
6434 array_snprintf(lines[allow_energy_converter], "Energy Converter\t %s", (netgame.AllowedItems & NETFLAG_DOCONVERTER)?TXT_YES:TXT_NO);
6435 array_snprintf(lines[allow_headlight], "Headlight\t %s", (netgame.AllowedItems & NETFLAG_DOHEADLIGHT)?TXT_YES:TXT_NO);
6436 #endif
6437 array_snprintf(lines[grant_laser_level], "Laser Level\t %u", static_cast<unsigned>(map_granted_flags_to_laser_level(netgame.SpawnGrantedItems)) + 1);
6438 array_snprintf(lines[grant_quad_laser], "Quad Lasers\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_QUAD)?TXT_YES:TXT_NO);
6439 array_snprintf(lines[grant_vulcan_cannon], "Vulcan Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_VULCAN)?TXT_YES:TXT_NO);
6440 array_snprintf(lines[grant_spreadfire_cannon], "Spreadfire Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_SPREAD)?TXT_YES:TXT_NO);
6441 array_snprintf(lines[grant_plasma_cannon], "Plasma Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_PLASMA)?TXT_YES:TXT_NO);
6442 array_snprintf(lines[grant_fusion_cannon], "Fusion Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_FUSION)?TXT_YES:TXT_NO);
6443 #if defined(DXX_BUILD_DESCENT_II)
6444 array_snprintf(lines[grant_gauss_cannon], "Gauss Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_GAUSS)?TXT_YES:TXT_NO);
6445 array_snprintf(lines[grant_helix_cannon], "Helix Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_HELIX)?TXT_YES:TXT_NO);
6446 array_snprintf(lines[grant_phoenix_cannon], "Phoenix Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_PHOENIX)?TXT_YES:TXT_NO);
6447 array_snprintf(lines[grant_omega_cannon], "Omega Cannon\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_OMEGA)?TXT_YES:TXT_NO);
6448 array_snprintf(lines[grant_afterburner], "Afterburner\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_AFTERBURNER)?TXT_YES:TXT_NO);
6449 array_snprintf(lines[grant_ammo_rack], "Ammo Rack\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_AMMORACK)?TXT_YES:TXT_NO);
6450 array_snprintf(lines[grant_energy_converter], "Energy Converter\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_CONVERTER)?TXT_YES:TXT_NO);
6451 array_snprintf(lines[grant_headlight], "Headlight\t %s", menu_bit_wrapper(netgame.SpawnGrantedItems.mask, NETGRANT_HEADLIGHT)?TXT_YES:TXT_NO);
6452 #endif
6453 array_snprintf(lines[show_all_players_on_automap], "Show All Players On Automap\t %s", netgame.game_flag.show_on_map?TXT_YES:TXT_NO);
6454 #if defined(DXX_BUILD_DESCENT_II)
6455 array_snprintf(lines[allow_marker_camera], "Allow Marker Camera Views\t %s", netgame.Allow_marker_view?TXT_YES:TXT_NO);
6456 array_snprintf(lines[indestructible_lights], "Indestructible Lights\t %s", netgame.AlwaysLighting?TXT_YES:TXT_NO);
6457 array_snprintf(lines[thief_permitted], "Thief permitted\t %s", (netgame.ThiefModifierFlags & ThiefModifier::Absent) ? TXT_NO : TXT_YES);
6458 array_snprintf(lines[thief_steals_energy], "Thief steals energy weapons\t %s", (netgame.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons) ? TXT_NO : TXT_YES);
6459 array_snprintf(lines[guidebot_enabled], "Guidebot enabled (experimental)\t %s", Netgame.AllowGuidebot ? TXT_YES : TXT_NO);
6460 #endif
6461 array_snprintf(lines[bright_player_ships], "Bright Player Ships\t %s", netgame.BrightPlayers?TXT_YES:TXT_NO);
6462 array_snprintf(lines[enemy_names_on_hud], "Enemy Names On Hud\t %s", netgame.ShowEnemyNames?TXT_YES:TXT_NO);
6463 array_snprintf(lines[friendly_fire], "Friendly Fire (Team, Coop)\t %s", netgame.NoFriendlyFire?TXT_NO:TXT_YES);
6464 array_snprintf(lines[packets_per_second], "Packets Per Second\t %i", netgame.PacketsPerSec);
6465 }
6466 };
6467 struct netgame_info_menu : netgame_info_menu_items, passive_newmenu
6468 {
6469 netgame_info_menu(const netgame_info &netgame, grs_canvas &src) :
6470 netgame_info_menu_items(netgame),
6471 passive_newmenu(menu_title{nullptr}, menu_subtitle{"Netgame Info & Rules"}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(menu_items, 0), src)
6472 {
6473 }
6474 };
6475 auto menu = window_create<netgame_info_menu>(netgame, grd_curscreen->sc_canvas);
6476 (void)menu;
6477 }
6478 }
6479