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
 * Routines to handle collisions
23
 *
24
 */
25
 
26
#include <algorithm>
27
#include <cstdlib>
28
#include <stdexcept>
29
#include <stdlib.h>
30
#include <stdio.h>
31
#include <type_traits>
32
 
33
#include "rle.h"
34
#include "inferno.h"
35
#include "game.h"
36
#include "gr.h"
37
#include "bm.h"
38
#include "3d.h"
39
#include "segment.h"
40
#include "texmap.h"
41
#include "laser.h"
42
#include "key.h"
43
#include "gameseg.h"
44
#include "object.h"
45
#include "physics.h"
46
#include "slew.h"
47
#include "render.h"
48
#include "wall.h"
49
#include "vclip.h"
50
#include "polyobj.h"
51
#include "fireball.h"
52
#include "hudmsg.h"
53
#include "laser.h"
54
#include "dxxerror.h"
55
#include "ai.h"
56
#include "hostage.h"
57
#include "fuelcen.h"
58
#include "sounds.h"
59
#include "robot.h"
60
#include "weapon.h"
61
#include "player.h"
62
#include "gauges.h"
63
#include "powerup.h"
64
#include "newmenu.h"
65
#include "scores.h"
66
#include "effects.h"
67
#include "textures.h"
68
#include "multi.h"
69
#include "cntrlcen.h"
70
#include "newdemo.h"
71
#include "endlevel.h"
72
#include "multibot.h"
73
#include "playsave.h"
74
#include "piggy.h"
75
#include "text.h"
76
#include "automap.h"
77
#include "switch.h"
78
#include "palette.h"
79
#include "gameseq.h"
80
#if DXX_USE_EDITOR
81
#include "editor/editor.h"
82
#endif
83
#include "collide.h"
84
#include "escort.h"
85
 
86
#include "dxxsconf.h"
87
#include "dsx-ns.h"
88
#include "partial_range.h"
89
#include <utility>
90
 
91
using std::min;
92
 
93
#if defined(DXX_BUILD_DESCENT_II)
94
constexpr std::array<uint8_t, MAX_WEAPON_TYPES> Weapon_is_energy{{
95
        1, 1, 1, 1, 1,
96
        1, 1, 1, 0, 1,
97
        1, 0, 1, 1, 1,
98
        0, 1, 0, 0, 1,
99
        1, 0, 0, 1, 1,
100
        1, 1, 1, 0, 1,
101
        1, 1, 0, 1, 1,
102
        1
103
}};
104
#endif
105
 
106
#define WALL_DAMAGE_SCALE (128) // Was 32 before 8:55 am on Thursday, September 15, changed by MK, walls were hurting me more than robots!
107
#define WALL_DAMAGE_THRESHOLD (F1_0/3)
108
#define WALL_LOUDNESS_SCALE (20)
109
#define FORCE_DAMAGE_THRESHOLD (F1_0/3)
110
#define STANDARD_EXPL_DELAY (F1_0/4)
111
 
112
static int check_collision_delayfunc_exec()
113
{
114
        static fix64 last_play_time=0;
115
        if (last_play_time + (F1_0/3) < GameTime64 || last_play_time > GameTime64)
116
        {
117
                last_play_time = GameTime64;
118
                last_play_time -= (d_rand()/2); // add some randomness
119
                return 1;
120
        }
121
        return 0;
122
}
123
 
