Files
2026-02-02 04:50:13 +01:00

417 lines
12 KiB
C++

/* 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/ultima4/map/dungeon.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
bool isDungeon(Map *punknown) {
Dungeon *pd;
if ((pd = dynamic_cast<Dungeon *>(punknown)) != nullptr)
return true;
else
return false;
}
/*-------------------------------------------------------------------*/
void DngRoom::load(Common::SeekableReadStream &s) {
int i;
s.read(_creatureTiles, 16);
for (i = 0; i < 16; ++i)
_creatureStart[i].x = s.readByte();
for (i = 0; i < 16; ++i)
_creatureStart[i].y = s.readByte();
#define READ_DIR(DIR, XY) \
for (i = 0; i < 8; ++i) \
_partyStart[i][DIR].XY = s.readByte()
READ_DIR(DIR_NORTH, x);
READ_DIR(DIR_NORTH, y);
READ_DIR(DIR_EAST, x);
READ_DIR(DIR_EAST, y);
READ_DIR(DIR_SOUTH, x);
READ_DIR(DIR_SOUTH, y);
READ_DIR(DIR_WEST, x);
READ_DIR(DIR_WEST, y);
#undef READ_DIR
}
void DngRoom::hythlothFix7() {
int i;
// Update party start positions when entering from the east
const byte X1[8] = { 0x8, 0x8, 0x9, 0x9, 0x9, 0xA, 0xA, 0xA },
Y1[8] = { 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x2, 0x1 };
for (i = 0; i < 8; ++i)
_partyStart[i]._eastStart.x = X1[i];
for (i = 0; i < 8; ++i)
_partyStart[i]._eastStart.y = Y1[i];
// Update party start positions when entering from the south
const byte X2[8] = { 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x2, 0x1 },
Y2[8] = { 0x8, 0x8, 0x9, 0x9, 0x9, 0xA, 0xA, 0xA };
for (i = 0; i < 8; ++i)
_partyStart[i]._southStart.x = X2[i];
for (i = 0; i < 8; ++i)
_partyStart[i]._southStart.y = Y2[i];
}
void DngRoom::hythlothFix9() {
int i;
// Update the starting position of monsters 7, 8, and 9
const byte X1[3] = { 0x4, 0x6, 0x5 },
Y1[3] = { 0x5, 0x5, 0x6 };
for (i = 0; i < 3; ++i)
_creatureStart[i + 7].x = X1[i];
for (i = 0; i < 3; ++i)
_creatureStart[i + 7].y = Y1[i];
// Update party start positions when entering from the west
const byte X2[8] = { 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0 },
Y2[8] = { 0x9, 0x8, 0x9, 0x8, 0x7, 0x9, 0x8, 0x7 };
for (i = 0; i < 8; ++i)
_partyStart[i]._westStart.x = X2[i];
for (i = 0; i < 8; ++i)
_partyStart[i]._westStart.y = Y2[i];
// Update the map data, moving the chest to the center of the room,
// and removing the walls at the lower-left corner thereby creating
// a connection to room 8
const Coords tile[6] = {
Coords(5, 5, 0x3C), // Chest
Coords(0, 7, 0x16), // Floor
Coords(1, 7, 0x16),
Coords(0, 8, 0x16),
Coords(1, 8, 0x16),
Coords(0, 9, 0x16)
};
for (i = 0; i < 6; ++i) {
const int index = (tile[i].y * CON_WIDTH) + tile[i].x;
_mapData[index] = g_tileMaps->get("base")->translate(tile[i].z);
}
}
/*-------------------------------------------------------------------*/
Dungeon::Dungeon() : _nRooms(0), _rooms(nullptr),
_roomMaps(nullptr), _currentRoom(0) {
Common::fill(&_partyStartX[0], &_partyStartX[8], 0);
Common::fill(&_partyStartY[0], &_partyStartY[8], 0);
}
Common::String Dungeon::getName() {
return _name;
}
DungeonToken Dungeon::tokenForTile(MapTile tile) {
const static Common::String tileNames[] = {
"brick_floor", "up_ladder", "down_ladder", "up_down_ladder", "chest",
"unimpl_ceiling_hole", "unimpl_floor_hole", "magic_orb",
"ceiling_hole", "fountain",
"brick_floor", "dungeon_altar", "dungeon_door", "dungeon_room",
"secret_door", "brick_wall", ""
};
const static Common::String fieldNames[] = { "poison_field", "energy_field", "fire_field", "sleep_field", "" };
int i;
Tile *t = _tileSet->get(tile.getId());
for (i = 0; !tileNames[i].empty(); i++) {
if (t->getName() == tileNames[i])
return DungeonToken(i << 4);
}
for (i = 0; !fieldNames[i].empty(); i++) {
if (t->getName() == fieldNames[i])
return DUNGEON_FIELD;
}
return (DungeonToken)0;
}
DungeonToken Dungeon::currentToken() {
return tokenAt(g_context->_location->_coords);
}
byte Dungeon::currentSubToken() {
return subTokenAt(g_context->_location->_coords);
}
DungeonToken Dungeon::tokenAt(MapCoords coords) {
return tokenForTile(*getTileFromData(coords));
}
byte Dungeon::subTokenAt(MapCoords coords) {
int index = coords.x + (coords.y * _width) + (_width * _height * coords.z);
return _dataSubTokens[index];
}
void dungeonSearch(void) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
DungeonToken token = dungeon->currentToken();
Annotation::List a = dungeon->_annotations->allAt(g_context->_location->_coords);
const ItemLocation *item;
if (a.size() > 0)
token = DUNGEON_CORRIDOR;
g_screen->screenMessage("Search...\n");
switch (token) {
case DUNGEON_MAGIC_ORB: /* magic orb */
g_screen->screenMessage("You find a Magical Ball...\nWho touches? ");
dungeonTouchOrb();
break;
case DUNGEON_FOUNTAIN: /* fountains */
dungeonDrinkFountain();
break;
default: {
/* see if there is an item at the current location (stones on altars, etc.) */
item = g_items->itemAtLocation(dungeon, g_context->_location->_coords);
if (item) {
if (item->_isItemInInventory && (g_items->*(item->_isItemInInventory))(item->_data)) {
g_screen->screenMessage("Nothing Here!\n");
} else {
if (item->_name)
g_screen->screenMessage("You find...\n%s!\n", item->_name);
(g_items->*(item->_putItemInInventory))(item->_data);
}
} else {
g_screen->screenMessage("\nYou find Nothing!\n");
}
}
break;
}
}
void dungeonDrinkFountain() {
g_screen->screenMessage("You find a Fountain.\nWho drinks? ");
int player = gameGetPlayer(false, false);
if (player == -1)
return;
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
FountainType type = (FountainType) dungeon->currentSubToken();
switch (type) {
/* plain fountain */
case FOUNTAIN_NORMAL:
g_screen->screenMessage("\nHmmm--No Effect!\n");
break;
/* healing fountain */
case FOUNTAIN_HEALING:
if (g_context->_party->member(player)->heal(HT_FULLHEAL))
g_screen->screenMessage("\nAhh-Refreshing!\n");
else
g_screen->screenMessage("\nHmmm--No Effect!\n");
break;
/* acid fountain */
case FOUNTAIN_ACID:
g_context->_party->member(player)->applyDamage(100); /* 100 damage to drinker */
g_screen->screenMessage("\nBleck--Nasty!\n");
break;
/* cure fountain */
case FOUNTAIN_CURE:
if (g_context->_party->member(player)->heal(HT_CURE))
g_screen->screenMessage("\nHmmm--Delicious!\n");
else
g_screen->screenMessage("\nHmmm--No Effect!\n");
break;
/* poison fountain */
case FOUNTAIN_POISON:
if (g_context->_party->member(player)->getStatus() != STAT_POISONED) {
soundPlay(SOUND_POISON_DAMAGE);
g_context->_party->member(player)->applyEffect(EFFECT_POISON);
g_context->_party->member(player)->applyDamage(100); /* 100 damage to drinker also */
g_screen->screenMessage("\nArgh-Choke-Gasp!\n");
} else {
g_screen->screenMessage("\nHmm--No Effect!\n");
}
break;
default:
error("Invalid call to dungeonDrinkFountain: no fountain at current location");
}
}
void dungeonTouchOrb() {
g_screen->screenMessage("You find a Magical Ball...\nWho touches? ");
int player = gameGetPlayer(false, false);
if (player == -1)
return;
int stats = 0;
int damage = 0;
/* Get current position and find a replacement tile for it */
Tile *orb_tile = g_context->_location->_map->_tileSet->getByName("magic_orb");
MapTile replacementTile(g_context->_location->getReplacementTile(g_context->_location->_coords, orb_tile));
switch (g_context->_location->_map->_id) {
case MAP_DECEIT:
stats = STATSBONUS_INT;
break;
case MAP_DESPISE:
stats = STATSBONUS_DEX;
break;
case MAP_DESTARD:
stats = STATSBONUS_STR;
break;
case MAP_WRONG:
stats = STATSBONUS_INT | STATSBONUS_DEX;
break;
case MAP_COVETOUS:
stats = STATSBONUS_DEX | STATSBONUS_STR;
break;
case MAP_SHAME:
stats = STATSBONUS_INT | STATSBONUS_STR;
break;
case MAP_HYTHLOTH:
stats = STATSBONUS_INT | STATSBONUS_DEX | STATSBONUS_STR;
break;
default:
break;
}
/* give stats bonuses */
if (stats & STATSBONUS_STR) {
g_screen->screenMessage("Strength + 5\n");
AdjustValueMax(g_ultima->_saveGame->_players[player]._str, 5, 50);
damage += 200;
}
if (stats & STATSBONUS_DEX) {
g_screen->screenMessage("Dexterity + 5\n");
AdjustValueMax(g_ultima->_saveGame->_players[player]._dex, 5, 50);
damage += 200;
}
if (stats & STATSBONUS_INT) {
g_screen->screenMessage("Intelligence + 5\n");
AdjustValueMax(g_ultima->_saveGame->_players[player]._intel, 5, 50);
damage += 200;
}
/* deal damage to the party member who touched the orb */
g_context->_party->member(player)->applyDamage(damage);
/* remove the orb from the map */
g_context->_location->_map->_annotations->add(g_context->_location->_coords, replacementTile);
}
bool dungeonHandleTrap(TrapType trap) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
switch ((TrapType)dungeon->currentSubToken()) {
case TRAP_WINDS:
g_screen->screenMessage("\nWinds!\n");
g_context->_party->quenchTorch();
break;
case TRAP_FALLING_ROCK:
// Treat falling rocks and pits like bomb traps
// XXX: That's a little harsh.
g_screen->screenMessage("\nFalling Rocks!\n");
g_context->_party->applyEffect(EFFECT_LAVA);
break;
case TRAP_PIT:
g_screen->screenMessage("\nPit!\n");
g_context->_party->applyEffect(EFFECT_LAVA);
break;
default:
break;
}
return true;
}
bool Dungeon::ladderUpAt(MapCoords coords) {
Annotation::List a = _annotations->allAt(coords);
if (tokenAt(coords) == DUNGEON_LADDER_UP ||
tokenAt(coords) == DUNGEON_LADDER_UPDOWN)
return true;
if (a.size() > 0) {
Annotation::List::iterator i;
for (i = a.begin(); i != a.end(); i++) {
if (i->getTile() == _tileSet->getByName("up_ladder")->getId())
return true;
}
}
return false;
}
bool Dungeon::ladderDownAt(MapCoords coords) {
Annotation::List a = _annotations->allAt(coords);
if (tokenAt(coords) == DUNGEON_LADDER_DOWN ||
tokenAt(coords) == DUNGEON_LADDER_UPDOWN)
return true;
if (a.size() > 0) {
Annotation::List::iterator i;
for (i = a.begin(); i != a.end(); i++) {
if (i->getTile() == _tileSet->getByName("down_ladder")->getId())
return true;
}
}
return false;
}
bool Dungeon::validTeleportLocation(MapCoords coords) {
MapTile *tile = tileAt(coords, WITH_OBJECTS);
return tokenForTile(*tile) == DUNGEON_CORRIDOR;
}
} // End of namespace Ultima4
} // End of namespace Ultima