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/ai.c $
21 * $Revision: 1.167 $
22 * $Author: xemu $
23 * $Date: 1994/10/18 19:50:51 $
24 */
25
26 #define __AI_SRC
27
28 #include <stdlib.h>
29
30 #include "Headers/ai.h"
31 #include "aiflags.h"
32 #include "objects.h"
33 #include "objsim.h"
34 #include "objcrit.h"
35 #include "objwpn.h"
36 #include "physics.h"
37 #include "faketime.h"
38 #include "player.h"
39 #include "damage.h"
40 #include "diffq.h"
41 #include "combat.h"
42 #include "grenades.h"
43 #include "musicai.h"
44 #include "otrip.h"
45 #include "objprop.h"
46 #include "objbit.h"
47 #include "mapflags.h"
48 #include "tilename.h"
49 #include "mainloop.h"
50 #include "physunit.h"
51 #include "tools.h"
52 #include "gamestrn.h"
53 #include "safeedms.h"
54 #include "treasure.h"
55 #include "fullscrn.h"
56 #include "game_screen.h"
57 #include "sfxlist.h"
58
59 #include "ice.h"
60 #include "cyber.h"
61
62 #define AI_EDMS
63
64 // errtype ai_fire_slow_projectile(ObjID src, int proj_triple, ObjLoc src_loc, ObjLoc target_loc, uchar a, int
65 // duration); errtype ai_throw_grenade(ObjID src, int proj_triple, ObjLoc src_loc, ObjLoc target_loc);
66 errtype ai_fire_special(ObjID src, ObjID target, int proj_triple, ObjLoc src_loc, ObjLoc target_loc, uchar a,
67 int duration);
68
69 #define SLOW_PROJECTILE_DURATION 1000
70 #define SLOW_PROJECTILE_SPEED fix_make(5, 0)
71 #define ATTACK_GRENADE_GRAVITY fix_make(0, 0x8000)
72 #define ATTACK_GRENADE_SPEED fix_make(7, 0)
73
74 // tolerance to completion of an EDMS-driven AI maneuver
75 #define AI_COMPLETE_TOLERANCE fix_make(0, 0x5fff)
76
77 // Number of frames beyond which, if we haven't seen the player,
78 // we stop trying to shoot at 'im.
79 #define UNSEEN_FIRE_THRESHOLD 10
80
81 fix there_yet;
82 ObjLoc last_known_loc;
83
84 // -----------
85 // PROTOTYPES
86 // -----------
87 errtype set_posture_safe(ObjSpecID osid, ubyte new_pos);
88 errtype set_posture_movesafe(ObjSpecID osid, ubyte new_pos);
89 errtype clear_critter_controls(ObjSpecID osid);
90 errtype apply_EDMS_controls(ObjSpecID osid);
91 errtype roll_on_dnd_treasure_tables(int *pcont, char treasure_type);
92
93 #define AI_HEAD_HIT_CHANCE 0x40
ai_find_player(ObjID id)94 void ai_find_player(ObjID id) {
95 State st;
96 extern void state_to_objloc(State * s, ObjLoc * l);
97 extern void get_phys_state(int ph, State *new_state, ObjID id);
98
99 if (global_fullmap->cyber)
100 last_known_loc = objs[PLAYER_OBJ].loc;
101 else {
102 uchar slow_proj = FALSE;
103 CritterProp cp = CritterProps[CPNUM(id)];
104 // Some of the time they shoot at yer head, the other times at yer body.
105 // Unless they have a slow projectile, in which case they always shoot at yer head.
106 if ((cp.attacks[0].slow_proj != 0) || ((cp.alt_perc > 0) && (cp.attacks[1].slow_proj)))
107 slow_proj = TRUE;
108 if ((slow_proj) || ((rand() & 0xFF) < AI_HEAD_HIT_CHANCE) || (global_fullmap->cyber))
109 get_phys_state(objs[PLAYER_OBJ].info.ph, &st, PLAYER_OBJ);
110 // EDMS_get_pelvic_viewpoint(objs[PLAYER_OBJ].info.ph, &st);
111 else
112 EDMS_get_state(objs[PLAYER_OBJ].info.ph, &st);
113 state_to_objloc(&st, &last_known_loc);
114 // Warning(("player loc = %x, %x, %x\n",last_known_loc.x,last_known_loc.y,last_known_loc.z));
115 }
116 }
117
set_posture(ObjSpecID osid,ubyte new_pos)118 errtype set_posture(ObjSpecID osid, ubyte new_pos) {
119 if (new_pos != get_crit_posture(osid)) {
120 set_crit_posture(osid, new_pos);
121 objs[objCritters[osid].id].info.current_frame = 0;
122 }
123 return (OK);
124 }
125
set_posture_safe(ObjSpecID osid,ubyte new_pos)126 errtype set_posture_safe(ObjSpecID osid, ubyte new_pos) {
127 if ((get_crit_posture(osid) == STANDING_CRITTER_POSTURE) || (get_crit_posture(osid) == MOVING_CRITTER_POSTURE))
128 return (set_posture(osid, new_pos));
129 return (OK);
130 }
131
set_posture_movesafe(ObjSpecID osid,ubyte new_pos)132 errtype set_posture_movesafe(ObjSpecID osid, ubyte new_pos) {
133 if ((get_crit_posture(osid) != STANDING_CRITTER_POSTURE) && (get_crit_posture(osid) != MOVING_CRITTER_POSTURE))
134 return (set_posture(osid, new_pos));
135 return (OK);
136 }
137
clear_critter_controls(ObjSpecID osid)138 errtype clear_critter_controls(ObjSpecID osid) {
139 objCritters[osid].des_heading = 0;
140 objCritters[osid].des_speed = 0;
141 objCritters[osid].urgency = 0;
142 objCritters[osid].sidestep = 0;
143 return (OK);
144 }
145
146 // tolerance between "standing" and "walking" anims
147 // when all your dots is greater than this, you are considered moving
148 //#define MOVE_TOLERANCE fix_make(0,0x0700)
149 fix move_tolerance = fix_make(0, 0x0400);
150
151 // copied in newai.c
152 #define DEFAULT_URGENCY fix_make(0x30, 0)
153
apply_EDMS_controls(ObjSpecID osid)154 errtype apply_EDMS_controls(ObjSpecID osid) {
155 #ifdef AI_EDMS
156 State crit_state;
157 ObjCritter *pcrit = &objCritters[osid];
158 ObjID id = pcrit->id;
159 fix curr_ai_dist = FIX_UNIT;
160 fix use_speed, use_step;
161
162 // Apply controls from last frame, see how close we came
163 if (CHECK_OBJ_PH(id)) {
164 safe_EDMS_get_state(objs[id].info.ph, &crit_state);
165
166 // Hmm, I wonder whether there is a faster way to do this.
167 if (fix_abs(crit_state.X_dot) + fix_abs(crit_state.Y_dot) + fix_abs(crit_state.Z_dot) > move_tolerance) {
168 if ((get_crit_posture(osid) == STANDING_CRITTER_POSTURE) ||
169 (get_crit_posture(osid) == ATTACK_REST_CRITTER_POSTURE)) {
170 set_posture_safe(osid, MOVING_CRITTER_POSTURE);
171 }
172 } else {
173 set_posture_safe(osid, STANDING_CRITTER_POSTURE);
174 }
175
176 if ((pcrit->urgency == 0) && !(pcrit->flags & AI_FLAG_TRANQ))
177 pcrit->urgency = DEFAULT_URGENCY;
178
179 if (pcrit->orders == AI_ORDERS_NOMOVE) {
180 use_speed = 0;
181 use_step = 0;
182 } else {
183 use_speed = pcrit->des_speed;
184 use_step = pcrit->sidestep;
185 }
186
187 safe_EDMS_ai_control_robot(objs[id].info.ph, pcrit->des_heading, use_speed, pcrit->sidestep, pcrit->urgency,
188 &there_yet, curr_ai_dist);
189 }
190 #endif
191 return (OK);
192 }
193
194 #define SHODAN_AVATAR_HOSAGE_DISTANCE 0x4
195 #define AGITATED_ICE_DIST 100
196
197 // percentage of hits to take in one attack in order to play SFX
198 #define HURT_SOUND_THRESHOLD 25
199
200 // All critters within ANGER_RADIUS of a critter that takes damage
201 // become angry unless their loner bit is set.
202 #define ANGER_RADIUS 3
203
ai_critter_seen(void)204 void ai_critter_seen(void) {
205 extern int mai_combat_length;
206 mlimbs_combat = player_struct.game_time + mai_combat_length;
207 }
208
ai_critter_hit(ObjSpecID osid,short damage,uchar tranq,uchar stun)209 errtype ai_critter_hit(ObjSpecID osid, short damage, uchar tranq, uchar stun) {
210 char i, j;
211 short r;
212 char x1, x2, y1, y2;
213 ObjRefID oref;
214 ObjID oid;
215 char diff;
216
217 // become unconfused & untranqed
218 // woo hoo, watch me pull odds out of my butt
219 if ((objCritters[osid].flags & (AI_FLAG_CONFUSED | AI_FLAG_TRANQ)) && ((rand() & 0xF) <= 6))
220 objCritters[osid].flags &= ~(AI_FLAG_CONFUSED | AI_FLAG_TRANQ);
221
222 // If we are truly asleep, let the enemy pound on us...
223 if (ai_critter_sleeping(osid))
224 return (OK);
225
226 oid = objCritters[osid].id;
227 switch (ID2TRIP(oid)) {
228 case ROBOBABE_TRIPLE: {
229 extern uchar *shodan_bitmask;
230 extern void shodan_phase_in(uchar * bitmask, short x, short y, short w, short h, short num, uchar dir);
231 shodan_phase_in(shodan_bitmask, 0, 0, FULL_VIEW_WIDTH, FULL_VIEW_HEIGHT, damage << 4, FALSE);
232 } break;
233 }
234
235 // Play a sound effect if hurt enough
236 if ((damage * 100 / ObjProps[OPNUM(objCritters[osid].id)].hit_points) > HURT_SOUND_THRESHOLD)
237 play_digi_fx_obj(CritterProps[CPNUM(objCritters[osid].id)].hurt_sound, 1, objCritters[osid].id);
238
239 // Depending on how disruptable we are, we might be disrupted
240 // that means we have to restart our attack timer
241 r = rand() % 255;
242 // Spew(DSRC_AI_Combat, ("r = %d, dp = %d + %d =
243 // %d\n",r,CritterProps[CPNUM(objCritters[osid].id)].disrupt_perc,damage >> 2,
244 // CritterProps[CPNUM(objCritters[osid].id)].disrupt_perc + (damage >> 2)));
245 if (r < CritterProps[CPNUM(objCritters[osid].id)].disrupt_perc + (damage >> (5 - QUESTVAR_GET(COMBAT_DIFF_QVAR)))) {
246 set_posture(osid, DISRUPT_CRITTER_POSTURE);
247 objCritters[osid].attack_count =
248 player_struct.game_time + CritterProps[CPNUM(objCritters[osid].id)].attacks[0].speed;
249 }
250
251 // And boy, are we ticked.
252 objCritters[osid].mood = AI_MOOD_HOSTILE;
253
254 // We get to butt in line, and we know exactly where the player is...
255 ai_find_player(oid);
256 objCritters[osid].wait_frames = 0;
257
258 if (tranq) {
259 objCritters[osid].flags |= AI_FLAG_TRANQ;
260 objCritters[osid].des_speed = 0;
261 objCritters[osid].urgency = 0;
262 objCritters[osid].sidestep = 0;
263 apply_gravity_to_one_object(oid, STANDARD_GRAVITY);
264 if (objs[oid].info.ph != -1) {
265 EDMS_control_robot(objs[oid].info.ph, fix_make(0, 0x0010), 0, 0);
266 }
267 if (get_crit_posture(osid) >= ATTACKING_CRITTER_POSTURE) {
268 set_crit_posture(osid, STANDING_CRITTER_POSTURE);
269 objs[oid].info.current_frame = 0;
270 }
271 }
272 if (stun)
273 objCritters[osid].flags |= AI_FLAG_CONFUSED;
274
275 diff = QUESTVAR_GET((global_fullmap->cyber) ? CYBER_DIFF_QVAR : COMBAT_DIFF_QVAR);
276 if (diff > 0) {
277 // Hey, we'll get our buddies in on the matter too...
278 // We anger all critters within ANGER_RADIUS who are the same
279 // loner-ness as ourselves.
280 // The radius someday might want to be objprop dependant rather
281 // than a constant.
282 // Oh, and we also don't have any effect on critters with a mood of ISOLATION
283
284 oid = objCritters[osid].id;
285 x1 = OBJ_LOC_BIN_X(objs[oid].loc) - ANGER_RADIUS;
286 x2 = x1 + (2 * ANGER_RADIUS);
287 y1 = OBJ_LOC_BIN_Y(objs[oid].loc) - ANGER_RADIUS;
288 y2 = y1 + (2 * ANGER_RADIUS);
289
290 for (j = y1; j <= y2; j++) {
291 for (i = x1; i <= x2; i++) {
292 oref = me_objref(MAP_GET_XY(i, j));
293 while (oref != OBJ_REF_NULL) {
294 oid = objRefs[oref].obj;
295 if ((objs[oid].ref = oref) && (objs[oid].obclass == CLASS_CRITTER)) {
296 if ((objCritters[objs[oid].specID].mood != AI_MOOD_ISOLATION) && (!ai_critter_sleeping(osid))) {
297 // Only get upset if loner-ness matches our own.
298 if ((objs[oid].info.inst_flags & CLASS_INST_FLAG) ==
299 (objs[objCritters[osid].id].info.inst_flags & CLASS_INST_FLAG)) {
300 objCritters[objs[oid].specID].mood = AI_MOOD_HOSTILE;
301 }
302 }
303 }
304 oref = objRefs[oref].next;
305 }
306 }
307 }
308 }
309 return (OK);
310 }
311
312 errtype ai_autobomb_explode(ObjID id, ObjSpecID osid);
313
ai_critter_die(ObjSpecID osid)314 errtype ai_critter_die(ObjSpecID osid) {
315 extern ObjID damage_sound_id;
316 extern char damage_sound_fx;
317 ObjID id = objCritters[osid].id;
318
319 if (ID2TRIP(id) == AUTOBOMB_TRIPLE)
320 ai_autobomb_explode(id, osid);
321
322 set_posture(osid, DEATH_CRITTER_POSTURE);
323 if (CritterProps[CPNUM(id)].death_sound != 255) {
324 damage_sound_fx = CritterProps[CPNUM(id)].death_sound;
325 damage_sound_id = id;
326 }
327 // play_digi_fx_obj(CritterProps[CPNUM(id)].death_sound,1,id);
328
329 // If we were flying, we ain't no more!
330 if (CritterProps[CPNUM(id)].flags & AI_FLAG_FLYING)
331 apply_gravity_to_one_object(id, STANDARD_GRAVITY);
332 return (OK);
333 }
334
335 #define FIRST_CORPSE_TTYPE 11
336
337 int treasure_table[NUM_TREASURE_TYPES][NUM_TREASURE_SLOTS][NUM_TREASURE_ENTRIES] = {
338 // No Treasure
339 {{100, NOTHING_TRIPLE}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
340 // Humanoid (1)
341 {{7, LSD_DRUG_TRIPLE}, {15, MEDI_DRUG_TRIPLE}, {13, BEV_CONT_TRIPLE}, {65, NOTHING_TRIPLE}, {0, 0}, {0, 0}, {0, 0}},
342 // Drone (2)
343 {{14, SPAMMO_TRIPLE},
344 {8, TEFAMMO_TRIPLE},
345 {17, NNAMMO_TRIPLE},
346 {6, MEDI_DRUG_TRIPLE},
347 {5, FRAG_G_TRIPLE},
348 {50, NOTHING_TRIPLE},
349 {0, 0}},
350 // Assassin (3)
351 {{40, SPAMMO_TRIPLE},
352 {15, NNAMMO_TRIPLE},
353 {8, TEFAMMO_TRIPLE},
354 {7, TNAMMO_TRIPLE},
355 {30, NOTHING_TRIPLE},
356 {0, 0},
357 {0, 0}},
358 // Warrior Cyborg (4)
359 {{25, SPAMMO_TRIPLE},
360 {25, TEFAMMO_TRIPLE},
361 {15, HNAMMO_TRIPLE},
362 {5, SPLAMMO_TRIPLE},
363 {5, FRAG_G_TRIPLE},
364 {25, NOTHING_TRIPLE},
365 {0, 0}},
366 // Flier Bots (5)
367 {{30, SPAMMO_TRIPLE},
368 {10, NNAMMO_TRIPLE},
369 {5, TEFAMMO_TRIPLE},
370 {5, HNAMMO_TRIPLE},
371 {50, NOTHING_TRIPLE},
372 {0, 0},
373 {0, 0}},
374 // Security 1 Bots (6)
375 {{20, HNAMMO_TRIPLE},
376 {20, HTAMMO_TRIPLE},
377 {15, SPLAMMO_TRIPLE},
378 {15, MRAMMO_TRIPLE},
379 {10, TEFAMMO_TRIPLE},
380 {10, HSAMMO_TRIPLE},
381 {10, NOTHING_TRIPLE}},
382 // Exec Bots (7)
383 {{25, NOTHING_TRIPLE},
384 {25, SPLAMMO_TRIPLE},
385 {15, HTAMMO_TRIPLE},
386 {10, HNAMMO_TRIPLE},
387 {10, MRAMMO_TRIPLE},
388 {7, HSAMMO_TRIPLE},
389 {8, NOTHING_TRIPLE}},
390 // Cyborg Enforcer (8)
391 {{10, EMP_G_TRIPLE},
392 {40, MRAMMO_TRIPLE},
393 {13, SLGAMMO_TRIPLE},
394 {12, BGAMMO_TRIPLE},
395 {8, MEDI_DRUG_TRIPLE},
396 {2, AIDKIT_TRIPLE},
397 {15, STAMINA_DRUG_TRIPLE}},
398 // Security II Bot (9)
399 {{40, HTAMMO_TRIPLE},
400 {40, HSAMMO_TRIPLE},
401 {10, MRAMMO_TRIPLE},
402 {7, NOTHING_TRIPLE},
403 {3, PRAMMO_TRIPLE}, // no not impart prammo in favor of nothing
404 {0, 0},
405 {0, 0}},
406 // Elite Cyborg (10)
407 {{15, RGAMMO_TRIPLE},
408 {20, BGAMMO_TRIPLE},
409 {20, HSAMMO_TRIPLE},
410 {11, MEDI_DRUG_TRIPLE},
411 {2, AIDKIT_TRIPLE},
412 {27, NOTHING_TRIPLE},
413 {5, PRAMMO_TRIPLE}}, // yeah, what he said
414 // Standard Corpse (11)
415 {{80, NOTHING_TRIPLE},
416 {5, HELMET_TRIPLE},
417 {5, BEV_CONT_TRIPLE},
418 {5, WRAPPER_TRIPLE},
419 {2, LSD_DRUG_TRIPLE},
420 {2, PHASER_TRIPLE},
421 {1, STAMINA_DRUG_TRIPLE}},
422 // loot-oriented corpse (12)
423 {{10, SPAMMO_TRIPLE},
424 {10, STAMINA_DRUG_TRIPLE},
425 {5, BATTERY_TRIPLE},
426 {11, MEDI_DRUG_TRIPLE},
427 {64, NOTHING_TRIPLE},
428 {0, 0},
429 {0, 0}},
430 // electro-stuff treasure (maint & repair bots)
431 {{5, EPICK_TRIPLE}, {15, BATTERY_TRIPLE}, {80, NOTHING_TRIPLE}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
432 // serv-bot treasure
433 {{35, BEV_CONT_TRIPLE},
434 {5, MEDI_DRUG_TRIPLE},
435 {15, BEAKER_CONT_TRIPLE},
436 {15, FLASK_CONT_TRIPLE},
437 {12, BATTERY_TRIPLE},
438 {1, SKULL_TRIPLE},
439 {17, NOTHING_TRIPLE}},
440 };
441
roll_on_dnd_treasure_tables(int * pcont,char treasure_type)442 errtype roll_on_dnd_treasure_tables(int *pcont, char treasure_type) {
443 char perc;
444 char count = 0;
445 uchar give, done = FALSE;
446 int chance, trip;
447
448 perc = rand() % 100;
449 while (!done && (count < NUM_TREASURE_SLOTS)) {
450 give = FALSE;
451 chance = treasure_table[treasure_type][count][0];
452 trip = treasure_table[treasure_type][count][1];
453 if (chance == 0)
454 done = TRUE;
455 else {
456 if (TRIP2CL(trip) == CLASS_AMMO) {
457 if (!player_struct.cartridges[get_nth_from_triple(trip)])
458 give = TRUE;
459 }
460 if (give || perc < chance) {
461 if (trip != NOTHING_TRIPLE) {
462 if ((QUESTVAR_GET(COMBAT_DIFF_QVAR) <= 2) || ((rand() & 0xFF) < 0x80))
463 *pcont = obj_create_base(trip);
464 }
465 done = TRUE;
466 } else
467 perc -= chance;
468 }
469 count++;
470 }
471 return (OK);
472 }
473
474 // Distribute loot as appropriate. If we go to loot being contained
475 // "in" corpses, this is the procedure to change.
do_regular_loot(ObjSpecID source_critter,ObjID corpse)476 errtype do_regular_loot(ObjSpecID source_critter, ObjID corpse) {
477 ObjID l1, l2 = OBJ_NULL;
478 ObjSpecID osid = objs[corpse].specID;
479
480 l1 = objCritters[source_critter].loot1;
481 if (objCritters[source_critter].orders != AI_ORDERS_HIGHWAY)
482 l2 = objCritters[source_critter].loot2;
483
484 if (l2 != OBJ_NULL && objs[l2].active) {
485 objContainers[osid].contents1 = OBJ_NULL; // in case we had set it to a triple for later random generation
486 objContainers[osid].contents2 = l2;
487 // unset freshness flag
488 objs[corpse].info.inst_flags &= ~CLASS_INST_FLAG;
489 if (objs[l2].info.current_hp == 0)
490 objs[corpse].info.current_hp = 0;
491 }
492
493 if (l1 != OBJ_NULL && objs[l1].active) {
494 objContainers[osid].contents1 = l1;
495 // unset freshness flag
496 objs[corpse].info.inst_flags &= ~CLASS_INST_FLAG;
497 if (objs[l1].info.current_hp == 0)
498 objs[corpse].info.current_hp = 0;
499 }
500
501 return (OK);
502 }
503
do_random_loot(ObjID corpse)504 errtype do_random_loot(ObjID corpse) {
505 ObjSpecID osid = objs[corpse].specID;
506 int *pc1, *pc2;
507 uchar t_type;
508 // char buf[80];
509
510 if ((((ID2TRIP(corpse) >= MUT_CORPSE1_TRIPLE) && (ID2TRIP(corpse) <= OTH_CORPSE8_TRIPLE)) ||
511 ((ID2TRIP(corpse) >= CORPSE1_TRIPLE) && (ID2TRIP(corpse) <= CORPSE8_TRIPLE))) &&
512 (objs[corpse].info.inst_flags & CLASS_INST_FLAG)) {
513 switch (objs[corpse].obclass) {
514 case CLASS_CONTAINER:
515 t_type = CritterProps[CPTRIP(objContainers[osid].contents1)].treasure_type;
516 pc1 = &objContainers[osid].contents1;
517 pc2 = &objContainers[osid].contents2;
518 *pc1 = 0;
519 *pc2 = 0;
520 break;
521 case CLASS_SMALLSTUFF:
522 t_type = FIRST_CORPSE_TTYPE + objSmallstuffs[osid].cosmetic_value;
523 pc1 = &objSmallstuffs[osid].data1;
524 pc2 = &objSmallstuffs[osid].data2;
525 break;
526 }
527
528 if (*pc1 == 0) {
529 roll_on_dnd_treasure_tables(pc1, t_type);
530 switch (QUESTVAR_GET(COMBAT_DIFF_QVAR)) {
531 case 0:
532 case 1:
533 if (*pc1 == 0)
534 roll_on_dnd_treasure_tables(pc1, t_type);
535 break;
536 }
537 }
538 if (*pc2 == 0) {
539 roll_on_dnd_treasure_tables(pc2, t_type);
540 switch (QUESTVAR_GET(COMBAT_DIFF_QVAR)) {
541 case 0:
542 case 1:
543 if (*pc2 == 0)
544 roll_on_dnd_treasure_tables(pc2, t_type);
545 break;
546 }
547 }
548
549 // unset the freshness bit
550 objs[corpse].info.inst_flags &= ~CLASS_INST_FLAG;
551 }
552 return (OK);
553 }
554
ai_critter_really_dead(ObjSpecID osid)555 errtype ai_critter_really_dead(ObjSpecID osid) {
556 int corpse_trip;
557 char f;
558 extern errtype obj_floor_func(ObjID id);
559
560 corpse_trip = CritterProps[CPNUM(objCritters[osid].id)].corpse;
561 if (corpse_trip != 0) {
562 ObjID new_obj;
563 new_obj = obj_create_base(corpse_trip);
564 if (new_obj) {
565 if ((f = FRAME_NUM_3D(ObjProps[OPNUM(new_obj)].bitmap_3d)))
566 objs[new_obj].info.current_frame = rand() % (f + 1);
567 else
568 objs[new_obj].info.current_frame = 0;
569 obj_move_to(new_obj, &objs[objCritters[osid].id].loc, TRUE);
570 // obj_floor_func(new_obj);
571
572 if (objCritters[osid].flags & AI_FLAG_NOLOOT) {
573 objContainers[objs[new_obj].specID].contents1 = OBJ_NULL;
574 objs[new_obj].info.inst_flags &= ~CLASS_INST_FLAG;
575 } else {
576 // set our contents1 to the triple so that we know how to generate loot right later
577 objContainers[objs[new_obj].specID].contents1 = ID2TRIP(objCritters[osid].id);
578
579 // fresh kill, yum!
580 objs[new_obj].info.inst_flags |= CLASS_INST_FLAG;
581 }
582
583 // if we have regular loot, however, do what we do
584 do_regular_loot(osid, new_obj);
585 }
586 }
587 return (OK);
588 }
589
590 uchar pacifism_on;
591 #define AUTOBOMB_RANGE fix_make(3, 0)
592
ai_misses(ObjSpecID osid)593 void ai_misses(ObjSpecID osid) {
594 // We aren't hitting, so get closer sometimes
595 if (rand() % 4 == 1) {
596 objs[objCritters[osid].id].info.inst_flags |= CLASS_INST_FLAG2;
597 objCritters[osid].mood = AI_MOOD_HOSTILE;
598 } else
599 objs[objCritters[osid].id].info.inst_flags &= ~CLASS_INST_FLAG2;
600 }
601
ai_autobomb_explode(ObjID id,ObjSpecID osid)602 errtype ai_autobomb_explode(ObjID id, ObjSpecID osid) {
603 CritterAttack ca;
604 ExplosionData edata;
605 extern void critter_light_world(ObjID id);
606
607 ca = CritterProps[CPNUM(id)].attacks[0];
608
609 edata.radius = AUTOBOMB_RANGE;
610 edata.radius_change = (AUTOBOMB_RANGE >> 1);
611 edata.damage_mod = ca.damage_modifier;
612 edata.damage_change = ca.damage_modifier >> 1;
613 edata.dtype = ca.damage_type;
614 edata.knock_mass = ca.attack_mass;
615 edata.offense = ca.offense_value;
616 edata.penet = ca.penetration;
617
618 // Do an explosion!
619 do_explosion(objs[id].loc, id, FALSE, &edata);
620 play_digi_fx_obj(SFX_EXPLOSION_1, 1, id);
621
622 // Make us dying....
623 set_posture(osid, DEATH_CRITTER_POSTURE);
624
625 // make us light up the world
626 critter_light_world(id);
627
628 return (OK);
629 }
630
ai_attack_player(ObjSpecID osid,char a)631 errtype ai_attack_player(ObjSpecID osid, char a) {
632 int wpnflags, wpnpower;
633 ObjID hit_obj = OBJ_NULL;
634 ObjID id = objCritters[osid].id;
635 ObjLoc dest_loc;
636 int cp_num;
637 fix attack_mass; // cause of new ray casting prototype - minman
638
639 if (pacifism_on)
640 return (OK);
641
642 cp_num = CPNUM(objCritters[osid].id);
643
644 // If we are an autobomb, don't attack as usual, instead go boom!
645 switch (ID2TRIP(id)) {
646 case AUTOBOMB_TRIPLE: {
647 return (ai_autobomb_explode(id, osid));
648 } break;
649 }
650
651 wpnpower = 100; // Full strength attack!
652 wpnflags = 0; // Normal attack
653 attack_mass = fix_make(CritterProps[cp_num].attacks[a].attack_mass, 0) * 20;
654
655 #ifdef CRITTER_ALWAYS_ACCURATE
656 hit_obj = ray_cast_objects(objCritters[osid].id, PLAYER_OBJ, attack_mass,
657 fix_make(0, CritterProps[cp_num].attacks[a].attack_size),
658 fix_make(CritterProps[cp_num].attacks[a].attack_velocity, 0),
659 fix_make(CritterProps[cp_num].attacks[a].att_range, 0));
660 #else
661 // If we have NO idea where the player is (all failed detection rolls)
662 // then don't bother firing.
663 if (last_known_loc.x != 255) {
664 short miss_amt = ((255 - CritterProps[cp_num].attacks[a].accuracy) - rand() % 255);
665
666 // Play sound effect
667 play_digi_fx_obj(CritterProps[cp_num].attack_sound, 1, objCritters[osid].id);
668 objs[objCritters[osid].id].info.inst_flags |= UNLIT_FLAG;
669
670 // Shoot at player's last known location.
671 // Also, modify where we fire by our accuracy variable
672 dest_loc = last_known_loc;
673
674 if (miss_amt > 0) {
675 // We've failed our accuracy roll, so let's perturb
676 // our target by an amount proportional to the amount
677 // that we missed by.
678 dest_loc.x += (rand() % miss_amt - (miss_amt / 2));
679 dest_loc.y += (rand() % miss_amt - (miss_amt / 2));
680 dest_loc.z += rand() % miss_amt;
681 }
682 if (CritterProps[cp_num].attacks[a].slow_proj == 0) {
683 #ifdef PLAYTEST
684 extern uchar prevent_ray_spew;
685 prevent_ray_spew = FALSE;
686 #endif
687 hit_obj = ray_cast_attack(objCritters[osid].id, dest_loc, attack_mass, RAYCAST_ATTACK_SIZE,
688 fix_make(CritterProps[cp_num].attacks[a].attack_velocity, 0),
689 fix_make(CritterProps[cp_num].attacks[a].att_range, 0));
690 #ifdef PLAYTEST
691 prevent_ray_spew = TRUE;
692 #endif
693 if (hit_obj != OBJ_NULL) {
694 attack_object(hit_obj, CritterProps[cp_num].attacks[a].damage_type,
695 CritterProps[cp_num].attacks[a].damage_modifier,
696 CritterProps[cp_num].attacks[a].offense_value,
697 CritterProps[cp_num].attacks[a].penetration, wpnflags, wpnpower, NULL, 0, 0, NULL);
698
699 objs[objCritters[osid].id].info.inst_flags &= ~CLASS_INST_FLAG2;
700 } else
701 ai_misses(osid);
702 } else {
703 ai_fire_special(objCritters[osid].id, PLAYER_OBJ, CritterProps[cp_num].attacks[a].slow_proj,
704 objs[objCritters[osid].id].loc, dest_loc, a, SLOW_PROJECTILE_DURATION);
705 }
706 } else {
707 objCritters[osid].mood = AI_MOOD_HOSTILE;
708 }
709 #endif
710 return (OK);
711 }
712
713 #define SLOW_PROJ_RAY_MASS (fix_make(0, 0x1000))
714 #define SLOW_PROJ_RAY_SIZE (fix_make(0, 0x1800))
715 #define SLOW_PROJ_RAY_RANGE (fix_make(20, 0))
716 extern void get_phys_state(int ph, State *new_state, ObjID id);
717
ai_fire_special(ObjID src,ObjID target,int proj_triple,ObjLoc src_loc,ObjLoc target_loc,uchar a,int duration)718 errtype ai_fire_special(ObjID src, ObjID target, int proj_triple, ObjLoc src_loc, ObjLoc target_loc, uchar a,
719 int duration) {
720 ObjID proj_id;
721 fix xvel, yvel, zvel;
722 fix xdiff, ydiff, zdiff;
723 fix dist;
724 fix fire_speed;
725 fixang new_angle;
726 ubyte head;
727 State new_state;
728 Robot da_robot;
729 extern void activate_grenade(ObjSpecID osid);
730
731 if (!global_fullmap->cyber) {
732 // let's attack only if we think we're going to hit our target
733 if (ray_cast_attack(src, target_loc, SLOW_PROJ_RAY_MASS, SLOW_PROJ_RAY_SIZE, NO_RAYCAST_KICKBACK_SPEED,
734 SLOW_PROJ_RAY_RANGE) != target)
735 return (OK);
736 }
737
738 proj_id = obj_create_base(proj_triple);
739 if (proj_id == OBJ_NULL) {
740 WARN("%s: Could not create slow projectile!", __FUNCTION__);
741 return (OK);
742 }
743
744 if (TRIP2CL(proj_triple) == CLASS_PHYSICS) {
745 objPhysicss[objs[proj_id].specID].owner = src;
746 objPhysicss[objs[proj_id].specID].bullet_triple = a;
747 objPhysicss[objs[proj_id].specID].duration = player_struct.game_time + duration;
748 fire_speed = SLOW_PROJECTILE_SPEED;
749 } else {
750 activate_grenade(objs[proj_id].specID);
751 fire_speed = ATTACK_GRENADE_SPEED;
752 }
753
754 #ifdef AI_EDMS
755
756 if ((objs[src].obclass == CLASS_CRITTER) && (CritterProps[CPNUM(src)].proj_offset))
757 src_loc.z += (CritterProps[CPNUM(src)].proj_offset >> (SLOPE_SHIFT_D - 2));
758
759 get_phys_state(objs[PLAYER_OBJ].info.ph, &new_state, PLAYER_OBJ);
760
761 // Compute distance
762 xdiff = fix_from_obj_coord(target_loc.x) - fix_from_obj_coord(src_loc.x);
763 ydiff = fix_from_obj_coord(target_loc.y) - fix_from_obj_coord(src_loc.y);
764 zdiff = new_state.Z - fix_from_obj_height_val(src_loc.z);
765 dist = fix_fast_pyth_dist(xdiff, ydiff);
766
767 if (global_fullmap->cyber) {
768 // let's get heading
769 new_angle = fix_atan2(ydiff, xdiff);
770 head = (ubyte)obj_angle_from_fixang(new_angle); //(fix_div(new_angle, FIXANG_PI) >> 9);
771
772 // let's start the work for pitch
773 new_angle = fix_atan2(zdiff, dist);
774 // pitch = (ubyte) (fix_div(new_angle, FIXANG_PI) >> 9);
775
776 // shift coordinate frames for heading
777 src_loc.h = (ubyte)((320L - head) % 256);
778 src_loc.p = obj_angle_from_fixang(new_angle); // pitch;
779 src_loc.b = 0;
780 }
781
782 xvel = fix_mul_div(xdiff, fire_speed, dist);
783 yvel = fix_mul_div(ydiff, fire_speed, dist);
784
785 // LET'S HACK HACK HACK THE WAY GRENADES ARE THROWN!!!!
786 // let's guess on the zvel!
787 if (TRIP2CL(proj_triple) == CLASS_PHYSICS)
788 zvel = fix_mul_div(zdiff, fire_speed, dist);
789 else
790 zvel = (zdiff < fix_make(0, 0x4000)) ? fix_mul(fix_make(0, 0x3800), dist) : fix_mul(zdiff, fix_make(4, 0));
791
792 obj_move_to_vel(proj_id, &src_loc, TRUE, xvel, yvel, zvel);
793 EDMS_ignore_collisions(objs[src].info.ph, objs[proj_id].info.ph);
794 if (TRIP2CL(proj_triple) == CLASS_PHYSICS)
795 apply_gravity_to_one_object(proj_id, SLOW_PROJECTILE_GRAVITY);
796 else
797 apply_gravity_to_one_object(proj_id, ATTACK_GRENADE_GRAVITY);
798
799 // don't ask me why i have to do this - but i do
800 EDMS_get_robot_parameters(objs[proj_id].info.ph, &da_robot);
801 da_robot.cyber_space = -1;
802 EDMS_set_robot_parameters(objs[proj_id].info.ph, &da_robot);
803
804 #endif
805 return (OK);
806 }
807
808 /* KLC - these don't do anything.
809 errtype ai_freeze_tag()
810 {
811 return(OK);
812 }
813
814 errtype ai_time_passes(ulong *ticks_passed)
815 {
816 return(OK);
817 }
818 */
819