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
 * Autonomous Individual movement.
23
 *
24
 */
25
 
26
#include <algorithm>
27
#include <cstdlib>
28
#include <stdio.h>
29
#include <time.h>
30
 
31
#include "inferno.h"
32
#include "game.h"
33
#include "console.h"
34
#include "3d.h"
35
 
36
#include "object.h"
37
#include "render.h"
38
#include "dxxerror.h"
39
#include "ai.h"
40
#include "escort.h"
41
#include "laser.h"
42
#include "fvi.h"
43
#include "physfsx.h"
44
#include "physfs-serial.h"
45
#include "robot.h"
46
#include "bm.h"
47
#include "weapon.h"
48
#include "physics.h"
49
#include "collide.h"
50
#include "player.h"
51
#include "wall.h"
52
#include "vclip.h"
53
#include "fireball.h"
54
#include "morph.h"
55
#include "effects.h"
56
#include "timer.h"
57
#include "sounds.h"
58
#include "gameseg.h"
59
#include "cntrlcen.h"
60
#include "multibot.h"
61
#include "multi.h"
62
#include "gameseq.h"
63
#include "key.h"
64
#include "powerup.h"
65
#include "gauges.h"
66
#include "text.h"
67
#include "args.h"
68
#include "fuelcen.h"
69
#include "controls.h"
70
#include "kconfig.h"
71
 
72
#if DXX_USE_EDITOR
73
#include "editor/editor.h"
74
#include "editor/esegment.h"
75
#include "editor/kdefs.h"
76
#endif
77
 
78
//added 05/17/99 Matt Mueller
79
#include "u_mem.h"
80
//end addition -MM
81
 
82
#include "compiler-range_for.h"
83
#include "segiter.h"
84
#include "d_enumerate.h"
85
#include "d_range.h"
86
#include <utility>
87
 
88
using std::min;
89
 
90
#define AI_TURN_SCALE   1
91
#define BABY_SPIDER_ID  14
92
 
93
namespace dsx {
94
static void init_boss_segments(const segment_array &segments, const object &boss_objnum, d_level_shared_boss_state::special_segment_array_t &a, int size_check, int one_wall_hack);
95
static void ai_multi_send_robot_position(object &objnum, int force);
96
 
97
#if defined(DXX_BUILD_DESCENT_I)
98
#define BOSS_DEATH_SOUND_DURATION       0x2ae14         //      2.68 seconds
99
 
100
constexpr d_level_shared_boss_state::D1_Boss_cloak_interval d_level_shared_boss_state::Boss_cloak_interval;
101
constexpr d_level_shared_boss_state::D1_Boss_teleport_interval d_level_shared_boss_state::Boss_teleport_interval;
102
 
103
#elif defined(DXX_BUILD_DESCENT_II)
104
#define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
105
 
106
#define FIRE_K  8               //      Controls average accuracy of robot firing.  Smaller numbers make firing worse.  Being power of 2 doesn't matter.
107
 
108
// ====================================================================================================================
109
 
110
#define MIN_LEAD_SPEED          (F1_0*4)
111
#define MAX_LEAD_DISTANCE       (F1_0*200)
112
#define LEAD_RANGE                      (F1_0/2)
113
 
114
constexpr std::array<std::array<int, 3>, NUM_D2_BOSSES> Spew_bots{{
115
        {{38, 40, -1}},
116
        {{37, -1, -1}},
117
        {{43, 57, -1}},
118
        {{26, 27, 58}},
119
        {{59, 58, 54}},
120
        {{60, 61, 54}},
121
 
122
        {{69, 29, 24}},
123
        {{72, 60, 73}}
124
}};
125
 
126
constexpr std::array<int, NUM_D2_BOSSES> Max_spew_bots{{2, 1, 2, 3, 3, 3, 3, 3}};
127
static fix Dist_to_last_fired_upon_player_pos;
128
#endif
129
}
130
 
131
namespace dcx {
132
constexpr std::integral_constant<int, F1_0 * 8> CHASE_TIME_LENGTH{};
133
constexpr std::integral_constant<int, F1_0> Robot_sound_volume{};
134
enum {
135
        Flinch_scale = 4,
136
        Attack_scale = 24,
137
};
138
#define ANIM_RATE               (F1_0/16)
139
#define DELTA_ANG_SCALE 16
140
 
141
constexpr std::array<int8_t, 8> Mike_to_matt_xlate{{
142
        AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST
143
}};
144
 
145
#define OVERALL_AGITATION_MAX   100
146
 
147
#define         MAX_AI_CLOAK_INFO       8       //      Must be a power of 2!
148
 
149
#define BOSS_CLOAK_DURATION     Boss_cloak_duration
150
#define BOSS_DEATH_DURATION     (F1_0*6)
151
//      Amount of time since the current robot was last processed for things such as movement.
152
//      It is not valid to use FrameTime because robots do not get moved every frame.
153
 
154
// ---------- John: These variables must be saved as part of gamesave. --------
155
static int             Overall_agitation;
156
point_seg_array_t       Point_segs;
157
point_seg_array_t::iterator       Point_segs_free_ptr;
158
static std::array<ai_cloak_info, MAX_AI_CLOAK_INFO>   Ai_cloak_info;
159
 
160
// ------ John: End of variables which must be saved as part of gamesave. -----
161
 
162
//      0       mech
163
//      1       green claw
164
//      2       spider
165
//      3       josh
166
//      4       violet
167
//      5       cloak vulcan
168
//      6       cloak mech
169
//      7       brain
170
//      8       onearm
171
//      9       plasma
172
//      10      toaster
173
//      11      bird
174
//      12      missile bird
175
//      13      polyhedron
176
//      14      baby spider
177
//      15      mini boss
178
//      16      super mech
179
//      17      shareware boss
180
//      18      cloak-green     ; note, gating in this guy benefits player, cloak objects
181
//      19      vulcan
182
//      20      toad
183
//      21      4-claw
184
//      22      quad-laser
185
// 23 super boss
186
 
187
// byte Super_boss_gate_list[] = {0, 1, 2, 9, 11, 16, 18, 19, 21, 22, 0, 9, 9, 16, 16, 18, 19, 19, 22, 22};
188
constexpr std::array<int8_t, 21> Super_boss_gate_list{{
189
        0, 1, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 0, 8, 11, 19, 20, 8, 20, 8
190
}};
191
}
192
#define MAX_GATE_INDEX  (Super_boss_gate_list.size())
193
 
194
#if defined(DXX_BUILD_DESCENT_II)
195
namespace dsx {
196
 
197
 
198
// ------ John: End of variables which must be saved as part of gamesave. -----
199
 
200
vms_vector      Last_fired_upon_player_pos;
201
 
202
 
203
// -- ubyte Boss_cloaks[NUM_D2_BOSSES]              = {1,1,1,1,1,1};      // Set byte if this boss can cloak
204
 
205
const boss_flags_t Boss_teleports{{1,1,1,1,1,1, 1,1}}; // Set byte if this boss can teleport
206
const boss_flags_t Boss_spew_more{{0,1,0,0,0,0, 0,0}}; // If set, 50% of time, spew two bots.
207
const boss_flags_t Boss_spews_bots_energy{{1,1,0,1,0,1, 1,1}}; // Set byte if boss spews bots when hit by energy weapon.
208
const boss_flags_t Boss_spews_bots_matter{{0,0,1,1,1,1, 0,1}}; // Set byte if boss spews bots when hit by matter weapon.
209
const boss_flags_t Boss_invulnerable_energy{{0,0,1,1,0,0, 0,0}}; // Set byte if boss is invulnerable to energy weapons.
210
const boss_flags_t Boss_invulnerable_matter{{0,0,0,0,1,1, 1,0}}; // Set byte if boss is invulnerable to matter weapons.
211
const boss_flags_t Boss_invulnerable_spot{{0,0,0,0,0,1, 0,1}}; // Set byte if boss is invulnerable in all but a certain spot.  (Dot product fvec|vec_to_collision < BOSS_INVULNERABLE_DOT)
212
 
213
segnum_t             Believed_player_seg;
214
}
215
#endif
216
 
217
namespace dcx {
218
 
219
namespace {
220
 
221
struct robot_to_player_visibility_state
222
{
223
        vms_vector vec_to_player;
224
        player_visibility_state visibility;
225
        uint8_t initialized = 0;
226
};
227
 
228
struct awareness_t : std::array<player_awareness_type_t, MAX_SEGMENTS>
229
{
230
};
231
 
232
}
233
 
234
}
235
 
236
static int ai_evaded;
237
 
238
// These globals are set by a call to find_vector_intersection, which is a slow routine,
239
// so we don't want to call it again (for this object) unless we have to.
240
static vms_vector  Hit_pos;
241
static int         Hit_type;
242
static fvi_info    Hit_data;
243
 
244
namespace dcx {
245
vms_vector      Believed_player_pos;
246
 
247
static bool silly_animation_angle(fixang vms_angvec::*const a, const vms_angvec &jp, const vms_angvec &pobjp, const int flinch_attack_scale, vms_angvec &goal_angles, vms_angvec &delta_angles)
248
{
249
        const fix delta_angle = jp.*a - pobjp.*a;
250
        if (!delta_angle)
251
                return false;
252
        goal_angles.*a = jp.*a;
253
        const fix delta_anim_rate = (delta_angle >= F1_0/2)
254
                ? -ANIM_RATE
255
                : (delta_angle >= 0)
256
                        ? ANIM_RATE
257
                        : (delta_angle >= -F1_0/2)
258
                                ? -ANIM_RATE
259
                                : ANIM_RATE
260
        ;
261
        const fix delta_2 = (flinch_attack_scale != 1)
262
                ? delta_anim_rate * flinch_attack_scale
263
                : delta_anim_rate;
264
        delta_angles.*a = delta_2 / DELTA_ANG_SCALE;            // complete revolutions per second
265
        return true;
266
}
267
 
268
static void frame_animation_angle(fixang vms_angvec::*const a, const fix frametime, const vms_angvec &deltaang, const vms_angvec &goalang, vms_angvec &curang)
269
{
270
        fix delta_to_goal = goalang.*a - curang.*a;
271
        if (delta_to_goal > 32767)
272
                delta_to_goal = delta_to_goal - 65536;
273
        else if (delta_to_goal < -32767)
274
                delta_to_goal = 65536 + delta_to_goal;
275
        if (delta_to_goal)
276
        {
277
                // Due to deltaang.*a being usually small values and frametime getting smaller with higher FPS, the usual use of fixmul will have scaled_delta_angle result in 0 way too often and long, making the robot animation run circles around itself. So multiply deltaang by DELTA_ANG_SCALE when passed to fixmul.
278
                const fix scaled_delta_angle = fixmul(deltaang.*a * DELTA_ANG_SCALE, frametime); // fixmul(deltaang.*a, frametime) * DELTA_ANG_SCALE;
279
                auto &ca = curang.*a;
280
                if (abs(delta_to_goal) < abs(scaled_delta_angle))
281
                        ca = goalang.*a;
282
                else
283
                        ca += scaled_delta_angle;
284
        }
285
}
286
 
287
static void move_toward_vector_component_assign(fix vms_vector::*const p, const vms_vector &vec_goal, const fix frametime32, vms_vector &velocity)
288
{
289
        velocity.*p = (velocity.*p / 2) + fixmul(vec_goal.*p, frametime32);
290
}
291
 
292
static void move_toward_vector_component_add(fix vms_vector::*const p, const vms_vector &vec_goal, const fix frametime64, const fix difficulty_scale, vms_vector &velocity)
293
{
294
        velocity.*p += fixmul(vec_goal.*p, frametime64) * difficulty_scale / 4;
295
}
296
 
297
}
298
 
299
#define AIS_MAX 8
300
#define AIE_MAX 4
301
 
302
#ifndef NDEBUG
303
#if PARALLAX
304
#if defined(DXX_BUILD_DESCENT_I)
305
// Index into this array with ailp->mode
306
constexpr char mode_text[][16] = {
307
        "STILL",
308
        "WANDER",
309
        "FOL_PATH",
310
        "CHASE_OBJ",
311
        "RUN_FROM",
312
        "HIDE",
313
        "FOL_PATH2",
314
        "OPEN_DOOR",
315
};
316
 
317
//      Index into this array with aip->behavior
318
constexpr std::array<char[9], 6> behavior_text{
319
        "STILL   ",
320
        "NORMAL  ",
321
        "HIDE    ",
322
        "RUN_FROM",
323
        "FOLPATH ",
324
        "STATION "
325
};
326
#endif
327
#endif
328
#endif
329
 
330
// Current state indicates where the robot current is, or has just done.
331
// Transition table between states for an AI object.
332
// First dimension is trigger event.
333
// Second dimension is current state.
334
// Third dimension is goal state.
335
// Result is new goal state.
336
// ERR_ means something impossible has happened.
337
constexpr int8_t Ai_transition_table[AI_MAX_EVENT][AI_MAX_STATE][AI_MAX_STATE] = {
338
        {
339
                // Event = AIE_FIRE, a nearby object fired
340
                // none     rest      srch      lock      flin      fire      reco        // CURRENT is rows, GOAL is columns
341
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // none
342
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // rest
343
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // search
344
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // lock
345
                { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO }, // flinch
346
                { AIS_ERR_, AIS_FIRE, AIS_FIRE, AIS_FIRE, AIS_FLIN, AIS_FIRE, AIS_RECO }, // fire
347
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE }  // recoil
348
        },
349
 
350
        // Event = AIE_HITT, a nearby object was hit (or a wall was hit)
351
        {
352
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
353
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
354
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
355
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
356
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FLIN},
357
                { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
358
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
359
        },
360
 
361
        // Event = AIE_COLL, player collided with robot
362
        {
363
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
364
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
365
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
366
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
367
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_LOCK, AIS_FLIN, AIS_FLIN},
368
                { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
369
                { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
370
        },
371
 
372
        // Event = AIE_HURT, player hurt robot (by firing at and hitting it)
373
        // Note, this doesn't necessarily mean the robot JUST got hit, only that that is the most recent thing that happened.
374
        {
375
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
376
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
377
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
378
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
379
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
380
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
381
                { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}
382
        }
383
};
384
 
385
namespace dsx {
386
 
387
weapon_id_type get_robot_weapon(const robot_info &ri, const unsigned gun_num)
388
{
389
#if defined(DXX_BUILD_DESCENT_I)
390
        (void)gun_num;
391
#elif defined(DXX_BUILD_DESCENT_II)
392
        if (ri.weapon_type2 != weapon_none && !gun_num)
393
                return ri.weapon_type2;
394
#endif
395
        return ri.weapon_type;
396
}
397
 
398
static int ready_to_fire_weapon1(const ai_local &ailp, fix threshold)
399
{
400
        return ailp.next_fire <= threshold;
401
}
402
 
403
static int ready_to_fire_weapon2(const robot_info &robptr, const ai_local &ailp, fix threshold)
404
{
405
#if defined(DXX_BUILD_DESCENT_I)
406
        (void)robptr;
407
        (void)ailp;
408
        (void)threshold;
409
        return 0;
410
#elif defined(DXX_BUILD_DESCENT_II)
411
        if (robptr.weapon_type2 == weapon_none)
412
                return 0;
413
        return ailp.next_fire2 <= threshold;
414
#endif
415
}
416
 
417
// ----------------------------------------------------------------------------
418
// Return firing status.
419
// If ready to fire a weapon, return true, else return false.
420
// Ready to fire a weapon if next_fire <= 0 or next_fire2 <= 0.
421
static int ready_to_fire_any_weapon(const robot_info &robptr, const ai_local &ailp, fix threshold)
422
{
423
        return ready_to_fire_weapon1(ailp, threshold) || ready_to_fire_weapon2(robptr, ailp, threshold);
424
}
425
 
426
// ---------------------------------------------------------------------------------------------------------------------
427
//      Given a behavior, set initial mode.
428
ai_mode ai_behavior_to_mode(ai_behavior behavior)
429
{
430
        switch (behavior) {
431
                case ai_behavior::AIB_STILL:
432
                        return ai_mode::AIM_STILL;
433
                case ai_behavior::AIB_NORMAL:
434
                        return ai_mode::AIM_CHASE_OBJECT;
435
                case ai_behavior::AIB_RUN_FROM:
436
                        return ai_mode::AIM_RUN_FROM_OBJECT;
437
                case ai_behavior::AIB_STATION:
438
                        return ai_mode::AIM_STILL;
439
#if defined(DXX_BUILD_DESCENT_I)
440
                case ai_behavior::AIB_HIDE:
441
                        return ai_mode::AIM_HIDE;
442
                case ai_behavior::AIB_FOLLOW_PATH:
443
                        return ai_mode::AIM_FOLLOW_PATH;
444
#elif defined(DXX_BUILD_DESCENT_II)
445
                case ai_behavior::AIB_BEHIND:
446
                        return ai_mode::AIM_BEHIND;
447
                case ai_behavior::AIB_SNIPE:
448
                        return ai_mode::AIM_STILL;      //      Changed, 09/13/95, MK, snipers are still until they see you or are hit.
449
                case ai_behavior::AIB_FOLLOW:
450
                        return ai_mode::AIM_FOLLOW_PATH;
451
#endif
452
                default:        Int3(); //      Contact Mike: Error, illegal behavior type
453
        }
454
 
455
        return ai_mode::AIM_STILL;
456
}
457
 
458
// ---------------------------------------------------------------------------------------------------------------------
459
//      Call every time the player starts a new ship.
460
void ai_init_boss_for_ship(void)
461
{
462
#if defined(DXX_BUILD_DESCENT_II)
463
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
464
        BossUniqueState.Boss_hit_time = -F1_0*10;
465
#endif
466
}
467
 
468
static void boss_init_all_segments(const segment_array &Segments, const object &boss_objnum)
469
{
470
        auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
471
        auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
472
        if (!Boss_teleport_segs.empty())
473
                return; // already have boss segs
474
 
475
        init_boss_segments(Segments, boss_objnum, Boss_gate_segs, 0, 0);
476
 
477
        init_boss_segments(Segments, boss_objnum, Boss_teleport_segs, 1, 0);
478
#if defined(DXX_BUILD_DESCENT_II)
479
        if (Boss_teleport_segs.size() < 2)
480
                init_boss_segments(Segments, boss_objnum, Boss_teleport_segs, 1, 1);
481
#endif
482
}
483
 
484
// ---------------------------------------------------------------------------------------------------------------------
485
//      initial_mode == -1 means leave mode unchanged.
486
void init_ai_object(const vmobjptridx_t objp, ai_behavior behavior, const imsegidx_t hide_segment)
487
{
488
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
489
        ai_static       *const aip = &objp->ctype.ai_info;
490
        ai_local                *const ailp = &aip->ail;
491
 
492
        *ailp = {};
493
 
494
        if (static_cast<unsigned>(behavior) == 0) {
495
                behavior = ai_behavior::AIB_NORMAL;
496
                aip->behavior = behavior;
497
        }
498
 
499
        //      mode is now set from the Robot dialog, so this should get overwritten.
500
        ailp->mode = ai_mode::AIM_STILL;
501
 
502
        ailp->previous_visibility = player_visibility_state::no_line_of_sight;
503
 
504
        {
505
                aip->behavior = behavior;
506
                ailp->mode = ai_behavior_to_mode(aip->behavior);
507
        }
508
 
509
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
510
        auto &robptr = Robot_info[get_robot_id(objp)];
511
#if defined(DXX_BUILD_DESCENT_II)
512
        if (robot_is_companion(robptr)) {
513
                auto &BuddyState = LevelUniqueObjectState.BuddyState;
514
                BuddyState.Buddy_objnum = objp;
515
                ailp->mode = ai_mode::AIM_GOTO_PLAYER;
516
        }
517
 
518
        if (robot_is_thief(robptr)) {
519
                aip->behavior = ai_behavior::AIB_SNIPE;
520
                ailp->mode = ai_mode::AIM_THIEF_WAIT;
521
        }
522
 
523
        if (robptr.attack_type) {
524
                aip->behavior = ai_behavior::AIB_NORMAL;
525
                ailp->mode = ai_behavior_to_mode(aip->behavior);
526
        }
527
#endif
528
 
529
        // This is astonishingly stupid!  This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
530
 
531
        objp->mtype.phys_info.velocity = {};
532
        ailp->player_awareness_time = 0;
533
        ailp->player_awareness_type = player_awareness_type_t::PA_NONE;
534
        aip->GOAL_STATE = AIS_SRCH;
535
        aip->CURRENT_STATE = AIS_REST;
536
        ailp->time_player_seen = GameTime64;
537
        ailp->next_misc_sound_time = GameTime64;
538
        ailp->time_player_sound_attacked = GameTime64;
539
 
540
        aip->hide_segment = hide_segment;
541
        ailp->goal_segment = hide_segment;
542
        aip->hide_index = -1;                   // This means the path has not yet been created.
543
        aip->cur_path_index = 0;
544
 
545
        aip->SKIP_AI_COUNT = 0;
546
 
547
        if (robptr.cloak_type == RI_CLOAKED_ALWAYS)
548
                aip->CLOAKED = 1;
549
        else
550
                aip->CLOAKED = 0;
551
 
552
        objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
553
 
554
        aip->REMOTE_OWNER = -1;
555
 
556
#if defined(DXX_BUILD_DESCENT_II)
557
        aip->dying_sound_playing = 0;
558
        aip->dying_start_time = 0;
559
#endif
560
        aip->danger_laser_num = object_none;
561
 
562
        if (robptr.boss_flag
563
#if DXX_USE_EDITOR
564
                && !EditorWindow
565
#endif
566
                )
567
        {
568
                BossUniqueState = {};
569
                boss_init_all_segments(Segments, objp);
570
        }
571
}
572
 
573
// ---------------------------------------------------------------------------------------------------------------------
574
void init_ai_objects(void)
575
{
576
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
577
        auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
578
        auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
579
        auto &Objects = LevelUniqueObjectState.Objects;
580
        auto &vmobjptridx = Objects.vmptridx;
581
        Point_segs_free_ptr = Point_segs.begin();
582
        Boss_gate_segs.clear();
583
        Boss_teleport_segs.clear();
584
 
585
        range_for (const auto &&o, vmobjptridx)
586
        {
587
                auto &obj = *o;
588
                if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI)
589
                        init_ai_object(o, obj.ctype.ai_info.behavior, obj.ctype.ai_info.hide_segment);
590
        }
591
 
592
        BossUniqueState.Boss_dying_sound_playing = 0;
593
        BossUniqueState.Boss_dying = 0;
594
 
595
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
596
#define D1_Boss_gate_interval   F1_0*5 - Difficulty_level*F1_0/2
597
#if defined(DXX_BUILD_DESCENT_I)
598
        GameUniqueState.Boss_gate_interval = D1_Boss_gate_interval;
599
#elif defined(DXX_BUILD_DESCENT_II)
600
        ai_do_cloak_stuff();
601
 
602
        init_buddy_for_level();
603
 
604
        if (EMULATING_D1)
605
        {
606
                LevelSharedBossState.Boss_cloak_interval = d_level_shared_boss_state::D1_Boss_cloak_interval::value;
607
                LevelSharedBossState.Boss_teleport_interval = d_level_shared_boss_state::D1_Boss_teleport_interval::value;
608
                GameUniqueState.Boss_gate_interval = D1_Boss_gate_interval;
609
        }
610
        else
611
        {
612
                GameUniqueState.Boss_gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
613
                if (Current_level_num == Last_level)
614
                {
615
                LevelSharedBossState.Boss_teleport_interval = F1_0*10;
616
                LevelSharedBossState.Boss_cloak_interval = F1_0*15;                                     //      Time between cloaks
617
                } else
618
                {
619
                LevelSharedBossState.Boss_teleport_interval = F1_0*7;
620
                LevelSharedBossState.Boss_cloak_interval = F1_0*10;                                     //      Time between cloaks
621
                }
622
        }
623
#endif
624
#undef D1_Boss_gate_interval
625
}
626
 
627
//-------------------------------------------------------------------------------------------
628
void ai_turn_towards_vector(const vms_vector &goal_vector, object_base &objp, fix rate)
629
{
630
        //      Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
631
        if (rate == 0)
632
                return;
633
 
634
        if (objp.type == OBJ_ROBOT && (get_robot_id(objp) == BABY_SPIDER_ID)) {
635
                physics_turn_towards_vector(goal_vector, objp, rate);
636
                return;
637
        }
638
 
639
        auto new_fvec = goal_vector;
640
 
641
        const fix dot = vm_vec_dot(goal_vector, objp.orient.fvec);
642
 
643
        if (dot < (F1_0 - FrameTime/2)) {
644
                fix     new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
645
                vm_vec_scale(new_fvec, new_scale);
646
                vm_vec_add2(new_fvec, objp.orient.fvec);
647
                auto mag = vm_vec_normalize_quick(new_fvec);
648
                if (mag < F1_0/256) {
649
                        new_fvec = goal_vector;         //      if degenerate vector, go right to goal
650
                }
651
        }
652
 
653
#if defined(DXX_BUILD_DESCENT_II)
654
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
655
        if (const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude)
656
        {
657
                fix                     scale;
658
                scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[get_robot_id(objp)].mass);
659
                vm_vec_scale_add2(new_fvec, make_random_vector(), scale);
660
        }
661
#endif
662
 
663
        vm_vector_2_matrix(objp.orient, new_fvec, nullptr, &objp.orient.rvec);
664
}
665
 
666
#if defined(DXX_BUILD_DESCENT_I)
667
static void ai_turn_randomly(const vms_vector &vec_to_player, object_base &obj, const fix rate, const player_visibility_state previous_visibility)
668
{
669
        vms_vector      curvec;
670
 
671
        //      Random turning looks too stupid, so 1/4 of time, cheat.
672
        if (player_is_visible(previous_visibility))
673
                if (d_rand() > 0x7400) {
674
                        ai_turn_towards_vector(vec_to_player, obj, rate);
675
                        return;
676
                }
677
//--debug--     if (d_rand() > 0x6000)
678
//--debug--             Prevented_turns++;
679
 
680
        curvec = obj.mtype.phys_info.rotvel;
681
 
682
        curvec.y += F1_0/64;
683
 
684
        curvec.x += curvec.y/6;
685
        curvec.y += curvec.z/4;
686
        curvec.z += curvec.x/10;
687
 
688
        if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
689
        if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
690
        if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
691
 
692
        obj.mtype.phys_info.rotvel = curvec;
693
 
694
}
695
#endif
696
 
697
//      Overall_agitation affects:
698
//              Widens field of view.  Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
699
//                      Overall_agitation/128 subtracted from field of view, making robots see wider.
700
//              Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
701
//              Decreases wait between fire times by Overall_agitation/64 seconds.
702
 
703
 
704
// --------------------------------------------------------------------------------------------------------------------
705
//      Returns:
706
//              0               Player is not visible from object, obstruction or something.
707
//              1               Player is visible, but not in field of view.
708
//              2               Player is visible and in field of view.
709
//      Note: Uses Believed_player_pos as player's position for cloak effect.
710
//      NOTE: Will destructively modify *pos if *pos is outside the mine.
711
player_visibility_state player_is_visible_from_object(const vmobjptridx_t objp, vms_vector &pos, const fix field_of_view, const vms_vector &vec_to_player)
712
{
713
        fix                     dot;
714
        fvi_query       fq;
715
 
716
#if defined(DXX_BUILD_DESCENT_II)
717
        //      Assume that robot's gun tip is in same segment as robot's center.
718
        if (objp->control_type == CT_AI)
719
                objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
720
#endif
721
 
722
        fq.p0                                           = &pos;
723
        if ((pos.x != objp->pos.x) || (pos.y != objp->pos.y) || (pos.z != objp->pos.z)) {
724
                auto &Segments = LevelSharedSegmentState.get_segments();
725
                const auto &&segnum = find_point_seg(LevelSharedSegmentState, pos, Segments.vcptridx(objp->segnum));
726
                if (segnum == segment_none) {
727
                        fq.startseg = objp->segnum;
728
                        pos = objp->pos;
729
                        move_towards_segment_center(LevelSharedSegmentState, objp);
730
                } else
731
                {
732
#if defined(DXX_BUILD_DESCENT_II)
733
                        if (segnum != objp->segnum) {
734
                                if (objp->control_type == CT_AI)
735
                                        objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
736
                        }
737
#endif
738
                        fq.startseg = segnum;
739
                }
740
        } else
741
                fq.startseg                     = objp->segnum;
742
        fq.p1                                           = &Believed_player_pos;
743
        fq.rad                                  = F1_0/4;
744
        fq.thisobjnum                   = objp;
745
        fq.ignore_obj_list.first = nullptr;
746
        fq.flags                                        = FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS;            //what about trans walls???
747
 
748
        Hit_type = find_vector_intersection(fq, Hit_data);
749
 
750
        Hit_pos = Hit_data.hit_pnt;
751
 
752
        if (Hit_type == HIT_NONE)
753
        {
754
                dot = vm_vec_dot(vec_to_player, objp->orient.fvec);
755
                if (dot > field_of_view - (Overall_agitation << 9)) {
756
                        return player_visibility_state::visible_and_in_field_of_view;
757
                } else {
758
                        return player_visibility_state::visible_not_in_field_of_view;
759
                }
760
        } else {
761
                return player_visibility_state::no_line_of_sight;
762
        }
763
}
764
 