124
//      -------------------------------------------------------------------------------------------------------------
125
//      The only reason this routine is called (as of 10/12/94) is so Brain guys can open doors.
126
namespace dsx {
127
static void collide_robot_and_wall(fvcwallptr &vcwallptr, object &robot, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &)
128
{
129
        const ubyte robot_id = get_robot_id(robot);
130
#if defined(DXX_BUILD_DESCENT_I)
131
        if (robot_id == ROBOT_BRAIN || robot.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)
132
#elif defined(DXX_BUILD_DESCENT_II)
133
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
134
        auto &Objects = LevelUniqueObjectState.Objects;
135
        auto &vmobjptr = Objects.vmptr;
136
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
137
        auto &robptr = Robot_info[robot_id];
138
        if (robot_id == ROBOT_BRAIN || robot.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM || robot_is_companion(robptr) == 1 || robot.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
139
#endif
140
        {
141
                const auto wall_num = hitseg->shared_segment::sides[hitwall].wall_num;
142
                if (wall_num != wall_none) {
143
                        auto &w = *vcwallptr(wall_num);
144
                        if (w.type == WALL_DOOR && w.keys == KEY_NONE && w.state == WALL_DOOR_CLOSED && !(w.flags & WALL_DOOR_LOCKED))
145
                        {
146
                                wall_open_door(hitseg, hitwall);
147
                        // -- Changed from this, 10/19/95, MK: Don't want buddy getting stranded from player
148
                        //-- } else if ((Robot_info[robot->id].companion == 1) && (Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys != KEY_NONE) && (Walls[wall_num].state == WALL_DOOR_CLOSED) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED)) {
149
                        }
150
#if defined(DXX_BUILD_DESCENT_II)
151
                        else if (robot_is_companion(robptr) && w.type == WALL_DOOR)
152
                        {
153
                                ai_local *const ailp = &robot.ctype.ai_info.ail;
154
                                if (ailp->mode == ai_mode::AIM_GOTO_PLAYER || BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM) {
155
                                        if (w.keys != KEY_NONE)
156
                                        {
157
                                                auto &player_info = get_local_plrobj().ctype.player_info;
158
                                                if (player_info.powerup_flags & static_cast<PLAYER_FLAG>(w.keys))
159
                                                        wall_open_door(hitseg, hitwall);
160
                                        }
161
                                        else if (!(w.flags & WALL_DOOR_LOCKED))
162
                                                wall_open_door(hitseg, hitwall);
163
                                }
164
                        } else if (Robot_info[get_robot_id(robot)].thief) {             //      Thief allowed to go through doors to which player has key.
165
                                if (w.keys != KEY_NONE)
166
                                {
167
                                        auto &player_info = get_local_plrobj().ctype.player_info;
168
                                        if (player_info.powerup_flags & static_cast<PLAYER_FLAG>(w.keys))
169
                                                wall_open_door(hitseg, hitwall);
170
                                }
171
                        }
172
#endif
173
                }
174
        }
175
 
176
        return;
177
}
178
}
179
 
180
//      -------------------------------------------------------------------------------------------------------------
181
 
182
static int apply_damage_to_clutter(const vmobjptridx_t clutter, fix damage)
183
{
184
        if ( clutter->flags&OF_EXPLODING) return 0;
185
 
186
        if (clutter->shields < 0 ) return 0;    //clutter already dead...
187
 
188
        clutter->shields -= damage;
189
 
190
        if (clutter->shields < 0) {
191
                explode_object(clutter,0);
192
                return 1;
193
        } else
194
                return 0;
195
}
196
 
197
//given the specified force, apply damage from that force to an object
198
namespace dsx {
199
static void apply_force_damage(const vmobjptridx_t obj,fix force,const vmobjptridx_t other_obj)
200
{
201
        auto &Objects = LevelUniqueObjectState.Objects;
202
        auto &vcobjptr = Objects.vcptr;
203
        fix damage;
204
 
205
        if (obj->flags & (OF_EXPLODING|OF_SHOULD_BE_DEAD))
206
                return;         //already exploding or dead
207
 
208
        damage = fixdiv(force,obj->mtype.phys_info.mass) / 8;
209
 
210
#if defined(DXX_BUILD_DESCENT_II)
211
        if ((other_obj->type == OBJ_PLAYER) && cheats.monsterdamage)
212
                damage = INT32_MAX;
213
#endif
214
 
215
        if (damage < FORCE_DAMAGE_THRESHOLD)
216
                return;
217
 
218
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
219
        switch (obj->type) {
220
 
221
                case OBJ_ROBOT:
222
                {
223
                        auto &robptr = Robot_info[get_robot_id(obj)];
224
                        const auto other_type = other_obj->type;
225
                        const auto result = apply_damage_to_robot(obj, (robptr.attack_type == 1)
226
                                ? damage / 4
227
                                : damage / 2,
228
                                (other_type == OBJ_WEAPON)
229
                                ? other_obj->ctype.laser_info.parent_num
230
                                : static_cast<objnum_t>(other_obj));
231
                        if (!result)
232
                                break;
233
 
234
                        const auto console = ConsoleObject;
235
                        if ((other_type == OBJ_PLAYER && other_obj == console) ||
236
                                (other_type == OBJ_WEAPON && laser_parent_is_player(vcobjptr, other_obj->ctype.laser_info, *console)))
237
                                add_points_to_score(console->ctype.player_info, robptr.score_value);
238
                        break;
239
                }
240
 
241
                case OBJ_PLAYER:
242
 
243
                        //      If colliding with a claw type robot, do damage proportional to FrameTime because you can collide with those
244
                        //      bots every frame since they don't move.
245
                        if ( (other_obj->type == OBJ_ROBOT) && (Robot_info[get_robot_id(other_obj)].attack_type) )
246
                                damage = fixmul(damage, FrameTime*2);
247
 
248
#if defined(DXX_BUILD_DESCENT_II)
249
                        //      Make trainee easier.
250
                        if (GameUniqueState.Difficulty_level == 0)
251
                                damage /= 2;
252
#endif
253
 
254
                        apply_damage_to_player(obj,other_obj,damage,0);
255
                        break;
256
 
257
                case OBJ_CLUTTER:
258
 
259
                        apply_damage_to_clutter(obj,damage);
260
                        break;
261
 
262
                case OBJ_CNTRLCEN: // Never hits! Reactor does not have MT_PHYSICS - it's stationary! So no force damage here.
263
 
264
                        apply_damage_to_controlcen(obj,damage, other_obj);
265
                        break;
266
 
267
                case OBJ_WEAPON:
268
 
269
                        break; //weapons don't take damage
270
 
271
                default:
272
 
273
                        Int3();
274
 
275
        }
276
}
277
}
278
 
279
//      -----------------------------------------------------------------------------
280
static void bump_this_object(const vmobjptridx_t objp, const vmobjptridx_t other_objp, const vms_vector &force, int damage_flag)
281
{
282
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
283
        if (! (objp->mtype.phys_info.flags & PF_PERSISTENT))
284
        {
285
                if (objp->type == OBJ_PLAYER) {
286
                        vms_vector force2;
287
                        force2.x = force.x/4;
288
                        force2.y = force.y/4;
289
                        force2.z = force.z/4;
290
                        phys_apply_force(objp,force2);
291
                        if (damage_flag && (other_objp->type != OBJ_ROBOT || !robot_is_companion(Robot_info[get_robot_id(other_objp)])))
292
                        {
293
                                auto force_mag = vm_vec_mag_quick(force2);
294
                                apply_force_damage(objp, force_mag, other_objp);
295
                        }
296
                } else if ((objp->type == OBJ_ROBOT) || (objp->type == OBJ_CLUTTER) || (objp->type == OBJ_CNTRLCEN)) {
297
                        if (!Robot_info[get_robot_id(objp)].boss_flag) {
298
                                vms_vector force2;
299
                                const auto Difficulty_level = GameUniqueState.Difficulty_level;
300
                                force2.x = force.x/(4 + Difficulty_level);
301
                                force2.y = force.y/(4 + Difficulty_level);
302
                                force2.z = force.z/(4 + Difficulty_level);
303
 
304
                                phys_apply_force(objp, force);
305
                                phys_apply_rot(objp, force2);
306
                                if (damage_flag) {
307
                                        auto force_mag = vm_vec_mag_quick(force);
308
                                        apply_force_damage(objp, force_mag, other_objp);
309
                                }
310
                        }
311
                }
312
        }
313
}
314
 
315
//      -----------------------------------------------------------------------------
316
//deal with two objects bumping into each other.  Apply force from collision
317
//to each robot.  The flags tells whether the objects should take damage from
318
//the collision.
319
static void bump_two_objects(const vmobjptridx_t obj0,const vmobjptridx_t obj1,int damage_flag)
320
{
321
        const vmobjptridx_t *pt;
322
        if ((obj0->movement_type != MT_PHYSICS && (pt = &obj1, true)) ||
323
                (obj1->movement_type != MT_PHYSICS && (pt = &obj0, true)))
324
        {
325
                object_base &t = *pt;
326
                Assert(t.movement_type == MT_PHYSICS);
327
                const auto force = vm_vec_copy_scale(t.mtype.phys_info.velocity, -t.mtype.phys_info.mass);
328
                phys_apply_force(t,force);
329
                return;
330
        }
331
 
332
        auto force = vm_vec_sub(obj0->mtype.phys_info.velocity,obj1->mtype.phys_info.velocity);
333
        vm_vec_scale2(force,2*fixmul(obj0->mtype.phys_info.mass,obj1->mtype.phys_info.mass),(obj0->mtype.phys_info.mass+obj1->mtype.phys_info.mass));
334
 
335
        bump_this_object(obj1, obj0, force, damage_flag);
336
        bump_this_object(obj0, obj1, vm_vec_negated(force), damage_flag);
337
}
338
 
339
void bump_one_object(object_base &obj0, const vms_vector &hit_dir, fix damage)
340
{
341
        const auto hit_vec = vm_vec_copy_scale(hit_dir, damage);
342
        phys_apply_force(obj0,hit_vec);
343
}
344
 
345
namespace dsx {
346
static void collide_player_and_wall(const vmobjptridx_t playerobj, const fix hitspeed, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &hitpt)
347
{
348
        auto &Objects = LevelUniqueObjectState.Objects;
349
        auto &vmobjptr = Objects.vmptr;
350
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
351
        fix damage;
352
 
353
        if (get_player_id(playerobj) != Player_num) // Execute only for local player
354
                return;
355
 
356
        const auto tmap_num = hitseg->unique_segment::sides[hitwall].tmap_num;
357
 
358
        //      If this wall does damage, don't make *BONK* sound, we'll be making another sound.
359
        if (TmapInfo[tmap_num].damage > 0)
360
                return;
361
 
362
#if defined(DXX_BUILD_DESCENT_I)
363
        const int ForceFieldHit = 0;
364
#elif defined(DXX_BUILD_DESCENT_II)
365
        int ForceFieldHit=0;
366
        if (TmapInfo[tmap_num].flags & TMI_FORCE_FIELD) {
367
                vms_vector force;
368
 
369
                PALETTE_FLASH_ADD(0, 0, 60);    //flash blue
370
 
371
                //knock player around
372
                force.x = 40*(d_rand() - 16384);
373
                force.y = 40*(d_rand() - 16384);
374
                force.z = 40*(d_rand() - 16384);
375
                phys_apply_rot(playerobj, force);
376
 
377
                //make sound
378
                multi_digi_link_sound_to_pos(SOUND_FORCEFIELD_BOUNCE_PLAYER, hitseg, 0, hitpt, 0, f1_0);
379
                ForceFieldHit=1;
380
        }
381
        else
382
#endif
383
        {
384
                auto &player_info = get_local_plrobj().ctype.player_info;
385
                wall_hit_process(player_info.powerup_flags, hitseg, hitwall, 20, get_player_id(playerobj), playerobj);
386
        }
387
 
388
        //      ** Damage from hitting wall **
389
        //      If the player has less than 10% shields, don't take damage from bump
390
#if defined(DXX_BUILD_DESCENT_I)
391
        damage = hitspeed / WALL_DAMAGE_SCALE;
392
#elif defined(DXX_BUILD_DESCENT_II)
393
        // Note: Does quad damage if hit a force field - JL
394
        damage = (hitspeed / WALL_DAMAGE_SCALE) * (ForceFieldHit*8 + 1);
395
 
396
        const auto tmap_num2 = hitseg->unique_segment::sides[hitwall].tmap_num2;
397
 
398
        //don't do wall damage and sound if hit lava or water
399
        if ((TmapInfo[tmap_num].flags & (TMI_WATER|TMI_VOLATILE)) || (tmap_num2 && (TmapInfo[tmap_num2&0x3fff].flags & (TMI_WATER|TMI_VOLATILE))))
400
                damage = 0;
401
#endif
402
 
403
        if (damage >= WALL_DAMAGE_THRESHOLD) {
404
                int     volume;
405
                volume = (hitspeed-(WALL_DAMAGE_SCALE*WALL_DAMAGE_THRESHOLD)) / WALL_LOUDNESS_SCALE ;
406
 
407
                create_awareness_event(playerobj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);
408
 
409
                if ( volume > F1_0 )
410
                        volume = F1_0;
411
                if (volume > 0 && !ForceFieldHit) {  // uhhhgly hack
412
                        multi_digi_link_sound_to_pos(SOUND_PLAYER_HIT_WALL, hitseg, 0, hitpt, 0, volume);
413
                }
414
 
415
                auto &player_info = playerobj->ctype.player_info;
416
                if (!(player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE))
417
                        if (playerobj->shields > f1_0*10 || ForceFieldHit)
418
                                apply_damage_to_player( playerobj, playerobj, damage, 0 );
419
 
420
                // -- No point in doing this unless we compute a reasonable hitpt.  Currently it is just the player's position. --MK, 01/18/96
421
                // -- if (!(TmapInfo[Segments[hitseg].unique_segment::sides[hitwall].tmap_num].flags & TMI_FORCE_FIELD)) {
422
                // --   vms_vector      hitpt1;
423
                // --   int                     hitseg1;
424
                // --
425
                // --           vm_vec_avg(&hitpt1, hitpt, &Objects[Players[Player_num].objnum].pos);
426
                // --   hitseg1 = find_point_seg(&hitpt1, Objects[Players[Player_num].objnum].segnum);
427
                // --   if (hitseg1 != -1)
428
                // --           object_create_explosion( hitseg, hitpt, Weapon_info[0].impact_size, Weapon_info[0].wall_hit_vclip );
429
                // -- }
430
 
431
        }
432
 
433
        return;
434
}
435
}
436
 
437
static fix64    Last_volatile_scrape_time = 0;
438
static fix64    Last_volatile_scrape_sound_time = 0;
439
 
440
namespace dsx {
441
 
442
#if defined(DXX_BUILD_DESCENT_I)
443
static
444
#endif
445
//see if wall is volatile or water
446
//if volatile, cause damage to player
447
//returns 1=lava, 2=water
448
volatile_wall_result check_volatile_wall(const vmobjptridx_t obj, const unique_side &side)
449
{
450
        Assert(obj->type==OBJ_PLAYER);
451
 
452
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
453
        const auto &ti = TmapInfo[side.tmap_num];
454
        const fix d = ti.damage;
455
        if (d > 0
456
#if defined(DXX_BUILD_DESCENT_II)
457
                || (ti.flags & TMI_WATER)
458
#endif
459
                )
460
        {
461
#if defined(DXX_BUILD_DESCENT_II)
462
                if (get_player_id(obj) == Player_num)
463
#endif
464
                {
465
                        if (!((GameTime64 > Last_volatile_scrape_time + DESIGNATED_GAME_FRAMETIME) || (GameTime64 < Last_volatile_scrape_time)))
466
                                return volatile_wall_result::none;
467
                        Last_volatile_scrape_time = GameTime64;
468
 
469
#if defined(DXX_BUILD_DESCENT_II)
470
                        if (d > 0)
471
#endif
472
                        {
473
                                fix damage = fixmul(d,((FrameTime>DESIGNATED_GAME_FRAMETIME)?FrameTime:DESIGNATED_GAME_FRAMETIME));
474
 
475
#if defined(DXX_BUILD_DESCENT_II)
476
                                if (GameUniqueState.Difficulty_level == 0)
477
                                        damage /= 2;
478
#endif
479
 
480
                                if (!(obj->ctype.player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE))
481
                                        apply_damage_to_player( obj, obj, damage, 0 );
482
 
483
                                PALETTE_FLASH_ADD(f2i(damage*4), 0, 0); //flash red
484
                        }
485
 
486
                        obj->mtype.phys_info.rotvel.x = (d_rand() - 16384)/2;
487
                        obj->mtype.phys_info.rotvel.z = (d_rand() - 16384)/2;
488
                }
489
 
490
                return
491
#if defined(DXX_BUILD_DESCENT_II)
492
                        (d <= 0)
493
                        ? volatile_wall_result::water
494
                        :
495
#endif
496
                        volatile_wall_result::lava;
497
        }
498
        else
499
         {
500
                return volatile_wall_result::none;
501
         }
502
}
503
 
504
//this gets called when an object is scraping along the wall
505
bool scrape_player_on_wall(const vmobjptridx_t obj, const vmsegptridx_t hitseg, const unsigned hitside, const vms_vector &hitpt)
506
{
507
        if (obj->type != OBJ_PLAYER || get_player_id(obj) != Player_num)
508
                return false;
509
 
510
        const auto type = check_volatile_wall(obj, hitseg->unique_segment::sides[hitside]);
511
        if (type != volatile_wall_result::none)
512
        {
513
                if ((GameTime64 > Last_volatile_scrape_sound_time + F1_0/4) || (GameTime64 < Last_volatile_scrape_sound_time)) {
514
                        Last_volatile_scrape_sound_time = GameTime64;
515
                        const auto sound =
516
#if defined(DXX_BUILD_DESCENT_II)
517
                                (type != volatile_wall_result::lava)
518
                                ? SOUND_SHIP_IN_WATER
519
                                :
520
#endif
521
                                SOUND_VOLATILE_WALL_HISS;
522
                        multi_digi_link_sound_to_pos(sound, hitseg, 0, hitpt, 0, F1_0);
523
                }
524
 
525
                auto hit_dir = hitseg->shared_segment::sides[hitside].normals[0];
526
 
527
                vm_vec_scale_add2(hit_dir, make_random_vector(), F1_0/8);
528
                vm_vec_normalize_quick(hit_dir);
529
                bump_one_object(obj, hit_dir, F1_0*8);
530
 
531
                return true;
532
        }
533
        return false;
534
}
535
 
536
#if defined(DXX_BUILD_DESCENT_II)
537
static int effect_parent_is_guidebot(fvcobjptr &vcobjptr, const laser_parent &laser)
538
{
539
        if (laser.parent_type != OBJ_ROBOT)
540
                return 0;
541
        const auto &&robot = vcobjptr(laser.parent_num);
542
        if (robot->type != OBJ_ROBOT)
543
                return 0;
544
        if (robot->signature != laser.parent_signature)
545
                /* parent replaced, no idea what it once was */
546
                return 0;
547
        const auto robot_id = get_robot_id(robot);
548
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
549
        auto &robptr = Robot_info[robot_id];
550
        return robot_is_companion(robptr);
551
}
552
#endif
553
 
554
//if an effect is hit, and it can blow up, then blow it up
555
//returns true if it blew up
556
int check_effect_blowup(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const d_vclip_array &Vclip, const vmsegptridx_t seg, unsigned side, const vms_vector &pnt, const laser_parent &blower, int force_blowup_flag, int remote)
557
{
558
        auto &Effects = LevelUniqueEffectsClipState.Effects;
559
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
560
        int tm;
561
 
562
#if defined(DXX_BUILD_DESCENT_I)
563
        static constexpr std::integral_constant<int, 0> force_blowup_flag{};
564
#elif defined(DXX_BUILD_DESCENT_II)
565
        auto &Objects = LevelUniqueObjectState.Objects;
566
        auto &vcobjptr = Objects.vcptr;
567
        const auto wall_num = seg->shared_segment::sides[side].wall_num;
568
 
569
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
570
        auto &vcwallptr = Walls.vcptr;
571
        const auto is_trigger = wall_num != wall_none && vcwallptr(wall_num)->trigger != trigger_none;
572
        if (is_trigger)
573
        {
574
                if (force_blowup_flag || (
575
                                (Game_mode & GM_MULTI)
576
        // If this wall has a trigger and the blower-upper is not the player or the buddy, abort!
577
        // For Multiplayer perform an additional check to see if it's a local-player hit. If a remote player hits, a packet is expected (remote 1) which would be followed by MULTI_TRIGGER to ensure sync with the switch and the actual trigger.
578
                                ? (!(blower.parent_type == OBJ_PLAYER && (blower.parent_num == get_local_player().objnum || remote)))
579
                                : !(blower.parent_type == OBJ_PLAYER || effect_parent_is_guidebot(vcobjptr, blower)))
580
                        )
581
                        return(0);
582
        }
583
#endif
584
 
585
        if ((tm=seg->unique_segment::sides[side].tmap_num2) != 0) {
586
                int tmf = tm&0xc000;            //tm flags
587
                tm &= 0x3fff;                   //tm without flags
588
 
589
                const auto ec = TmapInfo[tm].eclip_num;
590
                unsigned db = 0;
591
#if defined(DXX_BUILD_DESCENT_I)
592
                if (ec != eclip_none && (db = Effects[ec].dest_bm_num) != ~0u && !(Effects[ec].flags & EF_ONE_SHOT))
593
#elif defined(DXX_BUILD_DESCENT_II)
594
                //check if it's an animation (monitor) or casts light
595
                if ((ec != eclip_none && ((db = Effects[ec].dest_bm_num) != ~0u && !(Effects[ec].flags & EF_ONE_SHOT))) || (ec == eclip_none && (TmapInfo[tm].destroyed != -1)))
596
#endif
597
                {
598
                        const grs_bitmap *bm = &GameBitmaps[Textures[tm].index];
599
                        int x=0,y=0,t;
600
 
601
                        PIGGY_PAGE_IN(Textures[tm]);
602
 
603
                        //this can be blown up...did we hit it?
604
 
605
                        if (!force_blowup_flag) {
606
                                const auto hitpoint = find_hitpoint_uv(pnt,seg,side,0); //evil: always say face zero
607
                                auto &u = hitpoint.u;
608
                                auto &v = hitpoint.v;
609
 
610
                                x = static_cast<unsigned>(f2i(u*bm->bm_w)) % bm->bm_w;
611
                                y = static_cast<unsigned>(f2i(v*bm->bm_h)) % bm->bm_h;
612
 
613
                                switch (tmf) {          //adjust for orientation of paste-on
614
                                        case 0x0000:    break;
615
                                        case 0x4000:    t=y; y=x; x=bm->bm_w-t-1; break;
616
                                        case 0x8000:    y=bm->bm_h-y-1; x=bm->bm_w-x-1; break;
617
                                        case 0xc000:    t=x; x=y; y=bm->bm_h-t-1; break;
618
                                }
619
                                bm = rle_expand_texture(*bm);
620
                        }
621
 
622
#if defined(DXX_BUILD_DESCENT_I)
623
                        if (gr_gpixel (*bm, x, y) != TRANSPARENCY_COLOR)
624
#elif defined(DXX_BUILD_DESCENT_II)
625
                        if (force_blowup_flag || (bm->bm_data[y*bm->bm_w+x] != TRANSPARENCY_COLOR))
626
#endif
627
                        {               //not trans, thus on effect
628
                                int vc,sound_num;
629
 
630
#if defined(DXX_BUILD_DESCENT_II)
631
                                if ((Game_mode & GM_MULTI) && Netgame.AlwaysLighting)
632
                                        if (!(ec != eclip_none && db != -1 && !(Effects[ec].flags & EF_ONE_SHOT)))
633
                                                return 0;
634
 
635
                                //note: this must get called before the texture changes,
636
                                //because we use the light value of the texture to change
637
                                //the static light in the segment
638
                                subtract_light(LevelSharedDestructibleLightState, seg, side);
639
 
640
                                // we blew up something connected to a trigger. Send it to others!
641
                                if ((Game_mode & GM_MULTI) && is_trigger && !remote && !force_blowup_flag)
642
                                        multi_send_effect_blowup(seg, side, pnt);
643
#endif
644
                                if (Newdemo_state == ND_STATE_RECORDING)
645
                                        newdemo_record_effect_blowup( seg, side, pnt);
646
 
647
                                fix dest_size;
648
                                if (ec != eclip_none)
649
                                {
650
                                        dest_size = Effects[ec].dest_size;
651
                                        vc = Effects[ec].dest_vclip;
652
                                }
653
                                else
654
                                {
655
                                        dest_size = i2f(20);
656
                                        vc = 3;
657
                                }
658
 
659
                                object_create_explosion( seg, pnt, dest_size, vc );
660
 
661
#if defined(DXX_BUILD_DESCENT_II)
662
                                if (ec != eclip_none && db != -1 && !(Effects[ec].flags & EF_ONE_SHOT))
663
#endif
664
                                {
665
 
666
                                        if ((sound_num = Vclip[vc].sound_num) != -1)
667
                                                digi_link_sound_to_pos( sound_num, seg, 0, pnt,  0, F1_0 );
668
 
669
                                        if ((sound_num=Effects[ec].sound_num)!=-1)              //kill sound
670
                                                digi_kill_sound_linked_to_segment(seg,side,sound_num);
671
 
672
                                        if (Effects[ec].dest_eclip!=-1 && Effects[Effects[ec].dest_eclip].segnum == segment_none)
673
                                        {
674
                                                int bm_num;
675
                                                eclip *new_ec;
676
 
677
                                                new_ec = &Effects[Effects[ec].dest_eclip];
678
                                                bm_num = new_ec->changing_wall_texture;
679
 
680
                                                new_ec->time_left = new_ec->vc.frame_time;
681
                                                new_ec->frame_count = 0;
682
                                                new_ec->segnum = seg;
683
                                                new_ec->sidenum = side;
684
                                                new_ec->flags |= EF_ONE_SHOT;
685
                                                new_ec->dest_bm_num = Effects[ec].dest_bm_num;
686
 
687
                                                assert(bm_num != 0);
688
                                                auto &tmap_num2 = seg->unique_segment::sides[side].tmap_num2;
689
                                                assert(tmap_num2 != 0);
690
                                                tmap_num2 = bm_num | tmf;               //replace with destroyed
691
 
692
                                        }
693
                                        else {
694
                                                assert(db != 0);
695
                                                auto &tmap_num2 = seg->unique_segment::sides[side].tmap_num2;
696
                                                assert(tmap_num2 != 0);
697
                                                tmap_num2 = db | tmf;           //replace with destroyed
698
                                        }
699
                                }
700
#if defined(DXX_BUILD_DESCENT_II)
701
                                else {
702
                                        seg->unique_segment::sides[side].tmap_num2 = TmapInfo[tm].destroyed | tmf;
703
 
704
                                        //assume this is a light, and play light sound
705
                                        digi_link_sound_to_pos( SOUND_LIGHT_BLOWNUP, seg, 0, pnt,  0, F1_0 );
706
                                }
707
#endif
708
 
709
                                return 1;               //blew up!
710
                        }
711
                }
712
        }
713
 
714
        return 0;               //didn't blow up
715
}
716
}
717
 
718
//these gets added to the weapon's values when the weapon hits a volitle wall
719
#define VOLATILE_WALL_EXPL_STRENGTH i2f(10)
720
#define VOLATILE_WALL_IMPACT_SIZE       i2f(3)
721
#define VOLATILE_WALL_DAMAGE_FORCE      i2f(5)
722
#define VOLATILE_WALL_DAMAGE_RADIUS     i2f(30)
723
 
724
// int Show_seg_and_side = 0;
725
 
726
namespace dsx {
727
static window_event_result collide_weapon_and_wall(
728
#if defined(DXX_BUILD_DESCENT_II)
729
        const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
730
#endif
731
        object_array &Objects, fvmsegptridx &vmsegptridx, const vmobjptridx_t weapon, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &hitpt)
732
{
733
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
734
        auto &imobjptridx = Objects.imptridx;
735
        auto &vcobjptr = Objects.vcptr;
736
        auto &vmobjptr = Objects.vmptr;
737
        int blew_up;
738
        int playernum;
739
        auto result = window_event_result::handled;
740
 
741
#if defined(DXX_BUILD_DESCENT_I)
742
        if (weapon->mtype.phys_info.flags & PF_BOUNCE)
743
                return window_event_result::ignored;
744
        auto &uhitside = hitseg->unique_segment::sides[hitwall];
745
#elif defined(DXX_BUILD_DESCENT_II)
746
        auto &vcobjptridx = Objects.vcptridx;
747
        if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
748
                if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
749
                        return window_event_result::ignored;
750
 
751
        //      If this is a guided missile and it strikes fairly directly, clear bounce flag.
752
        if (get_weapon_id(weapon) == weapon_id_type::GUIDEDMISS_ID) {
753
                fix     dot;
754
 
755
                dot = vm_vec_dot(weapon->orient.fvec, hitseg->shared_segment::sides[hitwall].normals[0]);
756
                if (dot < -F1_0/6) {
757
                        weapon->mtype.phys_info.flags &= ~PF_BOUNCE;
758
                }
759
        }
760
 
761
        auto &uhitside = hitseg->unique_segment::sides[hitwall];
762
        //if an energy weapon hits a forcefield, let it bounce
763
        if ((TmapInfo[uhitside.tmap_num].flags & TMI_FORCE_FIELD) &&
764
                 !(weapon->type == OBJ_WEAPON && Weapon_info[get_weapon_id(weapon)].energy_usage==0)) {
765
 
766
                //make sound
767
                multi_digi_link_sound_to_pos(SOUND_FORCEFIELD_BOUNCE_WEAPON, hitseg, 0, hitpt, 0, f1_0);
768
                return window_event_result::ignored;    //bail here. physics code will bounce this object
769
        }
770
 
771
        #ifndef NDEBUG
772
        if (keyd_pressed[KEY_LAPOSTRO])
773
                if (weapon->ctype.laser_info.parent_num == get_local_player().objnum) {
774
                        //      MK: Real pain when you need to know a seg:side and you've got quad lasers.
775
                        HUD_init_message(HM_DEFAULT, "Hit at segment = %hu, side = %i", static_cast<vmsegptridx_t::integral_type>(hitseg), hitwall);
776
                        if (get_weapon_id(weapon) < 4)
777
                                subtract_light(LevelSharedDestructibleLightState, hitseg, hitwall);
778
                        else if (get_weapon_id(weapon) == weapon_id_type::FLARE_ID)
779
                                add_light(LevelSharedDestructibleLightState, hitseg, hitwall);
780
                }
781
 
782
                //@@#ifdef EDITOR
783
                //@@Cursegp = &Segments[hitseg];
784
                //@@Curside = hitwall;
785
                //@@#endif
786
        #endif
787
#endif
788
 
789
        if ((weapon->mtype.phys_info.velocity.x == 0) && (weapon->mtype.phys_info.velocity.y == 0) && (weapon->mtype.phys_info.velocity.z == 0)) {
790
                Int3(); //      Contact Matt: This is impossible.  A weapon with 0 velocity hit a wall, which doesn't move.
791
                return window_event_result::ignored;
792
        }
793
 
794
        blew_up = check_effect_blowup(LevelSharedDestructibleLightState, Vclip, hitseg, hitwall, hitpt, weapon->ctype.laser_info, 0, 0);
795
 
796
#if defined(DXX_BUILD_DESCENT_I)
797
        const unsigned robot_escort = 0;
798
#elif defined(DXX_BUILD_DESCENT_II)
799
        const unsigned robot_escort = effect_parent_is_guidebot(vcobjptr, weapon->ctype.laser_info);
800
        if (robot_escort) {
801
                /* Guidebot is always associated with the host */
802
                playernum = 0;
803
        }
804
        else
805
#endif
806
        {
807
                auto &objp = *vcobjptr(weapon->ctype.laser_info.parent_num);
808
                if (objp.type == OBJ_PLAYER)
809
                        playernum = get_player_id(objp);
810
                else
811
                        playernum = -1;         //not a player (thus a robot)
812
        }
813
 
814
        auto &plrobj = get_local_plrobj();
815
        auto &player_info = plrobj.ctype.player_info;
816
#if defined(DXX_BUILD_DESCENT_II)
817
        if (blew_up) {          //could be a wall switch
818
                //for wall triggers, always say that the player shot it out.  This is
819
                //because robots can shoot out wall triggers, and so the trigger better
820
                //take effect
821
                //      NO -- Changed by MK, 10/18/95.  We don't want robots blowing puzzles.  Only player or buddy can open!
822
                result = check_trigger(hitseg, hitwall, plrobj, vcobjptridx(weapon->ctype.laser_info.parent_num), 1);
823
        }
824
 
825
        if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
826
                smega_rock_stuff();
827
#endif
828
 
829
        const auto wall_type = wall_hit_process(player_info.powerup_flags, hitseg, hitwall, weapon->shields, playernum, weapon);
830
 
831
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
832
        // Wall is volatile if either tmap 1 or 2 is volatile
833
        if ((TmapInfo[uhitside.tmap_num].flags & TMI_VOLATILE) ||
834
                (uhitside.tmap_num2 && (TmapInfo[uhitside.tmap_num2 & 0x3fff].flags & TMI_VOLATILE))
835
                )
836
        {
837
                weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
838
 
839
                //we've hit a volatile wall
840
 
841
                digi_link_sound_to_pos( SOUND_VOLATILE_WALL_HIT,hitseg, 0, hitpt, 0, F1_0 );
842
 
843
                //      New by MK: If powerful badass, explode as badass, not due to lava, fixes megas being wimpy in lava.
844
                if (wi->damage_radius >= VOLATILE_WALL_DAMAGE_RADIUS/2) {
845
                        explode_badass_weapon(weapon, hitpt);
846
                }
847
                else
848
                {
849
                        object_create_badass_explosion( weapon, hitseg, hitpt,
850
                                wi->impact_size + VOLATILE_WALL_IMPACT_SIZE,
851
                //for most weapons, use volatile wall hit.  For mega, use its special vclip
852
                                (get_weapon_id(weapon) == weapon_id_type::MEGA_ID) ? wi->robot_hit_vclip : VCLIP_VOLATILE_WALL_HIT,
853
                                wi->strength[Difficulty_level]/4+VOLATILE_WALL_EXPL_STRENGTH,   //      diminished by mk on 12/08/94, i was doing 70 damage hitting lava on lvl 1.
854
                                wi->damage_radius+VOLATILE_WALL_DAMAGE_RADIUS,
855
                                wi->strength[Difficulty_level]/2+VOLATILE_WALL_DAMAGE_FORCE,
856
                                imobjptridx(weapon->ctype.laser_info.parent_num));
857
                }
858
 
859
                weapon->flags |= OF_SHOULD_BE_DEAD;             //make flares die in lava
860
 
861
        }
862
#if defined(DXX_BUILD_DESCENT_II)
863
        else if ((TmapInfo[uhitside.tmap_num].flags & TMI_WATER) ||
864
                        (uhitside.tmap_num2 && (TmapInfo[uhitside.tmap_num2 & 0x3fff].flags & TMI_WATER))
865
                        )
866
        {
867
                weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
868
 
869
                //we've hit water
870
 
871
                //      MK: 09/13/95: Badass in water is 1/2 normal intensity.
872
                if ( Weapon_info[get_weapon_id(weapon)].matter ) {
873
 
874
                        digi_link_sound_to_pos( SOUND_MISSILE_HIT_WATER,hitseg, 0, hitpt, 0, F1_0 );
875
 
876
                        if ( Weapon_info[get_weapon_id(weapon)].damage_radius ) {
877
 
878
                                digi_link_sound_to_object(SOUND_BADASS_EXPLOSION, weapon, 0, F1_0, sound_stack::allow_stacking);
879
 
880
                                //      MK: 09/13/95: Badass in water is 1/2 normal intensity.
881
                                object_create_badass_explosion( weapon, hitseg, hitpt,
882
                                        wi->impact_size/2,
883
                                        wi->robot_hit_vclip,
884
                                        wi->strength[Difficulty_level]/4,
885
                                        wi->damage_radius,
886
                                        wi->strength[Difficulty_level]/2,
887
                                        imobjptridx(weapon->ctype.laser_info.parent_num));
888
                        }
889
                        else
890
                                object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, Weapon_info[get_weapon_id(weapon)].wall_hit_vclip);
891
 
892
                } else {
893
                        digi_link_sound_to_pos( SOUND_LASER_HIT_WATER,hitseg, 0, hitpt, 0, F1_0 );
894
                        object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, VCLIP_WATER_HIT);
895
                }
896
 
897
                weapon->flags |= OF_SHOULD_BE_DEAD;             //make flares die in water
898
 
899
        }
900
#endif
901
        else {
902
 
903
#if defined(DXX_BUILD_DESCENT_II)
904
                if (weapon->mtype.phys_info.flags & PF_BOUNCE) {
905
 
906
                        //do special bound sound & effect
907
 
908
                }
909
                else
910
#endif
911
                {
912
 
913
                        //if it's not the player's weapon, or it is the player's and there
914
                        //is no wall, and no blowing up monitor, then play sound
915
                        if ((weapon->ctype.laser_info.parent_type != OBJ_PLAYER) ||     ((hitseg->shared_segment::sides[hitwall].wall_num == wall_none || wall_type == wall_hit_process_t::WHP_NOT_SPECIAL) && !blew_up))
916
                                if ((Weapon_info[get_weapon_id(weapon)].wall_hit_sound > -1 ) && (!(weapon->flags & OF_SILENT)))
917
                                digi_link_sound_to_pos(Weapon_info[get_weapon_id(weapon)].wall_hit_sound, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
918
 
919
                        if ( Weapon_info[get_weapon_id(weapon)].wall_hit_vclip > -1 )   {
920
                                if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
921
#if defined(DXX_BUILD_DESCENT_I)
922
                                        explode_badass_weapon(weapon, weapon->pos);
923
#elif defined(DXX_BUILD_DESCENT_II)
924
                                        explode_badass_weapon(weapon, hitpt);
925
#endif
926
                                else
927
                                        object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, Weapon_info[get_weapon_id(weapon)].wall_hit_vclip);
928
                        }
929
                }
930
        }
931
 
932
        //      If weapon fired by player or companion...
933
        if (( weapon->ctype.laser_info.parent_type== OBJ_PLAYER ) || robot_escort) {
934
 
935
                if (!(weapon->flags & OF_SILENT) && (weapon->ctype.laser_info.parent_num == get_local_player().objnum))
936
                        create_awareness_event(weapon, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);                      // object "weapon" can attract attention to player
937
 
938
//      We now allow flares to open doors.
939
                {
940
#if defined(DXX_BUILD_DESCENT_I)
941
                        if (get_weapon_id(weapon) != weapon_id_type::FLARE_ID)
942
                                weapon->flags |= OF_SHOULD_BE_DEAD;
943
#elif defined(DXX_BUILD_DESCENT_II)
944
                        if (((get_weapon_id(weapon) != weapon_id_type::FLARE_ID) || (weapon->ctype.laser_info.parent_type != OBJ_PLAYER)) && !(weapon->mtype.phys_info.flags & PF_BOUNCE))
945
                                weapon->flags |= OF_SHOULD_BE_DEAD;
946
 
947
                        //don't let flares stick in force fields
948
                        if ((get_weapon_id(weapon) == weapon_id_type::FLARE_ID) && (TmapInfo[uhitside.tmap_num].flags & TMI_FORCE_FIELD))
949
                                weapon->flags |= OF_SHOULD_BE_DEAD;
950
#endif
951
 
952
                        if (!(weapon->flags & OF_SILENT)) {
953
                                switch (wall_type) {
954
 
955
                                        case wall_hit_process_t::WHP_NOT_SPECIAL:
956
                                                //should be handled above
957
                                                //digi_link_sound_to_pos( Weapon_info[weapon->id].wall_hit_sound, weapon->segnum, 0, &weapon->pos, 0, F1_0 );
958
                                                break;
959
 
960
                                        case wall_hit_process_t::WHP_NO_KEY:
961
                                                //play special hit door sound (if/when we get it)
962
                                                multi_digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
963
 
964
                                                break;
965
 
966
                                        case wall_hit_process_t::WHP_BLASTABLE:
967
                                                //play special blastable wall sound (if/when we get it)
968
#if defined(DXX_BUILD_DESCENT_II)
969
                                                if ((Weapon_info[get_weapon_id(weapon)].wall_hit_sound > -1 ) && (!(weapon->flags & OF_SILENT)))
970
#endif
971
                                                        digi_link_sound_to_pos(SOUND_WEAPON_HIT_BLASTABLE, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
972
                                                break;
973
 
974
                                        case wall_hit_process_t::WHP_DOOR:
975
                                                //don't play anything, since door open sound will play
976
                                                break;
977
                                }
978
                        } // else
979
                } // else {
980
                        //      if (weapon->lifeleft <= 0)
981
                        //      weapon->flags |= OF_SHOULD_BE_DEAD;
982
                // }
983
 
984
        } else {
985
                // This is a robot's laser
986
#if defined(DXX_BUILD_DESCENT_II)
987
                if (!(weapon->mtype.phys_info.flags & PF_BOUNCE))
988
#endif
989
                        weapon->flags |= OF_SHOULD_BE_DEAD;
990
        }
991
 
992
        return result;
993
}
994
}
995
 
