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
 * object rendering
23
 *
24
 */
25
 
26
#include <algorithm>
27
#include <cstdlib>
28
#include <stdio.h>
29
 
30
#include "inferno.h"
31
#include "game.h"
32
#include "gr.h"
33
#include "bm.h"
34
#include "3d.h"
35
#include "segment.h"
36
#include "texmap.h"
37
#include "laser.h"
38
#include "key.h"
39
#include "gameseg.h"
40
#include "textures.h"
41
#include "object.h"
42
#include "controls.h"
43
#include "physics.h"
44
#include "slew.h"
45
#include "render.h"
46
#include "wall.h"
47
#include "vclip.h"
48
#include "robot.h"
49
#include "interp.h"
50
#include "fireball.h"
51
#include "laser.h"
52
#include "dxxerror.h"
53
#include "ai.h"
54
#include "hostage.h"
55
#include "morph.h"
56
#include "cntrlcen.h"
57
#include "powerup.h"
58
#include "fuelcen.h"
59
#include "endlevel.h"
60
#include "hudmsg.h"
61
#include "sounds.h"
62
#include "collide.h"
63
#include "lighting.h"
64
#include "newdemo.h"
65
#include "player.h"
66
#include "weapon.h"
67
#include "newmenu.h"
68
#include "gauges.h"
69
#include "multi.h"
70
#include "menu.h"
71
#include "args.h"
72
#include "text.h"
73
#include "piggy.h"
74
#include "switch.h"
75
#include "gameseq.h"
76
#include "playsave.h"
77
#include "timer.h"
78
#if DXX_USE_EDITOR
79
#include "editor/editor.h"
80
#endif
81
 
82
#include "compiler-range_for.h"
83
#include "d_range.h"
84
#include "partial_range.h"
85
#include <utility>
86
 
87
using std::min;
88
using std::max;
89
 
90
namespace dsx {
91
static void obj_detach_all(object_array &Objects, object_base &parent);
92
static void obj_detach_one(object_array &Objects, object &sub);
93
 
94
static int is_proximity_bomb_or_any_smart_mine(const weapon_id_type id)
95
{
96
        const auto r = is_proximity_bomb_or_player_smart_mine(id);
97
#if defined(DXX_BUILD_DESCENT_II)
98
        if (r)
99
                return r;
100
        // superprox dropped by robots have their own ID not considered by is_proximity_bomb_or_player_smart_mine() and since that function is used in many other places, I didn't feel safe to add this weapon type in it
101
        if (id == weapon_id_type::ROBOT_SUPERPROX_ID)
102
                return 1;
103
#endif
104
        return r;
105
}
106
 
107
/*
108
 *  Global variables
109
 */
110
 
111
object *ConsoleObject;                                  //the object that is the player
112
}
113
 
114
namespace dcx {
115
 
116
//Data for objects
117
 
118
// -- Object stuff
119
 
120
//info on the various types of objects
121
}
122
 
123
namespace dsx {
124
#ifndef RELEASE
125
//set viewer object to next object in array
126
void object_goto_next_viewer()
127
{
128
        auto &Objects = LevelUniqueObjectState.Objects;
129
        auto &vcobjptr = Objects.vcptr;
130
        auto &vcobjptridx = Objects.vcptridx;
131
        auto &vmobjptr = Objects.vmptr;
132
        objnum_t start_obj;
133
        start_obj = vcobjptridx(Viewer);                //get viewer object number
134
 
135
        range_for (const auto &&i, vcobjptr)
136
        {
137
                (void)i;
138
                start_obj++;
139
                if (start_obj > Highest_object_index ) start_obj = 0;
140
 
141
                auto &objp = *vmobjptr(start_obj);
142
                if (objp.type != OBJ_NONE)
143
                {
144
                        Viewer = &objp;
145
                        return;
146
                }
147
        }
148
 
149
        Error( "Could not find a viewer object!" );
150
 
151
}
152
#endif
153
 
154
imobjptridx_t obj_find_first_of_type(fvmobjptridx &vmobjptridx, const object_type_t type)
155
{
156
        range_for (const auto &&i, vmobjptridx)
157
        {
158
                if (i->type==type)
159
                        return i;
160
        }
161
        return object_none;
162
}
163
 
164
}
165
 
166
namespace dcx {
167
 
168
icobjidx_t laser_info::get_last_hitobj() const
169
{
170
        if (!hitobj_count)
171
                /* If no elements, return object_none */
172
                return object_none;
173
        /* Return the most recently written element.  `hitobj_pos`
174
         * indicates the element to write next, so return
175
         * hitobj_values[hitobj_pos - 1].  When hitobj_pos == 0, the
176
         * most recently written element is at the end of the array, not
177
         * before the beginning of the array.
178
         */
179
        if (!hitobj_pos)
180
                return hitobj_values.back();
181
        return hitobj_values[hitobj_pos - 1];
182
}
183
 
184
//draw an object that has one bitmap & doesn't rotate
185
void draw_object_blob(grs_canvas &canvas, const object_base &obj, const bitmap_index bmi)
186
{
187
        auto &bm = GameBitmaps[bmi.index];
188
        PIGGY_PAGE_IN( bmi );
189
 
190
        const auto osize = obj.size;
191
        // draw these with slight offset to viewer preventing too much ugly clipping
192
        auto pos = obj.pos;
193
        if (obj.type == OBJ_FIREBALL && get_fireball_id(obj) == VCLIP_VOLATILE_WALL_HIT)
194
        {
195
                vms_vector offs_vec;
196
                vm_vec_normalized_dir_quick(offs_vec, Viewer->pos, pos);
197
                vm_vec_scale_add2(pos,offs_vec,F1_0);
198
        }
199
 
200
        using wh = std::pair<fix, fix>;
201
        const auto bm_w = bm.bm_w;
202
        const auto bm_h = bm.bm_h;
203
        const auto p = (bm_w > bm_h)
204
                ? wh(osize, fixmuldiv(osize, bm_h, bm_w))
205
                : wh(fixmuldiv(osize, bm_w, bm_h), osize);
206
        g3_draw_bitmap(canvas, pos, p.first, p.second, bm);
207
}
208
 
209
}
210
 
211
namespace dsx {
212
 
213
//draw an object that is a texture-mapped rod
214
void draw_object_tmap_rod(grs_canvas &canvas, const d_level_unique_light_state *const LevelUniqueLightState, const vcobjptridx_t obj, const bitmap_index bitmapi)
215
{
216
        g3s_lrgb light;
217
        PIGGY_PAGE_IN(bitmapi);
218
 
219
        auto &bitmap = GameBitmaps[bitmapi.index];
220
 
221
        const auto delta = vm_vec_copy_scale(obj->orient.uvec,obj->size);
222
 
223
        const auto top_v = vm_vec_add(obj->pos,delta);
224
        const auto bot_v = vm_vec_sub(obj->pos,delta);
225
 
226
        const auto top_p = g3_rotate_point(top_v);
227
        const auto bot_p = g3_rotate_point(bot_v);
228
 
229
        if (LevelUniqueLightState)
230
        {
231
                light = compute_object_light(*LevelUniqueLightState, obj);
232
        }
233
        else
234
        {
235
                light.r = light.g = light.b = f1_0;
236
        }
237
        g3_draw_rod_tmap(canvas, bitmap, bot_p, obj->size, top_p, obj->size, light);
238
}
239
 
240
//used for robot engine glow
241
#define MAX_VELOCITY i2f(50)
242
 
243
//what darkening level to use when cloaked
244
#define CLOAKED_FADE_LEVEL              28
245
 
246
#define CLOAK_FADEIN_DURATION_PLAYER    F2_0
247
#define CLOAK_FADEOUT_DURATION_PLAYER   F2_0
248
 
249
#define CLOAK_FADEIN_DURATION_ROBOT     F1_0
250
#define CLOAK_FADEOUT_DURATION_ROBOT    F1_0
251
 
252
//do special cloaked render
253
static void draw_cloaked_object(grs_canvas &canvas, const object_base &obj, const g3s_lrgb light, glow_values_t glow, const fix64 cloak_start_time, const fix total_cloaked_time, const fix Cloak_fadein_duration, const fix Cloak_fadeout_duration)
254
{
255
        fix cloak_delta_time;
256
        fix light_scale=F1_0;
257
        int cloak_value=0;
258
        int fading=0;           //if true, fading, else cloaking
259
 
260
        cloak_delta_time = GameTime64 - cloak_start_time;
261
 
262
        if (cloak_delta_time < Cloak_fadein_duration/2) {
263
 
264
#if defined(DXX_BUILD_DESCENT_I)
265
                light_scale = Cloak_fadein_duration/2 - cloak_delta_time;
266
#elif defined(DXX_BUILD_DESCENT_II)
267
                light_scale = fixdiv(Cloak_fadein_duration/2 - cloak_delta_time,Cloak_fadein_duration/2);
268
#endif
269
                fading = 1;
270
 
271
        }
272
        else if (cloak_delta_time < Cloak_fadein_duration) {
273
 
274
#if defined(DXX_BUILD_DESCENT_I)
275
                cloak_value = f2i((cloak_delta_time - Cloak_fadein_duration/2) * CLOAKED_FADE_LEVEL);
276
#elif defined(DXX_BUILD_DESCENT_II)
277
                cloak_value = f2i(fixdiv(cloak_delta_time - Cloak_fadein_duration/2,Cloak_fadein_duration/2) * CLOAKED_FADE_LEVEL);
278
#endif
279
 
280
        } else if (GameTime64 < (cloak_start_time + total_cloaked_time) -Cloak_fadeout_duration) {
281
                static int cloak_delta=0,cloak_dir=1;
282
                static fix cloak_timer=0;
283
 
284
                //note, if more than one cloaked object is visible at once, the
285
                //pulse rate will change!
286
 
287
                cloak_timer -= FrameTime;
288
                while (cloak_timer < 0) {
289
 
290
                        cloak_timer += Cloak_fadeout_duration/12;
291
 
292
                        cloak_delta += cloak_dir;
293
 
294
                        if (cloak_delta==0 || cloak_delta==4)
295
                                cloak_dir = -cloak_dir;
296
                }
297
 
298
                cloak_value = CLOAKED_FADE_LEVEL - cloak_delta;
299
 
300
        } else if (GameTime64 < (cloak_start_time + total_cloaked_time) -Cloak_fadeout_duration/2) {
301
 
302
#if defined(DXX_BUILD_DESCENT_I)
303
                cloak_value = f2i((total_cloaked_time - Cloak_fadeout_duration/2 - cloak_delta_time) * CLOAKED_FADE_LEVEL);
304
#elif defined(DXX_BUILD_DESCENT_II)
305
                cloak_value = f2i(fixdiv(total_cloaked_time - Cloak_fadeout_duration/2 - cloak_delta_time,Cloak_fadeout_duration/2) * CLOAKED_FADE_LEVEL);
306
#endif
307
 
308
        } else {
309
 
310
#if defined(DXX_BUILD_DESCENT_I)
311
                light_scale = Cloak_fadeout_duration/2 - (total_cloaked_time - cloak_delta_time);
312
#elif defined(DXX_BUILD_DESCENT_II)
313
                light_scale = fixdiv(Cloak_fadeout_duration/2 - (total_cloaked_time - cloak_delta_time),Cloak_fadeout_duration/2);
314
#endif
315
                fading = 1;
316
        }
317
 
318
        alternate_textures alt_textures;
319
#if defined(DXX_BUILD_DESCENT_II)
320
        if (fading)
321
#endif
322
        {
323
                const unsigned ati = static_cast<unsigned>(obj.rtype.pobj_info.alt_textures) - 1;
324
                if (ati < multi_player_textures.size())
325
                {
326
                        alt_textures = multi_player_textures[ati];
327
                }
328
        }
329
 
330
        if (fading) {
331
                g3s_lrgb new_light;
332
 
333
                new_light.r = fixmul(light.r,light_scale);
334
                new_light.g = fixmul(light.g,light_scale);
335
                new_light.b = fixmul(light.b,light_scale);
336
                glow[0] = fixmul(glow[0],light_scale);
337
                draw_polygon_model(canvas, obj.pos,
338
                                   obj.orient,
339
                                   obj.rtype.pobj_info.anim_angles,
340
                                   obj.rtype.pobj_info.model_num, obj.rtype.pobj_info.subobj_flags,
341
                                   new_light,
342
                                   &glow,
343
                                   alt_textures );
344
        }
345
        else {
346
                gr_settransblend(canvas, cloak_value, gr_blend::normal);
347
                g3_set_special_render(draw_tmap_flat);          //use special flat drawer
348
                draw_polygon_model(canvas, obj.pos,
349
                                   obj.orient,
350
                                   obj.rtype.pobj_info.anim_angles,
351
                                   obj.rtype.pobj_info.model_num, obj.rtype.pobj_info.subobj_flags,
352
                                   light,
353
                                   &glow,
354
                                   alt_textures );
355
                g3_set_special_render(draw_tmap);
356
                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
357
        }
358
 
359
}
360
 
361
//draw an object which renders as a polygon model
362
static void draw_polygon_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vcobjptridx_t obj)
363
{
364
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
365
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
366
        g3s_lrgb light;
367
        glow_values_t engine_glow_value;
368
        engine_glow_value[0] = 0;
369
#if defined(DXX_BUILD_DESCENT_II)
370
        engine_glow_value[1] = -1;              //element 0 is for engine glow, 1 for headlight
371
#endif
372
 
373
        //      If option set for bright players in netgame, brighten them!
374
        light = unlikely(Netgame.BrightPlayers && (Game_mode & GM_MULTI) && obj->type == OBJ_PLAYER)
375
                ? g3s_lrgb{F1_0 * 2, F1_0 * 2, F1_0 * 2}
376
                : compute_object_light(LevelUniqueLightState, obj);
377
 
378
#if defined(DXX_BUILD_DESCENT_II)
379
        //make robots brighter according to robot glow field
380
        if (obj->type == OBJ_ROBOT)
381
        {
382
                const auto glow = Robot_info[get_robot_id(obj)].glow<<12;
383
                light.r += glow; //convert 4:4 to 16:16
384
                light.g += glow; //convert 4:4 to 16:16
385
                light.b += glow; //convert 4:4 to 16:16
386
        }
387
 
388
        if ((obj->type == OBJ_WEAPON &&
389
                        get_weapon_id(obj) == weapon_id_type::FLARE_ID) ||
390
                obj->type == OBJ_MARKER
391
                )
392
                {
393
                        light.r += F1_0*2;
394
                        light.g += F1_0*2;
395
                        light.b += F1_0*2;
396
                }
397
#endif
398
 
399
        push_interpolation_method imsave(1, true);
400
 
401
        //set engine glow value
402
        engine_glow_value[0] = f1_0/5;
403
        if (obj->movement_type == MT_PHYSICS) {
404
 
405
                if (obj->mtype.phys_info.flags & PF_USES_THRUST && obj->type==OBJ_PLAYER && get_player_id(obj)==Player_num) {
406
                        fix thrust_mag = vm_vec_mag_quick(obj->mtype.phys_info.thrust);
407
                        engine_glow_value[0] += (fixdiv(thrust_mag,Player_ship->max_thrust)*4)/5;
408
                }
409
                else {
410
                        fix speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity);
411
#if defined(DXX_BUILD_DESCENT_I)
412
                        engine_glow_value[0] += (fixdiv(speed,MAX_VELOCITY)*4)/5;
413
#elif defined(DXX_BUILD_DESCENT_II)
414
                        engine_glow_value[0] += (fixdiv(speed,MAX_VELOCITY)*3)/5;
415
#endif
416
                }
417
        }