765
// ------------------------------------------------------------------------------------------------------------------
766
//      Return 1 if animates, else return 0
767
static int do_silly_animation(object &objp)
768
{
769
        int                             robot_type, gun_num, robot_state;
770
        polyobj_info    *const pobj_info = &objp.rtype.pobj_info;
771
        auto &aip = objp.ctype.ai_info;
772
        int                             num_guns, at_goal;
773
        int                             attack_type;
774
        int                             flinch_attack_scale = 1;
775
 
776
        robot_type = get_robot_id(objp);
777
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
778
        auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
779
        auto &robptr = Robot_info[robot_type];
780
        num_guns = robptr.n_guns;
781
        attack_type = robptr.attack_type;
782
 
783
        if (num_guns == 0) {
784
                return 0;
785
        }
786
 
787
        //      This is a hack.  All positions should be based on goal_state, not GOAL_STATE.
788
        robot_state = Mike_to_matt_xlate[aip.GOAL_STATE];
789
        // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
790
 
791
        if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
792
                flinch_attack_scale = Attack_scale;
793
        else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
794
                flinch_attack_scale = Flinch_scale;
795
 
796
        at_goal = 1;
797
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
798
        for (gun_num=0; gun_num <= num_guns; gun_num++) {
799
                const auto &&ras = robot_get_anim_state(Robot_info, Robot_joints, robot_type, gun_num, robot_state);
800
 
801
                auto &ail = aip.ail;
802
                range_for (auto &jr, ras)
803
                {
804
                        unsigned jointnum = jr.jointnum;
805
                        auto &jp = jr.angles;
806
                        vms_angvec      *pobjp = &pobj_info->anim_angles[jointnum];
807
 
808
                        if (jointnum >= Polygon_models[objp.rtype.pobj_info.model_num].n_models) {
809
                                Int3();         // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
810
                                continue;
811
                        }
812
                        auto &goal_angles = ail.goal_angles[jointnum];
813
                        auto &delta_angles = ail.delta_angles[jointnum];
814
                        const auto animate_p = silly_animation_angle(&vms_angvec::p, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
815
                        const auto animate_b = silly_animation_angle(&vms_angvec::b, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
816
                        const auto animate_h = silly_animation_angle(&vms_angvec::h, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
817
                        if (gun_num == 0)
818
                        {
819
                                if (animate_p || animate_b || animate_h)
820
                                        at_goal = 0;
821
                        }
822
                }
823
 
824
                if (at_goal) {
825
                        //ai_static     *aip = &objp->ctype.ai_info;
826
                        ail.achieved_state[gun_num] = ail.goal_state[gun_num];
827
                        if (ail.achieved_state[gun_num] == AIS_RECO)
828
                                ail.goal_state[gun_num] = AIS_FIRE;
829
                        else if (ail.achieved_state[gun_num] == AIS_FLIN)
830
                                ail.goal_state[gun_num] = AIS_LOCK;
831
                }
832
        }
833
 
834
        if (at_goal == 1) //num_guns)
835
                aip.CURRENT_STATE = aip.GOAL_STATE;
836
 
837
        return 1;
838
}
839
 
840
//      ------------------------------------------------------------------------------------------
841
//      Move all sub-objects in an object towards their goals.
842
//      Current orientation of object is at:    pobj_info.anim_angles
843
//      Goal orientation of object is at:               ai_info.goal_angles
844
//      Delta orientation of object is at:              ai_info.delta_angles
845
static void ai_frame_animation(object &objp)
846
{
847
        int     joint;
848
        auto &pobj_info = objp.rtype.pobj_info;
849
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
850
        const auto num_joints = Polygon_models[pobj_info.model_num].n_models;
851
        const auto &ail = objp.ctype.ai_info.ail;
852
        for (joint=1; joint<num_joints; joint++) {
853
                auto &curang = pobj_info.anim_angles[joint];
854
                auto &goalang = ail.goal_angles[joint];
855
                auto &deltaang = ail.delta_angles[joint];
856
 
857
                const fix frametime = FrameTime;
858
                frame_animation_angle(&vms_angvec::p, frametime, deltaang, goalang, curang);
859
                frame_animation_angle(&vms_angvec::b, frametime, deltaang, goalang, curang);
860
                frame_animation_angle(&vms_angvec::h, frametime, deltaang, goalang, curang);
861
        }
862
}
863
 
864
// ----------------------------------------------------------------------------------
865
static void set_next_fire_time(object &objp, ai_local &ailp, const robot_info &robptr, const unsigned gun_num)
866
{
867
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
868
#if defined(DXX_BUILD_DESCENT_I)
869
        (void)objp;
870
        (void)gun_num;
871
        ailp.rapidfire_count++;
872
 
873
        if (ailp.rapidfire_count < robptr.rapidfire_count[Difficulty_level]) {
874
                ailp.next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
875
        } else {
876
                ailp.rapidfire_count = 0;
877
                ailp.next_fire = robptr.firing_wait[Difficulty_level];
878
        }
879
#elif defined(DXX_BUILD_DESCENT_II)
880
        //      For guys in snipe mode, they have a 50% shot of getting this shot in free.
881
        if ((gun_num != 0) || (robptr.weapon_type2 == weapon_none))
882
                if ((objp.ctype.ai_info.behavior != ai_behavior::AIB_SNIPE) || (d_rand() > 16384))
883
                        ailp.rapidfire_count++;
884
 
885
        //      Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
886
// --   if (((robptr.weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr.rapidfire_count[Difficulty_level])) {
887
// --           ailp->next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
888
// --   } else {
889
// --           if ((robptr.weapon_type2 == -1) || (gun_num != 0)) {
890
// --                   ailp->rapidfire_count = 0;
891
// --                   ailp->next_fire = robptr.firing_wait[Difficulty_level];
892
// --           } else
893
// --                   ailp->next_fire2 = robptr.firing_wait2[Difficulty_level];
894
// --   }
895
 
896
        if ((gun_num != 0 || robptr.weapon_type2 == weapon_none) && ailp.rapidfire_count < robptr.rapidfire_count[Difficulty_level])
897
        {
898
                ailp.next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
899
        } else {
900
                if ((robptr.weapon_type2 == weapon_none) || (gun_num != 0)) {
901
                        ailp.next_fire = robptr.firing_wait[Difficulty_level];
902
                        if (ailp.rapidfire_count >= robptr.rapidfire_count[Difficulty_level])
903
                                ailp.rapidfire_count = 0;
904
                } else
905
                        ailp.next_fire2 = robptr.firing_wait2[Difficulty_level];
906
        }
907
#endif
908
}
909
 
910
// ----------------------------------------------------------------------------------
911
//      When some robots collide with the player, they attack.
912
//      If player is cloaked, then robot probably didn't actually collide, deal with that here.
913
void do_ai_robot_hit_attack(const vmobjptridx_t robot, const vmobjptridx_t playerobj, const vms_vector &collision_point)
914
{
915
        ai_local &ailp = robot->ctype.ai_info.ail;
916
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
917
        auto &robptr = Robot_info[get_robot_id(robot)];
918
 
919
//#ifndef NDEBUG
920
        if (cheats.robotfiringsuspended)
921
                return;
922
//#endif
923
 
924
        //      If player is dead, stop firing.
925
        object &plrobj = *playerobj;
926
        if (plrobj.type == OBJ_GHOST)
927
                return;
928
 
929
        if (robptr.attack_type == 1) {
930
                if (ready_to_fire_weapon1(ailp, 0)) {
931
                        auto &player_info = plrobj.ctype.player_info;
932
                        if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
933
                                if (vm_vec_dist_quick(plrobj.pos, robot->pos) < robot->size + plrobj.size + F1_0*2)
934
                                {
935
                                        collide_player_and_nasty_robot( playerobj, robot, collision_point );
936
#if defined(DXX_BUILD_DESCENT_II)
937
                                        auto &energy = player_info.energy;
938
                                        if (robptr.energy_drain && energy) {
939
                                                energy -= robptr.energy_drain * F1_0;
940
                                                if (energy < 0)
941
                                                        energy = 0;
942
                                        }
943
#endif
944
                                }
945
 
946
                        robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
947
                        set_next_fire_time(robot, ailp, robptr, 1);     //      1 = gun_num: 0 is special (uses next_fire2)
948
                }
949
        }
950
 
951
}
952
 
953
#if defined(DXX_BUILD_DESCENT_II)
954
// --------------------------------------------------------------------------------------------------------------------
955
//      Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
956
static fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
957
{
958
        return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
959
}
960
 
961
static void compute_lead_component(fix vms_vector::*const m, vms_vector &out, const vms_vector &believed_player_pos, const vms_vector &fire_point, const vms_vector &velocity, const fix projected_time)
962
{
963
        out.*m = compute_lead_component(believed_player_pos.*m, fire_point.*m, velocity.*m, projected_time);
964
}
965
 
966
// --------------------------------------------------------------------------------------------------------------------
967
//      Lead the player, returning point to fire at in fire_point.
968
//      Rules:
969
//              Player not cloaked
970
//              Player must be moving at a speed >= MIN_LEAD_SPEED
971
//              Player not farther away than MAX_LEAD_DISTANCE
972
//              dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
973
//              if firing a matter weapon, less leading, based on skill level.
974
static int lead_player(const object_base &objp, const vms_vector &fire_point, const vms_vector &believed_player_pos, int gun_num, vms_vector &fire_vec)
975
{
976
        const auto &plrobj = *ConsoleObject;
977
        if (plrobj.ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
978
                return 0;
979
 
980
        const auto &velocity = plrobj.mtype.phys_info.velocity;
981
        auto player_movement_dir = velocity;
982
        const fix player_speed = vm_vec_normalize_quick(player_movement_dir);
983
 
984
        if (player_speed < MIN_LEAD_SPEED)
985
                return 0;
986
 
987
        auto vec_to_player = vm_vec_sub(believed_player_pos, fire_point);
988
        const fix dist_to_player = vm_vec_normalize_quick(vec_to_player);
989
        if (dist_to_player > MAX_LEAD_DISTANCE)
990
                return 0;
991
 
992
        const fix dot = vm_vec_dot(vec_to_player, player_movement_dir);
993
 
994
        if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
995
                return 0;
996
 
997
        //      Looks like it might be worth trying to lead the player.
998
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
999
        const auto weapon_type = get_robot_weapon(Robot_info[get_robot_id(objp)], gun_num);
1000
 
1001
        const weapon_info *const wptr = &Weapon_info[weapon_type];
1002
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1003
        fix max_weapon_speed = wptr->speed[Difficulty_level];
1004
        if (max_weapon_speed < F1_0)
1005
                return 0;
1006
 
1007
        //      Matter weapons:
1008
        //      At Rookie or Trainee, don't lead at all.
1009
        //      At higher skill levels, don't lead as well.  Accomplish this by screwing up max_weapon_speed.
1010
        if (wptr->matter)
1011
        {
1012
                if (Difficulty_level <= 1)
1013
                        return 0;
1014
                else
1015
                        max_weapon_speed *= (NDL-Difficulty_level);
1016
        }
1017
 
1018
        const fix projected_time = fixdiv(dist_to_player, max_weapon_speed);
1019
 
1020
        compute_lead_component(&vms_vector::x, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
1021
        compute_lead_component(&vms_vector::y, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
1022
        compute_lead_component(&vms_vector::z, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
1023
 
1024
        vm_vec_normalize_quick(fire_vec);
1025
 
1026
        Assert(vm_vec_dot(fire_vec, objp.orient.fvec) < 3*F1_0/2);
1027
 
1028
        //      Make sure not firing at especially strange angle.  If so, try to correct.  If still bad, give up after one try.
1029
        if (vm_vec_dot(fire_vec, objp.orient.fvec) < F1_0/2) {
1030
                vm_vec_add2(fire_vec, vec_to_player);
1031
                vm_vec_scale(fire_vec, F1_0/2);
1032
                if (vm_vec_dot(fire_vec, objp.orient.fvec) < F1_0/2) {
1033
                        return 0;
1034
                }
1035
        }
1036
 
1037
        return 1;
1038
}
1039
#endif
1040
 
1041
// --------------------------------------------------------------------------------------------------------------------
1042
//      Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
1043
//      center of the robot will not fire right at the player.  We need to aim the guns at the player.  Barring that, we cheat.
1044
//      When this routine is complete, the parameter vec_to_player should not be necessary.
1045
static void ai_fire_laser_at_player(const d_level_shared_segment_state &LevelSharedSegmentState, const vmobjptridx_t obj, const player_info &player_info, const vms_vector &fire_point, const int gun_num
1046
#if defined(DXX_BUILD_DESCENT_II)
1047
                                                                        , const vms_vector &believed_player_pos
1048
#endif
1049
                                                                        )
1050
{
1051
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1052
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1053
        const auto powerup_flags = player_info.powerup_flags;
1054
        ai_local &ailp = obj->ctype.ai_info.ail;
1055
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1056
        auto &robptr = Robot_info[get_robot_id(obj)];
1057
        vms_vector      fire_vec;
1058
        vms_vector      bpp_diff;
1059
 
1060
        Assert(robptr.attack_type == 0);        //      We should never be coming here for the green guy, as he has no laser!
1061
 
1062
        if (cheats.robotfiringsuspended)
1063
                return;
1064
 
1065
        if (obj->control_type == CT_MORPH)
1066
                return;
1067
 
1068
        //      If player is exploded, stop firing.
1069
        if (Player_dead_state == player_dead_state::exploded)
1070
                return;
1071
 
1072
#if defined(DXX_BUILD_DESCENT_II)
1073
        //      If this robot is only awake because a camera woke it up, don't fire.
1074
        if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
1075
                return;
1076
 
1077
        if (obj->ctype.ai_info.dying_start_time)
1078
                return;         //      No firing while in death roll.
1079
 
1080
        //      Don't let the boss fire while in death roll.  Sorry, this is the easiest way to do this.
1081
        //      If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
1082
        if (BossUniqueState.Boss_dying_start_time && Robot_info[get_robot_id(obj)].boss_flag)
1083
                return;
1084
#endif
1085
 
1086
        //      If player is cloaked, maybe don't fire based on how long cloaked and randomness.
1087
        if (powerup_flags & PLAYER_FLAGS_CLOAKED) {
1088
                fix64   cloak_time = Ai_cloak_info[static_cast<imobjptridx_t::index_type>(obj) % MAX_AI_CLOAK_INFO].last_time;
1089
 
1090
                if (GameTime64 - cloak_time > CLOAK_TIME_MAX/4)
1091
                        if (d_rand() > fixdiv(GameTime64 - cloak_time, CLOAK_TIME_MAX)/2) {
1092
                                set_next_fire_time(obj, ailp, robptr, gun_num);
1093
                                return;
1094
                        }
1095
        }
1096
 
1097
#if defined(DXX_BUILD_DESCENT_I)
1098
        (void)LevelSharedSegmentState;
1099
        //      Set position to fire at based on difficulty level.
1100
        bpp_diff.x = Believed_player_pos.x + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
1101
        bpp_diff.y = Believed_player_pos.y + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
1102
        bpp_diff.z = Believed_player_pos.z + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
1103
 
1104
        //      Half the time fire at the player, half the time lead the player.
1105
        if (d_rand() > 16384) {
1106
 
1107
                vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
1108
 
1109
        } else {
1110
                // If player is not moving, fire right at him!
1111
                //      Note: If the robot fires in the direction of its forward vector, this is bad because the weapon does not
1112
                //      come out from the center of the robot; it comes out from the side.  So it is common for the weapon to miss
1113
                //      its target.  Ideally, we want to point the guns at the player.  For now, just fire right at the player.
1114
                {
1115
                        vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
1116
                // Player is moving.  Determine where the player will be at the end of the next frame if he doesn't change his
1117
                //      behavior.  Fire at exactly that point.  This isn't exactly what you want because it will probably take the laser
1118
                //      a different amount of time to get there, since it will probably be a different distance from the player.
1119
                //      So, that's why we write games, instead of guiding missiles...
1120
                }
1121
        }
1122
#elif defined(DXX_BUILD_DESCENT_II)
1123
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1124
        auto &vcwallptr = Walls.vcptr;
1125
        //      Handle problem of a robot firing through a wall because its gun tip is on the other
1126
        //      side of the wall than the robot's center.  For speed reasons, we normally only compute
1127
        //      the vector from the gun point to the player.  But we need to know whether the gun point
1128
        //      is separated from the robot's center by a wall.  If so, don't fire!
1129
        if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
1130
                //      Well, the gun point is in a different segment than the robot's center.
1131
                //      This is almost always ok, but it is not ok if something solid is in between.
1132
                //      See if these segments are connected, which should almost always be the case.
1133
                auto &Segments = LevelSharedSegmentState.get_segments();
1134
                const auto &&csegp = Segments.vcptridx(obj->segnum);
1135
                const auto &&gun_segnum = find_point_seg(LevelSharedSegmentState, fire_point, csegp);
1136
                const auto conn_side = find_connect_side(gun_segnum, csegp);
1137
                if (conn_side != side_none)
1138
                {
1139
                        //      They are connected via conn_side in segment obj->segnum.
1140
                        //      See if they are unobstructed.
1141
                        if (!(WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, csegp, conn_side) & WID_FLY_FLAG))
1142
                        {
1143
                                //      Can't fly through, so don't let this bot fire through!
1144
                                return;
1145
                        }
1146
                } else {
1147
                        //      Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
1148
                        fvi_query       fq;
1149
                        fvi_info                hit_data;
1150
                        int                     fate;
1151
 
1152
                        fq.startseg                             = obj->segnum;
1153
                        fq.p0                                           = &obj->pos;
1154
                        fq.p1                                           = &fire_point;
1155
                        fq.rad                                  = 0;
1156
                        fq.thisobjnum                   = obj;
1157
                        fq.ignore_obj_list.first = nullptr;
1158
                        fq.flags                                        = FQ_TRANSWALL;
1159
 
1160
                        fate = find_vector_intersection(fq, hit_data);
1161
                        if (fate != HIT_NONE) {
1162
                                Int3();         //      This bot's gun is poking through a wall, so don't fire.
1163
                                move_towards_segment_center(LevelSharedSegmentState, obj);              //      And decrease chances it will happen again.
1164
                                return;
1165
                        }
1166
                }
1167
        }
1168
 
1169
        //      Set position to fire at based on difficulty level and robot's aiming ability
1170
        fix aim = FIRE_K*F1_0 - (FIRE_K-1)*(robptr.aim << 8);   //      F1_0 in bitmaps.tbl = same as used to be.  Worst is 50% more error.
1171
 
1172
        //      Robots aim more poorly during seismic disturbance.
1173
        if (const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude)
1174
        {
1175
                fix     temp;
1176
                temp = F1_0 - abs(Seismic_tremor_magnitude);
1177
                if (temp < F1_0/2)
1178
                        temp = F1_0/2;
1179
 
1180
                aim = fixmul(aim, temp);
1181
        }
1182
 
1183
        //      Lead the player half the time.
1184
        //      Note that when leading the player, aim is perfect.  This is probably acceptable since leading is so hacked in.
1185
        //      Problem is all robots will lead equally badly.
1186
        if (d_rand() < 16384) {
1187
                if (lead_player(obj, fire_point, believed_player_pos, gun_num, fire_vec))               //      Stuff direction to fire at in fire_point.
1188
                        goto player_led;
1189
        }
1190
 
1191
        {
1192
        fix dot = 0;
1193
        unsigned count = 0;                     //      Don't want to sit in this loop forever...
1194
        while ((count < 4) && (dot < F1_0/4)) {
1195
                bpp_diff.x = believed_player_pos.x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1196
                bpp_diff.y = believed_player_pos.y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1197
                bpp_diff.z = believed_player_pos.z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1198
 
1199
                vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
1200
                dot = vm_vec_dot(obj->orient.fvec, fire_vec);
1201
                count++;
1202
        }
1203
        }
1204
player_led: ;
1205
#endif
1206
 
1207
        const auto weapon_type = get_robot_weapon(robptr, gun_num);
1208
 
1209
        Laser_create_new_easy( fire_vec, fire_point, obj, weapon_type, 1);
1210
 
1211
        if (Game_mode & GM_MULTI)
1212
        {
1213
                ai_multi_send_robot_position(obj, -1);
1214
                multi_send_robot_fire(obj, obj->ctype.ai_info.CURRENT_GUN, fire_vec);
1215
        }
1216
 
1217
        create_awareness_event(obj, player_awareness_type_t::PA_NEARBY_ROBOT_FIRED, LevelUniqueRobotAwarenessState);
1218
 
1219
        set_next_fire_time(obj, ailp, robptr, gun_num);
1220
 
1221
        //      If the boss fired, allow him to teleport very soon (right after firing, cool!), pending other factors.
1222
        if (robptr.boss_flag == BOSS_D1 || robptr.boss_flag == BOSS_SUPER)
1223
                BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 2;
1224
}
1225
 
1226
// --------------------------------------------------------------------------------------------------------------------
1227
//      vec_goal must be normalized, or close to it.
1228
//      if dot_based set, then speed is based on direction of movement relative to heading
1229
static void move_towards_vector(object_base &objp, const vms_vector &vec_goal, int dot_based)
1230
{
1231
        auto &velocity = objp.mtype.phys_info.velocity;
1232
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1233
        auto &robptr = Robot_info[get_robot_id(objp)];
1234
 
1235
        //      Trying to move towards player.  If forward vector much different than velocity vector,
1236
        //      bash velocity vector twice as much towards player as usual.
1237
 
1238
        const auto vel = vm_vec_normalized_quick(velocity);
1239
        fix dot = vm_vec_dot(vel, objp.orient.fvec);
1240
 
1241
#if defined(DXX_BUILD_DESCENT_I)
1242
        dot_based = 1;
1243
#elif defined(DXX_BUILD_DESCENT_II)
1244
        if (robot_is_thief(robptr))
1245
                dot = (F1_0+dot)/2;
1246
#endif
1247
 
1248
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1249
        if (dot_based && (dot < 3*F1_0/4)) {
1250
                //      This funny code is supposed to slow down the robot and move his velocity towards his direction
1251
                //      more quickly than the general code
1252
                const fix frametime32 = FrameTime * 32;
1253
                move_toward_vector_component_assign(&vms_vector::x, vec_goal, frametime32, velocity);
1254
                move_toward_vector_component_assign(&vms_vector::y, vec_goal, frametime32, velocity);
1255
                move_toward_vector_component_assign(&vms_vector::z, vec_goal, frametime32, velocity);
1256
        } else {
1257
                const fix frametime64 = FrameTime * 64;
1258
                const fix difficulty_scale = Difficulty_level + 5;
1259
                move_toward_vector_component_add(&vms_vector::x, vec_goal, frametime64, difficulty_scale, velocity);
1260
                move_toward_vector_component_add(&vms_vector::y, vec_goal, frametime64, difficulty_scale, velocity);
1261
                move_toward_vector_component_add(&vms_vector::z, vec_goal, frametime64, difficulty_scale, velocity);
1262
        }
1263
 
1264
        const auto speed = vm_vec_mag_quick(velocity);
1265
        fix max_speed = robptr.max_speed[Difficulty_level];
1266
 
1267
        //      Green guy attacks twice as fast as he moves away.
1268
#if defined(DXX_BUILD_DESCENT_I)
1269
        if (robptr.attack_type == 1)
1270
#elif defined(DXX_BUILD_DESCENT_II)
1271
        if (robptr.attack_type == 1 || robot_is_thief(robptr) || robptr.kamikaze)
1272
#endif
1273
                max_speed *= 2;
1274
 
1275
        if (speed > max_speed) {
1276
                velocity.x = (velocity.x * 3) / 4;
1277
                velocity.y = (velocity.y * 3) / 4;
1278
                velocity.z = (velocity.z * 3) / 4;
1279
        }
1280
}
1281
 
1282
// --------------------------------------------------------------------------------------------------------------------
1283
#if defined(DXX_BUILD_DESCENT_I)
1284
static
1285
#endif
1286
void move_towards_player(object &objp, const vms_vector &vec_to_player)
1287
//      vec_to_player must be normalized, or close to it.
1288
{
1289
        move_towards_vector(objp, vec_to_player, 1);
1290
}
1291
 
1292
// --------------------------------------------------------------------------------------------------------------------
1293
//      I am ashamed of this: fast_flag == -1 means normal slide about.  fast_flag = 0 means no evasion.
1294
static void move_around_player(const vmobjptridx_t objp, const player_flags powerup_flags, const vms_vector &vec_to_player, int fast_flag)
1295
{
1296
        physics_info    *pptr = &objp->mtype.phys_info;
1297
        vms_vector              evade_vector;
1298
 
1299
        if (fast_flag == 0)
1300
                return;
1301
 
1302
        const unsigned dir = ((objp) ^ ((d_tick_count + 3*(objp)) >> 5)) & 3;
1303
        switch (dir) {
1304
                case 0:
1305
                        evade_vector.x = vec_to_player.z;
1306
                        evade_vector.y = vec_to_player.y;
1307
                        evade_vector.z = -vec_to_player.x;
1308
                        break;
1309
                case 1:
1310
                        evade_vector.x = -vec_to_player.z;
1311
                        evade_vector.y = vec_to_player.y;
1312
                        evade_vector.z = vec_to_player.x;
1313
                        break;
1314
                case 2:
1315
                        evade_vector.x = -vec_to_player.y;
1316
                        evade_vector.y = vec_to_player.x;
1317
                        evade_vector.z = vec_to_player.z;
1318
                        break;
1319
                case 3:
1320
                        evade_vector.x = vec_to_player.y;
1321
                        evade_vector.y = -vec_to_player.x;
1322
                        evade_vector.z = vec_to_player.z;
1323
                        break;
1324
                default:
1325
                        throw std::runtime_error("move_around_player: bad case");
1326
        }
1327
        const auto frametime32 = FrameTime * 32;
1328
        evade_vector.x = fixmul(evade_vector.x, frametime32);
1329
        evade_vector.y = fixmul(evade_vector.y, frametime32);
1330
        evade_vector.z = fixmul(evade_vector.z, frametime32);
1331
 
1332
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1333
        auto &robptr = Robot_info[get_robot_id(objp)];
1334
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1335
        //      Note: -1 means normal circling about the player.  > 0 means fast evasion.
1336
        if (fast_flag > 0) {
1337
                fix     dot;
1338
 
1339
                //      Only take evasive action if looking at player.
1340
                //      Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
1341
 
1342
                dot = vm_vec_dot(vec_to_player, objp->orient.fvec);
1343
                if (dot > robptr.field_of_view[Difficulty_level] && !(powerup_flags & PLAYER_FLAGS_CLOAKED)) {
1344
                        fix     damage_scale;
1345
 
1346
                        if (!robptr.strength)
1347
                                damage_scale = F1_0;
1348
                        else
1349
                                damage_scale = fixdiv(objp->shields, robptr.strength);
1350
                        if (damage_scale > F1_0)
1351
                                damage_scale = F1_0;            //      Just in case...
1352
                        else if (damage_scale < 0)
1353
                                damage_scale = 0;                       //      Just in case...
1354
 
1355
                        vm_vec_scale(evade_vector, i2f(fast_flag) + damage_scale);
1356
                }
1357
        }
1358
 
1359
        pptr->velocity.x += evade_vector.x;
1360
        pptr->velocity.y += evade_vector.y;
1361
        pptr->velocity.z += evade_vector.z;
1362
 
1363
        const auto speed = vm_vec_mag_quick(pptr->velocity);
1364
        if (speed > robptr.max_speed[Difficulty_level]) {
1365
                pptr->velocity.x = (pptr->velocity.x*3)/4;
1366
                pptr->velocity.y = (pptr->velocity.y*3)/4;
1367
                pptr->velocity.z = (pptr->velocity.z*3)/4;
1368
        }
1369
}
1370
 
1371
// --------------------------------------------------------------------------------------------------------------------
1372
static void move_away_from_player(const vmobjptridx_t objp, const vms_vector &vec_to_player, int attack_type)
1373
{
1374
        physics_info    *pptr = &objp->mtype.phys_info;
1375
 
1376
        const auto frametime = FrameTime;
1377
        pptr->velocity.x -= fixmul(vec_to_player.x, frametime*16);
1378
        pptr->velocity.y -= fixmul(vec_to_player.y, frametime*16);
1379
        pptr->velocity.z -= fixmul(vec_to_player.z, frametime*16);
1380
 
1381
        if (attack_type) {
1382
                //      Get value in 0..3 to choose evasion direction.
1383
                const int objref = objp ^ ((d_tick_count + 3 * objp) >> 5);
1384
                vm_vec_scale_add2(pptr->velocity, (objref & 2) ? objp->orient.rvec : objp->orient.uvec, ((objref & 1) ? -frametime : frametime) << 5);
1385
        }
1386
 
1387
 
1388
        auto speed = vm_vec_mag_quick(pptr->velocity);
1389
 
1390
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1391
        auto &robptr = Robot_info[get_robot_id(objp)];
1392
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1393
        if (speed > robptr.max_speed[Difficulty_level])
1394
        {
1395
                pptr->velocity.x = (pptr->velocity.x*3)/4;
1396
                pptr->velocity.y = (pptr->velocity.y*3)/4;
1397
                pptr->velocity.z = (pptr->velocity.z*3)/4;
1398
        }
1399
 
1400
}
1401
 
1402
// --------------------------------------------------------------------------------------------------------------------
1403
//      Move towards, away_from or around player.
1404
//      Also deals with evasion.
1405
//      If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
1406
static void ai_move_relative_to_player(const vmobjptridx_t objp, ai_local &ailp, const fix dist_to_player, const fix circle_distance, const int evade_only, const robot_to_player_visibility_state &player_visibility, const player_info &player_info)
1407
{
1408
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1409
        auto &vec_to_player = player_visibility.vec_to_player;
1410
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1411
        auto &robptr = Robot_info[get_robot_id(objp)];
1412
 
1413
        //      See if should take avoidance.
1414
 
1415
        // New way, green guys don't evade:     if ((robptr.attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1))
1416
        if (objp->ctype.ai_info.danger_laser_num != object_none) {
1417
                const auto &&dobjp = objp.absolute_sibling(objp->ctype.ai_info.danger_laser_num);
1418
 
1419
                if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
1420
                        vms_vector      laser_fvec;
1421
 
1422
                        const fix field_of_view = robptr.field_of_view[Difficulty_level];
1423
 
1424
                        auto vec_to_laser = vm_vec_sub(dobjp->pos, objp->pos);
1425
                        auto dist_to_laser = vm_vec_normalize_quick(vec_to_laser);
1426
                        const fix dot = vm_vec_dot(vec_to_laser, objp->orient.fvec);
1427
 
1428
                        if (dot > field_of_view || robot_is_companion(robptr))
1429
                        {
1430
                                fix                     laser_robot_dot;
1431
 
1432
                                //      The laser is seen by the robot, see if it might hit the robot.
1433
                                //      Get the laser's direction.  If it's a polyobj, it can be gotten cheaply from the orientation matrix.
1434
                                if (dobjp->render_type == RT_POLYOBJ)
1435
                                        laser_fvec = dobjp->orient.fvec;
1436
                                else {          //      Not a polyobj, get velocity and normalize.
1437
                                        laser_fvec = vm_vec_normalized_quick(dobjp->mtype.phys_info.velocity);  //dobjp->orient.fvec;
1438
                                }
1439
                                const auto laser_vec_to_robot = vm_vec_normalized_quick(vm_vec_sub(objp->pos, dobjp->pos));
1440
                                laser_robot_dot = vm_vec_dot(laser_fvec, laser_vec_to_robot);
1441
 
1442
                                if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
1443
                                        int     evade_speed;
1444
 
1445
                                        ai_evaded = 1;
1446
                                        evade_speed = robptr.evade_speed[Difficulty_level];
1447
                                        move_around_player(objp, player_info.powerup_flags, vec_to_player, evade_speed);
1448
                                }
1449
                        }
1450
                        return;
1451
                }
1452
        }
1453
 
1454
        //      If only allowed to do evade code, then done.
1455
        //      Hmm, perhaps brilliant insight.  If want claw-type guys to keep coming, don't return here after evasion.
1456
        if (!robptr.attack_type && !robot_is_thief(robptr) && evade_only)
1457
                return;
1458
 
1459
        //      If we fall out of above, then no object to be avoided.
1460
        objp->ctype.ai_info.danger_laser_num = object_none;
1461
 
1462
        //      Green guy selects move around/towards/away based on firing time, not distance.
1463
        if (robptr.attack_type == 1) {
1464
                if ((!ready_to_fire_weapon1(ailp, robptr.firing_wait[Difficulty_level]/4) && dist_to_player < F1_0*30) ||
1465
                        Player_dead_state != player_dead_state::no)
1466
                {
1467
                        //      1/4 of time, move around player, 3/4 of time, move away from player
1468
                        if (d_rand() < 8192) {
1469
                                move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
1470
                        } else {
1471
                                move_away_from_player(objp, vec_to_player, 1);
1472
                        }
1473
                } else {
1474
                        move_towards_player(objp, vec_to_player);
1475
                }
1476
        }
1477
        else if (robot_is_thief(robptr))
1478
        {
1479
                move_towards_player(objp, vec_to_player);
1480
        }
1481
        else {
1482
#if defined(DXX_BUILD_DESCENT_I)
1483
                if (dist_to_player < circle_distance)
1484
                        move_away_from_player(objp, vec_to_player, 0);
1485
                else if (dist_to_player < circle_distance*2)
1486
                        move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
1487
                else
1488
                        move_towards_player(objp, vec_to_player);
1489
#elif defined(DXX_BUILD_DESCENT_II)
1490
                int     objval = ((objp) & 0x0f) ^ 0x0a;
1491
 
1492
                //      Changes here by MK, 12/29/95.  Trying to get rid of endless circling around bots in a large room.
1493
                if (robptr.kamikaze) {
1494
                        move_towards_player(objp, vec_to_player);
1495
                } else if (dist_to_player < circle_distance)
1496
                        move_away_from_player(objp, vec_to_player, 0);
1497
                else if ((dist_to_player < (3+objval)*circle_distance/2) && !ready_to_fire_weapon1(ailp, -F1_0)) {
1498
                        move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
1499
                } else {
1500
                        if (ready_to_fire_weapon1(ailp, -(F1_0 + (objval << 12))) && player_is_visible(player_visibility.visibility))
1501
                        {
1502
                                //      Usually move away, but sometimes move around player.
1503
                                if ((((GameTime64 >> 18) & 0x0f) ^ objval) > 4) {
1504
                                        move_away_from_player(objp, vec_to_player, 0);
1505
                                } else {
1506
                                        move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
1507
                                }
1508
                        } else
1509
                                move_towards_player(objp, vec_to_player);
1510
                }
1511
#endif
1512
        }
1513
}
1514
 
1515
}
1516
 
1517
namespace dcx {
1518
 
1519
// --------------------------------------------------------------------------------------------------------------------
1520
//      Compute a somewhat random, normalized vector.
1521
void make_random_vector(vms_vector &vec)
1522
{
1523
        vec.x = (d_rand() - 16384) | 1; // make sure we don't create null vector
1524
        vec.y = d_rand() - 16384;
1525
        vec.z = d_rand() - 16384;
1526
        vm_vec_normalize_quick(vec);
1527
}
1528
 
1529
}
1530
 
1531
namespace dsx {
1532
 
1533
//      -------------------------------------------------------------------------------------------------------------------
1534
static void do_firing_stuff(object &obj, const player_flags powerup_flags, const robot_to_player_visibility_state &player_visibility)
1535
{
1536
#if defined(DXX_BUILD_DESCENT_I)
1537
        if (player_is_visible(player_visibility.visibility))
1538
#elif defined(DXX_BUILD_DESCENT_II)
1539
        if (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD || player_is_visible(player_visibility.visibility))
1540
#endif
1541
        {
1542
                //      Now, if in robot's field of view, lock onto player
1543
                const fix dot = vm_vec_dot(obj.orient.fvec, player_visibility.vec_to_player);
1544
                if ((dot >= 7*F1_0/8) || (powerup_flags & PLAYER_FLAGS_CLOAKED)) {
1545
                        ai_static *const aip = &obj.ctype.ai_info;
1546
                        ai_local *const ailp = &obj.ctype.ai_info.ail;
1547
 
1548
                        switch (aip->GOAL_STATE) {
1549
                                case AIS_NONE:
1550
                                case AIS_REST:
1551
                                case AIS_SRCH:
1552
                                case AIS_LOCK:
1553
                                        aip->GOAL_STATE = AIS_FIRE;
1554
                                        if (ailp->player_awareness_type <= player_awareness_type_t::PA_NEARBY_ROBOT_FIRED) {
1555
                                                ailp->player_awareness_type = player_awareness_type_t::PA_NEARBY_ROBOT_FIRED;
1556
                                                ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1557
                                        }
1558
                                        break;
1559
                        }
1560
                } else if (dot >= F1_0/2) {
1561
                        ai_static *const aip = &obj.ctype.ai_info;
1562
                        switch (aip->GOAL_STATE) {
1563
                                case AIS_NONE:
1564
                                case AIS_REST:
1565
                                case AIS_SRCH:
1566
                                        aip->GOAL_STATE = AIS_LOCK;
1567
                                        break;
1568
                        }
1569
                }
1570
        }
1571
}
1572
 
1573
// --------------------------------------------------------------------------------------------------------------------
1574
//      If a hiding robot gets bumped or hit, he decides to find another hiding place.
1575
void do_ai_robot_hit(const vmobjptridx_t objp, player_awareness_type_t type)
1576
{
1577
        if (objp->control_type == CT_AI) {
1578
                if (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION || type == player_awareness_type_t::PA_PLAYER_COLLISION)
1579
                        switch (objp->ctype.ai_info.behavior) {
1580
#if defined(DXX_BUILD_DESCENT_I)
1581
                                case ai_behavior::AIB_HIDE:
1582
                                        objp->ctype.ai_info.SUBMODE = AISM_GOHIDE;
1583
                                        break;
1584
                                case ai_behavior::AIB_STILL:
1585
                                        // objp->ctype.ai_info.ail.mode = ai_mode::AIM_CHASE_OBJECT; // NOTE: Should be triggered but causes unwanted movements with bosses. I leave this here for future reference.
1586
                                        break;
1587
                                case ai_behavior::AIB_FOLLOW_PATH:
1588
#elif defined(DXX_BUILD_DESCENT_II)
1589
                                case ai_behavior::AIB_STILL:
1590
                                {
1591
                                        int     r;
1592
 
1593
                                        r = d_rand();
1594
                                        //      1/8 time, charge player, 1/4 time create path, rest of time, do nothing
1595
                                        ai_local                *ailp = &objp->ctype.ai_info.ail;
1596
                                        if (r < 4096) {
1597
                                                create_path_to_believed_player_segment(objp, 10, create_path_safety_flag::safe);
1598
                                                objp->ctype.ai_info.behavior = ai_behavior::AIB_STATION;
1599
                                                objp->ctype.ai_info.hide_segment = objp->segnum;
1600
                                                ailp->mode = ai_mode::AIM_CHASE_OBJECT;
1601
                                        } else if (r < 4096+8192) {
1602
                                                create_n_segment_path(objp, d_rand()/8192 + 2, segment_none);
1603
                                                ailp->mode = ai_mode::AIM_FOLLOW_PATH;
1604
                                        }
1605
                                        break;
1606
                                }
1607
                                case ai_behavior::AIB_BEHIND:
1608
                                case ai_behavior::AIB_SNIPE:
1609
                                case ai_behavior::AIB_FOLLOW:
1610
#endif
1611
                                case ai_behavior::AIB_NORMAL:
1612
                                case ai_behavior::AIB_RUN_FROM:
1613
                                case ai_behavior::AIB_STATION:
1614
                                        break;
1615
                        }
1616
        }
1617
}
1618
 
1619
// --------------------------------------------------------------------------------------------------------------------
1620
//      Note: This function could be optimized.  Surely player_is_visible_from_object would benefit from the
1621
//      information of a normalized vec_to_player.
1622
//      Return player visibility:
1623
//              0               not visible
1624
//              1               visible, but robot not looking at player (ie, on an unobstructed vector)
1625
//              2               visible and in robot's field of view
1626
//              -1              player is cloaked
1627
//      If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
1628
//      Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
1629
//      and is copied to player_visibility
1630
 
1631
static void compute_vis_and_vec(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_info &player_info, vms_vector &pos, ai_local &ailp, robot_to_player_visibility_state &player_visibility, const robot_info &robptr)
1632
{
1633
        if (player_visibility.initialized)
1634
                return;
1635
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1636
        const auto powerup_flags = player_info.powerup_flags;
1637
                if (powerup_flags & PLAYER_FLAGS_CLOAKED)
1638
                {
1639
                        const unsigned cloak_index = (objp) % MAX_AI_CLOAK_INFO;
1640
                        const fix delta_time = GameTime64 - Ai_cloak_info[cloak_index].last_time;
1641
                        if (delta_time > F1_0*2) {
1642
                                Ai_cloak_info[cloak_index].last_time = GameTime64;
1643
                                vm_vec_scale_add2(Ai_cloak_info[cloak_index].last_position, make_random_vector(), 8 * delta_time);
1644
                        }
1645
 
1646
                        const auto dist = vm_vec_normalized_dir_quick(player_visibility.vec_to_player, Ai_cloak_info[cloak_index].last_position, pos);
1647
                        player_visibility.visibility = player_is_visible_from_object(objp, pos, robptr.field_of_view[Difficulty_level], player_visibility.vec_to_player);
1648
                        if (ailp.next_misc_sound_time < GameTime64 && ready_to_fire_any_weapon(robptr, ailp, F1_0) && dist < F1_0 * 20)
1649
                        {
1650
                                ailp.next_misc_sound_time = GameTime64 + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
1651
                                digi_link_sound_to_pos(robptr.see_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
1652
                        }
1653
                } else {
1654
                        //      Compute expensive stuff -- vec_to_player and player_visibility
1655
                        vm_vec_normalized_dir_quick(player_visibility.vec_to_player, Believed_player_pos, pos);
1656
                        if (player_visibility.vec_to_player.x == 0 && player_visibility.vec_to_player.y == 0 && player_visibility.vec_to_player.z == 0)
1657
                        {
1658
                                player_visibility.vec_to_player.x = F1_0;
1659
                        }
1660
                        player_visibility.visibility = player_is_visible_from_object(objp, pos, robptr.field_of_view[Difficulty_level], player_visibility.vec_to_player);
1661
 
1662
                        //      This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
1663
                        //      see you without killing frame rate.
1664
                        {
1665
                                ai_static       *aip = &objp->ctype.ai_info;
1666
                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && ailp.previous_visibility != player_visibility_state::visible_and_in_field_of_view)
1667
                                if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
1668
                                        aip->GOAL_STATE = AIS_FIRE;
1669
                                        aip->CURRENT_STATE = AIS_FIRE;
1670
                                }
1671
                        }
1672
 
1673
#if defined(DXX_BUILD_DESCENT_I)
1674
                        if (Player_dead_state != player_dead_state::exploded)
1675
#endif
1676
                        if (ailp.previous_visibility != player_visibility.visibility && player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
1677
                        {
1678
                                if (ailp.previous_visibility == player_visibility_state::no_line_of_sight)
1679
                                {
1680
                                        if (ailp.time_player_seen + F1_0/2 < GameTime64)
1681
                                        {
1682
                                                digi_link_sound_to_pos(robptr.see_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
1683
                                                ailp.time_player_sound_attacked = GameTime64;
1684
                                                ailp.next_misc_sound_time = GameTime64 + F1_0 + d_rand()*4;
1685
                                        }
1686
                                }
1687
                                else if (ailp.time_player_sound_attacked + F1_0/4 < GameTime64)
1688
                                {
1689
                                        digi_link_sound_to_pos(robptr.attack_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
1690
                                        ailp.time_player_sound_attacked = GameTime64;
1691
                                }
1692
                        }
1693
 
1694
                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && ailp.next_misc_sound_time < GameTime64)
1695
                        {
1696
                                ailp.next_misc_sound_time = GameTime64 + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
1697
                                digi_link_sound_to_pos(robptr.attack_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
1698
                        }
1699
                        ailp.previous_visibility = player_visibility.visibility;
1700
                }
1701
 
1702
#if defined(DXX_BUILD_DESCENT_II)
1703
                //      @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
1704
                //      act is if robot is looking at player.
1705
                if (ailp.player_awareness_type >= player_awareness_type_t::PA_NEARBY_ROBOT_FIRED)
1706
                        if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view)
1707
                                player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
1708
#endif
1709
 
1710
                if (player_is_visible(player_visibility.visibility))
1711
                {
1712
                        ailp.time_player_seen = GameTime64;
1713
                }
1714
        player_visibility.initialized = 1;
1715
}
1716
 
1717
#if defined(DXX_BUILD_DESCENT_II)
1718
static void compute_buddy_vis_vec(const vmobjptridx_t buddy_obj, const vms_vector &buddy_pos, robot_to_player_visibility_state &player_visibility, const robot_info &robptr)
1719
{
1720
        if (player_visibility.initialized)
1721
                return;
1722
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
1723
        auto &Objects = LevelUniqueObjectState.Objects;
1724
        auto &plr = get_player_controlling_guidebot(BuddyState, Players);
1725
        if (plr.objnum == object_none)
1726
        {
1727
                player_visibility.vec_to_player = {};
1728
                player_visibility.visibility = player_visibility_state::no_line_of_sight;
1729
                player_visibility.initialized = 1;
1730
                return;
1731
        }
1732
        auto &plrobj = *Objects.vcptr(plr.objnum);
1733
        /* Buddy ignores cloaking */
1734
        vm_vec_normalized_dir_quick(player_visibility.vec_to_player, plrobj.pos, buddy_pos);
1735
        if (player_visibility.vec_to_player.x == 0 && player_visibility.vec_to_player.y == 0 && player_visibility.vec_to_player.z == 0)
1736
                player_visibility.vec_to_player.x = F1_0;
1737
 
1738
        fvi_query fq;
1739
        fq.p0 = &buddy_pos;
1740
        fq.startseg = buddy_obj->segnum;
1741
        fq.p1 = &plrobj.pos;
1742
        fq.rad = F1_0/4;
1743
        fq.thisobjnum = buddy_obj;
1744
        fq.ignore_obj_list.first = nullptr;
1745
        fq.flags = FQ_TRANSWALL;
1746
        fvi_info hit_data;
1747
        const auto hit_type = find_vector_intersection(fq, hit_data);
1748
 
1749
        auto &ailp = buddy_obj->ctype.ai_info.ail;
1750
        player_visibility.visibility = (hit_type == HIT_NONE)
1751
                ? ((ailp.time_player_seen = GameTime64, vm_vec_dot(player_visibility.vec_to_player, buddy_obj->orient.fvec) > robptr.field_of_view[GameUniqueState.Difficulty_level])
1752
                   ? player_visibility_state::visible_and_in_field_of_view
1753
                   : player_visibility_state::visible_not_in_field_of_view
1754
                )
1755
                : player_visibility_state::no_line_of_sight;
1756
        ailp.previous_visibility = player_visibility.visibility;
1757
        player_visibility.initialized = 1;
1758
}
1759
#endif
1760
 
1761
// --------------------------------------------------------------------------------------------------------------------
1762
//      Move object one object radii from current position towards segment center.
1763
//      If segment center is nearer than 2 radii, move it to center.
1764
void move_towards_segment_center(const d_level_shared_segment_state &LevelSharedSegmentState, object_base &objp)
1765
{
1766
/* ZICO's change of 20081103:
1767
   Make move to segment center smoother by using move_towards vector.
1768
   Bot's should not jump around and maybe even intersect with each other!
1769
   In case it breaks something what I do not see, yet, old code is still there. */
1770
        const auto segnum = objp.segnum;
1771
        vms_vector      vec_to_center;
1772
 
1773
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1774
        auto &Vertices = LevelSharedVertexState.get_vertices();
1775
        auto &SSegments = LevelSharedSegmentState.get_segments();
1776
        const auto &&segment_center = compute_segment_center(Vertices.vcptr, SSegments.vcptr(segnum));
1777
        vm_vec_normalized_dir_quick(vec_to_center, segment_center, objp.pos);
1778
        move_towards_vector(objp, vec_to_center, 1);
1779
}
1780
 
1781
//      -----------------------------------------------------------------------------------------------------------
1782
//      Return true if door can be flown through by a suitable type robot.
1783
//      Brains, avoid robots, companions can open doors.
1784
//      objp == NULL means treat as buddy.
1785
int ai_door_is_openable(
1786
        const vmobjptr_t objp,
1787
#if defined(DXX_BUILD_DESCENT_II)
1788
        const player_flags powerup_flags,
1789
#endif
1790
        const shared_segment &segp, const unsigned sidenum)
1791
{
1792
        if (!IS_CHILD(segp.children[sidenum]))
1793
                return 0;               //trap -2 (exit side)
1794
 
1795
        const auto wall_num = segp.sides[sidenum].wall_num;
1796
 
1797
        if (wall_num == wall_none)              //if there's no door at all...
1798
                return 0;                               //..then say it can't be opened
1799
 
1800
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1801
        auto &vcwallptr = Walls.vcptr;
1802
        auto &wall = *vcwallptr(wall_num);
1803
        //      The mighty console object can open all doors (for purposes of determining paths).
1804
        if (objp == ConsoleObject) {
1805
                const auto wt = wall.type;
1806
                if (wt == WALL_DOOR)
1807
                {
1808
                        static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
1809
                        return wt;
1810
                }
1811
        }
1812
 
1813
#if defined(DXX_BUILD_DESCENT_I)
1814
        if ((get_robot_id(objp) == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM))
1815
        {
1816
 
1817
                if (wall_num != wall_none)
1818
                {
1819
                        const auto wt = wall.type;
1820
                        if (wt == WALL_DOOR && wall.keys == KEY_NONE && !(wall.flags & WALL_DOOR_LOCKED))
1821
                        {
1822
                                static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
1823
                                return wt;
1824
                        }
1825
                }
1826
        }
1827
#elif defined(DXX_BUILD_DESCENT_II)
1828
        auto &WallAnims = GameSharedState.WallAnims;
1829
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1830
        if (Robot_info[get_robot_id(objp)].companion)
1831
        {
1832
                const auto wt = wall.type;
1833
                if (wall.flags & WALL_BUDDY_PROOF) {
1834
                        if (wt == WALL_DOOR && wall.state == WALL_DOOR_CLOSED)
1835
                                return 0;
1836
                        else if (wt == WALL_CLOSED)
1837
                                return 0;
1838
                        else if (wt == WALL_ILLUSION && !(wall.flags & WALL_ILLUSION_OFF))
1839
                                return 0;
1840
                }
1841
                switch (const auto wall_keys = wall.keys)
1842
                {
1843
                                case KEY_BLUE:
1844
                                case KEY_GOLD:
1845
                                case KEY_RED:
1846
                                {
1847
                                        return powerup_flags & static_cast<PLAYER_FLAG>(wall_keys);
1848
                                }
1849
                        default:
1850
                                break;
1851
                }
1852
 
1853
                if (wt != WALL_DOOR && wt != WALL_CLOSED)
1854
                        return 1;
1855
 
1856
                //      If Buddy is returning to player, don't let him think he can get through triggered doors.
1857
                //      It's only valid to think that if the player is going to get him through.  But if he's
1858
                //      going to the player, the player is probably on the opposite side.
1859
                const ai_mode ailp_mode = objp->ctype.ai_info.ail.mode;
1860
 
1861
                // -- if (Buddy_got_stuck) {
1862
                if (ailp_mode == ai_mode::AIM_GOTO_PLAYER) {
1863
                        if (wt == WALL_BLASTABLE && wall.state != WALL_BLASTED)
1864
                                return 0;
1865
                        if (wt == WALL_CLOSED)
1866
                                return 0;
1867
                        if (wt == WALL_DOOR) {
1868
                                if ((wall.flags & WALL_DOOR_LOCKED) && (wall.state == WALL_DOOR_CLOSED))
1869
                                        return 0;
1870
                        }
1871
                }
1872
                // -- }
1873
 
1874
                if ((ailp_mode != ai_mode::AIM_GOTO_PLAYER) && (wall.controlling_trigger != -1)) {
1875
                        const auto clip_num = wall.clip_num;
1876
                        if (clip_num == -1)
1877
                                return clip_num;
1878
                        else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
1879
                                static_assert(WALL_DOOR_CLOSED == 0, "WALL_DOOR_CLOSED must be zero for this shortcut to work properly.");
1880
                                return wall.state;
1881
                        } else
1882
                                return 1;
1883
                }
1884
 
1885
                if (wt == WALL_DOOR)  {
1886
                                const auto clip_num = wall.clip_num;
1887
 
1888
                                if (clip_num == -1)
1889
                                        return clip_num;
1890
                                //      Buddy allowed to go through secret doors to get to player.
1891
                                else if ((ailp_mode != ai_mode::AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
1892
                                        static_assert(WALL_DOOR_CLOSED == 0, "WALL_DOOR_CLOSED must be zero for this shortcut to work properly.");
1893
                                        return wall.state;
1894
                                } else
1895
                                        return 1;
1896
                }
1897
        } else if ((get_robot_id(objp) == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)) {
1898
                if (wall_num != wall_none)
1899
                {
1900
                        const auto wt = wall.type;
1901
                        if (wt == WALL_DOOR && (wall.keys == KEY_NONE) && !(wall.flags & WALL_DOOR_LOCKED))
1902
                        {
1903
                                static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
1904
                                return wt;
1905
                        }
1906
                        else if (wall.keys != KEY_NONE) {       //      Allow bots to open doors to which player has keys.
1907
                                return powerup_flags & static_cast<PLAYER_FLAG>(wall.keys);
1908
                        }
1909
                }
1910
        }
1911
#endif
1912
        return 0;
1913
}
1914
 
1915
//      -----------------------------------------------------------------------------------------------------------
1916
//      Return side of openable door in segment, if any.  If none, return side_none.
1917
static unsigned openable_doors_in_segment(fvcwallptr &vcwallptr, const shared_segment &segp)
1918
{
1919
#if defined(DXX_BUILD_DESCENT_II)
1920
        auto &WallAnims = GameSharedState.WallAnims;
1921
#endif
1922
        range_for (const auto &&es, enumerate(segp.sides))
1923
        {
1924
                const auto wall_num = es.value.wall_num;
1925
                if (wall_num != wall_none)
1926
                {
1927
                        auto &w = *vcwallptr(wall_num);
1928
                        if (w.type != WALL_DOOR)
1929
                                continue;
1930
                        if (w.keys != KEY_NONE)
1931
                                continue;
1932
                        if (w.state != WALL_DOOR_CLOSED)
1933
                                continue;
1934
                        if (w.flags & WALL_DOOR_LOCKED)
1935
                                continue;
1936
#if defined(DXX_BUILD_DESCENT_II)
1937
                        if (WallAnims[w.clip_num].flags & WCF_HIDDEN)
1938
                                continue;
1939
#endif
1940
                        return es.idx;
1941
                }
1942
        }
1943
        return side_none;
1944
}
1945
 
1946
// --------------------------------------------------------------------------------------------------------------------
1947
//      Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
1948
static int check_object_object_intersection(const vms_vector &pos, fix size, const unique_segment &segp)
1949
{
1950
        auto &Objects = LevelUniqueObjectState.Objects;
1951
        auto &vcobjptridx = Objects.vcptridx;
1952
        //      If this would intersect with another object (only check those in this segment), then try to move.
1953
        range_for (const object_base &curobj, objects_in(segp, vcobjptridx, vcsegptr))
1954
        {
1955
                if (curobj.type == OBJ_PLAYER || curobj.type == OBJ_ROBOT || curobj.type == OBJ_CNTRLCEN)
1956
                {
1957
                        if (vm_vec_dist_quick(pos, curobj.pos) < size + curobj.size)
1958
                                return 1;
1959
                }
1960
        }
1961
        return 0;
1962
}
1963
 
1964
// --------------------------------------------------------------------------------------------------------------------
1965
//      Return objnum if object created, else return -1.
1966
//      If pos == NULL, pick random spot in segment.
1967
static imobjptridx_t create_gated_robot(const d_vclip_array &Vclip, fvcobjptr &vcobjptr, const vmsegptridx_t segp, const unsigned object_id, const vms_vector *const pos)
1968
{
1969
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
1970
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1971
        auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
1972
        auto &Vertices = LevelSharedVertexState.get_vertices();
1973
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
1974
        const auto Gate_interval = GameUniqueState.Boss_gate_interval;
1975
#if defined(DXX_BUILD_DESCENT_I)
1976
        const unsigned maximum_gated_robots = 2*Difficulty_level + 3;
1977
#elif defined(DXX_BUILD_DESCENT_II)
1978
        if (GameTime64 - BossUniqueState.Last_gate_time < Gate_interval)
1979
                return object_none;
1980
        const unsigned maximum_gated_robots = 2*Difficulty_level + 6;
1981
#endif
1982
 
1983
        unsigned count = 0;
1984
        range_for (const auto &&objp, vcobjptr)
1985
        {
1986
                auto &obj = *objp;
1987
                if (obj.type == OBJ_ROBOT)
1988
                        if (obj.matcen_creator == BOSS_GATE_MATCEN_NUM)
1989
                                count++;
1990
        }
1991
 
1992
        if (count > maximum_gated_robots)
1993
        {
1994
                BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
1995
                return object_none;
1996
        }
1997
 
1998
        auto &vcvertptr = Vertices.vcptr;
1999
        const auto object_pos = pos ? *pos : pick_random_point_in_seg(vcvertptr, segp);
2000
 
2001
        //      See if legal to place object here.  If not, move about in segment and try again.
2002
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2003
        auto &robptr = Robot_info[object_id];
2004
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
2005
        const fix objsize = Polygon_models[robptr.model_num].rad;
2006
        if (check_object_object_intersection(object_pos, objsize, segp)) {
2007
                BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
2008
                return object_none;
2009
        }
2010
 
2011
#if defined(DXX_BUILD_DESCENT_I)
2012
        const ai_behavior default_behavior = (object_id == 10) //       This is a toaster guy!
2013
        ? ai_behavior::AIB_RUN_FROM
2014
        : ai_behavior::AIB_NORMAL;
2015
#elif defined(DXX_BUILD_DESCENT_II)
2016
        const ai_behavior default_behavior = robptr.behavior;
2017
#endif
2018
        auto objp = robot_create(object_id, segp, object_pos, &vmd_identity_matrix, objsize, default_behavior);
2019
 
2020
        if ( objp == object_none ) {
2021
                BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
2022
                return object_none;
2023
        }
2024
 
2025
        //Set polygon-object-specific data
2026
 
2027
        objp->rtype.pobj_info.model_num = robptr.model_num;
2028
        objp->rtype.pobj_info.subobj_flags = 0;
2029
 
2030
        //set Physics info
2031
 
2032
        objp->mtype.phys_info.mass = robptr.mass;
2033
        objp->mtype.phys_info.drag = robptr.drag;
2034
 
2035
        objp->mtype.phys_info.flags |= (PF_LEVELLING);
2036
 
2037
        objp->shields = robptr.strength;
2038
        objp->matcen_creator = BOSS_GATE_MATCEN_NUM;    //      flag this robot as having been created by the boss.
2039
 
2040
#if defined(DXX_BUILD_DESCENT_II)
2041
        objp->lifeleft = F1_0*30;       //      Gated in robots only live 30 seconds.
2042
#endif
2043
 
2044
        object_create_explosion(segp, object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
2045
        digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segp, 0, object_pos, 0 , F1_0);
2046
        morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, objp);
2047
 
2048
        BossUniqueState.Last_gate_time = GameTime64;
2049
        ++LevelUniqueObjectState.accumulated_robots;
2050
        ++GameUniqueState.accumulated_robots;
2051
 
2052
        return objp;
2053
}
2054
 
2055
// --------------------------------------------------------------------------------------------------------------------
2056
//      Make object objp gate in a robot.
2057
//      The process of him bringing in a robot takes one second.
2058
//      Then a robot appears somewhere near the player.
2059
//      Return objnum if robot successfully created, else return -1
2060
imobjptridx_t gate_in_robot(const unsigned type, const vmsegptridx_t segnum)
2061
{
2062
        auto &Objects = LevelUniqueObjectState.Objects;
2063
        auto &vcobjptr = Objects.vcptr;
2064
        return create_gated_robot(Vclip, vcobjptr, segnum, type, nullptr);
2065
}
2066
 
2067
static imobjptridx_t gate_in_robot(fvmsegptridx &vmsegptridx, int type)
2068
{
2069
        auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
2070
        auto segnum = Boss_gate_segs[(d_rand() * Boss_gate_segs.size()) >> 15];
2071
        return gate_in_robot(type, vmsegptridx(segnum));
2072
}
2073
 
2074
}
2075
 
2076
namespace dcx {
2077
 
2078
static const shared_segment *boss_intersects_wall(fvcvertptr &vcvertptr, const object_base &boss_objp, const vcsegptridx_t segp)
2079
{
2080
        const auto size = boss_objp.size;
2081
        const auto &&segcenter = compute_segment_center(vcvertptr, segp);
2082
        auto pos = segcenter;
2083
        for (uint_fast32_t posnum = 0;;)
2084
        {
2085
                const auto seg = sphere_intersects_wall(vcvertptr, pos, segp, size).seg;
2086
                if (!seg)
2087
                        return seg;
2088
                if (posnum == segp->verts.size())
2089
                        return seg;
2090
                auto &vertex_pos = *vcvertptr(segp->verts[posnum ++]);
2091
                vm_vec_avg(pos, vertex_pos, segcenter);
2092
        }
2093
}
2094
 
2095
}
2096
 
2097
namespace dsx {
2098
 
2099
#if defined(DXX_BUILD_DESCENT_II)
2100
// --------------------------------------------------------------------------------------------------------------------
2101
//      Create a Buddy bot.
2102
//      This automatically happens when you bring up the Buddy menu in a debug version.
2103
//      It is available as a cheat in a non-debug (release) version.
2104
void create_buddy_bot(void)
2105
{
2106
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
2107
        auto &Vertices = LevelSharedVertexState.get_vertices();
2108
        const auto &&range = enumerate(partial_const_range(LevelSharedRobotInfoState.Robot_info, LevelSharedRobotInfoState.N_robot_types));
2109
        const auto &&predicate = [](const auto &ev) {
2110
                const robot_info &robptr = ev.value;
2111
                return robptr.companion;
2112
        };
2113
        const auto &&it = std::find_if(range.begin(), range.end(), predicate);
2114
        if (!(it != range.end()))
2115
                return;
2116
        const auto &&segp = vmsegptridx(ConsoleObject->segnum);
2117
        auto &vcvertptr = Vertices.vcptr;
2118
        const auto &&object_pos = compute_segment_center(vcvertptr, segp);
2119
        create_morph_robot(segp, object_pos, (*it).idx);
2120
}
2121
#endif
2122
 
2123
// --------------------------------------------------------------------------------------------------------------------
2124
//      Create list of segments boss is allowed to teleport to at imsegptr.
2125
//      Set *num_segs.
2126
//      Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
2127
//      he can reach from his initial position (calls find_connected_distance).
2128
//      If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
2129
//      one_wall_hack added by MK, 10/13/95: A mega-hack!  Set to !0 to ignore the 
2130
static void init_boss_segments(const segment_array &segments, const object &boss_objp, d_level_shared_boss_state::special_segment_array_t &a, const int size_check, int one_wall_hack)
2131
{
2132
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
2133
        auto &Vertices = LevelSharedVertexState.get_vertices();
2134
        constexpr unsigned QUEUE_SIZE = 256;
2135
        auto &vcsegptridx = segments.vcptridx;
2136
        auto &vmsegptr = segments.vmptr;
2137
#if defined(DXX_BUILD_DESCENT_I)
2138
        one_wall_hack = 0;
2139
#endif
2140
 
2141
        a.clear();
2142
#if DXX_USE_EDITOR
2143
        Selected_segs.clear();
2144
#endif
2145
 
2146
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
2147
        auto &vcwallptr = Walls.vcptr;
2148
        {
2149
                int                     head, tail;
2150
                std::array<segnum_t, QUEUE_SIZE> seg_queue;
2151
 
2152
                const auto original_boss_seg = boss_objp.segnum;
2153
                head = 0;
2154
                tail = 0;
2155
                seg_queue[head++] = original_boss_seg;
2156
                auto &vcvertptr = Vertices.vcptr;
2157
 
2158
                if (!size_check || !boss_intersects_wall(vcvertptr, boss_objp, vcsegptridx(original_boss_seg)))
2159
                {
2160
                        a.emplace_back(original_boss_seg);
2161
#if DXX_USE_EDITOR
2162
                        Selected_segs.emplace_back(original_boss_seg);
2163
#endif
2164
                }
2165
 
2166
                visited_segment_bitarray_t visited;
2167
 
2168
                while (tail != head) {
2169
                        const cscusegment segp = *vmsegptr(seg_queue[tail++]);
2170
 
2171
                        tail &= QUEUE_SIZE-1;
2172
 
2173
                        range_for (const auto &&es, enumerate(segp.s.children))
2174
                        {
2175
                                const uint_fast32_t sidenum = es.idx;
2176
                                const auto w = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum);
2177
                                if ((w & WID_FLY_FLAG) || one_wall_hack)
2178
                                {
2179
                                        const auto csegnum = es.value;
2180
#if defined(DXX_BUILD_DESCENT_II)
2181
                                        //      If we get here and w == WID_WALL, then we want to process through this wall, else not.
2182
                                        if (IS_CHILD(csegnum)) {
2183
                                                if (one_wall_hack)
2184
                                                        one_wall_hack--;
2185
                                        } else
2186
                                                continue;
2187
#endif
2188
 
2189
                                        if (auto &&v = visited[csegnum])
2190
                                        {
2191
                                        }
2192
                                        else
2193
                                        {
2194
                                                v = true;
2195
                                                seg_queue[head++] = csegnum;
2196
                                                head &= QUEUE_SIZE-1;
2197
                                                if (head > tail) {
2198
                                                        if (head == tail + QUEUE_SIZE-1)
2199
                                                                Int3(); //      queue overflow.  Make it bigger!
2200
                                                } else
2201
                                                        if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
2202
                                                                Int3(); //      queue overflow.  Make it bigger!
2203
 
2204
                                                if (!size_check || !boss_intersects_wall(vcvertptr, boss_objp, vcsegptridx(csegnum))) {
2205
                                                        a.emplace_back(csegnum);
2206
#if DXX_USE_EDITOR
2207
                                                        Selected_segs.emplace_back(csegnum);
2208
                                                        #endif
2209
                                                        if (a.size() >= a.max_size())
2210
                                                        {
2211
                                                                tail = head;
2212
                                                                break;
2213
                                                        }
2214
                                                }
2215
                                        }
2216
                                }
2217
                        }
2218
                }
2219
                // Last resort - add original seg even if boss doesn't fit in it
2220
                if (a.empty())
2221
                        a.emplace_back(original_boss_seg);
2222
        }
2223
}
2224
 
2225
// --------------------------------------------------------------------------------------------------------------------
2226
static void teleport_boss(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const vms_vector &target_pos)
2227
{
2228
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2229
        auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
2230
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
2231
        segnum_t                        rand_segnum;
2232
        int                     rand_index;
2233
        assert(!Boss_teleport_segs.empty());
2234
        auto &Objects = LevelUniqueObjectState.Objects;
2235
        auto &vmobjptr = Objects.vmptr;
2236
 
2237
        //      Pick a random segment from the list of boss-teleportable-to segments.
2238
        rand_index = (d_rand() * Boss_teleport_segs.size()) >> 15;
2239
        rand_segnum = Boss_teleport_segs[rand_index];
2240
        Assert(rand_segnum <= Highest_segment_index);
2241
 
2242
        if (Game_mode & GM_MULTI)
2243
                multi_send_boss_teleport(objp, rand_segnum);
2244
 
2245
        const auto &&rand_segp = vmsegptridx(rand_segnum);
2246
        auto &Vertices = LevelSharedVertexState.get_vertices();
2247
        auto &vcvertptr = Vertices.vcptr;
2248
        compute_segment_center(vcvertptr, objp->pos, rand_segp);
2249
        obj_relink(vmobjptr, vmsegptr, objp, rand_segp);
2250
 
2251
        BossUniqueState.Last_teleport_time = GameTime64;
2252
 
2253
        //      make boss point right at player
2254
        const auto boss_dir = vm_vec_sub(target_pos, objp->pos);
2255
        vm_vector_2_matrix(objp->orient, boss_dir, nullptr, nullptr);
2256
 
2257
        digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segp, 0, objp->pos, 0, F1_0);
2258
        digi_kill_sound_linked_to_object( objp);
2259
 
2260
        //      After a teleport, boss can fire right away.
2261
        ai_local                *ailp = &objp->ctype.ai_info.ail;
2262
        ailp->next_fire = 0;
2263
#if defined(DXX_BUILD_DESCENT_II)
2264
        ailp->next_fire2 = 0;
2265
#endif
2266
        boss_link_see_sound(objp);
2267
 
2268
}
2269
 
2270
//      ----------------------------------------------------------------------
2271
void start_boss_death_sequence(object &objp)
2272
{
2273
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2274
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2275
        auto &robptr = Robot_info[get_robot_id(objp)];
2276
        if (robptr.boss_flag)
2277
        {
2278
                BossUniqueState.Boss_dying = 1;
2279
                BossUniqueState.Boss_dying_start_time = GameTime64;
2280
        }
2281
 
2282
}
2283
 
2284
//      ----------------------------------------------------------------------------------------------------------
2285
#if defined(DXX_BUILD_DESCENT_I)
2286
static void do_boss_dying_frame(const vmobjptridx_t objp)
2287
{
2288
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2289
        const auto time_boss_dying = GameTime64 - BossUniqueState.Boss_dying_start_time;
2290
        {
2291
                auto &rotvel = objp->mtype.phys_info.rotvel;
2292
                rotvel.x = time_boss_dying / 9;
2293
                rotvel.y = time_boss_dying / 5;
2294
                rotvel.z = time_boss_dying / 7;
2295
        }
2296
 
2297
        if (BossUniqueState.Boss_dying_start_time + BOSS_DEATH_DURATION - BOSS_DEATH_SOUND_DURATION < GameTime64) {
2298
                if (!BossUniqueState.Boss_dying_sound_playing)
2299
                {
2300
                        BossUniqueState.Boss_dying_sound_playing = 1;
2301
                        digi_link_sound_to_object2(SOUND_BOSS_SHARE_DIE, objp, 0, F1_0*4, sound_stack::allow_stacking, vm_distance{F1_0*1024}); //      F1_0*512 means play twice as loud
2302
                } else if (d_rand() < FrameTime*16)
2303
                        create_small_fireball_on_object(objp, (F1_0 + d_rand()) * 8, 0);
2304
        } else if (d_rand() < FrameTime*8)
2305
                create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * 8, 1);
2306
 
2307
        if (BossUniqueState.Boss_dying_start_time + BOSS_DEATH_DURATION < GameTime64 || GameTime64+(F1_0*2) < BossUniqueState.Boss_dying_start_time)
2308
        {
2309
                BossUniqueState.Boss_dying_start_time = GameTime64; // make sure following only happens one time!
2310
                do_controlcen_destroyed_stuff(object_none);
2311
                explode_object(objp, F1_0/4);
2312
                digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
2313
        }
2314
}
2315
 
