Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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