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