2316
static int do_any_robot_dying_frame(const vmobjptridx_t)
2317
{
2318
        return 0;
2319
}
2320
#elif defined(DXX_BUILD_DESCENT_II)
2321
//      objp points at a boss.  He was presumably just hit and he's supposed to create a bot at the hit location *pos.
2322
imobjptridx_t boss_spew_robot(const object_base &objp, const vms_vector &pos)
2323
{
2324
        auto &Objects = LevelUniqueObjectState.Objects;
2325
        auto &vcobjptr = Objects.vcptr;
2326
        int             boss_index;
2327
 
2328
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2329
        boss_index = Robot_info[get_robot_id(objp)].boss_flag - BOSS_D2;
2330
 
2331
        Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
2332
 
2333
        auto &Segments = LevelUniqueSegmentState.get_segments();
2334
        const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, Segments.vmptridx(objp.segnum));
2335
        if (segnum == segment_none) {
2336
                return object_none;
2337
        }      
2338
 
2339
        const auto &&newobjp = create_gated_robot(Vclip, vcobjptr, segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], &pos);
2340
 
2341
        //      Make spewed robot come tumbling out as if blasted by a flash missile.
2342
        if (newobjp != object_none) {
2343
                int             force_val;
2344
 
2345
                force_val = F1_0/FrameTime;
2346
 
2347
                if (force_val) {
2348
                        newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
2349
                        newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
2350
                        newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
2351
                        newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
2352
                        newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
2353
 
2354
                        //      Now, give a big initial velocity to get moving away from boss.
2355
                        vm_vec_sub(newobjp->mtype.phys_info.velocity, pos, objp.pos);
2356
                        vm_vec_normalize_quick(newobjp->mtype.phys_info.velocity);
2357
                        vm_vec_scale(newobjp->mtype.phys_info.velocity, F1_0*128);
2358
                }
2359
        }
