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 | * Stuff for rendering the HUD |
||
23 | * |
||
24 | */ |
||
25 | |||
26 | #include <stdio.h> |
||
27 | #include <string.h> |
||
28 | #include <stdlib.h> |
||
29 | #include "timer.h" |
||
30 | #include "pstypes.h" |
||
31 | #include "console.h" |
||
32 | #include "inferno.h" |
||
33 | #include "dxxerror.h" |
||
34 | #include "gr.h" |
||
35 | #include "palette.h" |
||
36 | #include "bm.h" |
||
37 | #include "player.h" |
||
38 | #include "render.h" |
||
39 | #include "menu.h" |
||
40 | #include "newmenu.h" |
||
41 | #include "screens.h" |
||
42 | #include "config.h" |
||
43 | #include "maths.h" |
||
44 | #include "robot.h" |
||
45 | #include "game.h" |
||
46 | #include "gauges.h" |
||
47 | #include "gamefont.h" |
||
48 | #include "newdemo.h" |
||
49 | #include "text.h" |
||
50 | #include "multi.h" |
||
51 | #include "hudmsg.h" |
||
52 | #include "endlevel.h" |
||
53 | #include "cntrlcen.h" |
||
54 | #include "powerup.h" |
||
55 | #include "laser.h" |
||
56 | #include "playsave.h" |
||
57 | #include "automap.h" |
||
58 | #include "mission.h" |
||
59 | #include "gameseq.h" |
||
60 | #include "args.h" |
||
61 | #include "object.h" |
||
62 | |||
63 | #include "compiler-range_for.h" |
||
64 | #include "d_range.h" |
||
65 | |||
66 | #if DXX_USE_OGL |
||
67 | #include "ogl_init.h" |
||
68 | #endif |
||
69 | |||
70 | namespace dcx { |
||
71 | int netplayerinfo_on; |
||
72 | } |
||
73 | |||
74 | namespace dsx { |
||
75 | #if defined(DXX_BUILD_DESCENT_I) |
||
76 | static inline void game_draw_marker_message(grs_canvas &) |
||
77 | { |
||
78 | } |
||
79 | #elif defined(DXX_BUILD_DESCENT_II) |
||
80 | static void game_draw_marker_message(grs_canvas &canvas) |
||
81 | { |
||
82 | if (MarkerState.DefiningMarkerMessage()) |
||
83 | { |
||
84 | gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1); |
||
85 | auto &game_font = *GAME_FONT; |
||
86 | gr_printf(canvas, game_font, 0x8000, (LINE_SPACING(game_font, game_font) * 5) + FSPACY(1), "Marker: %s%c", &Marker_input[0], Marker_input[Marker_input.size() - 2] ? 0 : '_'); |
||
87 | } |
||
88 | } |
||
89 | #endif |
||
90 | } |
||
91 | |||
92 | namespace dcx { |
||
93 | |||
94 | static void game_draw_multi_message(grs_canvas &canvas) |
||
95 | { |
||
96 | if (!(Game_mode&GM_MULTI)) |
||
97 | return; |
||
98 | const auto sending = multi_sending_message[Player_num]; |
||
99 | int defining; |
||
100 | if (!sending && !(defining = multi_defining_message)) |
||
101 | return; |
||
102 | gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1); |
||
103 | auto &game_font = *GAME_FONT; |
||
104 | const auto &&y = (LINE_SPACING(game_font, game_font) * 5) + FSPACY(1); |
||
105 | if (sending) |
||
106 | gr_printf(canvas, game_font, 0x8000, y, "%s: %s_", TXT_MESSAGE, Network_message.data()); |
||
107 | else |
||
108 | gr_printf(canvas, game_font, 0x8000, y, "%s #%d: %s_", TXT_MACRO, defining, Network_message.data()); |
||
109 | } |
||
110 | |||
111 | static void show_framerate(grs_canvas &canvas) |
||
112 | { |
||
113 | static int fps_count = 0, fps_rate = 0; |
||
114 | static fix64 fps_time = 0; |
||
115 | fps_count++; |
||
116 | const auto tq = timer_query(); |
||
117 | if (tq >= fps_time + F1_0) |
||
118 | { |
||
119 | fps_rate = fps_count; |
||
120 | fps_count = 0; |
||
121 | fps_time += F1_0; |
||
122 | } |
||
123 | const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT); |
||
124 | unsigned line_displacement; |
||
125 | switch (PlayerCfg.CockpitMode[1]) |
||
126 | { |
||
127 | case CM_FULL_COCKPIT: |
||
128 | line_displacement = line_spacing * 2; |
||
129 | break; |
||
130 | case CM_STATUS_BAR: |
||
131 | line_displacement = line_spacing; |
||
132 | break; |
||
133 | case CM_FULL_SCREEN: |
||
134 | switch (PlayerCfg.HudMode) |
||
135 | { |
||
136 | case HudType::Standard: |
||
137 | line_displacement = line_spacing * 4; |
||
138 | break; |
||
139 | case HudType::Alternate1: |
||
140 | line_displacement = line_spacing * 6; |
||
141 | if (Game_mode & GM_MULTI) |
||
142 | line_displacement -= line_spacing * 4; |
||
143 | break; |
||
144 | case HudType::Alternate2: |
||
145 | line_displacement = line_spacing; |
||
146 | break; |
||
147 | case HudType::Hidden: |
||
148 | default: |
||
149 | return; |
||
150 | } |
||
151 | break; |
||
152 | case CM_LETTERBOX: |
||
153 | case CM_REAR_VIEW: |
||
154 | default: |
||
155 | return; |
||
156 | } |
||
157 | const auto &game_font = *GAME_FONT; |
||
158 | gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0),-1); |
||
159 | char buf[16]; |
||
160 | if (CGameArg.DbgVerbose) |
||
161 | snprintf(buf, sizeof(buf), "%iFPS (%.2fms)", fps_rate, (FrameTime * 1000.) / F1_0); |
||
162 | else |
||
163 | snprintf(buf, sizeof(buf), "%iFPS", fps_rate); |
||
164 | int w, h; |
||
165 | gr_get_string_size(game_font, buf, &w, &h, nullptr); |
||
166 | const auto bm_h = canvas.cv_bitmap.bm_h; |
||
167 | gr_string(canvas, game_font, FSPACX(318) - w, bm_h - line_displacement, buf, w, h); |
||
168 | } |
||
169 | |||
170 | } |
||
171 | |||
172 | namespace dsx { |
||
173 | static void show_netplayerinfo() |
||
174 | { |
||
175 | auto &Objects = LevelUniqueObjectState.Objects; |
||
176 | auto &vcobjptr = Objects.vcptr; |
||
177 | int x=0, y=0; |
||
178 | static const char *const eff_strings[]={"trashing","really hurting","seriously affecting","hurting","affecting","tarnishing"}; |
||
179 | |||
180 | gr_set_default_canvas(); |
||
181 | auto &canvas = *grd_curcanv; |
||
182 | gr_set_fontcolor(canvas, 255, -1); |
||
183 | |||
184 | const auto &&fspacx = FSPACX(); |
||
185 | const auto &&fspacx120 = fspacx(120); |
||
186 | const auto &&fspacy84 = FSPACY(84); |
||
187 | x = (SWIDTH / 2) - fspacx120; |
||
188 | y = (SHEIGHT / 2) - fspacy84; |
||
189 | |||
190 | gr_settransblend(canvas, 14, gr_blend::normal); |
||
191 | const uint8_t color000 = BM_XRGB(0, 0, 0); |
||
192 | gr_rect(canvas, (SWIDTH / 2) - fspacx120, (SHEIGHT / 2) - fspacy84, (SWIDTH / 2) + fspacx120, (SHEIGHT / 2) + fspacy84, color000); |
||
193 | gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); |
||
194 | |||
195 | // general game information |
||
196 | const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT); |
||
197 | y += line_spacing; |
||
198 | auto &game_font = *GAME_FONT; |
||
199 | gr_string(canvas, game_font, 0x8000, y, Netgame.game_name.data()); |
||
200 | y += line_spacing; |
||
201 | gr_printf(canvas, game_font, 0x8000, y, "%s - lvl: %i", Netgame.mission_title.data(), Netgame.levelnum); |
||
202 | |||
203 | const auto &&fspacx8 = fspacx(8); |
||
204 | x += fspacx8; |
||
205 | y += line_spacing * 2; |
||
206 | unsigned gamemode = Netgame.gamemode; |
||
207 | gr_printf(canvas, game_font, x, y, "game mode: %s", gamemode < GMNames.size() ? GMNames[gamemode] : "INVALID"); |
||
208 | y += line_spacing; |
||
209 | gr_printf(canvas, game_font, x, y,"difficulty: %s", MENU_DIFFICULTY_TEXT(Netgame.difficulty)); |
||
210 | y += line_spacing; |
||
211 | { |
||
212 | auto &plr = get_local_player(); |
||
213 | gr_printf(canvas, game_font, x, y,"level time: %i:%02i:%02i", plr.hours_level, f2i(plr.time_level) / 60 % 60, f2i(plr.time_level) % 60); |
||
214 | y += line_spacing; |
||
215 | gr_printf(canvas, game_font, x, y,"total time: %i:%02i:%02i", plr.hours_total, f2i(plr.time_total) / 60 % 60, f2i(plr.time_total) % 60); |
||
216 | } |
||
217 | y += line_spacing; |
||
218 | if (Netgame.KillGoal) |
||
219 | gr_printf(canvas, game_font, x, y,"Kill goal: %d",Netgame.KillGoal*5); |
||
220 | |||
221 | // player information (name, kills, ping, game efficiency) |
||
222 | y += line_spacing * 2; |
||
223 | gr_string(canvas, game_font, x, y, "player"); |
||
224 | gr_string(canvas, game_font, x + fspacx8 * 7, y, ((Game_mode & GM_MULTI_COOP) |
||
225 | ? "score" |
||
226 | : (gr_string(canvas, game_font, x + fspacx8 * 12, y, "deaths"), "kills") |
||
227 | )); |
||
228 | gr_string(canvas, game_font, x + fspacx8 * 18, y, "ping"); |
||
229 | gr_string(canvas, game_font, x + fspacx8 * 23, y, "efficiency"); |
||
230 | |||
231 | // process players table |
||
232 | for (unsigned i = 0; i < MAX_PLAYERS; ++i) |
||
233 | { |
||
234 | auto &plr = *vcplayerptr(i); |
||
235 | if (!plr.connected) |
||
236 | continue; |
||
237 | |||
238 | y += line_spacing; |
||
239 | |||
240 | const auto color = get_player_or_team_color(i); |
||
241 | auto &prgb = player_rgb[color]; |
||
242 | gr_set_fontcolor(canvas, BM_XRGB(prgb.r, prgb.g, prgb.b), -1); |
||
243 | gr_string(canvas, game_font, x, y, plr.callsign); |
||
244 | { |
||
245 | auto &plrobj = *vcobjptr(plr.objnum); |
||
246 | auto &player_info = plrobj.ctype.player_info; |
||
247 | auto v = ((Game_mode & GM_MULTI_COOP) |
||
248 | ? player_info.mission.score |
||
249 | : (gr_printf(canvas, game_font, x + fspacx8 * 12, y,"%-6d", player_info.net_killed_total), player_info.net_kills_total) |
||
250 | ); |
||
251 | gr_printf(canvas, game_font, x + fspacx8 * 7, y, "%-6d", v); |
||
252 | } |
||
253 | |||
254 | gr_printf(canvas, game_font, x + fspacx8 * 18, y,"%-6d", Netgame.players[i].ping); |
||
255 | if (i != Player_num) |
||
256 | gr_printf(canvas, game_font, x + fspacx8 * 23, y, "%hu/%hu", kill_matrix[Player_num][i], kill_matrix[i][Player_num]); |
||
257 | } |
||
258 | |||
259 | y += (line_spacing * 2) + (line_spacing * (MAX_PLAYERS - N_players)); |
||
260 | |||
261 | // printf team scores |
||
262 | if (Game_mode & GM_TEAM) |
||
263 | { |
||
264 | gr_set_fontcolor(canvas, 255, -1); |
||
265 | gr_string(canvas, game_font, x, y, "team"); |
||
266 | gr_string(canvas, game_font, x + fspacx8 * 8, y, "score"); |
||
267 | y += line_spacing; |
||
268 | gr_set_fontcolor(canvas, BM_XRGB(player_rgb[0].r, player_rgb[0].g, player_rgb[0].b),-1); |
||
269 | gr_printf(canvas, game_font, x, y, "%s:", static_cast<const char *>(Netgame.team_name[0])); |
||
270 | gr_printf(canvas, game_font, x + fspacx8 * 8, y, "%i", team_kills[0]); |
||
271 | y += line_spacing; |
||
272 | gr_set_fontcolor(canvas, BM_XRGB(player_rgb[1].r, player_rgb[1].g, player_rgb[1].b),-1); |
||
273 | gr_printf(canvas, game_font, x, y, "%s:", static_cast<const char *>(Netgame.team_name[1])); |
||
274 | gr_printf(canvas, game_font, x + fspacx8 * 8, y, "%i", team_kills[1]); |
||
275 | y += line_spacing * 2; |
||
276 | } |
||
277 | else |
||
278 | y += line_spacing * 4; |
||
279 | |||
280 | gr_set_fontcolor(canvas, 255, -1); |
||
281 | |||
282 | // additional information about game - hoard, ranking |
||
283 | |||
284 | #if defined(DXX_BUILD_DESCENT_II) |
||
285 | if (game_mode_hoard()) |
||
286 | { |
||
287 | if (hoard_highest_record_stats.player >= Players.size()) |
||
288 | gr_string(canvas, game_font, 0x8000, y, "There is no record yet for this level."); |
||
289 | else |
||
290 | gr_printf(canvas, game_font, 0x8000, y, "%s has the record at %d points.", static_cast<const char *>(vcplayerptr(hoard_highest_record_stats.player)->callsign), hoard_highest_record_stats.points); |
||
291 | } |
||
292 | else |
||
293 | #endif |
||
294 | if (!PlayerCfg.NoRankings) |
||
295 | { |
||
296 | const int ieff = (PlayerCfg.NetlifeKills + PlayerCfg.NetlifeKilled <= 0) |
||
297 | ? 0 |
||
298 | : static_cast<int>( |
||
299 | static_cast<float>(PlayerCfg.NetlifeKills) / ( |
||
300 | static_cast<float>(PlayerCfg.NetlifeKilled) + static_cast<float>(PlayerCfg.NetlifeKills) |
||
301 | ) * 100.0 |
||
302 | ); |
||
303 | const unsigned eff = ieff < 0 ? 0 : static_cast<unsigned>(ieff); |
||
304 | gr_printf(canvas, game_font, 0x8000, y, "Your lifetime efficiency of %d%% (%d/%d)", eff, PlayerCfg.NetlifeKills, PlayerCfg.NetlifeKilled); |
||
305 | y += line_spacing; |
||
306 | if (eff<60) |
||
307 | gr_printf(canvas, game_font, 0x8000, y, "is %s your ranking.", eff_strings[eff / 10]); |
||
308 | else |
||
309 | gr_string(canvas, game_font, 0x8000, y, "is serving you well."); |
||
310 | y += line_spacing; |
||
311 | gr_printf(canvas, game_font, 0x8000, y, "your rank is: %s", RankStrings[GetMyNetRanking()]); |
||
312 | } |
||
313 | } |
||
314 | } |
||
315 | |||
316 | #ifndef NDEBUG |
||
317 | |||
318 | fix Show_view_text_timer = -1; |
||
319 | |||
320 | static void draw_window_label(object_array &Objects, grs_canvas &canvas) |
||
321 | { |
||
322 | auto &vcobjptridx = Objects.vcptridx; |
||
323 | if ( Show_view_text_timer > 0 ) |
||
324 | { |
||
325 | const char *viewer_name,*control_name,*viewer_id; |
||
326 | Show_view_text_timer -= FrameTime; |
||
327 | |||
328 | viewer_id = ""; |
||
329 | switch( Viewer->type ) |
||
330 | { |
||
331 | case OBJ_FIREBALL: viewer_name = "Fireball"; break; |
||
332 | case OBJ_ROBOT: viewer_name = "Robot"; |
||
333 | #if DXX_USE_EDITOR |
||
334 | viewer_id = Robot_names[get_robot_id(Objects.vcptr(Viewer))].data(); |
||
335 | #endif |
||
336 | break; |
||
337 | case OBJ_HOSTAGE: viewer_name = "Hostage"; break; |
||
338 | case OBJ_PLAYER: viewer_name = "Player"; break; |
||
339 | case OBJ_WEAPON: viewer_name = "Weapon"; break; |
||
340 | case OBJ_CAMERA: viewer_name = "Camera"; break; |
||
341 | case OBJ_POWERUP: viewer_name = "Powerup"; |
||
342 | #if DXX_USE_EDITOR |
||
343 | viewer_id = Powerup_names[get_powerup_id(Objects.vcptr(Viewer))].data(); |
||
344 | #endif |
||
345 | break; |
||
346 | case OBJ_DEBRIS: viewer_name = "Debris"; break; |
||
347 | case OBJ_CNTRLCEN: viewer_name = "Reactor"; break; |
||
348 | default: viewer_name = "Unknown"; break; |
||
349 | } |
||
350 | |||
351 | switch ( Viewer->control_type) { |
||
352 | case CT_NONE: control_name = "Stopped"; break; |
||
353 | case CT_AI: control_name = "AI"; break; |
||
354 | case CT_FLYING: control_name = "Flying"; break; |
||
355 | case CT_SLEW: control_name = "Slew"; break; |
||
356 | case CT_FLYTHROUGH: control_name = "Flythrough"; break; |
||
357 | case CT_MORPH: control_name = "Morphing"; break; |
||
358 | default: control_name = "Unknown"; break; |
||
359 | } |
||
360 | |||
361 | gr_set_fontcolor(canvas, BM_XRGB(31, 0, 0),-1); |
||
362 | auto &game_font = *GAME_FONT; |
||
363 | gr_printf(canvas, game_font, 0x8000, (SHEIGHT / 10), "%hu: %s [%s] View - %s", static_cast<objnum_t>(vcobjptridx(Viewer)), viewer_name, viewer_id, control_name); |
||
364 | |||
365 | } |
||
366 | } |
||
367 | #endif |
||
368 | |||
369 | namespace dsx { |
||
370 | static void render_countdown_gauge(grs_canvas &canvas) |
||
371 | { |
||
372 | auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; |
||
373 | int Countdown_seconds_left; |
||
374 | if (!Endlevel_sequence && LevelUniqueControlCenterState.Control_center_destroyed && (Countdown_seconds_left = LevelUniqueControlCenterState.Countdown_seconds_left) > -1) |
||
375 | { // && (Countdown_seconds_left<127)) |
||
376 | #if defined(DXX_BUILD_DESCENT_II) |
||
377 | if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE) // no countdown on registered only |
||
378 | { |
||
379 | // On last level, we don't want a countdown. |
||
380 | if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level) |
||
381 | { |
||
382 | if (!(Game_mode & GM_MULTI)) |
||
383 | return; |
||
384 | if (Game_mode & GM_MULTI_ROBOTS) |
||
385 | return; |
||
386 | } |
||
387 | } |
||
388 | #endif |
||
389 | gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1); |
||
390 | auto &game_font = *GAME_FONT; |
||
391 | gr_printf(canvas, game_font, 0x8000, (LINE_SPACING(game_font, game_font) * 6) + FSPACY(1), "T-%d s", Countdown_seconds_left); |
||
392 | } |
||
393 | } |
||
394 | } |
||
395 | |||
396 | static void game_draw_hud_stuff(grs_canvas &canvas) |
||
397 | { |
||
398 | auto &Objects = LevelUniqueObjectState.Objects; |
||
399 | auto &vmobjptr = Objects.vmptr; |
||
400 | #ifndef NDEBUG |
||
401 | draw_window_label(Objects, canvas); |
||
402 | #endif |
||
403 | |||
404 | game_draw_multi_message(canvas); |
||
405 | |||
406 | game_draw_marker_message(canvas); |
||
407 | |||
408 | if (((Newdemo_state == ND_STATE_PLAYBACK) || (Newdemo_state == ND_STATE_RECORDING)) && (PlayerCfg.CockpitMode[1] != CM_REAR_VIEW)) { |
||
409 | int y; |
||
410 | |||
411 | auto &game_font = *GAME_FONT; |
||
412 | gr_set_curfont(canvas, GAME_FONT); |
||
413 | gr_set_fontcolor(canvas, BM_XRGB(27, 0, 0), -1); |
||
414 | |||
415 | y = canvas.cv_bitmap.bm_h - (LINE_SPACING(*canvas.cv_font, *GAME_FONT) * 2); |
||
416 | |||
417 | if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT) |
||
418 | y = canvas.cv_bitmap.bm_h / 1.2 ; |
||
419 | if (Newdemo_state == ND_STATE_PLAYBACK) { |
||
420 | if (Newdemo_show_percentage) { |
||
421 | gr_printf(canvas, game_font, 0x8000, y, "%s (%d%% %s)", TXT_DEMO_PLAYBACK, newdemo_get_percent_done(), TXT_DONE); |
||
422 | } |
||
423 | } else { |
||
424 | gr_printf(canvas, game_font, 0x8000, y, "%s (%dK)", TXT_DEMO_RECORDING, (Newdemo_num_written / 1024)); |
||
425 | } |
||
426 | } |
||
427 | |||
428 | render_countdown_gauge(canvas); |
||
429 | |||
430 | if (CGameCfg.FPSIndicator && PlayerCfg.CockpitMode[1] != CM_REAR_VIEW) |
||
431 | show_framerate(canvas); |
||
432 | |||
433 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
434 | Game_mode = Newdemo_game_mode; |
||
435 | |||
436 | auto &plrobj = get_local_plrobj(); |
||
437 | draw_hud(canvas, plrobj); |
||
438 | |||
439 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
440 | Game_mode = GM_NORMAL; |
||
441 | |||
442 | if (Player_dead_state != player_dead_state::no) |
||
443 | player_dead_message(canvas); |
||
444 | } |
||
445 | |||
446 | namespace dsx { |
||
447 | |||
448 | #if defined(DXX_BUILD_DESCENT_II) |
||
449 | |||
450 | ubyte RenderingType=0; |
||
451 | ubyte DemoDoingRight=0,DemoDoingLeft=0; |
||
452 | |||
453 | constexpr char DemoWBUType[]={0,WBU_GUIDED,WBU_MISSILE,WBU_REAR,WBU_ESCORT,WBU_MARKER,0}; |
||
454 | constexpr char DemoRearCheck[]={0,0,0,1,0,0,0}; |
||
455 | constexpr char DemoExtraMessage[][10] = { |
||
456 | "PLAYER", |
||
457 | "GUIDED", |
||
458 | "MISSILE", |
||
459 | "REAR", |
||
460 | "GUIDE-BOT", |
||
461 | "MARKER", |
||
462 | "SHIP" |
||
463 | }; |
||
464 | |||
465 | static const char *get_missile_name(const unsigned laser_type) |
||
466 | { |
||
467 | switch(laser_type) |
||
468 | { |
||
469 | case weapon_id_type::CONCUSSION_ID: |
||
470 | return "CONCUSSION"; |
||
471 | case weapon_id_type::HOMING_ID: |
||
472 | return "HOMING"; |
||
473 | case weapon_id_type::SMART_ID: |
||
474 | return "SMART"; |
||
475 | case weapon_id_type::MEGA_ID: |
||
476 | return "MEGA"; |
||
477 | case weapon_id_type::FLASH_ID: |
||
478 | return "FLASH"; |
||
479 | case weapon_id_type::MERCURY_ID: |
||
480 | return "MERCURY"; |
||
481 | case weapon_id_type::EARTHSHAKER_ID: |
||
482 | return "SHAKER"; |
||
483 | default: |
||
484 | return "MISSILE"; // Bad! |
||
485 | } |
||
486 | } |
||
487 | |||
488 | static void set_missile_viewer(object &o) |
||
489 | { |
||
490 | Missile_viewer = &o; |
||
491 | Missile_viewer_sig = o.signature; |
||
492 | } |
||
493 | |||
494 | static int clear_missile_viewer() |
||
495 | { |
||
496 | if (!Missile_viewer) |
||
497 | return 0; |
||
498 | Missile_viewer = nullptr; |
||
499 | return 1; |
||
500 | } |
||
501 | |||
502 | __attribute_warn_unused_result |
||
503 | static bool is_viewable_missile(weapon_id_type laser_type) |
||
504 | { |
||
505 | return laser_type == weapon_id_type::CONCUSSION_ID || |
||
506 | laser_type == weapon_id_type::HOMING_ID || |
||
507 | laser_type == weapon_id_type::SMART_ID || |
||
508 | laser_type == weapon_id_type::MEGA_ID || |
||
509 | laser_type == weapon_id_type::FLASH_ID || |
||
510 | laser_type == weapon_id_type::GUIDEDMISS_ID || |
||
511 | laser_type == weapon_id_type::MERCURY_ID || |
||
512 | laser_type == weapon_id_type::EARTHSHAKER_ID; |
||
513 | } |
||
514 | |||
515 | static bool choose_missile_viewer() |
||
516 | { |
||
517 | auto &Objects = LevelUniqueObjectState.Objects; |
||
518 | auto &vcobjptr = Objects.vcptr; |
||
519 | auto &vmobjptr = Objects.vmptr; |
||
520 | const auto MissileViewEnabled = PlayerCfg.MissileViewEnabled; |
||
521 | if (unlikely(MissileViewEnabled == MissileViewMode::None)) |
||
522 | return false; |
||
523 | const auto need_new_missile_viewer = []{ |
||
524 | if (!Missile_viewer) |
||
525 | return true; |
||
526 | if (Missile_viewer->type != OBJ_WEAPON) |
||
527 | return true; |
||
528 | if (Missile_viewer->signature != Missile_viewer_sig) |
||
529 | return true; |
||
530 | /* No check on parent here. Missile_viewer is cleared if needed |
||
531 | * when a missile is fired. |
||
532 | */ |
||
533 | return false; |
||
534 | }; |
||
535 | if (likely(!need_new_missile_viewer())) |
||
536 | /* Valid viewer already set */ |
||
537 | return true; |
||
538 | const auto better_match = [](object *const a, object &b) { |
||
539 | if (!a) |
||
540 | return true; |
||
541 | return a->lifeleft < b.lifeleft; |
||
542 | }; |
||
543 | /* Find new missile */ |
||
544 | object *local_player_missile = nullptr, *other_player_missile = nullptr; |
||
545 | const auto game_mode = Game_mode; |
||
546 | const auto local_player_objnum = get_local_player().objnum; |
||
547 | range_for (object &o, vmobjptr) |
||
548 | { |
||
549 | if (o.type != OBJ_WEAPON) |
||
550 | continue; |
||
551 | if (o.ctype.laser_info.parent_type != OBJ_PLAYER) |
||
552 | continue; |
||
553 | const auto laser_type = get_weapon_id(o); |
||
554 | if (!is_viewable_missile(laser_type)) |
||
555 | continue; |
||
556 | if (o.ctype.laser_info.parent_num == local_player_objnum) |
||
557 | { |
||
558 | if (!better_match(local_player_missile, o)) |
||
559 | continue; |
||
560 | local_player_missile = &o; |
||
561 | } |
||
562 | else |
||
563 | { |
||
564 | if (MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies) |
||
565 | continue; |
||
566 | if (!better_match(other_player_missile, o)) |
||
567 | continue; |
||
568 | else if (game_mode & GM_MULTI_COOP) |
||
569 | { |
||
570 | /* Always allow missiles of other players */ |
||
571 | } |
||
572 | else if (game_mode & GM_TEAM) |
||
573 | { |
||
574 | /* Allow missiles from same team */ |
||
575 | if (get_team(Player_num) != get_team(get_player_id(vcobjptr(o.ctype.laser_info.parent_num)))) |
||
576 | continue; |
||
577 | } |
||
578 | else |
||
579 | continue; |
||
580 | other_player_missile = &o; |
||
581 | } |
||
582 | } |
||
583 | object *o; |
||
584 | if ((o = local_player_missile) != nullptr || |
||
585 | (o = other_player_missile) != nullptr) |
||
586 | set_missile_viewer(*o); |
||
587 | else |
||
588 | return false; |
||
589 | return true; |
||
590 | } |
||
591 | |||
592 | static void show_one_extra_view(const int w); |
||
593 | static void show_extra_views() |
||
594 | { |
||
595 | auto &Objects = LevelUniqueObjectState.Objects; |
||
596 | auto &vmobjptr = Objects.vmptr; |
||
597 | int did_missile_view=0; |
||
598 | int save_newdemo_state = Newdemo_state; |
||
599 | if (Newdemo_state==ND_STATE_PLAYBACK) |
||
600 | { |
||
601 | if (DemoDoLeft) |
||
602 | { |
||
603 | DemoDoingLeft=DemoDoLeft; |
||
604 | |||
605 | if (DemoDoLeft==3) |
||
606 | do_cockpit_window_view(0, *ConsoleObject, 1, WBU_REAR, "REAR"); |
||
607 | else |
||
608 | do_cockpit_window_view(0, DemoLeftExtra, DemoRearCheck[DemoDoLeft], DemoWBUType[DemoDoLeft], DemoExtraMessage[DemoDoLeft], DemoDoLeft == 1 ? &get_local_plrobj().ctype.player_info : nullptr); |
||
609 | } |
||
610 | else |
||
611 | do_cockpit_window_view(0,WBU_WEAPON); |
||
612 | |||
613 | if (DemoDoRight) |
||
614 | { |
||
615 | DemoDoingRight=DemoDoRight; |
||
616 | |||
617 | if (DemoDoRight==3) |
||
618 | do_cockpit_window_view(1, *ConsoleObject, 1, WBU_REAR, "REAR"); |
||
619 | else |
||
620 | { |
||
621 | do_cockpit_window_view(1, DemoRightExtra, DemoRearCheck[DemoDoRight], DemoWBUType[DemoDoRight], DemoExtraMessage[DemoDoRight], DemoDoLeft == 1 ? &get_local_plrobj().ctype.player_info : nullptr); |
||
622 | } |
||
623 | } |
||
624 | else |
||
625 | do_cockpit_window_view(1,WBU_WEAPON); |
||
626 | |||
627 | DemoDoLeft=DemoDoRight=0; |
||
628 | DemoDoingLeft=DemoDoingRight=0; |
||
629 | return; |
||
630 | } |
||
631 | |||
632 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(vmobjptr, Player_num); |
||
633 | if (gimobj != nullptr) |
||
634 | { |
||
635 | if (PlayerCfg.GuidedInBigWindow) |
||
636 | { |
||
637 | RenderingType=6+(1<<4); |
||
638 | do_cockpit_window_view(1, *Viewer, 0, WBU_MISSILE, "SHIP"); |
||
639 | } |
||
640 | else |
||
641 | { |
||
642 | RenderingType=1+(1<<4); |
||
643 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
644 | do_cockpit_window_view(1, *gimobj, 0, WBU_GUIDED, "GUIDED", &player_info); |
||
645 | } |
||
646 | |||
647 | did_missile_view=1; |
||
648 | } |
||
649 | else { |
||
650 | if (choose_missile_viewer()) |
||
651 | //do missile view |
||
652 | { |
||
653 | RenderingType=2+(1<<4); |
||
654 | do_cockpit_window_view(1, *Missile_viewer, 0, WBU_MISSILE, get_missile_name(get_weapon_id(*Missile_viewer))); |
||
655 | did_missile_view=1; |
||
656 | } |
||
657 | else { |
||
658 | if (clear_missile_viewer()) |
||
659 | do_cockpit_window_view(1,WBU_STATIC); |
||
660 | RenderingType=255; |
||
661 | } |
||
662 | } |
||
663 | |||
664 | range_for (const int w, xrange(2u)) { |
||
665 | |||
666 | if (w==1 && did_missile_view) |
||
667 | continue; //if showing missile view in right window, can't show anything else |
||
668 | |||
669 | show_one_extra_view(w); |
||
670 | } |
||
671 | RenderingType=0; |
||
672 | Newdemo_state = save_newdemo_state; |
||
673 | } |
||
674 | |||
675 | static void show_one_extra_view(const int w) |
||
676 | { |
||
677 | auto &Objects = LevelUniqueObjectState.Objects; |
||
678 | auto &vcobjptr = Objects.vcptr; |
||
679 | auto &vmobjptridx = Objects.vmptridx; |
||
680 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
681 | //show special views if selected |
||
682 | switch (PlayerCfg.Cockpit3DView[w]) { |
||
683 | case CV_NONE: |
||
684 | RenderingType=255; |
||
685 | do_cockpit_window_view(w,WBU_WEAPON); |
||
686 | break; |
||
687 | case CV_REAR: |
||
688 | RenderingType=3 + (w << 4); |
||
689 | { |
||
690 | int rear_view_flag; |
||
691 | const char *label; |
||
692 | if (Rear_view) //if big window is rear view, show front here |
||
693 | { |
||
694 | rear_view_flag = 0; |
||
695 | label = "FRONT"; |
||
696 | } |
||
697 | else //show normal rear view |
||
698 | { |
||
699 | rear_view_flag = 1; |
||
700 | label = "REAR"; |
||
701 | } |
||
702 | do_cockpit_window_view(w, *ConsoleObject, rear_view_flag, WBU_REAR, label); |
||
703 | } |
||
704 | break; |
||
705 | case CV_ESCORT: { |
||
706 | const auto &&buddy = find_escort(vmobjptridx, Robot_info); |
||
707 | if (buddy == object_none) { |
||
708 | do_cockpit_window_view(w,WBU_WEAPON); |
||
709 | PlayerCfg.Cockpit3DView[w] = CV_NONE; |
||
710 | } |
||
711 | else { |
||
712 | RenderingType=4+(w<<4); |
||
713 | do_cockpit_window_view(w, *buddy, 0, WBU_ESCORT, PlayerCfg.GuidebotName); |
||
714 | } |
||
715 | break; |
||
716 | } |
||
717 | case CV_COOP: { |
||
718 | const auto player = Coop_view_player[w]; |
||
719 | |||
720 | RenderingType=255; // don't handle coop stuff |
||
721 | |||
722 | if (player < Players.size() && vcplayerptr(player)->connected && ((Game_mode & GM_MULTI_COOP) || ((Game_mode & GM_TEAM) && (get_team(player) == get_team(Player_num))))) |
||
723 | { |
||
724 | auto &p = *vcplayerptr(player); |
||
725 | do_cockpit_window_view(w, *vcobjptr(p.objnum), 0, WBU_COOP, p.callsign); |
||
726 | } |
||
727 | else { |
||
728 | do_cockpit_window_view(w,WBU_WEAPON); |
||
729 | PlayerCfg.Cockpit3DView[w] = CV_NONE; |
||
730 | } |
||
731 | break; |
||
732 | } |
||
733 | case CV_MARKER: { |
||
734 | char label[10]; |
||
735 | RenderingType=5+(w<<4); |
||
736 | const auto mvn = Marker_viewer_num[w]; |
||
737 | if (!MarkerState.imobjidx.valid_index(mvn)) |
||
738 | { |
||
739 | PlayerCfg.Cockpit3DView[w] = CV_NONE; |
||
740 | break; |
||
741 | } |
||
742 | const auto mo = MarkerState.imobjidx[mvn]; |
||
743 | if (mo == object_none) |
||
744 | { |
||
745 | PlayerCfg.Cockpit3DView[w] = CV_NONE; |
||
746 | break; |
||
747 | } |
||
748 | const auto displayed_marker_id = static_cast<unsigned>(mvn) + 1; |
||
749 | snprintf(label, sizeof(label), "Marker %u", displayed_marker_id); |
||
750 | do_cockpit_window_view(w, *vcobjptr(mo), 0, WBU_MARKER, label); |
||
751 | break; |
||
752 | } |
||
753 | default: |
||
754 | Int3(); //invalid window type |
||
755 | } |
||
756 | } |
||
757 | |||
758 | int BigWindowSwitch=0; |
||
759 | #endif |
||
760 | |||
761 | static void update_cockpits(); |
||
762 | |||
763 | //render a frame for the game |
||
764 | void game_render_frame_mono() |
||
765 | { |
||
766 | int no_draw_hud=0; |
||
767 | |||
768 | gr_set_current_canvas(Screen_3d_window); |
||
769 | #if defined(DXX_BUILD_DESCENT_II) |
||
770 | auto &Objects = LevelUniqueObjectState.Objects; |
||
771 | auto &vmobjptr = Objects.vmptr; |
||
772 | if (const auto &&gimobj = ( |
||
773 | PlayerCfg.GuidedInBigWindow |
||
774 | ? LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num) |
||
775 | : nullptr)) |
||
776 | { |
||
777 | const auto viewer_save = Viewer; |
||
778 | |||
779 | if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_REAR_VIEW) |
||
780 | { |
||
781 | BigWindowSwitch=1; |
||
782 | force_cockpit_redraw=1; |
||
783 | PlayerCfg.CockpitMode[1]=CM_STATUS_BAR; |
||
784 | return; |
||
785 | } |
||
786 | |||
787 | Viewer = gimobj; |
||
788 | |||
789 | window_rendered_data window; |
||
790 | update_rendered_data(window, *Viewer, 0); |
||
791 | render_frame(*grd_curcanv, 0, window); |
||
792 | |||
793 | wake_up_rendered_objects(*Viewer, window); |
||
794 | show_HUD_names(*grd_curcanv); |
||
795 | |||
796 | Viewer = viewer_save; |
||
797 | |||
798 | auto &game_font = *GAME_FONT; |
||
799 | gr_set_fontcolor(*grd_curcanv, BM_XRGB(27, 0, 0), -1); |
||
800 | |||
801 | gr_string(*grd_curcanv, game_font, 0x8000, FSPACY(1), "Guided Missile View"); |
||
802 | |||
803 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
804 | show_reticle(*grd_curcanv, player_info, RET_TYPE_CROSS_V1, 0); |
||
805 | |||
806 | HUD_render_message_frame(*grd_curcanv); |
||
807 | |||
808 | no_draw_hud=1; |
||
809 | } |
||
810 | else |
||
811 | #endif |
||
812 | { |
||
813 | #if defined(DXX_BUILD_DESCENT_II) |
||
814 | if (BigWindowSwitch) |
||
815 | { |
||
816 | force_cockpit_redraw=1; |
||
817 | PlayerCfg.CockpitMode[1]=(Rear_view?CM_REAR_VIEW:CM_FULL_COCKPIT); |
||
818 | BigWindowSwitch=0; |
||
819 | return; |
||
820 | } |
||
821 | #endif |
||
822 | window_rendered_data window; |
||
823 | #if defined(DXX_BUILD_DESCENT_II) |
||
824 | update_rendered_data(window, *Viewer, Rear_view); |
||
825 | #endif |
||
826 | render_frame(*grd_curcanv, 0, window); |
||
827 | } |
||
828 | |||
829 | #if defined(DXX_BUILD_DESCENT_II) |
||
830 | gr_set_current_canvas(Screen_3d_window); |
||
831 | #endif |
||
832 | |||
833 | update_cockpits(); |
||
834 | |||
835 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
836 | Game_mode = Newdemo_game_mode; |
||
837 | |||
838 | if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_STATUS_BAR) |
||
839 | render_gauges(); |
||
840 | |||
841 | if (Newdemo_state == ND_STATE_PLAYBACK) |
||
842 | Game_mode = GM_NORMAL; |
||
843 | |||
844 | gr_set_current_canvas(Screen_3d_window); |
||
845 | if (!no_draw_hud) |
||
846 | game_draw_hud_stuff(*grd_curcanv); |
||
847 | |||
848 | #if defined(DXX_BUILD_DESCENT_II) |
||
849 | gr_set_default_canvas(); |
||
850 | |||
851 | show_extra_views(); //missile view, buddy bot, etc. |
||
852 | #endif |
||
853 | |||
854 | if (netplayerinfo_on && Game_mode & GM_MULTI) |
||
855 | show_netplayerinfo(); |
||
856 | } |
||
857 | |||
858 | } |
||
859 | |||
860 | void toggle_cockpit() |
||
861 | { |
||
862 | enum cockpit_mode_t new_mode=CM_FULL_SCREEN; |
||
863 | |||
864 | if (Rear_view || Player_dead_state != player_dead_state::no) |
||
865 | return; |
||
866 | |||
867 | switch (PlayerCfg.CockpitMode[1]) |
||
868 | { |
||
869 | case CM_FULL_COCKPIT: |
||
870 | new_mode = CM_STATUS_BAR; |
||
871 | break; |
||
872 | case CM_STATUS_BAR: |
||
873 | new_mode = CM_FULL_SCREEN; |
||
874 | break; |
||
875 | case CM_FULL_SCREEN: |
||
876 | new_mode = CM_FULL_COCKPIT; |
||
877 | break; |
||
878 | case CM_REAR_VIEW: |
||
879 | case CM_LETTERBOX: |
||
880 | break; |
||
881 | } |
||
882 | |||
883 | select_cockpit(new_mode); |
||
884 | HUD_clear_messages(); |
||
885 | PlayerCfg.CockpitMode[0] = new_mode; |
||
886 | write_player_file(); |
||
887 | } |
||
888 | |||
889 | namespace dcx { |
||
890 | int last_drawn_cockpit = -1; |
||
891 | } |
||
892 | |||
893 | namespace dsx { |
||
894 | |||
895 | // This actually renders the new cockpit onto the screen. |
||
896 | static void update_cockpits() |
||
897 | { |
||
898 | grs_bitmap *bm; |
||
899 | int mode = PlayerCfg.CockpitMode[1]; |
||
900 | #if defined(DXX_BUILD_DESCENT_II) |
||
901 | mode += (HIRESMODE?(Num_cockpits/2):0); |
||
902 | #endif |
||
903 | |||
904 | switch( PlayerCfg.CockpitMode[1] ) { |
||
905 | case CM_FULL_COCKPIT: |
||
906 | case CM_REAR_VIEW: |
||
907 | PIGGY_PAGE_IN(cockpit_bitmap[mode]); |
||
908 | bm=&GameBitmaps[cockpit_bitmap[mode].index]; |
||
909 | gr_set_default_canvas(); |
||
910 | #if DXX_USE_OGL |
||
911 | ogl_ubitmapm_cs(*grd_curcanv, 0, 0, -1, -1, *bm, 255, F1_0); |
||
912 | #else |
||
913 | gr_ubitmapm(*grd_curcanv, 0, 0, *bm); |
||
914 | #endif |
||
915 | break; |
||
916 | |||
917 | case CM_FULL_SCREEN: |
||
918 | break; |
||
919 | |||
920 | case CM_STATUS_BAR: |
||
921 | PIGGY_PAGE_IN(cockpit_bitmap[mode]); |
||
922 | bm=&GameBitmaps[cockpit_bitmap[mode].index]; |
||
923 | gr_set_default_canvas(); |
||
924 | #if DXX_USE_OGL |
||
925 | ogl_ubitmapm_cs(*grd_curcanv, 0, (HIRESMODE ? (SHEIGHT * 2) / 2.6 : (SHEIGHT * 2) / 2.72), -1, (static_cast<int>(static_cast<double>(bm->bm_h) * (HIRESMODE ? static_cast<double>(SHEIGHT) / 480 : static_cast<double>(SHEIGHT) / 200) + 0.5)), *bm, 255, F1_0); |
||
926 | #else |
||
927 | gr_ubitmapm(*grd_curcanv, 0, SHEIGHT - bm->bm_h, *bm); |
||
928 | #endif |
||
929 | break; |
||
930 | |||
931 | case CM_LETTERBOX: |
||
932 | break; |
||
933 | } |
||
934 | gr_set_default_canvas(); |
||
935 | |||
936 | if (PlayerCfg.CockpitMode[1] != last_drawn_cockpit) |
||
937 | last_drawn_cockpit = PlayerCfg.CockpitMode[1]; |
||
938 | else |
||
939 | return; |
||
940 | |||
941 | if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_STATUS_BAR) |
||
942 | init_gauges(); |
||
943 | |||
944 | } |
||
945 | |||
946 | } |
||
947 | |||
948 | void game_render_frame() |
||
949 | { |
||
950 | auto &Objects = LevelUniqueObjectState.Objects; |
||
951 | auto &vmobjptr = Objects.vmptr; |
||
952 | set_screen_mode( SCREEN_GAME ); |
||
953 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
954 | play_homing_warning(player_info); |
||
955 | game_render_frame_mono(); |
||
956 | } |
||
957 | |||
958 | //show a message in a nice little box |
||
959 | void show_boxed_message(const char *msg, int RenderFlag) |
||
960 | { |
||
961 | int w,h; |
||
962 | int x,y; |
||
963 | |||
964 | gr_set_default_canvas(); |
||
965 | auto &canvas = *grd_curcanv; |
||
966 | gr_set_fontcolor(canvas, BM_XRGB(31, 31, 31), -1); |
||
967 | auto &medium1_font = *MEDIUM1_FONT; |
||
968 | gr_get_string_size(medium1_font, msg, &w, &h, nullptr); |
||
969 | |||
970 | x = (SWIDTH-w)/2; |
||
971 | y = (SHEIGHT-h)/2; |
||
972 | |||
973 | nm_draw_background(canvas, x - BORDERX, y - BORDERY, x + w + BORDERX, y + h + BORDERY); |
||
974 | |||
975 | gr_string(canvas, medium1_font, 0x8000, y, msg, w, h); |
||
976 | |||
977 | // If we haven't drawn behind it, need to flip |
||
978 | if (!RenderFlag) |
||
979 | gr_flip(); |
||
980 | } |