Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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/>.
*
*/
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/core/settings.h"
#include "common/debug.h"
namespace Ultima {
namespace Ultima4 {
Annotation::Annotation(const Coords &pos, MapTile t, bool v, bool coverUp) :
_coords(pos),
_tile(t),
_visual(v),
_ttl(-1),
_coverUp(coverUp) {
}
void Annotation::debug_output() const {
debug(1, "x: %d\n", _coords.x);
debug(1, "y: %d\n", _coords.y);
debug(1, "z: %d\n", _coords.z);
debug(1, "tile: %d\n", _tile.getId());
debug(1, "visual: %s\n", _visual ? "Yes" : "No");
}
bool Annotation::operator==(const Annotation &a) const {
return ((_coords == a.getCoords()) && (_tile == a._tile)) ? true : false;
}
/*-------------------------------------------------------------------*/
AnnotationMgr::AnnotationMgr() {}
Annotation *AnnotationMgr::add(Coords coords, MapTile tile, bool visual, bool isCoverUp) {
// New annotations go to the front so they're handled "on top"
_annotations.push_front(Annotation(coords, tile, visual, isCoverUp));
return &_annotations.front();
}
Annotation::List AnnotationMgr::allAt(Coords coords) {
Annotation::List list;
for (_it = _annotations.begin(); _it != _annotations.end(); _it++) {
if (_it->getCoords() == coords)
list.push_back(*_it);
}
return list;
}
Common::List<Annotation *> AnnotationMgr::ptrsToAllAt(Coords coords) {
Common::List<Annotation *> list;
for (_it = _annotations.begin(); _it != _annotations.end(); _it++) {
if (_it->getCoords() == coords)
list.push_back(&(*_it));
}
return list;
}
void AnnotationMgr::clear() {
_annotations.clear();
}
void AnnotationMgr::passTurn() {
for (_it = _annotations.begin(); _it != _annotations.end(); _it++) {
if (_it->getTTL() == 0) {
_it = _annotations.erase(_it);
if (_it == _annotations.end())
break;
} else if (_it->getTTL() > 0) {
_it->passTurn();
}
}
}
void AnnotationMgr::remove(Coords coords, MapTile tile) {
Annotation look_for(coords, tile);
remove(look_for);
}
void AnnotationMgr::remove(Annotation &a) {
for (_it = _annotations.begin(); _it != _annotations.end(); _it++) {
if (*_it == a) {
_it = _annotations.erase(_it);
break;
}
}
}
void AnnotationMgr::remove(Annotation::List l) {
Annotation::List::iterator it;
for (it = l.begin(); it != l.end(); it++) {
remove(*it);
}
}
int AnnotationMgr::size() const {
return _annotations.size();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,189 @@
/* 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 ULTIMA4_MAP_ANNOTATION_H
#define ULTIMA4_MAP_ANNOTATION_H
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/map_tile.h"
#include "common/list.h"
namespace Ultima {
namespace Ultima4 {
class Annotation;
/**
* Annotation are updates to a map.
* There are three types of annotations:
* - permanent: lasts until annotationClear is called
* - turn based: lasts a given number of cycles
* - time based: lasts a given number of time units (1/4 seconds)
*/
class Annotation {
public:
typedef Common::List<Annotation> List;
Annotation(const Coords &coords, MapTile tile, bool visual = false, bool coverUp = false);
void debug_output() const;
// Getters
/**
* Returns the coordinates of the annotation
*/
const Coords &getCoords() const {
return _coords;
}
/**
* Returns the annotation's tile
*/
MapTile &getTile() {
return _tile;
}
/**
* Returns true for visual-only annotations
*/
bool isVisualOnly() const {
return _visual;
}
/**
* Returns the number of turns the annotation has left to live
*/
int getTTL() const {
return _ttl;
}
bool isCoverUp() const {
return _coverUp;
}
// Setters
/**
* Sets the coordinates for the annotation
*/
void setCoords(const Coords &c) {
_coords = c;
}
/**
* Sets the tile for the annotation
*/
void setTile(const MapTile &t) {
_tile = t;
}
/**
* Sets whether or not the annotation is visual-only
*/
void setVisualOnly(bool v) {
_visual = v;
}
/**
* Sets the number of turns the annotation will live
*/
void setTTL(int turns) {
_ttl = turns;
}
/**
* Passes a turn for the annotation
*/
void passTurn() {
if (_ttl > 0) _ttl--;
}
bool operator==(const Annotation &) const;
// Properties
private:
Coords _coords;
MapTile _tile;
bool _visual;
int _ttl;
bool _coverUp;
};
/**
* Manages annotations for the current map. This includes
* adding and removing annotations, as well as finding annotations
* and managing their existence.
*/
class AnnotationMgr {
public:
AnnotationMgr();
/**
* Adds an annotation to the current map
*/
Annotation *add(Coords coords, MapTile tile, bool visual = false, bool isCoverUp = false);
/**
* Returns all annotations found at the given map coordinates
*/
Annotation::List allAt(Coords pos);
/**
* Returns pointers to all annotations found at the given map coordinates
*/
Common::List<Annotation *> ptrsToAllAt(Coords pos);
/**
* Removes all annotations on the map
*/
void clear();
/**
* Passes a turn for annotations and removes any
* annotations whose TTL has expired
*/
void passTurn();
/**
* Removes an annotation from the current map
*/
void remove(Coords pos, MapTile tile);
void remove(Annotation &);
/**
* Removes an entire list of annotations
*/
void remove(Annotation::List);
/**
* Returns the number of annotations on the map
*/
int size() const;
private:
Annotation::List _annotations;
Annotation::List::iterator _it;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,41 @@
/* 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 ULTIMA4_MAP_AREA_H
#define ULTIMA4_MAP_AREA_H
#include "ultima/ultima4/map/map.h"
namespace Ultima {
namespace Ultima4 {
#define AREA_MONSTERS 16
#define AREA_PLAYERS 8
struct Area {
MapCoords _monsterStart[AREA_MONSTERS];
MapCoords _playerStart[AREA_PLAYERS];
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,109 @@
/* 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/city.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/player.h"
namespace Ultima {
namespace Ultima4 {
City::City() : Map() {
}
City::~City() {
for (auto *i : _persons)
delete i;
for (auto *j : _personRoles)
delete j;
for (auto *k : _extraDialogues)
delete k;
}
Common::String City::getName() {
return _name;
}
Person *City::addPerson(Person *person) {
// Make a copy of the person before adding them, so
// things like angering the guards, etc. will be
// forgotten the next time you visit :)
Person *p = new Person(person);
// Set the start coordinates for the person
p->setMap(this);
p->goToStartLocation();
_objects.push_back(p);
return p;
}
void City::addPeople() {
PersonList::iterator current;
// Make sure the city has no people in it already
removeAllPeople();
for (current = _persons.begin(); current != _persons.end(); current++) {
Person *p = *current;
if ((p->getTile() != 0)
&& !(g_context->_party->canPersonJoin(p->getName(), nullptr)
&& g_context->_party->isPersonJoined(p->getName()))
)
addPerson(p);
}
}
void City::removeAllPeople() {
ObjectDeque::iterator obj;
for (obj = _objects.begin(); obj != _objects.end();) {
if (isPerson(*obj))
obj = removeObject(obj);
else
obj++;
}
}
Person *City::personAt(const Coords &coords) {
Object *obj;
obj = objectAt(coords);
if (isPerson(obj))
return dynamic_cast<Person *>(obj);
else
return nullptr;
}
bool isCity(Map *punknown) {
City *pCity;
if ((pCity = dynamic_cast<City *>(punknown)) != nullptr)
return true;
else
return false;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,92 @@
/* 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 ULTIMA4_MAP_CITY_H
#define ULTIMA4_MAP_CITY_H
#include "ultima/ultima4/map/map.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class Person;
class Dialogue;
struct PersonRole {
int _role;
int _id;
};
typedef Std::vector<Person *> PersonList;
typedef Common::List<PersonRole *> PersonRoleList;
class City : public Map {
public:
City();
~City() override;
// Members
/**
* Returns the name of the city
*/
Common::String getName() override;
/**
* Adds a person object to the map
*/
Person *addPerson(Person *p);
/**
* Add people to the map
*/
void addPeople();
/**
* Removes all people from the current map
*/
void removeAllPeople();
/**
* Returns the person object at the given (x,y,z) coords, if one exists.
* Otherwise, returns nullptr.
*/
Person *personAt(const Coords &coords);
// Properties
Common::String _name;
Common::String _type;
PersonList _persons;
Common::Path _tlkFname;
PersonRoleList _personRoles;
Std::vector<Dialogue *> _extraDialogues;
};
/**
* Returns true if the Map pointed to by 'punknown'
* is a City map
*/
bool isCity(Map *punknown);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,149 @@
/* 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/direction.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
Direction dirReverse(Direction dir) {
switch (dir) {
case DIR_NONE:
return DIR_NONE;
case DIR_WEST:
return DIR_EAST;
case DIR_NORTH:
return DIR_SOUTH;
case DIR_EAST:
return DIR_WEST;
case DIR_SOUTH:
return DIR_NORTH;
case DIR_ADVANCE:
case DIR_RETREAT:
default:
break;
}
error("invalid direction: %d", dir);
return DIR_NONE;
}
Direction dirFromMask(int dir_mask) {
if (dir_mask & MASK_DIR_NORTH) return DIR_NORTH;
else if (dir_mask & MASK_DIR_EAST) return DIR_EAST;
else if (dir_mask & MASK_DIR_SOUTH) return DIR_SOUTH;
else if (dir_mask & MASK_DIR_WEST) return DIR_WEST;
return DIR_NONE;
}
Direction dirRotateCW(Direction dir) {
dir = static_cast<Direction>(dir + 1);
if (dir > DIR_SOUTH)
dir = DIR_WEST;
return dir;
}
Direction dirRotateCCW(Direction dir) {
dir = static_cast<Direction>(dir - 1);
if (dir < DIR_WEST)
dir = DIR_SOUTH;
return dir;
}
int dirGetBroadsidesDirs(Direction dir) {
int dirmask = MASK_DIR_ALL;
dirmask = DIR_REMOVE_FROM_MASK(dir, dirmask);
dirmask = DIR_REMOVE_FROM_MASK(dirReverse(dir), dirmask);
return dirmask;
}
Direction dirRandomDir(int valid_directions_mask) {
int i, n;
Direction d[4];
n = 0;
for (i = DIR_WEST; i <= DIR_SOUTH; i++) {
if (DIR_IN_MASK(i, valid_directions_mask)) {
d[n] = static_cast<Direction>(i);
n++;
}
}
if (n == 0)
return DIR_NONE;
return d[xu4_random(n)];
}
Direction dirNormalize(Direction orientation, Direction dir) {
Direction temp = orientation,
realDir = dir;
while (temp != DIR_NORTH) {
temp = dirRotateCW(temp);
realDir = dirRotateCCW(realDir);
}
return realDir;
}
Direction keyToDirection(int key) {
switch (key) {
case Common::KEYCODE_UP:
return DIR_NORTH;
case Common::KEYCODE_DOWN:
return DIR_SOUTH;
case Common::KEYCODE_LEFT:
return DIR_WEST;
case Common::KEYCODE_RIGHT:
return DIR_EAST;
default:
return DIR_NONE;
}
}
int directionToKey(Direction dir) {
switch (dir) {
case DIR_WEST:
return Common::KEYCODE_LEFT;
case DIR_NORTH:
return Common::KEYCODE_UP;
case DIR_EAST:
return Common::KEYCODE_RIGHT;
case DIR_SOUTH:
return Common::KEYCODE_DOWN;
case DIR_NONE:
case DIR_ADVANCE:
case DIR_RETREAT:
default:
break;
}
error("Invalid diration passed to directionToKey()");
return 0;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,94 @@
/* 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 ULTIMA4_MAP_DIRECTION_H
#define ULTIMA4_MAP_DIRECTION_H
namespace Ultima {
namespace Ultima4 {
enum Direction {
DIR_NONE,
DIR_WEST,
DIR_NORTH,
DIR_EAST,
DIR_SOUTH,
DIR_ADVANCE,
DIR_RETREAT
};
#define MASK_DIR(dir) (1 << (dir))
#define MASK_DIR_WEST (1 << DIR_WEST)
#define MASK_DIR_NORTH (1 << DIR_NORTH)
#define MASK_DIR_EAST (1 << DIR_EAST)
#define MASK_DIR_SOUTH (1 << DIR_SOUTH)
#define MASK_DIR_ADVANCE (1 << DIR_ADVANCE)
#define MASK_DIR_RETREAT (1 << DIR_RETREAT)
#define MASK_DIR_ALL (MASK_DIR_WEST | MASK_DIR_NORTH | MASK_DIR_EAST | MASK_DIR_EAST | MASK_DIR_SOUTH | MASK_DIR_ADVANCE | MASK_DIR_RETREAT)
#define DIR_IN_MASK(dir,mask) ((1 << (dir)) & (mask))
#define DIR_ADD_TO_MASK(dir,mask) ((1 << (dir)) | (mask))
#define DIR_REMOVE_FROM_MASK(dir,mask) ((~(1 << (dir))) & (mask))
/**
* Returns the opposite direction.
*/
Direction dirReverse(Direction dir);
Direction dirFromMask(int dir_mask);
Direction dirRotateCW(Direction dir);
Direction dirRotateCCW(Direction dir);
/**
* Returns the a mask containing the broadsides directions for a given direction.
* For instance, dirGetBroadsidesDirs(DIR_NORTH) returns:
* (MASK_DIR(DIR_WEST) | MASK_DIR(DIR_EAST))
*/
int dirGetBroadsidesDirs(Direction dir);
/**
* Returns a random direction from a provided mask of available
* directions.
*/
Direction dirRandomDir(int valid_directions_mask);
/**
* Normalizes the direction based on the orientation given
* (if facing west, and 'up' is pressed, the 'up' is translated
* into DIR_NORTH -- this function tranlates that direction
* to DIR_WEST, the correct direction in this case).
*/
Direction dirNormalize(Direction orientation, Direction dir);
/**
* Translates a keyboard code into a direction
*/
Direction keyToDirection(int key);
/**
* Translates a direction into a keyboard code
*/
int directionToKey(Direction dir);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,416 @@
/* 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

View File

@@ -0,0 +1,235 @@
/* 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 ULTIMA4_MAP_DUNGEON_H
#define ULTIMA4_MAP_DUNGEON_H
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/map.h"
namespace Ultima {
namespace Ultima4 {
#define DNGROOM_NTRIGGERS 4
enum StatsBonusType {
STATSBONUS_INT = 0x1,
STATSBONUS_DEX = 0x2,
STATSBONUS_STR = 0x4
};
struct Trigger {
byte _tile;
byte x, y;
byte _changeX1, _changeY1, changeX2, changeY2;
};
struct PartyEntry {
private:
Common::Point *_array[5];
public:
Common::Point _northStart;
Common::Point _eastStart;
Common::Point _southStart;
Common::Point _westStart;
PartyEntry() {
_array[DIR_NONE] = nullptr;
_array[DIR_NORTH] = &_northStart;
_array[DIR_EAST] = &_eastStart;
_array[DIR_SOUTH] = &_southStart;
_array[DIR_WEST] = &_westStart;
}
Common::Point &operator[](int dir) {
return *_array[dir];
}
};
struct DngRoom {
Trigger _triggers[DNGROOM_NTRIGGERS];
byte _creatureTiles[16];
Common::Point _creatureStart[16];
PartyEntry _partyStart[8];
MapData _mapData; // This is OK to change to MapData since sizeof(DngRoom) or
// anything like it is not being used.
byte _buffer[7];
/**
* Load room data
*/
void load(Common::SeekableReadStream &s);
/**
* A couple rooms in hythloth have nullptr player start positions,
* which causes the entire party to appear in the upper-left
* tile when entering the dungeon room.
*
* Also, one dungeon room is apparently supposed to be connected
* to another, although the connection does not exist in the
* DOS U4 dungeon data file. This was fixed by removing a few
* wall tiles, and relocating a chest and the few monsters around
* it to the center of the room.
*/
void hythlothFix7();
void hythlothFix9();
};
/**
* Dungeon tokens
*/
enum DungeonToken {
DUNGEON_CORRIDOR = 0x00,
DUNGEON_LADDER_UP = 0x10,
DUNGEON_LADDER_DOWN = 0x20,
DUNGEON_LADDER_UPDOWN = 0x30,
DUNGEON_CHEST = 0x40,
DUNGEON_CEILING_HOLE = 0x50,
DUNGEON_FLOOR_HOLE = 0x60,
DUNGEON_MAGIC_ORB = 0x70,
DUNGEON_TRAP = 0x80,
DUNGEON_FOUNTAIN = 0x90,
DUNGEON_FIELD = 0xA0,
DUNGEON_ALTAR = 0xB0,
DUNGEON_DOOR = 0xC0,
DUNGEON_ROOM = 0xD0,
DUNGEON_SECRET_DOOR = 0xE0,
DUNGEON_WALL = 0xF0
};
class Dungeon : public Map {
public:
Dungeon();
~Dungeon() override {}
// Members
/**
* Returns the name of the dungeon
*/
Common::String getName() override;
/**
* Returns the dungeon token associated with the given dungeon tile
*/
DungeonToken tokenForTile(MapTile tile);
/**
* Returns the dungeon token for the current location
*/
DungeonToken currentToken();
/**
* Returns the dungeon sub-token for the current location
*/
byte currentSubToken();
/**
* Returns the dungeon token for the given coordinates
*/
DungeonToken tokenAt(MapCoords coords);
/**
* Returns the dungeon sub-token for the given coordinates. The
* subtoken is encoded in the lower bits of the map raw data. For
* instance, for the raw value 0x91, returns FOUNTAIN_HEALING NOTE:
* This function will always need type-casting to the token type
* necessary
*/
byte subTokenAt(MapCoords coords);
/**
* Returns true if a ladder-up is found at the given coordinates
*/
bool ladderUpAt(MapCoords coords);
/**
* Returns true if a ladder-down is found at the given coordinates
*/
bool ladderDownAt(MapCoords coords);
bool validTeleportLocation(MapCoords coords);
// Properties
Common::String _name;
uint _nRooms;
Std::vector<byte> _dataSubTokens;
DngRoom *_rooms;
CombatMap **_roomMaps;
int _currentRoom;
byte _partyStartX[8];
byte _partyStartY[8];
};
/**
* Dungeon sub-tokens
*/
enum TrapType {
TRAP_WINDS = 0x0,
TRAP_FALLING_ROCK = 0x1,
TRAP_PIT = 0xe
};
enum FountainType {
FOUNTAIN_NORMAL = 0x0,
FOUNTAIN_HEALING = 0x1,
FOUNTAIN_ACID = 0x2,
FOUNTAIN_CURE = 0x3,
FOUNTAIN_POISON = 0x4
};
enum FieldType {
FIELD_POISON = 0x0,
FIELD_ENERGY = 0x1,
FIELD_FIRE = 0x2,
FIELD_SLEEP = 0x3
};
/**
* Handles 's'earching while in dungeons
*/
void dungeonSearch();
/**
* Drink from the fountain at the current location
*/
void dungeonDrinkFountain();
/**
* Touch the magical ball at the current location
*/
void dungeonTouchOrb();
/**
* Handles dungeon traps
*/
bool dungeonHandleTrap(TrapType trap);
/**
* Returns true if 'map' points to a dungeon map
*/
bool isDungeon(Map *punknown);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,273 @@
/* 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/location.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/map/tileset.h"
namespace Ultima {
namespace Ultima4 {
/**
* Push a location onto the stack
*/
static Location *locationPush(Location *stack, Location *loc) {
loc->_prev = stack;
return loc;
}
/**
* Pop a location off the stack
*/
static Location *locationPop(Location **stack) {
Location *loc = *stack;
*stack = (*stack)->_prev;
loc->_prev = nullptr;
return loc;
}
/*-------------------------------------------------------------------*/
Location::Location(MapCoords coords, Map *map, int viewmode, LocationContext ctx,
TurnCompleter *turnCompleter, Location *prev) {
this->_coords = coords;
this->_map = map;
this->_viewMode = viewmode;
this->_context = ctx;
this->_turnCompleter = turnCompleter;
locationPush(prev, this);
}
Std::vector<MapTile> Location::tilesAt(MapCoords coords, bool &focus) {
Std::vector<MapTile> tiles;
Common::List<Annotation *> a = _map->_annotations->ptrsToAllAt(coords);
Common::List<Annotation *>::iterator i;
Object *obj = _map->objectAt(coords);
Creature *m = dynamic_cast<Creature *>(obj);
focus = false;
bool avatar = this->_coords == coords;
// Do not return objects for VIEW_GEM mode, show only the avatar and tiles
if (_viewMode == VIEW_GEM && (!settings._enhancements || !settings._enhancementsOptions._peerShowsObjects)) {
// When viewing a gem, always show the avatar regardless of whether or not
// it is shown in our normal view
if (avatar)
tiles.push_back(g_context->_party->getTransport());
else
tiles.push_back(*_map->getTileFromData(coords));
return tiles;
}
// Add the avatar to gem view
if (avatar && _viewMode == VIEW_GEM)
tiles.push_back(g_context->_party->getTransport());
// Add visual-only annotations to the list
for (i = a.begin(); i != a.end(); i++) {
if ((*i)->isVisualOnly()) {
tiles.push_back((*i)->getTile());
/* If this is the first cover-up annotation,
* everything underneath it will be invisible,
* so stop here
*/
if ((*i)->isCoverUp())
return tiles;
}
}
// then the avatar is drawn (unless on a ship)
if ((_map->_flags & SHOW_AVATAR) && (g_context->_transportContext != TRANSPORT_SHIP) && avatar)
//tiles.push_back(map->tileset->getByName("avatar")->id);
tiles.push_back(g_context->_party->getTransport());
// then camouflaged creatures that have a disguise
if (obj && (obj->getType() == Object::CREATURE) && !obj->isVisible() && (!m->getCamouflageTile().empty())) {
focus = focus || obj->hasFocus();
tiles.push_back(_map->_tileSet->getByName(m->getCamouflageTile())->getId());
}
// then visible creatures and objects
else if (obj && obj->isVisible()) {
focus = focus || obj->hasFocus();
MapTile visibleCreatureAndObjectTile = obj->getTile();
//Sleeping creatures and persons have their animation frozen
if (m && m->isAsleep())
visibleCreatureAndObjectTile._freezeAnimation = true;
tiles.push_back(visibleCreatureAndObjectTile);
}
// then the party's ship (because twisters and whirlpools get displayed on top of ships)
if ((_map->_flags & SHOW_AVATAR) && (g_context->_transportContext == TRANSPORT_SHIP) && avatar)
tiles.push_back(g_context->_party->getTransport());
// then permanent annotations
for (i = a.begin(); i != a.end(); i++) {
if (!(*i)->isVisualOnly()) {
tiles.push_back((*i)->getTile());
/* If this is the first cover-up annotation,
* everything underneath it will be invisible,
* so stop here
*/
if ((*i)->isCoverUp())
return tiles;
}
}
// finally the base tile
MapTile tileFromMapData = *_map->getTileFromData(coords);
const Tile *tileType = _map->getTileFromData(coords)->getTileType();
if (tileType->isLivingObject()) {
//This animation should be frozen because a living object represented on the map data is usually a statue of a monster or something
tileFromMapData._freezeAnimation = true;
}
tiles.push_back(tileFromMapData);
// But if the base tile requires a background, we must find it
if (tileType->isLandForeground() ||
tileType->isWaterForeground() ||
tileType->isLivingObject()) {
tiles.push_back(getReplacementTile(coords, tileType));
}
return tiles;
}
TileId Location::getReplacementTile(MapCoords atCoords, const Tile *forTile) {
Common::HashMap<TileId, int> validMapTileCount;
const static int dirs[][2] = {{ -1, 0}, {1, 0}, {0, -1}, {0, 1}};
const static int dirs_per_step = sizeof(dirs) / sizeof(*dirs);
int loop_count = 0;
//std::set<MapCoords> searched;
Common::List<MapCoords> searchQueue;
//Pathfinding to closest traversable tile with appropriate replacement properties.
//For tiles marked water-replaceable, pathfinding includes swimmables.
searchQueue.push_back(atCoords);
do {
MapCoords currentStep = searchQueue.front();
searchQueue.pop_front();
//searched.insert(currentStep);
for (int i = 0; i < dirs_per_step; i++) {
MapCoords newStep(currentStep);
newStep.move(dirs[i][0], dirs[i][1], _map);
Tile const *tileType = _map->tileTypeAt(newStep, WITHOUT_OBJECTS);
if (!tileType->isOpaque()) {
//if (searched.find(newStep) == searched.end()) -- the find mechanism doesn't work.
searchQueue.push_back(newStep);
}
if ((tileType->isReplacement() && (forTile->isLandForeground() || forTile->isLivingObject())) ||
(tileType->isWaterReplacement() && forTile->isWaterForeground())) {
Common::HashMap<TileId, int>::iterator validCount = validMapTileCount.find(tileType->getId());
if (validCount == validMapTileCount.end()) {
validMapTileCount[tileType->getId()] = 1;
} else {
validMapTileCount[tileType->getId()]++;
}
}
}
if (validMapTileCount.size() > 0) {
Common::HashMap<TileId, int>::iterator itr = validMapTileCount.begin();
TileId winner = itr->_key;
int score = itr->_value;
while (++itr != validMapTileCount.end()) {
if (score < itr->_value) {
score = itr->_value;
winner = itr->_key;
}
}
return winner;
}
// loop_count is an ugly hack to temporarily fix infinite loop
} while (++loop_count < 128 && searchQueue.size() > 0 && searchQueue.size() < 64);
// couldn't find a tile, give it the classic default
return _map->_tileSet->getByName("brick_floor")->getId();
}
int Location::getCurrentPosition(MapCoords *coords) {
if (_context & CTX_COMBAT) {
CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
assert(cc);
PartyMemberVector *party = cc->getParty();
*coords = (*party)[cc->getFocus()]->getCoords();
} else {
*coords = this->_coords;
}
return 1;
}
MoveResult Location::move(Direction dir, bool userEvent) {
MoveEvent event(dir, userEvent);
switch (_map->_type) {
case Map::DUNGEON:
moveAvatarInDungeon(event);
break;
case Map::COMBAT:
movePartyMember(event);
break;
default:
moveAvatar(event);
break;
}
setChanged();
notifyObservers(event);
return event._result;
}
void locationFree(Location **stack) {
delete locationPop(stack);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,93 @@
/* 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 ULTIMA4_MAP_LOCATION_H
#define ULTIMA4_MAP_LOCATION_H
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/map/movement.h"
#include "ultima/ultima4/core/observable.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
typedef enum {
CTX_WORLDMAP = 0x0001,
CTX_COMBAT = 0x0002,
CTX_CITY = 0x0004,
CTX_DUNGEON = 0x0008,
CTX_ALTAR_ROOM = 0x0010,
CTX_SHRINE = 0x0020
} LocationContext;
#define CTX_ANY (LocationContext)(0xffff)
#define CTX_NORMAL (LocationContext)(CTX_WORLDMAP | CTX_CITY)
#define CTX_NON_COMBAT (LocationContext)(CTX_ANY & ~CTX_COMBAT)
#define CTX_CAN_SAVE_GAME (LocationContext)(CTX_WORLDMAP | CTX_DUNGEON)
class TurnCompleter;
class Location : public Observable<Location *, MoveEvent &> {
public:
/**
* Add a new location to the stack, or start a new stack if 'prev' is nullptr
*/
Location(MapCoords coords, Map *map, int viewmode, LocationContext ctx, TurnCompleter *turnCompleter, Location *prev);
/**
* Return the entire stack of objects at the given location.
*/
Std::vector<MapTile> tilesAt(MapCoords coords, bool &focus);
/**
* Finds a valid replacement tile for the given location, using surrounding tiles
* as guidelines to choose the new tile. The new tile will only be chosen if it
* is marked as a valid replacement (or waterReplacement) tile in tiles.xml. If a valid replacement
* cannot be found, it returns a "best guess" tile.
*/
TileId getReplacementTile(MapCoords atCoords, Tile const *forTile);
/**
* Returns the current coordinates of the location given:
* If in combat - returns the coordinates of party member with focus
* If elsewhere - returns the coordinates of the avatar
*/
int getCurrentPosition(MapCoords *coords);
MoveResult move(Direction dir, bool userEvent);
MapCoords _coords;
Map *_map;
int _viewMode;
LocationContext _context;
TurnCompleter *_turnCompleter;
Location *_prev;
};
/**
* Pop a location from the stack and free the memory
*/
void locationFree(Location **stack);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,778 @@
/* 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/ultima4.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/movement.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/core/settings.h"
namespace Ultima {
namespace Ultima4 {
bool MapCoords::operator==(const MapCoords &a) const {
return (x == a.x) && (y == a.y) && (z == a.z);
}
bool MapCoords::operator!=(const MapCoords &a) const {
return !operator==(a);
}
bool MapCoords::operator<(const MapCoords &a) const {
if (x > a.x)
return false;
if (y > a.y)
return false;
return z < a.z;
}
MapCoords &MapCoords::wrap(const Map *map) {
if (map && map->_borderBehavior == Map::BORDER_WRAP) {
while (x < 0)
x += map->_width;
while (y < 0)
y += map->_height;
while (x >= (int)map->_width)
x -= map->_width;
while (y >= (int)map->_height)
y -= map->_height;
}
return *this;
}
MapCoords &MapCoords::putInBounds(const Map *map) {
if (map) {
if (x < 0)
x = 0;
if (x >= (int) map->_width)
x = map->_width - 1;
if (y < 0)
y = 0;
if (y >= (int) map->_height)
y = map->_height - 1;
if (z < 0)
z = 0;
if (z >= (int) map->_levels)
z = map->_levels - 1;
}
return *this;
}
MapCoords &MapCoords::move(Direction d, const Map *map) {
switch (d) {
case DIR_NORTH:
y--;
break;
case DIR_EAST:
x++;
break;
case DIR_SOUTH:
y++;
break;
case DIR_WEST:
x--;
break;
default:
break;
}
// Wrap the coordinates if necessary
wrap(map);
return *this;
}
MapCoords &MapCoords::move(int dx, int dy, const Map *map) {
x += dx;
y += dy;
// Wrap the coordinates if necessary
wrap(map);
return *this;
}
int MapCoords::getRelativeDirection(const MapCoords &c, const Map *map) const {
int dx, dy, dirmask;
dirmask = DIR_NONE;
if (z != c.z)
return dirmask;
// Adjust our coordinates to find the closest path
if (map && map->_borderBehavior == Map::BORDER_WRAP) {
MapCoords me = *this;
if (abs(int(me.x - c.x)) > abs(int(me.x + map->_width - c.x)))
me.x += map->_width;
else if (abs(int(me.x - c.x)) > abs(int(me.x - map->_width - c.x)))
me.x -= map->_width;
if (abs(int(me.y - c.y)) > abs(int(me.y + map->_width - c.y)))
me.y += map->_height;
else if (abs(int(me.y - c.y)) > abs(int(me.y - map->_width - c.y)))
me.y -= map->_height;
dx = me.x - c.x;
dy = me.y - c.y;
} else {
dx = x - c.x;
dy = y - c.y;
}
// Add x directions that lead towards to_x to the mask
if (dx < 0) dirmask |= MASK_DIR(DIR_EAST);
else if (dx > 0) dirmask |= MASK_DIR(DIR_WEST);
// Add y directions that lead towards to_y to the mask
if (dy < 0) dirmask |= MASK_DIR(DIR_SOUTH);
else if (dy > 0) dirmask |= MASK_DIR(DIR_NORTH);
// Return the result
return dirmask;
}
Direction MapCoords::pathTo(const MapCoords &c, int valid_directions, bool towards, const Map *map) const {
int directionsToObject;
// Find the directions that lead [to/away from] our target
directionsToObject = towards ? getRelativeDirection(c, map) : ~getRelativeDirection(c, map);
// Make sure we eliminate impossible options
directionsToObject &= valid_directions;
// Get the new direction to move
if (directionsToObject > DIR_NONE)
return dirRandomDir(directionsToObject);
// There are no valid directions that lead to our target, just move wherever we can!
else
return dirRandomDir(valid_directions);
}
Direction MapCoords::pathAway(const MapCoords &c, int valid_directions) const {
return pathTo(c, valid_directions, false);
}
int MapCoords::movementDistance(const MapCoords &c, const Map *map) const {
int dirmask = DIR_NONE;
int dist = 0;
MapCoords me = *this;
if (z != c.z)
return -1;
// Get the direction(s) to the coordinates
dirmask = getRelativeDirection(c, map);
while ((me.x != c.x) || (me.y != c.y)) {
if (me.x != c.x) {
if (dirmask & MASK_DIR_WEST)
me.move(DIR_WEST, map);
else
me.move(DIR_EAST, map);
dist++;
}
if (me.y != c.y) {
if (dirmask & MASK_DIR_NORTH)
me.move(DIR_NORTH, map);
else
me.move(DIR_SOUTH, map);
dist++;
}
}
return dist;
}
int MapCoords::distance(const MapCoords &c, const Map *map) const {
int dist = movementDistance(c, map);
if (dist <= 0)
return dist;
// Calculate how many fewer movements there would have been
dist -= abs(x - c.x) < abs(y - c.y) ? abs(x - c.x) : abs(y - c.y);
return dist;
}
/*-------------------------------------------------------------------*/
Map::Map() : _id(0), _type(WORLD), _width(0), _height(0), _levels(1),
_chunkWidth(0), _chunkHeight(0), _offset(0), _flags(0),
_borderBehavior(BORDER_WRAP), _music(Music::NONE),
_tileSet(nullptr), _tileMap(nullptr), _blank(0) {
_annotations = new AnnotationMgr();
}
Map::~Map() {
for (auto *i : _portals)
delete i;
delete _annotations;
}
Common::String Map::getName() {
return _baseSource._fname;
}
Object *Map::objectAt(const Coords &coords) {
// FIXME: return a list instead of one object
ObjectDeque::const_iterator i;
Object *objAt = nullptr;
for (i = _objects.begin(); i != _objects.end(); i++) {
Object *obj = *i;
if (obj->getCoords() == coords) {
// Get the most visible object
if (objAt && (objAt->getType() == Object::UNKNOWN) && (obj->getType() != Object::UNKNOWN))
objAt = obj;
// Give priority to objects that have the focus
else if (objAt && (!objAt->hasFocus()) && (obj->hasFocus()))
objAt = obj;
else if (!objAt)
objAt = obj;
}
}
return objAt;
}
const Portal *Map::portalAt(const Coords &coords, int actionFlags) {
PortalList::const_iterator i;
for (i = _portals.begin(); i != _portals.end(); i++) {
if (((*i)->_coords == coords) &&
((*i)->_triggerAction & actionFlags))
return *i;
}
return nullptr;
}
MapTile *Map::getTileFromData(const Coords &coords) {
if (MAP_IS_OOB(this, coords))
return &_blank;
int index = coords.x + (coords.y * _width) + (_width * _height * coords.z);
return &_data[index];
}
MapTile *Map::tileAt(const Coords &coords, int withObjects) {
// FIXME: this should return a list of tiles, with the most visible at the front
MapTile *tile;
Common::List<Annotation *> a = _annotations->ptrsToAllAt(coords);
Common::List<Annotation *>::iterator i;
Object *obj = objectAt(coords);
tile = getTileFromData(coords);
// FIXME: this only returns the first valid annotation it can find
if (a.size() > 0) {
for (i = a.begin(); i != a.end(); i++) {
if (!(*i)->isVisualOnly())
return &(*i)->getTile();
}
}
if ((withObjects == WITH_OBJECTS) && obj)
tile = &obj->getTile();
else if ((withObjects == WITH_GROUND_OBJECTS) &&
obj &&
obj->getTile().getTileType()->isWalkable())
tile = &obj->getTile();
return tile;
}
const Tile *Map::tileTypeAt(const Coords &coords, int withObjects) {
MapTile *tile = tileAt(coords, withObjects);
return tile->getTileType();
}
bool Map::isWorldMap() {
return _type == WORLD;
}
bool Map::isEnclosed(const Coords &party) {
uint x, y;
int *path_data;
if (_borderBehavior != BORDER_WRAP)
return true;
path_data = new int[_width * _height];
memset(path_data, -1, sizeof(int) * _width * _height);
// Determine what's walkable (1), and what's border-walkable (2)
findWalkability(party, path_data);
// Find two connecting pathways where the avatar can reach both without wrapping
for (x = 0; x < _width; x++) {
int index = x;
if (path_data[index] == 2 && path_data[index + ((_height - 1)*_width)] == 2)
return false;
}
for (y = 0; y < _width; y++) {
int index = (y * _width);
if (path_data[index] == 2 && path_data[index + _width - 1] == 2)
return false;
}
return true;
}
void Map::findWalkability(Coords coords, int *path_data) {
const Tile *mt = tileTypeAt(coords, WITHOUT_OBJECTS);
int index = coords.x + (coords.y * _width);
if (mt->isWalkable()) {
bool isBorderTile = (coords.x == 0) || (coords.x == signed(_width - 1)) || (coords.y == 0) || (coords.y == signed(_height - 1));
path_data[index] = isBorderTile ? 2 : 1;
if ((coords.x > 0) && path_data[coords.x - 1 + (coords.y * _width)] < 0)
findWalkability(Coords(coords.x - 1, coords.y, coords.z), path_data);
if ((coords.x < signed(_width - 1)) && path_data[coords.x + 1 + (coords.y * _width)] < 0)
findWalkability(Coords(coords.x + 1, coords.y, coords.z), path_data);
if ((coords.y > 0) && path_data[coords.x + ((coords.y - 1) * _width)] < 0)
findWalkability(Coords(coords.x, coords.y - 1, coords.z), path_data);
if ((coords.y < signed(_height - 1)) && path_data[coords.x + ((coords.y + 1) * _width)] < 0)
findWalkability(Coords(coords.x, coords.y + 1, coords.z), path_data);
} else {
path_data[index] = 0;
}
}
Creature *Map::addCreature(const Creature *creature, Coords coords) {
Creature *m = new Creature();
// Make a copy of the creature before placing it
*m = *creature;
m->setInitialHp();
m->setStatus(STAT_GOOD);
m->setCoords(coords);
m->setMap(this);
// initialize the creature before placing it
if (m->wanders())
m->setMovementBehavior(MOVEMENT_WANDER);
else if (m->isStationary())
m->setMovementBehavior(MOVEMENT_FIXED);
else m->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
// Hide camouflaged creatures from view during combat
if (m->camouflages() && (_type == COMBAT))
m->setVisible(false);
// place the creature on the map
_objects.push_back(m);
return m;
}
Object *Map::addObject(Object *obj, Coords coords) {
_objects.push_front(obj);
return obj;
}
Object *Map::addObject(MapTile tile, MapTile prevtile, Coords coords) {
Object *obj = new Object();
obj->setTile(tile);
obj->setPrevTile(prevtile);
obj->setCoords(coords);
obj->setPrevCoords(coords);
obj->setMap(this);
_objects.push_front(obj);
return obj;
}
void Map::removeObject(const Object *rem, bool deleteObject) {
ObjectDeque::iterator i;
for (i = _objects.begin(); i != _objects.end(); i++) {
if (*i == rem) {
// Party members persist through different maps, so don't delete them!
if (!isPartyMember(*i) && deleteObject)
delete(*i);
_objects.erase(i);
return;
}
}
}
ObjectDeque::iterator Map::removeObject(ObjectDeque::iterator rem, bool deleteObject) {
// Party members persist through different maps, so don't delete them!
if (!isPartyMember(*rem) && deleteObject)
delete(*rem);
return _objects.erase(rem);
}
Creature *Map::moveObjects(MapCoords avatar) {
Creature *attacker = nullptr;
for (auto *object : _objects) {
Creature *m = dynamic_cast<Creature *>(object);
if (m) {
/* check if the object is an attacking creature and not
just a normal, docile person in town or an inanimate object */
if ((m->getType() == Object::PERSON && m->getMovementBehavior() == MOVEMENT_ATTACK_AVATAR) ||
(m->getType() == Object::CREATURE && m->willAttack())) {
MapCoords o_coords = m->getCoords();
// Don't move objects that aren't on the same level as us
if (o_coords.z != avatar.z)
continue;
if (o_coords.movementDistance(avatar, this) <= 1) {
attacker = m;
continue;
}
}
// Before moving, Enact any special effects of the creature (such as storms eating objects, whirlpools teleporting, etc.)
m->specialEffect();
// Perform any special actions (such as pirate ships firing cannons, sea serpents' fireblast attect, etc.)
if (!m->specialAction()) {
if (moveObject(this, m, avatar)) {
m->animateMovement();
// After moving, Enact any special effects of the creature (such as storms eating objects, whirlpools teleporting, etc.)
m->specialEffect();
}
}
}
}
return attacker;
}
void Map::resetObjectAnimations() {
ObjectDeque::iterator i;
for (i = _objects.begin(); i != _objects.end(); i++) {
Object *obj = *i;
if (obj->getType() == Object::CREATURE)
obj->setPrevTile(creatureMgr->getByTile(obj->getTile())->getTile());
}
}
void Map::clearObjects() {
_objects.clear();
}
int Map::getNumberOfCreatures() {
ObjectDeque::const_iterator i;
int n = 0;
for (i = _objects.begin(); i != _objects.end(); i++) {
Object *obj = *i;
if (obj->getType() == Object::CREATURE)
n++;
}
return n;
}
int Map::getValidMoves(MapCoords from, MapTile transport) {
int retval;
Direction d;
Object *obj;
const Creature *m, *to_m;
int ontoAvatar, ontoCreature;
MapCoords coords = from;
// Get the creature object, if it exists (the one that's moving)
m = creatureMgr->getByTile(transport);
bool isAvatar = (g_context->_location->_coords == coords);
if (m && m->canMoveOntoPlayer())
isAvatar = false;
retval = 0;
for (d = DIR_WEST; d <= DIR_SOUTH; d = (Direction)(d + 1)) {
coords = from;
ontoAvatar = 0;
ontoCreature = 0;
// Move the coordinates in the current direction and test it
coords.move(d, this);
// You can always walk off the edge of the map
if (MAP_IS_OOB(this, coords)) {
retval = DIR_ADD_TO_MASK(d, retval);
continue;
}
obj = objectAt(coords);
// See if it's trying to move onto the avatar
if ((_flags & SHOW_AVATAR) && (coords == g_context->_location->_coords))
ontoAvatar = 1;
// See if it's trying to move onto a person or creature
else if (obj && (obj->getType() != Object::UNKNOWN))
ontoCreature = 1;
// Get the destination tile
MapTile tile;
if (ontoAvatar)
tile = g_context->_party->getTransport();
else if (ontoCreature)
tile = obj->getTile();
else
tile = *tileAt(coords, WITH_OBJECTS);
MapTile prev_tile = *tileAt(from, WITHOUT_OBJECTS);
// Get the other creature object, if it exists (the one that's being moved onto)
to_m = dynamic_cast<Creature *>(obj);
// Move on if unable to move onto the avatar or another creature
if (m && !isAvatar) { // some creatures/persons have the same tile as the avatar, so we have to adjust
// If moving onto the avatar, the creature must be able to move onto the player
// If moving onto another creature, it must be able to move onto other creatures,
// and the creature must be able to have others move onto it. If either of
// these conditions are not met, the creature cannot move onto another.
if ((ontoAvatar && m->canMoveOntoPlayer()) || (ontoCreature && m->canMoveOntoCreatures()))
tile = *tileAt(coords, WITHOUT_OBJECTS); //Ignore all objects, and just consider terrain
if ((ontoAvatar && !m->canMoveOntoPlayer())
|| (
ontoCreature &&
(
(!m->canMoveOntoCreatures() && !to_m->canMoveOntoCreatures())
|| (m->isForceOfNature() && to_m->isForceOfNature())
)
)
)
continue;
}
// Avatar movement
if (isAvatar) {
// if the transport is a ship, check sailable
if (transport.getTileType()->isShip() && tile.getTileType()->isSailable())
retval = DIR_ADD_TO_MASK(d, retval);
// if it is a balloon, check flyable
else if (transport.getTileType()->isBalloon() && tile.getTileType()->isFlyable())
retval = DIR_ADD_TO_MASK(d, retval);
// avatar or horseback: check walkable
else if (transport == _tileSet->getByName("avatar")->getId() || transport.getTileType()->isHorse()) {
if (tile.getTileType()->canWalkOn(d) &&
(!transport.getTileType()->isHorse() || tile.getTileType()->isCreatureWalkable()) &&
prev_tile.getTileType()->canWalkOff(d))
retval = DIR_ADD_TO_MASK(d, retval);
}
// else if (ontoCreature && to_m->canMoveOntoPlayer()) {
// retval = DIR_ADD_TO_MASK(d, retval);
// }
}
// Creature movement
else if (m) {
// Flying creatures
if (tile.getTileType()->isFlyable() && m->flies()) {
// FIXME: flying creatures behave differently on the world map?
if (isWorldMap())
retval = DIR_ADD_TO_MASK(d, retval);
else if (tile.getTileType()->isWalkable() ||
tile.getTileType()->isSwimable() ||
tile.getTileType()->isSailable())
retval = DIR_ADD_TO_MASK(d, retval);
}
// Swimming creatures and sailing creatures
else if (tile.getTileType()->isSwimable() ||
tile.getTileType()->isSailable() ||
tile.getTileType()->isShip()) {
if (m->swims() && tile.getTileType()->isSwimable())
retval = DIR_ADD_TO_MASK(d, retval);
if (m->sails() && tile.getTileType()->isSailable())
retval = DIR_ADD_TO_MASK(d, retval);
if (m->canMoveOntoPlayer() && tile.getTileType()->isShip())
retval = DIR_ADD_TO_MASK(d, retval);
}
// Ghosts and other incorporeal creatures
else if (m->isIncorporeal()) {
// can move anywhere but onto water, unless of course the creature can swim
if (!(tile.getTileType()->isSwimable() ||
tile.getTileType()->isSailable()))
retval = DIR_ADD_TO_MASK(d, retval);
}
// Walking creatures
else if (m->walks()) {
if (tile.getTileType()->canWalkOn(d) &&
prev_tile.getTileType()->canWalkOff(d) &&
tile.getTileType()->isCreatureWalkable())
retval = DIR_ADD_TO_MASK(d, retval);
}
// Creatures that can move onto player
else if (ontoAvatar && m->canMoveOntoPlayer()) {
// Tile should be transport
if (tile.getTileType()->isShip() && m->swims())
retval = DIR_ADD_TO_MASK(d, retval);
}
}
}
return retval;
}
bool Map::move(Object *obj, Direction d) {
MapCoords new_coords = obj->getCoords();
if (new_coords.move(d) != obj->getCoords()) {
obj->setCoords(new_coords);
return true;
}
return false;
}
void Map::alertGuards() {
ObjectDeque::iterator i;
const Creature *m;
// Switch all the guards to attack mode
for (i = _objects.begin(); i != _objects.end(); i++) {
m = creatureMgr->getByTile((*i)->getTile());
if (m && (m->getId() == GUARD_ID || m->getId() == LORDBRITISH_ID))
(*i)->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
}
}
MapCoords Map::getLabel(const Common::String &name) const {
Common::HashMap<Common::String, MapCoords>::const_iterator i = _labels.find(name);
if (i == _labels.end())
return MapCoords::nowhere();
return i->_value;
}
bool Map::fillMonsterTable() {
ObjectDeque::iterator current;
Object *obj;
ObjectDeque monsters;
ObjectDeque other_creatures;
ObjectDeque inanimate_objects;
Object empty;
int nCreatures = 0;
int nObjects = 0;
for (int idx = 0; idx < MONSTERTABLE_SIZE; ++idx)
_monsterTable[idx].clear();
/**
* First, categorize all the objects we have
*/
for (current = _objects.begin(); current != _objects.end(); current++) {
obj = *current;
// Moving objects first
if ((obj->getType() == Object::CREATURE) && (obj->getMovementBehavior() != MOVEMENT_FIXED)) {
Creature *c = dynamic_cast<Creature *>(obj);
assert(c);
// Whirlpools and storms are separated from other moving objects
if (c->getId() == WHIRLPOOL_ID || c->getId() == STORM_ID)
monsters.push_back(obj);
else
other_creatures.push_back(obj);
} else inanimate_objects.push_back(obj);
}
/**
* Add other monsters to our whirlpools and storms
*/
while (other_creatures.size() && nCreatures < MONSTERTABLE_CREATURES_SIZE) {
monsters.push_back(other_creatures.front());
other_creatures.pop_front();
}
/**
* Add empty objects to our list to fill things up
*/
while (monsters.size() < MONSTERTABLE_CREATURES_SIZE)
monsters.push_back(&empty);
/**
* Finally, add inanimate objects
*/
while (inanimate_objects.size() && nObjects < MONSTERTABLE_OBJECTS_SIZE) {
monsters.push_back(inanimate_objects.front());
inanimate_objects.pop_front();
}
/**
* Fill in the blanks
*/
while (monsters.size() < MONSTERTABLE_SIZE)
monsters.push_back(&empty);
/**
* Fill in our monster table
*/
int i = 0;
TileMap *base = g_tileMaps->get("base");
for (auto *monster : monsters) {
Coords c = monster->getCoords(),
prevc = monster->getPrevCoords();
_monsterTable[i]._tile = base->untranslate(monster->getTile());
_monsterTable[i]._x = c.x;
_monsterTable[i]._y = c.y;
_monsterTable[i]._prevTile = base->untranslate(monster->getPrevTile());
_monsterTable[i]._prevX = prevc.x;
_monsterTable[i]._prevY = prevc.y;
i++;
}
return true;
}
MapTile Map::translateFromRawTileIndex(int raw) const {
assertMsg(_tileMap != nullptr, "tilemap hasn't been set");
return _tileMap->translate(raw);
}
uint Map::translateToRawTileIndex(MapTile &tile) const {
return _tileMap->untranslate(tile);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,305 @@
/* 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 ULTIMA4_MAP_MAP_H
#define ULTIMA4_MAP_MAP_H
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/sound/music.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
#define MAP_IS_OOB(mapptr, c) (((c).x) < 0 || ((c).x) >= (static_cast<int>((mapptr)->_width)) || ((c).y) < 0 || ((c).y) >= (static_cast<int>((mapptr)->_height)) || ((c).z) < 0 || ((c).z) >= (static_cast<int>((mapptr)->_levels)))
class AnnotationMgr;
class Map;
class Object;
class Person;
class Creature;
class TileMap;
class Tileset;
struct Portal;
struct _Dungeon;
typedef Std::vector<Portal *> PortalList;
typedef Common::List<int> CompressedChunkList;
typedef Std::vector<MapTile> MapData;
/* flags */
#define SHOW_AVATAR (1 << 0)
#define NO_LINE_OF_SIGHT (1 << 1)
#define FIRST_PERSON (1 << 2)
/* mapTileAt flags */
#define WITHOUT_OBJECTS 0
#define WITH_GROUND_OBJECTS 1
#define WITH_OBJECTS 2
/**
* MapCoords class
*/
class MapCoords : public Coords {
public:
MapCoords(int initx = 0, int inity = 0, int initz = 0) : Coords(initx, inity, initz) {}
MapCoords(const Coords &a) : Coords(a.x, a.y, a.z) {}
MapCoords &operator=(const Coords &a) {
x = a.x;
y = a.y;
z = a.z;
return *this;
}
bool operator==(const MapCoords &a) const;
bool operator!=(const MapCoords &a) const;
bool operator<(const MapCoords &a) const;
MapCoords &wrap(const class Map *map);
MapCoords &putInBounds(const class Map *map);
MapCoords &move(Direction d, const class Map *map = nullptr);
MapCoords &move(int dx, int dy, const class Map *map = nullptr);
/**
* Returns a mask of directions that indicate where one point is relative
* to another. For instance, if the object at (x, y) is
* northeast of (c.x, c.y), then this function returns
* (MASK_DIR(DIR_NORTH) | MASK_DIR(DIR_EAST))
* This function also takes into account map boundaries and adjusts
* itself accordingly. If the two coordinates are not on the same z-plane,
* then this function return DIR_NONE.
*/
int getRelativeDirection(const MapCoords &c, const class Map *map = nullptr) const;
/**
* Finds the appropriate direction to travel to get from one point to
* another. This algorithm will avoid getting trapped behind simple
* obstacles, but still fails with anything mildly complicated.
* This function also takes into account map boundaries and adjusts
* itself accordingly, provided the 'map' parameter is passed
*/
Direction pathTo(const MapCoords &c, int valid_dirs = MASK_DIR_ALL, bool towards = true, const class Map *map = nullptr) const;
/**
* Finds the appropriate direction to travel to move away from one point
*/
Direction pathAway(const MapCoords &c, int valid_dirs = MASK_DIR_ALL) const;
/**
* Finds the movement distance (not using diagonals) from point a to point b
* on a map, taking into account map boundaries and such. If the two coords
* are not on the same z-plane, then this function returns -1;
*/
int movementDistance(const MapCoords &c, const class Map *map = nullptr) const;
/**
* Finds the distance (using diagonals) from point a to point b on a map
* If the two coordinates are not on the same z-plane, then this function
* returns -1. This function also takes into account map boundaries.
*/
int distance(const MapCoords &c, const class Map *map = nullptr) const;
/**
* Returns true if the co-ordinates point to nowhere
*/
static MapCoords nowhere() {
return MapCoords(-1, -1, -1);
}
};
/**
* Map class
*/
class Map {
public:
enum Type {
WORLD,
CITY,
SHRINE,
COMBAT,
DUNGEON,
XML
};
enum BorderBehavior {
BORDER_WRAP,
BORDER_EXIT2PARENT,
BORDER_FIXED
};
class Source {
public:
Source() : _type(WORLD) {}
Source(const Common::String &f, Type t) : _fname(f), _type(t) {}
Common::String _fname;
Type _type;
};
Map();
virtual ~Map();
// Member functions
virtual Common::String getName();
/**
* Returns the object at the given (x,y,z) coords, if one exists.
* Otherwise, returns nullptr.
*/
Object *objectAt(const Coords &coords);
/**
* Returns the portal for the correspoding action(s) given.
* If there is no portal that corresponds to the actions flagged
* by 'actionFlags' at the given (x,y,z) coords, it returns nullptr.
*/
const Portal *portalAt(const Coords &coords, int actionFlags);
/**
* Returns the raw tile for the given (x,y,z) coords for the given map
*/
MapTile *getTileFromData(const Coords &coords);
/**
* Returns the current ground tile at the given point on a map. Visual-only
* annotations like moongates and attack icons are ignored. Any walkable tiles
* are taken into account (treasure chests, ships, balloon, etc.)
*/
MapTile *tileAt(const Coords &coords, int withObjects);
const Tile *tileTypeAt(const Coords &coords, int withObjects);
/**
* Returns true if the given map is the world map
*/
bool isWorldMap();
/**
* Returns true if the map is enclosed (to see if gem layouts should cut themselves off)
*/
bool isEnclosed(const Coords &party);
/**
* Adds a creature object to the given map
*/
Creature *addCreature(const class Creature *m, Coords coords);
Object *addObject(MapTile tile, MapTile prevTile, Coords coords);
/**
* Adds an object to the given map
*/
Object *addObject(Object *obj, Coords coords);
/**
* Removes an object from the map
*
* This function should only be used when not iterating through an
* ObjectDeque, as the iterator will be invalidated and the
* results will be unpredictable. Instead, use the function below.
*/
void removeObject(const class Object *rem, bool deleteObject = true);
ObjectDeque::iterator removeObject(ObjectDeque::iterator rem, bool deleteObject = true);
/**
* Removes all objects from the given map
*/
void clearObjects();
/**
* Moves all of the objects on the given map.
* Returns an attacking object if there is a creature attacking.
* Also performs special creature actions and creature effects.
*/
Creature *moveObjects(MapCoords avatar);
/**
* Resets object animations to a value that is acceptable for
* savegame compatibility with u4dos.
*/
void resetObjectAnimations();
/**
* Returns the number of creatures on the given map
*/
int getNumberOfCreatures();
/**
* Returns a mask of valid moves for the given transport on the given map
*/
int getValidMoves(MapCoords from, MapTile transport);
bool move(Object *obj, Direction d);
/**
* Alerts the guards that the avatar is doing something bad
*/
void alertGuards();
MapCoords getLabel(const Common::String &name) const;
// u4dos compatibility
bool fillMonsterTable();
MapTile translateFromRawTileIndex(int c) const;
uint translateToRawTileIndex(MapTile &tile) const;
public:
MapId _id;
Common::Path _fname;
Type _type;
uint _width, _height, _levels;
uint _chunkWidth, _chunkHeight;
uint _offset;
Source _baseSource;
Common::List<Source> _extraSources;
CompressedChunkList _compressedChunks;
BorderBehavior _borderBehavior;
PortalList _portals;
AnnotationMgr *_annotations;
int _flags;
Music::Type _music;
MapData _data;
ObjectDeque _objects;
Common::HashMap<Common::String, MapCoords> _labels;
Tileset *_tileSet;
TileMap *_tileMap;
MapTile _blank;
// u4dos compatibility
SaveGameMonsterRecord _monsterTable[MONSTERTABLE_SIZE];
private:
// disallow map copying: all maps should be created and accessed
// through the MapMgr
Map(const Map &map);
Map &operator=(const Map &map);
void findWalkability(Coords coords, int *path_data);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,51 @@
/* 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/map_tile.h"
namespace Ultima {
namespace Ultima4 {
Direction MapTile::getDirection() const {
return getTileType()->directionForFrame(_frame);
}
bool MapTile::setDirection(Direction d) {
// if we're already pointing the right direction, do nothing!
if (getDirection() == d)
return false;
const Tile *type = getTileType();
int new_frame = type->frameForDirection(d);
if (new_frame != -1) {
_frame = new_frame;
return true;
}
return false;
}
const Tile *MapTile::getTileType() const {
return g_tileSets->findTileById(_id);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,84 @@
/* 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 ULTIMA4_MAP_MAP_TILE_H
#define ULTIMA4_MAP_MAP_TILE_H
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/tile.h"
namespace Ultima {
namespace Ultima4 {
/**
* A MapTile is a specific instance of a Tile.
*/
class MapTile {
public:
MapTile() : _id(0), _frame(0), _freezeAnimation(false) {
}
MapTile(const TileId &i, byte f = 0) : _id(i), _frame(f), _freezeAnimation(false) {
}
TileId getId() const {
return _id;
}
byte getFrame() const {
return _frame;
}
bool getFreezeAnimation() const {
return _freezeAnimation;
}
bool operator==(const MapTile &m) const {
return _id == m._id;
}
bool operator==(const TileId &i) const {
return _id == i;
}
bool operator!=(const MapTile &m) const {
return _id != m._id;
}
bool operator!=(const TileId &i) const {
return _id != i;
}
bool operator<(const MapTile &m) const {
return _id < m._id; // for Std::less
}
/**
* MapTile Class Implementation
*/
Direction getDirection() const;
bool setDirection(Direction d);
const Tile *getTileType() const;
// Properties
TileId _id;
byte _frame;
bool _freezeAnimation;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,461 @@
/* 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/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<City *>(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<PersonNpcType>((*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<Dungeon *>(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<CombatMap *>(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<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

@@ -0,0 +1,152 @@
/* 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 ULTIMA4_MAP_MAPLOADER_H
#define ULTIMA4_MAP_MAPLOADER_H
#include "ultima/ultima4/map/map.h"
#include "ultima/shared/std/containers.h"
namespace Common {
class SeekableReadStream;
}
namespace Ultima {
namespace Ultima4 {
class U4FILE;
class Dungeon;
struct MapType_Hash {
uint operator()(const Map::Type &v) const {
return (uint)v;
}
};
/**
* The generic map loader interface. Map loaders should override the
* load method to load a map from the meta data already initialized in
* the map object passed in. They must also register themselves with
* registerLoader for one or more Map::Types.
*
* @todo
* <ul>
* <li>
* Instead of loading dungeon room data into a u4dos-style structure and converting it to
* an xu4 Map when it's needed, convert it to an xu4 Map immediately upon loading it.
* </li>
* </ul>
*/
class MapLoader {
public:
virtual ~MapLoader() {}
virtual bool load(Map *map) = 0;
protected:
/**
* Registers a loader for the given map type.
*/
static MapLoader *registerLoader(MapLoader *loader, Map::Type type);
/**
* Loads raw data from the given file.
*/
static bool loadData(Map *map, Common::SeekableReadStream &f);
static bool isChunkCompressed(Map *map, int chunk);
private:
static Common::HashMap<Map::Type, MapLoader *, MapType_Hash> *loaderMap;
};
class CityMapLoader : public MapLoader {
public:
/**
* Load city data from 'ult' and 'tlk' files.
*/
virtual bool load(Map *map);
};
class ConMapLoader : public MapLoader {
public:
/**
* Loads a combat map from the 'con' file
*/
bool load(Map *map) override;
};
class DngMapLoader : public MapLoader {
public:
/**
* Loads a dungeon map from the 'dng' file
*/
bool load(Map *map) override;
private:
/**
* Loads a dungeon room into map->dungeon->room
*/
void initDungeonRoom(Dungeon *dng, int room);
};
class WorldMapLoader : public MapLoader {
public:
/**
* Loads the world map data in from the 'world' file.
*/
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 Common::HashMap<Map::Type, MapLoader *, MapType_Hash> {
public:
/**
* Constructor
*/
MapLoaders();
/**
* Destructor
*/
~MapLoaders();
/**
* Gets a map loader for the given map type.
*/
MapLoader *getLoader(Map::Type type);
};
extern MapLoaders *g_mapLoaders;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,354 @@
/* 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/ultima4.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/controllers/combat_controller.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/item.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/portal.h"
#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/core/config.h"
namespace Ultima {
namespace Ultima4 {
MapMgr *MapMgr::_instance = nullptr;
MapMgr *MapMgr::getInstance() {
if (_instance == nullptr)
_instance = new MapMgr();
return _instance;
}
void MapMgr::destroy() {
if (_instance != nullptr) {
delete _instance;
_instance = nullptr;
}
}
MapMgr::MapMgr() {
const Config *config = Config::getInstance();
Map *map;
Std::vector<ConfigElement> maps = config->getElement("maps").getChildren();
for (const auto &i : maps) {
map = initMapFromConf(i);
// Map actually gets loaded later, when it's needed
registerMap(map);
}
}
MapMgr::~MapMgr() {
for (auto *i : _mapList)
delete i;
}
void MapMgr::unloadMap(MapId id) {
delete _mapList[id];
const Config *config = Config::getInstance();
Std::vector<ConfigElement> maps = config->getElement("maps").getChildren();
for (const auto &i : maps) {
if (id == static_cast<MapId>(i.getInt("id"))) {
Map *map = initMapFromConf(i);
_mapList[id] = map;
break;
}
}
}
Map *MapMgr::initMap(Map::Type type) {
Map *map;
switch (type) {
case Map::WORLD:
map = new Map();
break;
case Map::COMBAT:
map = new CombatMap();
break;
case Map::SHRINE:
map = new Shrine();
break;
case Map::DUNGEON:
map = new Dungeon();
break;
case Map::CITY:
map = new City();
break;
case Map::XML:
map = new XMLMap();
break;
default:
error("Error: invalid map type used");
break;
}
return map;
}
Map *MapMgr::get(MapId id) {
// if the map hasn't been loaded yet, load it!
if (!_mapList[id]->_data.size()) {
MapLoader *loader = g_mapLoaders->getLoader(_mapList[id]->_type);
if (loader == nullptr)
error("can't load map of type \"%d\"", _mapList[id]->_type);
loader->load(_mapList[id]);
}
return _mapList[id];
}
void MapMgr::registerMap(Map *map) {
if (_mapList.size() <= map->_id)
_mapList.resize(map->_id + 1, nullptr);
if (_mapList[map->_id] != nullptr)
error("Error: A map with id '%d' already exists", map->_id);
_mapList[map->_id] = map;
}
Map *MapMgr::initMapFromConf(const ConfigElement &mapConf) {
Map *map;
static const char *const mapTypeEnumStrings[] = { "world", "city", "shrine", "combat", "dungeon", "xml", nullptr };
static const char *const borderBehaviorEnumStrings[] = { "wrap", "exit", "fixed", nullptr };
map = initMap(static_cast<Map::Type>(mapConf.getEnum("type", mapTypeEnumStrings)));
if (!map)
return nullptr;
map->_id = static_cast<MapId>(mapConf.getInt("id"));
map->_type = static_cast<Map::Type>(mapConf.getEnum("type", mapTypeEnumStrings));
map->_fname = mapConf.getString("fname");
map->_width = mapConf.getInt("width");
map->_height = mapConf.getInt("height");
map->_levels = mapConf.getInt("levels");
map->_chunkWidth = mapConf.getInt("chunkwidth");
map->_chunkHeight = mapConf.getInt("chunkheight");
map->_offset = mapConf.getInt("offset");
map->_borderBehavior = static_cast<Map::BorderBehavior>(mapConf.getEnum("borderbehavior", borderBehaviorEnumStrings));
if (isCombatMap(map)) {
CombatMap *cm = dynamic_cast<CombatMap *>(map);
assert(cm);
cm->setContextual(mapConf.getBool("contextual"));
}
if (mapConf.getBool("showavatar"))
map->_flags |= SHOW_AVATAR;
if (mapConf.getBool("nolineofsight"))
map->_flags |= NO_LINE_OF_SIGHT;
if (mapConf.getBool("firstperson"))
map->_flags |= FIRST_PERSON;
map->_music = static_cast<Music::Type>(mapConf.getInt("music"));
map->_tileSet = g_tileSets->get(mapConf.getString("tileset"));
map->_tileMap = g_tileMaps->get(mapConf.getString("tilemap"));
Std::vector<ConfigElement> children = mapConf.getChildren();
for (const auto &i : children) {
if (i.getName() == "city") {
City *city = dynamic_cast<City *>(map);
assert(city);
initCityFromConf(i, city);
} else if (i.getName() == "shrine") {
Shrine *shrine = dynamic_cast<Shrine *>(map);
assert(shrine);
initShrineFromConf(i, shrine);
} else if (i.getName() == "dungeon") {
Dungeon *dungeon = dynamic_cast<Dungeon *>(map);
assert(dungeon);
initDungeonFromConf(i, dungeon);
} else if (i.getName() == "portal")
map->_portals.push_back(initPortalFromConf(i));
else if (i.getName() == "moongate")
createMoongateFromConf(i);
else if (i.getName() == "compressedchunk")
map->_compressedChunks.push_back(initCompressedChunkFromConf(i));
else if (i.getName() == "label")
map->_labels[i.getString("name")] = MapCoords(i.getInt("x"), i.getInt("y"), i.getInt("z", 0));
else if (i.getName() == "tiles" && map->_type == Map::XML)
static_cast<XMLMap *>(map)->_tilesText = i.getNode()->firstChild()->text();
}
return map;
}
void MapMgr::initCityFromConf(const ConfigElement &cityConf, City *city) {
city->_name = cityConf.getString("name");
city->_type = cityConf.getString("type");
city->_tlkFname = cityConf.getString("tlk_fname");
Std::vector<ConfigElement> children = cityConf.getChildren();
for (const auto &i : children) {
if (i.getName() == "personrole")
city->_personRoles.push_back(initPersonRoleFromConf(i));
}
}
PersonRole *MapMgr::initPersonRoleFromConf(const ConfigElement &personRoleConf) {
PersonRole *personrole;
static const char *const roleEnumStrings[] = { "companion", "weaponsvendor", "armorvendor", "foodvendor", "tavernkeeper",
"reagentsvendor", "healer", "innkeeper", "guildvendor", "horsevendor",
"lordbritish", "hawkwind", nullptr
};
personrole = new PersonRole();
personrole->_role = personRoleConf.getEnum("role", roleEnumStrings) + NPC_TALKER_COMPANION;
personrole->_id = personRoleConf.getInt("id");
return personrole;
}
Portal *MapMgr::initPortalFromConf(const ConfigElement &portalConf) {
Portal *portal;
portal = new Portal();
portal->_portalConditionsMet = nullptr;
portal->_retroActiveDest = nullptr;
portal->_coords = MapCoords(
portalConf.getInt("x"),
portalConf.getInt("y"),
portalConf.getInt("z", 0));
portal->_destid = static_cast<MapId>(portalConf.getInt("destmapid"));
portal->_start.x = static_cast<unsigned short>(portalConf.getInt("startx"));
portal->_start.y = static_cast<unsigned short>(portalConf.getInt("starty"));
portal->_start.z = static_cast<unsigned short>(portalConf.getInt("startlevel", 0));
Common::String prop = portalConf.getString("action");
if (prop == "none")
portal->_triggerAction = ACTION_NONE;
else if (prop == "enter")
portal->_triggerAction = ACTION_ENTER;
else if (prop == "klimb")
portal->_triggerAction = ACTION_KLIMB;
else if (prop == "descend")
portal->_triggerAction = ACTION_DESCEND;
else if (prop == "exit_north")
portal->_triggerAction = ACTION_EXIT_NORTH;
else if (prop == "exit_east")
portal->_triggerAction = ACTION_EXIT_EAST;
else if (prop == "exit_south")
portal->_triggerAction = ACTION_EXIT_SOUTH;
else if (prop == "exit_west")
portal->_triggerAction = ACTION_EXIT_WEST;
else
error("unknown trigger_action: %s", prop.c_str());
prop = portalConf.getString("condition");
if (!prop.empty()) {
if (prop == "shrine")
portal->_portalConditionsMet = &shrineCanEnter;
else if (prop == "abyss")
portal->_portalConditionsMet = &Items::isAbyssOpened;
else
error("unknown portalConditionsMet: %s", prop.c_str());
}
portal->_saveLocation = portalConf.getBool("savelocation");
portal->_message = portalConf.getString("message");
prop = portalConf.getString("transport");
if (prop == "foot")
portal->_portalTransportRequisites = TRANSPORT_FOOT;
else if (prop == "footorhorse")
portal->_portalTransportRequisites = TRANSPORT_FOOT_OR_HORSE;
else
error("unknown transport: %s", prop.c_str());
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 (const auto &i : children) {
if (i.getName() == "retroActiveDest") {
portal->_retroActiveDest = new PortalDestination();
portal->_retroActiveDest->_coords = MapCoords(
i.getInt("x"),
i.getInt("y"),
i.getInt("z", 0));
portal->_retroActiveDest->_mapid = static_cast<MapId>(i.getInt("mapid"));
}
}
return portal;
}
void MapMgr::initShrineFromConf(const ConfigElement &shrineConf, Shrine *shrine) {
static const char *const virtues[] = {"Honesty", "Compassion", "Valor", "Justice", "Sacrifice", "Honor", "Spirituality", "Humility", nullptr};
shrine->setVirtue(static_cast<Virtue>(shrineConf.getEnum("virtue", virtues)));
shrine->setMantra(shrineConf.getString("mantra"));
}
void MapMgr::initDungeonFromConf(const ConfigElement &dungeonConf, Dungeon *dungeon) {
dungeon->_nRooms = dungeonConf.getInt("rooms");
dungeon->_rooms = nullptr;
dungeon->_roomMaps = nullptr;
dungeon->_name = dungeonConf.getString("name");
}
void MapMgr::createMoongateFromConf(const ConfigElement &moongateConf) {
int phase = moongateConf.getInt("phase");
Coords coords(moongateConf.getInt("x"), moongateConf.getInt("y"));
g_moongates->add(phase, coords);
}
int MapMgr::initCompressedChunkFromConf(const ConfigElement &compressedChunkConf) {
return compressedChunkConf.getInt("index");
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,140 @@
/* 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 ULTIMA4_MAP_MAPMGR_H
#define ULTIMA4_MAP_MAPMGR_H
#include "ultima/ultima4/map/map.h"
namespace Ultima {
namespace Ultima4 {
class City;
class ConfigElement;
class Debug;
class Dungeon;
struct PersonRole;
struct Portal;
class Shrine;
/*
* The map manager is responsible for loading and keeping track of the
* various maps.
*/
#define MAP_NONE 255
#define MAP_WORLD 0
#define MAP_CASTLE_OF_LORD_BRITISH 1
#define MAP_LYCAEUM 2
#define MAP_EMPATH_ABBEY 3
#define MAP_SERPENTS_HOLD 4
#define MAP_MOONGLOW 5
#define MAP_BRITAIN 6
#define MAP_JHELOM 7
#define MAP_YEW 8
#define MAP_MINOC 9
#define MAP_TRINSIC 10
#define MAP_SKARABRAE 11
#define MAP_MAGINCIA 12
#define MAP_PAWS 13
#define MAP_BUCCANEERS_DEN 14
#define MAP_VESPER 15
#define MAP_COVE 16
#define MAP_DECEIT 17
#define MAP_DESPISE 18
#define MAP_DESTARD 19
#define MAP_WRONG 20
#define MAP_COVETOUS 21
#define MAP_SHAME 22
#define MAP_HYTHLOTH 23
#define MAP_ABYSS 24
#define MAP_SHRINE_HONESTY 25
#define MAP_SHRINE_COMPASSION 26
#define MAP_SHRINE_VALOR 27
#define MAP_SHRINE_JUSTICE 28
#define MAP_SHRINE_SACRIFICE 29
#define MAP_SHRINE_HONOR 30
#define MAP_SHRINE_SPIRITUALITY 31
#define MAP_SHRINE_HUMILITY 32
#define MAP_BRICK_CON 33
#define MAP_BRIDGE_CON 34
#define MAP_BRUSH_CON 35
#define MAP_CAMP_CON 36
#define MAP_DNG0_CON 37
#define MAP_DNG1_CON 38
#define MAP_DNG2_CON 39
#define MAP_DNG3_CON 40
#define MAP_DNG4_CON 41
#define MAP_DNG5_CON 42
#define MAP_DNG6_CON 43
#define MAP_DUNGEON_CON 44
#define MAP_FOREST_CON 45
#define MAP_GRASS_CON 46
#define MAP_HILL_CON 47
#define MAP_INN_CON 48
#define MAP_MARSH_CON 49
#define MAP_SHIPSEA_CON 50
#define MAP_SHIPSHIP_CON 51
#define MAP_SHIPSHOR_CON 52
#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.
*/
class MapMgr {
public:
static MapMgr *getInstance();
static void destroy();
Map *get(MapId id);
Map *initMap(Map::Type type);
void unloadMap(MapId id);
private:
MapMgr();
~MapMgr();
void registerMap(Map *map);
Map *initMapFromConf(const ConfigElement &mapConf);
void initCityFromConf(const ConfigElement &cityConf, City *city);
PersonRole *initPersonRoleFromConf(const ConfigElement &cityConf);
Portal *initPortalFromConf(const ConfigElement &portalConf);
void initShrineFromConf(const ConfigElement &shrineConf, Shrine *shrine);
void initDungeonFromConf(const ConfigElement &dungeonConf, Dungeon *dungeon);
void initDungeonRoom(Dungeon *dng, int room);
void createMoongateFromConf(const ConfigElement &moongateConf);
int initCompressedChunkFromConf(const ConfigElement &compressedChunkConf);
static MapMgr *_instance;
Std::vector<Map *> _mapList;
};
#define mapMgr (MapMgr::getInstance())
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,443 @@
/* 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/movement.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/core/debugger.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
void moveAvatar(MoveEvent &event) {
MapCoords newCoords;
int slowed = 0;
SlowedType slowedType = SLOWED_BY_TILE;
/* Check to see if we're on the balloon */
if (g_context->_transportContext == TRANSPORT_BALLOON && event._userEvent) {
event._result = (MoveResult)(MOVE_DRIFT_ONLY | MOVE_END_TURN);
return;
}
if (g_context->_transportContext == TRANSPORT_SHIP)
slowedType = SLOWED_BY_WIND;
else if (g_context->_transportContext == TRANSPORT_BALLOON)
slowedType = SLOWED_BY_NOTHING;
// If you're on ship, you must turn first!
if (g_context->_transportContext == TRANSPORT_SHIP) {
if (g_context->_party->getDirection() != event._dir) {
g_context->_party->setDirection(event._dir);
event._result = (MoveResult)(MOVE_TURNED | MOVE_END_TURN);
return;
}
}
// Change direction of horse, if necessary
if (g_context->_transportContext == TRANSPORT_HORSE) {
if ((event._dir == DIR_WEST || event._dir == DIR_EAST) && (g_context->_party->getDirection() != event._dir))
g_context->_party->setDirection(event._dir);
}
// Figure out our new location we're trying to move to
newCoords = g_context->_location->_coords;
newCoords.move(event._dir, g_context->_location->_map);
// See if we moved off the map
if (MAP_IS_OOB(g_context->_location->_map, newCoords)) {
event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_EXIT_TO_PARENT | MOVE_SUCCEEDED);
return;
}
if (!g_debugger->_collisionOverride && !g_context->_party->isFlying()) {
int movementMask = g_context->_location->_map->getValidMoves(g_context->_location->_coords, g_context->_party->getTransport());
// See if movement was blocked
if (!DIR_IN_MASK(event._dir, movementMask)) {
event._result = (MoveResult)(MOVE_BLOCKED | MOVE_END_TURN);
return;
}
// Are we slowed by terrain or by wind direction?
switch (slowedType) {
case SLOWED_BY_TILE:
// TODO: CHEST: Make a user option to not make chests always fast to
// travel over
slowed = slowedByTile(g_context->_location->_map->tileTypeAt(newCoords, WITH_OBJECTS));
break;
case SLOWED_BY_WIND:
slowed = slowedByWind(event._dir);
break;
case SLOWED_BY_NOTHING:
default:
break;
}
if (slowed) {
event._result = (MoveResult)(MOVE_SLOWED | MOVE_END_TURN);
return;
}
}
// Move succeeded
g_context->_location->_coords = newCoords;
/* if the avatar moved onto a creature (whirlpool, twister), then do the creature's special effect (this current code does double damage according to changeset 2753.
Object *destObj = c->location->map->objectAt(newCoords);
if (destObj && destObj->getType() == Object::CREATURE) {
Creature *m = dynamic_cast<Creature*>(destObj);
//m->specialEffect();
}
*/
event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
}
void moveAvatarInDungeon(MoveEvent &event) {
MapCoords newCoords;
Direction realDir = dirNormalize((Direction)g_ultima->_saveGame->_orientation, event._dir); /* get our real direction */
int advancing = realDir == g_ultima->_saveGame->_orientation,
retreating = realDir == dirReverse((Direction)g_ultima->_saveGame->_orientation);
MapTile *tile;
// We're not in a dungeon, failed!
assertMsg(g_context->_location->_context & CTX_DUNGEON, "moveAvatarInDungeon() called outside of dungeon, failed!");
// You must turn first!
if (!advancing && !retreating) {
g_ultima->_saveGame->_orientation = realDir;
event._result = MOVE_TURNED;
return;
}
// Figure out our new location
newCoords = g_context->_location->_coords;
newCoords.move(realDir, g_context->_location->_map);
tile = g_context->_location->_map->tileAt(newCoords, WITH_OBJECTS);
// See if we moved off the map (really, this should never happen in a dungeon)
if (MAP_IS_OOB(g_context->_location->_map, newCoords)) {
event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_EXIT_TO_PARENT | MOVE_SUCCEEDED);
return;
}
if (!g_debugger->_collisionOverride) {
int movementMask = g_context->_location->_map->getValidMoves(g_context->_location->_coords, g_context->_party->getTransport());
if (advancing && !tile->getTileType()->canWalkOn(DIR_ADVANCE))
movementMask = DIR_REMOVE_FROM_MASK(realDir, movementMask);
else if (retreating && !tile->getTileType()->canWalkOn(DIR_RETREAT))
movementMask = DIR_REMOVE_FROM_MASK(realDir, movementMask);
if (!DIR_IN_MASK(realDir, movementMask)) {
event._result = (MoveResult)(MOVE_BLOCKED | MOVE_END_TURN);
return;
}
}
// Move succeeded
g_context->_location->_coords = newCoords;
event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
}
int moveObject(Map *map, Creature *obj, MapCoords avatar) {
int dirmask = DIR_NONE;
Direction dir;
MapCoords new_coords = obj->getCoords();
int slowed = 0;
// Determine a direction depending on the object's movement behavior
dir = DIR_NONE;
switch (obj->getMovementBehavior()) {
case MOVEMENT_FIXED:
break;
case MOVEMENT_WANDER:
/* World map wandering creatures always move, whereas
town creatures that wander sometimes stay put */
if (map->isWorldMap() || xu4_random(2) == 0)
dir = dirRandomDir(map->getValidMoves(new_coords, obj->getTile()));
break;
case MOVEMENT_FOLLOW_AVATAR:
case MOVEMENT_ATTACK_AVATAR:
dirmask = map->getValidMoves(new_coords, obj->getTile());
/* If the pirate ship turned last move instead of moving, this time it must
try to move, not turn again */
if (obj->getTile().getTileType()->isPirateShip() && DIR_IN_MASK(obj->getTile().getDirection(), dirmask) &&
(obj->getTile() != obj->getPrevTile()) && (obj->getPrevCoords() == obj->getCoords())) {
dir = obj->getTile().getDirection();
break;
}
dir = new_coords.pathTo(avatar, dirmask, true, g_context->_location->_map);
break;
}
// Now, get a new x and y for the object
if (dir)
new_coords.move(dir, g_context->_location->_map);
else
return 0;
// Figure out what method to use to tell if the object is getting slowed
SlowedType slowedType = SLOWED_BY_TILE;
if (obj->getType() == Object::CREATURE)
slowedType = obj->getSlowedType();
// Is the object slowed by terrain or by wind direction?
switch (slowedType) {
case SLOWED_BY_TILE:
slowed = slowedByTile(map->tileTypeAt(new_coords, WITHOUT_OBJECTS));
break;
case SLOWED_BY_WIND:
slowed = slowedByWind(obj->getTile().getDirection());
break;
case SLOWED_BY_NOTHING:
default:
break;
}
obj->setPrevCoords(obj->getCoords());
// See if the object needed to turn instead of move
if (obj->setDirection(dir))
return 0;
// Was the object slowed?
if (slowed)
return 0;
/**
* Set the new coordinates
*/
if (!(new_coords == obj->getCoords()) &&
!MAP_IS_OOB(map, new_coords)) {
obj->setCoords(new_coords);
}
return 1;
}
int moveCombatObject(int act, Map *map, Creature *obj, MapCoords target) {
MapCoords new_coords = obj->getCoords();
int valid_dirs = map->getValidMoves(new_coords, obj->getTile());
Direction dir;
CombatAction action = (CombatAction)act;
SlowedType slowedType = SLOWED_BY_TILE;
int slowed = 0;
// Fixed objects cannot move
if (obj->getMovementBehavior() == MOVEMENT_FIXED)
return 0;
if (action == CA_FLEE) {
// Run away from our target instead!
dir = new_coords.pathAway(target, valid_dirs);
} else {
assertMsg(action == CA_ADVANCE, "action must be CA_ADVANCE or CA_FLEE");
// If they're not fleeing, make sure they don't flee on accident
if (new_coords.x == 0)
valid_dirs = DIR_REMOVE_FROM_MASK(DIR_WEST, valid_dirs);
else if (new_coords.x >= (signed)(map->_width - 1))
valid_dirs = DIR_REMOVE_FROM_MASK(DIR_EAST, valid_dirs);
if (new_coords.y == 0)
valid_dirs = DIR_REMOVE_FROM_MASK(DIR_NORTH, valid_dirs);
else if (new_coords.y >= (signed)(map->_height - 1))
valid_dirs = DIR_REMOVE_FROM_MASK(DIR_SOUTH, valid_dirs);
dir = new_coords.pathTo(target, valid_dirs);
}
if (dir)
new_coords.move(dir, g_context->_location->_map);
else
return 0;
// Figure out what method to use to tell if the object is getting slowed
if (obj->getType() == Object::CREATURE)
slowedType = obj->getSlowedType();
// Is the object slowed by terrain or by wind direction?
switch (slowedType) {
case SLOWED_BY_TILE:
slowed = slowedByTile(map->tileTypeAt(new_coords, WITHOUT_OBJECTS));
break;
case SLOWED_BY_WIND:
slowed = slowedByWind(obj->getTile().getDirection());
break;
case SLOWED_BY_NOTHING:
default:
break;
}
// If the object wan't slowed...
if (!slowed) {
// Set the new coordinates
obj->setCoords(new_coords);
return 1;
}
return 0;
}
void movePartyMember(MoveEvent &event) {
CombatController *ct = dynamic_cast<CombatController *>(eventHandler->getController());
CombatMap *cm = getCombatMap();
assert(cm && ct);
int member = ct->getFocus();
MapCoords newCoords;
PartyMemberVector *party = ct->getParty();
event._result = MOVE_SUCCEEDED;
// Find our new location
newCoords = (*party)[member]->getCoords();
newCoords.move(event._dir, g_context->_location->_map);
if (MAP_IS_OOB(g_context->_location->_map, newCoords)) {
bool sameExit = (!cm->isDungeonRoom() || (ct->getExitDir() == DIR_NONE) || (event._dir == ct->getExitDir()));
if (sameExit) {
// If in a win-or-lose battle and not camping, then it can be bad to flee while healthy
if (ct->isWinOrLose() && !ct->isCamping()) {
// A fully-healed party member fled from an evil creature :(
if (ct->getCreature() && ct->getCreature()->isEvil() &&
g_context->_party->member(member)->getHp() == g_context->_party->member(member)->getMaxHp())
g_context->_party->adjustKarma(KA_HEALTHY_FLED_EVIL);
}
ct->setExitDir(event._dir);
g_context->_location->_map->removeObject((*party)[member]);
(*party)[member] = nullptr;
event._result = (MoveResult)(MOVE_EXIT_TO_PARENT | MOVE_MAP_CHANGE | MOVE_SUCCEEDED | MOVE_END_TURN);
return;
} else {
event._result = (MoveResult)(MOVE_MUST_USE_SAME_EXIT | MOVE_END_TURN);
return;
}
}
int movementMask = g_context->_location->_map->getValidMoves((*party)[member]->getCoords(), (*party)[member]->getTile());
if (!DIR_IN_MASK(event._dir, movementMask)) {
event._result = (MoveResult)(MOVE_BLOCKED | MOVE_END_TURN);
return;
}
// is the party member slowed?
if (!slowedByTile(g_context->_location->_map->tileTypeAt(newCoords, WITHOUT_OBJECTS))) {
// Move succeeded
(*party)[member]->setCoords(newCoords);
// Handle dungeon room triggers
if (cm->isDungeonRoom()) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_prev->_map);
assert(dungeon);
Trigger *triggers = dungeon->_rooms[dungeon->_currentRoom]._triggers;
int i;
for (i = 0; i < 4; i++) {
/*const Creature *m = creatures.getByTile(triggers[i].tile);*/
/* FIXME: when a creature is created by a trigger, it can be created over and over and over...
how do we fix this? In the c64 version is appears that such triggers (on the world map)
wipe the creature table and replace it with the triggered creatures. Thus, retriggering
it will reset the creatures.
*/
MapCoords trigger(triggers[i].x, triggers[i].y, g_context->_location->_coords.z);
// See if we're on a trigger
if (newCoords == trigger) {
MapCoords change1(triggers[i]._changeX1, triggers[i]._changeY1, g_context->_location->_coords.z),
change2(triggers[i].changeX2, triggers[i].changeY2, g_context->_location->_coords.z);
/**
* Remove any previous annotations placed at our target coordinates
*/
g_context->_location->_map->_annotations->remove(g_context->_location->_map->_annotations->allAt(change1));
g_context->_location->_map->_annotations->remove(g_context->_location->_map->_annotations->allAt(change2));
// Change the tiles!
if (change1.x || change1.y) {
/*if (m) combatAddCreature(m, triggers[i].change_x1, triggers[i].change_y1, c->location->coords.z);
else*/ g_context->_location->_map->_annotations->add(change1, triggers[i]._tile, false, true);
}
if (change2.x || change2.y) {
/*if (m) combatAddCreature(m, triggers[i].change_x2, triggers[i].change_y2, c->location->coords.z);
else*/ g_context->_location->_map->_annotations->add(change2, triggers[i]._tile, false, true);
}
}
}
}
} else {
event._result = (MoveResult)(MOVE_SLOWED | MOVE_END_TURN);
return;
}
}
bool slowedByTile(const Tile *tile) {
bool slow;
switch (tile->getSpeed()) {
case SLOW:
slow = xu4_random(8) == 0;
break;
case VSLOW:
slow = xu4_random(4) == 0;
break;
case VVSLOW:
slow = xu4_random(2) == 0;
break;
case FAST:
default:
slow = false;
break;
}
return slow;
}
bool slowedByWind(int direction) {
// 1 of 4 moves while trying to move into the wind succeeds
if (direction == g_context->_windDirection)
return (g_ultima->_saveGame->_moves % 4) != 0;
// 1 of 4 moves while moving directly away from wind fails
else if (direction == dirReverse((Direction) g_context->_windDirection))
return (g_ultima->_saveGame->_moves % 4) == 3;
else
return false;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View 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 ULTIMA4_MAP_MOVEMENT_H
#define ULTIMA4_MAP_MOVEMENT_H
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/map/map.h"
namespace Ultima {
namespace Ultima4 {
class Object;
class Map;
class Tile;
enum SlowedType {
SLOWED_BY_NOTHING,
SLOWED_BY_TILE,
SLOWED_BY_WIND
};
enum MoveResult {
MOVE_SUCCEEDED = 0x0001,
MOVE_END_TURN = 0x0002,
MOVE_BLOCKED = 0x0004,
MOVE_MAP_CHANGE = 0x0008,
MOVE_TURNED = 0x0010, /* dungeons and ship movement */
MOVE_DRIFT_ONLY = 0x0020, /* balloon -- no movement */
MOVE_EXIT_TO_PARENT = 0x0040,
MOVE_SLOWED = 0x0080,
MOVE_MUST_USE_SAME_EXIT = 0x0100
};
class MoveEvent {
public:
MoveEvent(Direction d, bool user) : _dir(d), _userEvent(user), _result(MOVE_SUCCEEDED) {}
Direction _dir; /**< the direction of the move */
bool _userEvent; /**< whether the user initiated the move */
MoveResult _result; /**< how the movement was resolved */
};
/**
* Attempt to move the avatar in the given direction. User event
* should be set if the avatar is being moved in response to a
* keystroke. Returns zero if the avatar is blocked.
*/
void moveAvatar(MoveEvent &event);
/**
* Moves the avatar while in dungeon view
*/
void moveAvatarInDungeon(MoveEvent &event);
/**
* Moves an object on the map according to its movement behavior
* Returns 1 if the object was moved successfully, 0 if slowed,
* tile direction changed, or object simply cannot move
* (fixed objects, nowhere to go, etc.)
*/
int moveObject(class Map *map, class Creature *obj, MapCoords avatar);
/**
* Moves an object in combat according to its chosen combat action
*/
int moveCombatObject(int action, class Map *map, class Creature *obj, MapCoords target);
/**
* Moves a party member during combat screens
*/
void movePartyMember(MoveEvent &event);
/**
* Default handler for slowing movement.
* Returns true if slowed, false if not slowed
*/
bool slowedByTile(const Tile *tile);
/**
* Slowed depending on the direction of object with respect to wind direction
* Returns true if slowed, false if not slowed
*/
bool slowedByWind(int direction);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,300 @@
/* 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/annotation.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/map/shrine.h"
#include "ultima/ultima4/controllers/read_choice_controller.h"
#include "ultima/ultima4/controllers/read_string_controller.h"
#include "ultima/ultima4/controllers/wait_controller.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/sound/music.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
Shrines *g_shrines;
Shrines::Shrines() : _cycles(0), _completedCycles(0) {
g_shrines = this;
}
Shrines::~Shrines() {
g_shrines = nullptr;
}
void Shrines::loadAdvice() {
_advice = u4read_stringtable("shrines");
}
/*-------------------------------------------------------------------*/
bool shrineCanEnter(const Portal *p) {
Shrine *shrine = dynamic_cast<Shrine *>(mapMgr->get(p->_destid));
assert(shrine);
if (!g_context->_party->canEnterShrine(shrine->getVirtue())) {
g_screen->screenMessage("Thou dost not bear the rune of entry! A strange force keeps you out!\n");
return 0;
}
return 1;
}
bool isShrine(Map *punknown) {
Shrine *ps;
if ((ps = dynamic_cast<Shrine *>(punknown)) != nullptr)
return true;
else
return false;
}
/*-------------------------------------------------------------------*/
Shrine::Shrine() : _virtue(VIRT_HONESTY) {}
Common::String Shrine::getName() {
if (_name.empty()) {
_name = "Shrine of ";
_name += getVirtueName(_virtue);
}
return _name;
}
Virtue Shrine::getVirtue() const {
return _virtue;
}
Common::String Shrine::getMantra() const {
return _mantra;
}
void Shrine::setVirtue(Virtue v) {
_virtue = v;
}
void Shrine::setMantra(const Common::String &m) {
_mantra = m;
}
void Shrine::enter() {
if (!g_shrines->isAdviceLoaded())
g_shrines->loadAdvice();
#ifdef IOS_ULTIMA4
U4IOS::IOSHideGameControllerHelper hideControllsHelper;
#endif
if (settings._enhancements && settings._enhancementsOptions._u5Shrines)
enhancedSequence();
else
g_screen->screenMessage("You enter the ancient shrine and sit before the altar...");
g_screen->screenMessage("\nUpon which virtue dost thou meditate?\n");
Common::String virtue;
#ifdef IOS_ULTIMA4
{
U4IOS::IOSConversationHelper inputVirture;
inputVirture.beginConversation(U4IOS::UIKeyboardTypeDefault, "Upon which virtue dost thou meditate?");
#endif
virtue = ReadStringController::get(32, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
#ifdef IOS_ULTIMA4
}
#endif
int choice;
g_screen->screenMessage("\n\nFor how many Cycles (0-3)? ");
#ifdef IOS_ULTIMA4
{
U4IOS::IOSConversationChoiceHelper cyclesChoice;
cyclesChoice.updateChoices("0123 \015\033");
#endif
choice = ReadChoiceController::get("0123\015\033");
#ifdef IOS_ULTIMA4
}
#endif
if (choice == '\033' || choice == '\015')
g_shrines->_cycles = 0;
else
g_shrines->_cycles = choice - '0';
g_shrines->_completedCycles = 0;
g_screen->screenMessage("\n\n");
// ensure the player chose the right virtue and entered a valid number for cycles
if (scumm_strnicmp(virtue.c_str(), getVirtueName(getVirtue()), 6) != 0 || g_shrines->_cycles == 0) {
g_screen->screenMessage("Thou art unable to focus thy thoughts on this subject!\n");
eject();
return;
}
if (((g_ultima->_saveGame->_moves / SHRINE_MEDITATION_INTERVAL) >= 0x10000) ||
(((g_ultima->_saveGame->_moves / SHRINE_MEDITATION_INTERVAL) & 0xffff) != g_ultima->_saveGame->_lastMeditation)) {
g_screen->screenMessage("Begin Meditation\n");
meditationCycle();
} else {
g_screen->screenMessage("Thy mind is still weary from thy last Meditation!\n");
eject();
}
}
void Shrine::enhancedSequence() {
// Replace the 'static' avatar tile with grass
_annotations->add(Coords(5, 6, g_context->_location->_coords.z), _tileSet->getByName("grass")->getId(), false, true);
g_screen->screenDisableCursor();
g_screen->screenMessage("You approach\nthe ancient\nshrine...\n");
gameUpdateScreen();
EventHandler::wait_cycles(settings._gameCyclesPerSecond);
Object *obj = addCreature(creatureMgr->getById(BEGGAR_ID), Coords(5, 10, g_context->_location->_coords.z));
obj->setTile(_tileSet->getByName("avatar")->getId());
gameUpdateScreen();
EventHandler::wait_msecs(400);
g_context->_location->_map->move(obj, DIR_NORTH);
gameUpdateScreen();
EventHandler::wait_msecs(400);
g_context->_location->_map->move(obj, DIR_NORTH);
gameUpdateScreen();
EventHandler::wait_msecs(400);
g_context->_location->_map->move(obj, DIR_NORTH);
gameUpdateScreen();
EventHandler::wait_msecs(400);
g_context->_location->_map->move(obj, DIR_NORTH);
gameUpdateScreen();
EventHandler::wait_msecs(800);
obj->setTile(creatureMgr->getById(BEGGAR_ID)->getTile());
gameUpdateScreen();
g_screen->screenMessage("\n...and kneel before the altar.\n");
EventHandler::wait_cycles(settings._gameCyclesPerSecond);
g_screen->screenEnableCursor();
}
void Shrine::meditationCycle() {
// Find our interval for meditation
int interval = (settings._shrineTime * 1000) / MEDITATION_MANTRAS_PER_CYCLE;
interval -= (interval % settings._eventTimerGranularity);
interval /= settings._eventTimerGranularity;
if (interval <= 0)
interval = 1;
g_ultima->_saveGame->_lastMeditation = (g_ultima->_saveGame->_moves / SHRINE_MEDITATION_INTERVAL) & 0xffff;
g_screen->screenDisableCursor();
for (int i = 0; i < MEDITATION_MANTRAS_PER_CYCLE; i++) {
WaitController controller(interval);
eventHandler->pushController(&controller);
controller.wait();
g_screen->screenMessage(".");
g_screen->update();
}
askMantra();
}
void Shrine::askMantra() {
g_screen->screenEnableCursor();
g_screen->screenMessage("\nMantra: ");
g_screen->update(); // FIXME: needed?
Common::String mantra;
#ifdef IOS_ULTIMA4
{
U4IOS::IOSConversationHelper mantraHelper;
mantraHelper.beginConversation(U4IOS::UIKeyboardTypeASCIICapable, "Mantra?");
#endif
mantra = ReadStringController::get(4, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
g_screen->screenMessage("\n");
#ifdef IOS_ULTIMA4
}
#endif
if (scumm_stricmp(mantra.c_str(), getMantra().c_str()) != 0) {
g_context->_party->adjustKarma(KA_BAD_MANTRA);
g_screen->screenMessage("Thou art not able to focus thy thoughts with that Mantra!\n");
eject();
} else if (--g_shrines->_cycles > 0) {
g_shrines->_completedCycles++;
g_context->_party->adjustKarma(KA_MEDITATION);
meditationCycle();
} else {
g_shrines->_completedCycles++;
g_context->_party->adjustKarma(KA_MEDITATION);
bool elevated = g_shrines->_completedCycles == 3 && g_context->_party->attemptElevation(getVirtue());
if (elevated)
g_screen->screenMessage("\nThou hast achieved partial Avatarhood in the Virtue of %s\n\n",
getVirtueName(getVirtue()));
else
g_screen->screenMessage("\nThy thoughts are pure. "
"Thou art granted a vision!\n");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper choiceDialog;
choiceDialog.updateChoices(" ");
U4IOS::testFlightPassCheckPoint(Common::String("Gained avatarhood in: ")
+ getVirtueName(getVirtue()));
#endif
ReadChoiceController::get("");
showVision(elevated);
ReadChoiceController::get("");
gameSetViewMode(VIEW_NORMAL);
eject();
}
}
void Shrine::showVision(bool elevated) {
static const char *visionImageNames[] = {
BKGD_SHRINE_HON, BKGD_SHRINE_COM, BKGD_SHRINE_VAL, BKGD_SHRINE_JUS,
BKGD_SHRINE_SAC, BKGD_SHRINE_HNR, BKGD_SHRINE_SPI, BKGD_SHRINE_HUM
};
if (elevated) {
g_screen->screenMessage("Thou art granted a vision!\n");
gameSetViewMode(VIEW_RUNE);
g_screen->screenDrawImageInMapArea(visionImageNames[getVirtue()]);
} else {
g_screen->screenMessage("\n%s", g_shrines->_advice[
getVirtue() * 3 + g_shrines->_completedCycles - 1].c_str());
}
}
void Shrine::eject() {
g_game->exitToParentMap();
g_music->playMapMusic();
g_context->_location->_turnCompleter->finishTurn();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View 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 ULTIMA4_MAP_SHRINE_H
#define ULTIMA4_MAP_SHRINE_H
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/filesys/savegame.h"
namespace Ultima {
namespace Ultima4 {
#define SHRINE_MEDITATION_INTERVAL 100
#define MEDITATION_MANTRAS_PER_CYCLE 16
class Shrine : public Map {
public:
Shrine();
~Shrine() override {}
// Methods
Common::String getName() override;
Virtue getVirtue() const;
Common::String getMantra() const;
void setVirtue(Virtue v);
void setMantra(const Common::String &mantra);
/**
* Enter the shrine
*/
void enter();
void enhancedSequence();
void meditationCycle();
void askMantra();
void eject();
void showVision(bool elevated);
// Properties
private:
Common::String _name;
Virtue _virtue;
Common::String _mantra;
};
class Shrines {
public:
int _cycles, _completedCycles;
Std::vector<Common::String> _advice;
public:
/**
* Constructor
*/
Shrines();
/**
* Destructor
*/
~Shrines();
/**
* Returns true if advice data has been loaded
*/
bool isAdviceLoaded() const {
return !_advice.empty();
}
/**
* Load advice
*/
void loadAdvice();
};
extern Shrines *g_shrines;
/**
* Returns true if the player can use the portal to the shrine
*/
bool shrineCanEnter(const Portal *p);
/**
* Returns true if 'map' points to a Shrine map
*/
bool isShrine(Map *punknown);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,238 @@
/* 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/tile.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/map/tileanim.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
TileId Tile::_nextId = 0;
Tile::Tile(Tileset *tileset)
: _id(_nextId++)
, _name()
, _tileSet(tileset)
, _w(0)
, _h(0)
, _frames(0)
, _scale(1)
, _anim(nullptr)
, _opaque(false)
, _foreground()
, _waterForeground()
, rule(nullptr)
, _imageName()
, _looksLike()
, _image(nullptr)
, _tiledInDungeon(false)
, _directions()
, _animationRule("") {
}
Tile::~Tile() {
deleteImage();
}
void Tile::loadProperties(const ConfigElement &conf) {
if (conf.getName() != "tile")
return;
_name = conf.getString("name"); // Get the name of the tile
// Get the animation for the tile, if one is specified
if (conf.exists("animation")) {
_animationRule = conf.getString("animation");
}
// See if the tile is opaque
_opaque = conf.getBool("opaque");
_foreground = conf.getBool("usesReplacementTileAsBackground");
_waterForeground = conf.getBool("usesWaterReplacementTileAsBackground");
/* find the rule that applies to the current tile, if there is one.
if there is no rule specified, it defaults to the "default" rule */
if (conf.exists("rule")) {
rule = g_tileRules->findByName(conf.getString("rule"));
if (rule == nullptr)
rule = g_tileRules->findByName("default");
} else {
rule = g_tileRules->findByName("default");
}
// Get the number of frames the tile has
_frames = conf.getInt("frames", 1);
// Get the name of the image that belongs to this tile
if (conf.exists("image"))
_imageName = conf.getString("image");
else
_imageName = Common::String("tile_") + _name;
_tiledInDungeon = conf.getBool("tiledInDungeon");
if (conf.exists("directions")) {
Common::String dirs = conf.getString("directions");
if (dirs.size() != (unsigned) _frames)
error("Error: %ld directions for tile but only %d frames", (long) dirs.size(), _frames);
for (unsigned i = 0; i < dirs.size(); i++) {
if (dirs[i] == 'w')
_directions.push_back(DIR_WEST);
else if (dirs[i] == 'n')
_directions.push_back(DIR_NORTH);
else if (dirs[i] == 'e')
_directions.push_back(DIR_EAST);
else if (dirs[i] == 's')
_directions.push_back(DIR_SOUTH);
else
error("Error: unknown direction specified by %c", dirs[i]);
}
}
}
Image *Tile::getImage() {
if (!_image)
loadImage();
return _image;
}
void Tile::loadImage() {
if (!_image) {
_scale = settings._scale;
SubImage *subimage = nullptr;
ImageInfo *info = imageMgr->get(_imageName);
if (!info) {
subimage = imageMgr->getSubImage(_imageName);
if (subimage)
info = imageMgr->get(subimage->_srcImageName);
}
if (!info) { // IF still no info loaded
warning("Error: couldn't load image for tile '%s'", _name.c_str());
return;
}
/* FIXME: This is a hack to address the fact that there are 4
frames for the guard in VGA mode, but only 2 in EGA. Is there
a better way to handle this? */
if (_name == "guard") {
if (settings._videoType == "EGA")
_frames = 2;
else
_frames = 4;
}
if (info->_image)
info->_image->alphaOff();
if (info) {
// Draw the tile from the image we found to our tile image
Image *tiles = info->_image;
assert(tiles);
_w = (subimage ? subimage->width() *_scale : info->_width * _scale / info->_prescale);
_h = (subimage ? (subimage->height() * _scale) / _frames : (info->_height * _scale / info->_prescale) / _frames);
_image = Image::create(_w, _h * _frames, tiles->format());
if (_image->isIndexed())
_image->setPaletteFromImage(tiles);
//info->image->alphaOff();
if (subimage) {
tiles->drawSubRectOn(_image, 0, 0,
subimage->left * _scale, subimage->top * _scale,
subimage->width() * _scale, subimage->height() * _scale);
} else {
tiles->drawOn(_image, 0, 0);
}
}
if (_animationRule.size() > 0) {
_anim = nullptr;
if (g_screen->_tileAnims)
_anim = g_screen->_tileAnims->getByName(_animationRule);
if (_anim == nullptr)
warning("Warning: animation style '%s' not found", _animationRule.c_str());
}
/* if we have animations, we always used 'animated' to draw from */
//if (anim)
// image->alphaOff();
}
}
void Tile::deleteImage() {
if (_image) {
delete _image;
_image = nullptr;
}
_scale = settings._scale;
}
bool Tile::isDungeonFloor() const {
Tile *floor = _tileSet->getByName("brick_floor");
if (_id == floor->_id)
return true;
return false;
}
bool Tile::isOpaque() const {
return g_context->_opacity ? _opaque : false;
}
bool Tile::isForeground() const {
return (rule->_mask & MASK_FOREGROUND);
}
Direction Tile::directionForFrame(int frame) const {
if (static_cast<unsigned>(frame) >= _directions.size())
return DIR_NONE;
else
return _directions[frame];
}
int Tile::frameForDirection(Direction d) const {
for (int i = 0; (unsigned) i < _directions.size() && i < _frames; i++) {
if (_directions[i] == d)
return i;
}
return -1;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,249 @@
/* 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 ULTIMA4_MAP_TILE_H
#define ULTIMA4_MAP_TILE_H
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class Image;
class Tileset;
class TileAnim;
/* attr masks */
#define MASK_SHIP 0x0001
#define MASK_HORSE 0x0002
#define MASK_BALLOON 0x0004
#define MASK_DISPEL 0x0008
#define MASK_TALKOVER 0x0010
#define MASK_DOOR 0x0020
#define MASK_LOCKEDDOOR 0x0040
#define MASK_CHEST 0x0080
#define MASK_ATTACKOVER 0x0100
#define MASK_CANLANDBALLOON 0x0200
#define MASK_REPLACEMENT 0x0400
#define MASK_WATER_REPLACEMENT 0x0800
#define MASK_FOREGROUND 0x1000
#define MASK_LIVING_THING 0x2000
/* movement masks */
#define MASK_SWIMABLE 0x0001
#define MASK_SAILABLE 0x0002
#define MASK_UNFLYABLE 0x0004
#define MASK_CREATURE_UNWALKABLE 0x0008
/**
* A Tile object represents a specific tile type. Every tile is a
* member of a Tileset.
*/
class Tile : private Uncopyable {
public:
Tile(Tileset *tileset);
~Tile();
/**
* Loads tile information.
*/
void loadProperties(const ConfigElement &conf);
TileId getId() const {
return _id;
}
const Common::String &getName() const {
return _name;
}
int getWidth() const {
return _w;
}
int getHeight() const {
return _h;
}
int getFrames() const {
return _frames;
}
int getScale() const {
return _scale;
}
TileAnim *getAnim() const {
return _anim;
}
Image *getImage();
const Common::String &getLooksLike() const {
return _looksLike;
}
bool isTiledInDungeon() const {
return _tiledInDungeon;
}
bool isLandForeground() const {
return _foreground;
}
bool isWaterForeground() const {
return _waterForeground;
}
int canWalkOn(Direction d) const {
return DIR_IN_MASK(d, rule->_walkOnDirs);
}
int canWalkOff(Direction d) const {
return DIR_IN_MASK(d, rule->_walkOffDirs);
}
/**
* All tiles that you can walk, swim, or sail on, can be attacked over. All others must declare
* themselves
*/
int canAttackOver() const {
return isWalkable() || isSwimable() || isSailable() || (rule->_mask & MASK_ATTACKOVER);
}
int canLandBalloon() const {
return rule->_mask & MASK_CANLANDBALLOON;
}
int isLivingObject() const {
return rule->_mask & MASK_LIVING_THING;
}
int isReplacement() const {
return rule->_mask & MASK_REPLACEMENT;
}
int isWaterReplacement() const {
return rule->_mask & MASK_WATER_REPLACEMENT;
}
int isWalkable() const {
return rule->_walkOnDirs > 0;
}
bool isCreatureWalkable() const {
return canWalkOn(DIR_ADVANCE) && !(rule->_movementMask & MASK_CREATURE_UNWALKABLE);
}
bool isDungeonWalkable() const;
bool isDungeonFloor() const;
int isSwimable() const {
return rule->_movementMask & MASK_SWIMABLE;
}
int isSailable() const {
return rule->_movementMask & MASK_SAILABLE;
}
bool isWater() const {
return (isSwimable() || isSailable());
}
int isFlyable() const {
return !(rule->_movementMask & MASK_UNFLYABLE);
}
int isDoor() const {
return rule->_mask & MASK_DOOR;
}
int isLockedDoor() const {
return rule->_mask & MASK_LOCKEDDOOR;
}
int isChest() const {
return rule->_mask & MASK_CHEST;
}
int isShip() const {
return rule->_mask & MASK_SHIP;
}
bool isPirateShip() const {
return _name == "pirate_ship";
}
int isHorse() const {
return rule->_mask & MASK_HORSE;
}
int isBalloon() const {
return rule->_mask & MASK_BALLOON;
}
int canDispel() const {
return rule->_mask & MASK_DISPEL;
}
int canTalkOver() const {
return rule->_mask & MASK_TALKOVER;
}
TileSpeed getSpeed() const {
return rule->_speed;
}
TileEffect getEffect() const {
return rule->_effect;
}
bool isOpaque() const;
/**
* Is tile a foreground tile (i.e. has transparent parts).
* Deprecated? Never used in XML. Other mechanisms exist, though this could help?
*/
bool isForeground() const;
Direction directionForFrame(int frame) const;
int frameForDirection(Direction d) const;
static void resetNextId() {
_nextId = 0;
}
static bool canTalkOverTile(const Tile *tile) {
return tile->canTalkOver() != 0;
}
static bool canAttackOverTile(const Tile *tile) {
return tile->canAttackOver() != 0;
}
void deleteImage();
private:
/**
* Loads the tile image
*/
void loadImage();
private:
TileId _id; /**< an id that is unique across all tilesets */
Common::String _name; /**< The name of this tile */
Tileset *_tileSet; /**< The tileset this tile belongs to */
int _w, _h; /**< width and height of the tile */
int _frames; /**< The number of frames this tile has */
int _scale; /**< The scale of the tile */
TileAnim *_anim; /**< The tile animation for this tile */
bool _opaque; /**< Is this tile opaque? */
bool _foreground; /**< As a maptile, is a foreground that will search neighbour maptiles for a land-based background replacement. ex: chests */
bool _waterForeground;/**< As a maptile, is a foreground that will search neighbour maptiles for a water-based background replacement. ex: chests */
TileRule *rule; /**< The rules that govern the behavior of this tile */
Common::String _imageName; /**< The name of the image that belongs to this tile */
Common::String _looksLike; /**< The name of the tile that this tile looks exactly like (if any) */
Image *_image; /**< The original image for this tile (with all of its frames) */
bool _tiledInDungeon;
Std::vector<Direction> _directions;
Common::String _animationRule;
static TileId _nextId;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,366 @@
/* 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/core/config.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/tileanim.h"
#include "ultima/ultima4/ultima4.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/map/tile.h"
namespace Ultima {
namespace Ultima4 {
TileAnimTransform *TileAnimTransform::create(const ConfigElement &conf) {
TileAnimTransform *transform;
static const char *const transformTypeEnumStrings[] = { "invert", "pixel", "scroll", "frame", "pixel_color", nullptr };
int type = conf.getEnum("type", transformTypeEnumStrings);
switch (type) {
case 0:
transform = new TileAnimInvertTransform(
conf.getInt("x"), conf.getInt("y"),
conf.getInt("width"), conf.getInt("height"));
break;
case 1: {
transform = new TileAnimPixelTransform(
conf.getInt("x"), conf.getInt("y"));
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "color") {
RGBA *rgba = loadColorFromConf(i);
((TileAnimPixelTransform *)transform)->_colors.push_back(rgba);
}
}
break;
}
case 2:
transform = new TileAnimScrollTransform(conf.getInt("increment"));
break;
case 3:
transform = new TileAnimFrameTransform();
break;
case 4: {
transform = new TileAnimPixelColorTransform(
conf.getInt("x"), conf.getInt("y"),
conf.getInt("width"), conf.getInt("height"));
Std::vector<ConfigElement> children = conf.getChildren();
for (Std::vector<ConfigElement>::iterator i = children.begin(); i != children.end(); i++) {
if (i->getName() == "color") {
RGBA *rgba = loadColorFromConf(*i);
if (i == children.begin())
((TileAnimPixelColorTransform *)transform)->_start = rgba;
else
((TileAnimPixelColorTransform *)transform)->_end = rgba;
}
}
break;
}
default:
error("Unknown type");
}
// See if the transform is performed randomly
if (conf.exists("random"))
transform->_random = conf.getInt("random");
else
transform->_random = 0;
return transform;
}
RGBA *TileAnimTransform::loadColorFromConf(const ConfigElement &conf) {
RGBA *rgba;
rgba = new RGBA();
rgba->r = conf.getInt("red");
rgba->g = conf.getInt("green");
rgba->b = conf.getInt("blue");
rgba->a = IM_OPAQUE;
return rgba;
}
TileAnimInvertTransform::TileAnimInvertTransform(int xp, int yp, int width, int height) {
this->x = xp;
this->y = yp;
this->w = width;
this->h = height;
}
bool TileAnimInvertTransform::drawsTile() const {
return false;
}
void TileAnimInvertTransform::draw(Image *dest, Tile *tile, MapTile &mapTile) {
int scale = tile->getScale();
tile->getImage()->drawSubRectInvertedOn(dest, x * scale, y * scale, x * scale,
(tile->getHeight() * mapTile._frame) + (y * scale), w * scale, h * scale);
}
TileAnimPixelTransform::TileAnimPixelTransform(int xp, int yp) {
this->x = xp;
this->y = yp;
}
bool TileAnimPixelTransform::drawsTile() const {
return false;
}
void TileAnimPixelTransform::draw(Image *dest, Tile *tile, MapTile &mapTile) {
RGBA *color = _colors[xu4_random(_colors.size())];
int scale = tile->getScale();
dest->fillRect(x * scale, y * scale, scale, scale, color->r, color->g, color->b, color->a);
}
bool TileAnimScrollTransform::drawsTile() const {
return true;
}
TileAnimScrollTransform::TileAnimScrollTransform(int i) : _increment(i), _current(0), _lastOffset(0) {}
void TileAnimScrollTransform::draw(Image *dest, Tile *tile, MapTile &mapTile) {
if (_increment == 0)
_increment = tile->getScale();
int offset = g_screen->_currentCycle * 4 / SCR_CYCLE_PER_SECOND * tile->getScale();
if (_lastOffset != offset) {
_lastOffset = offset;
_current += _increment;
if (_current >= tile->getHeight())
_current = 0;
}
tile->getImage()->drawSubRectOn(dest, 0, _current, 0, tile->getHeight() * mapTile._frame, tile->getWidth(), tile->getHeight() - _current);
if (_current != 0)
tile->getImage()->drawSubRectOn(dest, 0, 0, 0, (tile->getHeight() * mapTile._frame) + tile->getHeight() - _current, tile->getWidth(), _current);
}
bool TileAnimFrameTransform::drawsTile() const {
return true;
}
void TileAnimFrameTransform::draw(Image *dest, Tile *tile, MapTile &mapTile) {
if (++_currentFrame >= tile->getFrames())
_currentFrame = 0;
tile->getImage()->drawSubRectOn(dest, 0, 0, 0, _currentFrame * tile->getHeight(), tile->getWidth(), tile->getHeight());
}
TileAnimPixelColorTransform::TileAnimPixelColorTransform(int xp, int yp, int width, int height) {
this->x = xp;
this->y = yp;
this->w = width;
this->h = height;
_start = _end = nullptr;
}
TileAnimPixelColorTransform::~TileAnimPixelColorTransform() {
delete _start;
delete _end;
}
bool TileAnimPixelColorTransform::drawsTile() const {
return false;
}
void TileAnimPixelColorTransform::draw(Image *dest, Tile *tile, MapTile &mapTile) {
RGBA diff = *_end;
int scale = tile->getScale();
diff.r -= _start->r;
diff.g -= _start->g;
diff.b -= _start->b;
Image *tileImage = tile->getImage();
for (int j = y * scale; j < (y * scale) + (h * scale); j++) {
for (int i = x * scale; i < (x * scale) + (w * scale); i++) {
RGBA pixelAt;
tileImage->getPixel(i, j + (mapTile._frame * tile->getHeight()), pixelAt.r, pixelAt.g, pixelAt.b, pixelAt.a);
if (pixelAt.r >= _start->r && pixelAt.r <= _end->r &&
pixelAt.g >= _start->g && pixelAt.g <= _end->g &&
pixelAt.b >= _start->b && pixelAt.b <= _end->b) {
dest->putPixel(i, j, _start->r + xu4_random(diff.r), _start->g + xu4_random(diff.g), _start->b + xu4_random(diff.b), pixelAt.a);
}
}
}
}
TileAnimContext *TileAnimContext::create(const ConfigElement &conf) {
TileAnimContext *context;
static const char *const contextTypeEnumStrings[] = { "frame", "dir", nullptr };
static const char *const dirEnumStrings[] = { "none", "west", "north", "east", "south", nullptr };
TileAnimContext::Type type = (TileAnimContext::Type)conf.getEnum("type", contextTypeEnumStrings);
switch (type) {
case FRAME:
context = new TileAnimFrameContext(conf.getInt("frame"));
break;
case DIR:
context = new TileAnimPlayerDirContext(Direction(conf.getEnum("dir", dirEnumStrings)));
break;
default:
context = nullptr;
break;
}
// Add the transforms to the context
if (context) {
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "transform") {
TileAnimTransform *transform = TileAnimTransform::create(i);
context->add(transform);
}
}
}
return context;
}
void TileAnimContext::add(TileAnimTransform *transform) {
_animTransforms.push_back(transform);
}
TileAnimFrameContext::TileAnimFrameContext(int f) : _frame(f) {}
bool TileAnimFrameContext::isInContext(Tile *t, MapTile &mapTile, Direction dir) {
return (mapTile._frame == _frame);
}
TileAnimPlayerDirContext::TileAnimPlayerDirContext(Direction d) : _dir(d) {}
bool TileAnimPlayerDirContext::isInContext(Tile *t, MapTile &mapTile, Direction d) {
return (d == _dir);
}
/*-------------------------------------------------------------------*/
TileAnimSet::TileAnimSet(const ConfigElement &conf) {
_name = conf.getString("name");
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "tileanim") {
TileAnim *anim = new TileAnim(i);
_tileAnims[anim->_name] = anim;
}
}
}
TileAnimSet::~TileAnimSet() {
for (auto &t : _tileAnims)
delete t._value;
}
TileAnim *TileAnimSet::getByName(const Common::String &name) {
TileAnimMap::iterator i = _tileAnims.find(name);
if (i == _tileAnims.end())
return nullptr;
return i->_value;
}
/*------------------------------------------------------------------------*/
TileAnim::TileAnim(const ConfigElement &conf) : _random(0) {
_name = conf.getString("name");
if (conf.exists("random"))
_random = conf.getInt("random");
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "transform") {
TileAnimTransform *transform = TileAnimTransform::create(i);
_transforms.push_back(transform);
} else if (i.getName() == "context") {
TileAnimContext *context = TileAnimContext::create(i);
_contexts.push_back(context);
}
}
}
TileAnim::~TileAnim() {
for (uint idx = 0; idx < _transforms.size(); ++idx)
delete _transforms[idx];
for (uint idx = 0; idx < _contexts.size(); ++idx)
delete _contexts[idx];
}
void TileAnim::draw(Image *dest, Tile *tile, MapTile &mapTile, Direction dir) {
Std::vector<TileAnimTransform *>::const_iterator t;
Std::vector<TileAnimContext *>::const_iterator c;
bool drawn = false;
// Nothing to do, draw the tile and return!
if ((_random && xu4_random(100) > _random) || (!_transforms.size() && !_contexts.size()) || mapTile._freezeAnimation) {
tile->getImage()->drawSubRectOn(dest, 0, 0, 0, mapTile._frame * tile->getHeight(), tile->getWidth(), tile->getHeight());
return;
}
// Do global transforms
for (t = _transforms.begin(); t != _transforms.end(); t++) {
TileAnimTransform *transform = *t;
if (!transform->_random || xu4_random(100) < transform->_random) {
if (!transform->drawsTile() && !drawn)
tile->getImage()->drawSubRectOn(dest, 0, 0, 0, mapTile._frame * tile->getHeight(), tile->getWidth(), tile->getHeight());
transform->draw(dest, tile, mapTile);
drawn = true;
}
}
// Do contextual transforms
for (c = _contexts.begin(); c != _contexts.end(); c++) {
if ((*c)->isInContext(tile, mapTile, dir)) {
TileAnimContext::TileAnimTransformList ctx_transforms = (*c)->getTransforms();
for (t = ctx_transforms.begin(); t != ctx_transforms.end(); t++) {
TileAnimTransform *transform = *t;
if (!transform->_random || xu4_random(100) < transform->_random) {
if (!transform->drawsTile() && !drawn)
tile->getImage()->drawSubRectOn(dest, 0, 0, 0, mapTile._frame * tile->getHeight(), tile->getWidth(), tile->getHeight());
transform->draw(dest, tile, mapTile);
drawn = true;
}
}
}
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,235 @@
/* 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 ULTIMA4_MAP_TILEANIM_H
#define ULTIMA4_MAP_TILEANIM_H
#include "ultima/ultima4/map/direction.h"
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class Image;
class Tile;
struct RGBA;
/**
* The interface for tile animation transformations.
*/
class TileAnimTransform {
public:
static TileAnimTransform *create(const ConfigElement &config);
/**
* Loads a color from a config element
*/
static RGBA *loadColorFromConf(const ConfigElement &conf);
virtual void draw(Image *dest, Tile *tile, MapTile &mapTile) = 0;
virtual ~TileAnimTransform() {}
virtual bool drawsTile() const = 0;
// Properties
int _random;
};
/**
* A tile animation transformation that turns a piece of the tile
* upside down. Used for animating the flags on building and ships.
*/
class TileAnimInvertTransform : public TileAnimTransform {
public:
TileAnimInvertTransform(int xp, int yp, int width, int height);
void draw(Image *dest, Tile *tile, MapTile &mapTile) override;
bool drawsTile() const override;
private:
int x, y, w, h;
};
/**
* A tile animation transformation that changes a single pixels to a
* random color selected from a list. Used for animating the
* campfire in EGA mode.
*/
class TileAnimPixelTransform : public TileAnimTransform {
public:
TileAnimPixelTransform(int xp, int yp);
void draw(Image *dest, Tile *tile, MapTile &mapTile) override;
bool drawsTile() const override;
int x, y;
Std::vector<RGBA *> _colors;
};
/**
* A tile animation transformation that scrolls the tile's contents
* vertically within the tile's boundaries.
*/
class TileAnimScrollTransform : public TileAnimTransform {
public:
TileAnimScrollTransform(int increment);
void draw(Image *dest, Tile *tile, MapTile &mapTile) override;
bool drawsTile() const override;
private:
int _increment, _current, _lastOffset;
};
/**
* A tile animation transformation that advances the tile's frame
* by 1.
*/
class TileAnimFrameTransform : public TileAnimTransform {
public:
TileAnimFrameTransform() : _currentFrame(0) {
}
void draw(Image *dest, Tile *tile, MapTile &mapTile) override;
/**
* Advance the frame by one and draw it!
*/
bool drawsTile() const override;
protected:
int _currentFrame;
};
/**
* A tile animation transformation that changes pixels with colors
* that fall in a given range to another color. Used to animate
* the campfire in VGA mode.
*/
class TileAnimPixelColorTransform : public TileAnimTransform {
public:
TileAnimPixelColorTransform(int xp, int yp, int width, int height);
~TileAnimPixelColorTransform() override;
void draw(Image *dest, Tile *tile, MapTile &mapTile) override;
bool drawsTile() const override;
int x, y, w, h;
RGBA *_start, *_end;
};
/**
* A context in which to perform the animation
*/
class TileAnimContext {
public:
typedef Std::vector<TileAnimTransform *> TileAnimTransformList;
typedef enum {
FRAME,
DIR
} Type;
/**
* Creates a new animation context which controls if animation transforms are performed or not
*/
static TileAnimContext *create(const ConfigElement &config);
/**
* Adds a tile transform to the context
*/
void add(TileAnimTransform *);
virtual bool isInContext(Tile *t, MapTile &mapTile, Direction d) = 0;
TileAnimTransformList &getTransforms() {
return _animTransforms; /**< Returns a list of transformations under the context. */
}
virtual ~TileAnimContext() {}
private:
TileAnimTransformList _animTransforms;
};
/**
* An animation context which changes the animation based on the tile's current frame
*/
class TileAnimFrameContext : public TileAnimContext {
public:
/**
* A context which depends on the tile's current frame for animation
*/
TileAnimFrameContext(int frame);
bool isInContext(Tile *t, MapTile &mapTile, Direction d) override;
private:
int _frame;
};
/**
* An animation context which changes the animation based on the player's current facing direction
*/
class TileAnimPlayerDirContext : public TileAnimContext {
public:
/**
* An animation context which changes the animation based on the player's current facing direction
*/
TileAnimPlayerDirContext(Direction dir);
bool isInContext(Tile *t, MapTile &mapTile, Direction d) override;
private:
Direction _dir;
};
/**
* Instructions for animating a tile. Each tile animation is made up
* of a list of transformations which are applied to the tile after it
* is drawn.
*/
class TileAnim {
public:
TileAnim(const ConfigElement &conf);
~TileAnim();
Common::String _name;
Std::vector<TileAnimTransform *> _transforms;
Std::vector<TileAnimContext *> _contexts;
/* returns the frame to set the mapTile to (only relevant if persistent) */
void draw(Image *dest, Tile *tile, MapTile &mapTile, Direction dir);
int _random; /* true if the tile animation occurs randomely */
};
/**
* A set of tile animations. Tile animations are associated with a
* specific image set which shares the same name.
*/
class TileAnimSet {
typedef Common::HashMap<Common::String, TileAnim *> TileAnimMap;
public:
TileAnimSet(const ConfigElement &conf);
~TileAnimSet();
/**
* Returns the tile animation with the given name from the current set
*/
TileAnim *getByName(const Common::String &name);
Common::String _name;
TileAnimMap _tileAnims;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,144 @@
/* 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/tilemap.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/map/tileset.h"
namespace Ultima {
namespace Ultima4 {
TileMaps *g_tileMaps;
TileMaps::TileMaps() {
g_tileMaps = this;
loadAll();
}
TileMaps::~TileMaps() {
unloadAll();
g_tileMaps = nullptr;
}
void TileMaps::loadAll() {
const Config *config = Config::getInstance();
Std::vector<ConfigElement> conf;
// FIXME: make sure tilesets are loaded by now
unloadAll();
// Open the filename for the tileset and parse it!
conf = config->getElement("tilesets").getChildren();
// Load all of the tilemaps
for (const auto &i : conf) {
if (i.getName() == "tilemap") {
// Load the tilemap !
load(i);
}
}
}
void TileMaps::unloadAll() {
// Free all the memory for the tile maps
for (iterator it = begin(); it != end(); it++)
delete it->_value;
// Clear the map so we don't attempt to delete the memory again next time
clear();
}
void TileMaps::load(const ConfigElement &tilemapConf) {
TileMap *tm = new TileMap();
Common::String name = tilemapConf.getString("name");
Common::String tileset = tilemapConf.getString("tileset");
int index = 0;
Std::vector<ConfigElement> children = tilemapConf.getChildren();
for (const auto &i : children) {
if (i.getName() != "mapping")
continue;
// We assume tiles have already been loaded at this point,
// so let's do some translations!
int frames = 1;
Common::String tile = i.getString("tile");
// Find the tile this references
Tile *t = g_tileSets->get(tileset)->getByName(tile);
if (!t)
error("Error: tile '%s' from '%s' was not found in tileset %s", tile.c_str(), name.c_str(), tileset.c_str());
if (i.exists("index"))
index = i.getInt("index");
if (i.exists("frames"))
frames = i.getInt("frames");
// Insert the tile into the tile map
for (int idx = 0; idx < frames; idx++) {
if (idx < t->getFrames())
tm->_tileMap[index + idx] = MapTile(t->getId(), idx);
// Frame fell out of the scope of the tile -- frame is set to 0
else
tm->_tileMap[index + idx] = MapTile(t->getId(), 0);
}
index += frames;
}
// Add the tilemap to our list
(*this)[name] = tm;
}
TileMap *TileMaps::get(Common::String name) {
if (find(name) != end())
return (*this)[name];
else
return nullptr;
}
/*-------------------------------------------------------------------*/
MapTile TileMap::translate(uint index) {
return _tileMap[index];
}
uint TileMap::untranslate(MapTile &tile) {
uint index = 0;
for (const auto &i : _tileMap) {
if (i._value == tile) {
index = i._key;
break;
}
}
index += tile._frame;
return index;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,93 @@
/* 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 ULTIMA4_MAP_TILEMAP_H
#define ULTIMA4_MAP_TILEMAP_H
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/map_tile.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class TileMaps;
/**
* A tilemap maps the raw bytes in a map file to MapTiles.
*/
class TileMap {
friend class TileMaps;
private:
Common::HashMap<uint, MapTile> _tileMap;
public:
/**
* Translates a raw index to a MapTile.
*/
MapTile translate(uint index);
uint untranslate(MapTile &tile);
};
class TileMaps : public Common::HashMap<Common::String, TileMap *> {
private:
/**
* Loads a tile map which translates between tile indices and tile
* names. Tile maps are useful to translate from dos tile indices to
* xu4 tile ids.
*/
void load(const ConfigElement &tilemapConf);
public:
/**
* Constructor
*/
TileMaps();
/**
* Destructor
*/
~TileMaps();
/**
* Load all tilemaps from the specified xml file
*/
void loadAll();
/**
* Delete all tilemaps
*/
void unloadAll();
/**
* Returns the Tile index map with the specified name
*/
TileMap *get(Common::String name);
};
extern TileMaps *g_tileMaps;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,331 @@
/* 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/tileset.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/map/tilemap.h"
namespace Ultima {
namespace Ultima4 {
TileRules *g_tileRules;
TileSets *g_tileSets;
TileRules::TileRules() {
g_tileRules = this;
}
TileRules::~TileRules() {
// Delete the tile rules
for (iterator it = begin(); it != end(); ++it)
delete it->_value;
g_tileRules = nullptr;
}
void TileRules::load() {
const Config *config = Config::getInstance();
Std::vector<ConfigElement> rules = config->getElement("tileRules").getChildren();
for (const auto &i : rules) {
TileRule *rule = new TileRule();
rule->initFromConf(i);
(*this)[rule->_name] = rule;
}
if (findByName("default") == nullptr)
error("no 'default' rule found in tile rules");
}
TileRule *TileRules::findByName(const Common::String &name) {
TileRuleMap::iterator i = find(name);
if (i != end())
return i->_value;
return nullptr;
}
/*-------------------------------------------------------------------*/
TileSets::TileSets() {
g_tileSets = this;
loadAll();
}
TileSets::~TileSets() {
unloadAll();
g_tileSets = nullptr;
}
void TileSets::loadAll() {
const Config *config = Config::getInstance();
Std::vector<ConfigElement> conf;
unloadAll();
// Get the config element for all tilesets
conf = config->getElement("tilesets").getChildren();
// Load tile rules
if (g_tileRules->empty())
g_tileRules->load();
// Load all of the tilesets
for (const auto &i : conf) {
if (i.getName() == "tileset") {
Tileset *tileset = new Tileset();
tileset->load(i);
(*this)[tileset->_name] = tileset;
}
}
}
void TileSets::unloadAll() {
iterator i;
for (i = begin(); i != end(); i++) {
i->_value->unload();
delete i->_value;
}
clear();
Tile::resetNextId();
}
void TileSets::unloadAllImages() {
iterator i;
for (i = begin(); i != end(); i++) {
i->_value->unloadImages();
}
Tile::resetNextId();
}
Tileset *TileSets::get(const Common::String &name) {
if (find(name) != end())
return (*this)[name];
else
return nullptr;
}
Tile *TileSets::findTileByName(const Common::String &name) {
iterator i;
for (i = begin(); i != end(); i++) {
Tile *t = i->_value->getByName(name);
if (t)
return t;
}
return nullptr;
}
Tile *TileSets::findTileById(TileId id) {
iterator i;
for (i = begin(); i != end(); i++) {
Tile *t = i->_value->get(id);
if (t)
return t;
}
return nullptr;
}
/*-------------------------------------------------------------------*/
bool TileRule::initFromConf(const ConfigElement &conf) {
uint i;
static const struct {
const char *name;
uint mask;
} booleanAttributes[] = {
{ "dispel", MASK_DISPEL },
{ "talkover", MASK_TALKOVER },
{ "door", MASK_DOOR },
{ "lockeddoor", MASK_LOCKEDDOOR },
{ "chest", MASK_CHEST },
{ "ship", MASK_SHIP },
{ "horse", MASK_HORSE },
{ "balloon", MASK_BALLOON },
{ "canattackover", MASK_ATTACKOVER },
{ "canlandballoon", MASK_CANLANDBALLOON },
{ "replacement", MASK_REPLACEMENT },
{ "foreground", MASK_FOREGROUND },
{ "onWaterOnlyReplacement", MASK_WATER_REPLACEMENT},
{ "livingthing", MASK_LIVING_THING }
};
static const struct {
const char *_name;
uint _mask;
} movementBooleanAttr[] = {
{ "swimable", MASK_SWIMABLE },
{ "sailable", MASK_SAILABLE },
{ "unflyable", MASK_UNFLYABLE },
{ "creatureunwalkable", MASK_CREATURE_UNWALKABLE }
};
static const char *const speedEnumStrings[] = { "fast", "slow", "vslow", "vvslow", nullptr };
static const char *const effectsEnumStrings[] = { "none", "fire", "sleep", "poison", "poisonField", "electricity", "lava", nullptr };
_mask = 0;
_movementMask = 0;
_speed = FAST;
_effect = EFFECT_NONE;
_walkOnDirs = MASK_DIR_ALL;
_walkOffDirs = MASK_DIR_ALL;
_name = conf.getString("name");
for (i = 0; i < sizeof(booleanAttributes) / sizeof(booleanAttributes[0]); i++) {
if (conf.getBool(booleanAttributes[i].name))
_mask |= booleanAttributes[i].mask;
}
for (i = 0; i < sizeof(movementBooleanAttr) / sizeof(movementBooleanAttr[0]); i++) {
if (conf.getBool(movementBooleanAttr[i]._name))
_movementMask |= movementBooleanAttr[i]._mask;
}
Common::String cantwalkon = conf.getString("cantwalkon");
if (cantwalkon == "all")
_walkOnDirs = 0;
else if (cantwalkon == "west")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_WEST, _walkOnDirs);
else if (cantwalkon == "north")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_NORTH, _walkOnDirs);
else if (cantwalkon == "east")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_EAST, _walkOnDirs);
else if (cantwalkon == "south")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_SOUTH, _walkOnDirs);
else if (cantwalkon == "advance")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_ADVANCE, _walkOnDirs);
else if (cantwalkon == "retreat")
_walkOnDirs = DIR_REMOVE_FROM_MASK(DIR_RETREAT, _walkOnDirs);
Common::String cantwalkoff = conf.getString("cantwalkoff");
if (cantwalkoff == "all")
_walkOffDirs = 0;
else if (cantwalkoff == "west")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_WEST, _walkOffDirs);
else if (cantwalkoff == "north")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_NORTH, _walkOffDirs);
else if (cantwalkoff == "east")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_EAST, _walkOffDirs);
else if (cantwalkoff == "south")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_SOUTH, _walkOffDirs);
else if (cantwalkoff == "advance")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_ADVANCE, _walkOffDirs);
else if (cantwalkoff == "retreat")
_walkOffDirs = DIR_REMOVE_FROM_MASK(DIR_RETREAT, _walkOffDirs);
_speed = static_cast<TileSpeed>(conf.getEnum("speed", speedEnumStrings));
_effect = static_cast<TileEffect>(conf.getEnum("effect", effectsEnumStrings));
return true;
}
void Tileset::load(const ConfigElement &tilesetConf) {
_name = tilesetConf.getString("name");
if (tilesetConf.exists("imageName"))
_imageName = tilesetConf.getString("imageName");
if (tilesetConf.exists("extends"))
_extends = g_tileSets->get(tilesetConf.getString("extends"));
else
_extends = nullptr;
int index = 0;
Std::vector<ConfigElement> children = tilesetConf.getChildren();
for (const auto &i : children) {
if (i.getName() != "tile")
continue;
Tile *tile = new Tile(this);
tile->loadProperties(i);
// Add the tile to our tileset
_tiles[tile->getId()] = tile;
_nameMap[tile->getName()] = tile;
index += tile->getFrames();
}
_totalFrames = index;
}
void Tileset::unloadImages() {
Tileset::TileIdMap::iterator i;
// Free all the image memory and nullify so that reloading can automatically take place lazily
for (i = _tiles.begin(); i != _tiles.end(); i++) {
i->_value->deleteImage();
}
}
void Tileset::unload() {
Tileset::TileIdMap::iterator i;
// Free all the memory for the tiles
for (i = _tiles.begin(); i != _tiles.end(); i++)
delete i->_value;
_tiles.clear();
_totalFrames = 0;
_imageName.clear();
}
Tile *Tileset::get(TileId id) {
if (_tiles.find(id) != _tiles.end())
return _tiles[id];
else if (_extends)
return _extends->get(id);
return nullptr;
}
Tile *Tileset::getByName(const Common::String &name) {
if (_nameMap.find(name) != _nameMap.end())
return _nameMap[name];
else if (_extends)
return _extends->getByName(name);
else
return nullptr;
}
Common::String Tileset::getImageName() const {
if (_imageName.empty() && _extends)
return _extends->getImageName();
else
return _imageName;
}
uint Tileset::numTiles() const {
return _tiles.size();
}
uint Tileset::numFrames() const {
return _totalFrames;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,189 @@
/* 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 ULTIMA4_MAP_TILESET_H
#define ULTIMA4_MAP_TILESET_H
#include "ultima/ultima4/core/types.h"
#include "common/hash-str.h"
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class Tile;
class TileSets;
typedef Common::HashMap<Common::String, class TileRule *> TileRuleMap;
/**
* TileRule class
*/
class TileRule {
public:
/**
* Load properties for the current rule node
*/
bool initFromConf(const ConfigElement &tileRuleConf);
Common::String _name;
unsigned short _mask;
unsigned short _movementMask;
TileSpeed _speed;
TileEffect _effect;
int _walkOnDirs;
int _walkOffDirs;
};
/**
* Tileset class
*/
class Tileset {
friend class TileSets;
public:
typedef Common::HashMap<TileId, Tile *> TileIdMap;
typedef Common::HashMap<Common::String, Tile *> TileStrMap;
public:
Tileset() : _totalFrames(0), _extends(nullptr) {}
/**
* Loads a tileset.
*/
void load(const ConfigElement &tilesetConf);
/**
* Unload the current tileset
*/
void unload();
void unloadImages();
/**
* Returns the tile with the given id in the tileset
*/
Tile *get(TileId id);
/**
* Returns the tile with the given name from the tileset, if it exists
*/
Tile *getByName(const Common::String &name);
/**
* Returns the image name for the tileset, if it exists
*/
Common::String getImageName() const;
/**
* Returns the number of tiles in the tileset
*/
uint numTiles() const;
/**
* Returns the total number of frames in the tileset
*/
uint numFrames() const;
private:
Common::String _name;
TileIdMap _tiles;
uint _totalFrames;
Common::String _imageName;
Tileset *_extends;
TileStrMap _nameMap;
};
/**
* Tile rules container
*/
class TileRules : public TileRuleMap {
public:
/**
* Constructor
*/
TileRules();
/**
* Destructor
*/
~TileRules();
/**
* Load tile information from xml.
*/
void load();
/**
* Returns the tile rule with the given name, or nullptr if none could be found
*/
TileRule *findByName(const Common::String &name);
};
/**
* Tile sets container
*/
class TileSets : public Common::HashMap<Common::String, Tileset *> {
public:
/**
* Constructor
*/
TileSets();
/**
* Destructor
*/
~TileSets();
/**
* Loads all tilesets using the filename
* indicated by 'filename' as a definition
*/
void loadAll();
/**
* Delete all tilesets
*/
void unloadAll();
/**
* Delete all tileset images
*/
void unloadAllImages();
/**
* Returns the tileset with the given name, if it exists
*/
Tileset *get(const Common::String &name);
/**
* Returns the tile that has the given name from any tileset, if there is one
*/
Tile *findTileByName(const Common::String &name);
Tile *findTileById(TileId id);
};
extern TileRules *g_tileRules;
extern TileSets *g_tileSets;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,42 @@
/* 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 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