Initial commit

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

View File

@@ -0,0 +1,303 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/game/person.h"
#include "ultima/ultima4/game/script.h"
namespace Ultima {
namespace Ultima4 {
const uint Conversation::BUFFERLEN = 16;
ResponseParts *g_responseParts;
ResponseParts::ResponseParts() :
NONE("<NONE>", "", true),
ASK("<ASK>", "", true),
END("<END>", "", true),
ATTACK("<ATTACK>", "", true),
BRAGGED("<BRAGGED>", "", true),
HUMBLE("<HUMBLE>", "", true),
ADVANCELEVELS("<ADVANCELEVELS>", "", true),
HEALCONFIRM("<HEALCONFIRM>", "", true),
STARTMUSIC_LB("<STARTMUSIC_LB>", "", true),
STARTMUSIC_HW("<STARTMUSIC_HW>", "", true),
STOPMUSIC("<STOPMUSIC>", "", true),
HAWKWIND("<HAWKWIND>", "", true) {
g_responseParts = this;
}
ResponseParts::~ResponseParts() {
g_responseParts = nullptr;
}
/*-------------------------------------------------------------------*/
Response::Response(const Common::String &response) : _references(0) {
add(response);
}
void Response::add(const ResponsePart &part) {
_parts.push_back(part);
}
const Std::vector<ResponsePart> &Response::getParts() const {
return _parts;
}
Response::operator Common::String() const {
Common::String result;
for (const auto &i : _parts) {
result += i;
}
return result;
}
Response *Response::addref() {
_references++;
return this;
}
void Response::release() {
_references--;
if (_references <= 0)
delete this;
}
ResponsePart::ResponsePart(const Common::String &value, const Common::String &arg, bool command) {
_value = value;
_arg = arg;
_command = command;
}
ResponsePart::operator Common::String() const {
return _value;
}
bool ResponsePart::operator==(const ResponsePart &rhs) const {
return _value == rhs._value;
}
bool ResponsePart::isCommand() const {
return _command;
}
DynamicResponse::DynamicResponse(Response * (*generator)(const DynamicResponse *), const Common::String &param) :
Response(""), _param(param) {
_generator = generator;
_currentResponse = nullptr;
}
DynamicResponse::~DynamicResponse() {
if (_currentResponse)
delete _currentResponse;
}
const Std::vector<ResponsePart> &DynamicResponse::getParts() const {
// blah, must cast away constness
const_cast<DynamicResponse *>(this)->_currentResponse = (*_generator)(this);
return _currentResponse->getParts();
}
/*
* Dialogue::Question class
*/
Dialogue::Question::Question(const Common::String &txt, Response *yes, Response *no) :
_text(txt), _yesResp(yes->addref()), _noResp(no->addref()) {}
Common::String Dialogue::Question::getText() {
return _text;
}
Response *Dialogue::Question::getResponse(bool yes) {
if (yes)
return _yesResp;
return _noResp;
}
/*
* Dialogue::Keyword class
*/
Dialogue::Keyword::Keyword(const Common::String &kw, Response *resp) :
_keyword(kw), _response(resp->addref()) {
trim(_keyword);
lowercase(_keyword);
}
Dialogue::Keyword::Keyword(const Common::String &kw, const Common::String &resp) :
_keyword(kw), _response((new Response(resp))->addref()) {
trim(_keyword);
lowercase(_keyword);
}
Dialogue::Keyword::~Keyword() {
_response->release();
}
bool Dialogue::Keyword::operator==(const Common::String &kw) const {
// minimum 4-character "guessing"
int testLen = (_keyword.size() < 4) ? _keyword.size() : 4;
// exception: empty keyword only matches empty Common::String (alias for 'bye')
if (testLen == 0 && kw.size() > 0)
return false;
if (scumm_strnicmp(kw.c_str(), _keyword.c_str(), testLen) == 0)
return true;
return false;
}
/*
* Dialogue class
*/
Dialogue::Dialogue()
: _intro(nullptr)
, _longIntro(nullptr)
, _defaultAnswer(nullptr)
, _question(nullptr) {
}
Dialogue::~Dialogue() {
for (auto &i : _keywords) {
delete i._value;
}
}
void Dialogue::addKeyword(const Common::String &kw, Response *response) {
if (_keywords.find(kw) != _keywords.end())
delete _keywords[kw];
_keywords[kw] = new Keyword(kw, response);
}
Dialogue::Keyword *Dialogue::operator[](const Common::String &kw) {
KeywordMap::iterator i = _keywords.find(kw);
// If they entered the keyword verbatim, return it!
if (i != _keywords.end())
return i->_value;
// Otherwise, go find one that fits the description.
else {
for (i = _keywords.begin(); i != _keywords.end(); i++) {
if ((*i->_value) == kw)
return i->_value;
}
}
return nullptr;
}
const ResponsePart &Dialogue::getAction() const {
int prob = xu4_random(0x100);
/* Does the person turn away from/attack you? */
if (prob >= _turnAwayProb)
return g_responseParts->NONE;
else {
if (_attackProb - prob < 0x40)
return g_responseParts->END;
else
return g_responseParts->ATTACK;
}
}
Common::String Dialogue::dump(const Common::String &arg) {
Common::String result;
if (arg == "") {
result = "keywords:\n";
for (const auto &i : _keywords) {
result += i._key + "\n";
}
} else {
if (_keywords.find(arg) != _keywords.end())
result = static_cast<Common::String>(*_keywords[arg]->getResponse());
}
return result;
}
/*
* Conversation class
*/
Conversation::Conversation() : _state(INTRO), _script(new Script()),
_question(nullptr), _quant(0), _player(0), _price(0) {
#ifdef IOS_ULTIMA4
U4IOS::incrementConversationCount();
#endif
}
Conversation::~Conversation() {
#ifdef IOS_ULTIMA4
U4IOS::decrementConversationCount();
#endif
delete _script;
}
Conversation::InputType Conversation::getInputRequired(int *bufferlen) {
switch (_state) {
case BUY_QUANTITY:
case SELL_QUANTITY: {
*bufferlen = 2;
return INPUT_STRING;
}
case TALK:
case BUY_PRICE:
case TOPIC: {
*bufferlen = BUFFERLEN;
return INPUT_STRING;
}
case GIVEBEGGAR: {
*bufferlen = 2;
return INPUT_STRING;
}
case ASK:
case ASKYESNO: {
*bufferlen = 3;
return INPUT_STRING;
}
case VENDORQUESTION:
case BUY_ITEM:
case SELL_ITEM:
case CONFIRMATION:
case CONTINUEQUESTION:
case PLAYER:
return INPUT_CHARACTER;
case ATTACK:
case DONE:
case INTRO:
case FULLHEAL:
case ADVANCELEVELS:
return INPUT_NONE;
}
error("invalid state: %d", _state);
return INPUT_NONE;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_CONVERSATION_DIALOGUELOADER_H
#define ULTIMA4_CONVERSATION_DIALOGUELOADER_H
#include "common/hashmap.h"
#include "common/str.h"
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
class Dialogue;
/**
* The generic dialogue loader interface. Different dialogue
* loaders should override the load method to load dialogues from
* different sources (.tlk files, xml config elements, etc.). They
* must also register themselves with registerLoader for one or more
* source types. By convention, the source type of load() and
* registerLoader() is an xu4-specific mime type.
* The two main types used are application/x-u4tlk and text/x-u4cfg.
*/
class DialogueLoader {
public:
virtual ~DialogueLoader() {}
virtual Dialogue *load(Common::SeekableReadStream *source) = 0;
};
class DialogueLoaders {
private:
Common::HashMap<Common::String, DialogueLoader *> _loaders;
public:
static DialogueLoader *getLoader(const Common::String &mimeType);
public:
DialogueLoaders();
~DialogueLoaders();
void registerLoader(DialogueLoader *loader, const Common::String &mimeType) {
_loaders[mimeType] = loader;
}
DialogueLoader *operator[](const Common::String &mimeType) {
return _loaders[mimeType];
}
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,143 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/game/context.h"
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader_hw.h"
#include "ultima/ultima4/game/player.h"
#include "ultima/ultima4/filesys/savegame.h"
#include "ultima/ultima4/filesys/u4file.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/ultima4.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima4 {
Response *hawkwindGetAdvice(const DynamicResponse *kw);
Response *hawkwindGetIntro(const DynamicResponse *dynResp);
/* Hawkwind text indexes */
#define HW_SPEAKONLYWITH 40
#define HW_RETURNWHEN 41
#define HW_ISREVIVED 42
#define HW_WELCOME 43
#define HW_GREETING1 44
#define HW_GREETING2 45
#define HW_PROMPT 46
#define HW_DEFAULT 49
#define HW_ALREADYAVATAR 50
#define HW_GOTOSHRINE 51
#define HW_BYE 52
/**
* A special case dialogue loader for Hawkwind.
*/
Dialogue *U4HWDialogueLoader::load(Common::SeekableReadStream *source) {
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
hawkwindText = u4read_stringtable("hawkwind");
Dialogue *dlg = new Dialogue();
dlg->setTurnAwayProb(0);
dlg->setName("Hawkwind");
dlg->setPronoun("He");
dlg->setPrompt(hawkwindText[HW_PROMPT]);
Response *intro = new DynamicResponse(&hawkwindGetIntro);
dlg->setIntro(intro);
dlg->setLongIntro(intro);
dlg->setDefaultAnswer(new Response(Common::String("\n" + hawkwindText[HW_DEFAULT])));
for (int v = 0; v < VIRT_MAX; v++) {
Common::String virtue(getVirtueName((Virtue) v));
lowercase(virtue);
virtue = virtue.substr(0, 4);
dlg->addKeyword(virtue, new DynamicResponse(&hawkwindGetAdvice, virtue));
}
Response *bye = new Response(hawkwindText[HW_BYE]);
bye->add(g_responseParts->STOPMUSIC);
bye->add(g_responseParts->END);
dlg->addKeyword("bye", bye);
dlg->addKeyword("", bye);
return dlg;
}
/**
* Generate the appropriate response when the player asks Lord British
* for help. The help text depends on the current party status; when
* one quest item is complete, Lord British provides some direction to
* the next one.
*/
Response *hawkwindGetAdvice(const DynamicResponse *dynResp) {
Common::String text;
int virtue = -1, virtueLevel = -1;
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
/* check if asking about a virtue */
for (int v = 0; v < VIRT_MAX; v++) {
if (scumm_strnicmp(dynResp->getParam().c_str(), getVirtueName((Virtue) v), 4) == 0) {
virtue = v;
virtueLevel = g_ultima->_saveGame->_karma[v];
break;
}
}
if (virtue != -1) {
text = "\n\n";
if (virtueLevel == 0)
text += hawkwindText[HW_ALREADYAVATAR] + "\n";
else if (virtueLevel < 80)
text += hawkwindText[(virtueLevel / 20) * 8 + virtue];
else if (virtueLevel < 99)
text += hawkwindText[3 * 8 + virtue];
else /* virtueLevel >= 99 */
text = hawkwindText[4 * 8 + virtue] + hawkwindText[HW_GOTOSHRINE];
} else {
text = Common::String("\n") + hawkwindText[HW_DEFAULT];
}
return new Response(text);
}
Response *hawkwindGetIntro(const DynamicResponse *dynResp) {
Response *intro = new Response("");
Std::vector<Common::String> &hawkwindText = g_ultima->_hawkwindText;
if (g_context->_party->member(0)->getStatus() == STAT_SLEEPING ||
g_context->_party->member(0)->getStatus() == STAT_DEAD) {
intro->add(hawkwindText[HW_SPEAKONLYWITH] + g_context->_party->member(0)->getName() +
hawkwindText[HW_RETURNWHEN] + g_context->_party->member(0)->getName() +
hawkwindText[HW_ISREVIVED]);
intro->add(g_responseParts->END);
} else {
intro->add(g_responseParts->STARTMUSIC_HW);
intro->add(g_responseParts->HAWKWIND);
intro->add(hawkwindText[HW_WELCOME] + g_context->_party->member(0)->getName() +
hawkwindText[HW_GREETING1] + hawkwindText[HW_GREETING2]);
}
return intro;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,162 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/conversation/conversation.h"
#include "ultima/ultima4/conversation/dialogueloader_tlk.h"
#include "ultima/shared/std/containers.h"
#include "common/stream.h"
namespace Ultima {
namespace Ultima4 {
/**
* A dialogue loader for standard u4dos .tlk files.
*/
Dialogue *U4TlkDialogueLoader::load(Common::SeekableReadStream *source) {
enum QTrigger {
NONE = 0,
JOB = 3,
HEALTH = 4,
KEYWORD1 = 5,
KEYWORD2 = 6
};
/* there's no dialogues left in the file */
char tlk_buffer[288];
if (source->read(tlk_buffer, sizeof(tlk_buffer)) != sizeof(tlk_buffer))
return nullptr;
char *ptr = &tlk_buffer[3];
Std::vector<Common::String> strings;
for (int i = 0; i < 12; i++) {
strings.push_back(ptr);
ptr += strlen(ptr) + 1;
}
Dialogue *dlg = new Dialogue();
byte prob = tlk_buffer[2];
QTrigger qtrigger = QTrigger(tlk_buffer[0]);
bool humilityTestQuestion = tlk_buffer[1] == 1;
dlg->setTurnAwayProb(prob);
dlg->setName(strings[0]);
dlg->setPronoun(strings[1]);
dlg->setPrompt("\nYour Interest:\n");
// Fix the actor description Common::String, converting the first character
// to lower-case.
strings[2].setChar(tolower(strings[2][0]), 0);
// ... then replace any newlines in the Common::String with spaces
size_t index = strings[2].find("\n");
while (index != Common::String::npos) {
strings[2].setChar(' ', index);
index = strings[2].find("\n");
}
// ... then append a period to the end of the Common::String if one does
// not already exist
if (!Common::isPunct(strings[2][strings[2].size() - 1]))
strings[2] = strings[2] + Common::String(".");
// ... and finally, a few characters in the game have descriptions
// that do not begin with a definite (the) or indefinite (a/an)
// article. On those characters, insert the appropriate article.
if ((strings[0] == "Iolo")
|| (strings[0] == "Tracie")
|| (strings[0] == "Dupre")
|| (strings[0] == "Traveling Dan"))
strings[2] = Common::String("a ") + strings[2];
Common::String introBase = Common::String("\nYou meet ") + strings[2] + "\n";
dlg->setIntro(new Response(introBase + dlg->getPrompt()));
dlg->setLongIntro(new Response(introBase +
"\n" + dlg->getPronoun() + " says: I am " + dlg->getName() + "\n"
+ dlg->getPrompt()));
dlg->setDefaultAnswer(new Response("That I cannot\nhelp thee with."));
Response *yes = new Response(strings[8]);
Response *no = new Response(strings[9]);
if (humilityTestQuestion) {
yes->add(g_responseParts->BRAGGED);
no->add(g_responseParts->HUMBLE);
}
dlg->setQuestion(new Dialogue::Question(strings[7], yes, no));
// one of the following four keywords triggers the speaker's question
Response *job = new Response(Common::String("\n") + strings[3]);
Response *health = new Response(Common::String("\n") + strings[4]);
Response *kw1 = new Response(Common::String("\n") + strings[5]);
Response *kw2 = new Response(Common::String("\n") + strings[6]);
switch (qtrigger) {
case JOB:
job->add(g_responseParts->ASK);
break;
case HEALTH:
health->add(g_responseParts->ASK);
break;
case KEYWORD1:
kw1->add(g_responseParts->ASK);
break;
case KEYWORD2:
kw2->add(g_responseParts->ASK);
break;
case NONE:
default:
break;
}
dlg->addKeyword("job", job);
dlg->addKeyword("heal", health);
dlg->addKeyword(strings[10], kw1);
dlg->addKeyword(strings[11], kw2);
// NOTE: We let the talker's custom keywords override the standard
// keywords like HEAL and LOOK. This behavior differs from u4dos,
// but fixes a couple conversation files which have keywords that
// conflict with the standard ones (e.g. Calabrini in Moonglow has
// HEAL for healer, which is unreachable in u4dos, but clearly
// more useful than "Fine." for health).
Common::String look = Common::String("\nYou see ") + strings[2];
dlg->addKeyword("look", new Response(look));
dlg->addKeyword("name", new Response(Common::String("\n") + dlg->getPronoun() + " says: I am " + dlg->getName()));
dlg->addKeyword("give", new Response(Common::String("\n") + dlg->getPronoun() + " says: I do not need thy gold. Keep it!"));
dlg->addKeyword("join", new Response(Common::String("\n") + dlg->getPronoun() + " says: I cannot join thee."));
Response *bye = new Response("\nBye.");
bye->add(g_responseParts->END);
dlg->addKeyword("bye", bye);
dlg->addKeyword("", bye);
/*
* This little easter egg appeared in the Amiga version of Ultima IV.
* I've never figured out what the number means.
* "Banjo" Bob Hardy was the programmer for the Amiga version.
*/
dlg->addKeyword("ojna", new Response("\nHi Banjo Bob!\nYour secret\nnumber is\n4F4A4E0A"));
return dlg;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

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