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
 * AI path forming stuff.
23
 *
24
 */
25
 
26
#include <stdio.h>              //      for printf()
27
#include <stdlib.h>             // for d_rand() and qsort()
28
#include <string.h>             // for memset()
29
 
30
#include "inferno.h"
31
#include "console.h"
32
#include "3d.h"
33
 
34
#include "object.h"
35
#include "dxxerror.h"
36
#include "ai.h"
37
#include "robot.h"
38
#include "fvi.h"
39
#include "gameseg.h"
40
#include "physics.h"
41
#include "wall.h"
42
#if DXX_USE_EDITOR
43
#include "editor/editor.h"
44
#include "editor/esegment.h"
45
#endif
46
#include "player.h"
47
#include "fireball.h"
48
#include "game.h"
49
 
50
#include "compiler-range_for.h"
51
#include "d_enumerate.h"
52
 
53
//      Length in segments of avoidance path
54
#define AVOID_SEG_LENGTH        7
55
 
56
#ifdef NDEBUG
57
#define PATH_VALIDATION 0
58
#else
59
#define PATH_VALIDATION 1
60
#endif
61
 
62
namespace dsx {
63
static void ai_path_set_orient_and_vel(object &objp, const vms_vector &goal_point
64
#if defined(DXX_BUILD_DESCENT_II)
65
                                                                , player_visibility_state player_visibility, const vms_vector *vec_to_player
66
#endif
67
                                                                );
68
static void maybe_ai_path_garbage_collect(void);
69
static void ai_path_garbage_collect(void);
70
#if PATH_VALIDATION
71
static void validate_all_paths();
72
static int validate_path(int, point_seg* psegs, uint_fast32_t num_points);
73
#endif
74
}
75
 
76
//      ------------------------------------------------------------------------
77
static void create_random_xlate(std::array<uint8_t, MAX_SIDES_PER_SEGMENT> &xt)
78
{
79
        for (int i = 0; i<MAX_SIDES_PER_SEGMENT; i++)
80
                xt[i] = i;
81
 
82
        range_for (auto &i, xt)
83
        {
84
                uint_fast32_t j = (d_rand()*MAX_SIDES_PER_SEGMENT)/(D_RAND_MAX+1);
85
                Assert(j < xt.size());
86
                using std::swap;
87
                swap(i, xt[j]);
88
        }
89
}
90
 
