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 robot code
23
 *
24
 */
25
#include <type_traits>
26
#include "multiinternal.h"
27
 
28
#include <string.h>
29
#include <stdlib.h>
30
#include <stdexcept>
31
 
32
#include "vecmat.h"
33
#include "object.h"
34
#include "multibot.h"
35
#include "game.h"
36
#include "multi.h"
37
#include "laser.h"
38
#include "dxxerror.h"
39
#include "timer.h"
40
#include "text.h"
41
#include "player.h"
42
#include "ai.h"
43
#include "fireball.h"
44
#include "aistruct.h"
45
#include "gameseg.h"
46
#include "robot.h"
47
#include "powerup.h"
48
#include "scores.h"
49
#include "gauges.h"
50
#include "fuelcen.h"
51
#include "morph.h"
52
#include "digi.h"
53
#include "sounds.h"
54
#include "effects.h"
55
#include "automap.h"
56
#include "physics.h" 
57
#include "byteutil.h"
58
#include "escort.h"
59
 
60
#include "compiler-range_for.h"
61
#include "partial_range.h"
62
 
63
namespace dsx {
64
static int multi_add_controlled_robot(vmobjptridx_t objnum, int agitation);
65
}
66
static void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now);
67
static void multi_send_release_robot(vmobjptridx_t objnum);
68
static void multi_delete_controlled_robot(const vmobjptridx_t objnum);
69
 
70
constexpr serial::endian_access::foreign_endian_type serial::endian_access::foreign_endian;
71
constexpr serial::endian_access::little_endian_type serial::endian_access::little_endian;
72
constexpr serial::endian_access::big_endian_type serial::endian_access::big_endian;
73
constexpr serial::endian_access::native_endian_type serial::endian_access::native_endian;
74
 
75
//
76
// Code for controlling robots in multiplayer games
77
//
78
 
79
#define STANDARD_EXPL_DELAY (F1_0/4)
80
#if defined(DXX_BUILD_DESCENT_I)
81
#define MIN_CONTROL_TIME        F1_0*2
82
#define ROBOT_TIMEOUT           F1_0*3
83
 
84
#define MAX_TO_DELETE   67
85
#elif defined(DXX_BUILD_DESCENT_II)
86
#define MIN_CONTROL_TIME        F1_0*1
87
#define ROBOT_TIMEOUT           F1_0*2
88
#endif
89
 
90
#define MIN_TO_ADD      60
91
 
92
std::array<objnum_t, MAX_ROBOTS_CONTROLLED> robot_controlled;
93
std::array<int, MAX_ROBOTS_CONTROLLED> robot_agitation,
94
        robot_send_pending,
95
        robot_fired;
96
std::array<fix64, MAX_ROBOTS_CONTROLLED> robot_controlled_time,
97
        robot_last_send_time,
98
        robot_last_message_time;
99
ubyte robot_fire_buf[MAX_ROBOTS_CONTROLLED][18+3];
100
 
101
#define MULTI_ROBOT_PRIORITY(objnum, pnum) (((objnum % 4) + pnum) % N_players)
102
 
103
//#define MULTI_ROBOT_PRIORITY(objnum, pnum) multi_robot_priority(objnum, pnum)
104
//int multi_robot_priority(int objnum, int pnum)
105
//{
106
//      return( ((objnum % 4) + pnum) % N_players);
107
//}
108
 
109
int multi_can_move_robot(const vmobjptridx_t objnum, int agitation)
110
{
111
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
112
        // Determine whether or not I am allowed to move this robot.
113
        // Claim robot if necessary.
114
 
115
        if (Player_dead_state == player_dead_state::exploded)
116
                return 0;
117
 
118
        if (objnum->type != OBJ_ROBOT)
119
        {
120
#ifndef NDEBUG
121
                Int3();
122
#endif
123
                return 0;
124
        }
125
 
126
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
127
        if (Robot_info[get_robot_id(objnum)].boss_flag && BossUniqueState.Boss_dying)
128
                return 0;
129
        else if (objnum->ctype.ai_info.REMOTE_OWNER == Player_num) // Already my robot!
130
        {
131
                int slot_num = objnum->ctype.ai_info.REMOTE_SLOT_NUM;
132
 
133
      if ((slot_num < 0) || (slot_num >= MAX_ROBOTS_CONTROLLED))
134
                 {
135
                        return 0;
136
                 }
137
 
138
                if (robot_fired[slot_num]) {
139
                        return 0;
140
                }
141
                else {
142
                        robot_agitation[slot_num] = agitation;
143
                        robot_last_message_time[slot_num] = GameTime64;
144
                        return 1;
145
                }
146
        }
147
        else if ((objnum->ctype.ai_info.REMOTE_OWNER != -1) || (agitation < MIN_TO_ADD))
148
        {
149
                if (agitation == ROBOT_FIRE_AGITATION) // Special case for firing at non-player
150
                {
151
                        return 1; // Try to fire at player even tho we're not in control!
152
                }
153
                else
154
                        return 0;
155
        }
156
        else
157
                return multi_add_controlled_robot(objnum, agitation);
158
}
159
 
160
void multi_check_robot_timeout()
161
{
162
        auto &Objects = LevelUniqueObjectState.Objects;
163
        auto &vmobjptridx = Objects.vmptridx;
164
        static fix64 lastcheck = 0;
165
        int i;
166
 
167
        if (GameTime64 > lastcheck + F1_0 || lastcheck > GameTime64)
168
        {
169
                lastcheck = GameTime64;
170
                for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
171
                {
172
                        if (robot_controlled[i] != object_none && robot_last_send_time[i] + ROBOT_TIMEOUT < GameTime64)
173
                        {
174
                                const auto &&robot_objp = vmobjptridx(robot_controlled[i]);
175
                                if (robot_objp->ctype.ai_info.REMOTE_OWNER != Player_num)
176
                                {              
177
                                        robot_controlled[i] = object_none;
178
                                        Int3(); // Non-terminal but Rob is interesting, step over please...
179
                                        return;
180
                                }
181
                                if (robot_send_pending[i])
182
                                        multi_send_robot_position(robot_objp, 1);
183
                                multi_send_release_robot(robot_objp);
184
//                              multi_delete_controlled_robot(robot_controlled[i]);
185
//                              robot_controlled[i] = -1;
186
                        }
187
                }
188
        }                      
189
}
190
 
