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, ¤t_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