779 lines
21 KiB
C++
779 lines
21 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/ultima4/ultima4.h"
|
|
#include "ultima/ultima4/map/map.h"
|
|
#include "ultima/ultima4/map/annotation.h"
|
|
#include "ultima/ultima4/game/context.h"
|
|
#include "ultima/ultima4/map/direction.h"
|
|
#include "ultima/ultima4/map/location.h"
|
|
#include "ultima/ultima4/map/movement.h"
|
|
#include "ultima/ultima4/game/object.h"
|
|
#include "ultima/ultima4/game/person.h"
|
|
#include "ultima/ultima4/game/player.h"
|
|
#include "ultima/ultima4/game/portal.h"
|
|
#include "ultima/ultima4/filesys/savegame.h"
|
|
#include "ultima/ultima4/map/tileset.h"
|
|
#include "ultima/ultima4/map/tilemap.h"
|
|
#include "ultima/ultima4/core/types.h"
|
|
#include "ultima/ultima4/core/utils.h"
|
|
#include "ultima/ultima4/core/settings.h"
|
|
|
|
namespace Ultima {
|
|
namespace Ultima4 {
|
|
|
|
bool MapCoords::operator==(const MapCoords &a) const {
|
|
return (x == a.x) && (y == a.y) && (z == a.z);
|
|
}
|
|
|
|
bool MapCoords::operator!=(const MapCoords &a) const {
|
|
return !operator==(a);
|
|
}
|
|
|
|
bool MapCoords::operator<(const MapCoords &a) const {
|
|
if (x > a.x)
|
|
return false;
|
|
if (y > a.y)
|
|
return false;
|
|
return z < a.z;
|
|
}
|
|
|
|
MapCoords &MapCoords::wrap(const Map *map) {
|
|
if (map && map->_borderBehavior == Map::BORDER_WRAP) {
|
|
while (x < 0)
|
|
x += map->_width;
|
|
while (y < 0)
|
|
y += map->_height;
|
|
while (x >= (int)map->_width)
|
|
x -= map->_width;
|
|
while (y >= (int)map->_height)
|
|
y -= map->_height;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MapCoords &MapCoords::putInBounds(const Map *map) {
|
|
if (map) {
|
|
if (x < 0)
|
|
x = 0;
|
|
if (x >= (int) map->_width)
|
|
x = map->_width - 1;
|
|
if (y < 0)
|
|
y = 0;
|
|
if (y >= (int) map->_height)
|
|
y = map->_height - 1;
|
|
if (z < 0)
|
|
z = 0;
|
|
if (z >= (int) map->_levels)
|
|
z = map->_levels - 1;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MapCoords &MapCoords::move(Direction d, const Map *map) {
|
|
switch (d) {
|
|
case DIR_NORTH:
|
|
y--;
|
|
break;
|
|
case DIR_EAST:
|
|
x++;
|
|
break;
|
|
case DIR_SOUTH:
|
|
y++;
|
|
break;
|
|
case DIR_WEST:
|
|
x--;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Wrap the coordinates if necessary
|
|
wrap(map);
|
|
|
|
return *this;
|
|
}
|
|
|
|
MapCoords &MapCoords::move(int dx, int dy, const Map *map) {
|
|
x += dx;
|
|
y += dy;
|
|
|
|
// Wrap the coordinates if necessary
|
|
wrap(map);
|
|
|
|
return *this;
|
|
}
|
|
|
|
int MapCoords::getRelativeDirection(const MapCoords &c, const Map *map) const {
|
|
int dx, dy, dirmask;
|
|
|
|
dirmask = DIR_NONE;
|
|
if (z != c.z)
|
|
return dirmask;
|
|
|
|
// Adjust our coordinates to find the closest path
|
|
if (map && map->_borderBehavior == Map::BORDER_WRAP) {
|
|
MapCoords me = *this;
|
|
|
|
if (abs(int(me.x - c.x)) > abs(int(me.x + map->_width - c.x)))
|
|
me.x += map->_width;
|
|
else if (abs(int(me.x - c.x)) > abs(int(me.x - map->_width - c.x)))
|
|
me.x -= map->_width;
|
|
|
|
if (abs(int(me.y - c.y)) > abs(int(me.y + map->_width - c.y)))
|
|
me.y += map->_height;
|
|
else if (abs(int(me.y - c.y)) > abs(int(me.y - map->_width - c.y)))
|
|
me.y -= map->_height;
|
|
|
|
dx = me.x - c.x;
|
|
dy = me.y - c.y;
|
|
} else {
|
|
dx = x - c.x;
|
|
dy = y - c.y;
|
|
}
|
|
|
|
// Add x directions that lead towards to_x to the mask
|
|
if (dx < 0) dirmask |= MASK_DIR(DIR_EAST);
|
|
else if (dx > 0) dirmask |= MASK_DIR(DIR_WEST);
|
|
|
|
// Add y directions that lead towards to_y to the mask
|
|
if (dy < 0) dirmask |= MASK_DIR(DIR_SOUTH);
|
|
else if (dy > 0) dirmask |= MASK_DIR(DIR_NORTH);
|
|
|
|
// Return the result
|
|
return dirmask;
|
|
}
|
|
|
|
Direction MapCoords::pathTo(const MapCoords &c, int valid_directions, bool towards, const Map *map) const {
|
|
int directionsToObject;
|
|
|
|
// Find the directions that lead [to/away from] our target
|
|
directionsToObject = towards ? getRelativeDirection(c, map) : ~getRelativeDirection(c, map);
|
|
|
|
// Make sure we eliminate impossible options
|
|
directionsToObject &= valid_directions;
|
|
|
|
// Get the new direction to move
|
|
if (directionsToObject > DIR_NONE)
|
|
return dirRandomDir(directionsToObject);
|
|
// There are no valid directions that lead to our target, just move wherever we can!
|
|
else
|
|
return dirRandomDir(valid_directions);
|
|
}
|
|
|
|
Direction MapCoords::pathAway(const MapCoords &c, int valid_directions) const {
|
|
return pathTo(c, valid_directions, false);
|
|
}
|
|
|
|
int MapCoords::movementDistance(const MapCoords &c, const Map *map) const {
|
|
int dirmask = DIR_NONE;
|
|
int dist = 0;
|
|
MapCoords me = *this;
|
|
|
|
if (z != c.z)
|
|
return -1;
|
|
|
|
// Get the direction(s) to the coordinates
|
|
dirmask = getRelativeDirection(c, map);
|
|
|
|
while ((me.x != c.x) || (me.y != c.y)) {
|
|
if (me.x != c.x) {
|
|
if (dirmask & MASK_DIR_WEST)
|
|
me.move(DIR_WEST, map);
|
|
else
|
|
me.move(DIR_EAST, map);
|
|
|
|
dist++;
|
|
}
|
|
if (me.y != c.y) {
|
|
if (dirmask & MASK_DIR_NORTH)
|
|
me.move(DIR_NORTH, map);
|
|
else
|
|
me.move(DIR_SOUTH, map);
|
|
|
|
dist++;
|
|
}
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
int MapCoords::distance(const MapCoords &c, const Map *map) const {
|
|
int dist = movementDistance(c, map);
|
|
if (dist <= 0)
|
|
return dist;
|
|
|
|
// Calculate how many fewer movements there would have been
|
|
dist -= abs(x - c.x) < abs(y - c.y) ? abs(x - c.x) : abs(y - c.y);
|
|
|
|
return dist;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------*/
|
|
|
|
Map::Map() : _id(0), _type(WORLD), _width(0), _height(0), _levels(1),
|
|
_chunkWidth(0), _chunkHeight(0), _offset(0), _flags(0),
|
|
_borderBehavior(BORDER_WRAP), _music(Music::NONE),
|
|
_tileSet(nullptr), _tileMap(nullptr), _blank(0) {
|
|
_annotations = new AnnotationMgr();
|
|
}
|
|
|
|
Map::~Map() {
|
|
for (auto *i : _portals)
|
|
delete i;
|
|
delete _annotations;
|
|
}
|
|
|
|
Common::String Map::getName() {
|
|
return _baseSource._fname;
|
|
}
|
|
|
|
Object *Map::objectAt(const Coords &coords) {
|
|
// FIXME: return a list instead of one object
|
|
ObjectDeque::const_iterator i;
|
|
Object *objAt = nullptr;
|
|
|
|
for (i = _objects.begin(); i != _objects.end(); i++) {
|
|
Object *obj = *i;
|
|
|
|
if (obj->getCoords() == coords) {
|
|
// Get the most visible object
|
|
if (objAt && (objAt->getType() == Object::UNKNOWN) && (obj->getType() != Object::UNKNOWN))
|
|
objAt = obj;
|
|
// Give priority to objects that have the focus
|
|
else if (objAt && (!objAt->hasFocus()) && (obj->hasFocus()))
|
|
objAt = obj;
|
|
else if (!objAt)
|
|
objAt = obj;
|
|
}
|
|
}
|
|
return objAt;
|
|
}
|
|
|
|
const Portal *Map::portalAt(const Coords &coords, int actionFlags) {
|
|
PortalList::const_iterator i;
|
|
|
|
for (i = _portals.begin(); i != _portals.end(); i++) {
|
|
if (((*i)->_coords == coords) &&
|
|
((*i)->_triggerAction & actionFlags))
|
|
return *i;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
MapTile *Map::getTileFromData(const Coords &coords) {
|
|
if (MAP_IS_OOB(this, coords))
|
|
return &_blank;
|
|
|
|
int index = coords.x + (coords.y * _width) + (_width * _height * coords.z);
|
|
return &_data[index];
|
|
}
|
|
|
|
MapTile *Map::tileAt(const Coords &coords, int withObjects) {
|
|
// FIXME: this should return a list of tiles, with the most visible at the front
|
|
MapTile *tile;
|
|
Common::List<Annotation *> a = _annotations->ptrsToAllAt(coords);
|
|
Common::List<Annotation *>::iterator i;
|
|
Object *obj = objectAt(coords);
|
|
|
|
tile = getTileFromData(coords);
|
|
|
|
// FIXME: this only returns the first valid annotation it can find
|
|
if (a.size() > 0) {
|
|
for (i = a.begin(); i != a.end(); i++) {
|
|
if (!(*i)->isVisualOnly())
|
|
return &(*i)->getTile();
|
|
}
|
|
}
|
|
|
|
if ((withObjects == WITH_OBJECTS) && obj)
|
|
tile = &obj->getTile();
|
|
else if ((withObjects == WITH_GROUND_OBJECTS) &&
|
|
obj &&
|
|
obj->getTile().getTileType()->isWalkable())
|
|
tile = &obj->getTile();
|
|
|
|
return tile;
|
|
}
|
|
|
|
const Tile *Map::tileTypeAt(const Coords &coords, int withObjects) {
|
|
MapTile *tile = tileAt(coords, withObjects);
|
|
return tile->getTileType();
|
|
}
|
|
|
|
bool Map::isWorldMap() {
|
|
return _type == WORLD;
|
|
}
|
|
|
|
bool Map::isEnclosed(const Coords &party) {
|
|
uint x, y;
|
|
int *path_data;
|
|
|
|
if (_borderBehavior != BORDER_WRAP)
|
|
return true;
|
|
|
|
path_data = new int[_width * _height];
|
|
memset(path_data, -1, sizeof(int) * _width * _height);
|
|
|
|
// Determine what's walkable (1), and what's border-walkable (2)
|
|
findWalkability(party, path_data);
|
|
|
|
// Find two connecting pathways where the avatar can reach both without wrapping
|
|
for (x = 0; x < _width; x++) {
|
|
int index = x;
|
|
if (path_data[index] == 2 && path_data[index + ((_height - 1)*_width)] == 2)
|
|
return false;
|
|
}
|
|
|
|
for (y = 0; y < _width; y++) {
|
|
int index = (y * _width);
|
|
if (path_data[index] == 2 && path_data[index + _width - 1] == 2)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Map::findWalkability(Coords coords, int *path_data) {
|
|
const Tile *mt = tileTypeAt(coords, WITHOUT_OBJECTS);
|
|
int index = coords.x + (coords.y * _width);
|
|
|
|
if (mt->isWalkable()) {
|
|
bool isBorderTile = (coords.x == 0) || (coords.x == signed(_width - 1)) || (coords.y == 0) || (coords.y == signed(_height - 1));
|
|
path_data[index] = isBorderTile ? 2 : 1;
|
|
|
|
if ((coords.x > 0) && path_data[coords.x - 1 + (coords.y * _width)] < 0)
|
|
findWalkability(Coords(coords.x - 1, coords.y, coords.z), path_data);
|
|
if ((coords.x < signed(_width - 1)) && path_data[coords.x + 1 + (coords.y * _width)] < 0)
|
|
findWalkability(Coords(coords.x + 1, coords.y, coords.z), path_data);
|
|
if ((coords.y > 0) && path_data[coords.x + ((coords.y - 1) * _width)] < 0)
|
|
findWalkability(Coords(coords.x, coords.y - 1, coords.z), path_data);
|
|
if ((coords.y < signed(_height - 1)) && path_data[coords.x + ((coords.y + 1) * _width)] < 0)
|
|
findWalkability(Coords(coords.x, coords.y + 1, coords.z), path_data);
|
|
} else {
|
|
path_data[index] = 0;
|
|
}
|
|
}
|
|
|
|
Creature *Map::addCreature(const Creature *creature, Coords coords) {
|
|
Creature *m = new Creature();
|
|
|
|
// Make a copy of the creature before placing it
|
|
*m = *creature;
|
|
|
|
m->setInitialHp();
|
|
m->setStatus(STAT_GOOD);
|
|
m->setCoords(coords);
|
|
m->setMap(this);
|
|
|
|
// initialize the creature before placing it
|
|
if (m->wanders())
|
|
m->setMovementBehavior(MOVEMENT_WANDER);
|
|
else if (m->isStationary())
|
|
m->setMovementBehavior(MOVEMENT_FIXED);
|
|
else m->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
|
|
|
|
// Hide camouflaged creatures from view during combat
|
|
if (m->camouflages() && (_type == COMBAT))
|
|
m->setVisible(false);
|
|
|
|
// place the creature on the map
|
|
_objects.push_back(m);
|
|
return m;
|
|
}
|
|
|
|
Object *Map::addObject(Object *obj, Coords coords) {
|
|
_objects.push_front(obj);
|
|
return obj;
|
|
}
|
|
|
|
Object *Map::addObject(MapTile tile, MapTile prevtile, Coords coords) {
|
|
Object *obj = new Object();
|
|
|
|
obj->setTile(tile);
|
|
obj->setPrevTile(prevtile);
|
|
obj->setCoords(coords);
|
|
obj->setPrevCoords(coords);
|
|
obj->setMap(this);
|
|
|
|
_objects.push_front(obj);
|
|
|
|
return obj;
|
|
}
|
|
|
|
void Map::removeObject(const Object *rem, bool deleteObject) {
|
|
ObjectDeque::iterator i;
|
|
for (i = _objects.begin(); i != _objects.end(); i++) {
|
|
if (*i == rem) {
|
|
// Party members persist through different maps, so don't delete them!
|
|
if (!isPartyMember(*i) && deleteObject)
|
|
delete(*i);
|
|
_objects.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ObjectDeque::iterator Map::removeObject(ObjectDeque::iterator rem, bool deleteObject) {
|
|
// Party members persist through different maps, so don't delete them!
|
|
if (!isPartyMember(*rem) && deleteObject)
|
|
delete(*rem);
|
|
return _objects.erase(rem);
|
|
}
|
|
|
|
Creature *Map::moveObjects(MapCoords avatar) {
|
|
Creature *attacker = nullptr;
|
|
|
|
for (auto *object : _objects) {
|
|
Creature *m = dynamic_cast<Creature *>(object);
|
|
|
|
if (m) {
|
|
/* check if the object is an attacking creature and not
|
|
just a normal, docile person in town or an inanimate object */
|
|
if ((m->getType() == Object::PERSON && m->getMovementBehavior() == MOVEMENT_ATTACK_AVATAR) ||
|
|
(m->getType() == Object::CREATURE && m->willAttack())) {
|
|
MapCoords o_coords = m->getCoords();
|
|
|
|
// Don't move objects that aren't on the same level as us
|
|
if (o_coords.z != avatar.z)
|
|
continue;
|
|
|
|
if (o_coords.movementDistance(avatar, this) <= 1) {
|
|
attacker = m;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Before moving, Enact any special effects of the creature (such as storms eating objects, whirlpools teleporting, etc.)
|
|
m->specialEffect();
|
|
|
|
|
|
// Perform any special actions (such as pirate ships firing cannons, sea serpents' fireblast attect, etc.)
|
|
if (!m->specialAction()) {
|
|
if (moveObject(this, m, avatar)) {
|
|
m->animateMovement();
|
|
// After moving, Enact any special effects of the creature (such as storms eating objects, whirlpools teleporting, etc.)
|
|
m->specialEffect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return attacker;
|
|
}
|
|
|
|
void Map::resetObjectAnimations() {
|
|
ObjectDeque::iterator i;
|
|
|
|
for (i = _objects.begin(); i != _objects.end(); i++) {
|
|
Object *obj = *i;
|
|
|
|
if (obj->getType() == Object::CREATURE)
|
|
obj->setPrevTile(creatureMgr->getByTile(obj->getTile())->getTile());
|
|
}
|
|
}
|
|
|
|
void Map::clearObjects() {
|
|
_objects.clear();
|
|
}
|
|
|
|
int Map::getNumberOfCreatures() {
|
|
ObjectDeque::const_iterator i;
|
|
int n = 0;
|
|
|
|
for (i = _objects.begin(); i != _objects.end(); i++) {
|
|
Object *obj = *i;
|
|
|
|
if (obj->getType() == Object::CREATURE)
|
|
n++;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
int Map::getValidMoves(MapCoords from, MapTile transport) {
|
|
int retval;
|
|
Direction d;
|
|
Object *obj;
|
|
const Creature *m, *to_m;
|
|
int ontoAvatar, ontoCreature;
|
|
MapCoords coords = from;
|
|
|
|
// Get the creature object, if it exists (the one that's moving)
|
|
m = creatureMgr->getByTile(transport);
|
|
|
|
bool isAvatar = (g_context->_location->_coords == coords);
|
|
if (m && m->canMoveOntoPlayer())
|
|
isAvatar = false;
|
|
|
|
retval = 0;
|
|
for (d = DIR_WEST; d <= DIR_SOUTH; d = (Direction)(d + 1)) {
|
|
coords = from;
|
|
ontoAvatar = 0;
|
|
ontoCreature = 0;
|
|
|
|
// Move the coordinates in the current direction and test it
|
|
coords.move(d, this);
|
|
|
|
// You can always walk off the edge of the map
|
|
if (MAP_IS_OOB(this, coords)) {
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
continue;
|
|
}
|
|
|
|
obj = objectAt(coords);
|
|
|
|
// See if it's trying to move onto the avatar
|
|
if ((_flags & SHOW_AVATAR) && (coords == g_context->_location->_coords))
|
|
ontoAvatar = 1;
|
|
|
|
// See if it's trying to move onto a person or creature
|
|
else if (obj && (obj->getType() != Object::UNKNOWN))
|
|
ontoCreature = 1;
|
|
|
|
// Get the destination tile
|
|
MapTile tile;
|
|
if (ontoAvatar)
|
|
tile = g_context->_party->getTransport();
|
|
else if (ontoCreature)
|
|
tile = obj->getTile();
|
|
else
|
|
tile = *tileAt(coords, WITH_OBJECTS);
|
|
|
|
MapTile prev_tile = *tileAt(from, WITHOUT_OBJECTS);
|
|
|
|
// Get the other creature object, if it exists (the one that's being moved onto)
|
|
to_m = dynamic_cast<Creature *>(obj);
|
|
|
|
// Move on if unable to move onto the avatar or another creature
|
|
if (m && !isAvatar) { // some creatures/persons have the same tile as the avatar, so we have to adjust
|
|
// If moving onto the avatar, the creature must be able to move onto the player
|
|
// If moving onto another creature, it must be able to move onto other creatures,
|
|
// and the creature must be able to have others move onto it. If either of
|
|
// these conditions are not met, the creature cannot move onto another.
|
|
|
|
if ((ontoAvatar && m->canMoveOntoPlayer()) || (ontoCreature && m->canMoveOntoCreatures()))
|
|
tile = *tileAt(coords, WITHOUT_OBJECTS); //Ignore all objects, and just consider terrain
|
|
if ((ontoAvatar && !m->canMoveOntoPlayer())
|
|
|| (
|
|
ontoCreature &&
|
|
(
|
|
(!m->canMoveOntoCreatures() && !to_m->canMoveOntoCreatures())
|
|
|| (m->isForceOfNature() && to_m->isForceOfNature())
|
|
)
|
|
)
|
|
)
|
|
continue;
|
|
}
|
|
|
|
// Avatar movement
|
|
if (isAvatar) {
|
|
// if the transport is a ship, check sailable
|
|
if (transport.getTileType()->isShip() && tile.getTileType()->isSailable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
// if it is a balloon, check flyable
|
|
else if (transport.getTileType()->isBalloon() && tile.getTileType()->isFlyable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
// avatar or horseback: check walkable
|
|
else if (transport == _tileSet->getByName("avatar")->getId() || transport.getTileType()->isHorse()) {
|
|
if (tile.getTileType()->canWalkOn(d) &&
|
|
(!transport.getTileType()->isHorse() || tile.getTileType()->isCreatureWalkable()) &&
|
|
prev_tile.getTileType()->canWalkOff(d))
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
// else if (ontoCreature && to_m->canMoveOntoPlayer()) {
|
|
// retval = DIR_ADD_TO_MASK(d, retval);
|
|
// }
|
|
}
|
|
|
|
// Creature movement
|
|
else if (m) {
|
|
// Flying creatures
|
|
if (tile.getTileType()->isFlyable() && m->flies()) {
|
|
// FIXME: flying creatures behave differently on the world map?
|
|
if (isWorldMap())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
else if (tile.getTileType()->isWalkable() ||
|
|
tile.getTileType()->isSwimable() ||
|
|
tile.getTileType()->isSailable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
// Swimming creatures and sailing creatures
|
|
else if (tile.getTileType()->isSwimable() ||
|
|
tile.getTileType()->isSailable() ||
|
|
tile.getTileType()->isShip()) {
|
|
if (m->swims() && tile.getTileType()->isSwimable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
if (m->sails() && tile.getTileType()->isSailable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
if (m->canMoveOntoPlayer() && tile.getTileType()->isShip())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
// Ghosts and other incorporeal creatures
|
|
else if (m->isIncorporeal()) {
|
|
// can move anywhere but onto water, unless of course the creature can swim
|
|
if (!(tile.getTileType()->isSwimable() ||
|
|
tile.getTileType()->isSailable()))
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
// Walking creatures
|
|
else if (m->walks()) {
|
|
if (tile.getTileType()->canWalkOn(d) &&
|
|
prev_tile.getTileType()->canWalkOff(d) &&
|
|
tile.getTileType()->isCreatureWalkable())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
// Creatures that can move onto player
|
|
else if (ontoAvatar && m->canMoveOntoPlayer()) {
|
|
|
|
// Tile should be transport
|
|
if (tile.getTileType()->isShip() && m->swims())
|
|
retval = DIR_ADD_TO_MASK(d, retval);
|
|
}
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool Map::move(Object *obj, Direction d) {
|
|
MapCoords new_coords = obj->getCoords();
|
|
if (new_coords.move(d) != obj->getCoords()) {
|
|
obj->setCoords(new_coords);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Map::alertGuards() {
|
|
ObjectDeque::iterator i;
|
|
const Creature *m;
|
|
|
|
// Switch all the guards to attack mode
|
|
for (i = _objects.begin(); i != _objects.end(); i++) {
|
|
m = creatureMgr->getByTile((*i)->getTile());
|
|
if (m && (m->getId() == GUARD_ID || m->getId() == LORDBRITISH_ID))
|
|
(*i)->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
|
|
}
|
|
}
|
|
|
|
MapCoords Map::getLabel(const Common::String &name) const {
|
|
Common::HashMap<Common::String, MapCoords>::const_iterator i = _labels.find(name);
|
|
if (i == _labels.end())
|
|
return MapCoords::nowhere();
|
|
|
|
return i->_value;
|
|
}
|
|
|
|
bool Map::fillMonsterTable() {
|
|
ObjectDeque::iterator current;
|
|
Object *obj;
|
|
ObjectDeque monsters;
|
|
ObjectDeque other_creatures;
|
|
ObjectDeque inanimate_objects;
|
|
Object empty;
|
|
|
|
int nCreatures = 0;
|
|
int nObjects = 0;
|
|
|
|
for (int idx = 0; idx < MONSTERTABLE_SIZE; ++idx)
|
|
_monsterTable[idx].clear();
|
|
|
|
/**
|
|
* First, categorize all the objects we have
|
|
*/
|
|
for (current = _objects.begin(); current != _objects.end(); current++) {
|
|
obj = *current;
|
|
|
|
// Moving objects first
|
|
if ((obj->getType() == Object::CREATURE) && (obj->getMovementBehavior() != MOVEMENT_FIXED)) {
|
|
Creature *c = dynamic_cast<Creature *>(obj);
|
|
assert(c);
|
|
// Whirlpools and storms are separated from other moving objects
|
|
if (c->getId() == WHIRLPOOL_ID || c->getId() == STORM_ID)
|
|
monsters.push_back(obj);
|
|
else
|
|
other_creatures.push_back(obj);
|
|
} else inanimate_objects.push_back(obj);
|
|
}
|
|
|
|
/**
|
|
* Add other monsters to our whirlpools and storms
|
|
*/
|
|
while (other_creatures.size() && nCreatures < MONSTERTABLE_CREATURES_SIZE) {
|
|
monsters.push_back(other_creatures.front());
|
|
other_creatures.pop_front();
|
|
}
|
|
|
|
/**
|
|
* Add empty objects to our list to fill things up
|
|
*/
|
|
while (monsters.size() < MONSTERTABLE_CREATURES_SIZE)
|
|
monsters.push_back(&empty);
|
|
|
|
/**
|
|
* Finally, add inanimate objects
|
|
*/
|
|
while (inanimate_objects.size() && nObjects < MONSTERTABLE_OBJECTS_SIZE) {
|
|
monsters.push_back(inanimate_objects.front());
|
|
inanimate_objects.pop_front();
|
|
}
|
|
|
|
/**
|
|
* Fill in the blanks
|
|
*/
|
|
while (monsters.size() < MONSTERTABLE_SIZE)
|
|
monsters.push_back(&empty);
|
|
|
|
/**
|
|
* Fill in our monster table
|
|
*/
|
|
int i = 0;
|
|
TileMap *base = g_tileMaps->get("base");
|
|
for (auto *monster : monsters) {
|
|
Coords c = monster->getCoords(),
|
|
prevc = monster->getPrevCoords();
|
|
|
|
_monsterTable[i]._tile = base->untranslate(monster->getTile());
|
|
_monsterTable[i]._x = c.x;
|
|
_monsterTable[i]._y = c.y;
|
|
_monsterTable[i]._prevTile = base->untranslate(monster->getPrevTile());
|
|
_monsterTable[i]._prevX = prevc.x;
|
|
_monsterTable[i]._prevY = prevc.y;
|
|
i++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
MapTile Map::translateFromRawTileIndex(int raw) const {
|
|
assertMsg(_tileMap != nullptr, "tilemap hasn't been set");
|
|
|
|
return _tileMap->translate(raw);
|
|
}
|
|
|
|
uint Map::translateToRawTileIndex(MapTile &tile) const {
|
|
return _tileMap->untranslate(tile);
|
|
}
|
|
|
|
} // End of namespace Ultima4
|
|
} // End of namespace Ultima
|