91
namespace dsx {
92
 
93
//      -----------------------------------------------------------------------------------------------------------
94
//      Insert the point at the center of the side connecting two segments between the two points.
95
// This is messy because we must insert into the list.  The simplest (and not too slow) way to do this is to start
96
// at the end of the list and go backwards.
97
static uint_fast32_t insert_center_points(segment_array &segments, point_seg *psegs, uint_fast32_t count)
98
{
99
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
100
        auto &Vertices = LevelSharedVertexState.get_vertices();
101
        auto &vcsegptr = segments.vcptr;
102
        auto &vcsegptridx = segments.vcptridx;
103
        if (count < 2)
104
                return count;
105
        uint_fast32_t last_point = count - 1;
106
        for (uint_fast32_t i = last_point; i; --i)
107
        {
108
                psegs[2*i] = psegs[i];
109
                const auto &&seg1 = vcsegptr(psegs[i-1].segnum);
110
                auto connect_side = find_connect_side(vcsegptridx(psegs[i].segnum), seg1);
111
                Assert(connect_side != side_none);      //      Impossible!  These two segments must be connected, they were created by create_path_points (which was created by mk!)
112
                if (connect_side == side_none)                  //      Try to blow past the assert, this should at least prevent a hang.
113
                        connect_side = 0;
114
                auto &vcvertptr = Vertices.vcptr;
115
                const auto &&center_point = compute_center_point_on_side(vcvertptr, seg1, connect_side);
116
                auto new_point = vm_vec_sub(psegs[i-1].point, center_point);
117
                new_point.x /= 16;
118
                new_point.y /= 16;
119
                new_point.z /= 16;
120
                vm_vec_sub(psegs[2*i-1].point, center_point, new_point);
121
#if defined(DXX_BUILD_DESCENT_II)
122
                const auto &&segp = segments.imptridx(psegs[2*i].segnum);
123
                const auto &&temp_segnum = find_point_seg(LevelSharedSegmentState, psegs[2*i-1].point, segp);
124
                if (temp_segnum == segment_none) {
125
                        psegs[2*i-1].point = center_point;
126
                        find_point_seg(LevelSharedSegmentState, psegs[2*i-1].point, segp);
127
                }
128
#endif
129
 
130
                psegs[2*i-1].segnum = psegs[2*i].segnum;
131
                count++;
132
        }
133
 
134
#if defined(DXX_BUILD_DESCENT_II)
135
        //      Now, remove unnecessary center points.
136
        //      A center point is unnecessary if it is close to the line between the two adjacent points.
137
        //      MK, OPTIMIZE!  Can get away with about half the math since every vector gets computed twice.
138
        for (uint_fast32_t i = 1; i < count - 1; i += 2)
139
        {
140
                vms_vector      temp1, temp2;
141
                fix                     dot;
142
 
143
                dot = vm_vec_dot(vm_vec_sub(temp1, psegs[i].point, psegs[i-1].point), vm_vec_sub(temp2, psegs[i+1].point, psegs[i].point));
144
 
145
                if (dot * 9/8 > fixmul(vm_vec_mag(temp1), vm_vec_mag(temp2)))
146
                        psegs[i].segnum = segment_none;
147
 
148
        }
149
 
150
        //      Now, scan for points with segnum == -1
151
        auto predicate = [](const point_seg &p) { return p.segnum == segment_none; };
152
        count = std::distance(psegs, std::remove_if(psegs, psegs + count, predicate));
153
#endif
154
        return count;
155
}
156
 
157
#if defined(DXX_BUILD_DESCENT_II)
158
//      -----------------------------------------------------------------------------------------------------------
159
//      Move points halfway to outside of segment.
160
static void move_towards_outside(const d_level_shared_segment_state &LevelSharedSegmentState, point_seg *const psegs, const unsigned num_points, const vmobjptridx_t objp, const create_path_random_flag rand_flag)
161
{
162
        int     i;
163
        std::array<point_seg, 200> new_psegs;
164
        assert(num_points < new_psegs.size());
165
 
166
        auto &Segments = LevelSharedSegmentState.get_segments();
167
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
168
        auto &Vertices = LevelSharedVertexState.get_vertices();
169
        auto &vcvertptr = Vertices.vcptr;
170
        for (i = 1; i < num_points - 1; ++i)
171
        {
172
                fix                     segment_size;
173
                segnum_t                        segnum;
174
                vms_vector      e;
175
                int                     count;
176
                const auto &&temp_segnum = find_point_seg(LevelSharedSegmentState, psegs[i].point, Segments.vcptridx(psegs[i].segnum));
177
                Assert(temp_segnum != segment_none);
178
                psegs[i].segnum = temp_segnum;
179
                segnum = psegs[i].segnum;
180
 
181
                //      I don't think we can use quick version here and this is _very_ rarely called. --MK, 07/03/95
182
                const auto a = vm_vec_normalized_quick(vm_vec_sub(psegs[i].point, psegs[i-1].point));
183
                const auto b = vm_vec_normalized_quick(vm_vec_sub(psegs[i+1].point, psegs[i].point));
184
                const auto c = vm_vec_sub(psegs[i+1].point, psegs[i-1].point);
185
                if (abs(vm_vec_dot(a, b)) > 3*F1_0/4 ) {
186
                        if (abs(a.z) < F1_0/2) {
187
                                if (rand_flag != create_path_random_flag::nonrandom)
188
                                {
189
                                        e.x = (d_rand()-16384)/2;
190
                                        e.y = (d_rand()-16384)/2;
191
                                        e.z = abs(e.x) + abs(e.y) + 1;
192
                                        vm_vec_normalize_quick(e);
193
                                } else {
194
                                        e.x = 0;
195
                                        e.y = 0;
196
                                        e.z = F1_0;
197
                                }
198
                        } else {
199
                                if (rand_flag != create_path_random_flag::nonrandom)
200
                                {
201
                                        e.y = (d_rand()-16384)/2;
202
                                        e.z = (d_rand()-16384)/2;
203
                                        e.x = abs(e.y) + abs(e.z) + 1;
204
                                        vm_vec_normalize_quick(e);
205
                                } else {
206
                                        e.x = F1_0;
207
                                        e.y = 0;
208
                                        e.z = 0;
209
                                }
210
                        }
211
                } else {
212
                        const auto d = vm_vec_cross(a, b);
213
                        vm_vec_cross(e, c, d);
214
                        vm_vec_normalize_quick(e);
215
                }
216
 
217
                if (vm_vec_mag_quick(e) < F1_0/2)
218
        Int3();
219
 
220
                const shared_segment &segp = *vcsegptr(segnum);
221
                segment_size = vm_vec_dist_quick(vcvertptr(segp.verts[0]), vcvertptr(segp.verts[6]));
222
                if (segment_size > F1_0*40)
223
                        segment_size = F1_0*40;
224
 
225
                auto goal_pos = vm_vec_scale_add(psegs[i].point, e, segment_size/4);
226
 
227
                count = 3;
228
                while (count) {
229
                        fvi_query       fq;
230
                        fvi_info                hit_data;
231
                        int                     hit_type;
232
 
233
                        fq.p0                                           = &psegs[i].point;
234
                        fq.startseg                             = psegs[i].segnum;
235
                        fq.p1                                           = &goal_pos;
236
                        fq.rad                                  = objp->size;
237
                        fq.thisobjnum                   = objp;
238
                        fq.ignore_obj_list.first = nullptr;
239
                        fq.flags                                        = 0;
240
 
241
                        hit_type = find_vector_intersection(fq, hit_data);
242
 
243
                        if (hit_type == HIT_NONE)
244
                                count = 0;
245
                        else {
246
                                if ((count == 3) && (hit_type == HIT_BAD_P0))
247
                                        Int3();
248
                                goal_pos.x = (fq.p0->x + hit_data.hit_pnt.x)/2;
249
                                goal_pos.y = (fq.p0->y + hit_data.hit_pnt.y)/2;
250
                                goal_pos.z = (fq.p0->z + hit_data.hit_pnt.z)/2;
251
                                count--;
252
                                if (count == 0) {       //      Couldn't move towards outside, that's ok, sometimes things can't be moved.
253
                                        goal_pos = psegs[i].point;
254
                                }
255
                        }
256
                }
257
 
258
                //      Only move towards outside if remained inside segment.
259
                const auto &&new_segnum = find_point_seg(LevelSharedSegmentState, goal_pos, Segments.vcptridx(psegs[i].segnum));
260
                if (new_segnum == psegs[i].segnum) {
261
                        new_psegs[i].point = goal_pos;
262
                        new_psegs[i].segnum = new_segnum;
263
                } else {
264
                        new_psegs[i].point = psegs[i].point;
265
                        new_psegs[i].segnum = psegs[i].segnum;
266
                }
267
 
268
        }
269
 
270
        for (i = 1; i < num_points - 1; ++i)
271
                psegs[i] = new_psegs[i];
272
}
273
#endif
274
 
275
 
276
//      -----------------------------------------------------------------------------------------------------------
277
//      Create a path from objp->pos to the center of end_seg.
278
//      Return a list of (segment_num, point_locations) at psegs
279
//      Return number of points in *num_points.
280
//      if max_depth == -1, then there is no maximum depth.
281
//      If unable to create path, return -1, else return 0.
282
//      If random_flag !0, then introduce randomness into path by looking at sides in random order.  This means
283
//      that a path between two segments won't always be the same, unless it is unique.
284
//      If safety_flag is set, then additional points are added to "make sure" that points are reachable.  I would
285
//      like to say that it ensures that the object can move between the points, but that would require knowing what
286
//      the object is (which isn't passed, right?) and making fvi calls (slow, right?).  So, consider it the more_or_less_safe_flag.
287
//      If end_seg == -2, then end seg will never be found and this routine will drop out due to depth (probably called by create_n_segment_path).
288
std::pair<create_path_result, unsigned> create_path_points(const vmobjptridx_t objp, const vcsegidx_t start_seg, icsegidx_t end_seg, point_seg_array_t::iterator psegs, const unsigned max_depth, create_path_random_flag random_flag, const create_path_safety_flag safety_flag, icsegidx_t avoid_seg)
289
{
290
#if defined(DXX_BUILD_DESCENT_II)
291
        auto &Objects = LevelUniqueObjectState.Objects;
292
        auto &vmobjptr = Objects.vmptr;
293
#endif
294
        segnum_t                cur_seg;
295
        int             qtail = 0, qhead = 0;
296
        int             i;
297
        std::array<seg_seg, MAX_SEGMENTS> seg_queue;
298
        int             cur_depth;
299
        std::array<uint8_t, MAX_SIDES_PER_SEGMENT> random_xlate;
300
        DXX_POISON_VAR(random_xlate, 0xcc);
301
        point_seg_array_t::iterator     original_psegs = psegs;
302
        unsigned l_num_points = 0;
303
 
304
#if PATH_VALIDATION
305
        validate_all_paths();
306
#endif
307
 
308
if ((objp->type == OBJ_ROBOT) && (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)) {
309
        random_flag = create_path_random_flag::random;
310
        avoid_seg = ConsoleObject->segnum;
311
        // Int3();
312
}
313
 
314
        visited_segment_bitarray_t visited;
315
        std::array<uint16_t, MAX_SEGMENTS> depth{};
316
 
317
        //      If there is a segment we're not allowed to visit, mark it.
318
        if (avoid_seg != segment_none) {
319
                Assert(avoid_seg <= Highest_segment_index);
320
                if ((start_seg != avoid_seg) && (end_seg != avoid_seg)) {
321
                        visited[avoid_seg] = true;
322
                }
323
        }
324
 
325
        if (random_flag != create_path_random_flag::nonrandom)
326
                create_random_xlate(random_xlate);
327
 
328
        cur_seg = start_seg;
329
        visited[cur_seg] = true;
330
        cur_depth = 0;
331
 
332
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
333
        auto &Vertices = LevelSharedVertexState.get_vertices();
334
        auto &vcvertptr = Vertices.vcptr;
335
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
336
        auto &vcwallptr = Walls.vcptr;
337
        while (cur_seg != end_seg) {
338
                const auto &&segp = vcsegptr(cur_seg);
339
#if defined(DXX_BUILD_DESCENT_II)
340
                if (random_flag != create_path_random_flag::nonrandom)
341
                        if (d_rand() < 8192)
342
                                create_random_xlate(random_xlate);
343
#endif
344
 
345
                range_for (const auto &&es, enumerate(random_xlate))
346
                {
347
                        const unsigned snum = (random_flag != create_path_random_flag::nonrandom) ? es.value : es.idx;
348
 
349
                        if (!IS_CHILD(segp->children[snum]))
350
                                continue;
351
#if defined(DXX_BUILD_DESCENT_I)
352
#define AI_DOOR_OPENABLE_PLAYER_FLAGS
353
#elif defined(DXX_BUILD_DESCENT_II)
354
#define AI_DOOR_OPENABLE_PLAYER_FLAGS   player_info.powerup_flags,
355
                        auto &player_info = get_local_plrobj().ctype.player_info;
356
#endif
357
                        if ((WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, snum) & WID_FLY_FLAG) || ai_door_is_openable(objp, AI_DOOR_OPENABLE_PLAYER_FLAGS segp, snum))
358
#undef AI_DOOR_OPENABLE_PLAYER_FLAGS
359
                        {
360
                                const auto this_seg = segp->children[snum];
361
#if defined(DXX_BUILD_DESCENT_II)
362
                                Assert(this_seg != segment_none);
363
                                if (((cur_seg == avoid_seg) || (this_seg == avoid_seg)) && (ConsoleObject->segnum == avoid_seg)) {
364
                                        fvi_query       fq;
365
                                        fvi_info                hit_data;
366
                                        int                     hit_type;
367
 
368
                                        const auto &&center_point = compute_center_point_on_side(vcvertptr, segp, snum);
369
 
370
                                        fq.p0                                           = &objp->pos;
371
                                        fq.startseg                             = objp->segnum;
372
                                        fq.p1                                           = &center_point;
373
                                        fq.rad                                  = objp->size;
374
                                        fq.thisobjnum                   = objp;
375
                                        fq.ignore_obj_list.first = nullptr;
376
                                        fq.flags                                        = 0;
377
 
378
                                        hit_type = find_vector_intersection(fq, hit_data);
379
                                        if (hit_type != HIT_NONE) {
380
                                                goto dont_add;
381
                                        }
382
                                }
383
#endif
384
 
385
                                if (!visited[this_seg]) {
386
                                        seg_queue[qtail].start = cur_seg;
387
                                        seg_queue[qtail].end = this_seg;
388
                                        visited[this_seg] = true;
389
                                        depth[qtail++] = cur_depth+1;
390
                                        if (depth[qtail-1] == max_depth) {
391
                                                end_seg = seg_queue[qtail-1].end;
392
                                                goto cpp_done1;
393
                                        }       // end if (depth[...
394
                                }       // end if (!visited...
395
                        }       // if (WALL_IS_DOORWAY(...
396
#if defined(DXX_BUILD_DESCENT_II)
397
dont_add: ;
398
#endif
399
                }
400
 
401
                if (qtail <= 0)
402
                        break;
403
 
404
                if (qhead >= qtail) {
405
                        //      Couldn't get to goal, return a path as far as we got, which probably acceptable to the unparticular caller.
406
                        end_seg = seg_queue[qtail-1].end;
407
                        break;
408
                }
409
 
410
                cur_seg = seg_queue[qhead].end;
411
                cur_depth = depth[qhead];
412
                qhead++;
413
 
414
cpp_done1: ;
415
        }       //      while (cur_seg ...
416
 
417
        if (qtail > 0)
418
        {
419
                //      Set qtail to the segment which ends at the goal.
420
                while (seg_queue[--qtail].end != end_seg)
421
                        if (qtail < 0) {
422
                                return std::make_pair(create_path_result::early, l_num_points);
423
                        }
424
        }
425
        else
426
                qtail = -1;
427
 
428
#if defined(DXX_BUILD_DESCENT_I)
429
#if DXX_USE_EDITOR
430
        Selected_segs.clear();
431
        #endif
432
#endif
433
 
434
        while (qtail >= 0) {
435
                segnum_t        parent_seg, this_seg;
436
 
437
                this_seg = seg_queue[qtail].end;
438
                parent_seg = seg_queue[qtail].start;
439
                psegs->segnum = this_seg;
440
                compute_segment_center(vcvertptr, psegs->point, vcsegptr(this_seg));
441
                psegs++;
442
                l_num_points++;
443
#if defined(DXX_BUILD_DESCENT_I)
444
#if DXX_USE_EDITOR
445
                Selected_segs.emplace_back(this_seg);
446
                #endif
447
#endif
448
 
449
                if (parent_seg == start_seg)
450
                        break;
451
 
452
                while (seg_queue[--qtail].end != parent_seg)
453
                        Assert(qtail >= 0);
454
        }
455
 
456
        psegs->segnum = start_seg;
457
        compute_segment_center(vcvertptr, psegs->point, vcsegptr(start_seg));
458
        psegs++;
459
        l_num_points++;
460
 
461
#if PATH_VALIDATION
462
        validate_path(1, original_psegs, l_num_points);
463
#endif
464
 
465
        //      Now, reverse point_segs in place.
466
        for (i=0; i< l_num_points/2; i++) {
467
                point_seg               temp_point_seg = *(original_psegs + i);
468
                *(original_psegs + i) = *(original_psegs + l_num_points - i - 1);
469
                *(original_psegs + l_num_points - i - 1) = temp_point_seg;
470
        }
471
#if PATH_VALIDATION
472
        validate_path(2, original_psegs, l_num_points);
473
#endif
474
 
475
        //      Now, if safety_flag set, then insert the point at the center of the side connecting two segments
476
        //      between the two points.  This is messy because we must insert into the list.  The simplest (and not too slow)
477
        //      way to do this is to start at the end of the list and go backwards.
478
        if (safety_flag != create_path_safety_flag::unsafe) {
479
                if (psegs - Point_segs + l_num_points + 2 > MAX_POINT_SEGS) {
480
                        //      Ouch!  Cannot insert center points in path.  So return unsafe path.
481
                        ai_reset_all_paths();
482
                        return std::make_pair(create_path_result::early, l_num_points);
483
                } else {
484
                        l_num_points = insert_center_points(Segments, original_psegs, l_num_points);
485
                }
486
        }
487
 
488
#if PATH_VALIDATION
489
        validate_path(3, original_psegs, l_num_points);
490
#endif
491
 
492
#if defined(DXX_BUILD_DESCENT_II)
493
// -- MK, 10/30/95 -- This code causes apparent discontinuities in the path, moving a point
494
//      into a new segment.  It is not necessarily bad, but it makes it hard to track down actual
495
//      discontinuity problems.
496
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
497
        if (objp->type == OBJ_ROBOT)
498
                if (Robot_info[get_robot_id(objp)].companion)
499
                        move_towards_outside(LevelSharedSegmentState, original_psegs, l_num_points, objp, create_path_random_flag::nonrandom);
500
#endif
501
 
502
#if PATH_VALIDATION
503
        validate_path(4, original_psegs, l_num_points);
504
#endif
505
        return std::make_pair(create_path_result::finished, l_num_points);
506
}
507
 
