1 /*
2 
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4 
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 /*
20  * $Source: r:/prj/cit/src/RCS/newai.c $
21  * $Revision: 1.43 $
22  * $Author: xemu $
23  * $Date: 1994/10/24 23:15:00 $
24  *
25  */
26 
27 #include <stdlib.h>
28 
29 #include "ai.h"
30 #include "cyber.h"
31 #include "player.h"
32 #include "pathfind.h"
33 #include "otrip.h"
34 #include "objects.h"
35 #include "objprop.h"
36 #include "objgame.h"
37 #include "objcrit.h"
38 #include "objsim.h"
39 #include "objbit.h"
40 #include "faketime.h"
41 #include "musicai.h"
42 #include "tilename.h"
43 #include "ice.h"
44 #include "tools.h"
45 #include "trigger.h"
46 #include "map.h"
47 #include "mapflags.h"
48 #include "damage.h"
49 #include "combat.h"
50 #include "physics.h"
51 #include "safeedms.h"
52 #include "aiflags.h"
53 #include "doorparm.h"
54 #include "diffq.h"
55 #include "visible.h"
56 
57 // SOME YUMMY DEFINES!
58 #define AI_EDMS
59 
60 // A bunch of visibility related factors.
61 // These should probably wind up in a quickbox?
62 #define BASE_VISIBILITY        178
63 #define SHIELD_VISIBLE_FACTOR  52
64 #define LIGHT_VISIBLE_FACTOR   75
65 #define LANTERN_VISIBLE_FACTOR 80
66 #define GUN_VISIBLE_FACTOR     168
67 #define UNTRACK_VISIBLE_FACTOR 44
68 #define CROUCH_VISIBLE_FACTOR  16
69 #define ZDIFF_VISIBLE_FACTOR   1
70 #define DIST_VISIBLE_FACTOR    3
71 #define BEHIND_VISIBLE_FACTOR  128
72 
73 #define MEDIAN_PERCEPTION 64
74 
75 #define DEFAULT_SPEED fix_make(0xD, 0)
76 
77 // Number of ticks where, if we haven't seen the player in that long,
78 // we forget where he is.
79 #define AI_ATTENTION_SPAN CIT_CYCLE * 10
80 
81 extern ObjLoc last_known_loc;
82 ulong time_last_seen;
83 uchar priority_check;
84 extern uchar door_moving(ObjID id, uchar dir);
85 extern errtype set_posture_movesafe(ObjSpecID osid, ubyte new_pos);
86 
87 short compute_base_visibility();
88 errtype run_evil_otto(ObjID id, int dist);
89 errtype run_cspace_ice();
90 errtype ai_spot_player(ObjID id, uchar *raycast_success);
91 uchar do_physics_stupidity(ObjID id, int big_dist);
92 void check_attitude_adjustment(ObjID id, ObjSpecID osid, int big_dist, uchar raycast_success);
93 void load_combat_art(int cp_num);
94 errtype run_combat_ai(ObjID id, uchar raycast_success);
95 errtype do_stealth_stuff(ObjID id, short base_vis, uchar *raycast_success, fix dist);
96 void set_des_heading(ObjID id, ObjSpecID osid, fix targ_x, fix targ_y, fixang *angdiff, fixang *target_ang);
97 errtype follow_pathfinding(ObjID id, ObjSpecID osid);
98 LGPoint ai_patrol_func(ObjID id, ObjSpecID osid);
99 LGPoint ai_highway_func(ObjID id, ObjSpecID osid);
100 LGPoint ai_roam_func(ObjID id, ObjSpecID osid);
101 LGPoint ai_none_func(ObjID, ObjSpecID osid);
102 errtype run_peaceful_ai(ObjID id, int big_dist);
103 
104 // Run all the ICEs, deal with their agitation, etc.  Boy, this could probably
105 // be a lot smarter than iterating through all objects, like having the
106 // agitated objects flag themselves as such when they become agitated, etc.
107 
108 #define SHODAN_AVATAR_HOSAGE_DISTANCE 0xA0
109 #define AGITATED_ICE_DIST 49
110 #define ICE_INTERVAL (CIT_CYCLE >> 1)
111 
112 // chance vs 0xFF of firing on a given half-second
113 uchar ice_fire_chances[] = {0x40, 0x80, 0xD0, 0xF0};
114 ulong run_ice_time = 0;
115 
run_cspace_ice()116 errtype run_cspace_ice() {
117     int dx, dy, dist;
118     extern errtype ai_fire_special(ObjID src, ObjID target, int proj_triple, ObjLoc src_loc, ObjLoc target_loc, uchar a,
119                                    int duration);
120 
121     // Look for hostile ICEs, which closely resemble creatures
122     // of course, only do so if we be in cspace
123 
124     // I'm agitated.  Yeah, agitated.  -- Devo
125     for (ObjID id = (objs[OBJ_NULL]).headused; id != OBJ_NULL; id = objs[id].next) {
126         // Are we an agitated ice encrusted thing?  Is it our stochastic time as determined by Lord Chaos?
127         if (ICE_ICE_BABY(id) && ICE_AGIT(id) && ((rand() & 0xFF) < ice_fire_chances[ICE_LEVEL(id)])) {
128             dx = PLAYER_BIN_X - OBJ_LOC_BIN_X(objs[id].loc);
129             dy = PLAYER_BIN_Y - OBJ_LOC_BIN_Y(objs[id].loc);
130             dist = dx * dx + dy * dy;
131 
132             // Are we close enough to the player to care?
133             if (dist < AGITATED_ICE_DIST) {
134                 // Lob a slow projectile off at that wacky player.  Boy, we're perfectly statically accurate.
135                 ai_fire_special(id, PLAYER_OBJ, CYBERBOLT_TRIPLE, objs[id].loc, objs[PLAYER_OBJ].loc, ICE_LEVEL(id),
136                                 SLOW_PROJECTILE_DURATION);
137             }
138         }
139     }
140     run_ice_time = player_struct.game_time + ICE_INTERVAL;
141     return (OK);
142 }
143 
144 // Compute and return the player's basic visibility for this frame
compute_base_visibility()145 short compute_base_visibility() {
146     MapElem *pme;
147     short visibility;
148 
149     // Update detection variables
150     if (!cspace_decoy_obj) {
151         if (time_last_seen > player_struct.game_time + AI_ATTENTION_SPAN)
152             last_known_loc.x = 255;
153 
154         // compute basic visibility
155 
156         pme = MAP_GET_XY(PLAYER_BIN_X, PLAYER_BIN_Y);
157 
158         // Then we roll against our perception, after figuring in
159         // tons of factors...
160         // base visibility
161         visibility = BASE_VISIBILITY;
162 
163         // More visible if shields up (can't check yet)
164 
165         // More visible if standing in light
166         visibility += (LIGHT_VISIBLE_FACTOR / lg_max(1, (lg_max(0, me_light_flr(pme) - me_templight_flr(pme)) +
167                                                          lg_max(0, me_light_ceil(pme) - me_templight_ceil(pme))) /
168                                                             2));
169         visibility += LANTERN_VISIBLE_FACTOR * player_struct.light_value;
170 
171         // More visible if fired gun recently (this may want to be exponentially decaying
172         if (player_struct.last_fire) { // make sure we've actually fired a weapon
173             visibility +=
174                 ((GUN_VISIBLE_FACTOR * CIT_CYCLE) / lg_max(player_struct.game_time - player_struct.last_fire, 1));
175         }
176 
177         // Less visible if critters have lost track of ya
178         if (last_known_loc.x == 255)
179             visibility -= UNTRACK_VISIBLE_FACTOR;
180 
181         // Some things that don't affect you in cspace
182         if (!global_fullmap->cyber) {
183             // Less visible if crouching or crawling
184             visibility -= CROUCH_VISIBLE_FACTOR * player_struct.posture;
185         }
186     }
187     return (visibility);
188 }
189 
190 // id is evil otto, deal accordingly (moving, hosing, etc.)
run_evil_otto(ObjID id,int dist)191 errtype run_evil_otto(ObjID id, int dist) {
192     // Are we close enough to totally hose the player?
193     if (dist < SHODAN_AVATAR_HOSAGE_DISTANCE) {
194         damage_player(15, 0x7, NO_SHIELD_ABSORBTION);
195         //      attack_object(PLAYER_OBJ, CritterProps[cp_num].attacks[0].damage_type,
196         //      CritterProps[cp_num].attacks[0].damage_modifier,
197         //         CritterProps[cp_num].attacks[0].offense_value, CritterProps[cp_num].attacks[0].penetration, 0, 100,
198         //         NULL, 0, 0, NULL);
199     } else {
200         fix xvec, yvec, fdist;
201         int dx, dy;
202         ObjLoc newloc = objs[id].loc;
203         // Move the AVAMATAR OF SHODAN (Evil Otto) closer to the player
204         // first, get a normalized vector
205         dx = PLAYER_BIN_X - OBJ_LOC_BIN_X(objs[id].loc);
206         dy = PLAYER_BIN_Y - OBJ_LOC_BIN_Y(objs[id].loc);
207         xvec = fix_from_obj_coord(dx);
208         yvec = fix_from_obj_coord(dy);
209         fdist = fix_fast_pyth_dist(xvec, yvec);
210         xvec = fix_div(xvec, fdist) >> 2;
211         yvec = fix_div(yvec, fdist) >> 2;
212 
213         // then, move otto along it
214         newloc.x = obj_coord_from_fix(xvec + fix_from_obj_coord(newloc.x));
215         newloc.y = obj_coord_from_fix(yvec + fix_from_obj_coord(newloc.y));
216         newloc.z = objs[PLAYER_OBJ].loc.z;
217         //      Warning(("AVATAR_SHODAN at 0x%x, 0x%x (dist = 0x%x)\n",newloc.x,newloc.y,dist));
218         obj_move_to(id, &newloc, FALSE);
219     }
220     return (OK);
221 }
222 
223 // Does appropriate destruction & reconstitution of physics part
224 // of said critter.
225 // Returns whether or not we should continue to think
226 // about this particular creature.
227 short ignore_distance[] = {6, 10};
228 
do_physics_stupidity(ObjID id,int big_dist)229 uchar do_physics_stupidity(ObjID id, int big_dist) {
230     ObjSpecID osid = objs[id].specID;
231     int dist = big_dist >> 8;
232     int use_dist;
233     fix there_yet;
234 
235 #ifdef DISTANCE_AI_KILL
236     // don't phys-kill anything that is currently pathfinding or in combat mode (it'll leave combat mode
237     // after a while anyways)
238     if ((objCritters[osid].path_id != -1) || (objCritters[osid].mood == AI_MOOD_HOSTILE) ||
239         (objCritters[osid].mood == AI_MOOD_ATTACKING)) {
240         if (!EDMS_frere_jaques(objs[id].info.ph)) {
241             //         Spew(DSRC_PHYSICS_Sleeper, ("obj id %x, ph = %d(0x%x) is PF or attack but
242             //         asleep!\n",id,objs[id].info.ph,objs[id].info.ph)); Spew(DSRC_PHYSICS_Sleeper, ("obj id %x, head =
243             //         %x, spd = %x, urg = %x\n",id,objCritters[osid].des_heading,
244             //            objCritters[osid].des_speed, objCritters[osid].urgency));
245         }
246         return (TRUE);
247     }
248 
249     use_dist = ignore_distance[global_fullmap->cyber];
250 
251     // This really needs to deal with cameras!
252     if (dist > use_dist) {
253         // What, we've gone too far away?  Well, then set us to zero so EDMS sleeps us nicely
254         if (CHECK_OBJ_PH(id))
255             safe_EDMS_ai_control_robot(objs[id].info.ph, 0, 0, 0, 0, &there_yet, 0);
256         //      else if (!(get_crit_posture(osid) == DEATH_CRITTER_POSTURE))
257         //         Warning(("hey, trying to sleep id %x without no physics handle (ph = %x)!\n",id,objs[id].info.ph));
258         //      if (EDMS_frere_jaques(objs[id].info.ph))
259         //         Spew(DSRC_PHYSICS_Sleeper, ("obj id %x, ph = %d(0x%x) is too far but
260         //         awake!\n",id,objs[id].info.ph,objs[id].info.ph));
261         return (FALSE);
262     }
263 #endif
264     if ((!EDMS_frere_jaques(objs[id].info.ph)) && (objCritters[osid].des_speed != 0)) {
265         //      Spew(DSRC_PHYSICS_Sleeper, ("obj id %x, ph = %d(0x%x) is in range but
266         //      asleep!\n",id,objs[id].info.ph,objs[id].info.ph)); Spew(DSRC_PHYSICS_Sleeper, ("obj id %x, head = %x (vs
267         //      %x), spd = %x, urg = %x\n",id,objCritters[osid].des_heading,
268         //         objCritters[osid].des_speed, objCritters[osid].urgency));
269     }
270     return (TRUE);
271 }
272 
273 // If our current pathfind is greater than REPATHFIND_DIST away from reality,
274 // punt and repathfind
275 #define REPATHFIND_DIST 0x4
ai_spot_player(ObjID id,uchar * raycast_success)276 errtype ai_spot_player(ObjID id, uchar *raycast_success) {
277     ObjSpecID osid = objs[id].specID;
278     ObjCritter *pcrit = &objCritters[osid];
279 
280     time_last_seen = player_struct.game_time;
281     *raycast_success = TRUE;
282     ai_find_player(id);
283 
284     // If not friendly, trigger the combat music
285     if ((pcrit->mood != AI_MOOD_FRIENDLY) && (!ai_critter_sleeping(osid)))
286         ai_critter_seen();
287 
288     // Aha!  We have seen the player, so if we are of a sort to get ticked off,
289     // then let's do so.
290     if (QUESTVAR_GET(COMBAT_DIFF_QVAR) > 0) {
291         if ((pcrit->mood == AI_MOOD_NEUTRAL) || (pcrit->mood == AI_MOOD_ISOLATION)) {
292             // punt our old, probably irrelevant pathfind
293             if (pcrit->path_id != -1) {
294                 delete_path(pcrit->path_id);
295                 pcrit->path_id = -1;
296             }
297         } else if (pcrit->path_id != -1) {
298             // if the difference between our current path's destination and the actual
299             // location of the player is too great, punt the old one.
300             if (long_fast_pyth_dist((last_known_loc.x >> 8) - paths[pcrit->path_id].dest.x,
301                                     (last_known_loc.y >> 8) - paths[pcrit->path_id].dest.y) > REPATHFIND_DIST) {
302                 pcrit->path_id = -1;
303                 delete_path(pcrit->path_id);
304             }
305         }
306         if ((pcrit->mood == AI_MOOD_NEUTRAL) || (pcrit->mood == AI_MOOD_ISOLATION)) {
307             if (pcrit->path_id != -1)
308                 delete_path(pcrit->path_id);
309             pcrit->path_id = -1;
310             pcrit->mood = AI_MOOD_HOSTILE;
311             pcrit->flags |= AI_FLAG_CHASING;
312             // and play our "notice" sound effect
313             play_digi_fx_obj(CritterProps[CPNUM(id)].notice_sound, 1, id);
314         }
315     }
316     return (OK);
317 }
318 
319 // Do appropriate stuff for critter object id to try and find the
320 // player
do_stealth_stuff(ObjID id,short base_vis,uchar * raycast_success,fix dist)321 errtype do_stealth_stuff(ObjID id, short base_vis, uchar *raycast_success, fix dist) {
322     short use_vis;
323     State plr_state, our_state;
324     fixang ang_diff, real_ang;
325     int r;
326     ObjSpecID osid = objs[id].specID;
327     ObjCritter *pcrit = &objCritters[osid];
328 
329     if (cspace_decoy_obj) {
330         time_last_seen = player_struct.game_time;
331         *raycast_success = TRUE;
332         last_known_loc = objs[cspace_decoy_obj].loc;
333     } else {
334         // Where's Waldo?
335         // We only get to look when we're getting to run AI on ourselves.
336 
337         // If we are "chasing" the player (via our flags)
338         // Then we don't have to actually spot the player to know his current location
339         // although we don't count as having "spotted" the player (so that we still time out if we haven't
340         // found the player in ATTENTION_SPAN amount of time).
341         // Even if this is true, we do the whole visibility rigamarole so that if we DO see the player,
342         // then we reset our attention span.
343         if (pcrit->flags & AI_FLAG_CHASING)
344             last_known_loc = objs[PLAYER_OBJ].loc;
345 
346         // We do all the stealth rolling first, so that we can cut out raycasting whenever possible
347 
348         // We should also filter out cases where player is behind der robotenhausen
349         use_vis = base_vis;
350 
351         // Less visible if on far Z to searcher
352         if (!global_fullmap->cyber) {
353             use_vis -= ZDIFF_VISIBLE_FACTOR * abs(objs[PLAYER_OBJ].loc.z - objs[id].loc.z);
354 
355             // Less visible in far away in normal coords
356             use_vis -= DIST_VISIBLE_FACTOR * fix_int(dist);
357         }
358 
359         safe_EDMS_get_state(objs[PLAYER_OBJ].info.ph, &plr_state);
360         //      if (!CHECK_OBJ_PH(id))
361         //         Warning(("Ack! 0x%x ph == %d (osid = 0x%x) sanity = %d!\n",pcrit->id,objs[pcrit->id].info.ph,
362         //            osid,EDMS_sanity_check()));
363         //      else
364         {
365             safe_EDMS_get_state(objs[pcrit->id].info.ph, &our_state);
366             ang_diff =
367                 point_in_view_arc(plr_state.X, plr_state.Y, our_state.X, our_state.Y,
368                                   0x4000 - fixang_from_phys_angle(phys_angle_from_obj(objs[id].loc.h)), &real_ang);
369             use_vis -= BEHIND_VISIBLE_FACTOR * (ang_diff / 0x8000);
370         }
371 
372         // Now factor in our own perception skill.
373         use_vis += CritterProps[CPNUM(id)].perception - MEDIAN_PERCEPTION;
374 
375         if (use_vis > 0) {
376 
377             r = rand() % 255;
378             if (r < use_vis) {
379                 if (ray_cast_objects(id, PLAYER_OBJ, VISIBLE_MASS, VISIBLE_SIZE, VISIBLE_SPEED, VISIBLE_RANGE) ==
380                     PLAYER_OBJ)
381                     ai_spot_player(id, raycast_success);
382             }
383         }
384     }
385     return (OK);
386 }
387 
set_des_heading(ObjID id,ObjSpecID osid,fix targ_x,fix targ_y,fixang * angdiff,fixang * target_ang)388 void set_des_heading(ObjID id, ObjSpecID osid, fix targ_x, fix targ_y, fixang *angdiff, fixang *target_ang) {
389     State current_state;
390 
391 #ifdef AI_EDMS
392     safe_EDMS_get_state(objs[id].info.ph, &current_state);
393     *angdiff = point_in_view_arc(targ_x, targ_y, current_state.X, current_state.Y,
394                                  0x4000 - fixang_from_phys_angle(phys_angle_from_obj(objs[objCritters[osid].id].loc.h)),
395                                  target_ang);
396     objCritters[osid].des_heading = fixang_to_fixrad(*target_ang);
397 #else
398     objCritters[osid].des_heading = 0;
399 #endif
400 }
401 
402 // Continue along the pathfinding path, grabbing new steps as
403 // necessary when reaching old steps.
404 #define MAX_PATH_TRIES 25
405 
406 char dir_table[3][3] = {
407     { 2, 2, 2 },
408     { 3, 0, 1 },
409     { 0, 0, 0 },
410 };
411 
follow_pathfinding(ObjID id,ObjSpecID osid)412 errtype follow_pathfinding(ObjID id, ObjSpecID osid) {
413     LGPoint sq, csq;
414     char steps_left, path_id, newdir;
415     ObjID open_me = OBJ_NULL;
416 
417     path_id = objCritters[osid].path_id;
418 
419     // If our pathfind request ain't been filled yet, don't do anything
420     if (paths[path_id].num_steps == -1) {
421         return (OK);
422     }
423 
424     // See whether or not we've gone further ahead on our path than
425     // we expected to.  Hmm, if this is too slow we could probably keep track
426     // of our last location and only do this operation if that's changed.
427     sq.x = OBJ_LOC_BIN_X(objs[id].loc);
428     sq.y = OBJ_LOC_BIN_Y(objs[id].loc);
429     if (check_path_cutting(sq, objCritters[osid].path_id)) {
430         objCritters[osid].pf_x = sq.x;
431         objCritters[osid].pf_y = sq.y;
432     }
433 
434     if (paths[path_id].num_steps == 0) {
435         // If our path has been deleted from out from underneath us, stop
436         // trying to follow it...
437         delete_path(objCritters[osid].path_id);
438         objCritters[osid].path_id = -1;
439         return (OK);
440     }
441 
442     if ((objCritters[osid].pf_x == -1) || (paths[path_id].curr_step == -1) ||
443         ((OBJ_LOC_BIN_X(objs[id].loc) == objCritters[osid].pf_x) &&
444          (OBJ_LOC_BIN_Y(objs[id].loc) == objCritters[osid].pf_y))) {
445         // We're where we want to be, so lets get the next step!
446         objCritters[osid].path_tries = 0;
447         newdir = next_step_on_path(path_id, &sq, &steps_left);
448         if (steps_left == -1) {
449             objCritters[osid].path_id = -1;
450         } else {
451             // Plug the next step into our local state
452             objCritters[osid].pf_x = sq.x;
453             objCritters[osid].pf_y = sq.y;
454         }
455     } else {
456         // Keep on truckin' towards our old location
457         //      char ft1[50],ft2[50],ft3[50];
458         fixang angdiff, target_ang;
459 
460         if (objCritters[osid].path_tries++ > MAX_PATH_TRIES) {
461             // Okay, clearly something has rendered our path invalid,
462             // since we ain't having no success in getting there.  Let's punt.
463             // We could make this keep trying and resubmit a PF request.  Should we?
464             delete_path(objCritters[osid].path_id);
465             objCritters[osid].path_id = -1;
466         }
467         // Is there a door in the way?
468         // goddamn, this is a stupid way of doing things....argh!
469         if (!(((ObjProps[OPNUM(id)].flags & CLASS_FLAGS) >> CLASS_FLAGS_SHF) & CRITTER_NODOOR_OBJPROP_FLAG)) {
470             sq.x = objCritters[osid].pf_x;
471             sq.y = objCritters[osid].pf_y;
472             csq.x = OBJ_LOC_BIN_X(objs[id].loc);
473             csq.y = OBJ_LOC_BIN_Y(objs[id].loc);
474             if ((abs(csq.x - sq.x) < 2) && (abs(csq.y - sq.y) < 2))
475                 pf_obj_doors(MAP_GET_XY(csq.x, csq.y), MAP_GET_XY(sq.x, sq.y),
476                              dir_table[sq.y - csq.y + 1][sq.x - csq.x + 1], &open_me);
477             //      Warning(("open_me = %x, dt = %d from (%x,%x) to (%x,%x)!\n",open_me,dir_table[sq.y - csq.y + 1][sq.x
478             //      - csq.x + 1],
479             //         csq.x,csq.y,sq.x,sq.y));
480             if ((open_me != OBJ_NULL) && (DOOR_CLOSED(open_me)) && !(door_moving(open_me, FALSE))) {
481                 uchar use_door(ObjID id, uchar in_inv, ObjID cursor_obj);
482                 use_door(open_me, 0x2, OBJ_NULL);
483             }
484         }
485         set_des_heading(id, osid, fix_make(objCritters[osid].pf_x, 0x8000), fix_make(objCritters[osid].pf_y, 0x8000),
486                         &angdiff, &target_ang);
487 
488 #ifdef WACKY_SPEED_REDUCTION
489         if (paths[path_id].num_steps < 2) {
490             objCritters[osid].des_speed = DEFAULT_SPEED >> 3;
491             Warning(("speed slowing due to distance!\n"));
492         } else
493 #endif
494             if (objCritters[osid].des_speed == 0)
495             objCritters[osid].des_speed = DEFAULT_SPEED;
496 
497 #ifdef SPEED_QUARTERING
498         // Quarter speed if we are mostly turning and are going fast
499         if ((angdiff > 0x2000) && (objCritters[osid].des_speed > MAX_TURNING_SPEED)) {
500             objCritters[osid].des_speed = objCritters[osid].des_speed >> 2;
501         }
502 #endif
503     }
504     return (OK);
505 }
506 
507 char ai_ranges;
508 
509 // Are we legal to attack right now?  If so, slam us into ATTACKING,
510 // otherwise slam us into hostile.  Make appropriate adjustments so that
511 // we are actively looking for the player in either case.
512 
513 // Hey Rocky, watch me pull this constant out of my butt!
514 #define SHORT_RANGE_Z 0xA0
check_attitude_adjustment(ObjID id,ObjSpecID osid,int big_dist,uchar raycast_success)515 void check_attitude_adjustment(ObjID id, ObjSpecID osid, int big_dist, uchar raycast_success) {
516     char i;
517     short dist = big_dist >> 8;
518     int cp_num = CPNUM(id);
519     uchar care_mask = 0;
520 
521     ai_ranges = 0;
522 
523     // Frankly, if we have no clue where the player is then
524     // don't bother trying to find him or anything.... in fact
525     // we go back to being NEUTRAL, I think.  Although we will
526     // continue on our current pathfinding in hopes of reacquiring
527     // the player
528     if (last_known_loc.x == 255) {
529         objCritters[osid].mood = AI_MOOD_NEUTRAL;
530         objCritters[osid].flags &= ~AI_FLAG_CHASING;
531         set_posture(osid, STANDING_CRITTER_POSTURE);
532         return;
533     }
534 
535     // Check ranges
536     for (i = 0; i < 2; i++) {
537         short rng;
538         rng = CritterProps[cp_num].attacks[i].att_range;
539         if (dist <= rng) {
540             // If we are a "short range" attack, then check z before trying
541             if (rng <= 2) {
542                 if ((abs(objs[id].loc.z - objs[PLAYER_OBJ].loc.z) << SLOPE_SHIFT_U) < SHORT_RANGE_Z)
543                     ai_ranges |= 1 << i;
544             } else
545                 ai_ranges |= 1 << i;
546         }
547     }
548 
549     // If we have no chance of doing a given attack, then don't worry about it's range
550     care_mask = 0;
551     if (CritterProps[cp_num].alt_perc == 0)
552         care_mask = 0x1;
553     else if (CritterProps[cp_num].alt_perc == 0xFF)
554         care_mask = 0x2;
555     else
556         care_mask = 0x3;
557 
558     if ((care_mask & ai_ranges) != care_mask) {
559         // If we aren't in range of both weapons, get closer
560         objs[id].info.inst_flags |= CLASS_INST_FLAG2;
561     }
562     if (ai_ranges & care_mask) {
563         fixang angdiff, target_ang;
564         // If we're in range of all wpns, stop trying to get closer
565         // but do keep trying to face the player.  Note that normally the
566         // "face the player" part is dealt with by the pathfinder, hopefully,
567         // and so will blast out our des_heading set here.  We have to do the work
568         // anyways here in order to figure out wheher or not the critter is facing
569         // the player
570         set_des_heading(id, osid, fix_from_obj_coord(last_known_loc.x), fix_from_obj_coord(last_known_loc.y), &angdiff,
571                         &target_ang);
572         if (angdiff < 0x2000) {
573 #ifdef AI_EDMS
574             if (raycast_success || (ray_cast_objects(id, PLAYER_OBJ, VISIBLE_MASS, VISIBLE_SIZE, VISIBLE_SPEED,
575                                                      VISIBLE_RANGE) == PLAYER_OBJ)) {
576                 raycast_success = TRUE;
577                 objCritters[osid].mood = AI_MOOD_ATTACKING;
578 #ifdef ANNOYING_COMBAT_SPEW
579                 Spew(DSRC_AI_Combat, ("id %x Spotted player, attacking!\n"));
580 #endif
581             } else
582 #endif
583             {
584                 objCritters[osid].mood = AI_MOOD_HOSTILE;
585                 set_posture_movesafe(osid, STANDING_CRITTER_POSTURE);
586 #ifdef ANNOYING_COMBAT_SPEW
587                 Spew(DSRC_AI_Combat, ("id %x failed raycast!\n"));
588 #endif
589             }
590         } else {
591             objCritters[osid].mood = AI_MOOD_HOSTILE;
592             set_posture_movesafe(osid, STANDING_CRITTER_POSTURE);
593 #ifdef ANNOYING_COMBAT_SPEW
594             Spew(DSRC_AI_Combat, ("id %x failed angcheck, angdiff = %x\n", angdiff));
595 #endif
596         }
597     }
598 }
599 
load_combat_art(int cp_num)600 void load_combat_art(int cp_num) {
601     extern Id posture_bases[];
602     char p;
603     if (ResPtr(posture_bases[ATTACKING_CRITTER_POSTURE] + cp_num) == NULL) {
604         // Suspend time during loading of combat art
605         ulong old_ticks = *tmd_ticks;
606         extern ulong last_real_time;
607         for (p = ATTACKING_CRITTER_POSTURE; p <= ATTACKING2_CRITTER_POSTURE; p++) {
608             if (p != KNOCKBACK_CRITTER_POSTURE) {
609                 ResLock(posture_bases[p] + cp_num);
610                 ResUnlock(posture_bases[p] + cp_num);
611             }
612         }
613         last_real_time += *tmd_ticks - old_ticks;
614     }
615 }
616 
617 // copied in ai.c
618 #define DEFAULT_URGENCY fix_make(0x30, 0)
619 
620 // Run the AI for a combat-worthy critter, either looking actively for
621 // a nearby player, or actually shooting at such
622 // This really needs to have gnosis of:
623 // -- beelining for player when close enough & appropriate
624 // -- sidestepping intelligently, using cover and such (we sidestep very stupidly now)
run_combat_ai(ObjID id,uchar raycast_success)625 errtype run_combat_ai(ObjID id, uchar raycast_success) {
626     ObjSpecID osid = objs[id].specID;
627     ObjCritter *pcrit = &objCritters[osid];
628     LGPoint dest, source;
629     int cp_num;
630 
631     // Sidestep stupidly
632     //   pcrit->sidestep = fix_make(rand()%200 - 100, 0);
633 
634     cp_num = CPNUM(id);
635     if (pcrit->mood == AI_MOOD_ATTACKING) {
636         // Don't bother if we're already trying to attack
637         if ((get_crit_posture(osid) != ATTACKING_CRITTER_POSTURE) &&
638             (get_crit_posture(osid) != ATTACKING2_CRITTER_POSTURE)) {
639             if (ai_ranges) {
640                 if (pcrit->attack_count < player_struct.game_time) {
641                     char posture;
642                     extern uchar music_on;
643 
644                     load_combat_art(cp_num);
645                     if (!(ai_ranges & 0x1))
646                         posture = ATTACKING2_CRITTER_POSTURE;
647                     else if (!(ai_ranges & 0x2))
648                         posture = ATTACKING_CRITTER_POSTURE;
649                     else
650                         posture = (rand() % 255 < CritterProps[cp_num].alt_perc) ? ATTACKING2_CRITTER_POSTURE
651                                                                                  : ATTACKING_CRITTER_POSTURE;
652                     set_posture(osid, posture);
653                     if (music_on)
654                         mai_attack();
655                     // Set how long before we get to attack again!
656                     pcrit->attack_count = (posture == ATTACKING_CRITTER_POSTURE)
657                                               ? player_struct.game_time + CritterProps[cp_num].attacks[0].speed
658                                               : player_struct.game_time + CritterProps[cp_num].attacks[1].speed;
659                 }
660             } else
661                 // If we are out of range, stop being all attack-like in one's posturing
662                 set_posture(osid, STANDING_CRITTER_POSTURE);
663         }
664     }
665 
666     if (raycast_success) {
667         // jeepers!  that's the player!
668         fixang diffang, targang;
669 
670         if (pcrit->orders == AI_ORDERS_NOMOVE)
671             pcrit->sidestep = 0;
672         else
673             pcrit->sidestep = fix_make(rand() % 400 - 200, 0);
674 
675         delete_path(pcrit->path_id);
676         pcrit->path_id = -1;
677         set_des_heading(id, osid, fix_make(OBJ_LOC_BIN_X(objs[PLAYER_OBJ].loc), 0x8000),
678                         fix_make(OBJ_LOC_BIN_Y(objs[PLAYER_OBJ].loc), 0x8000), &diffang, &targang);
679 
680         // If we're out of range or keep missing, run hell-bent towards the player
681         // otherwise take careful potshots
682         if ((ai_ranges) && (!(objs[id].info.inst_flags & CLASS_INST_FLAG2)) && (QUESTVAR_GET(COMBAT_DIFF_QVAR) < 3) &&
683             (ID2TRIP(id) != AUTOBOMB_TRIPLE)) {
684             objCritters[osid].des_speed = DEFAULT_SPEED >> 3;
685             objCritters[osid].urgency = DEFAULT_URGENCY >> 1;
686         } else {
687             objCritters[osid].des_speed = DEFAULT_SPEED;
688             objCritters[osid].urgency = DEFAULT_URGENCY << 1;
689         }
690     } else {
691         objCritters[osid].sidestep = 0;
692         objCritters[osid].des_speed = DEFAULT_SPEED;
693         if (pcrit->path_id != -1) {
694             follow_pathfinding(id, osid);
695         } else {
696             // Do our damnedest to get closer to the player
697             // For now this is a pathfind, it should gain knowledge of beelining soon
698             if ((objs[id].info.inst_flags & CLASS_INST_FLAG2) || (pcrit->mood == AI_MOOD_HOSTILE)) {
699                 source.x = OBJ_LOC_BIN_X(objs[id].loc);
700                 source.y = OBJ_LOC_BIN_Y(objs[id].loc);
701                 dest.x = OBJ_LOC_BIN_X(objs[PLAYER_OBJ].loc);
702                 dest.y = OBJ_LOC_BIN_Y(objs[PLAYER_OBJ].loc);
703                 pcrit->path_id = request_pathfind(source, dest, objs[PLAYER_OBJ].loc.z, objs[id].loc.z, TRUE);
704                 //         mprintf("combat id %x pathfind request = 0x%x!\n",id,pcrit->path_id);
705 
706                 // if the path we want don't exist, stochastically go back to being neutral
707                 check_requests(TRUE);
708                 if ((paths[pcrit->path_id].num_steps == 0) && ((rand() & 0xFF) < 0x30)) {
709                     objCritters[osid].mood = AI_MOOD_NEUTRAL;
710                     objCritters[osid].flags &= ~AI_FLAG_CHASING;
711                     set_posture(osid, STANDING_CRITTER_POSTURE);
712                 }
713             }
714         }
715     }
716     return (OK);
717 }
718 
ai_patrol_func(ObjID oid,ObjSpecID osid)719 LGPoint ai_patrol_func(ObjID oid, ObjSpecID osid) {
720     char temp_x, temp_y;
721     ObjCritter *pcrit = &objCritters[osid];
722     LGPoint dest;
723 
724     // Swap destination
725     temp_x = pcrit->dest_x;
726     temp_y = pcrit->dest_y;
727     dest.x = pcrit->dest_x = pcrit->x1;
728     dest.y = pcrit->dest_y = pcrit->y1;
729     pcrit->x1 = temp_x;
730     pcrit->y1 = temp_y;
731     return (dest);
732 }
733 
ai_highway_func(ObjID oid,ObjSpecID osid)734 LGPoint ai_highway_func(ObjID oid, ObjSpecID osid) {
735     LGPoint dest = {-1, -1};
736     ObjID curr_id;
737     int param, interface_param;
738 
739     curr_id = objCritters[osid].loot2;
740     if (objs[curr_id].obclass != CLASS_TRAP) {
741         return (dest);
742     }
743     switch (objCritters[osid].x1) {
744     case 0:
745         param = objTraps[objs[curr_id].specID].p1;
746         break;
747     case 1:
748         param = objTraps[objs[curr_id].specID].p2;
749         break;
750     case 2:
751         param = objTraps[objs[curr_id].specID].p3;
752         break;
753     }
754 
755     // Secret usage in highway functions
756     if ((objTraps[objs[curr_id].specID].p4 & 0xFFFF) != 0) {
757         interface_param = objTraps[objs[curr_id].specID].p4 >> 16;
758         switch (objTraps[objs[curr_id].specID].p4 & 0xFFFF) {
759         case 1:
760             do_multi_stuff(interface_param);
761             break;
762         }
763     }
764     if ((param != OBJ_NULL) && (objs[param].active) && (objs[param].obclass == CLASS_TRAP)) {
765         dest.x = OBJ_LOC_BIN_X(objs[param].loc);
766         dest.y = OBJ_LOC_BIN_Y(objs[param].loc);
767         objCritters[osid].dest_x = dest.x;
768         objCritters[osid].dest_y = dest.y;
769         objCritters[osid].loot2 = param;
770     }
771     return (dest);
772 }
773 
774 #define DEFAULT_BROWNIAN_DIST 5
775 #define FIND_ROAM_TRIES 25
776 
ai_roam_func(ObjID id,ObjSpecID osid)777 LGPoint ai_roam_func(ObjID id, ObjSpecID osid) {
778     LGPoint dest, source;
779     char tries = 0;
780     uchar okay;
781     short bd;
782 
783     source.x = OBJ_LOC_BIN_X(objs[id].loc);
784     source.y = OBJ_LOC_BIN_Y(objs[id].loc);
785 
786     // reset number of tries
787     objCritters[osid].x1 = 0;
788 
789     bd = objCritters[osid].y1 ? objCritters[osid].y1 : DEFAULT_BROWNIAN_DIST;
790     okay = FALSE;
791     while (!okay && (tries < FIND_ROAM_TRIES)) {
792         dest.x = source.x + rand() % bd - (bd / 2);
793         dest.y = source.y + rand() % bd - (bd / 2);
794         if (me_tiletype(MAP_GET_XY(dest.x, dest.y)) == TILE_OPEN)
795             okay = TRUE;
796         tries++;
797     }
798     return (dest);
799 }
800 
ai_none_func(ObjID oid,ObjSpecID osid)801 LGPoint ai_none_func(ObjID oid, ObjSpecID osid) {
802     LGPoint goof = {-1, -1};
803     objCritters[osid].urgency = 0;
804     objCritters[osid].sidestep = 0;
805     objCritters[osid].des_speed = 0;
806     return (goof);
807 }
808 
809 // Run the AI for a non-combat critter, carrying out SHODANs will in
810 // some other way
811 LGPoint (*ai_order_funcs[])(ObjSpecID, ObjID) = {ai_none_func,   ai_roam_func,    ai_none_func,
812                                                  ai_patrol_func, ai_highway_func, ai_none_func};
813 
814 #define MAX_ROAM_PATH 32
815 #define MAX_ROAM_TRIES 126
816 
817 #define TRANQ_RAND_MASK 0xFFF
818 #define TRANQ_RAND_LEVEL 5
819 #define CONFUSE_RAND_MASK 0xFFF
820 #define CONFUSE_RAND_LEVEL 5
821 
run_peaceful_ai(ObjID id,int big_dist)822 errtype run_peaceful_ai(ObjID id, int big_dist) {
823     ObjSpecID osid = objs[id].specID;
824     LGPoint source, dest;
825     uchar do_path = TRUE;
826 
827     objCritters[osid].sidestep = 0;
828     if (objCritters[osid].path_id != -1) {
829         // Follow our pathfinding...
830 
831         // If we have too far to go, and we aren't very specific about
832         // what it is we are doing, try again
833         if ((objCritters[osid].orders == AI_ORDERS_ROAM) || (objCritters[osid].flags & AI_FLAG_CONFUSED)) {
834             objCritters[osid].x1++;
835 
836             // if confused we are more impatient to find new locations
837             if (objCritters[osid].flags & AI_FLAG_CONFUSED)
838                 objCritters[osid].x1 += 3;
839 
840             // stop this particular wandering if the path we just chose is too long or if we've been at it for too long.
841             if (((objCritters[osid].x1 == 0) && (path_length(objCritters[osid].path_id) > MAX_ROAM_PATH)) ||
842                 (objCritters[osid].x1 > MAX_ROAM_TRIES)) {
843                 //            Spew(DSRC_AI_Pathfind, ("punting roam path!\n"));
844                 delete_path(objCritters[osid].path_id);
845                 objCritters[osid].path_id = -1;
846                 do_path = FALSE;
847             }
848         }
849         if (do_path)
850             follow_pathfinding(id, osid);
851     } else {
852         // Hmm, we should make a new pathfinding request
853         // But punt out if we are too far away from the player
854         if (do_physics_stupidity(id, big_dist)) {
855             source.x = OBJ_LOC_BIN_X(objs[id].loc);
856             source.y = OBJ_LOC_BIN_Y(objs[id].loc);
857             if (ai_order_funcs[objCritters[osid].orders] != NULL) {
858                 dest = ai_order_funcs[objCritters[osid].orders](id, osid);
859                 if (dest.x != -1) {
860                     objCritters[osid].path_id = request_pathfind(source, dest, 0, objs[id].loc.z, FALSE);
861                     //               mprintf("peaceful id %x pathfind request = 0x%x!\n",id,objCritters[osid].path_id);
862                     //               Spew(DSRC_AI_Path, ("peace(%x): id %x getting new path (%d), from %x,%x to
863                     //               %x,%x\n",
864                     //                  big_dist,id,objCritters[osid].path_id,
865                     //                  PT_UNWRAP(source),PT_UNWRAP(dest)));
866                 }
867             }
868         }
869         //      else
870         //         Spew(DSRC_AI_Combat, ("combat skipping id %x due to distance %x\n",id,big_dist));
871     }
872     if ((objCritters[osid].flags & AI_FLAG_CONFUSED) && ((rand() & CONFUSE_RAND_MASK) < CONFUSE_RAND_LEVEL)) {
873         set_crit_posture(osid, MOVING_CRITTER_POSTURE);
874         objCritters[osid].flags &= ~AI_FLAG_CONFUSED;
875     }
876     return (OK);
877 }
878 
879 #define COMBAT_FRAMES 2
880 #define DEFAULT_FRAMES 13
881 
ai_run()882 errtype ai_run() {
883     ObjSpecID osid;
884     ObjID id;
885     short visibility;
886     uchar raycast_success;
887     int dist;
888     char mood;
889 #ifdef PLAYTEST
890     short crit_count = 0;
891 #endif
892     extern uchar physics_running;
893     extern ObjID shodan_avatar_id;
894     extern uchar ai_on;
895     extern errtype apply_EDMS_controls(ObjSpecID osid);
896 
897 #ifndef GAMEONLY
898     // Punt out if no physics or no ai
899     if (!physics_running)
900         return (OK);
901 #endif
902 
903     check_requests(FALSE);
904 
905     // Check ICE agitation
906     if ((global_fullmap->cyber) && (run_ice_time < player_struct.game_time))
907         run_cspace_ice();
908 
909     visibility = compute_base_visibility();
910 
911     // Cycle through all the critters
912     priority_check = FALSE;
913     osid = objCritters[0].id;
914     while (osid != OBJ_SPEC_NULL) {
915         if ((!CHECK_OBJ_PH(objCritters[osid].id)) && (objCritters[osid].id != shodan_avatar_id)) {
916             goto ai_loop_end;
917         }
918         if ((objCritters[osid].orders == AI_ORDERS_SLEEP) || (get_crit_posture(osid) == DEATH_CRITTER_POSTURE))
919             goto ai_loop_end;
920         else if (objCritters[osid].flags & AI_FLAG_TRANQ) {
921             if ((rand() & TRANQ_RAND_MASK) < TRANQ_RAND_LEVEL) {
922                 objCritters[osid].flags &= ~AI_FLAG_TRANQ;
923                 if (CritterProps[CPNUM(objCritters[osid].id)].flags & AI_FLAG_FLYING)
924                     apply_gravity_to_one_object(objCritters[osid].id, 0);
925             }
926             goto ai_loop_end;
927         }
928 
929         // Do some book-keeping
930         id = objCritters[osid].id;
931         if (id == PLAYER_OBJ)
932             goto ai_loop_end;
933         raycast_success = FALSE;
934 
935         dist = long_fast_pyth_dist(objs[id].loc.x - objs[PLAYER_OBJ].loc.x, objs[id].loc.y - objs[PLAYER_OBJ].loc.y);
936         if (global_fullmap->cyber && (id == shodan_avatar_id)) {
937             run_evil_otto(id, dist);
938             goto ai_loop_end;
939         }
940         if (!do_physics_stupidity(id, dist)) {
941             goto ai_loop_end;
942         }
943 
944         objCritters[osid].wait_frames--;
945 
946         // Tell EDMS what to do with us.
947 #ifdef AI_EDMS
948         apply_EDMS_controls(osid);
949 #endif
950 
951         // If it is our turn to get a bigger share of the
952         // computron pie, then let's crank.
953         if (objCritters[osid].wait_frames <= 0) {
954             do_stealth_stuff(id, visibility, &raycast_success, dist);
955             mood = objCritters[osid].mood;
956             if (((mood == AI_MOOD_HOSTILE) || (mood == AI_MOOD_ATTACKING)) &&
957                 !(objCritters[osid].flags & AI_FLAG_CONFUSED)) {
958                 check_attitude_adjustment(id, osid, dist, raycast_success);
959                 run_combat_ai(id, raycast_success);
960                 objCritters[osid].wait_frames = COMBAT_FRAMES;
961             } else {
962                 //            if (objCritters[osid].flags & AI_FLAG_CONFUSED)
963                 //               Spew(DSRC_AI_Hacks, ("critter %x, is confused! flags =
964                 //               %x\n",id,objCritters[osid].flags));
965                 run_peaceful_ai(id, dist);
966 #ifdef USE_DIST_OVERRIDE_FOR_DEFAULT_FRAMES
967                 objCritters[osid].wait_frames = min(DEFAULT_FRAMES, dist >> 8);
968 //            if ((dist >> 8) < DEFAULT_FRAMES)
969 //               Spew(DSRC_AI_AI, ("using %d frames for id %x instead of %d\n",dist >> 8, id, DEFAULT_FRAMES));
970 #else
971                 objCritters[osid].wait_frames = DEFAULT_FRAMES;
972 #endif
973             }
974             // a bit o' random deviation...
975             objCritters[osid].wait_frames += (*tmd_ticks & 0x2);
976         }
977 
978     ai_loop_end:
979         osid = objCritters[osid].next;
980     }
981     if (priority_check)
982         check_requests(TRUE);
983     return (OK);
984 }
985