191
void multi_strip_robots(const int playernum)
192
{
193
        auto &Objects = LevelUniqueObjectState.Objects;
194
        auto &vmobjptr = Objects.vmptr;
195
        auto &vmobjptridx = Objects.vmptridx;
196
        // Grab all robots away from a player 
197
        // (player died or exited the game)
198
        if (Game_mode & GM_MULTI_ROBOTS) {
199
 
200
                if (playernum == Player_num)
201
                {
202
                        range_for (const auto r, robot_controlled)
203
                                if (r != object_none)
204
                                        multi_delete_controlled_robot(vmobjptridx(r));
205
                }
206
 
207
                /* clang-3.7 crashes with a segmentation fault if asked to
208
                 * implicitly convert 1u to std::size_t in partial_range inline
209
                 * chain.  Add a conversion up front, which seems to avoid the
210
                 * bug.  The value must not be cast on non-clang targets because
211
                 * some non-clang targets issue a `-Wuseless-cast` diagnostic
212
                 * for the cast.  Fortunately, clang does not understand
213
                 * `-Wuseless-cast`, so the cast can be used on all clang
214
                 * targets, even ones where it is useless.
215
                 *
216
                 * This crash was first seen in x86_64-pc-linux-gnu-clang-3.7,
217
                 * then later reported by kreator in `Apple LLVM version 7.3.0`
218
                 * on `x86_64-apple-darwin15.6.0`.
219
                 *
220
                 * Comment from kreator regarding the clang crash bug:
221
                 *      This has been reported with Apple, bug report 28247284
222
                 */
223
#ifdef __clang__
224
#define DXX_partial_range_vobjptr_skip_distance static_cast<std::size_t>
225
#else
226
#define DXX_partial_range_vobjptr_skip_distance
227
#endif
228
                range_for (const auto &&objp, partial_range(vmobjptr, DXX_partial_range_vobjptr_skip_distance(1u), vmobjptr.count()))
229
#undef DXX_partial_range_vobjptr_skip_distance
230
                {
231
                        auto &obj = *objp;
232
                        if (obj.type == OBJ_ROBOT && obj.ctype.ai_info.REMOTE_OWNER == playernum)
233
                        {
234
                                assert(obj.control_type == CT_AI || obj.control_type == CT_NONE || obj.control_type == CT_MORPH);
235
                                obj.ctype.ai_info.REMOTE_OWNER = -1;
236
                                if (playernum == Player_num)
237
                                        obj.ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD;
238
                                else
239
                                        obj.ctype.ai_info.REMOTE_SLOT_NUM = 0;
240
                        }
241
                }
242
        }
243
        // Note -- only call this with playernum == Player_num if all other players
244
        // already know that we are clearing house.  This does not send a release
245
        // message for each controlled robot!!
246
 
247
}
248
 
249
namespace dsx {
250
int multi_add_controlled_robot(const vmobjptridx_t objnum, int agitation)
251
{
252
        auto &Objects = LevelUniqueObjectState.Objects;
253
        auto &vcobjptr = Objects.vcptr;
254
        auto &vmobjptridx = Objects.vmptridx;
255
        int i;
256
        int lowest_agitation = INT32_MAX; // MAX POSITIVE INT
257
        int lowest_agitated_bot = -1;
258
        int first_free_robot = -1;
259
 
260
        // Try to add a new robot to the controlled list, return 1 if added, 0 if not.
261
 
262
#if defined(DXX_BUILD_DESCENT_II)
263
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
264
   if (Robot_info[get_robot_id(objnum)].boss_flag) // this is a boss, so make sure he gets a slot
265
                agitation=(agitation*3)+Player_num;  
266
#endif
267
        if (objnum->ctype.ai_info.REMOTE_SLOT_NUM > 0)
268
        {
269
                objnum->ctype.ai_info.REMOTE_SLOT_NUM -= 1;
270
                return 0;
271
        }
272
 
273
        for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
274
        {
275
                if (robot_controlled[i] == object_none || vcobjptr(robot_controlled[i])->type != OBJ_ROBOT) {
276
                        first_free_robot = i;
277
                        break;
278
                }
279
 
280
                if (robot_last_message_time[i] + ROBOT_TIMEOUT < GameTime64) {
281
                        const auto &&robot_objp = vmobjptridx(robot_controlled[i]);
282
                        if (robot_send_pending[i])
283
                                multi_send_robot_position(robot_objp, 1);
284
                        multi_send_release_robot(robot_objp);
285
                        first_free_robot = i;
286
                        break;
287
                }
288
 
289
                if (robot_agitation[i] < lowest_agitation && robot_controlled_time[i] + MIN_CONTROL_TIME < GameTime64)
290
                {
291
                        lowest_agitation = robot_agitation[i];
292
                        lowest_agitated_bot = i;
293
                }
294
        }
295
 
296
        if (first_free_robot != -1)  // Room left for new robots
297
                i = first_free_robot;
298
 
299
        else if ((agitation > lowest_agitation)
300
#if defined(DXX_BUILD_DESCENT_I)
301
                         && (lowest_agitation <= MAX_TO_DELETE)
302
#endif
303
        ) // Replace some old robot with a more agitated one
304
        {
305
                const auto &&robot_objp = vmobjptridx(robot_controlled[lowest_agitated_bot]);
306
                if (robot_send_pending[lowest_agitated_bot])
307
                        multi_send_robot_position(robot_objp, 1);
308
                multi_send_release_robot(robot_objp);
309
 
310
                i = lowest_agitated_bot;
311
        }
312
        else {
313
                return(0); // Sorry, can't squeeze him in!
314
        }
315
 
316
        multi_send_claim_robot(objnum);
317
        robot_controlled[i] = objnum;
318
        robot_agitation[i] = agitation;
319
        objnum->ctype.ai_info.REMOTE_OWNER = Player_num;
320
        objnum->ctype.ai_info.REMOTE_SLOT_NUM = i;
321
        robot_controlled_time[i] = GameTime64;
322
        robot_last_send_time[i] = robot_last_message_time[i] = GameTime64;
323
        return(1);
324
}      
325
}
326
 
327
void multi_delete_controlled_robot(const vmobjptridx_t objnum)
328
{
329
        int i;
330
 
331
        // Delete robot object number objnum from list of controlled robots because it is dead
332
 
333
        for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
334
                if (robot_controlled[i] == objnum)
335
                        break;
336
 
337
        if (i == MAX_ROBOTS_CONTROLLED)
338
                return;
339
 
340
        if (objnum->ctype.ai_info.REMOTE_SLOT_NUM != i)
341
         {
342
          Int3();  // can't release this bot!
343
          return;
344
         }
345
 
346
        objnum->ctype.ai_info.REMOTE_OWNER = -1;
347
        objnum->ctype.ai_info.REMOTE_SLOT_NUM = 0;
348
        robot_controlled[i] = object_none;
349
        robot_send_pending[i] = 0;
350
        robot_fired[i] = 0;
351
}
352
 
353
struct multi_claim_robot
354
{
355
        uint8_t pnum;
356
        int8_t owner;
357
        uint16_t robjnum;
358
};
359
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_CLAIM, multi_claim_robot, b, (b.pnum, b.owner, b.robjnum));
360
 
361
void multi_send_claim_robot(const vmobjptridx_t objnum)
362
{
363
        if (objnum->type != OBJ_ROBOT)
364
                throw std::runtime_error("claiming non-robot"); // See rob
365
        // The AI tells us we should take control of this robot. 
366
        auto r = objnum_local_to_remote(objnum);
367
        multi_serialize_write(2, multi_claim_robot{static_cast<uint8_t>(Player_num), r.owner, r.objnum});
368
}
369
 
370
void multi_send_release_robot(const vmobjptridx_t objnum)
371
{
372
        multi_command<MULTI_ROBOT_RELEASE> multibuf;
373
        short s;
374
        if (objnum->type != OBJ_ROBOT)
375
        {
376
                Int3(); // See rob
377
                return;
378
        }
379
 
380
        multi_delete_controlled_robot(objnum);
381
 
382
        multibuf[1] = Player_num;
383
        s = objnum_local_to_remote(objnum, reinterpret_cast<int8_t *>(&multibuf[4]));
384
        PUT_INTEL_SHORT(&multibuf[2], s);
385
 
386
        multi_send_data(multibuf, 2);
387
}
388
 