508
#if defined(DXX_BUILD_DESCENT_II)
509
//      -------------------------------------------------------------------------------------------------------
510
//      polish_path
511
//      Takes an existing path and makes it nicer.
512
//      Drops as many leading points as possible still maintaining direct accessibility
513
//      from current position to first point.
514
//      Will not shorten path to fewer than 3 points.
515
//      Returns number of points.
516
//      Starting position in psegs doesn't change.
517
//      Changed, MK, 10/18/95.  I think this was causing robots to get hung up on walls.
518
//                              Only drop up to the first three points.
519
int polish_path(const vmobjptridx_t objp, point_seg *psegs, int num_points)
520
{
521
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
522
        int     i, first_point=0;
523
 
524
        if (num_points <= 4)
525
                return num_points;
526
 
527
        //      Prevent the buddy from polishing his path twice in one tick, which can cause him to get hung up.  Pretty ugly, huh?
528
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
529
        if (Robot_info[get_robot_id(objp)].companion)
530
        {
531
                if (d_tick_count == BuddyState.Last_buddy_polish_path_tick)
532
                        return num_points;
533
                else
534
                        BuddyState.Last_buddy_polish_path_tick = d_tick_count;
535
        }
536
 
537
        // -- MK: 10/18/95: for (i=0; i<num_points-3; i++)
538
        for (i=0; i<2; i++) {
539
                fvi_query       fq;
540
                fvi_info                hit_data;
541
                int                     hit_type;
542
 
543
                fq.p0                                           = &objp->pos;
544
                fq.startseg                             = objp->segnum;
545
                fq.p1                                           = &psegs[i].point;
546
                fq.rad                                  = objp->size;
547
                fq.thisobjnum                   = objp;
548
                fq.ignore_obj_list.first = nullptr;
549
                fq.flags                                        = 0;
550
 
551
                hit_type = find_vector_intersection(fq, hit_data);
552
 
553
                if (hit_type == HIT_NONE)
554
                        first_point = i+1;
555
                else
556
                        break;         
557
        }
558
 
559
        if (first_point) {
560
                //      Scrunch down all the psegs.
561
                for (i=first_point; i<num_points; i++)
562
                        psegs[i-first_point] = psegs[i];
563
        }
564
 
565
        return num_points - first_point;
566
}
567
#endif
568
 
569
#ifndef NDEBUG
570
//      -------------------------------------------------------------------------------------------------------
571
//      Make sure that there are connections between all segments on path.
572
//      Note that if path has been optimized, connections may not be direct, so this function is useless, or worse.
573
//      Return true if valid, else return false.
574
int validate_path(int, point_seg *psegs, uint_fast32_t num_points)
575
{
576
#if PATH_VALIDATION
577
        auto curseg = psegs->segnum;
578
        if (curseg > Highest_segment_index)
579
        {
580
                //Int3();               //      Contact Mike: Debug trap for elusive, nasty bug.
581
                return 0;
582
        }
583
 
584
        range_for (const auto &ps, unchecked_partial_range(psegs, 1u, num_points))
585
        {
586
                auto nextseg = ps.segnum;
587
                if (curseg != nextseg) {
588
                        const auto &&csegp = vcsegptr(curseg);
589
                        const auto &children = csegp->children;
590
                        if (std::find(children.begin(), children.end(), nextseg) == children.end())
591
                        {
592
                        // Assert(sidenum != MAX_SIDES_PER_SEGMENT);    //      Hey, created path is not contiguous, why!?
593
                                //Int3();
594
                                return 0;
595
                        }
596
                        curseg = nextseg;
597
                }
598
        }
599
#endif
600
        return 1;
601
 
602
}
603
 
604
//      -----------------------------------------------------------------------------------------------------------
605
void validate_all_paths(void)
606
{
607
 
608
#if PATH_VALIDATION
609
        auto &Objects = LevelUniqueObjectState.Objects;
610
        auto &vmobjptr = Objects.vmptr;
611
        range_for (const auto &&objp, vmobjptr)
612
        {
613
                auto &obj = *objp;
614
                if (obj.type == OBJ_ROBOT) {
615
                        auto &aip = obj.ctype.ai_info;
616
                        if (obj.control_type == CT_AI) {
617
                                if ((aip.hide_index != -1) && (aip.path_length > 0))
618
                                        if (!validate_path(4, &Point_segs[aip.hide_index], aip.path_length))
619
                                        {
620
                                                //Int3();       //      This path is bogus!  Who corrupted it!  Danger! Danger!
621
                                                                        //      Contact Mike, he caused this mess.
622
                                                //force_dump_ai_objects_all("Error in validate_all_paths");
623
                                                aip.path_length=0;      //      This allows people to resume without harm...
624
                                        }
625
                        }
626
                }
627
        }
628
#endif
629
 
630
}
631
#endif
632
 
633
//      -------------------------------------------------------------------------------------------------------
634
//      Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to
635
//      hide in Ai_local_info[objnum].goal_segment.
636
//      Sets    objp->ctype.ai_info.hide_index,         a pointer into Point_segs, the first point_seg of the path.
637
//                      objp->ctype.ai_info.path_length,                length of path
638
//                      Point_segs_free_ptr                             global pointer into Point_segs array
639
void create_path_to_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag, const icsegidx_t goal_segment)
640
{
641
        ai_static       *aip = &objp->ctype.ai_info;
642
        ai_local                *ailp = &objp->ctype.ai_info.ail;
643
 
644
        ailp->time_player_seen = GameTime64;                    //      Prevent from resetting path quickly.
645
        ailp->goal_segment = goal_segment;
646
 
647
        segnum_t                        start_seg;
648
        start_seg = objp->segnum;
649
        const auto end_seg = goal_segment;
650
 
651
        if (end_seg == segment_none) {
652
                ;
653
        } else {
654
                aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, safety_flag, segment_none).second;
655
#if defined(DXX_BUILD_DESCENT_II)
656
                aip->path_length = polish_path(objp, Point_segs_free_ptr, aip->path_length);
657
#endif
658
                aip->hide_index = Point_segs_free_ptr - Point_segs;
659
                aip->cur_path_index = 0;
660
#if defined(DXX_BUILD_DESCENT_I)
661
#ifndef NDEBUG
662
                validate_path(6, Point_segs_free_ptr, aip->path_length);
663
#endif
664
#endif
665
                Point_segs_free_ptr += aip->path_length;
666
                if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
667
                        //Int3();       //      Contact Mike: This is stupid.  Should call maybe_ai_garbage_collect before the add.
668
                        //force_dump_ai_objects_all("Error in create_path_to_player");
669
                        ai_reset_all_paths();
670
                        return;
671
                }
