Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
3 | * described in COPYING.txt. |
||
4 | * Portions of this file are copyright Parallax Software and licensed |
||
5 | * according to the Parallax license below. |
||
6 | * See COPYING.txt for license details. |
||
7 | |||
8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
18 | */ |
||
19 | |||
20 | /* |
||
21 | * |
||
22 | * Routines for EndGame, EndLevel, etc. |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include "dxxsconf.h" |
||
27 | #include <cctype> |
||
28 | #include <utility> |
||
29 | #include <stdio.h> |
||
30 | #include <stdlib.h> |
||
31 | #include <string.h> |
||
32 | #if !defined(_MSC_VER) && !defined(macintosh) |
||
33 | #include <unistd.h> |
||
34 | #endif |
||
35 | #include <time.h> |
||
36 | |||
37 | #if DXX_USE_OGL |
||
38 | #include "ogl_init.h" |
||
39 | #endif |
||
40 | |||
41 | #include "inferno.h" |
||
42 | #include "game.h" |
||
43 | #include "player.h" |
||
44 | #include "key.h" |
||
45 | #include "object.h" |
||
46 | #include "physics.h" |
||
47 | #include "dxxerror.h" |
||
48 | #include "joy.h" |
||
49 | #include "iff.h" |
||
50 | #include "pcx.h" |
||
51 | #include "timer.h" |
||
52 | #include "render.h" |
||
53 | #include "laser.h" |
||
54 | #include "event.h" |
||
55 | #include "screens.h" |
||
56 | #include "textures.h" |
||
57 | #include "slew.h" |
||
58 | #include "gauges.h" |
||
59 | #include "texmap.h" |
||
60 | #include "3d.h" |
||
61 | #include "effects.h" |
||
62 | #include "menu.h" |
||
63 | #include "gameseg.h" |
||
64 | #include "wall.h" |
||
65 | #include "ai.h" |
||
66 | #include "fuelcen.h" |
||
67 | #include "switch.h" |
||
68 | #include "digi.h" |
||
69 | #include "gamesave.h" |
||
70 | #include "scores.h" |
||
71 | #include "u_mem.h" |
||
72 | #include "palette.h" |
||
73 | #include "morph.h" |
||
74 | #include "lighting.h" |
||
75 | #include "newdemo.h" |
||
76 | #include "titles.h" |
||
77 | #include "collide.h" |
||
78 | #include "weapon.h" |
||
79 | #include "sounds.h" |
||
80 | #include "args.h" |
||
81 | #include "gameseq.h" |
||
82 | #include "gamefont.h" |
||
83 | #include "newmenu.h" |
||
84 | #include "hudmsg.h" |
||
85 | #include "endlevel.h" |
||
86 | #include "kmatrix.h" |
||
87 | # include "multi.h" |
||
88 | #include "playsave.h" |
||
89 | #include "fireball.h" |
||
90 | #include "kconfig.h" |
||
91 | #include "config.h" |
||
92 | #include "robot.h" |
||
93 | #include "automap.h" |
||
94 | #include "cntrlcen.h" |
||
95 | #include "powerup.h" |
||
96 | #include "text.h" |
||
97 | #include "piggy.h" |
||
98 | #include "texmerge.h" |
||
99 | #include "paging.h" |
||
100 | #include "mission.h" |
||
101 | #include "state.h" |
||
102 | #include "songs.h" |
||
103 | #include "gamepal.h" |
||
104 | #include "controls.h" |
||
105 | #include "credits.h" |
||
106 | #include "gamemine.h" |
||
107 | #if DXX_USE_EDITOR |
||
108 | #include "editor/editor.h" |
||
109 | #endif |
||
110 | #include "strutil.h" |
||
111 | #include "rle.h" |
||
112 | #include "segment.h" |
||
113 | #include "gameseg.h" |
||
114 | #include "fmtcheck.h" |
||
115 | |||
116 | #include "compiler-range_for.h" |
||
117 | #include "d_enumerate.h" |
||
118 | #include "partial_range.h" |
||
119 | #include "d_range.h" |
||
120 | #include "d_zip.h" |
||
121 | |||
122 | #if defined(DXX_BUILD_DESCENT_I) |
||
123 | #include "custom.h" |
||
124 | #define GLITZ_BACKGROUND Menu_pcx_name |
||
125 | |||
126 | #elif defined(DXX_BUILD_DESCENT_II) |
||
127 | #include "movie.h" |
||
128 | #define GLITZ_BACKGROUND STARS_BACKGROUND |
||
129 | |||
130 | namespace dsx { |
||
131 | static void StartNewLevelSecret(int level_num, int page_in_textures); |
||
132 | static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag); |
||
133 | static void DoEndGame(); |
||
134 | static void filter_objects_from_level(fvmobjptr &vmobjptr); |
||
135 | PHYSFSX_gets_line_t<FILENAME_LEN> Current_level_palette; |
||
136 | int First_secret_visit = 1; |
||
137 | } |
||
138 | #endif |
||
139 | |||
140 | namespace { |
||
141 | |||
142 | class preserve_player_object_info |
||
143 | { |
||
144 | player_info plr_info; |
||
145 | fix plr_shields; |
||
146 | /* Cache the reference, not the value. This class is designed |
||
147 | * to be alive across a call to LoadLevel, which may change the |
||
148 | * value of the object number. |
||
149 | */ |
||
150 | const objnum_t &objnum; |
||
151 | public: |
||
152 | preserve_player_object_info(fvcobjptr &vcobjptr, const objnum_t &o) : |
||
153 | objnum(o) |
||
154 | { |
||
155 | auto &plr = *vcobjptr(objnum); |
||
156 | plr_shields = plr.shields; |
||
157 | plr_info = plr.ctype.player_info; |
||
158 | } |
||
159 | void restore(fvmobjptr &vmobjptr) const |
||
160 | { |
||
161 | auto &plr = *vmobjptr(objnum); |
||
162 | plr.shields = plr_shields; |
||
163 | plr.ctype.player_info = plr_info; |
||
164 | } |
||
165 | }; |
||
166 | |||
167 | } |
||
168 | |||
169 | namespace dsx { |
||
170 | static void init_player_stats_ship(object &, fix GameTime64); |
||
171 | static window_event_result AdvanceLevel(int secret_flag); |
||
172 | static void StartLevel(int random_flag); |
||
173 | static void copy_defaults_to_robot_all(void); |
||
174 | } |
||
175 | |||
176 | namespace dcx { |
||
177 | //Current_level_num starts at 1 for the first level |
||
178 | //-1,-2,-3 are secret levels |
||
179 | //0 used to mean not a real level loaded (i.e. editor generated level), but this hack has been removed |
||
180 | int Current_level_num=1,Next_level_num; |
||
181 | PHYSFSX_gets_line_t<LEVEL_NAME_LEN> Current_level_name; |
||
182 | |||
183 | // Global variables describing the player |
||
184 | unsigned N_players=1; // Number of players ( >1 means a net game, eh?) |
||
185 | playernum_t Player_num; // The player number who is on the console. |
||
186 | fix StartingShields=INITIAL_SHIELDS; |
||
187 | std::array<obj_position, MAX_PLAYERS> Player_init; |
||
188 | |||
189 | // Global variables telling what sort of game we have |
||
190 | unsigned NumNetPlayerPositions; |
||
191 | int Do_appearance_effect=0; |
||
192 | |||
193 | template <object_type_t type> |
||
194 | static bool is_object_of_type(const object_base &o) |
||
195 | { |
||
196 | return o.type == type; |
||
197 | } |
||
198 | |||
199 | static unsigned get_starting_concussion_missile_count() |
||
200 | { |
||
201 | return 2 + NDL - GameUniqueState.Difficulty_level; |
||
202 | } |
||
203 | |||
204 | } |
||
205 | |||
206 | namespace dsx { |
||
207 | |||
208 | //-------------------------------------------------------------------- |
||
209 | static void verify_console_object() |
||
210 | { |
||
211 | auto &Objects = LevelUniqueObjectState.Objects; |
||
212 | auto &vmobjptr = Objects.vmptr; |
||
213 | Assert(Player_num < Players.size()); |
||
214 | const auto &&console = vmobjptr(get_local_player().objnum); |
||
215 | ConsoleObject = console; |
||
216 | Assert(console->type == OBJ_PLAYER); |
||
217 | Assert(get_player_id(console) == Player_num); |
||
218 | } |
||
219 | |||
220 | template <object_type_t type> |
||
221 | static unsigned count_number_of_objects_of_type(fvcobjptr &vcobjptr) |
||
222 | { |
||
223 | return std::count_if(vcobjptr.begin(), vcobjptr.end(), is_object_of_type<type>); |
||
224 | } |
||
225 | |||
226 | #define count_number_of_robots count_number_of_objects_of_type<OBJ_ROBOT> |
||
227 | #define count_number_of_hostages count_number_of_objects_of_type<OBJ_HOSTAGE> |
||
228 | |||
229 | static bool operator!=(const vms_vector &a, const vms_vector &b) |
||
230 | { |
||
231 | return a.x != b.x || a.y != b.y || a.z != b.z; |
||
232 | } |
||
233 | |||
234 | static unsigned generate_extra_starts_by_displacement_within_segment(const unsigned preplaced_starts, const unsigned total_required_num_starts) |
||
235 | { |
||
236 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
237 | auto &Objects = LevelUniqueObjectState.Objects; |
||
238 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
239 | auto &vcobjptr = Objects.vcptr; |
||
240 | auto &vmobjptr = Objects.vmptr; |
||
241 | std::array<uint8_t, MAX_PLAYERS> player_init_segment_capacity_flag{}; |
||
242 | DXX_MAKE_VAR_UNDEFINED(player_init_segment_capacity_flag); |
||
243 | static_assert(WRIGHT + 1 == WBOTTOM, "side ordering error"); |
||
244 | static_assert(WBOTTOM + 1 == WBACK, "side ordering error"); |
||
245 | constexpr uint8_t capacity_x = 1 << WRIGHT; |
||
246 | constexpr uint8_t capacity_y = 1 << WBOTTOM; |
||
247 | constexpr uint8_t capacity_z = 1 << WBACK; |
||
248 | /* When players are displaced, they are moved by their size |
||
249 | * multiplied by this constant. Larger values provide more |
||
250 | * separation between the player starts, but increase the chance |
||
251 | * that the player will be too close to a wall or that the segment |
||
252 | * will be deemed too small to support displacement. |
||
253 | */ |
||
254 | constexpr fix size_scalar = 0x18000; // 1.5 in fixed point |
||
255 | unsigned segments_with_spare_capacity = 0; |
||
256 | auto &vcvertptr = Vertices.vcptr; |
||
257 | for (unsigned i = 0; i < preplaced_starts; ++i) |
||
258 | { |
||
259 | /* For each existing Player_init, compute whether the segment is |
||
260 | * large enough in each dimension to support adding more ships. |
||
261 | */ |
||
262 | const auto &pi = Player_init[i]; |
||
263 | const auto segnum = pi.segnum; |
||
264 | const shared_segment &seg = *vcsegptr(segnum); |
||
265 | auto &plr = *Players.vcptr(i); |
||
266 | auto &old_player_obj = *vcobjptr(plr.objnum); |
||
267 | const vm_distance_squared size2(fixmul64(old_player_obj.size * old_player_obj.size, size_scalar)); |
||
268 | auto &v0 = *vcvertptr(seg.verts[0]); |
||
269 | uint8_t capacity_flag = 0; |
||
270 | if (vm_vec_dist2(v0, vcvertptr(seg.verts[1])) > size2) |
||
271 | capacity_flag |= capacity_x; |
||
272 | if (vm_vec_dist2(v0, vcvertptr(seg.verts[3])) > size2) |
||
273 | capacity_flag |= capacity_y; |
||
274 | if (vm_vec_dist2(v0, vcvertptr(seg.verts[4])) > size2) |
||
275 | capacity_flag |= capacity_z; |
||
276 | player_init_segment_capacity_flag[i] = capacity_flag; |
||
277 | con_printf(CON_NORMAL, "Original player %u has size %u, starts in segment #%hu, and has segment capacity flags %x.", i, old_player_obj.size, static_cast<segnum_t>(segnum), capacity_flag); |
||
278 | if (capacity_flag) |
||
279 | ++segments_with_spare_capacity; |
||
280 | } |
||
281 | if (!segments_with_spare_capacity) |
||
282 | return preplaced_starts; |
||
283 | unsigned k = preplaced_starts; |
||
284 | for (unsigned old_player_idx = -1, side = WRIGHT; ++ old_player_idx != preplaced_starts || (old_player_idx = 0, side ++ != WBACK);) |
||
285 | { |
||
286 | auto &old_player_ref = *Players.vcptr(old_player_idx); |
||
287 | const auto &&old_player_ptridx = Objects.vcptridx(old_player_ref.objnum); |
||
288 | auto &old_player_obj = *old_player_ptridx; |
||
289 | if (player_init_segment_capacity_flag[old_player_idx] & (1 << side)) |
||
290 | { |
||
291 | auto &&segp = vmsegptridx(old_player_obj.segnum); |
||
292 | /* Copy the start exactly. The next loop will fix the |
||
293 | * collisions caused by placing the clone on top of the |
||
294 | * original. |
||
295 | * |
||
296 | * Currently, there is no handling for the case that the |
||
297 | * level author already put two players too close together. |
||
298 | * If this is a problem, more logic can be added to suppress |
||
299 | * cloning in that case. |
||
300 | */ |
||
301 | const auto &&extra_player_ptridx = obj_create_copy(old_player_obj, segp); |
||
302 | if (extra_player_ptridx == object_none) |
||
303 | { |
||
304 | con_printf(CON_URGENT, "%s:%u: warning: failed to copy start object %hu", __FILE__, __LINE__, old_player_ptridx.get_unchecked_index()); |
||
305 | continue; |
||
306 | } |
||
307 | Players.vmptr(k)->objnum = extra_player_ptridx; |
||
308 | auto &extra_player_obj = *extra_player_ptridx; |
||
309 | set_player_id(extra_player_obj, k); |
||
310 | con_printf(CON_NORMAL, "Copied player %u (object %hu at {%i, %i, %i}) to create player %u (object %hu).", old_player_idx, old_player_ptridx.get_unchecked_index(), old_player_obj.pos.x, old_player_obj.pos.y, old_player_obj.pos.z, k, extra_player_ptridx.get_unchecked_index()); |
||
311 | if (++ k >= total_required_num_starts) |
||
312 | break; |
||
313 | } |
||
314 | } |
||
315 | for (unsigned old_player_idx = 0; old_player_idx < preplaced_starts; ++old_player_idx) |
||
316 | { |
||
317 | auto &old_player_init = Player_init[old_player_idx]; |
||
318 | const auto old_player_pos = old_player_init.pos; |
||
319 | auto &old_player_obj = *vmobjptr(Players.vcptr(old_player_idx)->objnum); |
||
320 | std::array<vms_vector, 3> vec_displacement{}; |
||
321 | DXX_MAKE_VAR_UNDEFINED(vec_displacement); |
||
322 | const shared_segment &seg = *vcsegptr(old_player_init.segnum); |
||
323 | /* For each of [right, bottom, back], compute the vector between |
||
324 | * the center of that side and the reference player's start |
||
325 | * point. This will be used in the next loop. |
||
326 | */ |
||
327 | for (unsigned side = WRIGHT; side != WBACK + 1; ++side) |
||
328 | { |
||
329 | const auto &¢er_on_side = compute_center_point_on_side(vcvertptr, seg, side); |
||
330 | const auto &&vec_pos_to_center_on_side = vm_vec_sub(center_on_side, old_player_init.pos); |
||
331 | const unsigned idxside = side - WRIGHT; |
||
332 | assert(idxside < vec_displacement.size()); |
||
333 | vec_displacement[idxside] = vec_pos_to_center_on_side; |
||
334 | } |
||
335 | const auto displace_player = [&](const unsigned plridx, object_base &plrobj, const unsigned displacement_direction) { |
||
336 | vms_vector disp{}; |
||
337 | unsigned dimensions = 0; |
||
338 | for (unsigned i = 0, side = WRIGHT; side != WBACK + 1; ++side, ++i) |
||
339 | { |
||
340 | if (!(player_init_segment_capacity_flag[old_player_idx] & (1 << side))) |
||
341 | { |
||
342 | con_printf(CON_NORMAL, "Cannot displace player %u at {%i, %i, %i}: not enough room in dimension %u.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, side); |
||
343 | continue; |
||
344 | } |
||
345 | const auto &v = vec_displacement[i]; |
||
346 | const auto &va = (displacement_direction & (1 << i)) ? v : vm_vec_negated(v); |
||
347 | con_printf(CON_NORMAL, "Add displacement of {%i, %i, %i} for dimension %u for player %u.", va.x, va.y, va.z, side, plridx); |
||
348 | ++ dimensions; |
||
349 | vm_vec_add2(disp, va); |
||
350 | } |
||
351 | if (!dimensions) |
||
352 | return; |
||
353 | vm_vec_normalize(disp); |
||
354 | vm_vec_scale(disp, fixmul(old_player_obj.size, size_scalar >> 1)); |
||
355 | const auto target_position = vm_vec_add(Player_init[plridx].pos, disp); |
||
356 | if (const auto sidemask = get_seg_masks(vcvertptr, target_position, vcsegptr(plrobj.segnum), 1).sidemask) |
||
357 | { |
||
358 | con_printf(CON_NORMAL, "Cannot displace player %u at {%i, %i, %i} to {%i, %i, %i}: would be outside segment for sides %x.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, target_position.x, target_position.y, target_position.z, sidemask); |
||
359 | return; |
||
360 | } |
||
361 | con_printf(CON_NORMAL, "Displace player %u at {%i, %i, %i} by {%i, %i, %i} to {%i, %i, %i}.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, disp.x, disp.y, disp.z, target_position.x, target_position.y, target_position.z); |
||
362 | Player_init[plridx].pos = target_position; |
||
363 | plrobj.pos = Player_init[plridx].pos; |
||
364 | }; |
||
365 | for (unsigned extra_player_idx = preplaced_starts, displacements = 0; extra_player_idx < k; ++extra_player_idx) |
||
366 | { |
||
367 | auto &extra_player_obj = *vmobjptr(Players.vcptr(extra_player_idx)->objnum); |
||
368 | if (old_player_pos != extra_player_obj.pos) |
||
369 | /* This clone is associated with some other player. |
||
370 | * Skip it here. It will be handled in a different pass |
||
371 | * of the loop. |
||
372 | */ |
||
373 | continue; |
||
374 | auto &extra_player_init = Player_init[extra_player_idx]; |
||
375 | extra_player_init = old_player_init; |
||
376 | if (!displacements++) |
||
377 | displace_player(old_player_idx, old_player_obj, 0); |
||
378 | displace_player(extra_player_idx, extra_player_obj, displacements); |
||
379 | } |
||
380 | } |
||
381 | return k; |
||
382 | } |
||
383 | |||
384 | //added 10/12/95: delete buddy bot if coop game. Probably doesn't really belong here. -MT |
||
385 | static void gameseq_init_network_players(object_array &Objects) |
||
386 | { |
||
387 | |||
388 | // Initialize network player start locations and object numbers |
||
389 | |||
390 | ConsoleObject = &Objects.front(); |
||
391 | unsigned j = 0, k = 0; |
||
392 | const auto multiplayer_coop = Game_mode & GM_MULTI_COOP; |
||
393 | #if defined(DXX_BUILD_DESCENT_II) |
||
394 | const auto remove_thief = Netgame.ThiefModifierFlags & ThiefModifier::Absent; |
||
395 | const auto multiplayer = Game_mode & GM_MULTI; |
||
396 | const auto retain_guidebot = Netgame.AllowGuidebot; |
||
397 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
398 | #endif |
||
399 | auto &vmobjptridx = Objects.vmptridx; |
||
400 | range_for (const auto &&o, vmobjptridx) |
||
401 | { |
||
402 | const auto type = o->type; |
||
403 | if (type == OBJ_PLAYER || type == OBJ_GHOST || type == OBJ_COOP) |
||
404 | { |
||
405 | if (likely(k < Player_init.size()) && |
||
406 | multiplayer_coop |
||
407 | ? (j == 0 || type == OBJ_COOP) |
||
408 | : (type == OBJ_PLAYER || type == OBJ_GHOST) |
||
409 | ) |
||
410 | { |
||
411 | o->type=OBJ_PLAYER; |
||
412 | auto &pi = Player_init[k]; |
||
413 | pi.pos = o->pos; |
||
414 | pi.orient = o->orient; |
||
415 | pi.segnum = o->segnum; |
||
416 | vmplayerptr(k)->objnum = o; |
||
417 | set_player_id(o, k); |
||
418 | k++; |
||
419 | } |
||
420 | else |
||
421 | obj_delete(LevelUniqueObjectState, Segments, o); |
||
422 | j++; |
||
423 | } |
||
424 | #if defined(DXX_BUILD_DESCENT_II) |
||
425 | else if (type == OBJ_ROBOT && multiplayer) |
||
426 | { |
||
427 | auto &ri = Robot_info[get_robot_id(o)]; |
||
428 | if ((!retain_guidebot && robot_is_companion(ri)) || |
||
429 | (remove_thief && robot_is_thief(ri))) |
||
430 | { |
||
431 | object_create_robot_egg(o); |
||
432 | obj_delete(LevelUniqueObjectState, Segments, o); //kill the buddy in netgames |
||
433 | } |
||
434 | } |
||
435 | #endif |
||
436 | } |
||
437 | if (multiplayer_coop) |
||
438 | { |
||
439 | const unsigned total_required_num_starts = Netgame.max_numplayers; |
||
440 | if (k < total_required_num_starts) |
||
441 | { |
||
442 | con_printf(CON_NORMAL, "Insufficient cooperative starts found in mission \"%s\" level %u (need %u, found %u). Generating extra starts...", Current_mission->path.c_str(), Current_level_num, total_required_num_starts, k); |
||
443 | /* |
||
444 | * First, try displacing the starts within the existing segment. |
||
445 | */ |
||
446 | const unsigned preplaced_starts = k; |
||
447 | k = generate_extra_starts_by_displacement_within_segment(preplaced_starts, total_required_num_starts); |
||
448 | con_printf(CON_NORMAL, "Generated %u starts by displacement within the original segment.", k - preplaced_starts); |
||
449 | } |
||
450 | else |
||
451 | con_printf(CON_NORMAL, "Found %u cooperative starts in mission \"%s\" level %u.", k, Current_mission->path.c_str(), Current_level_num); |
||
452 | } |
||
453 | NumNetPlayerPositions = k; |
||
454 | } |
||
455 | |||
456 | void gameseq_remove_unused_players() |
||
457 | { |
||
458 | auto &Objects = LevelUniqueObjectState.Objects; |
||
459 | auto &vmobjptridx = Objects.vmptridx; |
||
460 | // 'Remove' the unused players |
||
461 | |||
462 | if (Game_mode & GM_MULTI) |
||
463 | { |
||
464 | for (unsigned i = 0; i < NumNetPlayerPositions; ++i) |
||
465 | { |
||
466 | if ((!vcplayerptr(i)->connected) || (i >= N_players)) |
||
467 | { |
||
468 | multi_make_player_ghost(i); |
||
469 | } |
||
470 | } |
||
471 | } |
||
472 | else |
||
473 | { // Note link to above if!!! |
||
474 | range_for (auto &i, partial_const_range(Players, 1u, NumNetPlayerPositions)) |
||
475 | { |
||
476 | obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(i.objnum)); |
||
477 | } |
||
478 | #if defined(DXX_BUILD_DESCENT_II) |
||
479 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
480 | if (PlayerCfg.ThiefModifierFlags & ThiefModifier::Absent) |
||
481 | { |
||
482 | range_for (const auto &&o, vmobjptridx) |
||
483 | { |
||
484 | const auto type = o->type; |
||
485 | if (type == OBJ_ROBOT) |
||
486 | { |
||
487 | auto &ri = Robot_info[get_robot_id(o)]; |
||
488 | if (robot_is_thief(ri)) |
||
489 | { |
||
490 | object_create_robot_egg(o); |
||
491 | obj_delete(LevelUniqueObjectState, Segments, o); |
||
492 | } |
||
493 | } |
||
494 | } |
||
495 | } |
||
496 | #endif |
||
497 | } |
||
498 | } |
||
499 | |||
500 | // Setup player for new game |
||
501 | void init_player_stats_game(const playernum_t pnum) |
||
502 | { |
||
503 | auto &Objects = LevelUniqueObjectState.Objects; |
||
504 | auto &vmobjptr = Objects.vmptr; |
||
505 | auto &plr = *vmplayerptr(pnum); |
||
506 | plr.lives = INITIAL_LIVES; |
||
507 | plr.level = 1; |
||
508 | plr.time_level = 0; |
||
509 | plr.time_total = 0; |
||
510 | plr.hours_level = 0; |
||
511 | plr.hours_total = 0; |
||
512 | plr.num_kills_level = 0; |
||
513 | plr.num_kills_total = 0; |
||
514 | const auto &&plobj = vmobjptr(plr.objnum); |
||
515 | auto &player_info = plobj->ctype.player_info; |
||
516 | player_info.powerup_flags = {}; |
||
517 | player_info.net_killed_total = 0; |
||
518 | player_info.net_kills_total = 0; |
||
519 | player_info.KillGoalCount = 0; |
||
520 | player_info.mission.score = 0; |
||
521 | player_info.mission.last_score = 0; |
||
522 | player_info.mission.hostages_rescued_total = 0; |
||
523 | |||
524 | init_player_stats_new_ship(pnum); |
||
525 | #if defined(DXX_BUILD_DESCENT_II) |
||
526 | if (pnum == Player_num) |
||
527 | First_secret_visit = 1; |
||
528 | #endif |
||
529 | } |
||
530 | |||
531 | static void init_ammo_and_energy(object &plrobj) |
||
532 | { |
||
533 | auto &player_info = plrobj.ctype.player_info; |
||
534 | { |
||
535 | auto &energy = player_info.energy; |
||
536 | #if defined(DXX_BUILD_DESCENT_II) |
||
537 | if (player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(OMEGA_INDEX)) |
||
538 | { |
||
539 | const auto old_omega_charge = player_info.Omega_charge; |
||
540 | if (old_omega_charge < MAX_OMEGA_CHARGE) |
||
541 | { |
||
542 | const auto energy_used = get_omega_energy_consumption((player_info.Omega_charge = MAX_OMEGA_CHARGE) - old_omega_charge); |
||
543 | energy -= energy_used; |
||
544 | } |
||
545 | } |
||
546 | #endif |
||
547 | if (energy < INITIAL_ENERGY) |
||
548 | energy = INITIAL_ENERGY; |
||
549 | } |
||
550 | { |
||
551 | auto &shields = plrobj.shields; |
||
552 | if (shields < StartingShields) |
||
553 | shields = StartingShields; |
||
554 | } |
||
555 | const unsigned minimum_missiles = get_starting_concussion_missile_count(); |
||
556 | auto &concussion = player_info.secondary_ammo[CONCUSSION_INDEX]; |
||
557 | if (concussion < minimum_missiles) |
||
558 | concussion = minimum_missiles; |
||
559 | } |
||
560 | |||
561 | #if defined(DXX_BUILD_DESCENT_II) |
||
562 | extern ubyte Last_afterburner_state; |
||
563 | #endif |
||
564 | |||
565 | // Setup player for new level (After completion of previous level) |
||
566 | static void init_player_stats_level(player &plr, object &plrobj, const secret_restore secret_flag) |
||
567 | { |
||
568 | #if defined(DXX_BUILD_DESCENT_II) |
||
569 | auto &Objects = LevelUniqueObjectState.Objects; |
||
570 | auto &vcobjptridx = Objects.vcptridx; |
||
571 | #endif |
||
572 | |||
573 | plr.level = Current_level_num; |
||
574 | |||
575 | if (!Network_rejoined) { |
||
576 | plr.time_level = 0; |
||
577 | plr.hours_level = 0; |
||
578 | } |
||
579 | |||
580 | auto &player_info = plrobj.ctype.player_info; |
||
581 | player_info.mission.last_score = player_info.mission.score; |
||
582 | |||
583 | plr.num_kills_level = 0; |
||
584 | |||
585 | if (secret_flag == secret_restore::none) { |
||
586 | init_ammo_and_energy(plrobj); |
||
587 | |||
588 | auto &powerup_flags = player_info.powerup_flags; |
||
589 | powerup_flags &= ~(PLAYER_FLAGS_INVULNERABLE | PLAYER_FLAGS_CLOAKED); |
||
590 | #if defined(DXX_BUILD_DESCENT_II) |
||
591 | powerup_flags &= ~(PLAYER_FLAGS_MAP_ALL); |
||
592 | #endif |
||
593 | |||
594 | DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time); |
||
595 | DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time); |
||
596 | |||
597 | const auto all_keys = PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY; |
||
598 | if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)) |
||
599 | powerup_flags |= all_keys; |
||
600 | else |
||
601 | powerup_flags &= ~all_keys; |
||
602 | } |
||
603 | |||
604 | Player_dead_state = player_dead_state::no; // Added by RH |
||
605 | Dead_player_camera = NULL; |
||
606 | |||
607 | #if defined(DXX_BUILD_DESCENT_II) |
||
608 | Controls.state.afterburner = 0; |
||
609 | Last_afterburner_state = 0; |
||
610 | |||
611 | digi_kill_sound_linked_to_object(vcobjptridx(plr.objnum)); |
||
612 | #endif |
||
613 | init_gauges(); |
||
614 | #if defined(DXX_BUILD_DESCENT_II) |
||
615 | Missile_viewer = NULL; |
||
616 | #endif |
||
617 | init_player_stats_ship(plrobj, GameTime64); |
||
618 | } |
||
619 | |||
620 | // Setup player for a brand-new ship |
||
621 | void init_player_stats_new_ship(const playernum_t pnum) |
||
622 | { |
||
623 | auto &Objects = LevelUniqueObjectState.Objects; |
||
624 | auto &vmobjptridx = Objects.vmptridx; |
||
625 | auto &plr = *vcplayerptr(pnum); |
||
626 | const auto &&plrobj = vmobjptridx(plr.objnum); |
||
627 | plrobj->shields = StartingShields; |
||
628 | auto &player_info = plrobj->ctype.player_info; |
||
629 | player_info.energy = INITIAL_ENERGY; |
||
630 | player_info.secondary_ammo = {{ |
||
631 | static_cast<uint8_t>(get_starting_concussion_missile_count()) |
||
632 | }}; |
||
633 | const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0; |
||
634 | player_info.vulcan_ammo = map_granted_flags_to_vulcan_ammo(GrantedItems); |
||
635 | const auto granted_laser_level = map_granted_flags_to_laser_level(GrantedItems); |
||
636 | player_info.laser_level = granted_laser_level; |
||
637 | const auto granted_primary_weapon_flags = HAS_LASER_FLAG | map_granted_flags_to_primary_weapon_flags(GrantedItems); |
||
638 | player_info.primary_weapon_flags = granted_primary_weapon_flags; |
||
639 | player_info.powerup_flags &= ~(PLAYER_FLAGS_QUAD_LASERS | PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE); |
||
640 | #if defined(DXX_BUILD_DESCENT_II) |
||
641 | player_info.powerup_flags &= ~(PLAYER_FLAGS_AFTERBURNER | PLAYER_FLAGS_MAP_ALL | PLAYER_FLAGS_CONVERTER | PLAYER_FLAGS_AMMO_RACK | PLAYER_FLAGS_HEADLIGHT | PLAYER_FLAGS_HEADLIGHT_ON | PLAYER_FLAGS_FLAG); |
||
642 | player_info.Omega_charge = (granted_primary_weapon_flags & HAS_OMEGA_FLAG) |
||
643 | ? MAX_OMEGA_CHARGE |
||
644 | : 0; |
||
645 | player_info.Omega_recharge_delay = 0; |
||
646 | #endif |
||
647 | player_info.powerup_flags |= map_granted_flags_to_player_flags(GrantedItems); |
||
648 | DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time); |
||
649 | DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time); |
||
650 | if (pnum == Player_num) |
||
651 | { |
||
652 | if (Game_mode & GM_MULTI && Netgame.InvulAppear) |
||
653 | { |
||
654 | player_info.powerup_flags |= PLAYER_FLAGS_INVULNERABLE; |
||
655 | player_info.invulnerable_time = GameTime64 - (i2f(58 - Netgame.InvulAppear) >> 1); |
||
656 | player_info.FakingInvul = 1; |
||
657 | } |
||
658 | set_primary_weapon(player_info, [=]{ |
||
659 | range_for (auto i, PlayerCfg.PrimaryOrder) |
||
660 | { |
||
661 | if (i >= MAX_PRIMARY_WEAPONS) |
||
662 | break; |
||
663 | if (i == primary_weapon_index_t::LASER_INDEX) |
||
664 | break; |
||
665 | #if defined(DXX_BUILD_DESCENT_II) |
||
666 | if (i == primary_weapon_index_t::SUPER_LASER_INDEX) |
||
667 | { |
||
668 | if (granted_laser_level <= LASER_LEVEL_4) |
||
669 | /* Granted lasers are not super lasers */ |
||
670 | continue; |
||
671 | /* Super lasers still set LASER_INDEX, not |
||
672 | * SUPER_LASER_INDEX |
||
673 | */ |
||
674 | break; |
||
675 | } |
||
676 | #endif |
||
677 | if (HAS_PRIMARY_FLAG(i) & static_cast<unsigned>(granted_primary_weapon_flags)) |
||
678 | return static_cast<primary_weapon_index_t>(i); |
||
679 | } |
||
680 | return primary_weapon_index_t::LASER_INDEX; |
||
681 | }()); |
||
682 | #if defined(DXX_BUILD_DESCENT_II) |
||
683 | auto primary_last_was_super = player_info.Primary_last_was_super; |
||
684 | for (uint_fast32_t i = primary_weapon_index_t::VULCAN_INDEX, mask = 1 << i; i != primary_weapon_index_t::SUPER_LASER_INDEX; ++i, mask <<= 1) |
||
685 | { |
||
686 | /* If no super granted, force to non-super. */ |
||
687 | if (!(HAS_PRIMARY_FLAG(i + 5) & granted_primary_weapon_flags)) |
||
688 | primary_last_was_super &= ~mask; |
||
689 | /* If only super granted, force to super. */ |
||
690 | else if (!(HAS_PRIMARY_FLAG(i) & granted_primary_weapon_flags)) |
||
691 | primary_last_was_super |= mask; |
||
692 | /* else both granted, so leave as-is. */ |
||
693 | else |
||
694 | continue; |
||
695 | } |
||
696 | player_info.Primary_last_was_super = primary_last_was_super; |
||
697 | #endif |
||
698 | if (Newdemo_state == ND_STATE_RECORDING) |
||
699 | { |
||
700 | newdemo_record_laser_level(player_info.laser_level, 0); |
||
701 | } |
||
702 | set_secondary_weapon_to_concussion(player_info); |
||
703 | dead_player_end(); //player no longer dead |
||
704 | Player_dead_state = player_dead_state::no; |
||
705 | player_info.Player_eggs_dropped = false; |
||
706 | Dead_player_camera = 0; |
||
707 | #if defined(DXX_BUILD_DESCENT_II) |
||
708 | auto &Secondary_last_was_super = player_info.Secondary_last_was_super; |
||
709 | Secondary_last_was_super = {}; |
||
710 | Afterburner_charge = GrantedItems.has_afterburner() ? F1_0 : 0; |
||
711 | Controls.state.afterburner = 0; |
||
712 | Last_afterburner_state = 0; |
||
713 | Missile_viewer = nullptr; //reset missile camera if out there |
||
714 | #endif |
||
715 | init_ai_for_ship(); |
||
716 | } |
||
717 | digi_kill_sound_linked_to_object(plrobj); |
||
718 | init_player_stats_ship(plrobj, GameTime64); |
||
719 | } |
||
720 | |||
721 | void init_player_stats_ship(object &plrobj, const fix GameTime64) |
||
722 | { |
||
723 | auto &player_info = plrobj.ctype.player_info; |
||
724 | player_info.lavafall_hiss_playing = false; |
||
725 | player_info.missile_gun = 0; |
||
726 | player_info.Spreadfire_toggle = 0; |
||
727 | player_info.killer_objnum = object_none; |
||
728 | #if defined(DXX_BUILD_DESCENT_II) |
||
729 | player_info.Omega_recharge_delay = 0; |
||
730 | player_info.Helix_orientation = 0; |
||
731 | #endif |
||
732 | player_info.mission.hostages_on_board = 0; |
||
733 | player_info.homing_object_dist = -F1_0; // Added by RH |
||
734 | player_info.Next_flare_fire_time = player_info.Next_laser_fire_time = player_info.Next_missile_fire_time = GameTime64; |
||
735 | } |
||
736 | |||
737 | } |
||
738 | |||
739 | //do whatever needs to be done when a player dies in multiplayer |
||
740 | |||
741 | static void DoGameOver() |
||
742 | { |
||
743 | if (PLAYING_BUILTIN_MISSION) |
||
744 | scores_maybe_add_player(); |
||
745 | } |
||
746 | |||
747 | //update various information about the player |
||
748 | void update_player_stats() |
||
749 | { |
||
750 | auto &plr = get_local_player(); |
||
751 | plr.time_level += FrameTime; //the never-ending march of time... |
||
752 | if (plr.time_level > i2f(3600)) |
||
753 | { |
||
754 | plr.time_level -= i2f(3600); |
||
755 | ++ plr.hours_level; |
||
756 | } |
||
757 | |||
758 | plr.time_total += FrameTime; //the never-ending march of time... |
||
759 | if (plr.time_total > i2f(3600)) |
||
760 | { |
||
761 | plr.time_total -= i2f(3600); |
||
762 | ++ plr.hours_total; |
||
763 | } |
||
764 | } |
||
765 | |||
766 | //go through this level and start any eclip sounds |
||
767 | namespace dsx { |
||
768 | |||
769 | static void set_sound_sources(fvcsegptridx &vcsegptridx, fvcvertptr &vcvertptr) |
||
770 | { |
||
771 | auto &Effects = LevelUniqueEffectsClipState.Effects; |
||
772 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
773 | digi_init_sounds(); //clear old sounds |
||
774 | #if defined(DXX_BUILD_DESCENT_II) |
||
775 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
776 | auto &vcwallptr = Walls.vcptr; |
||
777 | #endif |
||
778 | Dont_start_sound_objects = 1; |
||
779 | |||
780 | const auto get_eclip_for_tmap = [](const d_level_unique_tmap_info_state::TmapInfo_array &TmapInfo, const unique_side &side) { |
||
781 | if (const auto tm2 = side.tmap_num2) |
||
782 | { |
||
783 | const auto ec = TmapInfo[tm2 & 0x3fff].eclip_num; |
||
784 | #if defined(DXX_BUILD_DESCENT_II) |
||
785 | if (ec != eclip_none) |
||
786 | #endif |
||
787 | return ec; |
||
788 | } |
||
789 | #if defined(DXX_BUILD_DESCENT_I) |
||
790 | return eclip_none.value; |
||
791 | #elif defined(DXX_BUILD_DESCENT_II) |
||
792 | return TmapInfo[side.tmap_num].eclip_num; |
||
793 | #endif |
||
794 | }; |
||
795 | |||
796 | range_for (const auto &&seg, vcsegptridx) |
||
797 | { |
||
798 | range_for (const uint_fast32_t sidenum, xrange(MAX_SIDES_PER_SEGMENT)) |
||
799 | { |
||
800 | int sn; |
||
801 | |||
802 | #if defined(DXX_BUILD_DESCENT_II) |
||
803 | const auto wid = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sidenum); |
||
804 | if (!(wid & WID_RENDER_FLAG)) |
||
805 | continue; |
||
806 | #endif |
||
807 | const auto ec = get_eclip_for_tmap(TmapInfo, seg->unique_segment::sides[sidenum]); |
||
808 | if (ec != eclip_none) |
||
809 | { |
||
810 | if ((sn=Effects[ec].sound_num)!=-1) { |
||
811 | #if defined(DXX_BUILD_DESCENT_II) |
||
812 | auto csegnum = seg->children[sidenum]; |
||
813 | |||
814 | //check for sound on other side of wall. Don't add on |
||
815 | //both walls if sound travels through wall. If sound |
||
816 | //does travel through wall, add sound for lower-numbered |
||
817 | //segment. |
||
818 | |||
819 | if (IS_CHILD(csegnum) && csegnum < seg) { |
||
820 | if (wid & (WID_FLY_FLAG|WID_RENDPAST_FLAG)) { |
||
821 | const auto &&csegp = vcsegptr(seg->children[sidenum]); |
||
822 | auto csidenum = find_connect_side(seg, csegp); |
||
823 | |||
824 | if (csegp->unique_segment::sides[csidenum].tmap_num2 == seg->unique_segment::sides[sidenum].tmap_num2) |
||
825 | continue; //skip this one |
||
826 | } |
||
827 | } |
||
828 | #endif |
||
829 | |||
830 | const auto &&pnt = compute_center_point_on_side(vcvertptr, seg, sidenum); |
||
831 | digi_link_sound_to_pos(sn, seg, sidenum, pnt, 1, F1_0/2); |
||
832 | } |
||
833 | } |
||
834 | } |
||
835 | } |
||
836 | Dont_start_sound_objects = 0; |
||
837 | } |
||
838 | |||
839 | constexpr fix flash_dist=fl2f(.9); |
||
840 | |||
841 | //create flash for player appearance |
||
842 | void create_player_appearance_effect(const d_vclip_array &Vclip, const object_base &player_obj) |
||
843 | { |
||
844 | const auto pos = (&player_obj == Viewer) |
||
845 | ? vm_vec_scale_add(player_obj.pos, player_obj.orient.fvec, fixmul(player_obj.size, flash_dist)) |
||
846 | : player_obj.pos; |
||
847 | |||
848 | const auto &&seg = vmsegptridx(player_obj.segnum); |
||
849 | const auto &&effect_obj = object_create_explosion(seg, pos, player_obj.size, VCLIP_PLAYER_APPEARANCE); |
||
850 | |||
851 | if (effect_obj) { |
||
852 | effect_obj->orient = player_obj.orient; |
||
853 | |||
854 | const auto sound_num = Vclip[VCLIP_PLAYER_APPEARANCE].sound_num; |
||
855 | if (sound_num > -1) |
||
856 | digi_link_sound_to_pos(sound_num, seg, 0, effect_obj->pos, 0, F1_0); |
||
857 | } |
||
858 | } |
||
859 | } |
||
860 | |||
861 | // |
||
862 | // New Game sequencing functions |
||
863 | // |
||
864 | |||
865 | //get level filename. level numbers start at 1. Secret levels are -1,-2,-3 |
||
866 | static const d_fname &get_level_file(int level_num) |
||
867 | { |
||
868 | if (level_num<0) //secret level |
||
869 | return Secret_level_names[-level_num-1]; |
||
870 | else //normal level |
||
871 | return Level_names[level_num-1]; |
||
872 | } |
||
873 | |||
874 | // routine to calculate the checksum of the segments. |
||
875 | static void do_checksum_calc(const uint8_t *b, int len, unsigned int *s1, unsigned int *s2) |
||
876 | { |
||
877 | |||
878 | while(len--) { |
||
879 | *s1 += *b++; |
||
880 | if (*s1 >= 255) *s1 -= 255; |
||
881 | *s2 += *s1; |
||
882 | } |
||
883 | } |
||
884 | |||
885 | namespace dsx { |
||
886 | static ushort netmisc_calc_checksum() |
||
887 | { |
||
888 | unsigned int sum1,sum2; |
||
889 | short s; |
||
890 | int t; |
||
891 | |||
892 | sum1 = sum2 = 0; |
||
893 | range_for (auto &&segp, vcsegptr) |
||
894 | { |
||
895 | const cscusegment i = *segp; |
||
896 | for (auto &&[sside, uside] : zip(i.s.sides, i.u.sides)) |
||
897 | { |
||
898 | do_checksum_calc(reinterpret_cast<const uint8_t *>(&(sside.get_type())), 1, &sum1, &sum2); |
||
899 | s = INTEL_SHORT(sside.wall_num); |
||
900 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
901 | s = INTEL_SHORT(uside.tmap_num); |
||
902 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
903 | s = INTEL_SHORT(uside.tmap_num2); |
||
904 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
905 | range_for (auto &k, uside.uvls) |
||
906 | { |
||
907 | t = INTEL_INT(k.u); |
||
908 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
909 | t = INTEL_INT(k.v); |
||
910 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
911 | t = INTEL_INT(k.l); |
||
912 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
913 | } |
||
914 | range_for (auto &k, sside.normals) |
||
915 | { |
||
916 | t = INTEL_INT(k.x); |
||
917 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
918 | t = INTEL_INT(k.y); |
||
919 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
920 | t = INTEL_INT(k.z); |
||
921 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
922 | } |
||
923 | } |
||
924 | range_for (auto &j, i.s.children) |
||
925 | { |
||
926 | s = INTEL_SHORT(j); |
||
927 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
928 | } |
||
929 | range_for (const uint16_t j, i.s.verts) |
||
930 | { |
||
931 | s = INTEL_SHORT(j); |
||
932 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
933 | } |
||
934 | s = INTEL_SHORT(i.u.objects); |
||
935 | do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2); |
||
936 | #if defined(DXX_BUILD_DESCENT_I) |
||
937 | do_checksum_calc(&i.s.special, 1, &sum1, &sum2); |
||
938 | do_checksum_calc(reinterpret_cast<const uint8_t *>(&i.s.matcen_num), 1, &sum1, &sum2); |
||
939 | t = INTEL_INT(i.u.static_light); |
||
940 | do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2); |
||
941 | #endif |
||
942 | do_checksum_calc(&i.s.station_idx, 1, &sum1, &sum2); |
||
943 | } |
||
944 | sum2 %= 255; |
||
945 | return ((sum1<<8)+ sum2); |
||
946 | } |
||
947 | } |
||
948 | |||
949 | #if defined(DXX_BUILD_DESCENT_II) |
||
950 | namespace dsx { |
||
951 | // load just the hxm file |
||
952 | void load_level_robots(int level_num) |
||
953 | { |
||
954 | Assert(level_num <= Last_level && level_num >= Last_secret_level && level_num != 0); |
||
955 | const d_fname &level_name = get_level_file(level_num); |
||
956 | if (Robot_replacements_loaded) { |
||
957 | free_polygon_models(); |
||
958 | load_mission_ham(); |
||
959 | Robot_replacements_loaded = 0; |
||
960 | } |
||
961 | load_robot_replacements(level_name); |
||
962 | } |
||
963 | } |
||
964 | #endif |
||
965 | |||
966 | //load a level off disk. level numbers start at 1. Secret levels are -1,-2,-3 |
||
967 | namespace dsx { |
||
968 | void LoadLevel(int level_num,int page_in_textures) |
||
969 | { |
||
970 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
971 | auto &Objects = LevelUniqueObjectState.Objects; |
||
972 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
973 | auto &vcobjptr = Objects.vcptr; |
||
974 | auto &vmobjptr = Objects.vmptr; |
||
975 | preserve_player_object_info p(vcobjptr, vcplayerptr(Player_num)->objnum); |
||
976 | |||
977 | auto &plr = get_local_player(); |
||
978 | auto save_player = plr; |
||
979 | |||
980 | Assert(level_num <= Last_level && level_num >= Last_secret_level && level_num != 0); |
||
981 | const d_fname &level_name = get_level_file(level_num); |
||
982 | #if defined(DXX_BUILD_DESCENT_I) |
||
983 | if (!load_level(level_name)) |
||
984 | Current_level_num=level_num; |
||
985 | |||
986 | gr_use_palette_table( "palette.256" ); |
||
987 | #elif defined(DXX_BUILD_DESCENT_II) |
||
988 | gr_set_default_canvas(); |
||
989 | gr_clear_canvas(*grd_curcanv, BM_XRGB(0, 0, 0)); //so palette switching is less obvious |
||
990 | |||
991 | load_level_robots(level_num); |
||
992 | |||
993 | auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights; |
||
994 | int load_ret = load_level(LevelSharedDestructibleLightState, level_name); //actually load the data from disk! |
||
995 | |||
996 | if (load_ret) |
||
997 | Error("Could not load level file <%s>, error = %d",static_cast<const char *>(level_name),load_ret); |
||
998 | |||
999 | Current_level_num=level_num; |
||
1000 | |||
1001 | load_palette(Current_level_palette,1,1); //don't change screen |
||
1002 | #endif |
||
1003 | |||
1004 | #if DXX_USE_EDITOR |
||
1005 | if (!EditorWindow) |
||
1006 | #endif |
||
1007 | show_boxed_message(TXT_LOADING, 0); |
||
1008 | #ifdef RELEASE |
||
1009 | timer_delay(F1_0); |
||
1010 | #endif |
||
1011 | |||
1012 | load_endlevel_data(level_num); |
||
1013 | #if defined(DXX_BUILD_DESCENT_I) |
||
1014 | load_custom_data(level_name); |
||
1015 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1016 | if (EMULATING_D1) |
||
1017 | load_d1_bitmap_replacements(); |
||
1018 | else |
||
1019 | load_bitmap_replacements(level_name); |
||
1020 | |||
1021 | if ( page_in_textures ) |
||
1022 | piggy_load_level_data(); |
||
1023 | #endif |
||
1024 | |||
1025 | my_segments_checksum = netmisc_calc_checksum(); |
||
1026 | |||
1027 | reset_network_objects(); |
||
1028 | |||
1029 | plr = save_player; |
||
1030 | |||
1031 | auto &vcvertptr = Vertices.vcptr; |
||
1032 | set_sound_sources(vcsegptridx, vcvertptr); |
||
1033 | |||
1034 | #if DXX_USE_EDITOR |
||
1035 | if (!EditorWindow) |
||
1036 | #endif |
||
1037 | songs_play_level_song( Current_level_num, 0 ); |
||
1038 | |||
1039 | gr_palette_load(gr_palette); //actually load the palette |
||
1040 | #if defined(DXX_BUILD_DESCENT_I) |
||
1041 | if ( page_in_textures ) |
||
1042 | piggy_load_level_data(); |
||
1043 | #endif |
||
1044 | |||
1045 | gameseq_init_network_players(Objects); |
||
1046 | p.restore(vmobjptr); |
||
1047 | } |
||
1048 | } |
||
1049 | |||
1050 | //sets up Player_num & ConsoleObject |
||
1051 | void InitPlayerObject() |
||
1052 | { |
||
1053 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1054 | auto &vmobjptr = Objects.vmptr; |
||
1055 | Assert(Player_num<MAX_PLAYERS); |
||
1056 | |||
1057 | if (Player_num != 0 ) { |
||
1058 | Players[0u] = get_local_player(); |
||
1059 | Player_num = 0; |
||
1060 | } |
||
1061 | |||
1062 | auto &plr = get_local_player(); |
||
1063 | plr.objnum = object_first; |
||
1064 | const auto &&console = vmobjptr(plr.objnum); |
||
1065 | ConsoleObject = console; |
||
1066 | console->type = OBJ_PLAYER; |
||
1067 | set_player_id(console, Player_num); |
||
1068 | console->control_type = CT_FLYING; |
||
1069 | console->movement_type = MT_PHYSICS; |
||
1070 | } |
||
1071 | |||
1072 | //starts a new game on the given level |
||
1073 | namespace dsx { |
||
1074 | |||
1075 | void StartNewGame(const int start_level) |
||
1076 | { |
||
1077 | GameUniqueState.quicksave_selection = d_game_unique_state::save_slot::None; // for first blind save, pick slot to save in |
||
1078 | reset_globals_for_new_game(); |
||
1079 | |||
1080 | Game_mode = GM_NORMAL; |
||
1081 | |||
1082 | Next_level_num = 0; |
||
1083 | |||
1084 | InitPlayerObject(); //make sure player's object set up |
||
1085 | |||
1086 | LevelUniqueObjectState.accumulated_robots = 0; |
||
1087 | LevelUniqueObjectState.total_hostages = 0; |
||
1088 | GameUniqueState.accumulated_robots = 0; |
||
1089 | GameUniqueState.total_hostages = 0; |
||
1090 | init_player_stats_game(Player_num); //clear all stats |
||
1091 | |||
1092 | N_players = 1; |
||
1093 | |||
1094 | #if defined(DXX_BUILD_DESCENT_II) |
||
1095 | if (start_level < 0) |
||
1096 | { |
||
1097 | /* Allow an autosave as soon as the user exits the secret level. |
||
1098 | */ |
||
1099 | state_set_immediate_autosave(GameUniqueState); |
||
1100 | StartNewLevelSecret(start_level, 0); |
||
1101 | } |
||
1102 | else |
||
1103 | #endif |
||
1104 | { |
||
1105 | StartNewLevel(start_level); |
||
1106 | /* Override Next_autosave to avoid creating an autosave |
||
1107 | * immediately after starting a new game. No state can be lost |
||
1108 | * at that point, so there is no reason to save. |
||
1109 | */ |
||
1110 | state_set_next_autosave(GameUniqueState, PlayerCfg.SPGameplayOptions.AutosaveInterval); |
||
1111 | } |
||
1112 | |||
1113 | auto &plr = get_local_player(); |
||
1114 | plr.starting_level = start_level; // Mark where they started |
||
1115 | plr.callsign = InterfaceUniqueState.PilotName; |
||
1116 | |||
1117 | game_disable_cheats(); |
||
1118 | #if defined(DXX_BUILD_DESCENT_II) |
||
1119 | init_seismic_disturbances(); |
||
1120 | #endif |
||
1121 | } |
||
1122 | |||
1123 | // ----------------------------------------------------------------------------- |
||
1124 | // Does the bonus scoring. |
||
1125 | // Call with dead_flag = 1 if player died, but deserves some portion of bonus (only skill points), anyway. |
||
1126 | static void DoEndLevelScoreGlitz() |
||
1127 | { |
||
1128 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1129 | auto &vmobjptr = Objects.vmptr; |
||
1130 | int level_points, skill_points, energy_points, shield_points, hostage_points; |
||
1131 | #define N_GLITZITEMS 9 |
||
1132 | char m_str[N_GLITZITEMS][32]; |
||
1133 | newmenu_item m[N_GLITZITEMS]; |
||
1134 | int i,c; |
||
1135 | char title[128]; |
||
1136 | #if defined(DXX_BUILD_DESCENT_I) |
||
1137 | gr_palette_load( gr_palette ); |
||
1138 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1139 | int mine_level; |
||
1140 | |||
1141 | // Compute level player is on, deal with secret levels (negative numbers) |
||
1142 | mine_level = get_local_player().level; |
||
1143 | if (mine_level < 0) |
||
1144 | mine_level *= -(Last_level/N_secret_levels); |
||
1145 | #endif |
||
1146 | |||
1147 | auto &plrobj = get_local_plrobj(); |
||
1148 | auto &player_info = plrobj.ctype.player_info; |
||
1149 | level_points = player_info.mission.score - player_info.mission.last_score; |
||
1150 | |||
1151 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
1152 | if (!cheats.enabled) { |
||
1153 | if (Difficulty_level > 1) { |
||
1154 | #if defined(DXX_BUILD_DESCENT_I) |
||
1155 | skill_points = level_points*(Difficulty_level-1)/2; |
||
1156 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1157 | skill_points = level_points*(Difficulty_level)/4; |
||
1158 | #endif |
||
1159 | skill_points -= skill_points % 100; |
||
1160 | } else |
||
1161 | skill_points = 0; |
||
1162 | |||
1163 | hostage_points = player_info.mission.hostages_on_board * 500 * (Difficulty_level+1); |
||
1164 | #if defined(DXX_BUILD_DESCENT_I) |
||
1165 | shield_points = f2i(plrobj.shields) * 10 * (Difficulty_level+1); |
||
1166 | energy_points = f2i(player_info.energy) * 5 * (Difficulty_level+1); |
||
1167 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1168 | shield_points = f2i(plrobj.shields) * 5 * mine_level; |
||
1169 | energy_points = f2i(player_info.energy) * 2 * mine_level; |
||
1170 | |||
1171 | shield_points -= shield_points % 50; |
||
1172 | energy_points -= energy_points % 50; |
||
1173 | #endif |
||
1174 | } else { |
||
1175 | skill_points = 0; |
||
1176 | shield_points = 0; |
||
1177 | energy_points = 0; |
||
1178 | hostage_points = 0; |
||
1179 | } |
||
1180 | |||
1181 | auto &plr = get_local_player(); |
||
1182 | |||
1183 | c = 0; |
||
1184 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_SHIELD_BONUS, shield_points); // Return at start to lower menu... |
||
1185 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_ENERGY_BONUS, energy_points); |
||
1186 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_HOSTAGE_BONUS, hostage_points); |
||
1187 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_SKILL_BONUS, skill_points); |
||
1188 | |||
1189 | const unsigned hostages_on_board = player_info.mission.hostages_on_board; |
||
1190 | unsigned all_hostage_points = 0; |
||
1191 | unsigned endgame_points = 0; |
||
1192 | uint8_t is_last_level = 0; |
||
1193 | auto &hostage_text = m_str[c++]; |
||
1194 | if (cheats.enabled) |
||
1195 | snprintf(hostage_text, sizeof(hostage_text), "Hostages saved: \t%u", hostages_on_board); |
||
1196 | else if (const auto hostages_lost = LevelUniqueObjectState.total_hostages - hostages_on_board) |
||
1197 | snprintf(hostage_text, sizeof(hostage_text), "Hostages lost: \t%u", hostages_lost); |
||
1198 | else |
||
1199 | { |
||
1200 | all_hostage_points = hostages_on_board * 1000 * (Difficulty_level + 1); |
||
1201 | snprintf(hostage_text, sizeof(hostage_text), "%s%i\n", TXT_FULL_RESCUE_BONUS, all_hostage_points); |
||
1202 | } |
||
1203 | |||
1204 | auto &endgame_text = m_str[c++]; |
||
1205 | endgame_text[0] = 0; |
||
1206 | if (cheats.enabled) |
||
1207 | { |
||
1208 | /* Nothing */ |
||
1209 | } |
||
1210 | else if (!(Game_mode & GM_MULTI) && plr.lives && Current_level_num == Last_level) |
||
1211 | { //player has finished the game! |
||
1212 | endgame_points = plr.lives * 10000; |
||
1213 | snprintf(endgame_text, sizeof(endgame_text), "%s%i\n", TXT_SHIP_BONUS, endgame_points); |
||
1214 | is_last_level=1; |
||
1215 | } |
||
1216 | |||
1217 | add_bonus_points_to_score(player_info, skill_points + energy_points + shield_points + hostage_points + all_hostage_points + endgame_points); |
||
1218 | |||
1219 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i\n", TXT_TOTAL_BONUS, shield_points + energy_points + hostage_points + skill_points + all_hostage_points + endgame_points); |
||
1220 | snprintf(m_str[c++], sizeof(m_str[0]), "%s%i", TXT_TOTAL_SCORE, player_info.mission.score); |
||
1221 | |||
1222 | for (i=0; i<c; i++) { |
||
1223 | nm_set_item_text(m[i], m_str[i]); |
||
1224 | } |
||
1225 | |||
1226 | auto current_level_num = Current_level_num; |
||
1227 | const auto txt_level = (current_level_num < 0) ? (current_level_num = -current_level_num, TXT_SECRET_LEVEL) : TXT_LEVEL; |
||
1228 | snprintf(title, sizeof(title), "%s%s %d %s\n%s %s", is_last_level?"\n\n\n":"\n", txt_level, current_level_num, TXT_COMPLETE, static_cast<const char *>(Current_level_name), TXT_DESTROYED); |
||
1229 | |||
1230 | Assert(c <= N_GLITZITEMS); |
||
1231 | |||
1232 | newmenu_do2(nullptr, title, c, m, unused_newmenu_subfunction, unused_newmenu_userdata, 0, GLITZ_BACKGROUND); |
||
1233 | } |
||
1234 | } |
||
1235 | |||
1236 | #if defined(DXX_BUILD_DESCENT_II) |
||
1237 | // ----------------------------------------------------------------------------------------------------- |
||
1238 | //called when the player is starting a level (new game or new ship) |
||
1239 | static void StartSecretLevel() |
||
1240 | { |
||
1241 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1242 | auto &vmobjptridx = Objects.vmptridx; |
||
1243 | Assert(Player_dead_state == player_dead_state::no); |
||
1244 | |||
1245 | InitPlayerPosition(vmobjptridx, vmsegptridx, 0); |
||
1246 | |||
1247 | verify_console_object(); |
||
1248 | |||
1249 | ConsoleObject->control_type = CT_FLYING; |
||
1250 | ConsoleObject->movement_type = MT_PHYSICS; |
||
1251 | |||
1252 | // -- WHY? -- disable_matcens(); |
||
1253 | clear_transient_objects(0); //0 means leave proximity bombs |
||
1254 | |||
1255 | // create_player_appearance_effect(ConsoleObject); |
||
1256 | Do_appearance_effect = 1; |
||
1257 | |||
1258 | ai_reset_all_paths(); |
||
1259 | // -- NO? -- reset_time(); |
||
1260 | |||
1261 | reset_rear_view(); |
||
1262 | auto &player_info = ConsoleObject->ctype.player_info; |
||
1263 | player_info.Auto_fire_fusion_cannon_time = 0; |
||
1264 | player_info.Fusion_charge = 0; |
||
1265 | } |
||
1266 | |||
1267 | // Returns true if secret level has been destroyed. |
||
1268 | int p_secret_level_destroyed(void) |
||
1269 | { |
||
1270 | if (First_secret_visit) { |
||
1271 | return 0; // Never been there, can't have been destroyed. |
||
1272 | } else { |
||
1273 | if (PHYSFSX_exists(SECRETC_FILENAME,0)) |
||
1274 | { |
||
1275 | return 0; |
||
1276 | } else { |
||
1277 | return 1; |
||
1278 | } |
||
1279 | } |
||
1280 | } |
||
1281 | |||
1282 | // ----------------------------------------------------------------------------------------------------- |
||
1283 | #define TXT_SECRET_RETURN "Returning to level %i", Entered_from_level |
||
1284 | #define TXT_SECRET_ADVANCE "Base level destroyed.\nAdvancing to level %i", Entered_from_level+1 |
||
1285 | #endif |
||
1286 | |||
1287 | static int draw_endlevel_background(newmenu *,const d_event &event, grs_bitmap *background) |
||
1288 | { |
||
1289 | switch (event.type) |
||
1290 | { |
||
1291 | case EVENT_WINDOW_DRAW: |
||
1292 | gr_set_default_canvas(); |
||
1293 | show_fullscr(*grd_curcanv, *background); |
||
1294 | break; |
||
1295 | |||
1296 | default: |
||
1297 | break; |
||
1298 | } |
||
1299 | |||
1300 | return 0; |
||
1301 | } |
||
1302 | |||
1303 | static void do_screen_message(const char *msg) __attribute_nonnull(); |
||
1304 | static void do_screen_message(const char *msg) |
||
1305 | { |
||
1306 | |||
1307 | if (Game_mode & GM_MULTI) |
||
1308 | return; |
||
1309 | grs_main_bitmap background; |
||
1310 | if (pcx_read_bitmap(GLITZ_BACKGROUND, background, gr_palette) != pcx_result::SUCCESS) |
||
1311 | return; |
||
1312 | |||
1313 | gr_palette_load(gr_palette); |
||
1314 | std::array<newmenu_item, 1> nm_message_items{{ |
||
1315 | nm_item_menu(TXT_OK), |
||
1316 | }}; |
||
1317 | newmenu_do( NULL, msg, nm_message_items, draw_endlevel_background, static_cast<grs_bitmap *>(&background)); |
||
1318 | } |
||
1319 | |||
1320 | namespace dsx { |
||
1321 | #if defined(DXX_BUILD_DESCENT_II) |
||
1322 | static void do_screen_message_fmt(const char *fmt, ...) __attribute_format_printf(1, 2); |
||
1323 | static void do_screen_message_fmt(const char *fmt, ...) |
||
1324 | { |
||
1325 | va_list arglist; |
||
1326 | char msg[1024]; |
||
1327 | va_start(arglist, fmt); |
||
1328 | vsnprintf(msg, sizeof(msg), fmt, arglist); |
||
1329 | va_end(arglist); |
||
1330 | do_screen_message(msg); |
||
1331 | } |
||
1332 | #define do_screen_message(F,...) dxx_call_printf_checked(do_screen_message_fmt,do_screen_message,(),F,##__VA_ARGS__) |
||
1333 | |||
1334 | // ----------------------------------------------------------------------------------------------------- |
||
1335 | // called when the player is starting a new level for normal game mode and restore state |
||
1336 | // Need to deal with whether this is the first time coming to this level or not. If not the |
||
1337 | // first time, instead of initializing various things, need to do a game restore for all the |
||
1338 | // robots, powerups, walls, doors, etc. |
||
1339 | static void StartNewLevelSecret(int level_num, int page_in_textures) |
||
1340 | { |
||
1341 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1342 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1343 | auto &vmobjptr = Objects.vmptr; |
||
1344 | |||
1345 | last_drawn_cockpit = -1; |
||
1346 | |||
1347 | if (Newdemo_state == ND_STATE_PAUSED) |
||
1348 | Newdemo_state = ND_STATE_RECORDING; |
||
1349 | |||
1350 | if (Newdemo_state == ND_STATE_RECORDING) { |
||
1351 | newdemo_set_new_level(level_num); |
||
1352 | newdemo_record_start_frame(FrameTime ); |
||
1353 | } else if (Newdemo_state != ND_STATE_PLAYBACK) { |
||
1354 | |||
1355 | set_screen_mode(SCREEN_MENU); |
||
1356 | |||
1357 | if (First_secret_visit) { |
||
1358 | do_screen_message(TXT_SECRET_EXIT); |
||
1359 | } else { |
||
1360 | if (PHYSFSX_exists(SECRETC_FILENAME,0)) |
||
1361 | { |
||
1362 | do_screen_message(TXT_SECRET_EXIT); |
||
1363 | } else { |
||
1364 | do_screen_message("Secret level already destroyed.\nAdvancing to level %i.", Current_level_num+1); |
||
1365 | } |
||
1366 | } |
||
1367 | } |
||
1368 | |||
1369 | LoadLevel(level_num,page_in_textures); |
||
1370 | |||
1371 | Assert(Current_level_num == level_num); // make sure level set right |
||
1372 | |||
1373 | HUD_clear_messages(); |
||
1374 | |||
1375 | automap_clear_visited(); |
||
1376 | |||
1377 | Viewer = &get_local_plrobj(); |
||
1378 | |||
1379 | gameseq_remove_unused_players(); |
||
1380 | |||
1381 | Game_suspended = 0; |
||
1382 | |||
1383 | LevelUniqueControlCenterState.Control_center_destroyed = 0; |
||
1384 | |||
1385 | init_cockpit(); |
||
1386 | reset_palette_add(); |
||
1387 | |||
1388 | if (First_secret_visit || (Newdemo_state == ND_STATE_PLAYBACK)) { |
||
1389 | init_robots_for_level(); |
||
1390 | init_ai_objects(); |
||
1391 | init_smega_detonates(); |
||
1392 | init_morphs(); |
||
1393 | init_all_matcens(); |
||
1394 | reset_special_effects(); |
||
1395 | StartSecretLevel(); |
||
1396 | } else { |
||
1397 | if (PHYSFSX_exists(SECRETC_FILENAME,0)) |
||
1398 | { |
||
1399 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1400 | const auto pw_save = player_info.Primary_weapon; |
||
1401 | const auto sw_save = player_info.Secondary_weapon; |
||
1402 | state_restore_all(1, secret_restore::survived, SECRETC_FILENAME, blind_save::no); |
||
1403 | player_info.Primary_weapon = pw_save; |
||
1404 | player_info.Secondary_weapon = sw_save; |
||
1405 | reset_special_effects(); |
||
1406 | StartSecretLevel(); |
||
1407 | // -- No: This is only for returning to base level: set_pos_from_return_segment(); |
||
1408 | } else { |
||
1409 | do_screen_message("Secret level already destroyed.\nAdvancing to level %i.", Current_level_num+1); |
||
1410 | return; |
||
1411 | } |
||
1412 | } |
||
1413 | |||
1414 | if (First_secret_visit) { |
||
1415 | copy_defaults_to_robot_all(); |
||
1416 | } |
||
1417 | |||
1418 | init_controlcen_for_level(); |
||
1419 | |||
1420 | // Say player can use FLASH cheat to mark path to exit. |
||
1421 | Last_level_path_created = -1; |
||
1422 | |||
1423 | First_secret_visit = 0; |
||
1424 | } |
||
1425 | |||
1426 | static int Entered_from_level; |
||
1427 | |||
1428 | // --------------------------------------------------------------------------------------------------------------- |
||
1429 | // Called from switch.c when player is on a secret level and hits exit to return to base level. |
||
1430 | window_event_result ExitSecretLevel() |
||
1431 | { |
||
1432 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1433 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1434 | auto &vmobjptr = Objects.vmptr; |
||
1435 | auto result = window_event_result::handled; |
||
1436 | |||
1437 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
1438 | return window_event_result::ignored; |
||
1439 | |||
1440 | if (Game_wind) |
||
1441 | window_set_visible(Game_wind, 0); |
||
1442 | |||
1443 | if (!LevelUniqueControlCenterState.Control_center_destroyed) |
||
1444 | { |
||
1445 | state_save_all(secret_save::c, blind_save::no); |
||
1446 | } |
||
1447 | |||
1448 | if (PHYSFSX_exists(SECRETB_FILENAME,0)) |
||
1449 | { |
||
1450 | do_screen_message(TXT_SECRET_RETURN); |
||
1451 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1452 | const auto pw_save = player_info.Primary_weapon; |
||
1453 | const auto sw_save = player_info.Secondary_weapon; |
||
1454 | state_restore_all(1, secret_restore::survived, SECRETB_FILENAME, blind_save::no); |
||
1455 | player_info.Primary_weapon = pw_save; |
||
1456 | player_info.Secondary_weapon = sw_save; |
||
1457 | } else { |
||
1458 | // File doesn't exist, so can't return to base level. Advance to next one. |
||
1459 | if (Entered_from_level == Last_level) |
||
1460 | { |
||
1461 | DoEndGame(); |
||
1462 | result = window_event_result::close; |
||
1463 | } |
||
1464 | else { |
||
1465 | do_screen_message(TXT_SECRET_ADVANCE); |
||
1466 | StartNewLevel(Entered_from_level+1); |
||
1467 | } |
||
1468 | } |
||
1469 | |||
1470 | if (Game_wind) |
||
1471 | window_set_visible(Game_wind, 1); |
||
1472 | reset_time(); |
||
1473 | |||
1474 | return result; |
||
1475 | } |
||
1476 | |||
1477 | // --------------------------------------------------------------------------------------------------------------- |
||
1478 | // Set invulnerable_time and cloak_time in player struct to preserve amount of time left to |
||
1479 | // be invulnerable or cloaked. |
||
1480 | void do_cloak_invul_secret_stuff(fix64 old_gametime, player_info &player_info) |
||
1481 | { |
||
1482 | auto &pl_flags = player_info.powerup_flags; |
||
1483 | if (pl_flags & PLAYER_FLAGS_INVULNERABLE) |
||
1484 | { |
||
1485 | fix64 time_used; |
||
1486 | |||
1487 | auto &t = player_info.invulnerable_time; |
||
1488 | time_used = old_gametime - t; |
||
1489 | t = GameTime64 - time_used; |
||
1490 | } |
||
1491 | |||
1492 | if (pl_flags & PLAYER_FLAGS_CLOAKED) |
||
1493 | { |
||
1494 | fix time_used; |
||
1495 | |||
1496 | auto &t = player_info.cloak_time; |
||
1497 | time_used = old_gametime - t; |
||
1498 | t = GameTime64 - time_used; |
||
1499 | } |
||
1500 | } |
||
1501 | |||
1502 | // --------------------------------------------------------------------------------------------------------------- |
||
1503 | // Called from switch.c when player passes through secret exit. That means he was on a non-secret level and he |
||
1504 | // is passing to the secret level. |
||
1505 | // Do a savegame. |
||
1506 | void EnterSecretLevel(void) |
||
1507 | { |
||
1508 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1509 | int i; |
||
1510 | |||
1511 | Assert(! (Game_mode & GM_MULTI) ); |
||
1512 | |||
1513 | if (Game_wind) |
||
1514 | window_set_visible(Game_wind, 0); |
||
1515 | |||
1516 | digi_play_sample( SOUND_SECRET_EXIT, F1_0 ); // after above call which stops all sounds |
||
1517 | |||
1518 | Entered_from_level = Current_level_num; |
||
1519 | |||
1520 | if (LevelUniqueControlCenterState.Control_center_destroyed) |
||
1521 | DoEndLevelScoreGlitz(); |
||
1522 | |||
1523 | if (Newdemo_state != ND_STATE_PLAYBACK) |
||
1524 | state_save_all(secret_save::b, blind_save::no); // Not between levels (ie, save all), IS a secret level, NO filename override |
||
1525 | |||
1526 | // Find secret level number to go to, stuff in Next_level_num. |
||
1527 | for (i=0; i<-Last_secret_level; i++) |
||
1528 | if (Secret_level_table[i]==Current_level_num) { |
||
1529 | Next_level_num = -(i+1); |
||
1530 | break; |
||
1531 | } else if (Secret_level_table[i] > Current_level_num) { // Allows multiple exits in same group. |
||
1532 | Next_level_num = -i; |
||
1533 | break; |
||
1534 | } |
||
1535 | |||
1536 | if (! (i<-Last_secret_level)) //didn't find level, so must be last |
||
1537 | Next_level_num = Last_secret_level; |
||
1538 | |||
1539 | // NMN 04/09/07 Do a REAL start level routine if we are playing a D1 level so we have |
||
1540 | // briefings |
||
1541 | if (EMULATING_D1) |
||
1542 | { |
||
1543 | set_screen_mode(SCREEN_MENU); |
||
1544 | do_screen_message("Alternate Exit Found!\n\nProceeding to Secret Level!"); |
||
1545 | StartNewLevel(Next_level_num); |
||
1546 | } else { |
||
1547 | StartNewLevelSecret(Next_level_num, 1); |
||
1548 | } |
||
1549 | // END NMN |
||
1550 | |||
1551 | // do_cloak_invul_stuff(); |
||
1552 | if (Game_wind) |
||
1553 | window_set_visible(Game_wind, 1); |
||
1554 | reset_time(); |
||
1555 | } |
||
1556 | #endif |
||
1557 | |||
1558 | //called when the player has finished a level |
||
1559 | window_event_result PlayerFinishedLevel(int secret_flag) |
||
1560 | { |
||
1561 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1562 | auto &vmobjptr = Objects.vmptr; |
||
1563 | if (Game_wind) |
||
1564 | window_set_visible(Game_wind, 0); |
||
1565 | |||
1566 | //credit the player for hostages |
||
1567 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
1568 | player_info.mission.hostages_rescued_total += player_info.mission.hostages_on_board; |
||
1569 | #if defined(DXX_BUILD_DESCENT_I) |
||
1570 | if (!(Game_mode & GM_MULTI) && (secret_flag)) { |
||
1571 | std::array<newmenu_item, 1> m{{ |
||
1572 | nm_item_text(" "), //TXT_SECRET_EXIT; |
||
1573 | }}; |
||
1574 | newmenu_do2(NULL, TXT_SECRET_EXIT, m.size(), m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 0, Menu_pcx_name); |
||
1575 | } |
||
1576 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1577 | Assert(!secret_flag); |
||
1578 | #endif |
||
1579 | if (Game_mode & GM_NETWORK) |
||
1580 | get_local_player().connected = CONNECT_WAITING; // Finished but did not die |
||
1581 | |||
1582 | last_drawn_cockpit = -1; |
||
1583 | |||
1584 | auto result = AdvanceLevel(secret_flag); //now go on to the next one (if one) |
||
1585 | |||
1586 | if (Game_wind) |
||
1587 | window_set_visible(Game_wind, 1); |
||
1588 | reset_time(); |
||
1589 | |||
1590 | return result; |
||
1591 | } |
||
1592 | |||
1593 | #if defined(DXX_BUILD_DESCENT_II) |
||
1594 | #define MOVIE_REQUIRED 1 |
||
1595 | #define ENDMOVIE "end" |
||
1596 | #endif |
||
1597 | |||
1598 | //called when the player has finished the last level |
||
1599 | static void DoEndGame() |
||
1600 | { |
||
1601 | if ((Newdemo_state == ND_STATE_RECORDING) || (Newdemo_state == ND_STATE_PAUSED)) |
||
1602 | newdemo_stop_recording(); |
||
1603 | |||
1604 | set_screen_mode( SCREEN_MENU ); |
||
1605 | |||
1606 | gr_set_default_canvas(); |
||
1607 | |||
1608 | key_flush(); |
||
1609 | |||
1610 | if (PLAYING_BUILTIN_MISSION && !(Game_mode & GM_MULTI)) |
||
1611 | { //only built-in mission, & not multi |
||
1612 | #if defined(DXX_BUILD_DESCENT_II) |
||
1613 | int played=MOVIE_NOT_PLAYED; //default is not played |
||
1614 | |||
1615 | played = PlayMovie(ENDMOVIE ".tex", ENDMOVIE ".mve",MOVIE_REQUIRED); |
||
1616 | if (!played) |
||
1617 | #endif |
||
1618 | { |
||
1619 | do_end_briefing_screens(Ending_text_filename); |
||
1620 | } |
||
1621 | } |
||
1622 | else if (!(Game_mode & GM_MULTI)) //not multi |
||
1623 | { |
||
1624 | char tname[FILENAME_LEN]; |
||
1625 | |||
1626 | do_end_briefing_screens (Ending_text_filename); |
||
1627 | |||
1628 | //try doing special credits |
||
1629 | snprintf(tname, sizeof(tname), "%s.ctb", &*Current_mission->filename); |
||
1630 | credits_show(tname); |
||
1631 | } |
||
1632 | |||
1633 | key_flush(); |
||
1634 | |||
1635 | if (Game_mode & GM_MULTI) |
||
1636 | multi_endlevel_score(); |
||
1637 | else |
||
1638 | // NOTE LINK TO ABOVE |
||
1639 | DoEndLevelScoreGlitz(); |
||
1640 | |||
1641 | if (PLAYING_BUILTIN_MISSION && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))) { |
||
1642 | gr_set_default_canvas(); |
||
1643 | gr_clear_canvas(*grd_curcanv, BM_XRGB(0,0,0)); |
||
1644 | #if defined(DXX_BUILD_DESCENT_II) |
||
1645 | load_palette(D2_DEFAULT_PALETTE,0,1); |
||
1646 | #endif |
||
1647 | scores_maybe_add_player(); |
||
1648 | } |
||
1649 | } |
||
1650 | |||
1651 | //called to go to the next level (if there is one) |
||
1652 | //if secret_flag is true, advance to secret level, else next normal one |
||
1653 | // Return true if game over. |
||
1654 | static window_event_result AdvanceLevel(int secret_flag) |
||
1655 | { |
||
1656 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1657 | auto rval = window_event_result::handled; |
||
1658 | |||
1659 | #if defined(DXX_BUILD_DESCENT_II) |
||
1660 | Assert(!secret_flag); |
||
1661 | |||
1662 | // Loading a level can write over homing_flag. |
||
1663 | // So for simplicity, reset the homing weapon cheat here. |
||
1664 | if (cheats.homingfire) |
||
1665 | { |
||
1666 | cheats.homingfire = 0; |
||
1667 | weapons_homing_all_reset(); |
||
1668 | } |
||
1669 | #endif |
||
1670 | if (Current_level_num != Last_level) |
||
1671 | { |
||
1672 | if (Game_mode & GM_MULTI) |
||
1673 | { |
||
1674 | const auto result = multi_endlevel_score(); |
||
1675 | if (result == kmatrix_result::abort) |
||
1676 | return window_event_result::close; // Exit out of game loop |
||
1677 | } |
||
1678 | else |
||
1679 | // NOTE LINK TO ABOVE!!! |
||
1680 | DoEndLevelScoreGlitz(); //give bonuses |
||
1681 | } |
||
1682 | |||
1683 | LevelUniqueControlCenterState.Control_center_destroyed = 0; |
||
1684 | |||
1685 | if (Game_mode & GM_MULTI) |
||
1686 | { |
||
1687 | int result; |
||
1688 | result = multi_endlevel(&secret_flag); // Wait for other players to reach this point |
||
1689 | if (result) // failed to sync |
||
1690 | { |
||
1691 | // check if player has finished the game |
||
1692 | return Current_level_num == Last_level ? window_event_result::close : rval; |
||
1693 | } |
||
1694 | } |
||
1695 | |||
1696 | if (Current_level_num == Last_level) { //player has finished the game! |
||
1697 | |||
1698 | DoEndGame(); |
||
1699 | rval = window_event_result::close; |
||
1700 | |||
1701 | } else { |
||
1702 | #if defined(DXX_BUILD_DESCENT_I) |
||
1703 | Next_level_num = Current_level_num+1; //assume go to next normal level |
||
1704 | |||
1705 | if (secret_flag) { //go to secret level instead |
||
1706 | int i; |
||
1707 | |||
1708 | for (i=0;i<-Last_secret_level;i++) |
||
1709 | if (Secret_level_table[i]==Current_level_num) { |
||
1710 | Next_level_num = -(i+1); |
||
1711 | break; |
||
1712 | } |
||
1713 | Assert(i<-Last_secret_level); //couldn't find which secret level |
||
1714 | } |
||
1715 | |||
1716 | if (Current_level_num < 0) { //on secret level, where to go? |
||
1717 | |||
1718 | Assert(!secret_flag); //shouldn't be going to secret level |
||
1719 | Assert(Current_level_num<=-1 && Current_level_num>=Last_secret_level); |
||
1720 | |||
1721 | Next_level_num = Secret_level_table[(-Current_level_num)-1]+1; |
||
1722 | } |
||
1723 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1724 | |||
1725 | //NMN 04/08/07 If we are in a secret level and playing a D1 |
||
1726 | // level, then use Entered_from_level # instead |
||
1727 | if (Current_level_num < 0 && EMULATING_D1) |
||
1728 | { |
||
1729 | Next_level_num = Entered_from_level+1; //assume go to next normal level |
||
1730 | } else { |
||
1731 | Next_level_num = Current_level_num+1; //assume go to next normal level |
||
1732 | } |
||
1733 | // END NMN |
||
1734 | #endif |
||
1735 | rval = std::max(StartNewLevel(Next_level_num), rval); |
||
1736 | } |
||
1737 | |||
1738 | return rval; |
||
1739 | } |
||
1740 | |||
1741 | window_event_result DoPlayerDead() |
||
1742 | { |
||
1743 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1744 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1745 | auto &vmobjptr = Objects.vmptr; |
||
1746 | const bool pause = !(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence)); |
||
1747 | auto result = window_event_result::handled; |
||
1748 | |||
1749 | if (pause) |
||
1750 | stop_time(); |
||
1751 | |||
1752 | reset_palette_add(); |
||
1753 | |||
1754 | gr_palette_load (gr_palette); |
||
1755 | |||
1756 | dead_player_end(); //terminate death sequence (if playing) |
||
1757 | |||
1758 | auto &plr = get_local_player(); |
||
1759 | if ( Game_mode&GM_MULTI ) |
||
1760 | { |
||
1761 | multi_do_death(plr.objnum); |
||
1762 | } |
||
1763 | else |
||
1764 | { //Note link to above else! |
||
1765 | -- plr.lives; |
||
1766 | if (plr.lives == 0) |
||
1767 | { |
||
1768 | DoGameOver(); |
||
1769 | if (pause) |
||
1770 | start_time(); |
||
1771 | return window_event_result::close; |
||
1772 | } |
||
1773 | } |
||
1774 | |||
1775 | if (LevelUniqueControlCenterState.Control_center_destroyed) |
||
1776 | { |
||
1777 | //clear out stuff so no bonus |
||
1778 | auto &plrobj = get_local_plrobj(); |
||
1779 | auto &player_info = plrobj.ctype.player_info; |
||
1780 | player_info.mission.hostages_on_board = 0; |
||
1781 | player_info.energy = 0; |
||
1782 | plrobj.shields = 0; |
||
1783 | plr.connected = CONNECT_DIED_IN_MINE; |
||
1784 | |||
1785 | do_screen_message(TXT_DIED_IN_MINE); // Give them some indication of what happened |
||
1786 | #if defined(DXX_BUILD_DESCENT_II) |
||
1787 | if (Current_level_num < 0) { |
||
1788 | if (PHYSFSX_exists(SECRETB_FILENAME,0)) |
||
1789 | { |
||
1790 | do_screen_message(TXT_SECRET_RETURN); |
||
1791 | state_restore_all(1, secret_restore::died, SECRETB_FILENAME, blind_save::no); // 2 means you died |
||
1792 | set_pos_from_return_segment(); |
||
1793 | plr.lives--; // re-lose the life, get_local_player().lives got written over in restore. |
||
1794 | } else { |
||
1795 | if (Entered_from_level == Last_level) |
||
1796 | { |
||
1797 | DoEndGame(); |
||
1798 | result = window_event_result::close; |
||
1799 | } |
||
1800 | else { |
||
1801 | do_screen_message(TXT_SECRET_ADVANCE); |
||
1802 | StartNewLevel(Entered_from_level+1); |
||
1803 | init_player_stats_new_ship(Player_num); // New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups! |
||
1804 | } |
||
1805 | } |
||
1806 | } else |
||
1807 | #endif |
||
1808 | { |
||
1809 | |||
1810 | if (Game_wind) |
||
1811 | window_set_visible(Game_wind, 0); |
||
1812 | result = AdvanceLevel(0); //if finished, go on to next level |
||
1813 | |||
1814 | init_player_stats_new_ship(Player_num); |
||
1815 | last_drawn_cockpit = -1; |
||
1816 | if (Game_wind) |
||
1817 | window_set_visible(Game_wind, 1); |
||
1818 | } |
||
1819 | #if defined(DXX_BUILD_DESCENT_II) |
||
1820 | } else if (Current_level_num < 0) { |
||
1821 | if (PHYSFSX_exists(SECRETB_FILENAME,0)) |
||
1822 | { |
||
1823 | do_screen_message(TXT_SECRET_RETURN); |
||
1824 | if (!LevelUniqueControlCenterState.Control_center_destroyed) |
||
1825 | state_save_all(secret_save::c, blind_save::no); |
||
1826 | state_restore_all(1, secret_restore::died, SECRETB_FILENAME, blind_save::no); |
||
1827 | set_pos_from_return_segment(); |
||
1828 | plr.lives--; // re-lose the life, get_local_player().lives got written over in restore. |
||
1829 | } else { |
||
1830 | do_screen_message(TXT_DIED_IN_MINE); // Give them some indication of what happened |
||
1831 | if (Entered_from_level == Last_level) |
||
1832 | { |
||
1833 | DoEndGame(); |
||
1834 | result = window_event_result::close; |
||
1835 | } |
||
1836 | else { |
||
1837 | do_screen_message(TXT_SECRET_ADVANCE); |
||
1838 | StartNewLevel(Entered_from_level+1); |
||
1839 | init_player_stats_new_ship(Player_num); // New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups! |
||
1840 | } |
||
1841 | } |
||
1842 | #endif |
||
1843 | } else { |
||
1844 | init_player_stats_new_ship(Player_num); |
||
1845 | StartLevel(1); |
||
1846 | } |
||
1847 | |||
1848 | digi_sync_sounds(); |
||
1849 | |||
1850 | if (pause) |
||
1851 | start_time(); |
||
1852 | reset_time(); |
||
1853 | |||
1854 | return result; |
||
1855 | } |
||
1856 | |||
1857 | //called when the player is starting a new level for normal game mode and restore state |
||
1858 | // secret_flag set if came from a secret level |
||
1859 | #if defined(DXX_BUILD_DESCENT_I) |
||
1860 | window_event_result StartNewLevelSub(const int level_num, const int page_in_textures) |
||
1861 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1862 | window_event_result StartNewLevelSub(const int level_num, const int page_in_textures, const secret_restore secret_flag) |
||
1863 | #endif |
||
1864 | { |
||
1865 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1866 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1867 | auto &vmobjptr = Objects.vmptr; |
||
1868 | if (!(Game_mode & GM_MULTI)) { |
||
1869 | last_drawn_cockpit = -1; |
||
1870 | } |
||
1871 | #if defined(DXX_BUILD_DESCENT_I) |
||
1872 | static constexpr std::integral_constant<secret_restore, secret_restore::none> secret_flag{}; |
||
1873 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1874 | BigWindowSwitch=0; |
||
1875 | #endif |
||
1876 | |||
1877 | |||
1878 | if (Newdemo_state == ND_STATE_PAUSED) |
||
1879 | Newdemo_state = ND_STATE_RECORDING; |
||
1880 | |||
1881 | if (Newdemo_state == ND_STATE_RECORDING) { |
||
1882 | newdemo_set_new_level(level_num); |
||
1883 | newdemo_record_start_frame(FrameTime ); |
||
1884 | } |
||
1885 | |||
1886 | LoadLevel(level_num,page_in_textures); |
||
1887 | |||
1888 | Assert(Current_level_num == level_num); //make sure level set right |
||
1889 | |||
1890 | Viewer = &get_local_plrobj(); |
||
1891 | |||
1892 | Assert(N_players <= NumNetPlayerPositions); |
||
1893 | //If this assert fails, there's not enough start positions |
||
1894 | |||
1895 | if (Game_mode & GM_NETWORK) |
||
1896 | { |
||
1897 | multi_prep_level_objects(Vclip); |
||
1898 | if (multi_level_sync() == window_event_result::close) // After calling this, Player_num is set |
||
1899 | { |
||
1900 | songs_play_song( SONG_TITLE, 1 ); // level song already plays but we fail to start level... |
||
1901 | return window_event_result::close; |
||
1902 | } |
||
1903 | } |
||
1904 | |||
1905 | HUD_clear_messages(); |
||
1906 | |||
1907 | automap_clear_visited(); |
||
1908 | |||
1909 | LevelUniqueObjectState.accumulated_robots = count_number_of_robots(Objects.vcptr); |
||
1910 | GameUniqueState.accumulated_robots += LevelUniqueObjectState.accumulated_robots; |
||
1911 | LevelUniqueObjectState.total_hostages = count_number_of_hostages(Objects.vcptr); |
||
1912 | GameUniqueState.total_hostages += LevelUniqueObjectState.total_hostages; |
||
1913 | init_player_stats_level(get_local_player(), get_local_plrobj(), secret_flag); |
||
1914 | |||
1915 | #if defined(DXX_BUILD_DESCENT_I) |
||
1916 | gr_use_palette_table( "palette.256" ); |
||
1917 | #elif defined(DXX_BUILD_DESCENT_II) |
||
1918 | load_palette(Current_level_palette,0,1); |
||
1919 | #endif |
||
1920 | gr_palette_load(gr_palette); |
||
1921 | |||
1922 | if ((Game_mode & GM_MULTI_COOP) && Network_rejoined) |
||
1923 | { |
||
1924 | for (playernum_t i = 0; i < N_players; ++i) |
||
1925 | { |
||
1926 | const auto &&plobj = vmobjptr(vcplayerptr(i)->objnum); |
||
1927 | plobj->ctype.player_info.powerup_flags |= Netgame.net_player_flags[i]; |
||
1928 | } |
||
1929 | } |
||
1930 | |||
1931 | if (Game_mode & GM_MULTI) |
||
1932 | { |
||
1933 | multi_prep_level_player(); |
||
1934 | } |
||
1935 | |||
1936 | gameseq_remove_unused_players(); |
||
1937 | |||
1938 | Game_suspended = 0; |
||
1939 | |||
1940 | LevelUniqueControlCenterState.Control_center_destroyed = 0; |
||
1941 | |||
1942 | #if defined(DXX_BUILD_DESCENT_II) |
||
1943 | set_screen_mode(SCREEN_GAME); |
||
1944 | #endif |
||
1945 | |||
1946 | init_cockpit(); |
||
1947 | init_robots_for_level(); |
||
1948 | init_ai_objects(); |
||
1949 | init_morphs(); |
||
1950 | init_all_matcens(); |
||
1951 | reset_palette_add(); |
||
1952 | LevelUniqueStuckObjectState.init_stuck_objects(); |
||
1953 | #if defined(DXX_BUILD_DESCENT_II) |
||
1954 | init_smega_detonates(); |
||
1955 | init_thief_for_level(); |
||
1956 | if (!(Game_mode & GM_MULTI)) |
||
1957 | filter_objects_from_level(vmobjptr); |
||
1958 | #endif |
||
1959 | |||
1960 | if (!(Game_mode & GM_MULTI) && !cheats.enabled) |
||
1961 | set_highest_level(Current_level_num); |
||
1962 | else |
||
1963 | read_player_file(); //get window sizes |
||
1964 | |||
1965 | reset_special_effects(); |
||
1966 | |||
1967 | #if DXX_USE_OGL |
||
1968 | ogl_cache_level_textures(); |
||
1969 | #endif |
||
1970 | |||
1971 | |||
1972 | if (Network_rejoined == 1) |
||
1973 | { |
||
1974 | Network_rejoined = 0; |
||
1975 | StartLevel(1); |
||
1976 | } |
||
1977 | else |
||
1978 | StartLevel(0); // Note link to above if! |
||
1979 | |||
1980 | copy_defaults_to_robot_all(); |
||
1981 | init_controlcen_for_level(); |
||
1982 | |||
1983 | // Say player can use FLASH cheat to mark path to exit. |
||
1984 | Last_level_path_created = -1; |
||
1985 | |||
1986 | // Initialise for palette_restore() |
||
1987 | // Also takes care of nm_draw_background() possibly being called |
||
1988 | if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK))) |
||
1989 | full_palette_save(); |
||
1990 | |||
1991 | if (!Game_wind) |
||
1992 | game(); |
||
1993 | |||
1994 | return window_event_result::handled; |
||
1995 | } |
||
1996 | |||
1997 | void bash_to_shield(const d_powerup_info_array &Powerup_info, const d_vclip_array &Vclip, object_base &i) |
||
1998 | { |
||
1999 | set_powerup_id(Powerup_info, Vclip, i, POW_SHIELD_BOOST); |
||
2000 | } |
||
2001 | |||
2002 | #if defined(DXX_BUILD_DESCENT_II) |
||
2003 | |||
2004 | static void filter_objects_from_level(fvmobjptr &vmobjptr) |
||
2005 | { |
||
2006 | range_for (const auto &&objp, vmobjptr) |
||
2007 | { |
||
2008 | if (objp->type==OBJ_POWERUP) |
||
2009 | { |
||
2010 | const auto powerup_id = get_powerup_id(objp); |
||
2011 | if (powerup_id == POW_FLAG_RED || powerup_id == POW_FLAG_BLUE) |
||
2012 | bash_to_shield(Powerup_info, Vclip, objp); |
||
2013 | } |
||
2014 | } |
||
2015 | |||
2016 | } |
||
2017 | |||
2018 | namespace { |
||
2019 | |||
2020 | struct intro_movie_t { |
||
2021 | int level_num; |
||
2022 | char movie_name[4]; |
||
2023 | }; |
||
2024 | |||
2025 | const std::array<intro_movie_t, 7> intro_movie{{ |
||
2026 | { 1, "PLA"}, |
||
2027 | { 5, "PLB"}, |
||
2028 | { 9, "PLC"}, |
||
2029 | {13, "PLD"}, |
||
2030 | {17, "PLE"}, |
||
2031 | {21, "PLF"}, |
||
2032 | {24, "PLG"} |
||
2033 | }}; |
||
2034 | |||
2035 | } |
||
2036 | |||
2037 | static void ShowLevelIntro(int level_num) |
||
2038 | { |
||
2039 | //if shareware, show a briefing? |
||
2040 | |||
2041 | if (!(Game_mode & GM_MULTI)) { |
||
2042 | palette_array_t save_pal; |
||
2043 | |||
2044 | save_pal = gr_palette; |
||
2045 | |||
2046 | if (PLAYING_BUILTIN_MISSION) { |
||
2047 | |||
2048 | if (is_SHAREWARE || is_MAC_SHARE) |
||
2049 | { |
||
2050 | if (level_num==1) |
||
2051 | do_briefing_screens (Briefing_text_filename, 1); |
||
2052 | } |
||
2053 | else if (is_D2_OEM) |
||
2054 | { |
||
2055 | if (level_num == 1 && !intro_played) |
||
2056 | do_briefing_screens(Briefing_text_filename, 1); |
||
2057 | } |
||
2058 | else // full version |
||
2059 | { |
||
2060 | range_for (auto &i, intro_movie) |
||
2061 | { |
||
2062 | if (i.level_num == level_num) |
||
2063 | { |
||
2064 | Screen_mode = -1; |
||
2065 | PlayMovie(NULL, i.movie_name, MOVIE_REQUIRED); |
||
2066 | break; |
||
2067 | } |
||
2068 | } |
||
2069 | |||
2070 | do_briefing_screens (Briefing_text_filename,level_num); |
||
2071 | } |
||
2072 | } |
||
2073 | else //not the built-in mission (maybe d1, too). check for add-on briefing |
||
2074 | { |
||
2075 | do_briefing_screens(Briefing_text_filename, level_num); |
||
2076 | } |
||
2077 | |||
2078 | gr_palette = save_pal; |
||
2079 | } |
||
2080 | } |
||
2081 | |||
2082 | // --------------------------------------------------------------------------- |
||
2083 | // If starting a level which appears in the Secret_level_table, then set First_secret_visit. |
||
2084 | // Reason: On this level, if player goes to a secret level, he will be going to a different |
||
2085 | // secret level than he's ever been to before. |
||
2086 | // Sets the global First_secret_visit if necessary. Otherwise leaves it unchanged. |
||
2087 | static void maybe_set_first_secret_visit(int level_num) |
||
2088 | { |
||
2089 | range_for (auto &i, unchecked_partial_range(Secret_level_table.get(), N_secret_levels)) |
||
2090 | { |
||
2091 | if (i == level_num) |
||
2092 | { |
||
2093 | First_secret_visit = 1; |
||
2094 | break; |
||
2095 | } |
||
2096 | } |
||
2097 | } |
||
2098 | #endif |
||
2099 | |||
2100 | //called when the player is starting a new level for normal game model |
||
2101 | // secret_flag if came from a secret level |
||
2102 | window_event_result StartNewLevel(int level_num) |
||
2103 | { |
||
2104 | hide_menus(); |
||
2105 | |||
2106 | GameTime64 = 0; |
||
2107 | /* Autosave is permitted immediately on entering a new level */ |
||
2108 | state_set_immediate_autosave(GameUniqueState); |
||
2109 | ThisLevelTime = {}; |
||
2110 | |||
2111 | #if defined(DXX_BUILD_DESCENT_I) |
||
2112 | if (!(Game_mode & GM_MULTI)) { |
||
2113 | do_briefing_screens(Briefing_text_filename, level_num); |
||
2114 | } |
||
2115 | #elif defined(DXX_BUILD_DESCENT_II) |
||
2116 | if (level_num > 0) { |
||
2117 | maybe_set_first_secret_visit(level_num); |
||
2118 | } |
||
2119 | |||
2120 | ShowLevelIntro(level_num); |
||
2121 | #endif |
||
2122 | |||
2123 | return StartNewLevelSub(level_num, 1, secret_restore::none); |
||
2124 | |||
2125 | } |
||
2126 | |||
2127 | namespace |
||
2128 | { |
||
2129 | |||
2130 | class respawn_locations |
||
2131 | { |
||
2132 | typedef std::pair<int, fix> site; |
||
2133 | unsigned max_usable_spawn_sites; |
||
2134 | std::array<site, MAX_PLAYERS> sites; |
||
2135 | public: |
||
2136 | respawn_locations(fvmobjptr &vmobjptr, fvcsegptridx &vcsegptridx) |
||
2137 | { |
||
2138 | const auto player_num = Player_num; |
||
2139 | const auto find_closest_player = [player_num, &vmobjptr, &vcsegptridx](const obj_position &candidate) { |
||
2140 | fix closest_dist = INT32_MAX; |
||
2141 | const auto &&candidate_segp = vcsegptridx(candidate.segnum); |
||
2142 | for (playernum_t i = N_players; i--;) |
||
2143 | { |
||
2144 | if (i == player_num) |
||
2145 | continue; |
||
2146 | const auto &&objp = vmobjptr(vcplayerptr(i)->objnum); |
||
2147 | if (objp->type != OBJ_PLAYER) |
||
2148 | continue; |
||
2149 | const auto dist = find_connected_distance(objp->pos, candidate_segp.absolute_sibling(objp->segnum), candidate.pos, candidate_segp, -1, WALL_IS_DOORWAY_FLAG<0>()); |
||
2150 | if (dist >= 0 && closest_dist > dist) |
||
2151 | closest_dist = dist; |
||
2152 | } |
||
2153 | return closest_dist; |
||
2154 | }; |
||
2155 | const auto max_spawn_sites = std::min<unsigned>(NumNetPlayerPositions, sites.size()); |
||
2156 | for (uint_fast32_t i = max_spawn_sites; i--;) |
||
2157 | { |
||
2158 | auto &s = sites[i]; |
||
2159 | s.first = i; |
||
2160 | s.second = find_closest_player(Player_init[i]); |
||
2161 | } |
||
2162 | const unsigned SecludedSpawns = Netgame.SecludedSpawns + 1; |
||
2163 | if (max_spawn_sites > SecludedSpawns) |
||
2164 | { |
||
2165 | max_usable_spawn_sites = SecludedSpawns; |
||
2166 | const auto &&predicate = [](const site &a, const site &b) { |
||
2167 | return a.second > b.second; |
||
2168 | }; |
||
2169 | const auto b = sites.begin(); |
||
2170 | const auto m = std::next(b, SecludedSpawns); |
||
2171 | const auto e = std::next(b, max_spawn_sites); |
||
2172 | std::partial_sort(b, m, e, predicate); |
||
2173 | } |
||
2174 | else |
||
2175 | max_usable_spawn_sites = max_spawn_sites; |
||
2176 | } |
||
2177 | unsigned get_usable_sites() const |
||
2178 | { |
||
2179 | return max_usable_spawn_sites; |
||
2180 | } |
||
2181 | const site &operator[](std::size_t i) const |
||
2182 | { |
||
2183 | return sites[i]; |
||
2184 | } |
||
2185 | }; |
||
2186 | |||
2187 | } |
||
2188 | |||
2189 | //initialize the player object position & orientation (at start of game, or new ship) |
||
2190 | static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag) |
||
2191 | { |
||
2192 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2193 | auto &vmobjptr = Objects.vmptr; |
||
2194 | reset_cruise(); |
||
2195 | int NewPlayer=0; |
||
2196 | |||
2197 | if (! ((Game_mode & GM_MULTI) && !(Game_mode&GM_MULTI_COOP)) ) // If not deathmatch |
||
2198 | NewPlayer = Player_num; |
||
2199 | else if (random_flag == 1) |
||
2200 | { |
||
2201 | const respawn_locations locations(vmobjptr, vcsegptridx); |
||
2202 | if (!locations.get_usable_sites()) |
||
2203 | return; |
||
2204 | uint_fast32_t trys=0; |
||
2205 | d_srand(static_cast<fix>(timer_update())); |
||
2206 | do { |
||
2207 | trys++; |
||
2208 | NewPlayer = d_rand() % locations.get_usable_sites(); |
||
2209 | const auto closest_dist = locations[NewPlayer].second; |
||
2210 | if (closest_dist >= i2f(15*20)) |
||
2211 | break; |
||
2212 | } while (trys < MAX_PLAYERS * 2); |
||
2213 | NewPlayer = locations[NewPlayer].first; |
||
2214 | } |
||
2215 | else { |
||
2216 | // If deathmatch and not random, positions were already determined by sync packet |
||
2217 | reset_player_object(); |
||
2218 | return; |
||
2219 | } |
||
2220 | Assert(NewPlayer >= 0); |
||
2221 | Assert(NewPlayer < NumNetPlayerPositions); |
||
2222 | ConsoleObject->pos = Player_init[NewPlayer].pos; |
||
2223 | ConsoleObject->orient = Player_init[NewPlayer].orient; |
||
2224 | obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), vmsegptridx(Player_init[NewPlayer].segnum)); |
||
2225 | reset_player_object(); |
||
2226 | } |
||
2227 | |||
2228 | // ----------------------------------------------------------------------------------------------------- |
||
2229 | // Initialize default parameters for one robot, copying from Robot_info to *objp. |
||
2230 | // What about setting size!? Where does that come from? |
||
2231 | void copy_defaults_to_robot(object_base &objp) |
||
2232 | { |
||
2233 | assert(objp.type == OBJ_ROBOT); |
||
2234 | const unsigned objid = get_robot_id(objp); |
||
2235 | assert(objid < LevelSharedRobotInfoState.N_robot_types); |
||
2236 | |||
2237 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
2238 | auto &robptr = Robot_info[objid]; |
||
2239 | |||
2240 | // Boost shield for Thief and Buddy based on level. |
||
2241 | fix shields = robptr.strength; |
||
2242 | |||
2243 | #if defined(DXX_BUILD_DESCENT_II) |
||
2244 | const auto &Difficulty_level = GameUniqueState.Difficulty_level; |
||
2245 | if ((robot_is_thief(robptr)) || (robot_is_companion(robptr))) { |
||
2246 | shields = (shields * (abs(Current_level_num)+7))/8; |
||
2247 | |||
2248 | if (robot_is_companion(robptr)) { |
||
2249 | // Now, scale guide-bot hits by skill level |
||
2250 | switch (Difficulty_level) { |
||
2251 | case 0: shields = i2f(20000); break; // Trainee, basically unkillable |
||
2252 | case 1: shields *= 3; break; // Rookie, pretty dang hard |
||
2253 | case 2: shields *= 2; break; // Hotshot, a bit tough |
||
2254 | default: break; |
||
2255 | } |
||
2256 | } |
||
2257 | } else if (robptr.boss_flag) // MK, 01/16/95, make boss shields lower on lower diff levels. |
||
2258 | { |
||
2259 | // Additional wimpification of bosses at Trainee |
||
2260 | shields = shields / (NDL + 3) * (Difficulty_level ? Difficulty_level + 4 : 2); |
||
2261 | } |
||
2262 | #endif |
||
2263 | objp.shields = shields; |
||
2264 | } |
||
2265 | |||
2266 | // ----------------------------------------------------------------------------------------------------- |
||
2267 | // Copy all values from the robot info structure to all instances of robots. |
||
2268 | // This allows us to change bitmaps.tbl and have these changes manifested in existing robots. |
||
2269 | // This function should be called at level load time. |
||
2270 | static void copy_defaults_to_robot_all(void) |
||
2271 | { |
||
2272 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2273 | auto &vmobjptr = Objects.vmptr; |
||
2274 | range_for (object_base &objp, vmobjptr) |
||
2275 | { |
||
2276 | if (objp.type == OBJ_ROBOT) |
||
2277 | copy_defaults_to_robot(objp); |
||
2278 | } |
||
2279 | } |
||
2280 | |||
2281 | // ----------------------------------------------------------------------------------------------------- |
||
2282 | //called when the player is starting a level (new game or new ship) |
||
2283 | static void StartLevel(int random_flag) |
||
2284 | { |
||
2285 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2286 | auto &vmobjptridx = Objects.vmptridx; |
||
2287 | assert(Player_dead_state == player_dead_state::no); |
||
2288 | |||
2289 | InitPlayerPosition(vmobjptridx, vmsegptridx, random_flag); |
||
2290 | |||
2291 | verify_console_object(); |
||
2292 | |||
2293 | ConsoleObject->control_type = CT_FLYING; |
||
2294 | ConsoleObject->movement_type = MT_PHYSICS; |
||
2295 | |||
2296 | // create_player_appearance_effect(ConsoleObject); |
||
2297 | Do_appearance_effect = 1; |
||
2298 | |||
2299 | if (Game_mode & GM_MULTI) |
||
2300 | { |
||
2301 | if (Game_mode & GM_MULTI_COOP) |
||
2302 | multi_send_score(); |
||
2303 | multi_send_reappear(); |
||
2304 | multi_do_protocol_frame(1, 1); |
||
2305 | } |
||
2306 | else // in Singleplayer, after we died ... |
||
2307 | { |
||
2308 | disable_matcens(); // ... disable matcens and ... |
||
2309 | clear_transient_objects(0); // ... clear all transient objects. |
||
2310 | } |
||
2311 | |||
2312 | ai_reset_all_paths(); |
||
2313 | ai_init_boss_for_ship(); |
||
2314 | |||
2315 | reset_rear_view(); |
||
2316 | auto &player_info = ConsoleObject->ctype.player_info; |
||
2317 | player_info.Auto_fire_fusion_cannon_time = 0; |
||
2318 | player_info.Fusion_charge = 0; |
||
2319 | } |
||
2320 | } |