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