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 | #include "wall.h" |
||
| 21 | #include "player.h" |
||
| 22 | #include "text.h" |
||
| 23 | #include "fireball.h" |
||
| 24 | #include "textures.h" |
||
| 25 | #include "newdemo.h" |
||
| 26 | #include "multi.h" |
||
| 27 | #include "gameseq.h" |
||
| 28 | #include "physfs-serial.h" |
||
| 29 | #include "gameseg.h" |
||
| 30 | #include "hudmsg.h" |
||
| 31 | #include "laser.h" // For seeing if a flare is stuck in a wall. |
||
| 32 | #include "effects.h" |
||
| 33 | |||
| 34 | #include "d_enumerate.h" |
||
| 35 | #include "d_range.h" |
||
| 36 | #include "compiler-range_for.h" |
||
| 37 | #include "segiter.h" |
||
| 38 | #include "d_zip.h" |
||
| 39 | |||
| 40 | // Special door on boss level which is locked if not in multiplayer...sorry for this awful solution --MK. |
||
| 41 | #define BOSS_LOCKED_DOOR_LEVEL 7 |
||
| 42 | #define BOSS_LOCKED_DOOR_SEG 595 |
||
| 43 | #define BOSS_LOCKED_DOOR_SIDE 5 |
||
| 44 | |||
| 45 | namespace dcx { |
||
| 46 | unsigned Num_wall_anims; |
||
| 47 | } |
||
| 48 | |||
| 49 | namespace dsx { |
||
| 50 | |||
| 51 | namespace { |
||
| 52 | |||
| 53 | struct ad_removal_predicate |
||
| 54 | { |
||
| 55 | bool operator()(active_door &) const; |
||
| 56 | }; |
||
| 57 | |||
| 58 | struct find_active_door_predicate |
||
| 59 | { |
||
| 60 | const wallnum_t wall_num; |
||
| 61 | explicit find_active_door_predicate(const wallnum_t i) : |
||
| 62 | wall_num(i) |
||
| 63 | { |
||
| 64 | } |
||
| 65 | bool operator()(active_door &d) const { |
||
| 66 | if (d.front_wallnum[0] == wall_num) |
||
| 67 | return true; |
||
| 68 | if (d.back_wallnum[0] == wall_num) |
||
| 69 | return true; |
||
| 70 | if (d.n_parts != 2) |
||
| 71 | return false; |
||
| 72 | if (d.front_wallnum[1] == wall_num) |
||
| 73 | return true; |
||
| 74 | if (d.back_wallnum[1] == wall_num) |
||
| 75 | return true; |
||
| 76 | return false; |
||
| 77 | } |
||
| 78 | }; |
||
| 79 | |||
| 80 | } |
||
| 81 | |||
| 82 | } |
||
| 83 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 84 | #include "collide.h" |
||
| 85 | namespace dsx { |
||
| 86 | constexpr std::integral_constant<unsigned, F1_0> CLOAKING_WALL_TIME{}; |
||
| 87 | |||
| 88 | namespace { |
||
| 89 | |||
| 90 | struct cwframe |
||
| 91 | { |
||
| 92 | wall &w; |
||
| 93 | std::array<uvl, 4> &uvls; |
||
| 94 | cwframe(fvmsegptr &vmsegptr, wall &wr) : |
||
| 95 | w(wr), |
||
| 96 | uvls(vmsegptr(w.segnum)->unique_segment::sides[w.sidenum].uvls) |
||
| 97 | { |
||
| 98 | } |
||
| 99 | }; |
||
| 100 | |||
| 101 | struct cwresult |
||
| 102 | { |
||
| 103 | bool remove; |
||
| 104 | bool record; |
||
| 105 | cwresult() = default; |
||
| 106 | explicit cwresult(bool r) : |
||
| 107 | remove(false), record(r) |
||
| 108 | { |
||
| 109 | } |
||
| 110 | }; |
||
| 111 | |||
| 112 | struct cw_removal_predicate |
||
| 113 | { |
||
| 114 | fvmsegptr &vmsegptr; |
||
| 115 | wall_array &Walls; |
||
| 116 | unsigned num_cloaking_walls = 0; |
||
| 117 | bool operator()(cloaking_wall &d); |
||
| 118 | cw_removal_predicate(fvmsegptr &v, wall_array &w) : |
||
| 119 | vmsegptr(v), Walls(w) |
||
| 120 | { |
||
| 121 | } |
||
| 122 | }; |
||
| 123 | |||
| 124 | struct find_cloaked_wall_predicate |
||
| 125 | { |
||
| 126 | const vmwallidx_t w; |
||
| 127 | find_cloaked_wall_predicate(const vmwallidx_t i) : |
||
| 128 | w(i) |
||
| 129 | { |
||
| 130 | } |
||
| 131 | bool operator()(const cloaking_wall &cw) const |
||
| 132 | { |
||
| 133 | return cw.front_wallnum == w || cw.back_wallnum == w; |
||
| 134 | } |
||
| 135 | }; |
||
| 136 | |||
| 137 | } |
||
| 138 | |||
| 139 | } |
||
| 140 | #endif |
||
| 141 | |||
| 142 | static std::pair<uint_fast32_t, uint_fast32_t> get_transparency_check_values(const unique_side &side) |
||
| 143 | { |
||
| 144 | if (const uint_fast32_t masked_tmap_num2 = side.tmap_num2 & 0x3FFF) |
||
| 145 | return {masked_tmap_num2, BM_FLAG_SUPER_TRANSPARENT}; |
||
| 146 | return {side.tmap_num, BM_FLAG_TRANSPARENT}; |
||
| 147 | } |
||
| 148 | |||
| 149 | // This function determines whether the current segment/side is transparent |
||
| 150 | // 1 = YES |
||
| 151 | // 0 = NO |
||
| 152 | static uint_fast32_t check_transparency(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, const unique_side &side) |
||
| 153 | { |
||
| 154 | const auto &&v = get_transparency_check_values(side); |
||
| 155 | return GameBitmaps[Textures[v.first].index].get_flag_mask(v.second); |
||
| 156 | } |
||
| 157 | |||
| 158 | //----------------------------------------------------------------- |
||
| 159 | // This function checks whether we can fly through the given side. |
||
| 160 | // In other words, whether or not we have a 'doorway' |
||
| 161 | // Flags: |
||
| 162 | // WID_FLY_FLAG 1 |
||
| 163 | // WID_RENDER_FLAG 2 |
||
| 164 | // WID_RENDPAST_FLAG 4 |
||
| 165 | // Return values: |
||
| 166 | // WID_WALL 2 // 0/1/0 wall |
||
| 167 | // WID_TRANSPARENT_WALL 6 // 0/1/1 transparent wall |
||
| 168 | // WID_ILLUSORY_WALL 3 // 1/1/0 illusory wall |
||
| 169 | // WID_TRANSILLUSORY_WALL 7 // 1/1/1 transparent illusory wall |
||
| 170 | // WID_NO_WALL 5 // 1/0/1 no wall, can fly through |
||
| 171 | namespace dsx { |
||
| 172 | |||
| 173 | static WALL_IS_DOORWAY_result_t wall_is_doorway(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, fvcwallptr &vcwallptr, const shared_side &sside, const unique_side &uside) |
||
| 174 | { |
||
| 175 | auto &w = *vcwallptr(sside.wall_num); |
||
| 176 | const auto type = w.type; |
||
| 177 | if (type == WALL_OPEN) |
||
| 178 | return WID_NO_WALL; |
||
| 179 | |||
| 180 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 181 | if (unlikely(type == WALL_CLOAKED)) |
||
| 182 | return WID_CLOAKED_WALL; |
||
| 183 | #endif |
||
| 184 | |||
| 185 | const auto flags = w.flags; |
||
| 186 | if (type == WALL_ILLUSION) { |
||
| 187 | if (flags & WALL_ILLUSION_OFF) |
||
| 188 | return WID_NO_WALL; |
||
| 189 | else { |
||
| 190 | if (check_transparency(GameBitmaps, Textures, uside)) |
||
| 191 | return WID_TRANSILLUSORY_WALL; |
||
| 192 | else |
||
| 193 | return WID_ILLUSORY_WALL; |
||
| 194 | } |
||
| 195 | } |
||
| 196 | |||
| 197 | if (type == WALL_BLASTABLE) { |
||
| 198 | if (flags & WALL_BLASTED) |
||
| 199 | return WID_TRANSILLUSORY_WALL; |
||
| 200 | } |
||
| 201 | else |
||
| 202 | { |
||
| 203 | if (unlikely(flags & WALL_DOOR_OPENED)) |
||
| 204 | return WID_TRANSILLUSORY_WALL; |
||
| 205 | if (likely(type == WALL_DOOR) && unlikely(w.state == WALL_DOOR_OPENING)) |
||
| 206 | return WID_TRANSPARENT_WALL; |
||
| 207 | } |
||
| 208 | // If none of the above flags are set, there is no doorway. |
||
| 209 | if (check_transparency(GameBitmaps, Textures, uside)) |
||
| 210 | return WID_TRANSPARENT_WALL; |
||
| 211 | else |
||
| 212 | return WID_WALL; // There are children behind the door. |
||
| 213 | } |
||
| 214 | |||
| 215 | WALL_IS_DOORWAY_result_t WALL_IS_DOORWAY(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, fvcwallptr &vcwallptr, const cscusegment seg, const uint_fast32_t side) |
||
| 216 | { |
||
| 217 | const auto child = seg.s.children[side]; |
||
| 218 | if (unlikely(child == segment_none)) |
||
| 219 | return WID_WALL; |
||
| 220 | if (unlikely(child == segment_exit)) |
||
| 221 | return WID_EXTERNAL; |
||
| 222 | auto &sside = seg.s.sides[side]; |
||
| 223 | if (likely(sside.wall_num == wall_none)) |
||
| 224 | return WID_NO_WALL; |
||
| 225 | auto &uside = seg.u.sides[side]; |
||
| 226 | return wall_is_doorway(GameBitmaps, Textures, vcwallptr, sside, uside); |
||
| 227 | } |
||
| 228 | |||
| 229 | } |
||
| 230 | |||
| 231 | #if DXX_USE_EDITOR |
||
| 232 | //----------------------------------------------------------------- |
||
| 233 | // Initializes all the walls (in other words, no special walls) |
||
| 234 | namespace dsx { |
||
| 235 | void wall_init() |
||
| 236 | { |
||
| 237 | init_exploding_walls(); |
||
| 238 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 239 | Walls.set_count(0); |
||
| 240 | range_for (auto &w, Walls) |
||
| 241 | { |
||
| 242 | w.segnum = segment_none; |
||
| 243 | w.sidenum = -1; |
||
| 244 | w.type = WALL_NORMAL; |
||
| 245 | w.flags = 0; |
||
| 246 | w.hps = 0; |
||
| 247 | w.trigger = -1; |
||
| 248 | w.clip_num = -1; |
||
| 249 | w.linked_wall = -1; |
||
| 250 | } |
||
| 251 | auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; |
||
| 252 | ActiveDoors.set_count(0); |
||
| 253 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 254 | auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls; |
||
| 255 | CloakingWalls.set_count(0); |
||
| 256 | #endif |
||
| 257 | |||
| 258 | } |
||
| 259 | } |
||
| 260 | #endif |
||
| 261 | |||
| 262 | //set the tmap_num or tmap_num2 field for a wall/door |
||
| 263 | void wall_set_tmap_num(const wclip &anim, const vmsegptridx_t seg, const unsigned side, const vmsegptridx_t csegp, const unsigned cside, const unsigned frame_num) |
||
| 264 | { |
||
| 265 | const auto newdemo_state = Newdemo_state; |
||
| 266 | if (newdemo_state == ND_STATE_PLAYBACK) |
||
| 267 | return; |
||
| 268 | |||
| 269 | const auto tmap = anim.frames[frame_num]; |
||
| 270 | auto &uside = seg->unique_segment::sides[side]; |
||
| 271 | auto &cuside = csegp->unique_segment::sides[cside]; |
||
| 272 | if (anim.flags & WCF_TMAP1) { |
||
| 273 | if (tmap != uside.tmap_num || tmap != cuside.tmap_num) |
||
| 274 | { |
||
| 275 | uside.tmap_num = cuside.tmap_num = tmap; |
||
| 276 | if (newdemo_state == ND_STATE_RECORDING) |
||
| 277 | newdemo_record_wall_set_tmap_num1(seg,side,csegp,cside,tmap); |
||
| 278 | } |
||
| 279 | } else { |
||
| 280 | assert(tmap != 0 && uside.tmap_num2 != 0); |
||
| 281 | if (tmap != uside.tmap_num2 || tmap != cuside.tmap_num2) |
||
| 282 | { |
||
| 283 | uside.tmap_num2 = cuside.tmap_num2 = tmap; |
||
| 284 | if (newdemo_state == ND_STATE_RECORDING) |
||
| 285 | newdemo_record_wall_set_tmap_num2(seg,side,csegp,cside,tmap); |
||
| 286 | } |
||
| 287 | } |
||
| 288 | } |
||
| 289 | |||
| 290 | |||
| 291 | // ------------------------------------------------------------------------------- |
||
| 292 | //when the wall has used all its hitpoints, this will destroy it |
||
| 293 | static void blast_blastable_wall(const vmsegptridx_t seg, const unsigned side, wall &w0) |
||
| 294 | { |
||
| 295 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 296 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 297 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 298 | auto &vmobjptr = Objects.vmptr; |
||
| 299 | auto &sside = seg->shared_segment::sides[side]; |
||
| 300 | const auto wall_num = sside.wall_num; |
||
| 301 | w0.hps = -1; //say it's blasted |
||
| 302 | |||
| 303 | const auto &&csegp = seg.absolute_sibling(seg->children[side]); |
||
| 304 | auto Connectside = find_connect_side(seg, csegp); |
||
| 305 | Assert(Connectside != side_none); |
||
| 306 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 307 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 308 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 309 | auto &imwallptr = Walls.imptr; |
||
| 310 | const auto &&w1 = imwallptr(cwall_num); |
||
| 311 | if (w1) |
||
| 312 | LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, cwall_num); |
||
| 313 | LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, wall_num); |
||
| 314 | flush_fcd_cache(); |
||
| 315 | |||
| 316 | const auto a = w0.clip_num; |
||
| 317 | auto &wa = WallAnims[a]; |
||
| 318 | //if this is an exploding wall, explode it |
||
| 319 | if (wa.flags & WCF_EXPLODES) |
||
| 320 | { |
||
| 321 | auto &vcvertptr = Vertices.vcptr; |
||
| 322 | explode_wall(vcvertptr, seg, side, w0); |
||
| 323 | } |
||
| 324 | else { |
||
| 325 | //if not exploding, set final frame, and make door passable |
||
| 326 | const auto n = wa.num_frames; |
||
| 327 | w0.flags |= WALL_BLASTED; |
||
| 328 | if (w1) |
||
| 329 | w1->flags |= WALL_BLASTED; |
||
| 330 | wall_set_tmap_num(wa, seg, side, csegp, Connectside, n - 1); |
||
| 331 | } |
||
| 332 | |||
| 333 | } |
||
| 334 | |||
| 335 | |||
| 336 | //----------------------------------------------------------------- |
||
| 337 | // Destroys a blastable wall. |
||
| 338 | void wall_destroy(const vmsegptridx_t seg, const unsigned side) |
||
| 339 | { |
||
| 340 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 341 | auto &vmwallptr = Walls.vmptr; |
||
| 342 | auto &w = *vmwallptr(seg->shared_segment::sides[side].wall_num); |
||
| 343 | if (w.type == WALL_BLASTABLE) |
||
| 344 | blast_blastable_wall(seg, side, w); |
||
| 345 | else |
||
| 346 | Error("Hey bub, you are trying to destroy an indestructable wall."); |
||
| 347 | } |
||
| 348 | |||
| 349 | //----------------------------------------------------------------- |
||
| 350 | // Deteriorate appearance of wall. (Changes bitmap (paste-ons)) |
||
| 351 | void wall_damage(const vmsegptridx_t seg, const unsigned side, fix damage) |
||
| 352 | { |
||
| 353 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 354 | int i; |
||
| 355 | |||
| 356 | auto &sside = seg->shared_segment::sides[side]; |
||
| 357 | const auto wall_num = sside.wall_num; |
||
| 358 | if (wall_num == wall_none) { |
||
| 359 | return; |
||
| 360 | } |
||
| 361 | |||
| 362 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 363 | auto &vmwallptr = Walls.vmptr; |
||
| 364 | auto &w0 = *vmwallptr(wall_num); |
||
| 365 | if (w0.type != WALL_BLASTABLE) |
||
| 366 | return; |
||
| 367 | |||
| 368 | if (!(w0.flags & WALL_BLASTED) && w0.hps >= 0) |
||
| 369 | { |
||
| 370 | const auto &&csegp = seg.absolute_sibling(seg->children[side]); |
||
| 371 | auto Connectside = find_connect_side(seg, csegp); |
||
| 372 | Assert(Connectside != side_none); |
||
| 373 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 374 | auto &imwallptr = Walls.imptr; |
||
| 375 | if (const auto &&w1p = imwallptr(cwall_num)) |
||
| 376 | { |
||
| 377 | auto &w1 = *w1p; |
||
| 378 | w1.hps -= damage; |
||
| 379 | } |
||
| 380 | w0.hps -= damage; |
||
| 381 | |||
| 382 | const auto a = w0.clip_num; |
||
| 383 | const auto n = WallAnims[a].num_frames; |
||
| 384 | |||
| 385 | if (w0.hps < WALL_HPS*1/n) { |
||
| 386 | blast_blastable_wall(seg, side, w0); |
||
| 387 | if (Game_mode & GM_MULTI) |
||
| 388 | multi_send_door_open(seg, side, w0.flags); |
||
| 389 | } |
||
| 390 | else |
||
| 391 | for (i=0;i<n;i++) |
||
| 392 | if (w0.hps < WALL_HPS*(n-i)/n) |
||
| 393 | { |
||
| 394 | wall_set_tmap_num(WallAnims[a], seg, side, csegp, Connectside, i); |
||
| 395 | } |
||
| 396 | } |
||
| 397 | } |
||
| 398 | |||
| 399 | |||
| 400 | //----------------------------------------------------------------- |
||
| 401 | // Opens a door |
||
| 402 | namespace dsx { |
||
| 403 | void wall_open_door(const vmsegptridx_t seg, const unsigned side) |
||
| 404 | { |
||
| 405 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 406 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 407 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 408 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 409 | auto &vmobjptr = Objects.vmptr; |
||
| 410 | active_door *d; |
||
| 411 | |||
| 412 | auto &sside = seg->shared_segment::sides[side]; |
||
| 413 | const auto wall_num = sside.wall_num; |
||
| 414 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 415 | auto &vmwallptr = Walls.vmptr; |
||
| 416 | wall *const w = vmwallptr(wall_num); |
||
| 417 | LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, wall_num); |
||
| 418 | |||
| 419 | if ((w->state == WALL_DOOR_OPENING) || //already opening |
||
| 420 | (w->state == WALL_DOOR_WAITING)) //open, waiting to close |
||
| 421 | return; |
||
| 422 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 423 | if (w->state == WALL_DOOR_OPEN) //open, & staying open |
||
| 424 | return; |
||
| 425 | #endif |
||
| 426 | |||
| 427 | auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; |
||
| 428 | auto &vmactdoorptr = ActiveDoors.vmptr; |
||
| 429 | if (w->state == WALL_DOOR_CLOSING) { //closing, so reuse door |
||
| 430 | const auto &&r = make_range(vmactdoorptr); |
||
| 431 | const auto &&i = std::find_if(r.begin(), r.end(), find_active_door_predicate(wall_num)); |
||
| 432 | if (i == r.end()) // likely in demo playback or multiplayer |
||
| 433 | { |
||
| 434 | const auto c = ActiveDoors.get_count(); |
||
| 435 | ActiveDoors.set_count(c + 1); |
||
| 436 | d = vmactdoorptr(static_cast<actdoornum_t>(c)); |
||
| 437 | d->time = 0; |
||
| 438 | } |
||
| 439 | else |
||
| 440 | { |
||
| 441 | d = *i; |
||
| 442 | d->time = WallAnims[w->clip_num].play_time - d->time; |
||
| 443 | if (d->time < 0) |
||
| 444 | d->time = 0; |
||
| 445 | } |
||
| 446 | } |
||
| 447 | else { //create new door |
||
| 448 | Assert(w->state == WALL_DOOR_CLOSED); |
||
| 449 | const auto i = ActiveDoors.get_count(); |
||
| 450 | ActiveDoors.set_count(i + 1); |
||
| 451 | d = vmactdoorptr(static_cast<actdoornum_t>(i)); |
||
| 452 | d->time = 0; |
||
| 453 | } |
||
| 454 | |||
| 455 | |||
| 456 | w->state = WALL_DOOR_OPENING; |
||
| 457 | |||
| 458 | // So that door can't be shot while opening |
||
| 459 | const auto &&csegp = vcsegptr(seg->children[side]); |
||
| 460 | auto Connectside = find_connect_side(seg, csegp); |
||
| 461 | if (Connectside != side_none) |
||
| 462 | { |
||
| 463 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 464 | auto &imwallptr = Walls.imptr; |
||
| 465 | if (const auto &&w1 = imwallptr(cwall_num)) |
||
| 466 | { |
||
| 467 | w1->state = WALL_DOOR_OPENING; |
||
| 468 | d->back_wallnum[0] = cwall_num; |
||
| 469 | } |
||
| 470 | d->front_wallnum[0] = seg->shared_segment::sides[side].wall_num; |
||
| 471 | } |
||
| 472 | else |
||
| 473 | con_printf(CON_URGENT, "Illegal Connectside %i in wall_open_door. Trying to hop over. Please check your level!", side); |
||
| 474 | |||
| 475 | if (Newdemo_state == ND_STATE_RECORDING) { |
||
| 476 | newdemo_record_door_opening(seg, side); |
||
| 477 | } |
||
| 478 | |||
| 479 | if (w->linked_wall != wall_none) |
||
| 480 | { |
||
| 481 | wall *const w2 = vmwallptr(w->linked_wall); |
||
| 482 | |||
| 483 | Assert(w2->linked_wall == seg->shared_segment::sides[side].wall_num); |
||
| 484 | //Assert(!(w2->flags & WALL_DOOR_OPENING || w2->flags & WALL_DOOR_OPENED)); |
||
| 485 | |||
| 486 | w2->state = WALL_DOOR_OPENING; |
||
| 487 | |||
| 488 | const auto &&seg2 = vcsegptridx(w2->segnum); |
||
| 489 | const auto &&csegp2 = vcsegptr(seg2->children[w2->sidenum]); |
||
| 490 | Connectside = find_connect_side(seg2, csegp2); |
||
| 491 | Assert(Connectside != side_none); |
||
| 492 | const auto cwall_num = csegp2->shared_segment::sides[Connectside].wall_num; |
||
| 493 | auto &imwallptr = Walls.imptr; |
||
| 494 | if (const auto &&w3 = imwallptr(cwall_num)) |
||
| 495 | w3->state = WALL_DOOR_OPENING; |
||
| 496 | |||
| 497 | d->n_parts = 2; |
||
| 498 | d->front_wallnum[1] = w->linked_wall; |
||
| 499 | d->back_wallnum[1] = cwall_num; |
||
| 500 | } |
||
| 501 | else |
||
| 502 | d->n_parts = 1; |
||
| 503 | |||
| 504 | |||
| 505 | if ( Newdemo_state != ND_STATE_PLAYBACK ) |
||
| 506 | { |
||
| 507 | // NOTE THE LINK TO ABOVE!!!! |
||
| 508 | auto &vcvertptr = Vertices.vcptr; |
||
| 509 | const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side); |
||
| 510 | const auto open_sound = WallAnims[w->clip_num].open_sound; |
||
| 511 | if (open_sound > -1) |
||
| 512 | digi_link_sound_to_pos(open_sound, seg, side, cp, 0, F1_0); |
||
| 513 | |||
| 514 | } |
||
| 515 | } |
||
| 516 | } |
||
| 517 | |||
| 518 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 519 | namespace dsx { |
||
| 520 | |||
| 521 | //----------------------------------------------------------------- |
||
| 522 | // start the transition from closed -> open wall |
||
| 523 | void start_wall_cloak(const vmsegptridx_t seg, const unsigned side) |
||
| 524 | { |
||
| 525 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 526 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 527 | cloaking_wall *d; |
||
| 528 | |||
| 529 | if ( Newdemo_state==ND_STATE_PLAYBACK ) return; |
||
| 530 | |||
| 531 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 532 | const auto &&w = Walls.vmptridx(seg->shared_segment::sides[side].wall_num); |
||
| 533 | |||
| 534 | if (w->type == WALL_OPEN || w->state == WALL_DOOR_CLOAKING) //already open or cloaking |
||
| 535 | return; |
||
| 536 | |||
| 537 | const auto &&csegp = vcsegptr(seg->children[side]); |
||
| 538 | auto Connectside = find_connect_side(seg, csegp); |
||
| 539 | Assert(Connectside != side_none); |
||
| 540 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 541 | |||
| 542 | auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls; |
||
| 543 | if (w->state == WALL_DOOR_DECLOAKING) { //decloaking, so reuse door |
||
| 544 | const auto &&r = make_range(CloakingWalls.vmptr); |
||
| 545 | const auto i = std::find_if(r.begin(), r.end(), find_cloaked_wall_predicate(w)); |
||
| 546 | if (i == r.end()) |
||
| 547 | { |
||
| 548 | d_debugbreak(); |
||
| 549 | return; |
||
| 550 | } |
||
| 551 | d = *i; |
||
| 552 | d->time = CLOAKING_WALL_TIME - d->time; |
||
| 553 | } |
||
| 554 | else if (w->state == WALL_DOOR_CLOSED) { //create new door |
||
| 555 | const clwallnum_t c = CloakingWalls.get_count(); |
||
| 556 | if (c >= CloakingWalls.size()) |
||
| 557 | { |
||
| 558 | Int3(); //ran out of cloaking wall slots |
||
| 559 | w->type = WALL_OPEN; |
||
| 560 | if (const auto &&w1 = Walls.imptr(cwall_num)) |
||
| 561 | w1->type = WALL_OPEN; |
||
| 562 | return; |
||
| 563 | } |
||
| 564 | CloakingWalls.set_count(c + 1); |
||
| 565 | d = CloakingWalls.vmptr(c); |
||
| 566 | d->time = 0; |
||
| 567 | } |
||
| 568 | else { |
||
| 569 | Int3(); //unexpected wall state |
||
| 570 | return; |
||
| 571 | } |
||
| 572 | |||
| 573 | w->state = WALL_DOOR_CLOAKING; |
||
| 574 | if (const auto &&w1 = Walls.imptr(cwall_num)) |
||
| 575 | w1->state = WALL_DOOR_CLOAKING; |
||
| 576 | |||
| 577 | d->front_wallnum = seg->shared_segment::sides[side].wall_num; |
||
| 578 | d->back_wallnum = cwall_num; |
||
| 579 | Assert(w->linked_wall == wall_none); |
||
| 580 | |||
| 581 | if ( Newdemo_state != ND_STATE_PLAYBACK ) { |
||
| 582 | auto &vcvertptr = Vertices.vcptr; |
||
| 583 | const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side); |
||
| 584 | digi_link_sound_to_pos( SOUND_WALL_CLOAK_ON, seg, side, cp, 0, F1_0 ); |
||
| 585 | } |
||
| 586 | |||
| 587 | for (auto &&[front_ls, back_ls, s0_uvls, s1_uvls] : zip( |
||
| 588 | d->front_ls, |
||
| 589 | d->back_ls, |
||
| 590 | seg->unique_segment::sides[side].uvls, |
||
| 591 | csegp->unique_segment::sides[Connectside].uvls |
||
| 592 | )) |
||
| 593 | { |
||
| 594 | front_ls = s0_uvls.l; |
||
| 595 | back_ls = s1_uvls.l; |
||
| 596 | } |
||
| 597 | } |
||
| 598 | |||
| 599 | //----------------------------------------------------------------- |
||
| 600 | // start the transition from open -> closed wall |
||
| 601 | void start_wall_decloak(const vmsegptridx_t seg, const unsigned side) |
||
| 602 | { |
||
| 603 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 604 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 605 | cloaking_wall *d; |
||
| 606 | |||
| 607 | if ( Newdemo_state==ND_STATE_PLAYBACK ) return; |
||
| 608 | |||
| 609 | auto &sside = seg->shared_segment::sides[side]; |
||
| 610 | assert(sside.wall_num != wall_none); //Opening door on illegal wall |
||
| 611 | |||
| 612 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 613 | const auto &&w = Walls.vmptridx(sside.wall_num); |
||
| 614 | |||
| 615 | if (w->type == WALL_CLOSED || w->state == WALL_DOOR_DECLOAKING) //already closed or decloaking |
||
| 616 | return; |
||
| 617 | |||
| 618 | auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls; |
||
| 619 | if (w->state == WALL_DOOR_CLOAKING) { //cloaking, so reuse door |
||
| 620 | const auto &&r = make_range(CloakingWalls.vmptr); |
||
| 621 | const auto i = std::find_if(r.begin(), r.end(), find_cloaked_wall_predicate(w)); |
||
| 622 | if (i == r.end()) |
||
| 623 | { |
||
| 624 | d_debugbreak(); |
||
| 625 | return; |
||
| 626 | } |
||
| 627 | d = *i; |
||
| 628 | d->time = CLOAKING_WALL_TIME - d->time; |
||
| 629 | } |
||
| 630 | else if (w->state == WALL_DOOR_CLOSED) { //create new door |
||
| 631 | const clwallnum_t c = CloakingWalls.get_count(); |
||
| 632 | if (c >= CloakingWalls.size()) |
||
| 633 | { |
||
| 634 | Int3(); //ran out of cloaking wall slots |
||
| 635 | /* what is this _doing_ here? |
||
| 636 | w->type = WALL_CLOSED; |
||
| 637 | Walls[csegp->sides[Connectside].wall_num].type = WALL_CLOSED; |
||
| 638 | */ |
||
| 639 | return; |
||
| 640 | } |
||
| 641 | CloakingWalls.set_count(c + 1); |
||
| 642 | d = CloakingWalls.vmptr(c); |
||
| 643 | d->time = 0; |
||
| 644 | } |
||
| 645 | else { |
||
| 646 | Int3(); //unexpected wall state |
||
| 647 | return; |
||
| 648 | } |
||
| 649 | |||
| 650 | w->state = WALL_DOOR_DECLOAKING; |
||
| 651 | |||
| 652 | // So that door can't be shot while opening |
||
| 653 | const auto &&csegp = vcsegptr(seg->children[side]); |
||
| 654 | auto Connectside = find_connect_side(seg, csegp); |
||
| 655 | Assert(Connectside != side_none); |
||
| 656 | auto &csside = csegp->shared_segment::sides[Connectside]; |
||
| 657 | const auto cwall_num = csside.wall_num; |
||
| 658 | if (const auto &&w1 = Walls.imptr(cwall_num)) |
||
| 659 | w1->state = WALL_DOOR_DECLOAKING; |
||
| 660 | |||
| 661 | d->front_wallnum = seg->shared_segment::sides[side].wall_num; |
||
| 662 | d->back_wallnum = csside.wall_num; |
||
| 663 | Assert(w->linked_wall == wall_none); |
||
| 664 | |||
| 665 | if ( Newdemo_state != ND_STATE_PLAYBACK ) { |
||
| 666 | auto &vcvertptr = Vertices.vcptr; |
||
| 667 | const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side); |
||
| 668 | digi_link_sound_to_pos( SOUND_WALL_CLOAK_OFF, seg, side, cp, 0, F1_0 ); |
||
| 669 | } |
||
| 670 | |||
| 671 | for (auto &&[front_ls, back_ls, s0_uvls, s1_uvls] : zip( |
||
| 672 | d->front_ls, |
||
| 673 | d->back_ls, |
||
| 674 | seg->unique_segment::sides[side].uvls, |
||
| 675 | csegp->unique_segment::sides[Connectside].uvls |
||
| 676 | )) |
||
| 677 | { |
||
| 678 | front_ls = s0_uvls.l; |
||
| 679 | back_ls = s1_uvls.l; |
||
| 680 | } |
||
| 681 | } |
||
| 682 | |||
| 683 | } |
||
| 684 | #endif |
||
| 685 | |||
| 686 | //----------------------------------------------------------------- |
||
| 687 | // This function closes the specified door and restores the closed |
||
| 688 | // door texture. This is called when the animation is done |
||
| 689 | void wall_close_door_ref(fvmsegptridx &vmsegptridx, wall_array &Walls, const wall_animations_array &WallAnims, active_door &d) |
||
| 690 | { |
||
| 691 | range_for (const auto p, partial_const_range(d.front_wallnum, d.n_parts)) |
||
| 692 | { |
||
| 693 | wall &w = *Walls.vmptr(p); |
||
| 694 | |||
| 695 | const auto &&seg = vmsegptridx(w.segnum); |
||
| 696 | const auto side = w.sidenum; |
||
| 697 | w.state = WALL_DOOR_CLOSED; |
||
| 698 | |||
| 699 | assert(seg->shared_segment::sides[side].wall_num != wall_none); //Closing door on illegal wall |
||
| 700 | |||
| 701 | const auto &&csegp = seg.absolute_sibling(seg->children[side]); |
||
| 702 | auto Connectside = find_connect_side(seg, csegp); |
||
| 703 | Assert(Connectside != side_none); |
||
| 704 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 705 | if (const auto &&w1 = Walls.imptr(cwall_num)) |
||
| 706 | w1->state = WALL_DOOR_CLOSED; |
||
| 707 | |||
| 708 | wall_set_tmap_num(WallAnims[w.clip_num], seg, side, csegp, Connectside, 0); |
||
| 709 | } |
||
| 710 | } |
||
| 711 | |||
| 712 | static unsigned check_poke(fvcvertptr &vcvertptr, const object_base &obj, const shared_segment &seg, const unsigned side) |
||
| 713 | { |
||
| 714 | //note: don't let objects with zero size block door |
||
| 715 | if (!obj.size) |
||
| 716 | return 0; |
||
| 717 | return get_seg_masks(vcvertptr, obj.pos, seg, obj.size).sidemask & (1 << side); //pokes through side! |
||
| 718 | } |
||
| 719 | |||
| 720 | namespace dsx { |
||
| 721 | static unsigned is_door_side_obstructed(fvcobjptridx &vcobjptridx, fvcsegptr &vcsegptr, const cscusegment seg, const unsigned side) |
||
| 722 | { |
||
| 723 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 724 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 725 | auto &vcvertptr = Vertices.vcptr; |
||
| 726 | range_for (const object_base &obj, objects_in(seg, vcobjptridx, vcsegptr)) |
||
| 727 | { |
||
| 728 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 729 | if (obj.type == OBJ_WEAPON) |
||
| 730 | continue; |
||
| 731 | if (obj.type == OBJ_FIREBALL) |
||
| 732 | continue; |
||
| 733 | #endif |
||
| 734 | if (const auto obstructed = check_poke(vcvertptr, obj, seg, side)) |
||
| 735 | return obstructed; |
||
| 736 | } |
||
| 737 | return 0; |
||
| 738 | } |
||
| 739 | |||
| 740 | //returns true if door is obstructed (& thus cannot close) |
||
| 741 | static unsigned is_door_obstructed(fvcobjptridx &vcobjptridx, fvcsegptr &vcsegptr, const vcsegptridx_t seg, const unsigned side) |
||
| 742 | { |
||
| 743 | if (const auto obstructed = is_door_side_obstructed(vcobjptridx, vcsegptr, seg, side)) |
||
| 744 | return obstructed; |
||
| 745 | const auto &&csegp = vcsegptr(seg->children[side]); |
||
| 746 | const auto &&Connectside = find_connect_side(seg, csegp); |
||
| 747 | Assert(Connectside != side_none); |
||
| 748 | //go through each object in each of two segments, and see if |
||
| 749 | //it pokes into the connecting seg |
||
| 750 | return is_door_side_obstructed(vcobjptridx, vcsegptr, csegp, Connectside); |
||
| 751 | } |
||
| 752 | |||
| 753 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 754 | //----------------------------------------------------------------- |
||
| 755 | // Closes a door |
||
| 756 | void wall_close_door(wall_array &Walls, const vmsegptridx_t seg, const unsigned side) |
||
| 757 | { |
||
| 758 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 759 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 760 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 761 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 762 | auto &vcobjptridx = Objects.vcptridx; |
||
| 763 | active_door *d; |
||
| 764 | |||
| 765 | const auto wall_num = seg->shared_segment::sides[side].wall_num; |
||
| 766 | wall *const w = Walls.vmptr(wall_num); |
||
| 767 | if ((w->state == WALL_DOOR_CLOSING) || //already closing |
||
| 768 | (w->state == WALL_DOOR_WAITING) || //open, waiting to close |
||
| 769 | (w->state == WALL_DOOR_CLOSED)) //closed |
||
| 770 | return; |
||
| 771 | |||
| 772 | if (is_door_obstructed(vcobjptridx, vcsegptr, seg, side)) |
||
| 773 | return; |
||
| 774 | |||
| 775 | auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; |
||
| 776 | auto &vmactdoorptr = ActiveDoors.vmptr; |
||
| 777 | if (w->state == WALL_DOOR_OPENING) { //reuse door |
||
| 778 | const auto &&r = make_range(vmactdoorptr); |
||
| 779 | const auto &&i = std::find_if(r.begin(), r.end(), find_active_door_predicate(wall_num)); |
||
| 780 | if (i == r.end()) |
||
| 781 | { |
||
| 782 | d_debugbreak(); |
||
| 783 | return; |
||
| 784 | } |
||
| 785 | d = *i; |
||
| 786 | d->time = WallAnims[w->clip_num].play_time - d->time; |
||
| 787 | |||
| 788 | if (d->time < 0) |
||
| 789 | d->time = 0; |
||
| 790 | |||
| 791 | } |
||
| 792 | else { //create new door |
||
| 793 | Assert(w->state == WALL_DOOR_OPEN); |
||
| 794 | const auto i = ActiveDoors.get_count(); |
||
| 795 | ActiveDoors.set_count(i + 1); |
||
| 796 | d = vmactdoorptr(static_cast<actdoornum_t>(i)); |
||
| 797 | d->time = 0; |
||
| 798 | } |
||
| 799 | |||
| 800 | w->state = WALL_DOOR_CLOSING; |
||
| 801 | |||
| 802 | // So that door can't be shot while opening |
||
| 803 | const auto &&csegp = vcsegptr(seg->children[side]); |
||
| 804 | const auto &&Connectside = find_connect_side(seg, csegp); |
||
| 805 | Assert(Connectside != side_none); |
||
| 806 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 807 | if (const auto &&w1 = Walls.imptr(cwall_num)) |
||
| 808 | w1->state = WALL_DOOR_CLOSING; |
||
| 809 | |||
| 810 | d->front_wallnum[0] = seg->shared_segment::sides[side].wall_num; |
||
| 811 | d->back_wallnum[0] = cwall_num; |
||
| 812 | if (Newdemo_state == ND_STATE_RECORDING) { |
||
| 813 | newdemo_record_door_opening(seg, side); |
||
| 814 | } |
||
| 815 | |||
| 816 | if (w->linked_wall != wall_none) |
||
| 817 | { |
||
| 818 | Int3(); //don't think we ever used linked walls |
||
| 819 | } |
||
| 820 | else |
||
| 821 | d->n_parts = 1; |
||
| 822 | |||
| 823 | |||
| 824 | if ( Newdemo_state != ND_STATE_PLAYBACK ) |
||
| 825 | { |
||
| 826 | // NOTE THE LINK TO ABOVE!!!! |
||
| 827 | auto &vcvertptr = Vertices.vcptr; |
||
| 828 | const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side); |
||
| 829 | const auto open_sound = WallAnims[w->clip_num].open_sound; |
||
| 830 | if (open_sound > -1) |
||
| 831 | digi_link_sound_to_pos(open_sound, seg, side, cp, 0, F1_0); |
||
| 832 | |||
| 833 | } |
||
| 834 | } |
||
| 835 | #endif |
||
| 836 | |||
| 837 | //----------------------------------------------------------------- |
||
| 838 | // Animates opening of a door. |
||
| 839 | // Called in the game loop. |
||
| 840 | static bool do_door_open(active_door &d) |
||
| 841 | { |
||
| 842 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 843 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 844 | auto &vmobjptr = Objects.vmptr; |
||
| 845 | bool remove = false; |
||
| 846 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 847 | auto &vmwallptr = Walls.vmptr; |
||
| 848 | for (unsigned p = 0; p < d.n_parts; ++p) |
||
| 849 | { |
||
| 850 | int side; |
||
| 851 | fix time_elapsed, time_total, one_frame; |
||
| 852 | int i; |
||
| 853 | |||
| 854 | wall &w = *vmwallptr(d.front_wallnum[p]); |
||
| 855 | LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, d.front_wallnum[p]); |
||
| 856 | LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, d.back_wallnum[p]); |
||
| 857 | |||
| 858 | const auto &&seg = vmsegptridx(w.segnum); |
||
| 859 | side = w.sidenum; |
||
| 860 | |||
| 861 | if (seg->shared_segment::sides[side].wall_num == wall_none) |
||
| 862 | { |
||
| 863 | con_printf(CON_URGENT, "Trying to do_door_open on illegal wall %i. Please check your level!",side); |
||
| 864 | continue; |
||
| 865 | } |
||
| 866 | |||
| 867 | const auto &&csegp = seg.absolute_sibling(seg->children[side]); |
||
| 868 | const auto &&Connectside = find_connect_side(seg, csegp); |
||
| 869 | Assert(Connectside != side_none); |
||
| 870 | |||
| 871 | d.time += FrameTime; |
||
| 872 | |||
| 873 | time_elapsed = d.time; |
||
| 874 | auto &wa = WallAnims[w.clip_num]; |
||
| 875 | const auto n = wa.num_frames; |
||
| 876 | time_total = wa.play_time; |
||
| 877 | |||
| 878 | one_frame = time_total/n; |
||
| 879 | |||
| 880 | i = time_elapsed/one_frame; |
||
| 881 | |||
| 882 | if (i < n) |
||
| 883 | wall_set_tmap_num(wa, seg, side, csegp, Connectside, i); |
||
| 884 | |||
| 885 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 886 | auto &w1 = *vmwallptr(cwall_num); |
||
| 887 | if (i> n/2) { |
||
| 888 | w.flags |= WALL_DOOR_OPENED; |
||
| 889 | w1.flags |= WALL_DOOR_OPENED; |
||
| 890 | } |
||
| 891 | |||
| 892 | if (i >= n-1) { |
||
| 893 | wall_set_tmap_num(wa, seg, side, csegp, Connectside, n - 1); |
||
| 894 | |||
| 895 | // If our door is not automatic just remove it from the list. |
||
| 896 | if (!(w.flags & WALL_DOOR_AUTO)) { |
||
| 897 | remove = true; |
||
| 898 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 899 | w.state = WALL_DOOR_OPEN; |
||
| 900 | w1.state = WALL_DOOR_OPEN; |
||
| 901 | #endif |
||
| 902 | } |
||
| 903 | else { |
||
| 904 | w.state = WALL_DOOR_WAITING; |
||
| 905 | w1.state = WALL_DOOR_WAITING; |
||
| 906 | } |
||
| 907 | } |
||
| 908 | |||
| 909 | } |
||
| 910 | flush_fcd_cache(); |
||
| 911 | return remove; |
||
| 912 | } |
||
| 913 | |||
| 914 | //----------------------------------------------------------------- |
||
| 915 | // Animates and processes the closing of a door. |
||
| 916 | // Called from the game loop. |
||
| 917 | static bool do_door_close(active_door &d) |
||
| 918 | { |
||
| 919 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 920 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 921 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 922 | auto &vcobjptridx = Objects.vcptridx; |
||
| 923 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 924 | auto &WallAnims = GameSharedState.WallAnims; |
||
| 925 | auto &vmwallptr = Walls.vmptr; |
||
| 926 | auto &w0 = *vmwallptr(d.front_wallnum[0]); |
||
| 927 | const auto &&seg0 = vmsegptridx(w0.segnum); |
||
| 928 | |||
| 929 | //check for objects in doorway before closing |
||
| 930 | if (w0.flags & WALL_DOOR_AUTO) |
||
| 931 | if (is_door_obstructed(vcobjptridx, vcsegptr, seg0, w0.sidenum)) |
||
| 932 | { |
||
| 933 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 934 | digi_kill_sound_linked_to_segment(w0.segnum, w0.sidenum, -1); |
||
| 935 | wall_open_door(seg0, w0.sidenum); //re-open door |
||
| 936 | #endif |
||
| 937 | return false; |
||
| 938 | } |
||
| 939 | |||
| 940 | bool played_sound = false; |
||
| 941 | bool remove = false; |
||
| 942 | range_for (const auto p, partial_const_range(d.front_wallnum, d.n_parts)) |
||
| 943 | { |
||
| 944 | int side; |
||
| 945 | fix time_elapsed, time_total, one_frame; |
||
| 946 | int i; |
||
| 947 | |||
| 948 | auto &wp = *vmwallptr(p); |
||
| 949 | |||
| 950 | const auto &&seg = vmsegptridx(wp.segnum); |
||
| 951 | side = wp.sidenum; |
||
| 952 | |||
| 953 | if (seg->shared_segment::sides[side].wall_num == wall_none) { |
||
| 954 | return false; |
||
| 955 | } |
||
| 956 | |||
| 957 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 958 | //if here, must be auto door |
||
| 959 | //don't assert here, because now we have triggers to close non-auto doors |
||
| 960 | Assert(wp.flags & WALL_DOOR_AUTO); |
||
| 961 | #endif |
||
| 962 | |||
| 963 | // Otherwise, close it. |
||
| 964 | const auto &&csegp = seg.absolute_sibling(seg->children[side]); |
||
| 965 | const auto &&Connectside = find_connect_side(seg, csegp); |
||
| 966 | Assert(Connectside != side_none); |
||
| 967 | |||
| 968 | |||
| 969 | auto &wa = WallAnims[wp.clip_num]; |
||
| 970 | if ( Newdemo_state != ND_STATE_PLAYBACK ) |
||
| 971 | { |
||
| 972 | // NOTE THE LINK TO ABOVE!! |
||
| 973 | if (!played_sound) //only play one sound for linked doors |
||
| 974 | { |
||
| 975 | played_sound = true; |
||
| 976 | if (d.time == 0) |
||
| 977 | { //first time |
||
| 978 | const auto close_sound = wa.close_sound; |
||
| 979 | if (close_sound > -1) |
||
| 980 | { |
||
| 981 | auto &vcvertptr = Vertices.vcptr; |
||
| 982 | digi_link_sound_to_pos(close_sound, seg, side, compute_center_point_on_side(vcvertptr, seg, side), 0, F1_0); |
||
| 983 | } |
||
| 984 | } |
||
| 985 | } |
||
| 986 | } |
||
| 987 | |||
| 988 | d.time += FrameTime; |
||
| 989 | |||
| 990 | time_elapsed = d.time; |
||
| 991 | const auto n = wa.num_frames; |
||
| 992 | time_total = wa.play_time; |
||
| 993 | |||
| 994 | one_frame = time_total/n; |
||
| 995 | |||
| 996 | i = n-time_elapsed/one_frame-1; |
||
| 997 | |||
| 998 | const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num; |
||
| 999 | auto &w1 = *vmwallptr(cwall_num); |
||
| 1000 | if (i < n/2) { |
||
| 1001 | wp.flags &= ~WALL_DOOR_OPENED; |
||
| 1002 | w1.flags &= ~WALL_DOOR_OPENED; |
||
| 1003 | } |
||
| 1004 | |||
| 1005 | // Animate door. |
||
| 1006 | if (i > 0) { |
||
| 1007 | wall_set_tmap_num(wa, seg, side, csegp, Connectside, i); |
||
| 1008 | |||
| 1009 | wp.state = WALL_DOOR_CLOSING; |
||
| 1010 | w1.state = WALL_DOOR_CLOSING; |
||
| 1011 | } else |
||
| 1012 | remove = true; |
||
| 1013 | } |
||
| 1014 | |||
| 1015 | if (remove) |
||
| 1016 | wall_close_door_ref(Segments.vmptridx, Walls, WallAnims, d); |
||
| 1017 | return remove; |
||
| 1018 | } |
||
| 1019 | |||
| 1020 | static std::pair<wall *, wall *> wall_illusion_op(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side) |
||
| 1021 | { |
||
| 1022 | const auto wall0 = seg->shared_segment::sides[side].wall_num; |
||
| 1023 | if (wall0 == wall_none) |
||
| 1024 | return {nullptr, nullptr}; |
||
| 1025 | const shared_segment &csegp = *seg.absolute_sibling(seg->children[side]); |
||
| 1026 | const auto &&cside = find_connect_side(seg, csegp); |
||
| 1027 | if (cside == side_none) |
||
| 1028 | { |
||
| 1029 | assert(cside != side_none); |
||
| 1030 | return {nullptr, nullptr}; |
||
| 1031 | } |
||
| 1032 | const auto wall1 = csegp.sides[cside].wall_num; |
||
| 1033 | if (wall1 == wall_none) |
||
| 1034 | return {nullptr, nullptr}; |
||
| 1035 | return {vmwallptr(wall0), vmwallptr(wall1)}; |
||
| 1036 | } |
||
| 1037 | |||
| 1038 | template <typename F> |
||
| 1039 | static void wall_illusion_op(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side, const F op) |
||
| 1040 | { |
||
| 1041 | const auto &&r = wall_illusion_op(vmwallptr, seg, side); |
||
| 1042 | if (r.first) |
||
| 1043 | { |
||
| 1044 | op(*r.first); |
||
| 1045 | op(*r.second); |
||
| 1046 | } |
||
| 1047 | } |
||
| 1048 | |||
| 1049 | //----------------------------------------------------------------- |
||
| 1050 | // Turns off an illusionary wall (This will be used primarily for |
||
| 1051 | // wall switches or triggers that can turn on/off illusionary walls.) |
||
| 1052 | void wall_illusion_off(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side) |
||
| 1053 | { |
||
| 1054 | const auto &&op = [](wall &w) { |
||
| 1055 | w.flags |= WALL_ILLUSION_OFF; |
||
| 1056 | }; |
||
| 1057 | wall_illusion_op(vmwallptr, seg, side, op); |
||
| 1058 | } |
||
| 1059 | |||
| 1060 | //----------------------------------------------------------------- |
||
| 1061 | // Turns on an illusionary wall (This will be used primarily for |
||
| 1062 | // wall switches or triggers that can turn on/off illusionary walls.) |
||
| 1063 | void wall_illusion_on(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side) |
||
| 1064 | { |
||
| 1065 | const auto &&op = [](wall &w) { |
||
| 1066 | w.flags &= ~WALL_ILLUSION_OFF; |
||
| 1067 | }; |
||
| 1068 | wall_illusion_op(vmwallptr, seg, side, op); |
||
| 1069 | } |
||
| 1070 | |||
| 1071 | } |
||
| 1072 | |||
| 1073 | // ----------------------------------------------------------------------------- |
||
| 1074 | // Allowed to open the normally locked special boss door if in multiplayer mode. |
||
| 1075 | static int special_boss_opening_allowed(segnum_t segnum, int sidenum) |
||
| 1076 | { |
||
| 1077 | if (Game_mode & GM_MULTI) |
||
| 1078 | return (Current_level_num == BOSS_LOCKED_DOOR_LEVEL) && (segnum == BOSS_LOCKED_DOOR_SEG) && (sidenum == BOSS_LOCKED_DOOR_SIDE); |
||
| 1079 | else |
||
| 1080 | return 0; |
||
| 1081 | } |
||
| 1082 | |||
| 1083 | //----------------------------------------------------------------- |
||
| 1084 | // Determines what happens when a wall is shot |
||
| 1085 | //returns info about wall. see wall.h for codes |
||
| 1086 | //obj is the object that hit...either a weapon or the player himself |
||
| 1087 | //playernum is the number the player who hit the wall or fired the weapon, |
||
| 1088 | //or -1 if a robot fired the weapon |
||
| 1089 | namespace dsx { |
||
| 1090 | wall_hit_process_t wall_hit_process(const player_flags powerup_flags, const vmsegptridx_t seg, const unsigned side, const fix damage, const unsigned playernum, const object &obj) |
||
| 1091 | { |
||
| 1092 | fix show_message; |
||
| 1093 | |||
| 1094 | // If it is not a "wall" then just return. |
||
| 1095 | const auto wall_num = seg->shared_segment::sides[side].wall_num; |
||
| 1096 | if (wall_num == wall_none) |
||
| 1097 | return wall_hit_process_t::WHP_NOT_SPECIAL; |
||
| 1098 | |||
| 1099 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1100 | auto &vmwallptr = Walls.vmptr; |
||
| 1101 | wall *const w = vmwallptr(wall_num); |
||
| 1102 | |||
| 1103 | if ( Newdemo_state == ND_STATE_RECORDING ) |
||
| 1104 | newdemo_record_wall_hit_process( seg, side, damage, playernum ); |
||
| 1105 | |||
| 1106 | if (w->type == WALL_BLASTABLE) { |
||
| 1107 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1108 | if (obj.ctype.laser_info.parent_type == OBJ_PLAYER) |
||
| 1109 | #endif |
||
| 1110 | wall_damage(seg, side, damage); |
||
| 1111 | return wall_hit_process_t::WHP_BLASTABLE; |
||
| 1112 | } |
||
| 1113 | |||
| 1114 | if (playernum != Player_num) //return if was robot fire |
||
| 1115 | return wall_hit_process_t::WHP_NOT_SPECIAL; |
||
| 1116 | |||
| 1117 | // Determine whether player is moving forward. If not, don't say negative |
||
| 1118 | // messages because he probably didn't intentionally hit the door. |
||
| 1119 | if (obj.type == OBJ_PLAYER) |
||
| 1120 | show_message = (vm_vec_dot(obj.orient.fvec, obj.mtype.phys_info.velocity) > 0); |
||
| 1121 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1122 | else if (obj.type == OBJ_ROBOT) |
||
| 1123 | show_message = 0; |
||
| 1124 | else if (obj.type == OBJ_WEAPON && obj.ctype.laser_info.parent_type == OBJ_ROBOT) |
||
| 1125 | show_message = 0; |
||
| 1126 | #endif |
||
| 1127 | else |
||
| 1128 | show_message = 1; |
||
| 1129 | |||
| 1130 | /* Set key_color only after the type matches, since TXT_* are macros |
||
| 1131 | * that trigger a load from memory. Use operator,() to suppress the |
||
| 1132 | * truth test on the second branch since the compiler cannot prove |
||
| 1133 | * that the loaded value will always be non-null. |
||
| 1134 | */ |
||
| 1135 | const char *key_color; |
||
| 1136 | if ( |
||
| 1137 | (w->keys == KEY_BLUE && (key_color = TXT_BLUE, true)) || |
||
| 1138 | (w->keys == KEY_GOLD && (key_color = TXT_YELLOW, true)) || |
||
| 1139 | (w->keys == KEY_RED && (key_color = TXT_RED, true)) |
||
| 1140 | ) |
||
| 1141 | { |
||
| 1142 | if (!(powerup_flags & static_cast<PLAYER_FLAG>(w->keys))) |
||
| 1143 | { |
||
| 1144 | static_assert(KEY_BLUE == static_cast<unsigned>(PLAYER_FLAGS_BLUE_KEY), "BLUE key flag mismatch"); |
||
| 1145 | static_assert(KEY_GOLD == static_cast<unsigned>(PLAYER_FLAGS_GOLD_KEY), "GOLD key flag mismatch"); |
||
| 1146 | static_assert(KEY_RED == static_cast<unsigned>(PLAYER_FLAGS_RED_KEY), "RED key flag mismatch"); |
||
| 1147 | if (show_message) |
||
| 1148 | HUD_init_message(HM_DEFAULT, "%s %s",key_color,TXT_ACCESS_DENIED); |
||
| 1149 | return wall_hit_process_t::WHP_NO_KEY; |
||
| 1150 | } |
||
| 1151 | } |
||
| 1152 | |||
| 1153 | if (w->type == WALL_DOOR) |
||
| 1154 | { |
||
| 1155 | if ((w->flags & WALL_DOOR_LOCKED ) && !(special_boss_opening_allowed(seg, side)) ) { |
||
| 1156 | if (show_message) |
||
| 1157 | HUD_init_message_literal(HM_DEFAULT, TXT_CANT_OPEN_DOOR); |
||
| 1158 | return wall_hit_process_t::WHP_NO_KEY; |
||
| 1159 | } |
||
| 1160 | else { |
||
| 1161 | if (w->state != WALL_DOOR_OPENING) |
||
| 1162 | { |
||
| 1163 | wall_open_door(seg, side); |
||
| 1164 | if (Game_mode & GM_MULTI) |
||
| 1165 | { |
||
| 1166 | int flags; |
||
| 1167 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1168 | flags = 0; |
||
| 1169 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1170 | flags = w->flags; |
||
| 1171 | #endif |
||
| 1172 | multi_send_door_open(seg, side,flags); |
||
| 1173 | } |
||
| 1174 | } |
||
| 1175 | return wall_hit_process_t::WHP_DOOR; |
||
| 1176 | |||
| 1177 | } |
||
| 1178 | } |
||
| 1179 | return wall_hit_process_t::WHP_NOT_SPECIAL; //default is treat like normal wall |
||
| 1180 | } |
||
| 1181 | } |
||
| 1182 | |||
| 1183 | //----------------------------------------------------------------- |
||
| 1184 | // Opens doors/destroys wall/shuts off triggers. |
||
| 1185 | void wall_toggle(fvmwallptr &vmwallptr, const vmsegptridx_t segp, const unsigned side) |
||
| 1186 | { |
||
| 1187 | if (side >= MAX_SIDES_PER_SEGMENT) |
||
| 1188 | { |
||
| 1189 | #ifndef NDEBUG |
||
| 1190 | Warning("Can't toggle side %u of segment %d (%u)!\n", side, static_cast<segnum_t>(segp), Highest_segment_index); |
||
| 1191 | #endif |
||
| 1192 | return; |
||
| 1193 | } |
||
| 1194 | const auto wall_num = segp->shared_segment::sides[side].wall_num; |
||
| 1195 | if (wall_num == wall_none) |
||
| 1196 | { |
||
| 1197 | LevelError("Ignoring attempt to toggle wall in segment %hu, side %u: no wall exists there.", segp.get_unchecked_index(), side); |
||
| 1198 | return; |
||
| 1199 | } |
||
| 1200 | |||
| 1201 | if ( Newdemo_state == ND_STATE_RECORDING ) |
||
| 1202 | newdemo_record_wall_toggle(segp, side); |
||
| 1203 | |||
| 1204 | wall *const w = vmwallptr(wall_num); |
||
| 1205 | if (w->type == WALL_BLASTABLE) |
||
| 1206 | wall_destroy(segp, side); |
||
| 1207 | |||
| 1208 | if (w->type == WALL_DOOR && w->state == WALL_DOOR_CLOSED) |
||
| 1209 | wall_open_door(segp, side); |
||
| 1210 | } |
||
| 1211 | |||
| 1212 | bool ad_removal_predicate::operator()(active_door &d) const |
||
| 1213 | { |
||
| 1214 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1215 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1216 | auto &vcobjptridx = Objects.vcptridx; |
||
| 1217 | #endif |
||
| 1218 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1219 | wall &w = *Walls.vmptr(d.front_wallnum[0]); |
||
| 1220 | if (w.state == WALL_DOOR_OPENING) |
||
| 1221 | return do_door_open(d); |
||
| 1222 | else if (w.state == WALL_DOOR_CLOSING) |
||
| 1223 | return do_door_close(d); |
||
| 1224 | else if (w.state == WALL_DOOR_WAITING) { |
||
| 1225 | d.time += FrameTime; |
||
| 1226 | // set flags to fix occasional netgame problem where door is waiting to close but open flag isn't set |
||
| 1227 | w.flags |= WALL_DOOR_OPENED; |
||
| 1228 | if (wall *const w1 = Walls.imptr(d.back_wallnum[0])) |
||
| 1229 | w1->flags |= WALL_DOOR_OPENED; |
||
| 1230 | if (d.time > DOOR_WAIT_TIME) |
||
| 1231 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1232 | if (!is_door_obstructed(vcobjptridx, vcsegptr, vcsegptridx(w.segnum), w.sidenum)) |
||
| 1233 | #endif |
||
| 1234 | { |
||
| 1235 | w.state = WALL_DOOR_CLOSING; |
||
| 1236 | d.time = 0; |
||
| 1237 | } |
||
| 1238 | } |
||
| 1239 | return false; |
||
| 1240 | } |
||
| 1241 | |||
| 1242 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1243 | static void copy_cloaking_wall_light_to_wall(std::array<uvl, 4> &back_uvls, std::array<uvl, 4> &front_uvls, const cloaking_wall &d) |
||
| 1244 | { |
||
| 1245 | range_for (const uint_fast32_t i, xrange(4u)) |
||
| 1246 | { |
||
| 1247 | back_uvls[i].l = d.back_ls[i]; |
||
| 1248 | front_uvls[i].l = d.front_ls[i]; |
||
| 1249 | } |
||
| 1250 | } |
||
| 1251 | |||
| 1252 | static void scale_cloaking_wall_light_to_wall(std::array<uvl, 4> &back_uvls, std::array<uvl, 4> &front_uvls, const cloaking_wall &d, const fix light_scale) |
||
| 1253 | { |
||
| 1254 | range_for (const uint_fast32_t i, xrange(4u)) |
||
| 1255 | { |
||
| 1256 | back_uvls[i].l = fixmul(d.back_ls[i], light_scale); |
||
| 1257 | front_uvls[i].l = fixmul(d.front_ls[i], light_scale); |
||
| 1258 | } |
||
| 1259 | } |
||
| 1260 | |||
| 1261 | static cwresult do_cloaking_wall_frame(const bool initial, cloaking_wall &d, const cwframe front, const cwframe back) |
||
| 1262 | { |
||
| 1263 | cwresult r(initial); |
||
| 1264 | if (d.time > CLOAKING_WALL_TIME) { |
||
| 1265 | front.w.type = back.w.type = WALL_OPEN; |
||
| 1266 | front.w.state = back.w.state = WALL_DOOR_CLOSED; //why closed? why not? |
||
| 1267 | r.remove = true; |
||
| 1268 | } |
||
| 1269 | else if (d.time > CLOAKING_WALL_TIME/2) { |
||
| 1270 | const int8_t cloak_value = ((d.time - CLOAKING_WALL_TIME / 2) * (GR_FADE_LEVELS - 2)) / (CLOAKING_WALL_TIME / 2); |
||
| 1271 | if (front.w.cloak_value != cloak_value) |
||
| 1272 | { |
||
| 1273 | r.record = true; |
||
| 1274 | front.w.cloak_value = back.w.cloak_value = cloak_value; |
||
| 1275 | } |
||
| 1276 | |||
| 1277 | if (front.w.type != WALL_CLOAKED) |
||
| 1278 | { //just switched |
||
| 1279 | front.w.type = back.w.type = WALL_CLOAKED; |
||
| 1280 | copy_cloaking_wall_light_to_wall(back.uvls, front.uvls, d); |
||
| 1281 | } |
||
| 1282 | } |
||
| 1283 | else { //fading out |
||
| 1284 | fix light_scale; |
||
| 1285 | light_scale = fixdiv(CLOAKING_WALL_TIME / 2 - d.time, CLOAKING_WALL_TIME / 2); |
||
| 1286 | scale_cloaking_wall_light_to_wall(back.uvls, front.uvls, d, light_scale); |
||
| 1287 | } |
||
| 1288 | return r; |
||
| 1289 | } |
||
| 1290 | |||
| 1291 | static cwresult do_decloaking_wall_frame(const bool initial, cloaking_wall &d, const cwframe front, const cwframe back) |
||
| 1292 | { |
||
| 1293 | cwresult r(initial); |
||
| 1294 | if (d.time > CLOAKING_WALL_TIME) { |
||
| 1295 | |||
| 1296 | back.w.state = WALL_DOOR_CLOSED; |
||
| 1297 | front.w.state = WALL_DOOR_CLOSED; |
||
| 1298 | copy_cloaking_wall_light_to_wall(back.uvls, front.uvls, d); |
||
| 1299 | r.remove = true; |
||
| 1300 | } |
||
| 1301 | else if (d.time > CLOAKING_WALL_TIME/2) { //fading in |
||
| 1302 | fix light_scale; |
||
| 1303 | front.w.type = back.w.type = WALL_CLOSED; |
||
| 1304 | |||
| 1305 | light_scale = fixdiv(d.time - CLOAKING_WALL_TIME / 2, CLOAKING_WALL_TIME / 2); |
||
| 1306 | scale_cloaking_wall_light_to_wall(back.uvls, front.uvls, d, light_scale); |
||
| 1307 | } |
||
| 1308 | else { //cloaking in |
||
| 1309 | const int8_t cloak_value = ((CLOAKING_WALL_TIME / 2 - d.time) * (GR_FADE_LEVELS - 2)) / (CLOAKING_WALL_TIME / 2); |
||
| 1310 | if (front.w.cloak_value != cloak_value) |
||
| 1311 | { |
||
| 1312 | front.w.cloak_value = back.w.cloak_value = cloak_value; |
||
| 1313 | r.record = true; |
||
| 1314 | } |
||
| 1315 | front.w.type = WALL_CLOAKED; |
||
| 1316 | back.w.type = WALL_CLOAKED; |
||
| 1317 | } |
||
| 1318 | return r; |
||
| 1319 | } |
||
| 1320 | |||
| 1321 | bool cw_removal_predicate::operator()(cloaking_wall &d) |
||
| 1322 | { |
||
| 1323 | const cwframe front(vmsegptr, *Walls.vmptr(d.front_wallnum)); |
||
| 1324 | const auto &&wpback = Walls.imptr(d.back_wallnum); |
||
| 1325 | const cwframe back = (wpback ? cwframe(vmsegptr, *wpback) : front); |
||
| 1326 | const bool initial = (d.time == 0); |
||
| 1327 | d.time += FrameTime; |
||
| 1328 | |||
| 1329 | cwresult r; |
||
| 1330 | if (front.w.state == WALL_DOOR_CLOAKING) |
||
| 1331 | r = do_cloaking_wall_frame(initial, d, front, back); |
||
| 1332 | else if (front.w.state == WALL_DOOR_DECLOAKING) |
||
| 1333 | r = do_decloaking_wall_frame(initial, d, front, back); |
||
| 1334 | else |
||
| 1335 | { |
||
| 1336 | d_debugbreak(); //unexpected wall state |
||
| 1337 | return false; |
||
| 1338 | } |
||
| 1339 | if (r.record) |
||
| 1340 | { |
||
| 1341 | // check if the actual cloak_value changed in this frame to prevent redundant recordings and wasted bytes |
||
| 1342 | if (Newdemo_state == ND_STATE_RECORDING && r.record) |
||
| 1343 | newdemo_record_cloaking_wall(d.front_wallnum, d.back_wallnum, front.w.type, front.w.state, front.w.cloak_value, front.uvls[0].l, front.uvls[1].l, front.uvls[2].l, front.uvls[3].l); |
||
| 1344 | } |
||
| 1345 | if (!r.remove) |
||
| 1346 | ++ num_cloaking_walls; |
||
| 1347 | return r.remove; |
||
| 1348 | } |
||
| 1349 | #endif |
||
| 1350 | |||
| 1351 | namespace dsx { |
||
| 1352 | static void process_exploding_walls() |
||
| 1353 | { |
||
| 1354 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
| 1355 | return; |
||
| 1356 | if (unsigned num_exploding_walls = Num_exploding_walls) |
||
| 1357 | { |
||
| 1358 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1359 | range_for (auto &&wp, Walls.vmptr) |
||
| 1360 | { |
||
| 1361 | auto &w1 = *wp; |
||
| 1362 | if (w1.flags & WALL_EXPLODING) |
||
| 1363 | { |
||
| 1364 | assert(num_exploding_walls); |
||
| 1365 | const auto n = do_exploding_wall_frame(w1); |
||
| 1366 | num_exploding_walls -= n; |
||
| 1367 | if (!num_exploding_walls) |
||
| 1368 | break; |
||
| 1369 | } |
||
| 1370 | } |
||
| 1371 | } |
||
| 1372 | } |
||
| 1373 | |||
| 1374 | void wall_frame_process() |
||
| 1375 | { |
||
| 1376 | process_exploding_walls(); |
||
| 1377 | { |
||
| 1378 | auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors; |
||
| 1379 | const auto &&r = partial_range(ActiveDoors, ActiveDoors.get_count()); |
||
| 1380 | auto &&i = std::remove_if(r.begin(), r.end(), ad_removal_predicate()); |
||
| 1381 | ActiveDoors.set_count(std::distance(r.begin(), i)); |
||
| 1382 | } |
||
| 1383 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1384 | if (Newdemo_state != ND_STATE_PLAYBACK) |
||
| 1385 | { |
||
| 1386 | auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls; |
||
| 1387 | const auto &&r = partial_range(CloakingWalls, CloakingWalls.get_count()); |
||
| 1388 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1389 | cw_removal_predicate rp{Segments.vmptr, Walls}; |
||
| 1390 | std::remove_if(r.begin(), r.end(), std::ref(rp)); |
||
| 1391 | CloakingWalls.set_count(rp.num_cloaking_walls); |
||
| 1392 | } |
||
| 1393 | #endif |
||
| 1394 | } |
||
| 1395 | |||
| 1396 | d_level_unique_stuck_object_state LevelUniqueStuckObjectState; |
||
| 1397 | |||
| 1398 | // An object got stuck in a door (like a flare). |
||
| 1399 | // Add global entry. |
||
| 1400 | void d_level_unique_stuck_object_state::add_stuck_object(fvcwallptr &vcwallptr, const vmobjptridx_t objp, const shared_segment &segp, const unsigned sidenum) |
||
| 1401 | { |
||
| 1402 | const auto wallnum = segp.sides[sidenum].wall_num; |
||
| 1403 | if (wallnum != wall_none) |
||
| 1404 | { |
||
| 1405 | if (vcwallptr(wallnum)->flags & WALL_BLASTED) |
||
| 1406 | { |
||
| 1407 | objp->flags |= OF_SHOULD_BE_DEAD; |
||
| 1408 | return; |
||
| 1409 | } |
||
| 1410 | if (Num_stuck_objects >= Stuck_objects.size()) |
||
| 1411 | { |
||
| 1412 | assert(Num_stuck_objects <= Stuck_objects.size()); |
||
| 1413 | con_printf(CON_NORMAL, "%s:%u: all stuck objects are busy; terminating %hu early", __FILE__, __LINE__, objp.get_unchecked_index()); |
||
| 1414 | objp->flags |= OF_SHOULD_BE_DEAD; |
||
| 1415 | return; |
||
| 1416 | } |
||
| 1417 | auto &so = Stuck_objects[Num_stuck_objects++]; |
||
| 1418 | so.wallnum = wallnum; |
||
| 1419 | so.objnum = objp; |
||
| 1420 | } |
||
| 1421 | } |
||
| 1422 | |||
| 1423 | void d_level_unique_stuck_object_state::remove_stuck_object(const vcobjidx_t obj) |
||
| 1424 | { |
||
| 1425 | auto &&pr = partial_range(Stuck_objects, Num_stuck_objects); |
||
| 1426 | const auto predicate = [obj](const stuckobj &so) { return so.objnum == obj; }; |
||
| 1427 | const auto i = std::find_if(pr.begin(), pr.end(), predicate); |
||
| 1428 | if (i == pr.end()) |
||
| 1429 | /* Objects enter this function if they are able to become stuck, |
||
| 1430 | * without regard to whether they actually are stuck. If the |
||
| 1431 | * object terminated without being stuck in a wall, then it will |
||
| 1432 | * not be found in Stuck_objects. |
||
| 1433 | */ |
||
| 1434 | return; |
||
| 1435 | /* If pr.begin() == pr.end(), then i == pr.end(), and this line |
||
| 1436 | * cannot be reached. |
||
| 1437 | * |
||
| 1438 | * If pr.begin() != pr.end(), then prev(pr.end()) must point to a |
||
| 1439 | * valid element. |
||
| 1440 | * |
||
| 1441 | * Move that valid element to the location vacated by the removed |
||
| 1442 | * object. This may be a self-move if the removed object is the |
||
| 1443 | * last object. |
||
| 1444 | */ |
||
| 1445 | auto &last_element = *std::prev(pr.end()); |
||
| 1446 | static_assert(std::is_trivially_move_assignable<stuckobj>::value, "stuckobj move may require a check to prevent self-move"); |
||
| 1447 | *i = std::move(last_element); |
||
| 1448 | DXX_POISON_VAR(last_element.wallnum, 0xcc); |
||
| 1449 | DXX_POISON_VAR(last_element.objnum, 0xcc); |
||
| 1450 | -- Num_stuck_objects; |
||
| 1451 | } |
||
| 1452 | |||
| 1453 | // ---------------------------------------------------------------------------------------------------- |
||
| 1454 | // Door with wall index wallnum is opening, kill all objects stuck in it. |
||
| 1455 | void d_level_unique_stuck_object_state::kill_stuck_objects(fvmobjptr &vmobjptr, const vcwallidx_t wallnum) |
||
| 1456 | { |
||
| 1457 | if (!Num_stuck_objects) |
||
| 1458 | return; |
||
| 1459 | auto &&pr = partial_range(Stuck_objects, Num_stuck_objects); |
||
| 1460 | const auto predicate = [&vmobjptr, wallnum](const stuckobj &so) |
||
| 1461 | { |
||
| 1462 | if (so.wallnum != wallnum) |
||
| 1463 | return false; |
||
| 1464 | auto &obj = *vmobjptr(so.objnum); |
||
| 1465 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1466 | #define DXX_WEAPON_LIFELEFT F1_0/4 |
||
| 1467 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1468 | #define DXX_WEAPON_LIFELEFT F1_0/8 |
||
| 1469 | #endif |
||
| 1470 | assert(obj.type == OBJ_WEAPON); |
||
| 1471 | assert(obj.movement_type == MT_PHYSICS); |
||
| 1472 | assert(obj.mtype.phys_info.flags & PF_STICK); |
||
| 1473 | obj.lifeleft = DXX_WEAPON_LIFELEFT; |
||
| 1474 | return true; |
||
| 1475 | }; |
||
| 1476 | const auto i = std::remove_if(pr.begin(), pr.end(), predicate); |
||
| 1477 | static_assert(std::is_trivially_destructible<stuckobj>::value, "stuckobj destructor not called"); |
||
| 1478 | Num_stuck_objects = std::distance(pr.begin(), i); |
||
| 1479 | std::array<stuckobj, 1> empty; |
||
| 1480 | DXX_POISON_VAR(empty, 0xcc); |
||
| 1481 | std::fill(i, pr.end(), empty[0]); |
||
| 1482 | } |
||
| 1483 | |||
| 1484 | } |
||
| 1485 | |||
| 1486 | namespace dcx { |
||
| 1487 | // ----------------------------------------------------------------------------------- |
||
| 1488 | // Initialize stuck objects array. Called at start of level |
||
| 1489 | void d_level_unique_stuck_object_state::init_stuck_objects() |
||
| 1490 | { |
||
| 1491 | DXX_POISON_VAR(Stuck_objects, 0xcc); |
||
| 1492 | Num_stuck_objects = 0; |
||
| 1493 | } |
||
| 1494 | } |
||
| 1495 | |||
| 1496 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1497 | // ----------------------------------------------------------------------------------- |
||
| 1498 | #define MAX_BLAST_GLASS_DEPTH 5 |
||
| 1499 | |||
| 1500 | namespace dsx { |
||
| 1501 | namespace { |
||
| 1502 | |||
| 1503 | class blast_nearby_glass_context |
||
| 1504 | { |
||
| 1505 | using TmapInfo_array = d_level_unique_tmap_info_state::TmapInfo_array; |
||
| 1506 | const object &obj; |
||
| 1507 | const fix damage; |
||
| 1508 | const d_eclip_array &Effects; |
||
| 1509 | const GameBitmaps_array &GameBitmaps; |
||
| 1510 | const Textures_array &Textures; |
||
| 1511 | const TmapInfo_array &TmapInfo; |
||
| 1512 | const d_vclip_array &Vclip; |
||
| 1513 | fvcvertptr &vcvertptr; |
||
| 1514 | fvcwallptr &vcwallptr; |
||
| 1515 | visited_segment_bitarray_t visited; |
||
| 1516 | unsigned can_blast(const int16_t &tmap_num2) const; |
||
| 1517 | public: |
||
| 1518 | blast_nearby_glass_context(const object &obj, const fix damage, const d_eclip_array &Effects, const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, const TmapInfo_array &TmapInfo, const d_vclip_array &Vclip, fvcvertptr &vcvertptr, fvcwallptr &vcwallptr) : |
||
| 1519 | obj(obj), damage(damage), Effects(Effects), GameBitmaps(GameBitmaps), |
||
| 1520 | Textures(Textures), TmapInfo(TmapInfo), Vclip(Vclip), |
||
| 1521 | vcvertptr(vcvertptr), vcwallptr(vcwallptr), visited{} |
||
| 1522 | { |
||
| 1523 | } |
||
| 1524 | blast_nearby_glass_context(const blast_nearby_glass_context &) = delete; |
||
| 1525 | blast_nearby_glass_context &operator=(const blast_nearby_glass_context &) = delete; |
||
| 1526 | void process_segment(vmsegptridx_t segp, unsigned steps_remaining); |
||
| 1527 | }; |
||
| 1528 | |||
| 1529 | unsigned blast_nearby_glass_context::can_blast(const int16_t &tmap_num2) const |
||
| 1530 | { |
||
| 1531 | const auto tm = tmap_num2 & 0x3fff; //tm without flags |
||
| 1532 | auto &ti = TmapInfo[tm]; |
||
| 1533 | const auto ec = ti.eclip_num; |
||
| 1534 | if (ec == eclip_none) |
||
| 1535 | { |
||
| 1536 | return ti.destroyed != -1; |
||
| 1537 | } |
||
| 1538 | else |
||
| 1539 | { |
||
| 1540 | auto &e = Effects[ec]; |
||
| 1541 | return e.dest_bm_num != ~0u && !(e.flags & EF_ONE_SHOT); |
||
| 1542 | } |
||
| 1543 | } |
||
| 1544 | |||
| 1545 | void blast_nearby_glass_context::process_segment(const vmsegptridx_t segp, const unsigned steps_remaining) |
||
| 1546 | { |
||
| 1547 | visited[segp] = true; |
||
| 1548 | |||
| 1549 | const auto &objp = obj; |
||
| 1550 | range_for (const auto &&e, enumerate(segp->unique_segment::sides)) |
||
| 1551 | { |
||
| 1552 | fix dist; |
||
| 1553 | |||
| 1554 | // Process only walls which have glass. |
||
| 1555 | auto &&uside = e.value; |
||
| 1556 | if (const auto &tmap_num2 = uside.tmap_num2) |
||
| 1557 | { |
||
| 1558 | if (can_blast(tmap_num2)) |
||
| 1559 | { |
||
| 1560 | auto &&sidenum = e.idx; |
||
| 1561 | const auto &&pnt = compute_center_point_on_side(vcvertptr, segp, sidenum); |
||
| 1562 | dist = vm_vec_dist_quick(pnt, objp.pos); |
||
| 1563 | if (dist < damage/2) { |
||
| 1564 | dist = find_connected_distance(pnt, segp, objp.pos, segp.absolute_sibling(objp.segnum), MAX_BLAST_GLASS_DEPTH, WID_RENDPAST_FLAG); |
||
| 1565 | if ((dist > 0) && (dist < damage/2)) |
||
| 1566 | { |
||
| 1567 | assert(objp.type == OBJ_WEAPON); |
||
| 1568 | check_effect_blowup(LevelSharedSegmentState.DestructibleLights, Vclip, segp, sidenum, pnt, objp.ctype.laser_info, 1, 0); |
||
| 1569 | } |
||
| 1570 | } |
||
| 1571 | } |
||
| 1572 | } |
||
| 1573 | } |
||
| 1574 | |||
| 1575 | const unsigned next_steps_remaining = steps_remaining - 1; |
||
| 1576 | if (!next_steps_remaining) |
||
| 1577 | return; |
||
| 1578 | range_for (const auto &&e, enumerate(segp->children)) |
||
| 1579 | { |
||
| 1580 | auto &&segnum = e.value; |
||
| 1581 | if (segnum != segment_none) { |
||
| 1582 | if (!visited[segnum]) { |
||
| 1583 | auto &&i = e.idx; |
||
| 1584 | if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, i) & WID_FLY_FLAG) |
||
| 1585 | { |
||
| 1586 | process_segment(segp.absolute_sibling(segnum), next_steps_remaining); |
||
| 1587 | } |
||
| 1588 | } |
||
| 1589 | } |
||
| 1590 | } |
||
| 1591 | } |
||
| 1592 | |||
| 1593 | struct d1wclip |
||
| 1594 | { |
||
| 1595 | wclip *const wc; |
||
| 1596 | d1wclip(wclip &w) : wc(&w) {} |
||
| 1597 | }; |
||
| 1598 | |||
| 1599 | DEFINE_SERIAL_UDT_TO_MESSAGE(d1wclip, dwc, (dwc.wc->play_time, dwc.wc->num_frames, dwc.wc->d1_frames, dwc.wc->open_sound, dwc.wc->close_sound, dwc.wc->flags, dwc.wc->filename, serial::pad<1>())); |
||
| 1600 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES_D1)); |
||
| 1601 | |||
| 1602 | } |
||
| 1603 | |||
| 1604 | // ----------------------------------------------------------------------------------- |
||
| 1605 | // objp is going to detonate |
||
| 1606 | // blast nearby monitors, lights, maybe other things |
||
| 1607 | void blast_nearby_glass(const object &objp, const fix damage) |
||
| 1608 | { |
||
| 1609 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 1610 | auto &Effects = LevelUniqueEffectsClipState.Effects; |
||
| 1611 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
| 1612 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 1613 | auto &vcvertptr = Vertices.vcptr; |
||
| 1614 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 1615 | auto &vcwallptr = Walls.vcptr; |
||
| 1616 | blast_nearby_glass_context{objp, damage, Effects, GameBitmaps, Textures, TmapInfo, Vclip, vcvertptr, vcwallptr}.process_segment(vmsegptridx(objp.segnum), MAX_BLAST_GLASS_DEPTH); |
||
| 1617 | } |
||
| 1618 | |||
| 1619 | } |
||
| 1620 | |||
| 1621 | #endif |
||
| 1622 | |||
| 1623 | DEFINE_SERIAL_UDT_TO_MESSAGE(wclip, wc, (wc.play_time, wc.num_frames, wc.frames, wc.open_sound, wc.close_sound, wc.flags, wc.filename, serial::pad<1>())); |
||
| 1624 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES)); |
||
| 1625 | |||
| 1626 | /* |
||
| 1627 | * reads a wclip structure from a PHYSFS_File |
||
| 1628 | */ |
||
| 1629 | void wclip_read(PHYSFS_File *fp, wclip &wc) |
||
| 1630 | { |
||
| 1631 | PHYSFSX_serialize_read(fp, wc); |
||
| 1632 | } |
||
| 1633 | |||
| 1634 | #if 0 |
||
| 1635 | void wclip_write(PHYSFS_File *fp, const wclip &wc) |
||
| 1636 | { |
||
| 1637 | PHYSFSX_serialize_write(fp, wc); |
||
| 1638 | } |
||
| 1639 | #endif |
||
| 1640 | |||
| 1641 | struct wrap_v16_wall |
||
| 1642 | { |
||
| 1643 | const wall *w; |
||
| 1644 | wrap_v16_wall(const wall &t) : w(&t) {} |
||
| 1645 | }; |
||
| 1646 | |||
| 1647 | #define _SERIAL_UDT_WALL_V16_MEMBERS(P) (P type, P flags, P hps, P trigger, P clip_num, P keys) |
||
| 1648 | |||
| 1649 | DEFINE_SERIAL_UDT_TO_MESSAGE(v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.)); |
||
| 1650 | DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.w->)); |
||
| 1651 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v16_wall, 9); |
||
| 1652 | |||
| 1653 | /* |
||
| 1654 | * reads a v16_wall structure from a PHYSFS_File |
||
| 1655 | */ |
||
| 1656 | void v16_wall_read(PHYSFS_File *fp, v16_wall &w) |
||
| 1657 | { |
||
| 1658 | PHYSFSX_serialize_read(fp, w); |
||
| 1659 | } |
||
| 1660 | |||
| 1661 | struct wrap_v19_wall |
||
| 1662 | { |
||
| 1663 | const wall *w; |
||
| 1664 | wrap_v19_wall(const wall &t) : w(&t) {} |
||
| 1665 | }; |
||
| 1666 | |||
| 1667 | DEFINE_SERIAL_UDT_TO_MESSAGE(v19_wall, w, (w.segnum, serial::pad<2>(), w.sidenum, w.type, w.flags, w.hps, w.trigger, w.clip_num, w.keys, w.linked_wall)); |
||
| 1668 | DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v19_wall, w, (w.w->segnum, serial::pad<2>(), w.w->sidenum, serial::pad<3>(), w.w->type, w.w->flags, w.w->hps, w.w->trigger, w.w->clip_num, w.w->keys, w.w->linked_wall, serial::pad<2>())); |
||
| 1669 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(v19_wall, 21); |
||
| 1670 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v19_wall, 21); |
||
| 1671 | |||
| 1672 | /* |
||
| 1673 | * reads a v19_wall structure from a PHYSFS_File |
||
| 1674 | */ |
||
| 1675 | void v19_wall_read(PHYSFS_File *fp, v19_wall &w) |
||
| 1676 | { |
||
| 1677 | PHYSFSX_serialize_read(fp, w); |
||
| 1678 | } |
||
| 1679 | |||
| 1680 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1681 | #define _SERIAL_UDT_WALL_D2X_MEMBERS serial::pad<2>() |
||
| 1682 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1683 | #define _SERIAL_UDT_WALL_D2X_MEMBERS w.controlling_trigger, w.cloak_value |
||
| 1684 | #endif |
||
| 1685 | DEFINE_SERIAL_UDT_TO_MESSAGE(wall, w, (serial::sign_extend<int>(w.segnum), w.sidenum, serial::pad<3, 0>(), w.hps, serial::sign_extend<int>(w.linked_wall), w.type, w.flags, w.state, w.trigger, w.clip_num, w.keys, _SERIAL_UDT_WALL_D2X_MEMBERS)); |
||
| 1686 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(wall, 24); |
||
| 1687 | |||
| 1688 | /* |
||
| 1689 | * reads a wall structure from a PHYSFS_File |
||
| 1690 | */ |
||
| 1691 | void wall_read(PHYSFS_File *fp, wall &w) |
||
| 1692 | { |
||
| 1693 | PHYSFSX_serialize_read(fp, w); |
||
| 1694 | w.flags &= ~WALL_EXPLODING; |
||
| 1695 | } |
||
| 1696 | |||
| 1697 | DEFINE_SERIAL_UDT_TO_MESSAGE(active_door, d, (d.n_parts, d.front_wallnum, d.back_wallnum, d.time)); |
||
| 1698 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(active_door, 16); |
||
| 1699 | |||
| 1700 | /* |
||
| 1701 | * reads an active_door structure from a PHYSFS_File |
||
| 1702 | */ |
||
| 1703 | void active_door_read(PHYSFS_File *fp, active_door &ad) |
||
| 1704 | { |
||
| 1705 | PHYSFSX_serialize_read(fp, ad); |
||
| 1706 | } |
||
| 1707 | |||
| 1708 | void active_door_write(PHYSFS_File *fp, const active_door &ad) |
||
| 1709 | { |
||
| 1710 | PHYSFSX_serialize_write(fp, ad); |
||
| 1711 | } |
||
| 1712 | |||
| 1713 | void wall_write(PHYSFS_File *fp, const wall &w, short version) |
||
| 1714 | { |
||
| 1715 | if (version <= 16) |
||
| 1716 | PHYSFSX_serialize_write<wrap_v16_wall>(fp, w); |
||
| 1717 | else if (version <= 19) |
||
| 1718 | PHYSFSX_serialize_write<wrap_v19_wall>(fp, w); |
||
| 1719 | else |
||
| 1720 | PHYSFSX_serialize_write(fp, w); |
||
| 1721 | } |
||
| 1722 | |||
| 1723 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1724 | DEFINE_SERIAL_UDT_TO_MESSAGE(cloaking_wall, cw, (cw.front_wallnum, cw.back_wallnum, cw.front_ls, cw.back_ls, cw.time)); |
||
| 1725 | ASSERT_SERIAL_UDT_MESSAGE_SIZE(cloaking_wall, 40); |
||
| 1726 | |||
| 1727 | namespace dsx { |
||
| 1728 | void cloaking_wall_read(cloaking_wall &cw, PHYSFS_File *fp) |
||
| 1729 | { |
||
| 1730 | PHYSFSX_serialize_read(fp, cw); |
||
| 1731 | } |
||
| 1732 | |||
| 1733 | void cloaking_wall_write(const cloaking_wall &cw, PHYSFS_File *fp) |
||
| 1734 | { |
||
| 1735 | PHYSFSX_serialize_write(fp, cw); |
||
| 1736 | } |
||
| 1737 | } |
||
| 1738 | #endif |