Files
scummvm-cursorfix/engines/ultima/nuvie/actors/actor.cpp
2026-02-02 04:50:13 +01:00

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