1690 lines
43 KiB
C++
1690 lines
43 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/usecode/u6_usecode.h"
|
|
#include "ultima/nuvie/pathfinder/sched_path_finder.h"
|
|
#include "ultima/nuvie/pathfinder/u6_astar_path.h"
|
|
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
|
|
#include "ultima/nuvie/actors/u6_actor.h"
|
|
|
|
#include "ultima/nuvie/core/party.h"
|
|
#include "ultima/nuvie/actors/actor_manager.h"
|
|
#include "ultima/nuvie/views/view_manager.h"
|
|
#include "ultima/nuvie/sound/sound_manager.h"
|
|
#include "ultima/nuvie/core/converse.h"
|
|
#include "ultima/nuvie/script/script.h"
|
|
#include "ultima/nuvie/core/effect.h"
|
|
#include "ultima/nuvie/pathfinder/combat_path_finder.h"
|
|
|
|
#include "ultima/nuvie/actors/u6_actor_types.h"
|
|
#include "ultima/nuvie/actors/u6_work_types.h"
|
|
#include "ultima/nuvie/core/weather.h"
|
|
|
|
namespace Ultima {
|
|
namespace Nuvie {
|
|
|
|
U6Actor::U6Actor(Map *m, ObjManager *om, GameClock *c): Actor(m, om, c),
|
|
actor_type(nullptr), walk_frame_inc(1), base_actor_type(nullptr),
|
|
current_movetype(MOVETYPE_U6_NONE) {
|
|
}
|
|
|
|
U6Actor::~U6Actor() {
|
|
}
|
|
|
|
bool U6Actor::init(uint8 obj_status) {
|
|
Actor::init();
|
|
base_actor_type = get_actor_type(base_obj_n);
|
|
if (base_actor_type->base_obj_n != base_obj_n) {
|
|
base_obj_n = base_actor_type->base_obj_n;
|
|
}
|
|
|
|
set_actor_obj_n(obj_n); //set actor_type
|
|
|
|
current_movetype = actor_type->movetype;
|
|
|
|
body_armor_class = base_actor_type->body_armor_class;
|
|
|
|
if (actor_type->tile_type == ACTOR_QT && frame_n == 0) //set the two quad tile actors to correct frame number.
|
|
frame_n = 3;
|
|
|
|
discover_direction();
|
|
|
|
if (has_surrounding_objs())
|
|
clear_surrounding_objs_list(); //clean up the old list if required.
|
|
|
|
if (is_alive() && x != 0 && y != 0) { //only try to init multi-tile actors if they are alive.
|
|
switch (obj_n) { //gather surrounding objects from map if required
|
|
case OBJ_U6_SHIP :
|
|
init_ship();
|
|
break;
|
|
|
|
case OBJ_U6_HYDRA :
|
|
init_hydra();
|
|
break;
|
|
|
|
case OBJ_U6_DRAGON :
|
|
init_dragon();
|
|
break;
|
|
|
|
case OBJ_U6_SILVER_SERPENT :
|
|
init_silver_serpent();
|
|
break;
|
|
|
|
case OBJ_U6_GIANT_SCORPION :
|
|
case OBJ_U6_GIANT_ANT :
|
|
case OBJ_U6_COW :
|
|
case OBJ_U6_ALLIGATOR :
|
|
case OBJ_U6_HORSE :
|
|
case OBJ_U6_HORSE_WITH_RIDER :
|
|
init_splitactor(obj_status);
|
|
break;
|
|
|
|
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (actor_type->can_sit) { // For some reason U6 starts with actors standing on their chairs.
|
|
// We need to sit them down.
|
|
Obj *obj = obj_manager->get_obj(x, y, z);
|
|
sit_on_chair(obj); // attempt to sit on obj.
|
|
}
|
|
|
|
inventory_make_all_objs_ok_to_take();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::init_ship() {
|
|
Obj *obj;
|
|
uint16 obj1_x, obj1_y, obj2_x, obj2_y;
|
|
|
|
obj1_x = x;
|
|
obj1_y = y;
|
|
obj2_x = x;
|
|
obj2_y = y;
|
|
|
|
switch (direction) {
|
|
case NUVIE_DIR_N :
|
|
obj1_y = y + 1;
|
|
obj2_y = y - 1;
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
obj1_x = x + 1;
|
|
obj2_x = x - 1;
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
obj1_y = y - 1;
|
|
obj2_y = y + 1;
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
obj1_x = x - 1;
|
|
obj2_x = x + 1;
|
|
break;
|
|
default:
|
|
error("Invalid direction in U6Actor::init_ship");
|
|
}
|
|
|
|
obj = obj_manager->get_obj(obj1_x, obj1_y, z);
|
|
if (obj == nullptr)
|
|
return false;
|
|
add_surrounding_obj(obj);
|
|
|
|
obj = obj_manager->get_obj(obj2_x, obj2_y, z);
|
|
if (obj == nullptr)
|
|
return false;
|
|
add_surrounding_obj(obj);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::init_splitactor(uint8 obj_status) {
|
|
uint16 obj_x, obj_y;
|
|
|
|
obj_x = x;
|
|
obj_y = y;
|
|
|
|
switch (direction) {
|
|
case NUVIE_DIR_N :
|
|
obj_y = WRAPPED_COORD(y + 1, z);
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
obj_x = WRAPPED_COORD(x - 1, z);
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
obj_y = WRAPPED_COORD(y - 1, z);
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
obj_x = WRAPPED_COORD(x + 1, z);
|
|
break;
|
|
default:
|
|
error("Invalid direction in U6Actor::init_splitactor");
|
|
}
|
|
|
|
// init back object
|
|
if (obj_status & OBJ_STATUS_MUTANT) {
|
|
init_surrounding_obj(obj_x, obj_y, z, obj_n, (get_reverse_direction(direction) * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1));
|
|
} else {
|
|
init_surrounding_obj(obj_x, obj_y, z, obj_n, frame_n + 8);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::init_dragon() {
|
|
uint16 head_x, head_y, tail_x, tail_y;
|
|
uint16 wing1_x, wing1_y, wing2_x, wing2_y;
|
|
|
|
head_x = tail_x = x;
|
|
wing1_x = wing2_x = x;
|
|
head_y = tail_y = y;
|
|
wing1_y = wing2_y = y;
|
|
|
|
switch (direction) {
|
|
case NUVIE_DIR_N :
|
|
head_y = y - 1;
|
|
tail_y = y + 1;
|
|
wing1_x = x - 1;
|
|
wing2_x = x + 1;
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
head_x = x + 1;
|
|
tail_x = x - 1;
|
|
wing1_y = y - 1;
|
|
wing2_y = y + 1;
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
head_y = y + 1;
|
|
tail_y = y - 1;
|
|
wing1_x = x + 1;
|
|
wing2_x = x - 1;
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
head_x = x - 1;
|
|
tail_x = x + 1;
|
|
wing1_y = y + 1;
|
|
wing2_y = y - 1;
|
|
break;
|
|
default:
|
|
error("Invalid direction in U6Actor::init_dragon");
|
|
}
|
|
|
|
init_surrounding_obj(head_x, head_y, z, obj_n, frame_n + 8);
|
|
init_surrounding_obj(tail_x, tail_y, z, obj_n, frame_n + 16);
|
|
init_surrounding_obj(wing1_x, wing1_y, z, obj_n, frame_n + 24);
|
|
init_surrounding_obj(wing2_x, wing2_y, z, obj_n, frame_n + 32);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::init_hydra() {
|
|
// For some reason a Hydra has a different object number for its tenticles. :-(
|
|
|
|
init_surrounding_obj(x, y - 1, z, OBJ_U6_HYDRA_BODY, 0);
|
|
init_surrounding_obj(x + 1, y - 1, z, OBJ_U6_HYDRA_BODY, 4);
|
|
init_surrounding_obj(x + 1, y, z, OBJ_U6_HYDRA_BODY, 8);
|
|
init_surrounding_obj(x + 1, y + 1, z, OBJ_U6_HYDRA_BODY, 12);
|
|
init_surrounding_obj(x, y + 1, z, OBJ_U6_HYDRA_BODY, 16);
|
|
init_surrounding_obj(x - 1, y + 1, z, OBJ_U6_HYDRA_BODY, 20);
|
|
init_surrounding_obj(x - 1, y, z, OBJ_U6_HYDRA_BODY, 24);
|
|
init_surrounding_obj(x - 1, y - 1, z, OBJ_U6_HYDRA_BODY, 28);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::init_silver_serpent() {
|
|
uint16 sx, sy, sz;
|
|
Obj *obj;
|
|
uint8 tmp_frame_n = 0;
|
|
|
|
sx = x;
|
|
sy = y;
|
|
sz = z;
|
|
|
|
switch (direction) {
|
|
case NUVIE_DIR_N :
|
|
sy++;
|
|
tmp_frame_n = 1;
|
|
break;
|
|
case NUVIE_DIR_E :
|
|
sx--;
|
|
tmp_frame_n = 3;
|
|
break;
|
|
case NUVIE_DIR_S :
|
|
sy--;
|
|
tmp_frame_n = 5;
|
|
break;
|
|
case NUVIE_DIR_W :
|
|
sx++;
|
|
tmp_frame_n = 7;
|
|
break;
|
|
default:
|
|
error("Invalid direction in U6Actor::init_silver_serpent");
|
|
}
|
|
|
|
obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, 1, id_n, sx, sy, sz);
|
|
|
|
if (obj != nullptr) //old snake
|
|
gather_snake_objs_from_map(obj, x, y, z);
|
|
else { //new snake
|
|
//FIXME: we need to make long, randomly laid out snakes here!
|
|
init_new_silver_serpent();
|
|
}
|
|
|
|
// FIXME: Unused variable
|
|
(void)tmp_frame_n;
|
|
|
|
return true;
|
|
}
|
|
|
|
void U6Actor::init_new_silver_serpent() {
|
|
const struct {
|
|
uint8 body_frame_n;
|
|
uint8 tail_frame_n;
|
|
sint8 x_offset;
|
|
sint8 y_offset;
|
|
} movetbl[4] = { {10, 1, 0, 1}, {13, 7, 1, 0}, {12, 5, 0, -1}, {11, 3, -1, 0} };
|
|
|
|
uint8 i, j;
|
|
uint16 nx, ny;
|
|
Obj *obj;
|
|
uint8 length = 4 + NUVIE_RAND() % 5; //FIXME. The original worked out length from qty in the serpent embryo obj.
|
|
|
|
nx = x;
|
|
ny = y;
|
|
|
|
set_direction(NUVIE_DIR_N); //make sure we are facing north.
|
|
|
|
for (i = 0, j = 0; i < length; i++) {
|
|
nx += movetbl[j].x_offset;
|
|
ny += movetbl[j].y_offset;
|
|
|
|
init_surrounding_obj(nx, ny, z, OBJ_U6_SILVER_SERPENT, (i == length - 1 ? movetbl[j].tail_frame_n : movetbl[j].body_frame_n));
|
|
|
|
obj = (Obj *)surrounding_objects.back();
|
|
obj->quality = i + 1; //body segment number
|
|
obj->qty = id_n; //actor id number
|
|
|
|
j = (j + 1) % 4;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void U6Actor::gather_snake_objs_from_map(Obj *start_obj, uint16 ax, uint16 ay, uint16 az) {
|
|
Obj *obj;
|
|
uint16 px, py; // , pz;
|
|
uint16 nx, ny, nz;
|
|
uint8 seg_num;
|
|
|
|
px = ax;
|
|
py = ay;
|
|
// pz = az;
|
|
|
|
obj = start_obj;
|
|
add_surrounding_obj(obj);
|
|
|
|
for (seg_num = 2; obj && obj->frame_n >= 8; seg_num++) {
|
|
|
|
nx = obj->x;
|
|
ny = obj->y;
|
|
nz = obj->z;
|
|
//work out the location of the next obj based on the current frame_n and relative movement.
|
|
switch (obj->frame_n) {
|
|
//up down
|
|
case 8 :
|
|
if (ny - 1 == py)
|
|
ny++;
|
|
else
|
|
ny--;
|
|
break;
|
|
//left right
|
|
case 9 :
|
|
if (nx - 1 == px)
|
|
nx++;
|
|
else
|
|
nx--;
|
|
break;
|
|
//up right
|
|
case 10 :
|
|
if (ny - 1 == py)
|
|
nx++;
|
|
else
|
|
ny--;
|
|
break;
|
|
//down right
|
|
case 11 :
|
|
if (ny + 1 == py)
|
|
nx++;
|
|
else
|
|
ny++;
|
|
break;
|
|
//left down
|
|
case 12 :
|
|
if (nx - 1 == px)
|
|
ny++;
|
|
else
|
|
nx--;
|
|
break;
|
|
//left up
|
|
case 13 :
|
|
if (nx - 1 == px)
|
|
ny--;
|
|
else
|
|
nx--;
|
|
break;
|
|
}
|
|
|
|
px = obj->x;
|
|
py = obj->y;
|
|
//pz = obj->z;
|
|
|
|
obj = obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, seg_num, id_n, nx, ny, nz);
|
|
|
|
if (obj)
|
|
add_surrounding_obj(obj);
|
|
}
|
|
|
|
}
|
|
|
|
uint16 U6Actor::get_downward_facing_tile_num() const {
|
|
uint8 shift = 0;
|
|
|
|
if (base_actor_type->frames_per_direction > 1) //we want the second frame for most actor types.
|
|
shift = 1;
|
|
|
|
return get_tile_num(base_actor_type->base_obj_n) + base_actor_type->tile_start_offset + (NUVIE_DIR_S * base_actor_type->tiles_per_direction + base_actor_type->tiles_per_frame - 1) + shift;
|
|
}
|
|
|
|
bool U6Actor::updateSchedule(uint8 hour, bool teleport) {
|
|
bool ret;
|
|
handle_lightsource(hour);
|
|
|
|
if ((ret = Actor::updateSchedule(hour, teleport)) == true) { //walk to next schedule location if required.
|
|
if (sched[sched_pos] != nullptr && (sched[sched_pos]->x != x || sched[sched_pos]->y != y || sched[sched_pos]->z != z
|
|
|| worktype == WORKTYPE_U6_SLEEP)) { // needed to go underneath bed if teleporting
|
|
set_worktype(WORKTYPE_U6_WALK_TO_LOCATION);
|
|
MapCoord loc(sched[sched_pos]->x, sched[sched_pos]->y, sched[sched_pos]->z);
|
|
pathfind_to(loc);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// workout our direction based on actor_type and frame_n
|
|
inline void U6Actor::discover_direction() {
|
|
if (actor_type->frames_per_direction != 0)
|
|
direction = static_cast<NuvieDir>((frame_n - actor_type->tile_start_offset) / actor_type->tiles_per_direction);
|
|
else
|
|
direction = NUVIE_DIR_S;
|
|
}
|
|
|
|
void U6Actor::change_base_obj_n(uint16 val) {
|
|
Actor::change_base_obj_n(val);
|
|
clear_surrounding_objs_list(REMOVE_SURROUNDING_OBJS);
|
|
init();
|
|
}
|
|
|
|
void U6Actor::set_direction(NuvieDir d) {
|
|
if (is_alive() == false || is_immobile())
|
|
return;
|
|
|
|
uint8 frames_per_dir = (actor_type->frames_per_direction != 0)
|
|
? actor_type->frames_per_direction : 4;
|
|
|
|
if (d >= 4) // ignore diagonals
|
|
return;
|
|
|
|
if (walk_frame == 0)
|
|
walk_frame_inc = 1; // loop forward
|
|
else if (walk_frame == (frames_per_dir - 1))
|
|
walk_frame_inc = -1; // loop backward
|
|
walk_frame = (walk_frame + walk_frame_inc) % frames_per_dir;
|
|
|
|
if (has_surrounding_objs()) {
|
|
if (direction != d)
|
|
set_direction_of_surrounding_objs(d);
|
|
else {
|
|
if (can_move && actor_type->twitch_rand) //only twitch actors with a non zero twitch_rand.
|
|
twitch_surrounding_objs();
|
|
}
|
|
}
|
|
|
|
direction = d;
|
|
|
|
//only change direction frame if the actor can twitch ie isn't sitting or in bed etc.
|
|
if (can_move && obj_n != OBJ_U6_SLIME)
|
|
frame_n = actor_type->tile_start_offset + (direction * actor_type->tiles_per_direction +
|
|
(walk_frame * actor_type->tiles_per_frame) + actor_type->tiles_per_frame - 1);
|
|
|
|
// tangle vines' north and east frames are in the wrong direction
|
|
// FIXME: see if the ActorType values can be changed to fix this
|
|
if (obj_n == OBJ_U6_TANGLE_VINE)
|
|
if (direction == NUVIE_DIR_N || direction == NUVIE_DIR_E)
|
|
frame_n += 3;
|
|
}
|
|
|
|
void U6Actor::face_location(uint16 lx, uint16 ly) {
|
|
if (obj_n != OBJ_U6_SILVER_SERPENT //snakes cannot turn on the spot.
|
|
&& obj_n != OBJ_U6_TANGLE_VINE && obj_n != OBJ_U6_TANGLE_VINE_POD)
|
|
Actor::face_location(lx, ly);
|
|
|
|
return;
|
|
}
|
|
|
|
void U6Actor::clear() {
|
|
if (has_surrounding_objs()) {
|
|
remove_surrounding_objs_from_map();
|
|
clear_surrounding_objs_list(REMOVE_SURROUNDING_OBJS);
|
|
}
|
|
|
|
Actor::clear();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
bool U6Actor::move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) {
|
|
assert(new_z < 6);
|
|
|
|
// bool force_move = flags & ACTOR_FORCE_MOVE;
|
|
bool ret;
|
|
sint16 rel_x, rel_y;
|
|
//MsgScroll *scroll = Game::get_game()->get_scroll();
|
|
Player *player = Game::get_game()->get_player();
|
|
//Party *party = player->get_party();
|
|
MapCoord old_pos = get_location();
|
|
|
|
if (has_surrounding_objs())
|
|
remove_surrounding_objs_from_map();
|
|
|
|
rel_x = new_x - x;
|
|
rel_y = new_y - y;
|
|
|
|
if ((flags & ACTOR_OPEN_DOORS) && worktype != WORKTYPE_U6_WALK_TO_LOCATION)
|
|
flags ^= ACTOR_OPEN_DOORS; // only use doors when walking to schedule location
|
|
ret = Actor::move(new_x, new_y, new_z, flags);
|
|
|
|
if (ret == true) {
|
|
if (has_surrounding_objs())
|
|
move_surrounding_objs_relative(rel_x, rel_y);
|
|
|
|
Obj *obj = obj_manager->get_obj(new_x, new_y, new_z, false); // Ouch, we get obj in Actor::move() too :(
|
|
if (obj) {
|
|
if (actor_type->can_sit)
|
|
sit_on_chair(obj); // make the Actor sit if they are on top of a chair.
|
|
}
|
|
|
|
set_hit_flag(false);
|
|
Game::get_game()->get_script()->call_actor_map_dmg(this, get_location());
|
|
}
|
|
|
|
|
|
// temp. fix; this too should be done with UseCode (and don't move the mirror)
|
|
if (old_pos.y > 0 && new_y > 0) {
|
|
Obj *old_mirror = obj_manager->get_obj_of_type_from_location(OBJ_U6_MIRROR, old_pos.x, old_pos.y - 1, old_pos.z);
|
|
Obj *mirror = obj_manager->get_obj_of_type_from_location(OBJ_U6_MIRROR, new_x, new_y - 1, new_z);
|
|
if (old_mirror && old_mirror->frame_n != 2) old_mirror->frame_n = 0;
|
|
if (mirror && mirror->frame_n != 2) mirror->frame_n = 1;
|
|
}
|
|
|
|
// Cyclops: shake ground if player is near
|
|
if (actor_type->base_obj_n == OBJ_U6_CYCLOPS && is_nearby(player->get_actor())) {
|
|
Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_EARTH_QUAKE);
|
|
new QuakeEffect(1, 200, player->get_actor());
|
|
}
|
|
|
|
if (has_surrounding_objs()) //add our surrounding objects back onto the map.
|
|
add_surrounding_objs_to_map();
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool U6Actor::check_move(uint16 new_x, uint16 new_y, uint8 new_z, ActorMoveFlags flags) {
|
|
// bool ignore_actors = flags & ACTOR_IGNORE_OTHERS;
|
|
const Tile *map_tile;
|
|
|
|
if (Actor::check_move(new_x, new_y, new_z, flags) == false)
|
|
return false;
|
|
|
|
if (obj_n == OBJ_U6_SILVER_SERPENT && check_move_silver_serpent(new_x, new_y) == false)
|
|
return false;
|
|
|
|
switch (current_movetype) {
|
|
case MOVETYPE_U6_ETHEREAL:
|
|
return true;
|
|
case MOVETYPE_U6_NONE :
|
|
return false;
|
|
case MOVETYPE_U6_WATER_HIGH : // for HIGH we only want to move to open water.
|
|
// No shorelines.
|
|
map_tile = map->get_tile(new_x, new_y, new_z, MAP_ORIGINAL_TILE);
|
|
if (map_tile->tile_num >= 16 && map_tile->tile_num <= 47)
|
|
return false;
|
|
|
|
if (!map->is_water(new_x, new_y, new_z))
|
|
return false;
|
|
break;
|
|
|
|
case MOVETYPE_U6_WATER_LOW :
|
|
if (!map->is_water(new_x, new_y, new_z))
|
|
return false;
|
|
break;
|
|
|
|
case MOVETYPE_U6_AIR_LOW :
|
|
map_tile = map->get_tile(new_x, new_y, new_z, MAP_ORIGINAL_TILE);
|
|
if (map_tile->flags1 & TILEFLAG_WALL) //low air boundary
|
|
return false;
|
|
|
|
map_tile = obj_manager->get_obj_tile(new_x, new_y, new_z, false);
|
|
if (map_tile && ((map_tile->flags1 & TILEFLAG_WALL) ||
|
|
(map_tile->flags2 & (TILEFLAG_DOUBLE_WIDTH | TILEFLAG_DOUBLE_HEIGHT)) == (TILEFLAG_DOUBLE_WIDTH | TILEFLAG_DOUBLE_HEIGHT)))
|
|
return false;
|
|
break;
|
|
|
|
case MOVETYPE_U6_AIR_HIGH :
|
|
if (map->is_boundary(new_x, new_y, new_z))
|
|
return false; //FIX for proper air boundary
|
|
break;
|
|
case MOVETYPE_U6_LAND :
|
|
default :
|
|
if (map->is_passable(new_x, new_y, new_z) == false) {
|
|
if (obj_n == OBJ_U6_MOUSE // try to go through mousehole
|
|
&& (obj_manager->get_obj_of_type_from_location(OBJ_U6_MOUSEHOLE, new_x, new_y, new_z) != nullptr
|
|
|| obj_manager->get_obj_of_type_from_location(OBJ_U6_BARS, new_x, new_y, new_z) != nullptr
|
|
|| obj_manager->get_obj_of_type_from_location(OBJ_U6_PORTCULLIS, new_x, new_y, new_z) != nullptr))
|
|
return true;
|
|
if (obj_n == OBJ_U6_SILVER_SERPENT //silver serpents can crossover themselves
|
|
&& obj_manager->get_obj_of_type_from_location(OBJ_U6_SILVER_SERPENT, new_x, new_y, new_z) != nullptr)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool U6Actor::check_move_silver_serpent(uint16 new_x, uint16 new_y) {
|
|
if (new_x != x && new_y != y) //snakes can't move diagonally
|
|
return false;
|
|
|
|
Obj *obj = (Obj *)surrounding_objects.front(); //retrieve the first body segment.
|
|
|
|
if (obj->x == new_x && obj->y == new_y) //snakes can't move backwards.
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// attempt to sit if obj is a chair.
|
|
|
|
bool U6Actor::sit_on_chair(Obj *obj) {
|
|
if (actor_type->can_sit && obj) {
|
|
if (obj->obj_n == OBJ_U6_CHAIR) { // make the actor sit on a chair.
|
|
if (obj_n == OBJ_U6_MUSICIAN_PLAYING)
|
|
frame_n = (obj->frame_n * 2);
|
|
else
|
|
frame_n = (obj->frame_n * 4) + 3;
|
|
direction = static_cast<NuvieDir>(obj->frame_n);
|
|
can_move = false;
|
|
return true;
|
|
}
|
|
|
|
//make actor sit on LB's throne.
|
|
if (obj->obj_n == OBJ_U6_THRONE && obj->x != x) { //throne is a double width obj. We only sit on the left tile.
|
|
frame_n = 8 + 3; //sitting facing south.
|
|
direction = NUVIE_DIR_S;
|
|
can_move = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint8 U6Actor::get_object_readiable_location(Obj *obj) {
|
|
uint16 i;
|
|
|
|
for (i = 0; readiable_objects[i].obj_n != OBJ_U6_NOTHING; i++) {
|
|
if (obj->obj_n == readiable_objects[i].obj_n)
|
|
return readiable_objects[i].readiable_location;
|
|
}
|
|
|
|
return ACTOR_NOT_READIABLE;
|
|
}
|
|
|
|
const CombatType *U6Actor::get_object_combat_type(uint16 objN) {
|
|
uint16 i;
|
|
|
|
for (i = 0; u6combat_objects[i].obj_n != OBJ_U6_NOTHING; i++) {
|
|
if (objN == u6combat_objects[i].obj_n)
|
|
return &u6combat_objects[i];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const CombatType *U6Actor::get_hand_combat_type() const {
|
|
if (obj_n == OBJ_U6_SHIP)
|
|
return &u6combat_ship_cannon;
|
|
|
|
return &u6combat_hand;
|
|
}
|
|
|
|
bool U6Actor::weapon_can_hit(const CombatType *weapon, Actor *target, uint16 *hit_x, uint16 *hit_y) {
|
|
if (Actor::weapon_can_hit(weapon, target->get_x(), target->get_y())) {
|
|
*hit_x = target->get_x();
|
|
*hit_y = target->get_y();
|
|
return true;
|
|
}
|
|
|
|
const Std::list<Obj *> &surrounding_objs = target->get_surrounding_obj_list();
|
|
for (Obj *obj : surrounding_objs) {
|
|
if (Actor::weapon_can_hit(weapon, obj->x, obj->y)) {
|
|
*hit_x = obj->x;
|
|
*hit_y = obj->y;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
uint16 target_x = target->get_x();
|
|
uint16 target_y = target->get_y();
|
|
|
|
Tile *tile = target->get_tile();
|
|
if (tile->dbl_width && tile->dbl_height) {
|
|
if (Actor::weapon_can_hit(weapon, target_x - 1, target_y - 1)) {
|
|
*hit_x = target_x - 1;
|
|
*hit_y = target_y - 1;
|
|
return true;
|
|
}
|
|
}
|
|
if (tile->dbl_width) {
|
|
if (Actor::weapon_can_hit(weapon, target_x - 1, target_y)) {
|
|
*hit_x = target_x - 1;
|
|
*hit_y = target_y;
|
|
return true;
|
|
}
|
|
}
|
|
if (tile->dbl_height) {
|
|
if (Actor::weapon_can_hit(weapon, target_x, target_y - 1)) {
|
|
*hit_x = target_x;
|
|
*hit_y = target_y - 1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void U6Actor::twitch() {
|
|
|
|
if (can_twitch() == false)
|
|
return;
|
|
|
|
if (NUVIE_RAND() % actor_type->twitch_rand == 1)
|
|
do_twitch();
|
|
|
|
return;
|
|
}
|
|
|
|
void U6Actor::do_twitch() {
|
|
if (actor_type->frames_per_direction == 0)
|
|
walk_frame = (walk_frame + 1) % 4;
|
|
else
|
|
walk_frame = NUVIE_RAND() % actor_type->frames_per_direction;
|
|
|
|
if (has_surrounding_objs()) {
|
|
switch (obj_n) {
|
|
case OBJ_U6_HYDRA :
|
|
twitch_surrounding_hydra_objs();
|
|
break;
|
|
case OBJ_U6_DRAGON :
|
|
default :
|
|
twitch_surrounding_objs();
|
|
break;
|
|
}
|
|
}
|
|
|
|
frame_n = actor_type->tile_start_offset + (direction * actor_type->tiles_per_direction + (walk_frame * actor_type->tiles_per_frame) + actor_type->tiles_per_frame - 1);
|
|
if (obj_n == OBJ_U6_WISP) {
|
|
Game::get_game()->get_map_window()->updateAmbience();
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_paralyzed(bool paralyzed) {
|
|
if (paralyzed) {
|
|
status_flags |= ACTOR_STATUS_PARALYZED;
|
|
} else {
|
|
status_flags &= (0xff ^ ACTOR_STATUS_PARALYZED);
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_protected(bool val) {
|
|
if (val) {
|
|
status_flags |= ACTOR_STATUS_PROTECTED;
|
|
} else {
|
|
status_flags &= (0xff ^ ACTOR_STATUS_PROTECTED);
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_charmed(bool val) {
|
|
if (val) {
|
|
obj_flags |= OBJ_STATUS_CHARMED;
|
|
} else {
|
|
obj_flags &= (0xff ^ OBJ_STATUS_CHARMED);
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_corpser_flag(bool val) {
|
|
if (val) {
|
|
movement_flags |= ACTOR_MOVEMENT_FLAGS_CORPSER;
|
|
} else {
|
|
movement_flags &= (0xff ^ ACTOR_MOVEMENT_FLAGS_CORPSER);
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_cursed(bool val) {
|
|
if (val) {
|
|
obj_flags |= OBJ_STATUS_CURSED;
|
|
} else {
|
|
obj_flags &= (0xff ^ OBJ_STATUS_CURSED);
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_asleep(bool val) {
|
|
if (val) {
|
|
status_flags |= ACTOR_STATUS_ASLEEP;
|
|
if (actor_type->dead_obj_n != OBJ_U6_NOTHING && actor_type->can_laydown) {
|
|
obj_n = actor_type->dead_obj_n;
|
|
frame_n = actor_type->dead_frame_n;
|
|
}
|
|
} else {
|
|
status_flags &= (0xff ^ ACTOR_STATUS_ASLEEP);
|
|
if (obj_n == base_actor_type->dead_obj_n || obj_n == OBJ_U6_PERSON_SLEEPING) {
|
|
if (worktype == WORKTYPE_U6_SLEEP)
|
|
can_move = true;
|
|
actor_type = base_actor_type;
|
|
obj_n = base_actor_type->base_obj_n;
|
|
frame_n = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void U6Actor::set_worktype(uint8 new_worktype, bool init) {
|
|
if (new_worktype == worktype)
|
|
return;
|
|
|
|
if (worktype == WORKTYPE_U6_SLEEP || worktype == WORKTYPE_U6_PLAY_LUTE) {
|
|
frame_n = old_frame_n;
|
|
}
|
|
|
|
//reset to base obj_n
|
|
if ((!is_in_party() || worktype > 0xe) && base_actor_type->base_obj_n != OBJ_U6_NOTHING) //don't revert for party worktypes as they might be riding a horse.
|
|
set_actor_obj_n(base_actor_type->base_obj_n);
|
|
|
|
if (worktype == WORKTYPE_U6_SLEEP && (status_flags & ACTOR_STATUS_ASLEEP)) //FIXME do we still need this??
|
|
status_flags ^= ACTOR_STATUS_ASLEEP;
|
|
|
|
Actor::set_worktype(new_worktype);
|
|
|
|
if (worktype == WORKTYPE_U6_WALK_TO_LOCATION) {
|
|
setup_walk_to_location();
|
|
}
|
|
|
|
//FIX from here.
|
|
|
|
switch (worktype) {
|
|
case WORKTYPE_U6_FACE_NORTH :
|
|
set_direction(NUVIE_DIR_N);
|
|
break;
|
|
case WORKTYPE_U6_FACE_EAST :
|
|
set_direction(NUVIE_DIR_E);
|
|
break;
|
|
case WORKTYPE_U6_FACE_SOUTH :
|
|
set_direction(NUVIE_DIR_S);
|
|
break;
|
|
case WORKTYPE_U6_FACE_WEST :
|
|
set_direction(NUVIE_DIR_W);
|
|
break;
|
|
|
|
case WORKTYPE_U6_SLEEP :
|
|
wt_sleep(init);
|
|
break;
|
|
case WORKTYPE_U6_PLAY_LUTE :
|
|
wt_play_lute();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void U6Actor::pathfind_to(const MapCoord &d) {
|
|
if (pathfinder) {
|
|
pathfinder->set_actor(this);
|
|
pathfinder->set_goal(d);
|
|
} else
|
|
set_pathfinder(new SchedPathFinder(this, d, new U6AStarPath));
|
|
|
|
pathfinder->update_location();
|
|
}
|
|
|
|
void U6Actor::setup_walk_to_location() {
|
|
if (sched[sched_pos] != nullptr) {
|
|
if (x == sched[sched_pos]->x && y == sched[sched_pos]->y
|
|
&& z == sched[sched_pos]->z) {
|
|
set_worktype(sched[sched_pos]->worktype);
|
|
delete_pathfinder();
|
|
return;
|
|
}
|
|
if (!pathfinder) {
|
|
work_location.x = sched[sched_pos]->x;
|
|
work_location.y = sched[sched_pos]->y;
|
|
work_location.z = sched[sched_pos]->z;
|
|
// if(!work_location.is_visible() || !get_location().is_visible())
|
|
// set_pathfinder(new OffScreenPathFinder(this, work_location, new U6AStarPath));
|
|
// else
|
|
set_pathfinder(new SchedPathFinder(this, work_location, new U6AStarPath));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// wander around but don't cross boundaries or fences. Used for cows and horses.
|
|
// now that hazards are working properly, this isn't needed --SB-X
|
|
/*void U6Actor::wt_farm_animal_wander()
|
|
{
|
|
uint8 new_direction;
|
|
sint8 rel_x = 0, rel_y = 0;
|
|
|
|
if(NUVIE_RAND()%8 == 1)
|
|
{
|
|
new_direction = NUVIE_RAND()%4;
|
|
|
|
switch(new_direction)
|
|
{
|
|
case NUVIE_DIR_N : rel_y = -1; break;
|
|
case NUVIE_DIR_E : rel_x = 1; break;
|
|
case NUVIE_DIR_S : rel_y = 1; break;
|
|
case NUVIE_DIR_W : rel_x = -1; break;
|
|
}
|
|
|
|
if(obj_manager->get_obj_of_type_from_location(OBJ_U6_FENCE,x + rel_x, y + rel_y, z) == nullptr)
|
|
{
|
|
if(moveRelative(rel_x,rel_y))
|
|
set_direction(new_direction);
|
|
}
|
|
|
|
}
|
|
else set_moves_left(moves - 5);
|
|
|
|
return;
|
|
}*/
|
|
|
|
void U6Actor::wt_sleep(bool init) {
|
|
if (init && !is_sleeping())
|
|
return;
|
|
Obj *obj = obj_manager->get_obj(x, y, z);
|
|
|
|
can_move = false;
|
|
status_flags |= ACTOR_STATUS_ASLEEP;
|
|
if (obj) {
|
|
if (obj->obj_n == OBJ_U6_BED) {
|
|
if (obj->frame_n == 1 || obj->frame_n == 5) { //horizontal bed
|
|
old_frame_n = frame_n;
|
|
obj_n = OBJ_U6_PERSON_SLEEPING;
|
|
frame_n = 0;
|
|
}
|
|
if (obj->frame_n == 7 || obj->frame_n == 10) { //vertical bed
|
|
old_frame_n = frame_n;
|
|
obj_n = OBJ_U6_PERSON_SLEEPING;
|
|
frame_n = 1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// lay down on the ground using the dead body frame
|
|
if (actor_type->can_laydown) {
|
|
old_frame_n = frame_n;
|
|
obj_n = actor_type->dead_obj_n;
|
|
frame_n = actor_type->dead_frame_n;
|
|
}
|
|
|
|
}
|
|
|
|
void U6Actor::wt_play_lute() {
|
|
set_actor_obj_n(OBJ_U6_MUSICIAN_PLAYING);
|
|
|
|
frame_n = direction * actor_type->tiles_per_direction;
|
|
Obj *obj = obj_manager->get_obj(x, y, z);
|
|
sit_on_chair(obj); // attempt to sit on obj.
|
|
|
|
return;
|
|
}
|
|
|
|
void U6Actor::set_actor_obj_n(uint16 new_obj_n) {
|
|
old_frame_n = frame_n;
|
|
|
|
obj_n = new_obj_n;
|
|
actor_type = get_actor_type(new_obj_n);
|
|
|
|
return;
|
|
}
|
|
|
|
inline const U6ActorType *U6Actor::get_actor_type(uint16 new_obj_n) {
|
|
const U6ActorType *type;
|
|
|
|
for (type = u6ActorTypes; type->base_obj_n != OBJ_U6_NOTHING; type++) {
|
|
if (type->base_obj_n == new_obj_n)
|
|
break;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
inline bool U6Actor::has_surrounding_objs() {
|
|
if (actor_type->tile_type == ACTOR_DT || actor_type->tile_type == ACTOR_MT)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
inline void U6Actor::remove_surrounding_objs_from_map() {
|
|
for (Obj *obj : surrounding_objects)
|
|
obj_manager->remove_obj_from_map(obj);
|
|
|
|
return;
|
|
}
|
|
|
|
inline void U6Actor::add_surrounding_objs_to_map() {
|
|
for (Obj *obj : surrounding_objects)
|
|
obj_manager->add_obj(obj, OBJ_ADD_TOP);
|
|
|
|
return;
|
|
}
|
|
|
|
inline void U6Actor::move_surrounding_objs_relative(sint16 rel_x, sint16 rel_y) {
|
|
if (obj_n == OBJ_U6_SILVER_SERPENT) {
|
|
move_silver_serpent_objs_relative(rel_x, rel_y);
|
|
} else {
|
|
for (Obj *obj : surrounding_objects) {
|
|
obj->x = WRAPPED_COORD(obj->x + rel_x, z);
|
|
obj->y = WRAPPED_COORD(obj->y + rel_y, z);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
inline void U6Actor::move_silver_serpent_objs_relative(sint16 rel_x, sint16 rel_y) {
|
|
static const uint8 new_frame_n_tbl[5][5] = {
|
|
{ 8, 10, 0, 13, 0},
|
|
{12, 9, 0, 0, 13},
|
|
{ 0, 0, 0, 0, 0},
|
|
{11, 0, 0, 9, 10},
|
|
{ 0, 11, 0, 12, 8}
|
|
};
|
|
|
|
static const uint8 new_tail_frame_n_tbl[8][6] = {
|
|
{0, 0, 0, 0, 0, 0},
|
|
{1, 0, 0, 3, 7, 0},
|
|
{0, 0, 0, 0, 0, 0},
|
|
{0, 3, 0, 0, 5, 1},
|
|
{0, 0, 0, 0, 0, 0},
|
|
{5, 0, 3, 0, 0, 7},
|
|
{0, 0, 0, 0, 0, 0},
|
|
{0, 7, 1, 5, 0, 0}
|
|
};
|
|
|
|
if (surrounding_objects.empty())
|
|
return;
|
|
|
|
Std::list<Obj *>::iterator obj = surrounding_objects.begin();
|
|
|
|
sint8 new_pos = 2 + rel_x + (rel_y * 2);
|
|
|
|
uint16 old_x = (*obj)->x;
|
|
uint16 old_y = (*obj)->y;
|
|
|
|
(*obj)->x = x - rel_x; // old actor x
|
|
(*obj)->y = y - rel_y; // old actor y
|
|
|
|
sint8 old_pos = 2 + ((*obj)->x - old_x) + (((*obj)->y - old_y) * 2);
|
|
|
|
uint8 objFrameN = (*obj)->frame_n;
|
|
(*obj)->frame_n = new_frame_n_tbl[new_pos][old_pos];
|
|
obj++;
|
|
for (; obj != surrounding_objects.end(); obj++) {
|
|
uint16 tmp_x = (*obj)->x;
|
|
uint16 tmp_y = (*obj)->y;
|
|
uint8 tmp_frame_n = (*obj)->frame_n;
|
|
|
|
(*obj)->x = old_x;
|
|
(*obj)->y = old_y;
|
|
|
|
if (tmp_frame_n < 8) //tail, work out new tail direction
|
|
(*obj)->frame_n = new_tail_frame_n_tbl[tmp_frame_n][objFrameN - 8];
|
|
else
|
|
(*obj)->frame_n = objFrameN;
|
|
|
|
old_x = tmp_x;
|
|
old_y = tmp_y;
|
|
objFrameN = tmp_frame_n;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
inline void U6Actor::set_direction_of_surrounding_objs(NuvieDir new_direction) {
|
|
remove_surrounding_objs_from_map();
|
|
|
|
switch (obj_n) {
|
|
case OBJ_U6_SHIP :
|
|
set_direction_of_surrounding_ship_objs(new_direction);
|
|
break;
|
|
|
|
case OBJ_U6_GIANT_SCORPION :
|
|
case OBJ_U6_GIANT_ANT :
|
|
case OBJ_U6_COW :
|
|
case OBJ_U6_ALLIGATOR :
|
|
case OBJ_U6_HORSE :
|
|
case OBJ_U6_HORSE_WITH_RIDER :
|
|
set_direction_of_surrounding_splitactor_objs(new_direction);
|
|
break;
|
|
|
|
case OBJ_U6_DRAGON :
|
|
set_direction_of_surrounding_dragon_objs(new_direction);
|
|
break;
|
|
}
|
|
|
|
add_surrounding_objs_to_map();
|
|
|
|
return;
|
|
}
|
|
|
|
inline void U6Actor::set_direction_of_surrounding_ship_objs(NuvieDir new_direction) {
|
|
Std::list<Obj *>::iterator obj = surrounding_objects.begin();
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
|
|
uint16 pitch = map->get_width(z);
|
|
|
|
(*obj)->x = x;
|
|
(*obj)->y = y;
|
|
|
|
(*obj)->frame_n = new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1;
|
|
switch (new_direction) {
|
|
case NUVIE_DIR_N :
|
|
if (y == 0)
|
|
(*obj)->y = pitch - 1;
|
|
else
|
|
(*obj)->y = y - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_E :
|
|
if (x == pitch - 1)
|
|
(*obj)->x = 0;
|
|
else
|
|
(*obj)->x = x + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_S :
|
|
if (y == pitch - 1)
|
|
(*obj)->y = 0;
|
|
else
|
|
(*obj)->y = y + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_W :
|
|
if (x == 0)
|
|
(*obj)->x = pitch - 1;
|
|
else
|
|
(*obj)->x = x - 1;
|
|
break;
|
|
|
|
default:
|
|
error("Invalid dir for U6Actor::set_direction_of_surrounding_ship_objs");
|
|
}
|
|
|
|
obj++;
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
|
|
(*obj)->x = x;
|
|
(*obj)->y = y;
|
|
|
|
(*obj)->frame_n = 16 + (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);
|
|
switch (new_direction) {
|
|
case NUVIE_DIR_N :
|
|
if (y == pitch - 1)
|
|
(*obj)->y = 0;
|
|
else
|
|
(*obj)->y = y + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_E :
|
|
if (x == 0)
|
|
(*obj)->x = pitch - 1;
|
|
else
|
|
(*obj)->x = x - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_S :
|
|
if (y == 0)
|
|
(*obj)->y = pitch - 1;
|
|
else
|
|
(*obj)->y = y - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_W :
|
|
if (x == pitch - 1)
|
|
(*obj)->x = 0;
|
|
else
|
|
(*obj)->x = x + 1;
|
|
break;
|
|
|
|
default:
|
|
error("Invalid dir for U6Actor::set_direction_of_surrounding_ship_objs");
|
|
}
|
|
|
|
}
|
|
|
|
inline void U6Actor::set_direction_of_surrounding_splitactor_objs(NuvieDir new_direction) {
|
|
if (surrounding_objects.empty())
|
|
return;
|
|
|
|
uint16 pitch = map->get_width(z);
|
|
Obj *obj = surrounding_objects.back();
|
|
|
|
if (obj->frame_n < 8)
|
|
obj->frame_n = (get_reverse_direction(new_direction) * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1); //mutant actor
|
|
else
|
|
obj->frame_n = 8 + (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);
|
|
|
|
obj->x = x;
|
|
obj->y = y;
|
|
|
|
switch (new_direction) {
|
|
case NUVIE_DIR_N :
|
|
if (y == pitch - 1)
|
|
obj->y = 0;
|
|
else
|
|
obj->y = y + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_E :
|
|
if (x == 0)
|
|
obj->x = pitch - 1;
|
|
else
|
|
obj->x = x - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_S :
|
|
if (y == 0)
|
|
obj->y = pitch - 1;
|
|
else
|
|
obj->y = y - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_W :
|
|
if (x == pitch - 1)
|
|
obj->x = 0;
|
|
else
|
|
obj->x = x + 1;
|
|
break;
|
|
|
|
default:
|
|
error("Invalid direction in U6Actor::set_direction_of_surrounding_splitactor_objs");
|
|
}
|
|
|
|
}
|
|
|
|
inline void U6Actor::set_direction_of_surrounding_dragon_objs(NuvieDir new_direction) {
|
|
Std::list<Obj *>::iterator obj;
|
|
uint8 frame_offset = (new_direction * actor_type->tiles_per_direction + actor_type->tiles_per_frame - 1);
|
|
Obj *head, *tail, *wing1, *wing2;
|
|
|
|
//NOTE! this is dependent on the order the in which the objects are loaded in U6Actor::init_dragon()
|
|
|
|
obj = surrounding_objects.begin();
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
head = *obj;
|
|
head->frame_n = 8 + frame_offset;
|
|
head->x = x;
|
|
head->y = y;
|
|
|
|
obj++;
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
tail = *obj;
|
|
tail->frame_n = 16 + frame_offset;
|
|
tail->x = x;
|
|
tail->y = y;
|
|
|
|
obj++;
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
wing1 = *obj;
|
|
wing1->frame_n = 24 + frame_offset;
|
|
wing1->x = x;
|
|
wing1->y = y;
|
|
|
|
obj++;
|
|
if (obj == surrounding_objects.end())
|
|
return;
|
|
wing2 = *obj;
|
|
wing2->frame_n = 32 + frame_offset;
|
|
wing2->x = x;
|
|
wing2->y = y;
|
|
|
|
switch (new_direction) {
|
|
case NUVIE_DIR_N :
|
|
head->y = y - 1;
|
|
tail->y = y + 1;
|
|
wing1->x = x - 1;
|
|
wing2->x = x + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_E :
|
|
head->x = x + 1;
|
|
tail->x = x - 1;
|
|
wing1->y = y - 1;
|
|
wing2->y = y + 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_S :
|
|
head->y = y + 1;
|
|
tail->y = y - 1;
|
|
wing1->x = x + 1;
|
|
wing2->x = x - 1;
|
|
break;
|
|
|
|
case NUVIE_DIR_W :
|
|
head->x = x - 1;
|
|
tail->x = x + 1;
|
|
wing1->y = y + 1;
|
|
wing2->y = y - 1;
|
|
break;
|
|
|
|
default:
|
|
error("Invalid direction in U6Actor::set_direction_of_surrounding_dragon_objs");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inline void U6Actor::twitch_surrounding_objs() {
|
|
for (Obj *obj : surrounding_objects)
|
|
twitch_obj(obj);
|
|
}
|
|
|
|
inline void U6Actor::twitch_surrounding_dragon_objs() {
|
|
}
|
|
|
|
inline void U6Actor::twitch_surrounding_hydra_objs() {
|
|
Std::list<Obj *>::iterator obj;
|
|
int i;
|
|
|
|
//Note! list order is important here. As it corresponds to the frame order in the tile set. This is defined in init_hydra()
|
|
for (i = 0, obj = surrounding_objects.begin(); obj != surrounding_objects.end(); obj++, i += 4) {
|
|
if (NUVIE_RAND() % 4 == 0)
|
|
(*obj)->frame_n = i + (((*obj)->frame_n - i + 1) % 4);
|
|
}
|
|
}
|
|
|
|
inline void U6Actor::twitch_obj(Obj *obj) {
|
|
if (actor_type->frames_per_direction == 0) {
|
|
DEBUG(0, LEVEL_WARNING, "FIXME: %s frames_per_direction == 0\n", get_name());
|
|
obj->frame_n = (obj->frame_n / (1 * 4) * (1 * 4)) + direction * actor_type->tiles_per_direction +
|
|
walk_frame * actor_type->tiles_per_frame;
|
|
return;
|
|
}
|
|
|
|
switch (obj->obj_n) {
|
|
case OBJ_U6_GIANT_SCORPION :
|
|
case OBJ_U6_GIANT_ANT :
|
|
case OBJ_U6_COW :
|
|
case OBJ_U6_ALLIGATOR :
|
|
case OBJ_U6_HORSE :
|
|
if (obj->frame_n < 8) { //mutant actor with two heads
|
|
obj->frame_n = get_reverse_direction(direction) * actor_type->tiles_per_direction +
|
|
walk_frame * actor_type->tiles_per_frame;
|
|
return;
|
|
}
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
|
|
|
|
obj->frame_n = (obj->frame_n / (actor_type->frames_per_direction * 4) * (actor_type->frames_per_direction * 4)) + direction * actor_type->tiles_per_direction +
|
|
walk_frame * actor_type->tiles_per_frame;
|
|
}
|
|
|
|
inline void U6Actor::clear_surrounding_objs_list(bool delete_objs) {
|
|
if (surrounding_objects.empty())
|
|
return;
|
|
|
|
if (delete_objs == false) {
|
|
surrounding_objects.clear();
|
|
return;
|
|
}
|
|
|
|
while (!surrounding_objects.empty()) {
|
|
Obj *obj = surrounding_objects.front();
|
|
obj_manager->remove_obj_from_map(obj);
|
|
delete_obj(obj);
|
|
surrounding_objects.pop_front();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
inline void U6Actor::init_surrounding_obj(uint16 x_, uint16 y_, uint8 z_, uint16 actor_obj_n, uint16 objFrame_n) {
|
|
Obj *obj;
|
|
|
|
obj = obj_manager->get_obj_of_type_from_location(actor_obj_n, id_n, -1, x_, y_, z_);
|
|
if (obj == nullptr)
|
|
obj = obj_manager->get_obj_of_type_from_location(actor_obj_n, 0, -1, x_, y_, z_);
|
|
|
|
if (obj == nullptr) {
|
|
obj = new Obj();
|
|
obj->x = x_;
|
|
obj->y = y_;
|
|
obj->z = z_;
|
|
obj->obj_n = actor_obj_n;
|
|
obj->frame_n = objFrame_n;
|
|
obj_manager->add_obj(obj);
|
|
}
|
|
|
|
obj->quality = id_n;
|
|
add_surrounding_obj(obj);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void U6Actor::die(bool create_body) {
|
|
Game *game = Game::get_game();
|
|
Party *party = game->get_party();
|
|
Player *player = game->get_player();
|
|
MapCoord actor_loc = get_location();
|
|
|
|
if (party->get_member_num(this) == 0) //avatar
|
|
return; //The avatar can't die. They just get teleported back to LB's castle.
|
|
|
|
if (has_surrounding_objs())
|
|
clear_surrounding_objs_list(true);
|
|
|
|
set_dead_flag(true); // needed sooner for unready usecode of torches
|
|
if (game->is_armageddon())
|
|
inventory_drop_all();
|
|
else if (base_actor_type->dead_obj_n != OBJ_U6_NOTHING) {
|
|
if (create_body) {
|
|
Obj *dead_body = new Obj;
|
|
dead_body->obj_n = base_actor_type->dead_obj_n;
|
|
if (base_actor_type->dead_frame_n == 255) // dog, cat, mouse, deer, wolf, drake, mongbat
|
|
dead_body->frame_n = frame_n; // same frame the actor died
|
|
else if (base_actor_type->dead_obj_n == OBJ_U6_BLOOD)
|
|
dead_body->frame_n = NUVIE_RAND() % 3;
|
|
else
|
|
dead_body->frame_n = base_actor_type->dead_frame_n;
|
|
dead_body->x = actor_loc.x;
|
|
dead_body->y = actor_loc.y;
|
|
dead_body->z = actor_loc.z;
|
|
dead_body->quality = id_n;
|
|
dead_body->status = OBJ_STATUS_OK_TO_TAKE;
|
|
if (temp_actor)
|
|
dead_body->status |= OBJ_STATUS_TEMPORARY;
|
|
|
|
if (base_actor_type->dead_obj_n == OBJ_U6_BLOOD)
|
|
inventory_drop_all();
|
|
else // move my inventory into the dead body container
|
|
all_items_to_container(dead_body, false);
|
|
obj_manager->add_obj(dead_body, true);
|
|
}
|
|
} else if (create_body)
|
|
inventory_drop_all();
|
|
|
|
Actor::die();
|
|
|
|
if (is_in_party()) {
|
|
party->remove_actor(this, true);
|
|
if (player->get_actor() == this)
|
|
player->set_party_mode(party->get_actor(0)); //set party mode with the avatar as the leader.
|
|
}
|
|
|
|
if (party->get_member_num(this) != 0)
|
|
move(0, 0, 0, ACTOR_FORCE_MOVE); // FIXME: move to another plane, same coords
|
|
}
|
|
|
|
// frozen by worktype or status
|
|
bool U6Actor::is_immobile() const {
|
|
return (((worktype == WORKTYPE_U6_MOTIONLESS
|
|
|| worktype == WORKTYPE_U6_IMMOBILE) && !is_in_party())
|
|
|| get_corpser_flag() || is_sleeping() || is_paralyzed()
|
|
/*|| can_move == false*/); // can_move really means can_twitch/animate
|
|
}
|
|
|
|
bool U6Actor::can_twitch() {
|
|
return ((can_move || obj_n == OBJ_U6_MUSICIAN_PLAYING)
|
|
&& visible_flag
|
|
&& actor_type->twitch_rand != 0
|
|
&& !get_corpser_flag()
|
|
&& !is_sleeping()
|
|
&& !is_paralyzed());
|
|
}
|
|
|
|
bool U6Actor::can_be_passed(const Actor *other, bool ignoreParty) const {
|
|
// FIXME: Original U6 instead uses a function that checks if a game object (actor or object)
|
|
// can occupy a world location:
|
|
// Figure out which of the remaining tests are relevant to actors and add them here.
|
|
|
|
const U6Actor *other_ = static_cast<const U6Actor *>(other);
|
|
|
|
if (other_->ethereal)
|
|
return true;
|
|
|
|
if (other_ == this)
|
|
return true;
|
|
|
|
if (!other_->isFlying() && is_passable())
|
|
goto FinalTest;
|
|
|
|
// Flying actors can not pass each other unless both are in the party
|
|
if (other_->isFlying() && !isFlying())
|
|
goto FinalTest;
|
|
|
|
// If the ignoreParty flag is set and we are in the party and not immobilized,
|
|
// other members can pass us.
|
|
if (ignoreParty && is_in_party() && other_->is_in_party() && !is_immobile()
|
|
&& this != Game::get_game()->get_player()->get_actor())
|
|
return true;
|
|
|
|
return false; // default
|
|
|
|
FinalTest:
|
|
|
|
// Same type can not pass us
|
|
if (obj_n == other_->get_obj_n())
|
|
return false;
|
|
|
|
// Some actors do not block others (e.g. mice, rabbits etc.)
|
|
if (!isNonBlocking())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void U6Actor::print() {
|
|
Actor::print();
|
|
// might print U6Actor members here
|
|
}
|
|
|
|
/* Returns name of NPC worktype/activity (game specific) or nullptr. */
|
|
const char *U6Actor::get_worktype_string(uint32 wt) const {
|
|
const char *wt_string = nullptr;
|
|
if (wt == WORKTYPE_U6_MOTIONLESS) wt_string = "Motionless";
|
|
else if (wt == WORKTYPE_U6_PLAYER) wt_string = "Player";
|
|
else if (wt == WORKTYPE_U6_IN_PARTY) wt_string = "In Party";
|
|
else if (wt == WORKTYPE_U6_ANIMAL_WANDER) wt_string = "Graze (animal wander)";
|
|
else if (wt == WORKTYPE_U6_WALK_TO_LOCATION) wt_string = "Walk to Schedule";
|
|
else if (wt == WORKTYPE_U6_FACE_NORTH) wt_string = "Stand (North)";
|
|
else if (wt == WORKTYPE_U6_FACE_SOUTH) wt_string = "Stand (South)";
|
|
else if (wt == WORKTYPE_U6_FACE_EAST) wt_string = "Stand (East)";
|
|
else if (wt == WORKTYPE_U6_FACE_WEST) wt_string = "Stand (West)";
|
|
else if (wt == WORKTYPE_U6_WALK_NORTH_SOUTH) wt_string = "Guard North/South";
|
|
else if (wt == WORKTYPE_U6_WALK_EAST_WEST) wt_string = "Guard East/West";
|
|
else if (wt == WORKTYPE_U6_WANDER_AROUND) wt_string = "Wander";
|
|
else if (wt == WORKTYPE_U6_WORK) wt_string = "Loiter (work)";
|
|
else if (wt == WORKTYPE_U6_SLEEP) wt_string = "Sleep";
|
|
else if (wt == WORKTYPE_U6_PLAY_LUTE) wt_string = "Play";
|
|
else if (wt == WORKTYPE_U6_BEG) wt_string = "Converse";
|
|
else if (wt == WORKTYPE_U6_COMBAT_FRONT) wt_string = "Combat Front";
|
|
else if (wt == 0x04) wt_string = "Combat Rear";
|
|
else if (wt == 0x05) wt_string = "Combat Flank";
|
|
else if (wt == 0x06) wt_string = "Combat Berserk";
|
|
else if (wt == 0x07) wt_string = "Combat Retreat";
|
|
else if (wt == 0x08) wt_string = "Combat Assault/Wild";
|
|
else if (wt == 0x09) wt_string = "Shy";
|
|
else if (wt == 0x0a) wt_string = "Like";
|
|
else if (wt == 0x0b) wt_string = "Unfriendly";
|
|
else if (wt == 0x0d) wt_string = "Tangle";
|
|
else if (wt == 0x0e) wt_string = "Immobile";
|
|
else if (wt == 0x92) wt_string = "Sit";
|
|
else if (wt == 0x93) wt_string = "Eat";
|
|
else if (wt == 0x94) wt_string = "Farm";
|
|
else if (wt == 0x98) wt_string = "Ring Bell";
|
|
else if (wt == 0x99) wt_string = "Brawl";
|
|
else if (wt == 0x9a) wt_string = "Mousing";
|
|
else if (wt == 0x9b) wt_string = "Attack Party";
|
|
return wt_string;
|
|
}
|
|
|
|
/* Return the first food or drink object in inventory. */
|
|
Obj *U6Actor::inventory_get_food(Obj *container) {
|
|
U6UseCode *uc = (U6UseCode *)Game::get_game()->get_usecode();
|
|
U6LList *inv = container ? container->container : get_inventory_list();
|
|
U6Link *link = nullptr;
|
|
for (link = inv->start(); link != nullptr; link = link->next) {
|
|
Obj *obj = (Obj *)link->data;
|
|
if (uc->is_food(obj))
|
|
return obj;
|
|
if (obj->container) { // search within container
|
|
if ((obj = inventory_get_food(obj)))
|
|
return obj;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void U6Actor::inventory_make_all_objs_ok_to_take() {
|
|
U6LList *inventory = get_inventory_list();
|
|
|
|
if (!inventory)
|
|
return;
|
|
|
|
for (U6Link *link = inventory->start(); link != nullptr;) {
|
|
Obj *obj = (Obj *)link->data;
|
|
link = link->next;
|
|
|
|
obj->set_ok_to_take(true, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
/* Set worktype to normal non-combat activity. */
|
|
void U6Actor::revert_worktype() {
|
|
const Party *party = Game::get_game()->get_party();
|
|
if (is_in_party())
|
|
set_worktype(WORKTYPE_U6_IN_PARTY);
|
|
if (party->get_leader_actor() == this)
|
|
set_worktype(WORKTYPE_U6_PLAYER);
|
|
}
|
|
|
|
/* Maximum magic points is derived from Intelligence and base_obj_n. */
|
|
uint8 U6Actor::get_maxmagic() const {
|
|
return Game::get_game()->get_script()->actor_get_max_magic_points(this);
|
|
}
|
|
|
|
bool U6Actor::will_not_talk() const {
|
|
if (worktype == WORKTYPE_U6_COMBAT_RETREAT || worktype == 0x12 // guard arrest player
|
|
|| Game::get_game()->is_armageddon()
|
|
|| worktype == WORKTYPE_U6_ATTACK_PARTY || worktype == 0x13) // repel undead and retreat
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void U6Actor::handle_lightsource(uint8 hour) {
|
|
Obj *torch = inventory_get_readied_object(ACTOR_ARM);
|
|
if (torch && torch->obj_n != OBJ_U6_TORCH)
|
|
torch = nullptr;
|
|
Obj *torch2 = inventory_get_readied_object(ACTOR_ARM_2);
|
|
if (torch2 && torch2->obj_n != OBJ_U6_TORCH)
|
|
torch2 = nullptr;
|
|
if (torch || torch2) {
|
|
U6UseCode *useCode = (U6UseCode *)Game::get_game()->get_usecode();
|
|
if ((hour < 6 || hour > 18 || (z != 0 && z != 5)
|
|
|| Game::get_game()->get_weather()->is_eclipse())) {
|
|
if (torch && torch->frame_n == 0) {
|
|
if (torch->qty != 1)
|
|
torch->qty = 1;
|
|
useCode->torch(torch, USE_EVENT_USE);
|
|
}
|
|
if (torch2 && torch2->frame_n == 0) {
|
|
if (torch2->qty != 1)
|
|
torch2->qty = 1;
|
|
useCode->torch(torch2, USE_EVENT_USE);
|
|
}
|
|
} else {
|
|
if (torch && torch->frame_n == 1)
|
|
useCode->torch(torch, USE_EVENT_USE);
|
|
if (torch2 && torch2->frame_n == 1)
|
|
useCode->torch(torch2, USE_EVENT_USE);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8 U6Actor::get_hp_text_color() const {
|
|
uint8 hp_text_color = 0x48; // standard text color
|
|
|
|
if (is_poisoned()) // actor is poisoned, display their hp in green
|
|
hp_text_color = 0xa;
|
|
else if (get_hp() < 10) // actor is critical, display their hp in red.
|
|
hp_text_color = 0x0c;
|
|
|
|
return hp_text_color;
|
|
}
|
|
|
|
} // End of namespace Nuvie
|
|
} // End of namespace Ultima
|