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 | } |