/* THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * $Source: /cvsroot/dxx-rebirth/d1x-rebirth/main/ai.c,v $ * $Revision: 1.1.1.1 $ * $Author: zicodxx $ * $Date: 2006/03/17 19:41:40 $ * * Autonomous Individual movement. * * $Log: ai.c,v $ * Revision 1.1.1.1 2006/03/17 19:41:40 zicodxx * initial import * * Revision 1.1.1.1 1999/06/14 22:05:03 donut * Import of d1x 1.37 source. * * Revision 2.11 1995/07/09 11:15:48 john * Put in Mike's code to fix bug where bosses don't gate in bots after * 32767 seconds of playing. * * Revision 2.10 1995/06/15 12:31:08 john * Fixed bug with cheats getting enabled when you type * the whole alphabet. * * Revision 2.9 1995/05/26 16:16:18 john * Split SATURN into define's for requiring cd, using cd, etc. * Also started adding all the Rockwell stuff. * * Revision 2.8 1995/04/06 15:12:27 john * Fixed bug with insane not working. * * Revision 2.7 1995/03/30 16:36:44 mike * text localization. * * Revision 2.6 1995/03/28 11:22:24 john * Added cheats to save file. Changed lunacy text. * * Revision 2.5 1995/03/27 16:45:07 john * Fixed some cheat bugs. Added astral cheat. * * Revision 2.4 1995/03/24 15:29:17 mike * add new cheats. * * Revision 2.3 1995/03/21 14:39:45 john * Ifdef'd out the NETWORK code. * * Revision 2.2 1995/03/14 18:24:39 john * Force Destination Saturn to use CD-ROM drive. * * Revision 2.1 1995/03/06 16:47:14 mike * destination saturn * * Revision 2.0 1995/02/27 11:30:01 john * New version 2.0, which has no anonymous unions, builds with * Watcom 10.0, and doesn't require parsing BITMAPS.TBL. * * Revision 1.295 1995/02/22 13:23:04 allender * remove anonymous unions from object structure * * Revision 1.294 1995/02/13 11:00:43 rob * Make brain guys high enough to get an open slot. * * Revision 1.293 1995/02/13 10:31:55 mike * Make brains understand they can't open locked doors. * * Revision 1.292 1995/02/13 10:18:01 rob * Reduced brain guy's level of awareness to keep him from hogging slots. * * Revision 1.291 1995/02/11 12:27:12 mike * fix path-to-exit cheat. * * Revision 1.290 1995/02/11 01:56:30 mike * robots don't fire cheat. * * Revision 1.289 1995/02/10 17:15:09 rob * Fixed some stuff with 64 awareness stuff. * * Revision 1.288 1995/02/10 16:31:32 mike * oops. * * Revision 1.287 1995/02/10 16:24:45 mike * fix the network follow path fix. * * Revision 1.286 1995/02/10 16:11:40 mike * in serial or modem games, follow path guys don't move if far away and * can't see player. * * Revision 1.285 1995/02/09 13:11:35 mike * comment out a bunch of mprintfs. * add toaster (drops prox bombs, runs away) to boss gate list. * * Revision 1.284 1995/02/08 22:44:53 rob * Lowerd anger level for follow path of any sort. * * Revision 1.283 1995/02/08 22:30:43 mike * lower awareness on station guys if they are returning home (multiplayer). * * Revision 1.282 1995/02/08 17:01:06 rob * Fixed problem with toasters dropping of proximity bombs. * * Revision 1.281 1995/02/08 11:49:35 rob * Reduce Green-guy attack awareness level so we don't let him attack us too. * * Revision 1.280 1995/02/08 11:37:52 mike * Check for failures in call to obj_create. * * Revision 1.279 1995/02/07 20:38:46 mike * fix toasters in multiplayer * * * Revision 1.278 1995/02/07 16:51:07 mike * fix sound time play bug. * * Revision 1.277 1995/02/06 22:33:04 mike * make robots follow path better in cooperative/roboarchy. * * Revision 1.276 1995/02/06 18:15:42 rob * Added forced sends for evasion movemnet. * * Revision 1.275 1995/02/06 16:41:22 rob * Change some positioning calls. * * Revision 1.274 1995/02/06 11:40:33 mike * replace some lint-related hacks with clean, proper code. * * Revision 1.273 1995/02/04 17:28:19 mike * make station guys return better. * * Revision 1.272 1995/02/03 17:40:55 mike * fix problem with robots falling asleep if you sit in game overnight, not in pause...bah. * * Revision 1.271 1995/02/02 21:11:25 rob * Tweaking stuff for multiplayer ai. * * Revision 1.270 1995/02/02 17:32:06 john * Added Hack for Assert that Mike put in after using Lint to find * uninitialized variables. * * Revision 1.269 1995/02/02 16:46:31 mike * fix boss gating. * * Revision 1.268 1995/02/02 16:27:29 mike * make boss not put out infinite robots. * * Revision 1.267 1995/02/01 21:10:02 mike * lint found bug! player_visibility not initialized! * * Revision 1.266 1995/02/01 20:51:27 john * Lintized * * Revision 1.265 1995/02/01 17:14:05 mike * fix robot sounds. * * Revision 1.264 1995/01/31 16:16:40 mike * Comment out "Darn you, John" Int3(). * * Revision 1.263 1995/01/30 20:55:04 mike * fix nonsense in robot firing when a player is cloaked. * * Revision 1.262 1995/01/30 17:15:10 rob * Fixed problems with bigboss eclip messages. * Tweaked robot position sending for modem purposes. * * Revision 1.261 1995/01/30 15:30:31 rob * Prevent non-master players from gating in robots. * * Revision 1.260 1995/01/30 13:30:55 mike * new cases for firing at other players were bogus, could send position * without permission. * * Revision 1.259 1995/01/30 13:01:17 mike * Make robots fire at player other than one they are controlled by sometimes. * * Revision 1.258 1995/01/29 16:09:17 rob * Trying to get robots to shoot at non-controlling players. * * Revision 1.257 1995/01/29 13:47:05 mike * Make boss have more fireballs on death, have until end (though silent at end). * Fix bug which was preventing him from teleporting until hit, so he'd always * be in the same place when the player enters the room. * * Revision 1.256 1995/01/28 17:40:18 mike * make boss teleport & gate before you see him. * * Revision 1.255 1995/01/27 17:02:08 mike * move code around, was sending one frame (or worse!) old robot information. * * Revision 1.254 1995/01/26 17:02:43 mike * make fusion cannon have more chrome, make fusion, mega rock you! * * Revision 1.253 1995/01/26 15:11:17 rob * Shutup! I fixed it! * * Revision 1.252 1995/01/26 15:08:55 rob * Changed robot gating to accomodate multiplayer. * * Revision 1.251 1995/01/26 14:49:04 rob * Increase awareness level for firing to 94. * * Revision 1.250 1995/01/26 12:41:20 mike * fix bogus multiplayer code, would send permission without getting permission. * * Revision 1.249 1995/01/26 12:23:23 rob * Removed defines that were moved to ai.h * * Revision 1.248 1995/01/25 23:38:48 mike * modify list of robots gated in by super boss. * * Revision 1.247 1995/01/25 21:21:13 rob * Trying to let robots fire at a player even if they're not in control. * * Revision 1.246 1995/01/25 13:50:37 mike * Robots make angry sounds. * * Revision 1.245 1995/01/25 10:53:47 mike * better handling of robots which poke out of mine and try to recover. * * Revision 1.244 1995/01/24 22:03:02 mike * Tricky code to move a robot to a legal position if he is poking out of * the mine, even if it means moving him to another segment. * * Revision 1.243 1995/01/24 20:12:06 rob * Changed robot fire awareness level from 74 to 94. * * Revision 1.242 1995/01/24 13:22:32 mike * make robots accelerate faster, and Difficulty_level dependent. * * Revision 1.241 1995/01/24 12:09:39 mike * make robots animate in multiplayer. * * Revision 1.240 1995/01/21 21:21:10 mike * Make boss only gate robots into specified segments. * * Revision 1.239 1995/01/20 20:21:26 mike * prevent unnecessary boss cloaking. * */ #ifdef RCS static char rcsid[] = "$Id: ai.c,v 1.1.1.1 2006/03/17 19:41:40 zicodxx Exp $"; #endif #include #include #include "inferno.h" #include "game.h" #include "mono.h" #include "3d.h" #include "object.h" #include "render.h" #include "error.h" #include "ai.h" #include "laser.h" #include "fvi.h" #include "polyobj.h" #include "bm.h" #include "weapon.h" #include "physics.h" #include "collide.h" #include "fuelcen.h" #include "player.h" #include "wall.h" #include "vclip.h" #include "digi.h" #include "fireball.h" #include "morph.h" #include "effects.h" #include "timer.h" #include "sounds.h" #include "cntrlcen.h" #include "multibot.h" #include "multi.h" #include "network.h" #include "gameseq.h" #include "key.h" #include "powerup.h" #include "gauges.h" #include "text.h" #ifdef EDITOR #include "editor/editor.h" #endif #ifndef NDEBUG #include "string.h" #ifdef __MSDOS__ #include #endif #endif #include "d_gamecv.h" //added 05/17/99 Matt Mueller #include "u_mem.h" //end addition -MM void init_boss_segments(short segptr[], int *num_segs, int size_check); void ai_multi_send_robot_position(int objnum, int force); #define JOHN_CHEATS_SIZE_1 6 #define JOHN_CHEATS_SIZE_2 6 #define JOHN_CHEATS_SIZE_3 6 ubyte john_cheats_1[JOHN_CHEATS_SIZE_1] = { KEY_P ^ 0x00 ^ 0x34, KEY_O ^ 0x10 ^ 0x34, KEY_B ^ 0x20 ^ 0x34, KEY_O ^ 0x30 ^ 0x34, KEY_Y ^ 0x40 ^ 0x34, KEY_S ^ 0x50 ^ 0x34 }; #define PARALLAX 0 // If !0, then special debugging info for Parallax eyes only enabled. #define MIN_D 0x100 int Flinch_scale = 4; int john_cheats_index_1; // POBOYS detonate reactor int Attack_scale = 24; #define ANIM_RATE (F1_0/16) #define DELTA_ANG_SCALE 16 byte Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST}; int john_cheats_index_2; // PORGYS high speed weapon firing // int No_ai_flag=0; #define OVERALL_AGITATION_MAX 100 #define MAX_AI_CLOAK_INFO 8 // Must be a power of 2! typedef struct { fix last_time; vms_vector last_position; } ai_cloak_info; #define BOSS_CLOAK_DURATION (F1_0*7) #define BOSS_DEATH_DURATION (F1_0*6) #define BOSS_DEATH_SOUND_DURATION 0x2ae14 // 2.68 seconds // Amount of time since the current robot was last processed for things such as movement. // It is not valid to use FrameTime because robots do not get moved every frame. //fix AI_proc_time; int Num_boss_teleport_segs; short Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS]; #ifndef SHAREWARE int Num_boss_gate_segs; short Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS]; #endif int john_cheats_index_3; // LUNACY lunacy (insane behavior, rookie firing) // ---------- John: These variables must be saved as part of gamesave. ---------- int Ai_initialized = 0; int Overall_agitation; ai_local Ai_local_info[MAX_OBJECTS]; point_seg Point_segs[MAX_POINT_SEGS]; point_seg *Point_segs_free_ptr = Point_segs; ai_cloak_info Ai_cloak_info[MAX_AI_CLOAK_INFO]; fix Boss_cloak_start_time = 0; fix Boss_cloak_end_time = 0; fix Last_teleport_time = 0; fix Boss_teleport_interval = F1_0*8; fix Boss_cloak_interval = F1_0*10; // Time between cloaks fix Boss_cloak_duration = BOSS_CLOAK_DURATION; fix Last_gate_time = 0; fix Gate_interval = F1_0*6; fix Boss_dying_start_time; int Boss_dying, Boss_dying_sound_playing, Boss_hit_this_frame; int Boss_been_hit=0; // ---------- John: End of variables which must be saved as part of gamesave. ---------- int john_cheats_index_4; // PLETCHnnn paint robots int ai_evaded=0; #ifndef SHAREWARE // 0 mech // 1 green claw // 2 spider // 3 josh // 4 violet // 5 cloak vulcan // 6 cloak mech // 7 brain // 8 onearm // 9 plasma // 10 toaster // 11 bird // 12 missile bird // 13 polyhedron // 14 baby spider // 15 mini boss // 16 super mech // 17 shareware boss // 18 cloak-green ; note, gating in this guy benefits player, cloak objects // 19 vulcan // 20 toad // 21 4-claw // 22 quad-laser // 23 super boss // byte Super_boss_gate_list[] = {0, 1, 2, 9, 11, 16, 18, 19, 21, 22, 0, 9, 9, 16, 16, 18, 19, 19, 22, 22}; byte Super_boss_gate_list[] = {0, 1, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 0, 8, 11, 19, 20, 8, 20, 8}; #define MAX_GATE_INDEX ( sizeof(Super_boss_gate_list) / sizeof(Super_boss_gate_list[0]) ) #endif int Ai_info_enabled=0; int Robot_firing_enabled = 1; extern int Ugly_robot_cheat, Ugly_robot_texture, Laser_rapid_fire; extern byte Enable_john_cheat_1, Enable_john_cheat_2, Enable_john_cheat_3, Enable_john_cheat_4; ubyte john_cheats_3[2*JOHN_CHEATS_SIZE_3+1] = { KEY_Y ^ 0x67, KEY_E ^ 0x66, KEY_C ^ 0x65, KEY_A ^ 0x64, KEY_N ^ 0x63, KEY_U ^ 0x62, KEY_L ^ 0x61 }; #define MAX_AWARENESS_EVENTS 64 typedef struct awareness_event { short segnum; // segment the event occurred in short type; // type of event, defines behavior vms_vector pos; // absolute 3 space location of event } awareness_event; // These globals are set by a call to find_vector_intersection, which is a slow routine, // so we don't want to call it again (for this object) unless we have to. vms_vector Hit_pos; int Hit_type, Hit_seg; fvi_info Hit_data; int Num_awareness_events = 0; awareness_event Awareness_events[MAX_AWARENESS_EVENTS]; vms_vector Believed_player_pos; #define AIS_MAX 8 #define AIE_MAX 4 //--unused-- int Processed_this_frame, LastFrameCount; #ifndef NDEBUG // Index into this array with ailp->mode char mode_text[8][9] = { "STILL ", "WANDER ", "FOL_PATH", "CHASE_OB", "RUN_FROM", "HIDE ", "FOL_PAT2", "OPENDOR2" }; // Index into this array with aip->behavior char behavior_text[6][9] = { "STILL ", "NORMAL ", "HIDE ", "RUN_FROM", "FOLPATH ", "STATION " }; // Index into this array with aip->GOAL_STATE or aip->CURRENT_STATE char state_text[8][5] = { "NONE", "REST", "SRCH", "LOCK", "FLIN", "FIRE", "RECO", "ERR_", }; int Ai_animation_test=0; #endif // Current state indicates where the robot current is, or has just done. // Transition table between states for an AI object. // First dimension is trigger event. // Second dimension is current state. // Third dimension is goal state. // Result is new goal state. // ERR_ means something impossible has happened. byte Ai_transition_table[AI_MAX_EVENT][AI_MAX_STATE][AI_MAX_STATE] = { { // Event = AIE_FIRE, a nearby object fired // none rest srch lock flin fire reco // CURRENT is rows, GOAL is columns { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, // none { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, // rest { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, // search { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, // lock { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO}, // flinch { AIS_ERR_, AIS_FIRE, AIS_FIRE, AIS_FIRE, AIS_FLIN, AIS_FIRE, AIS_RECO}, // fire { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE} // recoil }, // Event = AIE_HITT, a nearby object was hit (or a wall was hit) { { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE} }, // Event = AIE_COLL, player collided with robot { { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_LOCK, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO}, { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE} }, // Event = AIE_HURT, player hurt robot (by firing at and hitting it) // Note, this doesn't necessarily mean the robot JUST got hit, only that that is the most recent thing that happened. { { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}, { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN} } }; ubyte john_cheats_2[2*JOHN_CHEATS_SIZE_2] = { KEY_P ^ 0x00 ^ 0x43, 0x66, KEY_O ^ 0x10 ^ 0x43, 0x11, KEY_R ^ 0x20 ^ 0x43, 0x8, KEY_G ^ 0x30 ^ 0x43, 0x2, KEY_Y ^ 0x40 ^ 0x43, 0x0, KEY_S ^ 0x50 ^ 0x43 }; // --------------------------------------------------------- // On entry, N_robot_types had darn sure better be set. // Mallocs N_robot_types robot_info structs into global Robot_info. void init_ai_system(void) { #if 0 int i; mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info))); Robot_info = (robot_info *) malloc( N_robot_types * sizeof(*Robot_info) ); mprintf((0, "Robot_info = %i\n", Robot_info)); for (i=0; ictype.ai_info; ai_local *ailp = &Ai_local_info[objnum]; #ifdef DEST_SAT if (!(Game_mode & GM_MULTI) && Robot_info[objp->id].boss_flag) { mprintf((0, "Current_level_num = %i, Last_level = %i\n", Current_level_num, Last_level)); if (Current_level_num != Last_level) { mprintf((0, "Removing boss, object num = %i\n", objnum)); objp->id = 0; objp->flags |= OF_SHOULD_BE_DEAD; } } #endif if (behavior == 0) { // mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum)); behavior = AIB_NORMAL; objp->ctype.ai_info.behavior = behavior; } // mprintf((0, "Initializing object #%i\n", objnum)); // mode is now set from the Robot dialog, so this should get overwritten. ailp->mode = AIM_STILL; ailp->previous_visibility = 0; if (behavior != -1) { aip->behavior = behavior; ailp->mode = ai_behavior_to_mode(aip->behavior); } else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) { mprintf((0, "[obj %i -> normal] ", objnum)); aip->behavior = AIB_NORMAL; } // This is astonishingly stupid! This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs; vm_vec_zero(&objp->mtype.phys_info.velocity); // -- ailp->wait_time = F1_0*5; ailp->player_awareness_time = 0; ailp->player_awareness_type = 0; aip->GOAL_STATE = AIS_SRCH; aip->CURRENT_STATE = AIS_REST; ailp->time_player_seen = GameTime; ailp->next_misc_sound_time = GameTime; ailp->time_player_sound_attacked = GameTime; if ((behavior == AIB_HIDE) || (behavior == AIB_FOLLOW_PATH) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM)) { aip->hide_segment = hide_segment; ailp->goal_segment = hide_segment; aip->hide_index = -1; // This means the path has not yet been created. aip->cur_path_index = 0; } aip->SKIP_AI_COUNT = 0; if (Robot_info[objp->id].cloak_type == RI_CLOAKED_ALWAYS) aip->CLOAKED = 1; else aip->CLOAKED = 0; objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL); aip->REMOTE_OWNER = -1; } void john_cheat_func_2(int key) { if (!Cheats_enabled) return; if (key == (john_cheats_2[2*john_cheats_index_2] ^ (john_cheats_index_2 << 4) ^ 0x43)) { john_cheats_index_2++; if (john_cheats_index_2 == JOHN_CHEATS_SIZE_2) { Laser_rapid_fire = 0xBADA55; do_megawow_powerup(200); john_cheats_index_2 = 0; digi_play_sample( SOUND_CHEATER, F1_0); } } else john_cheats_index_2 = 0; } // --------------------------------------------------------------------------------------------------------------------- void init_ai_objects(void) { int i; Point_segs_free_ptr = Point_segs; for (i=0; icontrol_type == CT_AI) init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment); } init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1); #ifndef SHAREWARE init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0); #endif Boss_dying_sound_playing = 0; Boss_dying = 0; Boss_been_hit = 0; #ifndef SHAREWARE Gate_interval = F1_0*5 - Difficulty_level*F1_0/2; #endif Ai_initialized = 1; } int Lunacy = 0; int Diff_save = 1; fix Firing_wait_copy[MAX_ROBOT_TYPES]; byte Rapidfire_count_copy[MAX_ROBOT_TYPES]; void do_lunacy_on(void) { int i; if ( !Lunacy ) { Lunacy = 1; Diff_save = Difficulty_level; Difficulty_level = NDL-1; for (i=0; iid == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) { physics_turn_towards_vector(goal_vector, objp, rate); return; } new_fvec = *goal_vector; dot = vm_vec_dot(goal_vector, &objp->orient.fvec); if (dot < (F1_0 - FrameTime/2)) { fix mag; fix new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate); vm_vec_scale(&new_fvec, new_scale); vm_vec_add2(&new_fvec, &objp->orient.fvec); mag = vm_vec_normalize_quick(&new_fvec); if (mag < F1_0/256) { mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag))); new_fvec = *goal_vector; // if degenerate vector, go right to goal } } // // Every 8th time, do a correct matrix create, 7/8 time, do a quick one. // if (d_rand() < 0x1000) vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec); // else // vm_vector_2_matrix_norm(&objp->orient, &new_fvec, NULL, &objp->orient.rvec); //--{ //--vms_vector tvec; //--fix mag; //--tvec = objp->orient.fvec; //--mag = vm_vec_mag(&tvec); //--mprintf((0, "mags = %7.3f ", f2fl(mag))); //-- //--tvec = objp->orient.uvec; //--mag = vm_vec_mag(&tvec); //--mprintf((0, "%7.3f ", f2fl(mag))); //-- //--tvec = objp->orient.rvec; //--mag = vm_vec_mag(&tvec); //--mprintf((0, "%7.3f\n", f2fl(mag))); //--} //--simpler, but buggy: // The cross product of the forward vector with the right vector is the up vector //--simpler, but buggy: vm_vec_cross(&new_uvec, &new_fvec, &objp->orient.rvec); //--simpler, but buggy: vm_vec_cross(&new_rvec, &new_uvec, &new_fvec); //--simpler, but buggy: //--simpler, but buggy: objp->orient.fvec = new_fvec; //--simpler, but buggy: objp->orient.rvec = new_rvec; //--simpler, but buggy: objp->orient.uvec = new_uvec; } // -------------------------------------------------------------------------------------------------------------------- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility) { vms_vector curvec; // Random turning looks too stupid, so 1/4 of time, cheat. if (previous_visibility) if (d_rand() > 0x7400) { ai_turn_towards_vector(vec_to_player, obj, rate); return; } //--debug-- if (d_rand() > 0x6000) //--debug-- Prevented_turns++; curvec = obj->mtype.phys_info.rotvel; curvec.y += F1_0/64; curvec.x += curvec.y/6; curvec.y += curvec.z/4; curvec.z += curvec.x/10; if (abs(curvec.x) > F1_0/8) curvec.x /= 4; if (abs(curvec.y) > F1_0/8) curvec.y /= 4; if (abs(curvec.z) > F1_0/8) curvec.z /= 4; obj->mtype.phys_info.rotvel = curvec; } // Overall_agitation affects: // Widens field of view. Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees). // Overall_agitation/128 subtracted from field of view, making robots see wider. // Increases distance to which robot will search to create path to player by Overall_agitation/8 segments. // Decreases wait between fire times by Overall_agitation/64 seconds. void john_cheat_func_4(int key) { if (!Cheats_enabled) return; switch (john_cheats_index_4) { case 3: if (key == KEY_T) john_cheats_index_4++; else john_cheats_index_4 = 0; break; case 1: if (key == KEY_L) john_cheats_index_4++; else john_cheats_index_4 = 0; break; case 2: if (key == KEY_E) john_cheats_index_4++; else john_cheats_index_4 = 0; break; case 0: if (key == KEY_P) john_cheats_index_4++; break; case 4: if (key == KEY_C) john_cheats_index_4++; else john_cheats_index_4 = 0; break; case 5: if (key == KEY_H) john_cheats_index_4++; else john_cheats_index_4 = 0; break; case 6: Ugly_robot_texture = 0; case 7: case 8: if ((key >= KEY_1) && (key <= KEY_0)) { john_cheats_index_4++; Ugly_robot_texture *= 10; if (key != KEY_0) Ugly_robot_texture += key - 1; if (john_cheats_index_4 == 9) { if (Ugly_robot_texture == 999) { Ugly_robot_cheat = 0; hud_message(MSGC_GAME_FEEDBACK, TXT_ROBOT_PAINTING_OFF ); } else { hud_message(MSGC_GAME_FEEDBACK, TXT_ROBOT_PAINTING_ON, Ugly_robot_texture ); Ugly_robot_cheat = 0xBADA55; } mprintf((0, "Paint value = %i\n", Ugly_robot_texture)); john_cheats_index_4 = 0; } } else john_cheats_index_4 = 0; break; default: john_cheats_index_4 = 0; } } // -------------------------------------------------------------------------------------------------------------------- // Returns: // 0 Player is not visible from object, obstruction or something. // 1 Player is visible, but not in field of view. // 2 Player is visible and in field of view. // Note: Uses Believed_player_pos as player's position for cloak effect. // NOTE: Will destructively modify *pos if *pos is outside the mine. int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player) { fix dot; fvi_query fq; fq.p0 = pos; if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) { int segnum = find_point_seg(pos, objp->segnum); if (segnum == -1) { fq.startseg = objp->segnum; *pos = objp->pos; mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", objp-Objects)); move_towards_segment_center(objp); } else fq.startseg = segnum; } else fq.startseg = objp->segnum; fq.p1 = &Believed_player_pos; fq.rad = F1_0/4; fq.thisobjnum = objp-Objects; fq.ignore_obj_list = NULL; fq.flags = FQ_TRANSWALL | FQ_CHECK_OBJS; //what about trans walls??? Hit_type = find_vector_intersection(&fq,&Hit_data); Hit_pos = Hit_data.hit_pnt; Hit_seg = Hit_data.hit_seg; if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) { dot = vm_vec_dot(vec_to_player, &objp->orient.fvec); // mprintf((0, "Fvec = [%5.2f %5.2f %5.2f], vec_to_player = [%5.2f %5.2f %5.2f], dot = %7.3f\n", f2fl(objp->orient.fvec.x), f2fl(objp->orient.fvec.y), f2fl(objp->orient.fvec.z), f2fl(vec_to_player->x), f2fl(vec_to_player->y), f2fl(vec_to_player->z), f2fl(dot))); if (dot > field_of_view - (Overall_agitation << 9)) { // mprintf((0, "I can see you!\n")); return 2; } else { // mprintf((0, "Damn, I could see you if I were looking...\n")); return 1; } } else { // mprintf((0, " ** Where are you? **\n")); return 0; } } // ------------------------------------------------------------------------------------------------------------------ // Return 1 if animates, else return 0 int do_silly_animation(object *objp) { int objnum = objp-Objects; jointpos *jp_list; int robot_type, gun_num, robot_state, num_joint_positions; polyobj_info *pobj_info = &objp->rtype.pobj_info; ai_static *aip = &objp->ctype.ai_info; // ai_local *ailp = &Ai_local_info[objnum]; int num_guns, at_goal; int attack_type; int flinch_attack_scale = 1; robot_type = objp->id; num_guns = Robot_info[robot_type].n_guns; attack_type = Robot_info[robot_type].attack_type; if (num_guns == 0) { // mprintf((0, "Object #%i of type #%i has 0 guns.\n", objp-Objects, robot_type)); return 0; } // This is a hack. All positions should be based on goal_state, not GOAL_STATE. robot_state = Mike_to_matt_xlate[aip->GOAL_STATE]; // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE]; if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL))) flinch_attack_scale = Attack_scale; else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL)) flinch_attack_scale = Flinch_scale; at_goal = 1; for (gun_num=0; gun_num <= num_guns; gun_num++) { int joint; num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state); for (joint=0; jointanim_angles[jointnum]; if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) { //Int3(); // Contact Mike: incompatible data, illegal jointnum, problem in pof file? continue; } if (jp->p != pobjp->p) { if (gun_num == 0) at_goal = 0; Ai_local_info[objnum].goal_angles[jointnum].p = jp->p; delta_angle = jp->p - pobjp->p; if (delta_angle >= F1_0/2) delta_2 = -ANIM_RATE; else if (delta_angle >= 0) delta_2 = ANIM_RATE; else if (delta_angle >= -F1_0/2) delta_2 = -ANIM_RATE; else delta_2 = ANIM_RATE; if (flinch_attack_scale != 1) delta_2 *= flinch_attack_scale; Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE; // complete revolutions per second } if (jp->b != pobjp->b) { if (gun_num == 0) at_goal = 0; Ai_local_info[objnum].goal_angles[jointnum].b = jp->b; delta_angle = jp->b - pobjp->b; if (delta_angle >= F1_0/2) delta_2 = -ANIM_RATE; else if (delta_angle >= 0) delta_2 = ANIM_RATE; else if (delta_angle >= -F1_0/2) delta_2 = -ANIM_RATE; else delta_2 = ANIM_RATE; if (flinch_attack_scale != 1) delta_2 *= flinch_attack_scale; Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE; // complete revolutions per second } if (jp->h != pobjp->h) { if (gun_num == 0) at_goal = 0; Ai_local_info[objnum].goal_angles[jointnum].h = jp->h; delta_angle = jp->h - pobjp->h; if (delta_angle >= F1_0/2) delta_2 = -ANIM_RATE; else if (delta_angle >= 0) delta_2 = ANIM_RATE; else if (delta_angle >= -F1_0/2) delta_2 = -ANIM_RATE; else delta_2 = ANIM_RATE; if (flinch_attack_scale != 1) delta_2 *= flinch_attack_scale; Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE; // complete revolutions per second } } if (at_goal) { //ai_static *aip = &objp->ctype.ai_info; ai_local *ailp = &Ai_local_info[objp-Objects]; ailp->achieved_state[gun_num] = ailp->goal_state[gun_num]; if (ailp->achieved_state[gun_num] == AIS_RECO) ailp->goal_state[gun_num] = AIS_FIRE; if (ailp->achieved_state[gun_num] == AIS_FLIN) ailp->goal_state[gun_num] = AIS_LOCK; } } if (at_goal == 1) //num_guns) aip->CURRENT_STATE = aip->GOAL_STATE; return 1; } // ------------------------------------------------------------------------------------------ // Move all sub-objects in an object towards their goals. // Current orientation of object is at: pobj_info.anim_angles // Goal orientation of object is at: ai_info.goal_angles // Delta orientation of object is at: ai_info.delta_angles void ai_frame_animation(object *objp) { int objnum = objp-Objects; int joint; int num_joints; num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models; for (joint=1; jointrtype.pobj_info.anim_angles[joint]; vms_angvec *goalangp = &Ai_local_info[objnum].goal_angles[joint]; vms_angvec *deltaangp = &Ai_local_info[objnum].delta_angles[joint]; #ifndef NDEBUG if (Ai_animation_test) { printf("%i: [%7.3f %7.3f %7.3f] [%7.3f %7.3f %7.3f]\n", joint, f2fl(curangp->p), f2fl(curangp->b), f2fl(curangp->h), f2fl(goalangp->p), f2fl(goalangp->b), f2fl(goalangp->h)); } #endif delta_to_goal = goalangp->p - curangp->p; if (delta_to_goal > 32767) delta_to_goal = delta_to_goal - 65536; else if (delta_to_goal < -32767) delta_to_goal = 65536 + delta_to_goal; if (delta_to_goal) { scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE; curangp->p += scaled_delta_angle; if (abs(delta_to_goal) < abs(scaled_delta_angle)) curangp->p = goalangp->p; } delta_to_goal = goalangp->b - curangp->b; if (delta_to_goal > 32767) delta_to_goal = delta_to_goal - 65536; else if (delta_to_goal < -32767) delta_to_goal = 65536 + delta_to_goal; if (delta_to_goal) { scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE; curangp->b += scaled_delta_angle; if (abs(delta_to_goal) < abs(scaled_delta_angle)) curangp->b = goalangp->b; } delta_to_goal = goalangp->h - curangp->h; if (delta_to_goal > 32767) delta_to_goal = delta_to_goal - 65536; else if (delta_to_goal < -32767) delta_to_goal = 65536 + delta_to_goal; if (delta_to_goal) { scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE; curangp->h += scaled_delta_angle; if (abs(delta_to_goal) < abs(scaled_delta_angle)) curangp->h = goalangp->h; } } } // ---------------------------------------------------------------------------------- void set_next_fire_time(ai_local *ailp, robot_info *robptr) { ailp->rapidfire_count++; if (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level]) { ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2); } else { ailp->rapidfire_count = 0; ailp->next_fire = robptr->firing_wait[Difficulty_level]; } } // ---------------------------------------------------------------------------------- // When some robots collide with the player, they attack. // If player is cloaked, then robot probably didn't actually collide, deal with that here. void do_ai_robot_hit_attack(object *robot, object *player, vms_vector *collision_point) { ai_local *ailp = &Ai_local_info[robot-Objects]; robot_info *robptr = &Robot_info[robot->id]; //#ifndef NDEBUG if (!Robot_firing_enabled) return; //#endif // If player is dead, stop firing. if (Objects[Players[Player_num].objnum].type == OBJ_GHOST) return; if (robptr->attack_type == 1) { if (ailp->next_fire <= 0) { if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2) collide_player_and_nasty_robot( player, robot, collision_point ); robot->ctype.ai_info.GOAL_STATE = AIS_RECO; set_next_fire_time(ailp, robptr); } } } extern int Player_exploded; // -------------------------------------------------------------------------------------------------------------------- // Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the // center of the robot will not fire right at the player. We need to aim the guns at the player. Barring that, we cheat. // When this routine is complete, the parameter vec_to_player should not be necessary. void ai_fire_laser_at_player(object *obj, vms_vector *fire_point) { int objnum = obj-Objects; ai_local *ailp = &Ai_local_info[objnum]; robot_info *robptr = &Robot_info[obj->id]; vms_vector fire_vec; vms_vector bpp_diff; if (!Robot_firing_enabled) return; #ifndef NDEBUG // We should never be coming here for the green guy, as he has no laser! if (robptr->attack_type == 1) Int3(); // Contact Mike: This is impossible. #endif if (obj->control_type == CT_MORPH) return; // If player is exploded, stop firing. if (Player_exploded) return; // If player is cloaked, maybe don't fire based on how long cloaked and randomness. if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) { fix cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time; if (GameTime - cloak_time > CLOAK_TIME_MAX/4) if (d_rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) { set_next_fire_time(ailp, robptr); return; } } //-- // Find segment containing laser fire position. If the robot is straddling a segment, the position from //-- // which it fires may be in a different segment, which is bad news for find_vector_intersection. So, cast //-- // a ray from the object center (whose segment we know) to the laser position. Then, in the call to Laser_create_new //-- // use the data returned from this call to find_vector_intersection. //-- // Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is //-- // in the same segment as the source point. //-- //-- fq.p0 = &obj->pos; //-- fq.startseg = obj->segnum; //-- fq.p1 = fire_point; //-- fq.rad = 0; //-- fq.thisobjnum = obj-Objects; //-- fq.ignore_obj_list = NULL; //-- fq.flags = FQ_TRANSWALL | FQ_CHECK_OBJS; //what about trans walls??? //-- //-- fate = find_vector_intersection(&fq, &hit_data); //-- if (fate != HIT_NONE) //-- return; // Set position to fire at based on difficulty level. bpp_diff.x = Believed_player_pos.x + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4; bpp_diff.y = Believed_player_pos.y + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4; bpp_diff.z = Believed_player_pos.z + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4; // Half the time fire at the player, half the time lead the player. if (d_rand() > 16384) { vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point); } else { vms_vector player_direction_vector; vm_vec_sub(&player_direction_vector, &bpp_diff, &bpp_diff); // If player is not moving, fire right at him! // Note: If the robot fires in the direction of its forward vector, this is bad because the weapon does not // come out from the center of the robot; it comes out from the side. So it is common for the weapon to miss // its target. Ideally, we want to point the guns at the player. For now, just fire right at the player. if ((abs(player_direction_vector.x < 0x10000)) && (abs(player_direction_vector.y < 0x10000)) && (abs(player_direction_vector.z < 0x10000))) { vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point); // Player is moving. Determine where the player will be at the end of the next frame if he doesn't change his // behavior. Fire at exactly that point. This isn't exactly what you want because it will probably take the laser // a different amount of time to get there, since it will probably be a different distance from the player. // So, that's why we write games, instead of guiding missiles... } else { vm_vec_sub(&fire_vec, &bpp_diff, fire_point); vm_vec_scale(&fire_vec,fixmul(Weapon_info[Robot_info[obj->id].weapon_type].speed[Difficulty_level], FrameTime)); vm_vec_add2(&fire_vec, &player_direction_vector); vm_vec_normalize_quick(&fire_vec); } } //#ifndef NDEBUG // if (robptr->boss_flag) // mprintf((0, "Boss (%i) fires!\n", obj-Objects)); //#endif Laser_create_new_easy( &fire_vec, fire_point, obj-Objects, robptr->weapon_type, 1); #ifndef SHAREWARE #ifdef NETWORK if (Game_mode & GM_MULTI) { ai_multi_send_robot_position(objnum, -1); multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec); } #endif #endif create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED); set_next_fire_time(ailp, robptr); // If the boss fired, allow him to teleport very soon (right after firing, cool!), pending other factors. if (robptr->boss_flag) Last_teleport_time -= Boss_teleport_interval/2; } // -------------------------------------------------------------------------------------------------------------------- // vec_goal must be normalized, or close to it. void move_towards_vector(object *objp, vms_vector *vec_goal) { physics_info *pptr = &objp->mtype.phys_info; fix speed, dot, max_speed; robot_info *robptr = &Robot_info[objp->id]; vms_vector vel; // Trying to move towards player. If forward vector much different than velocity vector, // bash velocity vector twice as much towards player as usual. vel = pptr->velocity; vm_vec_normalize_quick(&vel); dot = vm_vec_dot(&vel, &objp->orient.fvec); if (dot < 3*F1_0/4) { // This funny code is supposed to slow down the robot and move his velocity towards his direction // more quickly than the general code //-! mprintf((0, "Th ")); pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32); pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32); pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32); } else { //-! mprintf((0, "Tn ")); pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4; pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4; pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4; } speed = vm_vec_mag_quick(&pptr->velocity); max_speed = robptr->max_speed[Difficulty_level]; // Green guy attacks twice as fast as he moves away. if (robptr->attack_type == 1) max_speed *= 2; if (speed > max_speed) { pptr->velocity.x = (pptr->velocity.x*3)/4; pptr->velocity.y = (pptr->velocity.y*3)/4; pptr->velocity.z = (pptr->velocity.z*3)/4; } } // -------------------------------------------------------------------------------------------------------------------- void move_towards_player(object *objp, vms_vector *vec_to_player) // vec_to_player must be normalized, or close to it. { move_towards_vector(objp, vec_to_player); } // -------------------------------------------------------------------------------------------------------------------- // I am ashamed of this: fast_flag == -1 means normal slide about. fast_flag = 0 means no evasion. void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag) { physics_info *pptr = &objp->mtype.phys_info; fix speed; robot_info *robptr = &Robot_info[objp->id]; int objnum = objp-Objects; int dir; int dir_change; fix ft; vms_vector evade_vector; int count=0; if (fast_flag == 0) return; dir_change = 48; ft = FrameTime; if (ft < F1_0/32) { dir_change *= 8; count += 3; } else while (ft < F1_0/4) { dir_change *= 2; ft *= 2; count++; } dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change; dir >>= (4+count); Assert((dir >= 0) && (dir <= 3)); switch (dir) { case 0: evade_vector.x = fixmul(vec_to_player->z, FrameTime*32); evade_vector.y = fixmul(vec_to_player->y, FrameTime*32); evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32); break; case 1: evade_vector.x = fixmul(-vec_to_player->z, FrameTime*32); evade_vector.y = fixmul(vec_to_player->y, FrameTime*32); evade_vector.z = fixmul(vec_to_player->x, FrameTime*32); break; case 2: evade_vector.x = fixmul(-vec_to_player->y, FrameTime*32); evade_vector.y = fixmul(vec_to_player->x, FrameTime*32); evade_vector.z = fixmul(vec_to_player->z, FrameTime*32); break; case 3: evade_vector.x = fixmul(vec_to_player->y, FrameTime*32); evade_vector.y = fixmul(-vec_to_player->x, FrameTime*32); evade_vector.z = fixmul(vec_to_player->z, FrameTime*32); break; } // Note: -1 means normal circling about the player. > 0 means fast evasion. if (fast_flag > 0) { fix dot; // Only take evasive action if looking at player. // Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively. dot = vm_vec_dot(vec_to_player, &objp->orient.fvec); if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) { fix damage_scale; damage_scale = fixdiv(objp->shields, robptr->strength); if (damage_scale > F1_0) damage_scale = F1_0; // Just in case... else if (damage_scale < 0) damage_scale = 0; // Just in case... vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale); } } pptr->velocity.x += evade_vector.x; pptr->velocity.y += evade_vector.y; pptr->velocity.z += evade_vector.z; speed = vm_vec_mag_quick(&pptr->velocity); if (speed > robptr->max_speed[Difficulty_level]) { pptr->velocity.x = (pptr->velocity.x*3)/4; pptr->velocity.y = (pptr->velocity.y*3)/4; pptr->velocity.z = (pptr->velocity.z*3)/4; } } // -------------------------------------------------------------------------------------------------------------------- void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type) { fix speed; physics_info *pptr = &objp->mtype.phys_info; robot_info *robptr = &Robot_info[objp->id]; int objref; pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16); pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16); pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16); if (attack_type) { // Get value in 0..3 to choose evasion direction. objref = ((objp-Objects) ^ ((FrameCount + 3*(objp-Objects)) >> 5)) & 3; switch (objref) { case 0: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5); break; case 1: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5); break; case 2: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5); break; case 3: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5); break; default: Int3(); // Impossible, bogus value on objref, must be in 0..3 } } speed = vm_vec_mag_quick(&pptr->velocity); if (speed > robptr->max_speed[Difficulty_level]) { pptr->velocity.x = (pptr->velocity.x*3)/4; pptr->velocity.y = (pptr->velocity.y*3)/4; pptr->velocity.z = (pptr->velocity.z*3)/4; } //--old-- fix speed, dot; //--old-- physics_info *pptr = &objp->mtype.phys_info; //--old-- robot_info *robptr = &Robot_info[objp->id]; //--old-- //--old-- // Trying to move away from player. If forward vector much different than velocity vector, //--old-- // bash velocity vector twice as much away from player as usual. //--old-- dot = vm_vec_dot(&pptr->velocity, &objp->orient.fvec); //--old-- if (dot > -3*F1_0/4) { //--old-- // This funny code is supposed to slow down the robot and move his velocity towards his direction //--old-- // more quickly than the general code //--old-- pptr->velocity.x = pptr->velocity.x/2 - fixmul(vec_to_player->x, FrameTime*16); //--old-- pptr->velocity.y = pptr->velocity.y/2 - fixmul(vec_to_player->y, FrameTime*16); //--old-- pptr->velocity.z = pptr->velocity.z/2 - fixmul(vec_to_player->z, FrameTime*16); //--old-- } else { //--old-- pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16); //--old-- pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16); //--old-- pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16); //--old-- } //--old-- //--old-- speed = vm_vec_mag_quick(&pptr->velocity); //--old-- //--old-- if (speed > robptr->max_speed[Difficulty_level]) { //--old-- pptr->velocity.x = (pptr->velocity.x*3)/4; //--old-- pptr->velocity.y = (pptr->velocity.y*3)/4; //--old-- pptr->velocity.z = (pptr->velocity.z*3)/4; //--old-- } } // -------------------------------------------------------------------------------------------------------------------- // Move towards, away_from or around player. // Also deals with evasion. // If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL). void ai_move_relative_to_player(object *objp, ai_local *ailp, fix dist_to_player, vms_vector *vec_to_player, fix circle_distance, int evade_only) { object *dobjp; robot_info *robptr = &Robot_info[objp->id]; // See if should take avoidance. // New way, green guys don't evade: if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) { if (objp->ctype.ai_info.danger_laser_num != -1) { dobjp = &Objects[objp->ctype.ai_info.danger_laser_num]; if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) { fix dot, dist_to_laser, field_of_view; vms_vector vec_to_laser, laser_fvec; field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level]; vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos); dist_to_laser = vm_vec_normalize_quick(&vec_to_laser); dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec); if (dot > field_of_view) { fix laser_robot_dot; vms_vector laser_vec_to_robot; // The laser is seen by the robot, see if it might hit the robot. // Get the laser's direction. If it's a polyobj, it can be gotten cheaply from the orientation matrix. if (dobjp->render_type == RT_POLYOBJ) laser_fvec = dobjp->orient.fvec; else { // Not a polyobj, get velocity and normalize. laser_fvec = dobjp->mtype.phys_info.velocity; //dobjp->orient.fvec; vm_vec_normalize_quick(&laser_fvec); } vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos); vm_vec_normalize_quick(&laser_vec_to_robot); laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot); if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) { int evade_speed; ai_evaded = 1; evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level]; move_around_player(objp, vec_to_player, evade_speed); } } return; } } // If only allowed to do evade code, then done. // Hmm, perhaps brilliant insight. If want claw-type guys to keep coming, don't return here after evasion. if ((!robptr->attack_type) && evade_only) return; // If we fall out of above, then no object to be avoided. objp->ctype.ai_info.danger_laser_num = -1; // Green guy selects move around/towards/away based on firing time, not distance. if (robptr->attack_type == 1) { if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) { // 1/4 of time, move around player, 3/4 of time, move away from player if (d_rand() < 8192) { move_around_player(objp, vec_to_player, -1); } else { move_away_from_player(objp, vec_to_player, 1); } } else { move_towards_player(objp, vec_to_player); } } else { if (dist_to_player < circle_distance) move_away_from_player(objp, vec_to_player, 0); else if (dist_to_player < circle_distance*2) move_around_player(objp, vec_to_player, -1); else move_towards_player(objp, vec_to_player); } } // -------------------------------------------------------------------------------------------------------------------- // Compute a somewhat random, normalized vector. void make_random_vector(vms_vector *vec) { vec->x = (d_rand() - 16384) | 1; // make sure we don't create null vector vec->y = d_rand() - 16384; vec->z = d_rand() - 16384; vm_vec_normalize_quick(vec); } #ifndef NDEBUG void mprintf_animation_info(object *objp) { ai_static *aip = &objp->ctype.ai_info; ai_local *ailp = &Ai_local_info[objp-Objects]; if (!Ai_info_enabled) return; mprintf((0, "Goal = ")); switch (aip->GOAL_STATE) { case AIS_NONE: mprintf((0, "NONE ")); break; case AIS_REST: mprintf((0, "REST ")); break; case AIS_SRCH: mprintf((0, "SRCH ")); break; case AIS_LOCK: mprintf((0, "LOCK ")); break; case AIS_FLIN: mprintf((0, "FLIN ")); break; case AIS_FIRE: mprintf((0, "FIRE ")); break; case AIS_RECO: mprintf((0, "RECO ")); break; case AIS_ERR_: mprintf((0, "ERR_ ")); break; } mprintf((0, " Cur = ")); switch (aip->CURRENT_STATE) { case AIS_NONE: mprintf((0, "NONE ")); break; case AIS_REST: mprintf((0, "REST ")); break; case AIS_SRCH: mprintf((0, "SRCH ")); break; case AIS_LOCK: mprintf((0, "LOCK ")); break; case AIS_FLIN: mprintf((0, "FLIN ")); break; case AIS_FIRE: mprintf((0, "FIRE ")); break; case AIS_RECO: mprintf((0, "RECO ")); break; case AIS_ERR_: mprintf((0, "ERR_ ")); break; } mprintf((0, " Aware = ")); switch (ailp->player_awareness_type) { case AIE_FIRE: mprintf((0, "FIRE ")); break; case AIE_HITT: mprintf((0, "HITT ")); break; case AIE_COLL: mprintf((0, "COLL ")); break; case AIE_HURT: mprintf((0, "HURT ")); break; } mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time))); } #endif // ------------------------------------------------------------------------------------------------------------------- int Break_on_object = -1; void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player) { //mprintf((0, "!")); if (player_visibility >= 1) { // Now, if in robot's field of view, lock onto player fix dot = vm_vec_dot(&obj->orient.fvec, vec_to_player); //mprintf((0, "dot = %8x ", dot)); if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) { ai_static *aip = &obj->ctype.ai_info; ai_local *ailp = &Ai_local_info[obj-Objects]; switch (aip->GOAL_STATE) { case AIS_NONE: case AIS_REST: case AIS_SRCH: case AIS_LOCK: aip->GOAL_STATE = AIS_FIRE; if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) { ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED; ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME; } break; } } else if (dot >= F1_0/2) { ai_static *aip = &obj->ctype.ai_info; switch (aip->GOAL_STATE) { case AIS_NONE: case AIS_REST: case AIS_SRCH: aip->GOAL_STATE = AIS_LOCK; break; } } } } // -------------------------------------------------------------------------------------------------------------------- // If a hiding robot gets bumped or hit, he decides to find another hiding place. void do_ai_robot_hit(object *objp, int type) { if (objp->control_type == CT_AI) { if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION)) switch (objp->ctype.ai_info.behavior) { case AIM_HIDE: objp->ctype.ai_info.SUBMODE = AISM_GOHIDE; break; case AIM_STILL: Ai_local_info[objp-Objects].mode = AIM_CHASE_OBJECT; break; } } } #ifndef NDEBUG int Do_ai_flag=1; int Cvv_test=0; int Cvv_last_time[MAX_OBJECTS]; int Gun_point_hack=0; #endif #define CHASE_TIME_LENGTH (F1_0*8) #define DEFAULT_ROBOT_SOUND_VOLUME F1_0 int Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME; // -------------------------------------------------------------------------------------------------------------------- // Note: This function could be optimized. Surely player_is_visible_from_object would benefit from the // information of a normalized vec_to_player. // Return player visibility: // 0 not visible // 1 visible, but robot not looking at player (ie, on an unobstructed vector) // 2 visible and in robot's field of view // -1 player is cloaked // If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position. // Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged // and is copied to player_visibility void compute_vis_and_vec(object *objp, vms_vector *pos, ai_local *ailp, vms_vector *vec_to_player, int *player_visibility, robot_info *robptr, int *flag) { if (!*flag) { if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) { fix delta_time, dist; int cloak_index = (objp-Objects) % MAX_AI_CLOAK_INFO; delta_time = GameTime - Ai_cloak_info[cloak_index].last_time; if (delta_time > F1_0*2) { vms_vector randvec; Ai_cloak_info[cloak_index].last_time = GameTime; make_random_vector(&randvec); vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time ); } dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos); *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player); // *player_visibility = 2; if ((ailp->next_misc_sound_time < GameTime) && (ailp->next_fire < F1_0) && (dist < F1_0*20)) { mprintf((0, "ANGRY! ")); ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 1; digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume); } } else { // Compute expensive stuff -- vec_to_player and player_visibility vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos); if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) { mprintf((0, "Warning: Player and robot at exactly the same location.\n")); vec_to_player->x = F1_0; } *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player); // This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they // see you without killing frame rate. { ai_static *aip = &objp->ctype.ai_info; if ((*player_visibility == 2) && (ailp->previous_visibility != 2)) if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) { aip->GOAL_STATE = AIS_FIRE; aip->CURRENT_STATE = AIS_FIRE; } } if (!Player_exploded && (ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) { if (ailp->previous_visibility == 0) { if (ailp->time_player_seen + F1_0/2 < GameTime) { // mprintf((0, "SEE! ")); digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume); ailp->time_player_sound_attacked = GameTime; ailp->next_misc_sound_time = GameTime + F1_0 + d_rand()*4; } } else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) { // mprintf((0, "ANGRY! ")); digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume); ailp->time_player_sound_attacked = GameTime; } } if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) { // mprintf((0, "ATTACK! ")); ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 2; digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume); } ailp->previous_visibility = *player_visibility; } *flag = 1; if (*player_visibility) { ailp->time_player_seen = GameTime; } } } // -------------------------------------------------------------------------------------------------------------------- // Move the object objp to a spot in which it doesn't intersect a wall. // It might mean moving it outside its current segment. void move_object_to_legal_spot(object *objp) { vms_vector original_pos = objp->pos; int i; segment *segp = &Segments[objp->segnum]; for (i=0; ichildren[i]]); vm_vec_sub(&goal_dir, &segment_center, &objp->pos); dist_to_center = vm_vec_normalize_quick(&goal_dir); vm_vec_scale(&goal_dir, objp->size); vm_vec_add2(&objp->pos, &goal_dir); if (!object_intersects_wall(objp)) { int new_segnum = find_point_seg(&objp->pos, objp->segnum); if (new_segnum != -1) { obj_relink(objp-Objects, new_segnum); return; } } else objp->pos = original_pos; } } // Int3(); // Darn you John, you done it again! (But contact Mike) mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", objp-Objects)); apply_damage_to_robot(objp, objp->shields*2, objp-Objects); } // -------------------------------------------------------------------------------------------------------------------- // Move object one object radii from current position towards segment center. // If segment center is nearer than 2 radii, move it to center. void move_towards_segment_center(object *objp) { int segnum = objp->segnum; fix dist_to_center; vms_vector segment_center, goal_dir; compute_segment_center(&segment_center, &Segments[segnum]); vm_vec_sub(&goal_dir, &segment_center, &objp->pos); dist_to_center = vm_vec_normalize_quick(&goal_dir); if (dist_to_center < objp->size) { // Center is nearer than the distance we want to move, so move to center. objp->pos = segment_center; mprintf((0, "Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f)\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z))); if (object_intersects_wall(objp)) { mprintf((0, "Object #%i still illegal, trying trickier move.\n")); move_object_to_legal_spot(objp); } } else { int new_segnum; // Move one radii towards center. vm_vec_scale(&goal_dir, objp->size); vm_vec_add2(&objp->pos, &goal_dir); new_segnum = find_point_seg(&objp->pos, objp->segnum); if (new_segnum == -1) { objp->pos = segment_center; move_object_to_legal_spot(objp); } mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)))); } } // ----------------------------------------------------------------------------------------------------------- // Return true if door can be flown through by a suitable type robot. // Only brains and avoid robots can open doors. int ai_door_is_openable(object *objp, segment *segp, int sidenum) { int wall_num; // The mighty console object can open all doors (for purposes of determining paths). if (objp == ConsoleObject) { int wall_num = segp->sides[sidenum].wall_num; if (Walls[wall_num].type == WALL_DOOR) return 1; } if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM)) { wall_num = segp->sides[sidenum].wall_num; if (wall_num != -1) if ((Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys == KEY_NONE) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED)) return 1; } return 0; } //--// ----------------------------------------------------------------------------------------------------------- //--// Return true if object *objp is allowed to open door at wall_num //--int door_openable_by_robot(object *objp, int wall_num) //--{ //-- if (objp->id == ROBOT_BRAIN) //-- if (Walls[wall_num].keys == KEY_NONE) //-- return 1; //-- //-- return 0; //--} // ----------------------------------------------------------------------------------------------------------- // Return side of openable door in segment, if any. If none, return -1. int openable_doors_in_segment(object *objp) { int i; int segnum = objp->segnum; for (i=0; isegnum; //--unused-- //--unused-- for (i=0; i> 15; while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG)) sidenum = (d_rand() * 6) >> 15; segnum = segp->children[sidenum]; return segnum; } // -------------------------------------------------------------------------------------------------------------------- // Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp. int check_object_object_intersection(vms_vector *pos, fix size, segment *segp) { int curobjnum; // If this would intersect with another object (only check those in this segment), then try to move. curobjnum = segp->objects; while (curobjnum != -1) { object *curobjp = &Objects[curobjnum]; if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) { if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size) return 1; } curobjnum = curobjp->next; } return 0; } #ifndef SHAREWARE // -------------------------------------------------------------------------------------------------------------------- // Return true if object created, else return false. int create_gated_robot( int segnum, int object_id) { int objnum; object *objp; segment *segp = &Segments[segnum]; vms_vector object_pos; robot_info *robptr = &Robot_info[object_id]; int i, count=0; fix objsize = Polygon_models[robptr->model_num].rad; int default_behavior; for (i=0; i<=Highest_object_index; i++) if (Objects[i].type == OBJ_ROBOT) if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM) count++; if (count > 2*Difficulty_level + 3) { // mprintf((0, "Cannot gate in a robot until you kill one.\n")); Last_gate_time = GameTime - 3*Gate_interval/4; return 0; } compute_segment_center(&object_pos, segp); pick_random_point_in_seg(&object_pos, segp-Segments); // See if legal to place object here. If not, move about in segment and try again. if (check_object_object_intersection(&object_pos, objsize, segp)) { // mprintf((0, "Can't get in because object collides with something.\n")); Last_gate_time = GameTime - 3*Gate_interval/4; return 0; } objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ); if ( objnum < 0 ) { // mprintf((1, "Can't get object to gate in robot. Not gating in.\n")); Last_gate_time = GameTime - 3*Gate_interval/4; return 0; } mprintf((0, "Gating in object %i in segment %i\n", objnum, segp-Segments)); #ifdef NETWORK Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer #endif objp = &Objects[objnum]; //Set polygon-object-specific data objp->rtype.pobj_info.model_num = robptr->model_num; objp->rtype.pobj_info.subobj_flags = 0; //set Physics info objp->mtype.phys_info.mass = robptr->mass; objp->mtype.phys_info.drag = robptr->drag; objp->mtype.phys_info.flags |= (PF_LEVELLING); objp->shields = robptr->strength; objp->matcen_creator = BOSS_GATE_MATCEN_NUM; // flag this robot as having been created by the boss. default_behavior = AIB_NORMAL; if (object_id == 10) // This is a toaster guy! default_behavior = AIB_RUN_FROM; init_ai_object(objp-Objects, default_behavior, -1 ); // Note, -1 = segment this robot goes to to hide, should probably be something useful object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT ); digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0); morph_start(objp); Last_gate_time = GameTime; Players[Player_num].num_robots_level++; Players[Player_num].num_robots_total++; return 1; } // -------------------------------------------------------------------------------------------------------------------- // Make object objp gate in a robot. // The process of him bringing in a robot takes one second. // Then a robot appears somewhere near the player. // Return true if robot successfully created, else return false int gate_in_robot(int type, int segnum) { if (segnum < 0) segnum = Boss_gate_segs[(d_rand() * Num_boss_gate_segs) >> 15]; Assert((segnum >= 0) && (segnum <= Highest_segment_index)); return create_gated_robot(segnum, type); } #endif //// -------------------------------------------------------------------------------------------------------------------- //// Return true if some distance function of vertices implies segment is too small for object. //int segment_too_small_for_object(object *objp, segment *segp) //{ // int i; // fix threshold_distance; // // threshold_distance = objp->size*2; // // for (i=1; iverts[i]], &Vertices[segp->verts[i-1]]) < threshold_distance) { //#ifndef NDEBUG // fix dist = vm_vec_dist_quick(&Vertices[segp->verts[i]], &Vertices[segp->verts[i-1]]); //#endif // mprintf((0, "Seg %i too small for obj %i (sz=%7.3f), verts %i, %i only %7.3f apart\n", segp-Segments, objp-Objects, f2fl(objp->size), segp->verts[i], segp->verts[i-1], f2fl(dist))); // return 1; // } // // return 0; //} //--unused-- int Shown_all_segments=0; // -------------------------------------------------------------------------------------------------------------------- int boss_fits_in_seg(object *boss_objp, int segnum) { vms_vector segcenter; int boss_objnum = boss_objp-Objects; int posnum; compute_segment_center(&segcenter, &Segments[segnum]); for (posnum=0; posnum<9; posnum++) { if (posnum > 0) { vms_vector vertex_pos; Assert((posnum-1 >= 0) && (posnum-1 < 8)); vertex_pos = Vertices[Segments[segnum].verts[posnum-1]]; vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter); } else boss_objp->pos = segcenter; obj_relink(boss_objnum, segnum); if (!object_intersects_wall(boss_objp)) return 1; } return 0; } #define QUEUE_SIZE 256 // -------------------------------------------------------------------------------------------------------------------- // Create list of segments boss is allowed to teleport to at segptr. // Set *num_segs. // Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and // he can reach from his initial position (calls find_connected_distance). // If size_check is set, then only add segment if boss can fit in it, else any segment is legal. void init_boss_segments(short segptr[], int *num_segs, int size_check) { int boss_objnum=-1; int i; *num_segs = 0; #ifdef EDITOR N_selected_segs = 0; #endif // See if there is a boss. If not, quick out. for (i=0; i<=Highest_object_index; i++) if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) { Assert(boss_objnum == -1); // There are two bosses in this mine! i and boss_objnum! boss_objnum = i; } if (boss_objnum != -1) { int original_boss_seg; vms_vector original_boss_pos; object *boss_objp = &Objects[boss_objnum]; int head, tail; int seg_queue[QUEUE_SIZE]; //ALREADY IN RENDER.H byte visited[MAX_SEGMENTS]; fix boss_size_save; boss_size_save = boss_objp->size; boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size); original_boss_seg = boss_objp->segnum; original_boss_pos = boss_objp->pos; head = 0; tail = 0; seg_queue[head++] = original_boss_seg; segptr[(*num_segs)++] = original_boss_seg; #ifdef EDITOR Selected_segs[N_selected_segs++] = original_boss_seg; #endif for (i=0; i<=Highest_segment_index; i++) visited[i] = 0; while (tail != head) { int sidenum; segment *segp = &Segments[seg_queue[tail++]]; tail &= QUEUE_SIZE-1; for (sidenum=0; sidenumchildren[sidenum]] == 0) { seg_queue[head++] = segp->children[sidenum]; visited[segp->children[sidenum]] = 1; head &= QUEUE_SIZE-1; if (head > tail) { if (head == tail + QUEUE_SIZE-1) Int3(); // queue overflow. Make it bigger! } else if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1) Int3(); // queue overflow. Make it bigger! if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) { segptr[(*num_segs)++] = segp->children[sidenum]; #ifdef EDITOR Selected_segs[N_selected_segs++] = segp->children[sidenum]; #endif if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) { mprintf((1, "Warning: Too many boss teleport segments. Found %i after searching %i/%i segments.\n", MAX_BOSS_TELEPORT_SEGS, segp->children[sidenum], Highest_segment_index+1)); tail = head; } } } } } } boss_objp->size = boss_size_save; boss_objp->pos = original_boss_pos; obj_relink(boss_objnum, original_boss_seg); } } // -------------------------------------------------------------------------------------------------------------------- void teleport_boss(object *objp) { int rand_segnum; vms_vector boss_dir; int rand_seg; Assert(Num_boss_teleport_segs > 0); // Pick a random segment from the list of boss-teleportable-to segments. rand_seg = (d_rand() * Num_boss_teleport_segs) >> 15; rand_segnum = Boss_teleport_segs[rand_seg]; Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index)); #ifndef SHAREWARE #ifdef NETWORK if (Game_mode & GM_MULTI) multi_send_boss_actions(objp-Objects, 1, rand_seg, 0); #endif #endif compute_segment_center(&objp->pos, &Segments[rand_segnum]); obj_relink(objp-Objects, rand_segnum); Last_teleport_time = GameTime; // make boss point right at player vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos); vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL); digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0); digi_kill_sound_linked_to_object( objp-Objects); digi_link_sound_to_object2( SOUND_BOSS_SHARE_SEE, objp-Objects, 1, F1_0, F1_0*512 ); // F1_0*512 means play twice as loud #ifndef NDEBUG mprintf((0, "Boss teleported to segment %i\n", rand_segnum)); #endif // After a teleport, boss can fire right away. Ai_local_info[objp-Objects].next_fire = 0; } // ---------------------------------------------------------------------- void start_boss_death_sequence(object *objp) { if (Robot_info[objp->id].boss_flag) { Boss_dying = 1; Boss_dying_start_time = GameTime; } } // ---------------------------------------------------------------------- void do_boss_dying_frame(object *objp) { fix boss_roll_val, temp; boss_roll_val = fixdiv(GameTime - Boss_dying_start_time, BOSS_DEATH_DURATION); fix_sincos(fixmul(boss_roll_val, boss_roll_val), &temp, &objp->mtype.phys_info.rotvel.x); fix_sincos(boss_roll_val, &temp, &objp->mtype.phys_info.rotvel.y); fix_sincos(boss_roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z); objp->mtype.phys_info.rotvel.x = (GameTime - Boss_dying_start_time)/9; objp->mtype.phys_info.rotvel.y = (GameTime - Boss_dying_start_time)/5; objp->mtype.phys_info.rotvel.z = (GameTime - Boss_dying_start_time)/7; if (Boss_dying_start_time + BOSS_DEATH_DURATION - BOSS_DEATH_SOUND_DURATION < GameTime) { if (!Boss_dying_sound_playing) { mprintf((0, "Starting boss death sound!\n")); Boss_dying_sound_playing = 1; digi_link_sound_to_object2( SOUND_BOSS_SHARE_DIE, objp-Objects, 0, F1_0*4, F1_0*1024 ); // F1_0*512 means play twice as loud } else if (d_rand() < FrameTime*16) create_small_fireball_on_object(objp, (F1_0 + d_rand()) * 8, 0); } else if (d_rand() < FrameTime*8) create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * 8, 1); if (Boss_dying_start_time + BOSS_DEATH_DURATION < GameTime) { do_controlcen_destroyed_stuff(NULL); explode_object(objp, F1_0/4); digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512); } } #ifndef SHAREWARE #ifdef NETWORK // -------------------------------------------------------------------------------------------------------------------- // Called for an AI object if it is fairly aware of the player. // awareness_level is in 0..100. Larger numbers indicate greater awareness (eg, 99 if firing at player). // In a given frame, might not get called for an object, or might be called more than once. // The fact that this routine is not called for a given object does not mean that object is not interested in the player. // Objects are moved by physics, so they can move even if not interested in a player. However, if their velocity or // orientation is changing, this routine will be called. // Return value: // 0 this player IS NOT allowed to move this robot. // 1 this player IS allowed to move this robot. int ai_multiplayer_awareness(object *objp, int awareness_level) { int rval=1; #ifndef SHAREWARE if (Game_mode & GM_MULTI) { if (awareness_level == 0) return 0; rval = multi_can_move_robot(objp-Objects, awareness_level); } #endif return rval; } #else #define ai_multiplayer_awareness(a, b) 1 #endif #else #define ai_multiplayer_awareness(a, b) 1 #endif #ifndef NDEBUG fix Prev_boss_shields = -1; #endif // -------------------------------------------------------------------------------------------------------------------- // Do special stuff for a boss. void do_boss_stuff(object *objp) { // New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played. if (Last_teleport_time > GameTime) Last_teleport_time = GameTime; if (Last_gate_time > GameTime) Last_gate_time = GameTime; #ifndef NDEBUG if (objp->shields != Prev_boss_shields) { mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), objp-Objects)); Prev_boss_shields = objp->shields; } #endif if (!Boss_dying) { if (objp->ctype.ai_info.CLOAKED == 1) { if ((GameTime - Boss_cloak_start_time > BOSS_CLOAK_DURATION/3) && (Boss_cloak_end_time - GameTime > BOSS_CLOAK_DURATION/3) && (GameTime - Last_teleport_time > Boss_teleport_interval)) { if (ai_multiplayer_awareness(objp, 98)) teleport_boss(objp); } else if (Boss_hit_this_frame) { Boss_hit_this_frame = 0; Last_teleport_time -= Boss_teleport_interval/4; } if (GameTime > Boss_cloak_end_time) objp->ctype.ai_info.CLOAKED = 0; } else { if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || Boss_hit_this_frame) { if (ai_multiplayer_awareness(objp, 95)) { Boss_hit_this_frame = 0; Boss_cloak_start_time = GameTime; Boss_cloak_end_time = GameTime+Boss_cloak_duration; objp->ctype.ai_info.CLOAKED = 1; #ifndef SHAREWARE #ifdef NETWORK if (Game_mode & GM_MULTI) multi_send_boss_actions(objp-Objects, 2, 0, 0); #endif #endif } } } } else do_boss_dying_frame(objp); } #define BOSS_TO_PLAYER_GATE_DISTANCE (F1_0*150) #ifndef SHAREWARE // -------------------------------------------------------------------------------------------------------------------- // Do special stuff for a boss. void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility) { #ifdef NETWORK static int eclip_state = 0; #endif do_boss_stuff(objp); // Only master player can cause gating to occur. #ifdef NETWORK if ((Game_mode & GM_MULTI) && !network_i_am_master()) return; #endif if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) { if (GameTime - Last_gate_time > Gate_interval/2) { restart_effect(BOSS_ECLIP_NUM); #ifndef SHAREWARE #ifdef NETWORK if (eclip_state == 0) { multi_send_boss_actions(objp-Objects, 4, 0, 0); eclip_state = 1; } #endif #endif } else { stop_effect(BOSS_ECLIP_NUM); #ifndef SHAREWARE #ifdef NETWORK if (eclip_state == 1) { multi_send_boss_actions(objp-Objects, 5, 0, 0); eclip_state = 0; } #endif #endif } if (GameTime - Last_gate_time > Gate_interval) if (ai_multiplayer_awareness(objp, 99)) { int rtval; int randtype = (d_rand() * MAX_GATE_INDEX) >> 15; Assert(randtype < MAX_GATE_INDEX); randtype = Super_boss_gate_list[randtype]; Assert(randtype < N_robot_types); rtval = gate_in_robot(randtype, -1); #ifndef SHAREWARE #ifdef NETWORK if (rtval && (Game_mode & GM_MULTI)) { multi_send_boss_actions(objp-Objects, 3, randtype, Net_create_objnums[0]); map_objnum_local_to_local(Net_create_objnums[0]); } #endif #endif } } } #endif //int multi_can_move_robot(object *objp, int awareness_level) //{ // return 0; //} #ifndef SHAREWARE void ai_multi_send_robot_position(int objnum, int force) { #ifndef SHAREWARE #ifdef NETWORK if (Game_mode & GM_MULTI) { if (force != -1) multi_send_robot_position(objnum, 1); else multi_send_robot_position(objnum, 0); } #endif #endif return; } #else #define ai_multi_send_robot_position(a, b) #endif // -------------------------------------------------------------------------------------------------------------------- // Returns true if this object should be allowed to fire at the player. int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip) { if (Game_mode & GM_MULTI) if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN)) if (aip->CURRENT_STATE == AIS_FIRE) return 1; return 0; } // -------------------------------------------------------------------------------------------------------------------- void ai_do_actual_firing_stuff(object *obj, ai_static *aip, ai_local *ailp, robot_info *robptr, vms_vector *vec_to_player, fix dist_to_player, vms_vector *gun_point, int player_visibility, int object_animates) { fix dot; if (player_visibility == 2) { // Changed by mk, 01/04/94, onearm would take about 9 seconds until he can fire at you. // if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0)) { if (!object_animates || (ailp->next_fire <= 0)) { dot = vm_vec_dot(&obj->orient.fvec, vec_to_player); if (dot >= 7*F1_0/8) { if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) { if (robptr->attack_type == 1) { if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) { if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2)) return; do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos); } else { // mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2))); return; } } else { if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) { ; //mprintf((0, "Would like to fire gun, but gun not selected.\n")); } else { if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION)) return; ai_fire_laser_at_player(obj, gun_point); } } // Wants to fire, so should go into chase mode, probably. if ( (aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && (aip->behavior != AIB_FOLLOW_PATH) && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL))) ailp->mode = AIM_CHASE_OBJECT; } aip->GOAL_STATE = AIS_RECO; ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO; // Switch to next gun for next fire. aip->CURRENT_GUN++; if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns) aip->CURRENT_GUN = 0; } } } else if (Weapon_info[Robot_info[obj->id].weapon_type].homing_flag == 1) { // Robots which fire homing weapons might fire even if they don't have a bead on the player. if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0) && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) { if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION)) return; ai_fire_laser_at_player(obj, gun_point); aip->GOAL_STATE = AIS_RECO; ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO; // Switch to next gun for next fire. aip->CURRENT_GUN++; if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns) aip->CURRENT_GUN = 0; } else { // Switch to next gun for next fire. aip->CURRENT_GUN++; if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns) aip->CURRENT_GUN = 0; } } } // -------------------------------------------------------------------------------------------------------------------- void do_ai_frame(object *obj) { int objnum = obj-Objects; ai_static *aip = &obj->ctype.ai_info; ai_local *ailp = &Ai_local_info[objnum]; fix dist_to_player; vms_vector vec_to_player; fix dot; robot_info *robptr; int player_visibility=-1; int obj_ref; int object_animates; int new_goal_state; int visibility_and_vec_computed = 0; int previous_visibility; vms_vector gun_point; vms_vector vis_vec_pos; if (aip->SKIP_AI_COUNT) { aip->SKIP_AI_COUNT--; return; } // Kind of a hack. If a robot is flinching, but it is time for it to fire, unflinch it. // Else, you can turn a big nasty robot into a wimp by firing flares at it. // This also allows the player to see the cool flinch effect for mechs without unbalancing the game. if ((aip->GOAL_STATE == AIS_FLIN) && (ailp->next_fire < 0)) { aip->GOAL_STATE = AIS_FIRE; } #ifndef NDEBUG if ((aip->behavior == AIB_RUN_FROM) && (ailp->mode != AIM_RUN_FROM_OBJECT)) Int3(); // This is peculiar. Behavior is run from, but mode is not. Contact Mike. mprintf_animation_info((obj)); if (Ai_animation_test) { if (aip->GOAL_STATE == aip->CURRENT_STATE) { aip->GOAL_STATE++; if (aip->GOAL_STATE > AIS_RECO) aip->GOAL_STATE = AIS_REST; } mprintf((0, "Frame %4i, current = %i, goal = %i\n", FrameCount, aip->CURRENT_STATE, aip->GOAL_STATE)); object_animates = do_silly_animation(obj); if (object_animates) ai_frame_animation(obj); return; } if (!Do_ai_flag) return; if (Break_on_object != -1) if ((obj-Objects) == Break_on_object) Int3(); // Contact Mike: This is a debug break #endif Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position; // mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, time = %7.3f\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, f2fl(ailp->player_awareness_time))); // mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, cur=%i, goal=%i\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, aip->CURRENT_STATE, aip->GOAL_STATE)); // Assert((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR)); if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) { // mprintf((0, "Object %i behavior is %i, setting to AIB_NORMAL, fix in editor!\n", objnum, aip->behavior)); aip->behavior = AIB_NORMAL; } Assert(obj->segnum != -1); Assert(obj->id < N_robot_types); robptr = &Robot_info[obj->id]; Assert(robptr->always_0xabcd == 0xabcd); obj_ref = objnum ^ FrameCount; // -- if (ailp->wait_time > -F1_0*8) // -- ailp->wait_time -= FrameTime; if (ailp->next_fire > -F1_0*8) ailp->next_fire -= FrameTime; if (ailp->time_since_processed < F1_0*256) ailp->time_since_processed += FrameTime; previous_visibility = ailp->previous_visibility; // Must get this before we toast the master copy! // Deal with cloaking for robots which are cloaked except just before firing. if (robptr->cloak_type == RI_CLOAKED_EXCEPT_FIRING) { if (ailp->next_fire < F1_0/2) aip->CLOAKED = 1; else aip->CLOAKED = 0; } if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) Believed_player_pos = ConsoleObject->pos; dist_to_player = vm_vec_dist_quick(&Believed_player_pos, &obj->pos); //--!-- mprintf((0, "%2i: %s, [vel = %5.1f %5.1f %5.1f] spd = %4.1f dtp=%5.1f ", objnum, mode_text[ailp->mode], f2fl(obj->mtype.phys_info.velocity.x), f2fl(obj->mtype.phys_info.velocity.y), f2fl(obj->mtype.phys_info.velocity.z), f2fl(vm_vec_mag(&obj->mtype.phys_info.velocity)), f2fl(dist_to_player))); //--!-- if (ailp->mode == AIM_FOLLOW_PATH) { //--!-- mprintf((0, "gseg = %i\n", Point_segs[aip->hide_index+aip->cur_path_index].segnum)); //--!-- } else //--!-- mprintf((0, "\n")); // If this robot can fire, compute visibility from gun position. // Don't want to compute visibility twice, as it is expensive. (So is call to calc_gun_point). if ((ailp->next_fire <= 0) && (dist_to_player < F1_0*200) && (robptr->n_guns) && !(robptr->attack_type)) { calc_gun_point(&gun_point, obj, aip->CURRENT_GUN); vis_vec_pos = gun_point; // mprintf((0, "Visibility = %i, computed from gun #%i\n", player_visibility, aip->CURRENT_GUN)); } else { vis_vec_pos = obj->pos; vm_vec_zero(&gun_point); // mprintf((0, "Visibility = %i, computed from center.\n", player_visibility)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Occasionally make non-still robots make a path to the player. Based on agitation and distance from player. if ((aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && !(Game_mode & GM_MULTI)) if (Overall_agitation > 70) { if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/4)) { if (d_rand() * (Overall_agitation - 40) > F1_0*5) { // -- mprintf((0, "(1) Object #%i going from still to path in frame %i.\n", objnum, FrameCount)); create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1); // -- show_path_and_other(obj); return; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If retry count not 0, then add it into consecutive_retries. // If it is 0, cut down consecutive_retries. // This is largely a hack to speed up physics and deal with stupid AI. This is low level // communication between systems of a sort that should not be done. if ((ailp->retry_count) && !(Game_mode & GM_MULTI)) { ailp->consecutive_retries += ailp->retry_count; ailp->retry_count = 0; if (ailp->consecutive_retries > 3) { switch (ailp->mode) { case AIM_CHASE_OBJECT: // -- mprintf((0, "(2) Object #%i, retries while chasing, creating path to player in frame %i\n", objnum, FrameCount)); create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1); break; case AIM_STILL: if (!((aip->behavior == AIB_STILL) || (aip->behavior == AIB_STATION))) // Behavior is still, so don't follow path. attempt_to_resume_path(obj); break; case AIM_FOLLOW_PATH: // mprintf((0, "Object %i following path got %i retries in frame %i\n", obj-Objects, ailp->consecutive_retries, FrameCount)); if (Game_mode & GM_MULTI) ailp->mode = AIM_STILL; else attempt_to_resume_path(obj); break; case AIM_RUN_FROM_OBJECT: move_towards_segment_center(obj); obj->mtype.phys_info.velocity.x = 0; obj->mtype.phys_info.velocity.y = 0; obj->mtype.phys_info.velocity.z = 0; create_n_segment_path(obj, 5, -1); ailp->mode = AIM_RUN_FROM_OBJECT; break; case AIM_HIDE: move_towards_segment_center(obj); obj->mtype.phys_info.velocity.x = 0; obj->mtype.phys_info.velocity.y = 0; obj->mtype.phys_info.velocity.z = 0; // -- mprintf((0, "Hiding, yet creating path to player.\n")); if (Overall_agitation > (50 - Difficulty_level*4)) create_path_to_player(obj, 4 + Overall_agitation/8, 1); else { create_n_segment_path(obj, 5, -1); } break; case AIM_OPEN_DOOR: create_n_segment_path_to_door(obj, 5, -1); break; #ifndef NDEBUG case AIM_FOLLOW_PATH_2: Int3(); // Should never happen! break; #endif } ailp->consecutive_retries = 0; } } else ailp->consecutive_retries /= 2; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If in materialization center, exit if (!(Game_mode & GM_MULTI) && (Segments[obj->segnum].special == SEGMENT_IS_ROBOTMAKER)) { ai_follow_path(obj, 1); // 1 = player is visible, which might be a lie, but it works. return; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Decrease player awareness due to the passage of time. //-----old, faster way from about 11/06/94----- if (ailp->player_awareness_type) { //-----old, faster way from about 11/06/94----- if (ailp->player_awareness_time > 0) { //-----old, faster way from about 11/06/94----- ailp->player_awareness_time -= FrameTime; //-----old, faster way from about 11/06/94----- if (ailp->player_awareness_time <= 0) { //-----old, faster way from about 11/06/94----- ailp->player_awareness_time = F1_0*2; //-----old, faster way from about 11/06/94----- ailp->player_awareness_type--; //-----old, faster way from about 11/06/94----- if (ailp->player_awareness_type < 0) { //-----old, faster way from about 11/06/94----- aip->GOAL_STATE = AIS_REST; //-----old, faster way from about 11/06/94----- ailp->player_awareness_type = 0; //-----old, faster way from about 11/06/94----- } //-----old, faster way from about 11/06/94----- //-----old, faster way from about 11/06/94----- } //-----old, faster way from about 11/06/94----- } else { //-----old, faster way from about 11/06/94----- ailp->player_awareness_type = 0; //-----old, faster way from about 11/06/94----- aip->GOAL_STATE = AIS_REST; //-----old, faster way from about 11/06/94----- } //-----old, faster way from about 11/06/94----- } else //-----old, faster way from about 11/06/94----- aip->GOAL_STATE = AIS_REST; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Decrease player awareness due to the passage of time. if (ailp->player_awareness_type) { if (ailp->player_awareness_time > 0) { ailp->player_awareness_time -= FrameTime; if (ailp->player_awareness_time <= 0) { ailp->player_awareness_time = F1_0*2; //new: 11/05/94 ailp->player_awareness_type--; //new: 11/05/94 } } else { ailp->player_awareness_type--; ailp->player_awareness_time = F1_0*2; // aip->GOAL_STATE = AIS_REST; } } else aip->GOAL_STATE = AIS_REST; //new: 12/13/94 if (Player_is_dead && (ailp->player_awareness_type == 0)) if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/8)) { if ((aip->behavior != AIB_STILL) && (aip->behavior != AIB_RUN_FROM)) { if (!ai_multiplayer_awareness(obj, 30)) return; ai_multi_send_robot_position(objnum, -1); if (!((ailp->mode == AIM_FOLLOW_PATH) && (aip->cur_path_index < aip->path_length-1))) { if (dist_to_player < F1_0*30) create_n_segment_path(obj, 5, 1); else create_path_to_player(obj, 20, 1); } } } // Make sure that if this guy got hit or bumped, then he's chasing player. if ((ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) { if ((aip->behavior != AIB_STILL) && (aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM) && (obj->id != ROBOT_BRAIN)) ailp->mode = AIM_CHASE_OBJECT; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ((aip->GOAL_STATE == AIS_FLIN) && (aip->CURRENT_STATE == AIS_FLIN)) aip->GOAL_STATE = AIS_LOCK; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Note: Should only do these two function calls for objects which animate if ((dist_to_player < F1_0*100)) { // && !(Game_mode & GM_MULTI)) { object_animates = do_silly_animation(obj); if (object_animates) ai_frame_animation(obj); //mprintf((0, "Object %i: goal=%i, current=%i\n", obj-Objects, obj->ctype.ai_info.GOAL_STATE, obj->ctype.ai_info.CURRENT_STATE)); } else { // If Object is supposed to animate, but we don't let it animate due to distance, then // we must change its state, else it will never update. aip->CURRENT_STATE = aip->GOAL_STATE; object_animates = 0; // If we're not doing the animation, then should pretend it doesn't animate. } //Processed_this_frame++; //if (FrameCount != LastFrameCount) { // LastFrameCount = FrameCount; // mprintf((0, "Processed in frame %i = %i robots\n", FrameCount-1, Processed_this_frame)); // Processed_this_frame = 0; //} switch (Robot_info[obj->id].boss_flag) { case 0: break; case 1: if (aip->GOAL_STATE == AIS_FLIN) aip->GOAL_STATE = AIS_FIRE; if (aip->CURRENT_STATE == AIS_FLIN) aip->CURRENT_STATE = AIS_FIRE; dist_to_player /= 4; do_boss_stuff(obj); dist_to_player *= 4; break; #ifndef SHAREWARE case 2: if (aip->GOAL_STATE == AIS_FLIN) aip->GOAL_STATE = AIS_FIRE; if (aip->CURRENT_STATE == AIS_FLIN) aip->CURRENT_STATE = AIS_FIRE; compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); { int pv = player_visibility; fix dtp = dist_to_player/4; // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to. if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) { pv = 0; dtp = vm_vec_dist_quick(&ConsoleObject->pos, &obj->pos)/4; } do_super_boss_stuff(obj, dtp, pv); } break; #endif default: Int3(); // Bogus boss flag value. } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Time-slice, don't process all the time, purely an efficiency hack. // Guys whose behavior is station and are not at their hide segment get processed anyway. if (ailp->player_awareness_type < PA_WEAPON_ROBOT_COLLISION-1) { // If robot got hit, he gets to attack player always! #ifndef NDEBUG if (Break_on_object != objnum) { // don't time slice if we're interested in this object. #endif if ((dist_to_player > F1_0*250) && (ailp->time_since_processed <= F1_0*2)) return; else if (!((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum))) { if ((dist_to_player > F1_0*150) && (ailp->time_since_processed <= F1_0)) return; else if ((dist_to_player > F1_0*100) && (ailp->time_since_processed <= F1_0/2)) return; } #ifndef NDEBUG } #endif } // -- if ((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum)) // -- mprintf((0, "[%i] ", obj-Objects)); // Reset time since processed, but skew objects so not everything processed synchronously, else // we get fast frames with the occasional very slow frame. // AI_proc_time = ailp->time_since_processed; ailp->time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Perform special ability switch (obj->id) { case ROBOT_BRAIN: // Robots function nicely if behavior is Station. This means they won't move until they // can see the player, at which time they will start wandering about opening doors. if (ConsoleObject->segnum == obj->segnum) { if (!ai_multiplayer_awareness(obj, 97)) return; compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); move_away_from_player(obj, &vec_to_player, 0); ai_multi_send_robot_position(objnum, -1); } else if (ailp->mode != AIM_STILL) { int r; r = openable_doors_in_segment(obj); if (r != -1) { ailp->mode = AIM_OPEN_DOOR; aip->GOALSIDE = r; } else if (ailp->mode != AIM_FOLLOW_PATH) { if (!ai_multiplayer_awareness(obj, 50)) return; create_n_segment_path_to_door(obj, 8+Difficulty_level, -1); // third parameter is avoid_seg, -1 means avoid nothing. ai_multi_send_robot_position(objnum, -1); } } else { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility) { if (!ai_multiplayer_awareness(obj, 50)) return; create_n_segment_path_to_door(obj, 8+Difficulty_level, -1); // third parameter is avoid_seg, -1 means avoid nothing. ai_multi_send_robot_position(objnum, -1); } } break; default: break; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - switch (ailp->mode) { case AIM_CHASE_OBJECT: { // chasing player, sort of, chase if far, back off if close, circle in between fix circle_distance; circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size; // Green guy doesn't get his circle distance boosted, else he might never attack. if (robptr->attack_type != 1) circle_distance += (objnum&0xf) * F1_0/2; compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); // @mk, 12/27/94, structure here was strange. Would do both clauses of what are now this if/then/else. Used to be if/then, if/then. if ((player_visibility < 2) && (previous_visibility == 2)) { // this is redundant: mk, 01/15/95: && (ailp->mode == AIM_CHASE_OBJECT)) { // mprintf((0, "I used to be able to see the player!\n")); if (!ai_multiplayer_awareness(obj, 53)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } // -- mprintf((0, "(3) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount)); create_path_to_player(obj, 8, 1); // -- show_path_and_other(obj); ai_multi_send_robot_position(objnum, -1); } else if ((player_visibility == 0) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) { // If pretty far from the player, player cannot be seen (obstructed) and in chase mode, switch to follow path mode. // This has one desirable benefit of avoiding physics retries. if (aip->behavior == AIB_STATION) { ailp->goal_segment = aip->hide_segment; // -- mprintf((0, "(1) Object #%i going from chase to STATION in frame %i.\n", objnum, FrameCount)); create_path_to_station(obj, 15); // -- show_path_and_other(obj); } else create_n_segment_path(obj, 5, -1); break; } if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) { if (player_visibility) { if (d_rand() < FrameTime*player_visibility) { if (dist_to_player/256 < d_rand()*player_visibility) { // mprintf((0, "Object %i searching for player.\n", obj-Objects)); aip->GOAL_STATE = AIS_SRCH; aip->CURRENT_STATE = AIS_SRCH; } } } } if (GameTime - ailp->time_player_seen > CHASE_TIME_LENGTH) { if (Game_mode & GM_MULTI) if (!player_visibility && (dist_to_player > F1_0*70)) { ailp->mode = AIM_STILL; return; } if (!ai_multiplayer_awareness(obj, 64)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } // -- mprintf((0, "(4) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount)); create_path_to_player(obj, 10, 1); // -- show_path_and_other(obj); ai_multi_send_robot_position(objnum, -1); } else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) { if (!ai_multiplayer_awareness(obj, 70)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 0); if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) { if (player_visibility) // == 2) ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); else ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility); } if (ai_evaded) { ai_multi_send_robot_position(objnum, 1); ai_evaded = 0; } else ai_multi_send_robot_position(objnum, -1); do_firing_stuff(obj, player_visibility, &vec_to_player); } break; } case AIM_RUN_FROM_OBJECT: compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility) { if (ailp->player_awareness_type == 0) ailp->player_awareness_type = PA_WEAPON_ROBOT_COLLISION; } // If in multiplayer, only do if player visible. If not multiplayer, do always. if (!(Game_mode & GM_MULTI) || player_visibility) if (ai_multiplayer_awareness(obj, 75)) { ai_follow_path(obj, player_visibility); ai_multi_send_robot_position(objnum, -1); } if (aip->GOAL_STATE != AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; else if (aip->CURRENT_STATE == AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; // Bad to let run_from robot fire at player because it will cause a war in which it turns towards the // player to fire and then towards its goal to move. // do_firing_stuff(obj, player_visibility, &vec_to_player); // Instead, do this: // (Note, only drop if player is visible. This prevents the bombs from being a giveaway, and // also ensures that the robot is moving while it is dropping. Also means fewer will be dropped.) if ((ailp->next_fire <= 0) && (player_visibility)) { vms_vector fire_vec, fire_pos; if (!ai_multiplayer_awareness(obj, 75)) return; fire_vec = obj->orient.fvec; vm_vec_negate(&fire_vec); vm_vec_add(&fire_pos, &obj->pos, &fire_vec); Laser_create_new_easy( &fire_vec, &fire_pos, obj-Objects, PROXIMITY_ID, 1); ailp->next_fire = F1_0*5; // Drop a proximity bomb every 5 seconds. #ifdef NETWORK #ifndef SHAREWARE if (Game_mode & GM_MULTI) { ai_multi_send_robot_position(obj-Objects, -1); multi_send_robot_fire(obj-Objects, -1, &fire_vec); } #endif #endif } break; case AIM_FOLLOW_PATH: { int anger_level = 65; if (aip->behavior == AIB_STATION) if (Point_segs[aip->hide_index + aip->path_length - 1].segnum == aip->hide_segment) { anger_level = 64; // mprintf((0, "Object %i, station, lowering anger to 64.\n")); } compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (Game_mode & (GM_MODEM | GM_SERIAL)) if (!player_visibility && (dist_to_player > F1_0*70)) { ailp->mode = AIM_STILL; return; } if (!ai_multiplayer_awareness(obj, anger_level)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); } return; } ai_follow_path(obj, player_visibility); if (aip->GOAL_STATE != AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; else if (aip->CURRENT_STATE == AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; if ((aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM)) do_firing_stuff(obj, player_visibility, &vec_to_player); if ((player_visibility == 2) && (aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM) && (obj->id != ROBOT_BRAIN)) { if (robptr->attack_type == 0) ailp->mode = AIM_CHASE_OBJECT; } else if ((player_visibility == 0) && (aip->behavior == AIB_NORMAL) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM)) { ailp->mode = AIM_STILL; aip->hide_index = -1; aip->path_length = 0; } ai_multi_send_robot_position(objnum, -1); break; } case AIM_HIDE: if (!ai_multiplayer_awareness(obj, 71)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); } return; } compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); ai_follow_path(obj, player_visibility); if (aip->GOAL_STATE != AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; else if (aip->CURRENT_STATE == AIS_FLIN) aip->GOAL_STATE = AIS_LOCK; ai_multi_send_robot_position(objnum, -1); break; case AIM_STILL: if ((dist_to_player < F1_0*120+Difficulty_level*F1_0*20) || (ailp->player_awareness_type >= PA_WEAPON_ROBOT_COLLISION-1)) { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); // turn towards vector if visible this time or last time, or rand // new! if ((player_visibility) || (previous_visibility) || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI))) { if (!ai_multiplayer_awareness(obj, 71)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); ai_multi_send_robot_position(objnum, -1); } do_firing_stuff(obj, player_visibility, &vec_to_player); // This is debugging code! Remove it! It's to make the green guy attack without doing other kinds of movement. if (player_visibility) { // Change, MK, 01/03/94 for Multiplayer reasons. If robots can't see you (even with eyes on back of head), then don't do evasion. if (robptr->attack_type == 1) { aip->behavior = AIB_NORMAL; if (!ai_multiplayer_awareness(obj, 80)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0); if (ai_evaded) { ai_multi_send_robot_position(objnum, 1); ai_evaded = 0; } else ai_multi_send_robot_position(objnum, -1); } else { // Robots in hover mode are allowed to evade at half normal speed. if (!ai_multiplayer_awareness(obj, 81)) { if (maybe_ai_do_actual_firing_stuff(obj, aip)) ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 1); if (ai_evaded) { ai_multi_send_robot_position(objnum, -1); ai_evaded = 0; } else ai_multi_send_robot_position(objnum, -1); } } else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) { // If pretty far from the player, player cannot be seen (obstructed) and in chase mode, switch to follow path mode. // This has one desirable benefit of avoiding physics retries. if (aip->behavior == AIB_STATION) { ailp->goal_segment = aip->hide_segment; // -- mprintf((0, "(2) Object #%i going from STILL to STATION in frame %i.\n", objnum, FrameCount)); create_path_to_station(obj, 15); // -- show_path_and_other(obj); } break; } } break; case AIM_OPEN_DOOR: { // trying to open a door. vms_vector center_point, goal_vector; Assert(obj->id == ROBOT_BRAIN); // Make sure this guy is allowed to be in this mode. if (!ai_multiplayer_awareness(obj, 62)) return; compute_center_point_on_side(¢er_point, &Segments[obj->segnum], aip->GOALSIDE); vm_vec_sub(&goal_vector, ¢er_point, &obj->pos); vm_vec_normalize_quick(&goal_vector); ai_turn_towards_vector(&goal_vector, obj, robptr->turn_time[Difficulty_level]); move_towards_vector(obj, &goal_vector); ai_multi_send_robot_position(objnum, -1); break; } default: mprintf((0, "Unknown mode = %i in robot %i, behavior = %i\n", ailp->mode, obj-Objects, aip->behavior)); ailp->mode = AIM_CHASE_OBJECT; break; } // end: switch (ailp->mode) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If the robot can see you, increase his awareness of you. // This prevents the problem of a robot looking right at you but doing nothing. // Assert(player_visibility != -1); // Means it didn't get initialized! compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility == 2) if (ailp->player_awareness_type == 0) ailp->player_awareness_type = PA_PLAYER_COLLISION; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (!object_animates) { aip->CURRENT_STATE = aip->GOAL_STATE; // mprintf((0, "Setting current to goal (%i) because object doesn't animate.\n", aip->GOAL_STATE)); } Assert(ailp->player_awareness_type <= AIE_MAX); Assert(aip->CURRENT_STATE < AIS_MAX); Assert(aip->GOAL_STATE < AIS_MAX); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (ailp->player_awareness_type) { new_goal_state = Ai_transition_table[ailp->player_awareness_type-1][aip->CURRENT_STATE][aip->GOAL_STATE]; if (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) { // Decrease awareness, else this robot will flinch every frame. ailp->player_awareness_type--; ailp->player_awareness_time = F1_0*3; } if (new_goal_state == AIS_ERR_) new_goal_state = AIS_REST; if (aip->CURRENT_STATE == AIS_NONE) aip->CURRENT_STATE = AIS_REST; aip->GOAL_STATE = new_goal_state; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If new state = fire, then set all gun states to fire. if ((aip->GOAL_STATE == AIS_FIRE) ) { int i,num_guns; num_guns = Robot_info[obj->id].n_guns; for (i=0; igoal_state[i] = AIS_FIRE; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Hack by mk on 01/04/94, if a guy hasn't animated to the firing state, but his next_fire says ok to fire, bash him there if ((ailp->next_fire < 0) && (aip->GOAL_STATE == AIS_FIRE)) aip->CURRENT_STATE = AIS_FIRE; if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN)) { switch (aip->CURRENT_STATE) { case AIS_NONE: compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); dot = vm_vec_dot(&obj->orient.fvec, &vec_to_player); if (dot >= F1_0/2) if (aip->GOAL_STATE == AIS_REST) aip->GOAL_STATE = AIS_SRCH; //mprintf((0, "State = none, goal = %i.\n", aip->GOAL_STATE)); break; case AIS_REST: if (aip->GOAL_STATE == AIS_REST) { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if ((ailp->next_fire <= 0) && (player_visibility)) { // mprintf((0, "Setting goal state to fire from rest.\n")); aip->GOAL_STATE = AIS_FIRE; } //mprintf((0, "State = rest, goal = %i.\n", aip->GOAL_STATE)); } break; case AIS_SRCH: if (!ai_multiplayer_awareness(obj, 60)) return; compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility) { ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); ai_multi_send_robot_position(objnum, -1); } else if (!(Game_mode & GM_MULTI)) ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility); break; case AIS_LOCK: compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (!(Game_mode & GM_MULTI) || (player_visibility)) { if (!ai_multiplayer_awareness(obj, 68)) return; if (player_visibility) { ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); ai_multi_send_robot_position(objnum, -1); } else if (!(Game_mode & GM_MULTI)) ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility); } break; case AIS_FIRE: compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility) { if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) { if (Game_mode & GM_MULTI) { ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); return; } } ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); ai_multi_send_robot_position(objnum, -1); } else if (!(Game_mode & GM_MULTI)) { ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility); } // Fire at player, if appropriate. ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates); break; case AIS_RECO: if (!(obj_ref & 3)) { compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed); if (player_visibility) { if (!ai_multiplayer_awareness(obj, 69)) return; ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]); ai_multi_send_robot_position(objnum, -1); } else if (!(Game_mode & GM_MULTI)) { ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility); } } break; case AIS_FLIN: // mprintf((0, "State = flinch, goal = %i.\n", aip->GOAL_STATE)); break; default: mprintf((1, "Unknown mode for AI object #%i\n", objnum)); aip->GOAL_STATE = AIS_REST; aip->CURRENT_STATE = AIS_REST; break; } } // end of: if (aip->GOAL_STATE != AIS_FLIN) { // Switch to next gun for next fire. if (player_visibility == 0) { aip->CURRENT_GUN++; if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns) aip->CURRENT_GUN = 0; } } //--mk, 121094 -- // ---------------------------------------------------------------------------------- //--mk, 121094 -- void spin_robot(object *robot, vms_vector *collision_point) //--mk, 121094 -- { //--mk, 121094 -- if (collision_point->x != 3) { //--mk, 121094 -- robot->phys_info.rotvel.x = 0x1235; //--mk, 121094 -- robot->phys_info.rotvel.y = 0x2336; //--mk, 121094 -- robot->phys_info.rotvel.z = 0x3737; //--mk, 121094 -- } //--mk, 121094 -- //--mk, 121094 -- } // ----------------------------------------------------------------------------------- void ai_do_cloak_stuff(void) { int i; for (i=0; ipos; Ai_cloak_info[i].last_time = GameTime; } // Make work for control centers. Believed_player_pos = Ai_cloak_info[0].last_position; } // ----------------------------------------------------------------------------------- // Returns false if awareness is considered too puny to add, else returns true. int add_awareness_event(object *objp, int type) { // If player cloaked and hit a robot, then increase awareness if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_WEAPON_WALL_COLLISION) || (type == PA_PLAYER_COLLISION)) ai_do_cloak_stuff(); if (Num_awareness_events < MAX_AWARENESS_EVENTS) { if ((type == PA_WEAPON_WALL_COLLISION) || (type == PA_WEAPON_ROBOT_COLLISION)) if (objp->id == VULCAN_ID) if (d_rand() > 3276) return 0; // For vulcan cannon, only about 1/10 actually cause awareness Awareness_events[Num_awareness_events].segnum = objp->segnum; Awareness_events[Num_awareness_events].pos = objp->pos; Awareness_events[Num_awareness_events].type = type; Num_awareness_events++; } else Assert(0); // Hey -- Overflowed Awareness_events, make more or something // This just gets ignored, so you can just continue. return 1; } // ---------------------------------------------------------------------------------- // Robots will become aware of the player based on something that occurred. // The object (probably player or weapon) which created the awareness is objp. void create_awareness_event(object *objp, int type) { if (add_awareness_event(objp, type)) { if (((d_rand() * (type+4)) >> 15) > 4) Overall_agitation++; if (Overall_agitation > OVERALL_AGITATION_MAX) Overall_agitation = OVERALL_AGITATION_MAX; } } byte New_awareness[MAX_SEGMENTS]; // ---------------------------------------------------------------------------------- void pae_aux(int segnum, int type, int level) { int j; if (New_awareness[segnum] < type) New_awareness[segnum] = type; // Process children. if (level <= 4) for (j=0; j Ai_local_info[i].player_awareness_type) { Ai_local_info[i].player_awareness_type = New_awareness[Objects[i].segnum]; Ai_local_info[i].player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME; } } #ifndef NDEBUG int Ai_dump_enable = 0; FILE *Ai_dump_file = NULL; char Ai_error_message[128] = ""; // ---------------------------------------------------------------------------------- void dump_ai_objects_all() { #if PARALLAX int objnum; int total=0; time_t time_of_day; time_of_day = time(NULL); if (!Ai_dump_enable) return; if (Ai_dump_file == NULL) Ai_dump_file = fopen("ai.out","a+t"); fprintf(Ai_dump_file, "\nnum: seg distance __mode__ behav. [velx vely velz] (Frame = %i)\n", FrameCount); fprintf(Ai_dump_file, "Date & Time = %s\n", ctime(&time_of_day)); if (Ai_error_message[0]) fprintf(Ai_dump_file, "Error message: %s\n", Ai_error_message); for (objnum=0; objnum <= Highest_object_index; objnum++) { object *objp = &Objects[objnum]; ai_static *aip = &objp->ctype.ai_info; ai_local *ailp = &Ai_local_info[objnum]; fix dist_to_player; dist_to_player = vm_vec_dist(&objp->pos, &ConsoleObject->pos); if (objp->control_type == CT_AI) { fprintf(Ai_dump_file, "%3i: %3i %8.3f %8s %8s [%3i %4i]\n", objnum, objp->segnum, f2fl(dist_to_player), mode_text[ailp->mode], behavior_text[aip->behavior-0x80], aip->hide_index, aip->path_length); if (aip->path_length) total += aip->path_length; } } fprintf(Ai_dump_file, "Total path length = %4i\n", total); #endif } // ---------------------------------------------------------------------------------- void force_dump_ai_objects_all(char *msg) { int tsave; tsave = Ai_dump_enable; Ai_dump_enable = 1; sprintf(Ai_error_message, "%s\n", msg); dump_ai_objects_all(); Ai_error_message[0] = 0; Ai_dump_enable = tsave; } // ---------------------------------------------------------------------------------- void turn_off_ai_dump(void) { if (Ai_dump_file != NULL) fclose(Ai_dump_file); Ai_dump_file = NULL; } #endif // ---------------------------------------------------------------------------------- // Do things which need to get done for all AI objects each frame. // This includes: // Setting player_awareness (a fix, time in seconds which object is aware of player) void do_ai_frame_all(void) { #ifndef NDEBUG dump_ai_objects_all(); #endif set_player_awareness_all(); // if ((FrameCount & 0x07) == 0) // mprintf((0, "[%i] ", Overall_agitation)); } //--unused-- // ---------------------------------------------------------------------------------- //--unused-- // Reset various state information for a new life. //--unused-- // Probably not going to be called for a new game because for that an entire object //--unused-- // gets reloaded. //--unused-- void reset_ai_states(object *objp) //--unused-- { //--unused-- int i; //--unused-- //ai_static *aip = &objp->ctype.ai_info; //--unused-- ai_local *ailp = &Ai_local_info[objp-Objects]; //--unused-- //--unused-- ailp->wait_time = 0; //--unused-- ailp->next_fire = 0; //--unused-- ailp->player_awareness_type = 0; //--unused-- for (i=0; igoal_angles[i].p = 0; ailp->goal_angles[i].b = 0; ailp->goal_angles[i].h = 0; //--unused-- ailp->delta_angles[i].p = 0; ailp->delta_angles[i].b = 0; ailp->delta_angles[i].h = 0; //--unused-- ailp->goal_state[i] = 0; //--unused-- ailp->achieved_state[i] = 0; //--unused-- } //--unused-- } // Initializations to be performed for all robots for a new level. void init_robots_for_level(void) { Overall_agitation = 0; } int ai_save_state( FILE * fp ) { char buffer[1024]; // That should be enough for us... char *ptr=buffer; int i; d_export_int(buffer, Ai_initialized); d_export_int(buffer+SIZEOF_INT, Overall_agitation); fwrite(buffer, SIZEOF_INT, 2, fp); for (i=0; ictype.ai_info; // -- for (i=0; ipath_length; i++) // -- mprintf((0, "%2i ", Point_segs[aip->hide_index+i].segnum)); // -- mprintf((0, "[pl: %i cur: st: %i]\n", ConsoleObject->segnum, objp->segnum, aip->hide_segment)); // -- }