Initial commit
This commit is contained in:
126
engines/ultima/ultima4/map/annotation.cpp
Normal file
126
engines/ultima/ultima4/map/annotation.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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
|
||||
189
engines/ultima/ultima4/map/annotation.h
Normal file
189
engines/ultima/ultima4/map/annotation.h
Normal 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
|
||||
41
engines/ultima/ultima4/map/area.h
Normal file
41
engines/ultima/ultima4/map/area.h
Normal 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
|
||||
109
engines/ultima/ultima4/map/city.cpp
Normal file
109
engines/ultima/ultima4/map/city.cpp
Normal 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
|
||||
92
engines/ultima/ultima4/map/city.h
Normal file
92
engines/ultima/ultima4/map/city.h
Normal 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
|
||||
149
engines/ultima/ultima4/map/direction.cpp
Normal file
149
engines/ultima/ultima4/map/direction.cpp
Normal 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
|
||||
94
engines/ultima/ultima4/map/direction.h
Normal file
94
engines/ultima/ultima4/map/direction.h
Normal 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
|
||||
416
engines/ultima/ultima4/map/dungeon.cpp
Normal file
416
engines/ultima/ultima4/map/dungeon.cpp
Normal 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
|
||||
235
engines/ultima/ultima4/map/dungeon.h
Normal file
235
engines/ultima/ultima4/map/dungeon.h
Normal 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
|
||||
273
engines/ultima/ultima4/map/location.cpp
Normal file
273
engines/ultima/ultima4/map/location.cpp
Normal 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
|
||||
93
engines/ultima/ultima4/map/location.h
Normal file
93
engines/ultima/ultima4/map/location.h
Normal 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
|
||||
778
engines/ultima/ultima4/map/map.cpp
Normal file
778
engines/ultima/ultima4/map/map.cpp
Normal 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
|
||||
305
engines/ultima/ultima4/map/map.h
Normal file
305
engines/ultima/ultima4/map/map.h
Normal 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
|
||||
51
engines/ultima/ultima4/map/map_tile.cpp
Normal file
51
engines/ultima/ultima4/map/map_tile.cpp
Normal 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
|
||||
84
engines/ultima/ultima4/map/map_tile.h
Normal file
84
engines/ultima/ultima4/map/map_tile.h
Normal 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
|
||||
461
engines/ultima/ultima4/map/maploader.cpp
Normal file
461
engines/ultima/ultima4/map/maploader.cpp
Normal 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
|
||||
152
engines/ultima/ultima4/map/maploader.h
Normal file
152
engines/ultima/ultima4/map/maploader.h
Normal 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
|
||||
354
engines/ultima/ultima4/map/mapmgr.cpp
Normal file
354
engines/ultima/ultima4/map/mapmgr.cpp
Normal 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
|
||||
140
engines/ultima/ultima4/map/mapmgr.h
Normal file
140
engines/ultima/ultima4/map/mapmgr.h
Normal 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
|
||||
443
engines/ultima/ultima4/map/movement.cpp
Normal file
443
engines/ultima/ultima4/map/movement.cpp
Normal 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
|
||||
107
engines/ultima/ultima4/map/movement.h
Normal file
107
engines/ultima/ultima4/map/movement.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
300
engines/ultima/ultima4/map/shrine.cpp
Normal file
300
engines/ultima/ultima4/map/shrine.cpp
Normal 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
|
||||
107
engines/ultima/ultima4/map/shrine.h
Normal file
107
engines/ultima/ultima4/map/shrine.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
238
engines/ultima/ultima4/map/tile.cpp
Normal file
238
engines/ultima/ultima4/map/tile.cpp
Normal 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
|
||||
249
engines/ultima/ultima4/map/tile.h
Normal file
249
engines/ultima/ultima4/map/tile.h
Normal 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
|
||||
366
engines/ultima/ultima4/map/tileanim.cpp
Normal file
366
engines/ultima/ultima4/map/tileanim.cpp
Normal 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
|
||||
235
engines/ultima/ultima4/map/tileanim.h
Normal file
235
engines/ultima/ultima4/map/tileanim.h
Normal 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
|
||||
144
engines/ultima/ultima4/map/tilemap.cpp
Normal file
144
engines/ultima/ultima4/map/tilemap.cpp
Normal 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
|
||||
93
engines/ultima/ultima4/map/tilemap.h
Normal file
93
engines/ultima/ultima4/map/tilemap.h
Normal 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
|
||||
|
||||
|
||||
331
engines/ultima/ultima4/map/tileset.cpp
Normal file
331
engines/ultima/ultima4/map/tileset.cpp
Normal 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
|
||||
189
engines/ultima/ultima4/map/tileset.h
Normal file
189
engines/ultima/ultima4/map/tileset.h
Normal 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
|
||||
42
engines/ultima/ultima4/map/xml_map.h
Normal file
42
engines/ultima/ultima4/map/xml_map.h
Normal 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
|
||||
Reference in New Issue
Block a user