389
#define MIN_ROBOT_COM_GAP F1_0/12
390
 
391
int multi_send_robot_frame(int sent)
392
{
393
        auto &Objects = LevelUniqueObjectState.Objects;
394
        auto &vmobjptridx = Objects.vmptridx;
395
        static int last_sent = 0;
396
 
397
        int i;
398
        int rval = 0;
399
 
400
        for (i = 0; i < MAX_ROBOTS_CONTROLLED; i++)
401
        {
402
                int sending = (last_sent+1+i)%MAX_ROBOTS_CONTROLLED;
403
                if (robot_controlled[sending] != object_none && (robot_send_pending[sending] > sent || robot_fired[sending] > sent))
404
                {
405
                        if (robot_send_pending[sending])
406
                        {
407
                                multi_send_robot_position_sub(vmobjptridx(robot_controlled[sending]), (std::exchange(robot_send_pending[sending], 0) > 1));
408
                        }
409
 
410
                        if (robot_fired[sending])
411
                        {
412
                                robot_fired[sending] = 0;
413
                                multi_send_data<MULTI_ROBOT_FIRE>(robot_fire_buf[sending], 18, 1);
414
                        }
415
 
416
                        if (!(Game_mode & GM_NETWORK))
417
                                sent += 1;
418
 
419
                        last_sent = sending;
420
                        rval++;
421
                }
422
        }
423
        Assert((last_sent >= 0) && (last_sent <= MAX_ROBOTS_CONTROLLED));
424
        return(rval);
425
}
426
 
427
#if defined(DXX_BUILD_DESCENT_II)
428
namespace dsx {
429
/*
430
 * The thief bot moves around even when not controlled by a player. Due to its erratic and random behaviour, it's movement will diverge heavily between players and cause it to teleport when a player takes over.
431
 * To counter this, let host update positions when no one controls it OR the client which does.
432
 * Seperated this function to allow the positions being updated more frequently then multi_send_robot_frame (see net_udp_do_frame()).
433
 */
434
void multi_send_thief_frame()
435
{
436
        auto &Objects = LevelUniqueObjectState.Objects;
437
        auto &vmobjptridx = Objects.vmptridx;
438
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
439
        if (!(Game_mode & GM_MULTI_ROBOTS))
440
                return;
441
 
442
        range_for (const auto &&objp, vmobjptridx)
443
        {
444
                if (objp->type == OBJ_ROBOT)
445
                {
446
                        if (robot_is_thief(Robot_info[get_robot_id(objp)]))
447
                        {
448
                                if ((multi_i_am_master() && objp->ctype.ai_info.REMOTE_OWNER == -1) || objp->ctype.ai_info.REMOTE_OWNER == Player_num)
449
                                {
450
                                        multi_send_robot_position_sub(objp,1);
451
                                }
452
                                return;
453
                        }
454
                }
455
        }
456
}
457
 
458
}
459
#endif
460
 
461
void multi_send_robot_position_sub(const vmobjptridx_t objnum, int now)
462
{
463
        multi_command<MULTI_ROBOT_POSITION> multibuf;
464
        int loc = 0;
465
        short s;
466
 
467
        loc += 1;
468
        multibuf[loc] = Player_num;                                            loc += 1;
469
        s = objnum_local_to_remote(objnum, reinterpret_cast<int8_t *>(&multibuf[loc+2]));
470
        PUT_INTEL_SHORT(&multibuf[loc], s);                                      loc += 3; // 5
471
 
472
        quaternionpos qpp{};
473
        create_quaternionpos(qpp, objnum);
474
        PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.w);                  loc += 2;
475
        PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.x);                  loc += 2;
476
        PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.y);                  loc += 2;
477
        PUT_INTEL_SHORT(&multibuf[loc], qpp.orient.z);                  loc += 2;
478
        PUT_INTEL_INT(&multibuf[loc], qpp.pos.x);                               loc += 4;
479
        PUT_INTEL_INT(&multibuf[loc], qpp.pos.y);                               loc += 4;
480
        PUT_INTEL_INT(&multibuf[loc], qpp.pos.z);                               loc += 4;
481
        PUT_INTEL_SHORT(&multibuf[loc], qpp.segment);                   loc += 2;
482
        PUT_INTEL_INT(&multibuf[loc], qpp.vel.x);                               loc += 4;
483
        PUT_INTEL_INT(&multibuf[loc], qpp.vel.y);                               loc += 4;
484
        PUT_INTEL_INT(&multibuf[loc], qpp.vel.z);                               loc += 4;
485
        PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.x);                    loc += 4;
486
        PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.y);                    loc += 4;
487
        PUT_INTEL_INT(&multibuf[loc], qpp.rotvel.z);                    loc += 4; // 46 + 5 = 51
488
 
489
        multi_send_data<MULTI_ROBOT_POSITION>(multibuf, now?1:0);
490
}
491
 
492
void multi_send_robot_position(object &obj, int force)
493
{
494
        // Send robot position to other player(s).  Includes a byte
495
        // value describing whether or not they fired a weapon
496
 
497
        if (!(Game_mode & GM_MULTI))
498
                return;
499
 
500
        if (obj.type != OBJ_ROBOT)
501
        {
502
                Int3(); // See rob
503
                return;
504
        }
505
 
506
        if (obj.ctype.ai_info.REMOTE_OWNER != Player_num)
507
                return;
508
 
509
//      Objects[objnum].phys_info.drag = Robot_info[Objects[objnum].id].drag; // Set drag to normal
510
 
511
        const auto slot = obj.ctype.ai_info.REMOTE_SLOT_NUM;
512
        robot_last_send_time[slot] = GameTime64;
513
        robot_send_pending[slot] = 1+force;
514
        return;
515
}
516
 
517
void multi_send_robot_fire(const vmobjptridx_t obj, int gun_num, const vms_vector &fire)
518
{
519
        multi_command<MULTI_ROBOT_FIRE> multibuf;
520
        // Send robot fire event
521
        int loc = 0;
522
        short s;
523
 
524
                                                                        loc += 1;
525
        multibuf[loc] = Player_num;                                     loc += 1;
526
        s = objnum_local_to_remote(obj, reinterpret_cast<int8_t *>(&multibuf[loc+2]));
527
        PUT_INTEL_SHORT(&multibuf[loc], s);
528
                                                                        loc += 3;
529
        multibuf[loc] = gun_num;                                        loc += 1;
530
        if constexpr (words_bigendian)
531
        {
532
                vms_vector swapped_vec;
533
                swapped_vec.x = INTEL_INT(static_cast<int>(fire.x));
534
                swapped_vec.y = INTEL_INT(static_cast<int>(fire.y));
535
                swapped_vec.z = INTEL_INT(static_cast<int>(fire.z));
536
                memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector));
537
        }
538
        else
539
        {
540
                memcpy(&multibuf[loc], &fire, sizeof(vms_vector));
541
        }
542
                                                                        loc += sizeof(vms_vector); // 12
543
                                                                        // --------------------------
544
                                                                        //      Total = 18
545
 
546
        if (obj->ctype.ai_info.REMOTE_OWNER == Player_num)
