/* 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 .
*
*/
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/map/tileset.h"
namespace Ultima {
namespace Ultima4 {
/**
* Push a location onto the stack
*/
static Location *locationPush(Location *stack, Location *loc) {
loc->_prev = stack;
return loc;
}
/**
* Pop a location off the stack
*/
static Location *locationPop(Location **stack) {
Location *loc = *stack;
*stack = (*stack)->_prev;
loc->_prev = nullptr;
return loc;
}
/*-------------------------------------------------------------------*/
Location::Location(MapCoords coords, Map *map, int viewmode, LocationContext ctx,
TurnCompleter *turnCompleter, Location *prev) {
this->_coords = coords;
this->_map = map;
this->_viewMode = viewmode;
this->_context = ctx;
this->_turnCompleter = turnCompleter;
locationPush(prev, this);
}
Std::vector Location::tilesAt(MapCoords coords, bool &focus) {
Std::vector tiles;
Common::List a = _map->_annotations->ptrsToAllAt(coords);
Common::List::iterator i;
Object *obj = _map->objectAt(coords);
Creature *m = dynamic_cast(obj);
focus = false;
bool avatar = this->_coords == coords;
// Do not return objects for VIEW_GEM mode, show only the avatar and tiles
if (_viewMode == VIEW_GEM && (!settings._enhancements || !settings._enhancementsOptions._peerShowsObjects)) {
// When viewing a gem, always show the avatar regardless of whether or not
// it is shown in our normal view
if (avatar)
tiles.push_back(g_context->_party->getTransport());
else
tiles.push_back(*_map->getTileFromData(coords));
return tiles;
}
// Add the avatar to gem view
if (avatar && _viewMode == VIEW_GEM)
tiles.push_back(g_context->_party->getTransport());
// Add visual-only annotations to the list
for (i = a.begin(); i != a.end(); i++) {
if ((*i)->isVisualOnly()) {
tiles.push_back((*i)->getTile());
/* If this is the first cover-up annotation,
* everything underneath it will be invisible,
* so stop here
*/
if ((*i)->isCoverUp())
return tiles;
}
}
// then the avatar is drawn (unless on a ship)
if ((_map->_flags & SHOW_AVATAR) && (g_context->_transportContext != TRANSPORT_SHIP) && avatar)
//tiles.push_back(map->tileset->getByName("avatar")->id);
tiles.push_back(g_context->_party->getTransport());
// then camouflaged creatures that have a disguise
if (obj && (obj->getType() == Object::CREATURE) && !obj->isVisible() && (!m->getCamouflageTile().empty())) {
focus = focus || obj->hasFocus();
tiles.push_back(_map->_tileSet->getByName(m->getCamouflageTile())->getId());
}
// then visible creatures and objects
else if (obj && obj->isVisible()) {
focus = focus || obj->hasFocus();
MapTile visibleCreatureAndObjectTile = obj->getTile();
//Sleeping creatures and persons have their animation frozen
if (m && m->isAsleep())
visibleCreatureAndObjectTile._freezeAnimation = true;
tiles.push_back(visibleCreatureAndObjectTile);
}
// then the party's ship (because twisters and whirlpools get displayed on top of ships)
if ((_map->_flags & SHOW_AVATAR) && (g_context->_transportContext == TRANSPORT_SHIP) && avatar)
tiles.push_back(g_context->_party->getTransport());
// then permanent annotations
for (i = a.begin(); i != a.end(); i++) {
if (!(*i)->isVisualOnly()) {
tiles.push_back((*i)->getTile());
/* If this is the first cover-up annotation,
* everything underneath it will be invisible,
* so stop here
*/
if ((*i)->isCoverUp())
return tiles;
}
}
// finally the base tile
MapTile tileFromMapData = *_map->getTileFromData(coords);
const Tile *tileType = _map->getTileFromData(coords)->getTileType();
if (tileType->isLivingObject()) {
//This animation should be frozen because a living object represented on the map data is usually a statue of a monster or something
tileFromMapData._freezeAnimation = true;
}
tiles.push_back(tileFromMapData);
// But if the base tile requires a background, we must find it
if (tileType->isLandForeground() ||
tileType->isWaterForeground() ||
tileType->isLivingObject()) {
tiles.push_back(getReplacementTile(coords, tileType));
}
return tiles;
}
TileId Location::getReplacementTile(MapCoords atCoords, const Tile *forTile) {
Common::HashMap validMapTileCount;
const static int dirs[][2] = {{ -1, 0}, {1, 0}, {0, -1}, {0, 1}};
const static int dirs_per_step = sizeof(dirs) / sizeof(*dirs);
int loop_count = 0;
//std::set searched;
Common::List searchQueue;
//Pathfinding to closest traversable tile with appropriate replacement properties.
//For tiles marked water-replaceable, pathfinding includes swimmables.
searchQueue.push_back(atCoords);
do {
MapCoords currentStep = searchQueue.front();
searchQueue.pop_front();
//searched.insert(currentStep);
for (int i = 0; i < dirs_per_step; i++) {
MapCoords newStep(currentStep);
newStep.move(dirs[i][0], dirs[i][1], _map);
Tile const *tileType = _map->tileTypeAt(newStep, WITHOUT_OBJECTS);
if (!tileType->isOpaque()) {
//if (searched.find(newStep) == searched.end()) -- the find mechanism doesn't work.
searchQueue.push_back(newStep);
}
if ((tileType->isReplacement() && (forTile->isLandForeground() || forTile->isLivingObject())) ||
(tileType->isWaterReplacement() && forTile->isWaterForeground())) {
Common::HashMap::iterator validCount = validMapTileCount.find(tileType->getId());
if (validCount == validMapTileCount.end()) {
validMapTileCount[tileType->getId()] = 1;
} else {
validMapTileCount[tileType->getId()]++;
}
}
}
if (validMapTileCount.size() > 0) {
Common::HashMap::iterator itr = validMapTileCount.begin();
TileId winner = itr->_key;
int score = itr->_value;
while (++itr != validMapTileCount.end()) {
if (score < itr->_value) {
score = itr->_value;
winner = itr->_key;
}
}
return winner;
}
// loop_count is an ugly hack to temporarily fix infinite loop
} while (++loop_count < 128 && searchQueue.size() > 0 && searchQueue.size() < 64);
// couldn't find a tile, give it the classic default
return _map->_tileSet->getByName("brick_floor")->getId();
}
int Location::getCurrentPosition(MapCoords *coords) {
if (_context & CTX_COMBAT) {
CombatController *cc = dynamic_cast(eventHandler->getController());
assert(cc);
PartyMemberVector *party = cc->getParty();
*coords = (*party)[cc->getFocus()]->getCoords();
} else {
*coords = this->_coords;
}
return 1;
}
MoveResult Location::move(Direction dir, bool userEvent) {
MoveEvent event(dir, userEvent);
switch (_map->_type) {
case Map::DUNGEON:
moveAvatarInDungeon(event);
break;
case Map::COMBAT:
movePartyMember(event);
break;
default:
moveAvatar(event);
break;
}
setChanged();
notifyObservers(event);
return event._result;
}
void locationFree(Location **stack) {
delete locationPop(stack);
}
} // End of namespace Ultima4
} // End of namespace Ultima