672
//              Assert(Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 < MAX_POINT_SEGS);
673
                aip->PATH_DIR = 1;              //      Initialize to moving forward.
674
#if defined(DXX_BUILD_DESCENT_I)
675
                aip->SUBMODE = AISM_GOHIDE;             //      This forces immediate movement.
676
#endif
677
                ailp->mode = ai_mode::AIM_FOLLOW_PATH;
678
                ailp->player_awareness_type = player_awareness_type_t::PA_NONE;         //      If robot too aware of player, will set mode to chase
679
        }
680
        maybe_ai_path_garbage_collect();
681
}
682
 
683
//      Change, 10/07/95: Used to create path to ConsoleObject->pos.  Now creates path to Believed_player_pos.
684
void create_path_to_believed_player_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag)
685
{
686
#if defined(DXX_BUILD_DESCENT_I)
687
        const auto goal_segment = ConsoleObject->segnum;
688
#elif defined(DXX_BUILD_DESCENT_II)
689
        const auto goal_segment = Believed_player_seg;
690
#endif
691
        create_path_to_segment(objp, max_length, safety_flag, goal_segment);
692
}
693
 
694
#if defined(DXX_BUILD_DESCENT_II)
695
void create_path_to_guidebot_player_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag)
696
{
697
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
698
        auto &Objects = LevelUniqueObjectState.Objects;
699
        auto &plr = get_player_controlling_guidebot(BuddyState, Players);
700
        if (plr.objnum == object_none)
701
                return;
702
        auto &plrobj = *Objects.vcptr(plr.objnum);
703
        const auto goal_segment = plrobj.segnum;
704
        create_path_to_segment(objp, max_length, safety_flag, goal_segment);
705
}
706
//      -------------------------------------------------------------------------------------------------------
707
//      Creates a path from the object's current segment (objp->segnum) to segment goalseg.
708
void create_path_to_segment(const vmobjptridx_t objp, segnum_t goalseg, const unsigned max_length, const create_path_safety_flag safety_flag)
709
{
710
        ai_static       *aip = &objp->ctype.ai_info;
711
        ai_local                *ailp = &objp->ctype.ai_info.ail;
712
 
713
        ailp->time_player_seen = GameTime64;                    //      Prevent from resetting path quickly.
714
        ailp->goal_segment = goalseg;
715
 
716
        segnum_t                        start_seg, end_seg;
717
        start_seg = objp->segnum;
718
        end_seg = ailp->goal_segment;
719
 
720
        if (end_seg == segment_none) {
721
                ;
722
        } else {
723
                aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, safety_flag, segment_none).second;
724
                aip->hide_index = Point_segs_free_ptr - Point_segs;
725
                aip->cur_path_index = 0;
726
                Point_segs_free_ptr += aip->path_length;
727
                if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
728
                        ai_reset_all_paths();
729
                        return;
730
                }
731
 
732
                aip->PATH_DIR = 1;              //      Initialize to moving forward.
733
                // -- UNUSED! aip->SUBMODE = AISM_GOHIDE;               //      This forces immediate movement.
734
                ailp->player_awareness_type = player_awareness_type_t::PA_NONE;         //      If robot too aware of player, will set mode to chase
735
        }
736
 
737
        maybe_ai_path_garbage_collect();
738
 
739
}
740
#endif
741
 
742
//      -------------------------------------------------------------------------------------------------------
743
//      Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to
744
//      hide in Ai_local_info[objnum].goal_segment
745
//      Sets    objp->ctype.ai_info.hide_index,         a pointer into Point_segs, the first point_seg of the path.
746
//                      objp->ctype.ai_info.path_length,                length of path
747
//                      Point_segs_free_ptr                             global pointer into Point_segs array
748
void create_path_to_station(const vmobjptridx_t objp, int max_length)
749
{
750
        ai_static       *aip = &objp->ctype.ai_info;
751
        ai_local                *ailp = &objp->ctype.ai_info.ail;
752
 
753
        if (max_length == -1)
754
                max_length = MAX_DEPTH_TO_SEARCH_FOR_PLAYER;
755
 
756
        ailp->time_player_seen = GameTime64;                    //      Prevent from resetting path quickly.
757
 
758
        segnum_t                        start_seg, end_seg;
759
        start_seg = objp->segnum;
760
        end_seg = aip->hide_segment;
761
 
762
        if (end_seg == segment_none) {
763
                ;
764
        } else {
765
                aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, create_path_safety_flag::safe, segment_none).second;
766
#if defined(DXX_BUILD_DESCENT_II)
767
                aip->path_length = polish_path(objp, Point_segs_free_ptr, aip->path_length);
768
#endif
769
                aip->hide_index = Point_segs_free_ptr - Point_segs;
770
                aip->cur_path_index = 0;
771
#if defined(DXX_BUILD_DESCENT_I)
772
#ifndef NDEBUG
773
                validate_path(7, Point_segs_free_ptr, aip->path_length);
774
#endif
775
#endif
776
                Point_segs_free_ptr += aip->path_length;
777
                if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
778
                        //Int3();       //      Contact Mike: Stupid.
779
                        //force_dump_ai_objects_all("Error in create_path_to_station");
780
                        ai_reset_all_paths();
781
                        return;
782
                }
783
//              Assert(Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 < MAX_POINT_SEGS);
784
                aip->PATH_DIR = 1;              //      Initialize to moving forward.
785
                // aip->SUBMODE = AISM_GOHIDE;          //      This forces immediate movement.
786
                ailp->mode = ai_mode::AIM_FOLLOW_PATH;
787
                ailp->player_awareness_type = player_awareness_type_t::PA_NONE;
788
        }
789
 
790
 
791
        maybe_ai_path_garbage_collect();
792
 
793
}
794
 
795
 
796
//      -------------------------------------------------------------------------------------------------------
797
//      Create a path of length path_length for an object, stuffing info in ai_info field.
798
void create_n_segment_path(const vmobjptridx_t objp, unsigned path_length, const imsegidx_t avoid_seg)
799
{
800
        ai_static       *aip=&objp->ctype.ai_info;
801
        ai_local                *ailp = &objp->ctype.ai_info.ail;
802
 
803
        const auto &&cr0 = create_path_points(objp, objp->segnum, segment_exit, Point_segs_free_ptr, path_length, create_path_random_flag::random, create_path_safety_flag::unsafe, avoid_seg);
804
        aip->path_length = cr0.second;
805
        if (cr0.first == create_path_result::early)
806
        {
807
                Point_segs_free_ptr += aip->path_length;
808
                for (;;)
809
                {
810
                        const auto &&crf = create_path_points(objp, objp->segnum, segment_exit, Point_segs_free_ptr, --path_length, create_path_random_flag::random, create_path_safety_flag::unsafe, segment_none);
811
                        aip->path_length = crf.second;
812
                        if (crf.first != create_path_result::early)
813
                                break;
814
                }
815
                assert(path_length);
816
        }
817
 
818
        aip->hide_index = Point_segs_free_ptr - Point_segs;
819
        aip->cur_path_index = 0;
820
#if PATH_VALIDATION
821
        validate_path(8, Point_segs_free_ptr, aip->path_length);
822
#endif
823
        Point_segs_free_ptr += aip->path_length;
824
        if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
825
                //Int3();       //      Contact Mike: This is curious, though not deadly. /eip++;g
826
                //force_dump_ai_objects_all("Error in crete_n_segment_path 2");
827
                ai_reset_all_paths();
828
        }
829
 
830
        aip->PATH_DIR = 1;              //      Initialize to moving forward.
831
#if defined(DXX_BUILD_DESCENT_I)
832
        aip->SUBMODE = -1;              //      Don't know what this means.
833
#endif
834
        ailp->mode = ai_mode::AIM_FOLLOW_PATH;
835
 
836
#if defined(DXX_BUILD_DESCENT_II)
837
        //      If this robot is visible (player_visibility is not available) and it's running away, move towards outside with
838
        //      randomness to prevent a stream of bots from going away down the center of a corridor.
839
        if (player_is_visible(ailp->previous_visibility))
840
        {
841
                if (aip->path_length) {
842
                        int     t_num_points = aip->path_length;
843
                        move_towards_outside(LevelSharedSegmentState, &Point_segs[aip->hide_index], t_num_points, objp, create_path_random_flag::random);
844
                        aip->path_length = t_num_points;
845
                }
846
        }
847
#endif
848
 
849
        maybe_ai_path_garbage_collect();
850
 
851
}
852
 
853
//      -------------------------------------------------------------------------------------------------------
854
void create_n_segment_path_to_door(const vmobjptridx_t objp, const unsigned path_length)
855
{
856
        create_n_segment_path(objp, path_length, segment_none);
857
}
858
 
859
#define Int3_if(cond) if (!cond) Int3();
860
 