547
        {
548
                int slot = obj->ctype.ai_info.REMOTE_SLOT_NUM;
549
                if (slot<0 || slot>=MAX_ROBOTS_CONTROLLED)
550
                {
551
                        return;
552
                }
553
                if (robot_fired[slot] != 0)
554
                {
555
                        // Int3(); // ROB!
556
                        return;
557
                }
558
                memcpy(robot_fire_buf[slot], multibuf.data(), loc);
559
                robot_fired[slot] = 1;
560
        }
561
        else
562
                multi_send_data(multibuf, 1); // Not our robot, send ASAP
563
}
564
 
565
struct multi_explode_robot
566
{
567
        int16_t robj_killer, robj_killed;
568
        int8_t owner_killer, owner_killed;
569
};
570
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_ROBOT_EXPLODE, multi_explode_robot, b, (b.robj_killer, b.robj_killed, b.owner_killer, b.owner_killed));
571
 
572
void multi_send_robot_explode(const imobjptridx_t objnum, objnum_t killer)
573
{
574
        // Send robot explosion event to the other players
575
        const auto k = objnum_local_to_remote(killer);
576
        multi_explode_robot e;
577
        e.robj_killer = k.objnum;
578
        e.owner_killer = k.owner;
579
        const auto d = objnum_local_to_remote(objnum);
580
        e.robj_killed = d.objnum;
581
        e.owner_killed = d.owner;
582
        multi_serialize_write(2, e);
583
 
584
        multi_delete_controlled_robot(objnum);
585
}
586
 
587
void multi_send_create_robot(int station, objnum_t objnum, int type)
588
{
589
        multi_command<MULTI_CREATE_ROBOT> multibuf;
590
        // Send create robot information
591
 
592
        int loc = 0;
593
 
594
        loc += 1;
595
        multibuf[loc] = Player_num;                                                             loc += 1;
596
        multibuf[loc] = static_cast<int8_t>(station);                         loc += 1;
597
        PUT_INTEL_SHORT(&multibuf[loc], objnum);                  loc += 2;
598
        multibuf[loc] = type;                                                                   loc += 1;
599
 
600
        map_objnum_local_to_local(objnum);
601
 
602
        multi_send_data(multibuf, 2);
603
}
604
 
605
namespace {
606
 
607
struct boss_teleport
608
{
609
        objnum_t objnum;
610
        segnum_t where;
611
};
612
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_TELEPORT, boss_teleport, b, (b.objnum, b.where));
613
 
614
struct boss_cloak
615
{
616
        objnum_t objnum;
617
};
618
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CLOAK, boss_cloak, b, (b.objnum));
619
 
620
struct boss_start_gate
621
{
622
        objnum_t objnum;
623
};
624
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_START_GATE, boss_start_gate, b, (b.objnum));
625
 
626
struct boss_stop_gate
627
{
628
        objnum_t objnum;
629
};
630
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_STOP_GATE, boss_stop_gate, b, (b.objnum));
631
 
632
struct boss_create_robot
633
{
634
        objnum_t objnum;
635
        objnum_t objrobot;
636
        segnum_t where;
637
        uint8_t robot_type;
638
};
639
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_BOSS_CREATE_ROBOT, boss_create_robot, b, (b.objnum, b.objrobot, b.where, b.robot_type));
640
 
641
#if defined(DXX_BUILD_DESCENT_II)
642
struct update_buddy_state
643
{
644
        uint8_t Looking_for_marker;
645
        escort_goal_t Escort_special_goal;
646
        int Last_buddy_key;
647
};
648
DEFINE_MULTIPLAYER_SERIAL_MESSAGE(MULTI_UPDATE_BUDDY_STATE, update_buddy_state, b, (b.Looking_for_marker, b.Escort_special_goal, b.Last_buddy_key));
649
#endif
650
 
651
}
652
 
653
template <typename T, typename... Args>
654
static inline void multi_send_boss_action(objnum_t bossobjnum, Args&&... args)
655
{
656
        multi_serialize_write(2, T{bossobjnum, std::forward<Args>(args)...});
657
}
658
 
659
namespace dsx {
660
void multi_send_boss_teleport(const vmobjptridx_t bossobj, const vcsegidx_t where)
661
{
662
        // Boss is up for grabs after teleporting
663
        Assert((bossobj->ctype.ai_info.REMOTE_SLOT_NUM >= 0) && (bossobj->ctype.ai_info.REMOTE_SLOT_NUM < MAX_ROBOTS_CONTROLLED));
664
        multi_delete_controlled_robot(bossobj);
665
#if defined(DXX_BUILD_DESCENT_I)
666
        bossobj->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD; // Hands-off period!
667
#endif
668
        multi_send_boss_action<boss_teleport>(bossobj, where);
669
}
670
}
671
 
672
void multi_send_boss_cloak(objnum_t bossobjnum)
673
{
674
        multi_send_boss_action<boss_cloak>(bossobjnum);
675
}
676
 
677
void multi_send_boss_start_gate(objnum_t bossobjnum)
678
{
679
        multi_send_boss_action<boss_start_gate>(bossobjnum);
680
}
681
 
682
void multi_send_boss_stop_gate(objnum_t bossobjnum)
683
{
684
        multi_send_boss_action<boss_stop_gate>(bossobjnum);
685
}
686
 
687
void multi_send_boss_create_robot(vmobjidx_t bossobjnum, const vmobjptridx_t objrobot)
688
{
689
        map_objnum_local_to_local(objrobot);
690
        multi_send_boss_action<boss_create_robot>(bossobjnum, objrobot, objrobot->segnum, get_robot_id(objrobot));
691
}
692
 
693
#define MAX_ROBOT_POWERUPS 4
694
 
695
namespace dsx {
696
 
697
static void multi_send_create_robot_powerups(const object_base &del_obj)
698
{
699
        multi_command<MULTI_CREATE_ROBOT_POWERUPS> multibuf;
700
        // Send create robot information
701
 
702
        int loc = 0;
703
 
704
        loc += 1;
705
        multibuf[loc] = Player_num;                                                                     loc += 1;
706
        multibuf[loc] = del_obj.contains_count;                                 loc += 1;
707
        multibuf[loc] = del_obj.contains_type;                                  loc += 1;
708
        multibuf[loc] = del_obj.contains_id;                                            loc += 1;
709
        PUT_INTEL_SHORT(&multibuf[loc], del_obj.segnum);                        loc += 2;
710
        if constexpr (words_bigendian)
711
        {
712
                vms_vector swapped_vec;
713
                swapped_vec.x = INTEL_INT(static_cast<int>(del_obj.pos.x));
714
                swapped_vec.y = INTEL_INT(static_cast<int>(del_obj.pos.y));
715
                swapped_vec.z = INTEL_INT(static_cast<int>(del_obj.pos.z));
716
                memcpy(&multibuf[loc], &swapped_vec, sizeof(vms_vector));
717
                loc += 12;
718
        }
719
        else
720
        {
721
                memcpy(&multibuf[loc], &del_obj.pos, sizeof(vms_vector));
722
                loc += 12;
723
        }
724
 
725
        memset(&multibuf[loc], -1, MAX_ROBOT_POWERUPS*sizeof(short));
726
#if defined(DXX_BUILD_DESCENT_II)
727
   if (del_obj.contains_count != Net_create_loc)
728
          Int3();  //Get Jason, a bad thing happened
729
#endif
730
 
731
        if ((Net_create_loc > MAX_ROBOT_POWERUPS) || (Net_create_loc < 1))
732
        {
733
                Int3(); // See Rob
734
        }
735
        range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
736
        {
737
                PUT_INTEL_SHORT(&multibuf[loc], i);
738
                loc += 2;
739
                map_objnum_local_to_local(i);
740
        }
741
 
742
        Net_create_loc = 0;
743
 
744
        multi_send_data(multibuf, 2);
745
}
746
}
747
 
