/* $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. * */ #ifdef HAVE_CONFIG_H #include #endif #include // for printf() #include // for rand() and qsort() #include // for memset() #include "inferno.h" #include "mono.h" #include "fix.h" #include "vecmat.h" #include "gr.h" #include "3d.h" #include "palette.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 #ifdef OGL #include "ogl_init.h" #endif extern void multi_send_stolen_items(); void say_escort_goal(int goal_num); void show_escort_menu(char *msg); char *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; fix Escort_last_path_created = 0; int Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0; fix Buddy_sorry_time; int Buddy_objnum, Buddy_allowed_to_talk; int Looking_for_marker; int Last_buddy_key; fix Last_buddy_message_time; char guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT"; char real_guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT"; 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 (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; i GameTime)) { if (ok_for_buddy_to_talk()) { char gb_str[16], new_format[128]; va_list args; int t; va_start(args, format ); vsprintf(new_format, format, args); va_end(args); gb_str[0] = 1; gb_str[1] = BM_XRGB(28, 0, 0); strcpy(&gb_str[2], guidebot_name); t = strlen(gb_str); gb_str[t] = ':'; gb_str[t+1] = 1; gb_str[t+2] = BM_XRGB(28, 28, 28); gb_str[t+3] = 0; HUD_init_message("%s %s", gb_str, new_format); Last_buddy_message_time = GameTime; } } } // ----------------------------------------------------------------------------- void thief_message(char * format, ... ) { char gb_str[16], new_format[128]; va_list args; va_start(args, format ); vsprintf(new_format, format, args); va_end(args); gb_str[0] = 1; gb_str[1] = BM_XRGB(28, 0, 0); strcpy(&gb_str[2], "THIEF:"); gb_str[8] = 1; gb_str[9] = BM_XRGB(28, 28, 28); gb_str[10] = 0; HUD_init_message("%s %s", gb_str, new_format); } // ----------------------------------------------------------------------------- // Return true if marker #id has been placed. int marker_exists_in_mine(int id) { int i; for (i=0; i<=Highest_object_index; i++) if (Objects[i].type == OBJ_MARKER) if (Objects[i].id == id) return 1; return 0; } // ----------------------------------------------------------------------------- void set_escort_special_goal(int special_key) { int marker_key; Buddy_messages_suppressed = 0; if (!Buddy_allowed_to_talk) { ok_for_buddy_to_talk(); if (!Buddy_allowed_to_talk) { int i; for (i=0; i<=Highest_object_index; i++) if ((Objects[i].type == OBJ_ROBOT) && Robot_info[Objects[i].id].companion) { HUD_init_message("%s has not been released.",guidebot_name); break; } if (i == Highest_object_index+1) HUD_init_message("No Guide-Bot in mine."); return; } } special_key = special_key & (~KEY_SHIFTED); marker_key = special_key; #ifdef MACINTOSH switch(special_key) { case KEY_5: marker_key = KEY_1+4; break; case KEY_6: marker_key = KEY_1+5; break; case KEY_7: marker_key = KEY_1+6; break; case KEY_8: marker_key = KEY_1+7; break; case KEY_9: marker_key = KEY_1+8; break; case KEY_0: marker_key = KEY_1+9; break; } #endif if (Last_buddy_key == special_key) { if ((Looking_for_marker == -1) && (special_key != KEY_0)) { if (marker_exists_in_mine(marker_key - KEY_1)) Looking_for_marker = marker_key - KEY_1; else { Last_buddy_message_time = 0; // Force this message to get through. buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1); Looking_for_marker = -1; } } else { Looking_for_marker = -1; } } Last_buddy_key = special_key; if (special_key == KEY_0) Looking_for_marker = -1; if ( Looking_for_marker != -1 ) { Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1; } else { switch (special_key) { case KEY_1: Escort_special_goal = ESCORT_GOAL_ENERGY; break; case KEY_2: Escort_special_goal = ESCORT_GOAL_ENERGYCEN; break; case KEY_3: Escort_special_goal = ESCORT_GOAL_SHIELD; break; case KEY_4: Escort_special_goal = ESCORT_GOAL_POWERUP; break; case KEY_5: Escort_special_goal = ESCORT_GOAL_ROBOT; break; case KEY_6: Escort_special_goal = ESCORT_GOAL_HOSTAGE; break; case KEY_7: Escort_special_goal = ESCORT_GOAL_SCRAM; break; case KEY_8: Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW; break; case KEY_9: Escort_special_goal = ESCORT_GOAL_EXIT; break; case KEY_0: Escort_special_goal = -1; break; default: Int3(); // Oops, called with illegal key value. } } Last_buddy_message_time = GameTime - 2*F1_0; // Allow next message to come through. say_escort_goal(Escort_special_goal); // -- Escort_goal_object = escort_set_goal_object(); Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; } // -- old, pre-bfs, way -- // ----------------------------------------------------------------------------- // -- old, pre-bfs, way -- // Return object of interest. // -- old, pre-bfs, way -- int exists_in_mine(int objtype, int objid) // -- old, pre-bfs, way -- { // -- old, pre-bfs, way -- int i; // -- old, pre-bfs, way -- // -- old, pre-bfs, way -- mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid)); // -- old, pre-bfs, way -- // -- old, pre-bfs, way -- if (objtype == FUELCEN_CHECK) { // -- old, pre-bfs, way -- for (i=0; i<=Highest_segment_index; i++) // -- old, pre-bfs, way -- if (Segments[i].special == SEGMENT_IS_FUELCEN) // -- old, pre-bfs, way -- return i; // -- old, pre-bfs, way -- } else { // -- old, pre-bfs, way -- for (i=0; i<=Highest_object_index; i++) { // -- old, pre-bfs, way -- if (Objects[i].type == objtype) { // -- old, pre-bfs, way -- // Don't find escort robots if looking for robot! // -- old, pre-bfs, way -- if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].companion)) // -- old, pre-bfs, way -- continue; // -- old, pre-bfs, way -- // -- old, pre-bfs, way -- if (objid == -1) { // -- old, pre-bfs, way -- if ((objtype == OBJ_POWERUP) && (Objects[i].id != POW_KEY_BLUE) && (Objects[i].id != POW_KEY_GOLD) && (Objects[i].id != POW_KEY_RED)) // -- old, pre-bfs, way -- return i; // -- old, pre-bfs, way -- else // -- old, pre-bfs, way -- return i; // -- old, pre-bfs, way -- } else if (Objects[i].id == objid) // -- old, pre-bfs, way -- return i; // -- old, pre-bfs, way -- } // -- old, pre-bfs, way -- } // -- old, pre-bfs, way -- } // -- old, pre-bfs, way -- // -- old, pre-bfs, way -- return -1; // -- old, pre-bfs, way -- // -- old, pre-bfs, way -- } // ----------------------------------------------------------------------------- // Return id of boss. int get_boss_id(void) { int i; for (i=0; i<=Highest_object_index; i++) if (Objects[i].type == OBJ_ROBOT) if (Robot_info[Objects[i].id].boss_flag) return Objects[i].id; return -1; } // ----------------------------------------------------------------------------- // Return object index if object of objtype, objid exists in mine, else return -1 // "special" is used to find objects spewed by player which is hacked into flags field of powerup. int exists_in_mine_2(int segnum, int objtype, int objid, int special) { if (Segments[segnum].objects != -1) { int objnum = Segments[segnum].objects; while (objnum != -1) { object *curobjp = &Objects[objnum]; if (special == ESCORT_GOAL_PLAYER_SPEW) { if (curobjp->flags & 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; // mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid)); create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS); if (objtype == FUELCEN_CHECK) { for (segindex=0; segindex