861
// -- too much work -- //       ----------------------------------------------------------------------------------------------------------
862
// -- too much work -- //       Return true if the object the companion wants to kill is reachable.
863
// -- too much work -- int attack_kill_object(object *objp)
864
// -- too much work -- {
865
// -- too much work --  object          *kill_objp;
866
// -- too much work --  fvi_info                hit_data;
867
// -- too much work --  int                     fate;
868
// -- too much work --  fvi_query       fq;
869
// -- too much work --
870
// -- too much work --  if (Escort_kill_object == -1)
871
// -- too much work --          return 0;
872
// -- too much work --
873
// -- too much work --  kill_objp = &Objects[Escort_kill_object];
874
// -- too much work --
875
// -- too much work --  fq.p0                                           = &objp->pos;
876
// -- too much work --  fq.startseg                             = objp->segnum;
877
// -- too much work --  fq.p1                                           = &kill_objp->pos;
878
// -- too much work --  fq.rad                                  = objp->size;
879
// -- too much work --  fq.thisobjnum                   = objp-Objects;
880
// -- too much work --  fq.ignore_obj_list      = NULL;
881
// -- too much work --  fq.flags                                        = 0;
882
// -- too much work --
883
// -- too much work --  fate = find_vector_intersection(&fq,&hit_data);
884
// -- too much work --
885
// -- too much work --  if (fate == HIT_NONE)
886
// -- too much work --          return 1;
887
// -- too much work --  else
888
// -- too much work --          return 0;
889
// -- too much work -- }
890
 
891
//      -------------------------------------------------------------------------------------------------------
892
//      Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to
893
//      hide in Ai_local_info[objnum].goal_segment.
894
//      Sets    objp->ctype.ai_info.hide_index,         a pointer into Point_segs, the first point_seg of the path.
895
//                      objp->ctype.ai_info.path_length,                length of path
896
//                      Point_segs_free_ptr                             global pointer into Point_segs array
897
#if defined(DXX_BUILD_DESCENT_I)
898
static void create_path(const vmobjptridx_t objp)
899
{
900
        ai_static       *aip = &objp->ctype.ai_info;
901
        ai_local                *ailp = &objp->ctype.ai_info.ail;
902
        segnum_t                        start_seg, end_seg;
903
 
904
        start_seg = objp->segnum;
905
        end_seg = ailp->goal_segment;
906
 
907
        if (end_seg == segment_none)
908
                create_n_segment_path(objp, 3, segment_none);
909
 
910
        if (end_seg == segment_none) {
911
                ;
912
        } else {
913
                aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none).second;
914
                aip->hide_index = Point_segs_free_ptr - Point_segs;
915
                aip->cur_path_index = 0;
916
#ifndef NDEBUG
917
                validate_path(5, Point_segs_free_ptr, aip->path_length);
918
#endif
919
                Point_segs_free_ptr += aip->path_length;
920
                if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
921
                        //Int3();       //      Contact Mike: This is curious, though not deadly. /eip++;g
922
                        //force_dump_ai_objects_all("Error in create_path");
923
                        ai_reset_all_paths();
924
                }
925
                aip->PATH_DIR = 1;              //      Initialize to moving forward.
926
                aip->SUBMODE = AISM_HIDING;             //      Pretend we are hiding, so we sit here until bothered.
927
        }
928
 
929
        maybe_ai_path_garbage_collect();
930
 
931
}
932
#endif
933
 
934
 
935
//      ----------------------------------------------------------------------------------------------------------
936
//      Optimization: If current velocity will take robot near goal, don't change velocity
937
void ai_follow_path(const vmobjptridx_t objp, const player_visibility_state player_visibility, const vms_vector *const vec_to_player)
938
{
939
        ai_static               *aip = &objp->ctype.ai_info;
940
 
941
        vms_vector      goal_point, new_goal_point;
942
#if defined(DXX_BUILD_DESCENT_II)
943
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
944
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
945
        auto &robptr = Robot_info[get_robot_id(objp)];
946
#endif
947
        int                     forced_break, original_dir, original_index;
948
        ai_local                *ailp = &objp->ctype.ai_info.ail;
949
 
950
 
951
        if ((aip->hide_index == -1) || (aip->path_length == 0))
952
        {
953
                if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) {
954
                        create_n_segment_path(objp, 5, segment_none);
955
                        //--Int3_if((aip->path_length != 0));
956
                        ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;
957
                } else {
958
#if defined(DXX_BUILD_DESCENT_I)
959
                        create_path(objp);
960
#elif defined(DXX_BUILD_DESCENT_II)
961
                        create_n_segment_path(objp, 5, segment_none);
962
#endif
963
                        //--Int3_if((aip->path_length != 0));
964
                }
965
        }
966
 
967
        if ((aip->hide_index + aip->path_length > Point_segs_free_ptr - Point_segs) && (aip->path_length>0)) {
968
#if defined(DXX_BUILD_DESCENT_II)
969
                Int3(); //      Contact Mike: Bad.  Path goes into what is believed to be free space.
970
                //      This is debugging code.  Figure out why garbage collection
971
                //      didn't compress this object's path information.
972
                ai_path_garbage_collect();
973
#endif
974
                ai_reset_all_paths();
975
        }
976
 
977
        if (aip->path_length < 2) {
978
#if defined(DXX_BUILD_DESCENT_I)
979
                if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT)
980
#elif defined(DXX_BUILD_DESCENT_II)
981
                if ((aip->behavior == ai_behavior::AIB_SNIPE) || (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT))
982
#endif
983
                {
984
                        create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum == objp->segnum ? segment_none : ConsoleObject->segnum);                    //      Can't avoid segment player is in, robot is already in it! (That's what the -1 is for)
985
                                //--Int3_if((aip->path_length != 0));
986
#if defined(DXX_BUILD_DESCENT_II)
987
                        if (aip->behavior == ai_behavior::AIB_SNIPE) {
988
                                if (robot_is_thief(robptr))
989
                                        ailp->mode = ai_mode::AIM_THIEF_ATTACK; //      It gets bashed in create_n_segment_path
990
                                else
991
                                        ailp->mode = ai_mode::AIM_SNIPE_FIRE;   //      It gets bashed in create_n_segment_path
992
                        } else
993
#endif
994
                        {
995
                                ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;      //      It gets bashed in create_n_segment_path
996
                        }
997
                }
998
#if defined(DXX_BUILD_DESCENT_I)
999
                else {
1000
                        ailp->mode = ai_mode::AIM_STILL;
1001
                }
1002
                return;
1003
#elif defined(DXX_BUILD_DESCENT_II)
1004
                else if (robot_is_companion(robptr) == 0) {
1005
                        ailp->mode = ai_mode::AIM_STILL;
1006
                        aip->path_length = 0;
1007
                        return;
1008
                }
1009
#endif
1010
        }
1011
 
1012
#if defined(DXX_BUILD_DESCENT_I)
1013
        Assert((aip->PATH_DIR == -1) || (aip->PATH_DIR == 1));
1014
 
1015
        if ((aip->SUBMODE == AISM_HIDING) && (aip->behavior == ai_behavior::AIB_HIDE))
1016
                return;
1017
#endif
1018
 
1019
        goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point;
1020
        auto dist_to_goal = vm_vec_dist_quick(goal_point, objp->pos);
1021
 
1022
        //      If running from player, only run until can't be seen.
1023
        if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) {
1024
                if (player_visibility == player_visibility_state::no_line_of_sight && ailp->player_awareness_type == player_awareness_type_t::PA_NONE)
1025
                {
1026
                        fix     vel_scale;
1027
 
1028
                        vel_scale = F1_0 - FrameTime/2;
1029
                        if (vel_scale < F1_0/2)
1030
                                vel_scale = F1_0/2;
1031
 
1032
                        vm_vec_scale(objp->mtype.phys_info.velocity, vel_scale);
1033
 
1034
                        return;
1035
                } else
1036
#if defined(DXX_BUILD_DESCENT_II)
1037
                        if (!(d_tick_count ^ ((objp) & 0x07)))
1038
#endif
1039
                        {               //      Done 1/8 ticks.
1040
                        //      If player on path (beyond point robot is now at), then create a new path.
1041
                        point_seg       *curpsp = &Point_segs[aip->hide_index];
1042
                        auto player_segnum = ConsoleObject->segnum;
1043
                        int                     i;
1044
 
1045
                        //      This is probably being done every frame, which is wasteful.
1046
                        for (i=aip->cur_path_index; i<aip->path_length; i++) {
1047
                                if (curpsp[i].segnum == player_segnum) {
1048
                                        create_n_segment_path(objp, AVOID_SEG_LENGTH, player_segnum != objp->segnum ? player_segnum : segment_none);
1049
#if defined(DXX_BUILD_DESCENT_I)
1050
                                        Assert(aip->path_length != 0);
1051
#endif
1052
                                        ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;      //      It gets bashed in create_n_segment_path
1053
                                        break;
1054
                                }
1055
                        }
1056
                        if (player_is_visible(player_visibility))
1057
                        {
1058
                                ailp->player_awareness_type = player_awareness_type_t::PA_NEARBY_ROBOT_FIRED;
1059
                                ailp->player_awareness_time = F1_0;
1060
                        }
1061
                }
1062
        }
1063
 
1064
        if (aip->cur_path_index < 0) {
1065
                aip->cur_path_index = 0;
1066
        } else if (aip->cur_path_index >= aip->path_length) {
1067
                if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) {
1068
                        create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum);
1069
                        ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;      //      It gets bashed in create_n_segment_path
1070
#if defined(DXX_BUILD_DESCENT_II)
1071
                        Assert(aip->path_length != 0);
