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
 * Code for the control center
23
 *
24
 */
25
 
26
 
27
#include <stdlib.h>
28
#include <stdio.h>
29
#if !defined(_WIN32) && !defined(macintosh)
30
#include <unistd.h>
31
#endif
32
#include "pstypes.h"
33
#include "dxxerror.h"
34
#include "inferno.h"
35
#include "cntrlcen.h"
36
#include "game.h"
37
#include "laser.h"
38
#include "gameseq.h"
39
#include "ai.h"
40
#include "player.h"
41
#include "multi.h"
42
#include "fwd-wall.h"
43
#include "segment.h"
44
#include "object.h"
45
#include "robot.h"
46
#include "vclip.h"
47
#include "physfs-serial.h"
48
#include "fireball.h"
49
#include "endlevel.h"
50
#include "state.h"
51
#include "args.h"
52
#include "wall.h"
53
 
54
#include "compiler-range_for.h"
55
#include "partial_range.h"
56
 
57
namespace dsx {
58
std::array<reactor, MAX_REACTORS> Reactors;
59
#if defined(DXX_BUILD_DESCENT_II)
60
unsigned Num_reactors;
61
#endif
62
}
63
 
64
control_center_triggers ControlCenterTriggers;
65
 
66
namespace dsx {
67
 
68
static window_event_result do_countdown_frame();
69
 
70
//      -----------------------------------------------------------------------------
71
//return the position & orientation of a gun on the control center object
72
static void calc_controlcen_gun_point(reactor &r, object &obj, const uint_fast32_t gun_num)
73
{
74
        //instance gun position & orientation
75
 
76
        auto &gun_point = obj.ctype.reactor_info.gun_pos[gun_num];
77
        auto &gun_dir = obj.ctype.reactor_info.gun_dir[gun_num];
78
        const auto &&m = vm_transposed_matrix(obj.orient);
79
        vm_vec_rotate(gun_point, r.gun_points[gun_num], m);
80
        vm_vec_add2(gun_point, obj.pos);
81
        vm_vec_rotate(gun_dir, r.gun_dirs[gun_num], m);
82
}
83
 
84
void calc_controlcen_gun_point(object &obj)
85
{
86
        assert(obj.type == OBJ_CNTRLCEN);
87
        assert(obj.render_type == RT_POLYOBJ);
88
        auto &reactor = get_reactor_definition(get_reactor_id(obj));
89
        for (uint_fast32_t i = reactor.n_guns; i--;)
90
                calc_controlcen_gun_point(reactor, obj, i);
91
}
92
 
93
//      -----------------------------------------------------------------------------
94
//      Look at control center guns, find best one to fire at *objp.
95
//      Return best gun number (one whose direction dotted with vector to player is largest).
96
//      If best gun has negative dot, return -1, meaning no gun is good.
97
static int calc_best_gun(const unsigned num_guns, const object &objreactor, const vms_vector &objpos)
98
{
99
        int     i;
100
        fix     best_dot;
101
        int     best_gun;
102
        auto &gun_pos = objreactor.ctype.reactor_info.gun_pos;
103
        auto &gun_dir = objreactor.ctype.reactor_info.gun_dir;
104
 
105
        best_dot = -F1_0*2;
106
        best_gun = -1;
107
 
108
        for (i=0; i<num_guns; i++) {
109
                fix                     dot;
110
                const auto gun_vec = vm_vec_normalized_quick(vm_vec_sub(objpos, gun_pos[i]));
111
                dot = vm_vec_dot(gun_dir[i], gun_vec);
112
 
113
                if (dot > best_dot) {
114
                        best_dot = dot;
115
                        best_gun = i;
116
                }
117
        }
118
 
119
        Assert(best_gun != -1);         // Contact Mike.  This is impossible.  Or maybe you're getting an unnormalized vector somewhere.
120
 
121
        if (best_dot < 0)
122
                return -1;
123
        else
124
                return best_gun;
125
}
126
 
127
}
128
 
