Initial commit

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

View File

@@ -0,0 +1,63 @@
/* 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/controllers/alpha_action_controller.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/gfx/screen.h"
namespace Ultima {
namespace Ultima4 {
bool AlphaActionController::keyPressed(int key) {
if (Common::isLower(key))
key = toupper(key);
if (key >= 'A' && key <= toupper(_lastValidLetter)) {
_value = key - 'A';
doneWaiting();
} else {
g_screen->screenMessage("\n%s", _prompt.c_str());
g_screen->update();
return KeyHandler::defaultHandler(key, nullptr);
}
return true;
}
void AlphaActionController::keybinder(KeybindingAction action) {
if (action == KEYBIND_ESCAPE) {
g_screen->screenMessage("\n");
_value = -1;
doneWaiting();
}
}
int AlphaActionController::get(char lastValidLetter, const Common::String &prompt, EventHandler *eh) {
if (!eh)
eh = eventHandler;
AlphaActionController ctrl(lastValidLetter, prompt);
eh->pushController(&ctrl);
return ctrl.waitFor();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,52 @@
/* 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_CONTROLLERS_ALPHA_ACTION_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_ALPHA_ACTION_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/events/event_handler.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to handle input for commands requiring a letter
* argument in the range 'a' - lastValidLetter.
*/
class AlphaActionController : public WaitableController<int> {
private:
char _lastValidLetter;
Common::String _prompt;
public:
AlphaActionController(char letter, const Common::String &p) :
WaitableController<int>(-1), _lastValidLetter(letter), _prompt(p) {}
bool keyPressed(int key) override;
void keybinder(KeybindingAction action) override;
static int get(char lastValidLetter, const Common::String &prompt, EventHandler *eh = nullptr);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,123 @@
/* 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/controllers/camp_controller.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
CampController::CampController() {
MapId id;
/* setup camp (possible, but not for-sure combat situation */
if (g_context->_location->_context & CTX_DUNGEON)
id = MAP_CAMP_DNG;
else
id = MAP_CAMP_CON;
_map = getCombatMap(mapMgr->get(id));
g_game->setMap(_map, true, nullptr, this);
}
void CampController::init(Creature *m) {
CombatController::init(m);
_camping = true;
}
void CampController::begin() {
// make sure everyone's asleep
for (int i = 0; i < g_context->_party->size(); i++)
g_context->_party->member(i)->putToSleep();
CombatController::begin();
g_music->camp();
g_screen->screenMessage("Resting...\n");
g_screen->screenDisableCursor();
EventHandler::wait_msecs(settings._campTime * 1000);
g_screen->screenEnableCursor();
/* Is the party ambushed during their rest? */
if (settings._campingAlwaysCombat || (xu4_random(8) == 0)) {
const Creature *m = creatureMgr->randomAmbushing();
g_music->playMapMusic();
g_screen->screenMessage("Ambushed!\n");
/* create an ambushing creature (so it leaves a chest) */
setCreature(g_context->_location->_prev->_map->addCreature(m, g_context->_location->_prev->_coords));
/* fill the creature table with creatures and place them */
fillCreatureTable(m);
placeCreatures();
/* creatures go first! */
finishTurn();
} else {
/* Wake everyone up! */
for (int i = 0; i < g_context->_party->size(); i++)
g_context->_party->member(i)->wakeUp();
/* Make sure we've waited long enough for camping to be effective */
bool healed = false;
if (((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) >= 0x10000) ||
(((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff) != g_ultima->_saveGame->_lastCamp))
healed = heal();
g_screen->screenMessage(healed ? "Party Healed!\n" : "No effect.\n");
g_ultima->_saveGame->_lastCamp = (g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff;
eventHandler->popController();
g_game->exitToParentMap();
g_music->fadeIn(CAMP_FADE_IN_TIME, true);
delete this;
}
}
void CampController::end(bool adjustKarma) {
// wake everyone up!
for (int i = 0; i < g_context->_party->size(); i++)
g_context->_party->member(i)->wakeUp();
CombatController::end(adjustKarma);
}
bool CampController::heal() {
// restore each party member to max mp, and restore some hp
bool healed = false;
for (int i = 0; i < g_context->_party->size(); i++) {
PartyMember *m = g_context->_party->member(i);
m->setMp(m->getMaxMp());
if ((m->getHp() < m->getMaxHp()) && m->heal(HT_CAMPHEAL))
healed = true;
}
return healed;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,46 @@
/* 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_CONTROLLERS_CAMP_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_CAMP_CONTROLLER_H
#include "ultima/ultima4/controllers/combat_controller.h"
namespace Ultima {
namespace Ultima4 {
#define CAMP_HEAL_INTERVAL 100 /* Number of moves before camping will heal the party */
class CampController : public CombatController {
public:
CampController();
void init(Creature *m) override;
void begin() override;
void end(bool adjustKarma) override;
private:
bool heal();
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,286 @@
/* 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_CONTROLLERS_COMBAT_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_COMBAT_CONTROLLER_H
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/player.h"
namespace Ultima {
namespace Ultima4 {
#define AREA_CREATURES 16
#define AREA_PLAYERS 8
class CombatMap;
class Creature;
class MoveEvent;
class Weapon;
typedef enum {
CA_ATTACK,
CA_CAST_SLEEP,
CA_ADVANCE,
CA_RANGED,
CA_FLEE,
CA_TELEPORT
} CombatAction;
/**
* CombatController class
*/
class CombatController : public Controller, public Observer<Party *, PartyEvent &>, public TurnCompleter {
protected:
CombatController();
public:
CombatController(CombatMap *m);
CombatController(MapId id);
virtual ~CombatController();
// Accessor Methods
bool isCombatController() const override {
return true;
}
/**
* Called when a controller is made active
*/
void setActive() override;
bool isCamping() const;
bool isWinOrLose() const;
Direction getExitDir() const;
byte getFocus() const;
CombatMap *getMap() const;
Creature *getCreature() const;
PartyMemberVector *getParty();
PartyMember *getCurrentPlayer();
void setExitDir(Direction d);
void setCreature(Creature *);
void setWinOrLose(bool worl = true);
void showCombatMessage(bool show = true);
// Methods
/**
* Initializes the combat controller with combat information
*/
virtual void init(Creature *m);
/**
* Initializes dungeon room combat
*/
void initDungeonRoom(int room, Direction from);
/**
* Apply tile effects to all creatures depending on what they're standing on
*/
void applyCreatureTileEffects();
/**
* Begin combat
*/
virtual void begin();
virtual void end(bool adjustKarma);
/**
* Fills the combat creature table with the creatures that the party will be facing.
* The creature table only contains *which* creatures will be encountered and
* *where* they are placed (by position in the table). Information like
* hit points and creature status will be created when the creature is actually placed
*/
void fillCreatureTable(const Creature *creature);
/**
* Generate the number of creatures in a group.
*/
int initialNumberOfCreatures(const Creature *creature) const;
/**
* Returns true if the player has won.
*/
bool isWon() const;
/**
* Returns true if the player has lost.
*/
bool isLost() const;
/**
* Performs all of the creature's actions
*/
void moveCreatures();
/**
* Places creatures on the map from the creature table and from the creature_start coords
*/
void placeCreatures();
/**
* Places the party members on the map
*/
void placePartyMembers();
/**
* Sets the active player for combat, showing which weapon they're weilding, etc.
*/
bool setActivePlayer(int player);
bool attackHit(Creature *attacker, Creature *defender);
virtual void awardLoot();
// attack functions
void attack(Direction dir = DIR_NONE, int distance = 0);
bool attackAt(const Coords &coords, PartyMember *attacker, int dir, int range, int distance);
bool rangedAttack(const Coords &coords, Creature *attacker);
void rangedMiss(const Coords &coords, Creature *attacker);
bool returnWeaponToOwner(const Coords &coords, int distance, int dir, const Weapon *weapon);
/**
* Static member functions
*/
static void attackFlash(const Coords &coords, MapTile tile, int timeFactor);
static void attackFlash(const Coords &coords, const Common::String &tilename, int timeFactor);
static void doScreenAnimationsWhilePausing(int timeFactor);
void keybinder(KeybindingAction action) override;
void finishTurn() override;
/**
* Move a party member during combat and display the appropriate messages
*/
void movePartyMember(MoveEvent &event);
void update(Party *party, PartyEvent &event) override;
// Properties
protected:
CombatMap *_map;
PartyMemberVector _party;
byte _focus;
const Creature *_creatureTable[AREA_CREATURES];
Creature *_creature;
bool _camping;
bool _forceStandardEncounterSize;
bool _placePartyOnMap;
bool _placeCreaturesOnMap;
bool _winOrLose;
bool _showMessage;
Direction _exitDir;
private:
CombatController(const CombatController &);
const CombatController &operator=(const CombatController &);
void init();
};
extern CombatController *g_combat;
/**
* CombatMap class
*/
class CombatMap : public Map {
public:
CombatMap();
~CombatMap() override {}
/**
* Returns a vector containing all of the creatures on the map
*/
CreatureVector getCreatures();
/**
* Returns a vector containing all of the party members on the map
*/
PartyMemberVector getPartyMembers();
/**
* Returns the party member at the given coords, if there is one,
* nullptr if otherwise.
*/
PartyMember *partyMemberAt(Coords coords);
/**
* Returns the creature at the given coords, if there is one,
* nullptr if otherwise.
*/
Creature *creatureAt(Coords coords);
/**
* Returns a valid combat map given the provided information
*/
static MapId mapForTile(const Tile *ground, const Tile *transport, Object *obj);
// Getters
bool isDungeonRoom() const {
return _dungeonRoom;
}
bool isAltarRoom() const {
return _altarRoom != VIRT_NONE;
}
bool isContextual() const {
return _contextual;
}
BaseVirtue getAltarRoom() const {
return _altarRoom;
}
// Setters
void setAltarRoom(BaseVirtue ar) {
_altarRoom = ar;
}
void setDungeonRoom(bool d) {
_dungeonRoom = d;
}
void setContextual(bool c) {
_contextual = c;
}
// Properties
protected:
bool _dungeonRoom;
BaseVirtue _altarRoom;
bool _contextual;
public:
Coords creature_start[AREA_CREATURES];
Coords player_start[AREA_PLAYERS];
};
bool isCombatMap(Map *punknown);
CombatMap *getCombatMap(Map *punknown = nullptr);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,82 @@
/* 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/controllers/controller.h"
#include "ultima/ultima4/events/event_handler.h"
#include "engines/engine.h"
namespace Ultima {
namespace Ultima4 {
Controller::Controller(int timerInterval) {
this->_timerInterval = timerInterval;
}
Controller::~Controller() {
}
bool Controller::notifyKeyPressed(int key) {
bool processed = KeyHandler::globalHandler(key);
if (!processed)
processed = keyPressed(key);
return processed;
}
bool Controller::notifyMousePress(const Common::Point &mousePos) {
return mousePressed(mousePos);
}
int Controller::getTimerInterval() {
return _timerInterval;
}
void Controller::setActive() {
// By default, only the Escape action is turned on for controllers,
// to allow the different sorts of input prompts to be aborted
MetaEngine::setKeybindingMode(KBMODE_MINIMAL);
}
void Controller::timerFired() {
}
void Controller::timerCallback(void *data) {
Controller *controller = static_cast<Controller *>(data);
controller->timerFired();
}
bool Controller::shouldQuit() const {
return g_engine->shouldQuit();
}
void Controller_startWait() {
eventHandler->run();
eventHandler->setControllerDone(false);
eventHandler->popController();
}
void Controller_endWait() {
eventHandler->setControllerDone();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,167 @@
/* 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_CONTROLLERS_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_CONTROLLER_H
#include "ultima/ultima4/metaengine.h"
namespace Ultima {
namespace Ultima4 {
/**
* A generic controller base class. Controllers are classes that
* contain the logic for responding to external events (e.g. keyboard,
* mouse, timers).
*/
class Controller {
public:
Controller(int timerInterval = 1);
virtual ~Controller();
/* methods for interacting with event manager */
virtual bool isCombatController() const {
return false;
}
/**
* The event manager will call this method to notify the active
* controller that a key has been pressed. The key will be passed on
* to the virtual keyPressed method.
*/
bool notifyKeyPressed(int key);
/**
* The event manager will call this method to notify that
* the left button was clicked
*/
bool notifyMousePress(const Common::Point &mousePos);
int getTimerInterval();
/**
* A simple adapter to make a timer callback into a controller method
* call.
*/
static void timerCallback(void *data);
/** control methods subclasses may want to override */
/**
* Called when a controller is made active
*/
virtual void setActive();
/**
* Key was pressed
*/
virtual bool keyPressed(int key) {
return false;
}
/**
* Mouse button was pressed
*/
virtual bool mousePressed(const Common::Point &mousePos) {
return false;
}
/**
* Handles keybinder actions
*/
virtual void keybinder(KeybindingAction action) {}
/**
* The default timerFired handler for a controller. By default,
* timers are ignored, but subclasses can override this method and it
* will be called every <interval> 1/4 seconds.
*/
virtual void timerFired();
/**
* Returns true if game should quit
*/
bool shouldQuit() const;
private:
int _timerInterval;
};
// helper functions for the waitable controller; they just avoid
// having eventhandler dependencies in this header file
void Controller_startWait();
void Controller_endWait();
/**
* Class template for controllers that can be "waited for".
* Subclasses should set the value variable and call doneWaiting when
* the controller has completed.
*/
template<class T>
class WaitableController : public Controller {
private:
bool _exitWhenDone;
T _defaultValue;
protected:
T _value;
void doneWaiting() {
if (_exitWhenDone)
Controller_endWait();
}
public:
WaitableController(T defaultValue) : _defaultValue(defaultValue),
_value(defaultValue), _exitWhenDone(false) {}
virtual T getValue() {
return shouldQuit() ? _defaultValue : _value;
}
virtual T waitFor() {
_exitWhenDone = true;
Controller_startWait();
return getValue();
}
/**
* Mouse button was pressed
*/
virtual bool mousePressed(const Common::Point &mousePos) {
// Treat mouse clicks as an abort
doneWaiting();
_value = _defaultValue;
return true;
}
};
class TurnCompleter {
public:
virtual ~TurnCompleter() {
}
virtual void finishTurn() = 0;
virtual void finishTurnAfterCombatEnds() {
finishTurn();
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,862 @@
/* 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/controllers/game_controller.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/debugger.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/death.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/map/shrine.h"
#include "ultima/ultima4/ultima4.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
GameController *g_game = nullptr;
static const MouseArea MOUSE_AREAS[] = {
{ 3, { { 8, 8 }, { 8, 184 }, { 96, 96 } }, MC_WEST, DIR_WEST },
{ 3, { { 8, 8 }, { 184, 8 }, { 96, 96 } }, MC_NORTH, DIR_NORTH },
{ 3, { { 184, 8 }, { 184, 184 }, { 96, 96 } }, MC_EAST, DIR_EAST },
{ 3, { { 8, 184 }, { 184, 184 }, { 96, 96 } }, MC_SOUTH, DIR_SOUTH },
{ 0, { { 0, 0 }, { 0, 0 }, { 0, 0 } }, MC_NORTH, DIR_NONE }
};
GameController::GameController() :
_mapArea(BORDER_WIDTH, BORDER_HEIGHT, VIEWPORT_W, VIEWPORT_H),
_paused(false), _combatFinished(false), _pausedTimer(0) {
g_game = this;
}
void GameController::initScreen() {
Image *screen = imageMgr->get("screen")->_image;
screen->fillRect(0, 0, screen->width(), screen->height(), 0, 0, 0);
g_screen->update();
}
void GameController::initScreenWithoutReloadingState() {
g_music->playMapMusic();
imageMgr->get(BKGD_BORDERS)->_image->draw(0, 0);
g_context->_stats->update(); /* draw the party stats */
g_screen->screenMessage("Press Alt-h for help\n");
g_screen->screenPrompt();
eventHandler->pushMouseAreaSet(MOUSE_AREAS);
eventHandler->setScreenUpdate(&gameUpdateScreen);
}
void GameController::init() {
initScreen();
// initialize the state of the global game context
g_context->_line = TEXT_AREA_H - 1;
g_context->_col = 0;
g_context->_stats = new StatsArea();
g_context->_moonPhase = 0;
g_context->_windDirection = DIR_NORTH;
g_context->_windCounter = 0;
g_context->_windLock = false;
g_context->_aura = new Aura();
g_context->_horseSpeed = 0;
g_context->_opacity = 1;
g_context->_lastCommandTime = g_system->getMillis();
g_context->_lastShip = nullptr;
}
void GameController::setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter) {
int viewMode;
LocationContext context;
int activePlayer = g_context->_party->getActivePlayer();
MapCoords coords;
if (!turnCompleter)
turnCompleter = this;
if (portal)
coords = portal->_start;
else
coords = MapCoords(map->_width / 2, map->_height / 2);
/* If we don't want to save the location, then just return to the previous location,
as there may still be ones in the stack we want to keep */
if (!saveLocation)
exitToParentMap();
switch (map->_type) {
case Map::WORLD:
context = CTX_WORLDMAP;
viewMode = VIEW_NORMAL;
break;
case Map::DUNGEON:
context = CTX_DUNGEON;
viewMode = VIEW_DUNGEON;
if (portal)
g_ultima->_saveGame->_orientation = DIR_EAST;
break;
case Map::COMBAT:
coords = MapCoords(-1, -1); /* set these to -1 just to be safe; we don't need them */
context = CTX_COMBAT;
viewMode = VIEW_NORMAL;
activePlayer = -1; /* different active player for combat, defaults to 'None' */
break;
case Map::SHRINE:
context = CTX_SHRINE;
viewMode = VIEW_NORMAL;
break;
case Map::CITY:
default:
context = CTX_CITY;
viewMode = VIEW_NORMAL;
break;
}
g_context->_location = new Location(coords, map, viewMode, context, turnCompleter, g_context->_location);
g_context->_location->addObserver(this);
g_context->_party->setActivePlayer(activePlayer);
#ifdef IOS_ULTIMA4
U4IOS::updateGameControllerContext(c->location->context);
#endif
/* now, actually set our new tileset */
_mapArea.setTileset(map->_tileSet);
if (isCity(map)) {
City *city = dynamic_cast<City *>(map);
assert(city);
city->addPeople();
}
}
int GameController::exitToParentMap() {
if (!g_context->_location)
return 0;
if (g_context->_location->_prev != nullptr) {
// Create the balloon for Hythloth
if (g_context->_location->_map->_id == MAP_HYTHLOTH)
createBalloon(g_context->_location->_prev->_map);
// free map info only if previous location was on a different map
if (g_context->_location->_prev->_map != g_context->_location->_map) {
g_context->_location->_map->_annotations->clear();
g_context->_location->_map->clearObjects();
/* quench the torch of we're on the world map */
if (g_context->_location->_prev->_map->isWorldMap())
g_context->_party->quenchTorch();
}
locationFree(&g_context->_location);
// restore the tileset to the one the current map uses
_mapArea.setTileset(g_context->_location->_map->_tileSet);
#ifdef IOS_ULTIMA4
U4IOS::updateGameControllerContext(c->location->context);
#endif
return 1;
}
return 0;
}
void GameController::finishTurn() {
g_context->_lastCommandTime = g_system->getMillis();
Creature *attacker = nullptr;
while (1) {
/* adjust food and moves */
g_context->_party->endTurn();
/* count down the aura, if there is one */
g_context->_aura->passTurn();
gameCheckHullIntegrity();
/* update party stats */
//c->stats->setView(STATS_PARTY_OVERVIEW);
g_screen->screenUpdate(&this->_mapArea, true, false);
g_screen->screenWait(1);
/* Creatures cannot spawn, move or attack while the avatar is on the balloon */
if (!g_context->_party->isFlying()) {
// apply effects from tile avatar is standing on
g_context->_party->applyEffect(g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->getEffect());
// WORKAROUND: This fixes infinite combat loop at the Shrine of Humility.
// I presume the original had code to show the game screen after combat
// without doing all the end turn logic
if (!_combatFinished) {
// Move creatures and see if something is attacking the avatar
attacker = g_context->_location->_map->moveObjects(g_context->_location->_coords);
// Something's attacking! Start combat!
if (attacker) {
gameCreatureAttack(attacker);
return;
}
// cleanup old creatures and spawn new ones
creatureCleanup();
checkRandomCreatures();
checkBridgeTrolls();
} else {
_combatFinished = false;
}
}
/* update map annotations */
g_context->_location->_map->_annotations->passTurn();
if (!g_context->_party->isImmobilized())
break;
if (g_context->_party->isDead()) {
g_death->start(0);
return;
} else {
g_screen->screenMessage("Zzzzzz\n");
g_screen->screenWait(4);
}
}
if (g_context->_location->_context == CTX_DUNGEON) {
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
if (g_context->_party->getTorchDuration() <= 0)
g_screen->screenMessage("It's Dark!\n");
else
g_context->_party->burnTorch();
/* handle dungeon traps */
if (dungeon->currentToken() == DUNGEON_TRAP) {
dungeonHandleTrap((TrapType)dungeon->currentSubToken());
// a little kludgey to have a second test for this
// right here. But without it you can survive an
// extra turn after party death and do some things
// that could cause a crash, like Hole up and Camp.
if (g_context->_party->isDead()) {
g_death->start(0);
return;
}
}
}
/* draw a prompt */
g_screen->screenPrompt();
//g_screen->screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
}
void GameController::flashTile(const Coords &coords, MapTile tile, int frames) {
g_context->_location->_map->_annotations->add(coords, tile, true);
g_screen->screenTileUpdate(&g_game->_mapArea, coords);
g_screen->screenWait(frames);
g_context->_location->_map->_annotations->remove(coords, tile);
g_screen->screenTileUpdate(&g_game->_mapArea, coords, false);
}
void GameController::flashTile(const Coords &coords, const Common::String &tilename, int timeFactor) {
Tile *tile = g_context->_location->_map->_tileSet->getByName(tilename);
assertMsg(tile, "no tile named '%s' found in tileset", tilename.c_str());
flashTile(coords, tile->getId(), timeFactor);
}
void GameController::update(Party *party, PartyEvent &event) {
int i;
switch (event._type) {
case PartyEvent::LOST_EIGHTH:
// inform a player he has lost zero or more eighths of avatarhood.
g_screen->screenMessage("\n %cThou hast lost\n an eighth!%c\n", FG_YELLOW, FG_WHITE);
break;
case PartyEvent::ADVANCED_LEVEL:
g_screen->screenMessage("\n%c%s\nThou art now Level %d%c\n", FG_YELLOW, event._player->getName().c_str(), event._player->getRealLevel(), FG_WHITE);
gameSpellEffect('r', -1, SOUND_MAGIC); // Same as resurrect spell
break;
case PartyEvent::STARVING:
g_screen->screenMessage("\n%cStarving!!!%c\n", FG_YELLOW, FG_WHITE);
/* FIXME: add sound effect here */
// 2 damage to each party member for starving!
for (i = 0; i < g_ultima->_saveGame->_members; i++)
g_context->_party->member(i)->applyDamage(2);
break;
default:
break;
}
}
void GameController::update(Location *location, MoveEvent &event) {
switch (location->_map->_type) {
case Map::DUNGEON:
avatarMovedInDungeon(event);
break;
case Map::COMBAT: {
// FIXME: let the combat controller handle it
CombatController *ctl = dynamic_cast<CombatController *>(eventHandler->getController());
assert(ctl);
ctl->movePartyMember(event);
break;
}
default:
avatarMoved(event);
break;
}
}
void GameController::setActive() {
// The game controller has the keybindings enabled
MetaEngine::setKeybindingMode(KBMODE_NORMAL);
}
void GameController::keybinder(KeybindingAction action) {
MetaEngine::executeAction(action);
}
bool GameController::mousePressed(const Common::Point &mousePos) {
const MouseArea *area = eventHandler->mouseAreaForPoint(mousePos.x, mousePos.y);
if (area) {
keybinder(KEYBIND_INTERACT);
return true;
}
return false;
}
void GameController::initMoons() {
int trammelphase = g_ultima->_saveGame->_trammelPhase,
feluccaphase = g_ultima->_saveGame->_feluccaPhase;
assertMsg(g_context != nullptr, "Game context doesn't exist!");
assertMsg(g_ultima->_saveGame != nullptr, "Savegame doesn't exist!");
//assertMsg(mapIsWorldMap(c->location->map) && c->location->viewMode == VIEW_NORMAL, "Can only call gameInitMoons() from the world map!");
g_ultima->_saveGame->_trammelPhase = g_ultima->_saveGame->_feluccaPhase = 0;
g_context->_moonPhase = 0;
while ((g_ultima->_saveGame->_trammelPhase != trammelphase) ||
(g_ultima->_saveGame->_feluccaPhase != feluccaphase))
updateMoons(false);
}
void GameController::updateMoons(bool showmoongates) {
int realMoonPhase,
oldTrammel,
trammelSubphase;
const Coords *gate;
if (g_context->_location->_map->isWorldMap()) {
oldTrammel = g_ultima->_saveGame->_trammelPhase;
if (++g_context->_moonPhase >= MOON_PHASES * MOON_SECONDS_PER_PHASE * 4)
g_context->_moonPhase = 0;
trammelSubphase = g_context->_moonPhase % (MOON_SECONDS_PER_PHASE * 4 * 3);
realMoonPhase = (g_context->_moonPhase / (4 * MOON_SECONDS_PER_PHASE));
g_ultima->_saveGame->_trammelPhase = realMoonPhase / 3;
g_ultima->_saveGame->_feluccaPhase = realMoonPhase % 8;
if (g_ultima->_saveGame->_trammelPhase > 7)
g_ultima->_saveGame->_trammelPhase = 7;
if (showmoongates) {
/* update the moongates if trammel changed */
if (trammelSubphase == 0) {
gate = g_moongates->getGateCoordsForPhase(oldTrammel);
if (gate)
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate)
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
} else if (trammelSubphase == 1) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
}
} else if (trammelSubphase == 2) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
}
} else if (trammelSubphase == 3) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
}
} else if ((trammelSubphase > 3) && (trammelSubphase < (MOON_SECONDS_PER_PHASE * 4 * 3) - 3)) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
}
} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 3) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
}
} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 2) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
}
} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 1) {
gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
if (gate) {
g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
}
}
}
}
}
void GameController::avatarMoved(MoveEvent &event) {
if (event._userEvent) {
// is filterMoveMessages even used? it doesn't look like the option is hooked up in the configuration menu
if (!settings._filterMoveMessages) {
switch (g_context->_transportContext) {
case TRANSPORT_FOOT:
case TRANSPORT_HORSE:
g_screen->screenMessage("%s\n", getDirectionName(event._dir));
break;
case TRANSPORT_SHIP:
if (event._result & MOVE_TURNED)
g_screen->screenMessage("Turn %s!\n", getDirectionName(event._dir));
else if (event._result & MOVE_SLOWED)
g_screen->screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
else
g_screen->screenMessage("Sail %s!\n", getDirectionName(event._dir));
break;
case TRANSPORT_BALLOON:
g_screen->screenMessage("%cDrift Only!%c\n", FG_GREY, FG_WHITE);
break;
default:
error("bad transportContext %d in avatarMoved()", g_context->_transportContext);
}
}
/* movement was blocked */
if (event._result & MOVE_BLOCKED) {
/* if shortcuts are enabled, try them! */
if (settings._shortcutCommands) {
MapCoords new_coords = g_context->_location->_coords;
MapTile *tile;
new_coords.move(event._dir, g_context->_location->_map);
tile = g_context->_location->_map->tileAt(new_coords, WITH_OBJECTS);
if (tile->getTileType()->isDoor()) {
g_debugger->openAt(new_coords);
event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
} else if (tile->getTileType()->isLockedDoor()) {
g_debugger->jimmyAt(new_coords);
event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
} /*else if (mapPersonAt(c->location->map, new_coords) != nullptr) {
talkAtCoord(newx, newy, 1, nullptr);
event.result = MOVE_SUCCEEDED | MOVE_END_TURN;
}*/
}
/* if we're still blocked */
if ((event._result & MOVE_BLOCKED) && !settings._filterMoveMessages) {
soundPlay(SOUND_BLOCKED, false);
g_screen->screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
}
} else if (g_context->_transportContext == TRANSPORT_FOOT || g_context->_transportContext == TRANSPORT_HORSE) {
/* movement was slowed */
if (event._result & MOVE_SLOWED) {
soundPlay(SOUND_WALK_SLOWED);
g_screen->screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
} else {
soundPlay(SOUND_WALK_NORMAL);
}
}
}
/* exited map */
if (event._result & MOVE_EXIT_TO_PARENT) {
g_screen->screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
exitToParentMap();
g_music->playMapMusic();
}
/* things that happen while not on board the balloon */
if (g_context->_transportContext & ~TRANSPORT_BALLOON)
checkSpecialCreatures(event._dir);
/* things that happen while on foot or horseback */
if ((g_context->_transportContext & TRANSPORT_FOOT_OR_HORSE) &&
!(event._result & (MOVE_SLOWED | MOVE_BLOCKED))) {
if (checkMoongates())
event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_END_TURN);
}
}
void GameController::avatarMovedInDungeon(MoveEvent &event) {
Direction realDir = dirNormalize((Direction)g_ultima->_saveGame->_orientation, event._dir);
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dungeon);
if (!settings._filterMoveMessages) {
if (event._userEvent) {
if (event._result & MOVE_TURNED) {
if (dirRotateCCW((Direction)g_ultima->_saveGame->_orientation) == realDir)
g_screen->screenMessage("Turn Left\n");
else
g_screen->screenMessage("Turn Right\n");
} else {
// Show 'Advance' or 'Retreat' in dungeons
g_screen->screenMessage("%s\n", realDir == g_ultima->_saveGame->_orientation ? "Advance" : "Retreat");
}
}
if (event._result & MOVE_BLOCKED)
g_screen->screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
}
// If we're exiting the map, do this
if (event._result & MOVE_EXIT_TO_PARENT) {
g_screen->screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
exitToParentMap();
g_music->playMapMusic();
}
// Check to see if we're entering a dungeon room
if (event._result & MOVE_SUCCEEDED) {
if (dungeon->currentToken() == DUNGEON_ROOM) {
int room = (int)dungeon->currentSubToken(); // Get room number
/**
* recalculate room for the abyss -- there are 16 rooms for every 2 levels,
* each room marked with 0xD* where (* == room number 0-15).
* for levels 1 and 2, there are 16 rooms, levels 3 and 4 there are 16 rooms, etc.
*/
if (g_context->_location->_map->_id == MAP_ABYSS)
room = (0x10 * (g_context->_location->_coords.z / 2)) + room;
Dungeon *dng = dynamic_cast<Dungeon *>(g_context->_location->_map);
assert(dng);
dng->_currentRoom = room;
// Set the map and start combat!
CombatController *cc = new CombatController(dng->_roomMaps[room]);
cc->initDungeonRoom(room, dirReverse(realDir));
cc->begin();
}
}
}
void GameController::timerFired() {
if (_pausedTimer > 0) {
_pausedTimer--;
if (_pausedTimer <= 0) {
_pausedTimer = 0;
_paused = false; /* unpause the game */
}
}
if (!_paused && !_pausedTimer) {
if (++g_context->_windCounter >= MOON_SECONDS_PER_PHASE * 4) {
if (xu4_random(4) == 1 && !g_context->_windLock)
g_context->_windDirection = dirRandomDir(MASK_DIR_ALL);
g_context->_windCounter = 0;
}
/* balloon moves about 4 times per second */
if ((g_context->_transportContext == TRANSPORT_BALLOON) &&
g_context->_party->isFlying()) {
g_context->_location->move(dirReverse((Direction) g_context->_windDirection), false);
}
updateMoons(true);
g_screen->screenCycle();
// Check for any right-button mouse movement
KeybindingAction action = eventHandler->getAction();
if (action != KEYBIND_NONE)
keybinder(action);
// Do game udpates to the screen, like tile animations
gameUpdateScreen();
/*
* force pass if no commands within last 20 seconds
*/
Controller *controller = eventHandler->getController();
if (controller != nullptr && (eventHandler->getController() == g_game ||
dynamic_cast<CombatController *>(eventHandler->getController()) != nullptr) &&
gameTimeSinceLastCommand() > 20) {
/* pass the turn, and redraw the text area so the prompt is shown */
MetaEngine::executeAction(KEYBIND_PASS);
g_screen->screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
}
}
}
void GameController::checkSpecialCreatures(Direction dir) {
int i;
Object *obj;
static const struct {
int x, y;
Direction dir;
} pirateInfo[] = {
{ 224, 220, DIR_EAST }, /* N'M" O'A" */
{ 224, 228, DIR_EAST }, /* O'E" O'A" */
{ 226, 220, DIR_EAST }, /* O'E" O'C" */
{ 227, 228, DIR_EAST }, /* O'E" O'D" */
{ 228, 227, DIR_SOUTH }, /* O'D" O'E" */
{ 229, 225, DIR_SOUTH }, /* O'B" O'F" */
{ 229, 223, DIR_NORTH }, /* N'P" O'F" */
{ 228, 222, DIR_NORTH } /* N'O" O'E" */
};
/*
* if heading east into pirates cove (O'A" N'N"), generate pirate
* ships
*/
if (dir == DIR_EAST &&
g_context->_location->_coords.x == 0xdd &&
g_context->_location->_coords.y == 0xe0) {
for (i = 0; i < 8; i++) {
obj = g_context->_location->_map->addCreature(creatureMgr->getById(PIRATE_ID), MapCoords(pirateInfo[i].x, pirateInfo[i].y));
obj->setDirection(pirateInfo[i].dir);
}
}
/*
* if heading south towards the shrine of humility, generate
* daemons unless horn has been blown
*/
if (dir == DIR_SOUTH &&
g_context->_location->_coords.x >= 229 &&
g_context->_location->_coords.x < 234 &&
g_context->_location->_coords.y >= 212 &&
g_context->_location->_coords.y < 217 &&
*g_context->_aura != Aura::HORN) {
for (i = 0; i < 8; i++)
g_context->_location->_map->addCreature(creatureMgr->getById(DAEMON_ID), MapCoords(231, g_context->_location->_coords.y + 1, g_context->_location->_coords.z));
}
}
bool GameController::checkMoongates() {
Coords dest;
if (g_moongates->findActiveGateAt(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase, g_context->_location->_coords, dest)) {
gameSpellEffect(-1, -1, SOUND_MOONGATE); // Default spell effect (screen inversion without 'spell' sound effects)
if (g_context->_location->_coords != dest) {
g_context->_location->_coords = dest;
gameSpellEffect(-1, -1, SOUND_MOONGATE); // Again, after arriving
}
if (g_moongates->isEntryToShrineOfSpirituality(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase)) {
Shrine *shrine_spirituality;
shrine_spirituality = dynamic_cast<Shrine *>(mapMgr->get(MAP_SHRINE_SPIRITUALITY));
assert(shrine_spirituality);
if (!g_context->_party->canEnterShrine(VIRT_SPIRITUALITY))
return true;
setMap(shrine_spirituality, 1, nullptr);
g_music->playMapMusic();
shrine_spirituality->enter();
}
return true;
}
return false;
}
void GameController::creatureCleanup() {
ObjectDeque::iterator i;
Map *map = g_context->_location->_map;
for (i = map->_objects.begin(); i != map->_objects.end();) {
Object *obj = *i;
MapCoords o_coords = obj->getCoords();
if ((obj->getType() == Object::CREATURE) && (o_coords.z == g_context->_location->_coords.z) &&
o_coords.distance(g_context->_location->_coords, g_context->_location->_map) > MAX_CREATURE_DISTANCE) {
/* delete the object and remove it from the map */
i = map->removeObject(i);
} else {
i++;
}
}
}
void GameController::checkRandomCreatures() {
int canSpawnHere = g_context->_location->_map->isWorldMap() || g_context->_location->_context & CTX_DUNGEON;
#ifdef IOS_ULTIMA4
int spawnDivisor = c->location->context & CTX_DUNGEON ? (53 - (c->location->coords.z << 2)) : 53;
#else
int spawnDivisor = g_context->_location->_context & CTX_DUNGEON ? (32 - (g_context->_location->_coords.z << 2)) : 32;
#endif
/* If there are too many creatures already,
or we're not on the world map, don't worry about it! */
if (!canSpawnHere ||
g_context->_location->_map->getNumberOfCreatures() >= MAX_CREATURES_ON_MAP ||
xu4_random(spawnDivisor) != 0)
return;
// If combat is turned off, then don't spawn any creator
if (g_debugger->_disableCombat)
return;
gameSpawnCreature(nullptr);
}
void GameController::checkBridgeTrolls() {
const Tile *bridge = g_context->_location->_map->_tileSet->getByName("bridge");
if (!bridge)
return;
// TODO: CHEST: Make a user option to not make chests block bridge trolls
if (!g_context->_location->_map->isWorldMap() ||
g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)->_id != bridge->getId() ||
xu4_random(8) != 0)
return;
g_screen->screenMessage("\nBridge Trolls!\n");
Creature *m = g_context->_location->_map->addCreature(creatureMgr->getById(TROLL_ID), g_context->_location->_coords);
CombatController *cc = new CombatController(MAP_BRIDGE_CON);
cc->init(m);
cc->begin();
}
bool GameController::createBalloon(Map *map) {
ObjectDeque::iterator i;
/* see if the balloon has already been created (and not destroyed) */
for (auto *obj : map->_objects) {
if (obj->getTile().getTileType()->isBalloon())
return false;
}
const Tile *balloon = map->_tileSet->getByName("balloon");
assertMsg(balloon, "no balloon tile found in tileset");
map->addObject(balloon->getId(), balloon->getId(), map->getLabel("balloon"));
return true;
}
void GameController::attack(Direction dir) {
g_screen->screenMessage("Attack: ");
if (g_context->_party->isFlying()) {
g_screen->screenMessage("\n%cDrift only!%c\n", FG_GREY, FG_WHITE);
return;
}
if (dir == DIR_NONE)
dir = gameGetDirection();
if (dir == DIR_NONE) {
g_screen->screenMessage("\n");
return;
}
Std::vector<Coords> path = gameGetDirectionalActionPath(
MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
1, 1, nullptr, true);
for (const auto &coords : path) {
if (attackAt(coords))
return;
}
g_screen->screenMessage("%cNothing to Attack!%c\n", FG_GREY, FG_WHITE);
}
bool GameController::attackAt(const Coords &coords) {
Object *under;
const Tile *ground;
Creature *m;
m = dynamic_cast<Creature *>(g_context->_location->_map->objectAt(coords));
// Nothing attackable: move on to next tile
if (m == nullptr || !m->isAttackable())
return false;
// Attack successful
/// TODO: CHEST: Make a user option to not make chests change battlefield
/// map (1 of 2)
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
if (!ground->isChest()) {
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS);
if ((under = g_context->_location->_map->objectAt(g_context->_location->_coords)) &&
under->getTile().getTileType()->isShip())
ground = under->getTile().getTileType();
}
// You're attacking a townsperson! Alert the guards!
if ((m->getType() == Object::PERSON) && (m->getMovementBehavior() != MOVEMENT_ATTACK_AVATAR))
g_context->_location->_map->alertGuards();
// Not good karma to be killing the innocent. Bad avatar!
if (m->isGood() || /* attacking a good creature */
/* attacking a docile (although possibly evil) person in town */
((m->getType() == Object::PERSON) && (m->getMovementBehavior() != MOVEMENT_ATTACK_AVATAR)))
g_context->_party->adjustKarma(KA_ATTACKED_GOOD);
CombatController *cc = new CombatController(CombatMap::mapForTile(ground, g_context->_party->getTransport().getTileType(), m));
cc->init(m);
cc->begin();
return false;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,195 @@
/* 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_CONTROLLERS_GAME_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_GAME_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/views/tileview.h"
namespace Ultima {
namespace Ultima4 {
/**
* The main game controller that handles basic game flow and keypresses.
*
* @todo
* <ul>
* <li>separate the dungeon specific stuff into another class (subclass?)</li>
* </ul>
*/
class GameController : public Controller, public Observer<Party *, PartyEvent &>, public Observer<Location *, MoveEvent &>,
public TurnCompleter {
private:
/**
* Handles feedback after avatar moved during normal 3rd-person view.
*/
void avatarMoved(MoveEvent &event);
/**
* Handles feedback after moving the avatar in the 3-d dungeon view.
*/
void avatarMovedInDungeon(MoveEvent &event);
/**
* Removes creatures from the current map if they are too far away from the avatar
*/
void creatureCleanup();
/**
* Handles trolls under bridges
*/
void checkBridgeTrolls();
/**
* Checks creature conditions and spawns new creatures if necessary
*/
void checkRandomCreatures();
/**
* Checks for valid conditions and handles
* special creatures guarding the entrance to the
* abyss and to the shrine of spirituality
*/
void checkSpecialCreatures(Direction dir);
/**
* Checks for and handles when the avatar steps on a moongate
*/
bool checkMoongates();
/**
* Creates the balloon near Hythloth, but only if the balloon doesn't already exists somewhere
*/
bool createBalloon(Map *map);
/**
* Attempts to attack a creature at map coordinates x,y. If no
* creature is present at that point, zero is returned.
*/
bool attackAt(const Coords &coords);
public:
/**
* Show an attack flash at x, y on the current map.
* This is used for 'being hit' or 'being missed'
* by weapons, cannon fire, spells, etc.
*/
static void flashTile(const Coords &coords, MapTile tile, int timeFactor);
static void flashTile(const Coords &coords, const Common::String &tilename, int timeFactor);
static void doScreenAnimationsWhilePausing(int timeFactor);
public:
TileView _mapArea;
bool _paused;
int _pausedTimer;
bool _combatFinished;
public:
GameController();
/* controller functions */
/**
* Called when a controller is made active
*/
void setActive() override;
/**
* Keybinder actions
*/
void keybinder(KeybindingAction action) override;
/**
* Mouse button was pressed
*/
bool mousePressed(const Common::Point &mousePos) override;
/**
* This function is called every quarter second.
*/
void timerFired() override;
/* main game functions */
void init();
void initScreen();
void initScreenWithoutReloadingState();
void setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter = nullptr);
/**
* Exits the current map and location and returns to its parent location
* This restores all relevant information from the previous location,
* such as the map, map position, etc. (such as exiting a city)
**/
int exitToParentMap();
/**
* Finishes the game turn after combat ends. It suppresses monster
* movement to prevent issues, such as infinite combat at the
* Shrine of Humility
*/
void finishTurnAfterCombatEnds() override {
_combatFinished = true;
finishTurn();
}
/**
* Terminates a game turn. This performs the post-turn housekeeping
* tasks like adjusting the party's food, incrementing the number of
* moves, etc.
*/
void finishTurn() override;
/**
* Provide feedback to user after a party event happens.
*/
void update(Party *party, PartyEvent &event) override;
/**
* Provide feedback to user after a movement event happens.
*/
void update(Location *location, MoveEvent &event) override;
/**
* Initializes the moon state according to the savegame file. This method of
* initializing the moons (rather than just setting them directly) is necessary
* to make sure trammel and felucca stay in sync
*/
void initMoons();
/**
* Updates the phases of the moons and shows
* the visual moongates on the map, if desired
*/
void updateMoons(bool showmoongates);
void attack(Direction dir = DIR_NONE);
};
extern GameController *g_game;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,175 @@
/* 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/controllers/inn_controller.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/mapmgr.h"
namespace Ultima {
namespace Ultima4 {
InnController::InnController() {
_map = nullptr;
/*
* Normally in cities, only one opponent per encounter; inn's
* override this to get the regular encounter size.
*/
_forceStandardEncounterSize = true;
}
void InnController::begin() {
/* first, show the avatar before sleeping */
gameUpdateScreen();
/* in the original, the vendor music plays straight through sleeping */
if (settings._enhancements)
g_music->fadeOut(INN_FADE_OUT_TIME); /* Fade volume out to ease into rest */
EventHandler::wait_msecs(INN_FADE_OUT_TIME);
/* show the sleeping avatar */
g_context->_party->setTransport(g_context->_location->_map->_tileSet->getByName("corpse")->getId());
gameUpdateScreen();
g_screen->screenDisableCursor();
EventHandler::wait_msecs(settings._innTime * 1000);
g_screen->screenEnableCursor();
/* restore the avatar to normal */
g_context->_party->setTransport(g_context->_location->_map->_tileSet->getByName("avatar")->getId());
gameUpdateScreen();
/* the party is always healed */
heal();
/* Is there a special encounter during your stay? */
// mwinterrowd suggested code, based on u4dos
if (g_context->_party->member(0)->isDead()) {
maybeMeetIsaac();
} else {
if (xu4_random(8) != 0) {
maybeMeetIsaac();
} else {
maybeAmbush();
}
}
g_screen->screenMessage("\nMorning!\n");
g_screen->screenPrompt();
g_music->fadeIn(INN_FADE_IN_TIME, true);
}
bool InnController::heal() {
// restore each party member to max mp, and restore some hp
bool healed = false;
for (int i = 0; i < g_context->_party->size(); i++) {
PartyMember *m = g_context->_party->member(i);
m->setMp(m->getMaxMp());
if ((m->getHp() < m->getMaxHp()) && m->heal(HT_INNHEAL))
healed = true;
}
return healed;
}
void InnController::maybeMeetIsaac() {
// Does Isaac the Ghost pay a visit to the Avatar?
// if ((location == skara_brae) && (random(4) = 0) {
// // create Isaac the Ghost
// }
if ((g_context->_location->_map->_id == 11) && (xu4_random(4) == 0)) {
City *city = dynamic_cast<City *>(g_context->_location->_map);
assert(city);
if (city->_extraDialogues.size() == 1 &&
city->_extraDialogues[0]->getName() == "Isaac") {
Coords coords(27, xu4_random(3) + 10, g_context->_location->_coords.z);
// If Isaac is already around, just bring him back to the inn
for (ObjectDeque::iterator i = g_context->_location->_map->_objects.begin();
i != g_context->_location->_map->_objects.end();
i++) {
Person *p = dynamic_cast<Person *>(*i);
if (p && p->getName() == "Isaac") {
p->setCoords(coords);
return;
}
}
// Otherwise, we need to create Isaac
Person *isaac = new Person(creatureMgr->getById(GHOST_ID)->getTile());
isaac->setMovementBehavior(MOVEMENT_WANDER);
isaac->setDialogue(city->_extraDialogues[0]);
isaac->getStart() = coords;
isaac->setPrevTile(isaac->getTile());
// Add Isaac near the Avatar
city->addPerson(isaac);
delete isaac;
}
}
}
void InnController::maybeAmbush() {
if (settings._innAlwaysCombat || (xu4_random(8) == 0)) {
MapId mapid;
Creature *creature;
bool showMessage = true;
/* Rats seem much more rare than meeting rogues in the streets */
if (xu4_random(4) == 0) {
/* Rats! */
mapid = MAP_BRICK_CON;
creature = g_context->_location->_map->addCreature(creatureMgr->getById(RAT_ID), g_context->_location->_coords);
} else {
/* While strolling down the street, attacked by rogues! */
mapid = MAP_INN_CON;
creature = g_context->_location->_map->addCreature(creatureMgr->getById(ROGUE_ID), g_context->_location->_coords);
g_screen->screenMessage("\nIn the middle of the night while out on a stroll...\n\n");
showMessage = false;
}
_map = getCombatMap(mapMgr->get(mapid));
g_game->setMap(_map, true, nullptr, this);
init(creature);
showCombatMessage(showMessage);
CombatController::begin();
}
}
void InnController::awardLoot() {
// never get a chest from inn combat
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,46 @@
/* 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_CONTROLLERS_INN_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_INN_CONTROLLER_H
#include "ultima/ultima4/controllers/combat_controller.h"
namespace Ultima {
namespace Ultima4 {
class InnController : public CombatController {
public:
InnController();
void begin() override;
void awardLoot() override;
private:
bool heal();
void maybeMeetIsaac();
void maybeAmbush();
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
/* 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_CONTROLLERS_INTRO_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_INTRO_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/views/textview.h"
#include "ultima/ultima4/views/imageview.h"
#include "ultima/ultima4/views/tileview.h"
#include "common/file.h"
namespace Ultima {
namespace Ultima4 {
class IntroObjectState;
class Tile;
/**
* Binary data loaded from the U4DOS title.exe file.
*/
class IntroBinData {
private:
// disallow assignments, copy construction
IntroBinData(const IntroBinData &);
const IntroBinData &operator=(const IntroBinData &);
void openFile(Common::File &f, const Common::String &name);
public:
IntroBinData();
~IntroBinData();
bool load();
Std::vector<MapTile> _introMap;
byte *_sigData;
byte *_scriptTable;
Tile **_baseTileTable;
byte *_beastie1FrameTable;
byte *_beastie2FrameTable;
Std::vector<Common::String> _introText;
Std::vector<Common::String> _introQuestions;
Std::vector<Common::String> _introGypsy;
};
/**
* Controls the title animation sequences, including the traditional
* plotted "Lord British" signature, the pixelized fade-in of the
* "Ultima IV" game title, as well as the other more simple animated
* features, followed by the traditional animated map and "Journey
* Onward" menu, plus the xU4-specific configuration menu.
*
* @todo
* <ul>
* <li>make initial menu a Menu too</li>
* <li>get rid of mode and switch(mode) statements</li>
* <li>get rid global intro instance -- should only need to be accessed from u4.cpp</li>
* </ul>
*/
class IntroController : public Controller, public Observer<Menu *, MenuEvent &> {
public:
IntroController();
~IntroController() override;
/**
* Initializes intro state and loads in introduction graphics, text
* and map data from title.exe.
*/
bool init();
bool hasInitiatedNewGame();
/**
* Frees up data not needed after introduction.
*/
void deleteIntro();
/**
* Handles keystrokes during the introduction.
*/
bool keyPressed(int key) override;
/**
* Mouse button was pressed
*/
bool mousePressed(const Common::Point &mousePos) override;
byte *getSigData();
/**
* Paints the screen.
*/
void updateScreen();
/**
* Timer callback for the intro sequence. Handles animating the intro
* map, the beasties, etc..
*/
void timerFired() override;
/**
* Preload map tiles
*/
void preloadMap();
/**
* Update the screen when an observed menu is reset or has an item
* activated.
* TODO: Reduce duped code.
*/
void update(Menu *menu, MenuEvent &event) override;
void updateConfMenu(MenuEvent &event);
void updateVideoMenu(MenuEvent &event);
void updateGfxMenu(MenuEvent &event);
void updateSoundMenu(MenuEvent &event);
void updateInputMenu(MenuEvent &event);
void updateSpeedMenu(MenuEvent &event);
void updateGameplayMenu(MenuEvent &event);
void updateInterfaceMenu(MenuEvent &event);
//
// Title methods
//
/**
* Initialize the title elements
*/
void initTitles();
/**
* Update the title element, drawing the appropriate frame of animation
*/
bool updateTitle();
private:
/**
* Draws the small map on the intro screen.
*/
void drawMap();
void drawMapStatic();
void drawMapAnimated();
/**
* Draws the animated beasts in the upper corners of the screen.
*/
void drawBeasties();
/**
* Animates the "beasties". The animate intro image is made up frames
* for the two creatures in the top left and top right corners of the
* screen. This function draws the frame for the given beastie on the
* screen. vertoffset is used lower the creatures down from the top
* of the screen.
*/
void drawBeastie(int beast, int vertoffset, int frame);
/**
* Animates the moongate in the tree intro image. There are two
* overlays in the part of the image normally covered by the text. If
* the frame parameter is "moongate", the moongate overlay is painted
* over the image. If frame is "items", the second overlay is
* painted: the circle without the moongate, but with a small white
* dot representing the anhk and history book.
*/
void animateTree(const Common::String &frame);
/**
* Draws the cards in the character creation sequence with the gypsy.
*/
void drawCard(int pos, int card);
/**
* Draws the beads in the abacus during the character creation sequence
*/
void drawAbacusBeads(int row, int selectedVirtue, int rejectedVirtue);
/**
* Initializes the question tree. The tree starts off with the first
* eight entries set to the numbers 0-7 in a random order.
*/
void initQuestionTree();
/**
* Updates the question tree with the given answer, and advances to
* the next round.
* @return true if all questions have been answered, false otherwise
*/
bool doQuestion(int answer);
/**
* Build the initial avatar player record from the answers to the
* gypsy's questions.
*/
void initPlayers(SaveGame *saveGame);
/**
* Get the text for the question giving a choice between virtue v1 and
* virtue v2 (zero based virtue index, starting at honesty).
*/
Common::String getQuestion(int v1, int v2);
#ifdef IOS_ULTIMA4
public:
/**
* Try to put the intro music back at just the correct moment on iOS;
* don't play it at the very beginning.
*/
void tryTriggerIntroMusic();
#endif
/**
* Initiate a new savegame by reading the name, sex, then presenting a
* series of questions to determine the class of the new character.
*/
void initiateNewGame();
void finishInitiateGame(const Common::String &nameBuffer, SexType sex);
/**
* Starts the gypsys questioning that eventually determines the new
* characters class.
*/
void startQuestions();
void showStory();
/**
* Starts the game.
*/
void journeyOnward();
/**
* Shows an about box.
*/
void about();
#ifdef IOS_ULTIMA4
private:
#endif
/**
* Shows text in the question area.
*/
void showText(const Common::String &text);
/**
* Run a menu and return when the menu has been closed. Screen
* updates are handled by observing the menu.
*/
void runMenu(Menu *menu, TextView *view, bool withBeasties);
/**
* The states of the intro.
*/
enum {
INTRO_TITLES, // displaying the animated intro titles
INTRO_MAP, // displaying the animated intro map
INTRO_MENU, // displaying the main menu: journey onward, etc.
INTRO_ABOUT
} _mode;
enum MenuConstants {
MI_CONF_VIDEO,
MI_CONF_SOUND,
MI_CONF_INPUT,
MI_CONF_SPEED,
MI_CONF_GAMEPLAY,
MI_CONF_INTERFACE,
MI_CONF_01,
MI_VIDEO_CONF_GFX,
MI_VIDEO_02,
MI_VIDEO_03,
MI_VIDEO_07,
MI_VIDEO_08,
MI_GFX_SCHEME,
MI_GFX_TILE_TRANSPARENCY,
MI_GFX_TILE_TRANSPARENCY_SHADOW_SIZE,
MI_GFX_TILE_TRANSPARENCY_SHADOW_OPACITY,
MI_GFX_RETURN,
MI_SOUND_03,
MI_INPUT_03,
MI_SPEED_01,
MI_SPEED_02,
MI_SPEED_03,
MI_SPEED_04,
MI_SPEED_05,
MI_SPEED_06,
MI_SPEED_07,
MI_GAMEPLAY_01,
MI_GAMEPLAY_02,
MI_GAMEPLAY_03,
MI_GAMEPLAY_04,
MI_GAMEPLAY_05,
MI_GAMEPLAY_06,
MI_INTERFACE_01,
MI_INTERFACE_02,
MI_INTERFACE_03,
MI_INTERFACE_04,
MI_INTERFACE_05,
MI_INTERFACE_06,
USE_SETTINGS = 0xFE,
CANCEL = 0xFF
};
ImageView _backgroundArea;
TextView _menuArea;
TextView _extendedMenuArea;
TextView _questionArea;
TileView _mapArea;
Image *_mapScreen;
// menus
Menu _mainMenu;
Menu _confMenu;
Menu _videoMenu;
Menu _gfxMenu;
Menu _soundMenu;
Menu _inputMenu;
Menu _speedMenu;
Menu _gameplayMenu;
Menu _interfaceMenu;
// data loaded in from title.exe
IntroBinData *_binData;
// additional introduction state data
Common::String _errorMessage;
int _answerInd;
int _questionRound;
int _questionTree[15];
int _beastie1Cycle;
int _beastie2Cycle;
int _beastieOffset;
bool _beastiesVisible;
int _sleepCycles;
int _scrPos; /* current position in the script table */
IntroObjectState *_objectStateTable;
bool _justInitiatedNewGame;
Common::String _profileName;
bool _useProfile;
//
// Title defs, structs, methods, and data members
//
enum AnimType {
SIGNATURE,
AND,
BAR,
ORIGIN,
PRESENT,
TITLE,
SUBTITLE,
MAP
};
struct AnimPlot {
uint8 x, y;
uint8 r, g, b, a;
};
struct AnimElement {
void shufflePlotData();
int _rx, _ry; // screen/source x and y
int _rw, _rh; // source width and height
AnimType _method; // render method
int _animStep; // tracks animation position
int _animStepMax;
int _timeBase; // initial animation time
uint32 _timeDelay; // delay before rendering begins
int _timeDuration; // total animation time
Image *_srcImage; // storage for the source image
Image *_destImage; // storage for the animation frame
Std::vector <AnimPlot> _plotData; // plot data
bool _prescaled;
};
/**
* Add the intro element to the element list
*/
void addTitle(int x, int y, int w, int h, AnimType method, uint32 delay, int duration);
/**
* The title element has finished drawing all frames, so delete, remove,
* or free data that is no longer needed
*/
void compactTitle();
/**
* Scale the animation canvas, then draw it to the screen
*/
void drawTitle();
/**
* Get the source data for title elements that have already been initialized
*/
void getTitleSourceData();
/**
* skip the remaining titles
*/
void skipTitles();
Std::vector<AnimElement> _titles; // list of title elements
Std::vector<AnimElement>::iterator _title; // current title element
int _transparentIndex; // palette index for transparency
RGBA _transparentColor; // palette color for transparency
bool _bSkipTitles;
// Temporary place-holder for settings changes
SettingsData _settingsChanged;
};
extern IntroController *g_intro;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,120 @@
/* 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/controllers/key_handler_controller.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
KeyHandler::KeyHandler(Callback func, void *d, bool asyncronous) :
_handler(func),
_async(asyncronous),
_data(d) {
}
bool KeyHandler::globalHandler(int key) {
if (key == Common::KEYCODE_F5) {
(void)g_ultima->saveGameDialog();
} else if (key == Common::KEYCODE_F7) {
(void)g_ultima->loadGameDialog();
}
return false;
}
bool KeyHandler::defaultHandler(int key, void *data) {
bool valid = true;
switch (key) {
case '`':
if (g_context && g_context->_location)
debug(1, "x = %d, y = %d, level = %d, tile = %d (%s)\n", g_context->_location->_coords.x, g_context->_location->_coords.y, g_context->_location->_coords.z, g_context->_location->_map->translateToRawTileIndex(*g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)), g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_OBJECTS)->getName().c_str());
break;
default:
valid = false;
break;
}
return valid;
}
bool KeyHandler::ignoreKeys(int key, void *data) {
return true;
}
bool KeyHandler::handle(int key) {
bool processed = false;
if (!isKeyIgnored(key)) {
processed = globalHandler(key);
if (!processed)
processed = _handler(key, _data);
}
return processed;
}
bool KeyHandler::isKeyIgnored(int key) {
switch (key) {
case Common::KEYCODE_RSHIFT:
case Common::KEYCODE_LSHIFT:
case Common::KEYCODE_RCTRL:
case Common::KEYCODE_LCTRL:
case Common::KEYCODE_RALT:
case Common::KEYCODE_LALT:
case Common::KEYCODE_RMETA:
case Common::KEYCODE_LMETA:
case Common::KEYCODE_TAB:
return true;
default:
return false;
}
}
bool KeyHandler::operator==(Callback cb) const {
return (_handler == cb) ? true : false;
}
/*-------------------------------------------------------------------*/
KeyHandlerController::KeyHandlerController(KeyHandler *handler) {
this->_handler = handler;
}
KeyHandlerController::~KeyHandlerController() {
delete _handler;
}
bool KeyHandlerController::keyPressed(int key) {
assertMsg(_handler != nullptr, "key handler must be initialized");
return _handler->handle(key);
}
KeyHandler *KeyHandlerController::getKeyHandler() {
return _handler;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,119 @@
/* 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_CONTROLLERS_KEY_HANDLER_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_KEY_HANDLER_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
namespace Ultima {
namespace Ultima4 {
/**
* A class for handling keystrokes.
*/
class KeyHandler {
public:
virtual ~KeyHandler() {}
/* Typedefs */
typedef bool (*Callback)(int, void *);
/** Additional information to be passed as data param for read buffer key handler */
typedef struct ReadBuffer {
int (*_handleBuffer)(Common::String *);
Common::String *_buffer;
int _bufferLen;
int _screenX, _screenY;
} ReadBuffer;
/** Additional information to be passed as data param for get choice key handler */
typedef struct GetChoice {
Common::String _choices;
int (*_handleChoice)(int);
} GetChoice;
/* Constructors */
KeyHandler(Callback func, void *data = nullptr, bool asyncronous = true);
/**
* Handles any and all keystrokes.
* Generally used to exit the application, switch applications,
* minimize, maximize, etc.
*/
static bool globalHandler(int key);
/* Static default key handler functions */
/**
* A default key handler that should be valid everywhere
*/
static bool defaultHandler(int key, void *data);
/**
* A key handler that ignores keypresses
*/
static bool ignoreKeys(int key, void *data);
/* Operators */
bool operator==(Callback cb) const;
/* Member functions */
/**
* Handles a keypress.
* First it makes sure the key combination is not ignored
* by the current key handler. Then, it passes the keypress
* through the global key handler. If the global handler
* does not process the keystroke, then the key handler
* handles it itself by calling its handler callback function.
*/
bool handle(int key);
/**
* Returns true if the key or key combination is always ignored by xu4
*/
virtual bool isKeyIgnored(int key);
protected:
Callback _handler;
bool _async;
void *_data;
};
/**
* A controller that wraps a keyhander function. Keyhandlers are
* deprecated -- please use a controller instead.
*/
class KeyHandlerController : public Controller {
public:
KeyHandlerController(KeyHandler *handler);
~KeyHandlerController();
bool keyPressed(int key) override;
KeyHandler *getKeyHandler();
private:
KeyHandler *_handler;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,121 @@
/* 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/controllers/menu_controller.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/views/textview.h"
namespace Ultima {
namespace Ultima4 {
MenuController::MenuController(Menu *menu, TextView *view) :
WaitableController<void *>(nullptr) {
this->_menu = menu;
this->_view = view;
}
void MenuController::setActive() {
MetaEngine::setKeybindingMode(KBMODE_MENU);
}
void MenuController::keybinder(KeybindingAction action) {
bool cursorOn = _view->getCursorEnabled();
if (cursorOn)
_view->disableCursor();
switch (action) {
case KEYBIND_UP:
_menu->prev();
break;
case KEYBIND_DOWN:
_menu->next();
break;
case KEYBIND_LEFT:
case KEYBIND_RIGHT:
case KEYBIND_INTERACT: {
MenuEvent::Type menuAction = MenuEvent::ACTIVATE;
if (action == KEYBIND_LEFT)
menuAction = MenuEvent::DECREMENT;
else if (action == KEYBIND_RIGHT)
menuAction = MenuEvent::INCREMENT;
_menu->activateItem(-1, menuAction);
break;
}
default:
break;
}
_menu->show(_view);
if (cursorOn)
_view->enableCursor();
_view->update();
if (_menu->getClosed())
doneWaiting();
}
bool MenuController::keyPressed(int key) {
bool handled = true;
bool cursorOn = _view->getCursorEnabled();
if (cursorOn)
_view->disableCursor();
handled = _menu->activateItemByShortcut(key, MenuEvent::ACTIVATE);
_menu->show(_view);
if (cursorOn)
_view->enableCursor();
_view->update();
if (_menu->getClosed())
doneWaiting();
return handled;
}
bool MenuController::mousePressed(const Common::Point &mousePos) {
bool cursorOn = _view->getCursorEnabled();
if (cursorOn)
_view->disableCursor();
_menu->activateItemAtPos(_view, mousePos);
_menu->show(_view);
if (cursorOn)
_view->enableCursor();
_view->update();
if (_menu->getClosed())
doneWaiting();
return true;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,68 @@
/* 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_CONTROLLERS_MENU_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_MENU_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
namespace Ultima {
namespace Ultima4 {
class Menu;
class TextView;
/**
* This class controls a menu. The value field of WaitableController
* isn't used.
*/
class MenuController : public WaitableController<void *> {
public:
MenuController(Menu *menu, TextView *view);
/**
* Called when a controller is made active
*/
void setActive() override;
/**
* Handles keybinder actions
*/
void keybinder(KeybindingAction action) override;
/**
* Key was pressed
*/
bool keyPressed(int key) override;
/**
* Mouse button was pressed
*/
bool mousePressed(const Common::Point &mousePos) override;
protected:
Menu *_menu;
TextView *_view;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,72 @@
/* 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/controllers/read_choice_controller.h"
namespace Ultima {
namespace Ultima4 {
ReadChoiceController::ReadChoiceController(const Common::String &choices) :
WaitableController<int>(-1) {
_choices = choices;
}
bool ReadChoiceController::keyPressed(int key) {
// Common::isUpper() accepts 1-byte characters, yet the modifier keys
// (ALT, SHIFT, ETC) produce values beyond 255
if ((key <= 0x7F) && (Common::isUpper(key)))
key = tolower(key);
_value = key;
if (_choices.empty() || _choices.findFirstOf(_value) < _choices.size()) {
// If the value is printable, display it
if (!Common::isSpace(key))
g_screen->screenMessage("%c", toupper(key));
doneWaiting();
return true;
}
return false;
}
void ReadChoiceController::keybinder(KeybindingAction action) {
if (action == KEYBIND_ESCAPE && _choices.contains('\x1B')) {
_value = 27;
doneWaiting();
} else {
WaitableController<int>::keybinder(action);
}
}
char ReadChoiceController::get(const Common::String &choices, EventHandler *eh) {
if (!eh)
eh = eventHandler;
ReadChoiceController ctrl(choices);
eh->pushController(&ctrl);
return ctrl.waitFor();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,49 @@
/* 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_CONTROLLERS_READ_CHOICE_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_READ_CHOICE_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/events/event_handler.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to read a single key from a provided list.
*/
class ReadChoiceController : public WaitableController<int> {
public:
ReadChoiceController(const Common::String &choices);
bool keyPressed(int key) override;
void keybinder(KeybindingAction action) override;
static char get(const Common::String &choices, EventHandler *eh = nullptr);
protected:
Common::String _choices;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,64 @@
/* 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/controllers/read_dir_controller.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/metaengine.h"
namespace Ultima {
namespace Ultima4 {
ReadDirController::ReadDirController() : WaitableController<Direction>(DIR_NONE) {
}
void ReadDirController::setActive() {
// Special mode for inputing directions
MetaEngine::setKeybindingMode(KBMODE_DIRECTION);
}
void ReadDirController::keybinder(KeybindingAction action) {
switch (action) {
case KEYBIND_UP:
_value = DIR_NORTH;
break;
case KEYBIND_DOWN:
_value = DIR_SOUTH;
break;
case KEYBIND_LEFT:
_value = DIR_WEST;
break;
case KEYBIND_RIGHT:
_value = DIR_EAST;
break;
case KEYBIND_ESCAPE:
_value = DIR_NONE;
doneWaiting();
break;
default:
return;
}
doneWaiting();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,52 @@
/* 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_CONTROLLERS_READ_DIR_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_READ_DIR_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/map/direction.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to read a direction enter with the arrow keys.
*/
class ReadDirController : public WaitableController<Direction> {
public:
ReadDirController();
/**
* Called when a controller is made active
*/
void setActive() override;
/**
* Handles keybinder actions
*/
void keybinder(KeybindingAction action) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,45 @@
/* 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/controllers/read_int_controller.h"
namespace Ultima {
namespace Ultima4 {
ReadIntController::ReadIntController(int maxlen, int screenX, int screenY) :
ReadStringController(maxlen, screenX, screenY, "0123456789 \n\r\010") {}
int ReadIntController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
if (!eh)
eh = eventHandler;
ReadIntController ctrl(maxlen, screenX, screenY);
eh->pushController(&ctrl);
ctrl.waitFor();
return ctrl.getInt();
}
int ReadIntController::getInt() const {
return static_cast<int>(strtol(_value.c_str(), nullptr, 10));
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,45 @@
/* 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_CONTROLLERS_READ_INT_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_READ_INT_CONTROLLER_H
#include "ultima/ultima4/controllers/read_string_controller.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to read a integer, terminated by the enter key.
* Non-numeric keys are ignored.
*/
class ReadIntController : public ReadStringController {
public:
ReadIntController(int maxlen, int screenX, int screenY);
static int get(int maxlen, int screenX, int screenY, EventHandler *eh = nullptr);
int getInt() const;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,63 @@
/* 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/controllers/read_player_controller.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
ReadPlayerController::ReadPlayerController() : ReadChoiceController("12345678 \033\n") {
#ifdef IOS_ULTIMA4
U4IOS::beginCharacterChoiceDialog();
#endif
}
ReadPlayerController::~ReadPlayerController() {
#ifdef IOS_ULTIMA4
U4IOS::endCharacterChoiceDialog();
#endif
}
bool ReadPlayerController::keyPressed(int key) {
bool valid = ReadChoiceController::keyPressed(key);
if (valid) {
if (_value < '1' ||
_value > ('0' + g_ultima->_saveGame->_members))
_value = '0';
} else {
_value = '0';
}
return valid;
}
int ReadPlayerController::getPlayer() {
return _value == 27 ? -1 : _value - '1';
}
int ReadPlayerController::waitFor() {
ReadChoiceController::waitFor();
return getPlayer();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,46 @@
/* 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_CONTROLLERS_READ_PLAYER_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_READ_PLAYER_CONTROLLER_H
#include "ultima/ultima4/controllers/read_choice_controller.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to read a player number.
*/
class ReadPlayerController : public ReadChoiceController {
public:
ReadPlayerController();
~ReadPlayerController();
bool keyPressed(int key) override;
int getPlayer();
int waitFor() override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,112 @@
/* 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/controllers/read_string_controller.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/gfx/screen.h"
namespace Ultima {
namespace Ultima4 {
ReadStringController::ReadStringController(int maxlen, int screenX, int screenY,
const Common::String &accepted_chars) : WaitableController<Common::String>("") {
_maxLen = maxlen;
_screenX = screenX;
_screenY = screenY;
_view = nullptr;
_accepted = accepted_chars;
}
ReadStringController::ReadStringController(int maxlen, TextView *view,
const Common::String &accepted_chars) : WaitableController<Common::String>("") {
_maxLen = maxlen;
_screenX = view->getCursorX();
_screenY = view->getCursorY();
_view = view;
_accepted = accepted_chars;
}
bool ReadStringController::keyPressed(int key) {
int valid = true, len = _value.size();
size_t pos = Common::String::npos;
if (key < 0x80)
pos = _accepted.findFirstOf(key);
if (pos != Common::String::npos) {
if (key == Common::KEYCODE_BACKSPACE) {
if (len > 0) {
/* remove the last character */
_value.erase(len - 1, 1);
if (_view) {
_view->textAt(_screenX + len - 1, _screenY, " ");
_view->setCursorPos(_screenX + len - 1, _screenY, true);
} else {
g_screen->screenHideCursor();
g_screen->screenTextAt(_screenX + len - 1, _screenY, " ");
g_screen->screenSetCursorPos(_screenX + len - 1, _screenY);
g_screen->screenShowCursor();
}
}
} else if (key == '\n' || key == '\r') {
doneWaiting();
} else if (len < _maxLen) {
/* add a character to the end */
_value += key;
if (_view) {
_view->textAt(_screenX + len, _screenY, "%c", key);
} else {
g_screen->screenHideCursor();
g_screen->screenTextAt(_screenX + len, _screenY, "%c", key);
g_screen->screenSetCursorPos(_screenX + len + 1, _screenY);
g_context->_col = len + 1;
g_screen->screenShowCursor();
}
}
} else {
valid = false;
}
return valid || KeyHandler::defaultHandler(key, nullptr);
}
Common::String ReadStringController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
if (!eh)
eh = eventHandler;
ReadStringController ctrl(maxlen, screenX, screenY);
eh->pushController(&ctrl);
return ctrl.waitFor();
}
Common::String ReadStringController::get(int maxlen, TextView *view, EventHandler *eh) {
if (!eh)
eh = eventHandler;
ReadStringController ctrl(maxlen, view);
eh->pushController(&ctrl);
return ctrl.waitFor();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,64 @@
/* 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_CONTROLLERS_READ_STRING_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_READ_STRING_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/views/textview.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to read a Common::String, terminated by the enter key.
*/
class ReadStringController : public WaitableController<Common::String> {
public:
/**
* @param maxlen the maximum length of the Common::String
* @param screenX the screen column where to begin input
* @param screenY the screen row where to begin input
* @param accepted_chars a Common::String characters to be accepted for input
*/
ReadStringController(int maxlen, int screenX, int screenY, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
ReadStringController(int maxlen, TextView *view, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
bool keyPressed(int key) override;
static Common::String get(int maxlen, int screenX, int screenY, EventHandler *eh = nullptr);
static Common::String get(int maxlen, TextView *view, EventHandler *eh = nullptr);
#ifdef IOS_ULTIMA4
void setValue(const Common::String &utf8StringValue) {
value = utf8StringValue;
}
#endif
protected:
int _maxLen, _screenX, _screenY;
TextView *_view;
Common::String _accepted;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,96 @@
/* 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/controllers/reagents_menu_controller.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/game/spell.h"
namespace Ultima {
namespace Ultima4 {
bool ReagentsMenuController::keyPressed(int key) {
switch (key) {
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h': {
// Select the corresponding reagent (if visible)
Menu::MenuItemList::iterator mi = _menu->getById(key - 'a');
if ((*mi)->isVisible()) {
_menu->setCurrent(_menu->getById(key - 'a'));
keyPressed(Common::KEYCODE_SPACE);
}
break;
}
default:
return MenuController::keyPressed(key);
}
return true;
}
void ReagentsMenuController::keybinder(KeybindingAction action) {
switch (action) {
case KEYBIND_ESCAPE:
_ingredients->revert();
eventHandler->setControllerDone();
break;
case KEYBIND_UP:
_menu->prev();
break;
case KEYBIND_DOWN:
_menu->next();
break;
case KEYBIND_LEFT:
case KEYBIND_RIGHT:
if (_menu->isVisible()) {
MenuItem *item = *_menu->getCurrent();
// change whether or not it's selected
item->setSelected(!item->isSelected());
if (item->isSelected())
_ingredients->addReagent((Reagent)item->getId());
else
_ingredients->removeReagent((Reagent)item->getId());
}
break;
case KEYBIND_INTERACT:
eventHandler->setControllerDone();
break;
default:
break;
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* 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_CONTROLLERS_REAGENTS_MENU_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_REAGENTS_MENU_CONTROLLER_H
#include "ultima/ultima4/controllers/menu_controller.h"
namespace Ultima {
namespace Ultima4 {
class Ingredients;
/**
* Controller for the reagents menu used when mixing spells. Fills
* the passed in Ingredients with the selected reagents.
*/
class ReagentsMenuController : public MenuController {
public:
ReagentsMenuController(Menu *menu, Ingredients *i, TextView *view) : MenuController(menu, view), _ingredients(i) { }
/**
* Handles spell mixing for the Ultima V-style menu-system
*/
bool keyPressed(int key) override;
void keybinder(KeybindingAction action) override;
private:
Ingredients *_ingredients;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/controllers/wait_controller.h"
#include "ultima/ultima4/events/event_handler.h"
namespace Ultima {
namespace Ultima4 {
WaitController::WaitController(uint c) : Controller(), _cycles(c), _current(0) {
}
void WaitController::timerFired() {
if (++_current >= _cycles) {
_current = 0;
eventHandler->setControllerDone(true);
}
}
bool WaitController::keyPressed(int key) {
return true;
}
void WaitController::wait() {
Controller_startWait();
}
void WaitController::setCycles(int c) {
_cycles = c;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CONTROLLERS_WAIT_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_WAIT_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
namespace Ultima {
namespace Ultima4 {
/**
* A controller to pause for a given length of time, ignoring all
* keyboard input.
*/
class WaitController : public Controller {
public:
WaitController(uint cycles);
bool keyPressed(int key) override;
void timerFired() override;
void wait();
void setCycles(int c);
private:
uint _cycles;
uint _current;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,71 @@
/* 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/controllers/ztats_controller.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
bool ZtatsController::keyPressed(int key) {
switch (key) {
case Common::KEYCODE_RETURN:
keybinder(KEYBIND_ESCAPE);
return true;
case Common::KEYCODE_UP:
case Common::KEYCODE_LEFT:
g_context->_stats->prevItem();
return true;
case Common::KEYCODE_DOWN:
case Common::KEYCODE_RIGHT:
g_context->_stats->nextItem();
return true;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
if (g_ultima->_saveGame->_members >= key - '0')
g_context->_stats->setView(StatsView(STATS_CHAR1 + key - '1'));
return true;
case '0':
g_context->_stats->setView(StatsView(STATS_WEAPONS));
return true;
default:
return KeyHandler::defaultHandler(key, nullptr);
}
}
void ZtatsController::keybinder(KeybindingAction action) {
if (action == KEYBIND_ESCAPE) {
g_context->_stats->setView(StatsView(STATS_PARTY_OVERVIEW));
doneWaiting();
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,44 @@
/* 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_CONTROLLERS_ZTATS_CONTROLLER_H
#define ULTIMA4_CONTROLLERS_ZTATS_CONTROLLER_H
#include "ultima/ultima4/controllers/controller.h"
namespace Ultima {
namespace Ultima4 {
/**
* Controls interaction while Ztats are being displayed.
*/
class ZtatsController : public WaitableController<void *> {
public:
ZtatsController() : WaitableController<void *>(nullptr) {}
bool keyPressed(int key) override;
void keybinder(KeybindingAction action) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,303 @@
/* 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/conversation/conversation.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/script.h"
namespace Ultima {
namespace Ultima4 {
const uint Conversation::BUFFERLEN = 16;
ResponseParts *g_responseParts;
ResponseParts::ResponseParts() :
NONE("<NONE>", "", true),
ASK("<ASK>", "", true),
END("<END>", "", true),
ATTACK("<ATTACK>", "", true),
BRAGGED("<BRAGGED>", "", true),
HUMBLE("<HUMBLE>", "", true),
ADVANCELEVELS("<ADVANCELEVELS>", "", true),
HEALCONFIRM("<HEALCONFIRM>", "", true),
STARTMUSIC_LB("<STARTMUSIC_LB>", "", true),
STARTMUSIC_HW("<STARTMUSIC_HW>", "", true),
STOPMUSIC("<STOPMUSIC>", "", true),
HAWKWIND("<HAWKWIND>", "", true) {
g_responseParts = this;
}
ResponseParts::~ResponseParts() {
g_responseParts = nullptr;
}
/*-------------------------------------------------------------------*/
Response::Response(const Common::String &response) : _references(0) {
add(response);
}
void Response::add(const ResponsePart &part) {
_parts.push_back(part);
}
const Std::vector<ResponsePart> &Response::getParts() const {
return _parts;
}
Response::operator Common::String() const {
Common::String result;
for (const auto &i : _parts) {
result += i;
}
return result;
}
Response *Response::addref() {
_references++;
return this;
}
void Response::release() {
_references--;
if (_references <= 0)
delete this;
}
ResponsePart::ResponsePart(const Common::String &value, const Common::String &arg, bool command) {
_value = value;
_arg = arg;
_command = command;
}
ResponsePart::operator Common::String() const {
return _value;
}
bool ResponsePart::operator==(const ResponsePart &rhs) const {
return _value == rhs._value;
}
bool ResponsePart::isCommand() const {
return _command;
}
DynamicResponse::DynamicResponse(Response * (*generator)(const DynamicResponse *), const Common::String &param) :
Response(""), _param(param) {
_generator = generator;
_currentResponse = nullptr;
}
DynamicResponse::~DynamicResponse() {
if (_currentResponse)
delete _currentResponse;
}
const Std::vector<ResponsePart> &DynamicResponse::getParts() const {
// blah, must cast away constness
const_cast<DynamicResponse *>(this)->_currentResponse = (*_generator)(this);
return _currentResponse->getParts();
}
/*
* Dialogue::Question class
*/
Dialogue::Question::Question(const Common::String &txt, Response *yes, Response *no) :
_text(txt), _yesResp(yes->addref()), _noResp(no->addref()) {}
Common::String Dialogue::Question::getText() {
return _text;
}
Response *Dialogue::Question::getResponse(bool yes) {
if (yes)
return _yesResp;
return _noResp;
}
/*
* Dialogue::Keyword class
*/
Dialogue::Keyword::Keyword(const Common::String &kw, Response *resp) :
_keyword(kw), _response(resp->addref()) {
trim(_keyword);
lowercase(_keyword);
}
Dialogue::Keyword::Keyword(const Common::String &kw, const Common::String &resp) :
_keyword(kw), _response((new Response(resp))->addref()) {
trim(_keyword);
lowercase(_keyword);
}
Dialogue::Keyword::~Keyword() {
_response->release();
}
bool Dialogue::Keyword::operator==(const Common::String &kw) const {
// minimum 4-character "guessing"
int testLen = (_keyword.size() < 4) ? _keyword.size() : 4;
// exception: empty keyword only matches empty Common::String (alias for 'bye')
if (testLen == 0 && kw.size() > 0)
return false;
if (scumm_strnicmp(kw.c_str(), _keyword.c_str(), testLen) == 0)
return true;
return false;
}
/*
* Dialogue class
*/
Dialogue::Dialogue()
: _intro(nullptr)
, _longIntro(nullptr)
, _defaultAnswer(nullptr)
, _question(nullptr) {
}
Dialogue::~Dialogue() {
for (auto &i : _keywords) {
delete i._value;
}
}
void Dialogue::addKeyword(const Common::String &kw, Response *response) {
if (_keywords.find(kw) != _keywords.end())
delete _keywords[kw];
_keywords[kw] = new Keyword(kw, response);
}
Dialogue::Keyword *Dialogue::operator[](const Common::String &kw) {
KeywordMap::iterator i = _keywords.find(kw);
// If they entered the keyword verbatim, return it!
if (i != _keywords.end())
return i->_value;
// Otherwise, go find one that fits the description.
else {
for (i = _keywords.begin(); i != _keywords.end(); i++) {
if ((*i->_value) == kw)
return i->_value;
}
}
return nullptr;
}
const ResponsePart &Dialogue::getAction() const {
int prob = xu4_random(0x100);
/* Does the person turn away from/attack you? */
if (prob >= _turnAwayProb)
return g_responseParts->NONE;
else {
if (_attackProb - prob < 0x40)
return g_responseParts->END;
else
return g_responseParts->ATTACK;
}
}
Common::String Dialogue::dump(const Common::String &arg) {
Common::String result;
if (arg == "") {
result = "keywords:\n";
for (const auto &i : _keywords) {
result += i._key + "\n";
}
} else {
if (_keywords.find(arg) != _keywords.end())
result = static_cast<Common::String>(*_keywords[arg]->getResponse());
}
return result;
}
/*
* Conversation class
*/
Conversation::Conversation() : _state(INTRO), _script(new Script()),
_question(nullptr), _quant(0), _player(0), _price(0) {
#ifdef IOS_ULTIMA4
U4IOS::incrementConversationCount();
#endif
}
Conversation::~Conversation() {
#ifdef IOS_ULTIMA4
U4IOS::decrementConversationCount();
#endif
delete _script;
}
Conversation::InputType Conversation::getInputRequired(int *bufferlen) {
switch (_state) {
case BUY_QUANTITY:
case SELL_QUANTITY: {
*bufferlen = 2;
return INPUT_STRING;
}
case TALK:
case BUY_PRICE:
case TOPIC: {
*bufferlen = BUFFERLEN;
return INPUT_STRING;
}
case GIVEBEGGAR: {
*bufferlen = 2;
return INPUT_STRING;
}
case ASK:
case ASKYESNO: {
*bufferlen = 3;
return INPUT_STRING;
}
case VENDORQUESTION:
case BUY_ITEM:
case SELL_ITEM:
case CONFIRMATION:
case CONTINUEQUESTION:
case PLAYER:
return INPUT_CHARACTER;
case ATTACK:
case DONE:
case INTRO:
case FULLHEAL:
case ADVANCELEVELS:
return INPUT_NONE;
}
error("invalid state: %d", _state);
return INPUT_NONE;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,321 @@
/* 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_CONVERSATION_CONVERSATION_H
#define ULTIMA4_CONVERSATION_CONVERSATION_H
#include "ultima/ultima4/core/utils.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class Debug;
class Person;
class Script;
/**
* A response part can be text or a "command" that triggers an
* action.
*/
class ResponsePart {
public:
ResponsePart(const Common::String &value, const Common::String &arg = "", bool command = false);
operator Common::String() const;
bool operator==(const ResponsePart &rhs) const;
bool isCommand() const;
private:
Common::String _value, _arg;
bool _command;
};
/**
* A list of valid command response parts
*/
class ResponseParts {
public:
const ResponsePart NONE;
const ResponsePart ASK;
const ResponsePart END;
const ResponsePart ATTACK;
const ResponsePart BRAGGED;
const ResponsePart HUMBLE;
const ResponsePart ADVANCELEVELS;
const ResponsePart HEALCONFIRM;
const ResponsePart STARTMUSIC_LB;
const ResponsePart STARTMUSIC_HW;
const ResponsePart STOPMUSIC;
const ResponsePart HAWKWIND;
ResponseParts();
~ResponseParts();
};
extern ResponseParts *g_responseParts;
/**
* A static response. Each response can be made up of any number of
* ResponseParts, which are either text fragments or commands.
*/
class Response {
public:
Response(const Common::String &response);
virtual ~Response() {}
void add(const ResponsePart &part);
virtual const Std::vector<ResponsePart> &getParts() const;
operator Common::String() const;
Response *addref();
void release();
private:
int _references;
Std::vector<ResponsePart> _parts;
};
/**
* A dynamically generated response. This class allows the response
* to be generated dynamically at the time of the conversation instead
* of when the conversation data is loaded.
*/
class DynamicResponse : public Response {
public:
DynamicResponse(Response * (*generator)(const DynamicResponse *), const Common::String &param = "");
virtual ~DynamicResponse();
const Std::vector<ResponsePart> &getParts() const override;
const Common::String &getParam() const {
return _param;
}
private:
Response *(*_generator)(const DynamicResponse *);
Response *_currentResponse;
Common::String _param;
};
/**
* The dialogue class, which holds conversation information for
* townspeople and others who may talk to you. It includes information
* like pronouns, keywords, actual conversation text (of course),
* questions, and what happens when you answer these questions.
*/
class Dialogue {
public:
/**
* A question-response to a keyword.
*/
class Question {
public:
Question(const Common::String &txt, Response *yes, Response *no);
Common::String getText();
Response *getResponse(bool yes);
private:
Common::String _text;
Response *_yesResp, *_noResp;
};
/**
* A dialogue keyword.
* It contains all the keywords that the talker will respond to, as
* well as the responses to those keywords.
*/
class Keyword {
public:
Keyword(const Common::String &kw, Response *resp);
Keyword(const Common::String &kw, const Common::String &resp);
~Keyword();
bool operator==(const Common::String &kw) const;
/*
* Accessor methods
*/
const Common::String &getKeyword() {
return _keyword;
}
Response *getResponse() {
return _response;
}
private:
Common::String _keyword;
Response *_response;
};
/**
* A mapping of keywords to the Keyword object that represents them
*/
typedef Common::HashMap<Common::String, Keyword *> KeywordMap;
/*
* Constructors/Destructors
*/
Dialogue();
virtual ~Dialogue();
/*
* Accessor methods
*/
const Common::String &getName() const {
return _name;
}
const Common::String &getPronoun() const {
return _pronoun;
}
const Common::String &getPrompt() const {
return _prompt;
}
Response *getIntro(bool familiar = false) {
return _intro;
}
Response *getLongIntro(bool familiar = false) {
return _longIntro;
}
Response *getDefaultAnswer() {
return _defaultAnswer;
}
Dialogue::Question *getQuestion() {
return _question;
}
/*
* Getters
*/
void setName(const Common::String &n) {
_name = n;
}
void setPronoun(const Common::String &pn) {
_pronoun = pn;
}
void setPrompt(const Common::String &prompt) {
this->_prompt = prompt;
}
void setIntro(Response *i) {
_intro = i;
}
void setLongIntro(Response *i) {
_longIntro = i;
}
void setDefaultAnswer(Response *a) {
_defaultAnswer = a;
}
void setTurnAwayProb(int prob) {
_turnAwayProb = prob;
}
void setQuestion(Question *q) {
_question = q;
}
void addKeyword(const Common::String &kw, Response *response);
const ResponsePart &getAction() const;
Common::String dump(const Common::String &arg);
/*
* Operators
*/
Keyword *operator[](const Common::String &kw);
private:
Common::String _name;
Common::String _pronoun;
Common::String _prompt;
Response *_intro;
Response *_longIntro;
Response *_defaultAnswer;
KeywordMap _keywords;
union {
int _turnAwayProb;
int _attackProb;
};
Question *_question;
};
/**
* The conversation class, which handles the flow of text from the
* player to the talker and vice-versa. It is responsible for beginning
* and termination conversations and handing state changes during.
*/
class Conversation {
public:
/** Different states the conversation may be in */
enum State {
INTRO, /**< The initial state of the conversation, before anything is said */
TALK, /**< The "default" state of the conversation */
ASK, /**< The talker is asking the player a question */
ASKYESNO, /**< The talker is asking the player a yes/no question */
VENDORQUESTION, /**< A vendor is asking the player a question */
BUY_ITEM, /**< Asked which item to buy */
SELL_ITEM, /**< Asked which item to sell */
BUY_QUANTITY, /**< Asked how many items to buy */
SELL_QUANTITY, /**< Asked how many items to sell */
BUY_PRICE, /**< Asked how much money to give someone */
CONFIRMATION, /**< Asked by a vendor to confirm something */
CONTINUEQUESTION, /**< Asked whether or not to continue */
TOPIC, /**< Asked a topic to speak about */
PLAYER, /**< Input for which player is required */
FULLHEAL, /**< Heal the entire party before continuing conversation */
ADVANCELEVELS, /**< Check and advance the party's levels before continuing */
GIVEBEGGAR, /**< Asked how much to give a beggar */
ATTACK, /**< The conversation ends with the talker attacking you */
DONE /**< The conversation is over */
};
/** Different types of conversation input required */
enum InputType {
INPUT_STRING,
INPUT_CHARACTER,
INPUT_NONE
};
/* Constructor/Destructors */
Conversation();
~Conversation();
/* Member functions */
InputType getInputRequired(int *bufferLen);
/* Static variables */
static const uint BUFFERLEN; /**< The default maxixum length of input */
public:
State _state; /**< The state of the conversation */
Common::String _playerInput; /**< A Common::String holding the text the player inputs */
Common::List<Common::String> _reply; /**< What the talker says */
class Script *_script; /**< A script that this person follows during the conversation (may be nullptr) */
Dialogue::Question *_question; /**< The current question the player is being asked */
int _quant; /**< For vendor transactions */
int _player; /**< For vendor transactions */
int _price; /**< For vendor transactions */
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader.h"
#include "ultima/ultima4/conversation/dialogueloader_hw.h"
#include "ultima/ultima4/conversation/dialogueloader_lb.h"
#include "ultima/ultima4/conversation/dialogueloader_tlk.h"
namespace Ultima {
namespace Ultima4 {
static DialogueLoaders *g_loaders;
DialogueLoaders::DialogueLoaders() {
g_loaders = this;
registerLoader(new U4HWDialogueLoader, "application/x-u4hwtlk");
registerLoader(new U4LBDialogueLoader, "application/x-u4lbtlk");
registerLoader(new U4TlkDialogueLoader(), "application/x-u4tlk");
}
DialogueLoaders::~DialogueLoaders() {
for (auto &l : _loaders)
delete l._value;
g_loaders = nullptr;
}
DialogueLoader *DialogueLoaders::getLoader(const Common::String &mimeType) {
return (*g_loaders)[mimeType];
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,71 @@
/* 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_CONVERSATION_DIALOGUELOADER_H
#define ULTIMA4_CONVERSATION_DIALOGUELOADER_H
#include "common/hashmap.h"
#include "common/str.h"
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
class Dialogue;
/**
* The generic dialogue loader interface. Different dialogue
* loaders should override the load method to load dialogues from
* different sources (.tlk files, xml config elements, etc.). They
* must also register themselves with registerLoader for one or more
* source types. By convention, the source type of load() and
* registerLoader() is an xu4-specific mime type.
* The two main types used are application/x-u4tlk and text/x-u4cfg.
*/
class DialogueLoader {
public:
virtual ~DialogueLoader() {}
virtual Dialogue *load(Common::SeekableReadStream *source) = 0;
};
class DialogueLoaders {
private:
Common::HashMap<Common::String, DialogueLoader *> _loaders;
public:
static DialogueLoader *getLoader(const Common::String &mimeType);
public:
DialogueLoaders();
~DialogueLoaders();
void registerLoader(DialogueLoader *loader, const Common::String &mimeType) {
_loaders[mimeType] = loader;
}
DialogueLoader *operator[](const Common::String &mimeType) {
return _loaders[mimeType];
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,143 @@
/* 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/game/context.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader_hw.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/ultima4.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
Response *hawkwindGetAdvice(const DynamicResponse *kw);
Response *hawkwindGetIntro(const DynamicResponse *dynResp);
/* Hawkwind text indexes */
#define HW_SPEAKONLYWITH 40
#define HW_RETURNWHEN 41
#define HW_ISREVIVED 42
#define HW_WELCOME 43
#define HW_GREETING1 44
#define HW_GREETING2 45
#define HW_PROMPT 46
#define HW_DEFAULT 49
#define HW_ALREADYAVATAR 50
#define HW_GOTOSHRINE 51
#define HW_BYE 52
/**
* A special case dialogue loader for Hawkwind.
*/
Dialogue *U4HWDialogueLoader::load(Common::SeekableReadStream *source) {
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
hawkwindText = u4read_stringtable("hawkwind");
Dialogue *dlg = new Dialogue();
dlg->setTurnAwayProb(0);
dlg->setName("Hawkwind");
dlg->setPronoun("He");
dlg->setPrompt(hawkwindText[HW_PROMPT]);
Response *intro = new DynamicResponse(&hawkwindGetIntro);
dlg->setIntro(intro);
dlg->setLongIntro(intro);
dlg->setDefaultAnswer(new Response(Common::String("\n" + hawkwindText[HW_DEFAULT])));
for (int v = 0; v < VIRT_MAX; v++) {
Common::String virtue(getVirtueName((Virtue) v));
lowercase(virtue);
virtue = virtue.substr(0, 4);
dlg->addKeyword(virtue, new DynamicResponse(&hawkwindGetAdvice, virtue));
}
Response *bye = new Response(hawkwindText[HW_BYE]);
bye->add(g_responseParts->STOPMUSIC);
bye->add(g_responseParts->END);
dlg->addKeyword("bye", bye);
dlg->addKeyword("", bye);
return dlg;
}
/**
* Generate the appropriate response when the player asks Lord British
* for help. The help text depends on the current party status; when
* one quest item is complete, Lord British provides some direction to
* the next one.
*/
Response *hawkwindGetAdvice(const DynamicResponse *dynResp) {
Common::String text;
int virtue = -1, virtueLevel = -1;
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
/* check if asking about a virtue */
for (int v = 0; v < VIRT_MAX; v++) {
if (scumm_strnicmp(dynResp->getParam().c_str(), getVirtueName((Virtue) v), 4) == 0) {
virtue = v;
virtueLevel = g_ultima->_saveGame->_karma[v];
break;
}
}
if (virtue != -1) {
text = "\n\n";
if (virtueLevel == 0)
text += hawkwindText[HW_ALREADYAVATAR] + "\n";
else if (virtueLevel < 80)
text += hawkwindText[(virtueLevel / 20) * 8 + virtue];
else if (virtueLevel < 99)
text += hawkwindText[3 * 8 + virtue];
else /* virtueLevel >= 99 */
text = hawkwindText[4 * 8 + virtue] + hawkwindText[HW_GOTOSHRINE];
} else {
text = Common::String("\n") + hawkwindText[HW_DEFAULT];
}
return new Response(text);
}
Response *hawkwindGetIntro(const DynamicResponse *dynResp) {
Response *intro = new Response("");
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
if (g_context->_party->member(0)->getStatus() == STAT_SLEEPING ||
g_context->_party->member(0)->getStatus() == STAT_DEAD) {
intro->add(hawkwindText[HW_SPEAKONLYWITH] + g_context->_party->member(0)->getName() +
hawkwindText[HW_RETURNWHEN] + g_context->_party->member(0)->getName() +
hawkwindText[HW_ISREVIVED]);
intro->add(g_responseParts->END);
} else {
intro->add(g_responseParts->STARTMUSIC_HW);
intro->add(g_responseParts->HAWKWIND);
intro->add(hawkwindText[HW_WELCOME] + g_context->_party->member(0)->getName() +
hawkwindText[HW_GREETING1] + hawkwindText[HW_GREETING2]);
}
return intro;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CONVERSATION_DIALOGUELOADER_HW_H
#define ULTIMA4_CONVERSATION_DIALOGUELOADER_HW_H
#include "ultima/ultima4/conversation/dialogueloader.h"
namespace Ultima {
namespace Ultima4 {
/**
* The dialogue loader for Hawkwind.
*/
class U4HWDialogueLoader : public DialogueLoader {
public:
Dialogue *load(Common::SeekableReadStream *source) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,196 @@
/* 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/game/context.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader_lb.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/filesys/u4file.h"
namespace Ultima {
namespace Ultima4 {
Response *lordBritishGetHelp(const DynamicResponse *resp);
Response *lordBritishGetIntro(const DynamicResponse *resp);
/**
* A special case dialogue loader for Lord British. Loads most of the
* keyword/responses from a string table originally extracted from the
* game executable. The "help" response is a special case that changes
* based on the current party status.
*/
Dialogue *U4LBDialogueLoader::load(Common::SeekableReadStream *source) {
Std::vector<Common::String> lbKeywords = u4read_stringtable("lb_keywords");
Std::vector<Common::String> lbText = u4read_stringtable("lb_text");
Dialogue *dlg = new Dialogue();
dlg->setTurnAwayProb(0);
dlg->setName("Lord British");
dlg->setPronoun("He");
dlg->setPrompt("What else?\n");
Response *intro = new DynamicResponse(&lordBritishGetIntro);
dlg->setIntro(intro);
dlg->setLongIntro(intro);
dlg->setDefaultAnswer(new Response("\nHe says: I\ncannot help thee\nwith that.\n"));
for (unsigned i = 0; i < lbKeywords.size(); i++) {
dlg->addKeyword(lbKeywords[i], new Response(lbText[i]));
}
Response *heal = new Response("\n\n\n\n\n\nHe says: I am\nwell, thank ye.");
heal->add(g_responseParts->HEALCONFIRM);
dlg->addKeyword("heal", heal);
Response *bye;
if (g_context->_party->size() > 1)
bye = new Response("Lord British says: Fare thee well my friends!");
else
bye = new Response("Lord British says: Fare thee well my friend!");
bye->add(g_responseParts->STOPMUSIC);
bye->add(g_responseParts->END);
dlg->addKeyword("bye", bye);
dlg->addKeyword("", bye);
dlg->addKeyword("help", new DynamicResponse(&lordBritishGetHelp));
return dlg;
}
/**
* Generate the appropriate response when the player asks Lord British
* for help. The help text depends on the current party status; when
* one quest item is complete, Lord British provides some direction to
* the next one.
*/
Response *lordBritishGetHelp(const DynamicResponse *resp) {
int v;
bool fullAvatar, partialAvatar;
Common::String text;
/*
* check whether player is full avatar (in all virtues) or partial
* avatar (in at least one virtue)
*/
fullAvatar = true;
partialAvatar = false;
for (v = 0; v < VIRT_MAX; v++) {
fullAvatar &= (g_ultima->_saveGame->_karma[v] == 0);
partialAvatar |= (g_ultima->_saveGame->_karma[v] == 0);
}
if (g_ultima->_saveGame->_moves <= 1000) {
text = "To survive in this hostile land thou must first know thyself! Seek ye to master thy weapons and thy magical ability!\n"
"\nTake great care in these thy first travels in Britannia.\n"
"\nUntil thou dost well know thyself, travel not far from the safety of the townes!\n";
}
else if (g_ultima->_saveGame->_members == 1) {
text = "Travel not the open lands alone. There are many worthy people in the diverse townes whom it would be wise to ask to Join thee!\n"
"\nBuild thy party unto eight travellers, for only a true leader can win the Quest!\n";
}
else if (g_ultima->_saveGame->_runes == 0) {
text = "Learn ye the paths of virtue. Seek to gain entry unto the eight shrines!\n"
"\nFind ye the Runes, needed for entry into each shrine, and learn each chant or \"Mantra\" used to focus thy meditations.\n"
"\nWithin the Shrines thou shalt learn of the deeds which show thy inner virtue or vice!\n"
"\nChoose thy path wisely for all thy deeds of good and evil are remembered and can return to hinder thee!\n";
}
else if (!partialAvatar) {
text = "Visit the Seer Hawkwind often and use his wisdom to help thee prove thy virtue.\n"
"\nWhen thou art ready, Hawkwind will advise thee to seek the Elevation unto partial Avatarhood in a virtue.\n"
"\nSeek ye to become a partial Avatar in all eight virtues, for only then shalt thou be ready to seek the codex!\n";
}
else if (g_ultima->_saveGame->_stones == 0) {
text = "Go ye now into the depths of the dungeons. Therein recover the 8 colored stones from the altar pedestals in the halls of the dungeons.\n"
"\nFind the uses of these stones for they can help thee in the Abyss!\n";
}
else if (!fullAvatar) {
text = "Thou art doing very well indeed on the path to Avatarhood! Strive ye to achieve the Elevation in all eight virtues!\n";
}
else if ((g_ultima->_saveGame->_items & ITEM_BELL) == 0 ||
(g_ultima->_saveGame->_items & ITEM_BOOK) == 0 ||
(g_ultima->_saveGame->_items & ITEM_CANDLE) == 0) {
text = "Find ye the Bell, Book and Candle! With these three things, one may enter the Great Stygian Abyss!\n";
}
else if ((g_ultima->_saveGame->_items & ITEM_KEY_C) == 0 ||
(g_ultima->_saveGame->_items & ITEM_KEY_L) == 0 ||
(g_ultima->_saveGame->_items & ITEM_KEY_T) == 0) {
text = "Before thou dost enter the Abyss thou shalt need the Key of Three Parts, and the Word of Passage.\n"
"\nThen might thou enter the Chamber of the Codex of Ultimate Wisdom!\n";
}
else {
text = "Thou dost now seem ready to make the final journey into the dark Abyss! Go only with a party of eight!\n"
"\nGood Luck, and may the powers of good watch over thee on this thy most perilous endeavor!\n"
"\nThe hearts and souls of all Britannia go with thee now. Take care, my friend.\n";
}
return new Response(Common::String("He says: ") + text);
}
Response *lordBritishGetIntro(const DynamicResponse *resp) {
Response *intro = new Response("");
intro->add(g_responseParts->STARTMUSIC_LB);
if (g_ultima->_saveGame->_lbIntro) {
if (g_ultima->_saveGame->_members == 1) {
intro->add(Common::String("\n\n\nLord British\nsays: Welcome\n") +
g_context->_party->member(0)->getName() + "!\n\n");
} else if (g_ultima->_saveGame->_members == 2) {
intro->add(Common::String("\n\nLord British\nsays: Welcome\n") +
g_context->_party->member(0)->getName() +
" and thee also " +
g_context->_party->member(1)->getName() +
"!\n\n");
} else {
intro->add(Common::String("\n\n\nLord British\nsays: Welcome\n") +
g_context->_party->member(0)->getName() +
" and thy\nworthy\nAdventurers!\n\n");
}
// Lord British automatically adds "What would thou ask of me?"
// Check levels here, just like the original!
intro->add(g_responseParts->ADVANCELEVELS);
}
else {
intro->add(Common::String("\n\n\nLord British rises and says: At long last!\n") +
g_context->_party->member(0)->getName() +
" thou hast come! We have waited such a long, long time...\n"
"\n\nLord British sits and says: A new age is upon Britannia. The great evil Lords are gone but our people lack direction and purpose in their lives...\n\n\n"
"A champion of virtue is called for. Thou may be this champion, but only time shall tell. I will aid thee any way that I can!\n\n"
"How may I help thee?\n");
g_ultima->_saveGame->_lbIntro = 1;
}
return intro;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CONVERSATION_DIALOGUELOADER_LB_H
#define ULTIMA4_CONVERSATION_DIALOGUELOADER_LB_H
#include "ultima/ultima4/conversation/dialogueloader.h"
namespace Ultima {
namespace Ultima4 {
/**
* The dialogue loader for Lord British
*/
class U4LBDialogueLoader : public DialogueLoader {
public:
Dialogue *load(Common::SeekableReadStream *source) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,162 @@
/* 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/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader_tlk.h"
#include "ultima/shared/std/containers.h"
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
/**
* A dialogue loader for standard u4dos .tlk files.
*/
Dialogue *U4TlkDialogueLoader::load(Common::SeekableReadStream *source) {
enum QTrigger {
NONE = 0,
JOB = 3,
HEALTH = 4,
KEYWORD1 = 5,
KEYWORD2 = 6
};
/* there's no dialogues left in the file */
char tlk_buffer[288];
if (source->read(tlk_buffer, sizeof(tlk_buffer)) != sizeof(tlk_buffer))
return nullptr;
char *ptr = &tlk_buffer[3];
Std::vector<Common::String> strings;
for (int i = 0; i < 12; i++) {
strings.push_back(ptr);
ptr += strlen(ptr) + 1;
}
Dialogue *dlg = new Dialogue();
byte prob = tlk_buffer[2];
QTrigger qtrigger = QTrigger(tlk_buffer[0]);
bool humilityTestQuestion = tlk_buffer[1] == 1;
dlg->setTurnAwayProb(prob);
dlg->setName(strings[0]);
dlg->setPronoun(strings[1]);
dlg->setPrompt("\nYour Interest:\n");
// Fix the actor description Common::String, converting the first character
// to lower-case.
strings[2].setChar(tolower(strings[2][0]), 0);
// ... then replace any newlines in the Common::String with spaces
size_t index = strings[2].find("\n");
while (index != Common::String::npos) {
strings[2].setChar(' ', index);
index = strings[2].find("\n");
}
// ... then append a period to the end of the Common::String if one does
// not already exist
if (!Common::isPunct(strings[2][strings[2].size() - 1]))
strings[2] = strings[2] + Common::String(".");
// ... and finally, a few characters in the game have descriptions
// that do not begin with a definite (the) or indefinite (a/an)
// article. On those characters, insert the appropriate article.
if ((strings[0] == "Iolo")
|| (strings[0] == "Tracie")
|| (strings[0] == "Dupre")
|| (strings[0] == "Traveling Dan"))
strings[2] = Common::String("a ") + strings[2];
Common::String introBase = Common::String("\nYou meet ") + strings[2] + "\n";
dlg->setIntro(new Response(introBase + dlg->getPrompt()));
dlg->setLongIntro(new Response(introBase +
"\n" + dlg->getPronoun() + " says: I am " + dlg->getName() + "\n"
+ dlg->getPrompt()));
dlg->setDefaultAnswer(new Response("That I cannot\nhelp thee with."));
Response *yes = new Response(strings[8]);
Response *no = new Response(strings[9]);
if (humilityTestQuestion) {
yes->add(g_responseParts->BRAGGED);
no->add(g_responseParts->HUMBLE);
}
dlg->setQuestion(new Dialogue::Question(strings[7], yes, no));
// one of the following four keywords triggers the speaker's question
Response *job = new Response(Common::String("\n") + strings[3]);
Response *health = new Response(Common::String("\n") + strings[4]);
Response *kw1 = new Response(Common::String("\n") + strings[5]);
Response *kw2 = new Response(Common::String("\n") + strings[6]);
switch (qtrigger) {
case JOB:
job->add(g_responseParts->ASK);
break;
case HEALTH:
health->add(g_responseParts->ASK);
break;
case KEYWORD1:
kw1->add(g_responseParts->ASK);
break;
case KEYWORD2:
kw2->add(g_responseParts->ASK);
break;
case NONE:
default:
break;
}
dlg->addKeyword("job", job);
dlg->addKeyword("heal", health);
dlg->addKeyword(strings[10], kw1);
dlg->addKeyword(strings[11], kw2);
// NOTE: We let the talker's custom keywords override the standard
// keywords like HEAL and LOOK. This behavior differs from u4dos,
// but fixes a couple conversation files which have keywords that
// conflict with the standard ones (e.g. Calabrini in Moonglow has
// HEAL for healer, which is unreachable in u4dos, but clearly
// more useful than "Fine." for health).
Common::String look = Common::String("\nYou see ") + strings[2];
dlg->addKeyword("look", new Response(look));
dlg->addKeyword("name", new Response(Common::String("\n") + dlg->getPronoun() + " says: I am " + dlg->getName()));
dlg->addKeyword("give", new Response(Common::String("\n") + dlg->getPronoun() + " says: I do not need thy gold. Keep it!"));
dlg->addKeyword("join", new Response(Common::String("\n") + dlg->getPronoun() + " says: I cannot join thee."));
Response *bye = new Response("\nBye.");
bye->add(g_responseParts->END);
dlg->addKeyword("bye", bye);
dlg->addKeyword("", bye);
/*
* This little easter egg appeared in the Amiga version of Ultima IV.
* I've never figured out what the number means.
* "Banjo" Bob Hardy was the programmer for the Amiga version.
*/
dlg->addKeyword("ojna", new Response("\nHi Banjo Bob!\nYour secret\nnumber is\n4F4A4E0A"));
return dlg;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CONVERSATION_DIALOGUELOADER_TLK_H
#define ULTIMA4_CONVERSATION_DIALOGUELOADER_TLK_H
#include "ultima/ultima4/conversation/dialogueloader.h"
namespace Ultima {
namespace Ultima4 {
/**
* The dialogue loader for u4dos .tlk files
*/
class U4TlkDialogueLoader : public DialogueLoader {
public:
Dialogue *load(Common::SeekableReadStream *source) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,126 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/settings.h"
namespace Ultima {
namespace Ultima4 {
Config *Config::_instance;
Config::Config() {
_instance = this;
if (!_doc.readConfigFile("data/conf/config.xml"))
error("Failed to read core configuration");
}
Config::~Config() {
_instance = nullptr;
}
ConfigElement Config::getElement(const Common::String &name) const {
Common::String key = Common::String::format("config/%s", name.c_str());
const Shared::XMLNode *node = _doc.getNode(key);
assert(node);
return ConfigElement(node);
}
Std::vector<Common::String> Config::getGames() {
Std::vector<Common::String> result;
result.push_back("Ultima IV");
return result;
}
void Config::setGame(const Common::String &name) {
// No implementation
}
/*-------------------------------------------------------------------*/
ConfigElement::ConfigElement(const Shared::XMLNode *xmlNode) :
_node(xmlNode), _name(xmlNode->id().c_str()) {
}
ConfigElement::ConfigElement(const ConfigElement &e) : _node(e._node), _name(e._name) {
}
ConfigElement::ConfigElement() : _node(nullptr) {
}
ConfigElement::~ConfigElement() {
}
ConfigElement &ConfigElement::operator=(const ConfigElement &e) {
if (&e != this) {
_node = e._node;
_name = e._name;
}
return *this;
}
bool ConfigElement::exists(const Common::String &name) const {
return !(*_node)[name].empty();
}
Common::String ConfigElement::getString(const Common::String &name) const {
return (*_node)[name];
}
int ConfigElement::getInt(const Common::String &name, int defaultValue) const {
Common::String str = (*_node)[name];
return str.empty() ? defaultValue : atol(str.c_str());
}
bool ConfigElement::getBool(const Common::String &name) const {
Common::String str = (*_node)[name];
if (str.empty())
return false;
return toupper(str[0]) == 'T' || str == "1";
}
int ConfigElement::getEnum(const Common::String &name, const char *const enumValues[]) const {
Common::String str = (*_node)[name];
if (str.empty())
return 0;
for (int i = 0; enumValues[i]; ++i) {
if (str.equalsIgnoreCase(enumValues[i]))
return i;
}
error("invalid enum value for %s: %s", name.c_str(), str.c_str());
}
Std::vector<ConfigElement> ConfigElement::getChildren() const {
const Common::Array<Shared::XMLNode *> &children = _node->children();
Std::vector<ConfigElement> result;
for (const auto &c : children)
result.push_back(c);
return result;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,159 @@
/* 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_CORE_CONFIG_H
#define ULTIMA4_CORE_CONFIG_H
#include "ultima/shared/conf/xml_tree.h"
#include "ultima/shared/conf/xml_node.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
/* info for loading city data from *.ult and *.tlk */
#define CITY_HEIGHT 32
#define CITY_WIDTH 32
#define CITY_MAX_PERSONS 32
/* info for loading area data from *.con */
#define CON_HEIGHT 11
#define CON_WIDTH 11
/* info for loading dungeon map data from *.dng */
#define DNG_HEIGHT 8
#define DNG_WIDTH 8
/* info for loading image data from shapes.ega */
#define N_TILES 256
#define TILE_WIDTH (2 * CHAR_WIDTH)
#define TILE_HEIGHT (2 * CHAR_HEIGHT)
/* info for loading image data from charset.ega */
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 8
/* some character defines */
#define CHARSET_ANKH '\0'
#define CHARSET_REDDOT '\01'
#define CHARSET_SDOOR '\02'
#define CHARSET_WALL '\03'
#define CHARSET_LADDER_UPDOWN '\04'
#define CHARSET_LADDER_DOWN '\05'
#define CHARSET_LADDER_UP '\06'
#define CHARSET_BULLET '\010'
#define CHARSET_COPYRIGHT '\011'
#define CHARSET_REGISTERED '\012'
#define CHARSET_MALE '\013'
#define CHARSET_FEMALE '\014'
#define CHARSET_HORIZBAR '\015'
#define CHARSET_ROOM '\016'
#define CHARSET_ORB '\017'
#define CHARSET_PROMPT '\020'
#define CHARSET_FLOOR '\022'
/* map viewport size (in tiles) */
#define VIEWPORT_W 11
#define VIEWPORT_H 11
/* screen border size (in pixels) */
#define BORDER_WIDTH 8
#define BORDER_HEIGHT 8
/* text area (in character units) */
#define TEXT_AREA_X 24
#define TEXT_AREA_Y 12
#define TEXT_AREA_W 16
#define TEXT_AREA_H 12
/* moons/moongates */
#define MOON_PHASES 24
#define MOON_SECONDS_PER_PHASE 4
#define MOON_CHAR 20
/* wind */
#define WIND_AREA_X 7
#define WIND_AREA_Y 23
#define WIND_AREA_W 10
#define WIND_AREA_H 1
#define WIND_SECONDS_PER_PHASE 1
class ConfigElement;
/**
* Singleton class that manages the XML configuration tree.
*/
class Config {
private:
static Config *_instance;
Shared::XMLTree _doc;
public:
static const Config *getInstance() {
return _instance;
}
public:
Config();
~Config();
ConfigElement getElement(const Common::String &name) const;
static Std::vector<Common::String> getGames();
static void setGame(const Common::String &name);
};
/**
* A single configuration element in the config tree. Right now, a
* thin wrapper around the XML DOM element.
*/
class ConfigElement {
private:
const Shared::XMLNode *_node;
Common::String _name;
public:
ConfigElement(const Shared::XMLNode *xmlNode);
ConfigElement(const ConfigElement &e);
ConfigElement();
~ConfigElement();
ConfigElement &operator=(const ConfigElement &e);
const Common::String getName() const {
return _name;
}
bool exists(const Common::String &name) const;
Common::String getString(const Common::String &name) const;
int getInt(const Common::String &name, int defaultValue = 0) const;
bool getBool(const Common::String &name) const;
int getEnum(const Common::String &name, const char *const enumValues[]) const;
Std::vector<ConfigElement> getChildren() const;
const Shared::XMLNode *getNode() const {
return _node;
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,48 @@
/* 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_CORE_COORDS_H
#define ULTIMA4_CORE_COORDS_H
namespace Ultima {
namespace Ultima4 {
/**
* A simple representation of a point in 3D space.
*/
class Coords {
public:
int x, y, z;
Coords(int initx = 0, int inity = 0, int initz = 0) : x(initx), y(inity), z(initz) {}
bool operator==(const Coords &a) const {
return x == a.x && y == a.y && z == a.z;
}
bool operator!=(const Coords &a) const {
return !operator==(a);
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
/* 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_CORE_DEBUGGER_H
#define ULTIMA4_CORE_DEBUGGER_H
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/core/debugger_actions.h"
#include "gui/debugger.h"
namespace Ultima {
namespace Ultima4 {
/**
* Debugger base class
*/
class Debugger : public GUI::Debugger, public DebuggerActions {
private:
MapTile _horse, _ship, _balloon;
bool _dontEndTurn;
protected:
/**
* Returns true if the debugger is active
*/
bool isDebuggerActive() const override {
return isActive();
}
/**
* Process the given command line.
* Returns true if and only if argv[0] is a known command and was
* handled, false otherwise.
*/
bool handleCommand(int argc, const char **argv, bool &keepRunning) override;
/**
* Prints a message to the console if it's active, or to the
* game screen if not
*/
void print(const char *fmt, ...) override;
/**
* Prints a message to the console if it's active, or to the
* game screen if not, with no newline
*/
void printN(const char *fmt, ...) override;
/**
* Prompts for input, but only if debugger isn't running
*/
void prompt() override;
/**
* Gets the direction for an action
*/
Direction getDirection(int argc, const char **argv);
/**
* Used by methods so that when they're triggered by a keybinding
* action, stops the turn from being finished when they're done
*/
void dontEndTurn() {
_dontEndTurn = true;
}
private:
/**
* Move the avatar in a given direction
*/
bool cmdMove(int argc, const char **argv);
/**
* Attack
*/
bool cmdAttack(int argc, const char **argv);
/**
* Board transport
*/
bool cmdBoard(int argc, const char **argv);
/**
* Cast spell
*/
bool cmdCastSpell(int argc, const char **argv);
/**
* Climb
*/
bool cmdClimb(int argc, const char **argv);
/**
* Descend
*/
bool cmdDescend(int argc, const char **argv);
/**
* Enter location
*/
bool cmdEnter(int argc, const char **argv);
/**
* Exit
*/
bool cmdExit(int argc, const char **argv);
/**
* Fire
*/
bool cmdFire(int argc, const char **argv);
/**
* Get chest
*/
bool cmdGetChest(int argc, const char **argv);
/**
* Hole Up & Camp
*/
bool cmdCamp(int argc, const char **argv);
/**
* Ignite Torch
*/
bool cmdIgnite(int argc, const char **argv);
/**
* Generic interaction
*/
bool cmdInteract(int argc, const char **argv);
/**
* Jimmy lock
*/
bool cmdJimmy(int argc, const char **argv);
/**
* Locate position
*/
bool cmdLocate(int argc, const char **argv);
/**
* Mix reagents
*/
bool cmdMixReagents(int argc, const char **argv);
/**
* Exchanges the position of two players in the party. Prompts the
* user for the player numbers.
*/
bool cmdNewOrder(int argc, const char **argv);
/**
* Open door
*/
bool cmdOpenDoor(int argc, const char **argv);
/**
* Specifies a particular party number
*/
bool cmdParty(int argc, const char **argv);
/**
* Pass turn
*/
bool cmdPass(int argc, const char **argv);
/**
* Peer
*/
bool cmdPeer(int argc, const char **argv);
/**
* Save and quit
*/
bool cmdQuitAndSave(int argc, const char **argv);
/**
* Readies a weapon for a player. Prompts for the player and/or the
* weapon if not provided.
*/
bool cmdReadyWeapon(int argc, const char **argv);
/**
* Search
*/
bool cmdSearch(int argc, const char **argv);
/**
* Speed up, down, or normal
*/
bool cmdSpeed(int argc, const char **argv);
/**
* Combat speed up, down, or normal
*/
bool cmdCombatSpeed(int argc, const char **argv);
/**
* Show character stats
*/
bool cmdStats(int argc, const char **argv);
/**
* Talk
*/
bool cmdTalk(int argc, const char **argv);
/**
* Use
*/
bool cmdUse(int argc, const char **argv);
/**
* Changes a player's armor
*/
bool cmdWearArmor(int argc, const char **argv);
/**
* Yell
*/
bool cmdYell(int argc, const char **argv);
private:
/**
* Collision detection on/off
*/
bool cmd3d(int argc, const char **argv);
/**
* Teleports to the Abyss final altar
*/
bool cmdAbyss(int argc, const char **argv);
/**
* Collision detection on/off
*/
bool cmdCollisions(int argc, const char **argv);
/**
* Have all the companions join the party
*/
bool cmdCompanions(int argc, const char **argv);
/**
* Toggle whether combat occurs
*/
bool cmdCombat(int argc, const char **argv);
/**
* Destroy an object
*/
bool cmdDestroy(int argc, const char **argv);
/**
* Destroy all creatures
*/
bool cmdDestroyCreatures(int argc, const char **argv);
/**
* Jumps to a given dungeon
*/
bool cmdDungeon(int argc, const char **argv);
/**
* Flee from combat
*/
bool cmdFlee(int argc, const char **argv);
/**
* All equipement
*/
bool cmdEquipment(int argc, const char **argv);
/**
* Full stats
*/
bool cmdFullStats(int argc, const char **argv);
/**
* Toggle hunger on or off
*/
bool cmdHunger(int argc, const char **argv);
/**
* Moongate teleportation
*/
bool cmdGate(int argc, const char **argv);
/**
* Go to any specified location by name
*/
bool cmdGoto(int argc, const char **argv);
/**
* Help.. sends the party to Lord British
*/
bool cmdLorddBritish(int argc, const char **argv);
/**
* Grant karma
*/
bool cmdKarma(int argc, const char **argv);
/**
* Give all the items
*/
bool cmdItems(int argc, const char **argv);
/**
* Leave the current location
*/
bool cmdLeave(int argc, const char **argv);
/**
* Displays the current location
*/
bool cmdLocation(int argc, const char **argv);
/**
* Give all the mixtures
*/
bool cmdMixtures(int argc, const char **argv);
/**
* Moon phase
*/
bool cmdMoon(int argc, const char **argv);
/**
* Toggle opacity
*/
bool cmdOpacity(int argc, const char **argv);
/**
* Toggle overhead view
*/
bool cmdOverhead(int argc, const char **argv);
/**
* Give all the reagents
*/
bool cmdReagents(int argc, const char **argv);
/**
* Summons a creature to fight
*/
bool cmdSummon(int argc, const char **argv);
/**
* Returns the torch duration
*/
bool cmdTorch(int argc, const char **argv);
/**
* Creates a given transport
*/
bool cmdTransport(int argc, const char **argv);
/**
* Move up a floor
*/
bool cmdUp(int argc, const char **argv);
/**
* Move down a floor
*/
bool cmdDown(int argc, const char **argv);
/**
* Gives full virtue, or increments a specific virtue
*/
bool cmdVirtue(int argc, const char **argv);
/**
* Set wind direction or locks the direction
*/
bool cmdWind(int argc, const char **argv);
/**
* Lists the triggers in a dungeon room
*/
bool cmdListTriggers(int argc, const char **argv);
public:
bool _collisionOverride;
bool _disableHunger;
bool _disableCombat;
public:
Debugger();
~Debugger() override;
/**
* Gets a chest.
* If the default -2 is used, it bypasses prompting for a
* user. Otherwise, a non-negative player number is expected
*/
void getChest(int player = -2);
/**
* Executes the given command
*/
void executeCommand(const Common::String &cmd);
/**
* Executes the given command
*/
void executeCommand(int argc, const char **argv);
};
extern Debugger *g_debugger;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,501 @@
/* 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/debugger_actions.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/controllers/read_choice_controller.h"
#include "ultima/ultima4/controllers/read_int_controller.h"
#include "ultima/ultima4/controllers/reagents_menu_controller.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/gfx/textcolor.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
namespace Ultima {
namespace Ultima4 {
void DebuggerActions::summonCreature(const Common::String &name) {
const Creature *m = nullptr;
Common::String creatureName = name;
creatureName.trim();
if (creatureName.empty()) {
print("\n");
return;
}
/* find the creature by its id and spawn it */
uint id = atoi(creatureName.c_str());
if (id > 0)
m = creatureMgr->getById(id);
if (!m)
m = creatureMgr->getByName(creatureName);
if (m) {
if (gameSpawnCreature(m))
print("\n%s summoned!\n", m->getName().c_str());
else
print("\n\nNo place to put %s!\n\n", m->getName().c_str());
return;
}
print("\n%s not found\n", creatureName.c_str());
}
Direction DebuggerActions::directionFromName(const Common::String &dirStr) {
Common::String dir = dirStr;
dir.toLowercase();
if (dir == "up" || dir == "north")
return DIR_NORTH;
else if (dir == "down" || dir == "south")
return DIR_SOUTH;
else if (dir == "right" || dir == "east")
return DIR_EAST;
else if (dir == "left" || dir == "west")
return DIR_WEST;
return DIR_NONE;
}
bool DebuggerActions::destroyAt(const Coords &coords) {
Object *obj = g_context->_location->_map->objectAt(coords);
if (obj) {
if (isCreature(obj)) {
Creature *c = dynamic_cast<Creature *>(obj);
assert(c);
g_screen->screenMessage("%s Destroyed!\n", c->getName().c_str());
} else {
Tile *t = g_context->_location->_map->_tileSet->get(obj->getTile()._id);
g_screen->screenMessage("%s Destroyed!\n", t->getName().c_str());
}
g_context->_location->_map->removeObject(obj);
g_screen->screenPrompt();
return true;
}
return false;
}
bool DebuggerActions::getChestTrapHandler(int player) {
TileEffect trapType;
int randNum = xu4_random(4);
/* Do we use u4dos's way of trap-determination, or the original intended way? */
int passTest = (settings._enhancements && settings._enhancementsOptions._c64ChestTraps) ?
(xu4_random(2) == 0) : /* xu4-enhanced */
((randNum & 1) == 0); /* u4dos original way (only allows even numbers through, so only acid and poison show) */
/* Chest is trapped! 50/50 chance */
if (passTest) {
/* Figure out which trap the chest has */
switch (randNum & xu4_random(4)) {
case 0:
trapType = EFFECT_FIRE;
break; /* acid trap (56% chance - 9/16) */
case 1:
trapType = EFFECT_SLEEP;
break; /* sleep trap (19% chance - 3/16) */
case 2:
trapType = EFFECT_POISON;
break; /* poison trap (19% chance - 3/16) */
case 3:
trapType = EFFECT_LAVA;
break; /* bomb trap (6% chance - 1/16) */
default:
trapType = EFFECT_FIRE;
break;
}
/* apply the effects from the trap */
if (trapType == EFFECT_FIRE)
g_screen->screenMessage("%cAcid%c Trap!\n", FG_RED, FG_WHITE);
else if (trapType == EFFECT_POISON)
g_screen->screenMessage("%cPoison%c Trap!\n", FG_GREEN, FG_WHITE);
else if (trapType == EFFECT_SLEEP)
g_screen->screenMessage("%cSleep%c Trap!\n", FG_PURPLE, FG_WHITE);
else if (trapType == EFFECT_LAVA)
g_screen->screenMessage("%cBomb%c Trap!\n", FG_RED, FG_WHITE);
// player is < 0 during the 'O'pen spell (immune to traps)
//
// if the chest was opened by a PC, see if the trap was
// evaded by testing the PC's dex
//
if ((player >= 0) &&
(g_ultima->_saveGame->_players[player]._dex + 25 < xu4_random(100))) {
if (trapType == EFFECT_LAVA) /* bomb trap */
g_context->_party->applyEffect(trapType);
else
g_context->_party->member(player)->applyEffect(trapType);
} else {
g_screen->screenMessage("Evaded!\n");
}
return true;
}
return false;
}
bool DebuggerActions::jimmyAt(const Coords &coords) {
MapTile *tile = g_context->_location->_map->tileAt(coords, WITH_OBJECTS);
if (!tile->getTileType()->isLockedDoor())
return false;
if (g_ultima->_saveGame->_keys) {
Tile *door = g_context->_location->_map->_tileSet->getByName("door");
assertMsg(door, "no door tile found in tileset");
g_ultima->_saveGame->_keys--;
g_context->_location->_map->_annotations->add(coords, door->getId());
g_screen->screenMessage("\nUnlocked!\n");
} else
g_screen->screenMessage("%cNo keys left!%c\n", FG_GREY, FG_WHITE);
return true;
}
bool DebuggerActions::mixReagentsForSpellU4(int spell) {
Ingredients ingredients;
g_screen->screenMessage("Reagent: ");
while (1) {
int choice = ReadChoiceController::get("abcdefgh\n\r \033");
// done selecting reagents? mix it up and prompt to mix
// another spell
if (choice == '\n' || choice == '\r' || choice == ' ') {
g_screen->screenMessage("\n\nYou mix the Reagents, and...\n");
if (g_spells->spellMix(spell, &ingredients))
g_screen->screenMessage("Success!\n\n");
else
g_screen->screenMessage("It Fizzles!\n\n");
return false;
}
// escape: put ingredients back and quit mixing
if (choice == -1 || choice == '\033') {
ingredients.revert();
return true;
}
g_screen->screenMessage("\n");
if (!ingredients.addReagent((Reagent)(choice - 'a')))
g_screen->screenMessage("%cNone Left!%c\n", FG_GREY, FG_WHITE);
g_screen->screenMessage("Reagent: ");
}
return true;
}
bool DebuggerActions::mixReagentsForSpellU5(int spell) {
Ingredients ingredients;
g_screen->screenDisableCursor();
g_context->_stats->getReagentsMenu()->reset(); // reset the menu, highlighting the first item
ReagentsMenuController getReagentsController(g_context->_stats->getReagentsMenu(), &ingredients, g_context->_stats->getMainArea());
eventHandler->pushController(&getReagentsController);
getReagentsController.waitFor();
g_context->_stats->getMainArea()->disableCursor();
g_screen->screenEnableCursor();
printN("How many? ");
int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
gameSpellMixHowMany(spell, howmany, &ingredients);
return true;
}
bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingredients) {
int i;
// Entered 0 mixtures, don't mix anything!
if (num == 0) {
print("\nNone mixed!");
ingredients->revert();
return false;
}
// If they ask for more than will give them 99, only use what they need
if (num > 99 - g_ultima->_saveGame->_mixtures[spell]) {
num = 99 - g_ultima->_saveGame->_mixtures[spell];
print("\n%cOnly need %d!%c", FG_GREY, num, FG_WHITE);
}
print("\nMixing %d...", num);
// See if there's enough reagents to make number of mixtures requested
if (!ingredients->checkMultiple(num)) {
print("\n%cYou don't have enough reagents to mix %d spells!%c", FG_GREY, num, FG_WHITE);
ingredients->revert();
return false;
}
print("\nYou mix the Reagents, and...");
if (g_spells->spellMix(spell, ingredients)) {
print("Success!\n");
// Mix the extra spells
ingredients->multiply(num);
for (i = 0; i < num - 1; i++)
g_spells->spellMix(spell, ingredients);
} else {
print("It Fizzles!\n");
}
return true;
}
bool DebuggerActions::openAt(const Coords &coords) {
const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS);
if (!tile->isDoor() &&
!tile->isLockedDoor())
return false;
if (tile->isLockedDoor()) {
g_screen->screenMessage("%cCan't!%c\n", FG_GREY, FG_WHITE);
return true;
}
Tile *floor = g_context->_location->_map->_tileSet->getByName("brick_floor");
assertMsg(floor, "no floor tile found in tileset");
g_context->_location->_map->_annotations->add(coords, floor->getId(), false, true)->setTTL(4);
g_screen->screenMessage("\nOpened!\n");
return true;
}
void DebuggerActions::gameCastSpell(uint spell, int caster, int param) {
SpellCastError spellError;
Common::String msg;
if (!g_spells->spellCast(spell, caster, param, &spellError, true)) {
msg = g_spells->spellGetErrorMessage(spell, spellError);
if (!msg.empty())
g_screen->screenMessage("%s", msg.c_str());
}
}
bool DebuggerActions::talkAt(const Coords &coords) {
extern int personIsVendor(const Person * person);
City *city;
/* can't have any conversations outside of town */
if (!isCity(g_context->_location->_map)) {
g_screen->screenMessage("Funny, no response!\n");
return true;
}
city = dynamic_cast<City *>(g_context->_location->_map);
assert(city);
Person *talker = city ? city->personAt(coords) : nullptr;
// Make sure we have someone we can talk with
if (!talker || !talker->canConverse())
return false;
/* No response from alerted guards... does any monster both
attack and talk besides Nate the Snake? */
if (talker->getMovementBehavior() == MOVEMENT_ATTACK_AVATAR &&
talker->getId() != PYTHON_ID)
return false;
// If we're talking to Lord British and the avatar is dead, LB resurrects them!
if (talker->getNpcType() == NPC_LORD_BRITISH &&
g_context->_party->member(0)->getStatus() == STAT_DEAD) {
g_screen->screenMessage("%s, Thou shalt live again!\n", g_context->_party->member(0)->getName().c_str());
g_context->_party->member(0)->setStatus(STAT_GOOD);
g_context->_party->member(0)->heal(HT_FULLHEAL);
gameSpellEffect('r', -1, SOUND_LBHEAL);
}
Conversation conv;
conv._script->addProvider("party", g_context->_party);
conv._script->addProvider("context", g_context);
conv._state = Conversation::INTRO;
conv._reply = talker->getConversationText(&conv, "");
conv._playerInput.clear();
talkRunConversation(conv, talker, false);
// Ensure the end of the conversation ends the line
if (g_context->_col != 0)
g_screen->screenMessage("\n");
return true;
}
void DebuggerActions::talkRunConversation(Conversation &conv, Person *talker, bool showPrompt) {
while (conv._state != Conversation::DONE) {
// TODO: instead of calculating linesused again, cache the
// result in person.cpp somewhere.
int linesused = linecount(conv._reply.front(), TEXT_AREA_W);
g_screen->screenMessage("%s", conv._reply.front().c_str());
conv._reply.pop_front();
/* if all chunks haven't been shown, wait for a key and process next chunk*/
int size = conv._reply.size();
if (size > 0) {
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper continueDialog;
continueDialog.updateChoices(" ");
#endif
ReadChoiceController::get("");
continue;
}
/* otherwise, clear current reply and proceed based on conversation state */
conv._reply.clear();
/* they're attacking you! */
if (conv._state == Conversation::ATTACK) {
conv._state = Conversation::DONE;
talker->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
}
if (conv._state == Conversation::DONE)
break;
/* When Lord British heals the party */
else if (conv._state == Conversation::FULLHEAL) {
int i;
for (i = 0; i < g_context->_party->size(); i++) {
g_context->_party->member(i)->heal(HT_CURE); // cure the party
g_context->_party->member(i)->heal(HT_FULLHEAL); // heal the party
}
gameSpellEffect('r', -1, SOUND_MAGIC); // same spell effect as 'r'esurrect
conv._state = Conversation::TALK;
}
/* When Lord British checks and advances each party member's level */
else if (conv._state == Conversation::ADVANCELEVELS) {
gameLordBritishCheckLevels();
conv._state = Conversation::TALK;
}
if (showPrompt) {
Common::String prompt = talker->getPrompt(&conv);
if (!prompt.empty()) {
if (linesused + linecount(prompt, TEXT_AREA_W) > TEXT_AREA_H) {
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper continueDialog;
continueDialog.updateChoices(" ");
#endif
ReadChoiceController::get("");
}
g_screen->screenMessage("%s", prompt.c_str());
}
}
int maxlen;
switch (conv.getInputRequired(&maxlen)) {
case Conversation::INPUT_STRING: {
conv._playerInput = gameGetInput(maxlen);
#ifdef IOS_ULTIMA4
g_screen->screenMessage("%s", conv.playerInput.c_str()); // Since we put this in a different window, we need to show it again.
#endif
conv._reply = talker->getConversationText(&conv, conv._playerInput.c_str());
conv._playerInput.clear();
showPrompt = true;
break;
}
case Conversation::INPUT_CHARACTER: {
char message[2];
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper yesNoHelper;
yesNoHelper.updateChoices("yn ");
#endif
int choice = ReadChoiceController::get("");
message[0] = choice;
message[1] = '\0';
conv._reply = talker->getConversationText(&conv, message);
conv._playerInput.clear();
showPrompt = true;
break;
}
case Conversation::INPUT_NONE:
conv._state = Conversation::DONE;
break;
}
}
if (conv._reply.size() > 0)
g_screen->screenMessage("%s", conv._reply.front().c_str());
}
void DebuggerActions::gameLordBritishCheckLevels() {
bool advanced = false;
for (int i = 0; i < g_context->_party->size(); i++) {
PartyMember *player = g_context->_party->member(i);
if (player->getRealLevel() <
player->getMaxLevel())
// add an extra space to separate messages
if (!advanced) {
g_screen->screenMessage("\n");
advanced = true;
}
player->advanceLevel();
}
g_screen->screenMessage("\nWhat would thou\nask of me?\n");
}
bool DebuggerActions::isCombat() const {
return dynamic_cast<CombatController *>(eventHandler->getController()) != nullptr;
}
int DebuggerActions::getCombatFocus() const {
CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
assert(cc);
return cc->getFocus();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,144 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CORE_DEBUGGER_ACTIONS_H
#define ULTIMA4_CORE_DEBUGGER_ACTIONS_H
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/game/spell.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
/**
* This is a secondary class inherited by the Debugger class
* that contains various support methods for implementing the
* different actions players can take in the game
*/
class DebuggerActions {
private:
/**
* Executes the current conversation until it is done.
*/
void talkRunConversation(Conversation &conv, Person *talker, bool showPrompt);
/**
* Check the levels of each party member while talking to Lord British
*/
void gameLordBritishCheckLevels();
protected:
/**
* Returns true if the debugger is active
*/
virtual bool isDebuggerActive() const = 0;
/**
* Prints a message to the console if it's active, or to the
* game screen if not
*/
virtual void print(const char *fmt, ...) = 0;
/**
* Prints a message to the console if it's active, or to the
* game screen if not
*/
virtual void printN(const char *fmt, ...) = 0;
/**
* Prompts for input, but only if debugger isn't running
*/
virtual void prompt() = 0;
/**
* Returns true if combat is currently active
*/
bool isCombat() const;
/**
* Returns currently focused character in combat mode
*/
int getCombatFocus() const;
public:
virtual ~DebuggerActions() {}
/**
* Summons a creature given by 'creatureName'. This can either be given
* as the creature's name, or the creature's id. Once it finds the
* creature to be summoned, it calls gameSpawnCreature() to spawn it.
*/
void summonCreature(const Common::String &name);
/**
* Destroy object at a given co-ordinate
*/
bool destroyAt(const Coords &coords);
/**
* Returns a direction from a given string
*/
Direction directionFromName(const Common::String &dirStr);
/**
* Called by getChest() to handle possible traps on chests
**/
bool getChestTrapHandler(int player);
/**
* Attempts to jimmy a locked door at map coordinates x,y. The locked
* door is replaced by a permanent annotation of an unlocked door
* tile.
*/
bool jimmyAt(const Coords &coords);
/**
* Prompts for spell reagents to mix in the traditional Ultima IV
* style.
*/
bool mixReagentsForSpellU4(int spell);
/**
* Prompts for spell reagents to mix with an Ultima V-like menu.
*/
bool mixReagentsForSpellU5(int spell);
bool gameSpellMixHowMany(int spell, int num, Ingredients *ingredients);
/**
* Attempts to open a door at map coordinates x,y. The door is
* replaced by a temporary annotation of a floor tile for 4 turns.
*/
bool openAt(const Coords &coords);
void gameCastSpell(uint spell, int caster, int param);
/**
* Begins a conversation with the NPC at map coordinates x,y. If no
* NPC is present at that point, zero is returned.
*/
bool talkAt(const Coords &coords);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,83 @@
/* 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/lzw/hash.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
int probe1(byte root, int codeword) {
int newHashCode = ((root << 4) ^ codeword) & 0xfff;
return (newHashCode);
}
/* The secondary probe uses some assembler instructions that aren't easily translated to C. */
int probe2(byte root, int codeword) {
/* registers[0] == AX, registers[1] == DX */
long registers[2], temp;
long carry, oldCarry;
int i, j;
/* the pre-mul part */
registers[1] = 0;
registers[0] = ((root << 1) + codeword) | 0x800;
/* the mul part (simulated mul instruction) */
/* DX:AX = AX * AX */
temp = (registers[0] & 0xff) * (registers[0] & 0xff);
temp += 2 * (registers[0] & 0xff) * (registers[0] >> 8) * 0x100;
registers[1] = (temp >> 16) + (registers[0] >> 8) * (registers[0] >> 8);
registers[0] = temp & 0xffff;
/* if DX != 0, the mul instruction sets the carry flag */
if (registers[1] == 00) {
carry = 0;
} else {
carry = 1;
}
/* the rcl part */
for (i = 0; i < 2; i++) { /* 2 rcl's */
for (j = 0; j < 2; j++) { /* rotate through 2 registers */
oldCarry = carry;
carry = (registers[j] >> 15) & 1;
registers[j] = (registers[j] << 1) | oldCarry;
registers[j] = registers[j] & 0xffff; /* make sure register stays 16 bit */
}
}
/* final touches */
registers[0] = ((registers[0] >> 8) | (registers[1] << 8)) & 0xfff;
return ((int)registers[0]);
}
int probe3(int hashCode) {
const long probeOffset = 0x1fd; /* I think 0x1fd is prime */
long newHashCode = (hashCode + probeOffset) & 0xfff;
return ((int)newHashCode);
}
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,39 @@
/* 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_CORE_LZW_HASH_H
#define ULTIMA4_CORE_LZW_HASH_H
#include "common/scummsys.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
int probe1(byte root, int codeword);
int probe2(byte root, int codeword);
int probe3(int hashCode);
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,315 @@
/* 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/>.
*
*/
/*
* A few files from Ultima 4 (PC version) have been compressed with the LZW algorithm.
* There are two things that make the U4 implementation of the LZW decoding algorithm special:
* 1) It uses a fixed codeword length of 12 bits.
* The advantages over variable-length codewords are faster decompression and simpler code.
* 2) The dictionary is implemented as a hash table.
* While the dictionary is supposed to implemented as a hash table in the LZW *en*coder (to speed up
* string searches), there is no reason not to implement it as a simple array in the decoder.
* But since U4 uses a hash table in the decoder, this C version must do the same (or it won't be
* able to decode the U4 files).
*
* An explanation on LZW data (de)compression can be found here:
* https://web.archive.org/web/20191231131544/https://marknelson.us/posts/1989/10/01/lzw-data-compression.html
* https://web.archive.org/web/20191231131532/https://marknelson.us/posts/2011/11/08/lzw-revisited.html
*/
#include "ultima/ultima4/core/lzw/lzw.h"
#include "ultima/ultima4/core/lzw/hash.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
typedef void (*WRITE_DECOMP)(byte root, byte *destination, long *position);
struct lzwDictionaryEntry {
byte root;
int codeword;
byte occupied;
};
long generalizedDecompress(WRITE_DECOMP outFunc, byte *compressedMem, byte *decompressedMem, long compressedSize);
int getNextCodeword(long *bitsRead, byte *compressedMem);
void discardRoot(byte root, byte *destination, long *position);
void outputRoot(byte root, byte *destination, long *position);
void getString(int codeword, lzwDictionaryEntry *lzwDictionary, byte *stack, int *elementsInStack);
int getNewHashCode(byte root, int codeword, lzwDictionaryEntry *dictionary);
byte hashPosFound(int hashCode, byte root, int codeword, lzwDictionaryEntry *dictionary);
/*
* This function returns the decompressed size of a block of compressed data.
* It doesn't decompress the data.
* Use this function if you want to decompress a block of data, but don't know the decompressed size
* in advance.
*
* There is some error checking to detect if the compressed data is corrupt, but it's only rudimentary.
* Returns:
* No errors: (long) decompressed size
* Error: (long) -1
*/
long lzwGetDecompressedSize(byte *compressedMem, long compressedSize) {
return (generalizedDecompress(&discardRoot, compressedMem, nullptr, compressedSize));
}
/*
* Decompresses a block of compressed data from memory to memory.
* Use this function if you already know the decompressed size.
*
* This function assumes that *decompressed_mem is already allocated, and that the decompressed data
* will fit into *decompressed_mem.
* There is some error checking to detect if the compressed data is corrupt, but it's only rudimentary.
* Returns:
* No errors: (long) decompressed size
* Error: (long) -1
*/
long lzwDecompress(byte *compressedMem, byte *decompressedMem, long compressedSize) {
return (generalizedDecompress(&outputRoot, compressedMem, decompressedMem, compressedSize));
}
/* --------------------------------------------------------------------------------------
Functions used only inside lzw.c
-------------------------------------------------------------------------------------- */
/*
* This function does the actual decompression work.
* Parameters:
* perform_decompression: FALSE ==> return decompressed size, but discard decompressed data
* compressed_mem: compressed data
* decompressed_mem: this is where the compressed data will be decompressed to
* compressed_size: size of the compressed data (in bytes)
*/
long generalizedDecompress(WRITE_DECOMP outFunc, byte *compressedMem, byte *decompressedMem, long compressedSize) {
int i;
/* re-initialize the dictionary when there are more than 0xccc entries */
const int maxDictEntries = 0xccc;
const int lzwStackSize = 0x8000;
const int lzwDictionarySize = 0x1000;
int old_code;
int new_code;
byte character;
long bitsRead = 0;
long bytesWritten = 0;
/* newpos: position in the dictionary where new codeword was added */
/* must be equal to current codeword (if it isn't, the compressed data must be corrupt) */
/* unknownCodeword: is the current codeword in the dictionary? */
int newpos;
byte unknownCodeword;
/* initialize the dictionary and the stack */
lzwDictionaryEntry *lzwDictionary = (lzwDictionaryEntry *) malloc(sizeof(lzwDictionaryEntry) * lzwDictionarySize);
int codewordsInDictionary = 0;
byte *lzwStack = (byte *) malloc(sizeof(byte) * lzwStackSize);
int elementsInStack = 0;
/* clear the dictionary */
memset(lzwDictionary, 0, sizeof(lzwDictionaryEntry) * lzwDictionarySize);
for (i = 0; i < 0x100; i++) {
lzwDictionary[i].occupied = 1;
}
if (bitsRead + 12 <= compressedSize * 8) {
/* read OLD_CODE */
old_code = getNextCodeword(&bitsRead, compressedMem);
/* CHARACTER = OLD_CODE */
character = (byte)old_code;
/* output OLD_CODE */
outFunc(character, decompressedMem, &bytesWritten);
while (bitsRead + 12 <= compressedSize * 8) { /* WHILE there are still input characters DO */
/* read NEW_CODE */
new_code = getNextCodeword(&bitsRead, compressedMem);
if (lzwDictionary[new_code].occupied) { /* is the codeword in the dictionary? */
/* codeword is present in the dictionary */
/* it must either be a root or a non-root that has already been added to the dicionary */
unknownCodeword = 0;
/* STRING = get translation of NEW_CODE */
getString(new_code, lzwDictionary, lzwStack, &elementsInStack);
} else {
/* codeword is yet to be defined */
unknownCodeword = 1;
/* STRING = get translation of OLD_CODE */
/* STRING = STRING+CHARACTER */
lzwStack[elementsInStack] = character; /* push character on the stack */
elementsInStack++;
getString(old_code, lzwDictionary, lzwStack, &elementsInStack);
}
/* CHARACTER = first character in STRING */
character = lzwStack[elementsInStack - 1]; /* element at top of stack */
/* output STRING */
while (elementsInStack > 0) {
outFunc(lzwStack[elementsInStack - 1], decompressedMem, &bytesWritten);
elementsInStack--;
}
/* add OLD_CODE + CHARACTER to the translation table */
newpos = getNewHashCode(character, old_code, lzwDictionary);
lzwDictionary[newpos].root = character;
lzwDictionary[newpos].codeword = old_code;
lzwDictionary[newpos].occupied = 1;
codewordsInDictionary++;
/* check for errors */
if (unknownCodeword && (newpos != new_code)) {
/* clean up */
free(lzwStack);
free(lzwDictionary);
return (-1);
}
if (codewordsInDictionary > maxDictEntries) {
/* wipe dictionary */
codewordsInDictionary = 0;
memset(lzwDictionary, 0, sizeof(lzwDictionaryEntry) * lzwDictionarySize);
for (i = 0; i < 0x100; i++) {
lzwDictionary[i].occupied = 1;
}
if (bitsRead + 12 <= compressedSize * 8) {
new_code = getNextCodeword(&bitsRead, compressedMem);
character = (byte)new_code;
outFunc(character, decompressedMem, &bytesWritten);
} else {
/* clean up */
free(lzwStack);
free(lzwDictionary);
return (bytesWritten);
}
}
/* OLD_CODE = NEW_CODE */
old_code = new_code;
}
}
/* clean up */
free(lzwStack);
free(lzwDictionary);
return (bytesWritten);
}
/* read the next 12-bit codeword from the compressed data */
int getNextCodeword(long *bitsRead, byte *compressedMem) {
int codeword = (compressedMem[(*bitsRead) / 8] << 8) + compressedMem[(*bitsRead) / 8 + 1];
codeword = codeword >> (4 - ((*bitsRead) % 8));
codeword = codeword & 0xfff;
(*bitsRead) += 12;
return (codeword);
}
/* increment position pointer, but do not write root to memory */
void discardRoot(byte root, byte *destination, long *position) {
(*position)++;
}
/* output a root to memory */
void outputRoot(byte root, byte *destination, long *position) {
destination[*position] = root;
(*position)++;
}
/* --------------------------------------------------------------------------------------
Dictionary-related functions
-------------------------------------------------------------------------------------- */
/* pushes the string associated with codeword onto the stack */
void getString(int codeword, lzwDictionaryEntry *dictionary, byte *stack, int *elementsInStack) {
byte root;
int currentCodeword = codeword;
while (currentCodeword > 0xff) {
root = dictionary[currentCodeword].root;
currentCodeword = dictionary[currentCodeword].codeword;
stack[*elementsInStack] = root;
(*elementsInStack)++;
}
/* push the root at the leaf */
stack[*elementsInStack] = (byte)currentCodeword;
(*elementsInStack)++;
}
int getNewHashCode(byte root, int codeword, lzwDictionaryEntry *dictionary) {
int hashCode;
/* probe 1 */
hashCode = probe1(root, codeword);
if (hashPosFound(hashCode, root, codeword, dictionary)) {
return (hashCode);
}
/* probe 2 */
hashCode = probe2(root, codeword);
if (hashPosFound(hashCode, root, codeword, dictionary)) {
return (hashCode);
}
/* probe 3 */
do {
hashCode = probe3(hashCode);
} while (! hashPosFound(hashCode, root, codeword, dictionary));
return (hashCode);
}
byte hashPosFound(int hashCode, byte root, int codeword, lzwDictionaryEntry *dictionary) {
if (hashCode > 0xff) {
// hash codes must not be roots
byte c1 = 0, c2 = 0, c3 = 0;
if (dictionary[hashCode].occupied) {
// hash table position is occupied
c1 = 1;
// is our (root,codeword) pair already in the hash table?
c2 = dictionary[hashCode].root == root;
c3 = dictionary[hashCode].codeword == codeword;
} else {
// hash table position is free
c1 = 0;
}
return (!c1) || (c1 && c2 && c3);
} else {
return 0;
}
}
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,38 @@
/* 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_CORE_LZW_LZW_H
#define ULTIMA4_CORE_LZW_LZW_H
#include "common/scummsys.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
long lzwGetDecompressedSize(byte *compressedMem, long compressedSize);
long lzwDecompress(byte *compressedMem, byte *decompressedMem, long compressedSize);
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,155 @@
/* 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/lzw/lzw.h"
#include "ultima/ultima4/core/lzw/u4decode.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
/*
* Loads a file, decompresses it (from memory to memory), and writes the decompressed data to another file
* Returns:
* -1 if there was an error
* the decompressed file length, on success
*/
long decompress_u4_file(Common::SeekableReadStream *in, long filesize, void **out) {
byte *compressed_mem, *decompressed_mem;
long compressed_filesize, decompressed_filesize;
long errorCode;
/* size of the compressed input file */
compressed_filesize = filesize;
/* input file should be longer than 0 bytes */
if (compressed_filesize == 0)
return (-1);
/* check if the input file is _not_ a valid LZW-compressed file */
if (!mightBeValidCompressedFile(in))
return (-1);
/* load compressed file into compressed_mem[] */
compressed_mem = (byte *) malloc(compressed_filesize);
in->read(compressed_mem, compressed_filesize);
/*
* determine decompressed file size
* if lzw_get_decompressed_size() can't determine the decompressed size (i.e. the compressed
* data is corrupt), it returns -1
*/
decompressed_filesize = lzwGetDecompressedSize(compressed_mem, compressed_filesize);
if (decompressed_filesize <= 0) {
return (-1);
}
/* decompress file from compressed_mem[] into decompressed_mem[] */
decompressed_mem = (byte *) malloc(decompressed_filesize);
/* testing: clear destination mem */
memset(decompressed_mem, 0, decompressed_filesize);
errorCode = lzwDecompress(compressed_mem, decompressed_mem, compressed_filesize);
free(compressed_mem);
*out = decompressed_mem;
return (errorCode);
}
long decompress_u4_memory(void *in, long inlen, void **out) {
byte *compressed_mem, *decompressed_mem;
long compressed_filesize, decompressed_filesize;
long errorCode;
/* size of the compressed input */
compressed_filesize = inlen;
/* input file should be longer than 0 bytes */
if (compressed_filesize == 0)
return (-1);
compressed_mem = (byte *) in;
/*
* determine decompressed data size
* if lzw_get_decompressed_size() can't determine the decompressed size (i.e. the compressed
* data is corrupt), it returns -1
*/
decompressed_filesize = lzwGetDecompressedSize(compressed_mem, compressed_filesize);
if (decompressed_filesize <= 0) {
return (-1);
}
/* decompress file from compressed_mem[] into decompressed_mem[] */
decompressed_mem = (byte *) malloc(decompressed_filesize);
/* testing: clear destination mem */
memset(decompressed_mem, 0, decompressed_filesize);
errorCode = lzwDecompress(compressed_mem, decompressed_mem, compressed_filesize);
*out = decompressed_mem;
return (errorCode);
}
/*
* Returns the size of a file, and moves the file pointer to the beginning.
* The file must already be open when this function is called.
*/
long getFilesize(Common::SeekableReadStream *input_file) {
return input_file->size();
}
/*
* If the input file is a valid LZW-compressed file, the upper 4 bits of
* the first byte must be 0, because the first codeword is always a root.
*/
byte mightBeValidCompressedFile(Common::SeekableReadStream *input_file) {
byte firstByte;
byte c1, c2, c3; /* booleans */
long input_filesize;
/* check if the input file has a valid size */
/* the compressed file is made up of 12-bit codewords, */
/* so there are either 0 or 4 bits of wasted space */
input_filesize = getFilesize(input_file);
c1 = (input_filesize * 8) % 12 == 0;
c2 = (input_filesize * 8 - 4) % 12 == 0;
// read first byte, and then reset back file pointer
input_file->seek(0);
firstByte = input_file->readByte();
input_file->seek(0);
c3 = (firstByte >> 4) == 0;
// check if upper 4 bits are 0
return ((c1 || c2) && c3);
}
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,40 @@
/* 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_CORE_LZW_U4DECODE_H
#define ULTIMA4_CORE_LZW_U4DECODE_H
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
namespace LZW {
long decompress_u4_file(Common::SeekableReadStream *in, long filesize, void **out);
long getFilesize(Common::SeekableReadStream *input_file);
byte mightBeValidCompressedFile(Common::SeekableReadStream *compressed_file);
long decompress_u4_memory(void *in, long inlen, void **out);
} // End of namespace LZW
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,107 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CORE_OBSERVABLE_H
#define ULTIMA4_CORE_OBSERVABLE_H
#include "ultima/ultima4/core/observer.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
/**
* Classes can report updates to a list of decoupled Observers by
* extending this class.
*
* The O class parameter should be a pointer to the class of the
* observable itself, so it can be passed in a typesafe manner to the
* observers update method.
*
* The A class can be any additional information to pass to observers.
* Observables that don't need to pass an argument when they update
* observers should use the default "NoArg" class for the second
* template parameter and pass nullptr to notifyObservers.
*/
template <class O, class A = NoArg *>
class Observable {
public:
Observable() : _changed(false) {}
void addObserver(Observer<O, A> *o) {
typename Std::vector< Observer<O, A> *>::iterator i;
i = Common::find(_observers.begin(), _observers.end(), o);
if (i == _observers.end())
_observers.push_back(o);
}
int countObservers() const {
return _observers.size();
}
void deleteObserver(Observer<O, A> *o) {
typename Std::vector< Observer<O, A> *>::iterator i;
i = Common::find(_observers.begin(), _observers.end(), o);
if (i != _observers.end())
_observers.erase(i);
}
void deleteObservers() {
_observers.clear();
}
bool hasChanged() const {
return _changed;
}
void notifyObservers(A arg) {
if (!_changed)
return;
// vector iterators are invalidated if erase is called, so a copy
// is used to prevent problems if the observer removes itself (or
// otherwise changes the observer list)
typename Std::vector< Observer<O, A> *> tmp = _observers;
clearChanged();
for (auto *observer : tmp) {
observer->update(static_cast<O>(this), arg);
}
}
protected:
void clearChanged() {
_changed = false;
}
void setChanged() {
_changed = true;
}
private:
bool _changed;
Std::vector< Observer<O, A> *> _observers;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,67 @@
/* 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_CORE_OBSERVER_H
#define ULTIMA4_CORE_OBSERVER_H
namespace Ultima {
namespace Ultima4 {
template<class O, class A>
class Observable;
class NoArg;
/**
* This is the interface a class must implement to watch an
* Observable.
*/
template<class O, class A = NoArg *>
class Observer {
public:
virtual void update(O observable, A arg) = 0;
virtual ~Observer() {}
};
/**
* A specialized observer for watching observables that don't use the
* "arg" parameter to update.
*/
template<class O>
class Observer<O, NoArg *> {
public:
virtual void update(O observable, NoArg *arg) {
update(observable);
}
virtual void update(O observable) = 0;
virtual ~Observer() {}
};
/**
* Just an empty marker class to identify observers that take no args
* on update.
*/
class NoArg {
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,190 @@
/* 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/settings.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/core/utils.h"
#include "common/file.h"
namespace Ultima {
namespace Ultima4 {
/*
* Initialize static members
*/
Settings *Settings::_instance = nullptr;
bool SettingsEnhancementOptions::operator==(const SettingsEnhancementOptions &s) const {
return _activePlayer == s._activePlayer
&& _u5SpellMixing == s._u5SpellMixing
&& _u5Shrines == s._u5Shrines
&& _u5Combat == s._u5Combat
&& _slimeDivides == s._slimeDivides
&& _gazerSpawnsInsects == s._gazerSpawnsInsects
&& _textColorization == s._textColorization
&& _c64ChestTraps == s._c64ChestTraps
&& _smartEnterKey == s._smartEnterKey
&& _peerShowsObjects == s._peerShowsObjects
&& _u4TileTransparencyHack == s._u4TileTransparencyHack
&& _u4TileTransparencyHackPixelShadowOpacity == s._u4TileTransparencyHackPixelShadowOpacity
&& _u4TrileTransparencyHackShadowBreadth == s._u4TrileTransparencyHackShadowBreadth;
}
/*-------------------------------------------------------------------*/
bool SettingsData::operator==(const SettingsData &s) const {
return _videoType == s._videoType
&& _battleSpeed == s._battleSpeed
&& _campingAlwaysCombat == s._campingAlwaysCombat
&& _campTime == s._campTime
&& _debug == s._debug
&& _enhancements == s._enhancements
&& _enhancementsOptions == s._enhancementsOptions
&& _filterMoveMessages == s._filterMoveMessages
&& _gameCyclesPerSecond == s._gameCyclesPerSecond
&& _screenAnimationFramesPerSecond == s._screenAnimationFramesPerSecond
&& _innAlwaysCombat == s._innAlwaysCombat
&& _innTime == s._innTime
&& _mouseOptions == s._mouseOptions
&& _screenShakes == s._screenShakes
&& _gamma == s._gamma
&& _shakeInterval == s._shakeInterval
&& _shortcutCommands == s._shortcutCommands
&& _shrineTime == s._shrineTime
&& _spellEffectSpeed == s._spellEffectSpeed
&& _validateXml == s._validateXml
&& _volumeFades == s._volumeFades
&& _titleSpeedRandom == s._titleSpeedRandom
&& _titleSpeedOther == s._titleSpeedOther;
}
bool SettingsData::operator!=(const SettingsData &s) const {
return !operator==(s);
}
/*-------------------------------------------------------------------*/
Settings::Settings() {
// Synchronize settings
Shared::ConfSerializer s(false);
synchronize(s);
// Set up various other constants that aren't configurable
_game = "Ultima IV";
_debug = gDebugLevel > 0;
_innAlwaysCombat = 0;
_campingAlwaysCombat = 0;
_screenAnimationFramesPerSecond = DEFAULT_ANIMATION_FRAMES_PER_SECOND;
bool isEnhanced = _videoType != "EGA";
_scale = isEnhanced ? 2 : 1;
_filter = isEnhanced ? "Scale2x" : "point";
_battleDiffs.push_back("Normal");
_battleDiffs.push_back("Hard");
_battleDiffs.push_back("Expert");
_eventTimerGranularity = (1000 / _gameCyclesPerSecond);
}
Settings &Settings::getInstance() {
if (_instance == nullptr)
_instance = new Settings();
return *_instance;
}
void Settings::setData(const SettingsData &data) {
// bitwise copy is safe
*(SettingsData *)this = data;
bool isEnhanced = _videoType != "EGA";
_scale = isEnhanced ? 2 : 1;
_filter = isEnhanced ? "Scale2x" : "point";
}
bool Settings::write() {
Shared::ConfSerializer s(true);
synchronize(s);
setChanged();
notifyObservers(nullptr);
return true;
}
void Settings::synchronize(Shared::ConfSerializer &s) {
// General settings
bool isEnhanced = g_ultima->isEnhanced();
s.syncAsString("video", _videoType, isEnhanced ? "new" : "EGA");
s.syncAsString("gemLayout", _gemLayout, DEFAULT_GEM_LAYOUT);
s.syncAsString("lineOfSight", _lineOfSight, DEFAULT_LINEOFSIGHT);
s.syncAsBool("screenShakes", _screenShakes, DEFAULT_SCREEN_SHAKES);
s.syncAsInt("gamma", _gamma, DEFAULT_GAMMA);
s.syncAsBool("volumeFades", _volumeFades, DEFAULT_VOLUME_FADES);
s.syncAsBool("shortcutCommands", _shortcutCommands, DEFAULT_SHORTCUT_COMMANDS);
s.syncAsBool("filterMoveMessages", _filterMoveMessages, DEFAULT_FILTER_MOVE_MESSAGES);
s.syncAsInt("battlespeed", _battleSpeed, DEFAULT_BATTLE_SPEED);
s.syncAsBool("enhancements", _enhancements, DEFAULT_ENHANCEMENTS);
s.syncAsInt("gameCyclesPerSecond", _gameCyclesPerSecond, DEFAULT_CYCLES_PER_SECOND);
s.syncAsString("battleDiff", _battleDiff, DEFAULT_BATTLE_DIFFICULTY);
s.syncAsBool("validateXml", _validateXml, DEFAULT_VALIDATE_XML);
s.syncAsInt("spellEffectSpeed", _spellEffectSpeed, DEFAULT_SPELL_EFFECT_SPEED);
s.syncAsInt("campTime", _campTime, DEFAULT_CAMP_TIME);
s.syncAsInt("innTime", _innTime, DEFAULT_INN_TIME);
s.syncAsInt("shrineTime", _shrineTime, DEFAULT_SHRINE_TIME);
s.syncAsInt("shakeInterval", _shakeInterval, DEFAULT_SHAKE_INTERVAL);
s.syncAsInt("titleSpeedRandom", _titleSpeedRandom, DEFAULT_TITLE_SPEED_RANDOM);
s.syncAsInt("titleSpeedOther", _titleSpeedOther, DEFAULT_TITLE_SPEED_OTHER);
s.syncAsBool("innAlwaysCombat", _innAlwaysCombat, false);
s.syncAsBool("campingAlwaysCombat", _campingAlwaysCombat, false);
s.syncAsBool("u5spellMixing", _enhancementsOptions._u5SpellMixing, isEnhanced);
// all specific minor enhancements default to "on", any major enhancements default to "off"
// minor enhancement options
s.syncAsBool("activePlayer", _enhancementsOptions._activePlayer, true);
s.syncAsBool("u5shrines", _enhancementsOptions._u5Shrines, true);
s.syncAsBool("slimeDivides", _enhancementsOptions._slimeDivides, true);
s.syncAsBool("gazerSpawnsInsects", _enhancementsOptions._gazerSpawnsInsects, true);
s.syncAsBool("textColorization", _enhancementsOptions._textColorization, false);
s.syncAsBool("c64chestTraps", _enhancementsOptions._c64ChestTraps, true);
s.syncAsBool("smartEnterKey", _enhancementsOptions._smartEnterKey, true);
// major enhancement options
s.syncAsBool("peerShowsObjects", _enhancementsOptions._peerShowsObjects, false);
s.syncAsBool("u5combat", _enhancementsOptions._u5Combat, false);
// graphics enhancements options
s.syncAsBool("renderTileTransparency", _enhancementsOptions._u4TileTransparencyHack, true);
s.syncAsInt("transparentTilePixelShadowOpacity", _enhancementsOptions._u4TileTransparencyHackPixelShadowOpacity, DEFAULT_SHADOW_PIXEL_OPACITY);
s.syncAsInt("transparentTileShadowSize", _enhancementsOptions._u4TrileTransparencyHackShadowBreadth, DEFAULT_SHADOW_PIXEL_SIZE);
// mouse options
s.syncAsBool("mouseEnabled", _mouseOptions._enabled, true);
s.syncAsString("logging", _logging, DEFAULT_LOGGING);
}
const Std::vector<Common::String> &Settings::getBattleDiffs() {
return _battleDiffs;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,186 @@
/* 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_CORE_SETTINGS_H
#define ULTIMA4_CORE_SETTINGS_H
#include "ultima/ultima4/core/observable.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/shared/conf/conf_serializer.h"
#include "common/hash-str.h"
namespace Ultima {
namespace Ultima4 {
#define MIN_SHAKE_INTERVAL 50
#define MAX_BATTLE_SPEED 10
#define MAX_KEY_DELAY 1000
#define MAX_KEY_INTERVAL 100
#define MAX_CYCLES_PER_SECOND 20
#define MAX_SPELL_EFFECT_SPEED 10
#define MAX_CAMP_TIME 10
#define MAX_INN_TIME 10
#define MAX_SHRINE_TIME 20
#define MAX_SHAKE_INTERVAL 200
#define MAX_VOLUME 10
#define DEFAULT_GEM_LAYOUT "Standard"
#define DEFAULT_LINEOFSIGHT "DOS"
#define DEFAULT_SCREEN_SHAKES 1
#define DEFAULT_GAMMA 100
#define DEFAULT_VOLUME_FADES 1
#define DEFAULT_SHORTCUT_COMMANDS 0
#define DEFAULT_KEY_DELAY 500
#define DEFAULT_KEY_INTERVAL 30
#define DEFAULT_FILTER_MOVE_MESSAGES 0
#define DEFAULT_BATTLE_SPEED 5
#define DEFAULT_ENHANCEMENTS 1
#define DEFAULT_CYCLES_PER_SECOND 4
#define DEFAULT_ANIMATION_FRAMES_PER_SECOND 24
#define DEFAULT_DEBUG 0
#define DEFAULT_VALIDATE_XML 1
#define DEFAULT_SPELL_EFFECT_SPEED 10
#define DEFAULT_CAMP_TIME 10
#define DEFAULT_INN_TIME 8
#define DEFAULT_SHRINE_TIME 16
#define DEFAULT_SHAKE_INTERVAL 100
#define DEFAULT_BATTLE_DIFFICULTY "Normal"
#define DEFAULT_LOGGING ""
#define DEFAULT_TITLE_SPEED_RANDOM 150
#define DEFAULT_TITLE_SPEED_OTHER 30
//--Tile transparency stuff
#define DEFAULT_SHADOW_PIXEL_OPACITY 64
#define DEFAULT_SHADOW_PIXEL_SIZE 2
struct SettingsEnhancementOptions {
bool _activePlayer;
bool _u5SpellMixing;
bool _u5Shrines;
bool _u5Combat;
bool _slimeDivides;
bool _gazerSpawnsInsects;
bool _textColorization;
bool _c64ChestTraps;
bool _smartEnterKey;
bool _peerShowsObjects;
bool _u4TileTransparencyHack;
int _u4TileTransparencyHackPixelShadowOpacity;
int _u4TrileTransparencyHackShadowBreadth;
bool operator==(const SettingsEnhancementOptions &s) const;
};
struct MouseOptions {
bool _enabled;
bool operator==(const MouseOptions &s) const {
return _enabled == s._enabled;
}
};
/**
* SettingsData stores all the settings information.
*/
class SettingsData {
public:
bool operator==(const SettingsData &) const;
bool operator!=(const SettingsData &) const;
int _battleSpeed;
bool _campingAlwaysCombat;
int _campTime;
bool _debug;
bool _enhancements;
SettingsEnhancementOptions _enhancementsOptions;
bool _filterMoveMessages;
int _gameCyclesPerSecond;
int _screenAnimationFramesPerSecond;
bool _innAlwaysCombat;
int _innTime;
MouseOptions _mouseOptions;
uint _scale;
bool _screenShakes;
int _gamma;
int _shakeInterval;
bool _shortcutCommands;
int _shrineTime;
int _spellEffectSpeed;
bool _validateXml;
bool _volumeFades;
int _titleSpeedRandom;
int _titleSpeedOther;
int _eventTimerGranularity;
Common::String _filter;
Common::String _gemLayout;
Common::String _lineOfSight;
Common::String _videoType;
Common::String _battleDiff;
Common::String _logging;
Common::String _game;
};
/**
* The settings class is a singleton that holds all the settings
* information. It is dynamically initialized when first accessed.
*/
class Settings : public SettingsData, public Observable<Settings *> {
typedef Common::HashMap<Common::String, Common::String> SettingsMap;
private:
static Settings *_instance;
Std::vector<Common::String> _battleDiffs;
private:
/**
* Default contructor. Settings is a singleton so this is private.
*/
Settings();
/**
* Synchronize settings with ConfMan
*/
void synchronize(Shared::ConfSerializer &s);
public:
/* Methods */
/**
* Return the global instance of settings.
*/
static Settings &getInstance();
void setData(const SettingsData &data);
/**
* Write the settings out into a human readable file. This also
* notifies observers that changes have been committed.
*/
bool write();
const Std::vector<Common::String> &getBattleDiffs();
};
/* the global settings */
#define settings (Settings::getInstance())
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,83 @@
/* 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_CORE_TYPES_H
#define ULTIMA4_CORE_TYPES_H
#include "ultima/ultima4/map/direction.h"
#include "common/scummsys.h"
namespace Ultima {
namespace Ultima4 {
class Tile;
typedef uint TileId;
typedef byte MapId;
enum TileSpeed {
FAST,
SLOW,
VSLOW,
VVSLOW
};
enum TileEffect {
EFFECT_NONE,
EFFECT_FIRE,
EFFECT_SLEEP,
EFFECT_POISON,
EFFECT_POISONFIELD,
EFFECT_ELECTRICITY,
EFFECT_LAVA
};
enum TileAnimationStyle {
ANIM_NONE,
ANIM_SCROLL,
ANIM_CAMPFIRE,
ANIM_CITYFLAG,
ANIM_CASTLEFLAG,
ANIM_SHIPFLAG,
ANIM_LCBFLAG,
ANIM_FRAMES
};
/**
* An Uncopyable has no default copy constructor of operator=. A subclass may derive from
* Uncopyable at any level of visibility, even private, and subclasses will not have a default copy
* constructor or operator=. See also, boost::noncopyable Uncopyable (from the Boost project) and
* Item 6 from Scott Meyers Effective C++.
*/
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable &);
const Uncopyable &operator=(const Uncopyable &);
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,103 @@
/* 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/utils.h"
namespace Ultima {
namespace Ultima4 {
void assertMsg(bool exp, const char *desc, ...) {
if (!exp) {
va_list args;
va_start(args, desc);
Common::String msg = Common::String::vformat(desc, args);
va_end(args);
error("Assertion failed: %s", msg.c_str());
}
}
void xu4_srandom() {
// srand((uint)time(nullptr));
}
int xu4_random(int upperRange) {
if (upperRange == 0) {
warning("No upper range specified");
return 0;
}
return g_ultima->getRandomNumber(upperRange - 1);
}
Common::String &trim(Common::String &val, const Common::String &chars_to_trim) {
Common::String::iterator i;
if (val.size()) {
size_t pos;
for (i = val.begin(); (i != val.end()) && (pos = chars_to_trim.find(*i)) != Common::String::npos;)
i = val.erase(i);
for (i = val.end() - 1; (i != val.begin()) && (pos = chars_to_trim.find(*i)) != Common::String::npos;)
i = val.erase(i) - 1;
}
return val;
}
Common::String &lowercase(Common::String &val) {
Common::String::iterator i;
for (i = val.begin(); i != val.end(); i++)
*i = tolower(*i);
return val;
}
Common::String &uppercase(Common::String &val) {
Common::String::iterator i;
for (i = val.begin(); i != val.end(); i++)
*i = toupper(*i);
return val;
}
Common::String xu4_to_string(int val) {
char buffer[16];
Common::sprintf_s(buffer, "%d", val);
return buffer;
}
Std::vector<Common::String> split(const Common::String &s, const Common::String &separators) {
Std::vector<Common::String> result;
Common::String current;
for (unsigned i = 0; i < s.size(); i++) {
if (separators.find(s[i]) != Common::String::npos) {
if (current.size() > 0)
result.push_back(current);
current.clear();
} else
current += s[i];
}
if (current.size() > 0)
result.push_back(current);
return result;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,124 @@
/* 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_CORE_UTILS_H
#define ULTIMA4_CORE_UTILS_H
#include "ultima/ultima4/ultima4.h"
#include "ultima/shared/std/containers.h"
#include "common/savefile.h"
#include "common/hash-str.h"
namespace Ultima {
namespace Ultima4 {
extern void assertMsg(bool exp, const char *desc, ...);
/* The AdjustValue functions used to be #define'd macros, but these are
* evil for several reasons, *especially* when they contain multiple
* statements, and have if statements in them. The macros did both.
* See https://isocpp.org/wiki/faq/inline-functions#inline-vs-macros
* for more information.
*/
inline void AdjustValueMax(int &v, int val, int max) {
v += val;
if (v > max) v = max;
}
inline void AdjustValueMin(int &v, int val, int min) {
v += val;
if (v < min) v = min;
}
inline void AdjustValue(int &v, int val, int max, int min) {
v += val;
if (v > max) v = max;
if (v < min) v = min;
}
inline void AdjustValueMax(short &v, int val, int max) {
v += val;
if (v > max) v = max;
}
inline void AdjustValueMin(short &v, int val, int min) {
v += val;
if (v < min) v = min;
}
inline void AdjustValue(short &v, int val, int max, int min) {
v += val;
if (v > max) v = max;
if (v < min) v = min;
}
inline void AdjustValueMax(unsigned short &v, int val, int max) {
v += val;
if (v > max) v = max;
}
inline void AdjustValueMin(unsigned short &v, int val, int min) {
v += val;
if (v < min) v = min;
}
inline void AdjustValue(unsigned short &v, int val, int max, int min) {
v += val;
if (v > max) v = max;
if (v < min) v = min;
}
/**
* Seed the random number generator.
*/
void xu4_srandom();
/**
* Generate a random number between 0 and (upperRange - 1)
*/
int xu4_random(int upperval);
/**
* Trims whitespace from a Common::String
* @param val The Common::String you are trimming
* @param chars_to_trim A list of characters that will be trimmed
*/
Common::String &trim(Common::String &val, const Common::String &chars_to_trim = "\t\013\014 \n\r");
/**
* Converts the Common::String to lowercase
*/
Common::String &lowercase(Common::String &val);
/**
* Converts the Common::String to uppercase
*/
Common::String &uppercase(Common::String &val);
/**
* Converts an integer value to a Common::String
*/
Common::String xu4_to_string(int val);
/**
* Splits a Common::String into substrings, divided by the charactars in
* separators. Multiple adjacent separators are treated as one.
*/
Std::vector<Common::String> split(const Common::String &s, const Common::String &separators);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,378 @@
/* 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/events/event_handler.h"
#include "ultima/ultima4/controllers/wait_controller.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/views/textview.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/location.h"
#include "common/events.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
bool EventHandler::_controllerDone = false;
bool EventHandler::_ended = false;
EventHandler *EventHandler::_instance = nullptr;
EventHandler::EventHandler() : _timer(settings._eventTimerGranularity),
_updateScreen(nullptr), _isRightButtonDown(false) {
}
EventHandler *EventHandler::getInstance() {
if (_instance == nullptr)
_instance = new EventHandler();
return _instance;
}
void EventHandler::wait_msecs(uint msecs) {
int msecs_per_cycle = (1000 / settings._gameCyclesPerSecond);
int cycles = msecs / msecs_per_cycle;
if (cycles > 0) {
WaitController waitCtrl(cycles);
getInstance()->pushController(&waitCtrl);
waitCtrl.wait();
}
// Sleep the rest of the msecs we can't wait for
EventHandler::sleep(msecs % msecs_per_cycle);
}
void EventHandler::sleep(uint msec) {
g_system->delayMillis(msec);
}
void EventHandler::wait_cycles(uint cycles) {
WaitController waitCtrl(cycles);
getInstance()->pushController(&waitCtrl);
waitCtrl.wait();
}
void EventHandler::setControllerDone(bool done) {
_controllerDone = done;
#if defined(IOS_ULTIMA4)
if (done)
controllerStopped_helper();
#endif
}
bool EventHandler::getControllerDone() {
return _controllerDone;
}
void EventHandler::end() {
// End all event processing
_ended = true;
}
TimedEventMgr *EventHandler::getTimer() {
return &_timer;
}
Controller *EventHandler::pushController(Controller *c) {
c->setActive();
_controllers.push_back(c);
getTimer()->add(&Controller::timerCallback, c->getTimerInterval(), c);
return c;
}
Controller *EventHandler::popController() {
if (_controllers.empty())
return nullptr;
Controller *controller = _controllers.back();
getTimer()->remove(&Controller::timerCallback, controller);
_controllers.pop_back();
controller = getController();
if (controller)
controller->setActive();
return controller;
}
Controller *EventHandler::getController() const {
if (_controllers.empty())
return nullptr;
return _controllers.back();
}
void EventHandler::setController(Controller *c) {
while (popController() != nullptr) {}
pushController(c);
}
void EventHandler::pushMouseAreaSet(const MouseArea *mouseAreas) {
_mouseAreaSets.push_front(mouseAreas);
}
void EventHandler::popMouseAreaSet() {
if (_mouseAreaSets.size())
_mouseAreaSets.pop_front();
}
const MouseArea *EventHandler::getMouseAreaSet() const {
if (_mouseAreaSets.size())
return _mouseAreaSets.front();
else
return nullptr;
}
void EventHandler::run() {
if (_updateScreen)
(*_updateScreen)();
g_screen->update();
while (!_ended && !_controllerDone && !g_ultima->shouldQuit()) {
Common::Event event;
if (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
handleKeyDownEvent(event, getController(), _updateScreen);
break;
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
case Common::EVENT_MBUTTONDOWN:
handleMouseButtonDownEvent(event, getController(), _updateScreen);
break;
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
case Common::EVENT_MBUTTONUP:
handleMouseButtonUpEvent(event, getController(), _updateScreen);
break;
case Common::EVENT_MOUSEMOVE:
handleMouseMotionEvent(event);
continue;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
getController()->keybinder((KeybindingAction)event.customType);
break;
case Common::EVENT_QUIT:
_ended = true;
return;
default:
break;
}
}
// Brief delay
g_system->delayMillis(10);
// Poll the timer manager
_timer.poll();
// Update the screen
g_screen->screenFrame();
}
}
void EventHandler::setScreenUpdate(void (*updateScreen)(void)) {
_updateScreen = updateScreen;
}
void EventHandler::pushKeyHandler(KeyHandler kh) {
KeyHandler *new_kh = new KeyHandler(kh);
KeyHandlerController *khc = new KeyHandlerController(new_kh);
pushController(khc);
}
void EventHandler::popKeyHandler() {
if (_controllers.empty())
return;
popController();
}
KeyHandler *EventHandler::getKeyHandler() const {
if (_controllers.empty())
return nullptr;
KeyHandlerController *khc = dynamic_cast<KeyHandlerController *>(_controllers.back());
assertMsg(khc != nullptr, "EventHandler::getKeyHandler called when controller wasn't a keyhandler");
if (khc == nullptr)
return nullptr;
return khc->getKeyHandler();
}
void EventHandler::setKeyHandler(KeyHandler kh) {
while (popController() != nullptr) {
}
pushKeyHandler(kh);
}
const MouseArea *EventHandler::mouseAreaForPoint(int x, int y) {
int i;
const MouseArea *areas = getMouseAreaSet();
if (!areas)
return nullptr;
for (i = 0; areas[i]._nPoints != 0; i++) {
if (g_screen->screenPointInMouseArea(x, y, &(areas[i]))) {
return &(areas[i]);
}
}
return nullptr;
}
void EventHandler::handleMouseMotionEvent(const Common::Event &event) {
if (!settings._mouseOptions._enabled)
return;
const MouseArea *area;
area = eventHandler->mouseAreaForPoint(event.mouse.x, event.mouse.y);
if (area) {
g_screen->setMouseCursor(area->_cursor);
if (_isRightButtonDown) {
int xd = (event.mouse.x / settings._scale) - 96,
yd = (event.mouse.y / settings._scale) - 96;
double dist = sqrt((double)(xd * xd + yd * yd));
_walk.setDelta(area->_direction, (int)dist);
}
} else {
g_screen->setMouseCursor(MC_DEFAULT);
if (_isRightButtonDown)
_walk.setDelta(DIR_NONE, 0);
}
}
void EventHandler::handleMouseButtonDownEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen) {
if (!settings._mouseOptions._enabled)
return;
if (event.type == Common::EVENT_LBUTTONDOWN) {
// handle the keypress
bool processed = controller->notifyMousePress(event.mouse);
if (processed) {
if (updateScreen)
(*updateScreen)();
g_screen->update();
}
} else if (event.type == Common::EVENT_RBUTTONDOWN) {
_isRightButtonDown = true;
handleMouseMotionEvent(event);
}
if (updateScreen)
(*updateScreen)();
g_screen->update();
}
void EventHandler::handleMouseButtonUpEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen) {
if (!settings._mouseOptions._enabled)
return;
if (event.type == Common::EVENT_RBUTTONUP)
_isRightButtonDown = false;
}
void EventHandler::handleKeyDownEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen) {
int key;
bool processed;
key = (event.kbd.ascii != 0 && event.kbd.ascii < 128) ?
event.kbd.ascii : (int)event.kbd.keycode;
key += (event.kbd.flags & (Common::KBD_CTRL |
Common::KBD_ALT | Common::KBD_META)) << 16;
debug(1, "key event: sym = %d, mod = %d; translated = %d",
event.kbd.keycode, event.kbd.flags, key);
// handle the keypress
processed = controller->notifyKeyPressed(key);
if (processed) {
if (updateScreen)
(*updateScreen)();
g_screen->update();
}
}
/*-------------------------------------------------------------------*/
void WalkTrigger::reset() {
_action = KEYBIND_NONE;
_ticksCtr = 0;
}
void WalkTrigger::setDelta(Direction dir, int distance) {
if (distance > 96) {
distance = 0;
dir = DIR_NONE;
}
KeybindingAction action;
switch (dir) {
case DIR_NORTH:
action = KEYBIND_UP;
break;
case DIR_SOUTH:
action = KEYBIND_DOWN;
break;
case DIR_WEST:
action = KEYBIND_LEFT;
break;
case DIR_EAST:
action = KEYBIND_RIGHT;
break;
default:
action = KEYBIND_NONE;
break;
}
if (action != _action) {
// Walk quadrant changed
_action = action;
_ticksCtr = 0;
}
_ticksPerWalk = 4 - (distance / 25);
}
KeybindingAction WalkTrigger::getAction() {
if (--_ticksCtr <= 0) {
_ticksCtr = _ticksPerWalk;
return _action;
}
return KEYBIND_NONE;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,209 @@
/* 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_EVENTS_EVENT_HANDLER_H
#define ULTIMA4_EVENTS_EVENT_HANDLER_H
#include "ultima/ultima4/events/timed_event_mgr.h"
#include "ultima/ultima4/controllers/key_handler_controller.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/shared/std/containers.h"
#include "common/events.h"
#include "common/list.h"
#include "common/rect.h"
#include "common/str.h"
namespace Ultima {
namespace Ultima4 {
#define eventHandler (EventHandler::getInstance())
#if defined(IOS_ULTIMA4)
#ifndef __OBJC__
typedef void *TimedManagerHelper;
typedef void *UIEvent;
#else
@class TimedManagerHelper;
@class UIEvent;
#endif
#endif
typedef void(*updateScreenCallback)();
/**
* Encapsulates the logic for deciding how frequently to walk
* when holding down the right moues button in enhanced mode
*/
class WalkTrigger {
private:
int _ticksCtr, _ticksPerWalk;
KeybindingAction _action;
public:
/**
* Constructor
*/
WalkTrigger() : _ticksCtr(0), _ticksPerWalk(0), _action(KEYBIND_NONE) {
}
/**
* Resets the walker
*/
void reset();
/**
* Sets the delta from the center of the map
*/
void setDelta(Direction dir, int distance);
/**
* Checks for whether to walk, and if so, returns the direction
*/
KeybindingAction getAction();
};
/**
* A class for handling game events.
*/
class EventHandler {
typedef Common::List<const MouseArea *> MouseAreaList;
private:
static EventHandler *_instance;
WalkTrigger _walk;
bool _isRightButtonDown;
private:
void handleMouseMotionEvent(const Common::Event &event);
void handleMouseButtonDownEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen);
void handleMouseButtonUpEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen);
void handleKeyDownEvent(const Common::Event &event, Controller *controller, updateScreenCallback updateScreen);
protected:
static bool _controllerDone;
static bool _ended;
TimedEventMgr _timer;
Std::vector<Controller *> _controllers;
MouseAreaList _mouseAreaSets;
updateScreenCallback _updateScreen;
public:
/**
* Constructor
*/
EventHandler();
/* Static functions */
static EventHandler *getInstance();
/**
* Delays program execution for the specified number of milliseconds.
* This doesn't actually stop events, but it stops the user from interacting
* While some important event happens (e.g., getting hit by a cannon ball or a spell effect).
*/
static void sleep(uint usec);
/**
* Waits a given number of milliseconds before continuing
*/
static void wait_msecs(uint msecs);
/**
* Waits a given number of game cycles before continuing
*/
static void wait_cycles(uint cycles);
static void setControllerDone(bool exit = true);
/**
* Returns the current value of the global exit flag
*/
static bool getControllerDone();
static void end();
/* Member functions */
TimedEventMgr *getTimer();
/* Event functions */
void run();
void setScreenUpdate(void (*updateScreen)(void));
#if defined(IOS_ULTIMA4)
void handleEvent(UIEvent *);
static void controllerStopped_helper();
updateScreenCallback screenCallback() {
return updateScreen;
}
#endif
/* Controller functions */
Controller *pushController(Controller *c);
Controller *popController();
Controller *getController() const;
void setController(Controller *c);
/* Key handler functions */
/**
* Adds a key handler to the stack.
*/
void pushKeyHandler(KeyHandler kh);
/**
* Pops a key handler off the stack.
* Returns a pointer to the resulting key handler after
* the current handler is popped.
*/
void popKeyHandler();
/**
* Returns a pointer to the current key handler.
* Returns nullptr if there is no key handler.
*/
KeyHandler *getKeyHandler() const;
/**
* Eliminates all key handlers and begins stack with new handler.
* This pops all key handlers off the stack and adds
* the key handler provided to the stack, making it the
* only key handler left. Use this function only if you
* are sure the key handlers in the stack are disposable.
*/
void setKeyHandler(KeyHandler kh);
/* Mouse area functions */
void pushMouseAreaSet(const MouseArea *mouseAreas);
void popMouseAreaSet();
/**
* Get the currently active mouse area set off the top of the stack.
*/
const MouseArea *getMouseAreaSet() const;
const MouseArea *mouseAreaForPoint(int x, int y);
/**
* Checks for whether to walk, and if so, returns the direction action
*/
KeybindingAction getAction() {
return _isRightButtonDown ? _walk.getAction() : KEYBIND_NONE;
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,133 @@
/* 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/events/timed_event_mgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
TimedEventMgr::TimedEventMgr(int baseInterval) :
_baseInterval(baseInterval), _lastTickTime(0), _locked(false) {
}
void TimedEventMgr::poll() {
uint32 time = g_system->getMillis();
if (time >= (_lastTickTime + _baseInterval)) {
_lastTickTime = time;
tick();
g_screen->update();
}
}
void TimedEventMgr::reset(uint interval) {
_baseInterval = interval;
}
/*-------------------------------------------------------------------*/
TimedEvent::TimedEvent(TimedEvent::Callback cb, int i, void *d) :
_callback(cb),
_data(d),
_interval(i),
_current(0) {
}
TimedEvent::Callback TimedEvent::getCallback() const {
return _callback;
}
void *TimedEvent::getData() {
return _data;
}
void TimedEvent::tick() {
if (++_current >= _interval) {
(*_callback)(_data);
_current = 0;
}
}
bool TimedEventMgr::isLocked() const {
return _locked;
}
void TimedEventMgr::add(TimedEvent::Callback theCallback, int interval, void *data) {
_events.push_back(new TimedEvent(theCallback, interval, data));
}
TimedEventMgr::List::iterator TimedEventMgr::remove(List::iterator i) {
if (isLocked()) {
_deferredRemovals.push_back(*i);
return i;
} else {
delete *i;
return _events.erase(i);
}
}
void TimedEventMgr::remove(TimedEvent *event) {
List::iterator i;
for (i = _events.begin(); i != _events.end(); i++) {
if ((*i) == event) {
remove(i);
break;
}
}
}
void TimedEventMgr::remove(TimedEvent::Callback theCallback, void *data) {
List::iterator i;
for (i = _events.begin(); i != _events.end(); i++) {
if ((*i)->getCallback() == theCallback && (*i)->getData() == data) {
remove(i);
break;
}
}
}
void TimedEventMgr::tick() {
List::iterator i;
lock();
for (i = _events.begin(); i != _events.end(); i++)
(*i)->tick();
unlock();
// Remove events that have been deferred for removal
for (i = _deferredRemovals.begin(); i != _deferredRemovals.end(); i++)
_events.remove(*i);
}
void TimedEventMgr::lock() {
_locked = true;
}
void TimedEventMgr::unlock() {
_locked = false;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,140 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_EVENTS_TIMED_EVENT_MGR_H
#define ULTIMA4_EVENTS_TIMED_EVENT_MGR_H
#include "common/list.h"
namespace Ultima {
namespace Ultima4 {
/**
* A class for handling timed events.
*/
class TimedEvent {
public:
/* Typedefs */
typedef Common::List<TimedEvent *> List;
typedef void (*Callback)(void *);
/* Constructors */
TimedEvent(Callback callback, int interval, void *data = nullptr);
/* Member functions */
Callback getCallback() const;
void *getData();
/**
* Advances the timed event forward a tick.
* When (current >= interval), then it executes its callback function.
*/
void tick();
/* Properties */
protected:
Callback _callback;
void *_data;
int _interval;
int _current;
};
/**
* A class for managing timed events
*/
class TimedEventMgr {
public:
/* Typedefs */
typedef TimedEvent::List List;
/* Constructors */
/**
* Constructs a timed event manager object.
* Adds a timer callback to the SDL subsystem, which
* will drive all of the timed events that this object
* controls.
*/
TimedEventMgr(int baseInterval);
/**
* Destructs a timed event manager object.
* It removes the callback timer and un-initializes the
* SDL subsystem if there are no other active TimedEventMgr
* objects.
*/
~TimedEventMgr() {}
/**
* Checks whether the frame time has expired, and if so,
* triggers a tick
*/
void poll();
/**
* Returns true if the event queue is locked (in use)
*/
bool isLocked() const;
/**
* Adds a timed event to the event queue.
*/
void add(TimedEvent::Callback theCallback, int interval, void *data = nullptr);
/**
* Removes a timed event from the event queue.
*/
List::iterator remove(List::iterator i);
void remove(TimedEvent *event);
void remove(TimedEvent::Callback theCallback, void *data = nullptr);
/**
* Runs each of the callback functions of the TimedEvents associated with this manager.
*/
void tick();
/**
* Re-initializes the timer manager to a new timer granularity
*/
void reset(uint interval); /**< Re-initializes the event manager to a new base interval */
#if defined(IOS_ULTIMA4)
bool hasActiveTimer() const;
#endif
private:
void lock(); /**< Locks the event list */
void unlock(); /**< Unlocks the event list */
/* Properties */
protected:
uint32 _lastTickTime;
uint32 _baseInterval;
bool _locked;
List _events;
List _deferredRemovals;
#if defined(IOS_ULTIMA4)
TimedManagerHelper *m_helper;
#endif
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,127 @@
/* 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/filesys/rle.h"
namespace Ultima {
namespace Ultima4 {
/**
* Decompress an RLE encoded file.
*/
long rleDecompressFile(Common::ReadStream *in, long inlen, void **out) {
void *indata;
long outlen;
/* input file should be longer than 0 bytes */
if (inlen <= 0)
return -1;
/* load compressed file into memory */
indata = malloc(inlen);
in->read(indata, inlen);
outlen = rleDecompressMemory(indata, inlen, out);
free(indata);
return outlen;
}
long rleDecompressMemory(void *in, long inlen, void **out) {
byte *indata, *outdata;
long outlen;
/* input should be longer than 0 bytes */
if (inlen <= 0)
return -1;
indata = (byte *)in;
/* determine decompressed file size */
outlen = rleGetDecompressedSize(indata, inlen);
if (outlen <= 0)
return -1;
/* decompress file from inlen to outlen */
outdata = (byte *) malloc(outlen);
rleDecompress(indata, inlen, outdata, outlen);
*out = outdata;
return outlen;
}
/**
* Determine the uncompressed size of RLE compressed data.
*/
long rleGetDecompressedSize(byte *indata, long inlen) {
byte *p;
byte ch, count;
long len = 0;
p = indata;
while ((p - indata) < inlen) {
ch = *p++;
if (ch == RLE_RUNSTART) {
count = *p++;
p++;
len += count;
} else
len++;
}
return len;
}
/**
* Decompress a block of RLE encoded memory.
*/
long rleDecompress(byte *indata, long inlen, byte *outdata, long outlen) {
int i;
byte *p, *q;
byte ch, count, val;
p = indata;
q = outdata;
while ((p - indata) < inlen) {
ch = *p++;
if (ch == RLE_RUNSTART) {
count = *p++;
val = *p++;
for (i = 0; i < count; i++) {
*q++ = val;
if ((q - outdata) >= outlen)
break;
}
} else {
*q++ = ch;
if ((q - outdata) >= outlen)
break;
}
}
return q - outdata;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,40 @@
/* 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_FILESYS_RLE_H
#define ULTIMA4_FILESYS_RLE_H
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
#define RLE_RUNSTART 02
long rleDecompressFile(Common::ReadStream *in, long inlen, void **out);
long rleDecompressMemory(void *in, long inlen, void **out);
long rleGetDecompressedSize(byte *indata, long inlen);
long rleDecompress(byte *indata, long inlen, byte *outdata, long outlen);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,362 @@
/* 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/filesys/savegame.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/spell.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/mapmgr.h"
namespace Ultima {
namespace Ultima4 {
void SaveGame::save(Common::WriteStream *stream) {
Common::Serializer ser(nullptr, stream);
assert(g_context && g_context->_location);
_positions.load();
synchronize(ser);
/*
* Save monsters
*/
// fix creature animations. This was done for compatibility with u4dos,
// so may be redundant now
g_context->_location->_map->resetObjectAnimations();
g_context->_location->_map->fillMonsterTable();
SaveGameMonsterRecord::synchronize(g_context->_location->_map->_monsterTable, ser);
/**
* Write dungeon info
*/
if (g_context->_location && g_context->_location->_prev) {
/**
* Write out monsters
*/
// fix creature animations so they are compatible with u4dos.
// This may be redundant now for ScummVM
g_context->_location->_prev->_map->resetObjectAnimations();
g_context->_location->_prev->_map->fillMonsterTable(); /* fill the monster table so we can save it */
SaveGameMonsterRecord::synchronize(g_context->_location->_prev->_map->_monsterTable, ser);
}
}
void SaveGame::load(Common::SeekableReadStream *stream) {
Common::Serializer *ser = nullptr;
assert(g_context);
if (stream) {
ser = new Common::Serializer(stream, nullptr);
synchronize(*ser);
}
// initialize our party
if (g_context->_party) {
g_context->_party->deleteObserver(g_game);
delete g_context->_party;
}
g_context->_party = new Party(this);
g_context->_party->addObserver(g_game);
// Delete any prior map
while (g_context->_location)
locationFree(&g_context->_location);
// set the map to the world map
Map *map = mapMgr->get(MAP_WORLD);
g_game->setMap(map, 0, nullptr);
assert(g_context->_location && g_context->_location->_map);
g_context->_location->_map->clearObjects();
// initialize the moons (must be done from the world map)
g_game->initMoons();
// initialize overworld position and any secondary map we're in
g_context->_location->_coords = _positions[0];
for (uint idx = 1; idx < _positions.size(); ++idx) {
map = mapMgr->get(_positions[idx]._map);
g_game->setMap(map, 1, nullptr);
g_context->_location->_coords = _positions[idx];
}
/**
* Fix the coordinates if they're out of bounds. This happens every
* time on the world map because (z == -1) is no longer valid.
* To maintain compatibility with u4dos, this value gets translated
* when the game is saved and loaded
*/
if (MAP_IS_OOB(g_context->_location->_map, g_context->_location->_coords))
g_context->_location->_coords.putInBounds(g_context->_location->_map);
// load in creatures
if (ser)
SaveGameMonsterRecord::synchronize(g_context->_location->_map->_monsterTable, *ser);
gameFixupObjects(g_context->_location->_map);
/* we have previous creature information as well, load it! */
if (g_context->_location->_prev) {
if (ser)
SaveGameMonsterRecord::synchronize(g_context->_location->_prev->_map->_monsterTable, *ser);
gameFixupObjects(g_context->_location->_prev->_map);
}
g_spells->spellSetEffectCallback(&gameSpellEffect);
g_items->setDestroyAllCreaturesCallback(&gameDestroyAllCreatures);
g_context->_stats->resetReagentsMenu();
/* add some observers */
g_context->_aura->addObserver(g_context->_stats);
g_context->_party->addObserver(g_context->_stats);
g_game->initScreenWithoutReloadingState();
delete ser;
}
void SaveGame::newGame() {
// Most default state has already been set up by the IntroController.
// Call the load method with no stream to handle pre-game setup
load(nullptr);
}
void SaveGame::synchronize(Common::Serializer &s) {
int i;
s.syncAsUint32LE(_unknown1);
s.syncAsUint32LE(_moves);
for (i = 0; i < 8; ++i)
_players[i].synchronize(s);
s.syncAsUint32LE(_food);
s.syncAsUint16LE(_gold);
for (i = 0; i < 8; ++i)
s.syncAsUint16LE(_karma[i]);
s.syncAsUint16LE(_torches);
s.syncAsUint16LE(_gems);
s.syncAsUint16LE(_keys);
s.syncAsUint16LE(_sextants);
for (i = 0; i < ARMR_MAX; ++i)
s.syncAsUint16LE(_armor[i]);
for (i = 0; i < WEAP_MAX; ++i)
s.syncAsUint16LE(_weapons[i]);
for (i = 0; i < REAG_MAX; ++i)
s.syncAsUint16LE(_reagents[i]);
for (i = 0; i < SPELL_MAX; ++i)
s.syncAsUint16LE(_mixtures[i]);
_positions.synchronize(s);
s.syncAsUint16LE(_orientation);
s.syncAsUint16LE(_items);
s.syncAsByte(_stones);
s.syncAsByte(_runes);
s.syncAsUint16LE(_members);
s.syncAsUint16LE(_transport);
s.syncAsUint16LE(_balloonState);
s.syncAsUint16LE(_trammelPhase);
s.syncAsUint16LE(_feluccaPhase);
s.syncAsUint16LE(_shipHull);
s.syncAsUint16LE(_lbIntro);
s.syncAsUint16LE(_lastCamp);
s.syncAsUint16LE(_lastReagent);
s.syncAsUint16LE(_lastMeditation);
s.syncAsUint16LE(_lastVirtue);
}
void SaveGame::init(const SaveGamePlayerRecord *avatarInfo) {
int i;
_unknown1 = 0;
_moves = 0;
_players[0] = *avatarInfo;
for (i = 1; i < 8; ++i)
_players[i].init();
_food = 0;
_gold = 0;
for (i = 0; i < 8; ++i)
_karma[i] = 20;
_torches = 0;
_gems = 0;
_keys = 0;
_sextants = 0;
for (i = 0; i < ARMR_MAX; ++i)
_armor[i] = 0;
for (i = 0; i < WEAP_MAX; ++i)
_weapons[i] = 0;
for (i = 0; i < REAG_MAX; ++i)
_reagents[i] = 0;
for (i = 0; i < SPELL_MAX; ++i)
_mixtures[i] = 0;
_items = 0;
_stones = 0;
_runes = 0;
_members = 1;
_transport = 0x1f;
_balloonState = 0;
_trammelPhase = 0;
_feluccaPhase = 0;
_shipHull = 50;
_lbIntro = 0;
_lastCamp = 0;
_lastReagent = 0;
_lastMeditation = 0;
_lastVirtue = 0;
_orientation = 0;
}
/*-------------------------------------------------------------------*/
void SaveGamePlayerRecord::synchronize(Common::Serializer &s) {
s.syncAsUint16LE(_hp);
s.syncAsUint16LE(_hpMax);
s.syncAsUint16LE(_xp);
s.syncAsUint16LE(_str);
s.syncAsUint16LE(_dex);
s.syncAsUint16LE(_intel);
s.syncAsUint16LE(_mp);
s.syncAsUint16LE(_unknown);
s.syncAsUint16LE(_weapon);
s.syncAsUint16LE(_armor);
s.syncBytes((byte *)_name, 16);
s.syncAsByte(_sex);
s.syncAsByte(_class);
s.syncAsByte(_status);
}
void SaveGamePlayerRecord::init() {
int i;
_hp = 0;
_hpMax = 0;
_xp = 0;
_str = 0;
_dex = 0;
_intel = 0;
_mp = 0;
_unknown = 0;
_weapon = WEAP_HANDS;
_armor = ARMR_NONE;
for (i = 0; i < 16; ++i)
_name[i] = '\0';
_sex = SEX_MALE;
_class = CLASS_MAGE;
_status = STAT_GOOD;
}
void SaveGameMonsterRecord::synchronize(SaveGameMonsterRecord *monsterTable, Common::Serializer &s) {
int i;
const uint32 IDENT = MKTAG('M', 'O', 'N', 'S');
uint32 val = IDENT;
s.syncAsUint32BE(val);
if (s.isLoading() && val != IDENT)
error("Invalid savegame");
if (s.isSaving() && !monsterTable) {
int dataSize = MONSTERTABLE_SIZE * 8;
byte b = 0;
while (dataSize-- > 0)
s.syncAsByte(b);
return;
}
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._tile);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._x);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._y);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._prevTile);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._prevX);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._prevY);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._unused1);
for (i = 0; i < MONSTERTABLE_SIZE; ++i)
s.syncAsByte(monsterTable[i]._unused2);
}
/*-------------------------------------------------------------------*/
void LocationCoordsArray::load() {
clear();
for (Location *l = g_context->_location; l; l = l->_prev)
insert_at(0, LocationCoords(l->_map->_id, l->_coords));
}
void LocationCoords::synchronize(Common::Serializer &s) {
s.syncAsByte(x);
s.syncAsByte(y);
s.syncAsByte(z);
s.syncAsByte(_map);
}
/*-------------------------------------------------------------------*/
void LocationCoordsArray::synchronize(Common::Serializer &s) {
byte count = size();
s.syncAsByte(count);
if (s.isLoading())
resize(count);
for (uint idx = 0; idx < count; ++idx)
(*this)[idx].synchronize(s);
assert(!empty() && (*this)[0]._map == MAP_WORLD);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,336 @@
/* 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_FILESYS_SAVEGAME_H
#define ULTIMA4_FILESYS_SAVEGAME_H
#include "common/array.h"
#include "common/rect.h"
#include "common/stream.h"
#include "common/serializer.h"
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
#define PARTY_SAV_BASE_FILENAME "party.sav"
#define MONSTERS_SAV_BASE_FILENAME "monsters.sav"
#define OUTMONST_SAV_BASE_FILENAME "outmonst.sav"
#define MONSTERTABLE_SIZE 32
#define MONSTERTABLE_CREATURES_SIZE 8
#define MONSTERTABLE_OBJECTS_SIZE (MONSTERTABLE_SIZE - MONSTERTABLE_CREATURES_SIZE)
class Object;
/**
* The list of all weapons. These values are used in both the
* inventory fields and character records of the savegame.
*/
enum WeaponType {
WEAP_HANDS,
WEAP_STAFF,
WEAP_DAGGER,
WEAP_SLING,
WEAP_MACE,
WEAP_AXE,
WEAP_SWORD,
WEAP_BOW,
WEAP_CROSSBOW,
WEAP_OIL,
WEAP_HALBERD,
WEAP_MAGICAXE,
WEAP_MAGICSWORD,
WEAP_MAGICBOW,
WEAP_MAGICWAND,
WEAP_MYSTICSWORD,
WEAP_MAX
};
/**
* The list of all armor types. These values are used in both the
* inventory fields and character records of the savegame.
*/
enum ArmorType {
ARMR_NONE,
ARMR_CLOTH,
ARMR_LEATHER,
ARMR_CHAIN,
ARMR_PLATE,
ARMR_MAGICCHAIN,
ARMR_MAGICPLATE,
ARMR_MYSTICROBES,
ARMR_MAX
};
/**
* The list of sex values for the savegame character records. The
* values match the male and female symbols in the character set.
*/
enum SexType {
SEX_MALE = 0xb,
SEX_FEMALE = 0xc
};
/**
* The list of class types for the savegame character records.
*/
enum ClassType {
CLASS_MAGE,
CLASS_BARD,
CLASS_FIGHTER,
CLASS_DRUID,
CLASS_TINKER,
CLASS_PALADIN,
CLASS_RANGER,
CLASS_SHEPHERD
};
/**
* The list of status values for the savegame character records. The
* values match the letter that's appear in the ztats area.
*/
enum StatusType {
STAT_GOOD = 'G',
STAT_POISONED = 'P',
STAT_SLEEPING = 'S',
STAT_DEAD = 'D'
};
enum Virtue {
VIRT_HONESTY,
VIRT_COMPASSION,
VIRT_VALOR,
VIRT_JUSTICE,
VIRT_SACRIFICE,
VIRT_HONOR,
VIRT_SPIRITUALITY,
VIRT_HUMILITY,
VIRT_MAX
};
enum BaseVirtue {
VIRT_NONE = 0x00,
VIRT_TRUTH = 0x01,
VIRT_LOVE = 0x02,
VIRT_COURAGE = 0x04
};
enum Reagent {
REAG_ASH,
REAG_GINSENG,
REAG_GARLIC,
REAG_SILK,
REAG_MOSS,
REAG_PEARL,
REAG_NIGHTSHADE,
REAG_MANDRAKE,
REAG_MAX
};
#define SPELL_MAX 26
enum Item {
ITEM_SKULL = 0x01,
ITEM_SKULL_DESTROYED = 0x02,
ITEM_CANDLE = 0x04,
ITEM_BOOK = 0x08,
ITEM_BELL = 0x10,
ITEM_KEY_C = 0x20,
ITEM_KEY_L = 0x40,
ITEM_KEY_T = 0x80,
ITEM_HORN = 0x100,
ITEM_WHEEL = 0x200,
ITEM_CANDLE_USED = 0x400,
ITEM_BOOK_USED = 0x800,
ITEM_BELL_USED = 0x1000
};
enum Stone {
STONE_BLUE = 0x01,
STONE_YELLOW = 0x02,
STONE_RED = 0x04,
STONE_GREEN = 0x08,
STONE_ORANGE = 0x10,
STONE_PURPLE = 0x20,
STONE_WHITE = 0x40,
STONE_BLACK = 0x80
};
enum Rune {
RUNE_HONESTY = 0x01,
RUNE_COMPASSION = 0x02,
RUNE_VALOR = 0x04,
RUNE_JUSTICE = 0x08,
RUNE_SACRIFICE = 0x10,
RUNE_HONOR = 0x20,
RUNE_SPIRITUALITY = 0x40,
RUNE_HUMILITY = 0x80
};
/**
* The Ultima IV savegame player record data.
*/
struct SaveGamePlayerRecord {
void synchronize(Common::Serializer &s);
void init();
unsigned short _hp;
unsigned short _hpMax;
unsigned short _xp;
unsigned short _str, _dex, _intel;
unsigned short _mp;
unsigned short _unknown;
WeaponType _weapon;
ArmorType _armor;
char _name[16];
SexType _sex;
ClassType _class;
StatusType _status;
};
/**
* How Ultima IV stores monster information
*/
struct SaveGameMonsterRecord {
byte _tile;
byte _x;
byte _y;
byte _prevTile;
byte _prevX;
byte _prevY;
byte _unused1;
byte _unused2;
SaveGameMonsterRecord() {
clear();
}
void clear() {
_tile = _x = _y = 0;
_prevTile = _prevX = _prevY = 0;
_unused1 = _unused2 = 0;
}
static void synchronize(SaveGameMonsterRecord *monsterTable, Common::Serializer &s);
};
class LocationCoords : public Coords {
public:
MapId _map;
LocationCoords() : Coords(), _map(0xff) {}
LocationCoords(MapId map, int x_, int y_, int z_) :
Coords(x_, y_, z_), _map(map) {}
LocationCoords(MapId map, const Coords &pos) :
Coords(pos), _map(map) {}
/**
* Synchronize to/from a savegame
*/
void synchronize(Common::Serializer &s);
};
class LocationCoordsArray : public Common::Array<LocationCoords> {
public:
/**
* Loads the list of map & coordinates from the game context
*/
void load();
/**
* Synchronize to/from a savegame
*/
void synchronize(Common::Serializer &s);
};
/**
* Represents the on-disk contents of PARTY.SAV.
*/
struct SaveGame {
/**
* Initialize a new savegame structure
*/
void init(const SaveGamePlayerRecord *avatarInfo);
/**
* Load an entire savegame, including monsters
*/
void save(Common::WriteStream *stream);
/**
* Save an entire savegame, including monsters
*/
void load(Common::SeekableReadStream *stream);
/**
* Called when a new game is started without loading an existing
* savegame from launcher.
*/
void newGame();
/**
* Synchronizes data for the savegame structure
*/
void synchronize(Common::Serializer &s);
uint _unknown1;
uint _moves;
SaveGamePlayerRecord _players[8];
int _food;
short _gold;
short _karma[VIRT_MAX];
short _torches;
short _gems;
short _keys;
short _sextants;
short _armor[ARMR_MAX];
short _weapons[WEAP_MAX];
short _reagents[REAG_MAX];
short _mixtures[SPELL_MAX];
unsigned short _items;
LocationCoordsArray _positions;
unsigned short _orientation;
byte _stones;
byte _runes;
unsigned short _members;
unsigned short _transport;
union {
unsigned short _balloonState;
unsigned short _torchDuration;
};
unsigned short _trammelPhase;
unsigned short _feluccaPhase;
unsigned short _shipHull;
unsigned short _lbIntro;
unsigned short _lastCamp;
unsigned short _lastReagent;
unsigned short _lastMeditation;
unsigned short _lastVirtue;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,44 @@
/* 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/filesys/u4file.h"
#include "common/file.h"
namespace Ultima {
namespace Ultima4 {
Std::vector<Common::String> u4read_stringtable(const Common::String &filename) {
Common::File f;
if (!f.open(Common::Path(Common::String::format("data/text/%s.dat", filename.c_str()))))
error("Could not open string table '%s'", filename.c_str());
Std::vector<Common::String> strs;
Common::String line;
int64 filesize = f.size();
while (f.pos() < filesize)
strs.push_back(f.readString());
return strs;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_FILE_H
#define ULTIMA4_FILE_H
#include "common/str.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
/**
* Read a series of zero terminated strings from a file. The strings
* are read from the given offset, or the current file position if
* offset is -1.
*/
extern Std::vector<Common::String> u4read_stringtable(const Common::String &filename);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,112 @@
/* 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/game/armor.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/map/tile.h"
#include "common/algorithm.h"
namespace Ultima {
namespace Ultima4 {
Armors *g_armors;
Armors::Armors() : _confLoaded(false) {
g_armors = this;
}
Armors::~Armors() {
g_armors = nullptr;
}
const Armor *Armors::get(ArmorType a) {
// Load in XML if it hasn't been already
loadConf();
if (static_cast<unsigned>(a) >= size())
return nullptr;
return (*this)[a];
}
const Armor *Armors::get(const Common::String &name) {
// Load in XML if it hasn't been already
loadConf();
for (unsigned i = 0; i < size(); i++) {
if (scumm_stricmp(name.c_str(), (*this)[i]->_name.c_str()) == 0)
return (*this)[i];
}
return nullptr;
}
void Armors::loadConf() {
if (!_confLoaded)
_confLoaded = true;
else
return;
const Config *config = Config::getInstance();
Std::vector<ConfigElement> armorConfs = config->getElement("armors").getChildren();
for (const auto &i : armorConfs) {
if (i.getName() != "armor")
continue;
ArmorType armorType = static_cast<ArmorType>(size());
push_back(new Armor(armorType, i));
}
}
/*-------------------------------------------------------------------*/
Armor::Armor(ArmorType armorType, const ConfigElement &conf) :
_type(armorType), _canUse(0xff) /*, _mask(0) */ {
_name = conf.getString("name");
_defense = conf.getInt("defense");
Std::vector<ConfigElement> contraintConfs = conf.getChildren();
for (const auto &i : contraintConfs) {
byte useMask = 0;
if (i.getName() != "constraint")
continue;
for (int cl = 0; cl < 8; cl++) {
if (scumm_stricmp(i.getString("class").c_str(), getClassName(static_cast<ClassType>(cl))) == 0)
useMask = (1 << cl);
}
if (useMask == 0 && scumm_stricmp(i.getString("class").c_str(), "all") == 0)
useMask = 0xFF;
if (useMask == 0) {
error("malformed armor.xml file: constraint has unknown class %s",
i.getString("class").c_str());
}
if (i.getBool("canuse"))
_canUse |= useMask;
else
_canUse &= ~useMask;
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,94 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GAME_ARMOR_H
#define ULTIMA4_GAME_ARMOR_H
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/shared/std/containers.h"
#include "ultima/shared/std/string.h"
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class Armors;
class Armor {
friend class Armors;
public:
// Getters
ArmorType getType() const {
return _type; /**< Returns the ArmorType of the armor */
}
const Common::String &getName() const {
return _name; /**< Returns the name of the armor */
}
int getDefense() const {
return _defense; /**< Returns the defense value of the armor */
}
/** Returns true if the class given can wear the armor */
bool canWear(ClassType klass) const {
return _canUse & (1 << klass);
}
private:
Armor(ArmorType armorType, const ConfigElement &conf);
ArmorType _type;
Common::String _name;
byte _canUse;
int _defense;
//unsigned short _mask;
};
class Armors : public Std::vector<Armor *> {
private:
void loadConf();
bool _confLoaded;
public:
/**
* Constructor
*/
Armors();
/**
* Destructor
*/
~Armors();
/**
* Returns armor by ArmorType.
*/
const Armor *get(ArmorType a);
/**
* Returns armor that has the given name
*/
const Armor *get(const Common::String &name);
};
extern Armors *g_armors;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,62 @@
/* 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/game/aura.h"
namespace Ultima {
namespace Ultima4 {
Aura::Aura() : _type(NONE), _duration(0) {}
void Aura::setDuration(int d) {
_duration = d;
setChanged();
notifyObservers(nullptr);
}
void Aura::set(Type t, int d) {
_type = t;
_duration = d;
setChanged();
notifyObservers(nullptr);
}
void Aura::setType(Type t) {
_type = t;
setChanged();
notifyObservers(nullptr);
}
void Aura::passTurn() {
if (_duration > 0) {
_duration--;
if (_duration == 0) {
_type = NONE;
setChanged();
notifyObservers(nullptr);
}
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,77 @@
/* 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_GAME_AURA_H
#define ULTIMA4_GAME_AURA_H
#include "ultima/ultima4/core/observable.h"
namespace Ultima {
namespace Ultima4 {
/**
* Aura class
*/
class Aura : public Observable<Aura *> {
public:
enum Type {
NONE,
HORN,
JINX,
NEGATE,
PROTECTION,
QUICKNESS
};
Aura();
int getDuration() const {
return _duration;
}
Aura::Type getType() const {
return _type;
}
bool isActive() const {
return _duration > 0;
}
void setDuration(int d);
void set(Type = NONE, int d = 0);
void setType(Type t);
bool operator==(const Type &t) const {
return _type == t;
}
bool operator!=(const Type &t) const {
return !operator==(t);
}
void passTurn();
private:
Type _type;
int _duration;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,435 @@
/* 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/game/codex.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/ultima4.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/core/utils.h"
namespace Ultima {
namespace Ultima4 {
Codex *g_codex;
Codex::Codex() {
g_codex = this;
}
Codex::~Codex() {
g_codex = nullptr;
}
int Codex::init() {
_virtueQuestions = u4read_stringtable("virtue");
_endgameText1 = u4read_stringtable("endgame1");
_endgameText2 = u4read_stringtable("endgame2");
return 1;
}
void Codex::deinit() {
_virtueQuestions.clear();
_endgameText1.clear();
_endgameText2.clear();
}
void Codex::start() {
init();
// Disable the whirlpool cursor and black out the screen
#ifdef IOS_ULTIMA4
U4IOS::IOSHideGameControllerHelper hideControllsHelper;
#endif
g_screen->screenDisableCursor();
g_screen->screenUpdate(&g_game->_mapArea, false, true);
// Make the avatar alone
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
g_context->_stats->update(true); // show just the avatar
g_screen->update();
// Change the view mode so the dungeon doesn't get shown
gameSetViewMode(VIEW_CODEX);
g_screen->screenMessage("\n\n\n\nThere is a sudden darkness, and you find yourself alone in an empty chamber.\n");
EventHandler::sleep(4000);
// Check to see if you have the 3-part key
if ((g_ultima->_saveGame->_items & (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T)) != (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T)) {
eject(CODEX_EJECT_NO_3_PART_KEY);
return;
}
g_screen->screenDrawImageInMapArea(BKGD_KEY);
g_screen->screenRedrawMapArea();
g_screen->screenMessage("\nYou use your key of Three Parts.\n");
EventHandler::sleep(3000);
g_screen->screenMessage("\nA voice rings out:\n\"What is the Word of Passage?\"\n\n");
// Get the Word of Passage
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("What is the Word of Passage?");
#endif
handleWOP(gameGetInput());
}
void Codex::eject(CodexEjectCode code) {
const struct {
int x, y;
} startLocations[] = {
{ 231, 136 },
{ 83, 105 },
{ 35, 221 },
{ 59, 44 },
{ 158, 21 },
{ 105, 183 },
{ 23, 129 },
{ 186, 171 }
};
switch (code) {
case CODEX_EJECT_NO_3_PART_KEY:
g_screen->screenMessage("\nThou dost not have the Key of Three Parts.\n\n");
break;
case CODEX_EJECT_NO_FULL_PARTY:
g_screen->screenMessage("\nThou hast not proved thy leadership in all eight virtues.\n\n");
EventHandler::sleep(2000);
g_screen->screenMessage("\nPassage is not granted.\n\n");
break;
case CODEX_EJECT_NO_FULL_AVATAR:
g_screen->screenMessage("\nThou art not ready.\n");
EventHandler::sleep(2000);
g_screen->screenMessage("\nPassage is not granted.\n\n");
break;
case CODEX_EJECT_BAD_WOP:
g_screen->screenMessage("\nPassage is not granted.\n\n");
break;
case CODEX_EJECT_HONESTY:
case CODEX_EJECT_COMPASSION:
case CODEX_EJECT_VALOR:
case CODEX_EJECT_JUSTICE:
case CODEX_EJECT_SACRIFICE:
case CODEX_EJECT_HONOR:
case CODEX_EJECT_SPIRITUALITY:
case CODEX_EJECT_HUMILITY:
case CODEX_EJECT_TRUTH:
case CODEX_EJECT_LOVE:
case CODEX_EJECT_COURAGE:
g_screen->screenMessage("\nThy quest is not yet complete.\n\n");
break;
case CODEX_EJECT_BAD_INFINITY:
g_screen->screenMessage("\nThou dost not know the true nature of the Universe.\n\n");
break;
default:
g_screen->screenMessage("\nOops, you just got too close to beating the game.\nBAD AVATAR!\n");
break;
}
EventHandler::sleep(2000);
// Free memory associated with the Codex
deinit();
// Re-enable the cursor and show it
g_screen->screenEnableCursor();
g_screen->screenShowCursor();
// Return view to normal and exit the Abyss
gameSetViewMode(VIEW_NORMAL);
g_game->exitToParentMap();
g_music->playMapMusic();
/*
* if being ejected because of a missed virtue question,
* then teleport the party to the starting location for
* that virtue.
*/
if (code >= CODEX_EJECT_HONESTY && code <= CODEX_EJECT_HUMILITY) {
int virtue = code - CODEX_EJECT_HONESTY;
g_context->_location->_coords.x = startLocations[virtue].x;
g_context->_location->_coords.y = startLocations[virtue].y;
}
// finally, finish the turn
g_context->_location->_turnCompleter->finishTurn();
eventHandler->setController(g_game);
}
void Codex::handleWOP(const Common::String &word) {
static int tries = 1;
int i;
eventHandler->popKeyHandler();
// slight pause before continuing
g_screen->screenMessage("\n");
g_screen->screenDisableCursor();
EventHandler::sleep(1000);
// entered correctly
if (scumm_stricmp(word.c_str(), "veramocor") == 0) {
tries = 1; // reset 'tries' in case we need to enter this again later
// eject them if they don't have all 8 party members
if (g_ultima->_saveGame->_members != 8) {
eject(CODEX_EJECT_NO_FULL_PARTY);
return;
}
// eject them if they're not a full avatar at this point
for (i = 0; i < VIRT_MAX; i++) {
if (g_ultima->_saveGame->_karma[i] != 0) {
eject(CODEX_EJECT_NO_FULL_AVATAR);
return;
}
}
g_screen->screenMessage("\nPassage is granted.\n");
EventHandler::sleep(4000);
g_screen->screenEraseMapArea();
g_screen->screenRedrawMapArea();
// Ask the Virtue questions
g_screen->screenMessage("\n\nThe voice asks:\n");
EventHandler::sleep(2000);
g_screen->screenMessage("\n%s\n\n", _virtueQuestions[0].c_str());
handleVirtues(gameGetInput());
return;
}
// entered incorrectly - give 3 tries before ejecting
else if (tries++ < 3) {
impureThoughts();
g_screen->screenMessage("\"What is the Word of Passage?\"\n\n");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which virtue?");
#endif
handleWOP(gameGetInput());
}
// 3 tries are up... eject!
else {
tries = 1;
eject(CODEX_EJECT_BAD_WOP);
}
}
void Codex::handleVirtues(const Common::String &virtue) {
static const char *const codexImageNames[] = {
BKGD_HONESTY, BKGD_COMPASSN, BKGD_VALOR, BKGD_JUSTICE,
BKGD_SACRIFIC, BKGD_HONOR, BKGD_SPIRIT, BKGD_HUMILITY,
BKGD_TRUTH, BKGD_LOVE, BKGD_COURAGE
};
static int current = 0;
static int tries = 1;
eventHandler->popKeyHandler();
// slight pause before continuing
g_screen->screenMessage("\n");
g_screen->screenDisableCursor();
EventHandler::sleep(1000);
// answered with the correct one of eight virtues
if ((current < VIRT_MAX) &&
(scumm_stricmp(virtue.c_str(), getVirtueName(static_cast<Virtue>(current))) == 0)) {
g_screen->screenDrawImageInMapArea(codexImageNames[current]);
g_screen->screenRedrawMapArea();
current++;
tries = 1;
EventHandler::sleep(2000);
if (current == VIRT_MAX) {
g_screen->screenMessage("\nThou art well versed in the virtues of the Avatar.\n");
EventHandler::sleep(5000);
}
g_screen->screenMessage("\n\nThe voice asks:\n");
EventHandler::sleep(2000);
g_screen->screenMessage("\n%s\n\n", _virtueQuestions[current].c_str());
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString((current != VIRT_MAX) ? "Which virtue?" : "Which principle?");
#endif
handleVirtues(gameGetInput());
}
// answered with the correct base virtue (truth, love, courage)
else if ((current >= VIRT_MAX) &&
(scumm_stricmp(virtue.c_str(), getBaseVirtueName(static_cast<BaseVirtue>(1 << (current - VIRT_MAX)))) == 0)) {
g_screen->screenDrawImageInMapArea(codexImageNames[current]);
g_screen->screenRedrawMapArea();
current++;
tries = 1;
if (current < VIRT_MAX + 3) {
g_screen->screenMessage("\n\nThe voice asks:\n");
EventHandler::sleep(2000);
g_screen->screenMessage("\n%s\n\n", _virtueQuestions[current].c_str());
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which principle?");
#endif
handleVirtues(gameGetInput());
} else {
g_screen->screenMessage("\nThe ground rumbles beneath your feet.\n");
EventHandler::sleep(1000);
g_screen->screenShake(10);
EventHandler::sleep(3000);
g_screen->screenEnableCursor();
g_screen->screenMessage("\nAbove the din, the voice asks:\n\nIf all eight virtues of the Avatar combine into and are derived from the Three Principles of Truth, Love and Courage...");
#ifdef IOS_ULTIMA4
// Ugh, we now enter happy callback land, so I know how to do these things manually. Good thing I kept these separate functions.
U4IOS::beginChoiceConversation();
U4IOS::updateChoicesInDialog(" ", "", -1);
#endif
eventHandler->pushKeyHandler(&handleInfinityAnyKey);
}
}
// give them 3 tries to enter the correct virtue, then eject them!
else if (tries++ < 3) {
impureThoughts();
g_screen->screenMessage("%s\n\n", _virtueQuestions[current].c_str());
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which virtue?");
#endif
handleVirtues(gameGetInput());
}
// failed 3 times... eject!
else {
eject(static_cast<CodexEjectCode>(CODEX_EJECT_HONESTY + current));
tries = 1;
current = 0;
}
}
bool Codex::handleInfinityAnyKey(int key, void *data) {
eventHandler->popKeyHandler();
g_screen->screenMessage("\n\nThen what is the one thing which encompasses and is the whole of all undeniable Truth, unending Love, and unyielding Courage?\n\n");
#ifdef IOS_ULTIMA4
U4IOS::endChoiceConversation();
U4IOS::IOSConversationHelper::setIntroString("What is the whole of all undeniable Truth, unending Love, and unyielding Courage?");
#endif
g_codex->handleInfinity(gameGetInput());
return true;
}
void Codex::handleInfinity(const Common::String &answer) {
static int tries = 1;
eventHandler->popKeyHandler();
#ifdef IOS_ULTIMA4
U4IOS::IOSHideGameControllerHelper hideControllsHelper;
#endif
// slight pause before continuing
g_screen->screenMessage("\n");
g_screen->screenDisableCursor();
EventHandler::sleep(1000);
if (scumm_stricmp(answer.c_str(), "infinity") == 0) {
EventHandler::sleep(2000);
g_screen->screenShake(10);
g_screen->screenEnableCursor();
g_screen->screenMessage("\n%s", _endgameText1[0].c_str());
#ifdef IOS_ULTIMA4
// Ugh, we now enter happy callback land, so I know how to do these things manually. Good thing I kept these separate functions.
U4IOS::hideGameButtons();
U4IOS::beginChoiceConversation();
U4IOS::updateChoicesInDialog(" ", "", -1);
U4IOS::testFlightPassCheckPoint("Game won!");
#endif
eventHandler->pushKeyHandler(&handleEndgameAnyKey);
} else if (tries++ < 3) {
impureThoughts();
g_screen->screenMessage("\nAbove the din, the voice asks:\n\nIf all eight virtues of the Avatar combine into and are derived from the Three Principles of Truth, Love and Courage...");
eventHandler->pushKeyHandler(&handleInfinityAnyKey);
} else eject(CODEX_EJECT_BAD_INFINITY);
}
bool Codex::handleEndgameAnyKey(int key, void *data) {
static int index = 1;
eventHandler->popKeyHandler();
if (index < 10) {
if (index < 7) {
if (index == 6) {
g_screen->screenEraseMapArea();
g_screen->screenRedrawMapArea();
}
g_screen->screenMessage("%s", g_codex->_endgameText1[index].c_str());
} else if (index == 7) {
g_screen->screenDrawImageInMapArea(BKGD_STONCRCL);
g_screen->screenRedrawMapArea();
g_screen->screenMessage("\n\n%s", g_codex->_endgameText2[index - 7].c_str());
} else if (index > 7)
g_screen->screenMessage("%s", g_codex->_endgameText2[index - 7].c_str());
index++;
eventHandler->pushKeyHandler(&g_codex->handleEndgameAnyKey);
} else {
// CONGRATULATIONS!... you have completed the game in x turns
g_screen->screenDisableCursor();
g_screen->screenMessage("%s%d%s", g_codex->_endgameText2[index - 7].c_str(),
g_ultima->_saveGame->_moves, g_codex->_endgameText2[index - 6].c_str());
#ifdef IOS_ULTIMA4
U4IOS::endChoiceConversation();
#endif
eventHandler->pushKeyHandler(&KeyHandler::ignoreKeys);
}
return true;
}
void Codex::impureThoughts() {
g_screen->screenMessage("\nThy thoughts are not pure.\nI ask again.\n");
EventHandler::sleep(2000);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,115 @@
/* 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_GAME_CODEX_H
#define ULTIMA4_GAME_CODEX_H
#include "common/array.h"
namespace Ultima {
namespace Ultima4 {
enum CodexEjectCode {
CODEX_EJECT_NO_3_PART_KEY,
CODEX_EJECT_BAD_WOP,
CODEX_EJECT_NO_FULL_PARTY,
CODEX_EJECT_NO_FULL_AVATAR,
CODEX_EJECT_HONESTY,
CODEX_EJECT_COMPASSION,
CODEX_EJECT_VALOR,
CODEX_EJECT_JUSTICE,
CODEX_EJECT_SACRIFICE,
CODEX_EJECT_HONOR,
CODEX_EJECT_SPIRITUALITY,
CODEX_EJECT_HUMILITY,
CODEX_EJECT_TRUTH,
CODEX_EJECT_LOVE,
CODEX_EJECT_COURAGE,
CODEX_EJECT_BAD_INFINITY
};
class Codex {
private:
Common::Array<Common::String> _virtueQuestions;
Common::Array<Common::String> _endgameText1;
Common::Array<Common::String> _endgameText2;
private:
/**
* Initializes the Chamber of the Codex sequence (runs from codexStart())
*/
int init();
/**
* Frees all memory associated with the Codex sequence
*/
void deinit();
/**
* Ejects you from the chamber of the codex (and the Abyss, for that matter)
* with the correct message.
*/
void eject(CodexEjectCode code);
/**
* Handles entering the Word of Passage
*/
void handleWOP(const Common::String &word);
/**
* Handles naming of virtues in the Chamber of the Codex
*/
void handleVirtues(const Common::String &virtue);
void handleInfinity(const Common::String &answer);
/**
* Pretty self-explanatory
*/
void impureThoughts();
/**
* Key handlers
*/
static bool handleInfinityAnyKey(int key, void *data);
static bool handleEndgameAnyKey(int key, void *data);
public:
/**
* Constructor
*/
Codex();
/**
* Destructor
*/
~Codex();
/**
* Begins the Chamber of the Codex sequence
*/
void start();
};
extern Codex *g_codex;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,70 @@
/* 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/game/context.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/map/location.h"
namespace Ultima {
namespace Ultima4 {
Context *g_context;
Context::Context() : _stats(nullptr), _aura(nullptr),
_party(nullptr), _location(nullptr) {
g_context = this;
reset();
}
Context::~Context() {
g_context = nullptr;
reset();
}
void Context::reset() {
delete _stats;
delete _aura;
delete _party;
while (_location)
locationFree(&_location);
_stats = nullptr;
_aura = nullptr;
_party = nullptr;
_location = nullptr;
_lastShip = nullptr;
_line = 9999;
_col = 0;
_moonPhase = 0;
_windDirection = 0;
_windCounter = 0;
_windLock = false;
_horseSpeed = 0;
_opacity = 0;
_lastCommandTime = 0;
_transportContext = TRANSPORT_ANY;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,97 @@
/* 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_GAME_CONTEXT_H
#define ULTIMA4_GAME_CONTEXT_H
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/game/aura.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/script.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
class Object;
class Party;
class Person;
class Script;
class StatsArea;
enum TransportContext {
TRANSPORT_FOOT = 0x1,
TRANSPORT_HORSE = 0x2,
TRANSPORT_SHIP = 0x4,
TRANSPORT_BALLOON = 0x8,
TRANSPORT_FOOT_OR_HORSE = TRANSPORT_FOOT | TRANSPORT_HORSE,
TRANSPORT_ANY = 0xffff
};
/**
* Context class
*/
class Context : public Script::Provider {
public:
Context();
~Context();
/**
* Reset the context
*/
void reset();
StatsArea *_stats;
Aura *_aura;
Party *_party;
Location *_location;
int _line, _col;
int _moonPhase;
int _windDirection;
int _windCounter;
bool _windLock;
int _horseSpeed;
int _opacity;
TransportContext _transportContext;
uint32 _lastCommandTime;
Object *_lastShip;
public:
/**
* Provides scripts with information
*/
Common::String translate(Std::vector<Common::String> &parts) override {
if (parts.size() == 1) {
if (parts[0] == "wind")
return getDirectionName(static_cast<Direction>(_windDirection));
}
return "";
}
};
extern Context *g_context;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,449 @@
/* 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_GAME_CREATURE_H
#define ULTIMA4_GAME_CREATURE_H
#include "ultima/ultima4/game/object.h"
#include "ultima/ultima4/map/movement.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
class CombatController;
class ConfigElement;
class Tile;
typedef unsigned short CreatureId;
typedef Common::HashMap<CreatureId, class Creature *> CreatureMap;
typedef Std::vector<class Creature *> CreatureVector;
#define MAX_CREATURES 128
/* Creatures on world map */
#define MAX_CREATURES_ON_MAP 4
#define MAX_CREATURE_DISTANCE 16
/* Creature ids */
typedef enum {
HORSE1_ID = 0,
HORSE2_ID = 1,
MAGE_ID = 2,
BARD_ID = 3,
FIGHTER_ID = 4,
DRUID_ID = 5,
TINKER_ID = 6,
PALADIN_ID = 7,
RANGER_ID = 8,
SHEPHERD_ID = 9,
GUARD_ID = 10,
VILLAGER_ID = 11,
SINGINGBARD_ID = 12,
JESTER_ID = 13,
BEGGAR_ID = 14,
CHILD_ID = 15,
BULL_ID = 16,
LORDBRITISH_ID = 17,
PIRATE_ID = 18,
NIXIE_ID = 19,
GIANT_SQUID_ID = 20,
SEA_SERPENT_ID = 21,
SEAHORSE_ID = 22,
WHIRLPOOL_ID = 23,
STORM_ID = 24,
RAT_ID = 25,
BAT_ID = 26,
GIANT_SPIDER_ID = 27,
GHOST_ID = 28,
SLIME_ID = 29,
TROLL_ID = 30,
GREMLIN_ID = 31,
MIMIC_ID = 32,
REAPER_ID = 33,
INSECT_SWARM_ID = 34,
GAZER_ID = 35,
PHANTOM_ID = 36,
ORC_ID = 37,
SKELETON_ID = 38,
ROGUE_ID = 39,
PYTHON_ID = 40,
ETTIN_ID = 41,
HEADLESS_ID = 42,
CYCLOPS_ID = 43,
WISP_ID = 44,
EVILMAGE_ID = 45,
LICH_ID = 46,
LAVA_LIZARD_ID = 47,
ZORN_ID = 48,
DAEMON_ID = 49,
HYDRA_ID = 50,
DRAGON_ID = 51,
BALRON_ID = 52
} CreatureType;
typedef enum {
MATTR_STEALFOOD = 0x1,
MATTR_STEALGOLD = 0x2,
MATTR_CASTS_SLEEP = 0x4,
MATTR_UNDEAD = 0x8,
MATTR_GOOD = 0x10,
MATTR_WATER = 0x20,
MATTR_NONATTACKABLE = 0x40,
MATTR_NEGATE = 0x80,
MATTR_CAMOUFLAGE = 0x100,
MATTR_NOATTACK = 0x200,
MATTR_AMBUSHES = 0x400,
MATTR_RANDOMRANGED = 0x800,
MATTR_INCORPOREAL = 0x1000,
MATTR_NOCHEST = 0x2000,
MATTR_DIVIDES = 0x4000,
MATTR_SPAWNSONDEATH = 0x8000,
MATTR_FORCE_OF_NATURE = 0x10000
} CreatureAttrib;
typedef enum {
MATTR_STATIONARY = 0x1,
MATTR_WANDERS = 0x2,
MATTR_SWIMS = 0x4,
MATTR_SAILS = 0x8,
MATTR_FLIES = 0x10,
MATTR_TELEPORT = 0x20,
MATTR_CANMOVECREATURES = 0x40,
MATTR_CANMOVEAVATAR = 0x80
} CreatureMovementAttrib;
typedef enum {
MSTAT_DEAD,
MSTAT_FLEEING,
MSTAT_CRITICAL,
MSTAT_HEAVILYWOUNDED,
MSTAT_LIGHTLYWOUNDED,
MSTAT_BARELYWOUNDED
} CreatureStatus;
/**
* Creature Class Definition
* @todo
* <ul>
* <li>split into a CreatureType (all the settings for a
* particular creature e.g. orc) and Creature (a specific
* creature instance)</li>
* <li>creatures can be looked up by name, ids can probably go away</li>
* </ul>
*/
class Creature : public Object {
typedef Common::List<StatusType> StatusList;
public:
/**
* Creature class implementation
*/
Creature(MapTile tile = MapTile(0));
void load(const ConfigElement &conf);
// Accessor methods
virtual Common::String getName() const {
return _name;
}
virtual const Common::String &getHitTile() const {
return _rangedHitTile;
}
virtual const Common::String &getMissTile() const {
return _rangedMissTile;
}
CreatureId getId() const {
return _id;
}
CreatureId getLeader() const {
return _leader;
}
virtual int getHp() const {
return _hp;
}
virtual int getXp() const {
return _xp;
}
virtual const Common::String &getWorldrangedtile() const {
return _worldRangedTile;
}
SlowedType getSlowedType() const {
return _slowedType;
}
int getEncounterSize() const {
return _encounterSize;
}
byte getResists() const {
return _resists;
}
// Setters
void setName(Common::String s) {
_name = s;
}
void setHitTile(const Common::String &t) {
_rangedHitTile = t;
}
void setMissTile(const Common::String &t) {
_rangedMissTile = t;
}
virtual void setHp(int points) {
_hp = points;
}
// Query methods
bool isGood() const {
return _mAttr & MATTR_GOOD;
}
bool isEvil() const {
return !isGood();
}
bool isUndead() const {
return _mAttr & MATTR_UNDEAD;
}
bool leavesChest() const {
return !isAquatic() && !(_mAttr & MATTR_NOCHEST);
}
bool isAquatic() const {
return _mAttr & MATTR_WATER;
}
bool wanders() const {
return _movementAttr & MATTR_WANDERS;
}
bool isStationary() const {
return _movementAttr & MATTR_STATIONARY;
}
bool flies() const {
return _movementAttr & MATTR_FLIES;
}
bool teleports() const {
return _movementAttr & MATTR_TELEPORT;
}
bool swims() const {
return _movementAttr & MATTR_SWIMS;
}
bool sails() const {
return _movementAttr & MATTR_SAILS;
}
bool walks() const {
return !(flies() || swims() || sails());
}
bool divides() const {
return _mAttr & MATTR_DIVIDES;
}
bool spawnsOnDeath() const {
return _mAttr & MATTR_SPAWNSONDEATH;
}
bool canMoveOntoCreatures() const {
return _movementAttr & MATTR_CANMOVECREATURES;
}
bool canMoveOntoPlayer() const {
return _movementAttr & MATTR_CANMOVEAVATAR;
}
bool isAttackable() const;
bool willAttack() const {
return !(_mAttr & MATTR_NOATTACK);
}
bool stealsGold() const {
return _mAttr & MATTR_STEALGOLD;
}
bool stealsFood() const {
return _mAttr & MATTR_STEALFOOD;
}
bool negates() const {
return _mAttr & MATTR_NEGATE;
}
bool camouflages() const {
return _mAttr & MATTR_CAMOUFLAGE;
}
bool ambushes() const {
return _mAttr & MATTR_AMBUSHES;
}
bool isIncorporeal() const {
return _mAttr & MATTR_INCORPOREAL;
}
bool hasRandomRanged() const {
return _mAttr & MATTR_RANDOMRANGED;
}
bool leavesTile() const {
return _leavesTile;
}
bool castsSleep() const {
return _mAttr & MATTR_CASTS_SLEEP;
}
bool isForceOfNature() const {
return _mAttr & MATTR_FORCE_OF_NATURE;
}
int getDamage() const;
const Common::String &getCamouflageTile() const {
return _camouflageTile;
}
void setRandomRanged();
int setInitialHp(int hp = -1);
/**
* Performs a special action for the creature
* Returns true if the action takes up the creatures
* whole turn (i.e. it can't move afterwards)
*/
bool specialAction();
/**
* Performs a special effect for the creature
* Returns true if something special happened,
* or false if nothing happened
*/
bool specialEffect();
/* combat methods */
void act(CombatController *controller);
/**
* Add status effects to the creature, in order of importance
*/
virtual void addStatus(StatusType status);
void applyTileEffect(TileEffect effect);
virtual int getAttackBonus() const;
virtual int getDefense() const;
bool divide();
bool spawnOnDeath();
virtual CreatureStatus getState() const;
StatusType getStatus() const;
bool isAsleep() const;
/**
* Hides or shows a camouflaged creature, depending on its distance from
* the nearest opponent
*/
bool hideOrShow();
Creature *nearestOpponent(int *dist, bool ranged);
virtual void putToSleep();
virtual void removeStatus(StatusType status);
virtual void setStatus(StatusType status);
virtual void wakeUp();
/**
* Applies damage to the creature.
* Returns true if the creature still exists after the damage has been applied
* or false, if the creature was destroyed
*
* If byplayer is false (when a monster is killed by walking through
* fire or poison, or as a result of jinx) we don't report experience
* on death
*/
virtual bool applyDamage(int damage, bool byplayer = true);
virtual bool dealDamage(Creature *m, int damage);
// Properties
protected:
Common::String _name;
Common::String _rangedHitTile;
Common::String _rangedMissTile;
CreatureId _id;
Common::String _camouflageTile;
CreatureId _leader;
int _baseHp;
int _hp;
StatusList _status;
int _xp;
byte _ranged;
Common::String _worldRangedTile;
bool _leavesTile;
CreatureAttrib _mAttr;
CreatureMovementAttrib _movementAttr;
SlowedType _slowedType;
int _encounterSize;
byte _resists;
CreatureId _spawn;
};
/**
* CreatureMgr Class Definition
*/
class CreatureMgr {
public:
static CreatureMgr *getInstance();
void loadAll();
/**
* Returns a creature using a tile to find which one to create
* or nullptr if a creature with that tile cannot be found
*/
Creature *getByTile(MapTile tile);
/**
* Returns the creature that has the corresponding id
* or returns nullptr if no creature with that id could
* be found.
*/
Creature *getById(CreatureId id);
/**
* Returns the creature that has the corresponding name
* or returns nullptr if no creature can be found with
* that name (case insensitive)
*/
Creature *getByName(Common::String name);
/**
* Creates a random creature based on the tile given
*/
Creature *randomForTile(const Tile *tile);
/**
* Creates a random creature based on the dungeon level given
*/
Creature *randomForDungeon(int dnglevel);
/**
* Creates a random ambushing creature
*/
Creature *randomAmbushing();
private:
CreatureMgr() {}
// disallow assignments, copy construction
CreatureMgr(const CreatureMgr &);
const CreatureMgr &operator=(const CreatureMgr &);
static CreatureMgr *_instance;
CreatureMap _creatures;
};
bool isCreature(Object *punknown);
#define creatureMgr (CreatureMgr::getInstance())
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,148 @@
/* 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/game/death.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/controllers/wait_controller.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/sound/music.h"
#include "ultima/ultima4/ultima4.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
Death *g_death;
#define REVIVE_WORLD_X 86
#define REVIVE_WORLD_Y 107
#define REVIVE_CASTLE_X 19
#define REVIVE_CASTLE_Y 8
const struct {
int _timeout; ///< pause in seconds
const char *_text; ///< text of message
} DEATH_MSGS[] = {
{ 5, "\n\n\nAll is Dark...\n" },
{ 5, "\nBut wait...\n" },
{ 5, "Where am I?...\n" },
{ 5, "Am I dead?...\n" },
{ 5, "Afterlife?...\n" },
{ 5, "You hear:\n %s\n" },
{ 5, "I feel motion...\n" },
{ 5, "\nLord British says: I have pulled thy spirit and some possessions from the void. Be more careful in the future!\n\n\020" }
};
#define N_MSGS (sizeof(DEATH_MSGS) / sizeof(DEATH_MSGS[0]))
Death::Death() : timerCount(0), timerMsg(0), deathSequenceRunning(false) {
g_death = this;
}
Death::~Death() {
g_death = nullptr;
}
void Death::start(int delay) {
if (deathSequenceRunning)
return;
// stop playing music
g_music->fadeOut(1000);
deathSequenceRunning = 1;
timerCount = 0;
timerMsg = 0;
WaitController waitCtrl(delay * settings._gameCyclesPerSecond);
eventHandler->pushController(&waitCtrl);
waitCtrl.wait();
gameSetViewMode(VIEW_DEAD);
eventHandler->pushKeyHandler(&KeyHandler::ignoreKeys);
g_screen->screenDisableCursor();
eventHandler->getTimer()->add(&deathTimer, settings._gameCyclesPerSecond);
}
void Death::deathTimer(void *data) {
g_death->timerCount++;
if ((g_death->timerMsg < N_MSGS) && (g_death->timerCount > DEATH_MSGS[g_death->timerMsg]._timeout)) {
g_screen->screenMessage(DEATH_MSGS[g_death->timerMsg]._text, g_context->_party->member(0)->getName().c_str());
g_screen->screenHideCursor();
g_death->timerCount = 0;
g_death->timerMsg++;
if (g_death->timerMsg >= N_MSGS) {
eventHandler->getTimer()->remove(&deathTimer);
g_death->revive();
}
}
}
void Death::revive() {
while (!g_context->_location->_map->isWorldMap() && g_context->_location->_prev != nullptr) {
g_game->exitToParentMap();
}
eventHandler->setController(g_game);
deathSequenceRunning = false;
gameSetViewMode(VIEW_NORMAL);
// Move our world map location to Lord British's Castle
g_context->_location->_coords = g_context->_location->_map->_portals[0]->_coords;
// Now, move the avatar into the castle and put him in front of Lord British
g_game->setMap(mapMgr->get(100), 1, nullptr);
g_context->_location->_coords.x = REVIVE_CASTLE_X;
g_context->_location->_coords.y = REVIVE_CASTLE_Y;
g_context->_location->_coords.z = 0;
g_context->_aura->set();
g_context->_horseSpeed = 0;
g_context->_lastCommandTime = g_system->getMillis();
g_music->playMapMusic();
g_context->_party->reviveParty();
g_screen->screenEnableCursor();
g_screen->screenShowCursor();
g_context->_stats->setView(STATS_PARTY_OVERVIEW);
g_screen->update();
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,61 @@
/* 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_GAME_DEATH_H
#define ULTIMA4_GAME_DEATH_H
#include "common/scummsys.h"
namespace Ultima {
namespace Ultima4 {
class Death {
private:
int timerCount;
uint timerMsg;
bool deathSequenceRunning;
private:
/**
* Timer
*/
static void deathTimer(void *data);
void revive();
public:
/**
* Constructor
*/
Death();
/**
* Destructor
*/
~Death();
void start(int delay);
};
extern Death *g_death;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,831 @@
/* 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/controllers/camp_controller.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/controllers/intro_controller.h"
#include "ultima/ultima4/controllers/read_choice_controller.h"
#include "ultima/ultima4/controllers/read_dir_controller.h"
#include "ultima/ultima4/controllers/read_int_controller.h"
#include "ultima/ultima4/controllers/read_player_controller.h"
#include "ultima/ultima4/controllers/read_string_controller.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/debugger.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/armor.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/death.h"
#include "ultima/ultima4/game/item.h"
#include "ultima/ultima4/views/menu.h"
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/game/spell.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/game/script.h"
#include "ultima/ultima4/game/weapon.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/map/movement.h"
#include "ultima/ultima4/map/shrine.h"
#include "ultima/ultima4/map/tilemap.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/sound/music.h"
#include "ultima/ultima4/sound/sound.h"
#include "ultima/ultima4/views/dungeonview.h"
#include "ultima/ultima4/metaengine.h"
#include "common/savefile.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
/*-----------------*/
/* Functions BEGIN */
/* main game functions */
void gameAdvanceLevel(PartyMember *player);
void gameInnHandler();
void gameLostEighth(Virtue virtue);
void gamePartyStarving();
void mixReagentsSuper();
/* action functions */
void wearArmor(int player = -1);
/* Functions END */
/*---------------*/
void gameSetViewMode(ViewMode newMode) {
g_context->_location->_viewMode = newMode;
}
void gameUpdateScreen() {
switch (g_context->_location->_viewMode) {
case VIEW_NORMAL:
g_screen->screenUpdate(&g_game->_mapArea, true, false);
break;
case VIEW_GEM:
g_screen->screenGemUpdate();
break;
case VIEW_RUNE:
g_screen->screenUpdate(&g_game->_mapArea, false, false);
break;
case VIEW_DUNGEON:
g_screen->screenUpdate(&g_game->_mapArea, true, false);
break;
case VIEW_DEAD:
g_screen->screenUpdate(&g_game->_mapArea, true, true);
break;
case VIEW_CODEX: /* the screen updates will be handled elsewhere */
break;
case VIEW_MIXTURES: /* still testing */
break;
default:
error("invalid view mode: %d", g_context->_location->_viewMode);
}
}
void gameSpellEffect(int spell, int player, Sound sound) {
int time;
Spell::SpecialEffects effect = Spell::SFX_INVERT;
if (player >= 0)
g_context->_stats->highlightPlayer(player);
time = settings._spellEffectSpeed * 800 / settings._gameCyclesPerSecond;
soundPlay(sound, false, time);
///The following effect multipliers are not accurate
switch (spell) {
case 'g': /* gate */
case 'r': /* resurrection */
break;
case 't': /* tremor */
effect = Spell::SFX_TREMOR;
break;
default:
/* default spell effect */
break;
}
switch (effect) {
case Spell::SFX_TREMOR:
case Spell::SFX_INVERT:
gameUpdateScreen();
g_game->_mapArea.highlight(0, 0, VIEWPORT_W * TILE_WIDTH, VIEWPORT_H * TILE_HEIGHT);
g_screen->update();
EventHandler::sleep(time);
g_game->_mapArea.unhighlight();
g_screen->update();
if (effect == Spell::SFX_TREMOR) {
gameUpdateScreen();
g_screen->update();
soundPlay(SOUND_RUMBLE, false);
g_screen->screenShake(8);
}
break;
default:
break;
}
}
Common::String gameGetInput(int maxlen) {
g_screen->screenEnableCursor();
g_screen->screenShowCursor();
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper helper;
helper.beginConversation(U4IOS::UIKeyboardTypeDefault);
#endif
return ReadStringController::get(maxlen, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
}
int gameGetPlayer(bool canBeDisabled, bool canBeActivePlayer) {
int player;
if (g_ultima->_saveGame->_members <= 1) {
player = 0;
} else {
if (canBeActivePlayer && (g_context->_party->getActivePlayer() >= 0)) {
player = g_context->_party->getActivePlayer();
} else {
ReadPlayerController readPlayerController;
eventHandler->pushController(&readPlayerController);
player = readPlayerController.waitFor();
}
if (player == -1) {
g_screen->screenMessage("None\n");
return -1;
}
}
g_context->_col--;// display the selected character name, in place of the number
if ((player >= 0) && (player < 8)) {
g_screen->screenMessage("%s\n", g_ultima->_saveGame->_players[player]._name); //Write player's name after prompt
}
if (!canBeDisabled && g_context->_party->member(player)->isDisabled()) {
g_screen->screenMessage("%cDisabled!%c\n", FG_GREY, FG_WHITE);
return -1;
}
assertMsg(player < g_context->_party->size(), "player %d, but only %d members\n", player, g_context->_party->size());
return player;
}
Direction gameGetDirection() {
ReadDirController dirController;
g_screen->screenMessage("Dir?");
#ifdef IOS_ULTIMA4
U4IOS::IOSDirectionHelper directionPopup;
#endif
eventHandler->pushController(&dirController);
Direction dir = dirController.waitFor();
g_screen->screenMessage("\b\b\b\b");
if (dir == DIR_NONE) {
g_screen->screenMessage(" \n");
return dir;
} else {
g_screen->screenMessage("%s\n", getDirectionName(dir));
return dir;
}
}
bool fireAt(const Coords &coords, bool originAvatar) {
bool validObject = false;
bool hitsAvatar = false;
bool objectHit = false;
Object *obj = nullptr;
MapTile tile(g_context->_location->_map->_tileSet->getByName("miss_flash")->getId());
GameController::flashTile(coords, tile, 1);
obj = g_context->_location->_map->objectAt(coords);
Creature *m = dynamic_cast<Creature *>(obj);
if (obj && obj->getType() == Object::CREATURE && m && m->isAttackable())
validObject = true;
/* See if it's an object to be destroyed (the avatar cannot destroy the balloon) */
else if (obj &&
(obj->getType() == Object::UNKNOWN) &&
!(obj->getTile().getTileType()->isBalloon() && originAvatar))
validObject = true;
/* Does the cannon hit the avatar? */
if (coords == g_context->_location->_coords) {
validObject = true;
hitsAvatar = true;
}
if (validObject) {
/* always displays as a 'hit' though the object may not be destroyed */
/* Is is a pirate ship firing at US? */
if (hitsAvatar) {
GameController::flashTile(coords, "hit_flash", 4);
if (g_context->_transportContext == TRANSPORT_SHIP)
gameDamageShip(-1, 10);
else gameDamageParty(10, 25); /* party gets hurt between 10-25 damage */
}
/* inanimate objects get destroyed instantly, while creatures get a chance */
else if (obj->getType() == Object::UNKNOWN) {
GameController::flashTile(coords, "hit_flash", 4);
g_context->_location->_map->removeObject(obj);
}
/* only the avatar can hurt other creatures with cannon fire */
else if (originAvatar) {
GameController::flashTile(coords, "hit_flash", 4);
if (xu4_random(4) == 0) /* reverse-engineered from u4dos */
g_context->_location->_map->removeObject(obj);
}
objectHit = true;
}
return objectHit;
}
bool gamePeerCity(int city, void *data) {
Map *peerMap;
peerMap = mapMgr->get((MapId)(city + 1));
if (peerMap != nullptr) {
g_game->setMap(peerMap, 1, nullptr);
g_context->_location->_viewMode = VIEW_GEM;
g_game->_paused = true;
g_game->_pausedTimer = 0;
g_screen->screenDisableCursor();
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper continueHelper;
continueHelper.updateChoices(" ");
continueHelper.fullSizeChoicePanel();
#endif
ReadChoiceController::get("\015 \033");
g_game->exitToParentMap();
g_screen->screenEnableCursor();
g_game->_paused = false;
return true;
}
return false;
}
void peer(bool useGem) {
if (useGem) {
if (g_ultima->_saveGame->_gems <= 0) {
g_screen->screenMessage("%cPeer at What?%c\n", FG_GREY, FG_WHITE);
return;
}
g_ultima->_saveGame->_gems--;
g_screen->screenMessage("Peer at a Gem!\n");
}
g_game->_paused = true;
g_game->_pausedTimer = 0;
g_screen->screenDisableCursor();
g_context->_location->_viewMode = VIEW_GEM;
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper continueHelper;
continueHelper.updateChoices(" ");
continueHelper.fullSizeChoicePanel();
#endif
ReadChoiceController::get("\015 \033");
g_screen->screenEnableCursor();
g_context->_location->_viewMode = VIEW_NORMAL;
g_game->_paused = false;
}
void gameCheckHullIntegrity() {
int i;
bool killAll = false;
/* see if the ship has sunk */
if ((g_context->_transportContext == TRANSPORT_SHIP) && g_ultima->_saveGame->_shipHull <= 0) {
g_screen->screenMessage("\nThy ship sinks!\n\n");
killAll = true;
}
if (!g_debugger->_collisionOverride && g_context->_transportContext == TRANSPORT_FOOT &&
g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS)->isSailable() &&
!g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->isShip() &&
!g_context->_location->_map->getValidMoves(g_context->_location->_coords, g_context->_party->getTransport())) {
g_screen->screenMessage("\nTrapped at sea without thy ship, thou dost drown!\n\n");
killAll = true;
}
if (killAll) {
for (i = 0; i < g_context->_party->size(); i++) {
g_context->_party->member(i)->setHp(0);
g_context->_party->member(i)->setStatus(STAT_DEAD);
}
g_screen->update();
g_death->start(5);
}
}
void gameFixupObjects(Map *map) {
int i;
Object *obj;
/* add stuff from the monster table to the map */
for (i = 0; i < MONSTERTABLE_SIZE; i++) {
SaveGameMonsterRecord *monster = &map->_monsterTable[i];
if (monster->_prevTile != 0) {
Coords coords(monster->_x, monster->_y);
// tile values stored in monsters.sav hardcoded to index into base tilemap
MapTile tile = g_tileMaps->get("base")->translate(monster->_tile),
oldTile = g_tileMaps->get("base")->translate(monster->_prevTile);
if (i < MONSTERTABLE_CREATURES_SIZE) {
const Creature *creature = creatureMgr->getByTile(tile);
/* make sure we really have a creature */
if (creature)
obj = map->addCreature(creature, coords);
else {
warning("A non-creature object was found in the creature section of the monster table. (Tile: %s)\n", tile.getTileType()->getName().c_str());
obj = map->addObject(tile, oldTile, coords);
}
} else {
obj = map->addObject(tile, oldTile, coords);
}
/* set the map for our object */
obj->setMap(map);
}
}
}
uint32 gameTimeSinceLastCommand() {
return (g_system->getMillis() - g_context->_lastCommandTime) / 1000;
}
void gameCreatureAttack(Creature *m) {
Object *under;
const Tile *ground;
g_screen->screenMessage("\nAttacked by %s\n", m->getName().c_str());
/// TODO: CHEST: Make a user option to not make chests change battlefield
/// map (2 of 2)
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
if (!ground->isChest()) {
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS);
if ((under = g_context->_location->_map->objectAt(g_context->_location->_coords)) &&
under->getTile().getTileType()->isShip())
ground = under->getTile().getTileType();
}
CombatController *cc = new CombatController(CombatMap::mapForTile(ground, g_context->_party->getTransport().getTileType(), m));
cc->init(m);
cc->begin();
}
bool creatureRangeAttack(const Coords &coords, Creature *m) {
// int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
// Figure out what the ranged attack should look like
MapTile tile(g_context->_location->_map->_tileSet->getByName((m && !m->getWorldrangedtile().empty()) ?
m->getWorldrangedtile() :
"hit_flash")->getId());
GameController::flashTile(coords, tile, 1);
// See if the attack hits the avatar
Object *obj = g_context->_location->_map->objectAt(coords);
m = dynamic_cast<Creature *>(obj);
// Does the attack hit the avatar?
if (coords == g_context->_location->_coords) {
/* always displays as a 'hit' */
GameController::flashTile(coords, tile, 3);
/* FIXME: check actual damage from u4dos -- values here are guessed */
if (g_context->_transportContext == TRANSPORT_SHIP)
gameDamageShip(-1, 10);
else
gameDamageParty(10, 25);
return true;
}
// Destroy objects that were hit
else if (obj) {
if ((obj->getType() == Object::CREATURE && m && m->isAttackable()) ||
obj->getType() == Object::UNKNOWN) {
GameController::flashTile(coords, tile, 3);
g_context->_location->_map->removeObject(obj);
return true;
}
}
return false;
}
Std::vector<Coords> gameGetDirectionalActionPath(int dirmask, int validDirections, const Coords &origin, int minDistance, int maxDistance, bool (*blockedPredicate)(const Tile *tile), bool includeBlocked) {
Std::vector<Coords> path;
Direction dirx = DIR_NONE,
diry = DIR_NONE;
/* Figure out which direction the action is going */
if (DIR_IN_MASK(DIR_WEST, dirmask))
dirx = DIR_WEST;
else if (DIR_IN_MASK(DIR_EAST, dirmask))
dirx = DIR_EAST;
if (DIR_IN_MASK(DIR_NORTH, dirmask))
diry = DIR_NORTH;
else if (DIR_IN_MASK(DIR_SOUTH, dirmask))
diry = DIR_SOUTH;
/*
* try every tile in the given direction, up to the given range.
* Stop when the range is exceeded, or the action is blocked.
*/
MapCoords t_c(origin);
if ((dirx <= 0 || DIR_IN_MASK(dirx, validDirections)) &&
(diry <= 0 || DIR_IN_MASK(diry, validDirections))) {
for (int distance = 0; distance <= maxDistance;
distance++, t_c.move(dirx, g_context->_location->_map), t_c.move(diry, g_context->_location->_map)) {
if (distance >= minDistance) {
/* make sure our action isn't taking us off the map */
if (MAP_IS_OOB(g_context->_location->_map, t_c))
break;
const Tile *tile = g_context->_location->_map->tileTypeAt(t_c, WITH_GROUND_OBJECTS);
/* should we see if the action is blocked before trying it? */
if (!includeBlocked && blockedPredicate &&
!(*(blockedPredicate))(tile))
break;
path.push_back(t_c);
/* see if the action was blocked only if it did not succeed */
if (includeBlocked && blockedPredicate &&
!(*(blockedPredicate))(tile))
break;
}
}
}
return path;
}
void gameDamageParty(int minDamage, int maxDamage) {
int i;
int damage;
int lastdmged = -1;
for (i = 0; i < g_context->_party->size(); i++) {
if (xu4_random(2) == 0) {
damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
xu4_random((maxDamage + 1) - minDamage) + minDamage :
maxDamage;
g_context->_party->member(i)->applyDamage(damage);
g_context->_stats->highlightPlayer(i);
lastdmged = i;
EventHandler::sleep(50);
}
}
g_screen->screenShake(1);
// Un-highlight the last player
if (lastdmged != -1) g_context->_stats->highlightPlayer(lastdmged);
}
void gameDamageShip(int minDamage, int maxDamage) {
int damage;
if (g_context->_transportContext == TRANSPORT_SHIP) {
damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
xu4_random((maxDamage + 1) - minDamage) + minDamage :
maxDamage;
g_screen->screenShake(1);
g_context->_party->damageShip(damage);
gameCheckHullIntegrity();
}
}
void gameSetActivePlayer(int player) {
if (player == -1) {
g_context->_party->setActivePlayer(-1);
g_screen->screenMessage("Set Active Player: None!\n");
} else if (player < g_context->_party->size()) {
g_screen->screenMessage("Set Active Player: %s!\n", g_context->_party->member(player)->getName().c_str());
if (g_context->_party->member(player)->isDisabled())
g_screen->screenMessage("Disabled!\n");
else
g_context->_party->setActivePlayer(player);
}
}
bool gameSpawnCreature(const Creature *m) {
int t, i;
const Creature *creature;
MapCoords coords = g_context->_location->_coords;
if (g_context->_location->_context & CTX_DUNGEON) {
/* FIXME: for some reason dungeon monsters aren't spawning correctly */
bool found = false;
MapCoords new_coords;
for (i = 0; i < 0x20; i++) {
new_coords = MapCoords(xu4_random(g_context->_location->_map->_width), xu4_random(g_context->_location->_map->_height), coords.z);
const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITH_OBJECTS);
if (tile->isCreatureWalkable()) {
found = true;
break;
}
}
if (!found)
return false;
coords = new_coords;
} else {
int dx = 0,
dy = 0;
bool ok = false;
int tries = 0;
static const int MAX_TRIES = 10;
while (!ok && (tries < MAX_TRIES)) {
dx = 7;
dy = xu4_random(7);
if (xu4_random(2))
dx = -dx;
if (xu4_random(2))
dy = -dy;
if (xu4_random(2)) {
t = dx;
dx = dy;
dy = t;
}
/* make sure we can spawn the creature there */
if (m) {
MapCoords new_coords = coords;
new_coords.move(dx, dy, g_context->_location->_map);
const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITHOUT_OBJECTS);
if ((m->sails() && tile->isSailable()) ||
(m->swims() && tile->isSwimable()) ||
(m->walks() && tile->isCreatureWalkable()) ||
(m->flies() && tile->isFlyable()))
ok = true;
else
tries++;
} else {
ok = true;
}
}
if (ok)
coords.move(dx, dy, g_context->_location->_map);
}
/* can't spawn creatures on top of the player */
if (coords == g_context->_location->_coords)
return false;
/* figure out what creature to spawn */
if (m)
creature = m;
else if (g_context->_location->_context & CTX_DUNGEON)
creature = creatureMgr->randomForDungeon(g_context->_location->_coords.z);
else
creature = creatureMgr->randomForTile(g_context->_location->_map->tileTypeAt(coords, WITHOUT_OBJECTS));
if (creature)
g_context->_location->_map->addCreature(creature, coords);
return true;
}
void gameDestroyAllCreatures(void) {
int i;
gameSpellEffect('t', -1, SOUND_MAGIC); /* same effect as tremor */
if (g_context->_location->_context & CTX_COMBAT) {
// Destroy all creatures in combat
for (i = 0; i < AREA_CREATURES; i++) {
CombatMap *cm = getCombatMap();
CreatureVector creatures = cm->getCreatures();
for (const auto *obj : creatures) {
if (obj->getId() != LORDBRITISH_ID)
cm->removeObject(obj);
}
}
} else {
// Destroy all creatures on the map
ObjectDeque::iterator current;
Map *map = g_context->_location->_map;
for (current = map->_objects.begin(); current != map->_objects.end();) {
Creature *m = dynamic_cast<Creature *>(*current);
if (m) {
// The skull does not destroy Lord British
if (m->getId() != LORDBRITISH_ID)
current = map->removeObject(current);
else
current++;
} else {
current++;
}
}
}
// Alert the guards! Really, the only one left should be LB himself :)
g_context->_location->_map->alertGuards();
}
// Colors assigned to reagents based on my best reading of them
// from the book of wisdom. Maybe we could use BOLD to distinguish
// the two grey and the two red reagents.
const int colors[] = {
FG_YELLOW, FG_GREY, FG_BLUE, FG_WHITE, FG_RED, FG_GREY, FG_GREEN, FG_RED
};
void showMixturesSuper(int page = 0) {
g_screen->screenTextColor(FG_WHITE);
for (int i = 0; i < 13; i++) {
const Spell *s = g_spells->getSpell(i + 13 * page);
int line = i + 8;
g_screen->screenTextAt(2, line, "%s", s->_name);
g_screen->screenTextAt(6, line, "%s", Common::String::format("%3d", g_ultima->_saveGame->_mixtures[i + 13 * page]).c_str());
g_screen->screenShowChar(32, 9, line);
int comp = s->_components;
for (int j = 0; j < 8; j++) {
g_screen->screenTextColor(colors[j]);
g_screen->screenShowChar(comp & (1 << j) ? CHARSET_BULLET : ' ', 10 + j, line);
}
g_screen->screenTextColor(FG_WHITE);
g_screen->screenTextAt(19, line, "%s", Common::String::format("%2d", s->_mp).c_str());
}
}
void mixReagentsSuper() {
g_screen->screenMessage("Mix reagents\n");
static int page = 0;
struct ReagentShop {
const char *name;
int price[6];
};
ReagentShop shops[] = {
{ "BuccDen", {6, 7, 9, 9, 9, 1} },
{ "Moonglo", {2, 5, 6, 3, 6, 9} },
{ "Paws", {3, 4, 2, 8, 6, 7} },
{ "SkaraBr", {2, 4, 9, 6, 4, 8} },
};
const int shopcount = sizeof(shops) / sizeof(shops[0]);
int oldlocation = g_context->_location->_viewMode;
g_context->_location->_viewMode = VIEW_MIXTURES;
g_screen->screenUpdate(&g_game->_mapArea, true, true);
g_screen->screenTextAt(16, 2, "%s", "<-Shops");
g_context->_stats->setView(StatsView(STATS_REAGENTS));
g_screen->screenTextColor(FG_PURPLE);
g_screen->screenTextAt(2, 7, "%s", "SPELL # Reagents MP");
for (int i = 0; i < shopcount; i++) {
int line = i + 1;
ReagentShop *s = &shops[i];
g_screen->screenTextColor(FG_WHITE);
g_screen->screenTextAt(2, line, "%s", s->name);
for (int j = 0; j < 6; j++) {
g_screen->screenTextColor(colors[j]);
g_screen->screenShowChar('0' + s->price[j], 10 + j, line);
}
}
for (int i = 0; i < 8; i++) {
g_screen->screenTextColor(colors[i]);
g_screen->screenShowChar('A' + i, 10 + i, 6);
}
bool done = false;
while (!done) {
showMixturesSuper(page);
g_screen->screenMessage("For Spell: ");
int spell = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
if (spell < 'a' || spell > 'z') {
g_screen->screenMessage("\nDone.\n");
done = true;
} else {
spell -= 'a';
const Spell *s = g_spells->getSpell(spell);
g_screen->screenMessage("%s\n", s->_name);
page = (spell >= 13);
showMixturesSuper(page);
// how many can we mix?
int mixQty = 99 - g_ultima->_saveGame->_mixtures[spell];
int ingQty = 99;
int comp = s->_components;
for (int i = 0; i < 8; i++) {
if (comp & 1 << i) {
int reagentQty = g_ultima->_saveGame->_reagents[i];
if (reagentQty < ingQty)
ingQty = reagentQty;
}
}
g_screen->screenMessage("You can make %d.\n", (mixQty > ingQty) ? ingQty : mixQty);
g_screen->screenMessage("How many? ");
int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
if (howmany == 0) {
g_screen->screenMessage("\nNone mixed!\n");
} else if (howmany > mixQty) {
g_screen->screenMessage("\n%cYou cannot mix that much more of that spell!%c\n", FG_GREY, FG_WHITE);
} else if (howmany > ingQty) {
g_screen->screenMessage("\n%cYou don't have enough reagents to mix %d spells!%c\n", FG_GREY, howmany, FG_WHITE);
} else {
g_ultima->_saveGame->_mixtures[spell] += howmany;
for (int i = 0; i < 8; i++) {
if (comp & 1 << i) {
g_ultima->_saveGame->_reagents[i] -= howmany;
}
}
g_screen->screenMessage("\nSuccess!\n\n");
}
}
g_context->_stats->setView(StatsView(STATS_REAGENTS));
}
g_context->_location->_viewMode = oldlocation;
return;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,153 @@
/* 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_GAME_GAME_H
#define ULTIMA4_GAME_GAME_H
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/controllers/game_controller.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/views/tileview.h"
#include "ultima/ultima4/sound/sound.h"
namespace Ultima {
namespace Ultima4 {
class Map;
struct Portal;
class Creature;
class Location;
class MoveEvent;
class Party;
class PartyEvent;
class PartyMember;
typedef enum {
VIEW_NORMAL,
VIEW_GEM,
VIEW_RUNE,
VIEW_DUNGEON,
VIEW_DEAD,
VIEW_CODEX,
VIEW_MIXTURES
} ViewMode;
/* map and screen functions */
/**
* Sets the view mode.
*/
void gameSetViewMode(ViewMode newMode);
void gameUpdateScreen();
/* spell functions */
void gameSpellEffect(int spell, int player, Sound sound);
/* action functions */
/**
* Peers at a city from A-P (Lycaeum telescope) and functions like a gem
*/
bool gamePeerCity(int city, void *data);
/**
* Peers at a gem
*/
void peer(bool useGem = true);
bool fireAt(const Coords &coords, bool originAvatar);
Direction gameGetDirection();
uint32 gameTimeSinceLastCommand();
/* checking functions */
/**
* Checks the hull integrity of the ship and handles
* the ship sinking, if necessary
*/
void gameCheckHullIntegrity();
/* creature functions */
/**
* Performs a ranged attack for the creature at x,y on the world map
*/
bool creatureRangeAttack(const Coords &coords, Creature *m);
void gameCreatureCleanup();
/**
* Spawns a creature (m) just offscreen of the avatar.
* If (m==nullptr) then it finds its own creature to spawn and spawns it.
*/
bool gameSpawnCreature(const class Creature *m);
/**
* Fixes objects initially loaded by saveGameMonstersRead,
* and alters movement behavior accordingly to match the creature
*/
void gameFixupObjects(Map *map);
/**
* Destroys all creatures on the current map.
*/
void gameDestroyAllCreatures();
/**
* Handles what happens when a creature attacks you
*/
void gameCreatureAttack(Creature *obj);
/* etc */
Common::String gameGetInput(int maxlen = 32);
int gameGetPlayer(bool canBeDisabled, bool canBeActivePlayer);
void gameGetPlayerForCommand(bool (*commandFn)(int player), bool canBeDisabled, bool canBeActivePlayer);
/**
* Deals an amount of damage between 'minDamage' and 'maxDamage'
* to each party member, with a 50% chance for each member to
* avoid the damage. If (minDamage == -1) or (minDamage >= maxDamage),
* deals 'maxDamage' damage to each member.
*/
void gameDamageParty(int minDamage, int maxDamage);
/**
* Deals an amount of damage between 'minDamage' and 'maxDamage'
* to the ship. If (minDamage == -1) or (minDamage >= maxDamage),
* deals 'maxDamage' damage to the ship.
*/
void gameDamageShip(int minDamage, int maxDamage);
/**
* Sets (or unsets) the active player
*/
void gameSetActivePlayer(int player);
/**
* Gets the path of coordinates for an action. Each tile in the
* direction specified by dirmask, between the minimum and maximum
* distances given, is included in the path, until blockedPredicate
* fails. If a tile is blocked, that tile is included in the path
* only if includeBlocked is true.
*/
Std::vector<Coords> gameGetDirectionalActionPath(int dirmask, int validDirections, const Coords &origin, int minDistance, int maxDistance, bool (*blockedPredicate)(const Tile *tile), bool includeBlocked);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,761 @@
/* 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/game/item.h"
#include "ultima/ultima4/game/codex.h"
#include "ultima/ultima4/game/game.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/game/portal.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/weapon.h"
#include "ultima/ultima4/controllers/alpha_action_controller.h"
#include "ultima/ultima4/controllers/combat_controller.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/map/annotation.h"
#include "ultima/ultima4/map/dungeon.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/map/mapmgr.h"
#include "ultima/ultima4/map/tileset.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
Items *g_items;
const ItemLocation Items::ITEMS[N_ITEMS] = {
{
"Mandrake Root", nullptr, "mandrake1",
&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
},
{
"Mandrake Root", nullptr, "mandrake2",
&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
},
{
"Nightshade", nullptr, "nightshade1",
&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
},
{
"Nightshade", nullptr, "nightshade2",
&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
},
{
"the Bell of Courage", "bell", "bell",
&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BELL, 0
},
{
"the Book of Truth", "book", "book",
&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BOOK, 0
},
{
"the Candle of Love", "candle", "candle",
&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_CANDLE, 0
},
{
"A Silver Horn", "horn", "horn",
&Items::isItemInInventory, &Items::putItemInInventory, &Items::useHorn, ITEM_HORN, 0
},
{
"the Wheel from the H.M.S. Cape", "wheel", "wheel",
&Items::isItemInInventory, &Items::putItemInInventory, &Items::useWheel, ITEM_WHEEL, 0
},
{
"the Skull of Modain the Wizard", "skull", "skull",
&Items::isSkullInInventory, &Items::putItemInInventory, &Items::useSkull, ITEM_SKULL, SC_NEWMOONS
},
{
"the Red Stone", "red", "redstone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_RED, 0
},
{
"the Orange Stone", "orange", "orangestone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_ORANGE, 0
},
{
"the Yellow Stone", "yellow", "yellowstone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_YELLOW, 0
},
{
"the Green Stone", "green", "greenstone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_GREEN, 0
},
{
"the Blue Stone", "blue", "bluestone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLUE, 0
},
{
"the Purple Stone", "purple", "purplestone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_PURPLE, 0
},
{
"the Black Stone", "black", "blackstone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLACK, SC_NEWMOONS
},
{
"the White Stone", "white", "whitestone",
&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_WHITE, 0
},
/* handlers for using generic objects */
{ nullptr, "stone", nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
{ nullptr, "stones", nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
{ nullptr, "key", nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
{ nullptr, "keys", nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
/* Lycaeum telescope */
{ nullptr, nullptr, "telescope", nullptr, &Items::useTelescope, nullptr, 0, 0 },
{
"Mystic Armor", nullptr, "mysticarmor",
&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, ARMR_MYSTICROBES, SC_FULLAVATAR
},
{
"Mystic Swords", nullptr, "mysticswords",
&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, WEAP_MYSTICSWORD, SC_FULLAVATAR
},
{
"the sulfury remains of an ancient Sosarian Laser Gun. It turns to ash in your fingers", nullptr, "lasergun", // lol, where'd that come from?
//Looks like someone was experimenting with "maps.xml". It effectively increments sulfur ash by one due to '16' being an invalid weapon index.
&Items::isWeaponInInventory, &Items::putWeaponInInventory, nullptr, 16, 0
},
{
"the rune of Honesty", nullptr, "honestyrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONESTY, 0
},
{
"the rune of Compassion", nullptr, "compassionrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_COMPASSION, 0
},
{
"the rune of Valor", nullptr, "valorrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_VALOR, 0
},
{
"the rune of Justice", nullptr, "justicerune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_JUSTICE, 0
},
{
"the rune of Sacrifice", nullptr, "sacrificerune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SACRIFICE, 0
},
{
"the rune of Honor", nullptr, "honorrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONOR, 0
},
{
"the rune of Spirituality", nullptr, "spiritualityrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SPIRITUALITY, 0
},
{
"the rune of Humility", nullptr, "humilityrune",
&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HUMILITY, 0
}
};
Items::Items() : destroyAllCreaturesCallback(nullptr),
needStoneNames(0), stoneMask(0) {
g_items = this;
}
Items::~Items() {
g_items = nullptr;
}
void Items::setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback) {
destroyAllCreaturesCallback = callback;
}
bool Items::isRuneInInventory(int virt) {
return g_ultima->_saveGame->_runes & virt;
}
void Items::putRuneInInventory(int virt) {
g_context->_party->member(0)->awardXp(100);
g_context->_party->adjustKarma(KA_FOUND_ITEM);
g_ultima->_saveGame->_runes |= virt;
#ifdef IOS_ULTIMA4
Common::String virtueName;
switch (virt) {
default:
case RUNE_HONESTY:
virtueName = "Honesty";
break;
case RUNE_HONOR:
virtueName = "Honor";
break;
case RUNE_HUMILITY:
virtueName = "Humility";
break;
case RUNE_JUSTICE:
virtueName = "Justice";
break;
case RUNE_SACRIFICE:
virtueName = "Sacrifice";
break;
case RUNE_SPIRITUALITY:
virtueName = "Spirituality";
break;
case RUNE_VALOR:
virtueName = "Valor";
break;
case RUNE_COMPASSION:
virtueName = "Compassion";
break;
}
U4IOS::testFlightPassCheckPoint("Player got stone: " + virtueName);
#endif
g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
}
bool Items::isStoneInInventory(int virt) {
/* generic test: does the party have any stones yet? */
if (virt == -1)
return (g_ultima->_saveGame->_stones > 0);
/* specific test: does the party have a specific stone? */
else
return g_ultima->_saveGame->_stones & virt;
}
void Items::putStoneInInventory(int virt) {
g_context->_party->member(0)->awardXp(200);
g_context->_party->adjustKarma(KA_FOUND_ITEM);
g_ultima->_saveGame->_stones |= virt;
#ifdef IOS_ULTIMA4
Common::String stoneName;
switch (virt) {
default:
case STONE_BLACK:
stoneName = "Black";
break;
case STONE_BLUE:
stoneName = "Blue";
break;
case STONE_GREEN:
stoneName = "Green";
break;
case STONE_ORANGE:
stoneName = "Orange";
break;
case STONE_PURPLE:
stoneName = "Purple";
break;
case STONE_RED:
stoneName = "Red";
break;
case STONE_WHITE:
stoneName = "White";
break;
case STONE_YELLOW:
stoneName = "Yellow";
break;
}
U4IOS::testFlightPassCheckPoint("Player got rune: " + stoneName);
#endif
g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
}
bool Items::isItemInInventory(int item) {
return g_ultima->_saveGame->_items & item;
}
bool Items::isSkullInInventory(int unused) {
return (g_ultima->_saveGame->_items & (ITEM_SKULL | ITEM_SKULL_DESTROYED));
}
void Items::putItemInInventory(int item) {
g_context->_party->member(0)->awardXp(400);
g_context->_party->adjustKarma(KA_FOUND_ITEM);
g_ultima->_saveGame->_items |= item;
#ifdef IOS_ULTIMA4
Common::String itemName;
switch (item) {
default:
case ITEM_BELL:
itemName = "Bell";
break;
case ITEM_BOOK:
itemName = "Book";
break;
case ITEM_CANDLE:
itemName = "Candle";
break;
case ITEM_HORN:
itemName = "Horn";
break;
case ITEM_KEY_C:
itemName = "Key Courage";
break;
case ITEM_KEY_L:
itemName = "Key Love";
break;
case ITEM_KEY_T:
itemName = "Key Truth";
break;
case ITEM_SKULL:
itemName = "Skull";
break;
case ITEM_WHEEL:
itemName = "Wheel";
break;
}
U4IOS::testFlightPassCheckPoint("Player got rune: " + itemName);
#endif
g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
}
void Items::useBBC(int item) {
Coords abyssEntrance(0xe9, 0xe9);
/* on top of the Abyss entrance */
if (g_context->_location->_coords == abyssEntrance) {
/* must use bell first */
if (item == ITEM_BELL) {
#ifdef IOS_ULTIMA4
U4IOS::testFlightPassCheckPoint("The Bell rings on and on!");
#endif
g_screen->screenMessage("\nThe Bell rings on and on!\n");
g_ultima->_saveGame->_items |= ITEM_BELL_USED;
}
/* then the book */
else if ((item == ITEM_BOOK) && (g_ultima->_saveGame->_items & ITEM_BELL_USED)) {
#ifdef IOS_ULTIMA4
U4IOS::testFlightPassCheckPoint("The words resonate with the ringing!");
#endif
g_screen->screenMessage("\nThe words resonate with the ringing!\n");
g_ultima->_saveGame->_items |= ITEM_BOOK_USED;
}
/* then the candle */
else if ((item == ITEM_CANDLE) && (g_ultima->_saveGame->_items & ITEM_BOOK_USED)) {
g_screen->screenMessage("\nAs you light the Candle the Earth Trembles!\n");
#ifdef IOS_ULTIMA4
U4IOS::testFlightPassCheckPoint("As you light the Candle the Earth Trembles!");
#endif
g_ultima->_saveGame->_items |= ITEM_CANDLE_USED;
} else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
}
/* somewhere else */
else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
}
void Items::useHorn(int item) {
g_screen->screenMessage("\nThe Horn sounds an eerie tone!\n");
g_context->_aura->set(Aura::HORN, 10);
}
void Items::useWheel(int item) {
if ((g_context->_transportContext == TRANSPORT_SHIP) && (g_ultima->_saveGame->_shipHull == 50)) {
g_screen->screenMessage("\nOnce mounted, the Wheel glows with a blue light!\n");
g_context->_party->setShipHull(99);
} else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
}
void Items::useSkull(int item) {
/* FIXME: check to see if the abyss must be opened first
for the skull to be *able* to be destroyed */
/* We do the check here instead of in the table, because we need to distinguish between a
never-found skull and a destroyed skull. */
if (g_ultima->_saveGame->_items & ITEM_SKULL_DESTROYED) {
g_screen->screenMessage("\nNone owned!\n");
return;
}
/* destroy the skull! pat yourself on the back */
if (g_context->_location->_coords.x == 0xe9 && g_context->_location->_coords.y == 0xe9) {
g_screen->screenMessage("\n\nYou cast the Skull of Mondain into the Abyss!\n");
#ifdef IOS_ULTIMA4
U4IOS::testFlightPassCheckPoint("You cast the Skull of Mondain into the Abyss!");
#endif
g_ultima->_saveGame->_items = (g_ultima->_saveGame->_items & ~ITEM_SKULL) | ITEM_SKULL_DESTROYED;
g_context->_party->adjustKarma(KA_DESTROYED_SKULL);
}
/* use the skull... bad, very bad */
else {
g_screen->screenMessage("\n\nYou hold the evil Skull of Mondain the Wizard aloft...\n");
#ifdef IOS_ULTIMA4
U4IOS::testFlightPassCheckPoint("You hold the evil Skull of Mondain the Wizard aloft...");
#endif
/* destroy all creatures */
(*destroyAllCreaturesCallback)();
/* we don't lose the skull until we toss it into the abyss */
//c->saveGame->_items = (c->saveGame->_items & ~ITEM_SKULL);
g_context->_party->adjustKarma(KA_USED_SKULL);
}
}
void Items::useStone(int item) {
MapCoords coords;
byte stone = static_cast<byte>(item);
static const byte truth = STONE_WHITE | STONE_PURPLE | STONE_GREEN | STONE_BLUE;
static const byte love = STONE_WHITE | STONE_YELLOW | STONE_GREEN | STONE_ORANGE;
static const byte courage = STONE_WHITE | STONE_RED | STONE_PURPLE | STONE_ORANGE;
static const byte *attr = nullptr;
g_context->_location->getCurrentPosition(&coords);
/**
* Named a specific stone (after using "stone" or "stones")
*/
if (item != -1) {
CombatMap *cm = getCombatMap();
if (needStoneNames) {
/* named a stone while in a dungeon altar room */
if (g_context->_location->_context & CTX_ALTAR_ROOM) {
needStoneNames--;
switch (cm->getAltarRoom()) {
case VIRT_TRUTH:
attr = &truth;
break;
case VIRT_LOVE:
attr = &love;
break;
case VIRT_COURAGE:
attr = &courage;
break;
default:
break;
}
/* make sure we're in an altar room */
if (attr) {
/* we need to use the stone, and we haven't used it yet */
if ((*attr & stone) && (stone & ~stoneMask))
stoneMask |= stone;
/* we already used that stone! */
else if (stone & stoneMask) {
g_screen->screenMessage("\nAlready used!\n");
needStoneNames = 0;
stoneMask = 0; /* reset the mask so you can try again */
return;
}
} else {
error("Not in an altar room!");
}
/* see if we have all the stones, if not, get more names! */
if (attr && needStoneNames) {
g_screen->screenMessage("\n%c:", 'E' - needStoneNames);
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which Color?");
#endif
itemHandleStones(gameGetInput());
}
/* all the stones have been entered, verify them! */
else {
unsigned short key = 0xFFFF;
switch (cm->getAltarRoom()) {
case VIRT_TRUTH:
key = ITEM_KEY_T;
break;
case VIRT_LOVE:
key = ITEM_KEY_L;
break;
case VIRT_COURAGE:
key = ITEM_KEY_C;
break;
default:
break;
}
/* in an altar room, named all of the stones, and don't have the key yet... */
if (attr && (stoneMask == *attr) && !(g_ultima->_saveGame->_items & key)) {
#ifdef IOS_ULTIMA4
Common::String keyName;
switch (key) {
case ITEM_KEY_C:
keyName = "Key Courage";
break;
case ITEM_KEY_L:
keyName = "Key Love";
break;
case ITEM_KEY_T:
keyName = "Key Truth";
break;
}
U4IOS::testFlightPassCheckPoint("Receive a key: " + keyName);
#endif
g_screen->screenMessage("\nThou doth find one third of the Three Part Key!\n");
g_ultima->_saveGame->_items |= key;
} else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
stoneMask = 0; /* reset the mask so you can try again */
}
} else {
/* Otherwise, we're asking for a stone while in the abyss on top of an altar */
/* see if they entered the correct stone */
if (stone == (1 << g_context->_location->_coords.z)) {
if (g_context->_location->_coords.z < 7) {
/* replace the altar with a down-ladder */
MapCoords pos;
g_screen->screenMessage("\n\nThe altar changes before thyne eyes!\n");
g_context->_location->getCurrentPosition(&pos);
g_context->_location->_map->_annotations->add(pos, g_context->_location->_map->_tileSet->getByName("down_ladder")->getId());
} else {
// Start chamber of the codex sequence...
g_codex->start();
}
} else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
}
} else {
g_screen->screenMessage("\nNot a Usable Item!\n");
stoneMask = 0; /* reset the mask so you can try again */
}
}
/**
* in the abyss, on an altar to place the stones
*/
else if ((g_context->_location->_map->_id == MAP_ABYSS) &&
(g_context->_location->_context & CTX_DUNGEON) &&
(static_cast<Dungeon *>(g_context->_location->_map)->currentToken() == DUNGEON_ALTAR)) {
int virtueMask = getBaseVirtues((Virtue)g_context->_location->_coords.z);
if (virtueMask > 0)
g_screen->screenMessage("\n\nAs thou doth approach, a voice rings out: What virtue dost stem from %s?\n\n", getBaseVirtueName(virtueMask));
else
g_screen->screenMessage("\n\nA voice rings out: What virtue exists independently of Truth, Love, and Courage?\n\n");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which virtue?");
#endif
Common::String virtue = gameGetInput();
if (scumm_strnicmp(virtue.c_str(), getVirtueName((Virtue)g_context->_location->_coords.z), 6) == 0) {
/* now ask for stone */
g_screen->screenMessage("\n\nThe Voice says: Use thy Stone.\n\nColor:\n");
needStoneNames = 1;
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which color?");
#endif
itemHandleStones(gameGetInput());
} else {
g_screen->screenMessage("\nHmm...No effect!\n");
}
}
/**
* in a dungeon altar room, on the altar
*/
else if ((g_context->_location->_context & CTX_ALTAR_ROOM) &&
coords.x == 5 && coords.y == 5) {
needStoneNames = 4;
g_screen->screenMessage("\n\nThere are holes for 4 stones.\nWhat colors:\nA:");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper::setIntroString("Which color?");
#endif
itemHandleStones(gameGetInput());
} else {
g_screen->screenMessage("\nNo place to Use them!\n");
// This used to say "\nNo place to Use them!\nHmm...No effect!\n"
// That doesn't match U4DOS; does it match another?
}
}
void Items::useKey(int item) {
g_screen->screenMessage("\nNo place to Use them!\n");
}
bool Items::isMysticInInventory(int mystic) {
/* FIXME: you could feasibly get more mystic weapons and armor if you
have 8 party members and equip them all with everything,
then search for Mystic Weapons/Armor again
or, you could just sell them all and search again. What an easy
way to make some cash!
This would be a good candidate for an xu4 "extended" savegame
format.
*/
if (mystic == WEAP_MYSTICSWORD)
return g_ultima->_saveGame->_weapons[WEAP_MYSTICSWORD] > 0;
else if (mystic == ARMR_MYSTICROBES)
return g_ultima->_saveGame->_armor[ARMR_MYSTICROBES] > 0;
else
error("Invalid mystic item was tested in isMysticInInventory()");
return false;
}
void Items::putMysticInInventory(int mystic) {
g_context->_party->member(0)->awardXp(400);
g_context->_party->adjustKarma(KA_FOUND_ITEM);
if (mystic == WEAP_MYSTICSWORD)
g_ultima->_saveGame->_weapons[WEAP_MYSTICSWORD] += 8;
else if (mystic == ARMR_MYSTICROBES)
g_ultima->_saveGame->_armor[ARMR_MYSTICROBES] += 8;
else
error("Invalid mystic item was added in putMysticInInventory()");
g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
}
bool Items::isWeaponInInventory(int weapon) {
if (g_ultima->_saveGame->_weapons[weapon])
return true;
else {
for (int i = 0; i < g_context->_party->size(); i++) {
if (g_context->_party->member(i)->getWeapon()->getType() == weapon)
return true;
}
}
return false;
}
void Items::putWeaponInInventory(int weapon) {
g_ultima->_saveGame->_weapons[weapon]++;
}
void Items::useTelescope(int notused) {
g_screen->screenMessage("You see a knob\non the telescope\nmarked A-P\nYou Select:");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper telescopeHelper;
telescopeHelper.updateChoices("abcdefghijklmnop ");
#endif
int choice = AlphaActionController::get('p', "You Select:");
if (choice == -1)
return;
gamePeerCity(choice, nullptr);
}
bool Items::isReagentInInventory(int reag) {
return false;
}
void Items::putReagentInInventory(int reag) {
g_context->_party->adjustKarma(KA_FOUND_ITEM);
g_ultima->_saveGame->_reagents[reag] += xu4_random(8) + 2;
g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
if (g_ultima->_saveGame->_reagents[reag] > 99) {
g_ultima->_saveGame->_reagents[reag] = 99;
g_screen->screenMessage("Dropped some!\n");
}
}
bool Items::itemConditionsMet(byte conditions) {
if ((conditions & SC_NEWMOONS) &&
!(g_ultima->_saveGame->_trammelPhase == 0 && g_ultima->_saveGame->_feluccaPhase == 0))
return false;
if (conditions & SC_FULLAVATAR) {
for (int i = 0; i < VIRT_MAX; i++) {
if (g_ultima->_saveGame->_karma[i] != 0)
return false;
}
}
if ((conditions & SC_REAGENTDELAY) &&
(g_ultima->_saveGame->_moves & 0xF0) == g_ultima->_saveGame->_lastReagent)
return false;
return true;
}
const ItemLocation *Items::itemAtLocation(const Map *map, const Coords &coords) {
for (uint i = 0; i < N_ITEMS; i++) {
if (!ITEMS[i]._locationLabel)
continue;
if (map->getLabel(ITEMS[i]._locationLabel) == coords &&
itemConditionsMet(ITEMS[i]._conditions))
return &(ITEMS[i]);
}
return nullptr;
}
void Items::itemUse(const Common::String &shortName) {
const ItemLocation *item = nullptr;
for (uint i = 0; i < N_ITEMS; i++) {
if (ITEMS[i]._shortName &&
scumm_stricmp(ITEMS[i]._shortName, shortName.c_str()) == 0) {
item = &ITEMS[i];
/* item name found, see if we have that item in our inventory */
if (!ITEMS[i]._isItemInInventory || (this->*(ITEMS[i]._isItemInInventory))(ITEMS[i]._data)) {
/* use the item, if we can! */
if (!item || !item->_useItem)
g_screen->screenMessage("\nNot a Usable item!\n");
else
(this->*(item->_useItem))(ITEMS[i]._data);
} else
g_screen->screenMessage("\nNone owned!\n");
/* we found the item, no need to keep searching */
break;
}
}
/* item was not found */
if (!item)
g_screen->screenMessage("\nNot a Usable item!\n");
}
void Items::itemHandleStones(const Common::String &color) {
bool found = false;
for (int i = 0; i < 8; i++) {
if (scumm_stricmp(color.c_str(), getStoneName((Virtue)i)) == 0 &&
isStoneInInventory(1 << i)) {
found = true;
itemUse(color.c_str());
}
}
if (!found) {
g_screen->screenMessage("\nNone owned!\n");
stoneMask = 0; /* make sure stone mask is reset */
}
}
bool Items::isAbyssOpened(const Portal *p) {
/* make sure the bell, book and candle have all been used */
int items = g_ultima->_saveGame->_items;
int isopened = (items & ITEM_BELL_USED) && (items & ITEM_BOOK_USED) && (items & ITEM_CANDLE_USED);
if (!isopened)
g_screen->screenMessage("Enter Can't!\n");
return isopened;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,150 @@
/* 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_GAME_ITEM_H
#define ULTIMA4_GAME_ITEM_H
#include "ultima/ultima4/core/types.h"
#include "common/str.h"
namespace Ultima {
namespace Ultima4 {
class Coords;
class Map;
struct Portal;
enum SearchCondition {
SC_NONE = 0x00,
SC_NEWMOONS = 0x01,
SC_FULLAVATAR = 0x02,
SC_REAGENTDELAY = 0x04
};
class Items;
typedef bool (Items::*IsInInventoryProc)(int item);
typedef void (Items::*InventoryActionProc)(int item);
struct
#ifndef NO_CXX11_ALIGNAS
alignas(8)
#endif
ItemLocation {
const char *_name;
const char *_shortName;
const char *_locationLabel;
IsInInventoryProc _isItemInInventory;
InventoryActionProc _putItemInInventory;
InventoryActionProc _useItem;
int _data;
byte _conditions;
};
typedef void (*DestroyAllCreaturesCallback)();
#define N_ITEMS 34
class Items {
private:
static const ItemLocation ITEMS[N_ITEMS];
DestroyAllCreaturesCallback destroyAllCreaturesCallback;
int needStoneNames;
byte stoneMask;
private:
bool isRuneInInventory(int virt);
void putRuneInInventory(int virt);
bool isStoneInInventory(int virt);
void putStoneInInventory(int virt);
bool isItemInInventory(int item);
bool isSkullInInventory(int item);
void putItemInInventory(int item);
/**
* Use bell, book, or candle on the entrance to the Abyss
*/
void useBBC(int item);
/**
* Uses the silver horn
*/
void useHorn(int item);
/**
* Uses the wheel (if on board a ship)
*/
void useWheel(int item);
/**
* Uses or destroys the skull of Mondain
*/
void useSkull(int item);
/**
* Handles using the virtue stones in dungeon altar rooms and on dungeon altars
*/
void useStone(int item);
void useKey(int item);
bool isMysticInInventory(int mystic);
void putMysticInInventory(int mystic);
bool isWeaponInInventory(int weapon);
void putWeaponInInventory(int weapon);
void useTelescope(int notused);
bool isReagentInInventory(int reag);
void putReagentInInventory(int reag);
/**
* Handles naming of stones when used
*/
void itemHandleStones(const Common::String &color);
/**
* Returns true if the specified conditions are met to be able to get the item
*/
bool itemConditionsMet(byte conditions);
public:
Items();
~Items();
void setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback);
/**
* Returns an item location record if a searchable object exists at
* the given location. nullptr is returned if nothing is there.
*/
const ItemLocation *itemAtLocation(const Map *map, const Coords &coords);
/**
* Uses the item indicated by 'shortname'
*/
void itemUse(const Common::String &shortName);
/**
* Checks to see if the abyss was opened
*/
static bool isAbyssOpened(const Portal *p);
};
extern Items *g_items;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,77 @@
/* 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/coords.h"
#include "ultima/ultima4/game/moongate.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
Moongates *g_moongates;
Moongates::Moongates() {
g_moongates = this;
}
Moongates::~Moongates() {
g_moongates = nullptr;
}
void Moongates::add(int phase, const Coords &coords) {
if (contains(phase))
error("Error: A moongate for phase %d already exists", phase);
(*this)[phase] = coords;
}
const Coords *Moongates::getGateCoordsForPhase(int phase) {
iterator moongate;
moongate = find(phase);
if (moongate != end())
return &moongate->_value;
return nullptr;
}
bool Moongates::findActiveGateAt(int trammel, int felucca, const Coords &src, Coords &dest) {
const Coords *moongate_coords;
moongate_coords = getGateCoordsForPhase(trammel);
if (moongate_coords && (src == *moongate_coords)) {
moongate_coords = getGateCoordsForPhase(felucca);
if (moongate_coords) {
dest = *moongate_coords;
return true;
}
}
return false;
}
bool Moongates::isEntryToShrineOfSpirituality(int trammel, int felucca) {
return (trammel == 4 && felucca == 4) ? true : false;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* 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_GAME_MOONGATE_H
#define ULTIMA4_GAME_MOONGATE_H
#include "ultima/ultima4/core/coords.h"
#include "common/hashmap.h"
namespace Ultima {
namespace Ultima4 {
class Moongates : public Common::HashMap<int, Coords> {
public:
/**
* Constructor
*/
Moongates();
/**
* Destructor
*/
~Moongates();
void add(int phase, const Coords &coords);
const Coords *getGateCoordsForPhase(int phase);
bool findActiveGateAt(int trammel, int felucca, const Coords &src, Coords &dest);
bool isEntryToShrineOfSpirituality(int trammel, int felucca);
};
extern Moongates *g_moongates;
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,178 @@
/* 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/game/names.h"
namespace Ultima {
namespace Ultima4 {
const char *getClassName(ClassType klass) {
switch (klass) {
case CLASS_MAGE:
return "Mage";
case CLASS_BARD:
return "Bard";
case CLASS_FIGHTER:
return "Fighter";
case CLASS_DRUID:
return "Druid";
case CLASS_TINKER:
return "Tinker";
case CLASS_PALADIN:
return "Paladin";
case CLASS_RANGER:
return "Ranger";
case CLASS_SHEPHERD:
return "Shepherd";
default:
return "???";
}
}
const char *getReagentName(Reagent reagent) {
static const char *const reagentNames[] = {
"Sulfur Ash", "Ginseng", "Garlic",
"Spider Silk", "Blood Moss", "Black Pearl",
"Nightshade", "Mandrake"
};
if (reagent < REAG_MAX)
return reagentNames[reagent - REAG_ASH];
else
return "???";
}
const char *getVirtueName(Virtue virtue) {
static const char *const virtueNames[] = {
"Honesty", "Compassion", "Valor",
"Justice", "Sacrifice", "Honor",
"Spirituality", "Humility"
};
if (virtue < 8)
return virtueNames[virtue - VIRT_HONESTY];
else
return "???";
}
const char *getBaseVirtueName(int virtueMask) {
if (virtueMask == VIRT_TRUTH) return "Truth";
else if (virtueMask == VIRT_LOVE) return "Love";
else if (virtueMask == VIRT_COURAGE) return "Courage";
else if (virtueMask == (VIRT_TRUTH | VIRT_LOVE)) return "Truth and Love";
else if (virtueMask == (VIRT_LOVE | VIRT_COURAGE)) return "Love and Courage";
else if (virtueMask == (VIRT_COURAGE | VIRT_TRUTH)) return "Courage and Truth";
else if (virtueMask == (VIRT_TRUTH | VIRT_LOVE | VIRT_COURAGE)) return "Truth, Love and Courage";
else return "???";
}
int getBaseVirtues(Virtue virtue) {
switch (virtue) {
case VIRT_HONESTY:
return VIRT_TRUTH;
case VIRT_COMPASSION:
return VIRT_LOVE;
case VIRT_VALOR:
return VIRT_COURAGE;
case VIRT_JUSTICE:
return VIRT_TRUTH | VIRT_LOVE;
case VIRT_SACRIFICE:
return VIRT_LOVE | VIRT_COURAGE;
case VIRT_HONOR:
return VIRT_COURAGE | VIRT_TRUTH;
case VIRT_SPIRITUALITY:
return VIRT_TRUTH | VIRT_LOVE | VIRT_COURAGE;
case VIRT_HUMILITY:
return 0;
default:
return 0;
}
}
const char *getVirtueAdjective(Virtue virtue) {
static const char *const virtueAdjectives[] = {
"honest",
"compassionate",
"valiant",
"just",
"sacrificial",
"honorable",
"spiritual",
"humble"
};
if (virtue < 8)
return virtueAdjectives[virtue - VIRT_HONESTY];
else
return "???";
}
const char *getStoneName(Virtue virtue) {
static const char *const virtueNames[] = {
"Blue", "Yellow", "Red",
"Green", "Orange", "Purple",
"White", "Black"
};
if (virtue < VIRT_MAX)
return virtueNames[virtue - VIRT_HONESTY];
else
return "???";
}
const char *getItemName(Item item) {
switch (item) {
case ITEM_SKULL:
return "Skull";
case ITEM_CANDLE:
return "Candle";
case ITEM_BOOK:
return "Book";
case ITEM_BELL:
return "Bell";
case ITEM_KEY_C:
return "Courage";
case ITEM_KEY_L:
return "Love";
case ITEM_KEY_T:
return "Truth";
case ITEM_HORN:
return "Horn";
case ITEM_WHEEL:
return "Wheel";
default:
return "???";
}
}
const char *getDirectionName(Direction dir) {
static const char *const directionNames[] = {
"West", "North", "East", "South"
};
if (dir >= DIR_WEST && dir <= DIR_SOUTH)
return directionNames[dir - DIR_WEST];
else
return "???";
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* 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_GAME_NAMES_H
#define ULTIMA4_GAME_NAMES_H
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/direction.h"
namespace Ultima {
namespace Ultima4 {
/*
* These routines convert the various enumerations for classes, reagents,
* etc. into the textual representations used in the game.
*/
const char *getClassName(ClassType klass);
const char *getReagentName(Reagent reagent);
const char *getVirtueName(Virtue virtue);
const char *getBaseVirtueName(int virtueMask);
int getBaseVirtues(Virtue virtue);
const char *getVirtueAdjective(Virtue virtue);
const char *getStoneName(Virtue virtue);
const char *getItemName(Item item);
const char *getDirectionName(Direction dir);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,66 @@
/* 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/game/object.h"
#include "ultima/ultima4/map/map.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/game/game.h"
#include "common/algorithm.h"
namespace Ultima {
namespace Ultima4 {
bool Object::setDirection(Direction d) {
return _tile.setDirection(d);
}
void Object::setMap(class Map *m) {
if (Common::find(_maps.begin(), _maps.end(), m) == _maps.end())
_maps.push_back(m);
}
Map *Object::getMap() {
if (_maps.empty())
return nullptr;
return _maps.back();
}
void Object::remove() {
uint size = _maps.size();
uint i = 0;
for (auto *map : _maps) {
if (i == size - 1)
map->removeObject(this);
else map->removeObject(this, false);
i++;
}
}
void Object::animateMovement() {
//TODO abstract movement - also make screen.h and game.h not required
g_screen->screenTileUpdate(&g_game->_mapArea, _prevCoords, false);
if (g_screen->screenTileUpdate(&g_game->_mapArea, _coords, false))
g_screen->screenWait(1);
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,147 @@
/* 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_GAME_OBJECT_H
#define ULTIMA4_GAME_OBJECT_H
#include "ultima/ultima4/core/coords.h"
#include "ultima/ultima4/map/map_tile.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
class Object;
typedef Common::List<Object *> ObjectDeque;
enum ObjectMovementBehavior {
MOVEMENT_FIXED,
MOVEMENT_WANDER,
MOVEMENT_FOLLOW_AVATAR,
MOVEMENT_ATTACK_AVATAR
};
class Object {
public:
enum Type {
UNKNOWN,
CREATURE,
PERSON
};
Object(Type type = UNKNOWN) :
_tile(0),
_prevTile(0),
_movementBehavior(MOVEMENT_FIXED),
_objType(type),
_focused(false),
_visible(true),
_animated(true) {
}
virtual ~Object() {}
// Methods
MapTile &getTile() {
return _tile;
}
MapTile &getPrevTile() {
return _prevTile;
}
const Coords &getCoords() const {
return _coords;
}
const Coords &getPrevCoords() const {
return _prevCoords;
}
ObjectMovementBehavior getMovementBehavior() const {
return _movementBehavior;
}
Type getType() const {
return _objType;
}
bool hasFocus() const {
return _focused;
}
bool isVisible() const {
return _visible;
}
bool isAnimated() const {
return _animated;
}
void setTile(MapTile t) {
_tile = t;
}
void setTile(Tile *t) {
_tile = t->getId();
}
void setPrevTile(MapTile t) {
_prevTile = t;
}
void setCoords(Coords c) {
_prevCoords = _coords;
_coords = c;
}
void setPrevCoords(Coords c) {
_prevCoords = c;
}
void setMovementBehavior(ObjectMovementBehavior b) {
_movementBehavior = b;
}
void setType(Type t) {
_objType = t;
}
void setFocus(bool f = true) {
_focused = f;
}
void setVisible(bool v = true) {
_visible = v;
}
void setAnimated(bool a = true) {
_animated = a;
}
void setMap(class Map *m);
Map *getMap();
void remove(); /**< Removes itself from any maps that it is a part of */
bool setDirection(Direction d);
void animateMovement();
// Properties
protected:
MapTile _tile, _prevTile;
Coords _coords, _prevCoords;
ObjectMovementBehavior _movementBehavior;
Type _objType;
Common::List<class Map *> _maps; /**< A list of maps this object is a part of */
bool _focused;
bool _visible;
bool _animated;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,613 @@
/* 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/game/person.h"
#include "ultima/ultima4/game/names.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/views/stats.h"
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/game/script.h"
#include "ultima/ultima4/controllers/read_choice_controller.h"
#include "ultima/ultima4/controllers/read_int_controller.h"
#include "ultima/ultima4/controllers/read_player_controller.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/events/event_handler.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/map/city.h"
#include "ultima/ultima4/map/location.h"
#include "ultima/ultima4/sound/music.h"
#include "ultima/ultima4/ultima4.h"
namespace Ultima {
namespace Ultima4 {
int chars_needed(const char *s, int columnmax, int linesdesired, int *real_lines);
/**
* Returns true of the object that 'punknown' points
* to is a person object
*/
bool isPerson(Object *punknown) {
Person *p;
if ((p = dynamic_cast<Person *>(punknown)) != nullptr)
return true;
else
return false;
}
/**
* Splits a piece of response text into screen-sized chunks.
*/
Common::List<Common::String> replySplit(const Common::String &text) {
Common::String str = text;
int pos, real_lines;
Common::List<Common::String> reply;
/* skip over any initial newlines */
if ((pos = str.find("\n\n")) == 0)
str = str.substr(pos + 1);
uint num_chars = chars_needed(str.c_str(), TEXT_AREA_W, TEXT_AREA_H, &real_lines);
/* we only have one chunk, no need to split it up */
uint len = str.size();
if (num_chars == len)
reply.push_back(str);
else {
Common::String pre = str.substr(0, num_chars);
/* add the first chunk to the list */
reply.push_back(pre);
/* skip over any initial newlines */
if ((pos = str.find("\n\n")) == 0)
str = str.substr(pos + 1);
while (num_chars != str.size()) {
/* go to the rest of the text */
str = str.substr(num_chars);
/* skip over any initial newlines */
if ((pos = str.find("\n\n")) == 0)
str = str.substr(pos + 1);
/* find the next chunk and add it */
num_chars = chars_needed(str.c_str(), TEXT_AREA_W, TEXT_AREA_H, &real_lines);
pre = str.substr(0, num_chars);
reply.push_back(pre);
}
}
return reply;
}
Person::Person(MapTile tile) : Creature(tile), _start(0, 0) {
setType(Object::PERSON);
_dialogue = nullptr;
_npcType = NPC_EMPTY;
}
Person::Person(const Person *p) : Creature(p->_tile) {
*this = *p;
}
bool Person::canConverse() const {
return isVendor() || _dialogue != nullptr;
}
bool Person::isVendor() const {
return
_npcType >= NPC_VENDOR_WEAPONS &&
_npcType <= NPC_VENDOR_STABLE;
}
Common::String Person::getName() const {
if (_dialogue)
return _dialogue->getName();
else if (_npcType == NPC_EMPTY)
return Creature::getName();
else
return "(unnamed person)";
}
void Person::goToStartLocation() {
setCoords(_start);
}
void Person::setDialogue(Dialogue *d) {
_dialogue = d;
if (_tile.getTileType()->getName() == "beggar")
_npcType = NPC_TALKER_BEGGAR;
else if (_tile.getTileType()->getName() == "guard")
_npcType = NPC_TALKER_GUARD;
else
_npcType = NPC_TALKER;
}
void Person::setNpcType(PersonNpcType t) {
_npcType = t;
assertMsg(!isVendor() || _dialogue == nullptr, "vendor has dialogue");
}
Common::List<Common::String> Person::getConversationText(Conversation *cnv, const char *inquiry) {
Common::String text;
/*
* a convsation with a vendor
*/
if (isVendor()) {
static const Common::String ids[] = {
"Weapons", "Armor", "Food", "Tavern", "Reagents", "Healer", "Inn", "Guild", "Stable"
};
Script *script = cnv->_script;
/**
* We aren't currently running a script, load the appropriate one!
*/
if (cnv->_state == Conversation::INTRO) {
// unload the previous script if it wasn't already unloaded
if (script->getState() != Script::STATE_UNLOADED)
script->unload();
script->load("vendorScript.xml", ids[_npcType - NPC_VENDOR_WEAPONS], "vendor", g_context->_location->_map->getName());
script->run("intro");
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationChoiceHelper choiceDialog;
#endif
while (script->getState() != Script::STATE_DONE) {
// Gather input for the script
if (script->getState() == Script::STATE_INPUT) {
switch (script->getInputType()) {
case Script::INPUT_CHOICE: {
const Common::String &choices = script->getChoices();
// Get choice
#ifdef IOS_ULTIMA4
choiceDialog.updateChoices(choices, script->getTarget(), npcType);
#endif
char val = ReadChoiceController::get(choices);
if (Common::isSpace(val) || val == '\033')
script->unsetVar(script->getInputName());
else {
Common::String s_val;
s_val = val;
script->setVar(script->getInputName(), s_val);
}
}
break;
case Script::INPUT_KEYPRESS:
ReadChoiceController::get(" \015\033");
break;
case Script::INPUT_NUMBER: {
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper ipadNumberInput;
ipadNumberInput.beginConversation(U4IOS::UIKeyboardTypeNumberPad, "Amount?");
#endif
int val = ReadIntController::get(script->getInputMaxLen(), TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
script->setVar(script->getInputName(), val);
}
break;
case Script::INPUT_STRING: {
#ifdef IOS_ULTIMA4
U4IOS::IOSConversationHelper ipadNumberInput;
ipadNumberInput.beginConversation(U4IOS::UIKeyboardTypeDefault);
#endif
Common::String str = ReadStringController::get(script->getInputMaxLen(), TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
if (str.size()) {
lowercase(str);
script->setVar(script->getInputName(), str);
} else script->unsetVar(script->getInputName());
}
break;
case Script::INPUT_PLAYER: {
ReadPlayerController getPlayerCtrl;
eventHandler->pushController(&getPlayerCtrl);
int player = getPlayerCtrl.waitFor();
if (player != -1) {
Common::String player_str = xu4_to_string(player + 1);
script->setVar(script->getInputName(), player_str);
} else script->unsetVar(script->getInputName());
}
break;
default:
break;
} // } switch
// Continue running the script!
g_context->_line++;
script->_continue();
} // } if
} // } while
}
// Unload the script
script->unload();
cnv->_state = Conversation::DONE;
}
/*
* a conversation with a non-vendor
*/
else {
text = "\n\n\n";
switch (cnv->_state) {
case Conversation::INTRO:
text = getIntro(cnv);
break;
case Conversation::TALK:
text += getResponse(cnv, inquiry) + "\n";
break;
case Conversation::CONFIRMATION:
assertMsg(_npcType == NPC_LORD_BRITISH, "invalid state: %d", cnv->_state);
text += lordBritishGetQuestionResponse(cnv, inquiry);
break;
case Conversation::ASK:
case Conversation::ASKYESNO:
assertMsg(_npcType != NPC_HAWKWIND, "invalid state for hawkwind conversation");
text += talkerGetQuestionResponse(cnv, inquiry) + "\n";
break;
case Conversation::GIVEBEGGAR:
assertMsg(_npcType == NPC_TALKER_BEGGAR, "invalid npc type: %d", _npcType);
text = beggarGetQuantityResponse(cnv, inquiry);
break;
case Conversation::FULLHEAL:
case Conversation::ADVANCELEVELS:
/* handled elsewhere */
break;
default:
error("invalid state: %d", cnv->_state);
}
}
return replySplit(text);
}
Common::String Person::getPrompt(Conversation *cnv) {
if (isVendor())
return "";
Common::String prompt;
if (cnv->_state == Conversation::ASK)
prompt = getQuestion(cnv);
else if (cnv->_state == Conversation::GIVEBEGGAR)
prompt = "How much? ";
else if (cnv->_state == Conversation::CONFIRMATION)
prompt = "\n\nHe asks: Art thou well?";
else if (cnv->_state != Conversation::ASKYESNO)
prompt = _dialogue->getPrompt();
return prompt;
}
const char *Person::getChoices(Conversation *cnv) {
if (isVendor())
return cnv->_script->getChoices().c_str();
switch (cnv->_state) {
case Conversation::CONFIRMATION:
case Conversation::CONTINUEQUESTION:
return "ny\015 \033";
case Conversation::PLAYER:
return "012345678\015 \033";
default:
error("invalid state: %d", cnv->_state);
}
return nullptr;
}
Common::String Person::getIntro(Conversation *cnv) {
if (_npcType == NPC_EMPTY) {
cnv->_state = Conversation::DONE;
return Common::String("Funny, no\nresponse!\n");
}
// As far as I can tell, about 50% of the time they tell you their
// name in the introduction
Response *intro;
if (xu4_random(2) == 0)
intro = _dialogue->getIntro();
else
intro = _dialogue->getLongIntro();
cnv->_state = Conversation::TALK;
Common::String text = processResponse(cnv, intro);
return text;
}
Common::String Person::processResponse(Conversation *cnv, Response *response) {
Common::String text;
const Std::vector<ResponsePart> &parts = response->getParts();
for (const auto &i : parts) {
// check for command triggers
if (i.isCommand())
runCommand(cnv, i);
// otherwise, append response part to reply
else
text += i;
}
return text;
}
void Person::runCommand(Conversation *cnv, const ResponsePart &command) {
if (command == g_responseParts->ASK) {
cnv->_question = _dialogue->getQuestion();
cnv->_state = Conversation::ASK;
} else if (command == g_responseParts->END) {
cnv->_state = Conversation::DONE;
} else if (command == g_responseParts->ATTACK) {
cnv->_state = Conversation::ATTACK;
} else if (command == g_responseParts->BRAGGED) {
g_context->_party->adjustKarma(KA_BRAGGED);
} else if (command == g_responseParts->HUMBLE) {
g_context->_party->adjustKarma(KA_HUMBLE);
} else if (command == g_responseParts->ADVANCELEVELS) {
cnv->_state = Conversation::ADVANCELEVELS;
} else if (command == g_responseParts->HEALCONFIRM) {
cnv->_state = Conversation::CONFIRMATION;
} else if (command == g_responseParts->STARTMUSIC_LB) {
g_music->lordBritish();
} else if (command == g_responseParts->STARTMUSIC_HW) {
g_music->hawkwind();
} else if (command == g_responseParts->STOPMUSIC) {
g_music->playMapMusic();
} else if (command == g_responseParts->HAWKWIND) {
g_context->_party->adjustKarma(KA_HAWKWIND);
} else {
error("unknown command trigger in dialogue response: %s\n", Common::String(command).c_str());
}
}
Common::String Person::getResponse(Conversation *cnv, const char *inquiry) {
Common::String reply;
Virtue v;
const ResponsePart &action = _dialogue->getAction();
reply = "\n";
/* Does the person take action during the conversation? */
if (action == g_responseParts->END) {
runCommand(cnv, action);
return _dialogue->getPronoun() + " turns away!\n";
} else if (action == g_responseParts->ATTACK) {
runCommand(cnv, action);
return Common::String("\n") + getName() + " says: On guard! Fool!";
}
if (_npcType == NPC_TALKER_BEGGAR && scumm_strnicmp(inquiry, "give", 4) == 0) {
reply.clear();
cnv->_state = Conversation::GIVEBEGGAR;
}
else if (scumm_strnicmp(inquiry, "join", 4) == 0 &&
g_context->_party->canPersonJoin(getName(), &v)) {
CannotJoinError join = g_context->_party->join(getName());
if (join == JOIN_SUCCEEDED) {
reply += "I am honored to join thee!";
g_context->_location->_map->removeObject(this);
cnv->_state = Conversation::DONE;
} else {
reply += "Thou art not ";
reply += (join == JOIN_NOT_VIRTUOUS) ? getVirtueAdjective(v) : "experienced";
reply += " enough for me to join thee.";
}
}
else if ((*_dialogue)[inquiry]) {
Dialogue::Keyword *kw = (*_dialogue)[inquiry];
reply = processResponse(cnv, kw->getResponse());
}
else if (settings._debug && scumm_strnicmp(inquiry, "dump", 4) == 0) {
Std::vector<Common::String> words = split(inquiry, " \t");
if (words.size() <= 1)
reply = _dialogue->dump("");
else
reply = _dialogue->dump(words[1]);
}
else
reply += processResponse(cnv, _dialogue->getDefaultAnswer());
return reply;
}
Common::String Person::talkerGetQuestionResponse(Conversation *cnv, const char *answer) {
bool valid = false;
bool yes = false;
char ans = tolower(answer[0]);
if (ans == 'y' || ans == 'n') {
valid = true;
yes = ans == 'y';
}
if (!valid) {
cnv->_state = Conversation::ASKYESNO;
return "Yes or no!";
}
cnv->_state = Conversation::TALK;
return "\n" + processResponse(cnv, cnv->_question->getResponse(yes));
}
Common::String Person::beggarGetQuantityResponse(Conversation *cnv, const char *response) {
Common::String reply;
cnv->_quant = (int) strtol(response, nullptr, 10);
cnv->_state = Conversation::TALK;
if (cnv->_quant > 0) {
if (g_context->_party->donate(cnv->_quant)) {
reply = "\n";
reply += _dialogue->getPronoun();
reply += " says: Oh Thank thee! I shall never forget thy kindness!\n";
}
else
reply = "\n\nThou hast not that much gold!\n";
} else
reply = "\n";
return reply;
}
Common::String Person::lordBritishGetQuestionResponse(Conversation *cnv, const char *answer) {
Common::String reply;
cnv->_state = Conversation::TALK;
if (tolower(answer[0]) == 'y') {
reply = "Y\n\nHe says: That is good.\n";
}
else if (tolower(answer[0]) == 'n') {
reply = "N\n\nHe says: Let me heal thy wounds!\n";
cnv->_state = Conversation::FULLHEAL;
}
else
reply = "\n\nThat I cannot\nhelp thee with.\n";
return reply;
}
Common::String Person::getQuestion(Conversation *cnv) {
return "\n" + cnv->_question->getText() + "\n\nYou say: ";
}
/**
* Returns the number of characters needed to get to
* the next line of text (based on column width).
*/
int chars_to_next_line(const char *s, int columnmax) {
int chars = -1;
if (strlen(s) > 0) {
int lastbreak = columnmax;
chars = 0;
for (const char *str = s; *str; str++) {
if (*str == '\n')
return (str - s);
else if (*str == ' ')
lastbreak = (str - s);
else if (++chars >= columnmax)
return lastbreak;
}
}
return chars;
}
/**
* Counts the number of lines (of the maximum width given by
* columnmax) in the Common::String.
*/
int linecount(const Common::String &s, int columnmax) {
int lines = 0;
unsigned ch = 0;
while (ch < s.size()) {
ch += chars_to_next_line(s.c_str() + ch, columnmax);
if (ch < s.size())
ch++;
lines++;
}
return lines;
}
/**
* Returns the number of characters needed to produce a
* valid screen of text (given a column width and row height)
*/
int chars_needed(const char *s, int columnmax, int linesdesired, int *real_lines) {
int chars = 0,
totalChars = 0;
Common::String new_str = s;
const char *str = new_str.c_str();
// try breaking text into paragraphs first
Common::String text = s;
Common::String paragraphs;
uint pos;
int lines = 0;
while ((pos = text.find("\n\n")) < text.size()) {
Common::String p = text.substr(0, pos);
lines += linecount(p.c_str(), columnmax);
if (lines <= linesdesired)
paragraphs += p + "\n";
else
break;
text = text.substr(pos + 1);
}
// Seems to be some sort of clang compilation bug in this code, that causes this addition
// to not work correctly.
int totalPossibleLines = lines + linecount(text.c_str(), columnmax);
if (totalPossibleLines <= linesdesired)
paragraphs += text;
if (!paragraphs.empty()) {
*real_lines = lines;
return paragraphs.size();
} else {
// reset variables and try another way
lines = 1;
}
// gather all the line breaks
while ((chars = chars_to_next_line(str, columnmax)) >= 0) {
if (++lines >= linesdesired)
break;
int num_to_move = chars;
if (*(str + num_to_move) == '\n')
num_to_move++;
totalChars += num_to_move;
str += num_to_move;
}
*real_lines = lines;
return totalChars;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,109 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GAME_PERSON_H
#define ULTIMA4_GAME_PERSON_H
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
class Conversation;
class Dialogue;
class Response;
class ResponsePart;
typedef enum {
NPC_EMPTY,
NPC_TALKER,
NPC_TALKER_BEGGAR,
NPC_TALKER_GUARD,
NPC_TALKER_COMPANION,
NPC_VENDOR_WEAPONS,
NPC_VENDOR_ARMOR,
NPC_VENDOR_FOOD,
NPC_VENDOR_TAVERN,
NPC_VENDOR_REAGENTS,
NPC_VENDOR_HEALER,
NPC_VENDOR_INN,
NPC_VENDOR_GUILD,
NPC_VENDOR_STABLE,
NPC_LORD_BRITISH,
NPC_HAWKWIND,
NPC_MAX
} PersonNpcType;
class Person : public Creature {
public:
Person(MapTile tile);
Person(const Person *p);
bool canConverse() const;
bool isVendor() const;
Common::String getName() const override;
void goToStartLocation();
void setDialogue(Dialogue *d);
MapCoords &getStart() {
return _start;
}
PersonNpcType getNpcType() const {
return _npcType;
}
void setNpcType(PersonNpcType t);
Common::List<Common::String> getConversationText(Conversation *cnv, const char *inquiry);
/**
* Get the prompt shown after each reply.
*/
Common::String getPrompt(Conversation *cnv);
/**
* Returns the valid keyboard choices for a given conversation.
*/
const char *getChoices(Conversation *cnv);
Common::String getIntro(Conversation *cnv);
Common::String processResponse(Conversation *cnv, Response *response);
void runCommand(Conversation *cnv, const ResponsePart &command);
Common::String getResponse(Conversation *cnv, const char *inquiry);
Common::String talkerGetQuestionResponse(Conversation *cnv, const char *inquiry);
Common::String beggarGetQuantityResponse(Conversation *cnv, const char *response);
Common::String lordBritishGetQuestionResponse(Conversation *cnv, const char *answer);
Common::String getQuestion(Conversation *cnv);
private:
Dialogue *_dialogue;
MapCoords _start;
PersonNpcType _npcType;
};
bool isPerson(Object *punknown);
Common::List<Common::String> replySplit(const Common::String &text);
int linecount(const Common::String &s, int columnmax);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,449 @@
/* 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_GAME_PLAYER_H
#define ULTIMA4_GAME_PLAYER_H
#include "ultima/ultima4/game/creature.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/core/observable.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/game/script.h"
#include "ultima/ultima4/map/tile.h"
#include "ultima/ultima4/core/types.h"
namespace Ultima {
namespace Ultima4 {
class Armor;
class Party;
class Weapon;
typedef Std::vector<class PartyMember *> PartyMemberVector;
#define ALL_PLAYERS -1
enum KarmaAction {
KA_FOUND_ITEM,
KA_STOLE_CHEST,
KA_GAVE_TO_BEGGAR,
KA_GAVE_ALL_TO_BEGGAR,
KA_BRAGGED,
KA_HUMBLE,
KA_HAWKWIND,
KA_MEDITATION,
KA_BAD_MANTRA,
KA_ATTACKED_GOOD,
KA_FLED_EVIL,
KA_FLED_GOOD,
KA_HEALTHY_FLED_EVIL,
KA_KILLED_EVIL,
KA_SPARED_GOOD,
KA_DONATED_BLOOD,
KA_DIDNT_DONATE_BLOOD,
KA_CHEAT_REAGENTS,
KA_DIDNT_CHEAT_REAGENTS,
KA_USED_SKULL,
KA_DESTROYED_SKULL
};
enum HealType {
HT_NONE,
HT_CURE,
HT_FULLHEAL,
HT_RESURRECT,
HT_HEAL,
HT_CAMPHEAL,
HT_INNHEAL
};
enum InventoryItem {
INV_NONE,
INV_WEAPON,
INV_ARMOR,
INV_FOOD,
INV_REAGENT,
INV_GUILDITEM,
INV_HORSE
};
enum CannotJoinError {
JOIN_SUCCEEDED,
JOIN_NOT_EXPERIENCED,
JOIN_NOT_VIRTUOUS
};
enum EquipError {
EQUIP_SUCCEEDED,
EQUIP_NONE_LEFT,
EQUIP_CLASS_RESTRICTED
};
/**
* PartyMember class
*/
class PartyMember : public Creature, public Script::Provider {
public:
PartyMember(Party *p, SaveGamePlayerRecord *pr);
virtual ~PartyMember();
/**
* Notify the party that this player has changed somehow
*/
void notifyOfChange();
/**
* Used to translate script values into something useful
*/
Common::String translate(Std::vector<Common::String> &parts) override;
// Accessor methods
int getHp() const override;
int getMaxHp() const {
return _player->_hpMax;
}
int getExp() const {
return _player->_xp;
}
int getStr() const {
return _player->_str;
}
int getDex() const {
return _player->_dex;
}
int getInt() const {
return _player->_intel;
}
int getMp() const {
return _player->_mp;
}
/**
* Determine the most magic points a character could have
* given his class and intelligence.
*/
int getMaxMp() const;
const Weapon *getWeapon() const;
const Armor *getArmor() const;
Common::String getName() const override;
SexType getSex() const;
ClassType getClass() const;
CreatureStatus getState() const override;
/**
* Determine what level a character has.
*/
int getRealLevel() const;
/**
* Determine the highest level a character could have with the number
* of experience points he has.
*/
int getMaxLevel() const;
/**
* Adds a status effect to the player
*/
void addStatus(StatusType status) override;
/**
* Adjusts the player's mp by 'pts'
*/
void adjustMp(int pts);
/**
* Advances the player to the next level if they have enough experience
*/
void advanceLevel();
/**
* Apply an effect to the party member
*/
void applyEffect(TileEffect effect);
/**
* Award a player experience points. Maxs out the players xp at 9999.
*/
void awardXp(int xp);
/**
* Perform a certain type of healing on the party member
*/
bool heal(HealType type);
/**
* Remove status effects from the party member
*/
void removeStatus(StatusType status) override;
void setHp(int hp) override;
void setMp(int mp);
EquipError setArmor(const Armor *a);
EquipError setWeapon(const Weapon *w);
/**
* Applies damage to a player, and changes status to dead if hit
* points drop below zero.
*
* Byplayer is ignored for now, since it should always be false for U4. (Is
* there anything special about being killed by a party member in U5?) Also
* keeps interface consistent for virtual base function Creature::applydamage()
*/
bool applyDamage(int damage, bool byplayer = false) override;
int getAttackBonus() const override;
int getDefense() const override;
bool dealDamage(Creature *m, int damage) override;
/**
* Calculate damage for an attack.
*/
int getDamage();
/**
* Returns the tile that will be displayed when the party
* member's attack hits
*/
const Common::String &getHitTile() const override;
/**
* Returns the tile that will be displayed when the party
* member's attack fails
*/
const Common::String &getMissTile() const override;
bool isDead();
bool isDisabled();
/**
* Lose the equipped weapon for the player (flaming oil, ranged daggers, etc.)
* Returns the number of weapons left of that type, including the one in
* the players hand
*/
int loseWeapon();
/**
* Put the party member to sleep
*/
void putToSleep() override;
/**
* Wakes up the party member
*/
void wakeUp() override;
protected:
static MapTile tileForClass(int klass);
SaveGamePlayerRecord *_player;
class Party *_party;
};
/**
* Party class
*/
class PartyEvent {
public:
enum Type {
GENERIC,
LOST_EIGHTH,
ADVANCED_LEVEL,
STARVING,
TRANSPORT_CHANGED,
PLAYER_KILLED,
ACTIVE_PLAYER_CHANGED,
MEMBER_JOINED,
PARTY_REVIVED,
INVENTORY_ADDED
};
PartyEvent(Type type, PartyMember *partyMember) : _type(type), _player(partyMember) { }
Type _type;
PartyMember *_player;
};
typedef Std::vector<PartyMember *> PartyMemberVector;
class Party : public Observable<Party *, PartyEvent &>, public Script::Provider {
friend class PartyMember;
public:
Party(SaveGame *saveGame);
virtual ~Party();
/**
* Notify the party that something about it has changed
*/
void notifyOfChange(PartyMember *partyMember = 0, PartyEvent::Type = PartyEvent::GENERIC);
// Used to translate script values into something useful
Common::String translate(Std::vector<Common::String> &parts) override;
void adjustFood(int food);
void adjustGold(int gold);
/**
* Adjusts the avatar's karma level for the given action. Notify
* observers with a lost eighth event if the player has lost
* avatarhood.
*/
void adjustKarma(KarmaAction action);
/**
* Apply effects to the entire party
*/
void applyEffect(TileEffect effect);
/**
* Attempt to elevate in the given virtue
*/
bool attemptElevation(Virtue virtue);
/**
* Burns a torch's duration down a certain number of turns
*/
void burnTorch(int turns = 1);
/**
* Returns true if the party can enter the shrine
*/
bool canEnterShrine(Virtue virtue);
/**
* Returns true if the person can join the party
*/
bool canPersonJoin(Common::String name, Virtue *v);
/**
* Damages the party's ship
*/
void damageShip(uint pts);
/**
* Donates 'quantity' gold. Returns true if the donation succeeded,
* or false if there was not enough gold to make the donation
*/
bool donate(int quantity);
/**
* Ends the party's turn
*/
void endTurn();
/**
* Adds a chest worth of gold to the party's inventory
*/
int getChest();
/**
* Returns the number of turns a currently lit torch will last (or 0 if no torch lit)
*/
int getTorchDuration() const;
/**
* Heals the ship's hull strength by 'pts' points
*/
void healShip(uint pts);
/**
* Returns true if the balloon is currently in the air
*/
bool isFlying() const;
/**
* Whether or not the party can make an action.
*/
bool isImmobilized();
/**
* Whether or not all the party members are dead.
*/
bool isDead();
/**
* Returns true if the person with that name
* is already in the party
*/
bool isPersonJoined(Common::String name);
/**
* Attempts to add the person to the party.
* Returns JOIN_SUCCEEDED if successful.
*/
CannotJoinError join(Common::String name);
/**
* Lights a torch with a default duration of 100
*/
bool lightTorch(int duration = 100, bool loseTorch = true);
/**
* Extinguishes a torch
*/
void quenchTorch();
/**
* Revives the party after the entire party has been killed
*/
void reviveParty();
MapTile getTransport() const;
void setTransport(MapTile transport);
void setShipHull(int str);
Direction getDirection() const;
void setDirection(Direction dir);
void adjustReagent(int reagent, int amt);
int getReagent(int reagent) const;
short *getReagentPtr(int reagent) const;
void setActivePlayer(int p);
int getActivePlayer() const;
void swapPlayers(int p1, int p2);
/**
* Returns the size of the party
*/
int size() const;
/**
* Returns a pointer to the party member indicated
*/
PartyMember *member(int index) const;
private:
void syncMembers();
PartyMemberVector _members;
SaveGame *_saveGame;
MapTile _transport;
int _torchDuration;
int _activePlayer;
#ifdef IOS_ULTIMA4
friend void U4IOS::syncPartyMembersWithSaveGame();
#endif
};
bool isPartyMember(Object *punknown);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

Some files were not shown because too many files have changed in this diff Show More