996
static void collide_debris_and_wall(const vmobjptridx_t debris, const unique_segment &hitseg, const unsigned hitwall, const vms_vector &)
997
{
998
        auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
999
        if (!PERSISTENT_DEBRIS || TmapInfo[hitseg.sides[hitwall].tmap_num].damage)
1000
                explode_object(debris,0);
1001
}
1002
 
1003
//      -------------------------------------------------------------------------------------------------------------------
1004
static void collide_robot_and_robot(const vmobjptridx_t robot1, const vmobjptridx_t robot2, const vms_vector &)
1005
{
1006
        bump_two_objects(robot1, robot2, 1);
1007
        return;
1008
}
1009
 
1010
static void collide_robot_and_controlcen(object_base &obj_robot, const object_base &obj_cc, const vms_vector &)
1011
{
1012
        assert(obj_cc.type == OBJ_CNTRLCEN);
1013
        assert(obj_robot.type == OBJ_ROBOT);
1014
        const auto &&hitvec = vm_vec_normalized(vm_vec_sub(obj_cc.pos, obj_robot.pos));
1015
        bump_one_object(obj_robot, hitvec, 0);
1016
}
1017
 
1018
namespace dsx {
1019
static void collide_robot_and_player(const vmobjptridx_t robot, const vmobjptridx_t playerobj, const vms_vector &collision_point)
1020
{
1021
#if defined(DXX_BUILD_DESCENT_II)
1022
        int     steal_attempt = 0;
1023
 
1024
        if (robot->flags&OF_EXPLODING)
1025
                return;
1026
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1027
#endif
1028
 
1029
        if (get_player_id(playerobj) == Player_num) {
1030
#if defined(DXX_BUILD_DESCENT_II)
1031
                auto &robptr = Robot_info[get_robot_id(robot)];
1032
                if (robot_is_companion(robptr)) //      Player and companion don't collide.
1033
                        return;
1034
                if (robptr.kamikaze) {
1035
                        apply_damage_to_robot(robot, robot->shields+1, playerobj);
1036
                        if (playerobj == ConsoleObject)
1037
                                add_points_to_score(playerobj->ctype.player_info, robptr.score_value);
1038
                }
1039
 
1040
                if (robot_is_thief(robptr)) {
1041
                        static fix64 Last_thief_hit_time;
1042
                        ai_local                *ailp = &robot->ctype.ai_info.ail;
1043
                        if (ailp->mode == ai_mode::AIM_THIEF_ATTACK)
1044
                        {
1045
                                Last_thief_hit_time = GameTime64;
1046
                                attempt_to_steal_item(robot, playerobj);
1047
                                steal_attempt = 1;
1048
                        } else if (GameTime64 - Last_thief_hit_time < F1_0*2)
1049
                                return;         //      ZOUNDS!  BRILLIANT!  Thief not collide with player if not stealing!
1050
                                                                // NO!  VERY DUMB!  makes thief look very stupid if player hits him while cloaked! -AP
1051
                        else
1052
                                Last_thief_hit_time = GameTime64;
1053
                }
1054
#endif
1055
 
1056
                create_awareness_event(playerobj, player_awareness_type_t::PA_PLAYER_COLLISION, LevelUniqueRobotAwarenessState);                        // object robot can attract attention to player
1057
                do_ai_robot_hit_attack(robot, playerobj, collision_point);
1058
                do_ai_robot_hit(robot, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION);
1059
        }
1060
        else
1061
        {
1062
                multi_robot_request_change(robot, get_player_id(playerobj));
1063
                return; // only controlling player should make damage otherwise we might juggle robot back and forth, killing it instantly
1064
        }
1065
 
1066
        if (check_collision_delayfunc_exec())
1067
        {
1068
                const auto &&player_segp = Segments.vmptridx(playerobj->segnum);
1069
                const auto &&collision_seg = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, player_segp);
1070
 
1071
#if defined(DXX_BUILD_DESCENT_II)
1072
                // added this if to remove the bump sound if it's the thief.
1073
                // A "steal" sound was added and it was getting obscured by the bump. -AP 10/3/95
1074
                //      Changed by MK to make this sound unless the robot stole.
1075
                if ((!steal_attempt) && !Robot_info[get_robot_id(robot)].energy_drain)
1076
#endif
1077
                        digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, player_segp, 0, collision_point, 0, F1_0);
1078
 
1079
                if (collision_seg != segment_none)
1080
                        object_create_explosion( collision_seg, collision_point, Weapon_info[0].impact_size, Weapon_info[0].wall_hit_vclip );
1081
        }
1082
 
1083
        bump_two_objects(robot, playerobj, 1);
1084
        return;
1085
}
1086
}
1087
 
1088
// Provide a way for network message to instantly destroy the control center
1089
// without awarding points or anything.
1090
 