418
 
419
#if defined(DXX_BUILD_DESCENT_II)
420
        //set value for player headlight
421
        if (obj->type == OBJ_PLAYER) {
422
                auto &player_flags = obj->ctype.player_info.powerup_flags;
423
                if (player_flags & PLAYER_FLAGS_HEADLIGHT && !Endlevel_sequence)
424
                        if (player_flags & PLAYER_FLAGS_HEADLIGHT_ON)
425
                                engine_glow_value[1] = -2;              //draw white!
426
                        else
427
                                engine_glow_value[1] = -1;              //draw normal color (grey)
428
                else
429
                        engine_glow_value[1] = -3;                      //don't draw
430
        }
431
#endif
432
 
433
        if (obj->rtype.pobj_info.tmap_override != -1) {
434
                std::array<bitmap_index, 12> bm_ptrs;
435
 
436
                //fill whole array, in case simple model needs more
437
                bm_ptrs.fill(Textures[obj->rtype.pobj_info.tmap_override]);
438
                draw_polygon_model(canvas, obj->pos,
439
                                   obj->orient,
440
                                   obj->rtype.pobj_info.anim_angles,
441
                                   obj->rtype.pobj_info.model_num,
442
                                   obj->rtype.pobj_info.subobj_flags,
443
                                   light,
444
                                   &engine_glow_value,
445
                                   bm_ptrs);
446
        }
447
        else {
448
                std::pair<fix64, fix> cloak_duration;
449
                std::pair<fix, fix> cloak_fade;
450
                if (obj->type==OBJ_PLAYER && (obj->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
451
                {
452
                        auto &cloak_time = obj->ctype.player_info.cloak_time;
453
                        cloak_duration = {cloak_time, CLOAK_TIME_MAX};
454
                        cloak_fade = {CLOAK_FADEIN_DURATION_PLAYER, CLOAK_FADEOUT_DURATION_PLAYER};
455
                }
456
                else if ((obj->type == OBJ_ROBOT) && (obj->ctype.ai_info.CLOAKED)) {
457
                        if (Robot_info[get_robot_id(obj)].boss_flag)
458
                                cloak_duration = {BossUniqueState.Boss_cloak_start_time, Boss_cloak_duration};
459
                        else
460
                                cloak_duration = {GameTime64-F1_0*10, F1_0 * 20};
461
                        cloak_fade = {CLOAK_FADEIN_DURATION_ROBOT, CLOAK_FADEOUT_DURATION_ROBOT};
462
                } else {
463
                        alternate_textures alt_textures;
464
                        const unsigned ati = static_cast<unsigned>(obj->rtype.pobj_info.alt_textures) - 1;
465
                        if (ati < multi_player_textures.size())
466
                                alt_textures = multi_player_textures[ati];
467
 
468
#if defined(DXX_BUILD_DESCENT_II)
469
                        if (obj->type == OBJ_ROBOT)
470
                        {
471
                        //      Snipers get bright when they fire.
472
                                if (obj->ctype.ai_info.ail.next_fire < F1_0/8) {
473
                                if (obj->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
474
                                {
475
                                        light.r = 2*light.r + F1_0;
476
                                        light.g = 2*light.g + F1_0;
477
                                        light.b = 2*light.b + F1_0;
478
                                }
479
                        }
480
                        }
481
#endif
482
 
483
                        const auto is_weapon_with_inner_model = (obj->type == OBJ_WEAPON && Weapon_info[get_weapon_id(obj)].model_num_inner > -1);
484
                        bool draw_simple_model;
485
                        if (is_weapon_with_inner_model)
486
                        {
487
                                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_a);
488
                                draw_simple_model = static_cast<fix>(vm_vec_dist_quick(Viewer->pos, obj->pos)) < Simple_model_threshhold_scale * F1_0*2;
489
                                if (draw_simple_model)
490
                                        draw_polygon_model(canvas, obj->pos,
491
                                                           obj->orient,
492
                                                           obj->rtype.pobj_info.anim_angles,
493
                                                           Weapon_info[get_weapon_id(obj)].model_num_inner,
494
                                                           obj->rtype.pobj_info.subobj_flags,
495
                                                           light,
496
                                                           &engine_glow_value,
497
                                                           alt_textures);
498
                        }
499
 
500
                        draw_polygon_model(canvas, obj->pos,
501
                                           obj->orient,
502
                                           obj->rtype.pobj_info.anim_angles,obj->rtype.pobj_info.model_num,
503
                                           obj->rtype.pobj_info.subobj_flags,
504
                                           light,
505
                                           &engine_glow_value,
506
                                           alt_textures);
507
 
508
                        if (is_weapon_with_inner_model)
509
                        {
510
#if !DXX_USE_OGL // in software rendering must draw inner model last
511
                                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_a);
512
                                if (draw_simple_model)
513
                                        draw_polygon_model(canvas, obj->pos,
514
                                                           obj->orient,
515
                                                           obj->rtype.pobj_info.anim_angles,
516
                                                           Weapon_info[obj->id].model_num_inner,
517
                                                           obj->rtype.pobj_info.subobj_flags,
518
                                                           light,
519
                                                           &engine_glow_value,
520
                                                           alt_textures);
521
#endif
522
                                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
523
                        }
524
                        return;
525
                }
526
                draw_cloaked_object(canvas, obj, light, engine_glow_value, cloak_duration.first, cloak_duration.second, cloak_fade.first, cloak_fade.second);
527
        }
528
}
529
 
530
}
531
 
532
//------------------------------------------------------------------------------
533
// These variables are used to keep a list of the 3 closest robots to the viewer.
534
// The code works like this: Every time render object is called with a polygon model,
535
// it finds the distance of that robot to the viewer.  If this distance if within 10
536
// segments of the viewer, it does the following: If there aren't already 3 robots in
537
// the closet-robots list, it just sticks that object into the list along with its distance.
538
// If the list already contains 3 robots, then it finds the robot in that list that is
539
// farthest from the viewer. If that object is farther than the object currently being
540
// rendered, then the new object takes over that far object's slot.  *Then* after all
541
// objects are rendered, object_render_targets is called an it draws a target on top
542
// of all the objects.
543
 
544
//091494: #define MAX_CLOSE_ROBOTS 3
545
//--unused-- static int Object_draw_lock_boxes = 0;
546
//091494: static int Object_num_close = 0;
547
//091494: static object * Object_close_ones[MAX_CLOSE_ROBOTS];
548
//091494: static fix Object_close_distance[MAX_CLOSE_ROBOTS];
549
 
550
//091494: set_close_objects(object *obj)
551
//091494: {
552
//091494:       fix dist;
553
//091494:
554
//091494:       if ( (obj->type != OBJ_ROBOT) || (Object_draw_lock_boxes==0) )  
555
//091494:               return;
556
//091494:
557
//091494:       // The following code keeps a list of the 10 closest robots to the
558
//091494:       // viewer.  See comments in front of this function for how this works.
559
//091494:       dist = vm_vec_dist( &obj->pos, &Viewer->pos );
560
//091494:       if ( dist < i2f(20*10) )        {                               
561
//091494:               if ( Object_num_close < MAX_CLOSE_ROBOTS )      {
562
//091494:                       Object_close_ones[Object_num_close] = obj;
563
//091494:                       Object_close_distance[Object_num_close] = dist;
564
//091494:                       Object_num_close++;
565
//091494:               } else {
566
//091494:                       int i, farthest_robot;
567
//091494:                       fix farthest_distance;
568
//091494:                       // Find the farthest robot in the list
569
//091494:                       farthest_robot = 0;
570
//091494:                       farthest_distance = Object_close_distance[0];
571
//091494:                       for (i=1; i<Object_num_close; i++ )     {
572
//091494:                               if ( Object_close_distance[i] > farthest_distance )     {
573
//091494:                                       farthest_distance = Object_close_distance[i];
574
//091494:                                       farthest_robot = i;
575
//091494:                               }
576
//091494:                       }
577
//091494:                       // If this object is closer to the viewer than
578
//091494:                       // the farthest in the list, replace the farthest with this object.
579
//091494:                       if ( farthest_distance > dist ) {
580
//091494:                               Object_close_ones[farthest_robot] = obj;
581
//091494:                               Object_close_distance[farthest_robot] = dist;
582
//091494:                       }
583
//091494:               }
584
//091494:       }
585
//091494: }
586
 
587
namespace dcx {
588
objnum_t        Player_fired_laser_this_frame=object_none;
589
 
590
static bool predicate_debris(const object_base &o)
591
{
592
        return o.type == OBJ_DEBRIS;
593
}
594
 
595
static bool predicate_flare(const object_base &o)
596
{
597
        return (o.type == OBJ_WEAPON) && (get_weapon_id(o) == weapon_id_type::FLARE_ID);
598
}
599
 
600
static bool predicate_nonflare_weapon(const object_base &o)
601
{
602
        return (o.type == OBJ_WEAPON) && (get_weapon_id(o) != weapon_id_type::FLARE_ID);
603
}
604
 
605
}
606
 
607
 
608
 
609
namespace dsx {
610
 
611
static bool predicate_fireball(const object &o)
612
{
613
        return o.type == OBJ_FIREBALL && o.ctype.expl_info.delete_objnum == object_none;
614
}
615
 
616
// -----------------------------------------------------------------------------
617
//this routine checks to see if an robot rendered near the middle of
618
//the screen, and if so and the player had fired, "warns" the robot
619
static void set_robot_location_info(object &objp)
620
{
621
        auto &Objects = LevelUniqueObjectState.Objects;
622
        auto &vcobjptr = Objects.vcptr;
623
        if (Player_fired_laser_this_frame != object_none) {
624
                const auto &&temp = g3_rotate_point(objp.pos);
625
                if (temp.p3_codes & CC_BEHIND)          //robot behind the screen
626
                        return;
627
 
628
                //the code below to check for object near the center of the screen
629
                //completely ignores z, which may not be good
630
 
631
                if ((abs(temp.p3_x) < F1_0*4) && (abs(temp.p3_y) < F1_0*4)) {
632
                        objp.ctype.ai_info.danger_laser_num = Player_fired_laser_this_frame;
633
                        objp.ctype.ai_info.danger_laser_signature = vcobjptr(Player_fired_laser_this_frame)->signature;
634
                }
635
        }
636
}
637
 
638
//      ------------------------------------------------------------------------------------------------------------------
639
void create_small_fireball_on_object(const vmobjptridx_t objp, fix size_scale, int sound_flag)
640
{
641
        auto &Objects = LevelUniqueObjectState.Objects;
642
        fix                     size;
643
        vms_vector      pos;
644
 
645
        pos = objp->pos;
646
        auto rand_vec = make_random_vector();
647
 
648
        vm_vec_scale(rand_vec, objp->size/2);
649
 
650
        vm_vec_add2(pos, rand_vec);
651
 
652
#if defined(DXX_BUILD_DESCENT_I)
653
        size = fixmul(size_scale, F1_0 + d_rand()*4);
654
#elif defined(DXX_BUILD_DESCENT_II)
655
        size = fixmul(size_scale, F1_0/2 + d_rand()*4/2);
656
#endif
657
 
658
        const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, Segments.vmptridx(objp->segnum));
659
        if (segnum != segment_none) {
660
                auto expl_obj = object_create_explosion(segnum, pos, size, VCLIP_SMALL_EXPLOSION);
661
                if (!expl_obj)
662
                        return;
663
                obj_attach(Objects, objp, expl_obj);
664
                if (d_rand() < 8192) {
665
                        fix     vol = F1_0/2;
666
                        if (objp->type == OBJ_ROBOT)
667
                                vol *= 2;
668
                        if (sound_flag)
669
                                digi_link_sound_to_object(SOUND_EXPLODING_WALL, objp, 0, vol, sound_stack::allow_stacking);
670
                }
671
        }
672
}
673
 
674
// -- mk, 02/05/95 -- #define   VCLIP_INVULNERABILITY_EFFECT    VCLIP_SMALL_EXPLOSION
675
// -- mk, 02/05/95 --
676
// -- mk, 02/05/95 -- // -----------------------------------------------------------------------------
677
// -- mk, 02/05/95 -- void do_player_invulnerability_effect(object *objp)
678
// -- mk, 02/05/95 -- {
679
// -- mk, 02/05/95 --   if (d_rand() < FrameTime*8) {
680
// -- mk, 02/05/95 --           create_vclip_on_object(objp, F1_0, VCLIP_INVULNERABILITY_EFFECT);
681
// -- mk, 02/05/95 --   }
682
// -- mk, 02/05/95 -- }
683
 
