/* 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/ultima1/maps/map_city_castle.h"
#include "ultima/ultima1/maps/map_tile.h"
#include "ultima/ultima1/core/resources.h"
#include "ultima/ultima1/game.h"
#include "ultima/ultima1/spells/spell.h"
#include "ultima/ultima1/widgets/urban_player.h"
#include "ultima/ultima1/widgets/person.h"
#include "ultima/ultima1/widgets/bard.h"
#include "ultima/ultima1/widgets/guard.h"
#include "ultima/ultima1/widgets/king.h"
#include "ultima/ultima1/widgets/princess.h"
#include "ultima/ultima1/widgets/wench.h"
#include "ultima/ultima1/widgets/merchant_armour.h"
#include "ultima/ultima1/widgets/merchant_grocer.h"
#include "ultima/ultima1/widgets/merchant_magic.h"
#include "ultima/ultima1/widgets/merchant_tavern.h"
#include "ultima/ultima1/widgets/merchant_transport.h"
#include "ultima/ultima1/widgets/merchant_weapons.h"
#include "ultima/ultima1/u1dialogs/drop.h"
namespace Ultima {
namespace Ultima1 {
namespace Maps {
void MapCityCastle::load(Shared::Maps::MapId mapId) {
clear();
Shared::Maps::MapBase::load(mapId);
setDimensions(Point(38, 18));
_tilesPerOrigTile = Point(1, 1);
}
void MapCityCastle::clear() {
Shared::Maps::MapBase::clear();
_guardsHostile = false;
}
void MapCityCastle::loadWidgets() {
// Set up widget for the player
_playerWidget = new Widgets::UrbanPlayer(_game, this);
addWidget(_playerWidget);
for (int idx = 0; idx < 15; ++idx) {
const int *lp = _game->_res->LOCATION_PEOPLE[_mapStyle * 15 + idx];
if (lp[0] == -1)
break;
Widgets::Person *person;
switch (lp[0]) {
case 17:
person = new Widgets::Guard(_game, this, lp[3]);
break;
case 19:
person = new Widgets::Bard(_game, this, lp[3]);
break;
case 20:
person = new Widgets::King(_game, this, lp[3]);
break;
case 21: {
U1MapTile tile;
getTileAt(Point(lp[1], lp[2]), &tile);
switch (tile._tileId) {
case 55:
person = new Widgets::MerchantArmour(_game, this, lp[3]);
break;
case 57:
person = new Widgets::MerchantGrocer(_game, this, lp[3]);
break;
case 59:
person = new Widgets::MerchantWeapons(_game, this, lp[3]);
break;
case 60:
person = new Widgets::MerchantMagic(_game, this, lp[3]);
break;
case 61:
person = new Widgets::MerchantTavern(_game, this, lp[3]);
break;
case 62:
person = new Widgets::MerchantTransport(_game, this, lp[3]);
break;
default:
error("Invalid merchant");
}
break;
}
case 22:
person = new Widgets::Princess(_game, this, lp[3]);
break;
case 50:
person = new Widgets::Wench(_game, this, lp[3]);
break;
default:
error("Unknown NPC type %d", lp[0]);
}
person->_position = Point(lp[1], lp[2]);
addWidget(person);
}
}
void MapCityCastle::getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer) {
MapBase::getTileAt(pt, tile, includePlayer);
// Special handling for the cells indicating various merchant talk/steal positions
if (tile->_tileDisplayNum >= 51)
tile->_tileDisplayNum = 1;
}
Point MapCityCastle::getViewportPosition(const Point &viewportSize) {
Point &topLeft = _viewportPos._topLeft;
if (!_viewportPos.isValid() || _viewportPos._size != viewportSize) {
// Calculate the new position
topLeft.x = _playerWidget->_position.x - (viewportSize.x - 1) / 2;
topLeft.y = _playerWidget->_position.y - (viewportSize.y - 1) / 2;
// Fixed maps, so constrain top left corner so the map fills the viewport. This will accommodate
// future renderings with more tiles, or greater tile size
topLeft.x = CLIP((int)topLeft.x, 0, (int)(width() - viewportSize.x));
topLeft.y = CLIP((int)topLeft.y, 0, (int)(height() - viewportSize.y));
_viewportPos._mapId = _mapId;
_viewportPos._size = viewportSize;
}
return topLeft;
}
void MapCityCastle::loadTownCastleData() {
// Load the contents of the map
Shared::File f("tcd.bin");
f.seek(_mapStyle * 684);
for (int x = 0; x < _size.x; ++x) {
for (int y = 0; y < _size.y; ++y)
_data[y][x] = f.readByte();
}
}
Widgets::Merchant *MapCityCastle::getStealMerchant() {
U1MapTile tile;
getTileAt(getPosition(), &tile);
// Scan for the correct merchant depending on the tile player is on
switch (tile._tileId) {
case 55:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantArmour::type()));
break;
case 57:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantGrocer::type()));
break;
case 59:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantWeapons::type()));
break;
default:
return nullptr;
}
}
Widgets::Person *MapCityCastle::getTalkPerson() {
U1MapTile tile;
getTileAt(getPosition(), &tile);
switch (tile._tileId) {
case 54:
case 55:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantArmour::type()));
case 56:
case 57:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantGrocer::type()));
case 58:
case 59:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantWeapons::type()));
case 60:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantMagic::type()));
case 61:
return dynamic_cast(_widgets.findByClass(Widgets::MerchantTavern::type()));
case 62:
return dynamic_cast(_widgets.findByClass(
dynamic_cast(this) ? Widgets::MerchantTransport::type() : Widgets::King::type() ));
default:
return nullptr;
}
}
void MapCityCastle::cast() {
addInfoMsg(Common::String::format(" -- %s", _game->_res->NO_EFFECT));
_game->playFX(6);
}
void MapCityCastle::drop() {
U1Dialogs::Drop *drop = new U1Dialogs::Drop(_game);
drop->show();
}
void MapCityCastle::inform() {
addInfoMsg("");
addInfoMsg(_name);
}
void MapCityCastle::steal() {
Widgets::Merchant *merchant = getStealMerchant();
if (merchant) {
// Found a merchant, so call their steal handler
merchant->steal();
} else {
addInfoMsg(_game->_res->NOTHING_HERE);
_game->playFX(1);
}
}
void MapCityCastle::attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) {
_game->playFX(effectId);
Point delta = getDirectionDelta();
U1MapTile tile;
Widgets::Person *person;
//int currTile;
// Scan in the given direction for a person to attack
uint distance = 1;
do {
Point pt = getPosition() + Point(delta.x * distance, delta.y * distance);
getTileAt(pt, &tile);
//currTile = tile._tileId == CTILE_63 ? -1 : tile._tileId;
person = dynamic_cast(tile._widget);
} while (++distance <= maxDistance && !person && (tile._tileId == CTILE_GROUND || tile._tileId >= CTILE_POND_EDGE1));
if (person && _game->getRandomNumber(1, 100) <= agility) {
addInfoMsg(Common::String::format(_game->_res->HIT_CREATURE, person->_name.c_str()), false);
// Damage the person
if (person->subtractHitPoints(amount)) {
// Killed them
addInfoMsg(_game->_res->KILLED);
} else {
// Still alive
addInfoMsg(Common::String::format("%u %s!", amount, _game->_res->DAMAGE));
}
} else {
addInfoMsg(_game->_res->MISSED);
}
_game->endOfTurn();
}
bool MapCityCastle::isWenchNearby() const {
Shared::Maps::MapWidget *widget = _widgets.findByClass(Widgets::Wench::type());
if (!widget)
return false;
const Point &playerPos = _playerWidget->_position;
const Point &wenchPos = widget->_position;
int distance = MAX(ABS(playerPos.x - wenchPos.x), ABS(playerPos.y - wenchPos.y));
return distance == 1;
}
/*-------------------------------------------------------------------*/
void MapCity::load(Shared::Maps::MapId mapId) {
MapCityCastle::load(mapId);
_mapStyle = ((_mapId - 1) % 8) + 2;
_mapIndex = _mapId;
_name = Common::String::format("%s %s", _game->_res->THE_CITY_OF, _game->_res->LOCATION_NAMES[_mapId - 1]);
loadTownCastleData();
// Load up the widgets for the given map
loadWidgets();
setPosition(Common::Point(width() / 2, height() - 1)); // Start at bottom center edge of map
}
void MapCity::dropCoins(uint coins) {
Shared::Character &c = *_game->_party;
U1MapTile tile;
getTileAt(getPosition(), &tile);
if (tile._tileId == CTILE_POND_EDGE1 || tile._tileId == CTILE_POND_EDGE2 || tile._tileId == CTILE_POND_EDGE3) {
addInfoMsg(_game->_res->SHAZAM);
_game->playFX(5);
switch (tile._tileId) {
case CTILE_POND_EDGE1: {
// Increase one of the attributes randomly
uint *attrList[6] = { &c._strength, &c._agility, &c._stamina, &c._charisma, &c._wisdom, &c._intelligence };
uint &attr = *attrList[_game->getRandomNumber(0, 5)];
attr = MIN(attr + coins / 10, 99U);
break;
}
case CTILE_POND_EDGE2: {
// Increase the quantity of a random weapon
uint weaponNum = _game->getRandomNumber(1, 15);
Shared::Weapon &weapon = *c._weapons[weaponNum];
weapon._quantity = MIN(weapon._quantity + 1, 255U);
break;
}
case CTILE_POND_EDGE3:
// Increase food
c._food += coins;
break;
default:
break;
}
} else {
addInfoMsg(_game->_res->OK);
}
}
void MapCity::get() {
addInfoMsg(_game->_res->WHAT);
_game->playFX(1);
}
void MapCity::talk() {
if (_guardsHostile) {
addInfoMsg(_game->_res->NONE_WILL_TALK);
} else {
Widgets::Person *person = getTalkPerson();
if (person) {
person->talk();
} else {
addInfoMsg("");
addInfoMsg(_game->_res->NOT_BY_COUNTER);
_game->endOfTurn();
}
}
}
void MapCity::unlock() {
addInfoMsg(_game->_res->WHAT);
_game->playFX(1);
}
/*-------------------------------------------------------------------*/
void MapCastle::load(Shared::Maps::MapId mapId) {
MapCityCastle::load(mapId);
_mapIndex = _mapId - 33;
_mapStyle = _mapIndex % 2;
_name = _game->_res->LOCATION_NAMES[_mapId - 1];
_castleKey = _game->getRandomNumber(255) & 1 ? 61 : 60;
_getCounter = 0;
loadTownCastleData();
// Set up door locks
_data[_mapStyle ? 4 : 14][35] = CTILE_GATE;
_data[_mapStyle ? 4 : 14][31] = CTILE_GATE;
// Load up the widgets for the given map
loadWidgets();
setPosition(Common::Point(0, height() / 2)); // Start at center left edge of map
}
void MapCastle::synchronize(Common::Serializer &s) {
MapCityCastle::synchronize(s);
s.syncAsByte(_castleKey);
s.syncAsByte(_getCounter);
s.syncAsByte(_freeingPrincess);
}
void MapCastle::dropCoins(uint coins) {
Shared::Character &c = *_game->_party;
U1MapTile tile;
getTileAt(getPosition(), &tile);
if (tile._tileId == CTILE_POND_EDGE1) {
uint hp = coins * 3 / 2;
c._hitPoints = MIN(c._hitPoints + hp, 9999U);
if (_game->getRandomNumber(1, 255) > 16) {
addInfoMsg(_game->_res->SHAZAM);
} else {
uint spellNum = _game->getRandomNumber(1, 7);
if (spellNum == Spells::SPELL_MAGIC_MISSILE)
spellNum = Spells::SPELL_STEAL;
c._spells[spellNum]->incrQuantity();
addInfoMsg(_game->_res->ALAKAZOT);
}
} else {
addInfoMsg(_game->_res->OK);
}
}
void MapCastle::get() {
Widgets::Merchant *merchant = getStealMerchant();
if (merchant) {
// Found a merchant, so call their get handler
merchant->get();
} else {
addInfoMsg(_game->_res->NOTHING_HERE);
_game->playFX(1);
}
}
void MapCastle::talk() {
addInfoMsg(_game->_res->WITH_KING);
Widgets::Person *person = getTalkPerson();
if (person) {
person->talk();
} else {
addInfoMsg(_game->_res->HE_IS_NOT_HERE);
_game->endOfTurn();
}
}
void MapCastle::unlock() {
U1MapTile tile;
Point pt = getPosition();
getTileAt(pt, &tile);
if (tile._tileId != CTILE_LOCK1 && tile._tileId != CTILE_LOCK2) {
addInfoMsg(_game->_res->WHAT);
_game->playFX(1);
} else if (!_castleKey) {
addInfoMsg(_game->_res->NO_KEY);
} else if (tile._tileId != (int)_castleKey) {
addInfoMsg(_game->_res->INCORRECT_KEY);
} else {
addInfoMsg(_game->_res->DOOR_IS_OPEN);
_data[pt.y][pt.x] = CTILE_GROUND;
_freeingPrincess = true;
}
}
bool MapCastle::isLordBritishCastle() const {
return getMapIndex() == 0;
}
} // End of namespace Maps
} // End of namespace Ultima1
} // End of namespace Ultima