1072
#endif
1073
                } else {
1074
                        aip->cur_path_index = aip->path_length-1;
1075
                }
1076
        }
1077
 
1078
        goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point;
1079
 
1080
        //      If near goal, pick another goal point.
1081
        forced_break = 0;               //      Gets set for short paths.
1082
        original_dir = aip->PATH_DIR;
1083
        original_index = aip->cur_path_index;
1084
        const vm_distance threshold_distance{fixmul(vm_vec_mag_quick(objp->mtype.phys_info.velocity), FrameTime)*2 + F1_0*2};
1085
 
1086
#if defined(DXX_BUILD_DESCENT_II)
1087
        new_goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point;
1088
 
1089
        //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length)));
1090
#endif
1091
 
1092
        while ((dist_to_goal < threshold_distance) && !forced_break) {
1093
 
1094
                //      Advance to next point on path.
1095
                aip->cur_path_index += aip->PATH_DIR;
1096
 
1097
                //      See if next point wraps past end of path (in either direction), and if so, deal with it based on mode.
1098
                if ((aip->cur_path_index >= aip->path_length) || (aip->cur_path_index < 0)) {
1099
#if defined(DXX_BUILD_DESCENT_II)
1100
                        //      Buddy bot.  If he's in mode to get away from player and at end of line,
1101
                        //      if player visible, then make a new path, else just return.
1102
                        if (robot_is_companion(robptr)) {
1103
                                if (BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM)
1104
                                {
1105
                                        if (player_is_visible(player_visibility))
1106
                                        {
1107
                                                create_n_segment_path(objp, 16 + d_rand() * 16, segment_none);
1108
                                                aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1109
                                                Assert(aip->path_length != 0);
1110
                                                ailp->mode = ai_mode::AIM_WANDER;       //      Special buddy mode.
1111
                                                //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length)));
1112
                                                return;
1113
                                        } else {
1114
                                                ailp->mode = ai_mode::AIM_WANDER;       //      Special buddy mode.
1115
                                                vm_vec_zero(objp->mtype.phys_info.velocity);
1116
                                                vm_vec_zero(objp->mtype.phys_info.rotvel);
1117
                                                //!!Assert((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length));
1118
                                                return;
1119
                                        }
1120
                                }
1121
                        }
1122
#endif
1123
 
1124
#if defined(DXX_BUILD_DESCENT_I)
1125
                        if (ailp->mode == ai_mode::AIM_HIDE) {
1126
                                ailp->mode = ai_mode::AIM_STILL;
1127
                                return;         // Stay here until bonked or hit by player.
1128
                        }
1129
#elif defined(DXX_BUILD_DESCENT_II)
1130
                        if (aip->behavior == ai_behavior::AIB_FOLLOW) {
1131
                                create_n_segment_path(objp, 10, ConsoleObject->segnum);
1132
                                //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length)));
1133
                        }
1134
#endif
1135
                        else if (aip->behavior == ai_behavior::AIB_STATION) {
1136
                                create_path_to_station(objp, 15);
1137
                                if ((aip->hide_segment != Point_segs[aip->hide_index+aip->path_length-1].segnum)
1138
#if defined(DXX_BUILD_DESCENT_II)
1139
                                        || (aip->path_length == 0)
1140
#endif
1141
                                        ) {
1142
                                        ailp->mode = ai_mode::AIM_STILL;
1143
                                }
1144
                                return;
1145
                        } else if (ailp->mode == ai_mode::AIM_FOLLOW_PATH
1146
#if defined(DXX_BUILD_DESCENT_I)
1147
                                           && (aip->behavior != ai_behavior::AIB_FOLLOW_PATH)
1148
#endif
1149
                                           ) {
1150
                                create_path_to_believed_player_segment(objp, 10, create_path_safety_flag::safe);
1151
#if defined(DXX_BUILD_DESCENT_II)
1152
                                if (aip->hide_segment != Point_segs[aip->hide_index+aip->path_length-1].segnum) {
1153
                                        ailp->mode = ai_mode::AIM_STILL;
1154
                                        return;
1155
                                }
1156
#endif
1157
                        } else if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) {
1158
                                create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum);
1159
                                ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;      //      It gets bashed in create_n_segment_path
1160
#if defined(DXX_BUILD_DESCENT_II)
1161
                                if (aip->path_length < 1) {
1162
                                        create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum);
1163
                                        ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT;      //      It gets bashed in create_n_segment_path
1164
                                        if (aip->path_length < 1) {
1165
                                                aip->behavior = ai_behavior::AIB_NORMAL;
1166
                                                ailp->mode = ai_mode::AIM_STILL;
1167
                                                return;
1168
                                        }
1169
                                }
1170
                                //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length)));
1171
#endif
1172
                        } else {
1173
                                //      Reached end of the line.  First see if opposite end point is reachable, and if so, go there.
1174
                                //      If not, turn around.
1175
                                int                     opposite_end_index;
1176
                                vms_vector      *opposite_end_point;
1177
                                fvi_info                hit_data;
1178
                                int                     fate;
1179
                                fvi_query       fq;
1180
 
1181
                                // See which end we're nearer and look at the opposite end point.
1182
                                if (abs(aip->cur_path_index - aip->path_length) < aip->cur_path_index) {
1183
                                        //      Nearer to far end (ie, index not 0), so try to reach 0.
1184
                                        opposite_end_index = 0;
1185
                                } else {
1186
                                        //      Nearer to 0 end, so try to reach far end.
1187
                                        opposite_end_index = aip->path_length-1;
1188
                                }
1189
 
1190
                                opposite_end_point = &Point_segs[aip->hide_index + opposite_end_index].point;
1191
 
1192
                                fq.p0                                           = &objp->pos;
1193
                                fq.startseg                             = objp->segnum;
1194
                                fq.p1                                           = opposite_end_point;
1195
                                fq.rad                                  = objp->size;
1196
                                fq.thisobjnum                   = objp;
1197
                                fq.ignore_obj_list.first = nullptr;
1198
                                fq.flags                                        = 0;                            //what about trans walls???
1199
 
1200
                                fate = find_vector_intersection(fq, hit_data);
1201
 
1202
                                if (fate != HIT_WALL) {
1203
                                        //      We can be circular!  Do it!
1204
                                        //      Path direction is unchanged.
1205
                                        aip->cur_path_index = opposite_end_index;
1206
                                } else {
1207
                                        aip->PATH_DIR = -aip->PATH_DIR;
1208
#if defined(DXX_BUILD_DESCENT_II)
1209
                                        aip->cur_path_index += aip->PATH_DIR;
1210
#endif
1211
                                }
1212
                        }
1213
                        break;
1214
                } else {
1215
                        new_goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point;
1216
                        goal_point = new_goal_point;
1217
                        dist_to_goal = vm_vec_dist_quick(goal_point, objp->pos);
1218
                }
1219
 
1220
                //      If went all the way around to original point, in same direction, then get out of here!
1221
                if ((aip->cur_path_index == original_index) && (aip->PATH_DIR == original_dir)) {
1222
                        create_path_to_believed_player_segment(objp, 3, create_path_safety_flag::safe);
1223
                        forced_break = 1;
1224
                }
1225
        }       //      end while
1226
 
1227
        //      Set velocity (objp->mtype.phys_info.velocity) and orientation (objp->orient) for this object.
1228
        ai_path_set_orient_and_vel(objp, goal_point
1229
#if defined(DXX_BUILD_DESCENT_II)
1230
                                                           , player_visibility, vec_to_player
1231
#endif
1232
                                                           );
1233
 
1234
}
1235
 
1236
}
1237
 
1238
namespace {
1239
 
1240
int     Last_tick_garbage_collected;
1241
 
1242
struct obj_path {
1243
        short   path_start;
1244
        objnum_t objnum;
1245
};
1246
 
1247
static int path_index_compare(const void *const v1, const void *const v2)
1248
{
1249
        const auto i1 = reinterpret_cast<const obj_path *>(v1);
1250
        const auto i2 = reinterpret_cast<const obj_path *>(v2);
1251
        if (i1->path_start < i2->path_start)
1252
                return -1;
1253
        else if (i1->path_start == i2->path_start)
1254
                return 0;
1255
        else
1256
                return 1;
1257
}
1258
 
1259
}
1260
 
1261
namespace dsx {
1262
 
1263
//      ----------------------------------------------------------------------------------------------------------
1264
//      Set orientation matrix and velocity for objp based on its desire to get to a point.
1265
void ai_path_set_orient_and_vel(object &objp, const vms_vector &goal_point
1266
#if defined(DXX_BUILD_DESCENT_II)
1267
                                                                , const player_visibility_state player_visibility, const vms_vector *const vec_to_player
1268
#endif
1269
                                                                )
1270
{
1271
        vms_vector      cur_vel = objp.mtype.phys_info.velocity;
1272
        vms_vector      cur_pos = objp.pos;
1273
        fix                     speed_scale;
1274
        fix                     dot;
1275
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1276
        auto &robptr = Robot_info[get_robot_id(objp)];
1277
        fix                     max_speed;
1278
 
1279
        //      If evading player, use highest difficulty level speed, plus something based on diff level
1280
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1281
        max_speed = robptr.max_speed[Difficulty_level];
1282
        ai_local                *ailp = &objp.ctype.ai_info.ail;
1283
        if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT
1284
#if defined(DXX_BUILD_DESCENT_II)
1285
                || objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE
1286
#endif
1287
                )
1288
                max_speed = max_speed*3/2;
1289
 
1290
        auto norm_vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, cur_pos));
