1969 lines
54 KiB
C++
1969 lines
54 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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<uint16, uint16>::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<NuvieDir>(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<NuvieDir>(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<NuvieDir>(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<NuvieDir>(direction + 2);
|
|
}
|
|
// wrap
|
|
if (new_direction >= 4)
|
|
new_direction = static_cast<NuvieDir>(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<uint8>::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<uint16, uint16>();
|
|
}
|
|
|
|
(*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
|