/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ultima/nuvie/core/nuvie_defs.h" #include "ultima/nuvie/misc/u6_misc.h" #include "ultima/nuvie/files/nuvie_io.h" #include "ultima/nuvie/core/game.h" #include "ultima/nuvie/core/converse.h" #include "ultima/nuvie/core/timed_event.h" #include "ultima/nuvie/conf/configuration.h" #include "ultima/nuvie/actors/actor_manager.h" #include "ultima/nuvie/sound/sound_manager.h" #include "ultima/nuvie/views/view_manager.h" #include "ultima/nuvie/core/player.h" #include "ultima/nuvie/core/map.h" #include "ultima/nuvie/gui/widgets/map_window.h" #include "ultima/nuvie/usecode/u6_usecode.h" #include "ultima/nuvie/gui/widgets/command_bar.h" #include "ultima/nuvie/pathfinder/party_path_finder.h" #include "ultima/nuvie/core/party.h" #include "ultima/nuvie/views/view.h" #include "ultima/nuvie/save/obj_list.h" #include "ultima/nuvie/core/events.h" namespace Ultima { namespace Nuvie { Party::Party(const Configuration *cfg) : config(cfg), game(nullptr), actor_manager(nullptr), map(nullptr), pathfinder(nullptr), rest_campfire(nullptr), formation(PARTY_FORM_STANDARD), num_in_party(0), prev_leader_x(0), prev_leader_y(0), defer_removing_dead_members(false), autowalk(false), in_vehicle(false), in_combat_mode(false), lightsources(0), combat_changes_music(false), vehicles_change_music(false) { memset(&member, 0, sizeof member); } Party::~Party() { if (pathfinder) delete pathfinder; } bool Party::init(Game *g, ActorManager *am) { Std::string formation_string; game = g; actor_manager = am; map = g->get_game_map(); if (!pathfinder) pathfinder = new PartyPathFinder(this); autowalk = false; in_vehicle = false; config->value("config/general/party_formation", formation_string, ""); if (formation_string == "row") formation = PARTY_FORM_ROW; else if (formation_string == "column") formation = PARTY_FORM_COLUMN; else if (formation_string == "delta") formation = PARTY_FORM_DELTA; else formation = PARTY_FORM_STANDARD; config->value("config/audio/combat_changes_music", combat_changes_music, true); config->value("config/audio/vehicles_change_music", vehicles_change_music, true); return true; } bool Party::load(NuvieIO *objlist) { uint8 actor_num; uint16 i; autowalk = false; in_vehicle = false; objlist->seek(OBJLIST_OFFSET_NUM_IN_PARTY); num_in_party = objlist->read1(); objlist->seek(OBJLIST_OFFSET_PARTY_NAMES); for (i = 0; i < num_in_party; i++) { objlist->readToBuf((unsigned char *)member[i].name, PARTY_NAME_MAX_LENGTH + 1); // read in Player name. } objlist->seek(OBJLIST_OFFSET_PARTY_ROSTER); for (i = 0; i < num_in_party; i++) { actor_num = objlist->read1(); member[i].actor = actor_manager->get_actor(actor_num); member[i].actor->set_in_party(true); //member[i].inactive = false; // false unless actor is asleep, or paralyzed (is_immobile) } objlist->seek(OBJLIST_OFFSET_U6_COMBAT_MODE); // combat mode flag. NOTE! this offset is probably U6 specifix FIXME in_combat_mode = (bool)objlist->read1(); MapCoord leader_loc = get_leader_location(); // previous leader location prev_leader_x = leader_loc.x; prev_leader_y = leader_loc.y; reform_party(); autowalk = false; if (actor_manager->get_actor(ACTOR_VEHICLE_ID_N)->get_worktype() == ACTOR_WT_PLAYER) { // WT_U6_PLAYER set_in_vehicle(true); hide(); } for (i = 0; i < PARTY_MAX_MEMBERS; i++) { clear_combat_target(i); } update_light_sources(); update_music(); return true; } bool Party::save(NuvieIO *objlist) { uint16 i; objlist->seek(OBJLIST_OFFSET_NUM_IN_PARTY); objlist->write1(num_in_party); objlist->seek(OBJLIST_OFFSET_PARTY_NAMES); for (i = 0; i < num_in_party; i++) { objlist->writeBuf((unsigned char *)member[i].name, PARTY_NAME_MAX_LENGTH + 1); } objlist->seek(OBJLIST_OFFSET_PARTY_ROSTER); for (i = 0; i < num_in_party; i++) { objlist->write1(member[i].actor->get_actor_num()); } objlist->seek(OBJLIST_OFFSET_U6_COMBAT_MODE); // combat mode flag. NOTE! this offset is probably U6 specifix FIXME objlist->write1((uint8)in_combat_mode); return true; } bool Party::add_actor(Actor *actor) { Converse *converse = game->get_converse(); if (num_in_party < PARTY_MAX_MEMBERS) { actor->set_in_party(true); member[num_in_party].actor = actor; //member[num_in_party].inactive = false; strncpy(member[num_in_party].name, converse->npc_name(actor->id_n), PARTY_NAME_MAX_LENGTH + 1); member[num_in_party].name[PARTY_NAME_MAX_LENGTH] = '\0'; // make sure name is terminated member[num_in_party].combat_position = 0; // member[num_in_party].leader_x = member[0].actor->get_location().x; // member[num_in_party].leader_y = member[0].actor->get_location().y; num_in_party++; reform_party(); return true; } return false; } // remove actor from member array shuffle remaining actors down if required. bool Party::remove_actor(Actor *actor, bool keep_party_flag) { if (defer_removing_dead_members) //we don't want to remove member while inside the Party::follow() method. return true; Game::get_game()->get_event()->set_control_cheat(false); for (int i = 0; i < num_in_party; i++) { if (member[i].actor->id_n == actor->id_n) { if (keep_party_flag == false) { for (int j = 0; j < member[i].actor->get_num_light_sources(); j++) subtract_light_source(); member[i].actor->set_in_party(false); } if (i != (num_in_party - 1)) { for (; i + 1 < num_in_party; i++) member[i] = member[i + 1]; } num_in_party--; reform_party(); if (game->is_new_style()) { Game::get_game()->get_event()->close_gumps(); return true; } //FIXME this is a bit hacky we need a better way to refresh views when things change //maybe using callbacks. //If the last actor dies and was being displayed in a view then we need to set the //view to the new last party member. View *cur_view = Game::get_game()->get_view_manager()->get_current_view(); if (cur_view && cur_view->get_party_member_num() >= num_in_party) cur_view->set_party_member(num_in_party - 1); else if (cur_view) cur_view->set_party_member(cur_view->get_party_member_num()); // needed if a middle party member dies return true; } } return false; } bool Party::remove_dead_actor(Actor *actor) { return remove_actor(actor, PARTY_KEEP_PARTY_FLAG); } bool Party::resurrect_dead_members() { uint16 i; Actor *actor; MapCoord new_pos = get_leader_location(); if (Game::get_game()->get_event()->using_control_cheat()) { Game::get_game()->get_event()->set_control_cheat(false); if (!Game::get_game()->is_new_style()) { Game::get_game()->get_view_manager()->set_inventory_mode(); Game::get_game()->get_view_manager()->get_current_view()->set_party_member(0); } } for (i = 0; i < ACTORMANAGER_MAX_ACTORS; i++) { actor = actor_manager->get_actor(i); if (actor->is_in_party() && actor->is_alive() == false) actor->resurrect(new_pos); } update_light_sources(); // should only be needed for control cheat but it won't hurt to recheck return true; } void Party::split_gold() { } void Party::gather_gold() { } uint8 Party::get_party_size() { return num_in_party; } Actor *Party::get_leader_actor() const { sint8 leader = get_leader(); if (leader < 0) { return nullptr; } return get_actor(leader); } Actor *Party::get_actor(uint8 member_num) const { if (num_in_party <= member_num) return nullptr; return member[member_num].actor; } const char *Party::get_actor_name(uint8 member_num) const { if (num_in_party <= member_num) return nullptr; return member[member_num].name; } /* Returns position of actor in party or -1. */ sint8 Party::get_member_num(const Actor *actor) const { for (int i = 0; i < num_in_party; i++) { if (member[i].actor->id_n == actor->id_n) return i; } return -1; } sint8 Party::get_member_num(uint8 a) const { return (get_member_num(actor_manager->get_actor(a))); } uint8 Party::get_actor_num(uint8 member_num) const { if (num_in_party <= member_num) return 0; // hmm how should we handle this error. return member[member_num].actor->id_n; } /* Rearrange member slot positions based on the number of members and the * selected formation. Used only when adding or removing actors. */ void Party::reform_party() { uint32 n; sint32 x, y, max_x; bool even_row; sint8 leader = get_leader(); if (leader < 0 || num_in_party == 1) return; member[leader].form_x = 0; member[leader].form_y = 0; switch (formation) { case PARTY_FORM_COLUMN: // line up behind Avatar x = 0; y = 1; for (n = leader + 1; n < num_in_party; n++) { member[n].form_x = x; member[n].form_y = y++; if (y == 5) { x += 1; y = 0; } } break; case PARTY_FORM_ROW: // line up left of Avatar x = -1; y = 0; for (n = leader + 1; n < num_in_party; n++) { member[n].form_x = x--; member[n].form_y = y; if (x == -5) { y += 1; x = 0; } } break; case PARTY_FORM_DELTA: // open triangle formation with Avatar at front x = -1; y = 1; for (n = leader + 1; n < num_in_party; n++) { member[n].form_x = x; member[n].form_y = y; // alternate X once, then move down x = -x; if (x == 0 || x < 0) { x -= 1; ++y; } if (y == 5) { // start at top of triangle y -= ((-x) - 1); x = 0; } } break; // case PARTY_FORM_COMBAT: // positions determined by COMBAT mode // break; case PARTY_FORM_REST: // special formation used while Resting member[1].form_x = 0; member[1].form_y = -2; member[2].form_x = 1; member[2].form_y = -1; member[3].form_x = -1; member[3].form_y = -1; member[4].form_x = 1; member[4].form_y = 0; member[5].form_x = -1; member[5].form_y = -2; member[6].form_x = 1; member[6].form_y = -2; member[7].form_x = -1; member[7].form_y = 0; break; case PARTY_FORM_STANDARD: // U6 default: // put first follower behind or behind and to the left of Avatar member[leader + 1].form_x = (num_in_party >= 3) ? -1 : 0; member[leader + 1].form_y = 1; x = y = 1; even_row = false; for (n = leader + 2, max_x = 1; n < num_in_party; n++) { member[n].form_x = x; member[n].form_y = y; // alternate & move X left/right by 2 x = (x == 0) ? x - 2 : (x < 0) ? -x : -x - 2; if (x > max_x || (x < 0 && -x > max_x)) { // reached row max. ++y; even_row = !even_row; // next row ++max_x; // increase row max. x = even_row ? 0 : -1; // next guy starts at center or left by 2 } } } } /* Returns number of person leading the party (the first active member), or -1 * if the party has no leader and can't move. */ sint8 Party::get_leader() const { for (int m = 0; m < num_in_party; m++) if (member[m].actor->is_immobile() == false && member[m].actor->is_charmed() == false) return m; return -1; } /* Get map location of a party member. */ MapCoord Party::get_location(uint8 m) const { return (member[m].actor->get_location()); } /* Get map location of the first active party member. */ MapCoord Party::get_leader_location() const { sint8 m = get_leader(); MapCoord loc; if (m >= 0) loc = member[m].actor->get_location(); return loc; } /* Returns absolute location where party member `m' SHOULD be (based on party * formation and leader location. */ MapCoord Party::get_formation_coords(uint8 m) const { MapCoord a = get_location(m); // my location MapCoord l = get_leader_location(); // leader location sint8 leader = get_leader(); if (leader < 0) return a; uint8 ldir = member[leader].actor->get_direction(); // leader direction // intended location uint16 x = (ldir == NUVIE_DIR_N) ? l.x + member[m].form_x : // X (ldir == NUVIE_DIR_E) ? l.x - member[m].form_y : (ldir == NUVIE_DIR_S) ? l.x - member[m].form_x : (ldir == NUVIE_DIR_W) ? l.x + member[m].form_y : a.x; uint16 y = (ldir == NUVIE_DIR_N) ? l.y + member[m].form_y : // Y (ldir == NUVIE_DIR_E) ? l.y + member[m].form_x : (ldir == NUVIE_DIR_S) ? l.y - member[m].form_y : (ldir == NUVIE_DIR_W) ? l.y - member[m].form_x : a.y; return (MapCoord(WRAPPED_COORD(x, a.z), WRAPPED_COORD(y, a.z), a.z // Z )); } /* Update the actual locations of the party actors on the map, so that they are * in the proper formation. */ void Party::follow(sint8 rel_x, sint8 rel_y) { Common::Array try_again; try_again.resize(get_party_max()); sint8 leader = get_leader(); if (leader <= -1) return; if (is_in_combat_mode()) { // just update everyone's combat mode for (int p = 0; p < get_party_size(); p++) get_actor(p)->set_worktype(get_actor(p)->get_combat_mode()); return; } defer_removing_dead_members = true; // set previous leader location first, just in case the leader changed prev_leader_x = WRAPPED_COORD(member[leader].actor->x - rel_x, member[leader].actor->z); prev_leader_y = member[leader].actor->y - rel_y; // PASS 1: Keep actors chained together. for (uint32 p = (leader + 1); p < num_in_party; p++) { if (member[p].actor->is_immobile()) continue; try_again[p] = false; if (!pathfinder->follow_passA(p)) try_again[p] = true; } // PASS 2: Catch up to party. for (uint32 p = (leader + 1); p < num_in_party; p++) { if (member[p].actor->is_immobile()) continue; if (try_again[p]) pathfinder->follow_passA(p); pathfinder->follow_passB(p); if (!pathfinder->is_contiguous(p)) { sint8 l = get_leader(); if (l >= 0) { DEBUG(0, LEVEL_DEBUGGING, "%s is looking for %s.\n", get_actor_name(p), get_actor_name(l)); } pathfinder->seek_leader(p); // enter/update seek mode } else if (member[p].actor->get_pathfinder()) pathfinder->end_seek(p); get_actor(p)->set_moves_left(get_actor(p)->get_moves_left() - 10); get_actor(p)->set_worktype(0x01); // revert to normal worktype } defer_removing_dead_members = false; //remove party members that died during follow routine. for (int p = get_party_size() - 1; p >= 0; p--) { Actor *a = get_actor(p); if (a->is_alive() == false) remove_actor(a, PARTY_KEEP_PARTY_FLAG); } } // Returns true if anyone in the party has a matching object. bool Party::has_obj(uint16 obj_n, uint8 quality, bool match_zero_qual) const { uint16 i; for (i = 0; i < num_in_party; i++) { if (member[i].actor->inventory_get_object(obj_n, quality, match_zero_qual) != nullptr) // we got a match return true; } return false; } // Removes the first occurrence of an object in the party. bool Party::remove_obj(uint16 obj_n, uint8 quality) { uint16 i; Obj *obj; for (i = 0; i < num_in_party; i++) { obj = member[i].actor->inventory_get_object(obj_n, quality); if (obj != nullptr) { if (member[i].actor->inventory_remove_obj(obj)) { delete_obj(obj); return true; } } } return false; } // Returns the actor id of the first person in the party to have a matching object. Actor *Party::who_has_obj(uint16 obj_n, uint8 quality, bool match_qual_zero) { uint16 i; for (i = 0; i < num_in_party; i++) { if (member[i].actor->inventory_get_object(obj_n, quality, match_qual_zero) != nullptr) return member[i].actor; } return nullptr; } Obj *Party::get_obj(uint16 obj_n, uint8 quality, bool match_qual_zero, uint8 frame_n, bool match_frame_n) { Obj *obj; for (uint16 i = 0; i < num_in_party; i++) { obj = member[i].actor->inventory_get_object(obj_n, quality, match_qual_zero, frame_n, match_frame_n); if (obj) return obj; } return nullptr; } /* Is EVERYONE in the party at or near the coordinates? */ bool Party::is_at(uint16 x, uint16 y, uint8 z, uint32 threshold) const { for (uint32 p = 0; p < num_in_party; p++) { MapCoord loc(x, y, z); if (!member[p].actor->is_nearby(loc, threshold)) return false; } return true; } bool Party::is_at(const MapCoord &xyz, uint32 threshold) const { return is_at(xyz.x, xyz.y, xyz.z, threshold); } /* Is ANYONE in the party at or near the coordinates? */ bool Party::is_anyone_at(uint16 x, uint16 y, uint8 z, uint32 threshold) const { for (uint32 p = 0; p < num_in_party; p++) { MapCoord loc(x, y, z); if (member[p].actor->is_nearby(loc, threshold)) return true; } return false; } bool Party::is_anyone_at(const MapCoord &xyz, uint32 threshold) const { return is_anyone_at(xyz.x, xyz.y, xyz.z, threshold); } bool Party::contains_actor(const Actor *actor) const { if (get_member_num(actor) >= 0) return true; return false; } bool Party::contains_actor(uint8 actor_num) const { return (contains_actor(actor_manager->get_actor(actor_num))); } void Party::set_in_combat_mode(bool value) { in_combat_mode = value; actor_manager->set_combat_movement(value); if (in_combat_mode) { for (int p = 0; p < get_party_size(); p++) get_actor(p)->set_worktype(get_actor(p)->get_combat_mode()); //set combat worktype } else { for (int p = 0; p < get_party_size(); p++) get_actor(p)->set_worktype(ACTOR_WT_FOLLOW); //set back to follow party leader. } // if(combat_changes_music) update_music(); if (game->get_command_bar() != nullptr) { game->get_command_bar()->set_combat_mode(in_combat_mode); } } void Party::update_music() { SoundManager *s = Game::get_game()->get_sound_manager(); MapCoord pos; if (in_vehicle && vehicles_change_music) { s->musicPlayFrom("boat"); return; } else if (in_combat_mode && combat_changes_music) { s->musicPlayFrom("combat"); return; } pos = get_leader_location(); switch (pos.z) { case 0 : s->musicPlayFrom("random"); break; case 5 : s->musicPlayFrom("gargoyle"); break; default : s->musicPlayFrom("dungeon"); break; } return; } void Party::heal() { uint16 i; for (i = 0; i < num_in_party; i++) { member[i].actor->heal(); } return; } void Party::cure() { for (uint16 i = 0; i < num_in_party; i++) { member[i].actor->cure(); } } void Party::set_ethereal(bool ethereal) { for (uint16 i = 0; i < num_in_party; i++) { member[i].actor->set_ethereal(ethereal); } } void Party::show() { uint16 i; for (i = 0; i < num_in_party; i++) { member[i].actor->show(); } return; } void Party::hide() { uint16 i; for (i = 0; i < num_in_party; i++) { member[i].actor->hide(); } return; } /* Move and center everyone in the party to one location. */ bool Party::move(uint16 dx, uint16 dy, uint8 dz) { for (sint32 m = 0; m < num_in_party; m++) if (!member[m].actor->move(dx, dy, dz, ACTOR_FORCE_MOVE)) return false; return true; } /* Automatically walk (timed) to a destination, and then teleport to new * location (optional). Used to enter/exit dungeons. * (step_delay 0 = default speed) */ void Party::walk(MapCoord *walkto, MapCoord *teleport, uint32 step_delay) { if (step_delay) new TimedPartyMove(walkto, teleport, step_delay); else new TimedPartyMove(walkto, teleport); game->pause_world(); // other actors won't move game->pause_user(); // don't allow input // view will snap back to player after everyone has moved game->get_player()->set_mapwindow_centered(false); autowalk = true; } /* Enter a moongate and teleport to a new location. * (step_delay 0 = default speed) */ void Party::walk(Obj *moongate, MapCoord *teleport, uint32 step_delay) { MapCoord walkto(moongate->x, moongate->y, moongate->z); if (step_delay) new TimedPartyMove(&walkto, teleport, moongate, step_delay); else new TimedPartyMove(&walkto, teleport, moongate); game->pause_world(); // other actors won't move game->pause_user(); // don't allow input // view will snap back to player after everyone has moved game->get_player()->set_mapwindow_centered(false); autowalk = true; } /* Automatically walk (timed) to vehicle. (step_delay 0 = default speed) */ void Party::enter_vehicle(Obj *ship_obj, uint32 step_delay) { MapCoord walkto(ship_obj->x, ship_obj->y, ship_obj->z); dismount_from_horses(); if (step_delay) new TimedPartyMoveToVehicle(&walkto, ship_obj, step_delay); else new TimedPartyMoveToVehicle(&walkto, ship_obj); game->pause_world(); // other actors won't move game->pause_user(); // don't allow input // view will snap back to player after everyone has moved game->get_player()->set_mapwindow_centered(false); autowalk = true; } void Party::exit_vehicle(uint16 x, uint16 y, uint16 z) { if (is_in_vehicle() == false) return; Actor *vehicle_actor = actor_manager->get_actor(0); show(); vehicle_actor->unlink_surrounding_objects(); vehicle_actor->hide(); vehicle_actor->set_worktype(0); Player *player = game->get_player(); player->set_actor(get_actor(0)); player->move(x, y, z, false); vehicle_actor->obj_n = 0;//OBJ_U6_NO_VEHICLE; vehicle_actor->frame_n = 0; vehicle_actor->init(); vehicle_actor->move(0, 0, 0, ACTOR_FORCE_MOVE); set_in_vehicle(false); } void Party::set_in_vehicle(bool value) { in_vehicle = value; if (vehicles_change_music) update_music(); if (value) { if (in_combat_mode == true) set_in_combat_mode(false); // break off combat when boarding a vehicle } return; } /* Done automatically walking, return view to player character. */ void Party::stop_walking(bool force_music_change) { game->get_player()->set_mapwindow_centered(true); game->unpause_world(); // allow user input, unfreeze actors game->unpause_user(); autowalk = false; if (force_music_change || vehicles_change_music) update_music(); } void Party::dismount_from_horses() { UseCode *usecode = Game::get_game()->get_usecode(); for (uint32 m = 0; m < num_in_party; m++) { if (member[m].actor->obj_n == OBJ_U6_HORSE_WITH_RIDER) { Obj *my_obj = member[m].actor->make_obj(); usecode->use_obj(my_obj, member[m].actor); delete_obj(my_obj); } } return; } Actor *Party::get_slowest_actor() { Actor *actor = nullptr; sint8 begin = get_leader(); if (begin >= 0) { actor = member[begin].actor; sint8 moves = actor->get_moves_left(); for (uint32 m = begin + 1; m < num_in_party; m++) { sint8 select_moves = member[m].actor->get_moves_left(); if (member[m].actor->is_immobile() == false && (select_moves < moves)) { moves = select_moves; actor = member[m].actor; } } } return actor; } /* Gather everyone around a campfire to Rest. */ void Party::rest_gather() { Actor *player_actor = get_leader_actor(); if (player_actor) { MapCoord player_loc = player_actor->get_location(); rest_campfire = new_obj(OBJ_U6_CAMPFIRE, 1, player_loc.x, player_loc.y, player_loc.z); rest_campfire->set_temporary(); rest_campfire->qty = 1; //this is set so the campfire may be destroyed by being attacked. game->get_obj_manager()->add_obj(rest_campfire, true); // addOnTop game->get_player()->set_mapwindow_centered(false); game->pause_user(); new TimedRestGather(player_loc.x, player_loc.y); } } /* Start Resting for the specified number of hours, optionally with a party * member standing guard. */ void Party::rest_sleep(uint8 hours, sint16 guard) { // FIXME: change music to Stones when asking "How many hours?", change to // a random song when finished camping (or if cancelled) new TimedRest(hours, guard >= 0 ? member[guard].actor : 0, rest_campfire); } bool Party::can_rest(Std::string &err_str) { Map *map_ = game->get_game_map(); Player *player = game->get_player(); Actor *pActor = player->get_actor(); MapCoord loc = pActor->get_location(); ActorList *enemies = nullptr; ActorList *all_actors = nullptr; if (is_in_combat_mode()) { if (Game::get_game()->get_game_type() == NUVIE_GAME_SE) err_str = "\nNot while in Combat mode!"; else if (Game::get_game()->get_game_type() == NUVIE_GAME_MD) err_str = "- Not while in Combat!"; else err_str = "-Not while in Combat!"; } else if (is_in_vehicle() && pActor->get_obj_n() != OBJ_U6_SHIP) // player is a vehicle err_str = "-Can not be repaired!"; else if (Game::get_game()->get_game_type() == NUVIE_GAME_U6 && game->get_map_window()->in_town()) err_str = "-Only in the wilderness!"; else if ((enemies = pActor->find_enemies())) { if (Game::get_game()->get_game_type() == NUVIE_GAME_MD) err_str = "\nNot while foes are near!"; if (Game::get_game()->get_game_type() == NUVIE_GAME_SE) err_str = "- Not while foes are near!"; else err_str = "-Not while foes are near!"; } else if ((all_actors = actor_manager->filter_party(actor_manager->filter_distance(actor_manager->get_actor_list(), loc.x, loc.y, loc.z, 5))) && !all_actors->empty() && !is_in_vehicle()) { if (Game::get_game()->get_game_type() == NUVIE_GAME_U6) err_str = "-Not while others are near!"; else err_str = "\nIt's too noisy to sleep here!"; delete all_actors; } else if (!player->in_party_mode()) err_str = "-Not in solo mode!"; else if (!is_in_vehicle() && !map_->is_passable(loc.x - 1, loc.y - 1, loc.x + 1, loc.y + 1, loc.z) && Game::get_game()->get_game_type() != NUVIE_GAME_SE) err_str = "-Not enough room!"; // FIXME: for ships the original checks all squares around the ship. Do we really need this? else if (is_horsed()) err_str = "-Dismount first!"; else return true; delete enemies; return false; } bool Party::is_horsed() const { for (int p = 0; p < num_in_party; p++) if (member[p].actor->get_obj_n() == OBJ_U6_HORSE_WITH_RIDER) return true; return false; } bool Party::is_everyone_horsed() const { for (int p = 0; p < num_in_party; p++) if (member[p].actor->get_obj_n() != OBJ_U6_HORSE_WITH_RIDER) return false; return true; } Obj *Party::get_food() { for (int p = 0; p < num_in_party; p++) { Obj *food = member[p].actor->inventory_get_food(); if (food) return food; } return 0; } void Party::set_combat_target(uint8 member_num, Actor *target) { if (num_in_party <= member_num) return; member[member_num].target.type = TARGET_ACTOR; member[member_num].target.actor_num = target->get_actor_num(); } void Party::set_combat_target(uint8 member_num, MapCoord target) { if (num_in_party <= member_num) return; member[member_num].target.type = TARGET_LOCATION; member[member_num].target.loc = target; } void Party::clear_combat_target(uint8 member_num) { if (member_num >= PARTY_MAX_MEMBERS) return; member[member_num].target.type = TARGET_NONE; member[member_num].target.loc = MapCoord(); member[member_num].target.actor_num = 0; } CombatTarget Party::get_combat_target(uint8 member_num) { if (num_in_party <= member_num) { CombatTarget noTarget; noTarget.type = TARGET_NONE; noTarget.loc = MapCoord(); noTarget.actor_num = 0; return noTarget; } return member[member_num].target; } void Party::update_light_sources() { lightsources = 0; for (int i = 0; i < num_in_party; i++) { for (int j = 0; j < member[i].actor->get_num_light_sources(); j++) add_light_source(); } if (game->get_event()->using_control_cheat()) { for (int i = 0; i < game->get_player()->get_actor()->get_num_light_sources(); i++) add_light_source(); } game->get_map_window()->updateAmbience(); } bool Party::has_light_source() { if (!game->get_player()->get_actor()) return false; if (lightsources > 0) { // the original engine didn't care about distance if (game->get_event()->using_control_cheat()) { if (game->get_player()->get_actor()->get_num_light_sources() > 0) return true; else return false; } for (int i = 0; i < num_in_party; i++) { if (member[i].actor->get_num_light_sources() > 0) { if (!game->get_map_window()->tile_is_black(member[i].actor->x, member[i].actor->y) && member[i].actor->is_nearby(game->get_player()->get_actor())) // within 5 tiles of player return true; } } } return false; } } // End of namespace Nuvie } // End of namespace Ultima