2360
 
2361
        return newobjp;
2362
}
2363
 
2364
//      ----------------------------------------------------------------------
2365
void start_robot_death_sequence(object &obj)
2366
{
2367
        auto &ai_info = obj.ctype.ai_info;
2368
        ai_info.dying_start_time = GameTime64;
2369
        ai_info.dying_sound_playing = 0;
2370
        ai_info.SKIP_AI_COUNT = 0;
2371
}
2372
 
2373
//      ----------------------------------------------------------------------
2374
//      General purpose robot-dies-with-death-roll-and-groan code.
2375
//      Return true if object just died.
2376
//      scale: F1_0*4 for boss, much smaller for much smaller guys
2377
static int do_robot_dying_frame(const vmobjptridx_t objp, fix64 start_time, fix roll_duration, sbyte *dying_sound_playing, int death_sound, fix expl_scale, fix sound_scale)
2378
{
2379
        fix     sound_duration;
2380
 
2381
        if (!roll_duration)
2382
                roll_duration = F1_0/4;
2383
 
2384
        objp->mtype.phys_info.rotvel.x = (GameTime64 - start_time)/9;
2385
        objp->mtype.phys_info.rotvel.y = (GameTime64 - start_time)/5;
2386
        objp->mtype.phys_info.rotvel.z = (GameTime64 - start_time)/7;
2387
 
2388
        if (const auto SndDigiSampleRate = GameArg.SndDigiSampleRate)
2389
                sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length, SndDigiSampleRate);
2390
        else
2391
                sound_duration = F1_0;
2392
 
2393
        if (start_time + roll_duration - sound_duration < GameTime64) {
2394
                if (!*dying_sound_playing) {
2395
                        *dying_sound_playing = 1;
2396
                        digi_link_sound_to_object2(death_sound, objp, 0, sound_scale, sound_stack::allow_stacking, vm_distance{sound_scale*256});       //      F1_0*512 means play twice as loud
2397
                } else if (d_rand() < FrameTime*16)
2398
                        create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
2399
        } else if (d_rand() < FrameTime*8)
2400
                create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
2401
 
2402
        if (start_time + roll_duration < GameTime64 || GameTime64+(F1_0*2) < start_time)
2403
                return 1;
2404
        else
2405
                return 0;
2406
}
2407
 
2408
//      ----------------------------------------------------------------------
2409
static void do_boss_dying_frame(const vmobjptridx_t objp)
2410
{
2411
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2412
        int     rval;
2413
 
2414
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2415
        rval = do_robot_dying_frame(objp, BossUniqueState.Boss_dying_start_time, BOSS_DEATH_DURATION, &BossUniqueState.Boss_dying_sound_playing, Robot_info[get_robot_id(objp)].deathroll_sound, F1_0*4, F1_0*4);
2416
 
2417
        if (rval)
2418
        {
2419
                BossUniqueState.Boss_dying_start_time = GameTime64; // make sure following only happens one time!
2420
                do_controlcen_destroyed_stuff(object_none);
2421
                explode_object(objp, F1_0/4);
2422
                digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
2423
        }
2424
}
2425
 
2426
//      ----------------------------------------------------------------------
2427
static int do_any_robot_dying_frame(const vmobjptridx_t objp)
2428
{
2429
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2430
        if (objp->ctype.ai_info.dying_start_time) {
2431
                int     rval, death_roll;
2432
 
2433
                death_roll = Robot_info[get_robot_id(objp)].death_roll;
2434
                rval = do_robot_dying_frame(objp, objp->ctype.ai_info.dying_start_time, min(death_roll/2+1,6)*F1_0, &objp->ctype.ai_info.dying_sound_playing, Robot_info[get_robot_id(objp)].deathroll_sound, death_roll*F1_0/8, death_roll*F1_0/2);
2435
 
2436
                if (rval) {
2437
                        objp->ctype.ai_info.dying_start_time = GameTime64; // make sure following only happens one time!
2438
                        explode_object(objp, F1_0/4);
2439
                        digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
2440
                        if (Current_level_num < 0)
2441
                        {
2442
                                const auto id = get_robot_id(objp);
2443
                                if (Robot_info[id].thief)
2444
                                        recreate_thief(id);
2445
                        }
2446
                }
2447
 
2448
                return 1;
2449
        }
2450
 
2451
        return 0;
2452
}
2453
#endif
2454
 
2455
// --------------------------------------------------------------------------------------------------------------------
2456
//      Called for an AI object if it is fairly aware of the player.
2457
//      awareness_level is in 0..100.  Larger numbers indicate greater awareness (eg, 99 if firing at player).
2458
//      In a given frame, might not get called for an object, or might be called more than once.
2459
//      The fact that this routine is not called for a given object does not mean that object is not interested in the player.
2460
//      Objects are moved by physics, so they can move even if not interested in a player.  However, if their velocity or
2461
//      orientation is changing, this routine will be called.
2462
//      Return value:
2463
//              0       this player IS NOT allowed to move this robot.
2464
//              1       this player IS allowed to move this robot.
2465
int ai_multiplayer_awareness(const vmobjptridx_t objp, int awareness_level)
2466
{
2467
        int     rval=1;
2468
 
2469
        if (Game_mode & GM_MULTI) {
2470
                if (awareness_level == 0)
2471
                        return 0;
2472
                rval = multi_can_move_robot(objp, awareness_level);
2473
        }
2474
 
2475
        return rval;
2476
}
2477
 
2478
}
2479
 
2480
#ifndef NDEBUG
2481
namespace {
2482
fix     Prev_boss_shields = -1;
2483
}
2484
#endif
2485
 
2486
namespace dsx {
2487
 
2488
#if defined(DXX_BUILD_DESCENT_I)
2489
#define do_d1_boss_stuff(FS,FO,PV)      do_d1_boss_stuff(FS,FO)
2490
#endif
2491
 
2492
// --------------------------------------------------------------------------------------------------------------------
2493
//      Do special stuff for a boss.
2494
static void do_d1_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_visibility_state player_visibility)
2495
{
2496
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2497
        auto &Objects = LevelUniqueObjectState.Objects;
2498
        auto &vmobjptr = Objects.vmptr;
2499
#ifndef NDEBUG
2500
        if (objp->shields != Prev_boss_shields) {
2501
                Prev_boss_shields = objp->shields;
2502
        }
2503
#endif
2504
 
2505
#if defined(DXX_BUILD_DESCENT_II)
2506
        if (!EMULATING_D1 && !player_is_visible(player_visibility) && !BossUniqueState.Boss_hit_this_frame)
2507
                return;
2508
#endif
2509
 
2510
        if (!BossUniqueState.Boss_dying) {
2511
                const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
2512
                if (objp->ctype.ai_info.CLOAKED == 1) {
2513
                        if (GameTime64 - Boss_cloak_start_time > Boss_cloak_duration / 3 &&
2514
                                (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 > Boss_cloak_duration / 3 &&
2515
                                GameTime64 - BossUniqueState.Last_teleport_time > LevelSharedBossState.Boss_teleport_interval)
2516
                        {
2517
                                if (ai_multiplayer_awareness(objp, 98))
2518
                                        teleport_boss(Vclip, vmsegptridx, objp, get_local_plrobj().pos);
2519
                        } else if (BossUniqueState.Boss_hit_this_frame) {
2520
                                BossUniqueState.Boss_hit_this_frame = 0;
2521
                                BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 4;
2522
                        }
2523
 
2524
                        if (GameTime64 > (Boss_cloak_start_time + Boss_cloak_duration) ||
2525
                                GameTime64 < Boss_cloak_start_time)
2526
                                objp->ctype.ai_info.CLOAKED = 0;
2527
                } else {
2528
                        if (BossUniqueState.Boss_hit_this_frame ||
2529
                                GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) > LevelSharedBossState.Boss_cloak_interval)
2530
                        {
2531
                                if (ai_multiplayer_awareness(objp, 95))
2532
                                {
2533
                                        BossUniqueState.Boss_hit_this_frame = 0;
2534
                                        BossUniqueState.Boss_cloak_start_time = GameTime64;
2535
                                        objp->ctype.ai_info.CLOAKED = 1;
2536
                                        if (Game_mode & GM_MULTI)
2537
                                                multi_send_boss_cloak(objp);
2538
                                }
2539
                        }
2540
                }
2541
        } else
2542
                do_boss_dying_frame(objp);
2543
 
2544
}
2545
 
2546
#define BOSS_TO_PLAYER_GATE_DISTANCE    (F1_0*150)
2547
 
2548
 
2549
// --------------------------------------------------------------------------------------------------------------------
2550
//      Do special stuff for a boss.
2551
static void do_super_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const fix dist_to_player, const player_visibility_state player_visibility)
2552
{
2553
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2554
        static int eclip_state = 0;
2555
        do_d1_boss_stuff(vmsegptridx, objp, player_visibility);
2556
 
2557
        // Only master player can cause gating to occur.
2558
        if ((Game_mode & GM_MULTI) && !multi_i_am_master())
2559
                return;
2560
 
2561
        if (dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE || player_is_visible(player_visibility) || (Game_mode & GM_MULTI)) {
2562
                const auto Gate_interval = GameUniqueState.Boss_gate_interval;
2563
                if (GameTime64 - BossUniqueState.Last_gate_time > Gate_interval/2) {
2564
                        restart_effect(ECLIP_NUM_BOSS);
2565
                        if (eclip_state == 0) {
2566
                                multi_send_boss_start_gate(objp);
2567
                                eclip_state = 1;
2568
                        }
2569
                }
2570
                else {
2571
                        stop_effect(ECLIP_NUM_BOSS);
2572
                        if (eclip_state == 1) {
2573
                                multi_send_boss_stop_gate(objp);
2574
                                eclip_state = 0;
2575
                        }
2576
                }
2577
 
2578
                if (GameTime64 - BossUniqueState.Last_gate_time > Gate_interval)
2579
                        if (ai_multiplayer_awareness(objp, 99)) {
2580
                                uint_fast32_t randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
2581
 
2582
                                Assert(randtype < MAX_GATE_INDEX);
2583
                                randtype = Super_boss_gate_list[randtype];
2584
 
2585
                                const auto &&rtval = gate_in_robot(vmsegptridx, randtype);
2586
                                if (rtval != object_none && (Game_mode & GM_MULTI))
2587
                                {
2588
                                        multi_send_boss_create_robot(objp, rtval);
2589
                                }
2590
                        }      
2591
        }
2592
}
2593
 
2594
#if defined(DXX_BUILD_DESCENT_II)
2595
static void do_d2_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_visibility_state player_visibility)
2596
{
2597
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
2598
        auto &Objects = LevelUniqueObjectState.Objects;
2599
        auto &vmobjptr = Objects.vmptr;
2600
        int     boss_id, boss_index;
2601
 
2602
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2603
        boss_id = Robot_info[get_robot_id(objp)].boss_flag;
2604
 
2605
        Assert((boss_id >= BOSS_D2) && (boss_id < BOSS_D2 + NUM_D2_BOSSES));
2606
 
2607
        boss_index = boss_id - BOSS_D2;
2608
 
2609
#ifndef NDEBUG
2610
        if (objp->shields != Prev_boss_shields) {
2611
                Prev_boss_shields = objp->shields;
2612
        }
2613
#endif
2614
 
2615
        //      @mk, 10/13/95:  Reason:
2616
        //              Level 4 boss behind locked door.  But he's allowed to teleport out of there.  So he
2617
        //              teleports out of there right away, and blasts player right after first door.
2618
        if (!player_is_visible(player_visibility) && GameTime64 - BossUniqueState.Boss_hit_time > F1_0 * 2)
2619
                return;
2620
 
2621
        if (!BossUniqueState.Boss_dying && Boss_teleports[boss_index]) {
2622
                const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
2623
                if (objp->ctype.ai_info.CLOAKED == 1) {
2624
                        BossUniqueState.Boss_hit_time = GameTime64;     //      Keep the cloak:teleport process going.
2625
                        if (GameTime64 - Boss_cloak_start_time > Boss_cloak_duration / 3 &&
2626
                                (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 > Boss_cloak_duration / 3 &&
2627
                                GameTime64 - BossUniqueState.Last_teleport_time > LevelSharedBossState.Boss_teleport_interval)
2628
                        {
2629
                                if (ai_multiplayer_awareness(objp, 98))
2630
                                        teleport_boss(Vclip, vmsegptridx, objp, get_local_plrobj().pos);
2631
                        } else if (GameTime64 - BossUniqueState.Boss_hit_time > F1_0*2) {
2632
                                BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 4;
2633
                        }
2634
 
2635
                        if (GameTime64 > (Boss_cloak_start_time + Boss_cloak_duration) ||
2636
                                GameTime64 < Boss_cloak_start_time)
2637
                                objp->ctype.ai_info.CLOAKED = 0;
2638
                } else if (GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) > LevelSharedBossState.Boss_cloak_interval ||
2639
                                GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) < -Boss_cloak_duration) {
2640
                        if (ai_multiplayer_awareness(objp, 95)) {
2641
                                BossUniqueState.Boss_cloak_start_time = GameTime64;
2642
                                objp->ctype.ai_info.CLOAKED = 1;
2643
                                if (Game_mode & GM_MULTI)
2644
                                        multi_send_boss_cloak(objp);
2645
                        }
2646
                }
2647
        }
