diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarQuests.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarQuests.cpp')
-rw-r--r-- | source/game/StarQuests.cpp | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/source/game/StarQuests.cpp b/source/game/StarQuests.cpp new file mode 100644 index 0000000..f8e77bd --- /dev/null +++ b/source/game/StarQuests.cpp @@ -0,0 +1,764 @@ +#include "StarQuests.hpp" +#include "StarJsonExtra.hpp" +#include "StarFile.hpp" +#include "StarRoot.hpp" +#include "StarAssets.hpp" +#include "StarTime.hpp" +#include "StarRandom.hpp" +#include "StarItemDatabase.hpp" +#include "StarItemDrop.hpp" +#include "StarMonster.hpp" +#include "StarNpc.hpp" +#include "StarObjectDatabase.hpp" +#include "StarObject.hpp" +#include "StarPlayer.hpp" +#include "StarPlayerInventory.hpp" +#include "StarPlayerTech.hpp" +#include "StarConfigLuaBindings.hpp" +#include "StarEntityLuaBindings.hpp" +#include "StarPlayerLuaBindings.hpp" +#include "StarStatusControllerLuaBindings.hpp" +#include "StarQuestManager.hpp" +#include "StarClientContext.hpp" +#include "StarUuid.hpp" +#include "StarCelestialLuaBindings.hpp" + +namespace Star { + +EnumMap<QuestState> const QuestStateNames { + {QuestState::New, "New"}, + {QuestState::Offer, "Offer"}, + {QuestState::Active, "Active"}, + {QuestState::Complete, "Complete"}, + {QuestState::Failed, "Failed"} +}; + +Quest::Quest(QuestArcDescriptor const& questArc, size_t arcPos, Player* player) { + m_trackedIndicator = Root::singleton().assets()->json("/quests/quests.config:trackedCustomIndicator").toString(); + m_untrackedIndicator = Root::singleton().assets()->json("/quests/quests.config:untrackedCustomIndicator").toString(); + + m_money = 0; + m_unread = true; + m_canTurnIn = false; + m_lastUpdatedOn = Time::monotonicMilliseconds(); + + m_arc = questArc; + m_arcPos = arcPos; + + auto itemDatabase = Root::singleton().itemDatabase(); + auto templateDatabase = Root::singleton().questTemplateDatabase(); + auto questTemplate = templateDatabase->questTemplate(templateId()); + + m_parameters = questDescriptor().parameters; + m_displayParameters = DisplayParameters { + questTemplate->ephemeral, + questTemplate->showInLog, + questTemplate->showAcceptDialog, + questTemplate->showCompleteDialog, + questTemplate->showFailDialog, + questTemplate->mainQuest, + questTemplate->hideCrossServer + }; + setEntityParameter("player", player); + + m_money = Random::randUInt(questTemplate->moneyRange[0], questTemplate->moneyRange[1]); + m_rewards = Random::randValueFrom(questTemplate->rewards, {}).transformed( + [&itemDatabase](ItemDescriptor const& item) -> ItemConstPtr { return itemDatabase->item(item); }); + + for (String const& rewardParamName : questTemplate->rewardParameters) { + if (!m_parameters.contains(rewardParamName)) + continue; + QuestParam const& rewardParam = m_parameters[rewardParamName]; + if (rewardParam.detail.is<QuestItem>()) { + addReward(rewardParam.detail.get<QuestItem>().descriptor()); + } else { + if (!rewardParam.detail.is<QuestItemList>()) + throw StarException::format("Quest parameter %s cannot be used as a reward parameter", rewardParamName); + for (auto item : rewardParam.detail.get<QuestItemList>()) + addReward(item); + } + } + + m_title = questTemplate->title; + m_text = questTemplate->text; + m_completionText = questTemplate->completionText; + m_failureText = questTemplate->failureText; + + m_state = QuestState::New; + m_showDialog = false; + + m_player = nullptr; + m_world = nullptr; + m_inited = false; +} + +Quest::Quest(Json const& spec) { + m_trackedIndicator = Root::singleton().assets()->json("/quests/quests.config:trackedCustomIndicator").toString(); + m_untrackedIndicator = Root::singleton().assets()->json("/quests/quests.config:untrackedCustomIndicator").toString(); + + auto versioningDatabase = Root::singleton().versioningDatabase(); + Json diskStore = versioningDatabase->loadVersionedJson(VersionedJson::fromJson(spec), "Quest"); + + m_state = QuestStateNames.getLeft(diskStore.getString("state")); + + m_arc = QuestArcDescriptor::diskLoad(diskStore.get("arc")); + m_arcPos = diskStore.getUInt("arcPos"); + m_parameters = questParamsDiskLoad(diskStore.get("parameters")); + m_worldId = diskStore.optString("worldId").apply(parseWorldId); + m_location = diskStore.opt("location").apply([](Json const& json) { + return make_pair(jsonToVec3I(json.get("system")), jsonToSystemLocation(json.get("location"))); + }); + m_serverUuid = diskStore.optString("serverUuid").apply(construct<Uuid>()); + m_money = diskStore.getUInt("money"); + + auto itemDatabase = Root::singleton().itemDatabase(); + m_rewards = diskStore.getArray("rewards").transformed( + [&itemDatabase](Json const& json) -> ItemConstPtr { return itemDatabase->diskLoad(json); }); + + m_lastUpdatedOn = diskStore.getInt("lastUpdatedOn"); + m_unread = diskStore.getBool("unread", true); + m_canTurnIn = diskStore.getBool("canTurnIn", false); + m_indicators = jsonToStringSet(diskStore.get("indicators", JsonArray{})); + m_scriptComponent.setScriptStorage(diskStore.getObject("scriptStorage", JsonObject{})); + + auto templateDatabase = Root::singleton().questTemplateDatabase(); + auto questTemplate = templateDatabase->questTemplate(templateId()); + m_displayParameters = DisplayParameters{ + questTemplate->ephemeral, + questTemplate->showInLog, + questTemplate->showAcceptDialog, + questTemplate->showCompleteDialog, + questTemplate->showFailDialog, + questTemplate->mainQuest, + questTemplate->hideCrossServer + }; + + m_title = diskStore.getString("title", questTemplate->title); + m_text = diskStore.getString("text", questTemplate->text); + m_completionText = diskStore.getString("completionText", questTemplate->completionText); + m_failureText = diskStore.getString("failureText", questTemplate->failureText); + + m_portraits = jsonToMapV<StringMap<List<Drawable>>>(diskStore.get("portraits", JsonObject{}), [](Json const& portrait) { + return portrait.toArray().transformed(construct<Drawable>()); + }); + m_portraitTitles = jsonToMapV<StringMap<String>>(diskStore.get("portraitTitles", JsonObject{}), mem_fn(&Json::toString)); + m_showDialog = diskStore.getBool("showDialog", false); + + m_player = nullptr; + m_world = nullptr; + m_inited = false; +} + +Json Quest::diskStore() const { + auto versioningDatabase = Root::singleton().versioningDatabase(); + + JsonObject result; + result["state"] = QuestStateNames.getRight(m_state); + result["arc"] = m_arc.diskStore(); + result["arcPos"] = m_arcPos; + result["parameters"] = questParamsDiskStore(m_parameters); + result["money"] = m_money; + + result["worldId"] = jsonFromMaybe(m_worldId.apply(printWorldId)); + result["location"] = jsonFromMaybe(m_location.apply([](pair<Vec3I, SystemLocation> const& location) { + return JsonObject{{"system", jsonFromVec3I(location.first)}, {"location", jsonFromSystemLocation(location.second)}}; + })); + result["serverUuid"] = jsonFromMaybe(m_serverUuid.apply(mem_fn(&Uuid::hex))); + + auto itemDatabase = Root::singleton().itemDatabase(); + result["rewards"] = + m_rewards.transformed([&itemDatabase](ItemConstPtr const& item) { return itemDatabase->diskStore(item); }); + + result["lastUpdatedOn"] = m_lastUpdatedOn; + result["unread"] = m_unread; + result["canTurnIn"] = m_canTurnIn; + result["indicators"] = jsonFromStringSet(m_indicators); + result["scriptStorage"] = m_scriptComponent.getScriptStorage(); + + result["title"] = m_title; + result["text"] = m_text; + result["completionText"] = m_completionText; + result["failureText"] = m_failureText; + + result["portraits"] = jsonFromMapV(m_portraits, [](List<Drawable> const& portrait) { + return portrait.transformed(mem_fn(&Drawable::toJson)); + }); + result["portraitTitles"] = jsonFromMap(m_portraitTitles); + result["showDialog"] = m_showDialog; + + return versioningDatabase->makeCurrentVersionedJson("Quest", result).toJson(); +} + +QuestTemplatePtr Quest::getTemplate() const { + return Root::singleton().questTemplateDatabase()->questTemplate(templateId()); +} + +void Quest::init(Player* player, World* world, UniverseClient* client) { + m_player = player; + m_world = world; + m_client = client; + + if (m_state == QuestState::Offer || m_state == QuestState::Active) + initScript(); +} + +void Quest::uninit() { + if (m_inited) + uninitScript(); + + m_player = nullptr; + m_world = nullptr; +} + +Maybe<Json> Quest::receiveMessage(String const& message, bool localMessage, JsonArray const& args) { + if (!m_inited) + return {}; + return m_scriptComponent.handleMessage(message, localMessage, args); +} + +void Quest::update() { + if (!m_inited) + return; + m_scriptComponent.update(m_scriptComponent.updateDt()); +} + +void Quest::offer() { + starAssert(m_player && m_world); + + if (!showAcceptDialog()) { + start(); + } else { + setState(QuestState::Offer); + initScript(); + m_scriptComponent.invoke("questOffer"); + } +} + +void Quest::declineOffer() { + setState(QuestState::New); + m_scriptComponent.invoke("questDecline"); + uninitScript(); +} + +void Quest::start() { + setState(QuestState::Active); + initScript(); + + auto current = m_player->questManager()->trackedQuest(); + if (mainQuest() || !current) + m_player->questManager()->setAsTracked(questId()); + + m_scriptComponent.invoke("questStart"); +} + +void Quest::complete(Maybe<size_t> followupIndex) { + setState(QuestState::Complete); + m_showDialog = showCompleteDialog(); + m_scriptComponent.invoke("questComplete"); + uninitScript(); + + // Grant reward items and money + for (auto const& item : rewards()) + m_player->giveItem(item->clone()); + m_player->inventory()->addCurrency("money", money()); + + // Offer follow-up quests + bool trackNewQuest = m_player->questManager()->isTracked(questId()); + size_t nextArcPos = followupIndex.value(questArcPosition() + 1); + if (nextArcPos < m_arc.quests.size()) { + auto followUp = make_shared<Quest>(m_arc, nextArcPos, m_player); + followUp->setWorldId(worldId()); + followUp->setLocation(location()); + followUp->setServerUuid(serverUuid()); + m_player->questManager()->offer(followUp); + if (trackNewQuest) + m_player->questManager()->setAsTracked(followUp->questId()); + } else if (trackNewQuest) { + // no followup, track another main quest or clear quest tracker + if (auto main = m_player->questManager()->getFirstMainQuest()) + m_player->questManager()->setAsTracked(main.value()->questId()); + else + m_player->questManager()->setAsTracked({}); + } +} + +void Quest::fail() { + setState(QuestState::Failed); + m_showDialog = showFailDialog(); + m_scriptComponent.invoke("questFail", false); + uninitScript(); +} + +void Quest::abandon() { + setState(QuestState::Failed); + m_showDialog = false; + m_scriptComponent.invoke("questFail", true); + uninitScript(); +} + +bool Quest::interactWithEntity(EntityId entity) { + Maybe<bool> result = m_scriptComponent.invoke<bool>("questInteract", entity); + return result && result.value(); +} + +String Quest::questId() const { + return questDescriptor().questId; +} + +String Quest::templateId() const { + return questDescriptor().templateId; +} + +StringMap<QuestParam> const& Quest::parameters() const { + return m_parameters; +} + +QuestState Quest::state() const { + return m_state; +} + +bool Quest::showDialog() const { + return m_showDialog; +} + +void Quest::setDialogShown() { + m_showDialog = false; +} + +void Quest::setEntityParameter(String const& paramName, EntityConstPtr const& entity) { + setEntityParameter(paramName, entity.get()); +} + +void Quest::setParameter(String const& paramName, QuestParam const& paramValue) { + m_parameters[paramName] = paramValue; +} + +Maybe<List<Drawable>> Quest::portrait(String const& portraitName) const { + return m_portraits.maybe(portraitName); +} + +Maybe<String> Quest::portraitTitle(String const& portraitName) const { + return m_portraitTitles.maybe(portraitName); +} + +QuestDescriptor Quest::questDescriptor() const { + return m_arc.quests[m_arcPos]; +} + +QuestArcDescriptor Quest::questArcDescriptor() const { + return m_arc; +} + +size_t Quest::questArcPosition() const { + return m_arcPos; +} + +Maybe<WorldId> Quest::worldId() const { + return m_worldId; +} + +Maybe<pair<Vec3I, SystemLocation>> Quest::location() const { + return m_location; +} + +Maybe<Uuid> Quest::serverUuid() const { + return m_serverUuid; +} + +void Quest::setWorldId(Maybe<WorldId> worldId) { + m_worldId = worldId; +} + +void Quest::setLocation(Maybe<pair<Vec3I, SystemLocation>> location) { + m_location = move(location); +} + +void Quest::setServerUuid(Maybe<Uuid> serverUuid) { + m_serverUuid = serverUuid; +} + +String Quest::title() const { + return m_title; +} + +String Quest::text() const { + return m_text; +} + +String Quest::completionText() const { + return m_completionText; +} + +String Quest::failureText() const { + return m_failureText; +} + +size_t Quest::money() const { + return m_money; +} + +List<ItemConstPtr> Quest::rewards() const { + return m_rewards; +} + +int64_t Quest::lastUpdatedOn() const { + return m_lastUpdatedOn; +} + +bool Quest::unread() const { + return m_unread; +} + +void Quest::markAsRead() { + m_unread = false; +} + +bool Quest::canTurnIn() const { + return m_canTurnIn; +} + +String Quest::questGiverIndicator() const { + return getTemplate()->questGiverIndicator; +} + +String Quest::questReceiverIndicator() const { + return getTemplate()->questReceiverIndicator; +} + +bool hasItemIndicator(EntityPtr const& entity, List<ItemDescriptor> indicatedItems) { + if (auto itemDrop = as<ItemDrop>(entity)) { + for (auto const& itemDesc : indicatedItems) { + if (itemDrop->item()->matches(itemDesc, true)) { + return true; + } + } + } else if (auto object = as<Object>(entity)) { + ObjectConfigPtr objectConfig = Root::singleton().objectDatabase()->getConfig(object->name()); + if (!objectConfig->hasObjectItem) + return false; + for (auto const& itemDesc : indicatedItems) { + if (object->name() == itemDesc.name()) + return true; + } + } + return false; +} + +Maybe<String> Quest::customIndicator(EntityPtr const& entity) const { + if (!m_inited) + return {}; + + for (String const& indicator : m_indicators) { + auto param = parameters().get(indicator); + if (param.detail.is<QuestEntity>()) { + QuestEntity questEntity = param.detail.get<QuestEntity>(); + if (questEntity.uniqueId && entity->uniqueId() == questEntity.uniqueId) { + return param.indicator.value(defaultCustomIndicator()); + } + + } else if (param.detail.is<QuestItem>()) { + QuestItem questItem = param.detail.get<QuestItem>(); + if (hasItemIndicator(entity, {questItem.descriptor()})) + return param.indicator.value(defaultCustomIndicator()); + + } else if (param.detail.is<QuestItemTag>()) { + String questItemTag = param.detail.get<QuestItemTag>(); + if (auto itemDrop = as<ItemDrop>(entity)) { + if (itemDrop->item()->itemTags().contains(questItemTag)) + return param.indicator.value(defaultCustomIndicator()); + } + + } else if (param.detail.is<QuestItemList>()) { + QuestItemList questItemList = param.detail.get<QuestItemList>(); + if (hasItemIndicator(entity, questItemList)) + return param.indicator.value(defaultCustomIndicator()); + + } else if (param.detail.is<QuestMonsterType>()) { + QuestMonsterType questMonsterType = param.detail.get<QuestMonsterType>(); + if (auto monster = as<Monster>(entity)) { + if (monster->typeName() == questMonsterType.typeName) { + TeamType team = monster->getTeam().type; + if (team == TeamType::Enemy || team == TeamType::Passive) + return param.indicator.value(defaultCustomIndicator()); + } + } + + } + } + return {}; +} + +Maybe<JsonArray> Quest::objectiveList() const { + return m_objectiveList; +} + +Maybe<float> Quest::progress() const { + return m_progress; +} + +Maybe<float> Quest::compassDirection() const { + return m_compassDirection; +} + +void Quest::setObjectiveList(Maybe<JsonArray> const& objectiveList) { + m_objectiveList = objectiveList; +} + +void Quest::setProgress(Maybe<float> const& progress) { + m_progress = progress; +} + +void Quest::setCompassDirection(Maybe<float> const& compassDirection) { + m_compassDirection = compassDirection; +} + +Maybe<String> Quest::completionCinema() const { + return getTemplate()->completionCinema; +} + +bool Quest::canBeAbandoned() const { + return getTemplate()->canBeAbandoned; +} + +bool Quest::ephemeral() const { + return m_displayParameters.ephemeral; +} + +bool Quest::showInLog() const { + return m_displayParameters.showInLog; +} + +bool Quest::showAcceptDialog() const { + return m_displayParameters.showAcceptDialog; +} + +bool Quest::showCompleteDialog() const { + return m_displayParameters.showCompleteDialog; +} + +bool Quest::showFailDialog() const { + return m_displayParameters.showFailDialog; +} + +bool Quest::mainQuest() const { + return m_displayParameters.mainQuest; +} + +bool Quest::hideCrossServer() const { + return m_displayParameters.hideCrossServer; +} + +void Quest::setState(QuestState state) { + m_state = state; + m_lastUpdatedOn = Time::monotonicMilliseconds(); +} + +void Quest::initScript() { + if (!m_player || !m_world || m_inited) + return; + + auto questTemplate = getTemplate(); + if (questTemplate->script.isValid()) + m_scriptComponent.setScript(*questTemplate->script); + else + m_scriptComponent.setScripts(StringList{}); + m_scriptComponent.setUpdateDelta(questTemplate->updateDelta); + + m_scriptComponent.addCallbacks("quest", makeQuestCallbacks(m_player)); + m_scriptComponent.addCallbacks("celestial", LuaBindings::makeCelestialCallbacks(m_client)); + m_scriptComponent.addCallbacks("player", LuaBindings::makePlayerCallbacks(m_player)); + m_scriptComponent.addCallbacks("config", LuaBindings::makeConfigCallbacks([this](String const& name, Json const& def) { + return Json(getTemplate()->scriptConfig).query(name, def); + })); + m_scriptComponent.addCallbacks("entity", LuaBindings::makeEntityCallbacks(m_player)); + m_scriptComponent.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(m_player->statusController())); + m_scriptComponent.addActorMovementCallbacks(m_player->movementController()); + m_inited = true; + + m_scriptComponent.init(m_world); +} + +void Quest::uninitScript() { + m_scriptComponent.uninit(); + m_scriptComponent.removeCallbacks("quest"); + m_scriptComponent.removeCallbacks("celestial"); + m_scriptComponent.removeCallbacks("player"); + m_scriptComponent.removeCallbacks("config"); + m_scriptComponent.removeCallbacks("entity"); + m_scriptComponent.removeCallbacks("status"); + m_scriptComponent.removeActorMovementCallbacks(); + m_inited = false; +} + +LuaCallbacks Quest::makeQuestCallbacks(Player* player) { + LuaCallbacks callbacks; + + callbacks.registerCallback("state", [this]() { return QuestStateNames.getRight(state()); }); + + callbacks.registerCallback("complete", [this](Maybe<size_t> followup) { complete(followup); }); + + callbacks.registerCallback("fail", [this]() { fail(); }); + + callbacks.registerCallback("setCanTurnIn", [this](bool value) { this->m_canTurnIn = value; }); + + callbacks.registerCallback("questId", [this]() { return questId(); }); + + callbacks.registerCallback("templateId", [this]() { return templateId(); }); + + callbacks.registerCallback("seed", [this]() { return questDescriptor().seed; }); + + callbacks.registerCallback("questDescriptor", [this]() { return questDescriptor().toJson(); }); + + callbacks.registerCallback("questArcDescriptor", [this]() { return questArcDescriptor().toJson(); }); + + callbacks.registerCallback("questArcPosition", [this]() { return m_arcPos; }); + + callbacks.registerCallback("worldId", [this]() { return worldId().apply(printWorldId); }); + + callbacks.registerCallback("setWorldId", [this](Maybe<String> const& worldId) { setWorldId(worldId.apply(parseWorldId)); }); + + callbacks.registerCallback("serverUuid", [this]() { return serverUuid().apply(mem_fn(&Uuid::hex)); }); + + callbacks.registerCallback("setServerUuid", [this](String const& serverUuid) { setServerUuid(Uuid(serverUuid)); }); + + callbacks.registerCallback("isCurrent", [this, player]() -> bool { return player->questManager()->isCurrent(questId()); }); + + callbacks.registerCallback("location", [this]() -> Json { + if (auto loc = location()) { + return JsonObject{ + {"system", jsonFromVec3I(loc.get().first)}, + {"location", jsonFromSystemLocation(loc.get().second)} + }; + } + return {}; + }); + + callbacks.registerCallback("setLocation", [this](Json const& json) { + if (json.isNull()) { + setLocation({}); + } else { + Vec3I system = jsonToVec3I(json.get("system")); + SystemLocation location = jsonToSystemLocation(json.opt("location").value({})); + setLocation(make_pair(system, location)); + } + }); + + callbacks.registerCallback("parameters", [this]() { return questParamsToJson(parameters()); }); + + callbacks.registerCallback("setParameter", + [this](String const& name, Json paramJson) { m_parameters[name] = QuestParam::fromJson(paramJson); }); + + callbacks.registerCallback( + "setIndicators", [this](StringList indicators) { m_indicators = StringSet::from(indicators); }); + + callbacks.registerCallbackWithSignature<void, Maybe<JsonArray>>("setObjectiveList", bind(&Quest::setObjectiveList, this, _1)); + callbacks.registerCallbackWithSignature<void, Maybe<float>>("setProgress", bind(&Quest::setProgress, this, _1)); + callbacks.registerCallbackWithSignature<void, Maybe<float>>("setCompassDirection", bind(&Quest::setCompassDirection, this, _1)); + + callbacks.registerCallbackWithSignature<void, String>("setTitle", [this](String const& title) { + m_title = title; + }); + callbacks.registerCallbackWithSignature<void, String>("setText", [this](String const& text) { + m_text = text; + }); + callbacks.registerCallbackWithSignature<void, String>("setCompletionText", [this](String const& completionText) { + m_completionText = completionText; + }); + callbacks.registerCallbackWithSignature<void, String>("setFailureText", [this](String const& failureText) { + m_failureText = failureText; + }); + + callbacks.registerCallbackWithSignature<void, String, Maybe<JsonArray>>("setPortrait", [this](String const& portraitName, Maybe<JsonArray> const& portrait) { + if (portrait) { + m_portraits[portraitName] = portrait->transformed(construct<Drawable>()); + } else { + m_portraits.remove(portraitName); + } + }); + callbacks.registerCallbackWithSignature<void, String, Maybe<String>>("setPortraitTitle", [this](String const& portraitName, Maybe<String> const& portrait) { + if (portrait) { + m_portraitTitles[portraitName] = *portrait; + } else { + m_portraitTitles.remove(portraitName); + } + }); + callbacks.registerCallbackWithSignature<void, Json>("addReward", [this](Json const& reward) { + addReward(ItemDescriptor(reward)); + }); + + return callbacks; +} + +void Quest::setEntityParameter(String const& paramName, Entity const* entity) { + Maybe<Json> portrait = {}; + Maybe<String> name = {}; + Maybe<String> species = {}; + Maybe<Gender> gender = {}; + + if (auto portraitEntity = as<PortraitEntity>(entity)) { + portrait = Json{portraitEntity->portrait(PortraitMode::Full).transformed(mem_fn(&Drawable::toJson))}; + name = portraitEntity->name(); + } + + if (auto npc = as<Npc>(entity)) { + species = npc->species(); + gender = npc->gender(); + } else if (auto player = as<Player>(entity)) { + species = player->species(); + gender = player->gender(); + } + + m_parameters[paramName] = QuestParam{QuestEntity{entity->uniqueId(), species, gender}, name, portrait, {}}; +} + +void Quest::addReward(ItemDescriptor const& reward) { + if (reward.name() == "money") { + m_money += reward.count(); + return; + } + auto itemDatabase = Root::singleton().itemDatabase(); + m_rewards.append(itemDatabase->item(reward)); +} + +String const& Quest::defaultCustomIndicator() const { + return m_player->questManager()->isCurrent(questId()) ? m_trackedIndicator : m_untrackedIndicator; +} + +QuestPtr createPreviewQuest( + String const& templateId, String const& position, String const& questGiverSpecies, Player* player) { + auto questTemplates = Root::singleton().questTemplateDatabase(); + auto questTemplate = questTemplates->questTemplate(templateId); + if (!questTemplate) + return {}; + + Json portrait = player->portrait(PortraitMode::Full).transformed(mem_fn(&Drawable::toJson)); + + JsonObject paramJson = questTemplate->parameterExamples; + for (auto const& paramName : paramJson.keys()) { + paramJson[paramName] = paramJson[paramName].set("type", questTemplate->parameterTypes[paramName]); + paramJson[paramName] = paramJson[paramName].set("portrait", portrait); + } + StringMap<QuestParam> parameters = questParamsFromJson(paramJson); + QuestDescriptor questDesc = QuestDescriptor{"preview", templateId, parameters, Random::randu64()}; + + List<QuestDescriptor> quests; + if (!position.equalsIgnoreCase("next") && !position.equalsIgnoreCase("last") && !position.equalsIgnoreCase("first")) { + quests = {questDesc}; + } else { + quests = {questDesc, questDesc, questDesc}; + } + QuestArcDescriptor arc = QuestArcDescriptor{quests, {}}; + + size_t arcPos = 0; + if (position.equalsIgnoreCase("next")) { + arcPos = 1; + } else if (position.equalsIgnoreCase("last")) { + arcPos = 2; + } + + auto quest = make_shared<Quest>(arc, arcPos, player); + quest->setParameter("questGiver", QuestParam{QuestEntity{{}, questGiverSpecies, {}}, {"Quest Giver"}, portrait, {}}); + return quest; +} + +} |