684
// -----------------------------------------------------------------------------
685
//      Render an object.  Calls one of several routines based on type
686
void render_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj)
687
{
688
        if (unlikely(obj == Viewer))
689
                return;
690
        if (unlikely(obj->type==OBJ_NONE))
691
        {
692
                Int3();
693
                return;
694
        }
695
 
696
#if !DXX_USE_OGL
697
        const auto mld_save = std::exchange(Max_linear_depth, Max_linear_depth_objects);
698
#endif
699
 
700
        bool alpha = false;
701
        switch (obj->render_type)
702
        {
703
                case RT_NONE:
704
                        break; //doesn't render, like the player
705
 
706
                case RT_POLYOBJ:
707
#if defined(DXX_BUILD_DESCENT_II)
708
                        if ( PlayerCfg.AlphaBlendMarkers && obj->type == OBJ_MARKER ) // set nice transparency/blending for certrain objects
709
                        {
710
                                alpha = true;
711
                                gr_settransblend(canvas, 10, gr_blend::additive_a);
712
                        }
713
#endif
714
                        draw_polygon_object(canvas, LevelUniqueLightState, obj);
715
 
716
                        if (obj->type == OBJ_ROBOT) //"warn" robot if being shot at
717
                                set_robot_location_info(obj);
718
                        break;
719
 
720
                case RT_MORPH:
721
                        draw_morph_object(canvas, LevelUniqueLightState, obj);
722
                        break;
723
 
724
                case RT_FIREBALL:
725
                        if (PlayerCfg.AlphaBlendFireballs) // set nice transparency/blending for certrain objects
726
                        {
727
                                alpha = true;
728
                                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c);
729
                        }
730
 
731
                        draw_fireball(Vclip, canvas, obj);
732
                        break;
733
 
734
                case RT_WEAPON_VCLIP:
735
                        if (PlayerCfg.AlphaBlendWeapons && (!is_proximity_bomb_or_any_smart_mine(get_weapon_id(obj))
736
                )) // set nice transparency/blending for certain objects
737
                        {
738
                                alpha = true;
739
                                gr_settransblend(canvas, 7, gr_blend::additive_a);
740
                        }
741
 
742
                        draw_weapon_vclip(Vclip, Weapon_info, canvas, obj);
743
                        break;
744
 
745
                case RT_HOSTAGE:
746
                        draw_hostage(Vclip, canvas, LevelUniqueLightState, obj);
747
                        break;
748
 
749
                case RT_POWERUP:
750
                        if (PlayerCfg.AlphaBlendPowerups) // set nice transparency/blending for certrain objects
751
                                switch ( get_powerup_id(obj) )
752
                                {
753
                                        case POW_EXTRA_LIFE:
754
                                        case POW_ENERGY:
755
                                        case POW_SHIELD_BOOST:
756
                                        case POW_CLOAK:
757
                                        case POW_INVULNERABILITY:
758
#if defined(DXX_BUILD_DESCENT_II)
759
                                        case POW_HOARD_ORB:
760
#endif
761
                                                alpha = true;
762
                                                gr_settransblend(canvas, 7, gr_blend::additive_a);
763
                                                break;
764
                                        case POW_LASER:
765
                                        case POW_KEY_BLUE:
766
                                        case POW_KEY_RED:
767
                                        case POW_KEY_GOLD:
768
                                        case POW_MISSILE_1:
769
                                        case POW_MISSILE_4:
770
                                        case POW_QUAD_FIRE:
771
                                        case POW_VULCAN_WEAPON:
772
                                        case POW_SPREADFIRE_WEAPON:
773
                                        case POW_PLASMA_WEAPON:
774
                                        case POW_FUSION_WEAPON:
775
                                        case POW_PROXIMITY_WEAPON:
776
                                        case POW_HOMING_AMMO_1:
777
                                        case POW_HOMING_AMMO_4:
778
                                        case POW_SMARTBOMB_WEAPON:
779
                                        case POW_MEGA_WEAPON:
780
                                        case POW_VULCAN_AMMO:
781
                                        case POW_TURBO:
782
                                        case POW_MEGAWOW:
783
#if defined(DXX_BUILD_DESCENT_II)
784
                                        case POW_FULL_MAP:
785
                                        case POW_HEADLIGHT:
786
                                        case POW_GAUSS_WEAPON:
787
                                        case POW_HELIX_WEAPON:
788
                                        case POW_PHOENIX_WEAPON:
789
                                        case POW_OMEGA_WEAPON:
790
                                        case POW_SUPER_LASER:
791
                                        case POW_CONVERTER:
792
                                        case POW_AMMO_RACK:
793
                                        case POW_AFTERBURNER:
794
                                        case POW_SMISSILE1_1:
795
                                        case POW_SMISSILE1_4:
796
                                        case POW_GUIDED_MISSILE_1:
797
                                        case POW_GUIDED_MISSILE_4:
798
                                        case POW_SMART_MINE:
799
                                        case POW_MERCURY_MISSILE_1:
800
                                        case POW_MERCURY_MISSILE_4:
801
                                        case POW_EARTHSHAKER_MISSILE:
802
                                        case POW_FLAG_BLUE:
803
                                        case POW_FLAG_RED:
804
#endif
805
                                                break;
806
                                }
807
 
808
                        draw_powerup(Vclip, canvas, obj);
809
                        break;
810
 
811
                case RT_LASER:
812
                        if (PlayerCfg.AlphaBlendLasers) // set nice transparency/blending for certrain objects
813
                        {
814
                                alpha = true;
815
                                gr_settransblend(canvas, 7, gr_blend::additive_a);
816
                        }
817
 
818
                        Laser_render(canvas, obj);
819
                        break;
820
 
821
                default:
822
                        Error("Unknown render_type <%d>",obj->render_type);
823
        }
824
 
825
        if (alpha)
826
                gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency/blending setting back to normal
827
 
828
        if ( obj->render_type != RT_NONE && Newdemo_state == ND_STATE_RECORDING )
829
                newdemo_record_render_object(obj);
830
#if !DXX_USE_OGL
831
        Max_linear_depth = mld_save;
832
#endif
833
}
834
 
835
void reset_player_object()
836
{
837
        //Init physics
838
 
839
        vm_vec_zero(ConsoleObject->mtype.phys_info.velocity);
840
        vm_vec_zero(ConsoleObject->mtype.phys_info.thrust);
841
        vm_vec_zero(ConsoleObject->mtype.phys_info.rotvel);
842
        vm_vec_zero(ConsoleObject->mtype.phys_info.rotthrust);
843
        ConsoleObject->mtype.phys_info.turnroll = 0;
844
        ConsoleObject->mtype.phys_info.mass = Player_ship->mass;
845
        ConsoleObject->mtype.phys_info.drag = Player_ship->drag;
846
        ConsoleObject->mtype.phys_info.flags = PF_TURNROLL | PF_LEVELLING | PF_WIGGLE | PF_USES_THRUST;
847
 
848
        //Init render info
849
 
850
        ConsoleObject->render_type = RT_POLYOBJ;
851
        ConsoleObject->rtype.pobj_info.model_num = Player_ship->model_num;              //what model is this?
852
        ConsoleObject->rtype.pobj_info.subobj_flags = 0;                //zero the flags
853
        ConsoleObject->rtype.pobj_info.tmap_override = -1;              //no tmap override!
854
        ConsoleObject->rtype.pobj_info.anim_angles = {};
855
 
856
        // Clear misc
857
 
858
        ConsoleObject->flags = 0;
859
}
860
 
861
 
862
//make object0 the player, setting all relevant fields
863
void init_player_object()
864
{
865
        auto &Objects = LevelUniqueObjectState.Objects;
866
        auto &vmobjptr = Objects.vmptr;
867
        const auto &&console = vmobjptr(ConsoleObject);
868
        console->type = OBJ_PLAYER;
869
        set_player_id(console, 0);                                      //no sub-types for player
870
        console->signature = object_signature_t{0};
871
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
872
        console->size = Polygon_models[Player_ship->model_num].rad;
873
        console->control_type = CT_SLEW;                        //default is player slewing
874
        console->movement_type = MT_PHYSICS;            //change this sometime
875
        console->lifeleft = IMMORTAL_TIME;
876
        console->attached_obj = object_none;
877
        reset_player_object();
878
}
879
 
880
//sets up the free list & init player & whatever else
881
void init_objects()
882
{
883
        auto &Objects = LevelUniqueObjectState.Objects;
884
        auto &vmobjptr = Objects.vmptr;
885
        for (objnum_t i = 0; i< MAX_OBJECTS; ++i)
886
        {
887
                LevelUniqueObjectState.free_obj_list[i] = i;
888
                auto &obj = *vmobjptr(i);
889
                DXX_POISON_VAR(obj, 0xfd);
890
                obj.type = OBJ_NONE;
891
        }
892
 
893
        range_for (unique_segment &j, Segments)
894
                j.objects = object_none;
895
 
896
        Viewer = ConsoleObject = &Objects.front();
897
 
898
        init_player_object();
899
        obj_link_unchecked(Objects.vmptr, Objects.vmptridx(ConsoleObject), Segments.vmptridx(segment_first));   //put in the world in segment 0
900
        LevelUniqueObjectState.num_objects = 1;                                         //just the player
901
        Objects.set_count(1);
902
}
903
 
904
//after calling init_object(), the network code has grabbed specific
905
//object slots without allocating them.  Go though the objects & build
906
//the free list, then set the apporpriate globals
907
void special_reset_objects(d_level_unique_object_state &LevelUniqueObjectState)
908
{
909
        unsigned num_objects = MAX_OBJECTS;
910
 
911
        auto &Objects = LevelUniqueObjectState.get_objects();
912
        Objects.set_count(1);
913
        assert(Objects.front().type != OBJ_NONE);               //0 should be used
914
 
915
        DXX_POISON_VAR(LevelUniqueObjectState.free_obj_list, 0xfd);
916
        for (objnum_t i = MAX_OBJECTS; i--;)
917
                if (Objects.vcptr(i)->type == OBJ_NONE)
918
                        LevelUniqueObjectState.free_obj_list[--num_objects] = i;
919
                else
920
                        if (i > Highest_object_index)
921
                                Objects.set_count(i + 1);
922
        LevelUniqueObjectState.num_objects = num_objects;
923
}
924
 
925
//link the object into the list for its segment
926
void obj_link(fvmobjptr &vmobjptr, const vmobjptridx_t obj, const vmsegptridx_t segnum)
927
{
928
        assert(obj->segnum == segment_none);
929
        assert(obj->next == object_none);
930
        assert(obj->prev == object_none);
931
        obj_link_unchecked(vmobjptr, obj, segnum);
932
}
933
 
934
void obj_link_unchecked(fvmobjptr &vmobjptr, const vmobjptridx_t obj, const vmsegptridx_t segnum)
935
{
936
        obj->segnum = segnum;
937
 
938
        obj->next = segnum->objects;
939
        obj->prev = object_none;
940
 
941
        segnum->objects = obj;
942
 
943
        if (obj->next != object_none)
944
                vmobjptr(obj->next)->prev = obj;
945
}
946
 
947
void obj_unlink(fvmobjptr &vmobjptr, fvmsegptr &vmsegptr, object_base &obj)
948
{
949
        const auto next = obj.next;
950
        /* It is a bug elsewhere if vmsegptr ever fails here.  However, it is
951
         * expensive to check, so only force verification in debug builds.
952
         *
953
         * In debug builds, always compute it, for the side effect of
954
         * validating the segment number.
955
         *
956
         * In release builds, compute it when it is needed.
957
         */
958
#ifndef NDEBUG
959
        const auto &&segp = vmsegptr(obj.segnum);
960
#endif
961
        ((obj.prev == object_none)
962
                ? (
963
#ifdef NDEBUG
964
                        vmsegptr(obj.segnum)
965
#else
966
                        segp
967
#endif
968
                )->objects
969
                : vmobjptr(obj.prev)->next) = next;
970
 
971
        obj.segnum = segment_none;
972
 
973
        if (next != object_none)
974
                vmobjptr(next)->prev = obj.prev;
975
        DXX_POISON_VAR(obj.next, 0xfa);
976
        DXX_POISON_VAR(obj.prev, 0xfa);
977
}
978
 
979
//returns the number of a free object, updating Highest_object_index.
980
//Generally, obj_create() should be called to get an object, since it
981
//fills in important fields and does the linking.
982
//returns -1 if no free objects
983
imobjptridx_t obj_allocate(d_level_unique_object_state &LevelUniqueObjectState)
984
{
985
        auto &Objects = LevelUniqueObjectState.Objects;
986
        if (LevelUniqueObjectState.num_objects >= Objects.size())
987
                return object_none;
988
 
989
        const auto objnum = LevelUniqueObjectState.free_obj_list[LevelUniqueObjectState.num_objects++];
990
        if (objnum >= Objects.get_count())
991
        {
992
                Objects.set_count(objnum + 1);
993
        }
994
        const auto &&r = Objects.vmptridx(objnum);
995
        assert(r->type == OBJ_NONE);
996
        return r;
997
}
998
 
999
//frees up an object.  Generally, obj_delete() should be called to get
1000
//rid of an object.  This function deallocates the object entry after
1001
//the object has been unlinked
1002
static void obj_free(d_level_unique_object_state &LevelUniqueObjectState, const vmobjidx_t objnum)
1003
{
1004
        const auto num_objects = -- LevelUniqueObjectState.num_objects;
1005
        assert(num_objects < LevelUniqueObjectState.free_obj_list.size());
1006
        LevelUniqueObjectState.free_obj_list[num_objects] = objnum;
1007
        auto &Objects = LevelUniqueObjectState.get_objects();
1008
 
1009
        objnum_t o = objnum;
1010
        if (o == Highest_object_index)
1011
        {
1012
                for (;;)
1013
                {
1014
                        --o;
1015
                        if (Objects.vcptr(o)->type != OBJ_NONE)
1016
                                break;
1017
                        if (o == 0)
1018
                                break;
1019
                }
1020
                Objects.set_count(o + 1);
1021
        }
1022
}
1023
 