2648
 
2649
}
2650
#endif
2651
 
2652
 
2653
static void ai_multi_send_robot_position(object &obj, int force)
2654
{
2655
        if (Game_mode & GM_MULTI)
2656
        {
2657
                multi_send_robot_position(obj, force != -1);
2658
        }
2659
        return;
2660
}
2661
 
2662
// --------------------------------------------------------------------------------------------------------------------
2663
//      Returns true if this object should be allowed to fire at the player.
2664
static int maybe_ai_do_actual_firing_stuff(object &obj)
2665
{
2666
        if (Game_mode & GM_MULTI)
2667
        {
2668
                auto &aip = obj.ctype.ai_info;
2669
                if (aip.GOAL_STATE != AIS_FLIN && get_robot_id(obj) != ROBOT_BRAIN)
2670
                {
2671
                        const auto s = aip.CURRENT_STATE;
2672
                        if (s == AIS_FIRE)
2673
                        {
2674
                                static_assert(AIS_FIRE != 0, "AIS_FIRE must be nonzero for this shortcut to work properly.");
2675
                                return s;
2676
                        }
2677
                }
2678
        }
2679
 
2680
        return 0;
2681
}
2682
 
2683
#if defined(DXX_BUILD_DESCENT_I)
2684
static void ai_do_actual_firing_stuff(fvmobjptridx &vmobjptridx, const vmobjptridx_t obj, ai_static *const aip, ai_local &ailp, const robot_info &robptr, const fix dist_to_player, const vms_vector &gun_point, const robot_to_player_visibility_state &player_visibility, const int object_animates, const player_info &player_info, int)
2685
{
2686
        auto &vec_to_player = player_visibility.vec_to_player;
2687
        fix     dot;
2688
 
2689
        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
2690
        {
2691
                //      Changed by mk, 01/04/94, onearm would take about 9 seconds until he can fire at you.
2692
                // if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0))
2693
                if (!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) {
2694
                        dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
2695
                        if (dot >= 7*F1_0/8) {
2696
 
2697
                                if (aip->CURRENT_GUN < robptr.n_guns) {
2698
                                        if (robptr.attack_type == 1) {
2699
                                                if (Player_dead_state != player_dead_state::exploded &&
2700
                                                        dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
2701
                                                {
2702
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2703
                                                                return;
2704
                                                        do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
2705
                                                } else {
2706
                                                        return;
2707
                                                }
2708
                                        } else {
2709
                                                if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
2710
                                                        ;
2711
                                                } else {
2712
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2713
                                                                return;
2714
                                                        ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0);
2715
                                                }
2716
                                        }
2717
 
2718
                                        //      Wants to fire, so should go into chase mode, probably.
2719
                                        if (aip->behavior != ai_behavior::AIB_RUN_FROM && aip->behavior != ai_behavior::AIB_STILL && aip->behavior != ai_behavior::AIB_FOLLOW_PATH && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
2720
                                                ailp.mode = ai_mode::AIM_CHASE_OBJECT;
2721
                                }
2722
 
2723
                                aip->GOAL_STATE = AIS_RECO;
2724
                                ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
2725
 
2726
                                // Switch to next gun for next fire.
2727
                                aip->CURRENT_GUN++;
2728
                                if (aip->CURRENT_GUN >= robptr.n_guns)
2729
                                        aip->CURRENT_GUN = 0;
2730
                        }
2731
                }
2732
        } else if (Weapon_info[robptr.weapon_type].homing_flag) {
2733
                //      Robots which fire homing weapons might fire even if they don't have a bead on the player.
2734
                if ((!object_animates || ailp.achieved_state[aip->CURRENT_GUN] == AIS_FIRE)
2735
                        && ready_to_fire_weapon1(ailp, 0)
2736
                        && (vm_vec_dist_quick(Hit_pos, obj->pos) > F1_0*40)) {
2737
                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2738
                                return;
2739
                        ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0);
2740
 
2741
                        aip->GOAL_STATE = AIS_RECO;
2742
                        ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
2743
 
2744
                        // Switch to next gun for next fire.
2745
                        aip->CURRENT_GUN++;
2746
                        if (aip->CURRENT_GUN >= robptr.n_guns)
2747
                                aip->CURRENT_GUN = 0;
2748
                } else {
2749
                        // Switch to next gun for next fire.
2750
                        aip->CURRENT_GUN++;
2751
                        if (aip->CURRENT_GUN >= robptr.n_guns)
2752
                                aip->CURRENT_GUN = 0;
2753
                }
2754
        }
2755
}
2756
#elif defined(DXX_BUILD_DESCENT_II)
2757
 
2758
// --------------------------------------------------------------------------------------------------------------------
2759
//      If fire_anyway, fire even if player is not visible.  We're firing near where we believe him to be.  Perhaps he's
2760
//      lurking behind a corner.
2761
static void ai_do_actual_firing_stuff(fvmobjptridx &vmobjptridx, const vmobjptridx_t obj, ai_static *const aip, ai_local &ailp, const robot_info &robptr, const fix dist_to_player, vms_vector &gun_point, const robot_to_player_visibility_state &player_visibility, const int object_animates, const player_info &player_info, const int gun_num)
2762
{
2763
        auto &vec_to_player = player_visibility.vec_to_player;
2764
        fix     dot;
2765
 
2766
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2767
        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view || Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)
2768
        {
2769
                vms_vector      fire_pos;
2770
 
2771
                fire_pos = Believed_player_pos;
2772
 
2773
                //      Hack: If visibility not == 2, we're here because we're firing at a nearby player.
2774
                //      So, fire at Last_fired_upon_player_pos instead of the player position.
2775
                if (!robptr.attack_type && player_visibility.visibility != player_visibility_state::visible_and_in_field_of_view)
2776
                        fire_pos = Last_fired_upon_player_pos;
2777
 
2778
                //      Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
2779
                //      Above comment corrected.  Date changed from 1994, to 1995.  Should fix some very subtle bugs, as well as not cause me to wonder, in the future, why I was writing AI code for onearm ten months before he existed.
2780
                if (!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) {
2781
                        dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
2782
                        if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) &&  robptr.boss_flag)) {
2783
 
2784
                                if (gun_num < robptr.n_guns) {
2785
                                        if (robptr.attack_type == 1) {
2786
                                                if (Player_dead_state != player_dead_state::exploded &&
2787
                                                        dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
2788
                                                {
2789
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2790
                                                                return;
2791
                                                        do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
2792
                                                } else {
2793
                                                        return;
2794
                                                }
2795
                                        } else {
2796
                                                if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
2797
                                                        ;
2798
                                                } else {
2799
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2800
                                                                return;
2801
                                                        //      New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2802
                                                        if (ready_to_fire_weapon1(ailp, 0)) {
2803
                                                                ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, fire_pos);
2804
                                                                Last_fired_upon_player_pos = fire_pos;
2805
                                                        }
2806
                                                        if (gun_num != 0) {
2807
                                                                if (ready_to_fire_weapon2(robptr, ailp, 0)) {
2808
                                                                        calc_gun_point(gun_point, obj, 0);
2809
                                                                        ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0, fire_pos);
2810
                                                                        Last_fired_upon_player_pos = fire_pos;
2811
                                                                }
2812
                                                        }
2813
                                                }
2814
                                        }
2815
 
2816
                                        //      Wants to fire, so should go into chase mode, probably.
2817
                                        if ( (aip->behavior != ai_behavior::AIB_RUN_FROM)
2818
                                                 && (aip->behavior != ai_behavior::AIB_STILL)
2819
                                                 && (aip->behavior != ai_behavior::AIB_SNIPE)
2820
                                                 && (aip->behavior != ai_behavior::AIB_FOLLOW)
2821
                                                 && (!robptr.attack_type)
2822
                                                 && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
2823
                                                ailp.mode = ai_mode::AIM_CHASE_OBJECT;
2824
                                }
2825
 
2826
                                aip->GOAL_STATE = AIS_RECO;
2827
                                ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
2828
 
2829
                                // Switch to next gun for next fire.  If has 2 gun types, select gun #1, if exists.
2830
                                aip->CURRENT_GUN++;
2831
                                if (aip->CURRENT_GUN >= robptr.n_guns)
2832
                                {
2833
                                        if ((robptr.n_guns == 1) || (robptr.weapon_type2 == weapon_none))
2834
                                                aip->CURRENT_GUN = 0;
2835
                                        else
2836
                                                aip->CURRENT_GUN = 1;
2837
                                }
2838
                        }
2839
                }
2840
        }
2841
        else if ((!robptr.attack_type && Weapon_info[Robot_info[get_robot_id(obj)].weapon_type].homing_flag) || ((Robot_info[get_robot_id(obj)].weapon_type2 != weapon_none && Weapon_info[Robot_info[get_robot_id(obj)].weapon_type2].homing_flag)))
2842
        {
2843
                //      Robots which fire homing weapons might fire even if they don't have a bead on the player.
2844
                if ((!object_animates || ailp.achieved_state[aip->CURRENT_GUN] == AIS_FIRE)
2845
                        && (((ready_to_fire_weapon1(ailp, 0)) && (aip->CURRENT_GUN != 0)) || ((ready_to_fire_weapon2(robptr, ailp, 0)) && (aip->CURRENT_GUN == 0)))
2846
                         && (vm_vec_dist_quick(Hit_pos, obj->pos) > F1_0*40)) {
2847
                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2848
                                return;
2849
                        ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, Believed_player_pos);
2850
 
2851
                        aip->GOAL_STATE = AIS_RECO;
2852
                        ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
2853
                }
2854
                        // Switch to next gun for next fire.
2855
                        aip->CURRENT_GUN++;
2856
                        if (aip->CURRENT_GUN >= robptr.n_guns)
2857
                                aip->CURRENT_GUN = 0;
2858
        } else {
2859
 
2860
 
2861
        //      ---------------------------------------------------------------
2862
 
2863
                vms_vector      vec_to_last_pos;
2864
 
2865
                if (d_rand()/2 < fixmul(FrameTime, (GameUniqueState.Difficulty_level << 12) + 0x4000)) {
2866
                if ((!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
2867
                        vm_vec_normalized_dir_quick(vec_to_last_pos, Believed_player_pos, obj->pos);
2868
                        dot = vm_vec_dot(obj->orient.fvec, vec_to_last_pos);
2869
                        if (dot >= 7*F1_0/8) {
2870
 
2871
                                if (aip->CURRENT_GUN < robptr.n_guns) {
2872
                                        if (robptr.attack_type == 1) {
2873
                                                if (Player_dead_state != player_dead_state::exploded &&
2874
                                                        dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
2875
                                                {
2876
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2877
                                                                return;
2878
                                                        do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
2879
                                                } else {
2880
                                                        return;
2881
                                                }
2882
                                        } else {
2883
                                                if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
2884
                                                        ;
2885
                                                } else {
2886
                                                        if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2887
                                                                return;
2888
                                                        //      New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2889
                                                        if (ready_to_fire_weapon1(ailp, 0))
2890
                                                        {
2891
                                                                ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, Last_fired_upon_player_pos);
2892
                                                        }
2893
                                                        if (gun_num != 0) {
2894
 
2895
                                                                if (ready_to_fire_weapon2(robptr, ailp, 0)) {
2896
                                                                        calc_gun_point(gun_point, obj, 0);
2897
                                                                        ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0, Last_fired_upon_player_pos);
2898
                                                                }
2899
                                                        }
2900
                                                }
2901
                                        }
2902
 
2903
                                        //      Wants to fire, so should go into chase mode, probably.
2904
                                        if (aip->behavior != ai_behavior::AIB_RUN_FROM && aip->behavior != ai_behavior::AIB_STILL && aip->behavior != ai_behavior::AIB_SNIPE && aip->behavior != ai_behavior::AIB_FOLLOW && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
2905
                                                ailp.mode = ai_mode::AIM_CHASE_OBJECT;
2906
                                }
2907
                                aip->GOAL_STATE = AIS_RECO;
2908
                                ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
2909
 
2910
                                // Switch to next gun for next fire.
2911
                                aip->CURRENT_GUN++;
2912
                                if (aip->CURRENT_GUN >= robptr.n_guns)
2913
                                {
2914
                                        if (robptr.n_guns == 1)
2915
                                                aip->CURRENT_GUN = 0;
2916
                                        else
2917
                                                aip->CURRENT_GUN = 1;
2918
                                }
2919
                        }
2920
                }
2921
                }
2922
 
2923
 
2924
        //      ---------------------------------------------------------------
2925
        }
2926
}
2927
 
2928
// ----------------------------------------------------------------------------
2929
void init_ai_frame(const player_flags powerup_flags)
2930
{
2931
        Dist_to_last_fired_upon_player_pos = vm_vec_dist_quick(Last_fired_upon_player_pos, Believed_player_pos);
2932
 
2933
        if (!(powerup_flags & PLAYER_FLAGS_CLOAKED) ||
2934
                (powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON) ||
2935
                (Afterburner_charge && Controls.state.afterburner && (powerup_flags & PLAYER_FLAGS_AFTERBURNER)))
2936
        {
2937
                ai_do_cloak_stuff();
2938
        }
2939
}
2940
 
2941
// ----------------------------------------------------------------------------
2942
// Make a robot near the player snipe.
2943
#define MNRS_SEG_MAX    70
2944
static void make_nearby_robot_snipe(fvmsegptr &vmsegptr, const vmobjptr_t robot, const player_flags powerup_flags)
2945
{
2946
        auto &Objects = LevelUniqueObjectState.Objects;
2947
        auto &vmobjptridx = Objects.vmptridx;
2948
        std::array<segnum_t, MNRS_SEG_MAX> bfs_list;
2949
        /* Passing powerup_flags here seems wrong.  Sniping robots do not
2950
         * open doors, so they should not care what doors the player can
2951
         * open.  However, passing powerup_flags here maintains the
2952
         * semantics that past versions used.
2953
         */
2954
        const auto bfs_length = create_bfs_list(robot, ConsoleObject->segnum, powerup_flags, bfs_list);
2955
 
2956
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2957
        range_for (auto &i, partial_const_range(bfs_list, bfs_length)) {
2958
                range_for (object &objp, objects_in(vmsegptr(i), vmobjptridx, vmsegptr))
2959
                {
2960
                        object &obj = objp;
2961
                        if (obj.type != OBJ_ROBOT)
2962
                                continue;
2963
                        if (obj.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
2964
                                continue;
2965
                        if (obj.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)
2966
                                continue;
2967
                        const auto rid = get_robot_id(obj);
2968
                        if (rid == ROBOT_BRAIN)
2969
                                continue;
2970
                        auto &robptr = Robot_info[rid];
2971
                        if (!robptr.boss_flag && !robot_is_companion(robptr))
2972
                        {
2973
                                        obj.ctype.ai_info.behavior = ai_behavior::AIB_SNIPE;
2974
                                        obj.ctype.ai_info.ail.mode = ai_mode::AIM_SNIPE_ATTACK;
2975
                                        return;
2976
                        }
2977
                }
2978
        }
2979
}
2980
 
2981
const object *Ai_last_missile_camera;
2982
 
2983
static int openable_door_on_near_path(fvcsegptr &vcsegptr, fvcwallptr &vcwallptr, const object &obj, const ai_static &aip)
2984
{
2985
        const auto path_length = aip.path_length;
2986
        if (path_length < 1)
2987
                return 0;
2988
        if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(obj.segnum)) - side_none)
2989
                return r;
2990
        if (path_length < 2)
2991
                return 0;
2992
        size_t idx;
2993
        idx = aip.hide_index + aip.cur_path_index + aip.PATH_DIR;
2994
        /* Hack: Point_segs[idx].segnum should never be none here, but
2995
         * sometimes the guidebot has a none.  Guard against the bogus none
2996
         * until someone can track down why the guidebot does this.
2997
         */
2998
        if (idx < Point_segs.size() && Point_segs[idx].segnum != segment_none)
2999
        {
3000
                if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(Point_segs[idx].segnum)) - side_none)
3001
                        return r;
3002
        }
3003
        if (path_length < 3)
3004
                return 0;
3005
        idx = aip.hide_index + aip.cur_path_index + 2*aip.PATH_DIR;
3006
        if (idx < Point_segs.size() && Point_segs[idx].segnum != segment_none)
3007
        {
3008
                if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(Point_segs[idx].segnum)) - side_none)
3009
                        return r;
3010
        }
3011
        return 0;
3012
}
3013
 
3014
static unsigned guidebot_should_fire_flare(fvcobjptr &vcobjptr, fvcsegptr &vcsegptr, fvcwallptr &vcwallptr, d_unique_buddy_state &BuddyState, const robot_info &robptr, const object &buddy_obj)
3015
{
3016
        auto &ais = buddy_obj.ctype.ai_info;
3017
        auto &ail = ais.ail;
3018
        if (const auto r = ready_to_fire_any_weapon(robptr, ail, 0))
3019
        {
3020
        }
3021
        else
3022
                return r;
3023
        if (const auto r = openable_door_on_near_path(vcsegptr, vcwallptr, buddy_obj, ais))
3024
                return r;
3025
        if (ail.mode != ai_mode::AIM_GOTO_PLAYER)
3026
                return 0;
3027
        auto &plr = get_player_controlling_guidebot(BuddyState, Players);
3028
        if (plr.objnum == object_none)
3029
                /* should never happen */
3030
                return 0;
3031
        auto &plrobj = *vcobjptr(plr.objnum);
3032
        if (plrobj.type != OBJ_PLAYER)
3033
                return 0;
3034
        vms_vector vec_to_controller;
3035
        const auto dist_to_controller = vm_vec_normalized_dir_quick(vec_to_controller, plrobj.pos, buddy_obj.pos);
3036
        if (dist_to_controller >= 3 * MIN_ESCORT_DISTANCE / 2)
3037
                return 0;
3038
        if (vm_vec_dot(plrobj.orient.fvec, vec_to_controller) <= -F1_0 / 4)
3039
                return 0;
3040
        return 1;
3041
}
3042
#endif
3043
 
3044
#ifdef NDEBUG
3045
static bool is_break_object(vcobjidx_t)
3046
{
3047
        return false;
3048
}
3049
#else
3050
__attribute_used
3051
static objnum_t Break_on_object = object_none;
3052
 
3053
static bool is_break_object(const vcobjidx_t robot)
3054
{
3055
        return Break_on_object == robot;
3056
}
3057
#endif
3058
 
3059
static bool skip_ai_for_time_splice(const vcobjptridx_t robot, const robot_info &robptr, const vm_distance &dist_to_player)
3060
{
3061
        if (unlikely(is_break_object(robot)))
3062
                // don't time slice if we're interested in this object.
3063
                return false;
3064
        const auto &aip = robot->ctype.ai_info;
3065
        const auto &ailp = aip.ail;
3066
#if defined(DXX_BUILD_DESCENT_I)
3067
        (void)robptr;
3068
        if (static_cast<uint8_t>(ailp.player_awareness_type) < static_cast<uint8_t>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1)
3069
        { // If robot got hit, he gets to attack player always!
3070
                {
3071
                        if ((dist_to_player > F1_0*250) && (ailp.time_since_processed <= F1_0*2))
3072
                                return true;
3073
                        else if (!((aip.behavior == ai_behavior::AIB_STATION) && (ailp.mode == ai_mode::AIM_FOLLOW_PATH) && (aip.hide_segment != robot->segnum))) {
3074
                                if ((dist_to_player > F1_0*150) && (ailp.time_since_processed <= F1_0))
3075
                                        return true;
3076
                                else if ((dist_to_player > F1_0*100) && (ailp.time_since_processed <= F1_0/2))
3077
                                        return true;
3078
                        }
3079
                }
3080
        }
3081
#elif defined(DXX_BUILD_DESCENT_II)
3082
        if (!((aip.behavior == ai_behavior::AIB_SNIPE) && (ailp.mode != ai_mode::AIM_SNIPE_WAIT)) && !robot_is_companion(robptr) && !robot_is_thief(robptr) && static_cast<uint8_t>(ailp.player_awareness_type) < static_cast<uint8_t>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1)
3083
        { // If robot got hit, he gets to attack player always!
3084
                {
3085
                        if ((aip.behavior == ai_behavior::AIB_STATION) && (ailp.mode == ai_mode::AIM_FOLLOW_PATH) && (aip.hide_segment != robot->segnum)) {
3086
                                if (dist_to_player > F1_0*250)  // station guys not at home always processed until 250 units away.
3087
                                        return true;
3088
                        }
3089
                        else if (!player_is_visible(ailp.previous_visibility) && ((dist_to_player >> 7) > ailp.time_since_processed))
3090
                        {  // 128 units away (6.4 segments) processed after 1 second.
3091
                                return true;
3092
                        }
3093
                }
3094
        }
3095
#endif
3096
        return false;
3097
}
3098
 
3099
// --------------------------------------------------------------------------------------------------------------------
3100
void do_ai_frame(const vmobjptridx_t obj)
3101
{
3102
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
3103
        auto &Vertices = LevelSharedVertexState.get_vertices();
3104
        const auto Difficulty_level = GameUniqueState.Difficulty_level;
3105
        const objnum_t &objnum = obj;
3106
        ai_static       *const aip = &obj->ctype.ai_info;
3107
        ai_local &ailp = obj->ctype.ai_info.ail;
3108
        int                     obj_ref;
3109
        int                     object_animates;
3110
        int                     new_goal_state;
3111
        vms_vector      gun_point;
3112
        vms_vector      vis_vec_pos;
3113
        auto &Objects = LevelUniqueObjectState.Objects;
3114
        auto &vmobjptr = Objects.vmptr;
3115
        auto &vmobjptridx = Objects.vmptridx;
3116
 
3117
#if defined(DXX_BUILD_DESCENT_II)
3118
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
3119
        auto &vcobjptr = Objects.vcptr;
3120
        ailp.next_action_time -= FrameTime;
3121
#endif
3122
 
3123
        if (aip->SKIP_AI_COUNT) {
3124
                aip->SKIP_AI_COUNT--;
3125
#if defined(DXX_BUILD_DESCENT_II)
3126
                if (obj->mtype.phys_info.flags & PF_USES_THRUST) {
3127
                        obj->mtype.phys_info.rotthrust.x = (obj->mtype.phys_info.rotthrust.x * 15)/16;
3128
                        obj->mtype.phys_info.rotthrust.y = (obj->mtype.phys_info.rotthrust.y * 15)/16;
3129
                        obj->mtype.phys_info.rotthrust.z = (obj->mtype.phys_info.rotthrust.z * 15)/16;
3130
                        if (!aip->SKIP_AI_COUNT)
3131
                                obj->mtype.phys_info.flags &= ~PF_USES_THRUST;
3132
                }
3133
#endif
3134
                return;
3135
        }
3136
 
3137
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
3138
#if defined(DXX_BUILD_DESCENT_II)
3139
        auto &Station = LevelUniqueFuelcenterState.Station;
3140
#endif
3141
        auto &robptr = Robot_info[get_robot_id(obj)];
3142
        Assert(robptr.always_0xabcd == 0xabcd);
3143
 
3144
        if (do_any_robot_dying_frame(obj))
3145
                return;
3146
 
3147
        // Kind of a hack.  If a robot is flinching, but it is time for it to fire, unflinch it.
3148
        // Else, you can turn a big nasty robot into a wimp by firing flares at it.
3149
        // This also allows the player to see the cool flinch effect for mechs without unbalancing the game.
3150
        if ((aip->GOAL_STATE == AIS_FLIN) && ready_to_fire_any_weapon(robptr, ailp, 0)) {
3151
                aip->GOAL_STATE = AIS_FIRE;
3152
        }
3153
 
3154
#ifndef NDEBUG
3155
        if (aip->behavior == ai_behavior::AIB_RUN_FROM && ailp.mode != ai_mode::AIM_RUN_FROM_OBJECT)
3156
                Int3(); // This is peculiar.  Behavior is run from, but mode is not.  Contact Mike.
3157
 
3158
        if (Break_on_object != object_none)
3159
                if ((obj) == Break_on_object)
3160
                        Int3(); // Contact Mike: This is a debug break
3161
#endif
3162
 
3163
        //Assert((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR));
3164
        switch (aip->behavior)
3165
        {
3166
                case ai_behavior::AIB_STILL:
3167
                case ai_behavior::AIB_NORMAL:
3168
                case ai_behavior::AIB_RUN_FROM:
3169
                case ai_behavior::AIB_STATION:
3170
#if defined(DXX_BUILD_DESCENT_I)
3171
                case ai_behavior::AIB_HIDE:
3172
                case ai_behavior::AIB_FOLLOW_PATH:
3173
#elif defined(DXX_BUILD_DESCENT_II)
3174
                case ai_behavior::AIB_BEHIND:
3175
                case ai_behavior::AIB_SNIPE:
3176
                case ai_behavior::AIB_FOLLOW:
3177
#endif
3178
                        break;
3179
                default:
3180
                        aip->behavior = ai_behavior::AIB_NORMAL;
3181
                        break;
3182
        }
3183
 
3184
        Assert(obj->segnum != segment_none);
3185
        assert(get_robot_id(obj) < LevelSharedRobotInfoState.N_robot_types);
3186
 
3187
        obj_ref = objnum ^ d_tick_count;
3188
 
3189
        if (!ready_to_fire_weapon1(ailp, -F1_0*8))
3190
                ailp.next_fire -= FrameTime;
3191
 
3192
#if defined(DXX_BUILD_DESCENT_I)
3193
        Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;
3194
#elif defined(DXX_BUILD_DESCENT_II)
3195
        if (robptr.weapon_type2 != weapon_none) {
3196
                if (!ready_to_fire_weapon2(robptr, ailp, -F1_0*8))
3197
                        ailp.next_fire2 -= FrameTime;
3198
        } else
3199
                ailp.next_fire2 = F1_0*8;
3200
#endif
3201
 
3202
        if (ailp.time_since_processed < F1_0*256)
3203
                ailp.time_since_processed += FrameTime;
3204
 
3205
        const auto previous_visibility = ailp.previous_visibility;    //  Must get this before we toast the master copy!
3206
 
3207
        auto &plrobj = get_local_plrobj();
3208
        auto &player_info = plrobj.ctype.player_info;
3209
        robot_to_player_visibility_state player_visibility;
3210
        auto &vec_to_player = player_visibility.vec_to_player;
3211
#if defined(DXX_BUILD_DESCENT_I)
3212
        if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
3213
                Believed_player_pos = ConsoleObject->pos;
3214
#elif defined(DXX_BUILD_DESCENT_II)
3215
        // If only awake because of a camera, make that the believed player position.
3216
        if ((aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE) && Ai_last_missile_camera)
3217
                Believed_player_pos = Ai_last_missile_camera->pos;
3218
        else {
3219
                if (cheats.robotskillrobots) {
3220
                        vis_vec_pos = obj->pos;
3221
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3222
                        if (player_is_visible(player_visibility.visibility))
3223
                        {
3224
                                icobjptr_t min_obj = nullptr;
3225
                                fix min_dist = F1_0*200, cur_dist;
3226
 
3227
                                range_for (const auto &&objp, vcobjptr)
3228
                                {
3229
                                        if (objp->type == OBJ_ROBOT && objp != obj)
3230
                                        {
3231
                                                cur_dist = vm_vec_dist_quick(obj->pos, objp->pos);
3232
                                                if (cur_dist < F1_0*100)
3233
                                                        if (object_to_object_visibility(obj, objp, FQ_TRANSWALL))
3234
                                                                if (cur_dist < min_dist) {
3235
                                                                        min_obj = objp;
3236
                                                                        min_dist = cur_dist;
3237
                                                                }
3238
                                        }
3239
                                }
3240
 
3241
                                if (min_obj != nullptr)
3242
                                {
3243
                                        Believed_player_pos = min_obj->pos;
3244
                                        Believed_player_seg = min_obj->segnum;
3245
                                        vm_vec_normalized_dir_quick(vec_to_player, Believed_player_pos, obj->pos);
3246
                                } else
3247
                                        goto _exit_cheat;
3248
                        } else
3249
                                goto _exit_cheat;
3250
                } else {
3251
_exit_cheat:
3252
                        DXX_MAKE_MEM_UNDEFINED(&player_visibility, sizeof(player_visibility));
3253
                        player_visibility.initialized = 0;
3254
                        if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
3255
                                Believed_player_pos = ConsoleObject->pos;
3256
                        else
3257
                                Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;
3258
                }
3259
        }
3260
#endif
3261
        auto dist_to_player = vm_vec_dist_quick(Believed_player_pos, obj->pos);
3262
 
3263
        // If this robot can fire, compute visibility from gun position.
3264
        // Don't want to compute visibility twice, as it is expensive.  (So is call to calc_gun_point).
3265
#if defined(DXX_BUILD_DESCENT_I)
3266
        if (ready_to_fire_any_weapon(robptr, ailp, 0) && (dist_to_player < F1_0*200) && (robptr.n_guns) && !(robptr.attack_type))
3267
#elif defined(DXX_BUILD_DESCENT_II)
3268
        if ((player_is_visible(previous_visibility) || !(obj_ref & 3)) && ready_to_fire_any_weapon(robptr, ailp, 0) && (dist_to_player < F1_0*200) && (robptr.n_guns) && !(robptr.attack_type))
3269
#endif
3270
        {
3271
                // Since we passed ready_to_fire_any_weapon(), either next_fire or next_fire2 <= 0.  calc_gun_point from relevant one.
3272
                // If both are <= 0, we will deal with the mess in ai_do_actual_firing_stuff
3273
                const auto gun_num =
3274
#if defined(DXX_BUILD_DESCENT_II)
3275
                        (!ready_to_fire_weapon1(ailp, 0))
3276
                        ? 0
3277
                        :
3278
#endif
3279
                        aip->CURRENT_GUN;
3280
                        calc_gun_point(gun_point, obj, gun_num);
3281
                vis_vec_pos = gun_point;
3282
        } else {
3283
                vis_vec_pos = obj->pos;
3284
                vm_vec_zero(gun_point);
3285
        }
3286
 
3287
        switch (const auto boss_flag = robptr.boss_flag) {
3288
                case 0:
3289
                        break;
3290
 
3291
                case BOSS_SUPER:
3292
                        if (aip->GOAL_STATE == AIS_FLIN)
3293
                                aip->GOAL_STATE = AIS_FIRE;
3294
                        if (aip->CURRENT_STATE == AIS_FLIN)
3295
                                aip->CURRENT_STATE = AIS_FIRE;
3296
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3297
 
3298
                        {
3299
                                auto pv = player_visibility.visibility;
3300
                        auto dtp = dist_to_player/4;
3301
 
3302
                        // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
3303
                        if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
3304
                                pv = player_visibility_state::no_line_of_sight;
3305
                                dtp = vm_vec_dist_quick(ConsoleObject->pos, obj->pos)/4;
3306
                        }
3307
 
3308
                        do_super_boss_stuff(vmsegptridx, obj, dtp, pv);
3309
                }
3310
                        break;
3311
 
3312
                default:
3313
#if defined(DXX_BUILD_DESCENT_I)
3314
                        (void)boss_flag;
3315
                        Int3(); //      Bogus boss flag value.
3316
                        break;
3317
#endif
3318
                case BOSS_D1:
3319
                {
3320
                        if (aip->GOAL_STATE == AIS_FLIN)
3321
                                aip->GOAL_STATE = AIS_FIRE;
3322
                        if (aip->CURRENT_STATE == AIS_FLIN)
3323
                                aip->CURRENT_STATE = AIS_FIRE;
3324
 
3325
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3326
 
3327
#if defined(DXX_BUILD_DESCENT_II)
3328
                        auto pv = player_visibility.visibility;
3329
                        // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
3330
                        if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
3331
                                pv = player_visibility_state::no_line_of_sight;
3332
                        }
3333
 
3334
                        if (boss_flag != BOSS_D1)
3335
                        {
3336
                                do_d2_boss_stuff(vmsegptridx, obj, pv);
3337
                                break;
3338
                        }
3339
#endif
3340
                        do_d1_boss_stuff(vmsegptridx, obj, pv);
3341
                }