748
void multi_do_claim_robot(const playernum_t pnum, const ubyte *buf)
749
{
750
        auto &Objects = LevelUniqueObjectState.Objects;
751
        auto &vmobjptridx = Objects.vmptridx;
752
        multi_claim_robot b;
753
        multi_serialize_read(buf, b);
754
        auto botnum = objnum_remote_to_local(b.robjnum, b.owner);
755
        if (botnum > Highest_object_index)
756
        {
757
                return;
758
        }
759
 
760
        const auto &&botp = vmobjptridx(botnum);
761
        if (botp->type != OBJ_ROBOT)
762
        {
763
                return;
764
        }
765
 
766
        if (botp->ctype.ai_info.REMOTE_OWNER != -1)
767
        {
768
                if (MULTI_ROBOT_PRIORITY(b.robjnum, pnum) <= MULTI_ROBOT_PRIORITY(b.robjnum, botp->ctype.ai_info.REMOTE_OWNER))
769
                        return;
770
        }
771
 
772
        // Perform the requested change
773
 
774
        if (botp->ctype.ai_info.REMOTE_OWNER == Player_num)
775
        {
776
                multi_delete_controlled_robot(botp);
777
        }
778
 
779
        botp->ctype.ai_info.REMOTE_OWNER = pnum;
780
        botp->ctype.ai_info.REMOTE_SLOT_NUM = 0;
781
}
782
 
783
void multi_do_release_robot(const playernum_t pnum, const ubyte *buf)
784
{
785
        auto &Objects = LevelUniqueObjectState.Objects;
786
        auto &vmobjptr = Objects.vmptr;
787
        short remote_botnum;
788
 
789
        remote_botnum = GET_INTEL_SHORT(buf + 2);
790
        auto botnum = objnum_remote_to_local(remote_botnum, buf[4]);
791
 
792
        if (botnum > Highest_object_index)
793
        {
794
                return;
795
        }
796
 
797
        const auto &&botp = vmobjptr(botnum);
798
        if (botp->type != OBJ_ROBOT)
799
        {
800
                return;
801
        }
802
 
803
        if (botp->ctype.ai_info.REMOTE_OWNER != pnum)
804
        {
805
                return;
806
        }
807
 
808
        // Perform the requested change
809
 
810
        botp->ctype.ai_info.REMOTE_OWNER = -1;
811
        botp->ctype.ai_info.REMOTE_SLOT_NUM = 0;
812
}
813
 
814
void multi_do_robot_position(const playernum_t pnum, const ubyte *buf)
815
{
816
        auto &Objects = LevelUniqueObjectState.Objects;
817
        auto &vmobjptridx = Objects.vmptridx;
818
        // Process robot movement sent by another player
819
 
820
        short remote_botnum;
821
        int loc = 1;
822
 
823
        ;                                                                               loc += 1;
824
 
825
        remote_botnum = GET_INTEL_SHORT(buf + loc);
826
        auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]); loc += 3;
827
 
828
        if (botnum > Highest_object_index)
829
        {
830
                return;
831
        }
832
 
833
        const auto robot = vmobjptridx(botnum);
834
 
835
        if ((robot->type != OBJ_ROBOT) || (robot->flags & OF_EXPLODING)) {
836
                return;
837
        }
838
 
839
        if (robot->ctype.ai_info.REMOTE_OWNER != pnum)
840
        {      
841
                if (robot->ctype.ai_info.REMOTE_OWNER == -1)
842
                {
843
                        // Robot claim packet must have gotten lost, let this player claim it.
844
                        if (robot->ctype.ai_info.REMOTE_SLOT_NUM >= MAX_ROBOTS_CONTROLLED) { // == HANDS_OFF_PERIOD should do the same trick
845
                                robot->ctype.ai_info.REMOTE_OWNER = pnum;
846
                                robot->ctype.ai_info.REMOTE_SLOT_NUM = 0;
847
                        }
848
                        else
849
                                robot->ctype.ai_info.REMOTE_SLOT_NUM++;
850
                }
851
                else
852
                {
853
                        return;
854
                }
855
        }
856
 
857
        set_thrust_from_velocity(robot); // Try to smooth out movement
858
//      Objects[botnum].phys_info.drag = Robot_info[Objects[botnum].id].drag >> 4; // Set drag to low
859
 
860
        quaternionpos qpp{};
861
        qpp.orient.w = GET_INTEL_SHORT(&buf[loc]);                                      loc += 2;
862
        qpp.orient.x = GET_INTEL_SHORT(&buf[loc]);                                      loc += 2;
863
        qpp.orient.y = GET_INTEL_SHORT(&buf[loc]);                                      loc += 2;
864
        qpp.orient.z = GET_INTEL_SHORT(&buf[loc]);                                      loc += 2;
865
        qpp.pos.x = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
866
        qpp.pos.y = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
867
        qpp.pos.z = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
868
        qpp.segment = GET_INTEL_SHORT(&buf[loc]);                                       loc += 2;
869
        qpp.vel.x = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
870
        qpp.vel.y = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
871
        qpp.vel.z = GET_INTEL_INT(&buf[loc]);                                           loc += 4;
872
        qpp.rotvel.x = GET_INTEL_INT(&buf[loc]);                                        loc += 4;
873
        qpp.rotvel.y = GET_INTEL_INT(&buf[loc]);                                        loc += 4;
874
        qpp.rotvel.z = GET_INTEL_INT(&buf[loc]);                                        loc += 4;
875
        extract_quaternionpos(robot, qpp);
876
}
877
 
878
static inline vms_vector calc_gun_point(const object_base &obj, unsigned gun_num)
879
{
880
        vms_vector v;
881
        return calc_gun_point(v, obj, gun_num), v;
882
}
883
 
884
namespace dsx {
885
void multi_do_robot_fire(const uint8_t *const buf)
886
{
887
        auto &Objects = LevelUniqueObjectState.Objects;
888
        auto &vmobjptridx = Objects.vmptridx;
889
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
890
        // Send robot fire event
891
        int loc = 1;
892
        short remote_botnum;
893
        int gun_num;
894
        vms_vector fire;
895
                                                                                        loc += 1; // pnum
896
        remote_botnum = GET_INTEL_SHORT(buf + loc);
897
        auto botnum = objnum_remote_to_local(remote_botnum, buf[loc+2]);                loc += 3;
898
        gun_num = static_cast<int8_t>(buf[loc]);                                                      loc += 1;
899
        memcpy(&fire, buf+loc, sizeof(vms_vector));
900
        fire.x = INTEL_INT(fire.x);
901
        fire.y = INTEL_INT(fire.y);
902
        fire.z = INTEL_INT(fire.z);
903
 
904
        if (botnum > Highest_object_index)
905
                return;
906
 
907
        auto botp = vmobjptridx(botnum);
908
        if (botp->type != OBJ_ROBOT || botp->flags & OF_EXPLODING)
909
                return;
910
 
911
        using pt_weapon = std::pair<vms_vector, weapon_id_type>;
912
        const pt_weapon pw =
913
        // Do the firing
914
                (gun_num == -1
915
#if defined(DXX_BUILD_DESCENT_II)
916
                || gun_num==-2
917
#endif
918
                )
919
                ? pt_weapon(vm_vec_add(botp->pos, fire),
920
#if defined(DXX_BUILD_DESCENT_II)
921
                        gun_num != -1 ? weapon_id_type::SUPERPROX_ID :
922
#endif
923
                        weapon_id_type::PROXIMITY_ID)
924
                : pt_weapon(calc_gun_point(botp, gun_num), get_robot_weapon(Robot_info[get_robot_id(botp)], 1));
925
        Laser_create_new_easy(fire, pw.first, botp, pw.second, 1);
926
}
927
}
928
 