1091
//      if controlcen == NULL, that means don't do the explosion because the control center
1092
//      was actually in another object.
1093
void net_destroy_controlcen(const imobjptridx_t controlcen)
1094
{
1095
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1096
        if (LevelUniqueControlCenterState.Control_center_destroyed != 1)
1097
        {
1098
                do_controlcen_destroyed_stuff(controlcen);
1099
 
1100
                if ((controlcen != object_none) && !(controlcen->flags&(OF_EXPLODING|OF_DESTROYED))) {
1101
                        digi_link_sound_to_pos(SOUND_CONTROL_CENTER_DESTROYED, vmsegptridx(controlcen->segnum), 0, controlcen->pos, 0, F1_0);
1102
                        explode_object(controlcen,0);
1103
                }
1104
        }
1105
 
1106
}
1107
 
1108
//      -----------------------------------------------------------------------------
1109
void apply_damage_to_controlcen(const vmobjptridx_t controlcen, fix damage, const object &who)
1110
{
1111
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1112
        int     whotype;
1113
 
1114
        //      Only allow a player to damage the control center.
1115
        whotype = who.type;
1116
        if (whotype != OBJ_PLAYER) {
1117
                return;
1118
        }
1119
 
1120
        if ((Game_mode & GM_MULTI) &&
1121
                !(Game_mode & GM_MULTI_COOP))
1122
        {
1123
                auto &player = get_local_player();
1124
                const auto t = i2f(player.hours_level * 3600) + player.time_level;
1125
                if (t < Netgame.control_invul_time)
1126
                {
1127
                if (get_player_id(who) == Player_num) {
1128
                        const auto r = f2i(Netgame.control_invul_time - t);
1129
                        HUD_init_message(HM_DEFAULT, "%s %d:%02d.", TXT_CNTRLCEN_INVUL, r / 60, r % 60);
1130
                }
1131
                return;
1132
                }
1133
        }
1134
 
1135
        if (get_player_id(who) == Player_num) {
1136
                LevelUniqueControlCenterState.Control_center_been_hit = 1;
1137
                ai_do_cloak_stuff();
1138
        }
1139
 
1140
        if ( controlcen->shields >= 0 )
1141
                controlcen->shields -= damage;
1142
 
1143
        if ( (controlcen->shields < 0) && !(controlcen->flags&(OF_EXPLODING|OF_DESTROYED)) ) {
1144
                do_controlcen_destroyed_stuff(controlcen);
1145
 
1146
                if (Game_mode & GM_MULTI) {
1147
                        if (get_player_id(who) == Player_num)
1148
                                add_points_to_score(ConsoleObject->ctype.player_info, CONTROL_CEN_SCORE);
1149
                        multi_send_destroy_controlcen(controlcen, get_player_id(who) );
1150
                }
1151
                else
1152
                        add_points_to_score(ConsoleObject->ctype.player_info, CONTROL_CEN_SCORE);
1153
 
1154
                digi_link_sound_to_pos(SOUND_CONTROL_CENTER_DESTROYED, vmsegptridx(controlcen->segnum), 0, controlcen->pos, 0, F1_0);
1155
 
1156
                explode_object(controlcen,0);
1157
        }
1158
}
1159
 
1160
static void collide_player_and_controlcen(const vmobjptridx_t playerobj, const vmobjptridx_t controlcen, const vms_vector &collision_point)
1161
{
1162
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1163
        if (get_player_id(playerobj) == Player_num) {
1164
                LevelUniqueControlCenterState.Control_center_been_hit = 1;
1165
                ai_do_cloak_stuff();                            //      In case player cloaked, make control center know where he is.
1166
        }
1167
 
1168
        if (check_collision_delayfunc_exec())
1169
                digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vmsegptridx(playerobj->segnum), 0, collision_point, 0, F1_0);
1170
 
1171
        bump_two_objects(controlcen, playerobj, 1);
1172
 
1173
        return;
1174
}
1175
 
1176
#if defined(DXX_BUILD_DESCENT_II)
1177
static void collide_player_and_marker(const object_base &playerobj, const vmobjptridx_t marker, const vms_vector &)
1178
{
1179
        if (get_player_id(playerobj)==Player_num) {
1180
                int drawn;
1181
 
1182
                const auto marker_id = get_marker_id(marker);
1183
                auto &msg = MarkerState.message[marker_id];
1184
                if (Game_mode & GM_MULTI)
1185
                {
1186
                        const auto marker_owner = get_marker_owner(Game_mode, marker_id, Netgame.max_numplayers);
1187
                        drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %s: %s", static_cast<const char *>(vcplayerptr(marker_owner)->callsign), &msg[0]);
1188
                }
1189
                else
1190
                {
1191
                        const auto displayed_marker_id = static_cast<unsigned>(marker_id) + 1;
1192
                        if (msg[0])
1193
                                drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %d: %s", displayed_marker_id, &msg[0]);
1194
                        else
1195
                                drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %d", displayed_marker_id);
1196
           }
1197
 
1198
                if (drawn)
1199
                        digi_play_sample( SOUND_MARKER_HIT, F1_0 );
1200
 
1201
                detect_escort_goal_accomplished(marker);
1202
   }
1203
}
1204
#endif
1205
 
1206
//      If a persistent weapon and other object is not a weapon, weaken it, else kill it.
1207
//      If both objects are weapons, weaken the weapon.
1208
namespace dsx {
1209
static void maybe_kill_weapon(object_base &weapon, const object_base &other_obj)
1210
{
1211
        if (is_proximity_bomb_or_player_smart_mine_or_placed_mine(get_weapon_id(weapon))) {
1212
                weapon.flags |= OF_SHOULD_BE_DEAD;
1213
                return;
1214
        }
1215
 
1216
#if defined(DXX_BUILD_DESCENT_I)
1217
        if (weapon.mtype.phys_info.flags & PF_PERSISTENT || other_obj.type == OBJ_WEAPON)
1218
#elif defined(DXX_BUILD_DESCENT_II)
1219
        //      Changed, 10/12/95, MK: Make weapon-weapon collisions always kill both weapons if not persistent.
1220
        //      Reason: Otherwise you can't use proxbombs to detonate incoming homing missiles (or mega missiles).
1221
        if (weapon.mtype.phys_info.flags & PF_PERSISTENT)
1222
#endif
1223
        {
1224
                //      Weapons do a lot of damage to weapons, other objects do much less.
1225
                if (!(weapon.mtype.phys_info.flags & PF_PERSISTENT)) {
1226
                        if (other_obj.type == OBJ_WEAPON)
1227
                                weapon.shields -= other_obj.shields/2;
1228
                        else
1229
                                weapon.shields -= other_obj.shields/4;
1230
 
1231
                        if (weapon.shields <= 0) {
1232
                                weapon.shields = 0;
1233
                                weapon.flags |= OF_SHOULD_BE_DEAD;
1234
                        }
1235
                }
1236
        } else
1237
                weapon.flags |= OF_SHOULD_BE_DEAD;
1238
 
1239
// --   if ((weapon->mtype.phys_info.flags & PF_PERSISTENT) || (other_obj->type == OBJ_WEAPON)) {
1240
// --           //      Weapons do a lot of damage to weapons, other objects do much less.
1241
// --           if (!(weapon->mtype.phys_info.flags & PF_PERSISTENT)) {
1242
// --                   if (other_obj->type == OBJ_WEAPON)
1243
// --                           weapon->shields -= other_obj->shields/2;
1244
// --                   else
1245
// --                           weapon->shields -= other_obj->shields/4;
1246
// --
1247
// --                   if (weapon->shields <= 0) {
1248
// --                           weapon->shields = 0;
1249
// --                           weapon->flags |= OF_SHOULD_BE_DEAD;
1250
// --                   }
1251
// --           }
1252
// --   } else
1253
// --           weapon->flags |= OF_SHOULD_BE_DEAD;
1254
}
1255
}
1256
 
1257
namespace dsx {
1258
static void collide_weapon_and_controlcen(const vmobjptridx_t weapon, const vmobjptridx_t controlcen, vms_vector &collision_point)
1259
{
1260
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1261
        auto &Objects = LevelUniqueObjectState.Objects;
1262
        auto &vcobjptr = Objects.vcptr;
1263
 
1264
#if defined(DXX_BUILD_DESCENT_I)
1265
        fix explosion_size = ((controlcen->size/3)*3)/4;
1266
#elif defined(DXX_BUILD_DESCENT_II)
1267
        if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
1268
                if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
1269
                        return;
1270
 
1271
        fix explosion_size = controlcen->size*3/20;
1272
#endif
1273
        if (weapon->ctype.laser_info.parent_type == OBJ_PLAYER) {
1274
                fix     damage = weapon->shields;
1275
 
1276
                /*
1277
                * Check if persistent weapon already hit this object. If yes, abort.
1278
                * If no, add this object to hitobj_list and do it's damage.
1279
                */
1280
                if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
1281
                {
1282
                        damage = weapon->shields*2; // to not alter Gameplay too much, multiply damage by 2.
1283
                        if (weapon->ctype.laser_info.test_set_hitobj(controlcen))
1284
                                return;
1285
                }
1286
 
1287
                if (get_player_id(vcobjptr(weapon->ctype.laser_info.parent_num)) == Player_num)
1288
                        LevelUniqueControlCenterState.Control_center_been_hit = 1;
1289
 
1290
                if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
1291
                {
1292
                        const auto obj2weapon = vm_vec_sub(collision_point, controlcen->pos);
1293
                        const auto mag = vm_vec_mag(obj2weapon);
1294
                        if(mag < controlcen->size && mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
1295
                        {
1296
                                vm_vec_scale_add(collision_point, controlcen->pos, obj2weapon, fixdiv(controlcen->size, mag));
1297
                                weapon->pos = collision_point;
1298
                        }
1299
#if defined(DXX_BUILD_DESCENT_I)
1300
                        explode_badass_weapon(weapon, weapon->pos);
1301
#elif defined(DXX_BUILD_DESCENT_II)
1302
                        explode_badass_weapon(weapon, collision_point);
1303
#endif
1304
                }
1305
                else
1306
                        object_create_explosion(vmsegptridx(controlcen->segnum), collision_point, explosion_size, VCLIP_SMALL_EXPLOSION);
1307
 
1308
                digi_link_sound_to_pos(SOUND_CONTROL_CENTER_HIT, vmsegptridx(controlcen->segnum), 0, collision_point, 0, F1_0);
1309
 
1310
                damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
1311
 
1312
                apply_damage_to_controlcen(controlcen, damage, vcobjptr(weapon->ctype.laser_info.parent_num));
1313
 
1314
                maybe_kill_weapon(weapon,controlcen);
1315
        } else {        //      If robot weapon hits control center, blow it up, make it go away, but do no damage to control center.
1316
                object_create_explosion(vmsegptridx(controlcen->segnum), collision_point, explosion_size, VCLIP_SMALL_EXPLOSION);
1317
                maybe_kill_weapon(weapon,controlcen);
1318
        }
1319
 
1320
}
1321
}
1322
 
1323
static void collide_weapon_and_clutter(object_base &weapon, const vmobjptridx_t clutter, const vms_vector &collision_point)
1324
{
1325
        short exp_vclip = VCLIP_SMALL_EXPLOSION;
1326
 
1327
        if ( clutter->shields >= 0 )
1328
                clutter->shields -= weapon.shields;
1329
 
1330
        digi_link_sound_to_pos(SOUND_LASER_HIT_CLUTTER, vmsegptridx(weapon.segnum), 0, collision_point, 0, F1_0);
1331
 
1332
        object_create_explosion(vmsegptridx(clutter->segnum), collision_point, ((clutter->size/3)*3)/4, exp_vclip);
1333
 
1334
        if ( (clutter->shields < 0) && !(clutter->flags&(OF_EXPLODING|OF_DESTROYED)))
1335
                explode_object(clutter,STANDARD_EXPL_DELAY);
1336
 
1337
        maybe_kill_weapon(weapon,clutter);
1338
}
1339
 
1340
//--mk, 121094 -- extern void spin_robot(object *robot, vms_vector *collision_point);
1341
 
1342
#if defined(DXX_BUILD_DESCENT_II)
1343
namespace dsx {
1344
 
1345
//      ------------------------------------------------------------------------------------------------------
1346
window_event_result do_final_boss_frame(void)
1347
{
1348
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1349
        auto Final_boss_countdown_time = GameUniqueState.Final_boss_countdown_time;
1350
        if (!Final_boss_countdown_time)
1351
                return window_event_result::ignored;
1352
 
1353
        if (!LevelUniqueControlCenterState.Control_center_destroyed)
1354
                return window_event_result::ignored;
1355
 
1356
        Final_boss_countdown_time -= FrameTime;
1357
        if (Final_boss_countdown_time > 0)
1358
        {
1359
                if (Final_boss_countdown_time <= F1_0*2)
1360
                {
1361
                        int flash_value = f2i(((F1_0*2)-Final_boss_countdown_time)*16); // during final 2 seconds of final boss countdown (as set by do_final_boss_hacks()), make a flash value that maxes out over that time
1362
                        PALETTE_FLASH_SET(-flash_value,-flash_value,-flash_value); // set palette to inverted flash_value to fade to black
1363
                }
1364
                GameUniqueState.Final_boss_countdown_time = Final_boss_countdown_time;
1365
                return window_event_result::ignored;
1366
        }
1367
        /* Ensure that GameUniqueState.Final_boss_countdown_time is not 0,
1368
         * so that the flag remains raised.  If Final_boss_countdown_time
1369
         * were greater than 0, the function already returned.  If
1370
         * Final_boss_countdown_time is exactly 0, write -1.  Otherwise,
1371
         * write a slightly more negative number.  If
1372
         * Final_boss_countdown_time is INT32_MIN, this would roll over, but
1373
         * that would only happen if FrameTime had an absurd value.
1374
         */
1375
        GameUniqueState.Final_boss_countdown_time = --Final_boss_countdown_time;
1376
 
1377
        return start_endlevel_sequence();               //pretend we hit the exit trigger
1378
}
1379
 
1380
//      ------------------------------------------------------------------------------------------------------
1381
//      This is all the ugly stuff we do when you kill the final boss so that you don't die or something
1382
//      which would ruin the logic of the cut sequence.
1383
void do_final_boss_hacks(void)
1384
{
1385
        auto &Objects = LevelUniqueObjectState.Objects;
1386
        auto &vmobjptr = Objects.vmptr;
1387
        if (Player_dead_state != player_dead_state::no)
1388
        {
1389
                Int3();         //      Uh-oh, player is dead.  Try to rescue him.
1390
                Player_dead_state = player_dead_state::no;
1391
        }
1392
 
1393
        auto &plrobj = get_local_plrobj();
1394
        auto &player_info = plrobj.ctype.player_info;
1395
        {
1396
                auto &shields = plrobj.shields;
1397
                if (shields <= 0)
1398
                        shields = 1;
1399
        }
1400
 
1401
        //      If you're not invulnerable, get invulnerable!
1402
        auto &pl_flags = player_info.powerup_flags;
1403
        if (!(pl_flags & PLAYER_FLAGS_INVULNERABLE)) {
1404
                pl_flags |= PLAYER_FLAGS_INVULNERABLE;
1405
                player_info.FakingInvul = 0;
1406
                player_info.invulnerable_time = GameTime64;
1407
        }
1408
        if (!(Game_mode & GM_MULTI))
1409
                buddy_message("Nice job, %s!", static_cast<const char *>(get_local_player().callsign));
1410
 
1411
        GameUniqueState.Final_boss_countdown_time = F1_0*4; // was F1_0*2 originally. extended to F1_0*4 to play fade to black which happened after countdown ended in the original game.
1412
}
1413
 
1414
}
1415
#endif
1416
 
1417
//      ------------------------------------------------------------------------------------------------------
1418
//      Return 1 if robot died, else return 0
1419
namespace dsx {
1420
int apply_damage_to_robot(const vmobjptridx_t robot, fix damage, objnum_t killer_objnum)
1421
{
1422
        if ( robot->flags&OF_EXPLODING) return 0;
1423
 
1424
        if (robot->shields < 0 ) return 0;      //robot already dead...
1425
 
1426
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1427
        auto &robptr = Robot_info[get_robot_id(robot)];
1428
#if defined(DXX_BUILD_DESCENT_II)
1429
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1430
        auto &Objects = LevelUniqueObjectState.Objects;
1431
        auto &vmobjptr = Objects.vmptr;
1432
        if (robptr.boss_flag)
1433
                BossUniqueState.Boss_hit_time = GameTime64;
1434
 
1435
        //      Buddy invulnerable on level 24 so he can give you his important messages.  Bah.
1436
        //      Also invulnerable if his cheat for firing weapons is in effect.
1437
        if (robot_is_companion(robptr)) {
1438
                if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
1439
                        return 0;
1440
        }
1441
#endif
1442
 
1443
        robot->shields -= damage;
1444
 
1445
#if defined(DXX_BUILD_DESCENT_II)
1446
        //      Do unspeakable hacks to make sure player doesn't die after killing boss.  Or before, sort of.
1447
        if (robptr.boss_flag)
1448
                if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
1449
                        if (robot->shields < 0)
1450
                         {
1451
                                if (Game_mode & GM_MULTI)
1452
                                  {
1453
                                         if (!multi_all_players_alive(Objects.vcptr, partial_range(Players, N_players))) // everyones gotta be alive
1454
                                           robot->shields=1;
1455
                                         else
1456
                                          {
1457
                                            multi_send_finish_game();
1458
                                            do_final_boss_hacks();
1459
                                          }
1460
 
1461
                                        }
1462
                                else
1463
                                  {     // NOTE LINK TO ABOVE!!!
1464
                                        if (get_local_plrobj().shields < 0 || Player_dead_state != player_dead_state::no)
1465
                                                robot->shields = 1;             //      Sorry, we can't allow you to kill the final boss after you've died.  Rough luck.
1466
                                        else
1467
                                                do_final_boss_hacks();
1468
                                  }
1469
                          }
1470
#endif
1471
 
1472
        if (robot->shields < 0) {
1473
                auto &plr = get_local_player();
1474
                plr.num_kills_level++;
1475
                plr.num_kills_total++;
1476
                if (Game_mode & GM_MULTI) {
1477
                        if (multi_explode_robot_sub(robot))
1478
                        {
1479
                                multi_send_robot_explode(robot, killer_objnum);
1480
                                return 1;
1481
                        }
1482
                        else
1483
                                return 0;
1484
                }
1485
 
1486
                if (robptr.boss_flag) {
1487
                        start_boss_death_sequence(robot);       //do_controlcen_destroyed_stuff(NULL);
1488
                }
1489
#if defined(DXX_BUILD_DESCENT_II)
1490
                else if (robptr.death_roll) {
1491
                        start_robot_death_sequence(robot);      //do_controlcen_destroyed_stuff(NULL);
1492
                }
1493
#endif
1494
                else {
1495
#if defined(DXX_BUILD_DESCENT_II)
1496
                        if (get_robot_id(robot) == SPECIAL_REACTOR_ROBOT)
1497
                                special_reactor_stuff();
1498
                        if (robptr.kamikaze)
1499
                                explode_object(robot,1);                //      Kamikaze, explode right away, IN YOUR FACE!
1500
                        else
1501
#endif
1502
                                explode_object(robot,STANDARD_EXPL_DELAY);
1503
                }
1504
                return 1;
1505
        } else
1506
                return 0;
1507
}
1508
 
1509
#if defined(DXX_BUILD_DESCENT_II)
1510
namespace {
1511
 
1512
enum class boss_weapon_collision_result : uint8_t
1513
{
1514
        normal,
1515
        invulnerable,
1516
        reflect,        // implies invulnerable
1517
};
1518
 
1519
}
1520
 
1521
static inline int Boss_invulnerable_dot()
1522
{
1523
        return F1_0 / 4 - i2f(GameUniqueState.Difficulty_level) / 8;
1524
}
1525
 
1526
//      ------------------------------------------------------------------------------------------------------
1527
//      Return true if damage done to boss, else return false.
1528
static boss_weapon_collision_result do_boss_weapon_collision(const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const vcobjptridx_t robotptridx, object &weapon, const vms_vector &collision_point)
1529
{
1530
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1531
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
1532
        const object_base &robot = robotptridx;
1533
        int     d2_boss_index;
1534
 
1535
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1536
        d2_boss_index = Robot_info[get_robot_id(robot)].boss_flag - BOSS_D2;
1537
 
1538
        Assert((d2_boss_index >= 0) && (d2_boss_index < NUM_D2_BOSSES));
1539
 
1540
        //      See if should spew a bot.
1541
        if (weapon.ctype.laser_info.parent_type == OBJ_PLAYER && weapon.ctype.laser_info.parent_num == get_local_player().objnum)
1542
                if ((Weapon_info[get_weapon_id(weapon)].matter
1543
                        ? Boss_spews_bots_matter
1544
                        : Boss_spews_bots_energy)[d2_boss_index])
1545
                {
1546
                        if (Boss_spew_more[d2_boss_index])
1547
                                if (d_rand() > 16384) {
1548
                                        const auto &&spew = boss_spew_robot(robot, collision_point);
1549
                                        if (spew != object_none)
1550
                                        {
1551
                                                BossUniqueState.Last_gate_time = GameTime64 - GameUniqueState.Boss_gate_interval - 1;   //      Force allowing spew of another bot.
1552
                                                multi_send_boss_create_robot(robotptridx, spew);
1553
                                        }
1554
                                }
1555
                        const auto &&spew = boss_spew_robot(robot, collision_point);
1556
                        if (spew != object_none)
1557
                                multi_send_boss_create_robot(robotptridx, spew);
1558
                }
1559
 
1560
        if (Boss_invulnerable_spot[d2_boss_index]) {
1561
                fix                     dot;
1562
                //      Boss only vulnerable in back.  See if hit there.
1563
                //      Note, if BOSS_INVULNERABLE_DOT is close to F1_0 (in magnitude), then should probably use non-quick version.
1564
                const auto tvec1 = vm_vec_normalized_quick(vm_vec_sub(collision_point, robot.pos));
1565
                dot = vm_vec_dot(tvec1, robot.orient.fvec);
1566
                if (dot > Boss_invulnerable_dot()) {
1567
                        if (const auto &&segp = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, Segments.vmptridx(robot.segnum)))
1568
                                digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, segp, 0, collision_point, 0, F1_0);
1569
                        if (BuddyState.Buddy_objnum != object_none)
1570
                        {
1571
                                if (BuddyState.Buddy_gave_hint_count) {
1572
                                        if (BuddyState.Last_time_buddy_gave_hint + F1_0*20 < GameTime64) {
1573
                                                int     sval;
1574
 
1575
                                                BuddyState.Buddy_gave_hint_count--;
1576
                                                BuddyState.Last_time_buddy_gave_hint = GameTime64;
1577
                                                sval = (d_rand()*4) >> 15;
1578
                                                const char *msg;
1579
                                                switch (sval) {
1580
                                                        case 0:
1581
                                                                msg = "Hit him in the back!";
1582
                                                                break;
1583
                                                        case 1:
1584
                                                                msg = "He's invulnerable there!";
1585
                                                                break;
1586
                                                        case 2:
1587
                                                                msg = "Get behind him and fire!";
1588
                                                                break;
1589
                                                        case 3:
1590
                                                        default:
1591
                                                                msg = "Hit the glowing spot!";
1592
                                                                break;
1593
                                                }
1594
                                                buddy_message_str(msg);
1595
                                        }
1596
                                }
1597
                        }
1598
 
1599
                        //      Cause weapon to bounce.
1600
                        if (!Weapon_info[get_weapon_id(weapon)].matter) {
1601
                                        fix                     speed;
1602
 
1603
                                        auto vec_to_point = vm_vec_normalized_quick(vm_vec_sub(collision_point, robot.pos));
1604
                                        auto weap_vec = weapon.mtype.phys_info.velocity;
1605
                                        speed = vm_vec_normalize_quick(weap_vec);
1606
                                        vm_vec_scale_add2(vec_to_point, weap_vec, -F1_0*2);
1607
                                        vm_vec_scale(vec_to_point, speed/4);
1608
                                        weapon.mtype.phys_info.velocity = vec_to_point;
1609
                                return boss_weapon_collision_result::reflect;
1610
                        }
1611
                        return boss_weapon_collision_result::invulnerable;
1612
                }
1613
        }
1614
        else if ((Weapon_info[get_weapon_id(weapon)].matter ? Boss_invulnerable_matter : Boss_invulnerable_energy)[d2_boss_index])
1615
        {
1616
                if (const auto &&segp = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, Segments.vmptridx(robot.segnum)))
1617
                        digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, segp, 0, collision_point, 0, F1_0);