1024
//-----------------------------------------------------------------------------
1025
//      Scan the object list, freeing down to num_used objects
1026
//      Returns number of slots freed.
1027
static void free_object_slots(uint_fast32_t num_used)
1028
{
1029
        auto &Objects = LevelUniqueObjectState.Objects;
1030
        auto &vmobjptr = Objects.vmptr;
1031
        std::array<object *, MAX_OBJECTS>       obj_list;
1032
        unsigned        num_already_free, num_to_free, olind = 0;
1033
 
1034
        num_already_free = MAX_OBJECTS - Highest_object_index - 1;
1035
 
1036
        if (MAX_OBJECTS - num_already_free < num_used)
1037
                return;
1038
 
1039
        range_for (const auto &&objp, vmobjptr)
1040
        {
1041
                if (objp->flags & OF_SHOULD_BE_DEAD)
1042
                {
1043
                        num_already_free++;
1044
                        if (MAX_OBJECTS - num_already_free < num_used)
1045
                                return;
1046
                } else
1047
                        switch (objp->type)
1048
                        {
1049
                                case OBJ_NONE:
1050
                                        num_already_free++;
1051
                                        if (MAX_OBJECTS - num_already_free < num_used)
1052
                                                return;
1053
                                        break;
1054
                                case OBJ_WALL:
1055
                                        Int3();         //      This is curious.  What is an object that is a wall?
1056
                                        break;
1057
                                case OBJ_FIREBALL:
1058
                                case OBJ_WEAPON:
1059
                                case OBJ_DEBRIS:
1060
                                        obj_list[olind++] = objp;
1061
                                        break;
1062
                                case OBJ_ROBOT:
1063
                                case OBJ_HOSTAGE:
1064
                                case OBJ_PLAYER:
1065
                                case OBJ_CNTRLCEN:
1066
                                case OBJ_CLUTTER:
1067
                                case OBJ_GHOST:
1068
                                case OBJ_LIGHT:
1069
                                case OBJ_CAMERA:
1070
                                case OBJ_POWERUP:
1071
                                case OBJ_COOP:
1072
                                case OBJ_MARKER:
1073
                                        break;
1074
                        }
1075
 
1076
        }
1077
 
1078
        num_to_free = MAX_OBJECTS - num_used - num_already_free;
1079
 
1080
        if (num_to_free > olind) {
1081
                num_to_free = olind;
1082
        }
1083
 
1084
        // Capture before num_to_free modified
1085
        const auto &&r = partial_const_range(obj_list, num_to_free);
1086
        auto l = [&vmobjptr, &r, &num_to_free](const auto predicate) -> bool {
1087
                range_for (const auto i, r)
1088
                {
1089
                        auto &o = *vmobjptr(i);
1090
                        if (predicate(o))
1091
                        {
1092
                                o.flags |= OF_SHOULD_BE_DEAD;
1093
                                if (!-- num_to_free)
1094
                                        return true;
1095
                        }
1096
                }
1097
                return false;
1098
        };
1099
 
1100
        if (l(predicate_debris))
1101
                return;
1102
 
1103
        if (l(predicate_fireball))
1104
                return;
1105
 
1106
        if (l(predicate_flare))
1107
                return;
1108
 
1109
        if (l(predicate_nonflare_weapon))
1110
                return;
1111
}
1112
 
1113
//-----------------------------------------------------------------------------
1114
//initialize a new object.  adds to the list for the given segment
1115
//note that segnum is really just a suggestion, since this routine actually
1116
//searches for the correct segment
1117
//returns the object number
1118
imobjptridx_t obj_create(const object_type_t type, const unsigned id, vmsegptridx_t segnum, const vms_vector &pos, const vms_matrix *const orient, const fix size, const unsigned ctype, const movement_type_t mtype, const render_type_t rtype)
1119
{
1120
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1121
        auto &Objects = LevelUniqueObjectState.Objects;
1122
        auto &Vertices = LevelSharedVertexState.get_vertices();
1123
        // Some consistency checking. FIXME: Add more debug output here to probably trace all possible occurances back.
1124
        Assert(ctype <= CT_CNTRLCEN);
1125
 
1126
        if (type == OBJ_DEBRIS && LevelUniqueObjectState.Debris_object_count >= Max_debris_objects && !PERSISTENT_DEBRIS)
1127
                return object_none;
1128
 
1129
        auto &vcvertptr = Vertices.vcptr;
1130
        if (get_seg_masks(vcvertptr, pos, segnum, 0).centermask != 0)
1131
        {
1132
                const auto &&p = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, segnum);
1133
                if (p == segment_none) {
1134
                        return object_none;             //don't create this object
1135
                }
1136
                segnum = p;
1137
        }
1138
 
1139
        // Find next free object
1140
        const auto &&obj = obj_allocate(LevelUniqueObjectState);
1141
 
1142
        if (obj == object_none)         //no free objects
1143
                return object_none;
1144
 
1145
        // Zero out object structure to keep weird bugs from happening
1146
        // in uninitialized fields.
1147
        const auto signature = obj->signature;
1148
        /* Test the version in the object structure, not the local copy.
1149
         * This produces a more useful diagnostic from Valgrind if the
1150
         * test reports a problem.
1151
         */
1152
        DXX_CHECK_VAR_IS_DEFINED(obj->signature);
1153
        *obj = {};
1154
        // Tell Valgrind to warn on any uninitialized fields.
1155
        DXX_POISON_VAR(*obj, 0xfd);
1156
 
1157
        obj->signature = object_signature_t(signature.get() + 1);
1158
        obj->type                               = type;
1159
        obj->id                                 = id;
1160
        obj->pos                                = pos;
1161
        obj->size                               = size;
1162
        obj->flags                              = 0;
1163
        //@@if (orient != NULL)
1164
        //@@    obj->orient                     = *orient;
1165
 
1166
        obj->orient                             = orient?*orient:vmd_identity_matrix;
1167
 
1168
        obj->control_type                       = ctype;
1169
        set_object_movement_type(*obj, mtype);
1170
        obj->render_type                        = rtype;
1171
        obj->contains_count                     = 0;
1172
        obj->matcen_creator                     = 0;
1173
        obj->lifeleft                           = IMMORTAL_TIME;                //assume immortal
1174
        obj->attached_obj                       = object_none;
1175
 
1176
        if (obj->control_type == CT_POWERUP)
1177
        {
1178
                obj->ctype.powerup_info.count = 1;
1179
                obj->ctype.powerup_info.flags = 0;
1180
                obj->ctype.powerup_info.creation_time = GameTime64;
1181
        }
1182
 
1183
        // Init physics info for this object
1184
        if (obj->movement_type == MT_PHYSICS) {
1185
                obj->mtype.phys_info = {};
1186
        }
1187
 
1188
        if (obj->render_type == RT_POLYOBJ)
1189
        {
1190
                obj->rtype.pobj_info.subobj_flags = 0;
1191
                obj->rtype.pobj_info.tmap_override = -1;
1192
                obj->rtype.pobj_info.alt_textures = 0;
1193
        }
1194
 
1195
        obj->shields                            = 20*F1_0;
1196
 
1197
        {
1198
                const auto &&p = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, segnum);         //find correct segment
1199
                // Previously this was only an assert check.  Now it is also
1200
                // checked at runtime.
1201
                segnum = p;
1202
        }
1203
 
1204
        obj_link_unchecked(Objects.vmptr, obj, segnum);
1205
 
1206
        //      Set (or not) persistent bit in phys_info.
1207
        if (obj->type == OBJ_WEAPON) {
1208
                Assert(obj->control_type == CT_WEAPON);
1209
                obj->mtype.phys_info.flags |= (Weapon_info[get_weapon_id(obj)].persistent*PF_PERSISTENT);
1210
                obj->ctype.laser_info.creation_time = GameTime64;
1211
                obj->ctype.laser_info.clear_hitobj();
1212
                obj->ctype.laser_info.multiplier = F1_0;
1213
#if defined(DXX_BUILD_DESCENT_II)
1214
                obj->ctype.laser_info.last_afterburner_time = 0;
1215
#endif
1216
        }
1217
 
1218
        if (obj->control_type == CT_EXPLOSION)
1219
                obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none;
1220
 
1221
        if (obj->type == OBJ_DEBRIS)
1222
                ++ LevelUniqueObjectState.Debris_object_count;
1223
 
1224
        return obj;
1225
}
1226
 
1227
//create a copy of an object. returns new object number
1228
imobjptridx_t obj_create_copy(const object &srcobj, const vmsegptridx_t newsegnum)
1229
{
1230
        auto &Objects = LevelUniqueObjectState.Objects;
1231
        // Find next free object
1232
        const auto &&obj = obj_allocate(LevelUniqueObjectState);
1233
 
1234
        if (obj == object_none)
1235
                return object_none;
1236
 
1237
        const auto signature = obj->signature;
1238
        *obj = srcobj;
1239
 
1240
        obj_link_unchecked(Objects.vmptr, obj, newsegnum);
1241
        obj->signature = object_signature_t(signature.get() + 1);
1242
 
1243
        //we probably should initialize sub-structures here
1244
 
1245
        return obj;
1246
}
1247
 
1248
//remove object from the world
1249
void obj_delete(d_level_unique_object_state &LevelUniqueObjectState, segment_array &Segments, const vmobjptridx_t obj)
1250
{
1251
        auto &Objects = LevelUniqueObjectState.get_objects();
1252
        Assert(obj->type != OBJ_NONE);
1253
        Assert(obj != ConsoleObject);
1254
 
1255
#if defined(DXX_BUILD_DESCENT_II)
1256
        if (obj->type==OBJ_WEAPON && get_weapon_id(obj)==weapon_id_type::GUIDEDMISS_ID && obj->ctype.laser_info.parent_type==OBJ_PLAYER)
1257
        {
1258
                const auto pnum = get_player_id(Objects.vcptr(obj->ctype.laser_info.parent_num));
1259
                const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(Objects.vmptridx, pnum);
1260
                if (gimobj == obj)
1261
                {
1262
                        LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(pnum);
1263
                        if (pnum == Player_num)
1264
                        {
1265
                                if (!PlayerCfg.GuidedInBigWindow)
1266
                                        do_cockpit_window_view(1, WBU_STATIC);
1267
                                if (Newdemo_state == ND_STATE_RECORDING)
1268
                                        newdemo_record_guided_end();
1269
                        }
1270
                }
1271
        }
1272
#endif
1273
 
1274
        if (obj == Viewer)              //deleting the viewer?
1275
                Viewer = ConsoleObject;                                         //..make the player the viewer
1276
 
1277
        if (obj->flags & OF_ATTACHED)           //detach this from object
1278
                obj_detach_one(Objects, obj);
1279
 
1280
        if (obj->attached_obj != object_none)           //detach all objects from this
1281
                obj_detach_all(Objects, obj);
1282
 
1283
        if (obj->type == OBJ_DEBRIS)
1284
                -- LevelUniqueObjectState.Debris_object_count;
1285
 
1286
        if (obj->movement_type == MT_PHYSICS && (obj->mtype.phys_info.flags & PF_STICK))
1287
                LevelUniqueStuckObjectState.remove_stuck_object(obj);
1288
        obj_unlink(Objects.vmptr, Segments.vmptr, obj);
1289
        const auto signature = obj->signature;
1290
        DXX_POISON_VAR(*obj, 0xfa);
1291
        obj->type = OBJ_NONE;           //unused!
1292
        /* Preserve signature across the poison value.  When the object slot
1293
         * is reused, the allocator will need the old signature so that the
1294
         * new one can be derived from it.  No other sites should read it
1295
         * until that happens.
1296
         */
1297
        obj->signature = signature;
1298
        obj_free(LevelUniqueObjectState, obj);
1299
}
1300
 
1301
#define DEATH_SEQUENCE_LENGTH                   (F1_0*5)
1302
#define DEATH_SEQUENCE_EXPLODE_TIME     (F1_0*2)
1303
 
1304
object  *Dead_player_camera = NULL;     //      Object index of object watching deader.
1305
static const object *Viewer_save;
1306
}
1307
namespace dcx {
1308
player_dead_state Player_dead_state = player_dead_state::no;                    //      If !0, then player is dead, but game continues so he can watch.
1309
static int Player_flags_save;
1310
static fix Camera_to_player_dist_goal = F1_0*4;
1311
static uint8_t Control_type_save;
1312
static render_type_t Render_type_save;
1313
 
1314
unsigned laser_parent_is_matching_signature(const laser_parent &l, const object_base &o)
1315
{
1316
        if (l.parent_type != o.type)
1317
                return 0;
1318
        return l.parent_signature == o.signature;
1319
}
1320
 
1321
}
1322
 
1323
namespace dsx {
1324
//      ------------------------------------------------------------------------------------------------------------------
1325
void dead_player_end(void)
1326
{
1327
        auto &Objects = LevelUniqueObjectState.Objects;
1328
        auto &vmobjptridx = Objects.vmptridx;
1329
        if (Player_dead_state == player_dead_state::no)
1330
                return;
1331
 
1332
        if (Newdemo_state == ND_STATE_RECORDING)
1333
                newdemo_record_restore_cockpit();
1334
 
1335
        Player_dead_state = player_dead_state::no;
1336
        obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(Dead_player_camera));
1337
        Dead_player_camera = NULL;
1338
        select_cockpit(PlayerCfg.CockpitMode[0]);
1339
        Viewer = Viewer_save;
1340
        ConsoleObject->type = OBJ_PLAYER;
1341
        ConsoleObject->flags = Player_flags_save;
1342
 
1343
        Assert((Control_type_save == CT_FLYING) || (Control_type_save == CT_SLEW));
1344
 
1345
        ConsoleObject->control_type = Control_type_save;
1346
        ConsoleObject->render_type = Render_type_save;
1347
        auto &player_info = ConsoleObject->ctype.player_info;
1348
        player_info.powerup_flags &= ~PLAYER_FLAGS_INVULNERABLE;
1349
        player_info.Player_eggs_dropped = false;
1350
}
1351
 
1352
//      ------------------------------------------------------------------------------------------------------------------
1353
//      Camera is less than size of player away from
1354
static void set_camera_pos(vms_vector &camera_pos, const vcobjptridx_t objp)
1355
{
1356
        int     count = 0;
1357
        fix     camera_player_dist;
1358
        fix     far_scale;
1359
 
1360
        camera_player_dist = vm_vec_dist_quick(camera_pos, objp->pos);
1361
 
1362
        if (camera_player_dist < Camera_to_player_dist_goal) {
1363
                //      Camera is too close to player object, so move it away.
1364
                fvi_query       fq;
1365
                fvi_info                hit_data;
1366
 
1367
                auto player_camera_vec = vm_vec_sub(camera_pos, objp->pos);
1368
                if ((player_camera_vec.x == 0) && (player_camera_vec.y == 0) && (player_camera_vec.z == 0))
1369
                        player_camera_vec.x += F1_0/16;
1370
 
1371
                hit_data.hit_type = HIT_WALL;
1372
                far_scale = F1_0;
1373
 
1374
                while ((hit_data.hit_type != HIT_NONE) && (count++ < 6)) {
1375
                        vm_vec_normalize_quick(player_camera_vec);
1376
                        vm_vec_scale(player_camera_vec, Camera_to_player_dist_goal);
1377
 
1378
                        fq.p0 = &objp->pos;
1379
                        const auto closer_p1 = vm_vec_add(objp->pos, player_camera_vec);                //      This is the actual point we want to put the camera at.
1380
                        vm_vec_scale(player_camera_vec, far_scale);                                             //      ...but find a point 50% further away...
1381
                        const auto local_p1 = vm_vec_add(objp->pos, player_camera_vec);         //      ...so we won't have to do as many cuts.
1382
 
1383
                        fq.p1 = &local_p1;
1384
                        fq.startseg = objp->segnum;
1385
                        fq.rad = 0;
1386
                        fq.thisobjnum = objp;
1387
                        fq.ignore_obj_list.first = nullptr;
1388
                        fq.flags = 0;
1389
                        find_vector_intersection(fq, hit_data);
1390
 
1391
                        if (hit_data.hit_type == HIT_NONE) {
1392
                                camera_pos = closer_p1;
1393
                        } else {
1394
                                make_random_vector(player_camera_vec);
1395
                                far_scale = 3*F1_0/2;
1396
                        }
1397
                }
1398
        }
1399
}
1400
 