929
namespace dsx {
930
int multi_explode_robot_sub(const vmobjptridx_t robot)
931
{
932
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
933
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
934
        if (robot->type != OBJ_ROBOT) { // Object is robot?
935
                return 0;
936
        }
937
 
938
        if (robot->flags & OF_EXPLODING) { // Object not already exploding
939
                return 0;
940
        }
941
 
942
        // Data seems valid, explode the sucker
943
 
944
        if (Network_send_objects && multi_objnum_is_past(robot))
945
        {
946
                Network_send_objnum = -1;
947
        }
948
 
949
        // Drop non-random KEY powerups locally only!
950
        if ((robot->contains_count > 0) && (robot->contains_type == OBJ_POWERUP) && (Game_mode & GM_MULTI_COOP) && (robot->contains_id >= POW_KEY_BLUE) && (robot->contains_id <= POW_KEY_GOLD))
951
        {
952
                object_create_robot_egg(robot);
953
        }
954
        else if (robot->ctype.ai_info.REMOTE_OWNER == Player_num)
955
        {
956
                multi_drop_robot_powerups(robot);
957
                multi_delete_controlled_robot(robot);
958
        }
959
        else if (robot->ctype.ai_info.REMOTE_OWNER == -1 && multi_i_am_master())
960
        {
961
                multi_drop_robot_powerups(robot);
962
        }
963
        if (robot_is_thief(Robot_info[get_robot_id(robot)]))
964
                drop_stolen_items(robot);
965
 
966
        if (Robot_info[get_robot_id(robot)].boss_flag) {
967
                if (!BossUniqueState.Boss_dying)
968
                        start_boss_death_sequence(robot);      
969
                else
970
                        return (0);
971
        }
972
#if defined(DXX_BUILD_DESCENT_II)
973
        else if (Robot_info[get_robot_id(robot)].death_roll) {
974
                start_robot_death_sequence(robot);
975
        }
976
#endif
977
        else
978
        {
979
#if defined(DXX_BUILD_DESCENT_II)
980
                const auto robot_id = get_robot_id(robot);
981
                if (robot_id == SPECIAL_REACTOR_ROBOT)
982
                        special_reactor_stuff();
983
                if (Robot_info[robot_id].kamikaze)
984
                        explode_object(robot,1);        //      Kamikaze, explode right away, IN YOUR FACE!
985
                else
986
#endif
987
                        explode_object(robot,STANDARD_EXPL_DELAY);
988
   }
989
 
990
        return 1;
991
}
992
}
993
 
994
void multi_do_robot_explode(const uint8_t *const buf)
995
{
996
        auto &Objects = LevelUniqueObjectState.Objects;
997
        auto &vmobjptridx = Objects.vmptridx;
998
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
999
        multi_explode_robot b;
1000
        multi_serialize_read(buf, b);
1001
        auto killer = objnum_remote_to_local(b.robj_killer, b.owner_killer);
1002
        auto botnum = objnum_remote_to_local(b.robj_killed, b.owner_killed);
1003
        // Explode robot controlled by other player
1004
        if (botnum > Highest_object_index)
1005
        {
1006
                return;
1007
        }
1008
 
1009
        const auto robot = vmobjptridx(botnum);
1010
        const auto rval = multi_explode_robot_sub(robot);
1011
        if (!rval)
1012
                return;
1013
 
1014
        ++ Players[0u].num_kills_level;
1015
        ++ Players[0u].num_kills_total;
1016
        if (killer == get_local_player().objnum)
1017
                add_points_to_score(ConsoleObject->ctype.player_info, Robot_info[get_robot_id(robot)].score_value);
1018
}
1019
 
1020
namespace dsx {
1021
 
1022
void multi_do_create_robot(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf)
1023
{
1024
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1025
        auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
1026
        auto &Station = LevelUniqueFuelcenterState.Station;
1027
        auto &Vertices = LevelSharedVertexState.get_vertices();
1028
        const uint_fast32_t fuelcen_num = buf[2];
1029
        int type = buf[5];
1030
 
1031
        FuelCenter *robotcen;
1032
 
1033
        objnum_t objnum;
1034
        objnum = GET_INTEL_SHORT(buf + 3);
1035
 
1036
        if (fuelcen_num >= LevelUniqueFuelcenterState.Num_fuelcenters || pnum >= N_players)
1037
        {
1038
                Int3(); // Bogus data
1039
                return;
1040
        }
1041
 
1042
        robotcen = &Station[fuelcen_num];
1043
 
1044
        // Play effect and sound
1045
 
1046
        const auto &&robotcen_segp = vmsegptridx(robotcen->segnum);
1047
        auto &vcvertptr = Vertices.vcptr;
1048
        const auto &&cur_object_loc = compute_segment_center(vcvertptr, robotcen_segp);
1049
        if (const auto &&obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT))
1050
                extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp);
1051
        if (Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1)
1052
                digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0);
1053
 
1054
        // Set robot center flags, in case we become the master for the next one
1055
 
1056
        robotcen->Flag = 0;
1057
        robotcen->Capacity -= EnergyToCreateOneRobot;
1058
        robotcen->Timer = 0;
1059
 
1060
        const auto &&obj = create_morph_robot(vmsegptridx(robotcen->segnum), cur_object_loc, type);
1061
        if (obj == object_none)
1062
                return; // Cannot create object!
1063
 
1064
        obj->matcen_creator = fuelcen_num | 0x80;
1065
//      extract_orient_from_segment(&obj->orient, &Segments[robotcen->segnum]);
1066
        const auto direction = vm_vec_sub(ConsoleObject->pos, obj->pos );
1067
        vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr);
1068
        morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj);
1069
 
1070
        map_objnum_local_to_remote(obj, objnum, pnum);
1071
 
1072
        Assert(obj->ctype.ai_info.REMOTE_OWNER == -1);
1073
}
1074
 
1075
#if defined(DXX_BUILD_DESCENT_II)
1076
void multi_send_escort_goal(const d_unique_buddy_state &BuddyState)
1077
{
1078
        update_buddy_state b;
1079
        b.Looking_for_marker = static_cast<uint8_t>(BuddyState.Looking_for_marker);
1080
        b.Escort_special_goal = BuddyState.Escort_special_goal;
1081
        b.Last_buddy_key = BuddyState.Last_buddy_key;
1082
        multi_serialize_write(2, b);
1083
}
1084
 
