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