ULTIMA4: Added support for XML-based maps

This includes the addition of a hidden ScummVM map, just as
an easter egg for anyone that stumbles across it
This commit is contained in:
Paul Gilbert 2020-05-16 16:25:10 -07:00
parent 077967fee4
commit 683959ea2d
12 changed files with 172 additions and 5 deletions

View File

@ -7,7 +7,7 @@
<!ELEMENT maps ( map+ ) >
<!ELEMENT map ( city | compressedchunk | dungeon | moongate | portal | shrine | label )* >
<!ELEMENT map ( city | compressedchunk | dungeon | moongate | portal | shrine | xml | label )* >
<!ATTLIST map id NMTOKEN #REQUIRED
type ( city | combat | dungeon | shrine | world | u3world ) #REQUIRED
fname NMTOKEN #REQUIRED

View File

@ -41,7 +41,9 @@
<portal x="81" y="207" destmapid="30" startx="0" starty="0" action="enter" condition="shrine" savelocation="true" transport="footorhorse"/>
<portal x="231" y="216" destmapid="32" startx="0" starty="0" action="enter" condition="shrine" savelocation="true" transport="footorhorse"/>
<moongate phase="0" x="224" y="133"/>
<portal x="102" y="105" destmapid="101" startx="1" starty="5" action="enter" savelocation="true" transport="footorhorse" tile="5"/>
<moongate phase="0" x="224" y="133"/>
<moongate phase="1" x="96" y="102"/>
<moongate phase="2" x="38" y="224"/>
<moongate phase="3" x="50" y="37"/>
@ -406,4 +408,6 @@
<map id="55" type="combat" fname="camp.dng" width="11" height="11" levels="1"
borderbehavior="fixed" nolineofsight="true" music="8" tileset="base" tilemap="base"/>
<!-- Custom maps introduced under ScummVM-->
<xi:include xmlns:xi="http://www.w3.org/2003/XInclude" href="scummvm_map.xml"/>
</maps>

View File

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<map id="101" type="xml" fname="scummvm.xml" width="57" height="11" levels="1"
borderbehavior="exit" showavatar="true" music="2" tileset="base" tilemap="base">
<tiles>
127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127
127,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,127
127,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,127
127,04,04,63,63,63,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,63,04,04,04,04,04,04,63,04,63,04,04,04,04,04,63,04,04,04,04,04,04,04,127
004,04,63,04,04,04,63,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,63,04,04,04,04,63,04,04,63,63,04,04,04,63,63,04,04,00,00,00,04,04,127
004,04,04,63,04,04,04,04,04,63,63,63,04,63,04,04,04,63,04,63,63,04,04,63,63,04,63,63,04,04,63,63,04,04,63,04,04,04,04,63,04,04,63,04,63,04,63,04,63,04,04,23,03,23,04,04,127
004,04,04,04,63,63,04,04,63,04,04,04,04,63,04,04,04,63,04,63,04,63,63,04,63,04,63,04,63,63,04,63,04,04,04,63,04,04,63,04,04,04,63,04,04,63,04,04,63,04,04,00,00,00,04,04,127
127,04,63,04,04,04,63,04,63,04,04,04,04,63,04,04,63,63,04,63,04,04,04,04,63,04,63,04,04,04,04,63,04,04,04,63,04,04,63,04,04,04,63,04,04,04,04,04,63,04,04,04,04,04,04,04,127
127,04,04,63,63,63,04,04,04,63,63,63,04,04,63,63,04,63,04,63,04,04,04,04,63,04,63,04,04,04,04,63,04,04,04,04,63,63,04,04,04,04,63,04,04,04,04,04,63,04,04,04,04,04,04,04,127
127,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,04,127
127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127
</tiles>
</map>

View File

