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 |