Initial commit
This commit is contained in:
101
engines/ultima/ultima1/maps/map.cpp
Normal file
101
engines/ultima/ultima1/maps/map.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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/ultima1/maps/map.h"
|
||||
#include "ultima/ultima1/maps/map_city_castle.h"
|
||||
#include "ultima/ultima1/maps/map_dungeon.h"
|
||||
#include "ultima/ultima1/maps/map_overworld.h"
|
||||
#include "ultima/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/core/resources.h"
|
||||
#include "ultima/ultima1/game.h"
|
||||
#include "ultima/shared/core/file.h"
|
||||
#include "ultima/shared/early/ultima_early.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
Ultima1Map::Ultima1Map(Ultima1Game *game) : Shared::Maps::Map(),
|
||||
_mapType(MAP_UNKNOWN), _moveCounter(0) {
|
||||
Ultima1Map::clear();
|
||||
_mapCity = new MapCity(game, this);
|
||||
_mapCastle = new MapCastle(game, this);
|
||||
_mapDungeon = new MapDungeon(game, this);
|
||||
_mapOverworld = new MapOverworld(game, this);
|
||||
}
|
||||
|
||||
Ultima1Map::~Ultima1Map() {
|
||||
delete _mapCity;
|
||||
delete _mapCastle;
|
||||
delete _mapDungeon;
|
||||
delete _mapOverworld;
|
||||
}
|
||||
|
||||
void Ultima1Map::clear() {
|
||||
_mapType = MAP_UNKNOWN;
|
||||
}
|
||||
|
||||
void Ultima1Map::load(Shared::Maps::MapId mapId) {
|
||||
// If we're leaving the overworld, update the cached copy of the position in the overworld
|
||||
if (_mapType == MAP_OVERWORLD)
|
||||
_worldPos = _mapArea->getPosition();
|
||||
|
||||
Shared::Maps::Map::load(mapId);
|
||||
|
||||
// Switch to the correct map area
|
||||
if (mapId == MAPID_OVERWORLD) {
|
||||
_mapType = MAP_OVERWORLD;
|
||||
_mapArea = _mapOverworld;
|
||||
} else if (mapId < 33) {
|
||||
_mapType = MAP_CITY;
|
||||
_mapArea = _mapCity;
|
||||
} else if (mapId < 41) {
|
||||
_mapType = MAP_CASTLE;
|
||||
_mapArea = _mapCastle;
|
||||
} else if (mapId < 49) {
|
||||
error("TODO: load Pillar");
|
||||
} else {
|
||||
_mapType = MAP_DUNGEON;
|
||||
_mapArea = _mapDungeon;
|
||||
}
|
||||
|
||||
// Load the map
|
||||
_mapArea->load(mapId);
|
||||
}
|
||||
|
||||
void Ultima1Map::synchronize(Common::Serializer &s) {
|
||||
Shared::Maps::Map::synchronize(s);
|
||||
if (_mapType != MAP_OVERWORLD) {
|
||||
if (s.isLoading())
|
||||
_mapOverworld->load(MAP_OVERWORLD);
|
||||
_mapOverworld->synchronize(s);
|
||||
}
|
||||
|
||||
s.syncAsUint32LE(_moveCounter);
|
||||
}
|
||||
|
||||
void Ultima1Map::dropCoins(uint amount) {
|
||||
static_cast<MapBase *>(_mapArea)->dropCoins(amount);
|
||||
}
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
147
engines/ultima/ultima1/maps/map.h
Normal file
147
engines/ultima/ultima1/maps/map.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAPS_MAP_H
|
||||
#define ULTIMA_ULTIMA1_MAPS_MAP_H
|
||||
|
||||
#include "ultima/shared/maps/map.h"
|
||||
#include "ultima/shared/maps/map_widget.h"
|
||||
#include "ultima/ultima1/maps/map_base.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
|
||||
class Ultima1Game;
|
||||
|
||||
namespace Maps {
|
||||
|
||||
enum MapType {
|
||||
MAP_OVERWORLD = 0, MAP_CITY = 1, MAP_CASTLE = 2, MAP_DUNGEON = 3, MAP_UNKNOWN = 4
|
||||
};
|
||||
|
||||
enum MapIdent {
|
||||
MAPID_OVERWORLD = 0
|
||||
};
|
||||
|
||||
class Ultima1Map;
|
||||
class MapCity;
|
||||
class MapCastle;
|
||||
class MapDungeon;
|
||||
class MapOverworld;
|
||||
|
||||
/**
|
||||
* Used to hold the total number of tiles surrounding location entrances
|
||||
*/
|
||||
struct SurroundingTotals {
|
||||
uint _water;
|
||||
uint _grass;
|
||||
uint _woods;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SurroundingTotals() : _water(0), _grass(0), _woods(0) {}
|
||||
|
||||
/**
|
||||
* Loads the totals from a passed map
|
||||
*/
|
||||
void load(Ultima1Map *map);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ultima 1 map manager
|
||||
*/
|
||||
class Ultima1Map : public Shared::Maps::Map {
|
||||
private:
|
||||
// Ultima1Game *_game;
|
||||
MapCity *_mapCity;
|
||||
MapCastle *_mapCastle;
|
||||
MapDungeon *_mapDungeon;
|
||||
MapOverworld *_mapOverworld;
|
||||
public:
|
||||
MapType _mapType; // Type of map
|
||||
Point _worldPos; // Point in the world map, updated when entering locations
|
||||
uint _moveCounter; // Movement counter
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Ultima1Map(Ultima1Game *game);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~Ultima1Map() override;
|
||||
|
||||
/**
|
||||
* Clears all map data
|
||||
*/
|
||||
void clear() override;
|
||||
|
||||
/**
|
||||
* Load a given map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Handles loading and saving the map's data
|
||||
*/
|
||||
void synchronize(Common::Serializer &s) override;
|
||||
|
||||
/**
|
||||
* Action pass-throughs
|
||||
*/
|
||||
#define PASS_METHOD(NAME) void NAME() { static_cast<MapBase *>(_mapArea)->NAME(); }
|
||||
PASS_METHOD(board)
|
||||
PASS_METHOD(cast)
|
||||
PASS_METHOD(drop)
|
||||
PASS_METHOD(enter)
|
||||
PASS_METHOD(get)
|
||||
PASS_METHOD(hyperjump)
|
||||
PASS_METHOD(inform)
|
||||
PASS_METHOD(climb)
|
||||
PASS_METHOD(open)
|
||||
PASS_METHOD(steal)
|
||||
PASS_METHOD(talk)
|
||||
PASS_METHOD(unlock)
|
||||
PASS_METHOD(view)
|
||||
PASS_METHOD(disembark)
|
||||
|
||||
void attack(int direction, int effectId) {
|
||||
static_cast<MapBase *>(_mapArea)->attack(direction, effectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles dropping an amount of coins
|
||||
*/
|
||||
void dropCoins(uint amount);
|
||||
|
||||
/**
|
||||
* Returns the overworld map
|
||||
*/
|
||||
MapOverworld *getOverworldMap() { return _mapOverworld; }
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
169
engines/ultima/ultima1/maps/map_base.cpp
Normal file
169
engines/ultima/ultima1/maps/map_base.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/* 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/ultima1/maps/map_base.h"
|
||||
#include "ultima/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/maps/map_dungeon.h"
|
||||
#include "ultima/ultima1/maps/map_city_castle.h"
|
||||
#include "ultima/ultima1/maps/map_overworld.h"
|
||||
#include "ultima/ultima1/maps/map.h"
|
||||
#include "ultima/ultima1/core/party.h"
|
||||
#include "ultima/ultima1/core/resources.h"
|
||||
#include "ultima/ultima1/game.h"
|
||||
#include "ultima/ultima1/spells/spell.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_widget.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_item.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/widgets/overworld_monster.h"
|
||||
#include "ultima/ultima1/widgets/transport.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_monster.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_player.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_chest.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_coffin.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/transport.h"
|
||||
#include "ultima/ultima1/widgets/urban_player.h"
|
||||
#include "ultima/ultima1/widgets/wench.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
MapBase::MapBase(Ultima1Game *game, Ultima1Map *map) : Shared::Maps::MapBase(game, map), _game(game) {
|
||||
}
|
||||
|
||||
void MapBase::getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer) {
|
||||
Shared::Maps::MapBase::getTileAt(pt, tile, includePlayer);
|
||||
|
||||
// Extended properties to set if an Ultima 1 map tile structure was passed in
|
||||
U1MapTile *mapTile = dynamic_cast<U1MapTile *>(tile);
|
||||
if (mapTile) {
|
||||
GameResources *res = _game->_res;
|
||||
mapTile->setMap(this);
|
||||
|
||||
// Check for a location at the given position
|
||||
mapTile->_locationNum = -1;
|
||||
if (dynamic_cast<MapOverworld *>(this)) {
|
||||
for (int idx = 0; idx < LOCATION_COUNT; ++idx) {
|
||||
if (pt.x == res->LOCATION_X[idx] && pt.y == res->LOCATION_Y[idx]) {
|
||||
mapTile->_locationNum = idx + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a dungeon item
|
||||
for (uint idx = 0; idx < _widgets.size() && !mapTile->_item; ++idx) {
|
||||
mapTile->_item = dynamic_cast<Widgets::DungeonItem *>(_widgets[idx].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapBase::unknownAction() {
|
||||
addInfoMsg("?");
|
||||
_game->playFX(1);
|
||||
}
|
||||
|
||||
void MapBase::attack(int direction, int effectId) {
|
||||
uint agility, damage, maxDistance;
|
||||
Widgets::Transport *transport = dynamic_cast<Widgets::Transport *>(_playerWidget);
|
||||
|
||||
if (effectId == 7) {
|
||||
Character &c = *static_cast<Party *>(_game->_party);
|
||||
maxDistance = c.equippedWeapon()->_distance;
|
||||
agility = c._agility + 50;
|
||||
damage = _game->getRandomNumber(2, c._strength + c._equippedWeapon * 8);
|
||||
} else {
|
||||
maxDistance = 3;
|
||||
agility = 80;
|
||||
damage = _game->getRandomNumber(1, (transport ? transport->transportId() : 0) * 10);
|
||||
}
|
||||
|
||||
attack(direction, effectId, maxDistance, damage, agility, "PhysicalAttack");
|
||||
}
|
||||
|
||||
void MapBase::board() {
|
||||
unknownAction();
|
||||
_game->endOfTurn();
|
||||
}
|
||||
|
||||
void MapBase::cast() {
|
||||
const Shared::Character &c = *_game->_party;
|
||||
Shared::Spell &spell = *c._spells[c._equippedSpell];
|
||||
addInfoMsg(Common::String::format(" %s", spell._name.c_str()), false);
|
||||
|
||||
if (c._equippedSpell == Spells::SPELL_PRAYER) {
|
||||
castSpell(c._equippedSpell);
|
||||
} else if (spell._quantity == 0) {
|
||||
addInfoMsg("");
|
||||
addInfoMsg(_game->_res->USED_UP_SPELL);
|
||||
_game->playFX(6);
|
||||
} else {
|
||||
spell.decrQuantity();
|
||||
castSpell(c._equippedSpell);
|
||||
}
|
||||
}
|
||||
|
||||
void MapBase::castSpell(uint spellId) {
|
||||
const Shared::Character &c = *_game->_party;
|
||||
static_cast<Spells::Spell *>(c._spells[spellId])->cast(this);
|
||||
}
|
||||
|
||||
Shared::Maps::MapWidget *MapBase::createWidget(const Common::String &name) {
|
||||
REGISTER_WIDGET(Bard);
|
||||
REGISTER_WIDGET(DungeonMonster);
|
||||
REGISTER_WIDGET(DungeonPlayer);
|
||||
REGISTER_WIDGET(DungeonChest);
|
||||
REGISTER_WIDGET(DungeonCoffin);
|
||||
REGISTER_WIDGET(Guard);
|
||||
REGISTER_WIDGET(King);
|
||||
REGISTER_WIDGET(MerchantArmour);
|
||||
REGISTER_WIDGET(MerchantGrocer);
|
||||
REGISTER_WIDGET(MerchantMagic);
|
||||
REGISTER_WIDGET(MerchantTavern);
|
||||
REGISTER_WIDGET(MerchantTransport);
|
||||
REGISTER_WIDGET(MerchantWeapons);
|
||||
REGISTER_WIDGET(OverworldMonster);
|
||||
REGISTER_WIDGET(Princess);
|
||||
REGISTER_WIDGET(TransportOnFoot);
|
||||
REGISTER_WIDGET(UrbanPlayer);
|
||||
REGISTER_WIDGET(Wench);
|
||||
REGISTER_WIDGET(Horse);
|
||||
REGISTER_WIDGET(Cart);
|
||||
REGISTER_WIDGET(Raft);
|
||||
REGISTER_WIDGET(Frigate);
|
||||
REGISTER_WIDGET(Aircar);
|
||||
REGISTER_WIDGET(Shuttle);
|
||||
|
||||
error("Unknown widget type '%s'", name.c_str());
|
||||
}
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
126
engines/ultima/ultima1/maps/map_base.h
Normal file
126
engines/ultima/ultima1/maps/map_base.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAPS_MAP_BASE_H
|
||||
#define ULTIMA_ULTIMA1_MAPS_MAP_BASE_H
|
||||
|
||||
#include "ultima/shared/maps/map_base.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
|
||||
class Ultima1Game;
|
||||
|
||||
namespace Maps {
|
||||
|
||||
class Ultima1Map;
|
||||
|
||||
/**
|
||||
* Intermediate base class for Ultima 1 maps
|
||||
*/
|
||||
class MapBase : public Shared::Maps::MapBase {
|
||||
private:
|
||||
/**
|
||||
* Default unknown/question mark display
|
||||
*/
|
||||
void unknownAction();
|
||||
protected:
|
||||
Ultima1Game *_game;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
MapBase(Ultima1Game *game, Ultima1Map *map);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~MapBase() override {}
|
||||
|
||||
/**
|
||||
* Gets a tile at a given position
|
||||
*/
|
||||
void getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer = true) override;
|
||||
|
||||
/**
|
||||
* Instantiates a widget type by name
|
||||
*/
|
||||
Shared::Maps::MapWidget *createWidget(const Common::String &name) override;
|
||||
|
||||
/**
|
||||
* Default implementation for actions
|
||||
*/
|
||||
#define DEFAULT_ACTION(NAME) virtual void NAME() { unknownAction(); }
|
||||
DEFAULT_ACTION(drop)
|
||||
DEFAULT_ACTION(enter)
|
||||
DEFAULT_ACTION(get)
|
||||
DEFAULT_ACTION(hyperjump)
|
||||
DEFAULT_ACTION(inform)
|
||||
DEFAULT_ACTION(climb)
|
||||
DEFAULT_ACTION(open)
|
||||
DEFAULT_ACTION(steal)
|
||||
DEFAULT_ACTION(talk)
|
||||
DEFAULT_ACTION(unlock)
|
||||
DEFAULT_ACTION(view)
|
||||
DEFAULT_ACTION(disembark)
|
||||
|
||||
/**
|
||||
* Perform an attack
|
||||
*/
|
||||
virtual void attack(int direction, int effectId);
|
||||
|
||||
/**
|
||||
* Perform an attack in a direction
|
||||
* @param direction Direction
|
||||
* @param effectId Sound effect to play
|
||||
* @param maxDistance Maximum distance in the given direction
|
||||
* @param amount Damage amount
|
||||
* @param agility Agility threshold
|
||||
* @param widgetNa
|
||||
*/
|
||||
virtual void attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) = 0;
|
||||
|
||||
/**
|
||||
* Board a transport
|
||||
*/
|
||||
virtual void board();
|
||||
|
||||
/**
|
||||
* Cast a spell
|
||||
*/
|
||||
virtual void cast();
|
||||
|
||||
/**
|
||||
* Cast a specific spell
|
||||
*/
|
||||
void castSpell(uint spell) override;
|
||||
|
||||
/**
|
||||
* Handles dropping an amount of coins
|
||||
*/
|
||||
virtual void dropCoins(uint coins) {}
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
471
engines/ultima/ultima1/maps/map_city_castle.cpp
Normal file
471
engines/ultima/ultima1/maps/map_city_castle.cpp
Normal file
@@ -0,0 +1,471 @@
|
||||
/* 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/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::MerchantArmour *>(_widgets.findByClass(Widgets::MerchantArmour::type()));
|
||||
break;
|
||||
case 57:
|
||||
return dynamic_cast<Widgets::MerchantGrocer *>(_widgets.findByClass(Widgets::MerchantGrocer::type()));
|
||||
break;
|
||||
case 59:
|
||||
return dynamic_cast<Widgets::MerchantWeapons *>(_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::Person *>(_widgets.findByClass(Widgets::MerchantArmour::type()));
|
||||
|
||||
case 56:
|
||||
case 57:
|
||||
return dynamic_cast<Widgets::Person *>(_widgets.findByClass(Widgets::MerchantGrocer::type()));
|
||||
|
||||
case 58:
|
||||
case 59:
|
||||
return dynamic_cast<Widgets::Person *>(_widgets.findByClass(Widgets::MerchantWeapons::type()));
|
||||
|
||||
case 60:
|
||||
return dynamic_cast<Widgets::Person *>(_widgets.findByClass(Widgets::MerchantMagic::type()));
|
||||
|
||||
case 61:
|
||||
return dynamic_cast<Widgets::Person *>(_widgets.findByClass(Widgets::MerchantTavern::type()));
|
||||
|
||||
case 62:
|
||||
return dynamic_cast<Widgets::Person *>(_widgets.findByClass(
|
||||
dynamic_cast<MapCity *>(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<Widgets::Person *>(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
|
||||
229
engines/ultima/ultima1/maps/map_city_castle.h
Normal file
229
engines/ultima/ultima1/maps/map_city_castle.h
Normal file
@@ -0,0 +1,229 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAP_MAP_CITY_CASTLE_H
|
||||
#define ULTIMA_ULTIMA1_MAP_MAP_CITY_CASTLE_H
|
||||
|
||||
#include "ultima/ultima1/maps/map_base.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Widgets {
|
||||
class Person;
|
||||
class Merchant;
|
||||
}
|
||||
|
||||
namespace Maps {
|
||||
|
||||
enum CityTile {
|
||||
CTILE_GROUND = 1, CTILE_POND_EDGE1 = 51, CTILE_POND_EDGE2 = 52, CTILE_POND_EDGE3 = 53,
|
||||
CTILE_GATE = 11, CTILE_LOCK1 = 60, CTILE_LOCK2 = 61, CTILE_63 = 63
|
||||
};
|
||||
|
||||
/**
|
||||
* Common base class for city and castle maps
|
||||
*/
|
||||
class MapCityCastle : public MapBase {
|
||||
protected:
|
||||
/**
|
||||
* Load widget list for the map
|
||||
*/
|
||||
void loadWidgets();
|
||||
|
||||
/**
|
||||
* Load the base map for towns and castles
|
||||
*/
|
||||
void loadTownCastleData();
|
||||
|
||||
/**
|
||||
* Get a merchant for a given steal-type tile
|
||||
*/
|
||||
Widgets::Merchant *getStealMerchant();
|
||||
|
||||
/**
|
||||
* Get a person to talk to based on the tile the player is on
|
||||
*/
|
||||
Widgets::Person *getTalkPerson();
|
||||
public:
|
||||
bool _guardsHostile; // Flag for whether guards are hostile
|
||||
uint _tipCounter; // Tip counter for taverns
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
MapCityCastle(Ultima1Game *game, Ultima1Map *map) : MapBase(game, map),
|
||||
_guardsHostile(false), _tipCounter(0) {}
|
||||
|
||||
/**
|
||||
* Load the map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Gets a tile at a given position
|
||||
*/
|
||||
void getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer = true) override;
|
||||
|
||||
/**
|
||||
* Clears all map data
|
||||
*/
|
||||
void clear() override;
|
||||
|
||||
/**
|
||||
* Get the viewport position
|
||||
*/
|
||||
Point getViewportPosition(const Point &viewportSize) override;
|
||||
|
||||
/**
|
||||
* Cast a spell
|
||||
*/
|
||||
void cast() override;
|
||||
|
||||
/**
|
||||
* Do a drop action
|
||||
*/
|
||||
void drop() override;
|
||||
|
||||
/**
|
||||
* Do an inform action
|
||||
*/
|
||||
void inform() override;
|
||||
|
||||
/**
|
||||
* Do a steal action
|
||||
*/
|
||||
void steal() override;
|
||||
|
||||
/**
|
||||
* Perform an attack in a direction
|
||||
* @param direction Direction
|
||||
* @param effectId Sound effect to play
|
||||
* @param maxDistance Maximum distance in the given direction
|
||||
* @param amount Damage amount
|
||||
* @param agility Agility threshold
|
||||
* @param widgetNa
|
||||
*/
|
||||
void attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) override;
|
||||
|
||||
/**
|
||||
* Returns true if a wench is on an adjacent tile to the player
|
||||
*/
|
||||
bool isWenchNearby() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* City map
|
||||
*/
|
||||
class MapCity : public MapCityCastle {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
MapCity(Ultima1Game *game, Ultima1Map *map) : MapCityCastle(game, map) {}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~MapCity() override {}
|
||||
|
||||
/**
|
||||
* Load the map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Handles dropping an amount of coins
|
||||
*/
|
||||
void dropCoins(uint coins) override;
|
||||
|
||||
/**
|
||||
* Do a get action
|
||||
*/
|
||||
void get() override;
|
||||
|
||||
/**
|
||||
* Do a talk action
|
||||
*/
|
||||
void talk() override;
|
||||
|
||||
/**
|
||||
* Do an unlock action
|
||||
*/
|
||||
void unlock() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Castle map
|
||||
*/
|
||||
class MapCastle : public MapCityCastle {
|
||||
public:
|
||||
uint _castleKey; // Key for castle map lock
|
||||
int _getCounter; // Counter for allowed gets without stealing check
|
||||
bool _freeingPrincess; // Set when freeing the princess is in progress
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
MapCastle(Ultima1Game *game, Ultima1Map *map) : MapCityCastle(game, map), _castleKey(0),
|
||||
_getCounter(0), _freeingPrincess(false) {}
|
||||
|
||||
/**
|
||||
* Load the map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Handles loading and saving the map's data
|
||||
*/
|
||||
void synchronize(Common::Serializer &s) override;
|
||||
|
||||
/**
|
||||
* Handles dropping an amount of coins
|
||||
*/
|
||||
void dropCoins(uint coins) override;
|
||||
|
||||
/**
|
||||
* Do a get action
|
||||
*/
|
||||
void get() override;
|
||||
|
||||
/**
|
||||
* Do a talk action
|
||||
*/
|
||||
void talk() override;
|
||||
|
||||
/**
|
||||
* Do an unlock action
|
||||
*/
|
||||
void unlock() override;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if Lord British's castle is the currently active map
|
||||
*/
|
||||
bool isLordBritishCastle() const;
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
349
engines/ultima/ultima1/maps/map_dungeon.cpp
Normal file
349
engines/ultima/ultima1/maps/map_dungeon.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
/* 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/ultima1/maps/map_dungeon.h"
|
||||
#include "ultima/ultima1/maps/map.h"
|
||||
#include "ultima/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/spells/spell.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_chest.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_coffin.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_monster.h"
|
||||
#include "ultima/ultima1/widgets/dungeon_player.h"
|
||||
#include "ultima/ultima1/game.h"
|
||||
#include "ultima/ultima1/core/party.h"
|
||||
#include "ultima/ultima1/core/resources.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
void MapDungeon::load(Shared::Maps::MapId mapId) {
|
||||
Shared::Maps::MapBase::load(mapId);
|
||||
|
||||
_tilesPerOrigTile = Point(1, 1);
|
||||
_dungeonLevel = 1;
|
||||
_dungeonExitHitPoints = 0;
|
||||
_name = _game->_res->LOCATION_NAMES[mapId - 1];
|
||||
|
||||
changeLevel(0);
|
||||
_playerWidget->moveTo(Point(1, 1), Shared::Maps::DIR_SOUTH);
|
||||
}
|
||||
|
||||
void MapDungeon::synchronize(Common::Serializer &s) {
|
||||
MapBase::synchronize(s);
|
||||
s.syncAsUint16LE(_dungeonLevel);
|
||||
s.syncAsUint16LE(_dungeonExitHitPoints);
|
||||
}
|
||||
|
||||
void MapDungeon::getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer) {
|
||||
MapBase::MapBase::getTileAt(pt, tile, includePlayer);
|
||||
|
||||
tile->_isHallway = tile->_tileId == DTILE_HALLWAY;
|
||||
tile->_isDoor = tile->_tileId == DTILE_DOOR;
|
||||
tile->_isSecretDoor = tile->_tileId == DTILE_SECRET_DOOR;
|
||||
tile->_isWall = tile->_tileId == DTILE_WALL;
|
||||
tile->_isLadderUp = tile->_tileId == DTILE_LADDER_UP;
|
||||
tile->_isLadderDown = tile->_tileId == DTILE_LADDER_DOWN;
|
||||
tile->_isBeams = tile->_tileId == DTILE_BEAMS;
|
||||
}
|
||||
|
||||
bool MapDungeon::changeLevel(int delta) {
|
||||
_dungeonLevel += delta;
|
||||
if (_dungeonLevel <= 0) {
|
||||
leavingDungeon();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set seed for generating a deterministic resulting dungoen level
|
||||
setRandomSeed();
|
||||
|
||||
// Reset dungeon area
|
||||
setDimensions(Point(DUNGEON_WIDTH, DUNGEON_HEIGHT));
|
||||
|
||||
if (_widgets.empty()) {
|
||||
// Set up widget for the player
|
||||
_playerWidget = new Widgets::DungeonPlayer(_game, this);
|
||||
addWidget(_playerWidget);
|
||||
} else {
|
||||
_widgets.resize(1);
|
||||
}
|
||||
|
||||
// Place walls around the edge of the map
|
||||
for (int y = 0; y < DUNGEON_HEIGHT; ++y) {
|
||||
_data[y][0] = DTILE_WALL;
|
||||
_data[y][DUNGEON_WIDTH - 1] = DTILE_WALL;
|
||||
}
|
||||
for (int x = 0; x < DUNGEON_WIDTH; ++x) {
|
||||
_data[0][x] = DTILE_WALL;
|
||||
_data[DUNGEON_HEIGHT - 1][x] = DTILE_WALL;
|
||||
}
|
||||
|
||||
// Set up walls vertically across the dungeon
|
||||
for (int x = 2; x < (DUNGEON_WIDTH - 1); x += 2)
|
||||
for (int y = 2; y < (DUNGEON_HEIGHT - 1); y += 2)
|
||||
_data[y][x] = DTILE_WALL;
|
||||
|
||||
// Randomly set up random tiles for all alternate positions in wall columns
|
||||
for (int x = 2; x < (DUNGEON_WIDTH - 1); x += 2)
|
||||
for (int y = 1; y < DUNGEON_HEIGHT; y += 2)
|
||||
_data[y][x] = getDeterministicRandomNumber(DTILE_HALLWAY, DTILE_DOOR);
|
||||
|
||||
// Set up wall and beams randomly to subdivide the blank columns
|
||||
const byte DATA1[15] = { 8, 5, 2, 8, 1, 5, 4, 6, 1, 3, 7, 3, 9, 2, 6 };
|
||||
const byte DATA2[15] = { 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 6, 8, 8, 9, 9 };
|
||||
for (uint ctr = 0; ctr < (_dungeonLevel * 2); ++ctr) {
|
||||
byte newTile = (getDeterministicRandomNumber(0, 255) <= 160) ? DTILE_WALL : DTILE_BEAMS;
|
||||
uint idx = getDeterministicRandomNumber(0, 14);
|
||||
|
||||
if (_dungeonLevel & 1) {
|
||||
_data[DATA2[idx]][DATA1[idx]] = newTile;
|
||||
}
|
||||
else {
|
||||
_data[DATA1[idx]][DATA2[idx]] = newTile;
|
||||
}
|
||||
}
|
||||
|
||||
// Place chests and/or coffins randomly throughout the level
|
||||
_random.setSeed(_random.getSeed() + 1777);
|
||||
for (uint ctr = 0; ctr <= _dungeonLevel; ++ctr) {
|
||||
Point pt(getDeterministicRandomNumber(10, 99) / 10, getDeterministicRandomNumber(10, 99) / 10);
|
||||
byte currTile = _data[pt.y][pt.x];
|
||||
|
||||
if (currTile != DTILE_WALL && currTile != DTILE_SECRET_DOOR && currTile != DTILE_BEAMS) {
|
||||
_widgets.push_back(Shared::Maps::MapWidgetPtr((getDeterministicRandomNumber(1, 100) & 1) ?
|
||||
(Widgets::DungeonItem *)new Widgets::DungeonChest(_game, this, pt) :
|
||||
(Widgets::DungeonItem *)new Widgets::DungeonCoffin(_game, this, pt)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Set up ladders
|
||||
_data[2][1] = DTILE_HALLWAY;
|
||||
if (_dungeonLevel & 1) {
|
||||
_data[3][7] = DTILE_LADDER_UP;
|
||||
_data[6][3] = DTILE_LADDER_DOWN;
|
||||
} else {
|
||||
_data[3][7] = DTILE_LADDER_DOWN;
|
||||
_data[6][3] = DTILE_LADDER_UP;
|
||||
}
|
||||
|
||||
if (_dungeonLevel == 10)
|
||||
_data[3][7] = DTILE_HALLWAY;
|
||||
if (_dungeonLevel == 1) {
|
||||
_data[1][1] = DTILE_LADDER_UP;
|
||||
_data[3][7] = DTILE_HALLWAY;
|
||||
}
|
||||
|
||||
for (int ctr = 0; ctr < 3; ++ctr)
|
||||
spawnMonster();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void MapDungeon::setRandomSeed() {
|
||||
Ultima1Map *map = static_cast<Ultima1Map *>(_game->getMap());
|
||||
uint32 seed = _game->_randomSeed + map->_worldPos.x * 5 + map->_worldPos.y * 3 + _dungeonLevel * 17;
|
||||
_random.setSeed(seed);
|
||||
}
|
||||
|
||||
void MapDungeon::spawnMonster() {
|
||||
U1MapTile tile;
|
||||
|
||||
// Pick a random position for the monster, trying again up to 500 times
|
||||
// if the chosen position isn't a valid place for the monster
|
||||
for (int tryNum = 0; tryNum < 500; ++tryNum) {
|
||||
Point newPos(_game->getRandomNumber(242) % 9 + 1, _game->getRandomNumber(242) % 9 + 1);
|
||||
getTileAt(newPos, &tile);
|
||||
|
||||
if (tile._tileId == DTILE_HALLWAY && tile._widgetNum == -1) {
|
||||
// Found a free spot
|
||||
spawnMonsterAt(newPos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::spawnMonsterAt(const Point &pt) {
|
||||
// Try up 50 times to randomly pick a monster not already present in the dungeon map
|
||||
for (int tryNum = 0; tryNum < 50; ++tryNum) {
|
||||
Widgets::DungeonWidgetId monsterId = (Widgets::DungeonWidgetId)((_dungeonLevel - 1) / 2 * 5 + _game->getRandomNumber(4));
|
||||
|
||||
// Only allow one of every type of monster on the map at the same time
|
||||
uint monsIdx;
|
||||
for (monsIdx = 0; monsIdx < _widgets.size(); ++monsIdx) {
|
||||
Widgets::DungeonMonster *mons = dynamic_cast<Widgets::DungeonMonster *>(_widgets[monsIdx].get());
|
||||
if (mons && mons->id() == monsterId)
|
||||
break;
|
||||
}
|
||||
|
||||
if (monsIdx == _widgets.size()) {
|
||||
// Monster not present, so can be added
|
||||
uint hp = _game->getRandomNumber(1, _dungeonLevel * _dungeonLevel + 1) +
|
||||
(int)monsterId + 10;
|
||||
Widgets::DungeonMonster *monster = new Widgets::DungeonMonster(_game, this, monsterId, hp, pt);
|
||||
addWidget(monster);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widgets::DungeonMonster *MapDungeon::findCreatureInCurrentDirection(uint maxDistance) {
|
||||
U1MapTile tile;
|
||||
Point delta = getDirectionDelta();
|
||||
|
||||
for (uint idx = 1; idx <= maxDistance; ++idx) {
|
||||
Point pt = getPosition() + Point(delta.x * idx, delta.y * idx);
|
||||
getTileAt(pt, &tile);
|
||||
|
||||
// If a monster found, return it
|
||||
Widgets::DungeonMonster *monster = dynamic_cast<Widgets::DungeonMonster *>(tile._widget);
|
||||
if (monster)
|
||||
return monster;
|
||||
|
||||
// If a blocking tile reached, then abort the loop
|
||||
if (tile._isWall || tile._isSecretDoor || tile._isBeams || tile._isDoor)
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MapDungeon::update() {
|
||||
U1MapTile tile;
|
||||
Point pt;
|
||||
|
||||
// Widgets in the dungeon are updated by row
|
||||
for (pt.y = 1; pt.y < ((int)height() - 1) && !_game->_party->isFoodless(); pt.y++) {
|
||||
for (pt.x = 1; pt.x < ((int)width() - 1); pt.x++) {
|
||||
// Check for a widget at the given position
|
||||
getTileAt(pt, &tile);
|
||||
|
||||
Shared::Maps::Creature *creature = dynamic_cast<Shared::Maps::Creature *>(tile._widget);
|
||||
if (creature)
|
||||
creature->update(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::inform() {
|
||||
U1MapTile currTile, destTile;
|
||||
Point pt = getPosition();
|
||||
getTileAt(pt, &currTile);
|
||||
getTileAt(pt + getDirectionDelta(), &destTile);
|
||||
|
||||
if (destTile._isSecretDoor && !currTile._isDoor) {
|
||||
addInfoMsg(Common::String::format("%s %s", _game->_res->FIND, _game->_res->A_SECRET_DOOR));
|
||||
_data[pt.y][pt.x] = DTILE_DOOR;
|
||||
} else {
|
||||
addInfoMsg(Common::String::format("%s %s", _game->_res->FIND, _game->_res->NOTHING));
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::open() {
|
||||
U1MapTile tile;
|
||||
getTileAt(getPosition(), &tile);
|
||||
addInfoMsg(Common::String::format(" %s", _game->_res->DUNGEON_ITEM_NAMES[1]), false);
|
||||
|
||||
// If there's an item on the cell, try and open it
|
||||
if (tile._item) {
|
||||
addInfoMsg(Common::String::format("%s ", tile._item->_name.c_str()));
|
||||
if (!tile._item->open()) {
|
||||
MapBase::open();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addInfoMsg(_game->_res->NONE_HERE);
|
||||
_game->playFX(1);
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::climb() {
|
||||
Maps::U1MapTile tile;
|
||||
getTileAt(getPosition(), &tile);
|
||||
|
||||
if (!tile._isLadderUp && !tile._isLadderDown) {
|
||||
addInfoMsg(_game->_res->WHAT);
|
||||
_game->playFX(1);
|
||||
} else if (getDirection() == Shared::Maps::DIR_LEFT || getDirection() == Shared::Maps::DIR_RIGHT) {
|
||||
addInfoMsg("");
|
||||
addInfoMsg(_game->_res->FACE_THE_LADDER);
|
||||
_game->playFX(1);
|
||||
} else if (tile._isLadderUp) {
|
||||
if (!changeLevel(-1))
|
||||
_game->getMap()->load(MAPID_OVERWORLD);
|
||||
} else {
|
||||
changeLevel(1);
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::castSpell(uint spellId) {
|
||||
const Shared::Character &c = *_game->_party;
|
||||
static_cast<Spells::Spell *>(c._spells[spellId])->dungeonCast(this);
|
||||
}
|
||||
|
||||
void MapDungeon::leavingDungeon() {
|
||||
Shared::Character &c = *_game->_party;
|
||||
|
||||
// Don't allow the hit points addition to push the hit point total beyond 9999
|
||||
if (c._hitPoints + _dungeonExitHitPoints > 9999)
|
||||
_dungeonExitHitPoints = 9999 - c._hitPoints;
|
||||
|
||||
if (_dungeonExitHitPoints) {
|
||||
addInfoMsg(Common::String::format(_game->_res->GAIN_HIT_POINTS, _dungeonExitHitPoints));
|
||||
c._hitPoints += _dungeonExitHitPoints;
|
||||
}
|
||||
}
|
||||
|
||||
void MapDungeon::attack(int direction, int effectId) {
|
||||
const Character &c = *static_cast<Party *>(_game->_party);
|
||||
Widgets::DungeonMonster *monster = findCreatureInCurrentDirection(
|
||||
c.equippedWeapon()->_distance);
|
||||
_game->playFX(7);
|
||||
|
||||
if (monster) {
|
||||
uint agility = c._agility + 50;
|
||||
uint damage = _game->getRandomNumber(2, agility + c._equippedWeapon * 8 + c._strength);
|
||||
monster->attackMonster(2, agility, damage);
|
||||
} else {
|
||||
addInfoMsg(_game->_res->NOTHING);
|
||||
}
|
||||
|
||||
_game->endOfTurn();
|
||||
}
|
||||
|
||||
void MapDungeon::attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) {
|
||||
//const Character &c = *static_cast<Party *>(_game->_party);
|
||||
Widgets::DungeonMonster *monster = findCreatureInCurrentDirection(maxDistance);
|
||||
_game->playFX(effectId);
|
||||
|
||||
if (monster) {
|
||||
monster->attackMonster(2, agility, amount);
|
||||
} else {
|
||||
addInfoMsg(_game->_res->NOTHING);
|
||||
}
|
||||
|
||||
_game->endOfTurn();
|
||||
}
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
170
engines/ultima/ultima1/maps/map_dungeon.h
Normal file
170
engines/ultima/ultima1/maps/map_dungeon.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAP_MAP_DUNGEON_H
|
||||
#define ULTIMA_ULTIMA1_MAP_MAP_DUNGEON_H
|
||||
|
||||
#include "ultima/ultima1/maps/map_base.h"
|
||||
#include "ultima/shared/core/rect.h"
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Widgets {
|
||||
class DungeonMonster;
|
||||
}
|
||||
|
||||
namespace Maps {
|
||||
|
||||
#define DUNGEON_WIDTH 11
|
||||
#define DUNGEON_HEIGHT 11
|
||||
|
||||
enum DungeonTile {
|
||||
DTILE_HALLWAY = 0, DTILE_WALL = 1, DTILE_SECRET_DOOR = 2, DTILE_DOOR = 3, DTILE_LADDER_DOWN = 6,
|
||||
DTILE_LADDER_UP = 7, DTILE_BEAMS = 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the dungeon map
|
||||
*/
|
||||
class MapDungeon : public MapBase {
|
||||
private:
|
||||
Common::RandomSource _random;
|
||||
uint _dungeonLevel; // Dungeon level number
|
||||
private:
|
||||
/**
|
||||
* Sets up a deterministic random seed for generating dungeon data
|
||||
*/
|
||||
void setRandomSeed();
|
||||
|
||||
/**
|
||||
* Gets a deterministic random number based on a given seed. Used in dungeon generation
|
||||
* so that a given dungeon and level will always be built the same
|
||||
*/
|
||||
uint getDeterministicRandomNumber(uint min, uint max) { return min + _random.getRandomNumber(max - min); }
|
||||
|
||||
/**
|
||||
* Called when the dungeon is being left
|
||||
*/
|
||||
void leavingDungeon();
|
||||
public:
|
||||
uint _dungeonExitHitPoints;
|
||||
public:
|
||||
MapDungeon(Ultima1Game *game, Ultima1Map *map) : MapBase(game, map), _random("UltimaDungeons"),
|
||||
_dungeonLevel(0), _dungeonExitHitPoints(0) {}
|
||||
~MapDungeon() override {}
|
||||
|
||||
/**
|
||||
* Handles loading and saving viewport
|
||||
*/
|
||||
void synchronize(Common::Serializer &s) override;
|
||||
|
||||
/**
|
||||
* Load the map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Gets a tile at a given position
|
||||
*/
|
||||
void getTileAt(const Point &pt, Shared::Maps::MapTile *tile, bool includePlayer = true) override;
|
||||
|
||||
/**
|
||||
* Changes the dungeon level by a given delta amount, and generates a new map
|
||||
* @param delta Delta to change dungeon level by
|
||||
* @returns False if dungeon left, true if still within dungeon
|
||||
*/
|
||||
bool changeLevel(int delta) override;
|
||||
|
||||
/**
|
||||
* Get the current map level
|
||||
*/
|
||||
uint getLevel() const override { return _dungeonLevel; }
|
||||
|
||||
/**
|
||||
* Updates the map at the end of a turn
|
||||
*/
|
||||
void update() override;
|
||||
|
||||
/**
|
||||
* Spawns a monster within dungeons
|
||||
*/
|
||||
void spawnMonster();
|
||||
|
||||
/**
|
||||
* Spawns a monster at a given position in the dungeon map
|
||||
*/
|
||||
void spawnMonsterAt(const Point &pt);
|
||||
|
||||
/**
|
||||
* Find a monster in the current direction being faced
|
||||
*/
|
||||
Widgets::DungeonMonster *findCreatureInCurrentDirection(uint maxDistance = 5);
|
||||
|
||||
/**
|
||||
* Perform an attack in a direction
|
||||
* @param direction Direction
|
||||
* @param effectId Sound effect to play
|
||||
*/
|
||||
void attack(int direction, int effectId) override;
|
||||
|
||||
/**
|
||||
* Perform an attack in a direction
|
||||
* @param direction Direction
|
||||
* @param effectId Sound effect to play
|
||||
* @param maxDistance Maximum distance in the given direction
|
||||
* @param amount Damage amount
|
||||
* @param agility Agility threshold
|
||||
* @param widgetNa
|
||||
*/
|
||||
void attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) override;
|
||||
|
||||
/**
|
||||
* Do an inform action
|
||||
*/
|
||||
void inform() override;
|
||||
|
||||
/**
|
||||
* Do a climb action
|
||||
*/
|
||||
void climb() override;
|
||||
|
||||
/**
|
||||
* Do an open action
|
||||
*/
|
||||
void open() override;
|
||||
|
||||
/**
|
||||
* Do an unlock action
|
||||
*/
|
||||
void unlock() override { open(); }
|
||||
|
||||
/**
|
||||
* Cast a specific spell
|
||||
*/
|
||||
void castSpell(uint spell) override;
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
213
engines/ultima/ultima1/maps/map_overworld.cpp
Normal file
213
engines/ultima/ultima1/maps/map_overworld.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/* 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/ultima1/maps/map_overworld.h"
|
||||
#include "ultima/ultima1/widgets/transport.h"
|
||||
#include "ultima/ultima1/widgets/overworld_monster.h"
|
||||
#include "ultima/ultima1/widgets/transport.h"
|
||||
#include "ultima/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/maps/map.h"
|
||||
#include "ultima/ultima1/game.h"
|
||||
#include "ultima/ultima1/core/resources.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
void MapOverworld::load(Shared::Maps::MapId mapId) {
|
||||
Shared::Maps::MapBase::load(mapId);
|
||||
setDimensions(Point(168, 156));
|
||||
_tilesPerOrigTile = Point(1, 1);
|
||||
|
||||
Shared::File f("map.bin");
|
||||
byte b;
|
||||
for (int y = 0; y < _size.y; ++y) {
|
||||
for (int x = 0; x < _size.x; x += 2) {
|
||||
b = f.readByte();
|
||||
_data[y][x] = b >> 4;
|
||||
_data[y][x + 1] = b & 0xf;
|
||||
}
|
||||
}
|
||||
|
||||
// Load widgets
|
||||
loadWidgets();
|
||||
}
|
||||
|
||||
void MapOverworld::loadWidgets() {
|
||||
// Note: the overworld player, transports, and monsters are persistent, so we only have to set up
|
||||
// the initial "on foot" transport the first time
|
||||
if (_widgets.empty()) {
|
||||
// Set up widget for the player
|
||||
_playerWidget = new Widgets::TransportOnFoot(_game, this);
|
||||
addWidget(_playerWidget);
|
||||
}
|
||||
}
|
||||
|
||||
Point MapOverworld::getDeltaPosition(const Point &delta) {
|
||||
Point pt = _playerWidget->_position + delta;
|
||||
|
||||
if (pt.x < 0)
|
||||
pt.x += _size.x;
|
||||
else if (pt.x >= _size.x)
|
||||
pt.x -= _size.x;
|
||||
if (pt.y < 0)
|
||||
pt.y += _size.y;
|
||||
else if (pt.y >= _size.y)
|
||||
pt.y -= _size.y;
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
Point MapOverworld::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;
|
||||
|
||||
// Non-fixed map, so it wraps around the edges if necessary
|
||||
if (topLeft.x < 0)
|
||||
topLeft.x += width();
|
||||
else if (topLeft.x >= (int)width())
|
||||
topLeft.x -= width();
|
||||
|
||||
if (topLeft.y < 0)
|
||||
topLeft.y += height();
|
||||
else if (topLeft.y >= (int)height())
|
||||
topLeft.y -= height();
|
||||
|
||||
_viewportPos._mapId = _mapId;
|
||||
_viewportPos._size = viewportSize;
|
||||
}
|
||||
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
void MapOverworld::shiftViewport(const Point &delta) {
|
||||
Point &topLeft = _viewportPos._topLeft;
|
||||
topLeft += delta;
|
||||
|
||||
if (topLeft.x < 0)
|
||||
topLeft.x += width();
|
||||
else if (topLeft.x >= (int16)width())
|
||||
topLeft.x -= width();
|
||||
if (topLeft.y < 0)
|
||||
topLeft.y += height();
|
||||
else if (topLeft.y >= (int16)height())
|
||||
topLeft.y -= height();
|
||||
}
|
||||
|
||||
void MapOverworld::board() {
|
||||
Maps::U1MapTile tile;
|
||||
getTileAt(getPosition(), &tile);
|
||||
Widgets::Transport *transport = dynamic_cast<Widgets::Transport *>(tile._widget);
|
||||
|
||||
if (!dynamic_cast<Widgets::TransportOnFoot *>(_playerWidget)) {
|
||||
addInfoMsg(_game->_res->EXIT_CRAFT_FIRST, true, true);
|
||||
_game->playFX(1);
|
||||
_game->endOfTurn();
|
||||
} else if (!transport) {
|
||||
addInfoMsg(_game->_res->NOTHING_TO_BOARD, true, true);
|
||||
_game->playFX(1);
|
||||
_game->endOfTurn();
|
||||
} else {
|
||||
transport->board();
|
||||
}
|
||||
}
|
||||
|
||||
void MapOverworld::enter() {
|
||||
Maps::U1MapTile tile;
|
||||
getTileAt(getPosition(), &tile);
|
||||
|
||||
if (tile._locationNum == -1) {
|
||||
// Fall back to base unknown action
|
||||
MapBase::enter();
|
||||
} else {
|
||||
// Load the location
|
||||
Shared::Maps::Map *map = _game->getMap();
|
||||
map->load(tile._locationNum);
|
||||
|
||||
// Add message for location having been entered
|
||||
addInfoMsg(_game->_res->ENTERING);
|
||||
addInfoMsg(map->getName());
|
||||
}
|
||||
}
|
||||
|
||||
void MapOverworld::inform() {
|
||||
Maps::U1MapTile tile;
|
||||
getTileAt(getPosition(), &tile, false);
|
||||
|
||||
addInfoMsg("");
|
||||
if (tile._locationNum != -1) {
|
||||
if (tile._locationNum < 33)
|
||||
addInfoMsg(Common::String::format("%s %s", _game->_res->THE_CITY_OF, _game->_res->LOCATION_NAMES[tile._locationNum - 1]));
|
||||
else
|
||||
addInfoMsg(_game->_res->LOCATION_NAMES[tile._locationNum - 1]);
|
||||
} else if (tile.isOriginalWater()) {
|
||||
addInfoMsg(_game->_res->YOU_ARE_AT_SEA);
|
||||
} else if (tile.isOriginalWoods()) {
|
||||
addInfoMsg(_game->_res->YOU_ARE_IN_WOODS);
|
||||
} else {
|
||||
addInfoMsg(_game->_res->YOU_ARE_IN_LANDS);
|
||||
addInfoMsg(_game->_res->LAND_NAMES[getLandsNumber()]);
|
||||
}
|
||||
}
|
||||
|
||||
void MapOverworld::disembark() {
|
||||
Widgets::Transport *transport = dynamic_cast<Widgets::Transport *>(_playerWidget);
|
||||
|
||||
if (transport) {
|
||||
addInfoMsg("");
|
||||
transport->disembark();
|
||||
} else {
|
||||
addInfoMsg(_game->_res->WHAT);
|
||||
}
|
||||
}
|
||||
|
||||
uint MapOverworld::getLandsNumber() const {
|
||||
Point pt = getPosition();
|
||||
return (pt.y > 77 ? 2 : 0) + (pt.x > 83 ? 1 : 0);
|
||||
}
|
||||
|
||||
void MapOverworld::addOnFoot() {
|
||||
_widgets.insert_at(0, Shared::Maps::MapWidgetPtr(new Widgets::TransportOnFoot(_game, this)));
|
||||
_playerWidget = _widgets[0].get();
|
||||
}
|
||||
|
||||
uint MapOverworld::getEnemyVesselCount() const {
|
||||
uint total = 0;
|
||||
for (uint idx = 0; idx < _widgets.size(); ++idx) {
|
||||
if (dynamic_cast<Widgets::EnemyVessel *>(_widgets[idx].get()))
|
||||
++total;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void MapOverworld::attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
121
engines/ultima/ultima1/maps/map_overworld.h
Normal file
121
engines/ultima/ultima1/maps/map_overworld.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAP_MAP_OVERWORLD_H
|
||||
#define ULTIMA_ULTIMA1_MAP_MAP_OVERWORLD_H
|
||||
|
||||
#include "ultima/ultima1/maps/map_base.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
enum OverworldTile {
|
||||
OTILE_OCEAN = 0, OTILE_GRASS = 1, OTILE_WOODS = 2
|
||||
};
|
||||
|
||||
class MapOverworld : public MapBase {
|
||||
private:
|
||||
/**
|
||||
* Load widget list for the map
|
||||
*/
|
||||
void loadWidgets();
|
||||
public:
|
||||
MapOverworld(Ultima1Game *game, Ultima1Map *map) : MapBase(game, map) {}
|
||||
~MapOverworld() override {}
|
||||
|
||||
/**
|
||||
* Load the map
|
||||
*/
|
||||
void load(Shared::Maps::MapId mapId) override;
|
||||
|
||||
/**
|
||||
* Returns whether the map wraps around to the other side at it's edges (i.e. the overworld)
|
||||
*/
|
||||
bool isMapWrapped() const override { return true; }
|
||||
|
||||
/**
|
||||
* Shifts the viewport by a given delta
|
||||
*/
|
||||
void shiftViewport(const Point &delta) override;
|
||||
|
||||
/**
|
||||
* Get the viewport position
|
||||
*/
|
||||
Point getViewportPosition(const Point &viewportSize) override;
|
||||
|
||||
/**
|
||||
* Gets a point relative to the current position
|
||||
*/
|
||||
Point getDeltaPosition(const Point &delta) override;
|
||||
|
||||
/**
|
||||
* Perform an attack in a direction
|
||||
* @param direction Direction
|
||||
* @param effectId Sound effect to play
|
||||
* @param maxDistance Maximum distance in the given direction
|
||||
* @param amount Damage amount
|
||||
* @param agility Agility threshold
|
||||
* @param widgetNa
|
||||
*/
|
||||
void attack(int direction, int effectId, uint maxDistance, uint amount, uint agility, const Common::String &hitWidget) override;
|
||||
|
||||
/**
|
||||
* Board a transport
|
||||
*/
|
||||
void board() override;
|
||||
|
||||
/**
|
||||
* Do an enter action
|
||||
*/
|
||||
void enter() override;
|
||||
|
||||
/**
|
||||
* Do an inform action
|
||||
*/
|
||||
void inform() override;
|
||||
|
||||
/**
|
||||
* Do an exit transport action
|
||||
*/
|
||||
void disembark() override;
|
||||
|
||||
/**
|
||||
* Get the lands number the player is currently within
|
||||
*/
|
||||
uint getLandsNumber() const;
|
||||
|
||||
/**
|
||||
* Adds a widget for the player being on foot, and sets it to the active player widget
|
||||
*/
|
||||
void addOnFoot();
|
||||
|
||||
/**
|
||||
* Get the number of active enemy vessels
|
||||
*/
|
||||
uint getEnemyVesselCount() const;
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
70
engines/ultima/ultima1/maps/map_tile.cpp
Normal file
70
engines/ultima/ultima1/maps/map_tile.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/maps/map_overworld.h"
|
||||
#include "ultima/ultima1/maps/map_city_castle.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Maps {
|
||||
|
||||
void U1MapTile::clear() {
|
||||
_map = nullptr;
|
||||
_locationNum = -1;
|
||||
}
|
||||
|
||||
bool U1MapTile::isWater() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_WATER;
|
||||
}
|
||||
|
||||
bool U1MapTile::isGrass() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_GRASS;
|
||||
}
|
||||
|
||||
bool U1MapTile::isWoods() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_WOODS;
|
||||
}
|
||||
|
||||
bool U1MapTile::isOriginalWater() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_WATER;
|
||||
}
|
||||
|
||||
bool U1MapTile::isOriginalGrass() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_GRASS;
|
||||
}
|
||||
|
||||
bool U1MapTile::isOriginalWoods() const {
|
||||
return dynamic_cast<MapOverworld *>(_map) && _tileId == TILE_WOODS;
|
||||
}
|
||||
|
||||
bool U1MapTile::isGround() const {
|
||||
if (dynamic_cast<MapCityCastle *>(_map) && (_tileId == 1 || _tileId >= 51))
|
||||
return true;
|
||||
else if (dynamic_cast<MapOverworld *>(_map))
|
||||
// Not water or mountains
|
||||
return _tileId != TILE_WATER && _tileId != TILE_MOUNTAINS;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
107
engines/ultima/ultima1/maps/map_tile.h
Normal file
107
engines/ultima/ultima1/maps/map_tile.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA_ULTIMA1_MAPS_MAP_TILE_H
|
||||
#define ULTIMA_ULTIMA1_MAPS_MAP_TILE_H
|
||||
|
||||
#include "ultima/shared/maps/map_tile.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Widgets {
|
||||
class DungeonItem;
|
||||
}
|
||||
|
||||
namespace Maps {
|
||||
|
||||
enum TileId {
|
||||
TILE_WATER = 0, TILE_GRASS = 1, TILE_WOODS = 2, TILE_MOUNTAINS = 3
|
||||
};
|
||||
|
||||
class MapBase;
|
||||
class Ultima1Map;
|
||||
|
||||
/**
|
||||
* Derived map tile class for Ultima 1 that adds extra properties
|
||||
*/
|
||||
class U1MapTile : public Shared::Maps::MapTile {
|
||||
private:
|
||||
MapBase *_map;
|
||||
public:
|
||||
int _locationNum;
|
||||
Widgets::DungeonItem *_item;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
U1MapTile() : Shared::Maps::MapTile(), _item(0), _locationNum(-1), _map(nullptr) {}
|
||||
|
||||
/**
|
||||
* Set the active map
|
||||
*/
|
||||
void setMap(MapBase *map) { _map = map; }
|
||||
|
||||
/**
|
||||
* Clears tile data
|
||||
*/
|
||||
void clear() override;
|
||||
|
||||
/**
|
||||
* Return true if the tile base is water
|
||||
*/
|
||||
bool isWater() const;
|
||||
|
||||
/**
|
||||
* Return true if the tile base is grass
|
||||
*/
|
||||
bool isGrass() const;
|
||||
|
||||
/**
|
||||
* Return true if the tile base is woods
|
||||
*/
|
||||
bool isWoods() const;
|
||||
|
||||
/**
|
||||
* Return true if the tile base in the original map is water
|
||||
*/
|
||||
bool isOriginalWater() const;
|
||||
|
||||
/**
|
||||
* Return true if the tile base in the original map is grass
|
||||
*/
|
||||
bool isOriginalGrass() const;
|
||||
|
||||
/**
|
||||
* Return true if the tile base in the original map is woods
|
||||
*/
|
||||
bool isOriginalWoods() const;
|
||||
|
||||
/**
|
||||
* Returns true if the tile is a ground type tool
|
||||
*/
|
||||
bool isGround() const;
|
||||
};
|
||||
|
||||
} // End of namespace Maps
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user