1618
                return boss_weapon_collision_result::invulnerable;
1619
        }
1620
        return boss_weapon_collision_result::normal;
1621
}
1622
#endif
1623
 
1624
//      ------------------------------------------------------------------------------------------------------
1625
static void collide_robot_and_weapon(const vmobjptridx_t  robot, const vmobjptridx_t  weapon, vms_vector &collision_point)
1626
{
1627
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1628
        auto &Objects = LevelUniqueObjectState.Objects;
1629
        auto &vcobjptr = Objects.vcptr;
1630
#if defined(DXX_BUILD_DESCENT_II)
1631
        auto &icobjptridx = Objects.icptridx;
1632
        if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
1633
                if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
1634
                        return;
1635
#endif
1636
 
1637
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1638
        auto &robptr = Robot_info[get_robot_id(robot)];
1639
        if (robptr.boss_flag)
1640
        {
1641
                BossUniqueState.Boss_hit_this_frame = 1;
1642
#if defined(DXX_BUILD_DESCENT_II)
1643
                BossUniqueState.Boss_hit_time = GameTime64;
1644
#endif
1645
        }
1646
#if defined(DXX_BUILD_DESCENT_II)
1647
        const boss_weapon_collision_result damage_flag = (robptr.boss_flag >= BOSS_D2)
1648
                ? do_boss_weapon_collision(LevelSharedSegmentState, LevelUniqueSegmentState, robot, weapon, collision_point)
1649
                : boss_weapon_collision_result::normal;
1650
#endif
1651
 
1652
#if defined(DXX_BUILD_DESCENT_II)
1653
        //      Put in at request of Jasen (and Adam) because the Buddy-Bot gets in their way.
1654
        //      MK has so much fun whacking his butt around the mine he never cared...
1655
        if ((robot_is_companion(robptr)) && ((weapon->ctype.laser_info.parent_type != OBJ_ROBOT) && !cheats.robotskillrobots))
1656
                return;
1657
 
1658
        if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
1659
                smega_rock_stuff();
1660
#endif
1661
 
1662
        /*
1663
         * Check if persistent weapon already hit this object. If yes, abort.
1664
         * If no, add this object to hitobj_list and do it's damage.
1665
         */
1666
        if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
1667
        {
1668
                if (weapon->ctype.laser_info.test_set_hitobj(robot))
1669
                        return;
1670
        }
1671
 
1672
        if (laser_parent_is_object(weapon->ctype.laser_info, robot))
1673
                return;
1674
 
1675
#if defined(DXX_BUILD_DESCENT_II)
1676
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1677
        //      Changed, 10/04/95, put out blobs based on skill level and power of weapon doing damage.
1678
        //      Also, only a weapon hit from a player weapon causes smart blobs.
1679
        if ((weapon->ctype.laser_info.parent_type == OBJ_PLAYER) && (robptr.energy_blobs))
1680
                if ((robot->shields > 0) && Weapon_is_energy[get_weapon_id(weapon)]) {
1681
                        fix     probval;
1682
                        int     num_blobs;
1683
 
1684
                        probval = (Difficulty_level+2) * min(weapon->shields, robot->shields);
1685
                        probval = robptr.energy_blobs * probval/(NDL*32);
1686
 
1687
                        num_blobs = probval >> 16;
1688
                        if (2*d_rand() < (probval & 0xffff))
1689
                                num_blobs++;
1690
 
1691
                        if (num_blobs)
1692
                                create_robot_smart_children(robot, num_blobs);
1693
                }
1694
 
1695
        //      Note: If weapon hits an invulnerable boss, it will still do badass damage, including to the boss,
1696
        //      unless this is trapped elsewhere.
1697
#endif
1698
        weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
1699
        if ( wi->damage_radius )
1700
        {
1701
                const auto obj2weapon = vm_vec_sub(collision_point, robot->pos);
1702
                const auto mag = vm_vec_mag(obj2weapon);
1703
                if(mag < robot->size && mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
1704
                {
1705
                        vm_vec_scale_add(collision_point, robot->pos, obj2weapon, fixdiv(robot->size, mag));
1706
                        weapon->pos = collision_point;
1707
                }
1708
#if defined(DXX_BUILD_DESCENT_I)
1709
                explode_badass_weapon(weapon, weapon->pos);
1710
#elif defined(DXX_BUILD_DESCENT_II)
1711
                if (damage_flag != boss_weapon_collision_result::normal) {                      //don't make badass sound
1712
 
1713
                        //this code copied from explode_badass_weapon()
1714
 
1715
                        object_create_badass_explosion(weapon, vmsegptridx(weapon->segnum), collision_point,
1716
                                                        wi->impact_size,
1717
                                                        wi->robot_hit_vclip,
1718
                                                        wi->strength[Difficulty_level],
1719
                                                        wi->damage_radius,wi->strength[Difficulty_level],
1720
                                                        icobjptridx(weapon->ctype.laser_info.parent_num));
1721
 
1722
                }
1723
                else            //normal badass explosion
1724
                        explode_badass_weapon(weapon, collision_point);
1725
#endif
1726
        }
1727
 
1728
#if defined(DXX_BUILD_DESCENT_I)
1729
        if ( (weapon->ctype.laser_info.parent_type==OBJ_PLAYER) && !(robot->flags & OF_EXPLODING) )
1730
#elif defined(DXX_BUILD_DESCENT_II)
1731
        if ( ((weapon->ctype.laser_info.parent_type==OBJ_PLAYER) || cheats.robotskillrobots) && !(robot->flags & OF_EXPLODING) )
1732
#endif
1733
        {
1734
                if (weapon->ctype.laser_info.parent_num == get_local_player().objnum) {
1735
                        create_awareness_event(weapon, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION, LevelUniqueRobotAwarenessState);                     // object "weapon" can attract attention to player
1736
                        do_ai_robot_hit(robot, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION);
1737
                }
1738
                else
1739
                        multi_robot_request_change(robot, get_player_id(vcobjptr(weapon->ctype.laser_info.parent_num)));
1740
 
1741
                std::pair<fix, int> explosion_size_and_vclip;
1742
                if ((robptr.exp1_vclip_num > -1 && (explosion_size_and_vclip = {(robot->size / 2 * 3) / 4, robptr.exp1_vclip_num}, true))
1743
#if defined(DXX_BUILD_DESCENT_II)
1744
                        || (wi->robot_hit_vclip > -1 && (explosion_size_and_vclip = {wi->impact_size, wi->robot_hit_vclip}, true))
1745
#endif
1746
                        )
1747
                {
1748
                        const auto &&expl_obj = object_create_explosion(vmsegptridx(weapon->segnum), collision_point, explosion_size_and_vclip.first, explosion_size_and_vclip.second);
1749
 
1750
                if (expl_obj != object_none)
1751
                                obj_attach(Objects, robot, expl_obj);
1752
                }
1753
 
1754
#if defined(DXX_BUILD_DESCENT_II)
1755
                if (damage_flag == boss_weapon_collision_result::normal)
1756
#endif
1757
                {
1758
                        if (robptr.exp1_sound_num > -1)
1759
                                digi_link_sound_to_pos(robptr.exp1_sound_num, vcsegptridx(robot->segnum), 0, collision_point, 0, F1_0);
1760
                        fix     damage = weapon->shields;
1761
 
1762
                                damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
1763
 
1764
#if defined(DXX_BUILD_DESCENT_II)
1765
                        //      Cut Gauss damage on bosses because it just breaks the game.  Bosses are so easy to
1766
                        //      hit, and missing a robot is what prevents the Gauss from being game-breaking.
1767
                        if (get_weapon_id(weapon) == weapon_id_type::GAUSS_ID)
1768
                                if (robptr.boss_flag)
1769
                                        damage = damage * (2*NDL-Difficulty_level)/(2*NDL);
1770
#endif
1771
 
1772
                        if (! apply_damage_to_robot(robot, damage, weapon->ctype.laser_info.parent_num))
1773
                                bump_two_objects(robot, weapon, 0);             //only bump if not dead. no damage from bump
1774
                        else if (laser_parent_is_player(vcobjptr, weapon->ctype.laser_info, *ConsoleObject))
1775
                        {
1776
                                add_points_to_score(ConsoleObject->ctype.player_info, robptr.score_value);
1777
                                detect_escort_goal_accomplished(robot);
1778
                        }
1779
                }
1780
 
1781
#if defined(DXX_BUILD_DESCENT_II)
1782
                //      If Gauss Cannon, spin robot.
1783
                if (!robot_is_companion(robptr) && (!robptr.boss_flag) && (get_weapon_id(weapon) == weapon_id_type::GAUSS_ID))
1784
                {
1785
                        ai_static       *aip = &robot->ctype.ai_info;
1786
 
1787
                        if (aip->SKIP_AI_COUNT * FrameTime < F1_0) {
1788
                                aip->SKIP_AI_COUNT++;
1789
                                robot->mtype.phys_info.rotthrust.x = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
1790
                                robot->mtype.phys_info.rotthrust.y = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
1791
                                robot->mtype.phys_info.rotthrust.z = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
1792
                                robot->mtype.phys_info.flags |= PF_USES_THRUST;
1793
 
1794
                        }
1795
                }
1796
#endif
1797
 
1798
        }
1799
 
1800
#if defined(DXX_BUILD_DESCENT_II)
1801
        if (damage_flag != boss_weapon_collision_result::reflect)
1802
#endif
1803
        maybe_kill_weapon(weapon,robot);
1804
 
1805
        return;
1806
}
1807
}
1808
 
