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