1291
        auto norm_cur_vel = vm_vec_normalized_quick(cur_vel);
1292
        const auto norm_fvec = vm_vec_normalized_quick(objp.orient.fvec);
1293
 
1294
        dot = vm_vec_dot(norm_vec_to_goal, norm_fvec);
1295
 
1296
        //      If very close to facing opposite desired vector, perturb vector
1297
        if (dot < -15*F1_0/16) {
1298
                norm_cur_vel = norm_vec_to_goal;
1299
        } else {
1300
                norm_cur_vel.x += norm_vec_to_goal.x/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1301
                norm_cur_vel.y += norm_vec_to_goal.y/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1302
                norm_cur_vel.z += norm_vec_to_goal.z/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1303
        }
1304
 
1305
        vm_vec_normalize_quick(norm_cur_vel);
1306
 
1307
        //      Set speed based on this robot type's maximum allowed speed and how hard it is turning.
1308
        //      How hard it is turning is based on the dot product of (vector to goal) and (current velocity vector)
1309
        //      Note that since 3*F1_0/4 is added to dot product, it is possible for the robot to back up.
1310
 
1311
        //      Set speed and orientation.
1312
        if (dot < 0)
1313
                dot /= -4;
1314
 
1315
#if defined(DXX_BUILD_DESCENT_II)
1316
        //      If in snipe mode, can move fast even if not facing that direction.
1317
        if (objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
1318
                if (dot < F1_0/2)
1319
                        dot = (dot + F1_0)/2;
1320
#endif
1321
 
1322
        speed_scale = fixmul(max_speed, dot);
1323
        vm_vec_scale(norm_cur_vel, speed_scale);
1324
        objp.mtype.phys_info.velocity = norm_cur_vel;
1325
 
1326
        fix rate;
1327
        if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT
1328
#if defined(DXX_BUILD_DESCENT_II)
1329
                || robot_is_companion(robptr) == 1 || objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE
1330
#endif
1331
                ) {
1332
#if defined(DXX_BUILD_DESCENT_II)
1333
                if (ailp->mode == ai_mode::AIM_SNIPE_RETREAT_BACKWARDS) {
1334
                        if (player_is_visible(player_visibility) && vec_to_player)
1335
                                norm_vec_to_goal = *vec_to_player;
1336
                        else
1337
                                vm_vec_negate(norm_vec_to_goal);
1338
                }
1339
#endif
1340
                rate = robptr.turn_time[NDL - 1] / 2;
1341
        } else
1342
                rate = robptr.turn_time[Difficulty_level];
1343
        ai_turn_towards_vector(norm_vec_to_goal, objp, rate);
1344
}
1345
 
1346
//      ----------------------------------------------------------------------------------------------------------
1347
//      Garbage colledion -- Free all unused records in Point_segs and compress all paths.
1348
void ai_path_garbage_collect()
1349
{
1350
        int     free_path_index = 0;
1351
        int     num_path_objects = 0;
1352
        int     objind;
1353
        obj_path                object_list[MAX_OBJECTS];
1354
 
1355
        auto &Objects = LevelUniqueObjectState.Objects;
1356
        auto &vcobjptridx = Objects.vcptridx;
1357
        auto &vmobjptridx = Objects.vmptridx;
1358
#ifndef NDEBUG
1359
        force_dump_ai_objects_all("***** Start ai_path_garbage_collect *****");
1360
#endif
1361
 
1362
        Last_tick_garbage_collected = d_tick_count;
1363
 
1364
#if PATH_VALIDATION
1365
        validate_all_paths();
1366
#endif
1367
        //      Create a list of objects which have paths of length 1 or more.
1368
        range_for (const auto &&objp, vcobjptridx)
1369
        {
1370
                if ((objp->type == OBJ_ROBOT) && ((objp->control_type == CT_AI)
1371
#if defined(DXX_BUILD_DESCENT_II)
1372
                        || (objp->control_type == CT_MORPH)
1373
#endif
1374
                                                                                  )) {
1375
                        const auto &aip = objp->ctype.ai_info;
1376
                        if (aip.path_length) {
1377
                                object_list[num_path_objects].path_start = aip.hide_index;
1378
                                object_list[num_path_objects++].objnum = objp;
1379
                        }
1380
                }
1381
        }
1382
 
1383
        qsort(object_list, num_path_objects, sizeof(object_list[0]),
1384
                        path_index_compare);
1385
 
1386
        for (objind=0; objind < num_path_objects; objind++) {
1387
                ai_static       *aip;
1388
                int                     i;
1389
                int                     old_index;
1390
 
1391
                auto objnum = object_list[objind].objnum;
1392
                object &objp = vmobjptridx(objnum);
1393
                aip = &objp.ctype.ai_info;
1394
                old_index = aip->hide_index;
1395
 
1396
                aip->hide_index = free_path_index;
1397
                for (i=0; i<aip->path_length; i++)
1398
                        Point_segs[free_path_index++] = Point_segs[old_index++];
1399
        }
1400
 
1401
        Point_segs_free_ptr = Point_segs.begin() + free_path_index;
1402
 
1403
#ifndef NDEBUG
1404
        {
1405
        force_dump_ai_objects_all("***** Finish ai_path_garbage_collect *****");
1406
 
1407
        auto &vcobjptr = Objects.vcptr;
1408
        range_for (const auto &&objp, vcobjptr)
1409
        {
1410
                auto &obj = *objp;
1411
                const auto &aip = obj.ctype.ai_info;
1412
 
1413
                if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI)
1414
                        if ((aip.hide_index + aip.path_length > Point_segs_free_ptr - Point_segs) && (aip.path_length>0))
1415
                                Int3();         //      Contact Mike: Debug trap for nasty, elusive bug.
1416
        }
1417
 
1418
        validate_all_paths();
1419
        }
1420
#endif
1421
 
1422
}
1423
 
1424
//      -----------------------------------------------------------------------------
1425
//      Do garbage collection if not been done for awhile, or things getting really critical.
1426
void maybe_ai_path_garbage_collect(void)
1427
{
1428
        if (Point_segs_free_ptr - Point_segs > MAX_POINT_SEGS - MAX_PATH_LENGTH) {
1429
                if (Last_tick_garbage_collected+1 >= d_tick_count) {
1430
                        //      This is kind of bad.  Garbage collected last frame or this frame.
1431
                        //      Just destroy all paths.  Too bad for the robots.  They are memory wasteful.
1432
                        ai_reset_all_paths();
1433
                } else {
1434
                        //      We are really close to full, but didn't just garbage collect, so maybe this is recoverable.
1435
                        ai_path_garbage_collect();
1436
                }
1437
        } else if (Point_segs_free_ptr - Point_segs > 3*MAX_POINT_SEGS/4) {
1438
                if (Last_tick_garbage_collected + 16 < d_tick_count) {
1439
                        ai_path_garbage_collect();
1440
                }
1441
        } else if (Point_segs_free_ptr - Point_segs > MAX_POINT_SEGS/2) {
1442
                if (Last_tick_garbage_collected + 256 < d_tick_count) {
1443
                        ai_path_garbage_collect();
1444
                }
1445
        }
1446
}
1447
 
1448
//      -----------------------------------------------------------------------------
1449
//      Reset all paths.  Do garbage collection.
1450
//      Should be called at the start of each level.
1451
void ai_reset_all_paths(void)
1452
{
1453
        auto &Objects = LevelUniqueObjectState.Objects;
1454
        auto &vmobjptr = Objects.vmptr;
1455
        range_for (const auto &&objp, vmobjptr)
1456
        {
1457
                auto &obj = *objp;
1458
                if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI)
1459
                {
1460
                        obj.ctype.ai_info.hide_index = -1;
1461
                        obj.ctype.ai_info.path_length = 0;
1462
                }
1463
        }
1464
 
1465
        ai_path_garbage_collect();
1466
 
1467
}
1468
 
1469
//      ---------------------------------------------------------------------------------------------------------
1470
//      Probably called because a robot bashed a wall, getting a bunch of retries.
1471
//      Try to resume path.
1472
void attempt_to_resume_path(const vmobjptridx_t objp)
1473
{
1474
        ai_static *aip = &objp->ctype.ai_info;
1475
        int new_path_index;
1476
#if defined(DXX_BUILD_DESCENT_II)
1477
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1478
#endif
1479
 
1480
        if (aip->behavior == ai_behavior::AIB_STATION
1481
#if defined(DXX_BUILD_DESCENT_II)
1482
                && Robot_info[get_robot_id(objp)].companion != 1
1483
#endif
1484
                )
1485
                if (d_rand() > 8192) {
1486
                        ai_local                *ailp = &objp->ctype.ai_info.ail;
1487
 
1488
                        aip->hide_segment = objp->segnum;
1489
                        ailp->mode = ai_mode::AIM_STILL;
1490
                }
1491
 
1492
        new_path_index = aip->cur_path_index - aip->PATH_DIR;
1493
 
1494
        if ((new_path_index >= 0) && (new_path_index < aip->path_length)) {
1495
                aip->cur_path_index = new_path_index;
1496
        } else {
1497
                // At end of line and have nowhere to go.
1498
                move_towards_segment_center(LevelSharedSegmentState, objp);
1499
                create_path_to_station(objp, 15);
1500
        }
1501
}
1502
 