1809
static void collide_hostage_and_player(const vmobjptridx_t hostage, object &player, const vms_vector &)
1810
{
1811
        // Give player points, etc.
1812
        if (&player == ConsoleObject)
1813
        {
1814
                detect_escort_goal_accomplished(hostage);
1815
                add_points_to_score(player.ctype.player_info, HOSTAGE_SCORE);
1816
 
1817
                // Do effect
1818
                hostage_rescue();
1819
 
1820
                // Remove the hostage object.
1821
                hostage->flags |= OF_SHOULD_BE_DEAD;
1822
 
1823
                if (Game_mode & GM_MULTI)
1824
                        multi_send_remobj(hostage);
1825
        }
1826
        return;
1827
}
1828
 
1829
static void collide_player_and_player(const vmobjptridx_t player1, const vmobjptridx_t player2, const vms_vector &collision_point)
1830
{
1831
        int damage_flag = 1;
1832
 
1833
        if (check_collision_delayfunc_exec())
1834
                digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vcsegptridx(player1->segnum), 0, collision_point, 0, F1_0);
1835
 
1836
        // Multi is special - as always. Clients do the bump damage locally but the remote players do their collision result (change velocity) on their own. So after our initial collision, ignore further collision damage till remote players (hopefully) react.
1837
        if (Game_mode & GM_MULTI)
1838
        {
1839
                damage_flag = 0;
1840
                if (!(get_player_id(player1) == Player_num || get_player_id(player2) == Player_num))
1841
                        return;
1842
                auto &last_player_bump = ((get_player_id(player1) == Player_num)
1843
                        ? player2
1844
                        : player1)->ctype.player_info.Last_bumped_local_player;
1845
                if (last_player_bump + (F1_0/Netgame.PacketsPerSec) < GameTime64 || last_player_bump > GameTime64)
1846
                {
1847
                        last_player_bump = GameTime64;
1848
                        damage_flag = 1;
1849
                }
1850
        }
1851
 
1852
        bump_two_objects(player1, player2, damage_flag);
1853
        return;
1854
}
1855
 
1856
static imobjptridx_t maybe_drop_primary_weapon_egg(const object &playerobj, int weapon_index)
1857
{
1858
        int weapon_flag = HAS_PRIMARY_FLAG(weapon_index);
1859
        const auto powerup_num = Primary_weapon_to_powerup[weapon_index];
1860
        auto &player_info = playerobj.ctype.player_info;
1861
        if (player_info.primary_weapon_flags & weapon_flag)
1862
                return call_object_create_egg(playerobj, 1, powerup_num);
1863
        else
1864
                return object_none;
1865
}
1866
 
1867
static void maybe_drop_secondary_weapon_egg(const object_base &playerobj, int weapon_index, int count)
1868
{
1869
        const auto powerup_num = Secondary_weapon_to_powerup[weapon_index];
1870
                int max_count = min(count, 3);
1871
                for (int i=0; i<max_count; i++)
1872
                        call_object_create_egg(playerobj, 1, powerup_num);
1873
}
1874
 
1875
static void drop_missile_1_or_4(const object &playerobj,int missile_index)
1876
{
1877
        unsigned num_missiles = playerobj.ctype.player_info.secondary_ammo[missile_index];
1878
        const auto powerup_id = Secondary_weapon_to_powerup[missile_index];
1879
 
1880
        if (num_missiles > 10)
1881
                num_missiles = 10;
1882
 
1883
        call_object_create_egg(playerobj, num_missiles / 4, powerup_id + 1);
1884
        call_object_create_egg(playerobj, num_missiles % 4, powerup_id);
1885
}
1886
 
1887
namespace dsx {
1888
void drop_player_eggs(const vmobjptridx_t playerobj)
1889
{
1890
        if ((playerobj->type == OBJ_PLAYER) || (playerobj->type == OBJ_GHOST)) {
1891
                // Seed the random number generator so in net play the eggs will always
1892
                // drop the same way
1893
                if (Game_mode & GM_MULTI)
1894
                {
1895
                        Net_create_loc = 0;
1896
                        d_srand(5483L);
1897
                }
1898
                auto &player_info = playerobj->ctype.player_info;
1899
                auto &plr_laser_level = player_info.laser_level;
1900
                if (const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0)
1901
                {
1902
                        if (const auto granted_laser_level = map_granted_flags_to_laser_level(GrantedItems))
1903
                        {
1904
                                if (plr_laser_level <= granted_laser_level)
1905
                                        /* All levels were from grant */
1906
                                        plr_laser_level = LASER_LEVEL_1;
1907
#if defined(DXX_BUILD_DESCENT_II)
1908
                                else if (granted_laser_level > MAX_LASER_LEVEL)
1909
                                {
1910
                                        /* Grant gives super laser 5.
1911
                                         * Player has super laser 6.
1912
                                         */
1913
                                        -- plr_laser_level;
1914
                                }
1915
                                else if (plr_laser_level > MAX_LASER_LEVEL)
1916
                                {
1917
                                        /* Grant gives only regular lasers.
1918
                                         * Player has super lasers, will drop only
1919
                                         * super lasers.
1920
                                         */
1921
                                }
1922
#endif
1923
                                else
1924
                                        plr_laser_level -= granted_laser_level;
1925
                        }
1926
                        if (uint16_t subtract_vulcan_ammo = map_granted_flags_to_vulcan_ammo(GrantedItems))
1927
                        {
1928
                                auto &v = playerobj->ctype.player_info.vulcan_ammo;
1929
                                if (v < subtract_vulcan_ammo)
1930
                                        v = 0;
1931
                                else
1932
                                        v -= subtract_vulcan_ammo;
1933
                        }
1934
                        player_info.powerup_flags &= ~map_granted_flags_to_player_flags(GrantedItems);
1935
                        player_info.primary_weapon_flags &= ~map_granted_flags_to_primary_weapon_flags(GrantedItems);
1936
                }
1937
 
1938
                auto &secondary_ammo = playerobj->ctype.player_info.secondary_ammo;
1939
#if defined(DXX_BUILD_DESCENT_II)
1940
                //      If the player had smart mines, maybe arm one of them.
1941
                const auto drop_armed_bomb = [&](uint8_t mines, weapon_id_type id) {
1942
                        mines %= 4;
1943
                        for (int rthresh = 30000; mines && d_rand() < rthresh; rthresh /= 2)
1944
                {
1945
                        const auto randvec = make_random_vector();
1946
                        const auto tvec = vm_vec_add(playerobj->pos, randvec);
1947
                        const auto &&newseg = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, tvec, Segments.vmptridx(playerobj->segnum));
1948
                        if (newseg != segment_none)
1949
                        {
1950
                                -- mines;
1951
                                Laser_create_new(randvec, tvec, newseg, playerobj, id, 0);
1952
                        }
1953
                }
1954
                };
1955
                drop_armed_bomb(secondary_ammo[SMART_MINE_INDEX], weapon_id_type::SUPERPROX_ID);
1956
 
1957
                //      If the player had proximity bombs, maybe arm one of them.
1958
                if (Game_mode & GM_MULTI)
1959
                        drop_armed_bomb(secondary_ammo[PROXIMITY_INDEX], weapon_id_type::PROXIMITY_ID);
1960
#endif
1961
 
1962
                //      If the player dies and he has powerful lasers, create the powerups here.
1963
 
1964
                std::pair<int, int> laser_level_and_id;
1965
                if (
1966
#if defined(DXX_BUILD_DESCENT_II)
1967
                        (plr_laser_level > MAX_LASER_LEVEL && (laser_level_and_id = {plr_laser_level - MAX_LASER_LEVEL, POW_SUPER_LASER}, true)) ||
1968
#endif
1969
                        (plr_laser_level && (laser_level_and_id = {plr_laser_level, POW_LASER}, true)))
1970
                        call_object_create_egg(playerobj, laser_level_and_id.first, laser_level_and_id.second);
1971
 
1972
                //      Drop quad laser if appropos
1973
                if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
1974
                        call_object_create_egg(playerobj, 1, POW_QUAD_FIRE);
1975
 
1976
                if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
1977
                        call_object_create_egg(playerobj, 1, POW_CLOAK);
1978
 
1979
#if defined(DXX_BUILD_DESCENT_II)
1980
                if (player_info.powerup_flags & PLAYER_FLAGS_MAP_ALL)
1981
                        call_object_create_egg(playerobj, 1, POW_FULL_MAP);
1982
 
1983
                if (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER)
1984
                        call_object_create_egg(playerobj, 1, POW_AFTERBURNER);
1985
 
1986
                if (player_info.powerup_flags & PLAYER_FLAGS_AMMO_RACK)
1987
                        call_object_create_egg(playerobj, 1, POW_AMMO_RACK);
1988
 
1989
                if (player_info.powerup_flags & PLAYER_FLAGS_CONVERTER)
1990
                        call_object_create_egg(playerobj, 1, POW_CONVERTER);
1991
 
1992
                if (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT)
1993
                        call_object_create_egg(playerobj, 1, POW_HEADLIGHT);
1994
 
1995
                // drop the other enemies flag if you have it
1996
 
1997
                if (game_mode_capture_flag() && (player_info.powerup_flags & PLAYER_FLAGS_FLAG))
1998
                {
1999
                        call_object_create_egg(playerobj, 1, get_team(get_player_id(playerobj)) == TEAM_RED ? POW_FLAG_BLUE : POW_FLAG_RED);
2000
                }
2001
 
2002
 
2003
                if (game_mode_hoard())
2004
                {
2005
                        // Drop hoard orbs
2006
                        for (unsigned max_count = std::min<uint8_t>(player_info.hoard.orbs, player_info.max_hoard_orbs); max_count--;)
2007
                                call_object_create_egg(playerobj, 1, POW_HOARD_ORB);
2008
                }
2009
#endif
2010
 
2011
                //Drop the vulcan, gauss, and ammo
2012
                auto vulcan_ammo = playerobj->ctype.player_info.vulcan_ammo;
2013
#if defined(DXX_BUILD_DESCENT_I)
2014
                const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG;
2015
#elif defined(DXX_BUILD_DESCENT_II)
2016
                const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
2017
                if ((player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS) == HAS_VULCAN_AND_GAUSS_FLAGS)
2018
                        vulcan_ammo /= 2;               //if both vulcan & gauss, each gets half
2019
#endif
2020
                if (vulcan_ammo < VULCAN_AMMO_AMOUNT)
2021
                        vulcan_ammo = VULCAN_AMMO_AMOUNT;       //make sure gun has at least as much as a powerup
2022
                auto objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::VULCAN_INDEX);
2023
                if (objnum!=object_none)
2024
                        objnum->ctype.powerup_info.count = vulcan_ammo;
2025
#if defined(DXX_BUILD_DESCENT_II)
2026
                objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::GAUSS_INDEX);
2027
                if (objnum!=object_none)
2028
                        objnum->ctype.powerup_info.count = vulcan_ammo;
2029
#endif
2030
 
2031
                //      Drop the rest of the primary weapons
2032
                maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::SPREADFIRE_INDEX);
2033
                maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::PLASMA_INDEX);
2034
                maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::FUSION_INDEX);
2035
 
2036
#if defined(DXX_BUILD_DESCENT_II)
2037
                maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::HELIX_INDEX);
2038
                maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::PHOENIX_INDEX);
2039
 
2040
                objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::OMEGA_INDEX);
2041
                if (objnum!=object_none)
2042
                        objnum->ctype.powerup_info.count = (get_player_id(playerobj) == Player_num) ? playerobj->ctype.player_info.Omega_charge : MAX_OMEGA_CHARGE;
2043
#endif
2044
 
2045
                //      Drop the secondary weapons
2046
                //      Note, proximity weapon only comes in packets of 4.  So drop n/2, but a max of 3 (handled inside maybe_drop..)  Make sense?
2047
 
2048
                maybe_drop_secondary_weapon_egg(playerobj, PROXIMITY_INDEX, secondary_ammo[PROXIMITY_INDEX] / 4);
2049
 
2050
                maybe_drop_secondary_weapon_egg(playerobj, SMART_INDEX, secondary_ammo[SMART_INDEX]);
2051
                maybe_drop_secondary_weapon_egg(playerobj, MEGA_INDEX, secondary_ammo[MEGA_INDEX]);
2052
 
2053
#if defined(DXX_BUILD_DESCENT_II)
2054
                maybe_drop_secondary_weapon_egg(playerobj, SMART_MINE_INDEX,(secondary_ammo[SMART_MINE_INDEX])/4);
2055
                maybe_drop_secondary_weapon_egg(playerobj, SMISSILE5_INDEX, secondary_ammo[SMISSILE5_INDEX]);
2056
#endif
2057
 
2058
                //      Drop the player's missiles in packs of 1 and/or 4
2059
                drop_missile_1_or_4(playerobj,HOMING_INDEX);
2060
#if defined(DXX_BUILD_DESCENT_II)
2061
                drop_missile_1_or_4(playerobj,GUIDED_INDEX);
2062
#endif
2063
                drop_missile_1_or_4(playerobj,CONCUSSION_INDEX);
2064
#if defined(DXX_BUILD_DESCENT_II)
2065
                drop_missile_1_or_4(playerobj,SMISSILE1_INDEX);
2066
                drop_missile_1_or_4(playerobj,SMISSILE4_INDEX);
2067
#endif
2068
 
2069
                //      If player has vulcan ammo, but no vulcan or gauss cannon, drop the ammo.
2070
                if (!(player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS))
2071
                {
2072
                        auto amount = player_info.vulcan_ammo;
2073
                        if (amount > 200) {
2074
                                amount = 200;
2075
                        }
2076
                        if (amount)
2077
                                for (;;)
2078
                        {
2079
                                call_object_create_egg(playerobj, 1, POW_VULCAN_AMMO);
2080
                                if (amount <= VULCAN_AMMO_AMOUNT)
2081
                                        break;
2082
                                amount -= VULCAN_AMMO_AMOUNT;
2083
                        }
2084
                }
2085
 
2086
                //      Always drop a shield and energy powerup.
2087
                if (Game_mode & GM_MULTI) {
2088
                        call_object_create_egg(playerobj, 1, POW_SHIELD_BOOST);
2089
                        call_object_create_egg(playerobj, 1, POW_ENERGY);
2090
                }
2091
        }
2092
}
2093
}
2094
 
2095
namespace dsx {
2096
 
2097
void apply_damage_to_player(object &playerobj, const icobjptridx_t killer, fix damage, ubyte possibly_friendly)
2098
{
2099
#if defined(DXX_BUILD_DESCENT_II)
2100
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
2101
#endif
2102
        if (Player_dead_state != player_dead_state::no)
2103
                return;
2104
 
2105
        auto &player_info = playerobj.ctype.player_info;
2106
        if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
2107
                return;
2108
 
2109
        if (possibly_friendly && multi_maybe_disable_friendly_fire(killer))
2110
                return;
2111
 
2112
        if (Endlevel_sequence)
2113
                return;
2114
 
2115
        //for the player, the 'real' shields are maintained in the Players[]
2116
        //array.  The shields value in the player's object are, I think, not
2117
        //used anywhere.  This routine, however, sets the objects shields to
2118
        //be a mirror of the value in the Player structure.
2119
 
2120
        if (get_player_id(playerobj) == Player_num) {           //is this the local player?
2121
                PALETTE_FLASH_ADD(f2i(damage)*4,-f2i(damage/2),-f2i(damage/2)); //flash red
2122
                if (playerobj.shields < 0)
2123
                {
2124
                        /*
2125
                         * If player is already dead (but Player_dead_state has not
2126
                         * caught up yet), preserve the original killer data.
2127
                         */
2128
                        assert(playerobj.flags & OF_SHOULD_BE_DEAD);
2129
                        return;
2130
                }
2131
                const auto shields = (playerobj.shields -= damage);
2132
 
2133
                if (shields < 0)
2134
                {
2135
                        playerobj.ctype.player_info.killer_objnum = killer;
2136
                        playerobj.flags |= OF_SHOULD_BE_DEAD;
2137
 
2138
#if defined(DXX_BUILD_DESCENT_II)
2139
                        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2140
                                if (killer && killer->type == OBJ_ROBOT && robot_is_companion(Robot_info[get_robot_id(killer)]))
2141
                                        BuddyState.Buddy_sorry_time = GameTime64;
2142
#endif
2143
                }
2144
        }
2145
}
2146
 
2147
}
2148
 