1401
//      ------------------------------------------------------------------------------------------------------------------
1402
window_event_result dead_player_frame()
1403
{
1404
        auto &Objects = LevelUniqueObjectState.Objects;
1405
        auto &vcobjptridx = Objects.vcptridx;
1406
        auto &vmobjptr = Objects.vmptr;
1407
        auto &vmobjptridx = Objects.vmptridx;
1408
        static fix      time_dead = 0;
1409
 
1410
        if (Player_dead_state != player_dead_state::no)
1411
        {
1412
                time_dead += FrameTime;
1413
 
1414
                //      If unable to create camera at time of death, create now.
1415
                if (Dead_player_camera == Viewer_save) {
1416
                        const auto &player = get_local_plrobj();
1417
                        const auto &&objnum = obj_create(OBJ_CAMERA, 0, vmsegptridx(player.segnum), player.pos, &player.orient, 0, CT_NONE, MT_NONE, RT_NONE);
1418
 
1419
                        if (objnum != object_none)
1420
                                Viewer = Dead_player_camera = objnum;
1421
                        else {
1422
                                Int3();
1423
                        }
1424
                }              
1425
 
1426
                ConsoleObject->mtype.phys_info.rotvel.x = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/4;
1427
                ConsoleObject->mtype.phys_info.rotvel.y = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/2;
1428
                ConsoleObject->mtype.phys_info.rotvel.z = max(0, DEATH_SEQUENCE_EXPLODE_TIME - time_dead)/3;
1429
 
1430
                Camera_to_player_dist_goal = min(time_dead*8, F1_0*20) + ConsoleObject->size;
1431
 
1432
                set_camera_pos(Dead_player_camera->pos, vcobjptridx(ConsoleObject));
1433
 
1434
                // the following line uncommented by WraithX, 4-12-00
1435
                if (time_dead < DEATH_SEQUENCE_EXPLODE_TIME + F1_0 * 2)
1436
                {
1437
                        const auto fvec = vm_vec_sub(ConsoleObject->pos, Dead_player_camera->pos);
1438
                        vm_vector_2_matrix(Dead_player_camera->orient, fvec, nullptr, nullptr);
1439
                        Dead_player_camera->mtype.phys_info = ConsoleObject->mtype.phys_info;
1440
 
1441
                        // the following "if" added by WraithX to get rid of camera "wiggle"
1442
                        Dead_player_camera->mtype.phys_info.flags &= ~PF_WIGGLE;
1443
                        // end "if" added by WraithX, 4/13/00
1444
 
1445
                // the following line uncommented by WraithX, 4-12-00
1446
                }
1447
                else
1448
                {
1449
                        // the following line uncommented by WraithX, 4-11-00
1450
                        Dead_player_camera->movement_type = MT_PHYSICS;
1451
                        //Dead_player_camera->mtype.phys_info.rotvel.y = F1_0/8;
1452
                // the following line uncommented by WraithX, 4-12-00
1453
                }
1454
                // end addition by WX
1455
 
1456
                if (time_dead > DEATH_SEQUENCE_EXPLODE_TIME) {
1457
                        if (Player_dead_state != player_dead_state::exploded)
1458
                        {
1459
                                auto &player_info = get_local_plrobj().ctype.player_info;
1460
                                const auto hostages_lost = std::exchange(player_info.mission.hostages_on_board, 0);
1461
 
1462
                                if (hostages_lost > 1)
1463
                                        HUD_init_message(HM_DEFAULT, TXT_SHIP_DESTROYED_2, hostages_lost);
1464
                                else
1465
                                        HUD_init_message_literal(HM_DEFAULT, hostages_lost == 1 ? TXT_SHIP_DESTROYED_1 : TXT_SHIP_DESTROYED_0);
1466
 
1467
                                Player_dead_state = player_dead_state::exploded;
1468
 
1469
                                const auto cobjp = vmobjptridx(ConsoleObject);
1470
                                drop_player_eggs(cobjp);
1471
                                player_info.Player_eggs_dropped = true;
1472
                                if (Game_mode & GM_MULTI)
1473
                                {
1474
                                        multi_send_player_deres(deres_explode);
1475
                                }
1476
 
1477
                                explode_badass_player(cobjp);
1478
 
1479
                                //is this next line needed, given the badass call above?
1480
                                explode_object(cobjp,0);
1481
                                ConsoleObject->flags &= ~OF_SHOULD_BE_DEAD;             //don't really kill player
1482
                                ConsoleObject->render_type = RT_NONE;                           //..just make him disappear
1483
                                ConsoleObject->type = OBJ_GHOST;                                                //..and kill intersections
1484
#if defined(DXX_BUILD_DESCENT_II)
1485
                                player_info.powerup_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1486
#endif
1487
                        }
1488
                } else {
1489
                        if (d_rand() < FrameTime*4) {
1490
                                if (Game_mode & GM_MULTI)
1491
                                        multi_send_create_explosion(Player_num);
1492
                                create_small_fireball_on_object(vmobjptridx(ConsoleObject), F1_0, 1);
1493
                        }
1494
                }
1495
 
1496
 
1497
                if (GameViewUniqueState.Death_sequence_aborted)
1498
                {
1499
                        auto &player_info = get_local_plrobj().ctype.player_info;
1500
                        if (!player_info.Player_eggs_dropped) {
1501
                                player_info.Player_eggs_dropped = true;
1502
                                drop_player_eggs(vmobjptridx(ConsoleObject));
1503
                                if (Game_mode & GM_MULTI)
1504
                                {
1505
                                        multi_send_player_deres(deres_explode);
1506
                                }
1507
                        }
1508
 
1509
                        return DoPlayerDead();          //kill_player();
1510
                }
1511
        }
1512
        else
1513
                time_dead = 0;
1514
 
1515
        return window_event_result::handled;
1516
}
1517
 
1518
//      ------------------------------------------------------------------------------------------------------------------
1519
static void start_player_death_sequence(object &player)
1520
{
1521
        auto &Objects = LevelUniqueObjectState.Objects;
1522
#if defined(DXX_BUILD_DESCENT_II)
1523
        auto &vmobjptr = Objects.vmptr;
1524
#endif
1525
        auto &vmobjptridx = Objects.vmptridx;
1526
        assert(&player == ConsoleObject);
1527
        if (Player_dead_state != player_dead_state::no ||
1528
                Dead_player_camera != NULL ||
1529
                ((Game_mode & GM_MULTI) && (get_local_player().connected != CONNECT_PLAYING)))
1530
                return;
1531
 
1532
        //Assert(Dead_player_camera == NULL);
1533
 
1534
        reset_rear_view();
1535
 
1536
        if (!(Game_mode & GM_MULTI))
1537
                HUD_clear_messages();
1538
 
1539
        GameViewUniqueState.Death_sequence_aborted = 0;
1540
 
1541
        if (Game_mode & GM_MULTI)
1542
        {
1543
#if defined(DXX_BUILD_DESCENT_II)
1544
                // If Hoard, increase number of orbs by 1. Only if you haven't killed yourself. This prevents cheating
1545
                if (game_mode_hoard())
1546
                {
1547
                        auto &player_info = player.ctype.player_info;
1548
                        auto &proximity = player_info.hoard.orbs;
1549
                        if (proximity < player_info.max_hoard_orbs)
1550
                        {
1551
                                const auto is_bad_kill = [&vmobjptr]{
1552
                                        auto &lplr = get_local_player();
1553
                                        auto &lplrobj = get_local_plrobj();
1554
                                        const auto killer_objnum = lplrobj.ctype.player_info.killer_objnum;
1555
                                        if (killer_objnum == lplr.objnum)
1556
                                                /* Self kill */
1557
                                                return true;
1558
                                        if (killer_objnum == object_none)
1559
                                                /* Non-player kill */
1560
                                                return true;
1561
                                        const auto &&killer_objp = vmobjptr(killer_objnum);
1562
                                        if (killer_objp->type != OBJ_PLAYER)
1563
                                                return true;
1564
                                        if (!(Game_mode & GM_TEAM))
1565
                                                return false;
1566
                                        return get_team(Player_num) == get_team(get_player_id(killer_objp));
1567
                                };
1568
                                if (!is_bad_kill())
1569
                                        ++ proximity;
1570
                        }
1571
                }
1572
#endif
1573
                multi_send_kill(vmobjptridx(get_local_player().objnum));
1574
        }
1575
 
1576
        PaletteRedAdd = 40;
1577
        Player_dead_state = player_dead_state::yes;
1578
 
1579
        vm_vec_zero(player.mtype.phys_info.rotthrust);
1580
        vm_vec_zero(player.mtype.phys_info.thrust);
1581
 
1582
        const auto &&objnum = obj_create(OBJ_CAMERA, 0, vmsegptridx(player.segnum), player.pos, &player.orient, 0, CT_NONE, MT_NONE, RT_NONE);
1583
        Viewer_save = Viewer;
1584
        if (objnum != object_none)
1585
                Viewer = Dead_player_camera = objnum;
1586
        else {
1587
                Int3();
1588
                Dead_player_camera = ConsoleObject;
1589
        }
1590
 
1591
        select_cockpit(CM_LETTERBOX);
1592
        if (Newdemo_state == ND_STATE_RECORDING)
1593
                newdemo_record_letterbox();
1594
 
1595
        Player_flags_save = player.flags;
1596
        Control_type_save = player.control_type;
1597
        Render_type_save = player.render_type;
1598
 
1599
        player.flags &= ~OF_SHOULD_BE_DEAD;
1600
//      Players[Player_num].flags |= PLAYER_FLAGS_INVULNERABLE;
1601
        player.control_type = CT_NONE;
1602
 
1603
        PALETTE_FLASH_SET(0,0,0);
1604
}
1605
 
1606
//      ------------------------------------------------------------------------------------------------------------------
1607
static void obj_delete_all_that_should_be_dead()
1608
{
1609
        auto &Objects = LevelUniqueObjectState.Objects;
1610
        auto &vmobjptridx = Objects.vmptridx;
1611
        objnum_t                local_dead_player_object=object_none;
1612
 
1613
        // Move all objects
1614
        range_for (const auto &&objp, vmobjptridx)
1615
        {
1616
                if ((objp->type!=OBJ_NONE) && (objp->flags&OF_SHOULD_BE_DEAD) ) {
1617
                        Assert(!(objp->type==OBJ_FIREBALL && objp->ctype.expl_info.delete_time!=-1));
1618
                        if (objp->type==OBJ_PLAYER) {
1619
                                if ( get_player_id(objp) == Player_num ) {
1620
                                        if (local_dead_player_object == object_none) {
1621
                                                start_player_death_sequence(objp);
1622
                                                local_dead_player_object = objp;
1623
                                        } else
1624
                                                Int3(); //      Contact Mike: Illegal, killed player twice in this frame!
1625
                                                                        // Ok to continue, won't start death sequence again!
1626
                                        // kill_player();
1627
                                }
1628
                        } else {                                       
1629
                                obj_delete(LevelUniqueObjectState, Segments, objp);
1630
                        }
1631
                }
1632
        }
1633
}
1634
 
1635
//when an object has moved into a new segment, this function unlinks it
1636
//from its old segment, and links it into the new segment
1637
void obj_relink(fvmobjptr &vmobjptr, fvmsegptr &vmsegptr, const vmobjptridx_t objnum, const vmsegptridx_t newsegnum)
1638
{
1639
        obj_unlink(vmobjptr, vmsegptr, objnum);
1640
        obj_link_unchecked(vmobjptr, objnum, newsegnum);
1641
}
1642
 
1643
// for getting out of messed up linking situations (i.e. caused by demo playback)
1644
void obj_relink_all(void)
1645
{
1646
        auto &Objects = LevelUniqueObjectState.Objects;
1647
        range_for (const auto &&segp, vmsegptr)
1648
        {
1649
                segp->objects = object_none;
1650
        }
1651
 
1652
        range_for (const auto &&obj, Objects.vmptridx)
1653
        {
1654
                if (obj->type != OBJ_NONE)
1655
                {
1656
                        auto segnum = obj->segnum;
1657
                        if (segnum > Highest_segment_index)
1658
                                segnum = segment_first;
1659
                        obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
1660
                }
1661
        }
1662
}
1663
 
1664
//process a continuously-spinning object
1665
static void spin_object(object_base &obj)
1666
{
1667
        vms_angvec rotangs;
1668
        assert(obj.movement_type == MT_SPINNING);
1669
 
1670
        const fix frametime = FrameTime;
1671
        rotangs.p = fixmul(obj.mtype.spin_rate.x, frametime);
1672
        rotangs.h = fixmul(obj.mtype.spin_rate.y, frametime);
1673
        rotangs.b = fixmul(obj.mtype.spin_rate.z, frametime);
1674
 
1675
        const auto &&rotmat = vm_angles_2_matrix(rotangs);
1676
        obj.orient = vm_matrix_x_matrix(obj.orient, rotmat);
1677
        check_and_fix_matrix(obj.orient);
1678
}
1679
 
1680
#if defined(DXX_BUILD_DESCENT_II)
1681
imobjidx_t d_guided_missile_indices::get_player_active_guided_missile(const unsigned pnum) const
1682
{
1683
        return operator[](pnum);
1684
}
1685
 
1686
/* Place debug checks out of line so that they are shared among the
1687
 * template instantiations.
1688
 */
1689
bool d_guided_missile_indices::debug_check_current_object(const object_base &obj)
1690
{
1691
        assert(obj.type == OBJ_WEAPON);
1692
        const auto gmid = get_weapon_id(obj);
1693
        if (obj.type != OBJ_WEAPON)
1694
                return false;
1695
        assert(gmid == weapon_id_type::GUIDEDMISS_ID);
1696
        if (gmid != weapon_id_type::GUIDEDMISS_ID)
1697
                return false;
1698
        return true;
1699
}
1700
 
1701
template <typename R, typename F>
1702
R d_guided_missile_indices::get_player_active_guided_missile_tmpl(F &fobjptr, const unsigned pnum) const
1703
{
1704
        const auto gmidx = get_player_active_guided_missile(pnum);
1705
        if (gmidx == object_none)
1706
                return object_none;
1707
        auto &&gmobj = fobjptr(gmidx);
1708
        if (!debug_check_current_object(gmobj))
1709
                return object_none;
1710
        return gmobj;
1711
}
1712
 
