Initial commit
This commit is contained in:
112
engines/ultima/ultima4/game/armor.cpp
Normal file
112
engines/ultima/ultima4/game/armor.cpp
Normal 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
|
||||
94
engines/ultima/ultima4/game/armor.h
Normal file
94
engines/ultima/ultima4/game/armor.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA4_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
|
||||
62
engines/ultima/ultima4/game/aura.cpp
Normal file
62
engines/ultima/ultima4/game/aura.cpp
Normal 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
|
||||
77
engines/ultima/ultima4/game/aura.h
Normal file
77
engines/ultima/ultima4/game/aura.h
Normal 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
|
||||
435
engines/ultima/ultima4/game/codex.cpp
Normal file
435
engines/ultima/ultima4/game/codex.cpp
Normal 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
|
||||
115
engines/ultima/ultima4/game/codex.h
Normal file
115
engines/ultima/ultima4/game/codex.h
Normal 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
|
||||
70
engines/ultima/ultima4/game/context.cpp
Normal file
70
engines/ultima/ultima4/game/context.cpp
Normal 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
|
||||
97
engines/ultima/ultima4/game/context.h
Normal file
97
engines/ultima/ultima4/game/context.h
Normal 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
|
||||
1013
engines/ultima/ultima4/game/creature.cpp
Normal file
1013
engines/ultima/ultima4/game/creature.cpp
Normal file
File diff suppressed because it is too large
Load Diff
449
engines/ultima/ultima4/game/creature.h
Normal file
449
engines/ultima/ultima4/game/creature.h
Normal 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
|
||||
148
engines/ultima/ultima4/game/death.cpp
Normal file
148
engines/ultima/ultima4/game/death.cpp
Normal 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
|
||||
61
engines/ultima/ultima4/game/death.h
Normal file
61
engines/ultima/ultima4/game/death.h
Normal 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
|
||||
831
engines/ultima/ultima4/game/game.cpp
Normal file
831
engines/ultima/ultima4/game/game.cpp
Normal 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
|
||||
153
engines/ultima/ultima4/game/game.h
Normal file
153
engines/ultima/ultima4/game/game.h
Normal 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
|
||||
761
engines/ultima/ultima4/game/item.cpp
Normal file
761
engines/ultima/ultima4/game/item.cpp
Normal 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
|
||||
150
engines/ultima/ultima4/game/item.h
Normal file
150
engines/ultima/ultima4/game/item.h
Normal 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
|
||||
77
engines/ultima/ultima4/game/moongate.cpp
Normal file
77
engines/ultima/ultima4/game/moongate.cpp
Normal 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
|
||||
54
engines/ultima/ultima4/game/moongate.h
Normal file
54
engines/ultima/ultima4/game/moongate.h
Normal 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
|
||||
178
engines/ultima/ultima4/game/names.cpp
Normal file
178
engines/ultima/ultima4/game/names.cpp
Normal 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
|
||||
48
engines/ultima/ultima4/game/names.h
Normal file
48
engines/ultima/ultima4/game/names.h
Normal 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
|
||||
66
engines/ultima/ultima4/game/object.cpp
Normal file
66
engines/ultima/ultima4/game/object.cpp
Normal 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
|
||||
147
engines/ultima/ultima4/game/object.h
Normal file
147
engines/ultima/ultima4/game/object.h
Normal 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
|
||||
613
engines/ultima/ultima4/game/person.cpp
Normal file
613
engines/ultima/ultima4/game/person.cpp
Normal 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
|
||||
109
engines/ultima/ultima4/game/person.h
Normal file
109
engines/ultima/ultima4/game/person.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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
|
||||
1172
engines/ultima/ultima4/game/player.cpp
Normal file
1172
engines/ultima/ultima4/game/player.cpp
Normal file
File diff suppressed because it is too large
Load Diff
449
engines/ultima/ultima4/game/player.h
Normal file
449
engines/ultima/ultima4/game/player.h
Normal 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
|
||||
175
engines/ultima/ultima4/game/portal.cpp
Normal file
175
engines/ultima/ultima4/game/portal.cpp
Normal 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/game/portal.h"
|
||||
#include "ultima/ultima4/map/annotation.h"
|
||||
#include "ultima/ultima4/map/city.h"
|
||||
#include "ultima/ultima4/game/context.h"
|
||||
#include "ultima/ultima4/map/dungeon.h"
|
||||
#include "ultima/ultima4/game/game.h"
|
||||
#include "ultima/ultima4/map/location.h"
|
||||
#include "ultima/ultima4/map/mapmgr.h"
|
||||
#include "ultima/ultima4/game/names.h"
|
||||
#include "ultima/ultima4/gfx/screen.h"
|
||||
#include "ultima/ultima4/map/shrine.h"
|
||||
#include "ultima/ultima4/map/tile.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
void createDngLadder(Location *location, PortalTriggerAction action, Portal *p) {
|
||||
if (!p) return;
|
||||
else {
|
||||
p->_destid = location->_map->_id;
|
||||
if (action == ACTION_KLIMB && location->_coords.z == 0) {
|
||||
p->_exitPortal = true;
|
||||
p->_destid = 1;
|
||||
} else p->_exitPortal = false;
|
||||
p->_message = "";
|
||||
p->_portalConditionsMet = nullptr;
|
||||
p->_portalTransportRequisites = TRANSPORT_FOOT_OR_HORSE;
|
||||
p->_retroActiveDest = nullptr;
|
||||
p->_saveLocation = false;
|
||||
p->_start = location->_coords;
|
||||
p->_start.z += (action == ACTION_KLIMB) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
int usePortalAt(Location *location, MapCoords coords, PortalTriggerAction action) {
|
||||
Map *destination;
|
||||
char msg[32] = {0};
|
||||
|
||||
const Portal *portal = location->_map->portalAt(coords, action);
|
||||
Portal dngLadder;
|
||||
|
||||
/* didn't find a portal there */
|
||||
if (!portal) {
|
||||
|
||||
/* if it's a dungeon, then ladders are predictable. Create one! */
|
||||
if (location->_context == CTX_DUNGEON) {
|
||||
Dungeon *dungeon = dynamic_cast<Dungeon *>(location->_map);
|
||||
assert(dungeon);
|
||||
|
||||
if ((action & ACTION_KLIMB) && dungeon->ladderUpAt(coords))
|
||||
createDngLadder(location, action, &dngLadder);
|
||||
else if ((action & ACTION_DESCEND) && dungeon->ladderDownAt(coords))
|
||||
createDngLadder(location, action, &dngLadder);
|
||||
else
|
||||
return 0;
|
||||
portal = &dngLadder;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* conditions not met for portal to work */
|
||||
if (portal && portal->_portalConditionsMet && !(*portal->_portalConditionsMet)(portal))
|
||||
return 0;
|
||||
/* must klimb or descend on foot! */
|
||||
else if (g_context->_transportContext & ~TRANSPORT_FOOT && (action == ACTION_KLIMB || action == ACTION_DESCEND)) {
|
||||
g_screen->screenMessage("%sOnly on foot!\n", action == ACTION_KLIMB ? "Klimb\n" : "");
|
||||
return 1;
|
||||
}
|
||||
|
||||
destination = mapMgr->get(portal->_destid);
|
||||
|
||||
if (portal->_message.empty()) {
|
||||
|
||||
switch (action) {
|
||||
case ACTION_DESCEND:
|
||||
Common::sprintf_s(msg, "Descend down to level %d\n", portal->_start.z + 1);
|
||||
break;
|
||||
case ACTION_KLIMB:
|
||||
if (portal->_exitPortal)
|
||||
Common::sprintf_s(msg, "Klimb up!\nLeaving...\n");
|
||||
else
|
||||
Common::sprintf_s(msg, "Klimb up!\nTo level %d\n", portal->_start.z + 1);
|
||||
break;
|
||||
case ACTION_ENTER:
|
||||
switch (destination->_type) {
|
||||
case Map::CITY: {
|
||||
City *city = dynamic_cast<City *>(destination);
|
||||
assert(city);
|
||||
g_screen->screenMessage("Enter %s!\n\n%s\n\n", city->_type.c_str(), city->getName().c_str());
|
||||
}
|
||||
break;
|
||||
case Map::SHRINE:
|
||||
g_screen->screenMessage("Enter the %s!\n\n", destination->getName().c_str());
|
||||
break;
|
||||
case Map::DUNGEON:
|
||||
#ifdef IOS_ULTIMA4
|
||||
U4IOS::testFlightPassCheckPoint("Enter " + destination->getName());
|
||||
#endif
|
||||
g_screen->screenMessage("Enter dungeon!\n\n%s\n\n", destination->getName().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ACTION_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* check the transportation requisites of the portal */
|
||||
if (g_context->_transportContext & ~portal->_portalTransportRequisites) {
|
||||
g_screen->screenMessage("Only on foot!\n");
|
||||
return 1;
|
||||
}
|
||||
/* ok, we know the portal is going to work -- now display the custom message, if any */
|
||||
else if (!portal->_message.empty() || strlen(msg))
|
||||
g_screen->screenMessage("%s", portal->_message.empty() ? msg : portal->_message.c_str());
|
||||
|
||||
/* portal just exits to parent map */
|
||||
if (portal->_exitPortal) {
|
||||
g_game->exitToParentMap();
|
||||
g_music->playMapMusic();
|
||||
return 1;
|
||||
} else if (portal->_destid == location->_map->_id)
|
||||
location->_coords = portal->_start;
|
||||
|
||||
else {
|
||||
g_game->setMap(destination, portal->_saveLocation, portal);
|
||||
g_music->playMapMusic();
|
||||
}
|
||||
|
||||
/* if the portal changes the map retroactively, do it here */
|
||||
/*
|
||||
* note that we use c->location instead of location, since
|
||||
* location has probably been invalidated above
|
||||
*/
|
||||
if (portal->_retroActiveDest && g_context->_location->_prev) {
|
||||
g_context->_location->_prev->_coords = portal->_retroActiveDest->_coords;
|
||||
g_context->_location->_prev->_map = mapMgr->get(portal->_retroActiveDest->_mapid);
|
||||
}
|
||||
|
||||
if (destination->_type == Map::SHRINE) {
|
||||
Shrine *shrine = dynamic_cast<Shrine *>(destination);
|
||||
assert(shrine);
|
||||
shrine->enter();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
82
engines/ultima/ultima4/game/portal.h
Normal file
82
engines/ultima/ultima4/game/portal.h
Normal 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA4_GAME_PORTAL_H
|
||||
#define ULTIMA4_GAME_PORTAL_H
|
||||
|
||||
#include "ultima/ultima4/game/context.h"
|
||||
#include "ultima/ultima4/map/map.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
class Map;
|
||||
class Location;
|
||||
struct Portal;
|
||||
|
||||
typedef enum {
|
||||
ACTION_NONE = 0x0,
|
||||
ACTION_ENTER = 0x1,
|
||||
ACTION_KLIMB = 0x2,
|
||||
ACTION_DESCEND = 0x4,
|
||||
ACTION_EXIT_NORTH = 0x8,
|
||||
ACTION_EXIT_EAST = 0x10,
|
||||
ACTION_EXIT_SOUTH = 0x20,
|
||||
ACTION_EXIT_WEST = 0x40
|
||||
} PortalTriggerAction;
|
||||
|
||||
typedef bool (*PortalConditionsMet)(const Portal *p);
|
||||
|
||||
struct PortalDestination {
|
||||
MapCoords _coords;
|
||||
MapId _mapid;
|
||||
};
|
||||
|
||||
struct Portal {
|
||||
MapCoords _coords;
|
||||
MapId _destid;
|
||||
MapCoords _start;
|
||||
PortalTriggerAction _triggerAction;
|
||||
PortalConditionsMet _portalConditionsMet;
|
||||
PortalDestination *_retroActiveDest;
|
||||
bool _saveLocation;
|
||||
Common::String _message;
|
||||
TransportContext _portalTransportRequisites;
|
||||
bool _exitPortal;
|
||||
int _tile;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a dungeon ladder portal based on the action given
|
||||
*/
|
||||
void createDngLadder(Location *location, PortalTriggerAction action, Portal *p);
|
||||
|
||||
/**
|
||||
* Finds a portal at the given (x,y,z) coords that will work with the action given
|
||||
* and uses it. If in a dungeon and trying to use a ladder, it creates a portal
|
||||
* based on the ladder and uses it.
|
||||
*/
|
||||
int usePortalAt(Location *location, MapCoords coords, PortalTriggerAction action);
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
1535
engines/ultima/ultima4/game/script.cpp
Normal file
1535
engines/ultima/ultima4/game/script.cpp
Normal file
File diff suppressed because it is too large
Load Diff
443
engines/ultima/ultima4/game/script.h
Normal file
443
engines/ultima/ultima4/game/script.h
Normal file
@@ -0,0 +1,443 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ULTIMA4_GAME_SCRIPT_H
|
||||
#define ULTIMA4_GAME_SCRIPT_H
|
||||
|
||||
#include "ultima/ultima4/core/types.h"
|
||||
#include "ultima/shared/conf/xml_node.h"
|
||||
#include "ultima/shared/std/containers.h"
|
||||
#include "common/file.h"
|
||||
#include "common/list.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
/**
|
||||
* An xml-scripting class. It loads and runs xml scripts that
|
||||
* take information and interact with the game environment itself.
|
||||
* Currently, it is mainly useful for writing vendor code; however,
|
||||
* it should be possible to write scripts for other parts of the
|
||||
* game.
|
||||
*
|
||||
* @todo
|
||||
* <ul>
|
||||
* <li>Strip vendor-specific code from the language</li>
|
||||
* <li>Fill in some of the missing integration with the game</li>
|
||||
* </ul>
|
||||
*/
|
||||
class Script {
|
||||
public:
|
||||
/**
|
||||
* A class that provides information to a script. It is designed to
|
||||
* translate qualifiers and identifiers in a script to another value.
|
||||
* Each provider is assigned a qualifier that the script uses to
|
||||
* select a provider. The provider then uses the rest of the information
|
||||
* to translate the information to something useful.
|
||||
*/
|
||||
class Provider {
|
||||
public:
|
||||
virtual ~Provider() {}
|
||||
virtual Common::String translate(Std::vector<Common::String> &parts) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* A class that represents a script variable
|
||||
*/
|
||||
class Variable {
|
||||
public:
|
||||
Variable();
|
||||
Variable(const Common::String &v);
|
||||
Variable(const int &v);
|
||||
|
||||
int &getInt();
|
||||
Common::String &getString();
|
||||
|
||||
void setValue(const int &v);
|
||||
void setValue(const Common::String &v);
|
||||
void unset();
|
||||
|
||||
bool isInt() const;
|
||||
bool isString() const;
|
||||
bool isSet() const;
|
||||
|
||||
private:
|
||||
int _iVal;
|
||||
Common::String _sVal;
|
||||
bool _set;
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* A script return code
|
||||
*/
|
||||
enum ReturnCode {
|
||||
RET_OK,
|
||||
RET_REDIRECTED,
|
||||
RET_STOP
|
||||
};
|
||||
|
||||
/**
|
||||
* The current state of the script
|
||||
*/
|
||||
enum State {
|
||||
STATE_UNLOADED,
|
||||
STATE_NORMAL,
|
||||
STATE_DONE,
|
||||
STATE_INPUT
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of input the script is requesting
|
||||
*/
|
||||
enum InputType {
|
||||
INPUT_CHOICE,
|
||||
INPUT_NUMBER,
|
||||
INPUT_STRING,
|
||||
INPUT_DIRECTION,
|
||||
INPUT_PLAYER,
|
||||
INPUT_KEYPRESS
|
||||
};
|
||||
|
||||
/**
|
||||
* The action that the script is taking
|
||||
*/
|
||||
enum Action {
|
||||
ACTION_SET_CONTEXT,
|
||||
ACTION_UNSET_CONTEXT,
|
||||
ACTION_END,
|
||||
ACTION_REDIRECT,
|
||||
ACTION_WAIT_FOR_KEY,
|
||||
ACTION_WAIT,
|
||||
ACTION_STOP,
|
||||
ACTION_INCLUDE,
|
||||
ACTION_FOR_LOOP,
|
||||
ACTION_RANDOM,
|
||||
ACTION_MOVE,
|
||||
ACTION_SLEEP,
|
||||
ACTION_CURSOR,
|
||||
ACTION_PAY,
|
||||
ACTION_IF,
|
||||
ACTION_INPUT,
|
||||
ACTION_ADD,
|
||||
ACTION_LOSE,
|
||||
ACTION_HEAL,
|
||||
ACTION_CAST_SPELL,
|
||||
ACTION_DAMAGE,
|
||||
ACTION_KARMA,
|
||||
ACTION_MUSIC,
|
||||
ACTION_SET_VARIABLE,
|
||||
ACTION_ZTATS
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a script object
|
||||
*/
|
||||
Script();
|
||||
~Script();
|
||||
|
||||
/**
|
||||
* Adds an information provider for the script
|
||||
*/
|
||||
void addProvider(const Common::String &name, Provider *p);
|
||||
|
||||
/**
|
||||
* Loads the vendor script
|
||||
*/
|
||||
bool load(const Common::String &filename, const Common::String &baseId, const Common::String &subNodeName = "", const Common::String &subNodeId = "");
|
||||
|
||||
/**
|
||||
* Unloads the script
|
||||
*/
|
||||
void unload();
|
||||
|
||||
/**
|
||||
* Runs a script after it's been loaded
|
||||
*/
|
||||
void run(const Common::String &script);
|
||||
|
||||
/**
|
||||
* Executes the subscript 'script' of the main script
|
||||
*/
|
||||
ReturnCode execute(Shared::XMLNode *script, Shared::XMLNode *currentItem = nullptr, Common::String *output = nullptr);
|
||||
|
||||
/**
|
||||
* Continues the script from where it left off, or where the last script indicated
|
||||
*/
|
||||
void _continue();
|
||||
|
||||
/**
|
||||
* Set and retrieve property values
|
||||
*/
|
||||
void resetState();
|
||||
void setState(State state);
|
||||
State getState();
|
||||
|
||||
void setTarget(const Common::String &val);
|
||||
void setChoices(const Common::String &val);
|
||||
void setVar(const Common::String &name, const Common::String &val);
|
||||
void setVar(const Common::String &name, int val);
|
||||
void unsetVar(const Common::String &name);
|
||||
|
||||
Common::String getTarget();
|
||||
InputType getInputType();
|
||||
Common::String getInputName();
|
||||
Common::String getChoices();
|
||||
int getInputMaxLen();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Translates a script Common::String with dynamic variables
|
||||
*/
|
||||
void translate(Common::String *script);
|
||||
|
||||
/**
|
||||
* Finds a subscript of script 'node'
|
||||
*/
|
||||
Shared::XMLNode *find(Shared::XMLNode *node, const Common::String &script, const Common::String &choice = "", bool _default = false);
|
||||
|
||||
/**
|
||||
* Gets a property as Common::String from the script, and
|
||||
* translates it using scriptTranslate.
|
||||
*/
|
||||
Common::String getPropAsStr(Common::List<Shared::XMLNode *> &nodes, const Common::String &prop, bool recursive);
|
||||
|
||||
Common::String getPropAsStr(Shared::XMLNode *node, const Common::String &prop, bool recursive = false);
|
||||
|
||||
/**
|
||||
* Gets a property as int from the script
|
||||
*/
|
||||
int getPropAsInt(Common::List<Shared::XMLNode *> &nodes, const Common::String &prop, bool recursive);
|
||||
|
||||
int getPropAsInt(Shared::XMLNode *node, const Common::String &prop, bool recursive = false);
|
||||
|
||||
/**
|
||||
* Gets the content of a script node
|
||||
*/
|
||||
Common::String getContent(Shared::XMLNode *node);
|
||||
|
||||
/*
|
||||
* Action Functions
|
||||
*/
|
||||
/**
|
||||
* Sets a new translation context for the script
|
||||
*/
|
||||
ReturnCode pushContext(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Removes a node from the translation context
|
||||
*/
|
||||
ReturnCode popContext(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* End script execution
|
||||
*/
|
||||
ReturnCode end(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Wait for keypress from the user
|
||||
*/
|
||||
ReturnCode waitForKeypress(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Redirects script execution to another script
|
||||
*/
|
||||
ReturnCode redirect(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Includes a script to be executed
|
||||
*/
|
||||
ReturnCode include(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Waits a given number of milliseconds before continuing execution
|
||||
*/
|
||||
ReturnCode wait(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Executes a 'for' loop script
|
||||
*/
|
||||
ReturnCode forLoop(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Randomely executes script code
|
||||
*/
|
||||
ReturnCode randomScript(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Moves the player's current position
|
||||
*/
|
||||
ReturnCode move(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Puts the player to sleep. Useful when coding inn scripts
|
||||
*/
|
||||
ReturnCode sleep(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Enables/Disables the keyboard cursor
|
||||
*/
|
||||
ReturnCode cursor(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Pay gold to someone
|
||||
*/
|
||||
ReturnCode pay(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Perform a limited 'if' statement
|
||||
*/
|
||||
ReturnCode _if(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Get input from the player
|
||||
*/
|
||||
ReturnCode input(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Add item to inventory
|
||||
*/
|
||||
ReturnCode add(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Lose item
|
||||
*/
|
||||
ReturnCode lose(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Heals a party member
|
||||
*/
|
||||
ReturnCode heal(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Performs all of the visual/audio effects of casting a spell
|
||||
*/
|
||||
ReturnCode castSpell(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Apply damage to a player
|
||||
*/
|
||||
ReturnCode damage(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Apply karma changes based on the action taken
|
||||
*/
|
||||
ReturnCode karma(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Set the currently playing music
|
||||
*/
|
||||
ReturnCode music(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Sets a variable
|
||||
*/
|
||||
ReturnCode setVar(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/**
|
||||
* Display a different ztats screen
|
||||
*/
|
||||
ReturnCode ztats(Shared::XMLNode *script, Shared::XMLNode *current);
|
||||
|
||||
/*
|
||||
* Math and comparison functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses a math Common::String's children into results so
|
||||
* there is only 1 equation remaining.
|
||||
*
|
||||
* ie. <math>5*<math>6/3</math></math>
|
||||
*/
|
||||
void mathParseChildren(Shared::XMLNode *math, Common::String *result);
|
||||
|
||||
/**
|
||||
* Takes a simple equation Common::String and returns the value
|
||||
*/
|
||||
int mathValue(const Common::String &str);
|
||||
|
||||
/**
|
||||
* Performs simple math operations in the script
|
||||
*/
|
||||
int math(int lval, int rval, Common::String &op);
|
||||
|
||||
/**
|
||||
* Parses a Common::String into left integer value, right integer value,
|
||||
* and operator. Returns false if the Common::String is not a valid
|
||||
* math equation
|
||||
*/
|
||||
bool mathParse(const Common::String &str, int *lval, int *rval, Common::String *op);
|
||||
|
||||
/**
|
||||
* Parses a Common::String containing an operator (+, -, *, /, etc.) into 3 parts,
|
||||
* left, right, and operator.
|
||||
*/
|
||||
void parseOperation(const Common::String &str, Common::String *lval, Common::String *rval, Common::String *op);
|
||||
|
||||
/**
|
||||
* Does a boolean comparison on a Common::String (math or Common::String),
|
||||
* fails if the Common::String doesn't contain a valid comparison
|
||||
*/
|
||||
bool compare(const Common::String &str);
|
||||
|
||||
/**
|
||||
* Parses a function into its name and contents
|
||||
*/
|
||||
void funcParse(const Common::String &str, Common::String *funcName, Common::String *contents);
|
||||
|
||||
/*
|
||||
* Static variables
|
||||
*/
|
||||
private:
|
||||
typedef Common::HashMap<Common::String, Action> ActionMap;
|
||||
ActionMap _actionMap;
|
||||
|
||||
private:
|
||||
void removeCurrentVariable(const Common::String &name);
|
||||
Shared::XMLNode *_vendorScriptDoc;
|
||||
Shared::XMLNode *_scriptNode;
|
||||
bool _debug;
|
||||
|
||||
State _state; /**< The state the script is in */
|
||||
Shared::XMLNode *_currentScript; /**< The currently running script */
|
||||
Shared::XMLNode *_currentItem; /**< The current position in the script */
|
||||
Common::List<Shared::XMLNode *> _translationContext; /**< A list of nodes that make up our translation context */
|
||||
Common::String _target; /**< The name of a target script */
|
||||
InputType _inputType; /**< The type of input required */
|
||||
Common::String _inputName; /**< The variable in which to place the input (by default, "input") */
|
||||
int _inputMaxLen; /**< The maximum length allowed for input */
|
||||
|
||||
Common::String _nounName; /**< The name that identifies a node name of noun nodes */
|
||||
Common::String _idPropName; /**< The name of the property that uniquely identifies a noun node
|
||||
and is used to find a new translation context */
|
||||
|
||||
Common::String _choices;
|
||||
int _iterator;
|
||||
|
||||
Common::HashMap<Common::String, Variable *> _variables;
|
||||
Common::HashMap<Common::String, Provider *> _providers;
|
||||
};
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
798
engines/ultima/ultima4/game/spell.cpp
Normal file
798
engines/ultima/ultima4/game/spell.cpp
Normal file
@@ -0,0 +1,798 @@
|
||||
/* 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/game.h"
|
||||
#include "ultima/ultima4/game/spell.h"
|
||||
#include "ultima/ultima4/game/context.h"
|
||||
#include "ultima/ultima4/game/creature.h"
|
||||
#include "ultima/ultima4/game/moongate.h"
|
||||
#include "ultima/ultima4/game/player.h"
|
||||
#include "ultima/ultima4/controllers/combat_controller.h"
|
||||
#include "ultima/ultima4/core/settings.h"
|
||||
#include "ultima/ultima4/core/debugger.h"
|
||||
#include "ultima/ultima4/core/utils.h"
|
||||
#include "ultima/ultima4/events/event_handler.h"
|
||||
#include "ultima/ultima4/gfx/screen.h"
|
||||
#include "ultima/ultima4/map/annotation.h"
|
||||
#include "ultima/ultima4/map/direction.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/tile.h"
|
||||
#include "ultima/ultima4/map/tileset.h"
|
||||
#include "ultima/ultima4/ultima4.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
/* masks for reagents */
|
||||
#define ASH (1 << REAG_ASH)
|
||||
#define GINSENG (1 << REAG_GINSENG)
|
||||
#define GARLIC (1 << REAG_GARLIC)
|
||||
#define SILK (1 << REAG_SILK)
|
||||
#define MOSS (1 << REAG_MOSS)
|
||||
#define PEARL (1 << REAG_PEARL)
|
||||
#define NIGHTSHADE (1 << REAG_NIGHTSHADE)
|
||||
#define MANDRAKE (1 << REAG_MANDRAKE)
|
||||
|
||||
/* spell error messages */
|
||||
static const struct {
|
||||
SpellCastError err;
|
||||
const char *msg;
|
||||
} SPELL_ERROR_MSGS[] = {
|
||||
{ CASTERR_NOMIX, "None Mixed!\n" },
|
||||
{ CASTERR_MPTOOLOW, "Not Enough MP!\n" },
|
||||
{ CASTERR_FAILED, "Failed!\n" },
|
||||
{ CASTERR_WRONGCONTEXT, "Not here!\n" },
|
||||
{ CASTERR_COMBATONLY, "Combat only!\nFailed!\n" },
|
||||
{ CASTERR_DUNGEONONLY, "Dungeon only!\nFailed!\n" },
|
||||
{ CASTERR_WORLDMAPONLY, "Outdoors only!\nFailed!\n" }
|
||||
};
|
||||
|
||||
const Spell Spells::SPELL_LIST[N_SPELLS] = {
|
||||
{ "Awaken", GINSENG | GARLIC, CTX_ANY, TRANSPORT_ANY, &Spells::spellAwaken, Spell::PARAM_PLAYER, 5 },
|
||||
{
|
||||
"Blink", SILK | MOSS, CTX_WORLDMAP, TRANSPORT_FOOT_OR_HORSE,
|
||||
&Spells::spellBlink, Spell::PARAM_DIR, 15
|
||||
},
|
||||
{ "Cure", GINSENG | GARLIC, CTX_ANY, TRANSPORT_ANY, &Spells::spellCure, Spell::PARAM_PLAYER, 5 },
|
||||
{ "Dispell", ASH | GARLIC | PEARL, CTX_ANY, TRANSPORT_ANY, &Spells::spellDispel, Spell::PARAM_DIR, 20 },
|
||||
{
|
||||
"Energy Field", ASH | SILK | PEARL, (LocationContext)(CTX_COMBAT | CTX_DUNGEON),
|
||||
TRANSPORT_ANY, &Spells::spellEField, Spell::PARAM_TYPEDIR, 10
|
||||
},
|
||||
{ "Fireball", ASH | PEARL, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellFireball, Spell::PARAM_DIR, 15 },
|
||||
{
|
||||
"Gate", ASH | PEARL | MANDRAKE, CTX_WORLDMAP, TRANSPORT_FOOT_OR_HORSE,
|
||||
&Spells::spellGate, Spell::PARAM_PHASE, 40
|
||||
},
|
||||
{ "Heal", GINSENG | SILK, CTX_ANY, TRANSPORT_ANY, &Spells::spellHeal, Spell::PARAM_PLAYER, 10 },
|
||||
{ "Iceball", PEARL | MANDRAKE, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellIceball, Spell::PARAM_DIR, 20 },
|
||||
{
|
||||
"Jinx", PEARL | NIGHTSHADE | MANDRAKE,
|
||||
CTX_ANY, TRANSPORT_ANY, &Spells::spellJinx, Spell::PARAM_NONE, 30
|
||||
},
|
||||
{ "Kill", PEARL | NIGHTSHADE, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellKill, Spell::PARAM_DIR, 25 },
|
||||
{ "Light", ASH, CTX_DUNGEON, TRANSPORT_ANY, &Spells::spellLight, Spell::PARAM_NONE, 5 },
|
||||
{ "Magic missile", ASH | PEARL, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellMMissle, Spell::PARAM_DIR, 5 },
|
||||
{ "Negate", ASH | GARLIC | MANDRAKE, CTX_ANY, TRANSPORT_ANY, &Spells::spellNegate, Spell::PARAM_NONE, 20 },
|
||||
{ "Open", ASH | MOSS, CTX_ANY, TRANSPORT_ANY, &Spells::spellOpen, Spell::PARAM_NONE, 5 },
|
||||
{ "Protection", ASH | GINSENG | GARLIC, CTX_ANY, TRANSPORT_ANY, &Spells::spellProtect, Spell::PARAM_NONE, 15 },
|
||||
{ "Quickness", ASH | GINSENG | MOSS, CTX_ANY, TRANSPORT_ANY, &Spells::spellQuick, Spell::PARAM_NONE, 20 },
|
||||
{
|
||||
"Resurrect", ASH | GINSENG | GARLIC | SILK | MOSS | MANDRAKE,
|
||||
CTX_NON_COMBAT, TRANSPORT_ANY, &Spells::spellRez, Spell::PARAM_PLAYER, 45
|
||||
},
|
||||
{ "Sleep", SILK | GINSENG, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellSleep, Spell::PARAM_NONE, 15 },
|
||||
{ "Tremor", ASH | MOSS | MANDRAKE, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellTremor, Spell::PARAM_NONE, 30 },
|
||||
{ "Undead", ASH | GARLIC, CTX_COMBAT, TRANSPORT_ANY, &Spells::spellUndead, Spell::PARAM_NONE, 15 },
|
||||
{ "View", NIGHTSHADE | MANDRAKE, CTX_NON_COMBAT, TRANSPORT_ANY, &Spells::spellView, Spell::PARAM_NONE, 15 },
|
||||
{ "Winds", ASH | MOSS, CTX_WORLDMAP, TRANSPORT_ANY, &Spells::spellWinds, Spell::PARAM_FROMDIR, 10 },
|
||||
{ "X-it", ASH | SILK | MOSS, CTX_DUNGEON, TRANSPORT_ANY, &Spells::spellXit, Spell::PARAM_NONE, 15 },
|
||||
{ "Y-up", SILK | MOSS, CTX_DUNGEON, TRANSPORT_ANY, &Spells::spellYup, Spell::PARAM_NONE, 10 },
|
||||
{ "Z-down", SILK | MOSS, CTX_DUNGEON, TRANSPORT_ANY, &Spells::spellZdown, Spell::PARAM_NONE, 5 }
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
Ingredients::Ingredients() {
|
||||
memset(_reagents, 0, sizeof(_reagents));
|
||||
}
|
||||
|
||||
bool Ingredients::addReagent(Reagent reagent) {
|
||||
assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
|
||||
if (g_context->_party->getReagent(reagent) < 1)
|
||||
return false;
|
||||
g_context->_party->adjustReagent(reagent, -1);
|
||||
_reagents[reagent]++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ingredients::removeReagent(Reagent reagent) {
|
||||
assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
|
||||
if (_reagents[reagent] == 0)
|
||||
return false;
|
||||
g_context->_party->adjustReagent(reagent, 1);
|
||||
_reagents[reagent]--;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Ingredients::getReagent(Reagent reagent) const {
|
||||
assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
|
||||
return _reagents[reagent];
|
||||
}
|
||||
|
||||
void Ingredients::revert() {
|
||||
int reg;
|
||||
|
||||
for (reg = 0; reg < REAG_MAX; reg++) {
|
||||
g_ultima->_saveGame->_reagents[reg] += _reagents[reg];
|
||||
_reagents[reg] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool Ingredients::checkMultiple(int batches) const {
|
||||
for (int i = 0; i < REAG_MAX; i++) {
|
||||
/* see if there's enough reagents to mix (-1 because one is already counted) */
|
||||
if (_reagents[i] > 0 && g_ultima->_saveGame->_reagents[i] < batches - 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ingredients::multiply(int batches) {
|
||||
assertMsg(checkMultiple(batches), "not enough reagents to multiply ingredients by %d\n", batches);
|
||||
for (int i = 0; i < REAG_MAX; i++) {
|
||||
if (_reagents[i] > 0) {
|
||||
g_ultima->_saveGame->_reagents[i] -= batches - 1;
|
||||
_reagents[i] += batches - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
Spells *g_spells;
|
||||
|
||||
Spells::Spells() : spellEffectCallback(nullptr) {
|
||||
g_spells = this;
|
||||
}
|
||||
|
||||
Spells::~Spells() {
|
||||
g_spells = nullptr;
|
||||
}
|
||||
|
||||
void Spells::spellSetEffectCallback(SpellEffectCallback callback) {
|
||||
spellEffectCallback = callback;
|
||||
}
|
||||
|
||||
const char *Spells::spellGetName(uint spell) const {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
return SPELL_LIST[spell]._name;
|
||||
}
|
||||
|
||||
int Spells::spellGetRequiredMP(uint spell) const {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
return SPELL_LIST[spell]._mp;
|
||||
}
|
||||
|
||||
LocationContext Spells::spellGetContext(uint spell) const {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
return SPELL_LIST[spell]._context;
|
||||
}
|
||||
|
||||
TransportContext Spells::spellGetTransportContext(uint spell) const {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
return SPELL_LIST[spell]._transportContext;
|
||||
}
|
||||
|
||||
Common::String Spells::spellGetErrorMessage(uint spell, SpellCastError error) {
|
||||
uint i;
|
||||
SpellCastError err = error;
|
||||
|
||||
/* try to find a more specific error message */
|
||||
if (err == CASTERR_WRONGCONTEXT) {
|
||||
switch (SPELL_LIST[spell]._context) {
|
||||
case CTX_COMBAT:
|
||||
err = CASTERR_COMBATONLY;
|
||||
break;
|
||||
case CTX_DUNGEON:
|
||||
err = CASTERR_DUNGEONONLY;
|
||||
break;
|
||||
case CTX_WORLDMAP:
|
||||
err = CASTERR_WORLDMAPONLY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* find the message that we're looking for and return it! */
|
||||
for (i = 0; i < sizeof(SPELL_ERROR_MSGS) / sizeof(SPELL_ERROR_MSGS[0]); i++) {
|
||||
if (err == SPELL_ERROR_MSGS[i].err)
|
||||
return Common::String(SPELL_ERROR_MSGS[i].msg);
|
||||
}
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
int Spells::spellMix(uint spell, const Ingredients *ingredients) {
|
||||
int regmask, reg;
|
||||
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
regmask = 0;
|
||||
for (reg = 0; reg < REAG_MAX; reg++) {
|
||||
if (ingredients->getReagent((Reagent) reg) > 0)
|
||||
regmask |= (1 << reg);
|
||||
}
|
||||
|
||||
if (regmask != SPELL_LIST[spell]._components)
|
||||
return 0;
|
||||
|
||||
g_ultima->_saveGame->_mixtures[spell]++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
Spell::Param Spells::spellGetParamType(uint spell) const {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
|
||||
return SPELL_LIST[spell]._paramType;
|
||||
}
|
||||
|
||||
bool Spells::isDebuggerActive() const {
|
||||
return g_ultima->getDebugger()->isActive();
|
||||
}
|
||||
|
||||
SpellCastError Spells::spellCheckPrerequisites(uint spell, int character) {
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
assertMsg(character >= 0 && character < g_ultima->_saveGame->_members, "character out of range: %d", character);
|
||||
|
||||
// Don't bother checking mix count and map when the spell
|
||||
// has been manually triggered from the debugger
|
||||
if (!isDebuggerActive()) {
|
||||
if (g_ultima->_saveGame->_mixtures[spell] == 0)
|
||||
return CASTERR_NOMIX;
|
||||
|
||||
if (g_context->_party->member(character)->getMp() < SPELL_LIST[spell]._mp)
|
||||
return CASTERR_MPTOOLOW;
|
||||
}
|
||||
|
||||
if ((g_context->_location->_context & SPELL_LIST[spell]._context) == 0)
|
||||
return CASTERR_WRONGCONTEXT;
|
||||
|
||||
if ((g_context->_transportContext & SPELL_LIST[spell]._transportContext) == 0)
|
||||
return CASTERR_FAILED;
|
||||
|
||||
return CASTERR_NOERROR;
|
||||
}
|
||||
|
||||
bool Spells::spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect) {
|
||||
int subject = (SPELL_LIST[spell]._paramType == Spell::PARAM_PLAYER) ? param : -1;
|
||||
PartyMember *p = g_context->_party->member(character);
|
||||
|
||||
assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
|
||||
assertMsg(character >= 0 && character < g_ultima->_saveGame->_members, "character out of range: %d", character);
|
||||
|
||||
*error = spellCheckPrerequisites(spell, character);
|
||||
|
||||
if (!isDebuggerActive())
|
||||
// Subtract the mixture for even trying to cast the spell
|
||||
AdjustValueMin(g_ultima->_saveGame->_mixtures[spell], -1, 0);
|
||||
|
||||
if (*error != CASTERR_NOERROR)
|
||||
return false;
|
||||
|
||||
// If there's a negate magic aura, spells fail!
|
||||
if (*g_context->_aura == Aura::NEGATE) {
|
||||
*error = CASTERR_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDebuggerActive())
|
||||
// Subtract the mp needed for the spell
|
||||
p->adjustMp(-SPELL_LIST[spell]._mp);
|
||||
|
||||
if (spellEffect) {
|
||||
int time;
|
||||
/* recalculate spell speed - based on 5/sec */
|
||||
float MP_OF_LARGEST_SPELL = 45;
|
||||
int spellMp = SPELL_LIST[spell]._mp;
|
||||
time = int(10000.0 / settings._spellEffectSpeed * spellMp / MP_OF_LARGEST_SPELL);
|
||||
soundPlay(SOUND_PREMAGIC_MANA_JUMBLE, false, time);
|
||||
EventHandler::wait_msecs(time);
|
||||
g_spells->spellEffect(spell + 'a', subject, SOUND_MAGIC);
|
||||
}
|
||||
|
||||
if (!(g_spells->*SPELL_LIST[spell]._spellFunc)(param)) {
|
||||
*error = CASTERR_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CombatController *Spells::spellCombatController() {
|
||||
CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
|
||||
return cc;
|
||||
}
|
||||
|
||||
void Spells::spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage) {
|
||||
CombatController *controller = spellCombatController();
|
||||
PartyMemberVector *party = controller->getParty();
|
||||
|
||||
MapTile tile = g_context->_location->_map->_tileSet->getByName(tilename)->getId();
|
||||
|
||||
int attackDamage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
|
||||
xu4_random((maxDamage + 1) - minDamage) + minDamage :
|
||||
maxDamage;
|
||||
|
||||
Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, (*party)[controller->getFocus()]->getCoords(),
|
||||
1, 11, Tile::canAttackOverTile, false);
|
||||
for (const auto &coords : path) {
|
||||
if (spellMagicAttackAt(coords, tile, attackDamage))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool Spells::spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage) {
|
||||
bool objectHit = false;
|
||||
// int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
|
||||
CombatMap *cm = getCombatMap();
|
||||
|
||||
Creature *creature = cm->creatureAt(coords);
|
||||
|
||||
if (!creature) {
|
||||
GameController::flashTile(coords, attackTile, 2);
|
||||
} else {
|
||||
objectHit = true;
|
||||
|
||||
/* show the 'hit' tile */
|
||||
soundPlay(SOUND_NPC_STRUCK);
|
||||
GameController::flashTile(coords, attackTile, 3);
|
||||
|
||||
|
||||
/* apply the damage to the creature */
|
||||
CombatController *controller = spellCombatController();
|
||||
controller->getCurrentPlayer()->dealDamage(creature, attackDamage);
|
||||
GameController::flashTile(coords, attackTile, 1);
|
||||
}
|
||||
|
||||
return objectHit;
|
||||
}
|
||||
|
||||
int Spells::spellAwaken(int player) {
|
||||
assertMsg(player < 8, "player out of range: %d", player);
|
||||
PartyMember *p = g_context->_party->member(player);
|
||||
|
||||
if ((player < g_context->_party->size()) && (p->getStatus() == STAT_SLEEPING)) {
|
||||
p->wakeUp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Spells::spellBlink(int dir) {
|
||||
int i,
|
||||
failed = 0,
|
||||
distance,
|
||||
diff,
|
||||
*var;
|
||||
Direction reverseDir = dirReverse((Direction)dir);
|
||||
MapCoords coords = g_context->_location->_coords;
|
||||
|
||||
/* Blink doesn't work near the mouth of the abyss */
|
||||
/* Note: This means you can teleport to Hythloth from the top of the map,
|
||||
and that you can teleport to the abyss from the left edge of the map,
|
||||
Unfortunately, this matches the bugs in the game. :( Consider fixing. */
|
||||
if (coords.x >= 192 && coords.y >= 192)
|
||||
return 0;
|
||||
|
||||
/* figure out what numbers we're working with */
|
||||
var = (dir & (DIR_WEST | DIR_EAST)) ? &coords.x : &coords.y;
|
||||
|
||||
/* find the distance we are going to move */
|
||||
distance = (*var) % 0x10;
|
||||
if (dir == DIR_EAST || dir == DIR_SOUTH)
|
||||
distance = 0x10 - distance;
|
||||
|
||||
/* see if we move another 16 spaces over */
|
||||
diff = 0x10 - distance;
|
||||
if ((diff > 0) && (xu4_random(diff * diff) > distance))
|
||||
distance += 0x10;
|
||||
|
||||
/* test our distance, and see if it works */
|
||||
for (i = 0; i < distance; i++)
|
||||
coords.move((Direction)dir, g_context->_location->_map);
|
||||
|
||||
i = distance;
|
||||
/* begin walking backward until you find a valid spot */
|
||||
while ((i-- > 0) && !g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS)->isWalkable())
|
||||
coords.move(reverseDir, g_context->_location->_map);
|
||||
|
||||
if (g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS)->isWalkable()) {
|
||||
/* we didn't move! */
|
||||
if (g_context->_location->_coords == coords)
|
||||
failed = 1;
|
||||
|
||||
g_context->_location->_coords = coords;
|
||||
} else {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
return (failed ? 0 : 1);
|
||||
}
|
||||
|
||||
int Spells::spellCure(int player) {
|
||||
assertMsg(player < 8, "player out of range: %d", player);
|
||||
|
||||
GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
|
||||
return g_context->_party->member(player)->heal(HT_CURE);
|
||||
}
|
||||
|
||||
int Spells::spellDispel(int dir) {
|
||||
MapTile *tile;
|
||||
MapCoords field;
|
||||
|
||||
/*
|
||||
* get the location of the avatar (or current party member, if in battle)
|
||||
*/
|
||||
g_context->_location->getCurrentPosition(&field);
|
||||
|
||||
/*
|
||||
* find where we want to dispel the field
|
||||
*/
|
||||
field.move((Direction)dir, g_context->_location->_map);
|
||||
|
||||
GameController::flashTile(field, "wisp", 2);
|
||||
/*
|
||||
* if there is a field annotation, remove it and replace it with a valid
|
||||
* replacement annotation. We do this because sometimes dungeon triggers
|
||||
* create annotations, that, if just removed, leave a wall tile behind
|
||||
* (or other unwalkable surface). So, we need to provide a valid replacement
|
||||
* annotation to fill in the gap :)
|
||||
*/
|
||||
Annotation::List a = g_context->_location->_map->_annotations->allAt(field);
|
||||
if (a.size() > 0) {
|
||||
for (auto &i : a) {
|
||||
if (i.getTile().getTileType()->canDispel()) {
|
||||
|
||||
/*
|
||||
* get a replacement tile for the field
|
||||
*/
|
||||
MapTile newTile(g_context->_location->getReplacementTile(field, i.getTile().getTileType()));
|
||||
|
||||
g_context->_location->_map->_annotations->remove(i);
|
||||
g_context->_location->_map->_annotations->add(field, newTile, false, true);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if the map tile itself is a field, overlay it with a replacement tile
|
||||
*/
|
||||
|
||||
tile = g_context->_location->_map->tileAt(field, WITHOUT_OBJECTS);
|
||||
if (!tile->getTileType()->canDispel())
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* get a replacement tile for the field
|
||||
*/
|
||||
MapTile newTile(g_context->_location->getReplacementTile(field, tile->getTileType()));
|
||||
|
||||
g_context->_location->_map->_annotations->add(field, newTile, false, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellEField(int param) {
|
||||
MapTile fieldTile(0);
|
||||
int fieldType;
|
||||
int dir;
|
||||
MapCoords coords;
|
||||
|
||||
/* Unpack fieldType and direction */
|
||||
fieldType = param >> 4;
|
||||
dir = param & 0xF;
|
||||
|
||||
/* Make sure params valid */
|
||||
switch (fieldType) {
|
||||
case ENERGYFIELD_FIRE:
|
||||
fieldTile = g_context->_location->_map->_tileSet->getByName("fire_field")->getId();
|
||||
break;
|
||||
case ENERGYFIELD_LIGHTNING:
|
||||
fieldTile = g_context->_location->_map->_tileSet->getByName("energy_field")->getId();
|
||||
break;
|
||||
case ENERGYFIELD_POISON:
|
||||
fieldTile = g_context->_location->_map->_tileSet->getByName("poison_field")->getId();
|
||||
break;
|
||||
case ENERGYFIELD_SLEEP:
|
||||
fieldTile = g_context->_location->_map->_tileSet->getByName("sleep_field")->getId();
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
g_context->_location->getCurrentPosition(&coords);
|
||||
|
||||
coords.move((Direction)dir, g_context->_location->_map);
|
||||
if (MAP_IS_OOB(g_context->_location->_map, coords))
|
||||
return 0;
|
||||
else {
|
||||
/*
|
||||
* Observed behaviour on Amiga version of Ultima IV:
|
||||
* Field cast on other field: Works, unless original field is lightning
|
||||
* in which case it doesn't.
|
||||
* Field cast on creature: Works, creature remains the visible tile
|
||||
* Field cast on top of field and then dispel = no fields left
|
||||
* The code below seems to produce this behaviour.
|
||||
*/
|
||||
const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_GROUND_OBJECTS);
|
||||
if (!tile->isWalkable()) return 0;
|
||||
|
||||
/* Get rid of old field, if any */
|
||||
Annotation::List a = g_context->_location->_map->_annotations->allAt(coords);
|
||||
if (a.size() > 0) {
|
||||
for (auto &i : a) {
|
||||
if (i.getTile().getTileType()->canDispel())
|
||||
g_context->_location->_map->_annotations->remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
g_context->_location->_map->_annotations->add(coords, fieldTile);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellFireball(int dir) {
|
||||
spellMagicAttack("hit_flash", (Direction)dir, 24, 128);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellGate(int phase) {
|
||||
const Coords *moongate;
|
||||
|
||||
GameController::flashTile(g_context->_location->_coords, "moongate", 2);
|
||||
|
||||
moongate = g_moongates->getGateCoordsForPhase(phase);
|
||||
if (moongate)
|
||||
g_context->_location->_coords = *moongate;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellHeal(int player) {
|
||||
assertMsg(player < 8, "player out of range: %d", player);
|
||||
|
||||
GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
|
||||
g_context->_party->member(player)->heal(HT_HEAL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellIceball(int dir) {
|
||||
spellMagicAttack("magic_flash", (Direction)dir, 32, 224);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellJinx(int unused) {
|
||||
g_context->_aura->set(Aura::JINX, 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellKill(int dir) {
|
||||
spellMagicAttack("whirlpool", (Direction)dir, -1, 232);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellLight(int unused) {
|
||||
g_context->_party->lightTorch(100, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellMMissle(int dir) {
|
||||
spellMagicAttack("miss_flash", (Direction)dir, 64, 16);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellNegate(int unused) {
|
||||
g_context->_aura->set(Aura::NEGATE, 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellOpen(int unused) {
|
||||
g_debugger->getChest();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellProtect(int unused) {
|
||||
g_context->_aura->set(Aura::PROTECTION, 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellRez(int player) {
|
||||
assertMsg(player < 8, "player out of range: %d", player);
|
||||
|
||||
return g_context->_party->member(player)->heal(HT_RESURRECT);
|
||||
}
|
||||
|
||||
int Spells::spellQuick(int unused) {
|
||||
g_context->_aura->set(Aura::QUICKNESS, 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellSleep(int unused) {
|
||||
CombatMap *cm = getCombatMap();
|
||||
CreatureVector creatures = cm->getCreatures();
|
||||
|
||||
/* try to put each creature to sleep */
|
||||
|
||||
for (auto *m : creatures) {
|
||||
Coords coords = m->getCoords();
|
||||
GameController::flashTile(coords, "wisp", 1);
|
||||
if ((m->getResists() != EFFECT_SLEEP) &&
|
||||
xu4_random(0xFF) >= m->getHp()) {
|
||||
soundPlay(SOUND_POISON_EFFECT);
|
||||
m->putToSleep();
|
||||
GameController::flashTile(coords, "sleep_field", 3);
|
||||
} else
|
||||
soundPlay(SOUND_EVADE);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellTremor(int unused) {
|
||||
CombatController *ct = spellCombatController();
|
||||
CreatureVector creatures = ct->getMap()->getCreatures();
|
||||
for (auto *m : creatures) {
|
||||
Coords coords = m->getCoords();
|
||||
//GameController::flashTile(coords, "rocks", 1);
|
||||
|
||||
/* creatures with over 192 hp are unaffected */
|
||||
if (m->getHp() > 192) {
|
||||
soundPlay(SOUND_EVADE);
|
||||
continue;
|
||||
} else {
|
||||
/* Deal maximum damage to creature */
|
||||
if (xu4_random(2) == 0) {
|
||||
soundPlay(SOUND_NPC_STRUCK);
|
||||
GameController::flashTile(coords, "hit_flash", 3);
|
||||
ct->getCurrentPlayer()->dealDamage(m, 0xFF);
|
||||
}
|
||||
/* Deal enough damage to creature to make it flee */
|
||||
else if (xu4_random(2) == 0) {
|
||||
soundPlay(SOUND_NPC_STRUCK);
|
||||
GameController::flashTile(coords, "hit_flash", 2);
|
||||
if (m->getHp() > 23)
|
||||
ct->getCurrentPlayer()->dealDamage(m, m->getHp() - 23);
|
||||
} else {
|
||||
soundPlay(SOUND_EVADE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellUndead(int unused) {
|
||||
CombatController *ct = spellCombatController();
|
||||
CreatureVector creatures = ct->getMap()->getCreatures();
|
||||
|
||||
for (auto *m : creatures) {
|
||||
if (m && m->isUndead() && xu4_random(2) == 0)
|
||||
m->setHp(23);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellView(int unsued) {
|
||||
peer(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellWinds(int fromdir) {
|
||||
g_context->_windDirection = fromdir;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Spells::spellXit(int unused) {
|
||||
if (!g_context->_location->_map->isWorldMap()) {
|
||||
g_screen->screenMessage("Leaving...\n");
|
||||
g_game->exitToParentMap();
|
||||
g_music->playMapMusic();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Spells::spellYup(int unused) {
|
||||
MapCoords coords = g_context->_location->_coords;
|
||||
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
|
||||
|
||||
/* can't cast in the Abyss */
|
||||
if (g_context->_location->_map->_id == MAP_ABYSS)
|
||||
return 0;
|
||||
/* staying in the dungeon */
|
||||
else if (coords.z > 0) {
|
||||
assert(dungeon);
|
||||
for (int i = 0; i < 0x20; i++) {
|
||||
coords = MapCoords(xu4_random(8), xu4_random(8), g_context->_location->_coords.z - 1);
|
||||
if (dungeon->validTeleportLocation(coords)) {
|
||||
g_context->_location->_coords = coords;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* exiting the dungeon */
|
||||
} else {
|
||||
g_screen->screenMessage("Leaving...\n");
|
||||
g_game->exitToParentMap();
|
||||
g_music->playMapMusic();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* didn't find a place to go, failed! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Spells::spellZdown(int unused) {
|
||||
MapCoords coords = g_context->_location->_coords;
|
||||
Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
|
||||
assert(dungeon);
|
||||
|
||||
/* can't cast in the Abyss */
|
||||
if (g_context->_location->_map->_id == MAP_ABYSS)
|
||||
return 0;
|
||||
/* can't go lower than level 8 */
|
||||
else if (coords.z >= 7)
|
||||
return 0;
|
||||
else {
|
||||
for (int i = 0; i < 0x20; i++) {
|
||||
coords = MapCoords(xu4_random(8), xu4_random(8), g_context->_location->_coords.z + 1);
|
||||
if (dungeon->validTeleportLocation(coords)) {
|
||||
g_context->_location->_coords = coords;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* didn't find a place to go, failed! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Spell *Spells::getSpell(int i) const {
|
||||
return &SPELL_LIST[i];
|
||||
}
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
201
engines/ultima/ultima4/game/spell.h
Normal file
201
engines/ultima/ultima4/game/spell.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/* 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_SPELL_H
|
||||
#define ULTIMA4_GAME_SPELL_H
|
||||
|
||||
#include "ultima/ultima4/game/context.h"
|
||||
#include "ultima/ultima4/map/location.h"
|
||||
#include "ultima/ultima4/map/map.h"
|
||||
#include "ultima/ultima4/filesys/savegame.h"
|
||||
#include "ultima/ultima4/sound/sound.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
enum SpellCastError {
|
||||
CASTERR_NOERROR, /* success */
|
||||
CASTERR_NOMIX, /* no mixture available */
|
||||
CASTERR_MPTOOLOW, /* caster doesn't have enough mp */
|
||||
CASTERR_FAILED, /* the spell failed */
|
||||
CASTERR_WRONGCONTEXT, /* generic 'wrong-context' error (generrally finds the correct
|
||||
context error message on its own) */
|
||||
CASTERR_COMBATONLY, /* e.g. spell must be cast in combat */
|
||||
CASTERR_DUNGEONONLY, /* e.g. spell must be cast in dungeons */
|
||||
CASTERR_WORLDMAPONLY /* e.g. spell must be cast on the world map */
|
||||
};
|
||||
|
||||
/**
|
||||
* Field types for the Energy field spell
|
||||
*/
|
||||
enum EnergyFieldType {
|
||||
ENERGYFIELD_NONE,
|
||||
ENERGYFIELD_FIRE,
|
||||
ENERGYFIELD_LIGHTNING,
|
||||
ENERGYFIELD_POISON,
|
||||
ENERGYFIELD_SLEEP
|
||||
};
|
||||
|
||||
/**
|
||||
* The ingredients for a spell mixture.
|
||||
*/
|
||||
class Ingredients {
|
||||
public:
|
||||
Ingredients();
|
||||
bool addReagent(Reagent reagent);
|
||||
bool removeReagent(Reagent reagent);
|
||||
int getReagent(Reagent reagent) const;
|
||||
void revert();
|
||||
bool checkMultiple(int mixes) const;
|
||||
void multiply(int mixes);
|
||||
|
||||
private:
|
||||
unsigned short _reagents[REAG_MAX];
|
||||
};
|
||||
|
||||
class Spells;
|
||||
typedef int (Spells::*SpellProc)(int);
|
||||
|
||||
struct Spell {
|
||||
enum Param {
|
||||
PARAM_NONE, ///< None
|
||||
PARAM_PLAYER, ///< number of a player required
|
||||
PARAM_DIR, ///< direction required
|
||||
PARAM_TYPEDIR, ///< type of field and direction required (energy field)
|
||||
PARAM_PHASE, ///< phase required (gate)
|
||||
PARAM_FROMDIR ///< direction from required (winds)
|
||||
};
|
||||
|
||||
enum SpecialEffects {
|
||||
SFX_NONE, ///< none
|
||||
SFX_INVERT, ///< invert the screen (moongates, most normal spells)
|
||||
SFX_TREMOR ///< tremor spell
|
||||
};
|
||||
|
||||
const char *_name;
|
||||
int _components;
|
||||
LocationContext _context;
|
||||
TransportContext _transportContext;
|
||||
SpellProc _spellFunc;
|
||||
Param _paramType;
|
||||
int _mp;
|
||||
};
|
||||
|
||||
typedef void (*SpellEffectCallback)(int spell, int player, Sound sound);
|
||||
#define N_SPELLS 26
|
||||
|
||||
class Spells {
|
||||
private:
|
||||
static const Spell SPELL_LIST[N_SPELLS];
|
||||
SpellEffectCallback spellEffectCallback;
|
||||
private:
|
||||
int spellAwaken(int player);
|
||||
int spellBlink(int dir);
|
||||
int spellCure(int player);
|
||||
int spellDispel(int dir);
|
||||
int spellEField(int param);
|
||||
int spellFireball(int dir);
|
||||
int spellGate(int phase);
|
||||
int spellHeal(int player);
|
||||
int spellIceball(int dir);
|
||||
int spellJinx(int unused);
|
||||
int spellKill(int dir);
|
||||
int spellLight(int unused);
|
||||
int spellMMissle(int dir);
|
||||
int spellNegate(int unused);
|
||||
int spellOpen(int unused);
|
||||
int spellProtect(int unused);
|
||||
int spellRez(int player);
|
||||
int spellQuick(int unused);
|
||||
int spellSleep(int unused);
|
||||
int spellTremor(int unused);
|
||||
int spellUndead(int unused);
|
||||
int spellView(int unsued);
|
||||
int spellWinds(int fromdir);
|
||||
int spellXit(int unused);
|
||||
int spellYup(int unused);
|
||||
int spellZdown(int unused);
|
||||
private:
|
||||
CombatController *spellCombatController();
|
||||
|
||||
/**
|
||||
* Makes a special magic ranged attack in the given direction
|
||||
*/
|
||||
void spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage);
|
||||
bool spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage);
|
||||
|
||||
LocationContext spellGetContext(uint spell) const;
|
||||
TransportContext spellGetTransportContext(uint spell) const;
|
||||
|
||||
/**
|
||||
* Returns true if the debugger is active
|
||||
*/
|
||||
bool isDebuggerActive() const;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Spells();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~Spells();
|
||||
|
||||
void spellSetEffectCallback(SpellEffectCallback callback);
|
||||
|
||||
void spellEffect(int spell, int player, Sound sound) {
|
||||
(spellEffectCallback)(spell, player, sound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix reagents for a spell. Fails and returns false if the reagents
|
||||
* selected were not correct.
|
||||
*/
|
||||
int spellMix(uint spell, const Ingredients *ingredients);
|
||||
|
||||
/**
|
||||
* Casts spell. Fails and returns false if the spell cannot be cast.
|
||||
* The error code is updated with the reason for failure.
|
||||
*/
|
||||
bool spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect);
|
||||
|
||||
Common::String spellGetErrorMessage(uint spell, SpellCastError error);
|
||||
|
||||
const char *spellGetName(uint spell) const;
|
||||
|
||||
/**
|
||||
* Checks some basic prerequistes for casting a spell. Returns an
|
||||
* error if no mixture is available, the context is invalid, or the
|
||||
* character doesn't have enough magic points.
|
||||
*/
|
||||
SpellCastError spellCheckPrerequisites(uint spell, int character);
|
||||
Spell::Param spellGetParamType(uint spell) const;
|
||||
int spellGetRequiredMP(uint spell) const;
|
||||
const Spell *getSpell(int i) const;
|
||||
};
|
||||
|
||||
extern Spells *g_spells;
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
164
engines/ultima/ultima4/game/weapon.cpp
Normal file
164
engines/ultima/ultima4/game/weapon.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/* 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/weapon.h"
|
||||
#include "ultima/ultima4/core/config.h"
|
||||
#include "ultima/ultima4/game/names.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
Weapons *g_weapons;
|
||||
|
||||
Weapons::Weapons() : _confLoaded(false) {
|
||||
g_weapons = this;
|
||||
}
|
||||
|
||||
Weapons::~Weapons() {
|
||||
for (uint idx = 0; idx < size(); ++idx)
|
||||
delete (*this)[idx];
|
||||
|
||||
g_weapons = nullptr;
|
||||
}
|
||||
|
||||
const Weapon *Weapons::get(WeaponType w) {
|
||||
// Load in XML if it hasn't been already
|
||||
loadConf();
|
||||
|
||||
if (static_cast<unsigned>(w) >= size())
|
||||
return nullptr;
|
||||
return (*this)[w];
|
||||
}
|
||||
|
||||
const Weapon *Weapons::get(const Common::String &name) {
|
||||
// Load in XML if it hasn't been already
|
||||
loadConf();
|
||||
|
||||
for (uint i = 0; i < size(); i++) {
|
||||
if (scumm_stricmp(name.c_str(), (*this)[i]->_name.c_str()) == 0)
|
||||
return (*this)[i];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Weapons::loadConf() {
|
||||
if (_confLoaded)
|
||||
return;
|
||||
|
||||
_confLoaded = true;
|
||||
const Config *config = Config::getInstance();
|
||||
|
||||
Std::vector<ConfigElement> weaponConfs = config->getElement("weapons").getChildren();
|
||||
for (const auto &i : weaponConfs) {
|
||||
if (i.getName() != "weapon")
|
||||
continue;
|
||||
|
||||
WeaponType weaponType = static_cast<WeaponType>(size());
|
||||
push_back(new Weapon(weaponType, i));
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
Weapon::Weapon(WeaponType weaponType, const ConfigElement &conf)
|
||||
: _type(weaponType)
|
||||
, _name(conf.getString("name"))
|
||||
, _abbr(conf.getString("abbr"))
|
||||
, _canUse(0xFF)
|
||||
, _range(0)
|
||||
, _damage(conf.getInt("damage"))
|
||||
, _hitTile("hit_flash")
|
||||
, _missTile("miss_flash")
|
||||
, _leaveTile("")
|
||||
, _flags(0) {
|
||||
static const struct {
|
||||
const char *name;
|
||||
uint flag;
|
||||
} booleanAttributes[] = {
|
||||
{ "lose", WEAP_LOSE },
|
||||
{ "losewhenranged", WEAP_LOSEWHENRANGED },
|
||||
{ "choosedistance", WEAP_CHOOSEDISTANCE },
|
||||
{ "alwayshits", WEAP_ALWAYSHITS },
|
||||
{ "magic", WEAP_MAGIC },
|
||||
{ "attackthroughobjects", WEAP_ATTACKTHROUGHOBJECTS },
|
||||
{ "returns", WEAP_RETURNS },
|
||||
{ "dontshowtravel", WEAP_DONTSHOWTRAVEL }
|
||||
};
|
||||
|
||||
/* Get the range of the weapon, whether it is absolute or normal range */
|
||||
Common::String range = conf.getString("range");
|
||||
if (range.empty()) {
|
||||
range = conf.getString("absolute_range");
|
||||
if (!range.empty())
|
||||
_flags |= WEAP_ABSOLUTERANGE;
|
||||
}
|
||||
if (range.empty())
|
||||
error("malformed weapons.xml file: range or absolute_range not found for weapon %s", _name.c_str());
|
||||
|
||||
_range = atoi(range.c_str());
|
||||
|
||||
/* Load weapon attributes */
|
||||
for (unsigned at = 0; at < sizeof(booleanAttributes) / sizeof(booleanAttributes[0]); at++) {
|
||||
if (conf.getBool(booleanAttributes[at].name)) {
|
||||
_flags |= booleanAttributes[at].flag;
|
||||
}
|
||||
}
|
||||
|
||||
/* Load hit tiles */
|
||||
if (conf.exists("hittile"))
|
||||
_hitTile = conf.getString("hittile");
|
||||
|
||||
/* Load miss tiles */
|
||||
if (conf.exists("misstile"))
|
||||
_missTile = conf.getString("misstile");
|
||||
|
||||
/* Load leave tiles */
|
||||
if (conf.exists("leavetile")) {
|
||||
_leaveTile = conf.getString("leavetile");
|
||||
}
|
||||
|
||||
Std::vector<ConfigElement> contraintConfs = conf.getChildren();
|
||||
for (const auto &i : contraintConfs) {
|
||||
byte mask = 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)
|
||||
mask = (1 << cl);
|
||||
}
|
||||
if (mask == 0 && scumm_stricmp(i.getString("class").c_str(), "all") == 0)
|
||||
mask = 0xFF;
|
||||
if (mask == 0) {
|
||||
error("malformed weapons.xml file: constraint has unknown class %s",
|
||||
i.getString("class").c_str());
|
||||
}
|
||||
if (i.getBool("canuse"))
|
||||
_canUse |= mask;
|
||||
else
|
||||
_canUse &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
157
engines/ultima/ultima4/game/weapon.h
Normal file
157
engines/ultima/ultima4/game/weapon.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 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_WEAPON_H
|
||||
#define ULTIMA4_GAME_WEAPON_H
|
||||
|
||||
#include "ultima/ultima4/filesys/savegame.h"
|
||||
#include "ultima/shared/std/containers.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima4 {
|
||||
|
||||
class ConfigElement;
|
||||
class Weapons;
|
||||
|
||||
class Weapon {
|
||||
friend class Weapons;
|
||||
public:
|
||||
/**< Flags affecting weapon's behavior. @see Weapon::flags */
|
||||
enum Flags {
|
||||
WEAP_LOSE = 0x0001, /**< lost when used */
|
||||
WEAP_LOSEWHENRANGED = 0x0002, /**< lost when used for ranged attack */
|
||||
WEAP_CHOOSEDISTANCE = 0x0004, /**< allows player to choose attack distance */
|
||||
WEAP_ALWAYSHITS = 0x0008, /**< always hits it's target */
|
||||
WEAP_MAGIC = 0x0010, /**< is magical */
|
||||
WEAP_ATTACKTHROUGHOBJECTS = 0x0040, /**< can attack through solid objects */
|
||||
WEAP_ABSOLUTERANGE = 0x0080, /**< range is absolute (only works at specific distance) */
|
||||
WEAP_RETURNS = 0x0100, /**< returns to user after used/thrown */
|
||||
WEAP_DONTSHOWTRAVEL = 0x0200 /**< do not show animations when attacking */
|
||||
};
|
||||
|
||||
public:
|
||||
WeaponType getType() const {
|
||||
return _type;
|
||||
}
|
||||
const Common::String &getName() const {
|
||||
return _name;
|
||||
}
|
||||
const Common::String &getAbbrev() const {
|
||||
return _abbr;
|
||||
}
|
||||
bool canReady(ClassType klass) const {
|
||||
return (_canUse & (1 << klass)) != 0;
|
||||
}
|
||||
int getRange() const {
|
||||
return _range;
|
||||
}
|
||||
int getDamage() const {
|
||||
return _damage;
|
||||
}
|
||||
const Common::String &getHitTile() const {
|
||||
return _hitTile;
|
||||
}
|
||||
const Common::String &getMissTile() const {
|
||||
return _missTile;
|
||||
}
|
||||
const Common::String &leavesTile() const {
|
||||
return _leaveTile;
|
||||
}
|
||||
unsigned short getFlags() const {
|
||||
return _flags;
|
||||
}
|
||||
|
||||
bool loseWhenUsed() const {
|
||||
return _flags & WEAP_LOSE;
|
||||
}
|
||||
bool loseWhenRanged() const {
|
||||
return _flags & WEAP_LOSEWHENRANGED;
|
||||
}
|
||||
bool canChooseDistance() const {
|
||||
return _flags & WEAP_CHOOSEDISTANCE;
|
||||
}
|
||||
bool alwaysHits() const {
|
||||
return _flags & WEAP_ALWAYSHITS;
|
||||
}
|
||||
bool isMagic() const {
|
||||
return _flags & WEAP_MAGIC;
|
||||
}
|
||||
bool canAttackThroughObjects() const {
|
||||
return _flags & WEAP_ATTACKTHROUGHOBJECTS;
|
||||
}
|
||||
bool rangeAbsolute() const {
|
||||
return _flags & WEAP_ABSOLUTERANGE;
|
||||
}
|
||||
bool returns() const {
|
||||
return _flags & WEAP_RETURNS;
|
||||
}
|
||||
bool showTravel() const {
|
||||
return !(_flags & WEAP_DONTSHOWTRAVEL);
|
||||
}
|
||||
|
||||
private:
|
||||
Weapon(WeaponType weaponType, const ConfigElement &conf);
|
||||
|
||||
WeaponType _type;
|
||||
Common::String _name;
|
||||
Common::String _abbr; /**< abbreviation for the weapon */
|
||||
byte _canUse; /**< bitmask of classes that can use weapon */
|
||||
int _range; /**< range of weapon */
|
||||
int _damage; /**< damage of weapon */
|
||||
Common::String _hitTile; /**< tile to display a hit */
|
||||
Common::String _missTile; /**< tile to display a miss */
|
||||
Common::String _leaveTile; /**< if the weapon leaves a tile, the tile #, zero otherwise */
|
||||
unsigned short _flags;
|
||||
};
|
||||
|
||||
class Weapons : public Common::Array<Weapon *> {
|
||||
private:
|
||||
bool _confLoaded;
|
||||
|
||||
void loadConf();
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Weapons();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~Weapons();
|
||||
|
||||
/**
|
||||
* Returns weapon by WeaponType.
|
||||
*/
|
||||
const Weapon *get(WeaponType w);
|
||||
|
||||
/**
|
||||
* Returns weapon that has the given name
|
||||
*/
|
||||
const Weapon *get(const Common::String &name);
|
||||
};
|
||||
|
||||
extern Weapons *g_weapons;
|
||||
|
||||
} // End of namespace Ultima4
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user