From 6352e8e3196f78388b6c771073f9e03eaa612673 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:33:09 +1000 Subject: everything everywhere all at once --- source/frontend/StarCharCreation.cpp | 448 +++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 source/frontend/StarCharCreation.cpp (limited to 'source/frontend/StarCharCreation.cpp') diff --git a/source/frontend/StarCharCreation.cpp b/source/frontend/StarCharCreation.cpp new file mode 100644 index 0000000..fd617df --- /dev/null +++ b/source/frontend/StarCharCreation.cpp @@ -0,0 +1,448 @@ +#include "StarCharCreation.hpp" +#include "StarJsonExtra.hpp" +#include "StarGuiReader.hpp" +#include "StarNameGenerator.hpp" +#include "StarLogging.hpp" +#include "StarRoot.hpp" +#include "StarWorldClient.hpp" +#include "StarSpeciesDatabase.hpp" +#include "StarButtonWidget.hpp" +#include "StarPortraitWidget.hpp" +#include "StarTextBoxWidget.hpp" +#include "StarLabelWidget.hpp" +#include "StarImageWidget.hpp" +#include "StarArmors.hpp" +#include "StarAssets.hpp" +#include "StarPlayerFactory.hpp" +#include "StarItemDatabase.hpp" +#include "StarPlayerInventory.hpp" +#include "StarPlayerLog.hpp" + +namespace Star { + +CharCreationPane::CharCreationPane(std::function requestCloseFunc) { + auto& root = Root::singleton(); + + m_speciesList = jsonToStringList(root.assets()->json("/interface/windowconfig/charcreation.config:speciesOrdering")); + + GuiReader guiReader; + guiReader.registerCallback("cancel", [=](Widget*) { requestCloseFunc({}); }); + guiReader.registerCallback("saveChar", [=](Widget*) { + if (fetchChild("btnSkipIntro")->isChecked()) + m_previewPlayer->log()->setIntroComplete(true); + requestCloseFunc(m_previewPlayer); + createPlayer(); + randomize(); + randomizeName(); + }); + + guiReader.registerCallback("mainSkinColor.up", [=](Widget*) { + m_bodyColor++; + changed(); + }); + guiReader.registerCallback("mainSkinColor.down", [=](Widget*) { + m_bodyColor--; + changed(); + }); + guiReader.registerCallback("alty.up", [=](Widget*) { + m_alty++; + changed(); + }); + guiReader.registerCallback("alty.down", [=](Widget*) { + m_alty--; + changed(); + }); + guiReader.registerCallback("hairStyle.up", [=](Widget*) { + m_hairChoice++; + changed(); + }); + guiReader.registerCallback("hairStyle.down", [=](Widget*) { + m_hairChoice--; + changed(); + }); + guiReader.registerCallback("shirt.up", [=](Widget*) { + m_shirtChoice++; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("shirt.down", [=](Widget*) { + m_shirtChoice--; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("pants.up", [=](Widget*) { + m_pantsChoice++; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("pants.down", [=](Widget*) { + m_pantsChoice--; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("heady.up", [=](Widget*) { + m_heady++; + changed(); + }); + guiReader.registerCallback("heady.down", [=](Widget*) { + m_heady--; + changed(); + }); + guiReader.registerCallback("shirtColor.up", [=](Widget*) { + m_shirtColor++; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("shirtColor.down", [=](Widget*) { + m_shirtColor--; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("pantsColor.up", [=](Widget*) { + m_pantsColor++; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("pantsColor.down", [=](Widget*) { + m_pantsColor--; + fetchChild("btnToggleClothing")->setChecked(true); + changed(); + }); + guiReader.registerCallback("personality.up", [=](Widget*) { + m_personality++; + changed(); + }); + guiReader.registerCallback("personality.down", [=](Widget*) { + m_personality--; + changed(); + }); + guiReader.registerCallback("toggleClothing", [=](Widget*) { + changed(); + }); + + guiReader.registerCallback("randomName", [=](Widget*) { randomizeName(); }); + guiReader.registerCallback("randomize", [=](Widget*) { randomize(); }); + + guiReader.registerCallback("name", [=](Widget* object) { nameBoxCallback(object); }); + + guiReader.registerCallback("species", [=](Widget* button) { + size_t speciesChoice = convert(button)->buttonGroupId(); + if (speciesChoice < m_speciesList.size() && speciesChoice != m_speciesChoice) { + m_speciesChoice = speciesChoice; + randomize(); + randomizeName(); + } + }); + guiReader.registerCallback("gender", [=](Widget* button) { + m_genderChoice = convert(button)->buttonGroupId(); + changed(); + }); + + guiReader.registerCallback("mode", [=](Widget* button) { + m_modeChoice = convert(button)->buttonGroupId(); + changed(); + }); + + guiReader.construct(root.assets()->json("/interface/windowconfig/charcreation.config:paneLayout"), this); + + createPlayer(); + + RandomSource random; + m_speciesChoice = random.randu32() % m_speciesList.size(); + m_genderChoice = random.randu32(); + m_modeChoice = 1; + randomize(); + randomizeName(); +} + +void CharCreationPane::createPlayer() { + m_previewPlayer = Root::singleton().playerFactory()->create(); + try { + auto portrait = fetchChild("charPreview"); + if ((bool)portrait) { + portrait->setEntity(m_previewPlayer); + } else { + throw CharCreationException("The charPreview portrait has the wrong type."); + } + } catch (CharCreationException const& e) { + Logger::error("Character Preview portrait was not found in the json specification. %s", outputException(e, false)); + } +} + +void CharCreationPane::randomize() { + RandomSource random; + m_bodyColor = random.randu32(); + m_hairChoice = random.randu32(); + m_alty = random.randu32(); + m_heady = random.randu32(); + m_shirtChoice = random.randu32(); + m_shirtColor = random.randu32(); + m_pantsChoice = random.randu32(); + m_pantsColor = random.randu32(); + m_personality = random.randu32(); + changed(); +} + +void CharCreationPane::tick() { + Pane::tick(); + if (!active()) + return; + if (!m_previewPlayer) + return; + m_previewPlayer->animatePortrait(); +} + +bool CharCreationPane::sendEvent(InputEvent const& event) { + if (active() && m_previewPlayer) { + if (event.is()) { + auto actions = context()->actions(event); + if (actions.contains(InterfaceAction::EmoteBlabbering)) + m_previewPlayer->addEmote(HumanoidEmote::Blabbering); + if (actions.contains(InterfaceAction::EmoteShouting)) + m_previewPlayer->addEmote(HumanoidEmote::Shouting); + if (actions.contains(InterfaceAction::EmoteHappy)) + m_previewPlayer->addEmote(HumanoidEmote::Happy); + if (actions.contains(InterfaceAction::EmoteSad)) + m_previewPlayer->addEmote(HumanoidEmote::Sad); + if (actions.contains(InterfaceAction::EmoteNeutral)) + m_previewPlayer->addEmote(HumanoidEmote::NEUTRAL); + if (actions.contains(InterfaceAction::EmoteLaugh)) + m_previewPlayer->addEmote(HumanoidEmote::Laugh); + if (actions.contains(InterfaceAction::EmoteAnnoyed)) + m_previewPlayer->addEmote(HumanoidEmote::Annoyed); + if (actions.contains(InterfaceAction::EmoteOh)) + m_previewPlayer->addEmote(HumanoidEmote::Oh); + if (actions.contains(InterfaceAction::EmoteOooh)) + m_previewPlayer->addEmote(HumanoidEmote::OOOH); + if (actions.contains(InterfaceAction::EmoteBlink)) + m_previewPlayer->addEmote(HumanoidEmote::Blink); + if (actions.contains(InterfaceAction::EmoteWink)) + m_previewPlayer->addEmote(HumanoidEmote::Wink); + if (actions.contains(InterfaceAction::EmoteEat)) + m_previewPlayer->addEmote(HumanoidEmote::Eat); + if (actions.contains(InterfaceAction::EmoteSleep)) + m_previewPlayer->addEmote(HumanoidEmote::Sleep); + } + } + return Pane::sendEvent(event); +} + +void CharCreationPane::randomizeName() { + auto species = Root::singleton().speciesDatabase()->species(m_speciesList[m_speciesChoice]); + auto tb = fetchChild("name"); + auto genderOption = species->options().genderOptions.wrap(m_genderChoice); + int limiter = 100; + while (!tb->setText(Root::singleton().nameGenerator()->generateName(species->nameGen(genderOption.gender)))) { + if (limiter == 0) + break; + limiter--; + } + changed(); +} + +void CharCreationPane::changed() { + auto& root = Root::singleton(); + + auto textBox = fetchChild("name"); + auto speciesDefinition = Root::singleton().speciesDatabase()->species(m_speciesList[m_speciesChoice]); + auto species = speciesDefinition->options(); + auto genderOptions = species.genderOptions.wrap(m_genderChoice); + int genderIdx = pmod(m_genderChoice, species.genderOptions.size()); + + auto labels = speciesDefinition->charGenTextLabels(); + + fetchChild("labelMainSkinColor")->setText(labels[0]); + fetchChild("labelHairStyle")->setText(labels[1]); + fetchChild("labelShirt")->setText(labels[2]); + fetchChild("labelPants")->setText(labels[3]); + if (!labels[4].empty()) { + fetchChild("labelAlty")->setText(labels[4]); + fetchChild("labelAlty")->show(); + fetchChild("alty")->show(); + } else { + fetchChild("labelAlty")->hide(); + fetchChild("alty")->hide(); + } + fetchChild("labelHeady")->setText(labels[5]); + fetchChild("labelShirtColor")->setText(labels[6]); + fetchChild("labelPantsColor")->setText(labels[7]); + fetchChild("labelPortrait")->setText(labels[8]); + fetchChild("labelPersonality")->setText(labels[9]); + + fetchChild(strf("species.%s", m_speciesChoice))->check(); + fetchChild(strf("gender.%s", genderIdx))->check(); + auto modeButton = fetchChild(strf("mode.%s", m_modeChoice)); + modeButton->check(); + setLabel("labelMode", modeButton->data().getString("description", "fail")); + + // Update the gender images for the new species + for (size_t i = 0; i < species.genderOptions.size(); i++) + fetchChild(strf("gender.%s", i))->setOverlayImage(species.genderOptions[i].image); + + for (auto const& nameDefPair : root.speciesDatabase()->allSpecies()) { + String name; + SpeciesDefinitionPtr def; + std::tie(name, def) = nameDefPair; + // NOTE: Probably not hot enough to matter, but this contains and indexOf makes this loop + // O(n^2). This is less than ideal. + if (m_speciesList.contains(name)) { + auto bw = fetchChild(strf("species.%s", m_speciesList.indexOf(name))); + if (bw) + bw->setOverlayImage(def->options().genderOptions[genderIdx].characterImage); + } + } + + auto portrait = fetchChild("charPreview"); + if (fetchChild("btnToggleClothing")->isChecked()) + portrait->setMode(PortraitMode::Full); + else + portrait->setMode(PortraitMode::FullNude); + + auto gender = species.genderOptions.wrap(m_genderChoice); + auto bodyColor = species.bodyColorDirectives.wrap(m_bodyColor); + + String altColor; + + if (species.altOptionAsUndyColor) { + // undyColor + altColor = species.undyColorDirectives.wrap(m_alty); + } + + auto hair = gender.hairOptions.wrap(m_hairChoice); + String hairColor = bodyColor; + if (species.headOptionAsHairColor && species.altOptionAsHairColor) { + hairColor = species.hairColorDirectives.wrap(m_heady); + hairColor += species.undyColorDirectives.wrap(m_alty); + } else if (species.headOptionAsHairColor) { + hairColor = species.hairColorDirectives.wrap(m_heady); + } + + if (species.hairColorAsBodySubColor) + bodyColor += hairColor; + + String facialHair; + String facialHairGroup; + String facialHairDirective; + if (species.headOptionAsFacialhair) { + facialHair = gender.facialHairOptions.wrap(m_heady); + facialHairGroup = gender.facialHairGroup; + facialHairDirective = hairColor; + } + + String facialMask; + String facialMaskGroup; + String facialMaskDirective; + if (species.altOptionAsFacialMask) { + facialMask = gender.facialMaskOptions.wrap(m_alty); + facialMaskGroup = gender.facialMaskGroup; + facialMaskDirective = ""; + } + if (species.bodyColorAsFacialMaskSubColor) + facialMaskDirective += bodyColor; + if (species.altColorAsFacialMaskSubColor) + facialMaskDirective += altColor; + + auto shirt = gender.shirtOptions.wrap(m_shirtChoice); + auto pants = gender.pantsOptions.wrap(m_pantsChoice); + + m_previewPlayer->setModeType((PlayerMode)m_modeChoice); + + m_previewPlayer->setName(textBox->getText()); + + m_previewPlayer->setSpecies(species.species); + m_previewPlayer->setBodyDirectives(bodyColor + altColor); + + m_previewPlayer->setGender(GenderNames.getLeft(gender.name)); + + m_previewPlayer->setHairType(gender.hairGroup, hair); + m_previewPlayer->setHairDirectives(hairColor); + + m_previewPlayer->setEmoteDirectives(bodyColor + altColor); + + m_previewPlayer->setFacialHair(facialHairGroup, facialHair, facialHairDirective); + + m_previewPlayer->setFacialMask(facialMaskGroup, facialMask, facialMaskDirective); + + auto personality = speciesDefinition->personalities().wrap(m_personality); + m_previewPlayer->setPersonality(personality); + + setShirt(shirt, m_shirtColor); + setPants(pants, m_pantsColor); + + m_previewPlayer->finalizeCreation(); +} + +void CharCreationPane::setShirt(String const& shirt, size_t colorIndex) { + auto& root = Root::singleton(); + + while (m_previewPlayer->inventory()->chestArmor()) + m_previewPlayer->inventory()->consumeSlot(InventorySlot(EquipmentSlot::Chest)); + if (!shirt.empty()) { + m_previewPlayer->inventory()->addItems( + root.itemDatabase()->item({shirt, 1, JsonObject{{"colorIndex", colorIndex}}})); + } + m_previewPlayer->refreshEquipment(); +} + +void CharCreationPane::setPants(String const& pants, size_t colorIndex) { + auto& root = Root::singleton(); + + while (m_previewPlayer->inventory()->legsArmor()) + m_previewPlayer->inventory()->consumeSlot(InventorySlot(EquipmentSlot::Legs)); + if (!pants.empty()) { + m_previewPlayer->inventory()->addItems( + root.itemDatabase()->item({pants, 1, JsonObject{{"colorIndex", colorIndex}}})); + } + m_previewPlayer->refreshEquipment(); +} + +void CharCreationPane::nameBoxCallback(Widget* object) { + if (as(object)) + changed(); + else + throw GuiException("Invalid object type, expected TextBoxWidget."); +} + +PanePtr CharCreationPane::createTooltip(Vec2I const& screenPosition) { + // what's under my cursor + if (WidgetPtr child = getChildAt(screenPosition)) { + // is it a species button ? + if (child->parent()->name() == "species") { + // which species is it ? + size_t speciesIndex = convert(child)->buttonGroupId(); + + // no tooltips for unassigned button indices + if (speciesIndex >= m_speciesList.size()) + return {}; + + String speciesName = m_speciesList[speciesIndex]; + Star::SpeciesDefinitionPtr speciesDefinition = Root::singleton().speciesDatabase()->species(speciesName); + + // make a tooltip from the config file + PanePtr tooltip = make_shared(); + tooltip->removeAllChildren(); + GuiReader reader; + auto& root = Root::singleton(); + String tooltipKind = "/interface/tooltips/species.tooltip"; + reader.construct(root.assets()->json(tooltipKind), tooltip.get()); + + // find out the gender option block from the currently selected gender + auto genderOption = speciesDefinition->options().genderOptions.wrap(m_genderChoice); + // makes an icon out of the default gendered character image + WidgetPtr titleIcon = make_shared(genderOption.characterImage); + + // read the description out of the already loaded species database. + String title = speciesDefinition->tooltip().title; + String subTitle = speciesDefinition->tooltip().subTitle; + tooltip->setTitle(titleIcon, title, subTitle); + + tooltip->setLabel("descriptionLabel", speciesDefinition->tooltip().description); + + return tooltip; + } + } + + return {}; +} + +} -- cgit v1.2.3