/* $Id: escort.c,v 1.1.1.1 2006/03/17 19:55:00 zicodxx Exp $ */ /* 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-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Escort robot behavior. * */ #include // for printf() #include // for rand() and qsort() #include // for memset() #include "window.h" #include "inferno.h" #include "console.h" #include "fix.h" #include "vecmat.h" #include "gr.h" #include "3d.h" #include "palette.h" #include "timer.h" #include "object.h" #include "error.h" #include "ai.h" #include "robot.h" #include "fvi.h" #include "physics.h" #include "wall.h" #include "player.h" #include "fireball.h" #include "game.h" #include "powerup.h" #include "cntrlcen.h" #include "gauges.h" #include "key.h" #include "fuelcen.h" #include "sounds.h" #include "screens.h" #include "text.h" #include "gamefont.h" #include "newmenu.h" #include "playsave.h" #include "gameseq.h" #include "automap.h" #include "laser.h" #include "escort.h" #ifdef EDITOR #include "editor/editor.h" #endif extern void multi_send_stolen_items(); void say_escort_goal(int goal_num); void show_escort_menu(char *msg); static const char *const Escort_goal_text[MAX_ESCORT_GOALS] = { "BLUE KEY", "YELLOW KEY", "RED KEY", "REACTOR", "EXIT", "ENERGY", "ENERGYCEN", "SHIELD", "POWERUP", "ROBOT", "HOSTAGES", "SPEW", "SCRAM", "EXIT", "BOSS", "MARKER 1", "MARKER 2", "MARKER 3", "MARKER 4", "MARKER 5", "MARKER 6", "MARKER 7", "MARKER 8", "MARKER 9", // -- too much work -- "KAMIKAZE " }; int Max_escort_length = 200; int Escort_kill_object = -1; ubyte Stolen_items[MAX_STOLEN_ITEMS]; int Stolen_item_index; fix64 Escort_last_path_created = 0; int Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0; fix64 Buddy_sorry_time; int Buddy_objnum, Buddy_allowed_to_talk; int Looking_for_marker; int Last_buddy_key; fix64 Last_buddy_message_time; void init_buddy_for_level(void) { int i; Buddy_allowed_to_talk = 0; Buddy_objnum = -1; Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; Escort_special_goal = -1; Escort_goal_index = -1; Buddy_messages_suppressed = 0; for (i=0; i<=Highest_object_index; i++) if (Robot_info[Objects[i].id].companion) break; if (i <= Highest_object_index) Buddy_objnum = i; Buddy_sorry_time = -F1_0; Looking_for_marker = -1; Last_buddy_key = -1; } // ----------------------------------------------------------------------------- // See if segment from curseg through sidenum is reachable. // Return true if it is reachable, else return false. int segment_is_reachable(int curseg, int sidenum) { int wall_num, rval; segment *segp = &Segments[curseg]; if (!IS_CHILD(segp->children[sidenum])) return 0; wall_num = segp->sides[sidenum].wall_num; // If no wall, then it is reachable if (wall_num == -1) return 1; rval = ai_door_is_openable(NULL, segp, sidenum); return rval; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- // Hmm, a closed wall. I think this mean not reachable. // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_CLOSED) // -- MK, 10/17/95 -- return 0; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_DOOR) { // -- MK, 10/17/95 -- if (Walls[wall_num].keys == KEY_NONE) { // -- MK, 10/17/95 -- return 1; // @MK, 10/17/95: Be consistent with ai_door_is_openable // -- MK, 10/17/95 -- // -- if (Walls[wall_num].flags & WALL_DOOR_LOCKED) // -- MK, 10/17/95 -- // -- return 0; // -- MK, 10/17/95 -- // -- else // -- MK, 10/17/95 -- // -- return 1; // -- MK, 10/17/95 -- } else if (Walls[wall_num].keys == KEY_BLUE) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY); // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_GOLD) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY); // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_RED) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY); // -- MK, 10/17/95 -- else // -- MK, 10/17/95 -- Int3(); // Impossible! Doesn't have no key, but doesn't have any key! // -- MK, 10/17/95 -- } else // -- MK, 10/17/95 -- return 1; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- Int3(); // Hmm, thought 'if' above had to return! // -- MK, 10/17/95 -- return 0; } // ----------------------------------------------------------------------------- // Create a breadth-first list of segments reachable from current segment. // max_segs is maximum number of segments to search. Use MAX_SEGMENTS to search all. // On exit, *length <= max_segs. // Input: // start_seg // Output: // bfs_list: array of shorts, each reachable segment. Includes start segment. // length: number of elements in bfs_list void create_bfs_list(int start_seg, short bfs_list[], int *length, int max_segs) { int i, head, tail; sbyte visited[MAX_SEGMENTS]; for (i=0; ichildren[i]; if (IS_CHILD(connected_seg) && (visited[connected_seg] == 0)) { if (segment_is_reachable(curseg, i)) { bfs_list[head++] = connected_seg; if (head >= max_segs) break; visited[connected_seg] = 1; Assert(head < MAX_SEGMENTS); } } } } *length = head; } // ----------------------------------------------------------------------------- // Return true if ok for buddy to talk, else return false. // Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted // AND he has never yet, since being initialized for level, been allowed to talk. int ok_for_buddy_to_talk(void) { int i; segment *segp; if (Buddy_objnum == -1) return 0; if (Objects[Buddy_objnum].type != OBJ_ROBOT) { Buddy_allowed_to_talk = 0; return 0; } if (Buddy_allowed_to_talk) return 1; if ((Objects[Buddy_objnum].type == OBJ_ROBOT) && (Buddy_objnum <= Highest_object_index) && !Robot_info[Objects[Buddy_objnum].id].companion) { for (i=0; i<=Highest_object_index; i++) if (Robot_info[Objects[i].id].companion) break; if (i > Highest_object_index) return 0; else Buddy_objnum = i; } segp = &Segments[Objects[Buddy_objnum].segnum]; for (i=0; isides[i].wall_num; if (wall_num != -1) { if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED)) return 0; } // Check one level deeper. if (IS_CHILD(segp->children[i])) { int j; segment *csegp = &Segments[segp->children[i]]; for (j=0; jsides[j].wall_num; if (wall2 != -1) { if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED)) return 0; } } } } Buddy_allowed_to_talk = 1; return 1; } // -------------------------------------------------------------------------------------------- void detect_escort_goal_accomplished(int index) { int i,j; int detected = 0; if (!Buddy_allowed_to_talk) return; // If goal is to go away, how can it be achieved? if (Escort_special_goal == ESCORT_GOAL_SCRAM) return; // See if goal found was a key. Need to handle default goals differently. // Note, no buddy_met_goal sound when blow up reactor or exit. Not great, but ok // since for reactor, noisy, for exit, buddy is disappearing. if ((Escort_special_goal == -1) && (Escort_goal_index == index)) { detected = 1; goto dega_ok; } if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) { if (Objects[index].type == OBJ_POWERUP) { if (Objects[index].id == POW_KEY_BLUE) { if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) { detected = 1; goto dega_ok; } } else if (Objects[index].id == POW_KEY_GOLD) { if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) { detected = 1; goto dega_ok; } } else if (Objects[index].id == POW_KEY_RED) { if (Escort_goal_index == ESCORT_GOAL_RED_KEY) { detected = 1; goto dega_ok; } } } } if (Escort_special_goal != -1) { if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) { if (index == -4) detected = 1; else { for (i=0; iflags & OF_PLAYER_DROPPED) return objnum; } if (curobjp->type == objtype) { // Don't find escort robots if looking for robot! if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion)) ; else if (objid == -1) { if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED)) return objnum; else return objnum; } else if (curobjp->id == objid) return objnum; } if (objtype == OBJ_POWERUP) if (curobjp->contains_count) if (curobjp->contains_type == OBJ_POWERUP) if (curobjp->contains_id == objid) return objnum; objnum = curobjp->next; } } return -1; } // ----------------------------------------------------------------------------- // Return nearest object of interest. // If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player. // -1 means object does not exist in mine. // -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall) int exists_in_mine(int start_seg, int objtype, int objid, int special) { int segindex, segnum; short bfs_list[MAX_SEGMENTS]; int length; create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS); if (objtype == FUELCEN_CHECK) { for (segindex=0; segindex