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 | * Functions for refueling centers. |
||
| 23 | * |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include <stdio.h> |
||
| 27 | #include <stdlib.h> |
||
| 28 | #include <math.h> |
||
| 29 | #include <string.h> |
||
| 30 | |||
| 31 | #include "fuelcen.h" |
||
| 32 | #include "gameseg.h" |
||
| 33 | #include "game.h" // For FrameTime |
||
| 34 | #include "dxxerror.h" |
||
| 35 | #include "gauges.h" |
||
| 36 | #include "vclip.h" |
||
| 37 | #include "fireball.h" |
||
| 38 | #include "robot.h" |
||
| 39 | #include "powerup.h" |
||
| 40 | |||
| 41 | #include "sounds.h" |
||
| 42 | #include "morph.h" |
||
| 43 | #include "3d.h" |
||
| 44 | #include "physfs-serial.h" |
||
| 45 | #include "bm.h" |
||
| 46 | #include "polyobj.h" |
||
| 47 | #include "ai.h" |
||
| 48 | #include "gamemine.h" |
||
| 49 | #include "gamesave.h" |
||
| 50 | #include "player.h" |
||
| 51 | #include "collide.h" |
||
| 52 | #include "laser.h" |
||
| 53 | #include "multi.h" |
||
| 54 | #include "multibot.h" |
||
| 55 | #include "escort.h" |
||
| 56 | #include "compiler-poison.h" |
||
| 57 | #include "d_enumerate.h" |
||
| 58 | #include "compiler-range_for.h" |
||
| 59 | #include "partial_range.h" |
||
| 60 | #include "segiter.h" |
||
| 61 | |||
| 62 | // The max number of fuel stations per mine. |
||
| 63 | |||
| 64 | namespace dcx { |
||
| 65 | constexpr fix Fuelcen_give_amount = i2f(25); |
||
| 66 | constexpr fix Fuelcen_max_amount = i2f(100); |
||
| 67 | |||
| 68 | // Every time a robot is created in the morphing code, it decreases capacity of the morpher |
||
| 69 | // by this amount... when capacity gets to 0, no more morphers... |
||
| 70 | constexpr fix EnergyToCreateOneRobot = i2f(1); |
||
| 71 | |||
| 72 | static int Num_extry_robots = 15; |
||
| 73 | } |
||
| 74 | namespace dsx { |
||
| 75 | #if DXX_USE_EDITOR |
||
| 76 | const char Special_names[MAX_CENTER_TYPES][11] = { |
||
| 77 | "NOTHING ", |
||
| 78 | "FUELCEN ", |
||
| 79 | "REPAIRCEN ", |
||
| 80 | "CONTROLCEN", |
||
| 81 | "ROBOTMAKER", |
||
| 82 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 83 | "GOAL_RED", |
||
| 84 | "GOAL_BLUE", |
||
| 85 | #endif |
||
| 86 | }; |
||
| 87 | #endif |
||
| 88 | |||
| 89 | //------------------------------------------------------------ |
||
| 90 | // Resets all fuel center info |
||
| 91 | void fuelcen_reset() |
||
| 92 | { |
||
| 93 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 94 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 95 | DXX_MAKE_MEM_UNDEFINED(Station.begin(), Station.end()); |
||
| 96 | DXX_MAKE_MEM_UNDEFINED(RobotCenters.begin(), RobotCenters.end()); |
||
| 97 | LevelSharedRobotcenterState.Num_robot_centers = 0; |
||
| 98 | LevelUniqueFuelcenterState.Num_fuelcenters = 0; |
||
| 99 | range_for (shared_segment &i, Segments) |
||
| 100 | i.special = SEGMENT_IS_NOTHING; |
||
| 101 | } |
||
| 102 | |||
| 103 | #ifndef NDEBUG //this is sometimes called by people from the debugger |
||
| 104 | static void reset_all_robot_centers() __attribute_used; |
||
| 105 | static void reset_all_robot_centers() |
||
| 106 | { |
||
| 107 | // Remove all materialization centers |
||
| 108 | range_for (shared_segment &i, partial_range(Segments, LevelSharedSegmentState.Num_segments)) |
||
| 109 | if (i.special == SEGMENT_IS_ROBOTMAKER) |
||
| 110 | { |
||
| 111 | i.special = SEGMENT_IS_NOTHING; |
||
| 112 | i.matcen_num = -1; |
||
| 113 | } |
||
| 114 | } |
||
| 115 | #endif |
||
| 116 | |||
| 117 | //------------------------------------------------------------ |
||
| 118 | // Turns a segment into a fully charged up fuel center... |
||
| 119 | void fuelcen_create(const vmsegptridx_t segp) |
||
| 120 | { |
||
| 121 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 122 | int station_type; |
||
| 123 | |||
| 124 | station_type = segp->special; |
||
| 125 | |||
| 126 | switch( station_type ) { |
||
| 127 | case SEGMENT_IS_NOTHING: |
||
| 128 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 129 | case SEGMENT_IS_GOAL_BLUE: |
||
| 130 | case SEGMENT_IS_GOAL_RED: |
||
| 131 | #endif |
||
| 132 | return; |
||
| 133 | case SEGMENT_IS_FUELCEN: |
||
| 134 | case SEGMENT_IS_REPAIRCEN: |
||
| 135 | case SEGMENT_IS_CONTROLCEN: |
||
| 136 | case SEGMENT_IS_ROBOTMAKER: |
||
| 137 | break; |
||
| 138 | default: |
||
| 139 | Error( "Invalid station type %d in fuelcen.c\n", station_type ); |
||
| 140 | } |
||
| 141 | |||
| 142 | const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++; |
||
| 143 | segp->station_idx = next_fuelcenter_idx; |
||
| 144 | auto &station = Station.at(next_fuelcenter_idx); |
||
| 145 | station.Type = station_type; |
||
| 146 | station.Capacity = Fuelcen_max_amount; |
||
| 147 | station.segnum = segp; |
||
| 148 | station.Timer = -1; |
||
| 149 | station.Flag = 0; |
||
| 150 | } |
||
| 151 | |||
| 152 | //------------------------------------------------------------ |
||
| 153 | // Adds a matcen that already is a special type into the Station array. |
||
| 154 | // This function is separate from other fuelcens because we don't want values reset. |
||
| 155 | static void matcen_create(const vmsegptridx_t segp) |
||
| 156 | { |
||
| 157 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 158 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 159 | int station_type = segp->special; |
||
| 160 | |||
| 161 | Assert(station_type == SEGMENT_IS_ROBOTMAKER); |
||
| 162 | |||
| 163 | const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++; |
||
| 164 | segp->station_idx = next_fuelcenter_idx; |
||
| 165 | auto &station = Station.at(next_fuelcenter_idx); |
||
| 166 | |||
| 167 | station.Type = station_type; |
||
| 168 | station.Capacity = i2f(GameUniqueState.Difficulty_level + 3); |
||
| 169 | station.segnum = segp; |
||
| 170 | station.Timer = -1; |
||
| 171 | station.Flag = 0; |
||
| 172 | |||
| 173 | const auto next_robot_center_idx = LevelSharedRobotcenterState.Num_robot_centers++; |
||
| 174 | segp->matcen_num = next_robot_center_idx; |
||
| 175 | auto &robotcenter = RobotCenters[next_robot_center_idx]; |
||
| 176 | robotcenter.fuelcen_num = next_fuelcenter_idx; |
||
| 177 | robotcenter.segnum = segp; |
||
| 178 | robotcenter.fuelcen_num = next_fuelcenter_idx; |
||
| 179 | } |
||
| 180 | |||
| 181 | //------------------------------------------------------------ |
||
| 182 | // Adds a segment that already is a special type into the Station array. |
||
| 183 | void fuelcen_activate(const vmsegptridx_t segp) |
||
| 184 | { |
||
| 185 | if (segp->special == SEGMENT_IS_ROBOTMAKER) |
||
| 186 | matcen_create( segp); |
||
| 187 | else |
||
| 188 | fuelcen_create( segp); |
||
| 189 | } |
||
| 190 | |||
| 191 | // The lower this number is, the more quickly the center can be re-triggered. |
||
| 192 | // If it's too low, it can mean all the robots won't be put out, but for about 5 |
||
| 193 | // robots, that's not real likely. |
||
| 194 | #define MATCEN_LIFE (i2f(30-2*Difficulty_level)) |
||
| 195 | |||
| 196 | //------------------------------------------------------------ |
||
| 197 | // Trigger (enable) the materialization center in segment segnum |
||
| 198 | void trigger_matcen(const vmsegptridx_t segp) |
||
| 199 | { |
||
| 200 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 201 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 202 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 203 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 204 | FuelCenter *robotcen; |
||
| 205 | |||
| 206 | Assert(segp->special == SEGMENT_IS_ROBOTMAKER); |
||
| 207 | assert(segp->matcen_num < LevelUniqueFuelcenterState.Num_fuelcenters); |
||
| 208 | Assert((segp->matcen_num >= 0) && (segp->matcen_num <= Highest_segment_index)); |
||
| 209 | |||
| 210 | robotcen = &Station[RobotCenters[segp->matcen_num].fuelcen_num]; |
||
| 211 | |||
| 212 | if (robotcen->Enabled == 1) |
||
| 213 | return; |
||
| 214 | |||
| 215 | if (!robotcen->Lives) |
||
| 216 | return; |
||
| 217 | |||
| 218 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 219 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 220 | // MK: 11/18/95, At insane, matcens work forever! |
||
| 221 | if (Difficulty_level+1 < NDL) |
||
| 222 | #endif |
||
| 223 | robotcen->Lives--; |
||
| 224 | |||
| 225 | robotcen->Timer = F1_0*1000; // Make sure the first robot gets emitted right away. |
||
| 226 | robotcen->Enabled = 1; // Say this center is enabled, it can create robots. |
||
| 227 | robotcen->Capacity = i2f(Difficulty_level + 3); |
||
| 228 | robotcen->Disable_time = MATCEN_LIFE; |
||
| 229 | |||
| 230 | // Create a bright object in the segment. |
||
| 231 | auto &vcvertptr = Vertices.vcptr; |
||
| 232 | auto &&pos = compute_segment_center(vcvertptr, segp); |
||
| 233 | const auto &&delta = vm_vec_sub(vcvertptr(segp->verts[0]), pos); |
||
| 234 | vm_vec_scale_add2(pos, delta, F1_0/2); |
||
| 235 | auto objnum = obj_create( OBJ_LIGHT, 0, segp, pos, NULL, 0, CT_LIGHT, MT_NONE, RT_NONE ); |
||
| 236 | if (objnum != object_none) { |
||
| 237 | objnum->lifeleft = MATCEN_LIFE; |
||
| 238 | objnum->ctype.light_info.intensity = i2f(8); // Light cast by a fuelcen. |
||
| 239 | } else { |
||
| 240 | Int3(); |
||
| 241 | } |
||
| 242 | } |
||
| 243 | |||
| 244 | #if DXX_USE_EDITOR |
||
| 245 | //------------------------------------------------------------ |
||
| 246 | // Takes away a segment's fuel center properties. |
||
| 247 | // Deletes the segment point entry in the FuelCenter list. |
||
| 248 | void fuelcen_delete(const vmsegptr_t segp) |
||
| 249 | { |
||
| 250 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 251 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 252 | auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters; |
||
| 253 | Restart: ; |
||
| 254 | segp->special = 0; |
||
| 255 | |||
| 256 | for (uint_fast32_t i = 0; i < Num_fuelcenters; i++ ) { |
||
| 257 | FuelCenter &fi = Station[i]; |
||
| 258 | if (vmsegptr(fi.segnum) == segp) |
||
| 259 | { |
||
| 260 | |||
| 261 | auto &Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers; |
||
| 262 | // If Robot maker is deleted, fix Segments and RobotCenters. |
||
| 263 | if (fi.Type == SEGMENT_IS_ROBOTMAKER) { |
||
| 264 | if (!Num_robot_centers) |
||
| 265 | { |
||
| 266 | con_printf(CON_URGENT, "%s:%u: error: Num_robot_centers=0 while deleting robot maker", __FILE__, __LINE__); |
||
| 267 | return; |
||
| 268 | } |
||
| 269 | const auto &&range = partial_range(RobotCenters, static_cast<unsigned>(segp->matcen_num), Num_robot_centers--); |
||
| 270 | |||
| 271 | std::move(std::next(range.begin()), range.end(), range.begin()); |
||
| 272 | range_for (auto &fj, partial_const_range(Station, Num_fuelcenters)) |
||
| 273 | { |
||
| 274 | if ( fj.Type == SEGMENT_IS_ROBOTMAKER ) |
||
| 275 | if ( Segments[fj.segnum].matcen_num > segp->matcen_num ) |
||
| 276 | Segments[fj.segnum].matcen_num--; |
||
| 277 | } |
||
| 278 | } |
||
| 279 | |||
| 280 | //fix RobotCenters so they point to correct fuelcenter |
||
| 281 | range_for (auto &j, partial_range(RobotCenters, Num_robot_centers)) |
||
| 282 | if (j.fuelcen_num > i) //this robotcenter's fuelcen is changing |
||
| 283 | j.fuelcen_num--; |
||
| 284 | Assert(Num_fuelcenters > 0); |
||
| 285 | Num_fuelcenters--; |
||
| 286 | for (uint_fast32_t j = i; j < Num_fuelcenters; j++ ) { |
||
| 287 | Station[j] = Station[j+1]; |
||
| 288 | Segments[Station[j].segnum].station_idx = j; |
||
| 289 | } |
||
| 290 | goto Restart; |
||
| 291 | } |
||
| 292 | } |
||
| 293 | LevelUniqueFuelcenterState.Num_fuelcenters = Num_fuelcenters; |
||
| 294 | } |
||
| 295 | #endif |
||
| 296 | |||
| 297 | #define ROBOT_GEN_TIME (i2f(5)) |
||
| 298 | |||
| 299 | imobjptridx_t create_morph_robot(const vmsegptridx_t segp, const vms_vector &object_pos, const unsigned object_id) |
||
| 300 | { |
||
| 301 | ai_behavior default_behavior; |
||
| 302 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 303 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 304 | default_behavior = ai_behavior::AIB_NORMAL; |
||
| 305 | if (object_id == 10) // This is a toaster guy! |
||
| 306 | default_behavior = ai_behavior::AIB_RUN_FROM; |
||
| 307 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 308 | default_behavior = Robot_info[object_id].behavior; |
||
| 309 | #endif |
||
| 310 | |||
| 311 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
| 312 | auto obj = robot_create(object_id, segp, object_pos, |
||
| 313 | &vmd_identity_matrix, Polygon_models[Robot_info[object_id].model_num].rad, |
||
| 314 | default_behavior); |
||
| 315 | |||
| 316 | if (obj == object_none) |
||
| 317 | { |
||
| 318 | Int3(); |
||
| 319 | return object_none; |
||
| 320 | } |
||
| 321 | |||
| 322 | ++LevelUniqueObjectState.accumulated_robots; |
||
| 323 | ++GameUniqueState.accumulated_robots; |
||
| 324 | //Set polygon-object-specific data |
||
| 325 | |||
| 326 | obj->rtype.pobj_info.model_num = Robot_info[get_robot_id(obj)].model_num; |
||
| 327 | obj->rtype.pobj_info.subobj_flags = 0; |
||
| 328 | |||
| 329 | //set Physics info |
||
| 330 | |||
| 331 | obj->mtype.phys_info.mass = Robot_info[get_robot_id(obj)].mass; |
||
| 332 | obj->mtype.phys_info.drag = Robot_info[get_robot_id(obj)].drag; |
||
| 333 | |||
| 334 | obj->mtype.phys_info.flags |= (PF_LEVELLING); |
||
| 335 | |||
| 336 | obj->shields = Robot_info[get_robot_id(obj)].strength; |
||
| 337 | |||
| 338 | create_n_segment_path(obj, 6, segment_none); // Create a 6 segment path from creation point. |
||
| 339 | |||
| 340 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 341 | if (default_behavior == ai_behavior::AIB_RUN_FROM) |
||
| 342 | obj->ctype.ai_info.ail.mode = ai_mode::AIM_RUN_FROM_OBJECT; |
||
| 343 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 344 | obj->ctype.ai_info.ail.mode = ai_behavior_to_mode(default_behavior); |
||
| 345 | #endif |
||
| 346 | |||
| 347 | return obj; |
||
| 348 | } |
||
| 349 | |||
| 350 | } |
||
| 351 | |||
| 352 | // ---------------------------------------------------------------------------------------------------------- |
||
| 353 | static void robotmaker_proc(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, FuelCenter *const robotcen, const unsigned numrobotcen) |
||
| 354 | { |
||
| 355 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 356 | auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState; |
||
| 357 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 358 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 359 | auto &vcobjptr = Objects.vcptr; |
||
| 360 | auto &vmobjptridx = Objects.vmptridx; |
||
| 361 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 362 | int matcen_num; |
||
| 363 | fix top_time; |
||
| 364 | |||
| 365 | if (robotcen->Enabled == 0) |
||
| 366 | return; |
||
| 367 | |||
| 368 | if (robotcen->Disable_time > 0) { |
||
| 369 | robotcen->Disable_time -= FrameTime; |
||
| 370 | if (robotcen->Disable_time <= 0) { |
||
| 371 | robotcen->Enabled = 0; |
||
| 372 | } |
||
| 373 | } |
||
| 374 | |||
| 375 | // No robot making in multiplayer mode. |
||
| 376 | if ((Game_mode & GM_MULTI) && (!(Game_mode & GM_MULTI_ROBOTS) || !multi_i_am_master())) |
||
| 377 | return; |
||
| 378 | |||
| 379 | // Wait until transmorgafier has capacity to make a robot... |
||
| 380 | if ( robotcen->Capacity <= 0 ) { |
||
| 381 | return; |
||
| 382 | } |
||
| 383 | |||
| 384 | const auto &&segp = vmsegptr(robotcen->segnum); |
||
| 385 | matcen_num = segp->matcen_num; |
||
| 386 | |||
| 387 | if ( matcen_num == -1 ) { |
||
| 388 | return; |
||
| 389 | } |
||
| 390 | |||
| 391 | matcen_info *mi = &RobotCenters[matcen_num]; |
||
| 392 | for (unsigned i = 0;; ++i) |
||
| 393 | { |
||
| 394 | if (i >= mi->robot_flags.size()) |
||
| 395 | return; |
||
| 396 | if (mi->robot_flags[i]) |
||
| 397 | break; |
||
| 398 | } |
||
| 399 | |||
| 400 | // Wait until we have a free slot for this puppy... |
||
| 401 | // <<<<<<<<<<<<<<<< Num robots in mine >>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<< Max robots in mine >>>>>>>>>>>>>>> |
||
| 402 | { |
||
| 403 | auto &plr = get_local_player(); |
||
| 404 | if (LevelUniqueObjectState.accumulated_robots - plr.num_kills_level >= Gamesave_num_org_robots + Num_extry_robots) |
||
| 405 | { |
||
| 406 | return; |
||
| 407 | } |
||
| 408 | } |
||
| 409 | |||
| 410 | robotcen->Timer += FrameTime; |
||
| 411 | |||
| 412 | auto &vcvertptr = Vertices.vcptr; |
||
| 413 | switch( robotcen->Flag ) { |
||
| 414 | case 0: // Wait until next robot can generate |
||
| 415 | if (Game_mode & GM_MULTI) |
||
| 416 | { |
||
| 417 | top_time = ROBOT_GEN_TIME; |
||
| 418 | } |
||
| 419 | else |
||
| 420 | { |
||
| 421 | const auto center = compute_segment_center(vcvertptr, segp); |
||
| 422 | const auto dist_to_player = vm_vec_dist_quick( ConsoleObject->pos, center ); |
||
| 423 | top_time = dist_to_player/64 + d_rand() * 2 + F1_0*2; |
||
| 424 | if ( top_time > ROBOT_GEN_TIME ) |
||
| 425 | top_time = ROBOT_GEN_TIME + d_rand(); |
||
| 426 | if ( top_time < F1_0*2 ) |
||
| 427 | top_time = F1_0*3/2 + d_rand()*2; |
||
| 428 | } |
||
| 429 | |||
| 430 | if (robotcen->Timer > top_time ) { |
||
| 431 | int count=0; |
||
| 432 | int my_station_num = numrobotcen; |
||
| 433 | |||
| 434 | // Make sure this robotmaker hasn't put out its max without having any of them killed. |
||
| 435 | range_for (const auto &&objp, vcobjptr) |
||
| 436 | { |
||
| 437 | auto &obj = *objp; |
||
| 438 | if (obj.type == OBJ_ROBOT) |
||
| 439 | if ((obj.matcen_creator ^ 0x80) == my_station_num) |
||
| 440 | count++; |
||
| 441 | } |
||
| 442 | if (count > GameUniqueState.Difficulty_level + 3) { |
||
| 443 | robotcen->Timer /= 2; |
||
| 444 | return; |
||
| 445 | } |
||
| 446 | |||
| 447 | // Whack on any robot or player in the matcen segment. |
||
| 448 | count=0; |
||
| 449 | auto segnum = robotcen->segnum; |
||
| 450 | const auto &&csegp = vmsegptr(segnum); |
||
| 451 | range_for (const auto objp, objects_in(csegp, vmobjptridx, vmsegptr)) |
||
| 452 | { |
||
| 453 | count++; |
||
| 454 | if ( count > MAX_OBJECTS ) { |
||
| 455 | Int3(); |
||
| 456 | return; |
||
| 457 | } |
||
| 458 | if (objp->type==OBJ_ROBOT) { |
||
| 459 | collide_robot_and_materialization_center(objp); |
||
| 460 | robotcen->Timer = top_time/2; |
||
| 461 | return; |
||
| 462 | } else if (objp->type==OBJ_PLAYER ) { |
||
| 463 | collide_player_and_materialization_center(objp); |
||
| 464 | robotcen->Timer = top_time/2; |
||
| 465 | return; |
||
| 466 | } |
||
| 467 | } |
||
| 468 | |||
| 469 | const auto &&cur_object_loc = compute_segment_center(vcvertptr, csegp); |
||
| 470 | const auto &&robotcen_segp = vmsegptridx(robotcen->segnum); |
||
| 471 | // HACK!!! The 10 under here should be something equal to the 1/2 the size of the segment. |
||
| 472 | auto obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT); |
||
| 473 | |||
| 474 | if (obj != object_none) |
||
| 475 | extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp); |
||
| 476 | |||
| 477 | if ( Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1 ) { |
||
| 478 | digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0); |
||
| 479 | } |
||
| 480 | robotcen->Flag = 1; |
||
| 481 | robotcen->Timer = 0; |
||
| 482 | |||
| 483 | } |
||
| 484 | break; |
||
| 485 | case 1: // Wait until 1/2 second after VCLIP started. |
||
| 486 | if (robotcen->Timer > (Vclip[VCLIP_MORPHING_ROBOT].play_time/2) ) { |
||
| 487 | |||
| 488 | robotcen->Capacity -= EnergyToCreateOneRobot; |
||
| 489 | robotcen->Flag = 0; |
||
| 490 | |||
| 491 | robotcen->Timer = 0; |
||
| 492 | const auto &&cur_object_loc = compute_segment_center(vcvertptr, vcsegptr(robotcen->segnum)); |
||
| 493 | |||
| 494 | // If this is the first materialization, set to valid robot. |
||
| 495 | { |
||
| 496 | int type; |
||
| 497 | ubyte legal_types[sizeof(mi->robot_flags) * 8]; // the width of robot_flags[]. |
||
| 498 | int num_types; |
||
| 499 | |||
| 500 | num_types = 0; |
||
| 501 | for (unsigned i = 0;; ++i) |
||
| 502 | { |
||
| 503 | if (i >= mi->robot_flags.size()) |
||
| 504 | break; |
||
| 505 | uint32_t flags = mi->robot_flags[i]; |
||
| 506 | for (unsigned j = 0; flags && j < 8 * sizeof(flags); ++j) |
||
| 507 | { |
||
| 508 | if (flags & 1) |
||
| 509 | legal_types[num_types++] = (i * 32) + j; |
||
| 510 | flags >>= 1; |
||
| 511 | } |
||
| 512 | } |
||
| 513 | |||
| 514 | if (num_types == 1) |
||
| 515 | type = legal_types[0]; |
||
| 516 | else |
||
| 517 | type = legal_types[(d_rand() * num_types) / 32768]; |
||
| 518 | |||
| 519 | const auto &&obj = create_morph_robot(vmsegptridx(robotcen->segnum), cur_object_loc, type ); |
||
| 520 | if (obj != object_none) { |
||
| 521 | if (Game_mode & GM_MULTI) |
||
| 522 | multi_send_create_robot(numrobotcen, obj, type); |
||
| 523 | obj->matcen_creator = (numrobotcen) | 0x80; |
||
| 524 | |||
| 525 | // Make object faces player... |
||
| 526 | const auto direction = vm_vec_sub(ConsoleObject->pos,obj->pos ); |
||
| 527 | vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr); |
||
| 528 | |||
| 529 | morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj); |
||
| 530 | //robotcen->last_created_obj = obj; |
||
| 531 | //robotcen->last_created_sig = robotcen->last_created_obj->signature; |
||
| 532 | } |
||
| 533 | } |
||
| 534 | |||
| 535 | } |
||
| 536 | break; |
||
| 537 | default: |
||
| 538 | robotcen->Flag = 0; |
||
| 539 | robotcen->Timer = 0; |
||
| 540 | } |
||
| 541 | } |
||
| 542 | |||
| 543 | //------------------------------------------------------------- |
||
| 544 | // Called once per frame, replenishes fuel supply. |
||
| 545 | void fuelcen_update_all() |
||
| 546 | { |
||
| 547 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 548 | range_for (auto &&e, enumerate(partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters))) |
||
| 549 | { |
||
| 550 | auto &i = e.value; |
||
| 551 | if (i.Type == SEGMENT_IS_ROBOTMAKER) |
||
| 552 | { |
||
| 553 | if (! (Game_suspended & SUSP_ROBOTS)) |
||
| 554 | robotmaker_proc(Vclip, vmsegptridx, &i, e.idx); |
||
| 555 | } |
||
| 556 | } |
||
| 557 | } |
||
| 558 | |||
| 559 | namespace dsx { |
||
| 560 | |||
| 561 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 562 | constexpr std::integral_constant<unsigned, F1_0 / 3> FUELCEN_SOUND_DELAY{}; |
||
| 563 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 564 | //play every half second |
||
| 565 | constexpr std::integral_constant<unsigned, F1_0 / 4> FUELCEN_SOUND_DELAY{}; |
||
| 566 | #endif |
||
| 567 | |||
| 568 | //------------------------------------------------------------- |
||
| 569 | fix fuelcen_give_fuel(const shared_segment &segp, fix MaxAmountCanTake) |
||
| 570 | { |
||
| 571 | static fix64 last_play_time = 0; |
||
| 572 | |||
| 573 | if (segp.special == SEGMENT_IS_FUELCEN) |
||
| 574 | { |
||
| 575 | fix amount; |
||
| 576 | |||
| 577 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 578 | detect_escort_goal_fuelcen_accomplished(); |
||
| 579 | #endif |
||
| 580 | |||
| 581 | if (MaxAmountCanTake <= 0 ) { |
||
| 582 | return 0; |
||
| 583 | } |
||
| 584 | |||
| 585 | amount = fixmul(FrameTime,Fuelcen_give_amount); |
||
| 586 | |||
| 587 | if (amount > MaxAmountCanTake ) |
||
| 588 | amount = MaxAmountCanTake; |
||
| 589 | |||
| 590 | if (last_play_time + FUELCEN_SOUND_DELAY < GameTime64 || last_play_time > GameTime64) |
||
| 591 | { |
||
| 592 | last_play_time = GameTime64; |
||
| 593 | multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2); |
||
| 594 | } |
||
| 595 | return amount; |
||
| 596 | } else { |
||
| 597 | return 0; |
||
| 598 | } |
||
| 599 | } |
||
| 600 | |||
| 601 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 602 | //------------------------------------------------------------- |
||
| 603 | // DM/050904 |
||
| 604 | // Repair centers |
||
| 605 | // use same values as fuel centers |
||
| 606 | fix repaircen_give_shields(const shared_segment &segp, const fix MaxAmountCanTake) |
||
| 607 | { |
||
| 608 | static fix last_play_time=0; |
||
| 609 | |||
| 610 | if (segp.special == SEGMENT_IS_REPAIRCEN) |
||
| 611 | { |
||
| 612 | fix amount; |
||
| 613 | if (MaxAmountCanTake <= 0 ) { |
||
| 614 | return 0; |
||
| 615 | } |
||
| 616 | amount = fixmul(FrameTime,Fuelcen_give_amount); |
||
| 617 | if (amount > MaxAmountCanTake ) |
||
| 618 | amount = MaxAmountCanTake; |
||
| 619 | if (last_play_time > GameTime64) |
||
| 620 | last_play_time = 0; |
||
| 621 | if (GameTime64 > last_play_time+FUELCEN_SOUND_DELAY) { |
||
| 622 | multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2); |
||
| 623 | last_play_time = GameTime64; |
||
| 624 | } |
||
| 625 | return amount; |
||
| 626 | } else { |
||
| 627 | return 0; |
||
| 628 | } |
||
| 629 | } |
||
| 630 | #endif |
||
| 631 | |||
| 632 | // -------------------------------------------------------------------------------------------- |
||
| 633 | void disable_matcens(void) |
||
| 634 | { |
||
| 635 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 636 | range_for (auto &s, partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters)) |
||
| 637 | if (s.Type == SEGMENT_IS_ROBOTMAKER) |
||
| 638 | { |
||
| 639 | s.Enabled = 0; |
||
| 640 | s.Disable_time = 0; |
||
| 641 | } |
||
| 642 | } |
||
| 643 | |||
| 644 | // -------------------------------------------------------------------------------------------- |
||
| 645 | // Initialize all materialization centers. |
||
| 646 | // Give them all the right number of lives. |
||
| 647 | void init_all_matcens(void) |
||
| 648 | { |
||
| 649 | auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters; |
||
| 650 | auto &Station = LevelUniqueFuelcenterState.Station; |
||
| 651 | const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters; |
||
| 652 | const auto &&robot_range = partial_const_range(RobotCenters, LevelSharedRobotcenterState.Num_robot_centers); |
||
| 653 | for (uint_fast32_t i = 0; i < Num_fuelcenters; i++) |
||
| 654 | if (Station[i].Type == SEGMENT_IS_ROBOTMAKER) { |
||
| 655 | Station[i].Lives = 3; |
||
| 656 | Station[i].Enabled = 0; |
||
| 657 | Station[i].Disable_time = 0; |
||
| 658 | // Make sure this fuelcen is pointed at by a matcen. |
||
| 659 | if (std::find_if(robot_range.begin(), robot_range.end(), [i](const matcen_info &mi) { |
||
| 660 | return mi.fuelcen_num == i; |
||
| 661 | }) == robot_range.end()) |
||
| 662 | { |
||
| 663 | Station[i].Lives = 0; |
||
| 664 | LevelError("Station %" PRIuFAST32 " has type robotmaker, but no robotmaker uses it; ignoring.", i); |
||
| 665 | } |
||
| 666 | } |
||
| 667 | |||
| 668 | #ifndef NDEBUG |
||
| 669 | // Make sure all matcens point at a fuelcen |
||
| 670 | range_for (auto &i, robot_range) |
||
| 671 | { |
||
| 672 | auto fuelcen_num = i.fuelcen_num; |
||
| 673 | Assert(fuelcen_num < Num_fuelcenters); |
||
| 674 | Assert(Station[fuelcen_num].Type == SEGMENT_IS_ROBOTMAKER); |
||
| 675 | } |
||
| 676 | #endif |
||
| 677 | |||
| 678 | } |
||
| 679 | |||
| 680 | } |
||
| 681 | |||
| 682 | struct d1mi_v25 |
||
| 683 | { |
||
| 684 | matcen_info *m; |
||
| 685 | d1mi_v25(matcen_info &mi) : m(&mi) {} |
||
| 686 | }; |
||
| 687 | |||
| 688 | struct d1cmi_v25 |
||
| 689 | { |
||
| 690 | const matcen_info *m; |
||
| 691 | d1cmi_v25(const matcen_info &mi) : m(&mi) {} |
||
| 692 | }; |
||
| 693 | |||
| 694 | #define D1_MATCEN_V25_MEMBERLIST (p.m->robot_flags[0], serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num) |
||
| 695 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v25, p, D1_MATCEN_V25_MEMBERLIST); |
||
| 696 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v25, p, D1_MATCEN_V25_MEMBERLIST); |
||
| 697 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v25, 16); |
||
| 698 | |||
| 699 | namespace dsx { |
||
| 700 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 701 | struct d1mi_v26 |
||
| 702 | { |
||
| 703 | matcen_info *m; |
||
| 704 | d1mi_v26(matcen_info &mi) : m(&mi) {} |
||
| 705 | }; |
||
| 706 | |||
| 707 | struct d1cmi_v26 |
||
| 708 | { |
||
| 709 | const matcen_info *m; |
||
| 710 | d1cmi_v26(const matcen_info &mi) : m(&mi) {} |
||
| 711 | }; |
||
| 712 | |||
| 713 | #define D1_MATCEN_V26_MEMBERLIST (p.m->robot_flags[0], serial::pad<sizeof(uint32_t)>(), serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num) |
||
| 714 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v26, p, D1_MATCEN_V26_MEMBERLIST); |
||
| 715 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v26, p, D1_MATCEN_V26_MEMBERLIST); |
||
| 716 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v26, 20); |
||
| 717 | |||
| 718 | void matcen_info_read(PHYSFS_File *fp, matcen_info &mi, int version) |
||
| 719 | { |
||
| 720 | if (version > 25) |
||
| 721 | PHYSFSX_serialize_read<const d1mi_v26>(fp, mi); |
||
| 722 | else |
||
| 723 | PHYSFSX_serialize_read<const d1mi_v25>(fp, mi); |
||
| 724 | } |
||
| 725 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 726 | void fuelcen_check_for_goal(object &plrobj, const shared_segment &segp) |
||
| 727 | { |
||
| 728 | Assert (game_mode_capture_flag()); |
||
| 729 | |||
| 730 | unsigned check_team; |
||
| 731 | powerup_type_t powerup_to_drop; |
||
| 732 | switch(segp.special) |
||
| 733 | { |
||
| 734 | case SEGMENT_IS_GOAL_BLUE: |
||
| 735 | check_team = TEAM_BLUE; |
||
| 736 | powerup_to_drop = POW_FLAG_RED; |
||
| 737 | break; |
||
| 738 | case SEGMENT_IS_GOAL_RED: |
||
| 739 | check_team = TEAM_RED; |
||
| 740 | powerup_to_drop = POW_FLAG_BLUE; |
||
| 741 | break; |
||
| 742 | default: |
||
| 743 | return; |
||
| 744 | } |
||
| 745 | if (get_team(Player_num) != check_team) |
||
| 746 | return; |
||
| 747 | auto &player_info = plrobj.ctype.player_info; |
||
| 748 | if (player_info.powerup_flags & PLAYER_FLAGS_FLAG) |
||
| 749 | { |
||
| 750 | player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; |
||
| 751 | multi_send_capture_bonus (Player_num); |
||
| 752 | maybe_drop_net_powerup(powerup_to_drop, 1, 0); |
||
| 753 | } |
||
| 754 | } |
||
| 755 | |||
| 756 | void fuelcen_check_for_hoard_goal(object &plrobj, const shared_segment &segp) |
||
| 757 | { |
||
| 758 | Assert (game_mode_hoard()); |
||
| 759 | |||
| 760 | if (Player_dead_state != player_dead_state::no) |
||
| 761 | return; |
||
| 762 | |||
| 763 | const auto special = segp.special; |
||
| 764 | if (special==SEGMENT_IS_GOAL_BLUE || special==SEGMENT_IS_GOAL_RED) |
||
| 765 | { |
||
| 766 | auto &player_info = plrobj.ctype.player_info; |
||
| 767 | if (auto &hoard_orbs = player_info.hoard.orbs) |
||
| 768 | { |
||
| 769 | player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG; |
||
| 770 | multi_send_orb_bonus(Player_num, std::exchange(hoard_orbs, 0)); |
||
| 771 | } |
||
| 772 | } |
||
| 773 | |||
| 774 | } |
||
| 775 | |||
| 776 | |||
| 777 | /* |
||
| 778 | * reads an d1_matcen_info structure from a PHYSFS_File |
||
| 779 | */ |
||
| 780 | void d1_matcen_info_read(PHYSFS_File *fp, matcen_info &mi) |
||
| 781 | { |
||
| 782 | PHYSFSX_serialize_read<const d1mi_v25>(fp, mi); |
||
| 783 | mi.robot_flags[1] = 0; |
||
| 784 | } |
||
| 785 | |||
| 786 | DEFINE_SERIAL_UDT_TO_MESSAGE(matcen_info, m, (m.robot_flags, serial::pad<sizeof(fix) * 2>(), m.segnum, m.fuelcen_num)); |
||
| 787 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(matcen_info, 20); |
||
| 788 | |||
| 789 | void matcen_info_read(PHYSFS_File *fp, matcen_info &mi) |
||
| 790 | { |
||
| 791 | PHYSFSX_serialize_read(fp, mi); |
||
| 792 | } |
||
| 793 | #endif |
||
| 794 | |||
| 795 | void matcen_info_write(PHYSFS_File *fp, const matcen_info &mi, short version) |
||
| 796 | { |
||
| 797 | if (version >= 27) |
||
| 798 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 799 | PHYSFSX_serialize_write<d1cmi_v26>(fp, mi); |
||
| 800 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 801 | PHYSFSX_serialize_write(fp, mi); |
||
| 802 | #endif |
||
| 803 | else |
||
| 804 | PHYSFSX_serialize_write<d1cmi_v25>(fp, mi); |
||
| 805 | } |
||
| 806 | } |
||
| 807 | |||
| 808 | DEFINE_SERIAL_UDT_TO_MESSAGE(FuelCenter, fc, (serial::sign_extend<int>(fc.Type), serial::sign_extend<int>(fc.segnum), fc.Flag, fc.Enabled, fc.Lives, serial::pad<1>(), fc.Capacity, serial::pad<sizeof(fix)>(), fc.Timer, fc.Disable_time, serial::pad<3 * sizeof(fix)>())); |
||
| 809 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(FuelCenter, 40); |
||
| 810 | |||
| 811 | void fuelcen_read(PHYSFS_File *fp, FuelCenter &fc) |
||
| 812 | { |
||
| 813 | PHYSFSX_serialize_read(fp, fc); |
||
| 814 | } |
||
| 815 | |||
| 816 | void fuelcen_write(PHYSFS_File *fp, const FuelCenter &fc) |
||
| 817 | { |
||
| 818 | PHYSFSX_serialize_write(fp, fc); |
||
| 819 | } |