129
namespace dcx {
130
constexpr int   D1_Alan_pavlish_reactor_times[NDL] = {50, 45, 40, 35, 30};
131
}
132
namespace dsx {
133
#if defined(DXX_BUILD_DESCENT_II)
134
constexpr int   D2_Alan_pavlish_reactor_times[NDL] = {90, 60, 45, 35, 30};
135
#endif
136
 
137
//      -----------------------------------------------------------------------------
138
//      Called every frame.  If control center been destroyed, then actually do something.
139
window_event_result do_controlcen_dead_frame()
140
{
141
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
142
        auto &Objects = LevelUniqueObjectState.Objects;
143
        auto &vmobjptridx = Objects.vmptridx;
144
        if ((Game_mode & GM_MULTI) && (get_local_player().connected != CONNECT_PLAYING)) // if out of level already there's no need for this
145
                return window_event_result::ignored;
146
 
147
        const auto Dead_controlcen_object_num = LevelUniqueControlCenterState.Dead_controlcen_object_num;
148
        if (Dead_controlcen_object_num != object_none && LevelUniqueControlCenterState.Countdown_seconds_left > 0)
149
                if (d_rand() < FrameTime*4)
150
#if defined(DXX_BUILD_DESCENT_I)
151
#define CC_FIREBALL_SCALE       F1_0*3
152
#elif defined(DXX_BUILD_DESCENT_II)
153
#define CC_FIREBALL_SCALE       F1_0
154
#endif
155
                        create_small_fireball_on_object(vmobjptridx(Dead_controlcen_object_num), CC_FIREBALL_SCALE, 1);
156
 
157
        if (LevelUniqueControlCenterState.Control_center_destroyed && !Endlevel_sequence)
158
                return do_countdown_frame();
159
 
160
        return window_event_result::ignored;
161
}
162
 
163
#define COUNTDOWN_VOICE_TIME fl2f(12.75)
164
 
165
window_event_result do_countdown_frame()
166
{
167
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
168
        fix     old_time;
169
 
170
        if (!LevelUniqueControlCenterState.Control_center_destroyed)
171
                return window_event_result::ignored;
172
 
173
#if defined(DXX_BUILD_DESCENT_II)
174
        if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE)   // get countdown in OEM and SHAREWARE only
175
        {
176
                // On last level, we don't want a countdown.
177
                if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
178
                {
179
                        if (!(Game_mode & GM_MULTI))
180
                                return window_event_result::ignored;
181
                        if (Game_mode & GM_MULTI_ROBOTS)
182
                                return window_event_result::ignored;
183
                }
184
        }
185
#endif
186
 
187
        //      Control center destroyed, rock the player's ship.
188
        if (d_tick_step)
189
        {
190
                auto &rotvel = ConsoleObject->mtype.phys_info.rotvel;
191
                const auto get_base_disturbance = [fc = std::min(LevelUniqueControlCenterState.Countdown_seconds_left, 16)]() {
192
                        return fixmul(d_rand() - 16384, 3 * F1_0 / 16 + (F1_0 * (16 - fc)) / 32);
193
                };
194
                fix disturb_x = get_base_disturbance(), disturb_z = get_base_disturbance();
195
                //      At Trainee, decrease rocking of ship by 4x.
196
                if (GameUniqueState.Difficulty_level == Difficulty_0)
197
                {
198
                        disturb_x /= 4;
199
                        disturb_z /= 4;
200
                }
201
                rotvel.x += disturb_x;
202
                rotvel.z += disturb_z;
203
        }
204
        //      Hook in the rumble sound effect here.
205
 
206
        old_time = LevelUniqueControlCenterState.Countdown_timer;
207
        LevelUniqueControlCenterState.Countdown_timer -= FrameTime;
208
        const auto Countdown_timer = LevelUniqueControlCenterState.Countdown_timer;
209
        const auto Countdown_seconds_left = LevelUniqueControlCenterState.Countdown_seconds_left = f2i(Countdown_timer + F1_0*7/8);
210
 
211
        if (old_time > COUNTDOWN_VOICE_TIME && Countdown_timer <= COUNTDOWN_VOICE_TIME)
212
        {
213
                digi_play_sample( SOUND_COUNTDOWN_13_SECS, F3_0 );
214
        }
215
        if (f2i(old_time + F1_0 * 7 / 8) != Countdown_seconds_left)
216
        {
217
                if (Countdown_seconds_left >= 0 && Countdown_seconds_left < 10)
218
                        digi_play_sample(SOUND_COUNTDOWN_0_SECS + Countdown_seconds_left, F3_0);
219
                if (Countdown_seconds_left == LevelUniqueControlCenterState.Total_countdown_time - 1)
220
                        digi_play_sample( SOUND_COUNTDOWN_29_SECS, F3_0 );
221
        }                                              
222
 
223
        if (Countdown_timer > 0) {
224
                fix size,old_size;
225
                const auto Total_countdown_time = LevelUniqueControlCenterState.Total_countdown_time;
226
                size = (i2f(Total_countdown_time) - Countdown_timer) / fl2f(0.65);
227
                old_size = (i2f(Total_countdown_time) - old_time) / fl2f(0.65);
228
                if (size != old_size && Countdown_seconds_left < Total_countdown_time - 5)
229
                {                       // Every 2 seconds!
230
                        //@@if (Dead_controlcen_object_num != -1) {
231
                        //@@    vms_vector vp;  //,v,c;
232
                        //@@    compute_segment_center(&vp, &Segments[Objects[Dead_controlcen_object_num].segnum]);
233
                        //@@    object_create_explosion( Objects[Dead_controlcen_object_num].segnum, &vp, size*10, VCLIP_SMALL_EXPLOSION);
234
                        //@@}
235
 
236
                        digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F3_0 );
237
                }
238
        }  else {
239
                int flash_value;
240
 
241
                if (old_time > 0)
242
                        digi_play_sample( SOUND_MINE_BLEW_UP, F1_0 );
243
 
244
                flash_value = f2i(-Countdown_timer * (64 / 4)); // 4 seconds to total whiteness
245
                PALETTE_FLASH_SET(flash_value,flash_value,flash_value);
246
 
247
                if (PaletteBlueAdd > 64 )       {
248
                        gr_set_default_canvas();
249
                        gr_clear_canvas(*grd_curcanv, BM_XRGB(31,31,31));                               //make screen all white to match palette effect
250
                        reset_palette_add();                                                    //restore palette for death message
251
                        //controlcen->MaxCapacity = Fuelcen_max_amount;
252
                        //gauge_message( "Control Center Reset" );
253
                        return DoPlayerDead();          //kill_player();
254
                }                                                                                                                                                              
255
        }