1713
imobjptr_t d_guided_missile_indices::get_player_active_guided_missile(fvmobjptr &vmobjptr, const unsigned pnum) const
1714
{
1715
        return this->template get_player_active_guided_missile_tmpl<imobjptr_t>(vmobjptr, pnum);
1716
}
1717
 
1718
imobjptridx_t d_guided_missile_indices::get_player_active_guided_missile(fvmobjptridx &vmobjptridx, const unsigned pnum) const
1719
{
1720
        return this->template get_player_active_guided_missile_tmpl<imobjptridx_t>(vmobjptridx, pnum);
1721
}
1722
 
1723
void d_guided_missile_indices::set_player_active_guided_missile(const vmobjidx_t obji, const unsigned pnum)
1724
{
1725
        auto &i = operator[](pnum);
1726
        i = obji;
1727
}
1728
 
1729
void d_guided_missile_indices::clear_player_active_guided_missile(const unsigned pnum)
1730
{
1731
        auto &i = operator[](pnum);
1732
        i = object_none;
1733
}
1734
 
1735
int Drop_afterburner_blob_flag;         //ugly hack
1736
//see if wall is volatile, and if so, cause damage to player
1737
//returns true if player is in lava
1738
#endif
1739
 
1740
//--------------------------------------------------------------------
1741
//move an object for the current frame
1742
static window_event_result object_move_one(const vmobjptridx_t obj)
1743
{
1744
#if defined(DXX_BUILD_DESCENT_II)
1745
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1746
        auto &Vertices = LevelSharedVertexState.get_vertices();
1747
#endif
1748
        auto &Objects = LevelUniqueObjectState.Objects;
1749
        auto &vmobjptr = Objects.vmptr;
1750
        const auto previous_segment = obj->segnum;
1751
        auto result = window_event_result::handled;
1752
 
1753
        const auto obj_previous_position = obj->pos;                    // Save the current position
1754
 
1755
        if ((obj->type==OBJ_PLAYER) && (Player_num==get_player_id(obj)))        {
1756
                const auto &&segp = vmsegptr(obj->segnum);
1757
#if defined(DXX_BUILD_DESCENT_II)
1758
                if (game_mode_capture_flag())
1759
                        fuelcen_check_for_goal(obj, segp);
1760
                else if (game_mode_hoard())
1761
                        fuelcen_check_for_hoard_goal(obj, segp);
1762
#endif
1763
 
1764
                auto &player_info = obj->ctype.player_info;
1765
                auto &energy = player_info.energy;
1766
                const fix fuel = fuelcen_give_fuel(segp, INITIAL_ENERGY - energy);
1767
                if (fuel > 0 )  {
1768
                        energy += fuel;
1769
                }
1770
#if defined(DXX_BUILD_DESCENT_II)
1771
                auto &pl_shields = get_local_plrobj().shields;
1772
                const fix shields = repaircen_give_shields(segp, INITIAL_SHIELDS - pl_shields);
1773
                if (shields > 0) {
1774
                        pl_shields += shields;
1775
                }
1776
#endif
1777
        }
1778
 
1779
        {
1780
                auto lifeleft = obj->lifeleft;
1781
                if (lifeleft != IMMORTAL_TIME) //if not immortal...
1782
                {
1783
                        lifeleft -= FrameTime; //...inevitable countdown towards death
1784
#if defined(DXX_BUILD_DESCENT_II)
1785
                        if (obj->type == OBJ_MARKER)
1786
                        {
1787
                                if (lifeleft < F1_0*1000)
1788
                                        lifeleft += F1_0; // Make sure this object doesn't go out.
1789
                        }
1790
#endif
1791
                        obj->lifeleft = lifeleft;
1792
                }
1793
        }
1794
#if defined(DXX_BUILD_DESCENT_II)
1795
        Drop_afterburner_blob_flag = 0;
1796
#endif
1797
 
1798
        switch (obj->control_type) {
1799
 
1800
                case CT_NONE: break;
1801
 
1802
                case CT_FLYING:
1803
 
1804
                        read_flying_controls( obj );
1805
 
1806
                        break;
1807
 
1808
                case CT_REPAIRCEN:
1809
                        Int3();
1810
                        // -- hey! these are no longer supported!! -- do_repair_sequence(obj);
1811
                        break;
1812
 
1813
                case CT_POWERUP:
1814
                        do_powerup_frame(Vclip, obj);
1815
                        break;
1816
 
1817
                case CT_MORPH:                  //morph implies AI
1818
                        do_morph_frame(obj);
1819
                        //NOTE: FALLS INTO AI HERE!!!!
1820
                        DXX_BOOST_FALLTHROUGH;
1821
 
1822
                case CT_AI:
1823
                        //NOTE LINK TO CT_MORPH ABOVE!!!
1824
                        if (Game_suspended & SUSP_ROBOTS) return window_event_result::ignored;
1825
                        do_ai_frame(obj);
1826
                        break;
1827
 
1828
                case CT_WEAPON:         Laser_do_weapon_sequence(obj); break;
1829
                case CT_EXPLOSION:      do_explosion_sequence(obj); break;
1830
 
1831
                case CT_SLEW:
1832
#ifdef RELEASE
1833
                        obj->control_type = CT_NONE;
1834
                        con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "BUG: object %hu has control type CT_SLEW, sig/type/id = %i/%i/%i"), static_cast<objnum_t>(obj), obj->signature.get(), obj->type, obj->id);
1835
#else
1836
                        if ( keyd_pressed[KEY_PAD5] ) slew_stop();
1837
                        if ( keyd_pressed[KEY_NUMLOCK] )                {
1838
                                slew_reset_orient();
1839
                        }
1840
                        slew_frame(0 );         // Does velocity addition for us.
1841
#endif
1842
                        break;
1843
 
1844
//              case CT_FLYTHROUGH:
1845
//                      do_flythrough(obj,0);                   // HACK:do_flythrough should operate on an object!!!!
1846
//                      //check_object_seg(obj);
1847
//                      return; // DON'T DO THE REST OF OBJECT STUFF SINCE THIS IS A SPECIAL CASE!!!
1848
//                      break;
1849
 
1850
                case CT_DEBRIS: do_debris_frame(obj); break;
1851
 
1852
                case CT_LIGHT: break;           //doesn't do anything
1853
 
1854
                case CT_REMOTE: break;          //doesn't do anything
1855
 
1856
                case CT_CNTRLCEN: do_controlcen_frame(obj); break;
1857
 
1858
                default:
1859
 
1860
                        Error("Unknown control type %d in object %hu, sig/type/id = %i/%i/%i",obj->control_type, static_cast<objnum_t>(obj), obj->signature.get(), obj->type, obj->id);
1861
 
1862
                        break;
1863
 
1864
        }
1865
 
1866
        if (obj->lifeleft < 0 ) {               // We died of old age
1867
                obj->flags |= OF_SHOULD_BE_DEAD;
1868
                if ( obj->type==OBJ_WEAPON && Weapon_info[get_weapon_id(obj)].damage_radius )
1869
                        explode_badass_weapon(obj, obj->pos);
1870
#if defined(DXX_BUILD_DESCENT_II)
1871
                else if ( obj->type==OBJ_ROBOT) //make robots explode
1872
                        explode_object(obj,0);
1873
#endif
1874
        }
1875
 
1876
        if (obj->type == OBJ_NONE || obj->flags&OF_SHOULD_BE_DEAD)
1877
                return window_event_result::ignored;         // object has been deleted
1878
 
1879
        bool prepare_seglist = false;
1880
        phys_visited_seglist phys_visited_segs;
1881
        switch (obj->movement_type) {
1882
 
1883
                case MT_NONE:                   break;                          //this doesn't move
1884
 
1885
                case MT_PHYSICS:        //move by physics
1886
                        result = do_physics_sim(obj, obj_previous_position, obj->type == OBJ_PLAYER ? (prepare_seglist = true, phys_visited_segs.nsegs = 0, &phys_visited_segs) : nullptr);
1887
                        break;
1888
 
1889
                case MT_SPINNING:               spin_object(obj); break;
1890
 
1891
        }
1892
 
1893
#if defined(DXX_BUILD_DESCENT_II)
1894
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1895
        auto &vcwallptr = Walls.vcptr;
1896
#endif
1897
        //      If player and moved to another segment, see if hit any triggers.
1898
        // also check in player under a lavafall
1899
        if (prepare_seglist)
1900
        {
1901
                if (previous_segment != obj->segnum && phys_visited_segs.nsegs > 1)
1902
                {
1903
                        auto seg0 = vmsegptridx(phys_visited_segs.seglist[0]);
1904
#if defined(DXX_BUILD_DESCENT_II)
1905
                        int     old_level = Current_level_num;
1906
#endif
1907
                        range_for (const auto i, partial_const_range(phys_visited_segs.seglist, 1u, phys_visited_segs.nsegs))
1908
                        {
1909
                                const auto &&seg1 = seg0.absolute_sibling(i);
1910
                                const auto connect_side = find_connect_side(seg1, seg0);
1911
                                if (connect_side != side_none)
1912
                                {
1913
                                        result = check_trigger(seg0, connect_side, get_local_plrobj(), obj, 0);
1914
#if defined(DXX_BUILD_DESCENT_II)
1915
                                //maybe we've gone on to the next level.  if so, bail!
1916
                                if (Current_level_num != old_level)
1917
                                        return window_event_result::ignored;
1918
#endif
1919
                                }
1920
                                seg0 = seg1;
1921
                        }
1922
                }
1923
#if defined(DXX_BUILD_DESCENT_II)
1924
                {
1925
                        bool under_lavafall = false;
1926
 
1927
                        auto &playing = obj->ctype.player_info.lavafall_hiss_playing;
1928
                        const auto &&segp = vcsegptr(obj->segnum);
1929
                        auto &vcvertptr = Vertices.vcptr;
1930
                        if (const auto sidemask = get_seg_masks(vcvertptr, obj->pos, segp, obj->size).sidemask)
1931
                        {
1932
                                range_for (const unsigned sidenum, xrange(MAX_SIDES_PER_SEGMENT))
1933
                                {
1934
                                        if (!(sidemask & (1 << sidenum)))
1935
                                                continue;
1936
                                        const auto wall_num = segp->shared_segment::sides[sidenum].wall_num;
1937
                                        if (wall_num != wall_none && vcwallptr(wall_num)->type == WALL_ILLUSION)
1938
                                        {
1939
                                                const auto type = check_volatile_wall(obj, segp->unique_segment::sides[sidenum]);
1940
                                                if (type != volatile_wall_result::none)
1941
                                                {
1942
                                                        under_lavafall = 1;
1943
                                                        if (!playing)
1944
                                                        {
1945
                                                                playing = 1;
1946
                                                                const auto sound = (type == volatile_wall_result::lava) ? SOUND_LAVAFALL_HISS : SOUND_SHIP_IN_WATERFALL;
1947
                                                                digi_link_sound_to_object3(sound, obj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, -1, -1);
1948
                                                                break;
1949
                                                        }
1950
                                                }
1951
                                        }
1952
                                }
1953
                        }
1954
 
1955
                        if (!under_lavafall && playing)
1956
                        {
1957
                                playing = 0;
1958
                                digi_kill_sound_linked_to_object( obj);
1959
                        }
1960
                }
1961
#endif
1962
        }
1963
 
1964
#if defined(DXX_BUILD_DESCENT_II)
1965
        //see if guided missile has flown through exit trigger
1966
        if (obj == LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(Player_num))
1967
        {
1968
                if (previous_segment != obj->segnum) {
1969
                        const auto &&psegp = vcsegptr(previous_segment);
1970
                        const auto &&connect_side = find_connect_side(vcsegptridx(obj->segnum), psegp);
1971
                        if (connect_side != side_none)
1972
                        {
1973
                                const auto wall_num = psegp->shared_segment::sides[connect_side].wall_num;
1974
                                if ( wall_num != wall_none ) {
1975
                                        auto trigger_num = vcwallptr(wall_num)->trigger;
1976
                                        if (trigger_num != trigger_none)
1977
                                        {
1978
                                                auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
1979
                                                auto &vctrgptr = Triggers.vcptr;
1980
                                                const auto &&t = vctrgptr(trigger_num);
1981
                                                if (t->type == trigger_action::normal_exit)
1982
                                                        obj->lifeleft = 0;
1983
                                        }
1984
                                }
1985
                        }
1986
                }
1987
        }
1988
 
1989
        if (Drop_afterburner_blob_flag) {
1990
                Assert(obj==ConsoleObject);
1991
                drop_afterburner_blobs(obj, 2, i2f(5)/2, -1);   //      -1 means use default lifetime
1992
                if (Game_mode & GM_MULTI)
1993
                        multi_send_drop_blobs(Player_num);
1994
                Drop_afterburner_blob_flag = 0;
1995
        }
1996
 
1997
        if ((obj->type == OBJ_WEAPON) && (Weapon_info[get_weapon_id(obj)].afterburner_size)) {
1998
                fix     vel = vm_vec_mag_quick(obj->mtype.phys_info.velocity);
1999
                fix     delay, lifetime;
2000
 
2001
                if (vel > F1_0*200)
2002
                        delay = F1_0/16;
2003
                else if (vel > F1_0*40)
2004
                        delay = fixdiv(F1_0*13,vel);
2005
                else
2006
                        delay = F1_0/4;
2007
 
2008
                lifetime = (delay * 3)/2;
2009
                if (!(Game_mode & GM_MULTI)) {
2010
                        delay /= 2;
2011
                        lifetime *= 2;
2012
                }
2013
 
2014
                assert(obj->control_type == CT_WEAPON);
2015
                if ((obj->ctype.laser_info.last_afterburner_time + delay < GameTime64) || (obj->ctype.laser_info.last_afterburner_time > GameTime64)) {
2016
                        drop_afterburner_blobs(obj, 1, i2f(Weapon_info[get_weapon_id(obj)].afterburner_size)/16, lifetime);
2017
                        obj->ctype.laser_info.last_afterburner_time = GameTime64;
2018
                }
2019
        }
2020
 
2021
#endif
2022
        return result;
2023
}
2024
 
