1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20 /*
21 *
22 * Routines for displaying the auto-map.
23 *
24 */
25
26 #include "dxxsconf.h"
27 #include <algorithm>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #if DXX_USE_OGL
33 #include "ogl_init.h"
34 #endif
35
36 #include "dxxerror.h"
37 #include "3d.h"
38 #include "inferno.h"
39 #include "u_mem.h"
40 #include "render.h"
41 #include "object.h"
42 #include "game.h"
43 #include "polyobj.h"
44 #include "sounds.h"
45 #include "player.h"
46 #include "bm.h"
47 #include "key.h"
48 #include "newmenu.h"
49 #include "textures.h"
50 #include "hudmsg.h"
51 #include "timer.h"
52 #include "segpoint.h"
53 #include "joy.h"
54 #include "pcx.h"
55 #include "palette.h"
56 #include "wall.h"
57 #include "physfsx.h"
58 #include "gameseq.h"
59 #include "gamefont.h"
60 #include "gameseg.h"
61 #include "common/3d/globvars.h"
62 #include "multi.h"
63 #include "kconfig.h"
64 #include "endlevel.h"
65 #include "text.h"
66 #include "gauges.h"
67 #include "powerup.h"
68 #include "switch.h"
69 #include "automap.h"
70 #include "timer.h"
71 #include "config.h"
72 #include "playsave.h"
73 #include "window.h"
74 #include "playsave.h"
75 #include "args.h"
76 #include "physics.h"
77
78 #include "compiler-range_for.h"
79 #include "d_levelstate.h"
80 #include "d_range.h"
81 #include "d_zip.h"
82 #include "partial_range.h"
83 #include <memory>
84
85 #define LEAVE_TIME 0x4000
86
87 #define EF_USED 1 // This edge is used
88 #define EF_DEFINING 2 // A structure defining edge that should always draw.
89 #define EF_FRONTIER 4 // An edge between the known and the unknown.
90 #define EF_SECRET 8 // An edge that is part of a secret wall.
91 #define EF_GRATE 16 // A grate... draw it all the time.
92 #define EF_NO_FADE 32 // An edge that doesn't fade with distance
93 #define EF_TOO_FAR 64 // An edge that is too far away
94
95 namespace dcx {
96
97 namespace {
98
99 struct Edge_info
100 {
101 std::array<vertnum_t, 2> verts; // 8 bytes
102 std::array<uint8_t, 4> sides; // 4 bytes
103 std::array<segnum_t, 4> segnum; // 16 bytes // This might not need to be stored... If you can access the normals of a side.
104 ubyte flags; // 1 bytes // See the EF_??? defines above.
105 color_t color; // 1 bytes
106 ubyte num_faces; // 1 bytes // 31 bytes...
107 };
108
109 struct automap : ::dcx::window
110 {
111 using ::dcx::window::window;
112 fix64 entry_time;
113 fix64 t1, t2;
114 static_assert(PF_WIGGLE < UINT8_MAX, "storing PF_WIGGLE into old_wiggle would truncate the value");
115 uint8_t old_wiggle;
116 uint8_t leave_mode = 0;
117 uint8_t pause_game;
118 vms_angvec tangles;
119 int max_segments_away = 0;
120 int segment_limit = 1;
121
122 // Edge list variables
123 int num_edges = 0;
124 unsigned max_edges; //set each frame
125 unsigned end_valid_edges = 0;
126 std::unique_ptr<Edge_info[]> edges;
127 std::unique_ptr<Edge_info *[]> drawingListBright;
128
129 // Screen canvas variables
130 grs_subcanvas automap_view;
131
132 grs_main_bitmap automap_background;
133
134 // Rendering variables
135 fix zoom = 0x9000;
136 vms_vector view_target;
137 vms_vector view_position;
138 fix farthest_dist = (F1_0 * 20 * 50); // 50 segments away
139 vms_matrix viewMatrix;
140 fix viewDist = 0;
141
142 segment_depth_array_t depth_array;
143 color_t wall_normal_color;
144 color_t wall_door_color;
145 color_t wall_door_blue;
146 color_t wall_door_gold;
147 color_t wall_door_red;
148 color_t hostage_color;
149 color_t green_31;
150 color_t white_63;
151 color_t blue_48;
152 color_t red_48;
153 };
154
155 }
156
157 }
158
159 namespace dsx {
160
161 namespace {
162
163 struct automap : ::dcx::automap
164 {
165 using ::dcx::automap::automap;
166 #if defined(DXX_BUILD_DESCENT_II)
167 color_t wall_revealed_color;
168 #endif
169 control_info controls;
170 virtual window_event_result event_handler(const d_event &) override;
171 };
172
init_automap_subcanvas(grs_subcanvas & view,grs_canvas & container)173 static void init_automap_subcanvas(grs_subcanvas &view, grs_canvas &container)
174 {
175 #if defined(DXX_BUILD_DESCENT_I)
176 if (MacHog)
177 gr_init_sub_canvas(view, container, 38*(SWIDTH/640.0), 77*(SHEIGHT/480.0), 564*(SWIDTH/640.0), 381*(SHEIGHT/480.0));
178 else
179 #endif
180 gr_init_sub_canvas(view, container, (SWIDTH/23), (SHEIGHT/6), (SWIDTH/1.1), (SHEIGHT/1.45));
181 }
182
183 static void automap_build_edge_list(automap &am, int add_all_edges);
184 }
185
186 }
187
188 namespace dcx {
189
190 #define MAX_EDGES_FROM_VERTS(v) ((v)*4)
191
192 #define K_WALL_NORMAL_COLOR BM_XRGB(29, 29, 29 )
193 #define K_WALL_DOOR_COLOR BM_XRGB(5, 27, 5 )
194 #define K_WALL_DOOR_BLUE BM_XRGB(0, 0, 31)
195 #define K_WALL_DOOR_GOLD BM_XRGB(31, 31, 0)
196 #define K_WALL_DOOR_RED BM_XRGB(31, 0, 0)
197 #define K_WALL_REVEALED_COLOR BM_XRGB(0, 0, 25 ) //what you see when you have the full map powerup
198 #define K_HOSTAGE_COLOR BM_XRGB(0, 31, 0 )
199 #define K_FONT_COLOR_20 BM_XRGB(20, 20, 20 )
200 #define K_GREEN_31 BM_XRGB(0, 31, 0)
201
202 int Automap_active = 0;
203
204 namespace {
205
206 #ifndef NDEBUG
207 static uint8_t Automap_debug_show_all_segments;
208 #endif
209
automap_clear_visited(d_level_unique_automap_state & LevelUniqueAutomapState)210 static void automap_clear_visited(d_level_unique_automap_state &LevelUniqueAutomapState)
211 {
212 #ifndef NDEBUG
213 Automap_debug_show_all_segments = 0;
214 #endif
215 LevelUniqueAutomapState.Automap_visited = {};
216 }
217
init_automap_colors(automap & am)218 static void init_automap_colors(automap &am)
219 {
220 am.wall_normal_color = K_WALL_NORMAL_COLOR;
221 am.wall_door_color = K_WALL_DOOR_COLOR;
222 am.wall_door_blue = K_WALL_DOOR_BLUE;
223 am.wall_door_gold = K_WALL_DOOR_GOLD;
224 am.wall_door_red = K_WALL_DOOR_RED;
225 am.hostage_color = K_HOSTAGE_COLOR;
226 am.green_31 = K_GREEN_31;
227 am.white_63 = gr_find_closest_color_current(63, 63, 63);
228 am.blue_48 = gr_find_closest_color_current(0, 0, 48);
229 am.red_48 = gr_find_closest_color_current(48, 0, 0);
230 }
231
adjust_segment_limit(automap & am,const unsigned SegmentLimit)232 void adjust_segment_limit(automap &am, const unsigned SegmentLimit)
233 {
234 const auto &depth_array = am.depth_array;
235 const auto predicate = [&depth_array, SegmentLimit](const segnum_t &e1) {
236 return depth_array[e1] <= SegmentLimit;
237 };
238 for (auto &i : unchecked_partial_range(am.edges.get(), am.end_valid_edges))
239 {
240 // Unchecked for speed
241 const auto &&range = unchecked_partial_range(i.segnum, i.num_faces);
242 if (std::any_of(range.begin(), range.end(), predicate))
243 i.flags &= ~EF_TOO_FAR;
244 else
245 i.flags |= EF_TOO_FAR;
246 }
247 }
248
249 }
250
251 }
252
253 namespace dsx {
254
255 namespace {
256
recompute_automap_segment_visibility(const d_level_unique_automap_state & LevelUniqueAutomapState,unsigned compute_depth_all_segments,const segnum_t initial_segnum,automap & am)257 static void recompute_automap_segment_visibility(const d_level_unique_automap_state &LevelUniqueAutomapState, unsigned compute_depth_all_segments, const segnum_t initial_segnum, automap &am)
258 {
259 #ifndef NDEBUG
260 if (Automap_debug_show_all_segments)
261 compute_depth_all_segments = 1;
262 #endif
263 if (cheats.fullautomap)
264 compute_depth_all_segments = 1;
265 automap_build_edge_list(am, compute_depth_all_segments);
266 am.max_segments_away = set_segment_depths(initial_segnum, compute_depth_all_segments ? nullptr : &LevelUniqueAutomapState.Automap_visited, am.depth_array);
267 am.segment_limit = am.max_segments_away;
268 adjust_segment_limit(am, am.segment_limit);
269 }
270
271 #if defined(DXX_BUILD_DESCENT_II)
272 struct marker_delete_are_you_sure_menu : std::array<newmenu_item, 2>, newmenu
273 {
274 using array_type = std::array<newmenu_item, 2>;
275 d_level_unique_object_state &LevelUniqueObjectState;
276 segment_array &Segments;
277 d_marker_state &MarkerState;
marker_delete_are_you_sure_menudsx::__anonf12f1bc00511::marker_delete_are_you_sure_menu278 marker_delete_are_you_sure_menu(grs_canvas &canvas, d_level_unique_object_state &LevelUniqueObjectState, segment_array &Segments, d_marker_state &MarkerState) :
279 array_type{{
280 newmenu_item::nm_item_menu{TXT_YES},
281 newmenu_item::nm_item_menu{TXT_NO},
282 }},
283 newmenu(menu_title{nullptr}, menu_subtitle{"Delete Marker?"}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(*static_cast<array_type *>(this), 0), canvas),
284 LevelUniqueObjectState(LevelUniqueObjectState),
285 Segments(Segments),
286 MarkerState(MarkerState)
287 {
288 }
289 virtual window_event_result event_handler(const d_event &) override;
290 static std::pair<imobjidx_t *, game_marker_index> get_marker_object(d_marker_state &MarkerState);
291 void handle_selected_yes() const;
292 };
293
event_handler(const d_event & event)294 window_event_result marker_delete_are_you_sure_menu::event_handler(const d_event &event)
295 {
296 switch (event.type)
297 {
298 case EVENT_NEWMENU_SELECTED:
299 {
300 const auto citem = static_cast<const d_select_event &>(event).citem;
301 if (citem == 0)
302 /* User chose Yes */
303 handle_selected_yes();
304 /* The dialog should close after the user picks Yes or No.
305 */
306 return window_event_result::close;
307 }
308 default:
309 return newmenu::event_handler(event);
310 }
311 }
312
get_marker_object(d_marker_state & MarkerState)313 std::pair<imobjidx_t *, game_marker_index> marker_delete_are_you_sure_menu::get_marker_object(d_marker_state &MarkerState)
314 {
315 const auto HighlightMarker = MarkerState.HighlightMarker;
316 if (!MarkerState.imobjidx.valid_index(HighlightMarker))
317 return {nullptr, HighlightMarker};
318 auto &mo = MarkerState.imobjidx[HighlightMarker];
319 return {mo == object_none ? nullptr : &mo, HighlightMarker};
320 }
321
handle_selected_yes() const322 void marker_delete_are_you_sure_menu::handle_selected_yes() const
323 {
324 const auto [mo, HighlightMarker] = get_marker_object(MarkerState);
325 if (!mo)
326 /* Check that the selected marker is still a valid object. */
327 return;
328 /* FIXME: this event should be sent to other players
329 * so that they remove the marker.
330 */
331 obj_delete(LevelUniqueObjectState, Segments, LevelUniqueObjectState.Objects.vmptridx(std::exchange(*mo, object_none)));
332 MarkerState.message[HighlightMarker] = {};
333 MarkerState.HighlightMarker = game_marker_index::None;
334 }
335
init_automap_colors(automap & am)336 void init_automap_colors(automap &am)
337 {
338 ::dcx::init_automap_colors(am);
339 am.wall_revealed_color = K_WALL_REVEALED_COLOR;
340 }
341 #endif
342
343 // Map movement defines
344 #define PITCH_DEFAULT 9000
345 #define ZOOM_DEFAULT i2f(20*10)
346 #define ZOOM_MIN_VALUE i2f(20*5)
347 #define ZOOM_MAX_VALUE i2f(20*100)
348
349 }
350
351 /* MAX_DROP_MULTI_* must be a power of 2 for LastMarkerDropped to work
352 * properly.
353 */
354 #define MAX_DROP_MULTI_COOP_0 2
355 #define MAX_DROP_MULTI_COOP_1 4
356 #define MAX_DROP_MULTI_COOP_P (max_numplayers > 4)
357 #define MAX_DROP_MULTI_COOP (MAX_DROP_MULTI_COOP_P ? MAX_DROP_MULTI_COOP_0 : MAX_DROP_MULTI_COOP_1)
358 #define MAX_DROP_MULTI_COMPETITIVE 2
359 #define MAX_DROP_SINGLE 9
360
361 #if defined(DXX_BUILD_DESCENT_II)
362 marker_message_text_t Marker_input;
363 static float MarkerScale=2.0;
364
365 d_marker_state MarkerState;
366
convert_player_marker_index_to_game_marker_index(const game_mode_flags game_mode,const unsigned max_numplayers,const unsigned player_num,const player_marker_index player_marker_num)367 game_marker_index convert_player_marker_index_to_game_marker_index(const game_mode_flags game_mode, const unsigned max_numplayers, const unsigned player_num, const player_marker_index player_marker_num)
368 {
369 if (game_mode & GM_MULTI_COOP)
370 return static_cast<game_marker_index>((player_num * MAX_DROP_MULTI_COOP) + static_cast<unsigned>(player_marker_num));
371 if (game_mode & GM_MULTI)
372 return static_cast<game_marker_index>((player_num * MAX_DROP_MULTI_COMPETITIVE) + static_cast<unsigned>(player_marker_num));
373 return game_marker_index{player_marker_num};
374 }
375
get_markers_per_player(const game_mode_flags game_mode,const unsigned max_numplayers)376 unsigned d_marker_state::get_markers_per_player(const game_mode_flags game_mode, const unsigned max_numplayers)
377 {
378 if (game_mode & GM_MULTI_COOP)
379 return MAX_DROP_MULTI_COOP;
380 if (game_mode & GM_MULTI)
381 return MAX_DROP_MULTI_COMPETITIVE;
382 return MAX_DROP_SINGLE;
383 }
384
get_player_marker_range(const unsigned maxdrop)385 xrange<player_marker_index> get_player_marker_range(const unsigned maxdrop)
386 {
387 const auto base = player_marker_index::_0;
388 return {base, static_cast<player_marker_index>(static_cast<unsigned>(base) + maxdrop)};
389 }
390
get_marker_owner(const game_mode_flags game_mode,const game_marker_index gmi,const unsigned max_numplayers)391 playernum_t get_marker_owner(const game_mode_flags game_mode, const game_marker_index gmi, const unsigned max_numplayers)
392 {
393 const auto ugmi = static_cast<unsigned>(gmi);
394 if (game_mode & GM_MULTI_COOP)
395 {
396 /* This is split out to encourage the compiler to recognize that
397 * the divisor is a constant in every path, and in every path,
398 * the divisor was chosen to allow use of right shift in place
399 * of division.
400 */
401 if (MAX_DROP_MULTI_COOP_P)
402 return ugmi / MAX_DROP_MULTI_COOP_0;
403 return ugmi / MAX_DROP_MULTI_COOP_1;
404 }
405 if (game_mode & GM_MULTI)
406 return ugmi / MAX_DROP_MULTI_COMPETITIVE;
407 return 0;
408 }
409
410 namespace {
411
get_game_marker_range(const game_mode_flags game_mode,const unsigned max_numplayers,const unsigned player_num,const unsigned maxdrop)412 xrange<game_marker_index> get_game_marker_range(const game_mode_flags game_mode, const unsigned max_numplayers, const unsigned player_num, const unsigned maxdrop)
413 {
414 const auto base = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, player_num, player_marker_index::_0);
415 return {base, static_cast<game_marker_index>(static_cast<unsigned>(base) + maxdrop)};
416 }
417
418 }
419 #endif
420
421 # define automap_draw_line g3_draw_line
422 #if DXX_USE_OGL
423 #define DrawMarkerNumber(C,a,b,c,d) DrawMarkerNumber(a,b,c,d)
424 #endif
425
426 // -------------------------------------------------------------
427
428 namespace {
429
430 static void draw_all_edges(automap &am);
431 #if defined(DXX_BUILD_DESCENT_II)
DrawMarkerNumber(grs_canvas & canvas,const automap & am,const game_marker_index gmi,const player_marker_index pmi,const g3s_point & BasePoint)432 static void DrawMarkerNumber(grs_canvas &canvas, const automap &am, const game_marker_index gmi, const player_marker_index pmi, const g3s_point &BasePoint)
433 {
434 struct xy
435 {
436 float x0, y0, x1, y1;
437 };
438 static constexpr enumerated_array<std::array<xy, 5>, 9, player_marker_index> sArray = {{{
439 {{
440 {-0.25, 0.75, 0, 1},
441 {0, 1, 0, -1},
442 {-1, -1, 1, -1},
443 }},
444 {{
445 {-1, 1, 1, 1},
446 {1, 1, 1, 0},
447 {-1, 0, 1, 0},
448 {-1, 0, -1, -1},
449 {-1, -1, 1, -1}
450 }},
451 {{
452 {-1, 1, 1, 1},
453 {1, 1, 1, -1},
454 {-1, -1, 1, -1},
455 {0, 0, 1, 0},
456 }},
457 {{
458 {-1, 1, -1, 0},
459 {-1, 0, 1, 0},
460 {1, 1, 1, -1},
461 }},
462 {{
463 {-1, 1, 1, 1},
464 {-1, 1, -1, 0},
465 {-1, 0, 1, 0},
466 {1, 0, 1, -1},
467 {-1, -1, 1, -1}
468 }},
469 {{
470 {-1, 1, 1, 1},
471 {-1, 1, -1, -1},
472 {-1, -1, 1, -1},
473 {1, -1, 1, 0},
474 {-1, 0, 1, 0}
475 }},
476 {{
477 {-1, 1, 1, 1},
478 {1, 1, 1, -1},
479 }},
480 {{
481 {-1, 1, 1, 1},
482 {1, 1, 1, -1},
483 {-1, -1, 1, -1},
484 {-1, -1, -1, 1},
485 {-1, 0, 1, 0}
486 }},
487 {{
488 {-1, 1, 1, 1},
489 {1, 1, 1, -1},
490 {-1, 0, 1, 0},
491 {-1, 0, -1, 1},
492 }}
493 }}};
494 static constexpr enumerated_array<uint_fast8_t, 9, player_marker_index> NumOfPoints = {{{3, 5, 4, 3, 5, 5, 2, 5, 4}}};
495
496 const auto color = (gmi == MarkerState.HighlightMarker ? am.white_63 : am.blue_48);
497 const auto scale_x = Matrix_scale.x;
498 const auto scale_y = Matrix_scale.y;
499 range_for (const auto &i, unchecked_partial_range(sArray[pmi], NumOfPoints[pmi]))
500 {
501 const auto ax0 = i.x0 * MarkerScale;
502 const auto ay0 = i.y0 * MarkerScale;
503 const auto ax1 = i.x1 * MarkerScale;
504 const auto ay1 = i.y1 * MarkerScale;
505 auto FromPoint = BasePoint;
506 auto ToPoint = BasePoint;
507 FromPoint.p3_x += fixmul(fl2f(ax0), scale_x);
508 FromPoint.p3_y += fixmul(fl2f(ay0), scale_y);
509 ToPoint.p3_x += fixmul(fl2f(ax1), scale_x);
510 ToPoint.p3_y += fixmul(fl2f(ay1), scale_y);
511 g3_code_point(FromPoint);
512 g3_code_point(ToPoint);
513 g3_project_point(FromPoint);
514 g3_project_point(ToPoint);
515 automap_draw_line(canvas, FromPoint, ToPoint, color);
516 }
517 }
518
DropMarker(fvmobjptridx & vmobjptridx,fvmsegptridx & vmsegptridx,const object & plrobj,const game_marker_index marker_num,const player_marker_index player_marker_num)519 static void DropMarker(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, const object &plrobj, const game_marker_index marker_num, const player_marker_index player_marker_num)
520 {
521 auto &marker_objidx = MarkerState.imobjidx[marker_num];
522 if (marker_objidx != object_none)
523 obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(marker_objidx));
524
525 marker_objidx = drop_marker_object(plrobj.pos, vmsegptridx(plrobj.segnum), plrobj.orient, marker_num);
526
527 if (Game_mode & GM_MULTI)
528 multi_send_drop_marker(Player_num, plrobj.pos, player_marker_num, MarkerState.message[marker_num]);
529 }
530
531 }
532
DropBuddyMarker(object & objp)533 void DropBuddyMarker(object &objp)
534 {
535 auto &Objects = LevelUniqueObjectState.Objects;
536 auto &vmobjptridx = Objects.vmptridx;
537
538 constexpr auto marker_num = game_marker_index::GuidebotDeathSite;
539 static_assert(MarkerState.message.valid_index(marker_num), "not enough markers");
540
541 auto &MarkerMessage = MarkerState.message[marker_num];
542 snprintf(&MarkerMessage[0], MarkerMessage.size(), "RIP: %s", static_cast<const char *>(PlayerCfg.GuidebotName));
543
544 auto &marker_objidx = MarkerState.imobjidx[marker_num];
545 if (marker_objidx != object_none)
546 obj_delete(LevelUniqueObjectState, Segments, vmobjptridx(marker_objidx));
547
548 marker_objidx = drop_marker_object(objp.pos, vmsegptridx(objp.segnum), objp.orient, marker_num);
549 }
550
551 namespace {
552
553 #define MARKER_SPHERE_SIZE 0x58000
554
DrawMarkers(fvcobjptr & vcobjptr,grs_canvas & canvas,automap & am)555 static void DrawMarkers(fvcobjptr &vcobjptr, grs_canvas &canvas, automap &am)
556 {
557 static int cyc=10,cycdir=1;
558
559 const auto game_mode = Game_mode;
560 const auto max_numplayers = Netgame.max_numplayers;
561 const auto maxdrop = MarkerState.get_markers_per_player(game_mode, max_numplayers);
562 const auto &&game_marker_range = get_game_marker_range(game_mode, max_numplayers, Player_num, maxdrop);
563 const auto &&player_marker_range = get_player_marker_range(maxdrop);
564 const auto &&zipped_marker_range = zip(game_marker_range, player_marker_range, unchecked_partial_range(&MarkerState.imobjidx[*game_marker_range.begin()], maxdrop));
565 const auto &&mb = zipped_marker_range.begin();
566 const auto &&me = zipped_marker_range.end();
567 auto iter = mb;
568 /* Find the first marker object in the player's marker range that is
569 * not object_none. If every marker object in the range is
570 * object_none, then there are no markers to draw, so return.
571 */
572 for (;;)
573 {
574 auto &&[gmi, pmi, objidx] = *iter;
575 (void)gmi;
576 (void)pmi;
577 if (objidx != object_none)
578 break;
579 if (++ iter == me)
580 return;
581 }
582 /* A marker was found, so at least one marker will be drawn. Set up
583 * colors for the markers.
584 */
585 const auto current_cycle_color = cyc;
586 const std::array<color_t, 3> colors{{
587 gr_find_closest_color_current(current_cycle_color, 0, 0),
588 gr_find_closest_color_current(current_cycle_color + 10, 0, 0),
589 gr_find_closest_color_current(current_cycle_color + 20, 0, 0),
590 }};
591 for (; iter != me; ++iter)
592 {
593 auto &&[gmi, pmi, objidx] = *iter;
594 if (objidx != object_none)
595 {
596 /* Use cg3s_point so that the type is const for OpenGL and
597 * mutable for SDL-only.
598 */
599 cg3s_point &&sphere_point = g3_rotate_point(vcobjptr(objidx)->pos);
600 g3_draw_sphere(canvas, sphere_point, MARKER_SPHERE_SIZE, colors[0]);
601 g3_draw_sphere(canvas, sphere_point, MARKER_SPHERE_SIZE / 2, colors[1]);
602 g3_draw_sphere(canvas, sphere_point, MARKER_SPHERE_SIZE / 4, colors[2]);
603 DrawMarkerNumber(canvas, am, gmi, pmi, sphere_point);
604 }
605 }
606
607 if (cycdir)
608 cyc+=2;
609 else
610 cyc-=2;
611
612 if (cyc>43)
613 {
614 cyc=43;
615 cycdir=0;
616 }
617 else if (cyc<10)
618 {
619 cyc=10;
620 cycdir=1;
621 }
622 }
623
ClearMarkers()624 static void ClearMarkers()
625 {
626 static_cast<d_marker_object_numbers &>(MarkerState) = {};
627 MarkerState.message = {};
628 }
629 #endif
630
631 }
632
automap_clear_visited(d_level_unique_automap_state & LevelUniqueAutomapState)633 void automap_clear_visited(d_level_unique_automap_state &LevelUniqueAutomapState)
634 {
635 #if defined(DXX_BUILD_DESCENT_II)
636 ClearMarkers();
637 #endif
638 ::dcx::automap_clear_visited(LevelUniqueAutomapState);
639 }
640
641 namespace {
642
draw_player(grs_canvas & canvas,const object_base & obj,const uint8_t color)643 static void draw_player(grs_canvas &canvas, const object_base &obj, const uint8_t color)
644 {
645 // Draw Console player -- shaped like a ellipse with an arrow.
646 auto sphere_point = g3_rotate_point(obj.pos);
647 const auto obj_size = obj.size;
648 g3_draw_sphere(canvas, sphere_point, obj_size, color);
649
650 // Draw shaft of arrow
651 const auto &&head_pos = vm_vec_scale_add(obj.pos, obj.orient.fvec, obj_size * 2);
652 {
653 auto &&arrow_point = g3_rotate_point(vm_vec_scale_add(obj.pos, obj.orient.fvec, obj_size * 3));
654 automap_draw_line(canvas, sphere_point, arrow_point, color);
655
656 // Draw right head of arrow
657 {
658 const auto &&rhead_pos = vm_vec_scale_add(head_pos, obj.orient.rvec, obj_size);
659 auto head_point = g3_rotate_point(rhead_pos);
660 automap_draw_line(canvas, arrow_point, head_point, color);
661 }
662
663 // Draw left head of arrow
664 {
665 const auto &&lhead_pos = vm_vec_scale_add(head_pos, obj.orient.rvec, -obj_size);
666 auto head_point = g3_rotate_point(lhead_pos);
667 automap_draw_line(canvas, arrow_point, head_point, color);
668 }
669 }
670
671 // Draw player's up vector
672 {
673 const auto &&arrow_pos = vm_vec_scale_add(obj.pos, obj.orient.uvec, obj_size * 2);
674 auto arrow_point = g3_rotate_point(arrow_pos);
675 automap_draw_line(canvas, sphere_point, arrow_point, color);
676 }
677 }
678
679 #if defined(DXX_BUILD_DESCENT_II)
680 //name for each group. maybe move somewhere else
681 constexpr char system_name[][17] = {
682 "Zeta Aquilae",
683 "Quartzon System",
684 "Brimspark System",
685 "Limefrost Spiral",
686 "Baloris Prime",
687 "Omega System"};
688 #endif
689
name_frame(grs_canvas & canvas,automap & am)690 static void name_frame(grs_canvas &canvas, automap &am)
691 {
692 gr_set_fontcolor(canvas, am.green_31, -1);
693 char name_level_left[128];
694
695 auto &game_font = *GAME_FONT;
696 #if defined(DXX_BUILD_DESCENT_I)
697 const char *name_level;
698 if (Current_level_num > 0)
699 {
700 snprintf(name_level_left, sizeof(name_level_left), "%s %i: %s",TXT_LEVEL, Current_level_num, static_cast<const char *>(Current_level_name));
701 name_level = name_level_left;
702 }
703 else
704 name_level = Current_level_name;
705
706 gr_string(canvas, game_font, (SWIDTH / 64), (SHEIGHT / 48), name_level);
707 #elif defined(DXX_BUILD_DESCENT_II)
708 char name_level_right[128];
709 if (Current_level_num > 0)
710 snprintf(name_level_left, sizeof(name_level_left), "%s %i",TXT_LEVEL, Current_level_num);
711 else
712 snprintf(name_level_left, sizeof(name_level_left), "Secret Level %i",-Current_level_num);
713
714 const char *const current_level_name = Current_level_name;
715 if (PLAYING_BUILTIN_MISSION && Current_level_num > 0)
716 snprintf(name_level_right, sizeof(name_level_right), "%s %d: %s", system_name[(Current_level_num-1)/4], ((Current_level_num - 1) % 4) + 1, current_level_name);
717 else
718 snprintf(name_level_right, sizeof(name_level_right), " %s", current_level_name);
719
720 gr_string(canvas, game_font, (SWIDTH / 64), (SHEIGHT / 48), name_level_left);
721 const auto &&[wr, h] = gr_get_string_size(game_font, name_level_right);
722 gr_string(canvas, game_font, canvas.cv_bitmap.bm_w - wr - (SWIDTH / 64), (SHEIGHT / 48), name_level_right, wr, h);
723 #endif
724 }
725
automap_apply_input(automap & am,const vms_matrix & plrorient,const vms_vector & plrpos)726 static void automap_apply_input(automap &am, const vms_matrix &plrorient, const vms_vector &plrpos)
727 {
728 constexpr int SLIDE_SPEED = 350;
729 constexpr int ZOOM_SPEED_FACTOR = 500; //(1500)
730 constexpr int ROT_SPEED_DIVISOR = 115000;
731 if (PlayerCfg.AutomapFreeFlight)
732 {
733 if (am.controls.state.fire_primary)
734 {
735 // Reset orientation
736 am.controls.state.fire_primary = 0;
737 am.viewMatrix = plrorient;
738 vm_vec_scale_add(am.view_position, plrpos, am.viewMatrix.fvec, -ZOOM_DEFAULT);
739 }
740
741 if (am.controls.pitch_time || am.controls.heading_time || am.controls.bank_time)
742 {
743 vms_angvec tangles;
744
745 tangles.p = fixdiv(am.controls.pitch_time, ROT_SPEED_DIVISOR);
746 tangles.h = fixdiv(am.controls.heading_time, ROT_SPEED_DIVISOR);
747 tangles.b = fixdiv(am.controls.bank_time, ROT_SPEED_DIVISOR * 2);
748
749 const auto &&tempm = vm_angles_2_matrix(tangles);
750 am.viewMatrix = vm_matrix_x_matrix(am.viewMatrix, tempm);
751 check_and_fix_matrix(am.viewMatrix);
752 }
753
754 if (am.controls.forward_thrust_time || am.controls.vertical_thrust_time || am.controls.sideways_thrust_time)
755 {
756 vm_vec_scale_add2(am.view_position, am.viewMatrix.fvec, am.controls.forward_thrust_time * ZOOM_SPEED_FACTOR);
757 vm_vec_scale_add2(am.view_position, am.viewMatrix.uvec, am.controls.vertical_thrust_time * SLIDE_SPEED);
758 vm_vec_scale_add2(am.view_position, am.viewMatrix.rvec, am.controls.sideways_thrust_time * SLIDE_SPEED);
759
760 // Crude wrapping check
761 clamp_fix_symmetric(am.view_position.x, F1_0*32000);
762 clamp_fix_symmetric(am.view_position.y, F1_0*32000);
763 clamp_fix_symmetric(am.view_position.z, F1_0*32000);
764 }
765 }
766 else
767 {
768 if (am.controls.state.fire_primary)
769 {
770 // Reset orientation
771 am.viewDist = ZOOM_DEFAULT;
772 am.tangles.p = PITCH_DEFAULT;
773 am.tangles.h = 0;
774 am.tangles.b = 0;
775 am.view_target = plrpos;
776 am.controls.state.fire_primary = 0;
777 }
778
779 am.viewDist -= am.controls.forward_thrust_time * ZOOM_SPEED_FACTOR;
780 am.tangles.p += fixdiv(am.controls.pitch_time, ROT_SPEED_DIVISOR);
781 am.tangles.h += fixdiv(am.controls.heading_time, ROT_SPEED_DIVISOR);
782 am.tangles.b += fixdiv(am.controls.bank_time, ROT_SPEED_DIVISOR * 2);
783
784 if (am.controls.vertical_thrust_time || am.controls.sideways_thrust_time)
785 {
786 vms_angvec tangles1;
787 vms_vector old_vt;
788
789 old_vt = am.view_target;
790 tangles1 = am.tangles;
791 const auto &&tempm = vm_angles_2_matrix(tangles1);
792 vm_matrix_x_matrix(am.viewMatrix, plrorient, tempm);
793 vm_vec_scale_add2(am.view_target, am.viewMatrix.uvec, am.controls.vertical_thrust_time * SLIDE_SPEED);
794 vm_vec_scale_add2(am.view_target, am.viewMatrix.rvec, am.controls.sideways_thrust_time * SLIDE_SPEED);
795 if (vm_vec_dist_quick(am.view_target, plrpos) > i2f(1000))
796 am.view_target = old_vt;
797 }
798
799 const auto &&tempm = vm_angles_2_matrix(am.tangles);
800 vm_matrix_x_matrix(am.viewMatrix, plrorient, tempm);
801
802 clamp_fix_lh(am.viewDist, ZOOM_MIN_VALUE, ZOOM_MAX_VALUE);
803 }
804 }
805
draw_automap(fvcobjptr & vcobjptr,automap & am)806 static void draw_automap(fvcobjptr &vcobjptr, automap &am)
807 {
808 if ( am.leave_mode==0 && am.controls.state.automap && (timer_query()-am.entry_time)>LEAVE_TIME)
809 am.leave_mode = 1;
810
811 {
812 auto &canvas = am.w_canv;
813 show_fullscr(canvas, am.automap_background);
814 gr_set_fontcolor(canvas, BM_XRGB(20, 20, 20), -1);
815 {
816 int x, y;
817 #if defined(DXX_BUILD_DESCENT_I)
818 if (MacHog)
819 x = 80 * (SWIDTH / 640.), y = 36 * (SHEIGHT / 480.);
820 else
821 #endif
822 x = SWIDTH / 8, y = SHEIGHT / 16;
823 gr_string(canvas, *HUGE_FONT, x, y, TXT_AUTOMAP);
824 }
825 {
826 int x;
827 int y0, y1, y2;
828 #if defined(DXX_BUILD_DESCENT_I)
829 const auto s1 = TXT_SLIDE_UPDOWN;
830 const auto &s2 = "F9/F10 Changes viewing distance";
831 if (!MacHog)
832 {
833 x = SWIDTH / 4.923;
834 y0 = SHEIGHT / 1.126;
835 y1 = SHEIGHT / 1.083;
836 y2 = SHEIGHT / 1.043;
837 }
838 else
839 {
840 // for the Mac automap they're shown up the top, hence the different layout
841 x = 265 * (SWIDTH / 640.);
842 y0 = 27 * (SHEIGHT / 480.);
843 y1 = 44 * (SHEIGHT / 480.);
844 y2 = 61 * (SHEIGHT / 480.);
845 }
846 #elif defined(DXX_BUILD_DESCENT_II)
847 const auto &s1 = "F9/F10 Changes viewing distance";
848 const auto s2 = TXT_AUTOMAP_MARKER;
849 x = SWIDTH / 10.666;
850 y0 = SHEIGHT / 1.126;
851 y1 = SHEIGHT / 1.083;
852 y2 = SHEIGHT / 1.043;
853 #endif
854 auto &game_font = *GAME_FONT;
855 gr_string(canvas, game_font, x, y0, TXT_TURN_SHIP);
856 gr_string(canvas, game_font, x, y1, s1);
857 gr_string(canvas, game_font, x, y2, s2);
858 }
859
860 }
861 auto &canvas = am.automap_view;
862
863 gr_clear_canvas(canvas, BM_XRGB(0,0,0));
864
865 g3_start_frame(canvas);
866 render_start_frame();
867
868 if (!PlayerCfg.AutomapFreeFlight)
869 vm_vec_scale_add(am.view_position,am.view_target,am.viewMatrix.fvec,-am.viewDist);
870
871 g3_set_view_matrix(am.view_position,am.viewMatrix,am.zoom);
872
873 draw_all_edges(am);
874
875 // Draw player...
876 const auto &self_ship_rgb = player_rgb[get_player_or_team_color(Player_num)];
877 const auto closest_color = BM_XRGB(self_ship_rgb.r, self_ship_rgb.g, self_ship_rgb.b);
878 draw_player(canvas, vcobjptr(get_local_player().objnum), closest_color);
879
880 #if defined(DXX_BUILD_DESCENT_II)
881 DrawMarkers(vcobjptr, canvas, am);
882 #endif
883
884 // Draw player(s)...
885 const unsigned show_all_players = (Game_mode & GM_MULTI_COOP) || Netgame.game_flag.show_on_map;
886 if (show_all_players || (Game_mode & GM_TEAM))
887 {
888 const unsigned local_player_team = get_team(Player_num);
889 for (unsigned i = 0; i < N_players; ++i)
890 {
891 if (i == Player_num)
892 continue;
893 if (show_all_players || local_player_team == get_team(i))
894 {
895 auto &plr = *vcplayerptr(i);
896 auto &objp = *vcobjptr(plr.objnum);
897 if (objp.type == OBJ_PLAYER)
898 {
899 const auto &other_ship_rgb = player_rgb[get_player_or_team_color(i)];
900 draw_player(canvas, objp, BM_XRGB(other_ship_rgb.r, other_ship_rgb.g, other_ship_rgb.b));
901 }
902 }
903 }
904 }
905
906 range_for (const auto &&objp, vcobjptr)
907 {
908 switch( objp->type ) {
909 case OBJ_HOSTAGE:
910 {
911 auto sphere_point = g3_rotate_point(objp->pos);
912 g3_draw_sphere(canvas, sphere_point, objp->size, am.hostage_color);
913 }
914 break;
915 case OBJ_POWERUP:
916 if (LevelUniqueAutomapState.Automap_visited[objp->segnum]
917 #ifndef NDEBUG
918 || Automap_debug_show_all_segments
919 #endif
920 )
921 {
922 ubyte id = get_powerup_id(objp);
923 unsigned r, g, b;
924 if (id==POW_KEY_RED)
925 r = 63 * 2, g = 5 * 2, b = 5 * 2;
926 else if (id==POW_KEY_BLUE)
927 r = 5 * 2, g = 5 * 2, b = 63 * 2;
928 else if (id==POW_KEY_GOLD)
929 r = 63 * 2, g = 63 * 2, b = 10 * 2;
930 else
931 break;
932 {
933 const auto color = gr_find_closest_color(r, g, b);
934 auto sphere_point = g3_rotate_point(objp->pos);
935 g3_draw_sphere(canvas, sphere_point, objp->size * 4, color);
936 }
937 }
938 break;
939 default:
940 break;
941 }
942 }
943
944 g3_end_frame();
945
946 name_frame(canvas, am);
947
948 #if defined(DXX_BUILD_DESCENT_II)
949 {
950 const auto HighlightMarker = MarkerState.HighlightMarker;
951 if (MarkerState.message.valid_index(HighlightMarker))
952 {
953 auto &m = MarkerState.message[HighlightMarker];
954 gr_printf(canvas, *canvas.cv_font, (SWIDTH/64), (SHEIGHT/18), "Marker %u%c %s", static_cast<unsigned>(HighlightMarker) + 1, m[0] ? ':' : 0, &m[0]);
955 }
956 }
957 #endif
958
959 if (PlayerCfg.MouseFlightSim && PlayerCfg.MouseFSIndicator)
960 {
961 const auto gwidth = canvas.cv_bitmap.bm_w;
962 const auto gheight = canvas.cv_bitmap.bm_h;
963 auto &raw_mouse_axis = am.controls.raw_mouse_axis;
964 show_mousefs_indicator(canvas, raw_mouse_axis[0], raw_mouse_axis[1], raw_mouse_axis[2], gwidth - (gheight / 8), gheight - (gheight / 8), gheight / 5);
965 }
966
967 am.t2 = timer_query();
968 const auto vsync = CGameCfg.VSync;
969 const auto bound = F1_0 / (vsync ? MAXIMUM_FPS : CGameArg.SysMaxFPS);
970 const auto may_sleep = !CGameArg.SysNoNiceFPS && !vsync;
971 while (am.t2 - am.t1 < bound) // ogl is fast enough that the automap can read the input too fast and you start to turn really slow. So delay a bit (and free up some cpu :)
972 {
973 if (Game_mode & GM_MULTI)
974 multi_do_frame(); // during long wait, keep packets flowing
975 if (may_sleep)
976 timer_delay(F1_0>>8);
977 am.t2 = timer_update();
978 }
979 if (am.pause_game)
980 {
981 FrameTime=am.t2-am.t1;
982 calc_d_tick();
983 }
984 am.t1 = am.t2;
985 }
986
987 #if defined(DXX_BUILD_DESCENT_I)
988 #define MAP_BACKGROUND_FILENAME (((SWIDTH>=640&&SHEIGHT>=480) && PHYSFSX_exists("maph.pcx",1))?"maph.pcx":"map.pcx")
989 #elif defined(DXX_BUILD_DESCENT_II)
990 #define MAP_BACKGROUND_FILENAME ((HIRESMODE && PHYSFSX_exists("mapb.pcx",1))?"mapb.pcx":"map.pcx")
991 #endif
992
recompute_automap_segment_visibility(const d_level_unique_automap_state & LevelUniqueAutomapState,const object & plrobj,automap & am)993 static void recompute_automap_segment_visibility(const d_level_unique_automap_state &LevelUniqueAutomapState, const object &plrobj, automap &am)
994 {
995 recompute_automap_segment_visibility(LevelUniqueAutomapState, plrobj.ctype.player_info.powerup_flags & PLAYER_FLAGS_MAP_ALL, plrobj.segnum, am);
996 }
997
automap_key_command(const d_event & event,automap & am)998 static window_event_result automap_key_command(const d_event &event, automap &am)
999 {
1000 #if defined(DXX_BUILD_DESCENT_I) || !defined(NDEBUG)
1001 auto &Objects = LevelUniqueObjectState.Objects;
1002 auto &vmobjptr = Objects.vmptr;
1003 #endif
1004 int c = event_key_get(event);
1005
1006 switch (c)
1007 {
1008 #if DXX_USE_SCREENSHOT
1009 case KEY_PRINT_SCREEN: {
1010 gr_set_default_canvas();
1011 save_screen_shot(1);
1012 return window_event_result::handled;
1013 }
1014 #endif
1015 case KEY_ESC:
1016 if (am.leave_mode == 0)
1017 {
1018 return window_event_result::close;
1019 }
1020 return window_event_result::handled;
1021 #if defined(DXX_BUILD_DESCENT_I)
1022 case KEY_ALTED+KEY_F: // Alt+F shows full map, if cheats enabled
1023 if (cheats.enabled)
1024 {
1025 cheats.fullautomap = !cheats.fullautomap;
1026 // if cheat of map powerup, work with full depth
1027 auto &plrobj = get_local_plrobj();
1028 recompute_automap_segment_visibility(LevelUniqueAutomapState, plrobj, am);
1029 }
1030 return window_event_result::handled;
1031 #endif
1032 #ifndef NDEBUG
1033 case KEY_DEBUGGED+KEY_F: {
1034 Automap_debug_show_all_segments = !Automap_debug_show_all_segments;
1035 auto &plrobj = get_local_plrobj();
1036 recompute_automap_segment_visibility(LevelUniqueAutomapState, plrobj, am);
1037 }
1038 return window_event_result::handled;
1039 #endif
1040 case KEY_F9:
1041 if (am.segment_limit > 1)
1042 {
1043 am.segment_limit--;
1044 adjust_segment_limit(am, am.segment_limit);
1045 }
1046 return window_event_result::handled;
1047 case KEY_F10:
1048 if (am.segment_limit < am.max_segments_away) {
1049 am.segment_limit++;
1050 adjust_segment_limit(am, am.segment_limit);
1051 }
1052 return window_event_result::handled;
1053 #if defined(DXX_BUILD_DESCENT_II)
1054 case KEY_1:
1055 case KEY_2:
1056 case KEY_3:
1057 case KEY_4:
1058 case KEY_5:
1059 case KEY_6:
1060 case KEY_7:
1061 case KEY_8:
1062 case KEY_9:
1063 case KEY_0:
1064 {
1065 const auto game_mode = Game_mode;
1066 const auto max_numplayers = Netgame.max_numplayers;
1067 const auto maxdrop = MarkerState.get_markers_per_player(game_mode, max_numplayers);
1068 const uint8_t marker_num = c - KEY_1;
1069 if (marker_num <= maxdrop)
1070 {
1071 const auto gmi = convert_player_marker_index_to_game_marker_index(game_mode, max_numplayers, Player_num, player_marker_index{marker_num});
1072 if (MarkerState.imobjidx[gmi] != object_none)
1073 MarkerState.HighlightMarker = gmi;
1074 }
1075 else
1076 MarkerState.HighlightMarker = game_marker_index::None;
1077 }
1078 return window_event_result::handled;
1079 case KEY_D+KEY_CTRLED:
1080 {
1081 if (const auto [mo, HighlightMarker] = marker_delete_are_you_sure_menu::get_marker_object(MarkerState); mo == nullptr)
1082 {
1083 (void)HighlightMarker;
1084 /* If the selected index is not a valid marker, do
1085 * not offer to delete anything.
1086 */
1087 return window_event_result::handled;
1088 }
1089 auto menu = window_create<marker_delete_are_you_sure_menu>(grd_curscreen->sc_canvas, LevelUniqueObjectState, Segments, MarkerState);
1090 (void)menu;
1091 }
1092 return window_event_result::handled;
1093 #ifndef RELEASE
1094 case KEY_F11: //KEY_COMMA:
1095 if (MarkerScale>.5)
1096 MarkerScale-=.5;
1097 return window_event_result::handled;
1098 case KEY_F12: //KEY_PERIOD:
1099 if (MarkerScale<30.0)
1100 MarkerScale+=.5;
1101 return window_event_result::handled;
1102 #endif
1103 #endif
1104 }
1105 return window_event_result::ignored;
1106 }
1107
automap_process_input(const d_event & event,automap & am)1108 static window_event_result automap_process_input(const d_event &event, automap &am)
1109 {
1110 kconfig_read_controls(am.controls, event, 1);
1111
1112 if (!am.controls.state.automap && am.leave_mode == 1)
1113 {
1114 return window_event_result::close;
1115 }
1116 if (am.controls.state.automap)
1117 {
1118 am.controls.state.automap = 0;
1119 if (am.leave_mode == 0)
1120 {
1121 return window_event_result::close;
1122 }
1123 }
1124 return window_event_result::ignored;
1125 }
1126
1127 }
1128
event_handler(const d_event & event)1129 window_event_result automap::event_handler(const d_event &event)
1130 {
1131 auto &Objects = LevelUniqueObjectState.Objects;
1132 auto &vcobjptr = Objects.vcptr;
1133 auto &vmobjptr = Objects.vmptr;
1134 switch (event.type)
1135 {
1136 case EVENT_WINDOW_ACTIVATED:
1137 game_flush_inputs(controls);
1138 event_toggle_focus(1);
1139 key_toggle_repeat(0);
1140 break;
1141
1142 case EVENT_WINDOW_DEACTIVATED:
1143 event_toggle_focus(0);
1144 key_toggle_repeat(1);
1145 break;
1146
1147 #if SDL_MAJOR_VERSION == 2
1148 case EVENT_WINDOW_RESIZE:
1149 init_automap_subcanvas(automap_view, grd_curscreen->sc_canvas);
1150 break;
1151 #endif
1152
1153 case EVENT_IDLE:
1154 #if DXX_MAX_BUTTONS_PER_JOYSTICK
1155 case EVENT_JOYSTICK_BUTTON_UP:
1156 case EVENT_JOYSTICK_BUTTON_DOWN:
1157 #endif
1158 #if DXX_MAX_AXES_PER_JOYSTICK
1159 case EVENT_JOYSTICK_MOVED:
1160 #endif
1161 case EVENT_MOUSE_BUTTON_UP:
1162 case EVENT_MOUSE_BUTTON_DOWN:
1163 case EVENT_MOUSE_MOVED:
1164 case EVENT_KEY_RELEASE:
1165 return automap_process_input(event, *this);
1166 case EVENT_KEY_COMMAND:
1167 {
1168 window_event_result kret = automap_key_command(event, *this);
1169 if (kret == window_event_result::ignored)
1170 kret = automap_process_input(event, *this);
1171 return kret;
1172 }
1173
1174 case EVENT_WINDOW_DRAW:
1175 {
1176 auto &plrobj = get_local_plrobj();
1177 automap_apply_input(*this, plrobj.orient, plrobj.pos);
1178 }
1179 draw_automap(vcobjptr, *this);
1180 break;
1181
1182 case EVENT_WINDOW_CLOSE:
1183 if (!pause_game)
1184 ConsoleObject->mtype.phys_info.flags |= old_wiggle; // Restore wiggle
1185 event_toggle_focus(0);
1186 key_toggle_repeat(1);
1187 /* grd_curcanv points to `automap_view`, so grd_curcanv
1188 * would become a dangling pointer after the call to delete.
1189 * Redirect it to the default screen to avoid pointing to
1190 * freed memory. Setting grd_curcanv to nullptr would be
1191 * better, but some code assumes that grd_curcanv is never
1192 * nullptr, so instead set it to the default canvas.
1193 * Eventually, grd_curcanv will be removed entirely.
1194 */
1195 gr_set_default_canvas();
1196 Game_wind->set_visible(1);
1197 Automap_active = 0;
1198 multi_send_msgsend_state(msgsend_state::none);
1199 return window_event_result::ignored; // continue closing
1200
1201 case EVENT_LOOP_BEGIN_LOOP:
1202 kconfig_begin_loop(controls);
1203 break;
1204
1205 default:
1206 return window_event_result::ignored;
1207 break;
1208 }
1209 return window_event_result::handled;
1210 }
1211
do_automap()1212 void do_automap()
1213 {
1214 auto &Objects = LevelUniqueObjectState.Objects;
1215 auto &vmobjptr = Objects.vmptr;
1216 palette_array_t pal;
1217 auto am = window_create<automap>(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT);
1218 const auto max_edges = LevelSharedSegmentState.Num_segments * 12;
1219 am->max_edges = max_edges;
1220 am->edges = std::make_unique<Edge_info[]>(max_edges);
1221 am->drawingListBright = std::make_unique<Edge_info *[]>(max_edges);
1222
1223 init_automap_colors(*am);
1224 am->pause_game = !((Game_mode & GM_MULTI) && (!Endlevel_sequence)); // Set to 1 if everything is paused during automap...No pause during net.
1225
1226 if (am->pause_game) {
1227 Game_wind->set_visible(0);
1228 }
1229 else
1230 {
1231 am->old_wiggle = ConsoleObject->mtype.phys_info.flags & PF_WIGGLE; // Save old wiggle
1232 ConsoleObject->mtype.phys_info.flags &= ~PF_WIGGLE; // Turn off wiggle
1233 }
1234
1235 //Max_edges = min(MAX_EDGES_FROM_VERTS(Num_vertices),MAX_EDGES); //make maybe smaller than max
1236
1237 gr_set_default_canvas();
1238
1239 if ( am->viewDist==0 )
1240 am->viewDist = ZOOM_DEFAULT;
1241
1242 auto &plrobj = get_local_plrobj();
1243 am->viewMatrix = plrobj.orient;
1244 am->tangles.p = PITCH_DEFAULT;
1245 am->tangles.h = 0;
1246 am->tangles.b = 0;
1247 am->view_target = plrobj.pos;
1248
1249 if (PlayerCfg.AutomapFreeFlight)
1250 vm_vec_scale_add(am->view_position, plrobj.pos, am->viewMatrix.fvec, -ZOOM_DEFAULT);
1251
1252 am->t1 = am->entry_time = timer_query();
1253 am->t2 = am->t1;
1254
1255 //Fill in Automap_visited from Objects[Players[Player_num].objnum].segnum
1256 recompute_automap_segment_visibility(LevelUniqueAutomapState, plrobj, *am);
1257
1258 // ZICO - code from above to show frame in OGL correctly. Redundant, but better readable.
1259 // KREATOR - Now applies to all platforms so double buffering is supported
1260 {
1261 const auto pcx_error = pcx_read_bitmap_or_default(MAP_BACKGROUND_FILENAME, am->automap_background, pal);
1262 if (pcx_error != pcx_result::SUCCESS)
1263 con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "automap: File %s - PCX error: %s"), MAP_BACKGROUND_FILENAME, pcx_errormsg(pcx_error));
1264 gr_remap_bitmap_good(am->automap_background, pal, -1, -1);
1265 }
1266 init_automap_subcanvas(am->automap_view, grd_curscreen->sc_canvas);
1267
1268 gr_palette_load( gr_palette );
1269 Automap_active = 1;
1270 multi_send_msgsend_state(msgsend_state::automap);
1271 }
1272
1273 namespace {
1274
draw_all_edges(automap & am)1275 void draw_all_edges(automap &am)
1276 {
1277 #if !DXX_USE_OGL
1278 grs_canvas &canvas = am.automap_view;
1279 #endif
1280 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1281 auto &Vertices = LevelSharedVertexState.get_vertices();
1282 int j;
1283 unsigned nbright = 0;
1284 ubyte nfacing,nnfacing;
1285 fix distance;
1286 fix min_distance = INT32_MAX;
1287
1288 auto &vcvertptr = Vertices.vcptr;
1289 range_for (auto &i, unchecked_partial_range(am.edges.get(), am.end_valid_edges))
1290 {
1291 const auto e = &i;
1292 if (!(e->flags & EF_USED)) continue;
1293
1294 if ( e->flags & EF_TOO_FAR) continue;
1295
1296 if (e->flags&EF_FRONTIER) { // A line that is between what we have seen and what we haven't
1297 if ((!(e->flags & EF_SECRET)) && (e->color == am.wall_normal_color))
1298 continue; // If a line isn't secret and is normal color, then don't draw it
1299 }
1300 distance = Segment_points[e->verts[1]].p3_z;
1301
1302 if (min_distance>distance )
1303 min_distance = distance;
1304
1305 if (!rotate_list(vcvertptr, e->verts).uand)
1306 { //all off screen?
1307 nfacing = nnfacing = 0;
1308 auto &tv1 = *vcvertptr(e->verts[0]);
1309 j = 0;
1310 while( j<e->num_faces && (nfacing==0 || nnfacing==0) ) {
1311 if (!g3_check_normal_facing(tv1, vcsegptr(e->segnum[j])->shared_segment::sides[e->sides[j]].normals[0]))
1312 nfacing++;
1313 else
1314 nnfacing++;
1315 j++;
1316 }
1317
1318 if ( nfacing && nnfacing ) {
1319 // a contour line
1320 am.drawingListBright[nbright++] = e;
1321 } else if ( e->flags&(EF_DEFINING|EF_GRATE) ) {
1322 if ( nfacing == 0 ) {
1323 const uint8_t color = (e->flags & EF_NO_FADE)
1324 ? e->color
1325 : gr_fade_table[(gr_fade_level{8})][e->color];
1326 g3_draw_line(canvas, Segment_points[e->verts[0]], Segment_points[e->verts[1]], color);
1327 } else {
1328 am.drawingListBright[nbright++] = e;
1329 }
1330 }
1331 }
1332 }
1333
1334 if ( min_distance < 0 ) min_distance = 0;
1335
1336 // Sort the bright ones using a shell sort
1337 const auto &&range = unchecked_partial_range(am.drawingListBright.get(), nbright);
1338 std::sort(range.begin(), range.end(), [](const Edge_info *const a, const Edge_info *const b) {
1339 const auto &v1 = a->verts[0];
1340 const auto &v2 = b->verts[0];
1341 return Segment_points[v1].p3_z < Segment_points[v2].p3_z;
1342 });
1343 // Draw the bright ones
1344 range_for (const auto e, range)
1345 {
1346 const auto p1 = &Segment_points[e->verts[0]];
1347 const auto p2 = &Segment_points[e->verts[1]];
1348 fix dist;
1349 dist = p1->p3_z - min_distance;
1350 // Make distance be 1.0 to 0.0, where 0.0 is 10 segments away;
1351 if ( dist < 0 ) dist=0;
1352 if ( dist >= am.farthest_dist ) continue;
1353
1354 const auto color = (e->flags & EF_NO_FADE)
1355 ? e->color
1356 : gr_fade_table[static_cast<gr_fade_level>(f2i((F1_0 - fixdiv(dist, am.farthest_dist)) * 31))][e->color];
1357 g3_draw_line(canvas, *p1, *p2, color);
1358 }
1359 }
1360
1361 //==================================================================
1362 //
1363 // All routines below here are used to build the Edge list
1364 //
1365 //==================================================================
1366
1367 //finds edge, filling in edge_ptr. if found old edge, returns index, else return -1
automap_find_edge(automap & am,const vertnum_t v0,const vertnum_t v1)1368 static std::pair<Edge_info &, std::size_t> automap_find_edge(automap &am, const vertnum_t v0, const vertnum_t v1)
1369 {
1370 const auto &&hash_object = std::hash<vertnum_t>{};
1371 const auto initial_hash_slot = (hash_object(v0) ^ (hash_object(v1) << 10)) % am.max_edges;
1372
1373 for (auto current_hash_slot = initial_hash_slot;;)
1374 {
1375 auto &e = am.edges[current_hash_slot];
1376 const auto ev0 = e.verts[0];
1377 const auto ev1 = e.verts[1];
1378 if (e.num_faces == 0)
1379 return {e, current_hash_slot};
1380 if (v1 == ev1 && v0 == ev0)
1381 return {e, UINT32_MAX};
1382 else {
1383 if (++ current_hash_slot == am.max_edges)
1384 current_hash_slot = 0;
1385 if (current_hash_slot == initial_hash_slot)
1386 throw std::runtime_error("edge list full: search wrapped without finding a free slot");
1387 }
1388 }
1389 }
1390
add_one_edge(automap & am,vertnum_t va,vertnum_t vb,const uint8_t color,const unsigned side,const segnum_t segnum,const uint8_t flags)1391 static void add_one_edge(automap &am, vertnum_t va, vertnum_t vb, const uint8_t color, const unsigned side, const segnum_t segnum, const uint8_t flags)
1392 {
1393 if (am.num_edges >= am.max_edges)
1394 {
1395 // GET JOHN! (And tell him that his
1396 // MAX_EDGES_FROM_VERTS formula is hosed.)
1397 // If he's not around, save the mine,
1398 // and send him mail so he can look
1399 // at the mine later. Don't modify it.
1400 // This is important if this happens.
1401 Int3(); // LOOK ABOVE!!!!!!
1402 return;
1403 }
1404
1405 if ( va > vb ) {
1406 std::swap(va, vb);
1407 }
1408 const auto &&ef = automap_find_edge(am, va, vb);
1409 const auto e = &ef.first;
1410
1411 if (ef.second != UINT32_MAX)
1412 {
1413 e->verts[0] = va;
1414 e->verts[1] = vb;
1415 e->color = color;
1416 e->num_faces = 1;
1417 e->flags = EF_USED | EF_DEFINING; // Assume a normal line
1418 e->sides[0] = side;
1419 e->segnum[0] = segnum;
1420 ++ am.num_edges;
1421 const auto i = ef.second + 1;
1422 if (am.end_valid_edges < i)
1423 am.end_valid_edges = i;
1424 } else {
1425 if ( color != am.wall_normal_color )
1426 #if defined(DXX_BUILD_DESCENT_II)
1427 if (color != am.wall_revealed_color)
1428 #endif
1429 e->color = color;
1430
1431 if ( e->num_faces < 4 ) {
1432 e->sides[e->num_faces] = side;
1433 e->segnum[e->num_faces] = segnum;
1434 e->num_faces++;
1435 }
1436 }
1437 e->flags |= flags;
1438 }
1439
add_one_unknown_edge(automap & am,vertnum_t va,vertnum_t vb)1440 static void add_one_unknown_edge(automap &am, vertnum_t va, vertnum_t vb)
1441 {
1442 if ( va > vb ) {
1443 std::swap(va, vb);
1444 }
1445
1446 const auto &&ef = automap_find_edge(am, va, vb);
1447 if (ef.second == UINT32_MAX)
1448 ef.first.flags |= EF_FRONTIER; // Mark as a border edge
1449 }
1450
add_segment_edges(fvcsegptr & vcsegptr,fvcwallptr & vcwallptr,automap & am,const vcsegptridx_t seg)1451 static void add_segment_edges(fvcsegptr &vcsegptr, fvcwallptr &vcwallptr, automap &am, const vcsegptridx_t seg)
1452 {
1453 auto &ControlCenterState = LevelUniqueObjectState.ControlCenterState;
1454 auto &WallAnims = GameSharedState.WallAnims;
1455 #if defined(DXX_BUILD_DESCENT_II)
1456 auto &Objects = LevelUniqueObjectState.Objects;
1457 auto &vmobjptr = Objects.vmptr;
1458 #endif
1459 ubyte color;
1460
1461 for (unsigned sn = 0; sn < MAX_SIDES_PER_SEGMENT; ++sn)
1462 {
1463 uint8_t hidden_flag = 0;
1464 uint8_t is_grate = 0;
1465 uint8_t no_fade = 0;
1466
1467 color = 255;
1468 if (seg->shared_segment::children[sn] == segment_none) {
1469 color = am.wall_normal_color;
1470 }
1471
1472 switch( seg->special ) {
1473 case segment_special::nothing:
1474 case segment_special::repaircen:
1475 case segment_special::goal_blue:
1476 case segment_special::goal_red:
1477 break;
1478 case segment_special::fuelcen:
1479 color = BM_XRGB( 29, 27, 13 );
1480 break;
1481 case segment_special::controlcen:
1482 if (ControlCenterState.Control_center_present)
1483 color = BM_XRGB( 29, 0, 0 );
1484 break;
1485 case segment_special::robotmaker:
1486 color = BM_XRGB( 29, 0, 31 );
1487 break;
1488 }
1489
1490 const auto wall_num = seg->shared_segment::sides[sn].wall_num;
1491 if (wall_num != wall_none)
1492 {
1493 auto &w = *vcwallptr(wall_num);
1494 #if defined(DXX_BUILD_DESCENT_II)
1495 auto trigger_num = w.trigger;
1496 auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
1497 auto &vmtrgptr = Triggers.vmptr;
1498 if (trigger_num != trigger_none && vmtrgptr(trigger_num)->type == trigger_action::secret_exit)
1499 {
1500 color = BM_XRGB( 29, 0, 31 );
1501 no_fade = EF_NO_FADE;
1502 goto Here;
1503 }
1504 #endif
1505
1506 switch(w.type)
1507 {
1508 case WALL_DOOR:
1509 if ((w.keys == wall_key::blue && (color = am.wall_door_blue, true)) ||
1510 (w.keys == wall_key::gold && (color = am.wall_door_gold, true)) ||
1511 (w.keys == wall_key::red && (color = am.wall_door_red, true)))
1512 {
1513 no_fade = EF_NO_FADE;
1514 } else if (!(WallAnims[w.clip_num].flags & WCF_HIDDEN)) {
1515 const auto connected_seg = seg->shared_segment::children[sn];
1516 if (connected_seg != segment_none) {
1517 const shared_segment &vcseg = *vcsegptr(connected_seg);
1518 const auto &connected_side = find_connect_side(seg, vcseg);
1519 auto &wall = *vcwallptr(vcseg.sides[connected_side].wall_num);
1520 switch (wall.keys)
1521 {
1522 case wall_key::blue:
1523 color = am.wall_door_blue;
1524 no_fade = EF_NO_FADE;
1525 break;
1526 case wall_key::gold:
1527 color = am.wall_door_gold;
1528 no_fade = EF_NO_FADE;
1529 break;
1530 case wall_key::red:
1531 color = am.wall_door_red;
1532 no_fade = EF_NO_FADE;
1533 break;
1534 default:
1535 color = am.wall_door_color;
1536 break;
1537 }
1538 }
1539 } else {
1540 color = am.wall_normal_color;
1541 hidden_flag = EF_SECRET;
1542 }
1543 break;
1544 case WALL_CLOSED:
1545 // Make grates draw properly
1546 // NOTE: In original D1, is_grate is 1, hidden_flag not used so grates never fade. I (zico) like this so I leave this alone for now.
1547 if (!(is_grate = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sn) & WALL_IS_DOORWAY_FLAG::rendpast))
1548 hidden_flag = EF_SECRET;
1549 color = am.wall_normal_color;
1550 break;
1551 case WALL_BLASTABLE:
1552 // Hostage doors
1553 color = am.wall_door_color;
1554 break;
1555 }
1556 }
1557
1558 if (seg==Player_init[Player_num].segnum)
1559 color = BM_XRGB(31,0,31);
1560
1561 if ( color != 255 ) {
1562 #if defined(DXX_BUILD_DESCENT_II)
1563 // If they have a map powerup, draw unvisited areas in dark blue.
1564 // NOTE: D1 originally had this part of code but w/o cheat-check. It's only supposed to draw blue with powerup that does not exist in D1. So make this D2-only
1565 #ifndef NDEBUG
1566 if (!Automap_debug_show_all_segments)
1567 #endif
1568 {
1569 auto &player_info = get_local_plrobj().ctype.player_info;
1570 if ((cheats.fullautomap || player_info.powerup_flags & PLAYER_FLAGS_MAP_ALL) && !LevelUniqueAutomapState.Automap_visited[seg])
1571 color = am.wall_revealed_color;
1572 }
1573 Here:
1574 #endif
1575 const auto vertex_list = get_side_verts(seg,sn);
1576 const uint8_t flags = hidden_flag | no_fade;
1577 add_one_edge(am, vertex_list[0], vertex_list[1], color, sn, seg, flags);
1578 add_one_edge(am, vertex_list[1], vertex_list[2], color, sn, seg, flags);
1579 add_one_edge(am, vertex_list[2], vertex_list[3], color, sn, seg, flags);
1580 add_one_edge(am, vertex_list[3], vertex_list[0], color, sn, seg, flags);
1581
1582 if ( is_grate ) {
1583 const uint8_t grate_flags = flags | EF_GRATE;
1584 add_one_edge(am, vertex_list[0], vertex_list[2], color, sn, seg, grate_flags);
1585 add_one_edge(am, vertex_list[1], vertex_list[3], color, sn, seg, grate_flags);
1586 }
1587 }
1588 }
1589 }
1590
1591
1592 // Adds all the edges from a segment we haven't visited yet.
1593
add_unknown_segment_edges(automap & am,const shared_segment & seg)1594 static void add_unknown_segment_edges(automap &am, const shared_segment &seg)
1595 {
1596 for (const auto sn : xrange(MAX_SIDES_PER_SEGMENT))
1597 {
1598 // Only add edges that have no children
1599 if (seg.children[sn] == segment_none) {
1600 const auto vertex_list = get_side_verts(seg, sn);
1601
1602 add_one_unknown_edge( am, vertex_list[0], vertex_list[1] );
1603 add_one_unknown_edge( am, vertex_list[1], vertex_list[2] );
1604 add_one_unknown_edge( am, vertex_list[2], vertex_list[3] );
1605 add_one_unknown_edge( am, vertex_list[3], vertex_list[0] );
1606 }
1607 }
1608 }
1609
automap_build_edge_list(automap & am,int add_all_edges)1610 void automap_build_edge_list(automap &am, int add_all_edges)
1611 {
1612 // clear edge list
1613 for (auto &i : unchecked_partial_range(am.edges.get(), am.max_edges))
1614 {
1615 i.num_faces = 0;
1616 i.flags = 0;
1617 }
1618 am.num_edges = 0;
1619 am.end_valid_edges = 0;
1620
1621 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1622 auto &vcwallptr = Walls.vcptr;
1623 if (add_all_edges) {
1624 // Cheating, add all edges as visited
1625 range_for (const auto &&segp, vcsegptridx)
1626 {
1627 #if DXX_USE_EDITOR
1628 if (segp->shared_segment::segnum != segment_none)
1629 #endif
1630 {
1631 add_segment_edges(vcsegptr, vcwallptr, am, segp);
1632 }
1633 }
1634 } else {
1635 // Not cheating, add visited edges, and then unvisited edges
1636 range_for (const auto &&segp, vcsegptridx)
1637 {
1638 #if DXX_USE_EDITOR
1639 if (segp->shared_segment::segnum != segment_none)
1640 #endif
1641 if (LevelUniqueAutomapState.Automap_visited[segp])
1642 {
1643 add_segment_edges(vcsegptr, vcwallptr, am, segp);
1644 }
1645 }
1646 range_for (const auto &&segp, vcsegptridx)
1647 {
1648 #if DXX_USE_EDITOR
1649 if (segp->shared_segment::segnum != segment_none)
1650 #endif
1651 if (!LevelUniqueAutomapState.Automap_visited[segp])
1652 {
1653 add_unknown_segment_edges(am, segp);
1654 }
1655 }
1656 }
1657
1658 // Find unnecessary lines (These are lines that don't have to be drawn because they have small curvature)
1659 for (auto &i : unchecked_partial_range(am.edges.get(), am.end_valid_edges))
1660 {
1661 const auto e = &i;
1662 if (!(e->flags&EF_USED)) continue;
1663
1664 const auto num_faces = e->num_faces;
1665 if (num_faces < 2)
1666 continue;
1667 for (unsigned e1 = 0; e1 < num_faces; ++e1)
1668 {
1669 const auto e1segnum = e->segnum[e1];
1670 const auto &e1siden0 = vcsegptr(e1segnum)->shared_segment::sides[e->sides[e1]].normals[0];
1671 for (unsigned e2 = 1; e2 < num_faces; ++e2)
1672 {
1673 if (e1 == e2)
1674 continue;
1675 const auto e2segnum = e->segnum[e2];
1676 if (e1segnum == e2segnum)
1677 continue;
1678 if (vm_vec_dot(e1siden0, vcsegptr(e2segnum)->shared_segment::sides[e->sides[e2]].normals[0]) > (F1_0 - (F1_0 / 10)))
1679 {
1680 e->flags &= (~EF_DEFINING);
1681 break;
1682 }
1683 }
1684 if (!(e->flags & EF_DEFINING))
1685 break;
1686 }
1687 }
1688 }
1689
1690 }
1691
1692 #if defined(DXX_BUILD_DESCENT_II)
1693 static unsigned Marker_index;
1694
InitMarkerInput()1695 void InitMarkerInput ()
1696 {
1697 //find free marker slot
1698 const auto game_mode = Game_mode;
1699 const auto max_numplayers = Netgame.max_numplayers;
1700 const auto maxdrop = MarkerState.get_markers_per_player(game_mode, max_numplayers);
1701 const auto &&game_marker_range = get_game_marker_range(game_mode, max_numplayers, Player_num, maxdrop);
1702 const auto &&player_marker_range = get_player_marker_range(maxdrop);
1703 const auto &&zipped_marker_range = zip(player_marker_range, unchecked_partial_range(&MarkerState.imobjidx[*game_marker_range.begin()], maxdrop));
1704 const auto &&mb = zipped_marker_range.begin();
1705 const auto &&me = zipped_marker_range.end();
1706 auto iter = mb;
1707 for (;;)
1708 {
1709 auto &&[pmi, objidx] = *iter;
1710 if (objidx == object_none) //found free slot!
1711 {
1712 MarkerState.MarkerBeingDefined = pmi;
1713 break;
1714 }
1715 if (++ iter == me) //no free slot
1716 {
1717 if (game_mode & GM_MULTI)
1718 {
1719 //in multi, replace oldest
1720 MarkerState.MarkerBeingDefined = static_cast<player_marker_index>((static_cast<unsigned>(MarkerState.LastMarkerDropped) + 1) & (maxdrop - 1));
1721 break;
1722 }
1723 else
1724 {
1725 HUD_init_message_literal(HM_DEFAULT, "No free marker slots");
1726 return;
1727 }
1728 }
1729 }
1730
1731 //got a free slot. start inputting marker message
1732
1733 Marker_input[0]=0;
1734 Marker_index=0;
1735 key_toggle_repeat(1);
1736 }
1737
MarkerInputMessage(int key,control_info & Controls)1738 window_event_result MarkerInputMessage(int key, control_info &Controls)
1739 {
1740 auto &Objects = LevelUniqueObjectState.Objects;
1741 auto &vmobjptr = Objects.vmptr;
1742 auto &vmobjptridx = Objects.vmptridx;
1743 switch( key )
1744 {
1745 case KEY_LEFT:
1746 case KEY_BACKSP:
1747 case KEY_PAD4:
1748 if (Marker_index > 0)
1749 Marker_index--;
1750 Marker_input[Marker_index] = 0;
1751 break;
1752 case KEY_ENTER:
1753 {
1754 const auto player_marker_num = MarkerState.MarkerBeingDefined;
1755 MarkerState.LastMarkerDropped = player_marker_num;
1756 const auto game_marker_num = convert_player_marker_index_to_game_marker_index(Game_mode, Netgame.max_numplayers, Player_num, player_marker_num);
1757 MarkerState.message[game_marker_num] = Marker_input;
1758 DropMarker(vmobjptridx, vmsegptridx, get_local_plrobj(), game_marker_num, player_marker_num);
1759 }
1760 DXX_BOOST_FALLTHROUGH;
1761 case KEY_F8:
1762 case KEY_ESC:
1763 MarkerState.MarkerBeingDefined = player_marker_index::None;
1764 key_toggle_repeat(0);
1765 game_flush_inputs(Controls);
1766 break;
1767 default:
1768 {
1769 int ascii = key_ascii();
1770 if ((ascii < 255 ))
1771 if (Marker_index < Marker_input.size() - 1)
1772 {
1773 Marker_input[Marker_index++] = ascii;
1774 Marker_input[Marker_index] = 0;
1775 }
1776 return window_event_result::ignored;
1777 }
1778 }
1779 return window_event_result::handled;
1780 }
1781 #endif
1782
1783 }
1784