1503
//      ----------------------------------------------------------------------------------------------------------
1504
//                                      DEBUG FUNCTIONS FOLLOW
1505
//      ----------------------------------------------------------------------------------------------------------
1506
 
1507
#if DXX_USE_EDITOR
1508
 
1509
__attribute_used
1510
static void test_create_path_many(fvmobjptridx &vmobjptridx, fimsegptridx &imsegptridx)
1511
{
1512
        std::array<point_seg, 200> point_segs;
1513
        int                     i;
1514
 
1515
        const unsigned Test_size = 1000;
1516
        for (i=0; i<Test_size; i++) {
1517
                Cursegp = imsegptridx(static_cast<segnum_t>((d_rand() * (Highest_segment_index + 1)) / D_RAND_MAX));
1518
                Markedsegp = imsegptridx(static_cast<segnum_t>((d_rand() * (Highest_segment_index + 1)) / D_RAND_MAX));
1519
                create_path_points(vmobjptridx(object_first), Cursegp, Markedsegp, point_segs.begin(), MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
1520
        }
1521
}
1522
 
1523
__attribute_used
1524
static void test_create_path(fvmobjptridx &vmobjptridx)
1525
{
1526
        std::array<point_seg, 200> point_segs;
1527
 
1528
        create_path_points(vmobjptridx(object_first), Cursegp, Markedsegp, point_segs.begin(), MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
1529
}
1530
 
1531
//      For all segments in mine, create paths to all segments in mine, print results.
1532
__attribute_used
1533
static void test_create_all_paths(fvmobjptridx &vmobjptridx, fvcsegptridx &vcsegptridx)
1534
{
1535
        Point_segs_free_ptr = Point_segs.begin();
1536
 
1537
        range_for (const auto &&segp0, vcsegptridx)
1538
        {
1539
                if (segp0->segnum != segment_none)
1540
                {
1541
                        range_for (const auto &&segp1, partial_range(vcsegptridx, static_cast<segnum_t>(segp0), vcsegptridx.count()))
1542
                        {
1543
                                if (segp1->segnum != segment_none)
1544
                                {
1545
                                        create_path_points(vmobjptridx(object_first), segp0, segp1, Point_segs_free_ptr, MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
1546
                                }
1547
                        }
1548
                }
1549
        }
1550
}
1551
 
1552
short   Player_path_length=0;
1553
int     Player_hide_index=-1;
1554
int     Player_cur_path_index=0;
1555
int     Player_following_path_flag=0;
1556
 
1557
//      ------------------------------------------------------------------------------------------------------------------
1558
//      Set orientation matrix and velocity for objp based on its desire to get to a point.
1559
static void player_path_set_orient_and_vel(object &objp, const vms_vector &goal_point)
1560
{
1561
        const auto &cur_vel = objp.mtype.phys_info.velocity;
1562
        const auto &cur_pos = objp.pos;
1563
        fix                     speed_scale;
1564
        fix                     dot;
1565
 
1566
        const fix max_speed = F1_0*50;
1567
 
1568
        const auto norm_vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, cur_pos));
1569
        auto norm_cur_vel = vm_vec_normalized_quick(cur_vel);
1570
        const auto &&norm_fvec = vm_vec_normalized_quick(objp.orient.fvec);
1571
 
1572
        dot = vm_vec_dot(norm_vec_to_goal, norm_fvec);
1573
 
1574
        //      If very close to facing opposite desired vector, perturb vector
1575
        if (dot < -15*F1_0/16) {
1576
                norm_cur_vel = norm_vec_to_goal;
1577
        } else {
1578
                norm_cur_vel.x += norm_vec_to_goal.x/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1579
                norm_cur_vel.y += norm_vec_to_goal.y/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1580
                norm_cur_vel.z += norm_vec_to_goal.z/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime);
1581
        }
1582
 
1583
        vm_vec_normalize_quick(norm_cur_vel);
1584
 
1585
        //      Set speed based on this robot type's maximum allowed speed and how hard it is turning.
1586
        //      How hard it is turning is based on the dot product of (vector to goal) and (current velocity vector)
1587
        //      Note that since 3*F1_0/4 is added to dot product, it is possible for the robot to back up.
1588
 
1589
        //      Set speed and orientation.
1590
        if (dot < 0)
1591
                dot /= 4;
1592
 
1593
        speed_scale = fixmul(max_speed, dot);
1594
        vm_vec_scale(norm_cur_vel, speed_scale);
1595
        objp.mtype.phys_info.velocity = norm_cur_vel;
1596
        physics_turn_towards_vector(norm_vec_to_goal, objp, F1_0);
1597
}
1598
 
1599
//      ----------------------------------------------------------------------------------------------------------
1600
//      Optimization: If current velocity will take robot near goal, don't change velocity
1601
void player_follow_path(object &objp)
1602
{
1603
        vms_vector      goal_point;
1604
        int                     count, forced_break, original_index;
1605
        int                     goal_seg;
1606
 
1607
        if (!Player_following_path_flag)
1608
                return;
1609
 
1610
        if (Player_hide_index == -1)
1611
                return;
1612
 
1613
        if (Player_path_length < 2)
1614
                return;
1615
 
1616
        goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point;
1617
        goal_seg = Point_segs[Player_hide_index + Player_cur_path_index].segnum;
1618
        Assert((goal_seg >= 0) && (goal_seg <= Highest_segment_index));
1619
        (void)goal_seg;
1620
        auto dist_to_goal = vm_vec_dist_quick(goal_point, objp.pos);
1621
 
1622
        if (Player_cur_path_index < 0)
1623
                Player_cur_path_index = 0;
1624
        else if (Player_cur_path_index >= Player_path_length)
1625
                Player_cur_path_index = Player_path_length-1;
1626
 
1627
        goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point;
1628
 
1629
        count=0;
1630
 
1631
        //      If near goal, pick another goal point.
1632
        forced_break = 0;               //      Gets set for short paths.
1633
        //original_dir = 1;
1634
        original_index = Player_cur_path_index;
1635
        const vm_distance threshold_distance{fixmul(vm_vec_mag_quick(objp.mtype.phys_info.velocity), FrameTime)*2 + F1_0*2};
1636
 
1637
        while ((dist_to_goal < threshold_distance) && !forced_break) {
1638
 
1639
                //      ----- Debug stuff -----
1640
                if (count++ > 20) {
1641
                        break;
1642
                }
1643
 
1644
                //      Advance to next point on path.
1645
                Player_cur_path_index += 1;
1646
 
1647
                //      See if next point wraps past end of path (in either direction), and if so, deal with it based on mode.
1648
                if ((Player_cur_path_index >= Player_path_length) || (Player_cur_path_index < 0)) {
1649
                        Player_following_path_flag = 0;
1650
                        forced_break = 1;
1651
                }
1652
 
1653
                //      If went all the way around to original point, in same direction, then get out of here!
1654
                if (Player_cur_path_index == original_index) {
1655
                        Player_following_path_flag = 0;
1656
                        forced_break = 1;
1657
                }
1658
 
1659
                goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point;
1660
                dist_to_goal = vm_vec_dist_quick(goal_point, objp.pos);
1661
 
1662
        }       //      end while
1663
 
1664
        //      Set velocity (objp->mtype.phys_info.velocity) and orientation (objp->orient) for this object.
1665
        player_path_set_orient_and_vel(objp, goal_point);
1666
}
1667
 
1668
 
1669
//      ------------------------------------------------------------------------------------------------------------------
1670
//      Create path for player from current segment to goal segment.
1671
static void create_player_path_to_segment(fvmobjptridx &vmobjptridx, segnum_t segnum)
1672
{
1673
        const auto objp = vmobjptridx(ConsoleObject);
1674
        Player_hide_index=-1;
1675
        Player_cur_path_index=0;
1676
        Player_following_path_flag=0;
1677
 
1678
        auto &&cr = create_path_points(objp, objp->segnum, segnum, Point_segs_free_ptr, 100, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
1679
        Player_path_length = cr.second;
1680
        if (cr.first == create_path_result::early)
1681
                con_printf(CON_DEBUG,"Unable to form path of length %i for myself", 100);
1682
 
1683
        Player_following_path_flag = 1;
1684
 
1685
        Player_hide_index = Point_segs_free_ptr - Point_segs;
1686
        Player_cur_path_index = 0;
1687
        Point_segs_free_ptr += Player_path_length;
1688
        if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
1689
                //Int3();       //      Contact Mike: This is curious, though not deadly. /eip++;g
1690
                ai_reset_all_paths();
1691
        }
1692
 
1693
}
1694
segnum_t        Player_goal_segment = segment_none;
1695
 
1696
void check_create_player_path(void)
1697
{
1698
        auto &Objects = LevelUniqueObjectState.Objects;
1699
        auto &vmobjptridx = Objects.vmptridx;
1700
        if (Player_goal_segment != segment_none)
1701
                create_player_path_to_segment(vmobjptridx, Player_goal_segment);
1702
 
1703
        Player_goal_segment = segment_none;
1704
}
1705
 
1706
#endif
1707
 
1708
}
1709
 
1710
//      ----------------------------------------------------------------------------------------------------------
1711
//                                      DEBUG FUNCTIONS ENDED
1712
//      ----------------------------------------------------------------------------------------------------------
1713