2025
//--------------------------------------------------------------------
2026
//move all objects for the current frame
2027
static window_event_result object_move_all()
2028
{
2029
        auto &Objects = LevelUniqueObjectState.Objects;
2030
        auto &vmobjptridx = Objects.vmptridx;
2031
        auto result = window_event_result::ignored;
2032
 
2033
        if (Highest_object_index > MAX_USED_OBJECTS)
2034
                free_object_slots(MAX_USED_OBJECTS);            //      Free all possible object slots.
2035
 
2036
        obj_delete_all_that_should_be_dead();
2037
 
2038
        if (PlayerCfg.AutoLeveling)
2039
                ConsoleObject->mtype.phys_info.flags |= PF_LEVELLING;
2040
        else
2041
                ConsoleObject->mtype.phys_info.flags &= ~PF_LEVELLING;
2042
 
2043
        // Move all objects
2044
        range_for (const auto &&objp, vmobjptridx)
2045
        {
2046
                if ( (objp->type != OBJ_NONE) && (!(objp->flags&OF_SHOULD_BE_DEAD)) )   {
2047
                        result = std::max(object_move_one( objp ), result);
2048
                }
2049
        }
2050
 
2051
//      check_duplicate_objects();
2052
//      remove_incorrect_objects();
2053
 
2054
        return result;
2055
}
2056
 
2057
window_event_result game_move_all_objects()
2058
{
2059
        LevelUniqueObjectState.last_console_player_position = ConsoleObject->pos;
2060
        return object_move_all();
2061
}
2062
 
2063
window_event_result endlevel_move_all_objects()
2064
{
2065
        return object_move_all();
2066
}
2067
 
2068
//--unused-- // -----------------------------------------------------------
2069
//--unused-- // Moved here from eobject.c on 02/09/94 by MK.
2070
//--unused-- int find_last_obj(int i)
2071
//--unused-- {
2072
//--unused--    for (i=MAX_OBJECTS;--i>=0;)
2073
//--unused--            if (Objects[i].type != OBJ_NONE) break;
2074
//--unused--
2075
//--unused--    return i;
2076
//--unused--
2077
//--unused-- }
2078
 
2079
 
2080
//make object array non-sparse
2081
void compress_objects(void)
2082
{
2083
        auto &Objects = LevelUniqueObjectState.Objects;
2084
        auto &vmobjptr = Objects.vmptr;
2085
        auto &vmobjptridx = Objects.vmptridx;
2086
        //last_i = find_last_obj(MAX_OBJECTS);
2087
 
2088
        //      Note: It's proper to do < (rather than <=) Highest_object_index here because we
2089
        //      are just removing gaps, and the last object can't be a gap.
2090
        for (objnum_t start_i=0;start_i<Highest_object_index;start_i++)
2091
        {
2092
                const auto &&start_objp = vmobjptridx(start_i);
2093
                if (start_objp->type == OBJ_NONE) {
2094
                        auto highest = Highest_object_index;
2095
                        const auto &&h = vmobjptr(static_cast<objnum_t>(highest));
2096
                        auto segnum_copy = h->segnum;
2097
 
2098
                        obj_unlink(Objects.vmptr, Segments.vmptr, h);
2099
 
2100
                        *start_objp = *h;
2101
 
2102
#if DXX_USE_EDITOR
2103
                        if (Cur_object_index == Highest_object_index)
2104
                                Cur_object_index = start_i;
2105
                        #endif
2106
 
2107
                        h->type = OBJ_NONE;
2108
 
2109
                        obj_link(Objects.vmptr, start_objp, vmsegptridx(segnum_copy));
2110
 
2111
                        while (vmobjptr(static_cast<objnum_t>(--highest))->type == OBJ_NONE)
2112
                        {
2113
                        }
2114
                        Objects.set_count(highest + 1);
2115
 
2116
                        //last_i = find_last_obj(last_i);
2117
 
2118
                }
2119
        }
2120
        reset_objects(LevelUniqueObjectState, LevelUniqueObjectState.num_objects);
2121
}
2122
 
2123
//called after load.  Takes number of objects,  and objects should be
2124
//compressed.  resets free list, marks unused objects as unused
2125
void reset_objects(d_level_unique_object_state &LevelUniqueObjectState, const unsigned n_objs)
2126
{
2127
        LevelUniqueObjectState.Debris_object_count = 0;
2128
        LevelUniqueObjectState.num_objects = n_objs;
2129
        assert(LevelUniqueObjectState.num_objects > 0);
2130
        auto &Objects = LevelUniqueObjectState.get_objects();
2131
        assert(LevelUniqueObjectState.num_objects < Objects.size());
2132
        Objects.set_count(n_objs);
2133
 
2134
        for (objnum_t i = n_objs; i < MAX_OBJECTS; ++i)
2135
        {
2136
                LevelUniqueObjectState.free_obj_list[i] = i;
2137
                auto &obj = *Objects.vmptr(i);
2138
                DXX_POISON_VAR(obj, 0xfd);
2139
                obj.type = OBJ_NONE;
2140
                obj.signature = object_signature_t{0};
2141
        }
2142
}
2143
 
2144
//Tries to find a segment for an object, using find_point_seg()
2145
imsegptridx_t find_object_seg(const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const object_base &obj)
2146
{
2147
        auto &Segments = LevelUniqueSegmentState.get_segments();
2148
        return find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj.pos, Segments.vmptridx(obj.segnum));
2149
}
2150
 
2151
 
2152
//If an object is in a segment, set its segnum field and make sure it's
2153
//properly linked.  If not in any segment, returns 0, else 1.
2154
//callers should generally use find_vector_intersection()
2155
int update_object_seg(fvmobjptr &vmobjptr, const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const vmobjptridx_t obj)
2156
{
2157
        const auto &&newseg = find_object_seg(LevelSharedSegmentState, LevelUniqueSegmentState, obj);
2158
        if (newseg == segment_none)
2159
                return 0;
2160
 
2161
        if ( newseg != obj->segnum )
2162
                obj_relink(vmobjptr, LevelUniqueSegmentState.get_segments().vmptr, obj, newseg);
2163
 
2164
        return 1;
2165
}
2166
 
2167
unsigned laser_parent_is_player(fvcobjptr &vcobjptr, const laser_parent &l, const object_base &o)
2168
{
2169
        /* Player objects are never recycled, so skip the signature check.
2170
         */
2171
        if (l.parent_type != OBJ_PLAYER)
2172
                return 0;
2173
        /* As a special case, let the player be recognized even if he died
2174
         * before the weapon hit the target.
2175
         */
2176
        if (o.type != OBJ_PLAYER && o.type != OBJ_GHOST)
2177
                return 0;
2178
        auto &parent_object = *vcobjptr(l.parent_num);
2179
        return (&parent_object == &o);
2180
}
2181
 
2182
unsigned laser_parent_is_object(fvcobjptr &vcobjptr, const laser_parent &l, const object_base &o)
2183
{
2184
        auto &parent_object = *vcobjptr(l.parent_num);
2185
        if (&parent_object != &o)
2186
                return 0;
2187
        return laser_parent_is_matching_signature(l, o);
2188
}
2189
 
2190
unsigned laser_parent_is_object(const laser_parent &l, const vcobjptridx_t o)
2191
{
2192
        if (l.parent_num != o.get_unchecked_index())
2193
                return 0;
2194
        return laser_parent_is_matching_signature(l, *o);
2195
}
2196
 
2197
unsigned laser_parent_object_exists(fvcobjptr &vcobjptr, const laser_parent &l)
2198
{
2199
        return laser_parent_is_matching_signature(l, *vcobjptr(l.parent_num));
2200
}
2201
 
2202
void set_powerup_id(const d_powerup_info_array &Powerup_info, const d_vclip_array &Vclip, object_base &o, powerup_type_t id)
2203
{
2204
        o.id = id;
2205
        o.size = Powerup_info[id].size;
2206
        const auto vclip_num = Powerup_info[id].vclip_num;
2207
        o.rtype.vclip_info.vclip_num = vclip_num;
2208
        o.rtype.vclip_info.frametime = Vclip[vclip_num].frame_time;
2209
}
2210
 
2211
//go through all objects and make sure they have the correct segment numbers
2212
void fix_object_segs()
2213
{
2214
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
2215
        auto &Objects = LevelUniqueObjectState.Objects;
2216
        auto &Vertices = LevelSharedVertexState.get_vertices();
2217
        auto &vmobjptr = Objects.vmptr;
2218
        auto &vmobjptridx = Objects.vmptridx;
2219
        auto &vcvertptr = Vertices.vcptr;
2220
        range_for (const auto &&o, vmobjptridx)
2221
        {
2222
                if (o->type != OBJ_NONE)
2223
                {
2224
                        const auto oldsegnum = o->segnum;
2225
                        if (update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, o) == 0)
2226
                        {
2227
                                const auto pos = o->pos;
2228
                                const auto segnum = o->segnum;
2229
                                compute_segment_center(vcvertptr, o->pos, vcsegptr(segnum));
2230
                                con_printf(CON_URGENT, "Object %hu claims segment %hu, but has position {%i,%i,%i}; moving to %hu:{%i,%i,%i}", o.get_unchecked_index(), oldsegnum, pos.x, pos.y, pos.z, segnum, o->pos.x, o->pos.y, o->pos.z);
2231
                        }
2232
                }
2233
        }
2234
}
2235
 
2236
 
2237
//--unused-- void object_use_new_object_list( object * new_list )
2238
//--unused-- {
2239
//--unused--    int i, segnum;
2240
//--unused--    object *obj;
2241
//--unused--
2242
//--unused--    // First, unlink all the old objects for the segments array
2243
//--unused--    for (segnum=0; segnum <= Highest_segment_index; segnum++) {
2244
//--unused--            Segments[segnum].objects = -1;
2245
//--unused--    }
2246
//--unused--    // Then, erase all the objects
2247
//--unused--    reset_objects(1);
2248
//--unused--
2249
//--unused--    // Fill in the object array
2250
//--unused--    memcpy( Objects, new_list, sizeof(object)*MAX_OBJECTS );
2251
//--unused--
2252
//--unused--    Highest_object_index=-1;
2253
//--unused--
2254
//--unused--    // Relink 'em
2255
//--unused--    for (i=0; i<MAX_OBJECTS; i++ )  {
2256
//--unused--            obj = &Objects[i];
2257
//--unused--            if ( obj->type != OBJ_NONE )    {
2258
//--unused--                    num_objects++;
2259
//--unused--                    Highest_object_index = i;
2260
//--unused--                    segnum = obj->segnum;
2261
//--unused--                    obj->next = obj->prev = obj->segnum = -1;
2262
//--unused--                    obj_link(i,segnum);
2263
//--unused--            } else {
2264
//--unused--                    obj->next = obj->prev = obj->segnum = -1;
2265
//--unused--            }
2266
//--unused--    }
2267
//--unused--    
2268
//--unused-- }
2269
 
2270
#if defined(DXX_BUILD_DESCENT_I)
2271
#define object_is_clearable_weapon(W,a,b)       object_is_clearable_weapon(a,b)
2272
#endif
2273
static unsigned object_is_clearable_weapon(const weapon_info_array &Weapon_info, const object_base obj, const unsigned clear_all)
2274
{
2275
        if (!(obj.type == OBJ_WEAPON))
2276
                return 0;
2277
        const auto weapon_id = get_weapon_id(obj);
2278
#if defined(DXX_BUILD_DESCENT_II)
2279
        if (Weapon_info[weapon_id].flags & WIF_PLACABLE)
2280
                return 0;
2281
#endif
2282
        if (clear_all)
2283
                return clear_all;
2284
        return !is_proximity_bomb_or_player_smart_mine(weapon_id);
2285
}
2286
 
2287
//delete objects, such as weapons & explosions, that shouldn't stay between levels
2288
//      Changed by MK on 10/15/94, don't remove proximity bombs.
2289
//if clear_all is set, clear even proximity bombs
2290
void clear_transient_objects(int clear_all)
2291
{
2292
        auto &Objects = LevelUniqueObjectState.Objects;
2293
        auto &vmobjptridx = Objects.vmptridx;
2294
        range_for (const auto &&obj, vmobjptridx)
2295
        {
2296
                if (object_is_clearable_weapon(Weapon_info, obj, clear_all) ||
2297
                         obj->type == OBJ_FIREBALL ||
2298
                         obj->type == OBJ_DEBRIS ||
2299
                         (obj->type!=OBJ_NONE && obj->flags & OF_EXPLODING)) {
2300
                        obj_delete(LevelUniqueObjectState, Segments, obj);
2301
                }
2302
        }
2303
}
2304
 
2305
//attaches an object, such as a fireball, to another object, such as a robot
2306
void obj_attach(object_array &Objects, const vmobjptridx_t parent, const vmobjptridx_t sub)
2307
{
2308
        Assert(sub->type == OBJ_FIREBALL);
2309
        Assert(sub->control_type == CT_EXPLOSION);
2310
 
2311
        Assert(sub->ctype.expl_info.next_attach==object_none);
2312
        Assert(sub->ctype.expl_info.prev_attach==object_none);
2313
 
2314
        assert(parent->attached_obj == object_none || Objects.vcptr(parent->attached_obj)->ctype.expl_info.prev_attach == object_none);
2315
 
2316
        sub->ctype.expl_info.next_attach = parent->attached_obj;
2317
 
2318
        if (sub->ctype.expl_info.next_attach != object_none)
2319
                Objects.vmptr(sub->ctype.expl_info.next_attach)->ctype.expl_info.prev_attach = sub;
2320
 
2321
        parent->attached_obj = sub;
2322
 
2323
        sub->ctype.expl_info.attach_parent = parent;
2324
        sub->flags |= OF_ATTACHED;
2325
 
2326
        Assert(sub->ctype.expl_info.next_attach != sub);
2327
        Assert(sub->ctype.expl_info.prev_attach != sub);
2328
}
2329
 
2330
//dettaches one object
2331
void obj_detach_one(object_array &Objects, object &sub)
2332
{
2333
        Assert(sub.flags & OF_ATTACHED);
2334
        Assert(sub.ctype.expl_info.attach_parent != object_none);
2335
 
2336
        const auto &&parent_objp = Objects.vcptr(sub.ctype.expl_info.attach_parent);
2337
        if (parent_objp->type == OBJ_NONE || parent_objp->attached_obj == object_none)
2338
        {
2339
                sub.flags &= ~OF_ATTACHED;
2340
                return;
2341
        }
2342
 
2343
        if (sub.ctype.expl_info.next_attach != object_none)
2344
        {
2345
                auto &a = Objects.vmptr(sub.ctype.expl_info.next_attach)->ctype.expl_info.prev_attach;
2346
                assert(Objects.vcptr(a) == &sub);
2347
                a = sub.ctype.expl_info.prev_attach;
2348
        }
2349
 
2350
        const auto use_prev_attach = (sub.ctype.expl_info.prev_attach != object_none);
2351
        auto &o = *Objects.vmptr(use_prev_attach ? std::exchange(sub.ctype.expl_info.prev_attach, object_none) : sub.ctype.expl_info.attach_parent);
2352
        auto &update_attach = use_prev_attach ? o.ctype.expl_info.next_attach : o.attached_obj;
2353
        assert(Objects.vcptr(update_attach) == &sub);
2354
        update_attach = sub.ctype.expl_info.next_attach;
2355
 
2356
        sub.ctype.expl_info.next_attach = object_none;
2357
        sub.flags &= ~OF_ATTACHED;
2358
 
2359
}
2360
 
