Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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