1085
void multi_recv_escort_goal(d_unique_buddy_state &BuddyState, const uint8_t *const buf)
1086
{
1087
        update_buddy_state b;
1088
        multi_serialize_read(buf, b);
1089
        const auto Looking_for_marker = b.Looking_for_marker;
1090
        BuddyState.Looking_for_marker = MarkerState.imobjidx.valid_index(Looking_for_marker)
1091
                ? static_cast<game_marker_index>(Looking_for_marker)
1092
                : game_marker_index::None;
1093
        BuddyState.Escort_special_goal = b.Escort_special_goal;
1094
        BuddyState.Last_buddy_key = b.Last_buddy_key;
1095
        BuddyState.Buddy_messages_suppressed = 0;
1096
        BuddyState.Last_buddy_message_time = GameTime64 - 2 * F1_0;
1097
        BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1098
}
1099
#endif
1100
 
1101
void multi_do_boss_teleport(const d_vclip_array &Vclip, const playernum_t pnum, const ubyte *buf)
1102
{
1103
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1104
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1105
        auto &Objects = LevelUniqueObjectState.Objects;
1106
        auto &Vertices = LevelSharedVertexState.get_vertices();
1107
        auto &vcobjptr = Objects.vcptr;
1108
        auto &vmobjptr = Objects.vmptr;
1109
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1110
        boss_teleport b;
1111
        multi_serialize_read(buf, b);
1112
        const auto &&guarded_boss_obj = Objects.vmptridx.check_untrusted(b.objnum);
1113
        if (!guarded_boss_obj)
1114
                return;
1115
        const auto &&boss_obj = *guarded_boss_obj;
1116
        if ((boss_obj->type != OBJ_ROBOT) || !(Robot_info[get_robot_id(boss_obj)].boss_flag))
1117
        {
1118
                Int3(); // Got boss actions for a robot who's not a boss?
1119
                return;
1120
        }
1121
        const auto &&guarded_teleport_segnum = vmsegptridx.check_untrusted(b.where);
1122
        if (!guarded_teleport_segnum)
1123
                return;
1124
        const auto &&teleport_segnum = *guarded_teleport_segnum;
1125
        auto &vcvertptr = Vertices.vcptr;
1126
        compute_segment_center(vcvertptr, boss_obj->pos, teleport_segnum);
1127
        obj_relink(vmobjptr, vmsegptr, boss_obj, teleport_segnum);
1128
        BossUniqueState.Last_teleport_time = GameTime64;
1129
 
1130
        const auto boss_dir = vm_vec_sub(vcobjptr(vcplayerptr(pnum)->objnum)->pos, boss_obj->pos);
1131
        vm_vector_2_matrix(boss_obj->orient, boss_dir, nullptr, nullptr);
1132
 
1133
        digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, teleport_segnum, 0, boss_obj->pos, 0 , F1_0);
1134
        digi_kill_sound_linked_to_object( boss_obj);
1135
        boss_link_see_sound(boss_obj);
1136
        ai_local                *ailp = &boss_obj->ctype.ai_info.ail;
1137
        ailp->next_fire = 0;
1138
 
1139
        if (boss_obj->ctype.ai_info.REMOTE_OWNER == Player_num)
1140
        {
1141
                multi_delete_controlled_robot(boss_obj);
1142
        }
1143
 
1144
        boss_obj->ctype.ai_info.REMOTE_OWNER = -1; // Boss is up for grabs again!
1145
        boss_obj->ctype.ai_info.REMOTE_SLOT_NUM = 0; // Available immediately!
1146
}
1147
 
1148
void multi_do_boss_cloak(const ubyte *buf)
1149
{
1150
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1151
        auto &Objects = LevelUniqueObjectState.Objects;
1152
        auto &vmobjptridx = Objects.vmptridx;
1153
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1154
        boss_cloak b;
1155
        multi_serialize_read(buf, b);
1156
        const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1157
        if (!guarded_boss_obj)
1158
                return;
1159
        const auto &&boss_obj = *guarded_boss_obj;
1160
        if (boss_obj->type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1161
        {
1162
                Int3(); // Got boss actions for a robot who's not a boss?
1163
                return;
1164
        }
1165
        BossUniqueState.Boss_hit_this_frame = 0;
1166
#if defined(DXX_BUILD_DESCENT_II)
1167
        BossUniqueState.Boss_hit_time = -F1_0*10;
1168
#endif
1169
        BossUniqueState.Boss_cloak_start_time = GameTime64;
1170
        boss_obj->ctype.ai_info.CLOAKED = 1;
1171
}
1172
}
1173
 
1174
void multi_do_boss_start_gate(const ubyte *buf)
1175
{
1176
        auto &Objects = LevelUniqueObjectState.Objects;
1177
        auto &vmobjptridx = Objects.vmptridx;
1178
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1179
        boss_start_gate b;
1180
        multi_serialize_read(buf, b);
1181
        const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1182
        if (!guarded_boss_obj)
1183
                return;
1184
        const object_base &boss_obj = *guarded_boss_obj;
1185
        if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1186
        {
1187
                Int3(); // Got boss actions for a robot who's not a boss?
1188
                return;
1189
        }
1190
        restart_effect(ECLIP_NUM_BOSS);
1191
}
1192
 
1193
void multi_do_boss_stop_gate(const ubyte *buf)
1194
{
1195
        auto &Objects = LevelUniqueObjectState.Objects;
1196
        auto &vmobjptridx = Objects.vmptridx;
1197
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1198
        boss_start_gate b;
1199
        multi_serialize_read(buf, b);
1200
        const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1201
        if (!guarded_boss_obj)
1202
                return;
1203
        const object_base &boss_obj = *guarded_boss_obj;
1204
        if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1205
        {
1206
                Int3(); // Got boss actions for a robot who's not a boss?
1207
                return;
1208
        }
1209
        stop_effect(ECLIP_NUM_BOSS);
1210
}
1211
 
1212
void multi_do_boss_create_robot(const playernum_t pnum, const ubyte *buf)
1213
{
1214
        auto &Objects = LevelUniqueObjectState.Objects;
1215
        auto &vmobjptridx = Objects.vmptridx;
1216
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1217
        boss_create_robot b;
1218
        multi_serialize_read(buf, b);
1219
        const auto &&guarded_boss_obj = vmobjptridx.check_untrusted(b.objnum);
1220
        if (!guarded_boss_obj)
1221
                return;
1222
        const object_base &boss_obj = *guarded_boss_obj;
1223
        if (boss_obj.type != OBJ_ROBOT || !Robot_info[get_robot_id(boss_obj)].boss_flag)
1224
        {
1225
                Int3(); // Got boss actions for a robot who's not a boss?
1226
                return;
1227
        }
1228
        // Do some validity checking
1229
        if (b.objrobot >= MAX_OBJECTS)
1230
        {
1231
                Int3(); // See Rob, bad data in boss gate action message
1232
                return;
1233
        }
1234
        // Gate one in!
1235
        const auto &&robot = gate_in_robot(b.robot_type, vmsegptridx(b.where));
1236
        if (robot != object_none)
1237
                map_objnum_local_to_remote(robot, b.objrobot, pnum);
1238
}
1239
 
