Initial commit
This commit is contained in:
778
engines/ultima/ultima4/map/map.cpp
Normal file
778
engines/ultima/ultima4/map/map.cpp
Normal file
@@ -0,0 +1,778 @@
|
||||
/* 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
|
||||
Reference in New Issue
Block a user