@ -208,7 +208,7 @@ void CombatController::initDungeonRoom(int room, Direction from) {
ASSERT(g_context->_location->_prev->_context & CTX_DUNGEON, "Error: called initDungeonRoom from non-dungeon context");
{
Dungeon *dng = dynamic_cast<Dungeon *>(g_context->_location->_prev->_map);
assert(dng);
assert(dng);
DngRoom &dngRoom = dng->_rooms[room];

View File

@ -1002,6 +1002,15 @@ bool Debugger::cmdSearch(int argc, const char **argv) {
dungeonSearch();
} else if (g_context->_party->isFlying()) {
print("Searching...\n%cDrift only!%c", FG_GREY, FG_WHITE);
} else if (g_context->_location->_map->_id == MAP_SCUMMVM &&
g_context->_location->_coords == Coords(52, 5, 0)) {
// Special hack for the ScummVM easter egg map. Searching on
// the given tile triggers the cheat to allow teleporting
print("Searching...\nFound teleport point!");
g_game->exitToParentMap();
g_music->playMapMusic();
return cmdGoto(argc, argv);
} else {
print("Searching...");

View File

@ -62,6 +62,7 @@ struct Portal {
Common::String _message;
TransportContext _portalTransportRequisites;
bool _exitPortal;
int _tile;
};
/**

View File

@ -140,7 +140,8 @@ public:
CITY,
SHRINE,
COMBAT,
DUNGEON
DUNGEON,
XML
};
enum BorderBehavior {

View File

@ -37,6 +37,7 @@
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/map/xml_map.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/gfx/image.h"
@ -56,6 +57,7 @@ MapLoaders::MapLoaders() {
(*this)[Map::DUNGEON] = new DngMapLoader();
(*this)[Map::WORLD] = new WorldMapLoader();
(*this)[Map::COMBAT] = new ConMapLoader();
(*this)[Map::XML] = new XMLMapLoader();
}
MapLoaders::~MapLoaders() {
@ -130,6 +132,8 @@ bool MapLoader::isChunkCompressed(Map *map, int chunk) {
return false;
}
/*-------------------------------------------------------------------*/
bool CityMapLoader::load(Map *map) {
City *city = dynamic_cast<City *>(map);
assert(city);
@ -249,6 +253,8 @@ bool CityMapLoader::load(Map *map) {
return true;
}
/*-------------------------------------------------------------------*/
bool ConMapLoader::load(Map *map) {
int i;
@ -286,6 +292,8 @@ bool ConMapLoader::load(Map *map) {
return true;
}
/*-------------------------------------------------------------------*/
bool DngMapLoader::load(Map *map) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(map);
assert(dungeon);
@ -372,6 +380,8 @@ bool DngMapLoader::load(Map *map) {
return true;
}
/*-------------------------------------------------------------------*/
void DngMapLoader::initDungeonRoom(Dungeon *dng, int room) {
dng->_roomMaps[room] = dynamic_cast<CombatMap *>(mapMgr->initMap(Map::COMBAT));
@ -385,6 +395,8 @@ void DngMapLoader::initDungeonRoom(Dungeon *dng, int room) {
dng->_roomMaps[room]->_tileSet = g_tileSets->get("base");
}
/*-------------------------------------------------------------------*/
bool WorldMapLoader::load(Map *map) {
Common::File *world = u4fopen(map->_fname);
if (!world)
@ -395,8 +407,64 @@ bool WorldMapLoader::load(Map *map) {
u4fclose(world);
// Check for any tile overrides for the portals
for (uint idx = 0; idx < map->_portals.size(); ++idx) {
const Portal *p = map->_portals[idx];
if (p->_tile != -1) {
MapTile mt = map->translateFromRawTileIndex(p->_tile);
map->_data[p->_coords.x + p->_coords.y * map->_width] = mt;
}
}
return true;
}
/*-------------------------------------------------------------------*/
bool XMLMapLoader::load(Map *map) {
XMLMap *xmlMap = dynamic_cast<XMLMap *>(map);
assert(xmlMap);
Common::String text = xmlMap->_tilesText;
text.trim();
// Allocate the space we need for the map data
map->_data.clear();
map->_data.resize(map->_width * map->_height);
// Split up the text lines
Common::StringArray lines, cols;
split(text, lines, '\n');
assert(lines.size() == map->_height);
// Iterate through the lines
for (uint y = 0; y < map->_height; ++y) {
text = lines[y];
text.trim();
split(text, cols, ',');
assert(cols.size() == map->_width);
for (uint x = 0; x < map->_width; ++x) {
int id = atoi(cols[x].c_str());
MapTile mt = map->translateFromRawTileIndex(id);
map->_data[x + y * map->_width] = mt;
}
}
return true;
}
void XMLMapLoader::split(const Common::String &text, Common::StringArray &values, char c) {
values.clear();
Common::String str = text;
size_t pos;
while ((pos = str.findFirstOf(c)) != Common::String::npos) {
values.push_back(Common::String(str.c_str(), pos));
str = Common::String(str.c_str() + pos + 1);
}
values.push_back(str);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@ -113,6 +113,16 @@ public:
bool load(Map *map) override;
};
class XMLMapLoader : public MapLoader {
private:
void split(const Common::String &text, Common::StringArray &values, char c);
public:
/**
* Loads the data for the map from the provided Xml
*/
bool load(Map *map) override;
};
class MapLoaders : public Std::map<Map::Type, MapLoader *, MapType_Hash> {
public:
/**

View File

@ -35,6 +35,7 @@
#include "ultima/ultima4/map/shrine.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/map/xml_map.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/core/config.h"
@ -116,6 +117,10 @@ Map *MapMgr::initMap(Map::Type type) {
map = new City();
break;
case Map::XML:
map = new XMLMap();
break;
default:
error("Error: invalid map type used");
break;
@ -149,7 +154,7 @@ void MapMgr::registerMap(Map *map) {
Map *MapMgr::initMapFromConf(const ConfigElement &mapConf) {
Map *map;
static const char *mapTypeEnumStrings[] = { "world", "city", "shrine", "combat", "dungeon", nullptr };
static const char *mapTypeEnumStrings[] = { "world", "city", "shrine", "combat", "dungeon", "xml", nullptr };
static const char *borderBehaviorEnumStrings[] = { "wrap", "exit", "fixed", nullptr };
map = initMap(static_cast<Map::Type>(mapConf.getEnum("type", mapTypeEnumStrings)));
@ -208,6 +213,8 @@ Map *MapMgr::initMapFromConf(const ConfigElement &mapConf) {
map->_compressedChunks.push_back(initCompressedChunkFromConf(*i));
else if (i->getName() == "label")
map->_labels.insert(initLabelFromConf(*i));
else if (i->getName() == "tiles" && map->_type == Map::XML)
static_cast<XMLMap *>(map)->_tilesText = i->getNode()->firstChild()->text();
}
return map;
@ -302,6 +309,10 @@ Portal *MapMgr::initPortalFromConf(const ConfigElement &portalConf) {
portal->_exitPortal = portalConf.getBool("exits");
// Used as a shortcut for specifying the display tile
// for new/fan maps being added to the overworld
portal->_tile = portalConf.exists("tile") ? portalConf.getInt("tile") : -1;
Std::vector<ConfigElement> children = portalConf.getChildren();
for (Std::vector<ConfigElement>::iterator i = children.begin(); i != children.end(); i++) {
if (i->getName() == "retroActiveDest") {
@ -314,6 +325,7 @@ Portal *MapMgr::initPortalFromConf(const ConfigElement &portalConf) {
portal->_retroActiveDest->_mapid = static_cast<MapId>(i->getInt("mapid"));
}
}
return portal;
}

View File

@ -99,6 +99,8 @@ class Shrine;
#define MAP_SHORE_CON 53
#define MAP_SHORSHIP_CON 54
#define MAP_CAMP_DNG 55
#define MAP_CASTLE_OF_LORD_BRITISH2 100
#define MAP_SCUMMVM 101
/**
* The map manager singleton that keeps track of all the maps.

View File

@ -0,0 +1,43 @@
/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef ULTIMA4_MAP_XML_MAP_H
#define ULTIMA4_MAP_XML_MAP_H
#include "ultima/ultima4/map/city.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class XMLMap : public City {
public:
Common::String _tilesText;
public:
XMLMap() : City() {}
~XMLMap() override {};
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif