/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ultima/ultima4/ultima4.h" #include "ultima/ultima4/core/config.h" #include "ultima/ultima4/map/maploader.h" #include "ultima/ultima4/map/city.h" #include "ultima/ultima4/controllers/combat_controller.h" #include "ultima/ultima4/conversation/conversation.h" #include "ultima/ultima4/conversation/dialogueloader.h" #include "ultima/ultima4/map/dungeon.h" #include "ultima/ultima4/map/map.h" #include "ultima/ultima4/map/maploader.h" #include "ultima/ultima4/map/mapmgr.h" #include "ultima/ultima4/game/object.h" #include "ultima/ultima4/game/person.h" #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/core/utils.h" #include "ultima/ultima4/gfx/image.h" #include "ultima/ultima4/gfx/imagemgr.h" #include "common/file.h" #include "common/system.h" namespace Ultima { namespace Ultima4 { MapLoaders *g_mapLoaders; MapLoaders::MapLoaders() { g_mapLoaders = this; (*this)[Map::CITY] = new CityMapLoader(); (*this)[Map::SHRINE] = new ConMapLoader(); (*this)[Map::DUNGEON] = new DngMapLoader(); (*this)[Map::WORLD] = new WorldMapLoader(); (*this)[Map::COMBAT] = new ConMapLoader(); (*this)[Map::XML] = new XMLMapLoader(); } MapLoaders::~MapLoaders() { // Free the loaders for (iterator it = begin(); it != end(); ++it) delete it->_value; g_mapLoaders = nullptr; } MapLoader *MapLoaders::getLoader(Map::Type type) { if (find(type) == end()) return nullptr; return (*this)[type]; } /*-------------------------------------------------------------------*/ bool MapLoader::loadData(Map *map, Common::SeekableReadStream &f) { uint x, xch, y, ych; // Allocate the space we need for the map data map->_data.clear(); map->_data.resize(map->_height * map->_width); if (map->_chunkHeight == 0) map->_chunkHeight = map->_height; if (map->_chunkWidth == 0) map->_chunkWidth = map->_width; uint32 total = 0; f.seek(map->_offset, SEEK_CUR); for (ych = 0; ych < (map->_height / map->_chunkHeight); ++ych) { for (xch = 0; xch < (map->_width / map->_chunkWidth); ++xch) { if (isChunkCompressed(map, ych * map->_chunkWidth + xch)) { MapTile water = map->_tileSet->getByName("sea")->getId(); for (y = 0; y < map->_chunkHeight; ++y) { for (x = 0; x < map->_chunkWidth; ++x) { map->_data[x + (y * map->_width) + (xch * map->_chunkWidth) + (ych * map->_chunkHeight * map->_width)] = water; } } } else { for (y = 0; y < map->_chunkHeight; ++y) { for (x = 0; x < map->_chunkWidth; ++x) { int c = f.readByte(); if (c == EOF) return false; uint32 s = g_system->getMillis(); MapTile mt = map->translateFromRawTileIndex(c); total += g_system->getMillis() - s; map->_data[x + (y * map->_width) + (xch * map->_chunkWidth) + (ych * map->_chunkHeight * map->_width)] = mt; } } } } } debug(10, "MapLoader::loadData translation took %d ms", total); return true; } bool MapLoader::isChunkCompressed(Map *map, int chunk) { CompressedChunkList::iterator i; for (i = map->_compressedChunks.begin(); i != map->_compressedChunks.end(); i++) { if (chunk == *i) return true; } return false; } /*-------------------------------------------------------------------*/ bool CityMapLoader::load(Map *map) { City *city = dynamic_cast(map); assert(city); uint i, j; Person *people[CITY_MAX_PERSONS]; Dialogue *dialogues[CITY_MAX_PERSONS]; DialogueLoader *dlgLoader = DialogueLoaders::getLoader("application/x-u4tlk"); Common::File ult, tlk; if (!ult.open(city->_fname) || !tlk.open(city->_tlkFname)) error("unable to load map data"); // The map must be 32x32 to be read from an .ULT file assertMsg(city->_width == CITY_WIDTH, "map width is %d, should be %d", city->_width, CITY_WIDTH); assertMsg(city->_height == CITY_HEIGHT, "map height is %d, should be %d", city->_height, CITY_HEIGHT); if (!loadData(city, ult)) return false; // Properly construct people for the city for (i = 0; i < CITY_MAX_PERSONS; i++) people[i] = new Person(map->translateFromRawTileIndex(ult.readByte())); for (i = 0; i < CITY_MAX_PERSONS; i++) people[i]->getStart().x = ult.readByte(); for (i = 0; i < CITY_MAX_PERSONS; i++) people[i]->getStart().y = ult.readByte(); for (i = 0; i < CITY_MAX_PERSONS; i++) people[i]->setPrevTile(map->translateFromRawTileIndex(ult.readByte())); for (i = 0; i < CITY_MAX_PERSONS * 2; i++) ult.readByte(); // Read redundant startx/starty for (i = 0; i < CITY_MAX_PERSONS; i++) { byte c = ult.readByte(); if (c == 0) people[i]->setMovementBehavior(MOVEMENT_FIXED); else if (c == 1) people[i]->setMovementBehavior(MOVEMENT_WANDER); else if (c == 0x80) people[i]->setMovementBehavior(MOVEMENT_FOLLOW_AVATAR); else if (c == 0xFF) people[i]->setMovementBehavior(MOVEMENT_ATTACK_AVATAR); else return false; } byte conv_idx[CITY_MAX_PERSONS]; for (i = 0; i < CITY_MAX_PERSONS; i++) { conv_idx[i] = ult.readByte(); } for (i = 0; i < CITY_MAX_PERSONS; i++) { people[i]->getStart().z = 0; } for (i = 0; i < CITY_MAX_PERSONS; i++) { dialogues[i] = dlgLoader->load(&tlk); if (!dialogues[i]) break; /* * Match up dialogues with their respective people */ bool found = false; for (j = 0; j < CITY_MAX_PERSONS; j++) { if (conv_idx[j] == i + 1) { people[j]->setDialogue(dialogues[i]); found = true; } } /* * if the dialogue doesn't match up with a person, attach it * to the city; Isaac the ghost in Skara Brae is handled like * this */ if (!found) { city->_extraDialogues.push_back(dialogues[i]); } } /* * Assign roles to certain people */ for (i = 0; i < CITY_MAX_PERSONS; i++) { PersonRoleList::iterator current; for (current = city->_personRoles.begin(); current != city->_personRoles.end(); current++) { if ((unsigned)(*current)->_id == (i + 1)) { if ((*current)->_role == NPC_LORD_BRITISH) people[i]->setDialogue(DialogueLoaders::getLoader("application/x-u4lbtlk")->load(nullptr)); else if ((*current)->_role == NPC_HAWKWIND) people[i]->setDialogue(DialogueLoaders::getLoader("application/x-u4hwtlk")->load(nullptr)); people[i]->setNpcType(static_cast((*current)->_role)); } } } /** * Add the people to the city structure */ for (i = 0; i < CITY_MAX_PERSONS; i++) { if (people[i]->getTile() != 0) city->_persons.push_back(people[i]); else delete people[i]; } return true; } /*-------------------------------------------------------------------*/ bool ConMapLoader::load(Map *map) { int i; Common::File con; if (!con.open(map->_fname)) error("unable to load map data"); // The map must be 11x11 to be read from an .CON file assertMsg(map->_width == CON_WIDTH, "map width is %d, should be %d", map->_width, CON_WIDTH); assertMsg(map->_height == CON_HEIGHT, "map height is %d, should be %d", map->_height, CON_HEIGHT); if (map->_type != Map::SHRINE) { CombatMap *cm = getCombatMap(map); for (i = 0; i < AREA_CREATURES; i++) cm->creature_start[i] = MapCoords(con.readByte()); for (i = 0; i < AREA_CREATURES; i++) cm->creature_start[i].y = con.readByte(); for (i = 0; i < AREA_PLAYERS; i++) cm->player_start[i] = MapCoords(con.readByte()); for (i = 0; i < AREA_PLAYERS; i++) cm->player_start[i].y = con.readByte(); con.seek(16L, SEEK_CUR); } if (!loadData(map, con)) return false; return true; } /*-------------------------------------------------------------------*/ bool DngMapLoader::load(Map *map) { Dungeon *dungeon = dynamic_cast(map); assert(dungeon); Common::File dng; if (!dng.open(dungeon->_fname)) error("unable to load map data"); // The map must be 11x11 to be read from an .CON file assertMsg(dungeon->_width == DNG_WIDTH, "map width is %d, should be %d", dungeon->_width, DNG_WIDTH); assertMsg(dungeon->_height == DNG_HEIGHT, "map height is %d, should be %d", dungeon->_height, DNG_HEIGHT); // Load the dungeon map uint i, j; for (i = 0; i < (DNG_HEIGHT * DNG_WIDTH * dungeon->_levels); i++) { byte mapData = dng.readByte(); MapTile tile = map->translateFromRawTileIndex(mapData); // Determine what type of tile it is dungeon->_data.push_back(tile); dungeon->_dataSubTokens.push_back(mapData % 16); } // Read in the dungeon rooms // FIXME: needs a cleanup function to free this memory later dungeon->_rooms = new DngRoom[dungeon->_nRooms]; for (i = 0; i < dungeon->_nRooms; i++) { byte room_tiles[121]; for (j = 0; j < DNGROOM_NTRIGGERS; j++) { int tmp; dungeon->_rooms[i]._triggers[j]._tile = g_tileMaps->get("base")->translate(dng.readByte())._id; tmp = dng.readByte(); if (tmp == EOF) return false; dungeon->_rooms[i]._triggers[j].x = (tmp >> 4) & 0x0F; dungeon->_rooms[i]._triggers[j].y = tmp & 0x0F; tmp = dng.readByte(); if (tmp == EOF) return false; dungeon->_rooms[i]._triggers[j]._changeX1 = (tmp >> 4) & 0x0F; dungeon->_rooms[i]._triggers[j]._changeY1 = tmp & 0x0F; tmp = dng.readByte(); if (tmp == EOF) return false; dungeon->_rooms[i]._triggers[j].changeX2 = (tmp >> 4) & 0x0F; dungeon->_rooms[i]._triggers[j].changeY2 = tmp & 0x0F; } dungeon->_rooms[i].load(dng); dng.read(room_tiles, sizeof(room_tiles)); dng.read(dungeon->_rooms[i]._buffer, sizeof(dungeon->_rooms[i]._buffer)); // Translate each creature tile to a tile id for (j = 0; j < sizeof(dungeon->_rooms[i]._creatureTiles); j++) dungeon->_rooms[i]._creatureTiles[j] = g_tileMaps->get("base")->translate(dungeon->_rooms[i]._creatureTiles[j])._id; // Translate each map tile to a tile id for (j = 0; j < sizeof(room_tiles); j++) dungeon->_rooms[i]._mapData.push_back(g_tileMaps->get("base")->translate(room_tiles[j])); // // dungeon room fixup // if (map->_id == MAP_HYTHLOTH) { if (i == 0x7) { dungeon->_rooms[i].hythlothFix7(); } else if (i == 0x9) { dungeon->_rooms[i].hythlothFix9(); } } } dungeon->_roomMaps = new CombatMap *[dungeon->_nRooms]; for (i = 0; i < dungeon->_nRooms; i++) initDungeonRoom(dungeon, i); return true; } /*-------------------------------------------------------------------*/ void DngMapLoader::initDungeonRoom(Dungeon *dng, int room) { dng->_roomMaps[room] = dynamic_cast(mapMgr->initMap(Map::COMBAT)); dng->_roomMaps[room]->_id = 0; dng->_roomMaps[room]->_borderBehavior = Map::BORDER_FIXED; dng->_roomMaps[room]->_width = dng->_roomMaps[room]->_height = 11; dng->_roomMaps[room]->_data = dng->_rooms[room]._mapData; // Load map data dng->_roomMaps[room]->_music = Music::COMBAT; dng->_roomMaps[room]->_type = Map::COMBAT; dng->_roomMaps[room]->_flags |= NO_LINE_OF_SIGHT; dng->_roomMaps[room]->_tileSet = g_tileSets->get("base"); } /*-------------------------------------------------------------------*/ bool WorldMapLoader::load(Map *map) { Common::File world; if (!world.open(map->_fname)) error("unable to load map data"); if (!loadData(map, world)) return false; // 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(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