2361
//dettaches all objects from this object
2362
static void obj_detach_all(object_array &Objects, object_base &parent)
2363
{
2364
        while (parent.attached_obj != object_none)
2365
                obj_detach_one(Objects, Objects.vmptr(parent.attached_obj));
2366
}
2367
 
2368
#if defined(DXX_BUILD_DESCENT_II)
2369
//creates a marker object in the world.  returns the object number
2370
imobjptridx_t drop_marker_object(const vms_vector &pos, const vmsegptridx_t segnum, const vms_matrix &orient, const game_marker_index marker_num)
2371
{
2372
        Assert(Marker_model_num != -1);
2373
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
2374
        const movement_type_t movement_type =
2375
                ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP) && Netgame.Allow_marker_view)
2376
                ? MT_NONE
2377
                : MT_SPINNING;
2378
        const auto &&obj = obj_create(OBJ_MARKER, static_cast<unsigned>(marker_num), segnum, pos, &orient, Polygon_models[Marker_model_num].rad, CT_NONE, movement_type, RT_POLYOBJ);
2379
        if (obj != object_none) {
2380
                auto &o = *obj;
2381
                o.rtype.pobj_info.model_num = Marker_model_num;
2382
 
2383
                if (movement_type == MT_SPINNING)
2384
                {
2385
                constexpr fix scale = F1_0 / 2;
2386
                const auto oi = obj.get_unchecked_index();
2387
                auto &spin_vec = o.mtype.spin_rate;
2388
                spin_vec = {};
2389
                if (oi & 1)
2390
                        vm_vec_scale_add2(spin_vec, o.orient.fvec, (oi & 8) ? scale : -scale);
2391
                if (oi & 2)
2392
                        vm_vec_scale_add2(spin_vec, o.orient.uvec, (oi & 16) ? scale : -scale);
2393
                if (oi & 4)
2394
                        vm_vec_scale_add2(spin_vec, o.orient.rvec, (oi & 32) ? scale : -scale);
2395
                }
2396
 
2397
                //      MK, 10/16/95: Using lifeleft to make it flash, thus able to trim lightlevel from all objects.
2398
                o.lifeleft = IMMORTAL_TIME - 1;
2399
        }
2400
        return obj;    
2401
}
2402
 
2403
//      *viewer is a viewer, probably a missile.
2404
//      wake up all robots that were rendered last frame subject to some constraints.
2405
void wake_up_rendered_objects(const object &viewer, window_rendered_data &window)
2406
{
2407
        auto &Objects = LevelUniqueObjectState.Objects;
2408
        auto &vmobjptr = Objects.vmptr;
2409
        //      Make sure that we are processing current data.
2410
        if (timer_query() != window.time) {
2411
                return;
2412
        }
2413
 
2414
        Ai_last_missile_camera = &viewer;
2415
 
2416
        range_for (const auto objnum, window.rendered_robots)
2417
        {
2418
                int     fcval = d_tick_count & 3;
2419
                if ((objnum & 3) == fcval) {
2420
                        const auto &&objp = vmobjptr(objnum);
2421
 
2422
                        if (objp->type == OBJ_ROBOT) {
2423
                                if (vm_vec_dist_quick(viewer.pos, objp->pos) < F1_0*100)
2424
                                {
2425
                                        ai_local                *ailp = &objp->ctype.ai_info.ail;
2426
                                        if (ailp->player_awareness_type == player_awareness_type_t::PA_NONE) {
2427
                                                objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_CAMERA_AWAKE;
2428
                                                ailp->player_awareness_type = player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION;
2429
                                                ailp->player_awareness_time = F1_0*3;
2430
                                                ailp->previous_visibility = player_visibility_state::visible_and_in_field_of_view;
2431
                                        }
2432
                                }
2433
                        }
2434
                }
2435
        }
2436
}
2437
#endif
2438
 
2439
// Swap endianess of given object_rw if swap == 1
2440
void object_rw_swap(object_rw *obj, int swap)
2441
{
2442
        if (!swap)
2443
                return;
2444
 
2445
        obj->signature     = SWAPINT(obj->signature);
2446
        obj->next          = SWAPSHORT(obj->next);
2447
        obj->prev          = SWAPSHORT(obj->prev);
2448
        obj->segnum        = SWAPSHORT(obj->segnum);
2449
        obj->attached_obj  = SWAPSHORT(obj->attached_obj);
2450
        obj->pos.x         = SWAPINT(obj->pos.x);
2451
        obj->pos.y         = SWAPINT(obj->pos.y);
2452
        obj->pos.z         = SWAPINT(obj->pos.z);
2453
        obj->orient.rvec.x = SWAPINT(obj->orient.rvec.x);
2454
        obj->orient.rvec.y = SWAPINT(obj->orient.rvec.y);
2455
        obj->orient.rvec.z = SWAPINT(obj->orient.rvec.z);
2456
        obj->orient.fvec.x = SWAPINT(obj->orient.fvec.x);
2457
        obj->orient.fvec.y = SWAPINT(obj->orient.fvec.y);
2458
        obj->orient.fvec.z = SWAPINT(obj->orient.fvec.z);
2459
        obj->orient.uvec.x = SWAPINT(obj->orient.uvec.x);
2460
        obj->orient.uvec.y = SWAPINT(obj->orient.uvec.y);
2461
        obj->orient.uvec.z = SWAPINT(obj->orient.uvec.z);
2462
        obj->size          = SWAPINT(obj->size);
2463
        obj->shields       = SWAPINT(obj->shields);
2464
        obj->last_pos.x    = SWAPINT(obj->last_pos.x);
2465
        obj->last_pos.y    = SWAPINT(obj->last_pos.y);
2466
        obj->last_pos.z    = SWAPINT(obj->last_pos.z);
2467
        obj->lifeleft      = SWAPINT(obj->lifeleft);
2468
 
2469
        switch (obj->movement_type)
2470
        {
2471
                case MT_PHYSICS:
2472
                        obj->mtype.phys_info.velocity.x  = SWAPINT(obj->mtype.phys_info.velocity.x);
2473
                        obj->mtype.phys_info.velocity.y  = SWAPINT(obj->mtype.phys_info.velocity.y);
2474
                        obj->mtype.phys_info.velocity.z  = SWAPINT(obj->mtype.phys_info.velocity.z);
2475
                        obj->mtype.phys_info.thrust.x    = SWAPINT(obj->mtype.phys_info.thrust.x);
2476
                        obj->mtype.phys_info.thrust.y    = SWAPINT(obj->mtype.phys_info.thrust.y);
2477
                        obj->mtype.phys_info.thrust.z    = SWAPINT(obj->mtype.phys_info.thrust.z);
2478
                        obj->mtype.phys_info.mass        = SWAPINT(obj->mtype.phys_info.mass);
2479
                        obj->mtype.phys_info.drag        = SWAPINT(obj->mtype.phys_info.drag);
2480
                        obj->mtype.phys_info.rotvel.x    = SWAPINT(obj->mtype.phys_info.rotvel.x);
2481
                        obj->mtype.phys_info.rotvel.y    = SWAPINT(obj->mtype.phys_info.rotvel.y);
2482
                        obj->mtype.phys_info.rotvel.z    = SWAPINT(obj->mtype.phys_info.rotvel.z);
2483
                        obj->mtype.phys_info.rotthrust.x = SWAPINT(obj->mtype.phys_info.rotthrust.x);
2484
                        obj->mtype.phys_info.rotthrust.y = SWAPINT(obj->mtype.phys_info.rotthrust.y);
2485
                        obj->mtype.phys_info.rotthrust.z = SWAPINT(obj->mtype.phys_info.rotthrust.z);
2486
                        obj->mtype.phys_info.turnroll    = SWAPINT(obj->mtype.phys_info.turnroll);
2487
                        obj->mtype.phys_info.flags       = SWAPSHORT(obj->mtype.phys_info.flags);
2488
                        break;
2489
 
2490
                case MT_SPINNING:
2491
                        obj->mtype.spin_rate.x = SWAPINT(obj->mtype.spin_rate.x);
2492
                        obj->mtype.spin_rate.y = SWAPINT(obj->mtype.spin_rate.y);
2493
                        obj->mtype.spin_rate.z = SWAPINT(obj->mtype.spin_rate.z);
2494
                        break;
2495
        }
2496
 
2497
        switch (obj->control_type)
2498
        {
2499
                case CT_WEAPON:
2500
                        obj->ctype.laser_info.parent_type      = SWAPSHORT(obj->ctype.laser_info.parent_type);
2501
                        obj->ctype.laser_info.parent_num       = SWAPSHORT(obj->ctype.laser_info.parent_num);
2502
                        obj->ctype.laser_info.parent_signature = SWAPINT(obj->ctype.laser_info.parent_signature);
2503
                        obj->ctype.laser_info.creation_time    = SWAPINT(obj->ctype.laser_info.creation_time);
2504
                        obj->ctype.laser_info.last_hitobj      = SWAPSHORT(obj->ctype.laser_info.last_hitobj);
2505
                        obj->ctype.laser_info.track_goal       = SWAPSHORT(obj->ctype.laser_info.track_goal);
2506
                        obj->ctype.laser_info.multiplier       = SWAPINT(obj->ctype.laser_info.multiplier);
2507
                        break;
2508
 
2509
                case CT_EXPLOSION:
2510
                        obj->ctype.expl_info.spawn_time    = SWAPINT(obj->ctype.expl_info.spawn_time);
2511
                        obj->ctype.expl_info.delete_time   = SWAPINT(obj->ctype.expl_info.delete_time);
2512
                        obj->ctype.expl_info.delete_objnum = SWAPSHORT(obj->ctype.expl_info.delete_objnum);
2513
                        obj->ctype.expl_info.attach_parent = SWAPSHORT(obj->ctype.expl_info.attach_parent);
2514
                        obj->ctype.expl_info.prev_attach   = SWAPSHORT(obj->ctype.expl_info.prev_attach);
2515
                        obj->ctype.expl_info.next_attach   = SWAPSHORT(obj->ctype.expl_info.next_attach);
2516
                        break;
2517
 
2518
                case CT_AI:
2519
                        obj->ctype.ai_info.hide_segment           = SWAPSHORT(obj->ctype.ai_info.hide_segment);
2520
                        obj->ctype.ai_info.hide_index             = SWAPSHORT(obj->ctype.ai_info.hide_index);
2521
                        obj->ctype.ai_info.path_length            = SWAPSHORT(obj->ctype.ai_info.path_length);
2522
#if defined(DXX_BUILD_DESCENT_I)
2523
                        obj->ctype.ai_info.cur_path_index         = SWAPSHORT(obj->ctype.ai_info.cur_path_index);
2524
#elif defined(DXX_BUILD_DESCENT_II)
2525
                        obj->ctype.ai_info.dying_start_time       = SWAPINT(obj->ctype.ai_info.dying_start_time);
2526
#endif
2527
                        obj->ctype.ai_info.danger_laser_num       = SWAPSHORT(obj->ctype.ai_info.danger_laser_num);
2528
                        obj->ctype.ai_info.danger_laser_signature = SWAPINT(obj->ctype.ai_info.danger_laser_signature);
2529
                        break;
2530
 
2531
                case CT_LIGHT:
2532
                        obj->ctype.light_info.intensity = SWAPINT(obj->ctype.light_info.intensity);
2533
                        break;
2534
 
2535
                case CT_POWERUP:
2536
                        obj->ctype.powerup_info.count         = SWAPINT(obj->ctype.powerup_info.count);
2537
#if defined(DXX_BUILD_DESCENT_II)
2538
                        obj->ctype.powerup_info.creation_time = SWAPINT(obj->ctype.powerup_info.creation_time);
2539
                        obj->ctype.powerup_info.flags         = SWAPINT(obj->ctype.powerup_info.flags);
2540
#endif
2541
                        break;
2542
        }
2543
 
2544
        switch (obj->render_type)
2545
        {
2546
                case RT_MORPH:
2547
                case RT_POLYOBJ:
2548
                case RT_NONE: // HACK below
2549
                {
2550
                        if (obj->render_type == RT_NONE && obj->type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time.
2551
                                break;
2552
                        obj->rtype.pobj_info.model_num                = SWAPINT(obj->rtype.pobj_info.model_num);
2553
                        for (uint_fast32_t i=0;i<MAX_SUBMODELS;i++)
2554
                        {
2555
                                obj->rtype.pobj_info.anim_angles[i].p = SWAPINT(obj->rtype.pobj_info.anim_angles[i].p);
2556
                                obj->rtype.pobj_info.anim_angles[i].b = SWAPINT(obj->rtype.pobj_info.anim_angles[i].b);
2557
                                obj->rtype.pobj_info.anim_angles[i].h = SWAPINT(obj->rtype.pobj_info.anim_angles[i].h);
2558
                        }
2559
                        obj->rtype.pobj_info.subobj_flags             = SWAPINT(obj->rtype.pobj_info.subobj_flags);
2560
                        obj->rtype.pobj_info.tmap_override            = SWAPINT(obj->rtype.pobj_info.tmap_override);
2561
                        obj->rtype.pobj_info.alt_textures             = SWAPINT(obj->rtype.pobj_info.alt_textures);
2562
                        break;
2563
                }
2564
 
2565
                case RT_WEAPON_VCLIP:
2566
                case RT_HOSTAGE:
2567
                case RT_POWERUP:
2568
                case RT_FIREBALL:
2569
                        obj->rtype.vclip_info.vclip_num = SWAPINT(obj->rtype.vclip_info.vclip_num);
2570
                        obj->rtype.vclip_info.frametime = SWAPINT(obj->rtype.vclip_info.frametime);
2571
                        break;
2572
 
2573
                case RT_LASER:
2574
                        break;
2575
 
2576
        }
2577
}
2578
 
2579
}
2580
 
2581
namespace dcx {
2582
 
2583
void (check_warn_object_type)(const object_base &o, object_type_t t, const char *file, unsigned line)
2584
{
2585
        if (o.type != t)
2586
                con_printf(CON_URGENT, "%s:%u: BUG: object %p has type %u, expected %u", file, line, &o, o.type, t);
2587
}
2588
 
2589
}