256
 
257
        return window_event_result::handled;
258
}
259
 
260
//      -----------------------------------------------------------------------------
261
//      Called when control center gets destroyed.
262
//      This code is common to whether control center is implicitly imbedded in a boss,
263
//      or is an object of its own.
264
//      if objp == NULL that means the boss was the control center and don't set Dead_controlcen_object_num
265
void do_controlcen_destroyed_stuff(const imobjidx_t objp)
266
{
267
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
268
        int i;
269
 
270
#if defined(DXX_BUILD_DESCENT_II)
271
        if ((Game_mode & GM_MULTI_ROBOTS) && LevelUniqueControlCenterState.Control_center_destroyed)
272
                return; // Don't allow resetting if control center and boss on same level
273
#endif
274
 
275
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
276
        auto &vmwallptr = Walls.vmptr;
277
        // Must toggle walls whether it is a boss or control center.
278
        for (i=0;i<ControlCenterTriggers.num_links;i++)
279
                wall_toggle(vmwallptr, vmsegptridx(ControlCenterTriggers.seg[i]), ControlCenterTriggers.side[i]);
280
 
281
        // And start the countdown stuff.
282
        LevelUniqueControlCenterState.Control_center_destroyed = 1;
283
 
284
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
285
        int Total_countdown_time;
286
#if defined(DXX_BUILD_DESCENT_II)
287
        // If a secret level, delete secret.sgc to indicate that we can't return to our secret level.
288
        if (Current_level_num < 0)
289
                PHYSFS_delete(SECRETC_FILENAME);
290
 
291
        const auto Base_control_center_explosion_time = LevelSharedControlCenterState.Base_control_center_explosion_time;
292
        if (Base_control_center_explosion_time != DEFAULT_CONTROL_CENTER_EXPLOSION_TIME)
293
                Total_countdown_time = Base_control_center_explosion_time + Base_control_center_explosion_time * (NDL-Difficulty_level-1)/2;
294
        else if (!EMULATING_D1)
295
                Total_countdown_time = D2_Alan_pavlish_reactor_times[Difficulty_level];
296
        else
297
#endif
298
                Total_countdown_time = D1_Alan_pavlish_reactor_times[Difficulty_level];
299
 
300
        LevelUniqueControlCenterState.Total_countdown_time = Total_countdown_time;
301
        LevelUniqueControlCenterState.Countdown_timer = i2f(Total_countdown_time);
302
 
303
        if (!LevelUniqueControlCenterState.Control_center_present || objp==object_none)
304
                return;
305
 
306
        LevelUniqueControlCenterState.Dead_controlcen_object_num = objp;
307
}
308
 
309
//      -----------------------------------------------------------------------------
310
//do whatever this thing does in a frame
311
void do_controlcen_frame(const vmobjptridx_t obj)
312
{
313
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
314
        int                     best_gun_num;
315
        auto &Objects = LevelUniqueObjectState.Objects;
316
        auto &vmobjptr = Objects.vmptr;
317
 
318
        //      If a boss level, then Control_center_present will be 0.
319
        if (!LevelUniqueControlCenterState.Control_center_present)
320
                return;
321
 
322
#ifndef NDEBUG
323
        if (cheats.robotfiringsuspended || (Game_suspended & SUSP_ROBOTS))
324
                return;
325
#else
326
        if (cheats.robotfiringsuspended)
327
                return;
328
#endif
329
 
330
        auto &plrobj = get_local_plrobj();
331
        if (!(LevelUniqueControlCenterState.Control_center_been_hit || player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen)))
332
        {
333
                if (!(d_tick_count % 8)) {              //      Do every so often...
334
                        // This is a hack.  Since the control center is not processed by
335
                        // ai_do_frame, it doesn't know to deal with cloaked dudes.  It
336
                        // seems to work in single-player mode because it is actually using
337
                        // the value of Believed_player_position that was set by the last
338
                        // person to go through ai_do_frame.  But since a no-robots game
339
                        // never goes through ai_do_frame, I'm making it so the control
340
                        // center can spot cloaked dudes.
341
 
342
                        if (Game_mode & GM_MULTI)
343
                                Believed_player_pos = plrobj.pos;
344
 
345
                        //      Hack for special control centers which are isolated and not reachable because the
346
                        //      real control center is inside the boss.
347
                        auto &children = vcsegptr(obj->segnum)->children;
348
                        if (std::none_of(children.begin(), children.end(), IS_CHILD))
349
                                return;
350
 
351
                        auto vec_to_player = vm_vec_sub(ConsoleObject->pos, obj->pos);
352
                        auto dist_to_player = vm_vec_normalize_quick(vec_to_player);
353
                        if (dist_to_player < F1_0*200) {
354
                                LevelUniqueControlCenterState.Control_center_player_been_seen = player_is_visible_from_object(obj, obj->pos, 0, vec_to_player);
355
                                LevelUniqueControlCenterState.Frametime_until_next_fire = 0;
356
                        }
357
                }                      
358
 
359
                return;
360
        }
361
 
362
#if defined(DXX_BUILD_DESCENT_II)
363
        //      Periodically, make the reactor fall asleep if player not visible.
364
        if (!EMULATING_D1 && (LevelUniqueControlCenterState.Control_center_been_hit || player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen)))
365
        {
366
                if (LevelUniqueControlCenterState.Last_time_cc_vis_check + F1_0 * 5 < GameTime64 || LevelUniqueControlCenterState.Last_time_cc_vis_check > GameTime64)
367
                {
368
                        LevelUniqueControlCenterState.Last_time_cc_vis_check = GameTime64;
369
                        fix                     dist_to_player;
370
                        auto vec_to_player = vm_vec_sub(ConsoleObject->pos, obj->pos);
371
                        dist_to_player = vm_vec_normalize_quick(vec_to_player);
372
                        if (dist_to_player < F1_0*120) {
373
                                LevelUniqueControlCenterState.Control_center_player_been_seen = player_is_visible_from_object(obj, obj->pos, 0, vec_to_player);
374
                                if (!player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen))
375
                                        LevelUniqueControlCenterState.Control_center_been_hit = 0;
376
                        }
377
                }
378
 
379
        }
380
#endif
381
 
382
        constexpr fix Relative_frametime_cease_fire = F1_0 * 2;
383
        auto &Frametime_since_player_died = LevelUniqueControlCenterState.Frametime_since_player_died;
384
        if (Player_dead_state != player_dead_state::no)
385
        {
386
                if (Frametime_since_player_died <= Relative_frametime_cease_fire)
387
                        Frametime_since_player_died += FrameTime;
388
        }
389
        else
390
                Frametime_since_player_died = 0;
391
 
392
        if (LevelUniqueControlCenterState.Frametime_until_next_fire < 0 && !(Frametime_since_player_died > Relative_frametime_cease_fire))
393
        {
394
                auto &player_info = plrobj.ctype.player_info;
395
                const auto &player_pos = (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) ? Believed_player_pos : ConsoleObject->pos;
396
                best_gun_num = calc_best_gun(
397
                        get_reactor_definition(get_reactor_id(obj)).n_guns,
398
                        obj,
399
                        player_pos
400
                );
401
 
402
                if (best_gun_num != -1) {
403
                        fix                     delta_fire_time;
404
 
405
                        auto vec_to_goal = vm_vec_sub(player_pos, obj->ctype.reactor_info.gun_pos[best_gun_num]);
406
                        auto dist_to_player = vm_vec_normalize_quick(vec_to_goal);
407
 
408
                        if (dist_to_player > F1_0*300)
409
                        {
410
                                LevelUniqueControlCenterState.Control_center_been_hit = 0;
411
                                LevelUniqueControlCenterState.Control_center_player_been_seen = player_visibility_state::no_line_of_sight;
412
                                return;
413
                        }
414
 
415
                        if (Game_mode & GM_MULTI)
416
                                multi_send_controlcen_fire(vec_to_goal, best_gun_num, obj);    
417
                        Laser_create_new_easy( vec_to_goal, obj->ctype.reactor_info.gun_pos[best_gun_num], obj, weapon_id_type::CONTROLCEN_WEAPON_NUM, 1);
418
 
419
                        int count = 0;
420
#if defined(DXX_BUILD_DESCENT_I)
421
                        const unsigned scale_divisor = 4;
422
                        if (d_rand() < 32767/4)
423
#elif defined(DXX_BUILD_DESCENT_II)
424
                        const unsigned scale_divisor = 6;
425
                        int                     rand_prob;
426
                        //      some of time, based on level, fire another thing, not directly at player, so it might hit him if he's constantly moving.
427
                        rand_prob = F1_0/(abs(Current_level_num)/4+2);
428
                        while ((d_rand() > rand_prob) && (count < 4))
429
#endif
430
                        {
431
                                vm_vec_scale_add2(vec_to_goal, make_random_vector(), F1_0/scale_divisor);
432
                                vm_vec_normalize_quick(vec_to_goal);
433
                                if (Game_mode & GM_MULTI)
434
                                        multi_send_controlcen_fire(vec_to_goal, best_gun_num, obj);
435
                                Laser_create_new_easy( vec_to_goal, obj->ctype.reactor_info.gun_pos[best_gun_num], obj, weapon_id_type::CONTROLCEN_WEAPON_NUM, count == 0);
436
                                count++;
437
                        }
438
 
439
                        const auto Difficulty_level = GameUniqueState.Difficulty_level;
440
                        delta_fire_time = (NDL - Difficulty_level) * F1_0/4;
441
#if defined(DXX_BUILD_DESCENT_II)
442
                        if (Difficulty_level == 0)
443
                                delta_fire_time += F1_0/2;
444
#endif
445
 
446
                        if (Game_mode & GM_MULTI) // slow down rate of fire in multi player
447
                                delta_fire_time *= 2;
448
 
449
                        LevelUniqueControlCenterState.Frametime_until_next_fire = delta_fire_time;
450
 
451
                }
452
        } else
453
                LevelUniqueControlCenterState.Frametime_until_next_fire -= FrameTime;
454
}
455
 
456
//      -----------------------------------------------------------------------------
457
//      This must be called at the start of each level.
458
//      If this level contains a boss and mode != multiplayer, don't do control center stuff.  (Ghost out control center object.)
459
//      If this level contains a boss and mode == multiplayer, do control center stuff.
460
void init_controlcen_for_level(void)
461
{
462
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
463
        imobjptr_t cntrlcen_objnum = nullptr, boss_objnum = nullptr;
464
 
465
        auto &Objects = LevelUniqueObjectState.Objects;
466
        auto &vmobjptridx = Objects.vmptridx;
467
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
468
        range_for (const auto &&objp, vmobjptridx)
469
        {
470
                if (objp->type == OBJ_CNTRLCEN)
471
                {
472
                        if (cntrlcen_objnum == nullptr)
473
                                cntrlcen_objnum = objp;
474
                }
475
                else if (objp->type == OBJ_ROBOT && (Robot_info[get_robot_id(objp)].boss_flag))
476
                {
477
                        if (boss_objnum == nullptr)
478
                                boss_objnum = objp;
479
                }
480
        }
481
 
482
        if (boss_objnum != nullptr && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_ROBOTS)))