3342
                        break;
3343
        }
3344
 
3345
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - 
3346
        // Occasionally make non-still robots make a path to the player.  Based on agitation and distance from player.
3347
#if defined(DXX_BUILD_DESCENT_I)
3348
        if ((aip->behavior != ai_behavior::AIB_RUN_FROM) && (aip->behavior != ai_behavior::AIB_STILL) && !(Game_mode & GM_MULTI))
3349
#elif defined(DXX_BUILD_DESCENT_II)
3350
        if ((aip->behavior != ai_behavior::AIB_SNIPE) && (aip->behavior != ai_behavior::AIB_RUN_FROM) && (aip->behavior != ai_behavior::AIB_STILL) && !(Game_mode & GM_MULTI) && (robot_is_companion(robptr) != 1) && (robot_is_thief(robptr) != 1))
3351
#endif
3352
                if (Overall_agitation > 70) {
3353
                        if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/4)) {
3354
                                if (d_rand() * (Overall_agitation - 40) > F1_0*5) {
3355
                                        create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8 + Difficulty_level, create_path_safety_flag::safe);
3356
                                        return;
3357
                                }
3358
                        }
3359
                }
3360
 
3361
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3362
        // If retry count not 0, then add it into consecutive_retries.
3363
        // If it is 0, cut down consecutive_retries.
3364
        // This is largely a hack to speed up physics and deal with stupid
3365
        // AI.  This is low level communication between systems of a sort
3366
        // that should not be done.
3367
        if (ailp.retry_count && !(Game_mode & GM_MULTI))
3368
        {
3369
                ailp.consecutive_retries += ailp.retry_count;
3370
                ailp.retry_count = 0;
3371
                if (ailp.consecutive_retries > 3)
3372
                {
3373
                        switch (ailp.mode) {
3374
#if defined(DXX_BUILD_DESCENT_II)
3375
                                case ai_mode::AIM_GOTO_PLAYER:
3376
                                        move_towards_segment_center(LevelSharedSegmentState, obj);
3377
                                        create_path_to_guidebot_player_segment(obj, 100, create_path_safety_flag::safe);
3378
                                        break;
3379
                                case ai_mode::AIM_GOTO_OBJECT:
3380
                                        BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
3381
                                        break;
3382
#endif
3383
                                case ai_mode::AIM_CHASE_OBJECT:
3384
                                        create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8 + Difficulty_level, create_path_safety_flag::safe);
3385
                                        break;
3386
                                case ai_mode::AIM_STILL:
3387
#if defined(DXX_BUILD_DESCENT_I)
3388
                                        if (!((aip->behavior == ai_behavior::AIB_STILL) || (aip->behavior == ai_behavior::AIB_STATION)))        //      Behavior is still, so don't follow path.
3389
#elif defined(DXX_BUILD_DESCENT_II)
3390
                                        if (robptr.attack_type)
3391
                                                move_towards_segment_center(LevelSharedSegmentState, obj);
3392
                                        else if (!((aip->behavior == ai_behavior::AIB_STILL) || (aip->behavior == ai_behavior::AIB_STATION) || (aip->behavior == ai_behavior::AIB_FOLLOW)))    // Behavior is still, so don't follow path.
3393
#endif
3394
                                                attempt_to_resume_path(obj);
3395
                                        break;
3396
                                case ai_mode::AIM_FOLLOW_PATH:
3397
                                        if (Game_mode & GM_MULTI)
3398
                                                ailp.mode = ai_mode::AIM_STILL;
3399
                                        else
3400
                                                attempt_to_resume_path(obj);
3401
                                        break;
3402
                                case ai_mode::AIM_RUN_FROM_OBJECT:
3403
                                        move_towards_segment_center(LevelSharedSegmentState, obj);
3404
                                        obj->mtype.phys_info.velocity = {};
3405
                                        create_n_segment_path(obj, 5, segment_none);
3406
                                        ailp.mode = ai_mode::AIM_RUN_FROM_OBJECT;
3407
                                        break;
3408
#if defined(DXX_BUILD_DESCENT_I)
3409
                                case ai_mode::AIM_HIDE:
3410
                                        move_towards_segment_center(LevelSharedSegmentState, obj);
3411
                                        obj->mtype.phys_info.velocity = {};
3412
                                        if (Overall_agitation > (50 - Difficulty_level*4))
3413
                                                create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8, create_path_safety_flag::safe);
3414
                                        else {
3415
                                                create_n_segment_path(obj, 5, segment_none);
3416
                                        }
3417
                                        break;
3418
#elif defined(DXX_BUILD_DESCENT_II)
3419
                                case ai_mode::AIM_BEHIND:
3420
                                        move_towards_segment_center(LevelSharedSegmentState, obj);
3421
                                        obj->mtype.phys_info.velocity = {};
3422
                                        break;
3423
                                case ai_mode::AIM_SNIPE_ATTACK:
3424
                                case ai_mode::AIM_SNIPE_FIRE:
3425
                                case ai_mode::AIM_SNIPE_RETREAT:
3426
                                case ai_mode::AIM_SNIPE_RETREAT_BACKWARDS:
3427
                                case ai_mode::AIM_SNIPE_WAIT:
3428
                                case ai_mode::AIM_THIEF_ATTACK:
3429
                                case ai_mode::AIM_THIEF_RETREAT:
3430
                                case ai_mode::AIM_THIEF_WAIT:
3431
                                        break;
3432
#endif
3433
                                case ai_mode::AIM_OPEN_DOOR:
3434
                                        create_n_segment_path_to_door(obj, 5);
3435
                                        break;
3436
                                case ai_mode::AIM_FOLLOW_PATH_2:
3437
                                        Int3(); // Should never happen!
3438
                                        break;
3439
                                case ai_mode::AIM_WANDER:
3440
                                        break;
3441
                        }
3442
                        ailp.consecutive_retries = 0;
3443
                }
3444
        } else
3445
                ailp.consecutive_retries /= 2;
3446
 
3447
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3448
        // If in materialization center, exit
3449
        if (!(Game_mode & GM_MULTI))
3450
        {
3451
                const shared_segment &seg = *vcsegptr(obj->segnum);
3452
                if (seg.special == SEGMENT_IS_ROBOTMAKER)
3453
                {
3454
#if defined(DXX_BUILD_DESCENT_II)
3455
                        if (Station[seg.station_idx].Enabled)
3456
#endif
3457
                {
3458
                        ai_follow_path(obj, player_visibility_state::visible_not_in_field_of_view, nullptr);    // 1 = player is visible, which might be a lie, but it works.
3459
                        return;
3460
                }
3461
                }
3462
        }
3463
 
3464
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3465
        // Decrease player awareness due to the passage of time.
3466
        if (ailp.player_awareness_type != player_awareness_type_t::PA_NONE)
3467
        {
3468
                if (ailp.player_awareness_time > 0)
3469
                {
3470
                        ailp.player_awareness_time -= FrameTime;
3471
                        if (ailp.player_awareness_time <= 0)
3472
                        {
3473
                                ailp.player_awareness_time = F1_0*2;   //new: 11/05/94
3474
                                ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);          //new: 11/05/94
3475
                        }
3476
                } else {
3477
                        ailp.player_awareness_time = F1_0*2;
3478
                        ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);
3479
                        //aip->GOAL_STATE = AIS_REST;
3480
                }
3481
        } else
3482
                aip->GOAL_STATE = AIS_REST;                     //new: 12/13/94
3483
 
3484
 
3485
        if (Player_dead_state != player_dead_state::no &&
3486
                ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
3487
                if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/8)) {
3488
                        if ((aip->behavior != ai_behavior::AIB_STILL) && (aip->behavior != ai_behavior::AIB_RUN_FROM)) {
3489
                                if (!ai_multiplayer_awareness(obj, 30))
3490
                                        return;
3491
                                ai_multi_send_robot_position(obj, -1);
3492
 
3493
                                if (!(ailp.mode == ai_mode::AIM_FOLLOW_PATH && aip->cur_path_index < aip->path_length - 1))
3494
#if defined(DXX_BUILD_DESCENT_II)
3495
                                        if ((aip->behavior != ai_behavior::AIB_SNIPE) && (aip->behavior != ai_behavior::AIB_RUN_FROM))
3496
#endif
3497
                                        {
3498
                                                if (dist_to_player < F1_0*30)
3499
                                                        create_n_segment_path(obj, 5, segment_none);
3500
                                                else
3501
                                                        create_path_to_believed_player_segment(obj, 20, create_path_safety_flag::safe);
3502
                                        }
3503
                        }
3504
                }
3505
 
3506
        //      Make sure that if this guy got hit or bumped, then he's chasing player.
3507
        if (ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION || ailp.player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION)
3508
#if defined(DXX_BUILD_DESCENT_I)
3509
        {
3510
                if ((aip->behavior != ai_behavior::AIB_STILL) && (aip->behavior != ai_behavior::AIB_FOLLOW_PATH) && (aip->behavior != ai_behavior::AIB_RUN_FROM) && (get_robot_id(obj) != ROBOT_BRAIN))
3511
                        ailp.mode = ai_mode::AIM_CHASE_OBJECT;
3512
        }
3513
#elif defined(DXX_BUILD_DESCENT_II)
3514
        {
3515
                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3516
                if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view) // Only increase visibility if unobstructed, else claw guys attack through doors.
3517
                        player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
3518
        } else if (((obj_ref&3) == 0) && !player_is_visible(previous_visibility) && (dist_to_player < F1_0*100)) {
3519
                fix sval, rval;
3520
 
3521
                rval = d_rand();
3522
                sval = (dist_to_player * (static_cast<int>(Difficulty_level) + 1)) / 64;
3523
 
3524
                if ((fixmul(rval, sval) < FrameTime) || (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON)) {
3525
                        ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
3526
                        ailp.player_awareness_time = F1_0*3;
3527
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3528
                        if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view)
3529
                                player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
3530
                }
3531
        }
3532
#endif
3533
 
3534
        //      - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
3535
        if ((aip->GOAL_STATE == AIS_FLIN) && (aip->CURRENT_STATE == AIS_FLIN))
3536
                aip->GOAL_STATE = AIS_LOCK;
3537
 
3538
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3539
        // Note: Should only do these two function calls for objects which animate
3540
        if (dist_to_player < F1_0*100) { // && !(Game_mode & GM_MULTI))
3541
                object_animates = do_silly_animation(obj);
3542
                if (object_animates)
3543
                        ai_frame_animation(obj);
3544
        } else {
3545
                // If Object is supposed to animate, but we don't let it animate due to distance, then
3546
                // we must change its state, else it will never update.
3547
                aip->CURRENT_STATE = aip->GOAL_STATE;
3548
                object_animates = 0;        // If we're not doing the animation, then should pretend it doesn't animate.
3549
        }
3550
 
3551
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3552
        // Time-slice, don't process all the time, purely an efficiency hack.
3553
        // Guys whose behavior is station and are not at their hide segment get processed anyway.
3554
        if (skip_ai_for_time_splice(obj, robptr, dist_to_player))
3555
                return;
3556
 
3557
        // Reset time since processed, but skew objects so not everything
3558
        // processed synchronously, else we get fast frames with the
3559
        // occasional very slow frame.
3560
        // AI_proc_time = ailp->time_since_processed;
3561
        ailp.time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2;
3562
 
3563
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
3564
        //      Perform special ability
3565
        switch (get_robot_id(obj)) {
3566
                case ROBOT_BRAIN:
3567
                        // Robots function nicely if behavior is Station.  This
3568
                        // means they won't move until they can see the player, at
3569
                        // which time they will start wandering about opening doors.
3570
                        if (ConsoleObject->segnum == obj->segnum) {
3571
                                if (!ai_multiplayer_awareness(obj, 97))
3572
                                        return;
3573
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3574
                                move_away_from_player(obj, vec_to_player, 0);
3575
                                ai_multi_send_robot_position(obj, -1);
3576
                        }
3577
                        else if (ailp.mode != ai_mode::AIM_STILL)
3578
                        {
3579
                                auto &Walls = LevelUniqueWallSubsystemState.Walls;
3580
                                auto &vcwallptr = Walls.vcptr;
3581
                                const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(obj->segnum));
3582
                                if (r != side_none)
3583
                                {
3584
                                        ailp.mode = ai_mode::AIM_OPEN_DOOR;
3585
                                        aip->GOALSIDE = r;
3586
                                }
3587
                                else if (ailp.mode != ai_mode::AIM_FOLLOW_PATH)
3588
                                {
3589
                                        if (!ai_multiplayer_awareness(obj, 50))
3590
                                                return;
3591
                                        create_n_segment_path_to_door(obj, 8+Difficulty_level);     // third parameter is avoid_seg, -1 means avoid nothing.
3592
                                        ai_multi_send_robot_position(obj, -1);
3593
                                }
3594
 
3595
#if defined(DXX_BUILD_DESCENT_II)
3596
                                if (ailp.next_action_time < 0)
3597
                                {
3598
                                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3599
                                        if (player_is_visible(player_visibility.visibility))
3600
                                        {
3601
                                                const auto powerup_flags = player_info.powerup_flags;
3602
                                                make_nearby_robot_snipe(vmsegptr, obj, powerup_flags);
3603
                                                ailp.next_action_time = (NDL - Difficulty_level) * 2*F1_0;
3604
                                        }
3605
                                }
3606
#endif
3607
                        } else {
3608
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3609
                                if (player_is_visible(player_visibility.visibility))
3610
                                {
3611
                                        if (!ai_multiplayer_awareness(obj, 50))
3612
                                                return;
3613
                                        create_n_segment_path_to_door(obj, 8+Difficulty_level);     // third parameter is avoid_seg, -1 means avoid nothing.
3614
                                        ai_multi_send_robot_position(obj, -1);
3615
                                }
3616
                        }
3617
                        break;
3618
                default:
3619
                        break;
3620
        }
3621
 
3622
#if defined(DXX_BUILD_DESCENT_II)
3623
        if (aip->behavior == ai_behavior::AIB_SNIPE) {
3624
                if ((Game_mode & GM_MULTI) && !robot_is_thief(robptr)) {
3625
                        aip->behavior = ai_behavior::AIB_NORMAL;
3626
                        ailp.mode = ai_mode::AIM_CHASE_OBJECT;
3627
                        return;
3628
                }
3629
 
3630
                if (!(obj_ref & 3) || player_is_visible(previous_visibility))
3631
                {
3632
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3633
 
3634
                        // If this sniper is in still mode, if he was hit or can see player, switch to snipe mode.
3635
                        if (ailp.mode == ai_mode::AIM_STILL)
3636
                                if (player_is_visible(player_visibility.visibility) || ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
3637
                                        ailp.mode = ai_mode::AIM_SNIPE_ATTACK;
3638
 
3639
                        if (!robot_is_thief(robptr) && ailp.mode != ai_mode::AIM_STILL)
3640
                                do_snipe_frame(obj, dist_to_player, player_visibility.visibility, vec_to_player);
3641
                } else if (!robot_is_thief(robptr) && !robot_is_companion(robptr))
3642
                        return;
3643
        }
3644
 
3645
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
3646
        auto &vcwallptr = Walls.vcptr;
3647
        // More special ability stuff, but based on a property of a robot, not its ID.
3648
        if (robot_is_companion(robptr)) {
3649
                compute_buddy_vis_vec(obj, obj->pos, player_visibility, robptr);
3650
                auto &player_controlling_guidebot = get_player_controlling_guidebot(BuddyState, Players);
3651
                if (player_controlling_guidebot.objnum != object_none)
3652
                {
3653
                        auto &plrobj_controlling_guidebot = *Objects.vcptr(player_controlling_guidebot.objnum);
3654
                        do_escort_frame(obj, plrobj_controlling_guidebot, player_visibility.visibility);
3655
                }
3656
 
3657
                if (obj->ctype.ai_info.danger_laser_num != object_none) {
3658
                        auto &dobjp = *vcobjptr(obj->ctype.ai_info.danger_laser_num);
3659
                        if ((dobjp.type == OBJ_WEAPON) && (dobjp.signature == obj->ctype.ai_info.danger_laser_signature))
3660
                        {
3661
                                fix circle_distance;
3662
                                circle_distance = robptr.circle_distance[Difficulty_level] + ConsoleObject->size;
3663
                                ai_move_relative_to_player(obj, ailp, dist_to_player, circle_distance, 1, player_visibility, player_info);
3664
                        }
3665
                }
3666
 
3667
                if (guidebot_should_fire_flare(vcobjptr, vcsegptr, vcwallptr, BuddyState, robptr, *obj))
3668
                {
3669
                                Laser_create_new_easy( obj->orient.fvec, obj->pos, obj, weapon_id_type::FLARE_ID, 1);
3670
                                ailp.next_fire = F1_0/2;
3671
                                if (!BuddyState.Buddy_allowed_to_talk) // If buddy not talking, make him fire flares less often.
3672
                                        ailp.next_fire += d_rand()*4;
3673
                }
3674
        }
3675
 
3676
        if (robot_is_thief(robptr)) {
3677
 
3678
                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3679
                do_thief_frame(obj, dist_to_player, player_visibility.visibility, vec_to_player);
3680
 
3681
                if (ready_to_fire_any_weapon(robptr, ailp, 0)) {
3682
                        if (openable_door_on_near_path(vmsegptr, vcwallptr, *obj, *aip))
3683
                        {
3684
                                // @mk, 05/08/95: Firing flare from center of object, this is dumb...
3685
                                Laser_create_new_easy( obj->orient.fvec, obj->pos, obj, weapon_id_type::FLARE_ID, 1);
3686
                                ailp.next_fire = F1_0/2;
3687
                                if (LevelUniqueObjectState.ThiefState.Stolen_item_index == 0)     // If never stolen an item, fire flares less often (bad: Stolen_item_index wraps, but big deal)
3688
                                        ailp.next_fire += d_rand()*4;
3689
                        }
3690
                }
3691
        }
3692
#endif
3693
 
3694
        switch (ailp.mode)
3695
        {
3696
                case ai_mode::AIM_CHASE_OBJECT: {        // chasing player, sort of, chase if far, back off if close, circle in between
3697
                        fix circle_distance;
3698
 
3699
                        circle_distance = robptr.circle_distance[Difficulty_level] + ConsoleObject->size;
3700
                        // Green guy doesn't get his circle distance boosted, else he might never attack.
3701
                        if (robptr.attack_type != 1)
3702
                                circle_distance += (objnum&0xf) * F1_0/2;
3703
 
3704
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3705
 
3706
                        // @mk, 12/27/94, structure here was strange.  Would do both clauses of what are now this if/then/else.  Used to be if/then, if/then.
3707
                        if (player_visibility.visibility != player_visibility_state::visible_and_in_field_of_view && previous_visibility == player_visibility_state::visible_and_in_field_of_view)
3708
                        { // this is redundant: mk, 01/15/95: && (ailp->mode == ai_mode::AIM_CHASE_OBJECT))
3709
                                if (!ai_multiplayer_awareness(obj, 53)) {
3710
                                        if (maybe_ai_do_actual_firing_stuff(obj))
3711
                                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
3712
                                        return;
3713
                                }
3714
                                create_path_to_believed_player_segment(obj, 8, create_path_safety_flag::safe);
3715
                                ai_multi_send_robot_position(obj, -1);
3716
                        } else if (!player_is_visible(player_visibility.visibility) && dist_to_player > F1_0 * 80 && !(Game_mode & GM_MULTI))
3717
                        {
3718
                                // If pretty far from the player, player cannot be seen
3719
                                // (obstructed) and in chase mode, switch to follow path mode.
3720
                                // This has one desirable benefit of avoiding physics retries.
3721
                                if (aip->behavior == ai_behavior::AIB_STATION) {
3722
                                        ailp.goal_segment = aip->hide_segment;
3723
                                        create_path_to_station(obj, 15);
3724
                                } // -- this looks like a dumb thing to do...robots following paths far away from you! else create_n_segment_path(obj, 5, -1);
3725
#if defined(DXX_BUILD_DESCENT_I)
3726
                                else
3727
                                        create_n_segment_path(obj, 5, segment_none);
3728
#endif
3729
                                break;
3730
                        }
3731
 
3732
                        if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) {
3733
                                const auto pv = player_visibility.visibility;
3734
                                if (player_is_visible(pv))
3735
                                {
3736
                                        const auto upv = static_cast<unsigned>(pv);
3737
                                        if (d_rand() < FrameTime * upv)
3738
                                                if (dist_to_player/256 < static_cast<fix>(d_rand() * upv))
3739
                                                {
3740
                                                        aip->GOAL_STATE = AIS_SRCH;
3741
                                                        aip->CURRENT_STATE = AIS_SRCH;
3742
                                                }
3743
                                }
3744
                        }
3745
 
3746
                        if (GameTime64 - ailp.time_player_seen > CHASE_TIME_LENGTH)
3747
                        {
3748
 
3749
                                if (Game_mode & GM_MULTI)
3750
                                {
3751
                                        if (!player_is_visible(player_visibility.visibility) && dist_to_player > F1_0 * 70)
3752
                                        {
3753
                                                ailp.mode = ai_mode::AIM_STILL;
3754
                                                return;
3755
                                        }
3756
                                }
3757
 
3758
                                if (!ai_multiplayer_awareness(obj, 64)) {
3759
                                        if (maybe_ai_do_actual_firing_stuff(obj))
3760
                                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
3761
                                        return;
3762
                                }
3763
#if defined(DXX_BUILD_DESCENT_I)
3764
                                create_path_to_believed_player_segment(obj, 10, create_path_safety_flag::safe);
3765
                                ai_multi_send_robot_position(obj, -1);
3766
#endif
3767
                        } else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) {
3768
                                if (!ai_multiplayer_awareness(obj, 70)) {
3769
                                        if (maybe_ai_do_actual_firing_stuff(obj))
3770
                                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
3771
                                        return;
3772
                                }
3773
                                ai_move_relative_to_player(obj, ailp, dist_to_player, circle_distance, 0, player_visibility, player_info);
3774
 
3775
                                if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) {
3776
                                        if (player_is_visible(player_visibility.visibility))
3777
                                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
3778
#if defined(DXX_BUILD_DESCENT_I)
3779
                                        else
3780
                                                ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
3781
#endif
3782
                                }
3783
 
3784
                                ai_multi_send_robot_position(obj, ai_evaded ? (ai_evaded = 0, 1) : -1);
3785
 
3786
                                do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
3787
                        }
3788
                        break;
3789
                }
3790
 
3791
                case ai_mode::AIM_RUN_FROM_OBJECT:
3792
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3793
 
3794
                        {
3795
                                const auto pv = player_visibility.visibility;
3796
                                const auto player_is_in_line_of_sight = player_is_visible(pv);
3797
                                if (player_is_in_line_of_sight)
3798
                                {
3799
                                if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
3800
                                        ailp.player_awareness_type = player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION;
3801
                                }
3802
 
3803
                        // If in multiplayer, only do if player visible.  If not multiplayer, do always.
3804
                                if (!(Game_mode & GM_MULTI) || player_is_in_line_of_sight)
3805
                                if (ai_multiplayer_awareness(obj, 75)) {
3806
                                        ai_follow_path(obj, player_visibility.visibility, &vec_to_player);
3807
                                        ai_multi_send_robot_position(obj, -1);
3808
                                }
3809
 
3810
                        if (aip->GOAL_STATE != AIS_FLIN)
3811
                                aip->GOAL_STATE = AIS_LOCK;
3812
                        else if (aip->CURRENT_STATE == AIS_FLIN)
3813
                                aip->GOAL_STATE = AIS_LOCK;
3814
 
3815
                        // Bad to let run_from robot fire at player because it
3816
                        // will cause a war in which it turns towards the player
3817
                        // to fire and then towards its goal to move.
3818
                        // do_firing_stuff(obj, player_visibility, &vec_to_player);
3819
                        // Instead, do this:
3820
                        // (Note, only drop if player is visible.  This prevents
3821
                        // the bombs from being a giveaway, and also ensures that
3822
                        // the robot is moving while it is dropping.  Also means
3823
                        // fewer will be dropped.)
3824
                        if (ready_to_fire_weapon1(ailp, 0) && player_is_in_line_of_sight)
3825
                        {
3826
                                if (!ai_multiplayer_awareness(obj, 75))
3827
                                        return;
3828
 
3829
                                const auto fire_vec = vm_vec_negated(obj->orient.fvec);
3830
                                const auto fire_pos = vm_vec_add(obj->pos, fire_vec);
3831
 
3832
#if defined(DXX_BUILD_DESCENT_I)
3833
                                ailp.next_fire = F1_0*5;                //      Drop a proximity bomb every 5 seconds.
3834
                                const auto weapon_id =
3835
#elif defined(DXX_BUILD_DESCENT_II)
3836
                                ailp.next_fire = (F1_0/2)*(NDL+5 - Difficulty_level);      // Drop a proximity bomb every 5 seconds.
3837
                                const auto weapon_id = (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
3838
                                        ? weapon_id_type::ROBOT_SUPERPROX_ID
3839
                                        :
3840
#endif
3841
                                        weapon_id_type::PROXIMITY_ID;
3842
                                Laser_create_new_easy(fire_vec, fire_pos, obj, weapon_id, 1);
3843
 
3844
                                if (Game_mode & GM_MULTI)
3845
                                {
3846
                                        ai_multi_send_robot_position(obj, -1);
3847
                                        const int gun_num =
3848
#if defined(DXX_BUILD_DESCENT_II)
3849
                                                (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
3850
                                                ? -2
3851
                                                :
3852
#endif
3853
                                        -1;
3854
                                        multi_send_robot_fire(obj, gun_num, fire_vec);
3855
                                }
3856
                        }
3857
                        }
3858
                        break;
3859
 
3860
#if defined(DXX_BUILD_DESCENT_II)
3861
                case ai_mode::AIM_GOTO_PLAYER:
3862
                case ai_mode::AIM_GOTO_OBJECT:
3863
                        ai_follow_path(obj, player_visibility_state::visible_and_in_field_of_view, &vec_to_player);    // Follows path as if player can see robot.
3864
                        ai_multi_send_robot_position(obj, -1);
3865
                        break;
3866
#endif
3867
 
3868
                case ai_mode::AIM_FOLLOW_PATH: {
3869
                        int anger_level = 65;
3870
 
3871
                        if (aip->behavior == ai_behavior::AIB_STATION)
3872
                        {
3873
                                const std::size_t idx = aip->hide_index + aip->path_length - 1;
3874
                                if (idx < Point_segs.size() && Point_segs[idx].segnum == aip->hide_segment)
3875
                                {
3876
                                        anger_level = 64;
3877
                                }
3878
                        }
3879
 
3880
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3881
 
3882
                        if (!ai_multiplayer_awareness(obj, anger_level)) {
3883
                                if (maybe_ai_do_actual_firing_stuff(obj)) {
3884
                                        ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
3885
                                }
3886
                                return;
3887
                        }
3888
 
3889
                        ai_follow_path(obj, player_visibility.visibility, &vec_to_player);
3890
 
3891
                        if (aip->GOAL_STATE != AIS_FLIN)
3892
                                aip->GOAL_STATE = AIS_LOCK;
3893
                        else if (aip->CURRENT_STATE == AIS_FLIN)
3894
                                aip->GOAL_STATE = AIS_LOCK;
3895
 
3896
#if defined(DXX_BUILD_DESCENT_I)
3897
                        if ((aip->behavior != ai_behavior::AIB_FOLLOW_PATH) && (aip->behavior != ai_behavior::AIB_RUN_FROM))
3898
                                do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
3899
 
3900
                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && aip->behavior != ai_behavior::AIB_FOLLOW_PATH && aip->behavior != ai_behavior::AIB_RUN_FROM && get_robot_id(obj) != ROBOT_BRAIN)
3901
                        {
3902
                                if (robptr.attack_type == 0)
3903
                                        ailp.mode = ai_mode::AIM_CHASE_OBJECT;
3904
                        }
3905
                        else if (!player_is_visible(player_visibility.visibility)
3906
                                && (aip->behavior == ai_behavior::AIB_NORMAL)
3907
                                && (ailp.mode == ai_mode::AIM_FOLLOW_PATH)
3908
                                && (aip->behavior != ai_behavior::AIB_RUN_FROM))
3909
                        {
3910
                                ailp.mode = ai_mode::AIM_STILL;
3911
                                aip->hide_index = -1;
3912
                                aip->path_length = 0;
3913
                        }
3914
#elif defined(DXX_BUILD_DESCENT_II)
3915
                        if (aip->behavior != ai_behavior::AIB_RUN_FROM)
3916
                                do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
3917
 
3918
                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view &&
3919
                                aip->behavior != ai_behavior::AIB_SNIPE &&
3920
                                aip->behavior != ai_behavior::AIB_FOLLOW &&
3921
                                aip->behavior != ai_behavior::AIB_RUN_FROM &&
3922
                                get_robot_id(obj) != ROBOT_BRAIN &&
3923
                                robot_is_companion(robptr) != 1 &&
3924
                                robot_is_thief(robptr) != 1)
3925
                        {
3926
                                if (robptr.attack_type == 0)
3927
                                        ailp.mode = ai_mode::AIM_CHASE_OBJECT;
3928
                                // This should not just be distance based, but also time-since-player-seen based.
3929
                        }
3930
                        else if ((dist_to_player > F1_0 * (20 * (2 * static_cast<int>(Difficulty_level) + robptr.pursuit)))
3931
                                && (GameTime64 - ailp.time_player_seen > (F1_0/2*(Difficulty_level + robptr.pursuit)))
3932
                                && !player_is_visible(player_visibility.visibility)
3933
                                && (aip->behavior == ai_behavior::AIB_NORMAL)
3934
                                && (ailp.mode == ai_mode::AIM_FOLLOW_PATH))
3935
                        {
3936
                                ailp.mode = ai_mode::AIM_STILL;
3937
                                aip->hide_index = -1;
3938
                                aip->path_length = 0;
3939
                        }
3940
#endif
3941
 
3942
                        ai_multi_send_robot_position(obj, -1);
3943
 
3944
                        break;
3945
                }
3946
 
3947
#if defined(DXX_BUILD_DESCENT_I)
3948
                case ai_mode::AIM_HIDE:
3949
#elif defined(DXX_BUILD_DESCENT_II)
3950
                case ai_mode::AIM_BEHIND:
3951
#endif
3952
                        if (!ai_multiplayer_awareness(obj, 71)) {
3953
                                if (maybe_ai_do_actual_firing_stuff(obj)) {
3954
                                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3955
                                        ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
3956
                                }
3957
                                return;
3958
                        }
3959
 
3960
                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
3961
 
3962
#if defined(DXX_BUILD_DESCENT_I)
3963
                        ai_follow_path(obj, player_visibility.visibility, nullptr);
3964
#elif defined(DXX_BUILD_DESCENT_II)
3965
                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
3966
                        {
3967
                                // Get behind the player.
3968
                                // Method:
3969
                                // If vec_to_player dot player_rear_vector > 0, behind is goal.
3970
                                // Else choose goal with larger dot from left, right.
3971
                                vms_vector  goal_vector;
3972
                                fix         dot;
3973
 
3974
                                dot = vm_vec_dot(ConsoleObject->orient.fvec, vec_to_player);
3975
                                if (dot > 0) {          // Remember, we're interested in the rear vector dot being < 0.
3976
                                        goal_vector = vm_vec_negated(ConsoleObject->orient.fvec);
3977
                                } else {
3978
                                        auto &orient = ConsoleObject->orient;
3979
                                        constexpr unsigned choice_count = 15;
3980
                                        const unsigned selector = (ConsoleObject->id ^ obj.get_unchecked_index() ^ ConsoleObject->segnum ^ obj->segnum) % (choice_count + 1);
3981
                                        if (selector == 0)
3982
                                                goal_vector = orient.rvec;
3983
                                        else if (selector == choice_count)
3984
                                                goal_vector = orient.uvec;
3985
                                        else
3986
                                        {
3987
                                                vm_vec_scale_add2(goal_vector, orient.rvec, (choice_count - selector) * F1_0);
3988
                                                vm_vec_copy_scale(goal_vector, orient.uvec, selector * F1_0);
3989
                                                vm_vec_normalize_quick(goal_vector);
3990
                                        }
3991
                                        if (vm_vec_dot(goal_vector, vec_to_player) > 0)
3992
                                                vm_vec_negate(goal_vector);
3993
                                }
3994
 
3995
                                vm_vec_scale(goal_vector, 2*(ConsoleObject->size + obj->size + (((objnum*4 + d_tick_count) & 63) << 12)));
3996
                                auto goal_point = vm_vec_add(ConsoleObject->pos, goal_vector);
3997
                                vm_vec_scale_add2(goal_point, make_random_vector(), F1_0*8);
3998
                                const auto vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, obj->pos));
3999
                                move_towards_vector(obj, vec_to_goal, 0);
4000
                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4001
                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4002
                        }
4003
#endif
4004
 
4005
                        if (aip->GOAL_STATE != AIS_FLIN)
4006
                                aip->GOAL_STATE = AIS_LOCK;
4007
                        else if (aip->CURRENT_STATE == AIS_FLIN)
4008
                                aip->GOAL_STATE = AIS_LOCK;
4009
 
4010
                        ai_multi_send_robot_position(obj, -1);
4011
                        break;
4012
 
4013
                case ai_mode::AIM_STILL:
4014
                        if ((dist_to_player < F1_0 * 120 + static_cast<int>(Difficulty_level) * F1_0 * 20) || (static_cast<unsigned>(ailp.player_awareness_type) >= static_cast<unsigned>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1))
4015
                        {
4016
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4017
 
4018
                                // turn towards vector if visible this time or last time, or rand
4019
                                // new!
4020
#if defined(DXX_BUILD_DESCENT_I)
4021
                                if (player_is_visible(player_visibility.visibility) || player_is_visible(previous_visibility) || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI)))
4022
#elif defined(DXX_BUILD_DESCENT_II)
4023
                                if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view || previous_visibility == player_visibility_state::visible_and_in_field_of_view) // -- MK, 06/09/95:  || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI)))
4024
#endif
4025
                                {
4026
                                        if (!ai_multiplayer_awareness(obj, 71)) {
4027
                                                if (maybe_ai_do_actual_firing_stuff(obj))
4028
                                                        ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4029
                                                return;
4030
                                        }
4031
                                        ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4032
                                        ai_multi_send_robot_position(obj, -1);
4033
                                }
4034
 
4035
                                do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
4036
#if defined(DXX_BUILD_DESCENT_I)
4037
                                if (player_is_visible(player_visibility.visibility)) // Change, MK, 01/03/94 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
4038
#elif defined(DXX_BUILD_DESCENT_II)
4039
                                if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view) // Changed @mk, 09/21/95: Require that they be looking to evade.  Change, MK, 01/03/95 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
4040
#endif
4041
                                {
4042
                                        if (robptr.attack_type == 1) {
4043
                                                aip->behavior = ai_behavior::AIB_NORMAL;
4044
                                                if (!ai_multiplayer_awareness(obj, 80)) {
4045
                                                        if (maybe_ai_do_actual_firing_stuff(obj))
4046
                                                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4047
                                                        return;
4048
                                                }
4049
                                                ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 0, player_visibility, player_info);
4050
                                                ai_multi_send_robot_position(obj, ai_evaded ? (ai_evaded = 0, 1) : -1);
4051
                                        } else {
4052
                                                // Robots in hover mode are allowed to evade at half normal speed.
4053
                                                if (!ai_multiplayer_awareness(obj, 81)) {
4054
                                                        if (maybe_ai_do_actual_firing_stuff(obj))
4055
                                                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4056
                                                        return;
4057
                                                }
4058
                                                ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 1, player_visibility, player_info);
4059
                                                if (ai_evaded) {
4060
                                                        ai_multi_send_robot_position(obj, -1);
4061
                                                        ai_evaded = 0;
4062
                                                }
4063
                                                else
4064
                                                        ai_multi_send_robot_position(obj, -1);
4065
                                        }
4066
                                } else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
4067
                                        // If pretty far from the player, player cannot be
4068
                                        // seen (obstructed) and in chase mode, switch to
4069
                                        // follow path mode.
4070
                                        // This has one desirable benefit of avoiding physics retries.
4071
                                        if (aip->behavior == ai_behavior::AIB_STATION) {
4072
                                                ailp.goal_segment = aip->hide_segment;
4073
                                                create_path_to_station(obj, 15);
4074
                                        }
4075
                                        break;
4076
                                }
4077
                        }
4078
 
4079
                        break;
4080
                case ai_mode::AIM_OPEN_DOOR: {       // trying to open a door.
4081
                        Assert(get_robot_id(obj) == ROBOT_BRAIN);     // Make sure this guy is allowed to be in this mode.
4082
 
4083
                        if (!ai_multiplayer_awareness(obj, 62))
4084
                                return;
4085
                        auto &vcvertptr = Vertices.vcptr;
4086
                        const auto &&center_point = compute_center_point_on_side(vcvertptr, vcsegptr(obj->segnum), aip->GOALSIDE);
4087
                        const auto goal_vector = vm_vec_normalized_quick(vm_vec_sub(center_point, obj->pos));
4088
                        ai_turn_towards_vector(goal_vector, obj, robptr.turn_time[Difficulty_level]);
4089
                        move_towards_vector(obj, goal_vector, 0);
4090
                        ai_multi_send_robot_position(obj, -1);
4091
 
4092
                        break;
4093
                }
4094
 
4095
#if defined(DXX_BUILD_DESCENT_II)
4096
                case ai_mode::AIM_SNIPE_WAIT:
4097
                        break;
4098
                case ai_mode::AIM_SNIPE_RETREAT:
4099
                        // -- if (ai_multiplayer_awareness(obj, 53))
4100
                        // --   if (ailp->next_fire < -F1_0)
4101
                        // --           ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
4102
                        break;
4103
                case ai_mode::AIM_SNIPE_RETREAT_BACKWARDS:
4104
                case ai_mode::AIM_SNIPE_ATTACK:
4105
                case ai_mode::AIM_SNIPE_FIRE:
4106
                        if (ai_multiplayer_awareness(obj, 53)) {
4107
                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4108
                                if (robot_is_thief(robptr))
4109
                                        ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 0, player_visibility, player_info);
4110
                                break;
4111
                        }
4112
                        break;
4113
 
4114
                case ai_mode::AIM_THIEF_WAIT:
4115
                case ai_mode::AIM_THIEF_ATTACK:
4116
                case ai_mode::AIM_THIEF_RETREAT:
4117
                case ai_mode::AIM_WANDER:    // Used for Buddy Bot
4118
                        break;
4119
#endif
4120
 
4121
                default:
4122
                        ailp.mode = ai_mode::AIM_CHASE_OBJECT;
4123
                        break;
4124
        }       // end: switch (ailp->mode)
4125
 
4126
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
4127
        // If the robot can see you, increase his awareness of you.
4128
        // This prevents the problem of a robot looking right at you but doing nothing.
4129
        // Assert(player_visibility != -1); // Means it didn't get initialized!
4130
        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4131
        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
4132
        {
4133
#if defined(DXX_BUILD_DESCENT_I)
4134
                if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
4135
                        ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
4136
#elif defined(DXX_BUILD_DESCENT_II)
4137
        if (aip->behavior != ai_behavior::AIB_FOLLOW && !robot_is_thief(robptr))
4138
        {
4139
                if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE && (aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE))
4140
                        aip->SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
4141
                else if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
4142
                        ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
4143
        }
4144
#endif
4145
        }
4146
 
4147
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
4148
        if (!object_animates) {
4149
                aip->CURRENT_STATE = aip->GOAL_STATE;
4150
        }
4151
 
4152
        assert(static_cast<unsigned>(ailp.player_awareness_type) <= AIE_MAX);
4153
        Assert(aip->CURRENT_STATE < AIS_MAX);
4154
        Assert(aip->GOAL_STATE < AIS_MAX);
4155
 
4156
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
4157
        if (ailp.player_awareness_type != player_awareness_type_t::PA_NONE) {
4158
                new_goal_state = Ai_transition_table[static_cast<unsigned>(ailp.player_awareness_type) - 1][aip->CURRENT_STATE][aip->GOAL_STATE];
4159
                if (ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
4160
                {
4161
                        // Decrease awareness, else this robot will flinch every frame.
4162
                        ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);
4163
                        ailp.player_awareness_time = F1_0*3;
4164
                }
4165
 
4166
                if (new_goal_state == AIS_ERR_)
4167
                        new_goal_state = AIS_REST;
4168
 
4169
                if (aip->CURRENT_STATE == AIS_NONE)
4170
                        aip->CURRENT_STATE = AIS_REST;
4171
 
4172
                aip->GOAL_STATE = new_goal_state;
4173
 
4174
        }
4175
 
4176
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
4177
        // If new state = fire, then set all gun states to fire.
4178
        if (aip->GOAL_STATE == AIS_FIRE)
4179
        {
4180
                uint_fast32_t num_guns = robptr.n_guns;
4181
                for (uint_fast32_t i=0; i<num_guns; i++)
4182
                        ailp.goal_state[i] = AIS_FIRE;
4183
        }
4184
 
4185
        // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
4186
        // Hack by mk on 01/04/94, if a guy hasn't animated to the firing state, but his next_fire says ok to fire, bash him there
4187
        if (ready_to_fire_any_weapon(robptr, ailp, 0) && (aip->GOAL_STATE == AIS_FIRE))
4188
                aip->CURRENT_STATE = AIS_FIRE;
4189
 
4190
        if ((aip->GOAL_STATE != AIS_FLIN)  && (get_robot_id(obj) != ROBOT_BRAIN)) {
4191
                switch (aip->CURRENT_STATE) {
4192
                        case AIS_NONE:
4193
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4194
 
4195
                                {
4196
                                const fix dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
4197
                                if (dot >= F1_0/2)
4198
                                        if (aip->GOAL_STATE == AIS_REST)
4199
                                                aip->GOAL_STATE = AIS_SRCH;
4200
                                }
4201
                                break;
4202
                        case AIS_REST:
4203
                                if (aip->GOAL_STATE == AIS_REST) {
4204
                                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4205
                                        if (ready_to_fire_any_weapon(robptr, ailp, 0) && player_is_visible(player_visibility.visibility))
4206
                                        {
4207
                                                aip->GOAL_STATE = AIS_FIRE;
4208
                                        }
4209
                                }
4210
                                break;
4211
                        case AIS_SRCH:
4212
                                if (!ai_multiplayer_awareness(obj, 60))
4213
                                        return;
4214
 
4215
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4216
 
4217
#if defined(DXX_BUILD_DESCENT_I)
4218
                                if (player_is_visible(player_visibility.visibility))
4219
                                {
4220
                                        ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4221
                                        ai_multi_send_robot_position(obj, -1);
4222
                                } else if (!(Game_mode & GM_MULTI))
4223
                                        ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
4224
#elif defined(DXX_BUILD_DESCENT_II)
4225
                                if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
4226
                                {
4227
                                        ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4228
                                        ai_multi_send_robot_position(obj, -1);
4229
                                }
4230
#endif
4231
                                break;
4232
                        case AIS_LOCK:
4233
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4234
 
4235
                                if (!(Game_mode & GM_MULTI) || player_is_visible(player_visibility.visibility))
4236
                                {
4237
                                        if (!ai_multiplayer_awareness(obj, 68))
4238
                                                return;
4239
 
4240
#if defined(DXX_BUILD_DESCENT_I)
4241
                                        if (player_is_visible(player_visibility.visibility))
4242
                                        {
4243
                                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4244
                                                ai_multi_send_robot_position(obj, -1);
4245
                                        }
4246
                                        else if (!(Game_mode & GM_MULTI))
4247
                                                ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
4248
#elif defined(DXX_BUILD_DESCENT_II)
4249
                                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
4250
                                        {   // @mk, 09/21/95, require that they be looking towards you to turn towards you.
4251
                                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4252
                                                ai_multi_send_robot_position(obj, -1);
4253
                                        }
4254
#endif
4255
                                }
4256
                                break;
4257
                        case AIS_FIRE:
4258
                                compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4259
 
4260
#if defined(DXX_BUILD_DESCENT_I)
4261
                                if (player_is_visible(player_visibility.visibility))
4262
                                {
4263
                                        if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1)))
4264
                                        {
4265
                                                if (Game_mode & GM_MULTI) {
4266
                                                        ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, 0);
4267
                                                        return;
4268
                                                }
4269
                                        }
4270
                                        ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4271
                                        ai_multi_send_robot_position(obj, -1);
4272
                                } else if (!(Game_mode & GM_MULTI)) {
4273
                                        ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
4274
                                }
4275
#elif defined(DXX_BUILD_DESCENT_II)
4276
                                if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
4277
                                {
4278
                                        if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) {
4279
                                                if (Game_mode & GM_MULTI) {
4280
                                                        ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4281
                                                        return;
4282
                                                }
4283
                                        }
4284
                                        ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4285
                                        ai_multi_send_robot_position(obj, -1);
4286
                                }
4287
#endif
4288
 
4289
                                // Fire at player, if appropriate.
4290
                                ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
4291
 
4292
                                break;
4293
                        case AIS_RECO:
4294
                                if (!(obj_ref & 3)) {
4295
                                        compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
4296
#if defined(DXX_BUILD_DESCENT_I)
4297
                                        if (player_is_visible(player_visibility.visibility))
4298
                                        {
4299
                                                if (!ai_multiplayer_awareness(obj, 69))
4300
                                                        return;
4301
                                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4302
                                                ai_multi_send_robot_position(obj, -1);
4303
                                        }
4304
                                        else if (!(Game_mode & GM_MULTI)) {
4305
                                                ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
4306
                                        }
4307
#elif defined(DXX_BUILD_DESCENT_II)
4308
                                        if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
4309
                                        {
4310
                                                if (!ai_multiplayer_awareness(obj, 69))
4311
                                                        return;
4312
                                                ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
4313
                                                ai_multi_send_robot_position(obj, -1);
4314
                                        }
4315
#endif
4316
                                }
4317
                                break;
4318
                        case AIS_FLIN:
4319
                                break;
4320
                        default:
4321
                                aip->GOAL_STATE = AIS_REST;
4322
                                aip->CURRENT_STATE = AIS_REST;
4323
                                break;
4324
                }
4325
        } // end of: if (aip->GOAL_STATE != AIS_FLIN)
4326
 
4327
        // Switch to next gun for next fire.
4328
        if (!player_is_visible(player_visibility.visibility))
4329
        {
4330
                aip->CURRENT_GUN++;
4331
                if (aip->CURRENT_GUN >= robptr.n_guns)
4332
                {
4333
#if defined(DXX_BUILD_DESCENT_II)
4334
                        if (!((robptr.n_guns == 1) || (robptr.weapon_type2 == weapon_none)))  // Two weapon types hack.
4335
                                aip->CURRENT_GUN = 1;
4336
                        else
4337
#endif
4338
                                aip->CURRENT_GUN = 0;
4339
                }
4340
        }
4341
 
4342
}
4343
 
4344
// ----------------------------------------------------------------------------
4345
void ai_do_cloak_stuff(void)
4346
{
4347
        range_for (auto &i, Ai_cloak_info) {
4348
                i.last_time = GameTime64;
4349
#if defined(DXX_BUILD_DESCENT_II)
4350
                i.last_segment = ConsoleObject->segnum;
4351
#endif
4352
                i.last_position = ConsoleObject->pos;
4353
        }
4354
 
4355
        // Make work for control centers.
4356
        Believed_player_pos = Ai_cloak_info[0].last_position;
4357
#if defined(DXX_BUILD_DESCENT_II)
4358
        Believed_player_seg = Ai_cloak_info[0].last_segment;
4359
#endif
4360
}
4361
 
4362
// --------------------------------------------------------------------------------------------------------------------
4363
//      Call this each time the player starts a new ship.
4364
void init_ai_for_ship()
4365
{
4366
        ai_do_cloak_stuff();
4367
}
4368
 
4369
// ----------------------------------------------------------------------------
4370
// Returns false if awareness is considered too puny to add, else returns true.
4371
static int add_awareness_event(const object_base &objp, player_awareness_type_t type, d_level_unique_robot_awareness_state &awareness)
4372
{
4373
        // If player cloaked and hit a robot, then increase awareness
4374
        if (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION ||
4375
                type == player_awareness_type_t::PA_WEAPON_WALL_COLLISION ||
4376
                type == player_awareness_type_t::PA_PLAYER_COLLISION)
4377
                ai_do_cloak_stuff();
4378
 
4379
        if (awareness.Num_awareness_events < awareness.Awareness_events.size())
4380
        {
4381
                if (type == player_awareness_type_t::PA_WEAPON_WALL_COLLISION ||
4382
                        type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
4383
                        if (objp.type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::VULCAN_ID)
4384
                                if (d_rand() > 3276)
4385
                                        return 0;       // For vulcan cannon, only about 1/10 actually cause awareness
4386
 
4387
                auto &e = awareness.Awareness_events[awareness.Num_awareness_events++];
4388
                e.segnum = objp.segnum;
4389
                e.pos = objp.pos;
4390
                e.type = type;
4391
        } else {
4392
                //Int3();   // Hey -- Overflowed Awareness_events, make more or something
4393
                // This just gets ignored, so you can just
4394
                // continue.
4395
        }
4396
        return 1;
4397
 
4398
}
4399
 
4400
// ----------------------------------------------------------------------------------
4401
// Robots will become aware of the player based on something that occurred.
4402
// The object (probably player or weapon) which created the awareness is objp.
4403
void create_awareness_event(object &objp, player_awareness_type_t type, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState)
4404
{
4405
        // If not in multiplayer, or in multiplayer with robots, do this, else unnecessary!
4406
        if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
4407
        {
4408
                if (add_awareness_event(objp, type, LevelUniqueRobotAwarenessState))
4409
                {
4410
                        if (((d_rand() * (static_cast<unsigned>(type) + 4)) >> 15) > 4)
4411
                                Overall_agitation++;
4412
                        if (Overall_agitation > OVERALL_AGITATION_MAX)
4413
                                Overall_agitation = OVERALL_AGITATION_MAX;
4414
                }
4415
        }
4416
}
4417
 
4418
// ----------------------------------------------------------------------------------
4419
static void pae_aux(const vcsegptridx_t segnum, const player_awareness_type_t type, const int level, awareness_t &New_awareness)
4420
{
4421
        if (New_awareness[segnum] < type)
4422
                New_awareness[segnum] = type;
4423
 
4424
        // Process children.
4425
#if defined(DXX_BUILD_DESCENT_I)
4426
        if (level <= 4)
4427
#elif defined(DXX_BUILD_DESCENT_II)
4428
        if (level <= 3)
4429
#endif
4430
        {
4431
                const auto subtype = (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
4432
                        ? player_awareness_type_t::PA_PLAYER_COLLISION
4433
                        : type;
4434
                const auto sublevel = level + 1;
4435
                range_for (auto &j, segnum->children)
4436
                        if (IS_CHILD(j))
4437
                                pae_aux(segnum.absolute_sibling(j), subtype, sublevel, New_awareness);
4438
        }
4439
}
4440
 
4441
 
4442
// ----------------------------------------------------------------------------------
4443
static void process_awareness_events(fvcsegptridx &vcsegptridx, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState, awareness_t &New_awareness)
4444
{
4445
        const auto Num_awareness_events = std::exchange(LevelUniqueRobotAwarenessState.Num_awareness_events, 0);
4446
        if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
4447
        {
4448
                New_awareness.fill(player_awareness_type_t::PA_NONE);
4449
                range_for (auto &i, partial_const_range(LevelUniqueRobotAwarenessState.Awareness_events, Num_awareness_events))
4450
                        pae_aux(vcsegptridx(i.segnum), i.type, 1, New_awareness);
4451
        }
4452
}
4453
 
4454
// ----------------------------------------------------------------------------------
4455
static void set_player_awareness_all(fvmobjptr &vmobjptr, fvcsegptridx &vcsegptridx, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState)
4456
{
4457
        awareness_t New_awareness;
4458
 
4459
        process_awareness_events(vcsegptridx, LevelUniqueRobotAwarenessState, New_awareness);
4460
 
4461
        range_for (const auto &&objp, vmobjptr)
4462
        {
4463
                if (objp->type == OBJ_ROBOT && objp->control_type == CT_AI)
4464
                {
4465
                        auto &ailp = objp->ctype.ai_info.ail;
4466
                        if (New_awareness[objp->segnum] > ailp.player_awareness_type) {
4467
                                ailp.player_awareness_type = New_awareness[objp->segnum];
4468
                                ailp.player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
4469
 
4470
#if defined(DXX_BUILD_DESCENT_II)
4471
                        // Clear the bit that says this robot is only awake because a camera woke it up.
4472
                                objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
4473
#endif
4474
                        }
4475
                }
4476
        }
4477
}
4478
 
4479
}
4480
 
4481
#ifndef NDEBUG
4482
#if PARALLAX
4483
int Ai_dump_enable = 0;
4484
 
4485
FILE *Ai_dump_file = NULL;
4486
 
4487
char Ai_error_message[128] = "";
4488
 
4489
// ----------------------------------------------------------------------------------
4490
namespace dsx {
4491
static void dump_ai_objects_all()
4492
{
4493
#if defined(DXX_BUILD_DESCENT_I)
4494
        int     total=0;
4495
        time_t  time_of_day;
4496
 
4497
        time_of_day = time(NULL);
4498
 
4499
        if (!Ai_dump_enable)
4500
                return;
4501
 
4502
        if (Ai_dump_file == NULL)
4503
                Ai_dump_file = fopen("ai.out","a+t");
4504
 
4505
        fprintf(Ai_dump_file, "\nnum: seg distance __mode__ behav.    [velx vely velz] (Tick = %i)\n", d_tick_count);
4506
        fprintf(Ai_dump_file, "Date & Time = %s\n", ctime(&time_of_day));
4507
 
4508
        if (Ai_error_message[0])
4509
                fprintf(Ai_dump_file, "Error message: %s\n", Ai_error_message);
4510
 
4511
        range_for (const auto &&objp, vcobjptridx)
4512
        {
4513
                ai_static       *aip = &objp->ctype.ai_info;
4514
                ai_local                *ailp = &objp->ctype.ai_info.ail;
4515
                fix                     dist_to_player;
4516
 
4517
                dist_to_player = vm_vec_dist(objp->pos, ConsoleObject->pos);
4518
 
4519
                if (objp->control_type == CT_AI) {
4520
                        fprintf(Ai_dump_file, "%3i: %3i %8.3f %8s %8s [%3i %4i]\n",
4521
                                static_cast<uint16_t>(objp), objp->segnum, f2fl(dist_to_player), mode_text[ailp->mode], behavior_text[aip->behavior-0x80], aip->hide_index, aip->path_length);
4522
                        if (aip->path_length)
4523
                                total += aip->path_length;
4524
                }
4525
        }
4526
 
4527
        fprintf(Ai_dump_file, "Total path length = %4i\n", total);
4528
#endif
4529
}
4530
}
4531
 
4532
void force_dump_ai_objects_all(const char *msg)
4533
{
4534
        int tsave;
4535
 
4536
        tsave = Ai_dump_enable;
4537
 
4538
        Ai_dump_enable = 1;
4539
 
4540
        sprintf(Ai_error_message, "%s\n", msg);
4541
        dump_ai_objects_all();
4542
        Ai_error_message[0] = 0;
4543
 
4544
        Ai_dump_enable = tsave;
4545
}
4546
 
4547
// ----------------------------------------------------------------------------------
4548
#else
4549
static inline void dump_ai_objects_all()
4550
{
4551
}
4552
#endif
4553
#endif
4554
 
4555
namespace dsx {
4556
 
4557
// ----------------------------------------------------------------------------------
4558
// Do things which need to get done for all AI objects each frame.
4559
// This includes:
4560
//  Setting player_awareness (a fix, time in seconds which object is aware of player)
4561
void do_ai_frame_all(void)
4562
{
4563
        auto &Objects = LevelUniqueObjectState.Objects;
4564
        auto &vmobjptr = Objects.vmptr;
4565
#ifndef NDEBUG
4566
        dump_ai_objects_all();
4567
#endif
4568
 
4569
        set_player_awareness_all(vmobjptr, vcsegptridx, LevelUniqueRobotAwarenessState);
4570
 
4571
#if defined(DXX_BUILD_DESCENT_II)
4572
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
4573
        auto &vmobjptridx = Objects.vmptridx;
4574
        if (Ai_last_missile_camera)
4575
        {
4576
                // Clear if supposed misisle camera is not a weapon, or just every so often, just in case.
4577
                if (((d_tick_count & 0x0f) == 0) || (Ai_last_missile_camera->type != OBJ_WEAPON)) {
4578
                        Ai_last_missile_camera = nullptr;
4579
                        range_for (const auto &&objp, vmobjptr)
4580
                        {
4581
                                if (objp->type == OBJ_ROBOT)
4582
                                        objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
4583
                        }
4584
                }
4585
        }
4586
 
4587
        // (Moved here from do_d2_boss_stuff() because that only gets called if robot aware of player.)
4588
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
4589
        if (BossUniqueState.Boss_dying) {
4590
                range_for (const auto &&objp, vmobjptridx)
4591
                {
4592
                        if (objp->type == OBJ_ROBOT)
4593
                                if (Robot_info[get_robot_id(objp)].boss_flag)
4594
                                        do_boss_dying_frame(objp);
4595
                }
4596
        }
4597
#endif
4598
}
4599
 
4600
 
4601
// Initializations to be performed for all robots for a new level.
4602
void init_robots_for_level(void)
4603
{
4604
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
4605
        BossUniqueState.Boss_dying_start_time = 0;
4606
        Overall_agitation = 0;
4607
#if defined(DXX_BUILD_DESCENT_II)
4608
        GameUniqueState.Final_boss_countdown_time = 0;
4609
        Ai_last_missile_camera = nullptr;
4610
#endif
4611
}
4612
 
4613
// Following functions convert ai_local/ai_cloak_info to ai_local/ai_cloak_info_rw to be written to/read from Savegames. Convertin back is not done here - reading is done specifically together with swapping (if necessary). These structs differ in terms of timer values (fix/fix64). as we reset GameTime64 for writing so it can fit into fix it's not necessary to increment savegame version. But if we once store something else into object which might be useful after restoring, it might be handy to increment Savegame version and actually store these new infos.
4614
static void state_ai_local_to_ai_local_rw(const ai_local *ail, ai_local_rw *ail_rw)
4615
{
4616
        int i = 0;
4617
 
4618
        ail_rw->player_awareness_type      = static_cast<int8_t>(ail->player_awareness_type);
4619
        ail_rw->retry_count                = ail->retry_count;
4620
        ail_rw->consecutive_retries        = ail->consecutive_retries;
4621
        ail_rw->mode                       = static_cast<uint8_t>(ail->mode);
4622
        ail_rw->previous_visibility        = static_cast<int8_t>(ail->previous_visibility);
4623
        ail_rw->rapidfire_count            = ail->rapidfire_count;
4624
        ail_rw->goal_segment               = ail->goal_segment;
4625
#if defined(DXX_BUILD_DESCENT_I)
4626
        ail_rw->last_see_time              = 0;
4627
        ail_rw->last_attack_time           = 0;
4628
#elif defined(DXX_BUILD_DESCENT_II)
4629
        ail_rw->next_fire2                 = ail->next_fire2;
4630
#endif
4631
        ail_rw->next_action_time           = ail->next_action_time;
4632
        ail_rw->next_fire                  = ail->next_fire;
4633
        ail_rw->player_awareness_time      = ail->player_awareness_time;
4634
        if (ail->time_player_seen - GameTime64 < F1_0*(-18000))
4635
                ail_rw->time_player_seen = F1_0*(-18000);
4636
        else
4637
                ail_rw->time_player_seen = ail->time_player_seen - GameTime64;
4638
        if (ail->time_player_sound_attacked - GameTime64 < F1_0*(-18000))
4639
                ail_rw->time_player_sound_attacked = F1_0*(-18000);
4640
        else
4641
                ail_rw->time_player_sound_attacked = ail->time_player_sound_attacked - GameTime64;
4642
        ail_rw->time_player_sound_attacked = ail->time_player_sound_attacked;
4643
        ail_rw->next_misc_sound_time       = ail->next_misc_sound_time - GameTime64;
4644
        ail_rw->time_since_processed       = ail->time_since_processed;
4645
        for (i = 0; i < MAX_SUBMODELS; i++)
4646
        {
4647
                ail_rw->goal_angles[i].p   = ail->goal_angles[i].p;
4648
                ail_rw->goal_angles[i].b   = ail->goal_angles[i].b;
4649
                ail_rw->goal_angles[i].h   = ail->goal_angles[i].h;
4650
                ail_rw->delta_angles[i].p  = ail->delta_angles[i].p;
4651
                ail_rw->delta_angles[i].b  = ail->delta_angles[i].b;
4652
                ail_rw->delta_angles[i].h  = ail->delta_angles[i].h;
4653
                ail_rw->goal_state[i]      = ail->goal_state[i];
4654
                ail_rw->achieved_state[i]  = ail->achieved_state[i];
4655
        }
4656
}
4657
 
4658
static void state_ai_cloak_info_to_ai_cloak_info_rw(ai_cloak_info *aic, ai_cloak_info_rw *aic_rw)
4659
{
4660
        if (aic->last_time - GameTime64 < F1_0*(-18000))
4661
                aic_rw->last_time = F1_0*(-18000);
4662
        else
4663
                aic_rw->last_time = aic->last_time - GameTime64;
4664
#if defined(DXX_BUILD_DESCENT_II)
4665
        aic_rw->last_segment    = aic->last_segment;
4666
#endif
4667
        aic_rw->last_position.x = aic->last_position.x;
4668
        aic_rw->last_position.y = aic->last_position.y;
4669
        aic_rw->last_position.z = aic->last_position.z;
4670
}
4671
 
4672
}
4673
 
4674
namespace dcx {
4675
 
4676
DEFINE_SERIAL_VMS_VECTOR_TO_MESSAGE();
4677
DEFINE_SERIAL_UDT_TO_MESSAGE(point_seg, p, (p.segnum, serial::pad<2>(), p.point));
4678
ASSERT_SERIAL_UDT_MESSAGE_SIZE(point_seg, 16);
4679
 
4680
DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(point_seg_array_t, p, (static_cast<std::array<point_seg, MAX_POINT_SEGS> &>(p)));
4681
DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(point_seg_array_t, p, (static_cast<const std::array<point_seg, MAX_POINT_SEGS> &>(p)));
4682
 
4683
}
4684
 
4685
namespace dsx {
4686
 
4687
int ai_save_state(PHYSFS_File *fp)
4688
{
4689
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
4690
#if defined(DXX_BUILD_DESCENT_II)
4691
        auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
4692
        auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
4693
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
4694
#endif
4695
        auto &Objects = LevelUniqueObjectState.Objects;
4696
        fix tmptime32 = 0;
4697
 
4698
        const int Ai_initialized = 0;
4699
        PHYSFS_write(fp, &Ai_initialized, sizeof(int), 1);
4700
        PHYSFS_write(fp, &Overall_agitation, sizeof(int), 1);
4701
        {
4702
                ai_local_rw zero{};
4703
                range_for (const auto &i, Objects)
4704
                {
4705
                        ai_local_rw ail_rw;
4706
                        PHYSFS_write(fp, i.type == OBJ_ROBOT ? (state_ai_local_to_ai_local_rw(&i.ctype.ai_info.ail, &ail_rw), &ail_rw) : &zero, sizeof(ail_rw), 1);
4707
                }
4708
        }
4709
        PHYSFSX_serialize_write(fp, Point_segs);
4710
        //PHYSFS_write(fp, Ai_cloak_info, sizeof(ai_cloak_info) * MAX_AI_CLOAK_INFO, 1);
4711
        range_for (auto &i, Ai_cloak_info)
4712
        {
4713
                ai_cloak_info_rw aic_rw;
4714
                state_ai_cloak_info_to_ai_cloak_info_rw(&i, &aic_rw);
4715
                PHYSFS_write(fp, &aic_rw, sizeof(aic_rw), 1);
4716
        }
4717
        {
4718
                const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
4719
        if (Boss_cloak_start_time - GameTime64 < F1_0*(-18000))
4720
                tmptime32 = F1_0*(-18000);
4721
        else
4722
                tmptime32 = Boss_cloak_start_time - GameTime64;
4723
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4724
        if ((Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 < F1_0*(-18000))
4725
                tmptime32 = F1_0*(-18000);
4726
        else
4727
                tmptime32 = (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64;
4728
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4729
        }
4730
        {
4731
                const auto Last_teleport_time = BossUniqueState.Last_teleport_time;
4732
        if (Last_teleport_time - GameTime64 < F1_0*(-18000))
4733
                tmptime32 = F1_0*(-18000);
4734
        else
4735
                tmptime32 = Last_teleport_time - GameTime64;
4736
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4737
        }
4738
        {
4739
                const fix Boss_teleport_interval = LevelSharedBossState.Boss_teleport_interval;
4740
                PHYSFS_write(fp, &Boss_teleport_interval, sizeof(fix), 1);
4741
                const fix Boss_cloak_interval = LevelSharedBossState.Boss_cloak_interval;
4742
                PHYSFS_write(fp, &Boss_cloak_interval, sizeof(fix), 1);
4743
        }
4744
        PHYSFS_write(fp, &Boss_cloak_duration, sizeof(fix), 1);
4745
        if (BossUniqueState.Last_gate_time - GameTime64 < F1_0*(-18000))
4746
                tmptime32 = F1_0*(-18000);
4747
        else
4748
                tmptime32 = BossUniqueState.Last_gate_time - GameTime64;
4749
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4750
        PHYSFS_write(fp, &GameUniqueState.Boss_gate_interval, sizeof(fix), 1);
4751
        {
4752
                const auto Boss_dying_start_time = BossUniqueState.Boss_dying_start_time;
4753
        if (Boss_dying_start_time == 0) // if Boss not dead, yet we expect this to be 0, so do not convert!
4754
        {
4755
                tmptime32 = 0;
4756
        }
4757
        else
4758
        {
4759
                if (Boss_dying_start_time - GameTime64 < F1_0*(-18000))
4760
                        tmptime32 = F1_0*(-18000);
4761
                else
4762
                        tmptime32 = Boss_dying_start_time - GameTime64;
4763
                if (tmptime32 == 0) // now if our converted value went 0 we should do something against it
4764
                        tmptime32 = -1;
4765
        }
4766
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4767
        }
4768
        {
4769
        const int boss_dying = BossUniqueState.Boss_dying;
4770
        PHYSFS_write(fp, &boss_dying, sizeof(int), 1);
4771
        }
4772
        const int boss_dying_sound_playing = BossUniqueState.Boss_dying_sound_playing;
4773
        PHYSFS_write(fp, &boss_dying_sound_playing, sizeof(int), 1);
4774
#if defined(DXX_BUILD_DESCENT_I)
4775
        const int boss_hit_this_frame = BossUniqueState.Boss_hit_this_frame;
4776
        PHYSFS_write(fp, &boss_hit_this_frame, sizeof(int), 1);
4777
        const int Boss_been_hit = 0;
4778
        PHYSFS_write(fp, &Boss_been_hit, sizeof(int), 1);
4779
#elif defined(DXX_BUILD_DESCENT_II)
4780
        if (BossUniqueState.Boss_hit_time - GameTime64 < F1_0*(-18000))
4781
                tmptime32 = F1_0*(-18000);
4782
        else
4783
                tmptime32 = BossUniqueState.Boss_hit_time - GameTime64;
4784
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4785
        PHYSFS_writeSLE32(fp, -1);
4786
        if (BuddyState.Escort_last_path_created - GameTime64 < F1_0*(-18000))
4787
                tmptime32 = F1_0*(-18000);
4788
        else
4789
                tmptime32 = BuddyState.Escort_last_path_created - GameTime64;
4790
        PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
4791
        {
4792
                const uint32_t Escort_goal_object = BuddyState.Escort_goal_object;
4793
        PHYSFS_write(fp, &Escort_goal_object, sizeof(Escort_goal_object), 1);
4794
        }
4795
        {
4796
                const uint32_t Escort_special_goal = BuddyState.Escort_special_goal;
4797
        PHYSFS_write(fp, &Escort_special_goal, sizeof(Escort_special_goal), 1);
4798
        }
4799
        {
4800
                const int egi = BuddyState.Escort_goal_reachable == d_unique_buddy_state::Escort_goal_reachability::unreachable ? -2 : BuddyState.Escort_goal_objidx.get_unchecked_index();
4801
                PHYSFS_write(fp, &egi, sizeof(int), 1);
4802
        }
4803
        {
4804
                auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
4805
                PHYSFS_write(fp, &Stolen_items, sizeof(Stolen_items[0]) * Stolen_items.size(), 1);
4806
        }
4807
 
4808
        {
4809
                int temp;
4810
                temp = Point_segs_free_ptr - Point_segs;
4811
                PHYSFS_write(fp, &temp, sizeof(int), 1);
4812
        }
4813
 
4814
        unsigned Num_boss_teleport_segs = Boss_teleport_segs.size();
4815
        PHYSFS_write(fp, &Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1);
4816
        unsigned Num_boss_gate_segs = Boss_gate_segs.size();
4817
        PHYSFS_write(fp, &Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1);
4818
 
4819
        if (Num_boss_gate_segs)
4820
                PHYSFS_write(fp, &Boss_gate_segs[0], sizeof(Boss_gate_segs[0]), Num_boss_gate_segs);
4821
 
4822
        if (Num_boss_teleport_segs)
4823
                PHYSFS_write(fp, &Boss_teleport_segs[0], sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs);
4824
#endif
4825
 
4826
        return 1;
4827
}
4828
 
4829
}
4830
 
4831
namespace dcx {
4832
 
4833
static void PHYSFSX_readAngleVecX(PHYSFS_File *file, vms_angvec &v, int swap)
4834
{
4835
        v.p = PHYSFSX_readSXE16(file, swap);
4836
        v.b = PHYSFSX_readSXE16(file, swap);
4837
        v.h = PHYSFSX_readSXE16(file, swap);
4838
}
4839
 
4840
}
4841
 
4842
namespace dsx {
4843
 
4844
static void ai_local_read_swap(ai_local *ail, int swap, PHYSFS_File *fp)
4845
{
4846
        {
4847
                fix tmptime32 = 0;
4848
 
4849
#if defined(DXX_BUILD_DESCENT_I)
4850
                ail->player_awareness_type = static_cast<player_awareness_type_t>(PHYSFSX_readByte(fp));
4851
                ail->retry_count = PHYSFSX_readByte(fp);
4852
                ail->consecutive_retries = PHYSFSX_readByte(fp);
4853
                ail->mode = static_cast<ai_mode>(PHYSFSX_readByte(fp));
4854
                ail->previous_visibility = static_cast<player_visibility_state>(PHYSFSX_readByte(fp));
4855
                ail->rapidfire_count = PHYSFSX_readByte(fp);
4856
                ail->goal_segment = PHYSFSX_readSXE16(fp, swap);
4857
                PHYSFSX_readSXE32(fp, swap);
4858
                PHYSFSX_readSXE32(fp, swap);
4859
                ail->next_action_time = PHYSFSX_readSXE32(fp, swap);
4860
                ail->next_fire = PHYSFSX_readSXE32(fp, swap);
4861
#elif defined(DXX_BUILD_DESCENT_II)
4862
                ail->player_awareness_type = static_cast<player_awareness_type_t>(PHYSFSX_readSXE32(fp, swap));
4863
                ail->retry_count = PHYSFSX_readSXE32(fp, swap);
4864
                ail->consecutive_retries = PHYSFSX_readSXE32(fp, swap);
4865
                ail->mode = static_cast<ai_mode>(PHYSFSX_readSXE32(fp, swap));
4866
                ail->previous_visibility = static_cast<player_visibility_state>(PHYSFSX_readSXE32(fp, swap));
4867
                ail->rapidfire_count = PHYSFSX_readSXE32(fp, swap);
4868
                ail->goal_segment = PHYSFSX_readSXE32(fp, swap);
4869
                ail->next_action_time = PHYSFSX_readSXE32(fp, swap);
4870
                ail->next_fire = PHYSFSX_readSXE32(fp, swap);
4871
                ail->next_fire2 = PHYSFSX_readSXE32(fp, swap);
4872
#endif
4873
                ail->player_awareness_time = PHYSFSX_readSXE32(fp, swap);
4874
                tmptime32 = PHYSFSX_readSXE32(fp, swap);
4875
                ail->time_player_seen = static_cast<fix64>(tmptime32);
4876
                tmptime32 = PHYSFSX_readSXE32(fp, swap);
4877
                ail->time_player_sound_attacked = static_cast<fix64>(tmptime32);
4878
                tmptime32 = PHYSFSX_readSXE32(fp, swap);
4879
                ail->next_misc_sound_time = static_cast<fix64>(tmptime32);
4880
                ail->time_since_processed = PHYSFSX_readSXE32(fp, swap);
4881
 
4882
                range_for (auto &j, ail->goal_angles)
4883
                        PHYSFSX_readAngleVecX(fp, j, swap);
4884
                range_for (auto &j, ail->delta_angles)
4885
                        PHYSFSX_readAngleVecX(fp, j, swap);
4886
                range_for (auto &j, ail->goal_state)
4887
                        j = PHYSFSX_readByte(fp);
4888
                range_for (auto &j, ail->achieved_state)
4889
                        j = PHYSFSX_readByte(fp);
4890
        }
4891
}
4892
 
4893
}
4894
 
4895
namespace dcx {
4896
 
4897
static void PHYSFSX_readVectorX(PHYSFS_File *file, vms_vector &v, int swap)
4898
{
4899
        v.x = PHYSFSX_readSXE32(file, swap);
4900
        v.y = PHYSFSX_readSXE32(file, swap);
4901
        v.z = PHYSFSX_readSXE32(file, swap);
4902
}
4903
 
4904
}
4905
 
4906
namespace dsx {
4907
 
4908
static void ai_cloak_info_read_n_swap(ai_cloak_info *ci, int n, int swap, PHYSFS_File *fp)
4909
{
4910
        int i;
4911
        fix tmptime32 = 0;
4912
 
4913
        for (i = 0; i < n; i++, ci++)
4914
        {
4915
                tmptime32 = PHYSFSX_readSXE32(fp, swap);
4916
                ci->last_time = static_cast<fix64>(tmptime32);
4917
#if defined(DXX_BUILD_DESCENT_II)
4918
                ci->last_segment = PHYSFSX_readSXE32(fp, swap);
4919
#endif
4920
                PHYSFSX_readVectorX(fp, ci->last_position, swap);
4921
        }
4922
}
4923
 
4924
int ai_restore_state(PHYSFS_File *fp, int version, int swap)
4925
{
4926
        auto &BossUniqueState = LevelUniqueObjectState.BossState;
4927
#if defined(DXX_BUILD_DESCENT_II)
4928
        auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
4929
        auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
4930
        auto &BuddyState = LevelUniqueObjectState.BuddyState;
4931
#endif
4932
        auto &Objects = LevelUniqueObjectState.Objects;
4933
        auto &vmobjptridx = Objects.vmptridx;
4934
        fix tmptime32 = 0;
4935
 
4936
        PHYSFSX_readSXE32(fp, swap);
4937
        Overall_agitation = PHYSFSX_readSXE32(fp, swap);
4938
        range_for (object &obj, Objects)
4939
        {
4940
                ai_local discard;
4941
                ai_local_read_swap(obj.type == OBJ_ROBOT ? &obj.ctype.ai_info.ail : &discard, swap, fp);
4942
        }
4943
        PHYSFSX_serialize_read(fp, Point_segs);
4944
        ai_cloak_info_read_n_swap(Ai_cloak_info.data(), Ai_cloak_info.size(), swap, fp);
4945
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4946
        BossUniqueState.Boss_cloak_start_time = static_cast<fix64>(tmptime32);
4947
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4948
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4949
        BossUniqueState.Last_teleport_time = static_cast<fix64>(tmptime32);
4950
 
4951
        // If boss teleported, set the looping 'see' sound -kreatordxx
4952
        // Also make sure any bosses that were generated/released during the game have teleport segs
4953
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
4954
#if DXX_USE_EDITOR
4955
        if (!EditorWindow)
4956
#endif
4957
                range_for (const auto &&o, vmobjptridx)
4958
                {
4959
                        if (o->type == OBJ_ROBOT)
4960
                        {
4961
                                auto boss_id = Robot_info[get_robot_id(o)].boss_flag;
4962
                                if (boss_id
4963
#if defined(DXX_BUILD_DESCENT_II)
4964
                                && (boss_id == BOSS_D1 || boss_id == BOSS_D2 || (boss_id >= BOSS_D2 && Boss_teleports[boss_id - BOSS_D2]))
4965
#endif
4966
                                )
4967
                                {
4968
                                        const auto Last_teleport_time = BossUniqueState.Last_teleport_time;
4969
                                        if (Last_teleport_time != 0 && Last_teleport_time != BossUniqueState.Boss_cloak_start_time)
4970
                                                boss_link_see_sound(o);
4971
                                        boss_init_all_segments(Segments, o);
4972
                                }
4973
                        }
4974
                }
4975
 
4976
#if defined(DXX_BUILD_DESCENT_II)
4977
        LevelSharedBossState.Boss_teleport_interval =
4978
#endif
4979
                PHYSFSX_readSXE32(fp, swap);
4980
#if defined(DXX_BUILD_DESCENT_II)
4981
        LevelSharedBossState.Boss_cloak_interval =
4982
#endif
4983
                PHYSFSX_readSXE32(fp, swap);
4984
        PHYSFSX_readSXE32(fp, swap);
4985
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4986
        BossUniqueState.Last_gate_time = static_cast<fix64>(tmptime32);
4987
        GameUniqueState.Boss_gate_interval = PHYSFSX_readSXE32(fp, swap);
4988
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4989
        BossUniqueState.Boss_dying_start_time = static_cast<fix64>(tmptime32);
4990
        BossUniqueState.Boss_dying = PHYSFSX_readSXE32(fp, swap);
4991
        BossUniqueState.Boss_dying_sound_playing = PHYSFSX_readSXE32(fp, swap);
4992
#if defined(DXX_BUILD_DESCENT_I)
4993
        (void)version;
4994
        BossUniqueState.Boss_hit_this_frame = PHYSFSX_readSXE32(fp, swap);
4995
        PHYSFSX_readSXE32(fp, swap);
4996
#elif defined(DXX_BUILD_DESCENT_II)
4997
        tmptime32 = PHYSFSX_readSXE32(fp, swap);
4998
        BossUniqueState.Boss_hit_time = static_cast<fix64>(tmptime32);
4999
 
5000
        if (version >= 8) {
5001
                PHYSFSX_readSXE32(fp, swap);
5002
                tmptime32 = PHYSFSX_readSXE32(fp, swap);
5003
                BuddyState.Escort_last_path_created = static_cast<fix64>(tmptime32);
5004
                BuddyState.Escort_goal_object = static_cast<escort_goal_t>(PHYSFSX_readSXE32(fp, swap));
5005
                BuddyState.Escort_special_goal = static_cast<escort_goal_t>(PHYSFSX_readSXE32(fp, swap));
5006
                const int egi = PHYSFSX_readSXE32(fp, swap);
5007
                if (static_cast<unsigned>(egi) < Objects.size())
5008
                {
5009
                        BuddyState.Escort_goal_objidx = egi;
5010
                        BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::reachable;
5011
                }
5012
                else
5013
                {
5014
                        BuddyState.Escort_goal_objidx = object_none;
5015
                        BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable;
5016
                }
5017
                {
5018
                        auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
5019
                        PHYSFS_read(fp, &Stolen_items, sizeof(Stolen_items[0]) * Stolen_items.size(), 1);
5020
                }
5021
        } else {
5022
                BuddyState.Escort_last_path_created = 0;
5023
                BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
5024
                BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED;
5025
                BuddyState.Escort_goal_objidx = object_none;
5026
                BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable;
5027
 
5028
                LevelUniqueObjectState.ThiefState.Stolen_items.fill(255);
5029
        }
5030
 
5031
        if (version >= 15) {
5032
                unsigned temp;
5033
                temp = PHYSFSX_readSXE32(fp, swap);
5034
                if (temp > Point_segs.size())
5035
                        throw std::out_of_range("too many points");
5036
                Point_segs_free_ptr = Point_segs.begin() + temp;
5037
        } else
5038
                ai_reset_all_paths();
5039
 
5040
        if (version >= 21) {
5041
                unsigned Num_boss_teleport_segs = PHYSFSX_readSXE32(fp, swap);
5042
                unsigned Num_boss_gate_segs = PHYSFSX_readSXE32(fp, swap);
5043
 
5044
                Boss_gate_segs.clear();
5045
                for (unsigned i = 0; i < Num_boss_gate_segs; i++)
5046
                        Boss_gate_segs.emplace_back(PHYSFSX_readSXE16(fp, swap));
5047
 
5048
                Boss_teleport_segs.clear();
5049
                for (unsigned i = 0; i < Num_boss_teleport_segs; i++)
5050
                        Boss_teleport_segs.emplace_back(PHYSFSX_readSXE16(fp, swap));
5051
        }
5052
#endif
5053
 
5054
        return 1;
5055
}
5056
 
5057
}