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 | * Game loop for Inferno |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include "dxxsconf.h" |
||
27 | #include <stdio.h> |
||
28 | #include <stdlib.h> |
||
29 | #include <string.h> |
||
30 | #include <stdarg.h> |
||
31 | #include <SDL.h> |
||
32 | #include <ctime> |
||
33 | #if DXX_USE_SCREENSHOT_FORMAT_PNG |
||
34 | #include <png.h> |
||
35 | #include "vers_id.h" |
||
36 | #endif |
||
37 | |||
38 | #if DXX_USE_OGL |
||
39 | #include "ogl_init.h" |
||
40 | #endif |
||
41 | |||
42 | #include "pstypes.h" |
||
43 | #include "console.h" |
||
44 | #include "gr.h" |
||
45 | #include "inferno.h" |
||
46 | #include "game.h" |
||
47 | #include "key.h" |
||
48 | #include "config.h" |
||
49 | #include "object.h" |
||
50 | #include "physics.h" |
||
51 | #include "dxxerror.h" |
||
52 | #include "joy.h" |
||
53 | #include "iff.h" |
||
54 | #include "pcx.h" |
||
55 | #include "timer.h" |
||
56 | #include "render.h" |
||
57 | #include "laser.h" |
||
58 | #include "screens.h" |
||
59 | #include "textures.h" |
||
60 | #include "slew.h" |
||
61 | #include "gauges.h" |
||
62 | #include "texmap.h" |
||
63 | #include "3d.h" |
||
64 | #include "effects.h" |
||
65 | #include "menu.h" |
||
66 | #include "player.h" |
||
67 | #include "gameseg.h" |
||
68 | #include "wall.h" |
||
69 | #include "ai.h" |
||
70 | #include "fuelcen.h" |
||
71 | #include "digi.h" |
||
72 | #include "u_mem.h" |
||
73 | #include "palette.h" |
||
74 | #include "morph.h" |
||
75 | #include "lighting.h" |
||
76 | #include "newdemo.h" |
||
77 | #include "collide.h" |
||
78 | #include "weapon.h" |
||
79 | #include "sounds.h" |
||
80 | #include "args.h" |
||
81 | #include "gameseq.h" |
||
82 | #include "automap.h" |
||
83 | #include "text.h" |
||
84 | #include "powerup.h" |
||
85 | #include "fireball.h" |
||
86 | #include "newmenu.h" |
||
87 | #include "gamefont.h" |
||
88 | #include "endlevel.h" |
||
89 | #include "kconfig.h" |
||
90 | #include "mouse.h" |
||
91 | #include "switch.h" |
||
92 | #include "controls.h" |
||
93 | #include "songs.h" |
||
94 | #include "rbaudio.h" |
||
95 | |||
96 | #include "multi.h" |
||
97 | #include "cntrlcen.h" |
||
98 | #include "pcx.h" |
||
99 | #include "state.h" |
||
100 | #include "piggy.h" |
||
101 | #include "multibot.h" |
||
102 | #include "fvi.h" |
||
103 | #include "ai.h" |
||
104 | #include "robot.h" |
||
105 | #include "playsave.h" |
||
106 | #include "maths.h" |
||
107 | #include "hudmsg.h" |
||
108 | #if defined(DXX_BUILD_DESCENT_II) |
||
109 | #include <climits> |
||
110 | #include "gamepal.h" |
||
111 | #include "movie.h" |
||
112 | #endif |
||
113 | #include "event.h" |
||
114 | #include "window.h" |
||
115 | |||
116 | #if DXX_USE_EDITOR |
||
117 | #include "editor/editor.h" |
||
118 | #include "editor/esegment.h" |
||
119 | #endif |
||
120 | |||
121 | #include "d_enumerate.h" |
||
122 | #include "d_range.h" |
||
123 | #include "compiler-range_for.h" |
||
124 | #include "partial_range.h" |
||
125 | #include "segiter.h" |
||
126 | |||
127 | static fix64 last_timer_value=0; |
||
128 | static fix64 sync_timer_value=0; |
||
129 | d_time_fix ThisLevelTime; |
||
130 | |||
131 | grs_canvas Screen_3d_window; // The rectangle for rendering the mine to |
||
132 | |||
133 | namespace dcx { |
||
134 | int force_cockpit_redraw=0; |
||
135 | int PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd; |
||
136 | |||
137 | int Game_suspended=0; //if non-zero, nothing moves but player |
||
138 | int Game_mode = GM_GAME_OVER; |
||
139 | int Global_missile_firing_count = 0; |
||
140 | } |
||
141 | |||
142 | // Function prototypes for GAME.C exclusively. |
||
143 | |||
144 | namespace dsx { |
||
145 | static window_event_result GameProcessFrame(void); |
||
146 | static bool FireLaser(player_info &); |
||
147 | static void powerup_grab_cheat_all(); |
||
148 | |||
149 | #if defined(DXX_BUILD_DESCENT_II) |
||
150 | d_flickering_light_state Flickering_light_state; |
||
151 | static void slide_textures(void); |
||
152 | static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx); |
||
153 | #endif |
||
154 | |||
155 | // Cheats |
||
156 | game_cheats cheats; |
||
157 | |||
158 | // ============================================================================================== |
||
159 | |||
160 | //this is called once per game |
||
161 | void init_game() |
||
162 | { |
||
163 | init_objects(); |
||
164 | |||
165 | init_special_effects(); |
||
166 | Clear_window = 2; // do portal only window clear. |
||
167 | } |
||
168 | |||
169 | } |
||
170 | |||
171 | namespace dcx { |
||
172 | |||
173 | void reset_palette_add() |
||
174 | { |
||
175 | PaletteRedAdd = 0; |
||
176 | PaletteGreenAdd = 0; |
||
177 | PaletteBlueAdd = 0; |
||
178 | } |
||
179 | |||
180 | screen_mode Game_screen_mode{640, 480}; |
||
181 | |||
182 | } |
||
183 | |||
184 | namespace dsx { |
||
185 | |||
186 | //initialize the various canvases on the game screen |
||
187 | //called every time the screen mode or cockpit changes |
||
188 | void init_cockpit() |
||
189 | { |
||
190 | //Initialize the on-screen canvases |
||
191 | |||
192 | if (Screen_mode != SCREEN_GAME) |
||
193 | return; |
||
194 | |||
195 | if ( Screen_mode == SCREEN_EDITOR ) |
||
196 | PlayerCfg.CockpitMode[1] = CM_FULL_SCREEN; |
||
197 | |||
198 | #if !DXX_USE_OGL |
||
199 | if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX) |
||
200 | { |
||
201 | #if defined(DXX_BUILD_DESCENT_II) |
||
202 | int HiresGFXAvailable = !GameArg.GfxSkipHiresGFX; |
||
203 | #endif |
||
204 | auto full_screen_mode = HiresGFXAvailable ? screen_mode{640, 480} : screen_mode{320, 200}; |
||
205 | if (Game_screen_mode != full_screen_mode) { |
||
206 | PlayerCfg.CockpitMode[1] = CM_FULL_SCREEN; |
||
207 | } |
||
208 | } |
||
209 | #endif |
||
210 | |||
211 | gr_set_default_canvas(); |
||
212 | |||
213 | switch( PlayerCfg.CockpitMode[1] ) { |
||
214 | case CM_FULL_COCKPIT: |
||
215 | game_init_render_sub_buffers(0, 0, SWIDTH, (SHEIGHT*2)/3); |
||
216 | break; |
||
217 | |||
218 | case CM_REAR_VIEW: |
||
219 | { |
||
220 | unsigned x1 = 0, y1 = 0, x2 = SWIDTH, y2 = (SHEIGHT*2)/3; |
||
221 | int mode = PlayerCfg.CockpitMode[1]; |
||
222 | #if defined(DXX_BUILD_DESCENT_II) |
||
223 | mode += (HIRESMODE?(Num_cockpits/2):0); |
||
224 | #endif |
||
225 | |||
226 | PIGGY_PAGE_IN(cockpit_bitmap[mode]); |
||
227 | auto &bm = GameBitmaps[cockpit_bitmap[mode].index]; |
||
228 | gr_bitblt_find_transparent_area(bm, x1, y1, x2, y2); |
||
229 | game_init_render_sub_buffers(x1*(static_cast<float>(SWIDTH)/bm.bm_w), y1*(static_cast<float>(SHEIGHT)/bm.bm_h), (x2-x1+1)*(static_cast<float>(SWIDTH)/bm.bm_w), (y2-y1+2)*(static_cast<float>(SHEIGHT)/bm.bm_h)); |
||
230 | break; |
||
231 | } |
||
232 | |||
233 | case CM_FULL_SCREEN: |
||
234 | game_init_render_sub_buffers(0, 0, SWIDTH, SHEIGHT); |
||
235 | break; |
||
236 | |||
237 | case CM_STATUS_BAR: |
||
238 | game_init_render_sub_buffers( 0, 0, SWIDTH, (HIRESMODE?(SHEIGHT*2)/2.6:(SHEIGHT*2)/2.72) ); |
||
239 | break; |
||
240 | |||
241 | case CM_LETTERBOX: { |
||
242 | const unsigned gsm_height = grd_curscreen->get_screen_height(); |
||
243 | const unsigned w = grd_curscreen->get_screen_width(); |
||
244 | const unsigned h = (gsm_height * 3) / 4; // true letterbox size (16:9) |
||
245 | const unsigned x = 0; |
||
246 | const unsigned y = (gsm_height - h) / 2; |
||
247 | |||
248 | const uint8_t color = 0; |
||
249 | auto &canvas = *grd_curcanv; |
||
250 | gr_rect(canvas, x, 0, w, gsm_height - h, color); |
||
251 | gr_rect(canvas, x, gsm_height - h, w, gsm_height, color); |
||
252 | |||
253 | game_init_render_sub_buffers( x, y, w, h ); |
||
254 | break; |
||
255 | } |
||
256 | } |
||
257 | |||
258 | gr_set_default_canvas(); |
||
259 | } |
||
260 | |||
261 | } |
||
262 | |||
263 | //selects a given cockpit (or lack of one). See types in game.h |
||
264 | void select_cockpit(cockpit_mode_t mode) |
||
265 | { |
||
266 | if (mode != PlayerCfg.CockpitMode[1]) { //new mode |
||
267 | PlayerCfg.CockpitMode[1]=mode; |
||
268 | init_cockpit(); |
||
269 | } |
||
270 | } |
||
271 | |||
272 | namespace dcx { |
||
273 | |||
274 | //force cockpit redraw next time. call this if you've trashed the screen |
||
275 | void reset_cockpit() |
||
276 | { |
||
277 | force_cockpit_redraw=1; |
||
278 | last_drawn_cockpit = -1; |
||
279 | } |
||
280 | |||
281 | void game_init_render_sub_buffers( int x, int y, int w, int h ) |
||
282 | { |
||
283 | gr_clear_canvas(*grd_curcanv, 0); |
||
284 | gr_init_sub_canvas(Screen_3d_window, grd_curscreen->sc_canvas, x, y, w, h); |
||
285 | } |
||
286 | |||
287 | } |
||
288 | |||
289 | namespace dsx { |
||
290 | |||
291 | //called to change the screen mode. Parameter sm is the new mode, one of |
||
292 | //SMODE_GAME or SMODE_EDITOR. returns mode acutally set (could be other |
||
293 | //mode if cannot init requested mode) |
||
294 | int set_screen_mode(int sm) |
||
295 | { |
||
296 | if ( (Screen_mode == sm) && !((sm==SCREEN_GAME) && (grd_curscreen->get_screen_mode() != Game_screen_mode)) && !(sm==SCREEN_MENU) ) |
||
297 | { |
||
298 | return 1; |
||
299 | } |
||
300 | |||
301 | #if DXX_USE_EDITOR |
||
302 | Canv_editor = NULL; |
||
303 | #endif |
||
304 | |||
305 | Screen_mode = sm; |
||
306 | |||
307 | #if SDL_MAJOR_VERSION == 1 |
||
308 | switch( Screen_mode ) |
||
309 | { |
||
310 | case SCREEN_MENU: |
||
311 | if (grd_curscreen->get_screen_mode() != Game_screen_mode) |
||
312 | if (gr_set_mode(Game_screen_mode)) |
||
313 | Error("Cannot set screen mode."); |
||
314 | break; |
||
315 | |||
316 | case SCREEN_GAME: |
||
317 | if (grd_curscreen->get_screen_mode() != Game_screen_mode) |
||
318 | if (gr_set_mode(Game_screen_mode)) |
||
319 | Error("Cannot set screen mode."); |
||
320 | break; |
||
321 | #if DXX_USE_EDITOR |
||
322 | case SCREEN_EDITOR: |
||
323 | { |
||
324 | const screen_mode editor_mode{800, 600}; |
||
325 | if (grd_curscreen->get_screen_mode() != editor_mode) |
||
326 | { |
||
327 | int gr_error; |
||
328 | if ((gr_error = gr_set_mode(editor_mode)) != 0) { //force into game scrren |
||
329 | Warning("Cannot init editor screen (error=%d)",gr_error); |
||
330 | return 0; |
||
331 | } |
||
332 | } |
||
333 | } |
||
334 | break; |
||
335 | #endif |
||
336 | #if defined(DXX_BUILD_DESCENT_II) |
||
337 | case SCREEN_MOVIE: |
||
338 | { |
||
339 | const screen_mode movie_mode{MOVIE_WIDTH, MOVIE_HEIGHT}; |
||
340 | if (grd_curscreen->get_screen_mode() != movie_mode) |
||
341 | { |
||
342 | if (gr_set_mode(movie_mode)) |
||
343 | Error("Cannot set screen mode for game!"); |
||
344 | gr_palette_load( gr_palette ); |
||
345 | } |
||
346 | } |
||
347 | break; |
||
348 | #endif |
||
349 | default: |
||
350 | Error("Invalid screen mode %d",sm); |
||
351 | } |
||
352 | #endif |
||
353 | return 1; |
||
354 | } |
||
355 | |||
356 | } |
||
357 | |||
358 | namespace dcx { |
||
359 | |||
360 | namespace { |
||
361 | |||
362 | class game_world_time_paused |
||
363 | { |
||
364 | unsigned time_paused; |
||
365 | public: |
||
366 | explicit operator bool() const |
||
367 | { |
||
368 | return time_paused; |
||
369 | } |
||
370 | void increase_pause_count(); |
||
371 | void decrease_pause_count(); |
||
372 | }; |
||
373 | |||
374 | } |
||
375 | |||
376 | static game_world_time_paused time_paused; |
||
377 | |||
378 | void game_world_time_paused::increase_pause_count() |
||
379 | { |
||
380 | if (time_paused==0) { |
||
381 | const fix64 time = timer_update(); |
||
382 | last_timer_value = time - last_timer_value; |
||
383 | if (last_timer_value < 0) { |
||
384 | last_timer_value = 0; |
||
385 | } |
||
386 | } |
||
387 | time_paused++; |
||
388 | } |
||
389 | |||
390 | void game_world_time_paused::decrease_pause_count() |
||
391 | { |
||
392 | Assert(time_paused > 0); |
||
393 | --time_paused; |
||
394 | if (time_paused==0) { |
||
395 | const fix64 time = timer_update(); |
||
396 | last_timer_value = time - last_timer_value; |
||
397 | } |
||
398 | } |
||
399 | |||
400 | void start_time() |
||
401 | { |
||
402 | time_paused.decrease_pause_count(); |
||
403 | } |
||
404 | |||
405 | void stop_time() |
||
406 | { |
||
407 | time_paused.increase_pause_count(); |
||
408 | } |
||
409 | |||
410 | pause_game_world_time::pause_game_world_time() |
||
411 | { |
||
412 | stop_time(); |
||
413 | } |
||
414 | |||
415 | pause_game_world_time::~pause_game_world_time() |
||
416 | { |
||
417 | start_time(); |
||
418 | } |
||
419 | |||
420 | static void game_flush_common_inputs() |
||
421 | { |
||
422 | event_flush(); |
||
423 | key_flush(); |
||
424 | joy_flush(); |
||
425 | mouse_flush(); |
||
426 | } |
||
427 | |||
428 | } |
||
429 | |||
430 | namespace dsx { |
||
431 | |||
432 | void game_flush_inputs() |
||
433 | { |
||
434 | Controls = {}; |
||
435 | game_flush_common_inputs(); |
||
436 | } |
||
437 | |||
438 | } |
||
439 | |||
440 | namespace dcx { |
||
441 | |||
442 | void game_flush_respawn_inputs() |
||
443 | { |
||
444 | static_cast<control_info::fire_controls_t &>(Controls.state) = {}; |
||
445 | } |
||
446 | |||
447 | /* |
||
448 | * timer that every sets d_tick_step true and increments d_tick_count every 1000/DESIGNATED_GAME_FPS ms. |
||
449 | */ |
||
450 | void calc_d_tick() |
||
451 | { |
||
452 | static fix timer = 0; |
||
453 | auto t = timer + FrameTime; |
||
454 | |||
455 | d_tick_step = t >= DESIGNATED_GAME_FRAMETIME; |
||
456 | if (d_tick_step) |
||
457 | { |
||
458 | d_tick_count++; |
||
459 | if (d_tick_count > 1000000) |
||
460 | d_tick_count = 0; |
||
461 | t -= DESIGNATED_GAME_FRAMETIME; |
||
462 | } |
||
463 | timer = t; |
||
464 | } |
||
465 | |||
466 | void reset_time() |
||
467 | { |
||
468 | last_timer_value = timer_update(); |
||
469 | } |
||
470 | |||
471 | } |
||
472 | |||
473 | void calc_frame_time() |
||
474 | { |
||
475 | fix last_frametime = FrameTime; |
||
476 | |||
477 | const auto vsync = CGameCfg.VSync; |
||
478 | const auto bound = f1_0 / (likely(vsync) ? MAXIMUM_FPS : CGameArg.SysMaxFPS); |
||
479 | const auto may_sleep = !CGameArg.SysNoNiceFPS && !vsync; |
||
480 | for (;;) |
||
481 | { |
||
482 | const auto timer_value = timer_update(); |
||
483 | FrameTime = timer_value - last_timer_value; |
||
484 | if (FrameTime > 0 && timer_value - sync_timer_value >= bound) |
||
485 | { |
||
486 | last_timer_value = timer_value; |
||
487 | |||
488 | sync_timer_value += bound; |
||
489 | if (sync_timer_value + bound < timer_value) { |
||
490 | sync_timer_value = timer_value; |
||
491 | } |
||
492 | break; |
||
493 | } |
||
494 | if (Game_mode & GM_MULTI) |
||
495 | multi_do_frame(); // during long wait, keep packets flowing |
||
496 | if (may_sleep) |
||
497 | timer_delay_ms(1); |
||
498 | } |
||
499 | |||
500 | if ( cheats.turbo ) |
||
501 | FrameTime *= 2; |
||
502 | |||
503 | if (FrameTime < 0) //if bogus frametime... |
||
504 | FrameTime = (last_frametime==0?1:last_frametime); //...then use time from last frame |
||
505 | |||
506 | GameTime64 += FrameTime; |
||
507 | |||
508 | calc_d_tick(); |
||
509 | #ifdef NEWHOMER |
||
510 | calc_d_homer_tick(); |
||
511 | #endif |
||
512 | } |
||
513 | |||
514 | namespace dsx { |
||
515 | |||
516 | #if DXX_USE_EDITOR |
||
517 | void move_player_2_segment(const vmsegptridx_t seg, const unsigned side) |
||
518 | { |
||
519 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
520 | auto &Objects = LevelUniqueObjectState.Objects; |
||
521 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
522 | auto &vmobjptr = Objects.vmptr; |
||
523 | auto &vmobjptridx = Objects.vmptridx; |
||
524 | const auto &&console = vmobjptridx(ConsoleObject); |
||
525 | auto &vcvertptr = Vertices.vcptr; |
||
526 | compute_segment_center(vcvertptr, console->pos, seg); |
||
527 | auto vp = compute_center_point_on_side(vcvertptr, seg, side); |
||
528 | vm_vec_sub2(vp, console->pos); |
||
529 | vm_vector_2_matrix(console->orient, vp, nullptr, nullptr); |
||
530 | obj_relink(vmobjptr, vmsegptr, console, seg); |
||
531 | } |
||
532 | #endif |
||
533 | |||
534 | } |
||
535 | |||
536 | namespace dcx { |
||
537 | |||
538 | namespace { |
||
539 | |||
540 | #if DXX_USE_SCREENSHOT_FORMAT_PNG |
||
541 | struct RAIIpng_struct |
||
542 | { |
||
543 | png_struct *png_ptr; |
||
544 | png_info *info_ptr = nullptr; |
||
545 | RAIIpng_struct(png_struct *const p) : |
||
546 | png_ptr(p) |
||
547 | { |
||
548 | } |
||
549 | ~RAIIpng_struct() |
||
550 | { |
||
551 | png_destroy_write_struct(&png_ptr, &info_ptr); |
||
552 | } |
||
553 | }; |
||
554 | |||
555 | struct d_screenshot : RAIIpng_struct |
||
556 | { |
||
557 | DXX_INHERIT_CONSTRUCTORS(d_screenshot,RAIIpng_struct); |
||
558 | /* error handling callbacks */ |
||
559 | [[noreturn]] |
||
560 | static void png_error_cb(png_struct *png, const char *str); |
||
561 | static void png_warn_cb(png_struct *png, const char *str); |
||
562 | /* output callbacks */ |
||
563 | static void png_write_cb(png_struct *png, uint8_t *buf, png_size_t); |
||
564 | static void png_flush_cb(png_struct *png); |
||
565 | class png_exception : std::exception |
||
566 | { |
||
567 | }; |
||
568 | }; |
||
569 | |||
570 | void d_screenshot::png_error_cb(png_struct *const png, const char *const str) |
||
571 | { |
||
572 | /* libpng requires that this function not return to its caller, and |
||
573 | * will abort the program if this requirement is violated. However, |
||
574 | * throwing an exception that unwinds out past libpng is permitted. |
||
575 | */ |
||
576 | (void)png; |
||
577 | con_printf(CON_URGENT, "libpng error: %s", str); |
||
578 | throw png_exception(); |
||
579 | } |
||
580 | |||
581 | void d_screenshot::png_warn_cb(png_struct *const png, const char *const str) |
||
582 | { |
||
583 | (void)png; |
||
584 | con_printf(CON_URGENT, "libpng warning: %s", str); |
||
585 | } |
||
586 | |||
587 | void d_screenshot::png_write_cb(png_struct *const png, uint8_t *const buf, const png_size_t size) |
||
588 | { |
||
589 | const auto file = reinterpret_cast<PHYSFS_File *>(png_get_io_ptr(png)); |
||
590 | PHYSFS_write(file, buf, size, 1); |
||
591 | } |
||
592 | |||
593 | void d_screenshot::png_flush_cb(png_struct *const png) |
||
594 | { |
||
595 | (void)png; |
||
596 | } |
||
597 | |||
598 | void record_screenshot_time(const struct tm &tm, png_struct *const png_ptr, png_info *const info_ptr) |
||
599 | { |
||
600 | #ifdef PNG_tIME_SUPPORTED |
||
601 | png_time pt{}; |
||
602 | pt.year = tm.tm_year + 1900; |
||
603 | pt.month = tm.tm_mon + 1; |
||
604 | pt.day = tm.tm_mday; |
||
605 | pt.hour = tm.tm_hour; |
||
606 | pt.minute = tm.tm_min; |
||
607 | pt.second = tm.tm_sec; |
||
608 | png_set_tIME(png_ptr, info_ptr, &pt); |
||
609 | #else |
||
610 | (void)png_ptr; |
||
611 | (void)info_ptr; |
||
612 | con_printf(CON_NORMAL, "libpng configured without support for time chunk: screenshot will lack time record."); |
||
613 | #endif |
||
614 | } |
||
615 | |||
616 | #ifdef PNG_TEXT_SUPPORTED |
||
617 | void record_screenshot_text_metadata(png_struct *const png_ptr, png_info *const info_ptr) |
||
618 | { |
||
619 | std::array<png_text, 6> text_fields{}; |
||
620 | char descent_version[80]; |
||
621 | char descent_build_datetime[21]; |
||
622 | std::string current_mission_path; |
||
623 | ntstring<MISSION_NAME_LEN> current_mission_name; |
||
624 | char current_level_number[4]; |
||
625 | char viewer_segment[sizeof("65536")]; |
||
626 | unsigned idx = 0; |
||
627 | char key_descent_version[] = "Rebirth.version"; |
||
628 | { |
||
629 | auto &t = text_fields[idx++]; |
||
630 | auto &text = descent_version; |
||
631 | t.key = key_descent_version; |
||
632 | text[sizeof(text) - 1] = 0; |
||
633 | strncpy(text, g_descent_version, sizeof(text) - 1); |
||
634 | t.text = text; |
||
635 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
636 | } |
||
637 | char key_descent_build_datetime[] = "Rebirth.build_datetime"; |
||
638 | { |
||
639 | auto &t = text_fields[idx++]; |
||
640 | auto &text = descent_build_datetime; |
||
641 | t.key = key_descent_build_datetime; |
||
642 | text[sizeof(text) - 1] = 0; |
||
643 | strncpy(text, g_descent_build_datetime, sizeof(text) - 1); |
||
644 | t.text = text; |
||
645 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
646 | } |
||
647 | char key_current_mission_path[] = "Rebirth.mission.pathname"; |
||
648 | char key_current_mission_name[] = "Rebirth.mission.textname"; |
||
649 | char key_viewer_segment[] = "Rebirth.viewer_segment"; |
||
650 | char key_current_level_number[] = "Rebirth.current_level_number"; |
||
651 | if (const auto current_mission = Current_mission.get()) |
||
652 | { |
||
653 | { |
||
654 | auto &t = text_fields[idx++]; |
||
655 | t.key = key_current_mission_path; |
||
656 | current_mission_path = current_mission->path; |
||
657 | t.text = ¤t_mission_path[0]; |
||
658 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
659 | } |
||
660 | { |
||
661 | auto &t = text_fields[idx++]; |
||
662 | t.key = key_current_mission_name; |
||
663 | current_mission_name = current_mission->mission_name; |
||
664 | t.text = current_mission_name.data(); |
||
665 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
666 | } |
||
667 | { |
||
668 | auto &t = text_fields[idx++]; |
||
669 | t.key = key_current_level_number; |
||
670 | t.text = current_level_number; |
||
671 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
672 | snprintf(current_level_number, sizeof(current_level_number), "%i", Current_level_num); |
||
673 | } |
||
674 | if (const auto viewer = Viewer) |
||
675 | { |
||
676 | auto &t = text_fields[idx++]; |
||
677 | t.key = key_viewer_segment; |
||
678 | t.text = viewer_segment; |
||
679 | t.compression = PNG_TEXT_COMPRESSION_NONE; |
||
680 | snprintf(viewer_segment, sizeof(viewer_segment), "%hu", viewer->segnum); |
||
681 | } |
||
682 | } |
||
683 | png_set_text(png_ptr, info_ptr, text_fields.data(), idx); |
||
684 | } |
||
685 | #endif |
||
686 | |||
687 | #if DXX_USE_OGL |
||
688 | #define write_screenshot_png(F,T,B,P) write_screenshot_png(F,T,B) |
||
689 | #endif |
||
690 | unsigned write_screenshot_png(PHYSFS_File *const file, const struct tm *const tm, const grs_bitmap &bitmap, const palette_array_t &pal) |
||
691 | { |
||
692 | const unsigned bm_w = ((bitmap.bm_w + 3) & ~3); |
||
693 | const unsigned bm_h = ((bitmap.bm_h + 3) & ~3); |
||
694 | #if DXX_USE_OGL |
||
695 | const unsigned bufsize = bm_w * bm_h * 3; |
||
696 | const auto buf = std::make_unique<uint8_t[]>(bufsize); |
||
697 | const auto begin_byte_buffer = buf.get(); |
||
698 | glReadPixels(0, 0, bm_w, bm_h, GL_RGB, GL_UNSIGNED_BYTE, begin_byte_buffer); |
||
699 | #else |
||
700 | const unsigned bufsize = bitmap.bm_rowsize * bm_h; |
||
701 | const auto begin_byte_buffer = bitmap.bm_mdata; |
||
702 | #endif |
||
703 | d_screenshot ss(png_create_write_struct(PNG_LIBPNG_VER_STRING, &ss, &d_screenshot::png_error_cb, &d_screenshot::png_warn_cb)); |
||
704 | if (!ss.png_ptr) |
||
705 | { |
||
706 | con_puts(CON_URGENT, "Cannot save screenshot: libpng png_create_write_struct failed"); |
||
707 | return 1; |
||
708 | } |
||
709 | /* Assert that Rebirth type rgb_t is layout compatible with |
||
710 | * libpng type png_color, so that the Rebirth palette_array_t |
||
711 | * can be safely reinterpret_cast to an array of png_color. |
||
712 | * Without this, it would be necessary to copy each rgb_t |
||
713 | * palette entry into a libpng png_color. |
||
714 | */ |
||
715 | static_assert(sizeof(png_color) == sizeof(rgb_t), "size mismatch"); |
||
716 | static_assert(offsetof(png_color, red) == offsetof(rgb_t, r), "red offsetof mismatch"); |
||
717 | static_assert(offsetof(png_color, green) == offsetof(rgb_t, g), "green offsetof mismatch"); |
||
718 | static_assert(offsetof(png_color, blue) == offsetof(rgb_t, b), "blue offsetof mismatch"); |
||
719 | try { |
||
720 | ss.info_ptr = png_create_info_struct(ss.png_ptr); |
||
721 | if (tm) |
||
722 | record_screenshot_time(*tm, ss.png_ptr, ss.info_ptr); |
||
723 | png_set_write_fn(ss.png_ptr, file, &d_screenshot::png_write_cb, &d_screenshot::png_flush_cb); |
||
724 | #if DXX_USE_OGL |
||
725 | const auto color_type = PNG_COLOR_TYPE_RGB; |
||
726 | #else |
||
727 | png_set_PLTE(ss.png_ptr, ss.info_ptr, reinterpret_cast<const png_color *>(pal.data()), pal.size()); |
||
728 | const auto color_type = PNG_COLOR_TYPE_PALETTE; |
||
729 | #endif |
||
730 | png_set_IHDR(ss.png_ptr, ss.info_ptr, bm_w, bm_h, 8 /* always 256 colors */, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
||
731 | #ifdef PNG_TEXT_SUPPORTED |
||
732 | record_screenshot_text_metadata(ss.png_ptr, ss.info_ptr); |
||
733 | #endif |
||
734 | png_write_info(ss.png_ptr, ss.info_ptr); |
||
735 | std::array<png_byte *, 1024> row_pointers; |
||
736 | const auto rpb = row_pointers.begin(); |
||
737 | auto o = rpb; |
||
738 | const auto end_byte_buffer = begin_byte_buffer + bufsize; |
||
739 | #if DXX_USE_OGL |
||
740 | /* OpenGL glReadPixels returns an image with origin in bottom |
||
741 | * left. Write rows from end back to beginning, since PNG |
||
742 | * expects origin in top left. If rows were written in memory |
||
743 | * order, the image would be vertically flipped. |
||
744 | */ |
||
745 | const uint_fast32_t stride = bm_w * 3; /* Without palette, written data is 3-byte-sized RGB tuples of color */ |
||
746 | for (auto p = end_byte_buffer; p != begin_byte_buffer;) |
||
747 | #else |
||
748 | const uint_fast32_t stride = bm_w; /* With palette, written data is byte-sized indices into a color table */ |
||
749 | /* SDL canvas uses an image with origin in top left. Write rows |
||
750 | * in memory order, since this matches the PNG layout. |
||
751 | */ |
||
752 | for (auto p = begin_byte_buffer; p != end_byte_buffer;) |
||
753 | #endif |
||
754 | { |
||
755 | #if DXX_USE_OGL |
||
756 | p -= stride; |
||
757 | #else |
||
758 | p += stride; |
||
759 | #endif |
||
760 | *o++ = p; |
||
761 | if (o == row_pointers.end()) |
||
762 | { |
||
763 | /* Internal capacity exhausted. Flush rows and rewind |
||
764 | * to the beginning of the array. |
||
765 | */ |
||
766 | o = rpb; |
||
767 | png_write_rows(ss.png_ptr, o, row_pointers.size()); |
||
768 | } |
||
769 | } |
||
770 | /* Flush any trailing rows */ |
||
771 | if (const auto len = o - rpb) |
||
772 | png_write_rows(ss.png_ptr, rpb, len); |
||
773 | png_write_end(ss.png_ptr, ss.info_ptr); |
||
774 | return 0; |
||
775 | } catch (const d_screenshot::png_exception &) { |
||
776 | /* Destructor unwind will handle the exception. This catch is |
||
777 | * only required to prevent further propagation. |
||
778 | * |
||
779 | * Return nonzero to instruct the caller to delete the failed |
||
780 | * file. |
||
781 | */ |
||
782 | return 1; |
||
783 | } |
||
784 | } |
||
785 | #endif |
||
786 | |||
787 | } |
||
788 | |||
789 | #if DXX_USE_SCREENSHOT |
||
790 | void save_screen_shot(int automap_flag) |
||
791 | { |
||
792 | #if DXX_USE_OGL |
||
793 | if (!CGameArg.DbgGlReadPixelsOk) |
||
794 | { |
||
795 | if (!automap_flag) |
||
796 | HUD_init_message_literal(HM_DEFAULT, "glReadPixels not supported on your configuration"); |
||
797 | return; |
||
798 | } |
||
799 | #endif |
||
800 | #if DXX_USE_SCREENSHOT_FORMAT_PNG |
||
801 | #define DXX_SCREENSHOT_FILE_EXTENSION "png" |
||
802 | #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY |
||
803 | #if DXX_USE_OGL |
||
804 | #define DXX_SCREENSHOT_FILE_EXTENSION "tga" |
||
805 | #else |
||
806 | #define DXX_SCREENSHOT_FILE_EXTENSION "pcx" |
||
807 | #endif |
||
808 | #endif |
||
809 | |||
810 | if (!PHYSFSX_exists(SCRNS_DIR,0)) |
||
811 | PHYSFS_mkdir(SCRNS_DIR); //try making directory |
||
812 | |||
813 | pause_game_world_time p; |
||
814 | unsigned tm_sec; |
||
815 | unsigned tm_min; |
||
816 | unsigned tm_hour; |
||
817 | unsigned tm_mday; |
||
818 | unsigned tm_mon; |
||
819 | unsigned tm_year; |
||
820 | const auto t = time(nullptr); |
||
821 | struct tm *tm = nullptr; |
||
822 | if (t == static_cast<time_t>(-1) || !(tm = gmtime(&t))) |
||
823 | tm_year = tm_mon = tm_mday = tm_hour = tm_min = tm_sec = 0; |
||
824 | else |
||
825 | { |
||
826 | tm_sec = tm->tm_sec; |
||
827 | tm_min = tm->tm_min; |
||
828 | tm_hour = tm->tm_hour; |
||
829 | tm_mday = tm->tm_mday; |
||
830 | tm_mon = tm->tm_mon + 1; |
||
831 | tm_year = tm->tm_year + 1900; |
||
832 | } |
||
833 | /* Colon is not legal in Windows filenames, so use - to separate |
||
834 | * hour:minute:second. |
||
835 | */ |
||
836 | #define DXX_SCREENSHOT_TIME_FORMAT_STRING SCRNS_DIR "%04u-%02u-%02u.%02u-%02u-%02u" |
||
837 | #define DXX_SCREENSHOT_TIME_FORMAT_VALUES tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec |
||
838 | /* Reserve extra space for the trailing -NN disambiguation. This |
||
839 | * is only used if multiple screenshots get the same time, so it is |
||
840 | * unlikely to be seen in practice and very unlikely to be exhausted |
||
841 | * (unless the clock is frozen or returns invalid times). |
||
842 | */ |
||
843 | char savename[sizeof(SCRNS_DIR) + sizeof("2000-01-01.00-00-00.NN.ext")]; |
||
844 | snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING "." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES); |
||
845 | for (unsigned savenum = 0; PHYSFS_exists(savename) && savenum != 100; ++savenum) |
||
846 | { |
||
847 | snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING ".%02u." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES, savenum); |
||
848 | #undef DXX_SCREENSHOT_TIME_FORMAT_VALUES |
||
849 | #undef DXX_SCREENSHOT_TIME_FORMAT_STRING |
||
850 | #undef DXX_SCREENSHOT_FILE_EXTENSION |
||
851 | } |
||
852 | unsigned write_error; |
||
853 | if (const auto file = PHYSFSX_openWriteBuffered(savename)) |
||
854 | { |
||
855 | if (!automap_flag) |
||
856 | HUD_init_message(HM_DEFAULT, "%s '%s'", TXT_DUMPING_SCREEN, &savename[sizeof(SCRNS_DIR) - 1]); |
||
857 | |||
858 | #if DXX_USE_OGL |
||
859 | #if !DXX_USE_OGLES |
||
860 | glReadBuffer(GL_FRONT); |
||
861 | #endif |
||
862 | #if DXX_USE_SCREENSHOT_FORMAT_PNG |
||
863 | write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, void /* unused */); |
||
864 | #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY |
||
865 | write_bmp(file, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height()); |
||
866 | /* write_bmp never fails */ |
||
867 | write_error = 0; |
||
868 | #endif |
||
869 | #else |
||
870 | grs_canvas &screen_canv = grd_curscreen->sc_canvas; |
||
871 | palette_array_t pal; |
||
872 | |||
873 | const auto &&temp_canv = gr_create_canvas(screen_canv.cv_bitmap.bm_w, screen_canv.cv_bitmap.bm_h); |
||
874 | gr_ubitmap(*temp_canv, screen_canv.cv_bitmap); |
||
875 | |||
876 | gr_palette_read(pal); //get actual palette from the hardware |
||
877 | // Correct palette colors |
||
878 | range_for (auto &i, pal) |
||
879 | { |
||
880 | i.r <<= 2; |
||
881 | i.g <<= 2; |
||
882 | i.b <<= 2; |
||
883 | } |
||
884 | #if DXX_USE_SCREENSHOT_FORMAT_PNG |
||
885 | write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, pal); |
||
886 | #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY |
||
887 | write_error = pcx_write_bitmap(file, &temp_canv->cv_bitmap, pal); |
||
888 | #endif |
||
889 | #endif |
||
890 | } |
||
891 | else |
||
892 | { |
||
893 | if (!automap_flag) |
||
894 | HUD_init_message(HM_DEFAULT, "Failed to open screenshot file for writing: %s", &savename[sizeof(SCRNS_DIR) - 1]); |
||
895 | else |
||
896 | con_printf(CON_URGENT, "Failed to open screenshot file for writing: %s", savename); |
||
897 | return; |
||
898 | } |
||
899 | if (write_error) |
||
900 | PHYSFS_delete(savename); |
||
901 | } |
||
902 | #endif |
||
903 | |||
904 | //initialize flying |
||
905 | void fly_init(object_base &obj) |
||
906 | { |
||
907 | obj.control_type = CT_FLYING; |
||
908 | obj.movement_type = MT_PHYSICS; |
||
909 | |||
910 | obj.mtype.phys_info.velocity = {}; |
||
911 | obj.mtype.phys_info.thrust = {}; |
||
912 | obj.mtype.phys_info.rotvel = {}; |
||
913 | obj.mtype.phys_info.rotthrust = {}; |
||
914 | } |
||
915 | |||
916 | } |
||
917 | |||
918 | namespace dsx { |
||
919 | |||
920 | // ------------------------------------------------------------------------------------ |
||
921 | static void do_cloak_stuff(void) |
||
922 | { |
||
923 | auto &Objects = LevelUniqueObjectState.Objects; |
||
924 | auto &vmobjptr = Objects.vmptr; |
||
925 | range_for (auto &&e, enumerate(partial_range(Players, N_players))) |
||
926 | { |
||
927 | auto &plobj = *vmobjptr(e.value.objnum); |
||
928 | auto &player_info = plobj.ctype.player_info; |
||
929 | auto &pl_flags = player_info.powerup_flags; |
||
930 | if (pl_flags & PLAYER_FLAGS_CLOAKED) |
||
931 | { |
||
932 | if (GameTime64 > player_info.cloak_time+CLOAK_TIME_MAX) |
||
933 | { |
||
934 | pl_flags &= ~PLAYER_FLAGS_CLOAKED; |
||
935 | auto &i = e.idx; |
||
936 | if (i == Player_num) { |
||
937 | multi_digi_play_sample(SOUND_CLOAK_OFF, F1_0); |
||
938 | maybe_drop_net_powerup(POW_CLOAK, 1, 0); |
||
939 | if ( Newdemo_state != ND_STATE_PLAYBACK ) |
||
940 | multi_send_decloak(); // For demo recording |
||
941 | } |
||
942 | } |
||
943 | } |
||
944 | } |
||
945 | } |
||
946 | |||
947 | // ------------------------------------------------------------------------------------ |
||
948 | static void do_invulnerable_stuff(player_info &player_info) |
||
949 | { |
||
950 | auto &pl_flags = player_info.powerup_flags; |
||
951 | if (pl_flags & PLAYER_FLAGS_INVULNERABLE) |
||
952 | { |
||
953 | if (GameTime64 > player_info.invulnerable_time + INVULNERABLE_TIME_MAX) |
||
954 | { |
||
955 | pl_flags &= ~PLAYER_FLAGS_INVULNERABLE; |
||
956 | if (auto &FakingInvul = player_info.FakingInvul) |
||
957 | { |
||
958 | FakingInvul = 0; |
||
959 | return; |
||
960 | } |
||
961 | multi_digi_play_sample(SOUND_INVULNERABILITY_OFF, F1_0); |
||
962 | if (Game_mode & GM_MULTI) |
||
963 | { |
||
964 | maybe_drop_net_powerup(POW_INVULNERABILITY, 1, 0); |
||
965 | } |
||
966 | } |
||
967 | } |
||
968 | } |
||
969 | |||
970 | #if defined(DXX_BUILD_DESCENT_I) |
||
971 | static inline void do_afterburner_stuff(object_array &) |
||
972 | { |
||
973 | } |
||
974 | #elif defined(DXX_BUILD_DESCENT_II) |
||
975 | ubyte Last_afterburner_state = 0; |
||
976 | static fix Last_afterburner_charge; |
||
977 | fix64 Time_flash_last_played; |
||
978 | |||
979 | #define AFTERBURNER_LOOP_START ((GameArg.SndDigiSampleRate==SAMPLE_RATE_22K)?32027:(32027/2)) //20098 |
||
980 | #define AFTERBURNER_LOOP_END ((GameArg.SndDigiSampleRate==SAMPLE_RATE_22K)?48452:(48452/2)) //25776 |
||
981 | |||
982 | static void do_afterburner_stuff(object_array &Objects) |
||
983 | { |
||
984 | auto &vmobjptr = Objects.vmptr; |
||
985 | auto &vcobjptridx = Objects.vcptridx; |
||
986 | static sbyte func_play = 0; |
||
987 | |||
988 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
989 | const auto have_afterburner = player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER; |
||
990 | if (!have_afterburner) |
||
991 | Afterburner_charge = 0; |
||
992 | |||
993 | const auto plobj = vcobjptridx(get_local_player().objnum); |
||
994 | if (Endlevel_sequence || Player_dead_state != player_dead_state::no) |
||
995 | { |
||
996 | digi_kill_sound_linked_to_object(plobj); |
||
997 | if (Game_mode & GM_MULTI && func_play) |
||
998 | { |
||
999 | multi_send_sound_function (0,0); |
||
1000 | func_play = 0; |
||
1001 | } |
||
1002 | } |
||
1003 | |||
1004 | if ((Controls.state.afterburner != Last_afterburner_state && Last_afterburner_charge) || (Last_afterburner_state && Last_afterburner_charge && !Afterburner_charge)) { |
||
1005 | if (Afterburner_charge && Controls.state.afterburner && have_afterburner) { |
||
1006 | digi_link_sound_to_object3(SOUND_AFTERBURNER_IGNITE, plobj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, AFTERBURNER_LOOP_START, AFTERBURNER_LOOP_END); |
||
1007 | if (Game_mode & GM_MULTI) |
||
1008 | { |
||
1009 | multi_send_sound_function (3,SOUND_AFTERBURNER_IGNITE); |
||
1010 | func_play = 1; |
||
1011 | } |
||
1012 | } else { |
||
1013 | digi_kill_sound_linked_to_object(plobj); |
||
1014 | digi_link_sound_to_object2(SOUND_AFTERBURNER_PLAY, plobj, 0, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}); |
||
1015 | if (Game_mode & GM_MULTI) |
||
1016 | { |
||
1017 | multi_send_sound_function (0,0); |
||
1018 | func_play = 0; |
||
1019 | } |
||
1020 | } |
||
1021 | } |
||
1022 | |||
1023 | //@@if (Controls.state.afterburner && Afterburner_charge) |
||
1024 | //@@ afterburner_shake(); |
||
1025 | |||
1026 | Last_afterburner_state = Controls.state.afterburner; |
||
1027 | Last_afterburner_charge = Afterburner_charge; |
||
1028 | } |
||
1029 | #endif |
||
1030 | |||
1031 | // Amount to diminish guns towards normal, per second. |
||
1032 | #define DIMINISH_RATE 16 // gots to be a power of 2, else change the code in diminish_palette_towards_normal |
||
1033 | |||
1034 | //adds to rgb values for palette flash |
||
1035 | void PALETTE_FLASH_ADD(int _dr, int _dg, int _db) |
||
1036 | { |
||
1037 | int maxval; |
||
1038 | |||
1039 | PaletteRedAdd += _dr; |
||
1040 | PaletteGreenAdd += _dg; |
||
1041 | PaletteBlueAdd += _db; |
||
1042 | |||
1043 | #if defined(DXX_BUILD_DESCENT_II) |
||
1044 | if (Flash_effect) |
||
1045 | maxval = 60; |
||
1046 | else |
||
1047 | #endif |
||
1048 | maxval = MAX_PALETTE_ADD; |
||
1049 | |||
1050 | if (PaletteRedAdd > maxval) |
||
1051 | PaletteRedAdd = maxval; |
||
1052 | |||
1053 | if (PaletteGreenAdd > maxval) |
||
1054 | PaletteGreenAdd = maxval; |
||
1055 | |||
1056 | if (PaletteBlueAdd > maxval) |
||
1057 | PaletteBlueAdd = maxval; |
||
1058 | |||
1059 | if (PaletteRedAdd < -maxval) |
||
1060 | PaletteRedAdd = -maxval; |
||
1061 | |||
1062 | if (PaletteGreenAdd < -maxval) |
||
1063 | PaletteGreenAdd = -maxval; |
||
1064 | |||
1065 | if (PaletteBlueAdd < -maxval) |
||
1066 | PaletteBlueAdd = -maxval; |
||
1067 | } |
||
1068 | |||
1069 | } |
||
1070 | |||
1071 | static void diminish_palette_color_toward_zero(int& palette_color_add, const int& dec_amount) |
||
1072 | { |
||
1073 | if (palette_color_add > 0 ) { |
||
1074 | if (palette_color_add < dec_amount) |
||
1075 | palette_color_add = 0; |
||
1076 | else |
||
1077 | palette_color_add -= dec_amount; |
||
1078 | } else if (palette_color_add < 0 ) { |
||
1079 | if (palette_color_add > -dec_amount ) |
||
1080 | palette_color_add = 0; |
||
1081 | else |
||
1082 | palette_color_add += dec_amount; |
||
1083 | } |
||
1084 | } |
||
1085 | |||
1086 | namespace dsx { |
||
1087 | |||
1088 | // ------------------------------------------------------------------------------------ |
||
1089 | // Diminish palette effects towards normal. |
||
1090 | static void diminish_palette_towards_normal(void) |
||
1091 | { |
||
1092 | int dec_amount = 0; |
||
1093 | float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64); // to compensate for brightness setting of the game |
||
1094 | |||
1095 | // Diminish at DIMINISH_RATE units/second. |
||
1096 | if (FrameTime < (F1_0/DIMINISH_RATE)) |
||
1097 | { |
||
1098 | static fix diminish_timer = 0; |
||
1099 | diminish_timer += FrameTime; |
||
1100 | if (diminish_timer >= (F1_0/DIMINISH_RATE)) |
||
1101 | { |
||
1102 | diminish_timer -= (F1_0/DIMINISH_RATE); |
||
1103 | dec_amount = 1; |
||
1104 | } |
||
1105 | } |
||
1106 | else |
||
1107 | { |
||
1108 | dec_amount = f2i(FrameTime*DIMINISH_RATE); // one second = DIMINISH_RATE counts |
||
1109 | if (dec_amount == 0) |
||
1110 | dec_amount++; // make sure we decrement by something |
||
1111 | } |
||
1112 | |||
1113 | #if defined(DXX_BUILD_DESCENT_II) |
||
1114 | if (Flash_effect) { |
||
1115 | int force_do = 0; |
||
1116 | static fix Flash_step_up_timer = 0; |
||
1117 | |||
1118 | // Part of hack system to force update of palette after exiting a menu. |
||
1119 | if (Time_flash_last_played) { |
||
1120 | force_do = 1; |
||
1121 | PaletteRedAdd ^= 1; // Very Tricky! In gr_palette_step_up, if all stepups same as last time, won't do anything! |
||
1122 | } |
||
1123 | |||
1124 | if (Time_flash_last_played + F1_0/8 < GameTime64) { |
||
1125 | digi_play_sample( SOUND_CLOAK_OFF, Flash_effect/4); |
||
1126 | Time_flash_last_played = GameTime64; |
||
1127 | } |
||
1128 | |||
1129 | Flash_effect -= FrameTime; |
||
1130 | Flash_step_up_timer += FrameTime; |
||
1131 | if (Flash_effect < 0) |
||
1132 | Flash_effect = 0; |
||
1133 | |||
1134 | if (force_do || (Flash_step_up_timer >= F1_0/26)) // originally time interval based on (d_rand() > 4096) |
||
1135 | { |
||
1136 | Flash_step_up_timer -= (F1_0/26); |
||
1137 | if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) ) |
||
1138 | newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd); |
||
1139 | |||
1140 | gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction ); |
||
1141 | |||
1142 | return; |
||
1143 | } |
||
1144 | |||
1145 | } |
||
1146 | #endif |
||
1147 | |||
1148 | diminish_palette_color_toward_zero(PaletteRedAdd, dec_amount); |
||
1149 | diminish_palette_color_toward_zero(PaletteGreenAdd, dec_amount); |
||
1150 | diminish_palette_color_toward_zero(PaletteBlueAdd, dec_amount); |
||
1151 | |||
1152 | if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) ) |
||
1153 | newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd); |
||
1154 | |||
1155 | gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction ); |
||
1156 | } |
||
1157 | |||
1158 | } |
||
1159 | |||
1160 | namespace { |
||
1161 | |||
1162 | int Redsave, Bluesave, Greensave; |
||
1163 | |||
1164 | } |
||
1165 | |||
1166 | #if defined(DXX_BUILD_DESCENT_II) |
||
1167 | static |
||
1168 | #endif |
||
1169 | void palette_save(void) |
||
1170 | { |
||
1171 | Redsave = PaletteRedAdd; Bluesave = PaletteBlueAdd; Greensave = PaletteGreenAdd; |
||
1172 | } |
||
1173 | |||
1174 | namespace dsx { |
||
1175 | void palette_restore(void) |
||
1176 | { |
||
1177 | float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64); |
||
1178 | |||
1179 | PaletteRedAdd = Redsave; PaletteBlueAdd = Bluesave; PaletteGreenAdd = Greensave; |
||
1180 | gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction ); |
||
1181 | |||
1182 | #if defined(DXX_BUILD_DESCENT_II) |
||
1183 | // Forces flash effect to fixup palette next frame. |
||
1184 | Time_flash_last_played = 0; |
||
1185 | #endif |
||
1186 | } |
||
1187 | |||
1188 | // -------------------------------------------------------------------------------------------------- |
||
1189 | bool allowed_to_fire_laser(const player_info &player_info) |
||
1190 | { |
||
1191 | if (Player_dead_state != player_dead_state::no) |
||
1192 | { |
||
1193 | Global_missile_firing_count = 0; |
||
1194 | return 0; |
||
1195 | } |
||
1196 | |||
1197 | auto &Next_laser_fire_time = player_info.Next_laser_fire_time; |
||
1198 | // Make sure enough time has elapsed to fire laser |
||
1199 | if (Next_laser_fire_time > GameTime64) |
||
1200 | return 0; |
||
1201 | |||
1202 | return 1; |
||
1203 | } |
||
1204 | |||
1205 | int allowed_to_fire_flare(player_info &player_info) |
||
1206 | { |
||
1207 | auto &Next_flare_fire_time = player_info.Next_flare_fire_time; |
||
1208 | if (Next_flare_fire_time > GameTime64) |
||
1209 | return 0; |
||
1210 | |||
1211 | #if defined(DXX_BUILD_DESCENT_II) |
||
1212 | if (player_info.energy < Weapon_info[weapon_id_type::FLARE_ID].energy_usage) |
||
1213 | #define FLARE_BIG_DELAY (F1_0*2) |
||
1214 | Next_flare_fire_time = GameTime64 + FLARE_BIG_DELAY; |
||
1215 | else |
||
1216 | #endif |
||
1217 | Next_flare_fire_time = GameTime64 + F1_0/4; |
||
1218 | |||
1219 | return 1; |
||
1220 | } |
||
1221 | |||
1222 | int allowed_to_fire_missile(const player_info &player_info) |
||
1223 | { |
||
1224 | auto &Next_missile_fire_time = player_info.Next_missile_fire_time; |
||
1225 | // Make sure enough time has elapsed to fire missile |
||
1226 | if (Next_missile_fire_time > GameTime64) |
||
1227 | return 0; |
||
1228 | |||
1229 | return 1; |
||
1230 | } |
||
1231 | } |
||
1232 | |||
1233 | #if defined(DXX_BUILD_DESCENT_II) |
||
1234 | void full_palette_save(void) |
||
1235 | { |
||
1236 | palette_save(); |
||
1237 | reset_palette_add(); |
||
1238 | gr_palette_load( gr_palette ); |
||
1239 | } |
||
1240 | #endif |
||
1241 | |||
1242 | #if DXX_USE_SDLMIXER |
||
1243 | #define EXT_MUSIC_TEXT "Jukebox/Audio CD" |
||
1244 | #else |
||
1245 | #define EXT_MUSIC_TEXT "Audio CD" |
||
1246 | #endif |
||
1247 | |||
1248 | static int free_help(newmenu *, const d_event &event, newmenu_item *items) |
||
1249 | { |
||
1250 | if (event.type == EVENT_WINDOW_CLOSE) |
||
1251 | { |
||
1252 | std::default_delete<newmenu_item[]>()(items); |
||
1253 | } |
||
1254 | return 0; |
||
1255 | } |
||
1256 | |||
1257 | #if (defined(__APPLE__) || defined(macintosh)) |
||
1258 | #define _DXX_HELP_MENU_SAVE_LOAD(VERB) \ |
||
1259 | DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/o)\t SAVE/LOAD GAME", HELP_AF2_3) \ |
||
1260 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3 (\x85-s/o)\t Quick Save/Load", HELP_ASF2_3) |
||
1261 | #define _DXX_HELP_MENU_PAUSE(VERB) DXX_MENUITEM(VERB, TEXT, "Pause (\x85-P)\t Pause", HELP_PAUSE) |
||
1262 | |||
1263 | #if DXX_USE_SDL_REDBOOK_AUDIO |
||
1264 | #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \ |
||
1265 | DXX_MENUITEM(VERB, TEXT, "\x85-E\t Eject Audio CD", HELP_ASF9) \ |
||
1266 | |||
1267 | #else |
||
1268 | #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) |
||
1269 | #endif |
||
1270 | |||
1271 | #define _DXX_HELP_MENU_AUDIO(VERB) \ |
||
1272 | _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \ |
||
1273 | DXX_MENUITEM(VERB, TEXT, "\x85-Up/Down\t Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10) \ |
||
1274 | DXX_MENUITEM(VERB, TEXT, "\x85-Left/Right\t Previous/Next Song", HELP_ASF11_12) |
||
1275 | #define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX) \ |
||
1276 | DXX_MENUITEM(VERB, TEXT, "", PREFIX##_SEP_HINT_CMD) \ |
||
1277 | DXX_MENUITEM(VERB, TEXT, "(Use \x85-# for F#. e.g. \x85-1 for F1)", PREFIX##_HINT_CMD) |
||
1278 | #define _DXX_NETHELP_SAVELOAD_GAME(VERB) \ |
||
1279 | DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/\x85-o)\t SAVE/LOAD COOP GAME", NETHELP_SAVELOAD) |
||
1280 | #else |
||
1281 | #define _DXX_HELP_MENU_SAVE_LOAD(VERB) \ |
||
1282 | DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t SAVE/LOAD GAME", HELP_AF2_3) \ |
||
1283 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3\t Fast Save", HELP_ASF2_3) |
||
1284 | #define _DXX_HELP_MENU_PAUSE(VERB) DXX_MENUITEM(VERB, TEXT, TXT_HELP_PAUSE, HELP_PAUSE) |
||
1285 | |||
1286 | #if DXX_USE_SDL_REDBOOK_AUDIO |
||
1287 | #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \ |
||
1288 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F9\t Eject Audio CD", HELP_ASF9) \ |
||
1289 | |||
1290 | #else |
||
1291 | #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) |
||
1292 | #endif |
||
1293 | |||
1294 | #define _DXX_HELP_MENU_AUDIO(VERB) \ |
||
1295 | _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \ |
||
1296 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F10\t Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10) \ |
||
1297 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F11/F12\t Previous/Next Song", HELP_ASF11_12) |
||
1298 | #define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX) |
||
1299 | #define _DXX_NETHELP_SAVELOAD_GAME(VERB) \ |
||
1300 | DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t SAVE/LOAD COOP GAME", NETHELP_SAVELOAD) |
||
1301 | #endif |
||
1302 | |||
1303 | #if defined(DXX_BUILD_DESCENT_II) |
||
1304 | #define _DXX_HELP_MENU_D2_DXX_F4(VERB) DXX_MENUITEM(VERB, TEXT, TXT_HELP_F4, HELP_F4) |
||
1305 | #define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB) \ |
||
1306 | DXX_MENUITEM(VERB, TEXT, "Shift-F1/F2\t Cycle left/right window", HELP_SF1_2) \ |
||
1307 | DXX_MENUITEM(VERB, TEXT, "Shift-F4\t GuideBot menu", HELP_SF4) \ |
||
1308 | DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F4\t Rename GuideBot", HELP_ASF4) \ |
||
1309 | DXX_MENUITEM(VERB, TEXT, "Shift-F5/F6\t Drop primary/secondary", HELP_SF5_6) \ |
||
1310 | DXX_MENUITEM(VERB, TEXT, "Shift-number\t GuideBot commands", HELP_GUIDEBOT_COMMANDS) |
||
1311 | #define _DXX_NETHELP_DROPFLAG(VERB) \ |
||
1312 | DXX_MENUITEM(VERB, TEXT, "ALT-0\t DROP FLAG", NETHELP_DROPFLAG) |
||
1313 | #else |
||
1314 | #define _DXX_HELP_MENU_D2_DXX_F4(VERB) |
||
1315 | #define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB) |
||
1316 | #define _DXX_NETHELP_DROPFLAG(VERB) |
||
1317 | #endif |
||
1318 | |||
1319 | #define DXX_HELP_MENU(VERB) \ |
||
1320 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_ESC, HELP_ESC) \ |
||
1321 | DXX_MENUITEM(VERB, TEXT, "SHIFT-ESC\t SHOW GAME LOG", HELP_LOG) \ |
||
1322 | DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", HELP_HELP) \ |
||
1323 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, HELP_F2) \ |
||
1324 | _DXX_HELP_MENU_SAVE_LOAD(VERB) \ |
||
1325 | DXX_MENUITEM(VERB, TEXT, "F3\t SWITCH COCKPIT MODES", HELP_F3) \ |
||
1326 | _DXX_HELP_MENU_D2_DXX_F4(VERB) \ |
||
1327 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_F5, HELP_F5) \ |
||
1328 | DXX_MENUITEM(VERB, TEXT, "ALT-F7\t SWITCH HUD MODES", HELP_AF7) \ |
||
1329 | _DXX_HELP_MENU_PAUSE(VERB) \ |
||
1330 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_PRTSCN, HELP_PRTSCN) \ |
||
1331 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_1TO5, HELP_1TO5) \ |
||
1332 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_6TO10, HELP_6TO10) \ |
||
1333 | _DXX_HELP_MENU_D2_DXX_FEATURES(VERB) \ |
||
1334 | _DXX_HELP_MENU_AUDIO(VERB) \ |
||
1335 | _DXX_HELP_MENU_HINT_CMD_KEY(VERB, HELP) \ |
||
1336 | |||
1337 | enum { |
||
1338 | DXX_HELP_MENU(ENUM) |
||
1339 | }; |
||
1340 | |||
1341 | void show_help() |
||
1342 | { |
||
1343 | const unsigned nitems = DXX_HELP_MENU(COUNT); |
||
1344 | auto m = new newmenu_item[nitems]; |
||
1345 | DXX_HELP_MENU(ADD); |
||
1346 | newmenu_dotiny(NULL, TXT_KEYS, nitems, m, 0, free_help, m); |
||
1347 | } |
||
1348 | |||
1349 | #undef DXX_HELP_MENU |
||
1350 | |||
1351 | #define DXX_NETHELP_MENU(VERB) \ |
||
1352 | DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", NETHELP_HELP) \ |
||
1353 | _DXX_NETHELP_DROPFLAG(VERB) \ |
||
1354 | _DXX_NETHELP_SAVELOAD_GAME(VERB) \ |
||
1355 | DXX_MENUITEM(VERB, TEXT, "ALT-F4\t SHOW PLAYER NAMES ON HUD", NETHELP_HUDNAMES) \ |
||
1356 | DXX_MENUITEM(VERB, TEXT, "F7\t TOGGLE KILL LIST", NETHELP_TOGGLE_KILL_LIST) \ |
||
1357 | DXX_MENUITEM(VERB, TEXT, "F8\t SEND MESSAGE", NETHELP_SENDMSG) \ |
||
1358 | DXX_MENUITEM(VERB, TEXT, "(SHIFT-)F9 to F12\t (DEFINE)SEND MACRO", NETHELP_MACRO) \ |
||
1359 | DXX_MENUITEM(VERB, TEXT, "PAUSE\t SHOW NETGAME INFORMATION", NETHELP_GAME_INFO) \ |
||
1360 | DXX_MENUITEM(VERB, TEXT, "SHIFT-PAUSE\t SHOW NETGAME INFO & RULES", NETHELP_GAME_INFORULES) \ |
||
1361 | _DXX_HELP_MENU_HINT_CMD_KEY(VERB, NETHELP) \ |
||
1362 | DXX_MENUITEM(VERB, TEXT, "", NETHELP_SEP1) \ |
||
1363 | DXX_MENUITEM(VERB, TEXT, "MULTIPLAYER MESSAGE COMMANDS:", NETHELP_COMMAND_HEADER) \ |
||
1364 | DXX_MENUITEM(VERB, TEXT, "(*): TEXT\t SEND TEXT TO PLAYER/TEAM (*)", NETHELP_DIRECT_MESSAGE) \ |
||
1365 | DXX_MENUITEM(VERB, TEXT, "/Handicap: (*)\t SET YOUR STARTING SHIELDS TO (*) [10-100]", NETHELP_COMMAND_HANDICAP) \ |
||
1366 | DXX_MENUITEM(VERB, TEXT, "/move: (*)\t MOVE PLAYER (*) TO OTHER TEAM (Host-only)", NETHELP_COMMAND_MOVE) \ |
||
1367 | DXX_MENUITEM(VERB, TEXT, "/kick: (*)\t KICK PLAYER (*) FROM GAME (Host-only)", NETHELP_COMMAND_KICK) \ |
||
1368 | DXX_MENUITEM(VERB, TEXT, "/KillReactor\t BLOW UP THE MINE (Host-only)", NETHELP_COMMAND_KILL_REACTOR) \ |
||
1369 | |||
1370 | enum { |
||
1371 | DXX_NETHELP_MENU(ENUM) |
||
1372 | }; |
||
1373 | |||
1374 | void show_netgame_help() |
||
1375 | { |
||
1376 | const unsigned nitems = DXX_NETHELP_MENU(COUNT); |
||
1377 | auto m = new newmenu_item[nitems]; |
||
1378 | DXX_NETHELP_MENU(ADD); |
||
1379 | newmenu_dotiny(NULL, TXT_KEYS, nitems, m, 0, free_help, m); |
||
1380 | } |
||
1381 | |||
1382 | #undef DXX_NETHELP_MENU |
||
1383 | |||
1384 | #define DXX_NEWDEMO_HELP_MENU(VERB) \ |
||
1385 | DXX_MENUITEM(VERB, TEXT, "ESC\t QUIT DEMO PLAYBACK", DEMOHELP_QUIT) \ |
||
1386 | DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", DEMOHELP_HELP) \ |
||
1387 | DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, DEMOHELP_F2) \ |
||
1388 | DXX_MENUITEM(VERB, TEXT, "F3\t SWITCH COCKPIT MODES", DEMOHELP_F3) \ |
||
1389 | DXX_MENUITEM(VERB, TEXT, "F4\t TOGGLE PERCENTAGE DISPLAY", DEMOHELP_F4) \ |
||
1390 | DXX_MENUITEM(VERB, TEXT, "UP\t PLAY", DEMOHELP_PLAY) \ |
||
1391 | DXX_MENUITEM(VERB, TEXT, "DOWN\t PAUSE", DEMOHELP_PAUSE) \ |
||
1392 | DXX_MENUITEM(VERB, TEXT, "RIGHT\t ONE FRAME FORWARD", DEMOHELP_FRAME_FORWARD) \ |
||
1393 | DXX_MENUITEM(VERB, TEXT, "LEFT\t ONE FRAME BACKWARD", DEMOHELP_FRAME_BACKWARD) \ |
||
1394 | DXX_MENUITEM(VERB, TEXT, "SHIFT-RIGHT\t FAST FORWARD", DEMOHELP_FAST_FORWARD) \ |
||
1395 | DXX_MENUITEM(VERB, TEXT, "SHIFT-LEFT\t FAST BACKWARD", DEMOHELP_FAST_BACKWARD) \ |
||
1396 | DXX_MENUITEM(VERB, TEXT, "CTRL-RIGHT\t JUMP TO END", DEMOHELP_JUMP_END) \ |
||
1397 | DXX_MENUITEM(VERB, TEXT, "CTRL-LEFT\t JUMP TO START", DEMOHELP_JUMP_START) \ |
||
1398 | _DXX_HELP_MENU_HINT_CMD_KEY(VERB, DEMOHELP) \ |
||
1399 | |||
1400 | enum { |
||
1401 | DXX_NEWDEMO_HELP_MENU(ENUM) |
||
1402 | }; |
||
1403 | |||
1404 | void show_newdemo_help() |
||
1405 | { |
||
1406 | const unsigned nitems = DXX_NEWDEMO_HELP_MENU(COUNT); |
||
1407 | auto m = new newmenu_item[nitems]; |
||
1408 | DXX_NEWDEMO_HELP_MENU(ADD); |
||
1409 | newmenu_dotiny(NULL, "DEMO PLAYBACK CONTROLS", nitems, m, 0, free_help, m); |
||
1410 | } |
||
1411 | |||
1412 | #undef DXX_NEWDEMO_HELP_MENU |
||
1413 | |||
1414 | #define LEAVE_TIME 0x4000 //how long until we decide key is down (Used to be 0x4000) |
||
1415 | |||
1416 | enum class leave_type : uint_fast8_t |
||
1417 | { |
||
1418 | none, |
||
1419 | maybe_on_release, |
||
1420 | wait_for_release, |
||
1421 | on_press, |
||
1422 | }; |
||
1423 | |||
1424 | static leave_type leave_mode; |
||
1425 | |||
1426 | static void end_rear_view() |
||
1427 | { |
||
1428 | Rear_view = 0; |
||
1429 | if (PlayerCfg.CockpitMode[1] == CM_REAR_VIEW) |
||
1430 | select_cockpit(PlayerCfg.CockpitMode[0]); |
||
1431 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1432 | newdemo_record_restore_rearview(); |
||
1433 | } |
||
1434 | |||
1435 | static void check_end_rear_view() |
||
1436 | { |
||
1437 | leave_mode = leave_type::none; |
||
1438 | if (Rear_view) |
||
1439 | end_rear_view(); |
||
1440 | } |
||
1441 | |||
1442 | //deal with rear view - switch it on, or off, or whatever |
||
1443 | void check_rear_view() |
||
1444 | { |
||
1445 | static fix64 entry_time; |
||
1446 | |||
1447 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
1448 | return; |
||
1449 | |||
1450 | const auto rear_view = Controls.state.rear_view; |
||
1451 | switch (leave_mode) |
||
1452 | { |
||
1453 | case leave_type::none: |
||
1454 | if (!rear_view) |
||
1455 | return; |
||
1456 | if (Rear_view) |
||
1457 | end_rear_view(); |
||
1458 | else |
||
1459 | { |
||
1460 | Rear_view = 1; |
||
1461 | leave_mode = leave_type::maybe_on_release; //means wait for another key |
||
1462 | entry_time = timer_query(); |
||
1463 | if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) |
||
1464 | select_cockpit(CM_REAR_VIEW); |
||
1465 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1466 | newdemo_record_rearview(); |
||
1467 | } |
||
1468 | return; |
||
1469 | case leave_type::maybe_on_release: |
||
1470 | if (rear_view) |
||
1471 | { |
||
1472 | if (timer_query() - entry_time > LEAVE_TIME) |
||
1473 | leave_mode = leave_type::wait_for_release; |
||
1474 | } |
||
1475 | else |
||
1476 | leave_mode = leave_type::on_press; |
||
1477 | return; |
||
1478 | case leave_type::wait_for_release: |
||
1479 | if (!rear_view) |
||
1480 | check_end_rear_view(); |
||
1481 | return; |
||
1482 | case leave_type::on_press: |
||
1483 | if (rear_view) |
||
1484 | { |
||
1485 | Controls.state.rear_view = 0; |
||
1486 | check_end_rear_view(); |
||
1487 | } |
||
1488 | return; |
||
1489 | default: |
||
1490 | break; |
||
1491 | } |
||
1492 | } |
||
1493 | |||
1494 | void reset_rear_view(void) |
||
1495 | { |
||
1496 | if (Rear_view) { |
||
1497 | if (Newdemo_state == ND_STATE_RECORDING) |
||
1498 | newdemo_record_restore_rearview(); |
||
1499 | } |
||
1500 | |||
1501 | Rear_view = 0; |
||
1502 | select_cockpit(PlayerCfg.CockpitMode[0]); |
||
1503 | } |
||
1504 | |||
1505 | int cheats_enabled() |
||
1506 | { |
||
1507 | return cheats.enabled; |
||
1508 | } |
||
1509 | |||
1510 | //turns off all cheats & resets cheater flag |
||
1511 | void game_disable_cheats() |
||
1512 | { |
||
1513 | #if defined(DXX_BUILD_DESCENT_II) |
||
1514 | if (cheats.homingfire) |
||
1515 | weapons_homing_all_reset(); |
||
1516 | #endif |
||
1517 | |||
1518 | cheats = {}; |
||
1519 | } |
||
1520 | |||
1521 | // game_setup() |
||
1522 | // ---------------------------------------------------------------------------- |
||
1523 | |||
1524 | namespace dsx { |
||
1525 | |||
1526 | window *game_setup(void) |
||
1527 | { |
||
1528 | |||
1529 | PlayerCfg.CockpitMode[1] = PlayerCfg.CockpitMode[0]; |
||
1530 | last_drawn_cockpit = -1; // Force cockpit to redraw next time a frame renders. |
||
1531 | Endlevel_sequence = 0; |
||
1532 | |||
1533 | const auto game_wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, game_handler, unused_window_userdata); |
||
1534 | if (!game_wind) |
||
1535 | return NULL; |
||
1536 | |||
1537 | reset_palette_add(); |
||
1538 | init_cockpit(); |
||
1539 | init_gauges(); |
||
1540 | netplayerinfo_on = 0; |
||
1541 | |||
1542 | #if DXX_USE_EDITOR |
||
1543 | if (!Cursegp) |
||
1544 | { |
||
1545 | Cursegp = imsegptridx(segment_first); |
||
1546 | Curside = 0; |
||
1547 | } |
||
1548 | #endif |
||
1549 | |||
1550 | Viewer = ConsoleObject; |
||
1551 | fly_init(*ConsoleObject); |
||
1552 | Game_suspended = 0; |
||
1553 | reset_time(); |
||
1554 | FrameTime = 0; //make first frame zero |
||
1555 | |||
1556 | fix_object_segs(); |
||
1557 | if (CGameArg.SysAutoRecordDemo && Newdemo_state == ND_STATE_NORMAL) |
||
1558 | newdemo_start_recording(); |
||
1559 | return game_wind; |
||
1560 | } |
||
1561 | |||
1562 | } |
||
1563 | |||
1564 | window *Game_wind = NULL; |
||
1565 | |||
1566 | namespace dsx { |
||
1567 | |||
1568 | // Event handler for the game |
||
1569 | window_event_result game_handler(window *,const d_event &event, const unused_window_userdata_t *) |
||
1570 | { |
||
1571 | auto result = window_event_result::ignored; |
||
1572 | |||
1573 | switch (event.type) |
||
1574 | { |
||
1575 | case EVENT_WINDOW_ACTIVATED: |
||
1576 | set_screen_mode(SCREEN_GAME); |
||
1577 | |||
1578 | event_toggle_focus(1); |
||
1579 | key_toggle_repeat(0); |
||
1580 | game_flush_inputs(); |
||
1581 | |||
1582 | if (time_paused) |
||
1583 | start_time(); |
||
1584 | |||
1585 | if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK))) |
||
1586 | digi_resume_digi_sounds(); |
||
1587 | |||
1588 | if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK))) |
||
1589 | palette_restore(); |
||
1590 | |||
1591 | reset_cockpit(); |
||
1592 | break; |
||
1593 | |||
1594 | case EVENT_WINDOW_DEACTIVATED: |
||
1595 | if (!(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence)) ) |
||
1596 | stop_time(); |
||
1597 | |||
1598 | if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK))) |
||
1599 | digi_pause_digi_sounds(); |
||
1600 | |||
1601 | if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK))) |
||
1602 | full_palette_save(); |
||
1603 | |||
1604 | event_toggle_focus(0); |
||
1605 | key_toggle_repeat(1); |
||
1606 | break; |
||
1607 | |||
1608 | case EVENT_JOYSTICK_BUTTON_UP: |
||
1609 | case EVENT_JOYSTICK_BUTTON_DOWN: |
||
1610 | case EVENT_JOYSTICK_MOVED: |
||
1611 | case EVENT_MOUSE_BUTTON_UP: |
||
1612 | case EVENT_MOUSE_BUTTON_DOWN: |
||
1613 | case EVENT_MOUSE_MOVED: |
||
1614 | case EVENT_KEY_COMMAND: |
||
1615 | case EVENT_KEY_RELEASE: |
||
1616 | case EVENT_IDLE: |
||
1617 | return ReadControls(event); |
||
1618 | |||
1619 | case EVENT_WINDOW_DRAW: |
||
1620 | if (!time_paused) |
||
1621 | { |
||
1622 | calc_frame_time(); |
||
1623 | result = GameProcessFrame(); |
||
1624 | } |
||
1625 | |||
1626 | if (!Automap_active) // efficiency hack |
||
1627 | { |
||
1628 | if (force_cockpit_redraw) { //screen need redrawing? |
||
1629 | init_cockpit(); |
||
1630 | force_cockpit_redraw=0; |
||
1631 | } |
||
1632 | game_render_frame(); |
||
1633 | } |
||
1634 | break; |
||
1635 | |||
1636 | case EVENT_WINDOW_CLOSE: |
||
1637 | digi_stop_digi_sounds(); |
||
1638 | |||
1639 | if ( (Newdemo_state == ND_STATE_RECORDING) || (Newdemo_state == ND_STATE_PAUSED) ) |
||
1640 | newdemo_stop_recording(); |
||
1641 | |||
1642 | multi_leave_game(); |
||
1643 | |||
1644 | if ( Newdemo_state == ND_STATE_PLAYBACK ) |
||
1645 | newdemo_stop_playback(); |
||
1646 | |||
1647 | songs_play_song( SONG_TITLE, 1 ); |
||
1648 | |||
1649 | game_disable_cheats(); |
||
1650 | Game_mode = GM_GAME_OVER; |
||
1651 | #if DXX_USE_EDITOR |
||
1652 | if (!EditorWindow) // have to do it this way because of the necessary longjmp. Yuck. |
||
1653 | #endif |
||
1654 | show_menus(); |
||
1655 | event_toggle_focus(0); |
||
1656 | key_toggle_repeat(1); |
||
1657 | Game_wind = nullptr; |
||
1658 | return window_event_result::ignored; |
||
1659 | break; |
||
1660 | |||
1661 | case EVENT_LOOP_BEGIN_LOOP: |
||
1662 | kconfig_begin_loop(Controls); |
||
1663 | break; |
||
1664 | |||
1665 | default: |
||
1666 | break; |
||
1667 | } |
||
1668 | |||
1669 | return result; |
||
1670 | } |
||
1671 | |||
1672 | // Initialise game, actually runs in main event loop |
||
1673 | void game() |
||
1674 | { |
||
1675 | hide_menus(); |
||
1676 | Game_wind = game_setup(); |
||
1677 | } |
||
1678 | |||
1679 | } |
||
1680 | |||
1681 | //called at the end of the program |
||
1682 | void close_game() |
||
1683 | { |
||
1684 | close_gauges(); |
||
1685 | restore_effect_bitmap_icons(); |
||
1686 | } |
||
1687 | |||
1688 | #if defined(DXX_BUILD_DESCENT_II) |
||
1689 | namespace dsx { |
||
1690 | object *Missile_viewer=NULL; |
||
1691 | object_signature_t Missile_viewer_sig; |
||
1692 | |||
1693 | std::array<game_marker_index, 2> Marker_viewer_num{{ |
||
1694 | game_marker_index::None, |
||
1695 | game_marker_index::None, |
||
1696 | }}; |
||
1697 | std::array<unsigned, 2> Coop_view_player{{UINT_MAX, UINT_MAX}}; |
||
1698 | |||
1699 | //returns ptr to escort robot, or NULL |
||
1700 | imobjptridx_t find_escort(fvmobjptridx &vmobjptridx, const d_level_shared_robot_info_state::d_robot_info_array &Robot_info) |
||
1701 | { |
||
1702 | range_for (const auto &&o, vmobjptridx) |
||
1703 | { |
||
1704 | if (o->type == OBJ_ROBOT && Robot_info[get_robot_id(o)].companion) |
||
1705 | return imobjptridx_t(o); |
||
1706 | } |
||
1707 | return object_none; |
||
1708 | } |
||
1709 | |||
1710 | //if water or fire level, make occasional sound |
||
1711 | static void do_ambient_sounds() |
||
1712 | { |
||
1713 | int has_water,has_lava; |
||
1714 | int sound; |
||
1715 | |||
1716 | const auto s2_flags = vcsegptr(ConsoleObject->segnum)->s2_flags; |
||
1717 | has_lava = (s2_flags & S2F_AMBIENT_LAVA); |
||
1718 | has_water = (s2_flags & S2F_AMBIENT_WATER); |
||
1719 | |||
1720 | if (has_lava) { //has lava |
||
1721 | sound = SOUND_AMBIENT_LAVA; |
||
1722 | if (has_water && (d_rand() & 1)) //both, pick one |
||
1723 | sound = SOUND_AMBIENT_WATER; |
||
1724 | } |
||
1725 | else if (has_water) //just water |
||
1726 | sound = SOUND_AMBIENT_WATER; |
||
1727 | else |
||
1728 | return; |
||
1729 | |||
1730 | if (((d_rand() << 3) < FrameTime)) { //play the sound |
||
1731 | fix volume = d_rand() + f1_0/2; |
||
1732 | digi_play_sample(sound,volume); |
||
1733 | } |
||
1734 | } |
||
1735 | } |
||
1736 | #endif |
||
1737 | |||
1738 | void game_leave_menus(void) |
||
1739 | { |
||
1740 | if (!Game_wind) |
||
1741 | return; |
||
1742 | for (;;) // go through all windows and actually close them if they want to |
||
1743 | { |
||
1744 | const auto wind = window_get_front(); |
||
1745 | if (!wind) |
||
1746 | break; |
||
1747 | if (wind == Game_wind) |
||
1748 | break; |
||
1749 | if (!window_close(wind)) |
||
1750 | break; |
||
1751 | } |
||
1752 | } |
||
1753 | |||
1754 | namespace dsx { |
||
1755 | |||
1756 | window_event_result GameProcessFrame() |
||
1757 | { |
||
1758 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
1759 | auto &Objects = LevelUniqueObjectState.Objects; |
||
1760 | auto &vmobjptr = Objects.vmptr; |
||
1761 | auto &plrobj = get_local_plrobj(); |
||
1762 | auto &player_info = plrobj.ctype.player_info; |
||
1763 | auto &local_player_shields_ref = plrobj.shields; |
||
1764 | fix player_shields = local_player_shields_ref; |
||
1765 | const auto player_was_dead = Player_dead_state; |
||
1766 | auto result = window_event_result::ignored; |
||
1767 | |||
1768 | state_poll_autosave_game(GameUniqueState, LevelUniqueObjectState); |
||
1769 | update_player_stats(); |
||
1770 | diminish_palette_towards_normal(); // Should leave palette effect up for as long as possible by putting right before render. |
||
1771 | do_afterburner_stuff(Objects); |
||
1772 | do_cloak_stuff(); |
||
1773 | do_invulnerable_stuff(player_info); |
||
1774 | #if defined(DXX_BUILD_DESCENT_II) |
||
1775 | init_ai_frame(player_info.powerup_flags); |
||
1776 | result = do_final_boss_frame(); |
||
1777 | |||
1778 | auto &pl_flags = player_info.powerup_flags; |
||
1779 | if (pl_flags & PLAYER_FLAGS_HEADLIGHT_ON) |
||
1780 | { |
||
1781 | static int turned_off=0; |
||
1782 | auto &energy = player_info.energy; |
||
1783 | energy -= (FrameTime*3/8); |
||
1784 | if (energy < i2f(10)) { |
||
1785 | if (!turned_off) { |
||
1786 | pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON; |
||
1787 | turned_off = 1; |
||
1788 | if (Game_mode & GM_MULTI) |
||
1789 | multi_send_flags(Player_num); |
||
1790 | } |
||
1791 | } |
||
1792 | else |
||
1793 | turned_off = 0; |
||
1794 | |||
1795 | if (energy <= 0) |
||
1796 | { |
||
1797 | energy = 0; |
||
1798 | pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON; |
||
1799 | if (Game_mode & GM_MULTI) |
||
1800 | multi_send_flags(Player_num); |
||
1801 | } |
||
1802 | } |
||
1803 | #endif |
||
1804 | |||
1805 | #if DXX_USE_EDITOR |
||
1806 | check_create_player_path(); |
||
1807 | player_follow_path(vmobjptr(ConsoleObject)); |
||
1808 | #endif |
||
1809 | |||
1810 | if (Game_mode & GM_MULTI) |
||
1811 | { |
||
1812 | result = std::max(multi_do_frame(), result); |
||
1813 | if (Netgame.PlayTimeAllowed.count()) |
||
1814 | { |
||
1815 | if (ThisLevelTime >= Netgame.PlayTimeAllowed) |
||
1816 | multi_check_for_killgoal_winner(); |
||
1817 | ThisLevelTime += d_time_fix(FrameTime); |
||
1818 | } |
||
1819 | } |
||
1820 | |||
1821 | result = std::max(dead_player_frame(), result); |
||
1822 | if (Newdemo_state != ND_STATE_PLAYBACK) |
||
1823 | result = std::max(do_controlcen_dead_frame(), result); |
||
1824 | if (result == window_event_result::close) |
||
1825 | return result; // skip everything else - don't set Player_dead_state again |
||
1826 | |||
1827 | #if defined(DXX_BUILD_DESCENT_II) |
||
1828 | process_super_mines_frame(); |
||
1829 | do_seismic_stuff(); |
||
1830 | do_ambient_sounds(); |
||
1831 | #endif |
||
1832 | |||
1833 | digi_sync_sounds(); |
||
1834 | |||
1835 | if (Endlevel_sequence) { |
||
1836 | result = std::max(do_endlevel_frame(), result); |
||
1837 | powerup_grab_cheat_all(); |
||
1838 | do_special_effects(); |
||
1839 | return result; //skip everything else |
||
1840 | } |
||
1841 | |||
1842 | if ((Newdemo_state != ND_STATE_PLAYBACK) || (Newdemo_vcr_state != ND_STATE_PAUSED)) { |
||
1843 | do_special_effects(); |
||
1844 | wall_frame_process(); |
||
1845 | } |
||
1846 | |||
1847 | if (LevelUniqueControlCenterState.Control_center_destroyed) |
||
1848 | { |
||
1849 | if (Newdemo_state==ND_STATE_RECORDING ) |
||
1850 | newdemo_record_control_center_destroyed(); |
||
1851 | } |
||
1852 | |||
1853 | flash_frame(); |
||
1854 | |||
1855 | if ( Newdemo_state == ND_STATE_PLAYBACK ) |
||
1856 | { |
||
1857 | result = std::max(newdemo_playback_one_frame(), result); |
||
1858 | if ( Newdemo_state != ND_STATE_PLAYBACK ) |
||
1859 | { |
||
1860 | Assert(result == window_event_result::close); |
||
1861 | return window_event_result::close; // Go back to menu |
||
1862 | } |
||
1863 | } |
||
1864 | else |
||
1865 | { // Note the link to above! |
||
1866 | #ifndef NEWHOMER |
||
1867 | player_info.homing_object_dist = -1; // Assume not being tracked. Laser_do_weapon_sequence modifies this. |
||
1868 | #endif |
||
1869 | result = std::max(game_move_all_objects(), result); |
||
1870 | powerup_grab_cheat_all(); |
||
1871 | |||
1872 | if (Endlevel_sequence) //might have been started during move |
||
1873 | return result; |
||
1874 | |||
1875 | fuelcen_update_all(); |
||
1876 | |||
1877 | do_ai_frame_all(); |
||
1878 | |||
1879 | auto laser_firing_count = FireLaser(player_info); |
||
1880 | if (auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time) |
||
1881 | { |
||
1882 | if (player_info.Primary_weapon != primary_weapon_index_t::FUSION_INDEX) |
||
1883 | Auto_fire_fusion_cannon_time = 0; |
||
1884 | else if ((laser_firing_count = (GameTime64 + FrameTime/2 >= Auto_fire_fusion_cannon_time))) |
||
1885 | { |
||
1886 | Auto_fire_fusion_cannon_time = 0; |
||
1887 | } else if (d_tick_step) { |
||
1888 | const auto rx = (d_rand() - 16384) / 8; |
||
1889 | const auto rz = (d_rand() - 16384) / 8; |
||
1890 | const auto &&console = vmobjptr(ConsoleObject); |
||
1891 | auto &rotvel = console->mtype.phys_info.rotvel; |
||
1892 | rotvel.x += rx; |
||
1893 | rotvel.z += rz; |
||
1894 | |||
1895 | const auto bump_amount = player_info.Fusion_charge > F1_0*2 ? player_info.Fusion_charge * 4 : F1_0 * 4; |
||
1896 | bump_one_object(console, make_random_vector(), bump_amount); |
||
1897 | } |
||
1898 | } |
||
1899 | |||
1900 | if (laser_firing_count) |
||
1901 | do_laser_firing_player(plrobj); |
||
1902 | delayed_autoselect(player_info); |
||
1903 | } |
||
1904 | |||
1905 | if (Do_appearance_effect) { |
||
1906 | Do_appearance_effect = 0; |
||
1907 | create_player_appearance_effect(Vclip, *ConsoleObject); |
||
1908 | } |
||
1909 | |||
1910 | #if defined(DXX_BUILD_DESCENT_II) |
||
1911 | omega_charge_frame(player_info); |
||
1912 | slide_textures(); |
||
1913 | auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights; |
||
1914 | flicker_lights(LevelSharedDestructibleLightState, Flickering_light_state, vmsegptridx); |
||
1915 | |||
1916 | //if the player is taking damage, give up guided missile control |
||
1917 | if (local_player_shields_ref != player_shields) |
||
1918 | release_guided_missile(LevelUniqueObjectState, Player_num); |
||
1919 | #endif |
||
1920 | |||
1921 | // Check if we have to close in-game menus for multiplayer |
||
1922 | if ((Game_mode & GM_MULTI) && (get_local_player().connected == CONNECT_PLAYING)) |
||
1923 | { |
||
1924 | if (Endlevel_sequence || Player_dead_state != player_was_dead || local_player_shields_ref < player_shields || (LevelUniqueControlCenterState.Control_center_destroyed && LevelUniqueControlCenterState.Countdown_seconds_left < 10)) |
||
1925 | game_leave_menus(); |
||
1926 | } |
||
1927 | |||
1928 | return result; |
||
1929 | } |
||
1930 | |||
1931 | #if defined(DXX_BUILD_DESCENT_II) |
||
1932 | void compute_slide_segs() |
||
1933 | { |
||
1934 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
1935 | range_for (const auto &&segp, vmsegptr) |
||
1936 | { |
||
1937 | uint8_t slide_textures = 0; |
||
1938 | range_for (const int sidenum, xrange(6u)) { |
||
1939 | const auto &sside = segp->shared_segment::sides[sidenum]; |
||
1940 | const auto &uside = segp->unique_segment::sides[sidenum]; |
||
1941 | const auto &ti = TmapInfo[uside.tmap_num]; |
||
1942 | if (!(ti.slide_u || ti.slide_v)) |
||
1943 | continue; |
||
1944 | if (IS_CHILD(segp->children[sidenum]) && sside.wall_num == wall_none) |
||
1945 | /* If a wall exists, it could be visible at start or |
||
1946 | * become visible later, so always enable sliding for |
||
1947 | * walls. |
||
1948 | */ |
||
1949 | continue; |
||
1950 | slide_textures |= 1 << sidenum; |
||
1951 | } |
||
1952 | segp->slide_textures = slide_textures; |
||
1953 | } |
||
1954 | } |
||
1955 | |||
1956 | template <fix uvl::*p> |
||
1957 | static void update_uv(std::array<uvl, 4> &uvls, uvl &i, fix a) |
||
1958 | { |
||
1959 | if (!a) |
||
1960 | return; |
||
1961 | const auto ip = (i.*p += a); |
||
1962 | if (ip > f2_0) |
||
1963 | range_for (auto &j, uvls) |
||
1964 | j.*p -= f1_0; |
||
1965 | else if (ip < -f2_0) |
||
1966 | range_for (auto &j, uvls) |
||
1967 | j.*p += f1_0; |
||
1968 | } |
||
1969 | |||
1970 | // ----------------------------------------------------------------------------- |
||
1971 | static void slide_textures(void) |
||
1972 | { |
||
1973 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
1974 | range_for (const auto &&segp, vmsegptr) |
||
1975 | { |
||
1976 | if (const auto slide_seg = segp->slide_textures) |
||
1977 | { |
||
1978 | range_for (const int sidenum, xrange(6u)) { |
||
1979 | if (slide_seg & (1 << sidenum)) |
||
1980 | { |
||
1981 | auto &side = segp->unique_segment::sides[sidenum]; |
||
1982 | const auto &ti = TmapInfo[side.tmap_num]; |
||
1983 | const auto tiu = ti.slide_u; |
||
1984 | const auto tiv = ti.slide_v; |
||
1985 | if (tiu || tiv) |
||
1986 | { |
||
1987 | const auto frametime = FrameTime; |
||
1988 | const auto ua = fixmul(frametime, tiu << 8); |
||
1989 | const auto va = fixmul(frametime, tiv << 8); |
||
1990 | auto &uvls = side.uvls; |
||
1991 | range_for (auto &i, uvls) |
||
1992 | { |
||
1993 | update_uv<&uvl::u>(uvls, i, ua); |
||
1994 | update_uv<&uvl::v>(uvls, i, va); |
||
1995 | } |
||
1996 | } |
||
1997 | } |
||
1998 | } |
||
1999 | } |
||
2000 | } |
||
2001 | } |
||
2002 | |||
2003 | constexpr std::integral_constant<fix, INT32_MIN> flicker_timer_disabled{}; |
||
2004 | |||
2005 | static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx) |
||
2006 | { |
||
2007 | auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo; |
||
2008 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
2009 | auto &vcwallptr = Walls.vcptr; |
||
2010 | range_for (auto &f, partial_range(fls.Flickering_lights, fls.Num_flickering_lights)) |
||
2011 | { |
||
2012 | if (f.timer == flicker_timer_disabled) //disabled |
||
2013 | continue; |
||
2014 | const auto &&segp = vmsegptridx(f.segnum); |
||
2015 | const auto sidenum = f.sidenum; |
||
2016 | { |
||
2017 | auto &side = segp->unique_segment::sides[sidenum]; |
||
2018 | if (!(TmapInfo[side.tmap_num].lighting || TmapInfo[side.tmap_num2 & 0x3fff].lighting)) |
||
2019 | continue; |
||
2020 | } |
||
2021 | |||
2022 | //make sure this is actually a light |
||
2023 | if (! (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum) & WID_RENDER_FLAG)) |
||
2024 | continue; |
||
2025 | |||
2026 | if ((f.timer -= FrameTime) < 0) |
||
2027 | { |
||
2028 | while (f.timer < 0) |
||
2029 | f.timer += f.delay; |
||
2030 | f.mask = ((f.mask & 0x80000000) ? 1 : 0) + (f.mask << 1); |
||
2031 | if (f.mask & 1) |
||
2032 | add_light(LevelSharedDestructibleLightState, segp, sidenum); |
||
2033 | else |
||
2034 | subtract_light(LevelSharedDestructibleLightState, segp, sidenum); |
||
2035 | } |
||
2036 | } |
||
2037 | } |
||
2038 | |||
2039 | //returns ptr to flickering light structure, or NULL if can't find |
||
2040 | static std::pair<d_flickering_light_state::Flickering_light_array_t::iterator, d_flickering_light_state::Flickering_light_array_t::iterator> find_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum) |
||
2041 | { |
||
2042 | //see if there's already an entry for this seg/side |
||
2043 | const auto &&pr = partial_range(fls.Flickering_lights, fls.Num_flickering_lights); |
||
2044 | const auto &&predicate = [segnum, sidenum](const flickering_light &f) { |
||
2045 | return f.segnum == segnum && f.sidenum == sidenum; //found it! |
||
2046 | }; |
||
2047 | const auto &&pe = pr.end(); |
||
2048 | return {std::find_if(pr.begin(), pe, predicate), pe}; |
||
2049 | } |
||
2050 | |||
2051 | static void update_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum, const fix timer) |
||
2052 | { |
||
2053 | const auto &&i = find_flicker(fls, segnum, sidenum); |
||
2054 | if (i.first != i.second) |
||
2055 | i.first->timer = timer; |
||
2056 | } |
||
2057 | |||
2058 | //turn flickering off (because light has been turned off) |
||
2059 | void disable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum) |
||
2060 | { |
||
2061 | update_flicker(fls, segnum, sidenum, flicker_timer_disabled); |
||
2062 | } |
||
2063 | |||
2064 | //turn flickering off (because light has been turned on) |
||
2065 | void enable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum) |
||
2066 | { |
||
2067 | update_flicker(fls, segnum, sidenum, 0); |
||
2068 | } |
||
2069 | #endif |
||
2070 | |||
2071 | // ----------------------------------------------------------------------------- |
||
2072 | // Fire Laser: Registers a laser fire, and performs special stuff for the fusion |
||
2073 | // cannon. |
||
2074 | bool FireLaser(player_info &player_info) |
||
2075 | { |
||
2076 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2077 | auto &vmobjptr = Objects.vmptr; |
||
2078 | auto &vmobjptridx = Objects.vmptridx; |
||
2079 | if (!Controls.state.fire_primary) |
||
2080 | return false; |
||
2081 | if (!allowed_to_fire_laser(player_info)) |
||
2082 | return false; |
||
2083 | auto &Primary_weapon = player_info.Primary_weapon; |
||
2084 | if (!Weapon_info[Primary_weapon_to_weapon_info[Primary_weapon]].fire_count) |
||
2085 | /* Retail data sets fire_count=1 for all primary weapons */ |
||
2086 | return false; |
||
2087 | |||
2088 | if (Primary_weapon == primary_weapon_index_t::FUSION_INDEX) |
||
2089 | { |
||
2090 | auto &energy = player_info.energy; |
||
2091 | auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time; |
||
2092 | if (energy < F1_0 * 2 && Auto_fire_fusion_cannon_time == 0) |
||
2093 | { |
||
2094 | return false; |
||
2095 | } else { |
||
2096 | static fix64 Fusion_next_sound_time = 0; |
||
2097 | |||
2098 | if (player_info.Fusion_charge == 0) |
||
2099 | energy -= F1_0*2; |
||
2100 | |||
2101 | const auto Fusion_charge = (player_info.Fusion_charge += FrameTime); |
||
2102 | energy -= FrameTime; |
||
2103 | |||
2104 | if (energy <= 0) |
||
2105 | { |
||
2106 | energy = 0; |
||
2107 | Auto_fire_fusion_cannon_time = GameTime64 -1; // Fire now! |
||
2108 | } else |
||
2109 | Auto_fire_fusion_cannon_time = GameTime64 + FrameTime/2 + 1; // Fire the fusion cannon at this time in the future. |
||
2110 | |||
2111 | { |
||
2112 | int dg, db; |
||
2113 | const int dr = Fusion_charge >> 11; |
||
2114 | if (Fusion_charge < F1_0*2) |
||
2115 | dg = 0, db = dr; |
||
2116 | else |
||
2117 | dg = dr, db = 0; |
||
2118 | PALETTE_FLASH_ADD(dr, dg, db); |
||
2119 | } |
||
2120 | |||
2121 | if (Fusion_next_sound_time > GameTime64 + F1_0/8 + D_RAND_MAX/4) // GameTime64 is smaller than max delay - player in new level? |
||
2122 | Fusion_next_sound_time = GameTime64 - 1; |
||
2123 | |||
2124 | if (Fusion_next_sound_time < GameTime64) { |
||
2125 | if (Fusion_charge > F1_0*2) { |
||
2126 | digi_play_sample( 11, F1_0 ); |
||
2127 | #if defined(DXX_BUILD_DESCENT_I) |
||
2128 | if(Game_mode & GM_MULTI) |
||
2129 | multi_send_play_sound(11, F1_0, sound_stack::allow_stacking); |
||
2130 | #endif |
||
2131 | const auto cobjp = vmobjptridx(ConsoleObject); |
||
2132 | apply_damage_to_player(cobjp, cobjp, d_rand() * 4, 0); |
||
2133 | } else { |
||
2134 | create_awareness_event(vmobjptr(ConsoleObject), player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION, LevelUniqueRobotAwarenessState); |
||
2135 | multi_digi_play_sample(SOUND_FUSION_WARMUP, F1_0); |
||
2136 | } |
||
2137 | Fusion_next_sound_time = GameTime64 + F1_0/8 + d_rand()/4; |
||
2138 | } |
||
2139 | } |
||
2140 | } |
||
2141 | return true; |
||
2142 | } |
||
2143 | |||
2144 | |||
2145 | // ------------------------------------------------------------------------------------------------------- |
||
2146 | // If player is close enough to objnum, which ought to be a powerup, pick it up! |
||
2147 | // This could easily be made difficulty level dependent. |
||
2148 | static void powerup_grab_cheat(object &player, const vmobjptridx_t powerup) |
||
2149 | { |
||
2150 | fix powerup_size; |
||
2151 | fix player_size; |
||
2152 | |||
2153 | Assert(powerup->type == OBJ_POWERUP); |
||
2154 | |||
2155 | powerup_size = powerup->size; |
||
2156 | player_size = player.size; |
||
2157 | |||
2158 | const auto dist = vm_vec_dist_quick(powerup->pos, player.pos); |
||
2159 | |||
2160 | if ((dist < 2*(powerup_size + player_size)) && !(powerup->flags & OF_SHOULD_BE_DEAD)) { |
||
2161 | collide_live_local_player_and_powerup(powerup); |
||
2162 | } |
||
2163 | } |
||
2164 | |||
2165 | // ------------------------------------------------------------------------------------------------------- |
||
2166 | // Make it easier to pick up powerups. |
||
2167 | // For all powerups in this segment, pick them up at up to twice pickuppable distance based on dot product |
||
2168 | // from player to powerup and player's forward vector. |
||
2169 | // This has the effect of picking them up more easily left/right and up/down, but not making them disappear |
||
2170 | // way before the player gets there. |
||
2171 | void powerup_grab_cheat_all(void) |
||
2172 | { |
||
2173 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2174 | auto &vmobjptr = Objects.vmptr; |
||
2175 | auto &vmobjptridx = Objects.vmptridx; |
||
2176 | if (Endlevel_sequence) |
||
2177 | return; |
||
2178 | if (Player_dead_state != player_dead_state::no) |
||
2179 | return; |
||
2180 | const auto &&console = vmobjptr(ConsoleObject); |
||
2181 | range_for (const auto objnum, objects_in(vmsegptr(console->segnum), vmobjptridx, vmsegptr)) |
||
2182 | if (objnum->type == OBJ_POWERUP) |
||
2183 | powerup_grab_cheat(console, objnum); |
||
2184 | } |
||
2185 | |||
2186 | } |
||
2187 | |||
2188 | int Last_level_path_created = -1; |
||
2189 | |||
2190 | #ifdef SHOW_EXIT_PATH |
||
2191 | |||
2192 | // ------------------------------------------------------------------------------------------------------------------ |
||
2193 | // Create path for player from current segment to goal segment. |
||
2194 | // Return true if path created, else return false. |
||
2195 | static int mark_player_path_to_segment(const d_vclip_array &Vclip, fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, segnum_t segnum) |
||
2196 | { |
||
2197 | int player_hide_index=-1; |
||
2198 | |||
2199 | if (Last_level_path_created == Current_level_num) { |
||
2200 | return 0; |
||
2201 | } |
||
2202 | |||
2203 | Last_level_path_created = Current_level_num; |
||
2204 | |||
2205 | auto objp = vmobjptridx(ConsoleObject); |
||
2206 | const auto &&cr = create_path_points(objp, objp->segnum, segnum, Point_segs_free_ptr, 100, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none); |
||
2207 | const unsigned player_path_length = cr.second; |
||
2208 | if (cr.first == create_path_result::early) |
||
2209 | return 0; |
||
2210 | |||
2211 | player_hide_index = Point_segs_free_ptr - Point_segs; |
||
2212 | Point_segs_free_ptr += player_path_length; |
||
2213 | |||
2214 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
2215 | ai_reset_all_paths(); |
||
2216 | return 0; |
||
2217 | } |
||
2218 | |||
2219 | for (int i=1; i<player_path_length; i++) { |
||
2220 | vms_vector seg_center; |
||
2221 | |||
2222 | seg_center = Point_segs[player_hide_index+i].point; |
||
2223 | |||
2224 | const auto &&obj = obj_create(OBJ_POWERUP, POW_ENERGY, vmsegptridx(Point_segs[player_hide_index+i].segnum), seg_center, &vmd_identity_matrix, Powerup_info[POW_ENERGY].size, CT_POWERUP, MT_NONE, RT_POWERUP); |
||
2225 | if (obj == object_none) { |
||
2226 | Int3(); // Unable to drop energy powerup for path |
||
2227 | return 1; |
||
2228 | } |
||
2229 | |||
2230 | obj->rtype.vclip_info.vclip_num = Powerup_info[get_powerup_id(obj)].vclip_num; |
||
2231 | obj->rtype.vclip_info.frametime = Vclip[obj->rtype.vclip_info.vclip_num].frame_time; |
||
2232 | obj->rtype.vclip_info.framenum = 0; |
||
2233 | obj->lifeleft = F1_0*100 + d_rand() * 4; |
||
2234 | } |
||
2235 | |||
2236 | return 1; |
||
2237 | } |
||
2238 | |||
2239 | // Return true if it happened, else return false. |
||
2240 | int create_special_path(void) |
||
2241 | { |
||
2242 | auto &Objects = LevelUniqueObjectState.Objects; |
||
2243 | auto &vmobjptridx = Objects.vmptridx; |
||
2244 | // ---------- Find exit doors ---------- |
||
2245 | range_for (const auto &&segp, vcsegptridx) |
||
2246 | { |
||
2247 | for (int j=0; j<MAX_SIDES_PER_SEGMENT; j++) |
||
2248 | if (segp->children[j] == segment_exit) |
||
2249 | { |
||
2250 | return mark_player_path_to_segment(Vclip, vmobjptridx, vmsegptridx, segp); |
||
2251 | } |
||
2252 | } |
||
2253 | |||
2254 | return 0; |
||
2255 | } |
||
2256 | |||
2257 | #endif |
||
2258 | |||
2259 | |||
2260 | #if defined(DXX_BUILD_DESCENT_II) |
||
2261 | namespace dsx { |
||
2262 | /* |
||
2263 | * reads a flickering_light structure from a PHYSFS_File |
||
2264 | */ |
||
2265 | void flickering_light_read(flickering_light &fl, PHYSFS_File *fp) |
||
2266 | { |
||
2267 | fl.segnum = PHYSFSX_readShort(fp); |
||
2268 | fl.sidenum = PHYSFSX_readShort(fp); |
||
2269 | fl.mask = PHYSFSX_readInt(fp); |
||
2270 | fl.timer = PHYSFSX_readFix(fp); |
||
2271 | fl.delay = PHYSFSX_readFix(fp); |
||
2272 | } |
||
2273 | |||
2274 | void flickering_light_write(const flickering_light &fl, PHYSFS_File *fp) |
||
2275 | { |
||
2276 | PHYSFS_writeSLE16(fp, fl.segnum); |
||
2277 | PHYSFS_writeSLE16(fp, fl.sidenum); |
||
2278 | PHYSFS_writeULE32(fp, fl.mask); |
||
2279 | PHYSFSX_writeFix(fp, fl.timer); |
||
2280 | PHYSFSX_writeFix(fp, fl.delay); |
||
2281 | } |
||
2282 | } |
||
2283 | #endif |