/* 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/misc/u6_llist.h" #include "ultima/nuvie/core/game.h" #include "ultima/nuvie/core/game_clock.h" #include "ultima/nuvie/gui/widgets/map_window.h" #include "ultima/nuvie/core/obj_manager.h" #include "ultima/nuvie/actors/actor_manager.h" #include "ultima/nuvie/views/view_manager.h" #include "ultima/nuvie/usecode/u6_usecode.h" #include "ultima/nuvie/core/party.h" #include "ultima/nuvie/pathfinder/combat_path_finder.h" #include "ultima/nuvie/pathfinder/seek_path.h" #include "ultima/nuvie/core/converse.h" #include "ultima/nuvie/core/effect.h" #include "ultima/nuvie/actors/actor.h" #include "ultima/nuvie/script/script.h" #include "ultima/nuvie/core/events.h" #include "ultima/nuvie/actors/u6_actor.h" #include "ultima/shared/std/containers.h" namespace Ultima { namespace Nuvie { extern const uint8 walk_frame_tbl[4] = {0, 1, 2, 1}; class ActorManager; Actor::Actor(Map *m, ObjManager *om, GameClock *c) : sched(nullptr), obj_inventory(nullptr), map(m), obj_manager(om), usecode(nullptr), pathfinder(nullptr), direction(NUVIE_DIR_N), walk_frame(0), ethereal(false), can_move(true), temp_actor(false), visible_flag(true), met_player(false), worktype(0), sched_pos(0), move_time(0), num_schedules(0), alignment(ACTOR_ALIGNMENT_NEUTRAL), moves(0), light(0), status_flags(0), talk_flags(0), obj_flags(0), body_armor_class(0), readied_armor_class(0), custom_tile_tbl(nullptr), id_n(0), x(0), y(0), z(0), obj_n(0), frame_n(0), base_obj_n(0), old_frame_n(0), movement_flags(0), strength(0), dex(0), intelligence(0), hp(0), level(0), exp(0), magic(0), combat_mode(0), _clock(c) { memset(readied_objects, 0, sizeof(readied_objects)); clear_error(); } Actor::~Actor() { // free sched array if (sched != nullptr) { Schedule **cursched = sched; while (*cursched != nullptr) free(*cursched++); free(sched); } if (pathfinder) delete pathfinder; for (uint8 location = 0; location < ACTOR_MAX_READIED_OBJECTS; location++) { if (readied_objects[location] != nullptr) { delete readied_objects[location]; } } if (custom_tile_tbl) { delete custom_tile_tbl; } return; } bool Actor::init(uint8 obj_status) { if (pathfinder) delete_pathfinder(); set_moves_left(dex); return true; } void Actor::init_from_obj(Obj *obj, bool change_base_obj) { x = obj->x; y = obj->y; z = obj->z; if (change_base_obj) { base_obj_n = obj->obj_n; } obj_n = obj->obj_n; frame_n = obj->frame_n; obj_flags = obj->status; set_dead_flag(false); init(); show(); return; } /* Returns true if another NPC `n' is in proximity to location `where'. */ bool Actor::is_nearby(const MapCoord &where, uint8 thresh) const { MapCoord here(x, y, z); if (here.xdistance(where) <= thresh && here.ydistance(where) <= thresh && z == where.z) return true; return false; } bool Actor::is_nearby(const Actor *other) const { MapCoord there(other->get_location()); return is_nearby(there); } bool Actor::is_nearby(uint8 actor_num) const { return is_nearby(Game::get_game()->get_actor_manager()->get_actor(actor_num)); } bool Actor::is_at_position(const Obj *obj) const { if (obj->x == x && obj->y == y && obj->z == z) return true; return false; } bool Actor::is_passable() const { if (ethereal) return true; const Tile *tile = obj_manager->get_obj_tile(obj_n, frame_n); return tile->passable; } bool Actor::is_in_vehicle() const { if (is_in_party() == false) return false; return Game::get_game()->get_party()->is_in_vehicle(); } void Actor::get_location(uint16 *ret_x, uint16 *ret_y, uint8 *ret_level) const { if (ret_x) *ret_x = x; if (ret_y) *ret_y = y; if (ret_level) *ret_level = z; } MapCoord Actor::get_location() const { return MapCoord(x, y, z); } uint16 Actor::get_tile_num() const { if (custom_tile_tbl) { return get_custom_tile_num(obj_n); } return obj_manager->get_obj_tile_num(obj_n); } uint16 Actor::get_tile_num(uint16 obj_num) const { if (custom_tile_tbl) { return get_custom_tile_num(obj_num); } return obj_manager->get_obj_tile_num(obj_num); } uint16 Actor::get_custom_tile_num(uint16 obj_num) const { if (custom_tile_tbl) { Common::HashMap::iterator it; it = custom_tile_tbl->find(obj_num); if (it != custom_tile_tbl->end()) { return it->_value; } } return obj_manager->get_obj_tile_num(obj_num); } Tile *Actor::get_tile() const { return Game::get_game()->get_tile_manager()->get_tile(get_tile_num() + frame_n); } uint8 Actor::get_worktype() { return worktype; } uint8 Actor::get_sched_worktype() { if (sched[sched_pos]) return sched[sched_pos]->worktype; return 0; //no worktype } uint16 Actor::get_downward_facing_tile_num() const { return obj_manager->get_obj_tile_num(obj_n) + frame_n; } /* Set direction faced by actor and change walk frame. */ void Actor::set_direction(NuvieDir d) { if (is_alive() == false || is_immobile()) return; if (d < 4) direction = d; walk_frame = (walk_frame + 1) % 4; frame_n = direction * 4 + walk_frame_tbl[walk_frame]; } /* Set direction as if moving in relative direction rel_x,rel_y. */ void Actor::set_direction(sint16 rel_x, sint16 rel_y) { NuvieDir new_direction = direction; if (rel_x == 0 && rel_y == 0) // nowhere (just update frame) new_direction = direction; else if (rel_x == 0) // up or down new_direction = (rel_y < 0) ? NUVIE_DIR_N : NUVIE_DIR_S; else if (rel_y == 0) // left or right new_direction = (rel_x < 0) ? NUVIE_DIR_W : NUVIE_DIR_E; // Add 2 to current direction if it is opposite the new direction else if (rel_x < 0 && rel_y < 0) { // up-left if (direction != NUVIE_DIR_N && direction != NUVIE_DIR_W) new_direction = static_cast(direction + 2); } else if (rel_x > 0 && rel_y < 0) { // up-right if (direction != NUVIE_DIR_N && direction != NUVIE_DIR_E) new_direction = static_cast(direction + 2); } else if (rel_x < 0 && rel_y > 0) { // down-left if (direction != NUVIE_DIR_S && direction != NUVIE_DIR_W) new_direction = static_cast(direction + 2); } else if (rel_x > 0 && rel_y > 0) { // down-right if (direction != NUVIE_DIR_S && direction != NUVIE_DIR_E) new_direction = static_cast(direction + 2); } // wrap if (new_direction >= 4) new_direction = static_cast(new_direction - 4); set_direction(new_direction); } void Actor::face_location(const MapCoord &loc) { face_location(loc.x, loc.y); } /* Set direction towards an x,y location on the map. */ void Actor::face_location(uint16 lx, uint16 ly) { set_direction(lx - x, ly - y); } #if 0 /* Set direction towards an x,y location on the map. */ void Actor::face_location(uint16 lx, uint16 ly) { uint16 xdiff = abs(x - lx), ydiff = abs(y - ly); if (ydiff) { if (y < ly && direction != NUVIE_DIR_S) set_direction(NUVIE_DIR_S); // down else if (y > ly && direction != NUVIE_DIR_N) set_direction(NUVIE_DIR_N); // up } if (xdiff) { if (x < lx && direction != NUVIE_DIR_E) set_direction(NUVIE_DIR_E); // right else if (x > lx && direction != NUVIE_DIR_W) set_direction(NUVIE_DIR_W); // left } } #endif void Actor::face_actor(Actor *a) { uint16 ax, ay; uint8 al; a->get_location(&ax, &ay, &al); face_location(ax, ay); } void Actor::set_poisoned(bool poisoned) { if (poisoned) { status_flags |= ACTOR_STATUS_POISONED; new HitEffect(this); // no direct hp loss } else { status_flags &= (0xff ^ ACTOR_STATUS_POISONED); } if (is_in_party()) Game::get_game()->get_view_manager()->update(); } /* Returns the proper (NPC) name of this actor if the Player knows it, or their * description if the name is unknown. */ const char *Actor::get_name(bool force_real_name) { ActorManager *actor_manager = Game::get_game()->get_actor_manager(); Converse *converse = Game::get_game()->get_converse(); const Party *party = Game::get_game()->get_party(); const char *talk_name = nullptr; // name from conversation script bool statue = (Game::get_game()->get_game_type() == NUVIE_GAME_U6 && id_n >= 189 && id_n <= 200); if (is_alive() && is_in_party()) { sint8 party_pos = party->get_member_num(this); if (party_pos != -1) name = party->get_actor_name((uint8)party_pos); } else if ((is_met() || is_in_party() || force_real_name) && (talk_name = converse->npc_name(id_n)) // assignment && !statue) name = talk_name; else name = actor_manager->look_actor(this, false); return name.c_str(); } void Actor::add_surrounding_obj(Obj *obj) { obj->set_actor_obj(true); surrounding_objects.push_back(obj); } void Actor::unlink_surrounding_objects(bool make_objects_temporary) { // if(make_objects_temporary) { for (Obj *obj : surrounding_objects) { if (make_objects_temporary) obj->set_temporary(); obj->set_actor_obj(false); } } surrounding_objects.clear(); } bool Actor::moveRelative(sint16 rel_x, sint16 rel_y, ActorMoveFlags flags) { return move(x + rel_x, y + rel_y, z, flags); } bool Actor::check_move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) { const bool ignore_actors = flags & ACTOR_IGNORE_OTHERS; const bool ignore_danger = flags & ACTOR_IGNORE_DANGER; // bool ignore_danger = true; /* uint16 pitch = map->get_width(new_z); if(new_x < 0 || new_x >= pitch) return(false); if(new_y < 0 || new_y >= pitch) return(false); */ if (!ignore_actors) { const bool ignoreParty = flags & ACTOR_IGNORE_PARTY_MEMBERS; auto isBlocker = [=](const Actor *a) { // we can move over or under some actors. eg mice, dragons etc. return !(a->can_be_passed(this, ignoreParty)); }; if (Game::get_game()->get_actor_manager()->findActorAt(new_x, new_y, new_z, isBlocker, true, false)) return false; } // if(map->is_passable(new_x,new_y,new_z) == false) // return(false); if (!ignore_danger) if (map->is_damaging(new_x, new_y, new_z)) return false; return true; } bool Actor::check_moveRelative(sint16 rel_x, sint16 rel_y, ActorMoveFlags flags) { return check_move(x + rel_x, y + rel_y, z, flags); } bool Actor::can_be_moved() { return can_move; } bool Actor::can_be_passed(const Actor *other, bool ignoreParty) const { // ethereal actors can always pass us return other->ethereal || is_passable(); } uint8 Actor::get_object_readiable_location(Obj *obj) { int loc = Game::get_game()->get_script()->call_obj_get_readiable_location(obj); if (loc >= 0) return (uint8)loc; return ACTOR_NOT_READIABLE; } bool Actor::move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) { // assert(new_z < 6); // shouldn't need to check anymore //const uint8 move_cost = 5; // base cost to move bool force_move = (bool)(flags & ACTOR_FORCE_MOVE); bool open_doors = (bool)(flags & ACTOR_OPEN_DOORS); bool ignore_actors = (bool)(flags & ACTOR_IGNORE_OTHERS); bool ignore_danger = (bool)(flags & ACTOR_IGNORE_DANGER); // bool ignore_danger = true; bool ignore_moves = (bool)(flags & ACTOR_IGNORE_MOVES); MapCoord oldpos(x, y, z); clear_error(); if (!usecode) usecode = obj_manager->get_usecode(); // no moves left if (!(force_move || ignore_moves) && moves <= 0) { set_error(ACTOR_OUT_OF_MOVES); // return false; DEBUG(0, LEVEL_WARNING, "actor %d is out of moves %d\n", id_n, moves); } // blocking actors are checked for later Obj *obj = obj_manager->get_obj(new_x, new_y, new_z, OBJ_SEARCH_TOP, OBJ_INCLUDE_IGNORED); //we include ignored objects here to pick up the sacred quest blocking object. if (!force_move && !check_move(new_x, new_y, new_z, ACTOR_IGNORE_DANGER | ACTOR_IGNORE_OTHERS)) { // open door if (!(obj && usecode->is_unlocked_door(obj) && open_doors) || (!usecode->use_obj(obj, this))) { set_error(ACTOR_BLOCKED_BY_OBJECT); error_struct.blocking_obj = obj; return false; // blocked by object or map tile } } // avoid dangerous objects if (!ignore_danger && !force_move && ((is_in_party() && map->is_damaging(new_x, new_y, new_z)) || (obj && obj_manager->is_damaging(new_x, new_y, new_z)))) { set_error(ACTOR_BLOCKED_BY_OBJECT); error_struct.blocking_obj = obj; return false; } // usecode must allow movement if (obj && usecode->has_passcode(obj)) { if (!usecode->pass_obj(obj, this, new_x, new_y) && !force_move) { // calling item is this actor set_error(ACTOR_BLOCKED_BY_OBJECT); error_struct.blocking_obj = obj; return false; } } Game *game = Game::get_game(); if (!ignore_actors && !force_move) { const bool ignoreParty = flags & ACTOR_IGNORE_PARTY_MEMBERS; auto isBlocker = [=](const Actor *a) { return !a->can_be_passed(this, ignoreParty) && (!game->get_party()->get_autowalk() || a->is_visible()); }; Actor *const other = game->get_actor_manager()->findActorAt(new_x, new_y, new_z, isBlocker, true, false); if (other) { set_error(ACTOR_BLOCKED_BY_ACTOR); error_struct.blocking_actor = other; return false; // blocked by actor } } // move x = WRAPPED_COORD(new_x, new_z); // FIXME: this is probably needed because PathFinder is not wrapping coords y = WRAPPED_COORD(new_y, new_z); z = new_z; can_move = true; //FIXME move this into Player::moveRelative() /* if(!(force_move || ignore_moves) && (id_n == game->get_player()->get_actor()->id_n || id_n == 0 || (is_in_party() && game->get_party()->is_in_combat_mode() == false))) // subtract from moves left for party members only. Other actors have their movement points deducted in actor_update_all() { set_moves_left(moves - (move_cost+map->get_impedance(oldpos.x, oldpos.y, oldpos.z))); if(oldpos.x != x && oldpos.y != y) // diagonal move, double cost set_moves_left(moves - (move_cost+map->get_impedance(oldpos.x, oldpos.y, oldpos.z))); } */ // post-move // close door if (open_doors) { obj = obj_manager->get_obj(oldpos.x, oldpos.y, z); if (obj && usecode->is_door(obj)) usecode->use_obj(obj, this); } // re-center map if actor is player character if (id_n == game->get_player()->get_actor()->id_n && game->get_player()->is_mapwindow_centered()) game->get_map_window()->centerMapOnActor(this); // allows a delay to be set on actor movement, in lieu of using animations move_time = _clock->get_ticks(); return true; } void Actor::update() { if (!is_alive()) //we don't need to update dead actors. return; if (pathfinder) { // NOTE: don't delete pathfinder right after walking, because the scheduled // activity still needs to be checked, and depends on pathfinder existing if (pathfinder->reached_goal()) delete_pathfinder(); else walk_path(); } // update_time = clock->get_ticks(); moved to move() } /* Returns true if actor moved. */ bool Actor::walk_path() { pathfinder->update_location(); // set location from actor, if already moved // validate path and get move MapCoord next_loc, loc(x, y, z); if (!pathfinder->get_next_move(next_loc)) // nothing to do here return false; // FIXME: move to SchedPathFinder (or delete; worktype will handle refresh) if (next_loc == loc) { // ran out of steps? get a new path if (pathfinder->have_path()) pathfinder->find_path(); return false; } if (!move(next_loc.x, next_loc.y, next_loc.z, ACTOR_OPEN_DOORS)) return false; // don't get a new path; probably just blocked by an actor set_direction(x - loc.x, y - loc.y); pathfinder->actor_moved(); return true; } // gz 255 = current map plane void Actor::pathfind_to(uint16 gx, uint16 gy, uint8 gz) { if (gz == 255) gz = z; MapCoord d(gx, gy, gz); pathfind_to(d); } void Actor::pathfind_to(const MapCoord &d) { if (pathfinder) { pathfinder->set_actor(this); pathfinder->set_goal(d); } else set_pathfinder(new ActorPathFinder(this, d), new SeekPath); pathfinder->update_location(); } // actor will take management of new_pf, and delete it when no longer needed void Actor::set_pathfinder(ActorPathFinder *new_pf, Path *path_type) { if (pathfinder != nullptr && pathfinder != new_pf) delete_pathfinder(); pathfinder = new_pf; if (path_type != 0) pathfinder->set_search(path_type); } void Actor::delete_pathfinder() { delete pathfinder; pathfinder = nullptr; } void Actor::set_in_party(bool state) { if (Game::get_game()->is_ethereal()) set_ethereal(state); //in_party = state; delete_pathfinder(); if (state == true) { // joined // obj_n = base_obj_n; U6Actor::set_worktype can_move = true; set_worktype(0x01); // U6_IN_PARTY status_flags |= ACTOR_STATUS_IN_PARTY; if (!is_charmed()) set_alignment(ACTOR_ALIGNMENT_GOOD); else set_old_alignment(ACTOR_ALIGNMENT_GOOD); } else { // left if (is_alive() == true) { if (is_invisible()) visible_flag = false; set_worktype(0x8f); // U6_WANDER_AROUND status_flags ^= ACTOR_STATUS_IN_PARTY; inventory_drop_all(); // needs to be after party status change if (is_charmed()) set_old_alignment(ACTOR_ALIGNMENT_NEUTRAL); else set_alignment(ACTOR_ALIGNMENT_NEUTRAL); } } } /*void Actor::attack(const MapCoord &pos) { return; }*/ Obj *Actor::get_weapon_obj(sint8 readied_obj_location) { if (readied_obj_location != ACTOR_NO_READIABLE_LOCATION && readied_objects[readied_obj_location] && readied_objects[readied_obj_location]->obj != nullptr) return readied_objects[readied_obj_location]->obj; return nullptr; } uint8 Actor::get_range(uint16 target_x, uint16 target_y) { sint16 off_x, off_y; uint16 map_pitch = map->get_width(z); if (target_x <= x) off_x = x - target_x; else { //target_x > x if (target_x - x < 8) //small positive offset off_x = target_x - x; else { // target wrapped around the map. if (map_pitch - target_x + x < 11) off_x = target_x - map_pitch - x; //negative offset else off_x = 9; // x out of range } } if (target_y <= y) off_y = y - target_y; else { //target_y > y if (target_y - y < 8) //small positive offset off_y = target_y - y; else { // target wrapped around the map. if (map_pitch - target_y + y < 11) off_y = target_y - map_pitch - y; //negative offset else off_y = 9; // y out of range } } return Game::get_game()->get_script()->call_get_combat_range(abs(off_x), abs(off_y)); } bool Actor::weapon_can_hit(const CombatType *weapon, uint16 target_x, uint16 target_y) { if (!weapon) return false; Script *script = Game::get_game()->get_script(); if (get_range(target_x, target_y) > script->call_get_weapon_range(weapon->obj_n)) return false; return true; } // attack another actor with melee attack or a weapon (short or long range) void Actor::attack(sint8 readied_obj_location, MapCoord target, Actor *foe) { const uint8 attack_cost = 10; // base cost to attack Game::get_game()->get_script()->call_actor_attack(this, target, get_weapon_obj(readied_obj_location), foe); set_moves_left(moves - attack_cost); } const CombatType *Actor::get_weapon(sint8 readied_obj_location) { if (readied_obj_location == ACTOR_NO_READIABLE_LOCATION) return get_hand_combat_type(); if (readied_objects[readied_obj_location]) return readied_objects[readied_obj_location]->combat_type; return nullptr; } U6LList *Actor::get_inventory_list() { return obj_manager->get_actor_inventory(id_n); } const U6LList *Actor::get_inventory_list() const { return obj_manager->get_actor_inventory(id_n); } bool Actor::inventory_has_object(uint16 objN, uint8 qual, bool match_quality, uint8 frameN, bool match_frame_n) { if (inventory_get_object(objN, qual, match_quality, frameN, match_frame_n)) return true; return false; } uint32 Actor::inventory_count_objects(bool inc_readied_objects) const { uint32 count = 0; const U6LList *inventory = get_inventory_list(); if (inc_readied_objects) { return inventory->count(); } else { for (const U6Link *link = inventory->start(); link != nullptr; link = link->next) { const Obj *obj = (const Obj *)link->data; if (!obj->is_readied()) count++; } } return count; } /* Returns the number of objects in the actor's inventory with matching object * number and quality. */ uint32 Actor::inventory_count_object(uint16 objN) { uint32 qty = 0; U6Link *link = nullptr; U6LList *inv = get_inventory_list(); for (link = inv->start(); link != nullptr; link = link->next) { Obj *obj = (Obj *)link->data; if (obj) qty += obj->get_total_qty(objN); } return qty; } /* Returns object descriptor of object in the actor's inventory, or nullptr if no * matching object is found. */ Obj *Actor::inventory_get_object(uint16 objN, uint8 qual, bool match_quality, uint8 frameN, bool match_frame_n) { U6LList *inventory; U6Link *link; Obj *obj; inventory = get_inventory_list(); for (link = inventory->start(); link != nullptr; link = link->next) { obj = (Obj *)link->data; if (obj->obj_n == objN && (match_quality == false || obj->quality == qual) && (match_frame_n == false || obj->frame_n == frameN)) //FIXME should qual = 0 be an all quality search!? return obj; else if (obj->has_container()) { if ((obj = obj->find_in_container(objN, qual, match_quality))) return obj; } } return nullptr; } bool Actor::is_double_handed_obj_readied() { if (readied_objects[ACTOR_ARM] != nullptr && readied_objects[ACTOR_ARM]->double_handed == true) return true; return false; } Obj *Actor::inventory_get_readied_object(uint8 location) { if (readied_objects[location] != nullptr) return readied_objects[location]->obj; return nullptr; } const CombatType *Actor::inventory_get_readied_object_combat_type(uint8 location) { if (readied_objects[location] != nullptr) return readied_objects[location]->combat_type; return nullptr; } bool Actor::inventory_add_object(Obj *obj, Obj *container, bool stack) { obj_manager->unlink_from_engine(obj); U6LList *inventory = get_inventory_list(), *add_to = inventory; // we have the item now so we don't consider it stealing if we get it at any time in the future. obj->set_ok_to_take(true); //remove temp flag on inventory items. obj->set_temporary(false); if (container) { // assumes actor is holding the container container->add(obj, stack); } else { // only objects outside containers are marked in_inventory /* obj->status |= OBJ_STATUS_IN_INVENTORY; */ // luteijn: don't manipulate this directly! obj->set_in_inventory(); obj->x = id_n; obj->parent = (void *)this; if (obj->is_lit()) // light up actor add_light(TORCH_LIGHT_LEVEL); obj_manager->list_add_obj(add_to, obj, stack); } return true; } /* Stacks the new object with existing objects if possible. Returns a pointer to the new object in inventory. */ Obj *Actor::inventory_new_object(uint16 objN, uint32 qty, uint8 quality) { uint8 frameN = 0; if (objN >= 1024) { frameN = (uint8)floorf(objN / 1024); objN -= frameN * 1024; } Obj *obj = new Obj; obj->obj_n = objN; obj->frame_n = frameN; obj->quality = quality; obj->qty = obj_manager->is_stackable(obj) ? 1 : 0; // stackable objects must have a quantity if (qty > 1) // this will combine with others, only if object is stackable for (uint32 q = 1; q < qty; q++) { inventory_add_object(obj_manager->copy_obj(obj), nullptr); } inventory_add_object(obj, nullptr); return inventory_get_object(objN, quality); } /* Delete `qty' objects of type from inventory (or from a container). * Returns the number removed (may be less than requested). */ uint32 Actor::inventory_del_object(uint16 objN, uint32 qty, uint8 quality) { Obj *obj; uint16 oqty = 0; uint32 deleted = 0; while ((obj = inventory_get_object(objN, quality, false)) && (deleted < qty)) { oqty = obj->qty == 0 ? 1 : obj->qty; if (oqty <= (qty - deleted)) { inventory_remove_obj(obj); delete_obj(obj); deleted += oqty; } else { obj->qty = oqty - (qty - deleted); deleted += (qty - deleted); } } return deleted; } void Actor::inventory_del_all_objs() { U6LList *inventory = get_inventory_list(); if (!inventory) return; U6Link *link = inventory->start(); for (; link != nullptr;) { Obj *obj = (Obj *)link->data; link = link->next; inventory_remove_obj(obj); delete_obj(obj); } } bool Actor::inventory_remove_obj(Obj *obj, bool run_usecode) { U6LList *inventory; Obj *container = nullptr; inventory = get_inventory_list(); if (obj->is_readied()) remove_readied_object(obj, run_usecode); if (obj->is_in_container()) container = obj->get_container_obj(); obj->set_noloc(); //remove engine location if (container) { return container->remove(obj); } if (obj->status & OBJ_STATUS_LIT) // remove light from actor subtract_light(TORCH_LIGHT_LEVEL); return inventory->remove(obj); } float Actor::get_inventory_weight() const { U6LList *inventory; U6Link *link; Obj *obj; float weight = 0; if (obj_manager->actor_has_inventory(id_n) == false) return 0; inventory = obj_manager->get_actor_inventory(id_n); for (link = inventory->start(); link != nullptr; link = link->next) { obj = (Obj *)link->data; weight += obj_manager->get_obj_weight(obj); } return weight; } float Actor::get_inventory_equip_weight() { U6LList *inventory; U6Link *link; Obj *obj; float weight = 0; if (obj_manager->actor_has_inventory(id_n) == false) return 0; inventory = obj_manager->get_actor_inventory(id_n); for (link = inventory->start(); link != nullptr; link = link->next) { obj = (Obj *)link->data; if (obj->is_readied()) //object readied weight += obj_manager->get_obj_weight(obj); } return weight; } /* Can the actor carry a new object of this type? */ bool Actor::can_carry_object(uint16 objN, uint32 qty) const { if (Game::get_game()->using_hackmove()) return true; float obj_weight = obj_manager->get_obj_weight(objN); if (qty) obj_weight *= qty; return can_carry_weight(obj_weight); } bool Actor::can_carry_object(Obj *obj) const { if (Game::get_game()->using_hackmove()) return true; if (obj_manager->can_get_obj(obj) == false) return false; return can_carry_weight(obj); } bool Actor::can_carry_weight(Obj *obj) const { return can_carry_weight(obj_manager->get_obj_weight(obj, OBJ_WEIGHT_INCLUDE_CONTAINER_ITEMS, OBJ_WEIGHT_DO_SCALE)); } /* Can the actor carry new object(s) of this weight? * (return from get_obj_weight()) */ bool Actor::can_carry_weight(float obj_weight) const { if (Game::get_game()->using_hackmove()) return true; // obj_weight /= 10; float inv_weight = get_inventory_weight() + obj_weight; float max_weight = inventory_get_max_weight(); return inv_weight <= max_weight; } void Actor::inventory_parse_readied_objects() { U6LList *inventory; U6Link *link; Obj *obj; if (obj_manager->actor_has_inventory(id_n) == false) return; inventory = obj_manager->get_actor_inventory(id_n); for (link = inventory->start(); link != nullptr;) { obj = (Obj *)link->data; link = link->next; obj->parent = (void *)this; if (obj->is_readied()) { //object readied add_readied_object(obj); } if (obj->status & OBJ_STATUS_LIT) { // torch add_light(TORCH_LIGHT_LEVEL); // light up actor } } return; } bool Actor::can_ready_obj(Obj *obj) { uint8 location = get_object_readiable_location(obj); switch (location) { case ACTOR_NOT_READIABLE : return false; case ACTOR_ARM : if (readied_objects[ACTOR_ARM] != nullptr) { //if full try other arm if (readied_objects[ACTOR_ARM]->double_handed) return false; location = ACTOR_ARM_2; } break; case ACTOR_ARM_2 : if (readied_objects[ACTOR_ARM] != nullptr || readied_objects[ACTOR_ARM_2] != nullptr) return false; location = ACTOR_ARM; break; case ACTOR_HAND : if (readied_objects[ACTOR_HAND] != nullptr) // if full try other hand location = ACTOR_HAND_2; break; } if (readied_objects[location] != nullptr) return false; return true; } //FIX handle not readiable, no place to put, double handed objects bool Actor::add_readied_object(Obj *obj) { uint8 location; bool double_handed = false; location = get_object_readiable_location(obj); switch (location) { case ACTOR_NOT_READIABLE : return false; case ACTOR_ARM : if (readied_objects[ACTOR_ARM] != nullptr) { //if full try other arm if (readied_objects[ACTOR_ARM]->double_handed) return false; location = ACTOR_ARM_2; } break; case ACTOR_ARM_2 : if (readied_objects[ACTOR_ARM] != nullptr || readied_objects[ACTOR_ARM_2] != nullptr) return false; location = ACTOR_ARM; double_handed = true; break; case ACTOR_HAND : if (readied_objects[ACTOR_HAND] != nullptr) // if full try other hand location = ACTOR_HAND_2; break; } if (readied_objects[location] != nullptr) return false; readied_objects[location] = new ReadiedObj; if (obj->is_in_container()) inventory_add_object_nostack(obj); readied_objects[location]->obj = obj; readied_objects[location]->combat_type = get_object_combat_type(obj->obj_n); readied_objects[location]->double_handed = double_handed; if (readied_objects[location]->combat_type != nullptr) readied_armor_class += readied_objects[location]->combat_type->defence; obj->readied(); //set object to readied status return true; } void Actor::remove_readied_object(Obj *obj, bool run_usecode) { uint8 location; for (location = 0; location < ACTOR_MAX_READIED_OBJECTS; location++) { if (readied_objects[location] != nullptr && readied_objects[location]->obj == obj) { remove_readied_object(location, run_usecode); break; } } return; } void Actor::remove_readied_object(uint8 location, bool run_usecode) { Obj *obj; obj = inventory_get_readied_object(location); if (obj) { if (readied_objects[location]->combat_type) readied_armor_class -= readied_objects[location]->combat_type->defence; if (obj_manager->get_usecode()->has_readycode(obj) && run_usecode) obj_manager->get_usecode()->ready_obj(obj, this); delete readied_objects[location]; readied_objects[location] = nullptr; //ERIC obj->status ^= 0x18; // remove "readied" bit flag. //ERIC obj->status |= OBJ_STATUS_IN_INVENTORY; // keep "in inventory" obj->set_in_inventory(); if (location == ACTOR_ARM && readied_objects[ACTOR_ARM_2] != nullptr) { //move contents of left hand to right hand. readied_objects[ACTOR_ARM] = readied_objects[ACTOR_ARM_2]; readied_objects[ACTOR_ARM_2] = nullptr; } if (location == ACTOR_HAND && readied_objects[ACTOR_HAND_2] != nullptr) { //move contents of left hand to right hand. readied_objects[ACTOR_HAND] = readied_objects[ACTOR_HAND_2]; readied_objects[ACTOR_HAND_2] = nullptr; } } return; } void Actor::remove_all_readied_objects() { uint8 location; for (location = 0; location < ACTOR_MAX_READIED_OBJECTS; location++) { if (readied_objects[location] != nullptr) remove_readied_object(location); } return; } // returns true if the actor has one or more readied objects bool Actor::has_readied_objects() { uint8 location; for (location = 0; location < ACTOR_MAX_READIED_OBJECTS; location++) { if (readied_objects[location] != nullptr) return true; } return false; } void Actor::inventory_drop_all() { U6LList *inv = nullptr; Obj *obj = nullptr; while (inventory_count_objects(true)) { inv = get_inventory_list(); obj = (Obj *)(inv->start()->data); if (!inventory_remove_obj(obj)) break; const Tile *obj_tile = obj_manager->get_obj_tile(obj->obj_n, obj->frame_n); if (obj_tile && (obj_tile->flags3 & TILEFLAG_IGNORE)) { //Don't drop charges. delete_obj(obj); } else { if (temp_actor) obj->status |= OBJ_STATUS_TEMPORARY; obj->status |= OBJ_STATUS_OK_TO_TAKE; obj->x = x; obj->y = y; obj->z = z; obj_manager->add_obj(obj, true); // add to map } } } // Moves inventory and all readied items into a container object. void Actor::all_items_to_container(Obj *container_obj, bool stack) { U6LList *inventory = get_inventory_list(); if (!inventory) return; for (U6Link *link = inventory->start(); link != nullptr;) { Obj *obj = (Obj *)link->data; link = link->next; if (temp_actor) obj->status |= OBJ_STATUS_TEMPORARY; const Tile *obj_tile = obj_manager->get_obj_tile(obj->obj_n, obj->frame_n); if (obj_tile && obj_tile->flags3 & TILEFLAG_IGNORE) { inventory_remove_obj(obj); delete_obj(obj); } else obj_manager->moveto_container(obj, container_obj, stack); } return; } void Actor::loadSchedule(const unsigned char *sched_data, uint16 num) { sched = (Schedule **)malloc(sizeof(Schedule *) * (num + 1)); num_schedules = num; const unsigned char *sched_data_ptr = sched_data; uint16 i; for (i = 0; i < num; i++) { sched[i] = (Schedule *)malloc(sizeof(Schedule)); sched[i]->hour = sched_data_ptr[0] & 0x1f; // 5 bits for hour sched[i]->day_of_week = sched_data_ptr[0] >> 5; // 3 bits for day of week sched[i]->worktype = sched_data_ptr[1]; sched[i]->x = sched_data_ptr[2]; sched[i]->x += (sched_data_ptr[3] & 0x3) << 8; sched[i]->y = (sched_data_ptr[3] & 0xfc) >> 2; sched[i]->y += (sched_data_ptr[4] & 0xf) << 6; sched[i]->z = (sched_data_ptr[4] & 0xf0) >> 4; sched_data_ptr += 5; #ifdef ACTOR_DEBUG DEBUG(0, LEVEL_DEBUGGING, "#%04d %03x,%03x,%x hour %2d day of week %2d worktype %02x\n", id_n, sched[i]->x, sched[i]->y, sched[i]->z, sched[i]->hour, sched[i]->day_of_week, sched[i]->worktype); #endif } sched[i] = nullptr; return; } //FIX for day_of_week bool Actor::updateSchedule(uint8 hour, bool teleport) { //uint8 day_of_week; uint16 new_pos; if (is_alive() == false //don't update schedule for dead actors. || (Game::get_game()->get_player()->get_actor() == this && Game::get_game()->get_event()->using_control_cheat())) return false; //hour = clock->get_hour(); // day_of_week = clock->get_day_of_week(); new_pos = getSchedulePos(hour); if (new_pos == sched_pos) // schedules are the same so we do nothing. return false; sched_pos = new_pos; if (sched[sched_pos] == nullptr) return false; // U6: temp. fix for walking statues; they shouldn't have schedules if (Game::get_game()->get_game_type() == NUVIE_GAME_U6 && id_n >= 188 && id_n <= 200) { DEBUG(0, LEVEL_WARNING, "tried to update schedule for non-movable actor %d\n", id_n); return false; } set_worktype(sched[sched_pos]->worktype); if (teleport) move(sched[sched_pos]->x, sched[sched_pos]->y, sched[sched_pos]->z, ACTOR_FORCE_MOVE); return true; } // returns the current schedule entry based on hour uint16 Actor::getSchedulePos(uint8 hour) { uint16 i; for (i = 0; sched[i] != nullptr; i++) { if (sched[i]->hour > hour) { if (i != 0) return i - 1; else // i == 0 this means we are in the last schedule entry for (; sched[i + 1] != nullptr;) i++; } } if (i == 0) return 0; return i - 1; } /* // returns the current schedule entry based on hour uint16 Actor::getSchedulePos(uint8 hour, uint8 day_of_week) { uint16 i,j; if(id_n == 133) DEBUG(0,LEVEL_DEBUGGING,"."); i = getSchedulePos(hour); for(j=i;sched[j] != nullptr && sched[j]->hour == sched[i]->hour;j++) { if(sched[j]->day_of_week > day_of_week) { if(j != i) return j-1; else // hour is in the last schedule entry. { for(;sched[j+1] != nullptr && sched[j+1]->hour == sched[i]->hour;) // move to the last schedule entry. j++; } } } if(j==i) return j; return j-1; } inline uint16 Actor::getSchedulePos(uint8 hour) { uint16 i; uint8 cur_hour; for(i=0;sched[i] != nullptr;i++) { if(sched[i]->hour > hour) { if(i != 0) return i-1; else // hour is in the last schedule entry. { for(;sched[i+1] != nullptr;) // move to the last schedule entry. i++; if(sched[i]->day_of_week > 0) //rewind to the start of the hour set. { cur_hour = sched[i]->hour; for(;i >= 1 && sched[i-1]->hour == cur_hour;) i--; } } } else for(;sched[i+1] != nullptr && sched[i+1]->hour == sched[i]->hour;) //skip to next hour set. i++; } if(sched[i] != nullptr && sched[i]->day_of_week > 0) //rewind to the start of the hour set. { cur_hour = sched[i]->hour; for(;i >= 1 && sched[i-1]->hour == cur_hour;) i--; } if(i==0) return 0; return i-1; } */ void Actor::set_combat_mode(uint8 new_mode) { combat_mode = new_mode; if (Game::get_game()->get_party()->is_in_combat_mode()) { set_worktype(combat_mode); } } void Actor::set_worktype(uint8 new_worktype, bool init) { worktype = new_worktype; work_location.x = x; work_location.y = y; work_location.z = z; return ; } uint8 Actor::get_flag(uint8 bitflag) { if (bitflag > 7) return 0; return (talk_flags >> bitflag) & 1; } /* Set NPC flag `bitflag' to 1. */ void Actor::set_flag(uint8 bitflag) { if (bitflag > 7) return; talk_flags = talk_flags | (1 << bitflag); } /* Set NPC flag `bitflag' to 0. */ void Actor::clear_flag(uint8 bitflag) { if (bitflag > 7) return; talk_flags = talk_flags & ~(1 << bitflag); } Obj *Actor::make_obj() { Obj *obj; obj = new Obj(); obj->x = x; obj->y = y; obj->z = z; obj->obj_n = obj_n; obj->frame_n = frame_n; obj->quality = id_n; obj->status = obj_flags; return obj; } void Actor::clear() { x = 0; y = 0; z = 0; hide(); Actor::set_worktype(0); light = 0; light_source.clear(); } void Actor::show() { visible_flag = true; for (Obj *obj : surrounding_objects) { obj->set_invisible(false); } } void Actor::hide() { visible_flag = false; for (Obj *obj : surrounding_objects) { obj->set_invisible(true); } } /* Get pushed by `pusher' to location determined by `where'. */ bool Actor::push(Actor *pusher, uint8 where) { if (where == ACTOR_PUSH_HERE) { // move to pusher's square and use up moves MapCoord to(pusher->x, pusher->y, pusher->z), from(get_location()); if (to.distance(from) > 1 || z != to.z) return false; face_location(to.x, to.y); move(to.x, to.y, to.z, ACTOR_FORCE_MOVE); // can even move onto blocked squares if (moves > 0) set_moves_left(0); // we use up our moves exchanging positions return true; } else if (where == ACTOR_PUSH_ANYWHERE) { // go to any neighboring direction MapCoord from(get_location()); const uint16 square = 1; if (this->push(pusher, ACTOR_PUSH_FORWARD)) return true; // prefer forward push for (uint16 xp = (from.x - square); xp <= (from.x + square); xp += square) for (uint16 yp = (from.y - square); yp <= (from.y + square); yp += square) if (xp != from.x && yp != from.y && move(xp, yp, from.z)) return true; } else if (where == ACTOR_PUSH_FORWARD) { // move away from pusher MapCoord from(get_location()); MapCoord pusher_loc(pusher->x, pusher->y, pusher->z); if (pusher_loc.distance(from) > 1 || z != pusher->z) return false; sint8 rel_x = -(pusher_loc.x - from.x), rel_y = -(pusher_loc.y - from.y); if (moveRelative(rel_x, rel_y)) { set_direction(rel_x, rel_y); return true; } } return false; } /* Subtract amount from hp. May die if hp is too low. */ void Actor::reduce_hp(uint8 amount) { DEBUG(0, LEVEL_DEBUGGING, "hit %s for %d points\n", get_name(), amount); if (amount <= hp) set_hp(hp - amount); else set_hp(0); // FIXME... game specific? if (hp == 0) die(); } void Actor::set_hp(uint8 val) { hp = val; if (is_in_party()) { Game::get_game()->get_view_manager()->update(); } } void Actor::set_obj_flag(uint8 bitFlag, bool value) { if (value) { obj_flags |= (1 << bitFlag); } else { obj_flags &= (0xff ^ (1 << bitFlag)); } } void Actor::set_status_flag(uint8 bitFlag, bool value) { if (value) { status_flags |= (1 << bitFlag); } else { status_flags &= (0xff ^ (1 << bitFlag)); } } void Actor::set_hit_flag(bool val) { if (val) { movement_flags |= ACTOR_MOVEMENT_HIT_FLAG; } else { movement_flags &= (0xff ^ ACTOR_MOVEMENT_HIT_FLAG); } } void Actor::die(bool create_body) { hp = 0; visible_flag = false; Game *game = Game::get_game(); if (game->get_game_type() != NUVIE_GAME_U6) // set in U6 before removing items for torch usecode set_dead_flag(true); // may need to add it elsewhere for other games if (game->get_player()->get_actor() == this && game->get_event()->using_control_cheat()) game->get_event()->party_mode(); if (is_temp()) game->get_actor_manager()->clear_actor(this); } void Actor::resurrect(const MapCoord &new_position, Obj *body_obj) { U6Link *link; bool remove_obj = false; if (body_obj == nullptr) { body_obj = find_body(); if (body_obj != nullptr) remove_obj = true; } set_dead_flag(false); show(); x = new_position.x; y = new_position.y; z = new_position.z; obj_n = base_obj_n; init((Game::get_game()->get_game_type() == NUVIE_GAME_U6 && id_n == 130) ? OBJ_STATUS_MUTANT : NO_OBJ_STATUS); frame_n = 0; set_direction(NUVIE_DIR_N); if (Game::get_game()->get_game_type() == NUVIE_GAME_U6) ((U6Actor *)this)->do_twitch(); // fixes actors with more than 1 tile set_hp(1); //actor->set_worktype(0x1); if (is_in_party()) //actor in party Game::get_game()->get_party()->add_actor(this); if (body_obj != nullptr) { //add body container objects back into actor's inventory. if (body_obj->has_container()) { for (link = body_obj->container->start(); link != nullptr;) { Obj *inv_obj = (Obj *)link->data; link = link->next; inventory_add_object(inv_obj); } body_obj->container->removeAll(); } obj_manager->unlink_from_engine(body_obj); } if (remove_obj) delete_obj(body_obj); Game::get_game()->get_script()->call_actor_resurrect(this); return; } void Actor::display_condition() { MsgScroll *scroll = Game::get_game()->get_scroll(); if (hp == get_maxhp()) return; scroll->display_string(get_name()); scroll->display_string(" "); if (hp < get_maxhp() / 4) // 25% scroll->display_string("critical!\n"); else { if (hp < get_maxhp() / 2) // 50% scroll->display_string("heavily"); else if (hp < get_maxhp() / 1.33) // 75% scroll->display_string("lightly"); else scroll->display_string("barely"); scroll->display_string(" wounded.\n"); } } /* Get hit and take damage by some indirect effect. (no source) */ void Actor::hit(uint8 dmg, bool force_hit) { MsgScroll *scroll = Game::get_game()->get_scroll(); uint8 total_armor_class = body_armor_class; //+ readied_armor_class; if (dmg == 0) { scroll->display_string(get_name()); scroll->display_string(" grazed!\n"); } else if (dmg > total_armor_class || force_hit) { new HitEffect(this); reduce_hp(force_hit ? dmg : dmg - total_armor_class); // if(!force_hit) // { if (hp == 0) { scroll->display_string(get_name()); scroll->display_string(" killed!\n"); } else { display_condition(); } // } } } void Actor::attract_to(Actor *target) { delete_pathfinder(); set_pathfinder(new CombatPathFinder(this)); ((CombatPathFinder *)pathfinder)->set_chase_mode(target); } void Actor::repel_from(Actor *target) { delete_pathfinder(); set_pathfinder(new CombatPathFinder(this, target)); ((CombatPathFinder *)pathfinder)->set_flee_mode(target); ((CombatPathFinder *)pathfinder)->set_distance(2); } uint8 Actor::get_light_level() const { const Tile *tile = get_tile(); return MAX(light, GET_TILE_LIGHT_LEVEL(tile)); } void Actor::add_light(uint8 val) { if (is_in_party() || (Actor *)this == Game::get_game()->get_player()->get_actor()) Game::get_game()->get_party()->add_light_source(); // light += val; light_source.push_back(val); if (val > light) light = val; } void Actor::subtract_light(uint8 val) { if (is_in_party() || (Actor *)this == Game::get_game()->get_player()->get_actor()) Game::get_game()->get_party()->subtract_light_source(); // if(light >= val) // light -= val; // else // light = 0; for (vector::iterator l = light_source.begin(); l != light_source.end(); l++) { if (*l == val) { light_source.erase(l); break; } } light = 0; // change to next highest light source for (unsigned int lCtr = 0; lCtr < light_source.size(); lCtr++) if (light_source[lCtr] > light) light = light_source[lCtr]; } void Actor::set_moves_left(sint8 val) { moves = clamp(val, -127, dex); } void Actor::set_dead_flag(bool value) { if (value) status_flags |= ACTOR_STATUS_DEAD; else if (!is_alive()) //if not alive then clear dead flag status_flags ^= ACTOR_STATUS_DEAD; return; } /* Set error/status information. */ void Actor::set_error(ActorErrorCode err) { clear_error(); error_struct.err = err; } void Actor::clear_error() { error_struct.err = ACTOR_NO_ERROR; error_struct.blocking_obj = nullptr; error_struct.blocking_actor = nullptr; } ActorError *Actor::get_error() { return &error_struct; } // frozen by worktype or status bool Actor::is_immobile() const { return false; } void Actor::print() { Actor *actor = this; DEBUG(0, LEVEL_INFORMATIONAL, "\n"); DEBUG(1, LEVEL_INFORMATIONAL, "%s at %x, %x, %x\n", get_name(), actor->x, actor->y, actor->z); DEBUG(1, LEVEL_INFORMATIONAL, "id_n: %d\n", actor->id_n); DEBUG(1, LEVEL_INFORMATIONAL, "obj_n: %03d frame_n: %d\n", actor->obj_n, actor->frame_n); DEBUG(1, LEVEL_INFORMATIONAL, "base_obj_n: %03d old_frame_n: %d\n", actor->base_obj_n, actor->old_frame_n); NuvieDir dir = actor->direction; DEBUG(1, LEVEL_INFORMATIONAL, "direction: %d (%s)\n", dir, (dir == NUVIE_DIR_N) ? "north" : (dir == NUVIE_DIR_E) ? "east" : (dir == NUVIE_DIR_S) ? "south" : (dir == NUVIE_DIR_W) ? "west" : "???"); DEBUG(1, LEVEL_INFORMATIONAL, "walk_frame: %d\n", actor->walk_frame); DEBUG(1, LEVEL_INFORMATIONAL, "can_move: %s\n", actor->can_move ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "alive: %s\n", actor->is_alive() ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "in_party: %s\n", is_in_party() ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "visible_flag: %s\n", actor->visible_flag ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "met_player: %s\n", actor->met_player ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "is_immobile: %s\n", actor->is_immobile() ? "true" : "false"); DEBUG(1, LEVEL_INFORMATIONAL, "moves: %d\n", actor->moves); const char *wt_string = get_worktype_string(actor->worktype); if (!wt_string) wt_string = "???"; DEBUG(1, LEVEL_INFORMATIONAL, "worktype: 0x%02x/%03d %s\n", actor->worktype, actor->worktype, wt_string); DEBUG(1, LEVEL_INFORMATIONAL, "NPC stats:\n"); DEBUG(1, LEVEL_INFORMATIONAL, " level: %d exp: %d hp: %d / %d\n", actor->level, actor->exp, actor->hp, actor->get_maxhp()); DEBUG(1, LEVEL_INFORMATIONAL, " strength: %d dex: %d int: %d\n", actor->strength, actor->dex, actor->intelligence); DEBUG(1, LEVEL_INFORMATIONAL, " magic: %d / %d\n", actor->magic, actor->get_maxmagic()); DEBUG(1, LEVEL_INFORMATIONAL, "alignment: %s (%d)\n", get_actor_alignment_str(actor->get_alignment()), actor->get_alignment()); uint8 combatMode = actor->combat_mode; wt_string = get_worktype_string(actor->combat_mode); if (!wt_string) wt_string = "???"; DEBUG(1, LEVEL_INFORMATIONAL, "combat_mode: %d %s\n", combatMode, wt_string); DEBUG(1, LEVEL_INFORMATIONAL, "Object flags: "); print_b(LEVEL_INFORMATIONAL, actor->obj_flags); DEBUG(1, LEVEL_INFORMATIONAL, "\n"); DEBUG(1, LEVEL_INFORMATIONAL, "NPC flags: "); print_b(LEVEL_INFORMATIONAL, actor->status_flags); DEBUG(1, LEVEL_INFORMATIONAL, "\n"); DEBUG(1, LEVEL_INFORMATIONAL, "Talk flags: "); print_b(LEVEL_INFORMATIONAL, actor->talk_flags); DEBUG(1, LEVEL_INFORMATIONAL, "\n"); uint32 inv = actor->inventory_count_objects(true); if (inv) { DEBUG(1, LEVEL_INFORMATIONAL, "Inventory (+readied): %d objects\n", inv); U6LList *inv_list = actor->get_inventory_list(); for (U6Link *link = inv_list->start(); link != nullptr; link = link->next) { Obj *obj = (Obj *)link->data; DEBUG(1, LEVEL_INFORMATIONAL, " %24s (%03d:%d) status=%d qual=%d qty=%d (weighs %f)\n", obj_manager->look_obj(obj), obj->obj_n, obj->frame_n, obj->status, obj->quality, obj->qty, obj_manager->get_obj_weight(obj, false)); } DEBUG(1, LEVEL_INFORMATIONAL, "(weight %f / %f)\n", actor->get_inventory_weight(), actor->inventory_get_max_weight()); } if (actor->sched && *actor->sched) { DEBUG(1, LEVEL_INFORMATIONAL, "Schedule:\n"); Schedule **s = actor->sched; uint32 sp = 0; do { wt_string = get_worktype_string(s[sp]->worktype); if (!wt_string) wt_string = "???"; if (sp == actor->sched_pos && s[sp]->worktype == actor->worktype) DEBUG(1, LEVEL_INFORMATIONAL, "*%d: location=0x%03x,0x%03x,0x%x time=%02d:00 day=%d worktype=0x%02x(%s)*\n", sp, s[sp]->x, s[sp]->y, s[sp]->z, s[sp]->hour, s[sp]->day_of_week, s[sp]->worktype, wt_string); else DEBUG(1, LEVEL_INFORMATIONAL, " %d: location=0x%03x,0x%03x,0x%x time=%02d:00 day=%d worktype=0x%02x(%s)\n", sp, s[sp]->x, s[sp]->y, s[sp]->z, s[sp]->hour, s[sp]->day_of_week, s[sp]->worktype, wt_string); } while (s[++sp]); } if (!actor->surrounding_objects.empty()) DEBUG(1, LEVEL_INFORMATIONAL, "Actor has multiple tiles\n"); if (actor->pathfinder) DEBUG(1, LEVEL_INFORMATIONAL, "Actor is on a path\n"); DEBUG(1, LEVEL_INFORMATIONAL, "\n"); } const char *get_actor_alignment_str(ActorAlignment alignment) { switch (alignment) { case ACTOR_ALIGNMENT_DEFAULT : return "default"; case ACTOR_ALIGNMENT_NEUTRAL : return "neutral"; case ACTOR_ALIGNMENT_EVIL : return "evil"; case ACTOR_ALIGNMENT_GOOD : return "good"; case ACTOR_ALIGNMENT_CHAOTIC : return "chaotic"; default : break; } return "unknown"; } void Actor::set_invisible(bool invisible) { if (invisible) { if (!is_in_party()) visible_flag = false; obj_flags |= OBJ_STATUS_INVISIBLE; } else { visible_flag = true; obj_flags &= ~OBJ_STATUS_INVISIBLE; } } sint8 Actor::count_readied_objects(sint32 objN, sint16 frameN, sint16 quality) { sint8 count = 0; for (int o = 0; o < ACTOR_MAX_READIED_OBJECTS; o++) { if (readied_objects[o] == 0) continue; if (objN == -1 || (readied_objects[o]->obj->obj_n == objN && (frameN == -1 || frameN == readied_objects[o]->obj->frame_n) && (quality == -1 || quality == readied_objects[o]->obj->quality))) ++count; } return count; } // GOOD->CHAOTIC,EVIL // NEUTRAL->CHAOTIC // EVIL->GOOD,CHAOTIC // CHAOTIC->ALL except CHAOTIC ActorList *Actor::find_enemies() { const uint8 in_range = 24; ActorManager *actor_mgr = Game::get_game()->get_actor_manager(); ActorList *actors = actor_mgr->filter_distance(actor_mgr->get_actor_list(), x, y, z, in_range); actor_mgr->filter_alignment(actors, alignment); // filter own alignment if (alignment != ACTOR_ALIGNMENT_CHAOTIC) { if (alignment == ACTOR_ALIGNMENT_NEUTRAL) { actor_mgr->filter_alignment(actors, ACTOR_ALIGNMENT_GOOD); // filter other friendlies actor_mgr->filter_alignment(actors, ACTOR_ALIGNMENT_EVIL); } else actor_mgr->filter_alignment(actors, ACTOR_ALIGNMENT_NEUTRAL); } // remove party members and invisible actors FIXME: set party members to leader's alignment ActorIterator a = actors->begin(); while (a != actors->end()) if (is_in_party() && (*a)->is_in_party()) a = actors->erase(a); else if ((*a)->is_invisible()) a = actors->erase(a); else ++a; if (actors->empty()) { delete actors; return nullptr; // no enemies in range } return actors; } Obj *Actor::find_body() { Party *party; Actor *actor; Obj *body_obj = nullptr; uint8 lvl; party = Game::get_game()->get_party(); actor = party->who_has_obj(339, id_n, true); if (actor) //get from collective party inventory if possible return actor->inventory_get_object(339, id_n, OBJ_MATCH_QUALITY); // try to find on map. for (lvl = 0; lvl < 5 && body_obj == nullptr; lvl++) body_obj = obj_manager->find_obj(lvl, 339, id_n); return body_obj; } /* Change actor type. */ bool Actor::morph(uint16 objN) { NuvieDir old_dir = get_direction(); // FIXME: this should get saved through init_from_obj() Obj *actor_obj = make_obj(); actor_obj->obj_n = objN; actor_obj->frame_n = 0; init_from_obj(actor_obj); delete actor_obj; set_dead_flag(false); set_direction(old_dir); // FIXME: this should get saved through init_from_obj() return true; } bool Actor::get_schedule_location(MapCoord *loc) const { if (sched[sched_pos] == nullptr) return false; loc->x = sched[sched_pos]->x; loc->y = sched[sched_pos]->y; loc->z = sched[sched_pos]->z; return true; } bool Actor::is_at_scheduled_location() const { if (sched[sched_pos] != nullptr && x == sched[sched_pos]->x && y == sched[sched_pos]->y && z == sched[sched_pos]->z) return true; return false; } Schedule *Actor::get_schedule(uint8 index) { if (index >= num_schedules) return nullptr; return sched[index]; } void Actor::cure() { set_poisoned(false); set_paralyzed(false); set_charmed(false); set_corpser_flag(false); set_cursed(false); set_asleep(false); } void Actor::set_custom_tile_num(uint16 obj_num, uint16 tile_num) { if (custom_tile_tbl == nullptr) { custom_tile_tbl = new Common::HashMap(); } (*custom_tile_tbl)[obj_num] = tile_num; } bool Actor::doesOccupyLocation(uint16 lx, uint16 ly, uint8 lz, bool incDoubleTile, bool incSurroundingObjs) const { if (z != lz) return false; const Tile *const tile = get_tile(); // If actor has double height, also check tile S of location. // If actor has double width, also check tile E of location. for (int relX = 0; relX < 2 && (tile->dbl_width || !relX); ++relX) for (int relY = 0; relY < 2 && (tile->dbl_height || !relY); ++relY) if (relY && !incDoubleTile) goto NotFound; else if (WRAPPED_COORD(lx + relX, lz) == x && WRAPPED_COORD(ly + relY, lz) == y) return true; NotFound: if (incSurroundingObjs) for (const Obj *obj : surrounding_objects) if (obj && obj->x == WRAPPED_COORD(lx, lz) && obj->y == WRAPPED_COORD(ly, lz) && obj->z == lz) return true; return false; } } // End of namespace Nuvie } // End of namespace Ultima