1240
void multi_do_create_robot_powerups(const playernum_t pnum, const ubyte *buf)
1241
{
1242
        auto &Objects = LevelUniqueObjectState.Objects;
1243
        auto &vmobjptr = Objects.vmptr;
1244
        // Code to drop remote-controlled robot powerups
1245
 
1246
        int loc = 1;
1247
        ;                                       loc += 1;
1248
        uint8_t contains_count = buf[loc];                      loc += 1;
1249
        uint8_t contains_type = buf[loc];                       loc += 1;
1250
        uint8_t contains_id = buf[loc];                         loc += 1;
1251
        segnum_t segnum = GET_INTEL_SHORT(buf + loc);            loc += 2;
1252
        vms_vector pos;
1253
        memcpy(&pos, &buf[loc], sizeof(pos));      loc += 12;
1254
 
1255
        vms_vector velocity{};
1256
        pos.x = INTEL_INT(pos.x);
1257
        pos.y = INTEL_INT(pos.y);
1258
        pos.z = INTEL_INT(pos.z);
1259
 
1260
        Assert(pnum < N_players);
1261
        Assert (pnum!=Player_num); // What? How'd we send ourselves this?
1262
 
1263
        Net_create_loc = 0;
1264
        d_srand(1245L);
1265
 
1266
        const auto &&egg_objnum = object_create_robot_egg(contains_type, contains_id, contains_count, velocity, pos, vmsegptridx(segnum));
1267
 
1268
        if (egg_objnum == object_none)
1269
                return; // Object buffer full
1270
 
1271
//      Assert(egg_objnum > -1);
1272
        Assert((Net_create_loc > 0) && (Net_create_loc <= MAX_ROBOT_POWERUPS));
1273
 
1274
        range_for (const auto i, partial_const_range(Net_create_objnums, Net_create_loc))
1275
        {
1276
                short s;
1277
 
1278
                s = GET_INTEL_SHORT(buf + loc);
1279
                if ( s != -1)
1280
                        map_objnum_local_to_remote(i, s, pnum);
1281
                else
1282
                        vmobjptr(i)->flags |= OF_SHOULD_BE_DEAD; // Delete objects other guy didn't create one of
1283
                loc += 2;
1284
        }
1285
}
1286
 
1287
void multi_drop_robot_powerups(const vmobjptr_t del_obj)
1288
{
1289
        // Code to handle dropped robot powerups in network mode ONLY!
1290
 
1291
        objnum_t egg_objnum = object_none;
1292
 
1293
        if (del_obj->type != OBJ_ROBOT)
1294
        {
1295
                Int3(); // dropping powerups for non-robot, Rob's fault
1296
                return;
1297
        }
1298
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1299
        auto &robptr = Robot_info[get_robot_id(del_obj)];
1300
 
1301
        Net_create_loc = 0;
1302
 
1303
        if (del_obj->contains_count > 0) {
1304
                //      If dropping a weapon that the player has, drop energy instead, unless it's vulcan, in which case drop vulcan ammo.
1305
                if (del_obj->contains_type == OBJ_POWERUP) {
1306
                        maybe_replace_powerup_with_energy(del_obj);
1307
                        if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems))
1308
                                del_obj->contains_id=POW_SHIELD_BOOST;
1309
 
1310
                        // No key drops in non-coop games!
1311
                        if (!(Game_mode & GM_MULTI_COOP)) {
1312
                                if ((del_obj->contains_id >= POW_KEY_BLUE) && (del_obj->contains_id <= POW_KEY_GOLD))
1313
                                        del_obj->contains_count = 0;
1314
                        }
1315
                }
1316
                d_srand(1245L);
1317
                if (del_obj->contains_count > 0)
1318
                        egg_objnum = object_create_robot_egg(del_obj);
1319
        }
1320
 
1321
        else if (del_obj->ctype.ai_info.REMOTE_OWNER == -1) // No random goodies for robots we weren't in control of
1322
                return;
1323
 
1324
        else if (robptr.contains_count) {
1325
                d_srand(static_cast<fix>(timer_query()));
1326
                if (((d_rand() * 16) >> 15) < robptr.contains_prob) {
1327
                        del_obj->contains_count = ((d_rand() * robptr.contains_count) >> 15) + 1;
1328
                        del_obj->contains_type = robptr.contains_type;
1329
                        del_obj->contains_id = robptr.contains_id;
1330
                        if (del_obj->contains_type == OBJ_POWERUP)
1331
                         {
1332
                                maybe_replace_powerup_with_energy(del_obj);
1333
                                if (!multi_powerup_is_allowed(del_obj->contains_id, Netgame.AllowedItems))
1334
                                        del_obj->contains_id=POW_SHIELD_BOOST;
1335
                         }
1336
 
1337
                        d_srand(1245L);
1338
                        if (del_obj->contains_count > 0)
1339
                                egg_objnum = object_create_robot_egg(del_obj);
1340
                }
1341
        }
1342
 
1343
        if (egg_objnum != object_none) {
1344
                // Transmit the object creation to the other players            
1345
                multi_send_create_robot_powerups(del_obj);
1346
        }
1347
}
1348
 
1349
//      -----------------------------------------------------------------------------
1350
//      Robot *robot got whacked by player player_num and requests permission to do something about it.
1351
//      Note: This function will be called regardless of whether Game_mode is a multiplayer mode, so it
1352
//      should quick-out if not in a multiplayer mode.  On the other hand, it only gets called when a
1353
//      player or player weapon whacks a robot, so it happens rarely.
1354
namespace dsx {
1355
void multi_robot_request_change(const vmobjptridx_t robot, int player_num)
1356
{
1357
        int remote_objnum;
1358
        sbyte dummy;
1359
 
1360
        if (!(Game_mode & GM_MULTI_ROBOTS))
1361
                return;
1362
#if defined(DXX_BUILD_DESCENT_I)
1363
        if (robot->ctype.ai_info.REMOTE_OWNER != Player_num)
1364
                return;
1365
#endif
1366
 
1367
        const auto slot = robot->ctype.ai_info.REMOTE_SLOT_NUM;
1368
 
1369
        if (slot == HANDS_OFF_PERIOD)
1370
        {
1371
                con_printf(CON_DEBUG, "Suppressing debugger trap for hands off robot %hu with player %i", static_cast<vmobjptridx_t::integral_type>(robot), player_num);
1372
                return;
1373
        }
1374
        if ((slot < 0) || (slot >= MAX_ROBOTS_CONTROLLED)) {
1375
                Int3();
1376
                return;
1377
        }
1378
        if (robot_controlled[slot] == object_none)
1379
                return;
1380
        const auto &&rcrobot = robot.absolute_sibling(robot_controlled[slot]);
1381
        remote_objnum = objnum_local_to_remote(robot, &dummy);
1382
        if (remote_objnum < 0)
1383
                return;
1384
 
1385
        if ( (robot_agitation[slot] < 70) || (MULTI_ROBOT_PRIORITY(remote_objnum, player_num) > MULTI_ROBOT_PRIORITY(remote_objnum, Player_num)) || (d_rand() > 0x4400))
1386
        {
1387
                if (robot_send_pending[slot])
1388
                        multi_send_robot_position(rcrobot, -1);
1389
                multi_send_release_robot(rcrobot);
1390
                robot->ctype.ai_info.REMOTE_SLOT_NUM = HANDS_OFF_PERIOD;  // Hands-off period
1391
        }
1392
}
1393
 
1394
}