2149
namespace dsx {
2150
static void collide_player_and_weapon(const vmobjptridx_t playerobj, const vmobjptridx_t weapon, vms_vector &collision_point)
2151
{
2152
        auto &Objects = LevelUniqueObjectState.Objects;
2153
        auto &imobjptridx = Objects.imptridx;
2154
        fix             damage = weapon->shields;
2155
 
2156
#if defined(DXX_BUILD_DESCENT_II)
2157
        if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
2158
                if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
2159
                        return;
2160
 
2161
        //      Don't collide own smart mines unless direct hit.
2162
        if (get_weapon_id(weapon) == weapon_id_type::SUPERPROX_ID)
2163
                if (playerobj == weapon->ctype.laser_info.parent_num)
2164
                        if (vm_vec_dist_quick(collision_point, playerobj->pos) > playerobj->size)
2165
                                return;
2166
 
2167
        if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
2168
                smega_rock_stuff();
2169
#endif
2170
 
2171
        damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
2172
#if defined(DXX_BUILD_DESCENT_II)
2173
        if (Game_mode & GM_MULTI)
2174
                damage = fixmul(damage, Weapon_info[get_weapon_id(weapon)].multi_damage_scale);
2175
#endif
2176
 
2177
#if 1
2178
        /*
2179
         * Check if persistent weapon already hit this object. If yes, abort.
2180
         * If no, add this object to hitobj_list and do it's damage.
2181
         */
2182
        if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
2183
        {
2184
                if (weapon->ctype.laser_info.test_set_hitobj(playerobj))
2185
                        return;
2186
        }
2187
#endif
2188
 
2189
        const auto &&player_segp = vmsegptridx(playerobj->segnum);
2190
        if (get_player_id(playerobj) == Player_num)
2191
        {
2192
                auto &player_info = playerobj->ctype.player_info;
2193
                multi_digi_link_sound_to_pos((player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE) ? SOUND_WEAPON_HIT_DOOR : SOUND_PLAYER_GOT_HIT, player_segp, 0, collision_point, 0, F1_0);
2194
        }
2195
 
2196
        object_create_explosion(player_segp, collision_point, i2f(10)/2, VCLIP_PLAYER_HIT);
2197
        if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
2198
        {
2199
                const auto obj2weapon = vm_vec_sub(collision_point, playerobj->pos);
2200
                const auto mag = vm_vec_mag(obj2weapon);
2201
                if(mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
2202
                {
2203
                        vm_vec_scale_add(collision_point, playerobj->pos, obj2weapon, fixdiv(playerobj->size, mag));
2204
                        weapon->pos = collision_point;
2205
                }
2206
#if defined(DXX_BUILD_DESCENT_I)
2207
                explode_badass_weapon(weapon, weapon->pos);
2208
#elif defined(DXX_BUILD_DESCENT_II)
2209
                explode_badass_weapon(weapon, collision_point);
2210
#endif
2211
        }
2212
 
2213
        maybe_kill_weapon(weapon,playerobj);
2214
 
2215
        bump_two_objects(playerobj, weapon, 0); //no damage from bump
2216
 
2217
        if ( !Weapon_info[get_weapon_id(weapon)].damage_radius ) {
2218
                imobjptridx_t killer = object_none;
2219
                if ( weapon->ctype.laser_info.parent_num != object_none )
2220
                        killer = imobjptridx(weapon->ctype.laser_info.parent_num);
2221
 
2222
//              if (weapon->id == SMART_HOMING_ID)
2223
//                      damage /= 4;
2224
 
2225
                        apply_damage_to_player( playerobj, killer, damage, 1);
2226
        }
2227
 
2228
        //      Robots become aware of you if you get hit.
2229
        ai_do_cloak_stuff();
2230
 
2231
        return;
2232
}
2233
}
2234
 
2235
//      Nasty robots are the ones that attack you by running into you and doing lots of damage.
2236
void collide_player_and_nasty_robot(const vmobjptridx_t playerobj, const vmobjptridx_t robot, const vms_vector &collision_point)
2237
{
2238
        const auto &&player_segp = vmsegptridx(playerobj->segnum);
2239
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2240
        digi_link_sound_to_pos(Robot_info[get_robot_id(robot)].claw_sound, player_segp, 0, collision_point, 0, F1_0);
2241
        object_create_explosion(player_segp, collision_point, i2f(10)/2, VCLIP_PLAYER_HIT);
2242
 
2243
        bump_two_objects(playerobj, robot, 0);  //no damage from bump
2244
 
2245
        apply_damage_to_player(playerobj, robot, F1_0 * (GameUniqueState.Difficulty_level + 1), 0);
2246
}
2247
 
2248
static vms_vector find_exit_direction(vms_vector result, const object &objp, const cscusegment segp)
2249
{
2250
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
2251
        auto &Vertices = LevelSharedVertexState.get_vertices();
2252
        auto &vcvertptr = Vertices.vcptr;
2253
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
2254
        auto &vcwallptr = Walls.vcptr;
2255
        for (unsigned side = MAX_SIDES_PER_SEGMENT; side --;)
2256
                if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, side) & WID_FLY_FLAG)
2257
                {
2258
                        const auto &&exit_point = compute_center_point_on_side(vcvertptr, segp, side);
2259
                        vm_vec_add2(result, vm_vec_normalized_quick(vm_vec_sub(exit_point, objp.pos)));
2260
                        break;
2261
                }
2262
        return result;
2263
}
2264
 
2265
namespace dsx {
2266
void collide_player_and_materialization_center(const vmobjptridx_t objp)
2267
{
2268
        const auto &&segp = vmsegptridx(objp->segnum);
2269
        digi_link_sound_to_pos(SOUND_PLAYER_GOT_HIT, segp, 0, objp->pos, 0, F1_0);
2270
        object_create_explosion(segp, objp->pos, i2f(10)/2, VCLIP_PLAYER_HIT);
2271
 
2272
        if (get_player_id(objp) != Player_num)
2273
                return;
2274
 
2275
        auto rand_vec = make_random_vector();
2276
        rand_vec.x /= 4;
2277
        rand_vec.y /= 4;
2278
        rand_vec.z /= 4;
2279
        auto exit_dir = find_exit_direction(rand_vec, objp, segp);
2280
        vm_vec_normalize_quick(exit_dir);
2281
        bump_one_object(objp, exit_dir, 64*F1_0);
2282
 
2283
#if defined(DXX_BUILD_DESCENT_I)
2284
        apply_damage_to_player( objp, object_none, 4*F1_0, 0);
2285
#elif defined(DXX_BUILD_DESCENT_II)
2286
        apply_damage_to_player( objp, objp, 4*F1_0, 0); //      Changed, MK, 2/19/96, make killer the player, so if you die in matcen, will say you killed yourself
2287
#endif
2288
 
2289
        return;
2290
 
2291
}
2292
}
2293
 
2294
void collide_robot_and_materialization_center(const vmobjptridx_t objp)
2295
{
2296
        const auto &&segp = vmsegptridx(objp->segnum);
2297
        digi_link_sound_to_pos(SOUND_ROBOT_HIT, segp, 0, objp->pos, 0, F1_0);
2298
 
2299
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2300
        if ( Robot_info[get_robot_id(objp)].exp1_vclip_num > -1 )
2301
                object_create_explosion(segp, objp->pos, (objp->size / 2 * 3) / 4, Robot_info[get_robot_id(objp)].exp1_vclip_num);
2302
 
2303
        auto exit_dir = find_exit_direction({}, objp, segp);
2304
        bump_one_object(objp, exit_dir, 8*F1_0);
2305
 
2306
        apply_damage_to_robot( objp, F1_0, object_none);
2307
 
2308
        return;
2309
}
2310
 
2311
void collide_live_local_player_and_powerup(const vmobjptridx_t powerup)
2312
{
2313
        if (do_powerup(powerup))
2314
        {
2315
                powerup->flags |= OF_SHOULD_BE_DEAD;
2316
                if (Game_mode & GM_MULTI)
2317
                        multi_send_remobj(powerup);
2318
        }
2319
}
2320
 
2321
static void collide_player_and_powerup(object &playerobj, const vmobjptridx_t powerup, const vms_vector &)
2322
{
2323
        if (!Endlevel_sequence &&
2324
                Player_dead_state == player_dead_state::no &&
2325
                get_player_id(playerobj) == Player_num)
2326
        {
2327
                collide_live_local_player_and_powerup(powerup);
2328
        }
2329
        else if ((Game_mode & GM_MULTI_COOP) && (get_player_id(playerobj) != Player_num))
2330
        {
2331
                switch (get_powerup_id(powerup)) {
2332
                        case POW_KEY_BLUE:
2333
                                playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_BLUE_KEY;
2334
                                break;
2335
                        case POW_KEY_RED:
2336
                                playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_RED_KEY;
2337
                                break;
2338
                        case POW_KEY_GOLD:
2339
                                playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_GOLD_KEY;
2340
                                break;
2341
                        default:
2342
                                break;
2343
                }
2344
        }
2345
        return;
2346
}
2347
 
2348
static void collide_player_and_clutter(const vmobjptridx_t  playerobj, const vmobjptridx_t  clutter, const vms_vector &collision_point)
2349
{
2350
        if (check_collision_delayfunc_exec())
2351
                digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vcsegptridx(playerobj->segnum), 0, collision_point, 0, F1_0);
2352
        bump_two_objects(clutter, playerobj, 1);
2353
        return;
2354
}
2355
 
2356
//      See if weapon1 creates a badass explosion.  If so, create the explosion
2357
//      Return true if weapon does proximity (as opposed to only contact) damage when it explodes.
2358
namespace dsx {
2359
int maybe_detonate_weapon(const vmobjptridx_t weapon1, object &weapon2, const vms_vector &collision_point)
2360
{
2361
        if ( Weapon_info[get_weapon_id(weapon1)].damage_radius ) {
2362
                auto dist = vm_vec_dist_quick(weapon1->pos, weapon2.pos);
2363
                if (dist < F1_0*5) {
2364
                        maybe_kill_weapon(weapon1,weapon2);
2365
                        if (weapon1->flags & OF_SHOULD_BE_DEAD) {
2366
#if defined(DXX_BUILD_DESCENT_I)
2367
                                explode_badass_weapon(weapon1, weapon1->pos);
2368
#elif defined(DXX_BUILD_DESCENT_II)
2369
                                explode_badass_weapon(weapon1, collision_point);
2370
#endif
2371
                                digi_link_sound_to_pos(Weapon_info[get_weapon_id(weapon1)].robot_hit_sound, vcsegptridx(weapon1->segnum), 0, collision_point, 0, F1_0);
2372
                        }
2373
                        return 1;
2374
                } else {
2375
                        weapon1->lifeleft = min(static_cast<fix>(dist) / 64, F1_0);
2376
                        return 1;
2377
                }
2378
        } else
2379
                return 0;
2380
}
2381
}
2382
 
2383
namespace dsx {
2384
static void collide_weapon_and_weapon(const vmobjptridx_t weapon1, const vmobjptridx_t weapon2, const vms_vector &collision_point)
2385
{
2386
#if defined(DXX_BUILD_DESCENT_II)
2387
        // -- Does this look buggy??:  if (weapon1->id == PMINE_ID && weapon1->id == PMINE_ID)
2388
        if (get_weapon_id(weapon1) == weapon_id_type::PMINE_ID && get_weapon_id(weapon2) == weapon_id_type::PMINE_ID)
2389
                return;         //these can't blow each other up
2390
 
2391
        if (get_weapon_id(weapon1) == weapon_id_type::OMEGA_ID) {
2392
                if (!ok_to_do_omega_damage(weapon1)) // see comment in laser.c
2393
                        return;
2394
        } else if (get_weapon_id(weapon2) == weapon_id_type::OMEGA_ID) {
2395
                if (!ok_to_do_omega_damage(weapon2)) // see comment in laser.c
2396
                        return;
2397
        }
2398
#endif
2399
 
2400
        if ((Weapon_info[get_weapon_id(weapon1)].destroyable) || (Weapon_info[get_weapon_id(weapon2)].destroyable)) {
2401
                // shooting Plasma will make bombs explode one drops at the same time since hitboxes overlap. Small HACK to get around this issue. if the player moves away from the bomb at least...
2402
                if ((GameTime64 < weapon1->ctype.laser_info.creation_time + (F1_0/5)) && (GameTime64 < weapon2->ctype.laser_info.creation_time + (F1_0/5)) && (weapon1->ctype.laser_info.parent_num == weapon2->ctype.laser_info.parent_num))
2403
                        return;
2404
                //      Bug reported by Adam Q. Pletcher on September 9, 1994, smart bomb homing missiles were toasting each other.
2405
                if ((get_weapon_id(weapon1) == get_weapon_id(weapon2)) && (weapon1->ctype.laser_info.parent_num == weapon2->ctype.laser_info.parent_num))
2406
                        return;
2407
 
2408
#if defined(DXX_BUILD_DESCENT_I)
2409
                if (Weapon_info[get_weapon_id(weapon1)].destroyable)
2410
                        if (maybe_detonate_weapon(weapon1, weapon2, collision_point))
2411
                                maybe_kill_weapon(weapon2,weapon1);
2412
 
2413
                if (Weapon_info[get_weapon_id(weapon2)].destroyable)
2414
                        if (maybe_detonate_weapon(weapon2, weapon1, collision_point))
2415
                                maybe_kill_weapon(weapon1,weapon2);
2416
#elif defined(DXX_BUILD_DESCENT_II)
2417
                if (Weapon_info[get_weapon_id(weapon1)].destroyable)
2418
                        if (maybe_detonate_weapon(weapon1, weapon2, collision_point))
2419
                                maybe_detonate_weapon(weapon2,weapon1, collision_point);
2420
 
2421
                if (Weapon_info[get_weapon_id(weapon2)].destroyable)
2422
                        if (maybe_detonate_weapon(weapon2, weapon1, collision_point))
2423
                                maybe_detonate_weapon(weapon1,weapon2, collision_point);
2424
#endif
2425
        }
2426
 
2427
}
2428
}
2429
 
2430
namespace dsx {
2431
static void collide_weapon_and_debris(const vmobjptridx_t weapon, const vmobjptridx_t debris, const vms_vector &collision_point)
2432
{
2433
#if defined(DXX_BUILD_DESCENT_II)
2434
        //      Hack!  Prevent debris from causing bombs spewed at player death to detonate!
2435
        if ((get_weapon_id(weapon) == weapon_id_type::PROXIMITY_ID) || (get_weapon_id(weapon) == weapon_id_type::SUPERPROX_ID)) {
2436
                if (weapon->ctype.laser_info.creation_time + F1_0/2 > GameTime64)
2437
                        return;
2438
        }
2439
#endif
2440
        if ( (weapon->ctype.laser_info.parent_type==OBJ_PLAYER) && !(debris->flags & OF_EXPLODING) )    {
2441
                digi_link_sound_to_pos(SOUND_ROBOT_HIT, vcsegptridx(weapon->segnum), 0, collision_point, 0, F1_0);
2442
 
2443
                explode_object(debris,0);
2444
                if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
2445
                        explode_badass_weapon(weapon, collision_point);
2446
                maybe_kill_weapon(weapon,debris);
2447
                if (!(weapon->mtype.phys_info.flags & PF_PERSISTENT))
2448
                        weapon->flags |= OF_SHOULD_BE_DEAD;
2449
        }
2450
        return;
2451
}
2452
}
2453
 
2454
#if defined(DXX_BUILD_DESCENT_I)
2455
#define DXX_COLLISION_TABLE(NO,DO)      \
2456
 
2457
#elif defined(DXX_BUILD_DESCENT_II)
2458
#define DXX_COLLISION_TABLE(NO,DO)      \
2459
        NO##_SAME_COLLISION(OBJ_MARKER) \
2460
        DO##_COLLISION(OBJ_PLAYER, OBJ_MARKER, collide_player_and_marker)       \
2461
        NO##_COLLISION(OBJ_ROBOT, OBJ_MARKER)   \
2462
        NO##_COLLISION(OBJ_HOSTAGE, OBJ_MARKER) \
2463
        NO##_COLLISION(OBJ_WEAPON, OBJ_MARKER)  \
2464
        NO##_COLLISION(OBJ_CAMERA, OBJ_MARKER)  \
2465
        NO##_COLLISION(OBJ_POWERUP, OBJ_MARKER) \
2466
        NO##_COLLISION(OBJ_DEBRIS, OBJ_MARKER)  \
2467
 
2468
#endif
2469
 