483
        {
484
                if (cntrlcen_objnum != nullptr)
485
                {
486
                        auto &objp = *cntrlcen_objnum;
487
                        objp.type = OBJ_GHOST;
488
                        objp.control_type = CT_NONE;
489
                        objp.render_type = RT_NONE;
490
                        LevelUniqueControlCenterState.Control_center_present = 0;
491
                }
492
        }
493
        else if (cntrlcen_objnum != nullptr)
494
        {
495
                //      Compute all gun positions.
496
                object &objp = cntrlcen_objnum;
497
                calc_controlcen_gun_point(objp);
498
                LevelUniqueControlCenterState.Control_center_present = 1;
499
 
500
#if defined(DXX_BUILD_DESCENT_I)
501
                const unsigned secret_level_shield_multiplier = 100;
502
#elif defined(DXX_BUILD_DESCENT_II)
503
                const unsigned secret_level_shield_multiplier = 150;
504
                const auto Reactor_strength = LevelSharedControlCenterState.Reactor_strength;
505
                if (Reactor_strength != -1)
506
                        objp.shields = i2f(Reactor_strength);
507
                else
508
#endif
509
                {               //use old defaults
510
                        //      Boost control center strength at higher levels.
511
                        if (Current_level_num >= 0)
512
                                objp.shields = F1_0*200 + (F1_0*200/4) * Current_level_num;
513
                        else
514
                                objp.shields = F1_0*200 - Current_level_num*F1_0*secret_level_shield_multiplier;
515
                }
516
        }
517
 
518
        //      Say the control center has not yet been hit.
519
        LevelUniqueControlCenterState.Control_center_been_hit = 0;
520
        LevelUniqueControlCenterState.Control_center_player_been_seen = player_visibility_state::no_line_of_sight;
521
        LevelUniqueControlCenterState.Frametime_until_next_fire = 0;
522
        LevelUniqueControlCenterState.Dead_controlcen_object_num = object_none;
523
}
524
 
525
#if defined(DXX_BUILD_DESCENT_II)
526
void special_reactor_stuff()
527
{
528
        auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
529
        if (LevelUniqueControlCenterState.Control_center_destroyed) {
530
                const auto Base_control_center_explosion_time = LevelSharedControlCenterState.Base_control_center_explosion_time;
531
                LevelUniqueControlCenterState.Countdown_timer += i2f(Base_control_center_explosion_time + (NDL - 1 - GameUniqueState.Difficulty_level) * Base_control_center_explosion_time / (NDL - 1));
532
                LevelUniqueControlCenterState.Total_countdown_time = f2i(LevelUniqueControlCenterState.Countdown_timer) + 2;    //      Will prevent "Self destruct sequence activated" message from replaying.
533
        }
534
}
535
 
536
/*
537
 * reads n reactor structs from a PHYSFS_File
538
 */
539
void reactor_read_n(PHYSFS_File *fp, partial_range_t<reactor *> r)
540
{
541
        range_for (auto &i, r)
542
        {
543
                i.model_num = PHYSFSX_readInt(fp);
544
                i.n_guns = PHYSFSX_readInt(fp);
545
                range_for (auto &j, i.gun_points)
546
                        PHYSFSX_readVector(fp, j);
547
                range_for (auto &j, i.gun_dirs)
548
                        PHYSFSX_readVector(fp, j);
549
        }
550
}
551
#endif
552
}
553
 
554
DEFINE_SERIAL_UDT_TO_MESSAGE(control_center_triggers, cct, (cct.num_links, cct.seg, cct.side));
555
ASSERT_SERIAL_UDT_MESSAGE_SIZE(control_center_triggers, 42);
556
 
557
/*
558
 * reads n control_center_triggers structs from a PHYSFS_File and swaps if specified
559
 */
560
void control_center_triggers_read(control_center_triggers *cct, PHYSFS_File *fp)
561
{
562
        PHYSFSX_serialize_read(fp, *cct);
563
}
564
 
565
void control_center_triggers_write(const control_center_triggers *cct, PHYSFS_File *fp)
566
{
567
        PHYSFSX_serialize_write(fp, *cct);
568
}