2470
#define COLLIDE_IGNORE_COLLISION(O1,O2,C)
2471
 
2472
#define COLLISION_TABLE(NO,DO)  \
2473
        NO##_SAME_COLLISION( OBJ_FIREBALL)      \
2474
        DO##_SAME_COLLISION( OBJ_ROBOT, collide_robot_and_robot )       \
2475
        NO##_SAME_COLLISION( OBJ_HOSTAGE)       \
2476
        DO##_SAME_COLLISION( OBJ_PLAYER, collide_player_and_player )    \
2477
        DO##_SAME_COLLISION( OBJ_WEAPON, collide_weapon_and_weapon )    \
2478
        NO##_SAME_COLLISION( OBJ_CAMERA)        \
2479
        NO##_SAME_COLLISION( OBJ_POWERUP)       \
2480
        NO##_SAME_COLLISION( OBJ_DEBRIS)        \
2481
        DO##_COLLISION( OBJ_WALL, OBJ_ROBOT, COLLIDE_IGNORE_COLLISION)  \
2482
        DO##_COLLISION( OBJ_WALL, OBJ_WEAPON, COLLIDE_IGNORE_COLLISION) \
2483
        DO##_COLLISION( OBJ_WALL, OBJ_PLAYER, COLLIDE_IGNORE_COLLISION) \
2484
        DO##_COLLISION( OBJ_WALL, OBJ_POWERUP, COLLIDE_IGNORE_COLLISION)        \
2485
        DO##_COLLISION( OBJ_WALL, OBJ_DEBRIS, COLLIDE_IGNORE_COLLISION) \
2486
        NO##_COLLISION( OBJ_FIREBALL, OBJ_ROBOT)        \
2487
        NO##_COLLISION( OBJ_FIREBALL, OBJ_HOSTAGE)      \
2488
        NO##_COLLISION( OBJ_FIREBALL, OBJ_PLAYER)       \
2489
        NO##_COLLISION( OBJ_FIREBALL, OBJ_WEAPON)       \
2490
        NO##_COLLISION( OBJ_FIREBALL, OBJ_CAMERA)       \
2491
        NO##_COLLISION( OBJ_FIREBALL, OBJ_POWERUP)      \
2492
        NO##_COLLISION( OBJ_FIREBALL, OBJ_DEBRIS)       \
2493
        NO##_COLLISION( OBJ_ROBOT, OBJ_HOSTAGE) \
2494
        DO##_COLLISION( OBJ_ROBOT, OBJ_PLAYER,  collide_robot_and_player )      \
2495
        DO##_COLLISION( OBJ_ROBOT, OBJ_WEAPON,  collide_robot_and_weapon )      \
2496
        NO##_COLLISION( OBJ_ROBOT, OBJ_CAMERA)  \
2497
        NO##_COLLISION( OBJ_ROBOT, OBJ_POWERUP) \
2498
        NO##_COLLISION( OBJ_ROBOT, OBJ_DEBRIS)  \
2499
        DO##_COLLISION( OBJ_HOSTAGE, OBJ_PLAYER,  collide_hostage_and_player )  \
2500
        DO##_COLLISION( OBJ_HOSTAGE, OBJ_WEAPON, COLLIDE_IGNORE_COLLISION)      \
2501
        NO##_COLLISION( OBJ_HOSTAGE, OBJ_CAMERA)        \
2502
        NO##_COLLISION( OBJ_HOSTAGE, OBJ_POWERUP)       \
2503
        NO##_COLLISION( OBJ_HOSTAGE, OBJ_DEBRIS)        \
2504
        DO##_COLLISION( OBJ_PLAYER, OBJ_WEAPON,  collide_player_and_weapon )    \
2505
        NO##_COLLISION( OBJ_PLAYER, OBJ_CAMERA) \
2506
        DO##_COLLISION( OBJ_PLAYER, OBJ_POWERUP, collide_player_and_powerup )   \
2507
        NO##_COLLISION( OBJ_PLAYER, OBJ_DEBRIS) \
2508
        DO##_COLLISION( OBJ_PLAYER, OBJ_CNTRLCEN, collide_player_and_controlcen )       \
2509
        DO##_COLLISION( OBJ_PLAYER, OBJ_CLUTTER, collide_player_and_clutter )   \
2510
        NO##_COLLISION( OBJ_WEAPON, OBJ_CAMERA) \
2511
        NO##_COLLISION( OBJ_WEAPON, OBJ_POWERUP)        \
2512
        DO##_COLLISION( OBJ_WEAPON, OBJ_DEBRIS,  collide_weapon_and_debris )    \
2513
        NO##_COLLISION( OBJ_CAMERA, OBJ_POWERUP)        \
2514
        NO##_COLLISION( OBJ_CAMERA, OBJ_DEBRIS) \
2515
        NO##_COLLISION( OBJ_POWERUP, OBJ_DEBRIS)        \
2516
        DO##_COLLISION( OBJ_WEAPON, OBJ_CNTRLCEN, collide_weapon_and_controlcen )       \
2517
        DO##_COLLISION( OBJ_ROBOT, OBJ_CNTRLCEN, collide_robot_and_controlcen ) \
2518
        DO##_COLLISION( OBJ_WEAPON, OBJ_CLUTTER, collide_weapon_and_clutter )   \
2519
        DXX_COLLISION_TABLE(NO,DO)      \
2520
 
2521
 
2522
/* DPH: Put these macros on one long line to avoid CR/LF problems on linux */
2523
#define COLLISION_OF(a,b) (((a)<<4) + (b))
2524
 
2525
#define DO_COLLISION(type1,type2,collision_function)    \
2526
        case COLLISION_OF( (type1), (type2) ):  \
2527
                static_assert(type1 < type2, "do " #type1 " < " #type2);        \
2528
                collision_function( (A), (B), collision_point );        \
2529
                break;
2530
#define DO_SAME_COLLISION(type1,collision_function)     \
2531
        case COLLISION_OF( (type1), (type1) ):  \
2532
                collision_function( (A), (B), collision_point );        \
2533
                break;
2534
 
2535
//these next two macros define a case that does nothing
2536
#define NO_COLLISION(type1,type2)       \
2537
        case COLLISION_OF( (type1), (type2) ):  \
2538
                static_assert(type1 < type2, "no " #type1 " < " #type2);        \
2539
                break;
2540
 
2541
#define NO_SAME_COLLISION(type1)        \
2542
        case COLLISION_OF( (type1), (type1) ):  \
2543
                break;
2544
 
2545
template <typename T, std::size_t V>
2546
struct assert_no_truncation
2547
{
2548
        static_assert(static_cast<T>(V) == V, "truncation error");
2549
};
2550
 
2551
void collide_two_objects(vmobjptridx_t A, vmobjptridx_t B, vms_vector &collision_point)
2552
{
2553
        if (B->type < A->type)
2554
        {
2555
                using std::swap;
2556
                swap(A, B);
2557
        }
2558
        uint_fast8_t at, bt;
2559
        const char *emsg;
2560
        if (((at = A->type) >= MAX_OBJECT_TYPES && (emsg = "illegal object type A", true)) ||
2561
                ((bt = B->type) >= MAX_OBJECT_TYPES && (emsg = "illegal object type B", true)))
2562
                throw std::runtime_error(emsg);
2563
        uint_fast8_t collision_type = COLLISION_OF(at, bt);
2564
        struct assert_object_type_not_truncated : std::pair<assert_no_truncation<decltype(at), MAX_OBJECT_TYPES>, assert_no_truncation<decltype(bt), MAX_OBJECT_TYPES>> {};
2565
        struct assert_collision_of_not_truncated : assert_no_truncation<decltype(collision_type), COLLISION_OF(MAX_OBJECT_TYPES - 1, MAX_OBJECT_TYPES - 1)> {};
2566
        switch( collision_type )        {
2567
                COLLISION_TABLE(NO,DO)
2568
        default:
2569
                Int3(); //Error( "Unhandled collision_type in collide.c!\n" );
2570
        }
2571
}
2572
 
2573
#define ENABLE_COLLISION(type1,type2,f) \
2574
        COLLISION_RESULT(type1,type2,RESULT_CHECK);     \
2575
        COLLISION_RESULT(type2,type1,RESULT_CHECK);
2576
 
2577
#define DISABLE_COLLISION(type1,type2)  \
2578
        COLLISION_RESULT(type1,type2,RESULT_NOTHING);   \
2579
        COLLISION_RESULT(type2,type1,RESULT_NOTHING);
2580
 
2581
#define ENABLE_SAME_COLLISION(type,f)   COLLISION_RESULT(type,type,RESULT_CHECK);
2582
#define DISABLE_SAME_COLLISION(type)    COLLISION_RESULT(type,type,RESULT_NOTHING);
2583
 
2584
namespace {
2585
 
2586
template <object_type_t A, object_type_t B>
2587
struct collision_result_t : public std::conditional<(B < A), collision_result_t<B, A>, std::integral_constant<ubyte, RESULT_NOTHING>>::type {};
2588
 
2589
#define COLLISION_RESULT(type1,type2,result)    \
2590
        template <>     \
2591
        struct collision_result_t<type1, type2> : public std::integral_constant<ubyte, result> {}
2592
 
2593
COLLISION_TABLE(DISABLE, ENABLE);
2594
 
2595
template <std::size_t R, std::size_t... C>
2596
static inline constexpr collision_inner_array_t collide_init(std::index_sequence<C...>)
2597
{
2598
        static_assert((COLLISION_OF(R, 0) < COLLISION_OF(R, sizeof...(C) - 1)), "ambiguous collision");
2599
        static_assert((COLLISION_OF(R, sizeof...(C) - 1) < COLLISION_OF(R + 1, 0)), "ambiguous collision");
2600
        return collision_inner_array_t{{
2601
                collision_result_t<static_cast<object_type_t>(R), static_cast<object_type_t>(C)>::value...
2602
        }};
2603
}
2604
 
2605
template <std::size_t... R, std::size_t... C>
2606
static inline constexpr collision_outer_array_t collide_init(std::index_sequence<R...>, std::index_sequence<C...> c)
2607
{
2608
        return collision_outer_array_t{{collide_init<R>(c)...}};
2609
}
2610
 
2611
}
2612
 
2613
namespace dsx {
2614
 
2615
constexpr collision_outer_array_t CollisionResult = collide_init(std::make_index_sequence<MAX_OBJECT_TYPES>(), std::make_index_sequence<MAX_OBJECT_TYPES>());
2616
 
2617
}
2618
 
2619
#undef DISABLE_COLLISION
2620
#undef ENABLE_COLLISION
2621
 
2622
#define ENABLE_COLLISION(T1,T2) static_assert((!!collision_result_t<T1, T2>::value && !!collision_result_t<T2, T1>::value), #T1 " " #T2);
2623
#define DISABLE_COLLISION(T1,T2)        static_assert((!collision_result_t<T1, T2>::value && !collision_result_t<T2, T1>::value), #T1 " " #T2);
2624
 
2625
        ENABLE_COLLISION( OBJ_WALL, OBJ_ROBOT );
2626
        ENABLE_COLLISION( OBJ_WALL, OBJ_WEAPON );
2627
        ENABLE_COLLISION( OBJ_WALL, OBJ_PLAYER  );
2628
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_FIREBALL );
2629
 
2630
        ENABLE_COLLISION( OBJ_ROBOT, OBJ_ROBOT );
2631
//      DISABLE_COLLISION( OBJ_ROBOT, OBJ_ROBOT );      //      ALERT: WARNING: HACK: MK = RESPONSIBLE! TESTING!!
2632
        DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_HOSTAGE );
2633
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_PLAYER );
2634
        ENABLE_COLLISION( OBJ_WEAPON, OBJ_WEAPON );
2635
        DISABLE_COLLISION( OBJ_CAMERA, OBJ_CAMERA );
2636
        DISABLE_COLLISION( OBJ_POWERUP, OBJ_POWERUP );
2637
        DISABLE_COLLISION( OBJ_DEBRIS, OBJ_DEBRIS );
2638
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_ROBOT );
2639
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_HOSTAGE );
2640
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_PLAYER );
2641
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_WEAPON );
2642
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_CAMERA );
2643
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_POWERUP );
2644
        DISABLE_COLLISION( OBJ_FIREBALL, OBJ_DEBRIS );
2645
        DISABLE_COLLISION( OBJ_ROBOT, OBJ_HOSTAGE );
2646
        ENABLE_COLLISION( OBJ_ROBOT, OBJ_PLAYER );
2647
        ENABLE_COLLISION( OBJ_ROBOT, OBJ_WEAPON );
2648
        DISABLE_COLLISION( OBJ_ROBOT, OBJ_CAMERA );
2649
        DISABLE_COLLISION( OBJ_ROBOT, OBJ_POWERUP );
2650
        DISABLE_COLLISION( OBJ_ROBOT, OBJ_DEBRIS );
2651
        ENABLE_COLLISION( OBJ_HOSTAGE, OBJ_PLAYER );
2652
        ENABLE_COLLISION( OBJ_HOSTAGE, OBJ_WEAPON );
2653
        DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_CAMERA );
2654
        DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_POWERUP );
2655
        DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_DEBRIS );
2656
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_WEAPON );
2657
        DISABLE_COLLISION( OBJ_PLAYER, OBJ_CAMERA );
2658
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_POWERUP );
2659
        DISABLE_COLLISION( OBJ_PLAYER, OBJ_DEBRIS );
2660
        DISABLE_COLLISION( OBJ_WEAPON, OBJ_CAMERA );
2661
        DISABLE_COLLISION( OBJ_WEAPON, OBJ_POWERUP );
2662
        ENABLE_COLLISION( OBJ_WEAPON, OBJ_DEBRIS );
2663
        DISABLE_COLLISION( OBJ_CAMERA, OBJ_POWERUP );
2664
        DISABLE_COLLISION( OBJ_CAMERA, OBJ_DEBRIS );
2665
        DISABLE_COLLISION( OBJ_POWERUP, OBJ_DEBRIS );
2666
        ENABLE_COLLISION( OBJ_POWERUP, OBJ_WALL );
2667
        ENABLE_COLLISION( OBJ_WEAPON, OBJ_CNTRLCEN )
2668
        ENABLE_COLLISION( OBJ_WEAPON, OBJ_CLUTTER )
2669
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_CNTRLCEN )
2670
        ENABLE_COLLISION( OBJ_ROBOT, OBJ_CNTRLCEN )
2671
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_CLUTTER )
2672
#if defined(DXX_BUILD_DESCENT_II)
2673
        ENABLE_COLLISION( OBJ_PLAYER, OBJ_MARKER );
2674
#endif
2675
        ENABLE_COLLISION( OBJ_DEBRIS, OBJ_WALL );
2676
 
2677
namespace dsx {
2678
 
2679
window_event_result collide_object_with_wall(
2680
#if defined(DXX_BUILD_DESCENT_II)
2681
        const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
2682
#endif
2683
        const vmobjptridx_t A, fix hitspeed, const vmsegptridx_t hitseg, short hitwall, const vms_vector &hitpt)
2684
{
2685
        auto &Objects = LevelUniqueObjectState.Objects;
2686
 
2687
        switch( A->type )       {
2688
        case OBJ_NONE:
2689
                Error( "A object of type NONE hit a wall!\n");
2690
                break;
2691
        case OBJ_PLAYER:                collide_player_and_wall(A,hitspeed,hitseg,hitwall,hitpt); break;
2692
                case OBJ_WEAPON:
2693
                        return collide_weapon_and_wall(
2694
#if defined(DXX_BUILD_DESCENT_II)
2695
                                LevelSharedDestructibleLightState,
2696
#endif
2697
                                Objects, vmsegptridx, A, hitseg, hitwall, hitpt);
2698
        case OBJ_DEBRIS:                collide_debris_and_wall(A,hitseg,hitwall,hitpt); break;
2699
 
2700
        case OBJ_FIREBALL:      break;          //collide_fireball_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
2701
                case OBJ_ROBOT:
2702
                {
2703
                        auto &Walls = LevelUniqueWallSubsystemState.Walls;
2704
                        auto &vcwallptr = Walls.vcptr;
2705
                        collide_robot_and_wall(vcwallptr, A, hitseg, hitwall, hitpt);
2706
                        break;
2707
                }
2708
        case OBJ_HOSTAGE:               break;          //collide_hostage_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
2709
        case OBJ_CAMERA:                break;          //collide_camera_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
2710
        case OBJ_POWERUP:               break;          //collide_powerup_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
2711
        case OBJ_GHOST:         break;  //do nothing
2712
 
2713
        default:
2714
                Error( "Unhandled object type hit wall in collide.c\n" );
2715
        }
2716
 
2717
        return window_event_result::handled;    // assume handled
2718
}
2719
 
2720
}