1522 lines
58 KiB
C++
1522 lines
58 KiB
C++
#include "battle.h"
|
|
|
|
#include "gui.h"
|
|
|
|
#include <uf/utils/hook/hook.h>
|
|
#include <uf/utils/time/time.h>
|
|
#include <uf/utils/serialize/serializer.h>
|
|
#include <uf/utils/userdata/userdata.h>
|
|
#include <uf/utils/window/window.h>
|
|
#include <uf/utils/camera/camera.h>
|
|
#include <uf/utils/mesh/mesh.h>
|
|
#include <uf/utils/string/ext.h>
|
|
// #include <uf/gl/glyph/glyph.h>
|
|
#include "../world/world.h"
|
|
#include "../world//battle.h"
|
|
#include "../asset/asset.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <uf/ext/vulkan/vulkan.h>
|
|
#include <uf/ext/vulkan/graphics/gui.h>
|
|
|
|
#include <uf/utils/http/http.h>
|
|
#include <uf/utils/audio/audio.h>
|
|
#include <sys/stat.h>
|
|
#include <fstream>
|
|
|
|
namespace {
|
|
ext::Gui* circleIn;
|
|
ext::Gui* circleOut;
|
|
ext::Gui* battleMessage;
|
|
ext::Gui* commandOptions;
|
|
ext::Gui* battleOptions;
|
|
ext::Gui* targetOptions;
|
|
ext::Gui* optionsBackground;
|
|
ext::Gui* turnCounters;
|
|
ext::Gui* partyMemberCommandCircle;
|
|
ext::Gui* critCutInBackground;
|
|
ext::Gui* critCutIn;
|
|
|
|
std::vector<ext::Gui*> partyMemberButtons(5);
|
|
// std::vector<ext::Gui*> partyMemberIcons(5);
|
|
// std::vector<ext::Gui*> partyMemberTexts(5);
|
|
std::vector<ext::Gui*> particles;
|
|
|
|
ext::HousamoBattle* battleManager;
|
|
|
|
ext::World* world;
|
|
|
|
uf::Serializer masterTableGet( const std::string& table ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return std::string();
|
|
uf::Serializer& mastertable = world->getComponent<uf::Serializer>();
|
|
return mastertable["system"]["mastertable"][table];
|
|
}
|
|
uf::Serializer masterDataGet( const std::string& table, const std::string& key ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return std::string();
|
|
uf::Serializer& mastertable = world->getComponent<uf::Serializer>();
|
|
return mastertable["system"]["mastertable"][table][key];
|
|
}
|
|
inline int64_t parseInt( const std::string& str ) {
|
|
return atoi(str.c_str());
|
|
}
|
|
inline std::string colorString( const std::string& hex ) {
|
|
return "%#" + hex + "%";
|
|
}
|
|
|
|
void playSound( ext::GuiBattle& entity, const std::string& id, const std::string& key ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return;
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
|
|
uf::Serializer cardData = masterDataGet("Card", id);
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
std::string name = charaData["filename"].asString();
|
|
|
|
if ( name == "none" ) return;
|
|
|
|
std::string url = "https://cdn..xyz//unity/Android/voice/voice_" + name + "_"+key+".ogg";
|
|
if ( charaData["internal"].asBool() ) {
|
|
url = "./data/smtsamo/voice/voice_" + name + "_" + key + ".ogg";
|
|
}
|
|
|
|
ext::Asset& assetLoader = world->getComponent<ext::Asset>();
|
|
assetLoader.cache(url, "asset:Cache.Voice." + std::to_string(entity.getUid()));
|
|
}
|
|
void playSound( ext::GuiBattle& entity, std::size_t uid, const std::string& key ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return;
|
|
uf::Serializer& pMetadata = entity.getComponent<uf::Serializer>();
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
|
|
uf::Entity* = world->findByUid(uid);
|
|
if ( ! ) return;
|
|
uf::Serializer& metadata = ->getComponent<uf::Serializer>();
|
|
std::string id = metadata[""]["id"].asString();
|
|
playSound( entity, id, key );
|
|
}
|
|
void playSound( ext::GuiBattle& entity, const std::string& key ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return;
|
|
uf::Serializer& metadata = entity.getComponent<uf::Serializer>();
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
|
|
std::string url = "./data/audio/ui/" + key + ".ogg";
|
|
|
|
ext::Asset& assetLoader = world->getComponent<ext::Asset>();
|
|
assetLoader.cache(url, "asset:Cache.SFX." + std::to_string(entity.getUid()));
|
|
}
|
|
void playMusic( ext::GuiBattle& entity, const std::string& filename ) {
|
|
if ( !world ) world = (ext::World*) uf::Entity::globalFindByName("World");
|
|
if ( !world ) return;
|
|
ext::Asset& assetLoader = world->getComponent<ext::Asset>();
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
|
|
assetLoader.load(filename, "asset:Load." + std::to_string(world->getUid()));
|
|
}
|
|
std::string getKeyFromIndex( uf::Serializer& object, uint64_t index ) {
|
|
uint64_t i = 0;
|
|
std::string key = "";
|
|
for ( auto it = object.begin(); it != object.end(); ++it ) {
|
|
if ( i++ == index ) key = it.key().asString();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
uf::Audio sfx;
|
|
uf::Audio voice;
|
|
|
|
struct BattleStats {
|
|
struct {
|
|
int selection = 0;
|
|
int selectionsMax = 3;
|
|
std::vector<std::string> invalids;
|
|
} skill;
|
|
struct {
|
|
int selection = 0;
|
|
int selectionsMax = 3;
|
|
std::vector<std::string> invalids;
|
|
uf::Serializer current;
|
|
} actions;
|
|
struct {
|
|
int selection = 0;
|
|
int selectionsMax = 3;
|
|
std::vector<std::string> invalids;
|
|
uf::Serializer current;
|
|
} targets;
|
|
uf::Serializer currentMember;
|
|
uf::Serializer previousMember;
|
|
std::string state = "open";
|
|
bool initialized = false;
|
|
};
|
|
static BattleStats stats;
|
|
}
|
|
|
|
|
|
void ext::GuiBattle::initialize() {
|
|
ext::Gui::initialize();
|
|
|
|
uf::Serializer& metadata = this->getComponent<uf::Serializer>();
|
|
battleManager = (ext::HousamoBattle*) this->getRootParent().findByName( "Battle Manager" );
|
|
|
|
partyMemberButtons = std::vector<ext::Gui*>(5);
|
|
|
|
this->addHook( "asset:Cache.SFX." + std::to_string(this->getUid()), [&](const std::string& event)->std::string{
|
|
uf::Serializer json = event;
|
|
|
|
std::string filename = json["filename"].asString();
|
|
|
|
if ( uf::string::extension(filename) != "ogg" ) return "false";
|
|
|
|
if ( filename == "" ) return "false";
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
uf::Audio& sfx = this->getComponent<uf::SoundEmitter>().add(filename);
|
|
/*
|
|
uf::Audio& sfx = this->getComponent<uf::Audio>();
|
|
if ( sfx.playing() ) sfx.stop();
|
|
sfx = uf::Audio();
|
|
sfx.load(filename);
|
|
*/
|
|
sfx.setVolume(masterdata["volumes"]["sfx"].asFloat());
|
|
sfx.play();
|
|
|
|
return "true";
|
|
});
|
|
|
|
this->addHook( "asset:Cache.Voice." + std::to_string(this->getUid()), [&](const std::string& event)->std::string{
|
|
uf::Serializer json = event;
|
|
|
|
std::string filename = json["filename"].asString();
|
|
|
|
if ( uf::string::extension(filename) != "ogg" ) return "false";
|
|
|
|
if ( filename == "" ) return "false";
|
|
uf::Serializer& masterdata = world->getComponent<uf::Serializer>();
|
|
uf::Audio& voice = this->getComponent<uf::Audio>();
|
|
if ( voice.playing() ) voice.stop();
|
|
voice = uf::Audio();
|
|
voice.load(filename);
|
|
voice.setVolume(masterdata["volumes"]["voice"].asFloat());
|
|
voice.play();
|
|
|
|
return "true";
|
|
});
|
|
|
|
// lock control
|
|
{
|
|
uf::Serializer payload;
|
|
payload["state"] = true;
|
|
uf::hooks.call("window:Mouse.CursorVisibility", payload);
|
|
uf::hooks.call("window:Mouse.Lock");
|
|
}
|
|
{
|
|
battleMessage = new ext::Gui;
|
|
this->addChild(*battleMessage);
|
|
battleMessage->load("./entities/gui/battle/battle-text.json");
|
|
battleMessage->initialize();
|
|
}
|
|
/* Magic Circle Outter */ {
|
|
circleOut = new ext::Gui;
|
|
this->addChild(*circleOut);
|
|
circleOut->load("./entities/gui/battle/circle-out.json");
|
|
circleOut->initialize();
|
|
}
|
|
/* Magic Circle Inner */ {
|
|
circleIn = new ext::Gui;
|
|
this->addChild(*circleIn);
|
|
circleIn->load("./entities/gui/battle/circle-in.json");
|
|
circleIn->initialize();
|
|
}
|
|
/* Command Circle */ {
|
|
partyMemberCommandCircle = new ext::Gui;
|
|
this->addChild(*partyMemberCommandCircle);
|
|
partyMemberCommandCircle->load("./entities/gui/battle/partyMemberCommandCircle.json");
|
|
partyMemberCommandCircle->initialize();
|
|
}
|
|
/* Command Circle */ {
|
|
optionsBackground = new ext::Gui;
|
|
this->addChild(*optionsBackground);
|
|
optionsBackground->load("./entities/gui/battle/optionsBackground.json");
|
|
optionsBackground->initialize();
|
|
}
|
|
/* Turn Counter Container */ {
|
|
turnCounters = new ext::Gui;
|
|
this->addChild(*turnCounters);
|
|
turnCounters->load("./entities/gui/battle/turnCounters.json");
|
|
turnCounters->initialize();
|
|
}
|
|
|
|
this->addHook("world:Battle.TurnStart.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
|
|
|
|
// turn start, play sfx
|
|
if ( json["battle"]["turn"]["start of turn"].asBool() ) {
|
|
playSound(*this, "turn");
|
|
}
|
|
|
|
return "true";
|
|
});
|
|
this->addHook("world:Battle.Damage.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
|
|
std::string uid = json["target"]["uid"].asString();
|
|
std::string damage = json["target"]["damage"].asString();
|
|
|
|
pod::Transform<> pTransform;
|
|
pTransform.position = { 0, 0, 0 };
|
|
|
|
for ( auto* pointerButton : partyMemberButtons ) {
|
|
if ( !pointerButton ) continue;
|
|
if ( pointerButton->getUid() == 0 ) continue;
|
|
for ( auto* pointer : pointerButton->getChildren() ) {
|
|
if ( !pointer ) continue;
|
|
if ( pointer->getUid() == 0 ) continue;
|
|
if ( pointer->getName() == "Menu: Party Member Icon" ) {
|
|
uf::Serializer& pMetadata = pointer->getComponent<uf::Serializer>();
|
|
if ( pMetadata[""]["uid"].asString() != uid ) continue;
|
|
pTransform = pointer->getComponent<pod::Transform<>>();
|
|
pTransform.position.y -= (0.1);
|
|
}
|
|
}
|
|
}
|
|
ext::Gui* particle = new ext::Gui;
|
|
this->addChild(*particle);
|
|
uf::Serializer& pMetadata = particle->getComponent<uf::Serializer>();
|
|
particle->load("./entities/gui/battle/damageText.json");
|
|
std::string color = json["color"].isString() ? json["color"].asString() : "FF0000";
|
|
pMetadata["text settings"]["string"] = "%#"+color+"%" + damage;
|
|
pod::Transform<>& transform = particle->getComponent<pod::Transform<>>();
|
|
transform = pTransform;
|
|
pMetadata["system"]["percent"] = 0;
|
|
particle->initialize();
|
|
particles.push_back(particle);
|
|
|
|
return "true";
|
|
});
|
|
this->addHook("world:Battle.Message.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
std::string message = json["message"].asString();
|
|
|
|
if ( battleMessage ) {
|
|
battleMessage->destroy();
|
|
this->removeChild(*battleMessage);
|
|
delete battleMessage;
|
|
}
|
|
battleMessage = new ext::Gui;
|
|
this->addChild(*battleMessage);
|
|
uf::Serializer& pMetadata = battleMessage->getComponent<uf::Serializer>();
|
|
battleMessage->load("./entities/gui/battle/battle-text.json");
|
|
pMetadata["text settings"]["string"] = message;
|
|
battleMessage->initialize();
|
|
|
|
// std::cout << message << std::endl;
|
|
|
|
return "true";
|
|
});
|
|
this->addHook("world:Battle.RemoveOnCrit.%UID%", [&](const std::string& event) -> std::string {
|
|
if ( critCutInBackground && critCutInBackground->getUid() != 0 ) {
|
|
critCutInBackground->destroy();
|
|
this->removeChild(*critCutInBackground);
|
|
delete critCutInBackground;
|
|
critCutInBackground = NULL;
|
|
}
|
|
if ( critCutIn && critCutIn->getUid() != 0 ) {
|
|
critCutIn->destroy();
|
|
this->removeChild(*critCutIn);
|
|
delete critCutIn;
|
|
critCutIn = NULL;
|
|
}
|
|
return "true";
|
|
});
|
|
this->addHook("world:Battle.OnCrit.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
if ( critCutInBackground && critCutInBackground->getUid() != 0 ) {
|
|
critCutInBackground->destroy();
|
|
this->removeChild(*critCutInBackground);
|
|
delete critCutInBackground;
|
|
critCutInBackground = NULL;
|
|
}
|
|
if ( critCutIn && critCutIn->getUid() != 0 ) {
|
|
critCutIn->destroy();
|
|
this->removeChild(*critCutIn);
|
|
delete critCutIn;
|
|
critCutIn = NULL;
|
|
}
|
|
{
|
|
critCutInBackground = new ext::Gui;
|
|
this->addChild(*critCutInBackground);
|
|
critCutInBackground->load("./entities/gui/battle/critCutInBackground.json");
|
|
critCutInBackground->initialize();
|
|
}
|
|
{
|
|
critCutIn = new ext::Gui;
|
|
this->addChild(*critCutIn);
|
|
uf::Serializer& pMetadata = critCutIn->getComponent<uf::Serializer>();
|
|
|
|
uf::Serializer cardData = masterDataGet("Card", json["id"].asString());
|
|
pMetadata["system"]["assets"][0] = "./data/smtsamo/ci/ci_"+ cardData["filename"].asString() +".png";
|
|
if ( !uf::string::exists( pMetadata["system"]["assets"][0].asString() ) ) pMetadata["system"]["assets"][0] = Json::nullValue;
|
|
critCutIn->load("./entities/gui/battle/critCutIn.json");
|
|
critCutIn->initialize();
|
|
}
|
|
|
|
playSound(*this, "crit");
|
|
this->queueHook("world:Battle.RemoveOnCrit.%UID%", "", 1.0);
|
|
|
|
return "true";
|
|
});
|
|
this->addHook("world:Battle.Update.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
|
|
{
|
|
float turns = json["battle"]["turn"]["counter"].asFloat();
|
|
if ( turnCounters && turnCounters->getUid() != 0 ) {
|
|
this->removeChild(*turnCounters);
|
|
turnCounters->destroy();
|
|
delete turnCounters;
|
|
}
|
|
{
|
|
turnCounters = new ext::Gui;
|
|
this->addChild(*turnCounters);
|
|
turnCounters->load("./entities/gui/battle/turnCounters.json");
|
|
turnCounters->initialize();
|
|
}
|
|
// show full counters
|
|
float i = 0;
|
|
while ( turns > 0 ) {
|
|
ext::Gui* counter = new ext::Gui;
|
|
turnCounters->addChild(*counter);
|
|
counter->load("./entities/gui/battle/turnCounter.json");
|
|
counter->initialize();
|
|
pod::Transform<>& pTransform = counter->getComponent<pod::Transform<>>();
|
|
pTransform.position.x -= 0.142716f * (i++);
|
|
|
|
uf::Serializer& pMetadata = counter->getComponent<uf::Serializer>();
|
|
if ( json["battle"]["turn"]["phase"] == "enemy" ) {
|
|
pMetadata["color"][0] = 1.0f;
|
|
pMetadata["color"][1] = 0.4f;
|
|
pMetadata["color"][2] = 0.4f;
|
|
pMetadata["color"][3] = 1.0f;
|
|
} else {
|
|
pMetadata["color"][0] = 0.4f;
|
|
pMetadata["color"][1] = 0.0f;
|
|
pMetadata["color"][2] = 0.8f;
|
|
pMetadata["color"][3] = 1.0f;
|
|
}
|
|
|
|
// if ( turns < 1.0f && turns > 0 ) pMetadata["uv"][0] = 0.5f;
|
|
if ( turns < 1.0f && turns > 0 ) {
|
|
pMetadata["half counter"] = true;
|
|
if ( json["battle"]["turn"]["phase"] == "enemy" ) {
|
|
pMetadata["color"][0] = 1.0f;
|
|
pMetadata["color"][1] = 0.7f;
|
|
pMetadata["color"][2] = 0.7f;
|
|
pMetadata["color"][3] = 1.0f;
|
|
} else {
|
|
pMetadata["color"][0] = 0.4f;
|
|
pMetadata["color"][1] = 0.3f;
|
|
pMetadata["color"][2] = 0.9f;
|
|
pMetadata["color"][3] = 1.0f;
|
|
}
|
|
}
|
|
turns = turns - 1.0f;
|
|
}
|
|
}
|
|
|
|
|
|
int i = 0;
|
|
|
|
for ( auto& member : json["battle"]["transients"] ) {
|
|
if ( member["uid"].isNull() ) continue;
|
|
if ( member["type"] == "enemy" ) continue;
|
|
if ( member["hp"].asFloat() <= 0 ) continue;
|
|
if ( i >= 4 ) break;
|
|
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
|
|
std::string name = cardData["filename"].asString();
|
|
if ( member["skin"].isString() ) name += "_" + member["skin"].asString();
|
|
std::string filename = "https://cdn..xyz//unity/Android/icon/icon_" + name + ".png";
|
|
if ( cardData["internal"].asBool() ) {
|
|
filename = "./data/smtsamo/icon/icon_" + name + ".png";
|
|
}
|
|
|
|
ext::Gui* partyMemberButton = NULL;
|
|
pod::Transform<> bTransform;
|
|
|
|
// completely recreate if different transient
|
|
if ( partyMemberButtons[i] != NULL ) {
|
|
partyMemberButton = partyMemberButtons[i];
|
|
uf::Serializer& pMetadata = partyMemberButton->getComponent<uf::Serializer>();
|
|
if ( pMetadata[""]["id"] != member["id"] ) {
|
|
this->removeChild(*partyMemberButton);
|
|
partyMemberButton->destroy();
|
|
delete partyMemberButton;
|
|
partyMemberButton = NULL;
|
|
} else {
|
|
pod::Transform<>& transform = partyMemberButton->getComponent<pod::Transform<>>();
|
|
bTransform = transform;
|
|
}
|
|
}
|
|
|
|
// update
|
|
if ( partyMemberButton ) {
|
|
{
|
|
ext::Gui* partyMemberText = (ext::Gui*) partyMemberButton->findByName("Menu: Party Member Text");
|
|
if ( partyMemberText ) {
|
|
partyMemberButton->removeChild(*partyMemberText);
|
|
partyMemberText->destroy();
|
|
delete partyMemberText;
|
|
partyMemberText = NULL;
|
|
}
|
|
partyMemberText = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberText);
|
|
uf::Serializer& pMetadata = partyMemberText->getComponent<uf::Serializer>();
|
|
partyMemberText->load("./entities/gui/battle/partyMemberText.json");
|
|
pMetadata["text settings"]["string"] = "" + colorString("00FF00") + "" + member["hp"].asString() + "\n" + colorString("0000FF") + "" + member["mp"].asString();
|
|
pMetadata[""] = member;
|
|
pod::Transform<>& transform = partyMemberText->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x += (-0.781352 - -0.694634) + 0.1;
|
|
partyMemberText->initialize();
|
|
}
|
|
|
|
ext::Gui* partyMemberHP = (ext::Gui*) partyMemberButton->findByName("Menu: Party Member HP Bar");
|
|
if ( partyMemberHP ) {
|
|
uf::Serializer& pMetadata = partyMemberHP->getComponent<uf::Serializer>();
|
|
float percentage = 1.0f;
|
|
percentage = member["hp"].asFloat() / member["max hp"].asFloat();
|
|
// uv[0, 0] -> uv[09.43, 23.81]
|
|
// uv[1, 1] -> uv[84.91, 68.25]
|
|
pMetadata["uv"][2] = (percentage * 0.8491) + 0.0660;
|
|
// pMetadata["uv"][3] = (percentage * 0.6808) + (1 - 0.0952);
|
|
}
|
|
ext::Gui* partyMemberMP = (ext::Gui*) partyMemberButton->findByName("Menu: Party Member MP Bar");
|
|
if ( partyMemberMP ) {
|
|
uf::Serializer& pMetadata = partyMemberMP->getComponent<uf::Serializer>();
|
|
float percentage = 1.0f;
|
|
percentage = member["mp"].asFloat() / member["max mp"].asFloat();
|
|
// uv[0, 0] -> uv[09.43, 23.81]
|
|
// uv[1, 1] -> uv[84.91, 68.25]
|
|
pMetadata["uv"][2] = (percentage * 0.8491) + 0.0943;
|
|
// pMetadata["uv"][3] = (percentage * 0.6825) + (1 - 0.2381);
|
|
}
|
|
// recreate
|
|
} else {
|
|
{
|
|
partyMemberButton = new ext::Gui;
|
|
this->addChild(*partyMemberButton);
|
|
partyMemberButton->load("./entities/gui/battle/partyMemberButton.json");
|
|
|
|
pod::Transform<>& transform = partyMemberButton->getComponent<pod::Transform<>>();
|
|
transform.position.y += (i * 0.45);
|
|
bTransform = transform;
|
|
|
|
partyMemberButton->initialize();
|
|
|
|
uf::Serializer& pMetadata = partyMemberButton->getComponent<uf::Serializer>();
|
|
pMetadata[""] = member;
|
|
|
|
partyMemberButtons[i] = partyMemberButton;
|
|
}
|
|
{
|
|
ext::Gui* partyMemberIconShadow = NULL;
|
|
partyMemberIconShadow = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberIconShadow);
|
|
uf::Serializer& pMetadata = partyMemberIconShadow->getComponent<uf::Serializer>();
|
|
pMetadata["system"]["assets"][0] = filename;
|
|
partyMemberIconShadow->load("./entities/gui/battle/partyMemberIconShadow.json");
|
|
pMetadata[""] = member;
|
|
pod::Transform<>& transform = partyMemberIconShadow->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x += (-0.781352 - -0.694634) + 0.01;
|
|
transform.position.y -= 0.01;
|
|
partyMemberIconShadow->initialize();
|
|
}
|
|
{
|
|
ext::Gui* partyMemberIcon = NULL;
|
|
partyMemberIcon = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberIcon);
|
|
uf::Serializer& pMetadata = partyMemberIcon->getComponent<uf::Serializer>();
|
|
pMetadata["system"]["assets"][0] = filename;
|
|
partyMemberIcon->load("./entities/gui/battle/partyMemberIcon.json");
|
|
pMetadata[""] = member;
|
|
pod::Transform<>& transform = partyMemberIcon->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x += (-0.781352 - -0.694634);
|
|
partyMemberIcon->initialize();
|
|
}
|
|
{
|
|
ext::Gui* partyMemberText = NULL;
|
|
partyMemberText = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberText);
|
|
uf::Serializer& pMetadata = partyMemberText->getComponent<uf::Serializer>();
|
|
partyMemberText->load("./entities/gui/battle/partyMemberText.json");
|
|
pMetadata["text settings"]["string"] = "" + colorString("FF0000") + "" + member["hp"].asString() + "\n" + colorString("0000FF") + "" + member["mp"].asString();
|
|
pMetadata[""] = member;
|
|
pod::Transform<>& transform = partyMemberText->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x += (-0.781352 - -0.694634) + 0.1;
|
|
partyMemberText->initialize();
|
|
}
|
|
{
|
|
ext::Gui* partyMemberBar = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberBar);
|
|
partyMemberBar->load("./entities/gui/battle/partyMemberBars.json");
|
|
pod::Transform<>& transform = partyMemberBar->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x = 0.742573;
|
|
transform.position.y += 0.1;
|
|
partyMemberBar->initialize();
|
|
}
|
|
{
|
|
ext::Gui* partyMemberHP = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberHP);
|
|
partyMemberHP->load("./entities/gui/battle/partyMemberHP.json");
|
|
pod::Transform<>& transform = partyMemberHP->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x = 0.742573;
|
|
transform.position.y += 0.1;
|
|
partyMemberHP->initialize();
|
|
|
|
uf::Serializer& pMetadata = partyMemberHP->getComponent<uf::Serializer>();
|
|
float percentage = 1.0f;
|
|
percentage = member["hp"].asFloat() / member["max hp"].asFloat();
|
|
// uv[0, 0] -> uv[09.43, 23.81]
|
|
// uv[1, 1] -> uv[84.91, 68.25]
|
|
pMetadata["uv"][2] = (percentage * 0.8491) + 0.0660;
|
|
// pMetadata["uv"][3] = (percentage * 0.6808) + (1 - 0.0952);
|
|
}
|
|
{
|
|
ext::Gui* partyMemberMP = new ext::Gui;
|
|
partyMemberButton->addChild(*partyMemberMP);
|
|
partyMemberMP->load("./entities/gui/battle/partyMemberMP.json");
|
|
pod::Transform<>& transform = partyMemberMP->getComponent<pod::Transform<>>();
|
|
transform.position = bTransform.position;
|
|
transform.position.x = 0.742573;
|
|
transform.position.y += 0.1;
|
|
partyMemberMP->initialize();
|
|
|
|
uf::Serializer& pMetadata = partyMemberMP->getComponent<uf::Serializer>();
|
|
float percentage = 1.0f;
|
|
percentage = member["mp"].asFloat() / member["max mp"].asFloat();
|
|
// uv[0, 0] -> uv[09.43, 23.81]
|
|
// uv[1, 1] -> uv[84.91, 68.25]
|
|
pMetadata["uv"][2] = (percentage * 0.8491) + 0.0943;
|
|
// pMetadata["uv"][3] = (percentage * 0.6825) + (1 - 0.2381);
|
|
}
|
|
}
|
|
++i;
|
|
};
|
|
for ( ; i < 4; ++i ) {
|
|
if ( partyMemberButtons[i] != NULL ) {
|
|
ext::Gui*& partyMemberButton = partyMemberButtons[i];
|
|
this->removeChild(*partyMemberButton);
|
|
partyMemberButton->destroy();
|
|
delete partyMemberButton;
|
|
partyMemberButton = NULL;
|
|
}
|
|
}
|
|
return "true";
|
|
});
|
|
|
|
this->addHook("menu:Close.%UID%", [&]( const std::string& event ) -> std::string {
|
|
// kill
|
|
this->getParent().removeChild(*this);
|
|
this->destroy();
|
|
delete this;
|
|
return "true";
|
|
});
|
|
|
|
stats = BattleStats();
|
|
}
|
|
void ext::GuiBattle::tick() {
|
|
if ( !battleManager ) return;
|
|
|
|
ext::World& world = this->getRootParent<ext::World>();
|
|
uf::Serializer& masterdata = world.getComponent<uf::Serializer>();
|
|
uf::Serializer& bMetadata = this->getComponent<uf::Serializer>();
|
|
|
|
static float alpha = 0.0f;
|
|
static uf::Timer<long long> timer(false);
|
|
static float timeout = 1.0f;
|
|
if ( !timer.running() ) timer.start();
|
|
static std::string queuedAction;
|
|
|
|
std::function<void(uf::Serializer&)> postParseResult = [&]( uf::Serializer& result ) {
|
|
if ( result["end"].asBool() ) bMetadata["system"]["closing"] = true;
|
|
if ( !result["timeout"].isNull() ) timeout = result["timeout"].asFloat();
|
|
else timeout = 1;
|
|
// modify timeout based on string length
|
|
if ( result["message"].isString() && !result["target"].isNull() ) {
|
|
uint64_t size = result["message"].asString().size();
|
|
float modified = size * 0.025f;
|
|
if ( timeout < modified ) timeout = modified;
|
|
}
|
|
|
|
uf::Serializer payload;
|
|
payload["message"] = result["message"];
|
|
this->callHook("world:Battle.Message.%UID%", payload);
|
|
|
|
timer.reset();
|
|
};
|
|
|
|
std::function<void(uf::Serializer)> renderSkillOptions = [&]( uf::Serializer member ) {
|
|
if ( battleOptions ) {
|
|
this->removeChild(*battleOptions);
|
|
battleOptions->destroy();
|
|
delete battleOptions;
|
|
battleOptions = NULL;
|
|
}
|
|
if ( member["uid"].isNull() ) {
|
|
stats.previousMember = stats.currentMember;
|
|
return;
|
|
}
|
|
stats.currentMember = member;
|
|
std::string string = "";
|
|
std::size_t counter = 0;
|
|
std::size_t start = stats.skill.selection;
|
|
stats.actions.invalids.clear();
|
|
stats.currentMember["skills"] = Json::arrayValue;
|
|
for ( int i = 0; i < member["skills"].size(); ++i ) {
|
|
std::string id = member["skills"][i].asString();
|
|
uf::Serializer skillData = masterDataGet("Skill", id);
|
|
bool add = false;
|
|
if ( skillData["type"].asInt() > 0 && skillData["type"].asInt() < 16 ) add = true;
|
|
if ( add ) stats.currentMember["skills"].append(id);
|
|
}
|
|
|
|
std::string skillDescription = "";
|
|
|
|
for ( int i = start; i < stats.currentMember["skills"].size(); ++i ) {
|
|
std::string id = stats.currentMember["skills"][i].asString();
|
|
std::string text = "";
|
|
if ( std::find( stats.skill.invalids.begin(), stats.skill.invalids.end(), id ) != stats.skill.invalids.end() ) continue;
|
|
if ( ++counter > stats.skill.selectionsMax ) break;
|
|
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
uf::Serializer skillData = masterDataGet("Skill", id);
|
|
if ( id != "0" ) {
|
|
text = skillData["name"].asString();
|
|
} else {
|
|
text = "攻撃";
|
|
}
|
|
if ( i != stats.skill.selection ) {
|
|
text = "" + colorString("FFFFFF") + "" + text;
|
|
}
|
|
else {
|
|
text = "" + colorString("FF0000") + "" + text;
|
|
skillDescription = skillData["effect"].asString();
|
|
if ( skillData["mp"].isNumeric() ) {
|
|
int64_t cost = skillData["mp"].asInt64();
|
|
skillDescription = skillData["effect"].asString() + "\nCost: " + colorString("0000FF") + ""+std::to_string(cost)+" MP" + colorString("FFFFFF") + "";
|
|
}
|
|
if ( skillData["hp%"].isNumeric() ) {
|
|
int64_t cost = skillData["hp%"].asInt64();
|
|
cost = member["max hp"].asFloat() * ( cost / 100.0f );
|
|
skillDescription = skillData["effect"].asString() + "\nCost: " + colorString("00FF00") + ""+std::to_string(cost)+" HP" + colorString("FFFFFF") + "";
|
|
}
|
|
}
|
|
string += "\n" + text;
|
|
}
|
|
if ( stats.currentMember["skills"].size() > stats.skill.selectionsMax ) {
|
|
for ( int i = 0; i < i < stats.currentMember["skills"].size(); ++i ) {
|
|
std::string id = stats.currentMember["skills"][i].asString();
|
|
std::string text = "";
|
|
if ( std::find( stats.skill.invalids.begin(), stats.skill.invalids.end(), id ) != stats.skill.invalids.end() ) continue;
|
|
if ( ++counter > stats.skill.selectionsMax ) break;
|
|
if ( id != "0" ) {
|
|
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
uf::Serializer skillData = masterDataGet("Skill", id);
|
|
|
|
text = skillData["name"].asString();
|
|
} else {
|
|
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
|
|
text = "攻撃";
|
|
}
|
|
if ( i != stats.skill.selection ) text = "" + colorString("FFFFFF") + "" + text;
|
|
else text = "" + colorString("FF0000") + "" + text;
|
|
string += "\n" + text;
|
|
}
|
|
}
|
|
if ( string != "" ) {
|
|
string = string.substr(1);
|
|
battleOptions = new ext::Gui;
|
|
this->addChild(*battleOptions);
|
|
uf::Serializer& pMetadata = battleOptions->getComponent<uf::Serializer>();
|
|
battleOptions->load("./entities/gui/battle/battle-option.json");
|
|
pMetadata["text settings"]["string"] = string;
|
|
pod::Transform<>& pTransform = battleOptions->getComponent<pod::Transform<>>();
|
|
battleOptions->initialize();
|
|
}
|
|
if ( skillDescription != "" ) {
|
|
uf::Serializer payload;
|
|
payload["message"] = skillDescription;
|
|
this->callHook("world:Battle.Message.%UID%", payload);
|
|
}
|
|
};
|
|
std::function<void(uf::Serializer)> renderCommandOptions = [&]( uf::Serializer actions ) {
|
|
if ( commandOptions ) {
|
|
this->removeChild(*commandOptions);
|
|
commandOptions->destroy();
|
|
delete commandOptions;
|
|
commandOptions = NULL;
|
|
}
|
|
if ( actions.isNull() ) {
|
|
stats.actions.selection = 0;
|
|
queuedAction = "";
|
|
return;
|
|
}
|
|
stats.actions.current = actions;
|
|
std::string string = "";
|
|
std::size_t counter = 0;
|
|
std::size_t start = stats.actions.selection;
|
|
auto keys = actions.getMemberNames();
|
|
for ( int i = start; i < keys.size(); ++i ) {
|
|
std::string key = getKeyFromIndex( actions, i );
|
|
std::string text = actions[key].asString();
|
|
if ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), key ) != stats.actions.invalids.end() ) continue;
|
|
if ( ++counter > stats.actions.selectionsMax ) break;
|
|
if ( i != stats.actions.selection ) text = "" + colorString("FFFFFF") + "" + text;
|
|
else text = "" + colorString("FF0000") + "" + text;
|
|
string += "\n" + text;
|
|
}
|
|
if ( keys.size() > stats.actions.selectionsMax ) {
|
|
for ( int i = 0; i < keys.size(); ++i ) {
|
|
std::string key = getKeyFromIndex( actions, i );
|
|
std::string text = actions[key].asString();
|
|
if ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), key ) != stats.actions.invalids.end() ) continue;
|
|
if ( ++counter > stats.actions.selectionsMax ) break;
|
|
if ( i != stats.actions.selection ) text = "" + colorString("FFFFFF") + "" + text;
|
|
else text = "" + colorString("FF0000") + "" + text;
|
|
string += "\n" + text;
|
|
}
|
|
}
|
|
if ( string != "" ) {
|
|
string = string.substr(1);
|
|
commandOptions = new ext::Gui;
|
|
this->addChild(*commandOptions);
|
|
uf::Serializer& pMetadata = commandOptions->getComponent<uf::Serializer>();
|
|
commandOptions->load("./entities/gui/battle/battle-option.json");
|
|
pMetadata["text settings"]["string"] = string;
|
|
pod::Transform<>& pTransform = commandOptions->getComponent<pod::Transform<>>();
|
|
commandOptions->initialize();
|
|
}
|
|
};
|
|
std::function<void(uf::Serializer)> renderTargetOptions = [&]( uf::Serializer actions ) {
|
|
if ( targetOptions ) {
|
|
this->removeChild(*targetOptions);
|
|
targetOptions->destroy();
|
|
delete targetOptions;
|
|
targetOptions = NULL;
|
|
}
|
|
if ( actions.isNull() ) {
|
|
stats.skill.selection = 0;
|
|
stats.targets.selection = 0;
|
|
return;
|
|
}
|
|
stats.targets.current = actions;
|
|
std::string string = "";
|
|
std::size_t counter = 0;
|
|
std::size_t start = stats.targets.selection;
|
|
stats.skill.invalids.clear();
|
|
stats.actions.invalids.clear();
|
|
std::string targetDescription = "";
|
|
for ( int i = start; i < actions.size(); ++i ) {
|
|
uf::Serializer cardData = masterDataGet("Card", actions[i]["id"].asString());
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
std::string text = charaData["name"].asString();
|
|
if ( std::find( stats.targets.invalids.begin(), stats.targets.invalids.end(), text ) != stats.targets.invalids.end() ) continue;
|
|
if ( ++counter > stats.targets.selectionsMax ) break;
|
|
if ( i != stats.targets.selection ) text = "" + colorString("FFFFFF") + "" + text;
|
|
else text = "" + colorString("FF0000") + "" + text;
|
|
string += "\n" + text;
|
|
}
|
|
if ( actions.size() > stats.targets.selectionsMax ) {
|
|
for ( int i = 0; i < actions.size(); ++i ) {
|
|
uf::Serializer cardData = masterDataGet("Card", actions[i]["id"].asString());
|
|
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
|
|
std::string text = charaData["name"].asString();
|
|
if ( std::find( stats.targets.invalids.begin(), stats.targets.invalids.end(), text ) != stats.targets.invalids.end() ) continue;
|
|
if ( ++counter > stats.targets.selectionsMax ) break;
|
|
if ( i != stats.targets.selection ) text = "" + colorString("FFFFFF") + "" + text;
|
|
else text = "" + colorString("FF0000") + "" + text;
|
|
string += "\n" + text;
|
|
}
|
|
}
|
|
if ( string != "" ) {
|
|
string = string.substr(1);
|
|
targetOptions = new ext::Gui;
|
|
this->addChild(*targetOptions);
|
|
uf::Serializer& pMetadata = targetOptions->getComponent<uf::Serializer>();
|
|
targetOptions->load("./entities/gui/battle/battle-option.json");
|
|
pMetadata["text settings"]["string"] = string;
|
|
pod::Transform<>& pTransform = targetOptions->getComponent<pod::Transform<>>();
|
|
targetOptions->initialize();
|
|
}
|
|
if ( queuedAction == "analyze" ) {
|
|
uf::Serializer payload;
|
|
payload["uid"] = stats.targets.current[stats.targets.selection]["uid"].asString();
|
|
// if ( payload["uid"].asString() == "")
|
|
payload["action"] = "analyze";
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
|
|
targetDescription = result["message"].asString();
|
|
}
|
|
if ( targetDescription != "" ) {
|
|
uf::Serializer payload;
|
|
payload["message"] = targetDescription;
|
|
this->callHook("world:Battle.Message.%UID%", payload);
|
|
}
|
|
};
|
|
|
|
|
|
if ( !stats.initialized ) {
|
|
stats.initialized = true;
|
|
uf::Serializer& metadata = this->getComponent<uf::Serializer>();
|
|
this->addHook("world:Battle.Turn.%UID%", [&](const std::string& event) -> std::string {
|
|
uf::Serializer json = event;
|
|
uf::Serializer payload;
|
|
payload["action"] = "turn-start";
|
|
if ( !stats.previousMember["uid"].isNull() ) payload["uid"] = stats.previousMember["uid"].asUInt64() + 1;
|
|
auto hookCall = battleManager->callHook("world:Battle.Action.%UID%", payload);
|
|
if ( hookCall.empty() ) {
|
|
metadata["system"]["closing"] = true;
|
|
return "false";
|
|
}
|
|
uf::Serializer result = hookCall[0];
|
|
|
|
if ( result["end"].asBool() ) {
|
|
if ( metadata.isObject() ) metadata["system"]["closing"] = true;
|
|
return "false";
|
|
}
|
|
|
|
postParseResult(result);
|
|
if ( result["uid"].isNull() ) return "false";
|
|
uint64_t uid = result["uid"].asUInt64();
|
|
|
|
renderCommandOptions(std::string(""));
|
|
renderSkillOptions(std::string(""));
|
|
renderTargetOptions(std::string(""));
|
|
|
|
payload = uf::Serializer{};
|
|
payload["uid"] = uid;
|
|
if ( result[""]["type"] == "enemy" ) {
|
|
payload["action"] = "enemy-attack";
|
|
} else if ( result["skipped"].asBool() ) {
|
|
payload["action"] = "forced-pass";
|
|
} else {
|
|
stats.currentMember = result[""];
|
|
renderCommandOptions(result["actions"]);
|
|
return "true";
|
|
}
|
|
result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
postParseResult(result);
|
|
stats.previousMember = uf::Serializer{};
|
|
timer.reset();
|
|
stats.state = "waiting";
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
return "true";
|
|
});
|
|
this->queueHook("world:Battle.Turn.%UID%");
|
|
}
|
|
|
|
{
|
|
uf::Serializer& metadata = this->getComponent<uf::Serializer>();
|
|
if ( metadata["system"]["closing"].asBool() ) {
|
|
if ( alpha >= 1.0f ) {
|
|
ext::Asset assetLoader;
|
|
std::string canonical = assetLoader.load("./data/audio/ui/menu close.ogg");
|
|
uf::Audio& sfx = assetLoader.get<uf::Audio>(canonical);
|
|
sfx.setVolume(masterdata["volumes"]["sfx"].asFloat());
|
|
sfx.play();
|
|
}
|
|
if ( alpha <= 0.0f ) {
|
|
alpha = 0.0f;
|
|
metadata["system"]["closing"] = false;
|
|
metadata["system"]["closed"] = true;
|
|
} else alpha -= uf::physics::time::delta;
|
|
metadata["color"][3] = alpha;
|
|
} else if ( metadata["system"]["closed"].asBool() ) {
|
|
ext::Object& parent = this->getParent<ext::Object>();
|
|
parent.getComponent<uf::Serializer>()["system"]["closed"] = true;
|
|
this->destroy();
|
|
parent.removeChild(*this);
|
|
} else {
|
|
if ( !metadata["initialized"].asBool() ) alpha = 0.0f;
|
|
metadata["initialized"] = true;
|
|
if ( alpha >= 1.0f ) alpha = 1.0f;
|
|
else alpha += uf::physics::time::delta * 1.5f;
|
|
metadata["color"][3] = alpha;
|
|
}
|
|
}
|
|
|
|
if ( circleIn ) {
|
|
pod::Transform<>& transform = circleIn->getComponent<pod::Transform<>>();
|
|
static float time = 0.0f;
|
|
float speed = 0.0125f;
|
|
uf::Serializer& metadata = circleIn->getComponent<uf::Serializer>();
|
|
if ( metadata["hovered"].asBool() ) speed = -0.25f;
|
|
time += uf::physics::time::delta * speed;
|
|
transform.orientation = uf::quaternion::axisAngle( { 0.0f, 0.0f, 1.0f }, time );
|
|
|
|
static pod::Vector3f start = { transform.position.x, -2.0f, 0.0f };
|
|
static pod::Vector3f end = transform.position;
|
|
static float delta = 0.0f;
|
|
if ( !metadata["initialized"].asBool() ) delta = 0.0f;
|
|
metadata["initialized"] = true;
|
|
if ( delta >= 1 ) delta = 1;
|
|
else {
|
|
delta += uf::physics::time::delta * 1.5f;
|
|
transform.position = uf::vector::lerp( start, end, delta );
|
|
}
|
|
metadata["color"][3] = alpha * 0.1f;
|
|
}
|
|
if ( circleOut ) {
|
|
pod::Transform<>& transform = circleOut->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = circleOut->getComponent<uf::Serializer>();
|
|
static float time = 0.0f;
|
|
float speed = 0.0125f;
|
|
if ( metadata["hovered"].asBool() ) speed = 0.25f;
|
|
time += uf::physics::time::delta * speed;
|
|
transform.orientation = uf::quaternion::axisAngle( { 0.0f, 0.0f, 1.0f }, time );
|
|
|
|
static pod::Vector3f start = { transform.position.x, 2.0f, 0.0f };
|
|
static pod::Vector3f end = transform.position;
|
|
static float delta = 0.0f;
|
|
if ( !metadata["initialized"].asBool() ) delta = 0.0f;
|
|
metadata["initialized"] = true;
|
|
if ( delta >= 1 ) delta = 1;
|
|
else {
|
|
delta += uf::physics::time::delta * 1.5f;
|
|
transform.position = uf::vector::lerp( start, end, delta );
|
|
}
|
|
metadata["color"][3] = alpha * 0.1f;
|
|
}
|
|
if ( turnCounters ) {
|
|
static float time = 0.0f;
|
|
time += uf::physics::time::delta;
|
|
for ( uf::Entity* pointer : turnCounters->getChildren() ) {
|
|
uf::Serializer& pMetadata = pointer->getComponent<uf::Serializer>();
|
|
float coeff = 2.0f;
|
|
// if ( pMetadata["half counter"].asBool() ) coeff = 4.0f;
|
|
// pMetadata["color"][3] = sin(time * coeff) * 0.25f + 0.75f;
|
|
pMetadata["color"][3] = std::min( 1.0f, (float) sin(time * coeff) * 0.5f + 1.0f );
|
|
}
|
|
}
|
|
if ( optionsBackground ) {
|
|
pod::Transform<>& transform = optionsBackground->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = optionsBackground->getComponent<uf::Serializer>();
|
|
|
|
static pod::Vector3f start = { transform.position.x, 2.0f, 0.0f };
|
|
static pod::Vector3f end = transform.position;
|
|
static float delta = 0.0f;
|
|
if ( !metadata["initialized"].asBool() ) delta = 0.0f;
|
|
metadata["initialized"] = true;
|
|
if ( delta >= 1 ) delta = 1;
|
|
else {
|
|
delta += uf::physics::time::delta * 1.5f;
|
|
transform.position = uf::vector::lerp( start, end, delta );
|
|
}
|
|
metadata["color"][3] = alpha;
|
|
if ( !commandOptions && !battleOptions && !targetOptions ) {
|
|
metadata["color"][3] = 0.1f;
|
|
}
|
|
}
|
|
|
|
for ( auto* pointerButton : partyMemberButtons ) {
|
|
if ( !pointerButton ) continue;
|
|
if ( pointerButton->getUid() == 0 ) continue;
|
|
for ( auto* pointer : pointerButton->getChildren() ) {
|
|
if ( !pointer ) continue;
|
|
if ( pointer->getUid() == 0 ) continue;
|
|
if ( pointer->getName() == "Menu: Party Member Icon" ) {
|
|
pod::Transform<>& transform = pointer->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = pointer->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
if ( metadata["clicked"].asBool() && timer.elapsed().asDouble() >= timeout ) {
|
|
}
|
|
static float color = 0.0f;
|
|
// std::cout << pointer << ": " << metadata << ": " << stats.currentMember << std::endl;
|
|
if ( metadata[""]["uid"] == stats.currentMember["uid"] ) {
|
|
color += uf::physics::time::delta * 4.0f;
|
|
float factor = (1 - (1.0f + cos(color)) / 8.0f);
|
|
metadata["color"][0] = 1.0f * factor;
|
|
metadata["color"][1] = 1.0f * factor;
|
|
metadata["color"][2] = 0.6f * factor;
|
|
|
|
if ( partyMemberCommandCircle && partyMemberCommandCircle->getUid() != 0 ) {
|
|
uf::Serializer& metadata = partyMemberCommandCircle->getComponent<uf::Serializer>();
|
|
pod::Transform<>& pTransform = partyMemberCommandCircle->getComponent<pod::Transform<>>();
|
|
if ( pTransform.position.x == 0 && pTransform.position.y == 0 ) {
|
|
pTransform.position.x = transform.position.x;
|
|
pTransform.position.y = transform.position.y;
|
|
}
|
|
metadata["system"]["lerp"]["delta"] = 0;
|
|
metadata["system"]["lerp"]["start"][0] = pTransform.position.x;
|
|
metadata["system"]["lerp"]["start"][1] = pTransform.position.y;
|
|
metadata["system"]["lerp"]["start"][2] = pTransform.position.z;
|
|
metadata["system"]["lerp"]["end"][0] = transform.position.x;
|
|
metadata["system"]["lerp"]["end"][1] = transform.position.y;
|
|
metadata["system"]["lerp"]["end"][2] = transform.position.z;
|
|
}
|
|
} else {
|
|
metadata["color"][0] = 1.0f;
|
|
metadata["color"][2] = 1.0f;
|
|
metadata["color"][2] = 1.0f;
|
|
}
|
|
} else if ( pointer->getName() == "Menu: Party Member Text" ) {
|
|
pod::Transform<>& transform = pointer->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = pointer->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
if ( metadata["clicked"].asBool() && timer.elapsed().asDouble() >= timeout ) {
|
|
}
|
|
static float color = 0.0f;
|
|
// std::cout << pointer << ": " << metadata << ": " << stats.currentMember << std::endl;
|
|
if ( metadata[""]["uid"] == stats.currentMember["uid"] ) {
|
|
color += uf::physics::time::delta * 4.0f;
|
|
float factor = (1 - (1.0f + cos(color)) / 8.0f);
|
|
metadata["color"][0] = 1.0f * factor;
|
|
metadata["color"][1] = 1.0f * factor;
|
|
metadata["color"][2] = 0.6f * factor;
|
|
} else {
|
|
metadata["color"][0] = 1.0f;
|
|
metadata["color"][2] = 1.0f;
|
|
metadata["color"][2] = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* particles */ for ( auto it = particles.begin(); it != particles.end(); ++it ) {
|
|
ext::Gui* particle = *it;
|
|
if ( !particle ) { particles.erase( it ); return; }
|
|
if ( particle->getUid() == 0 ) { particles.erase( it ); return; }
|
|
uf::Serializer& metadata = particle->getComponent<uf::Serializer>();
|
|
pod::Transform<>& transform = particle->getComponent<pod::Transform<>>();
|
|
// state 1
|
|
// std::cout << particle << ": " << metadata["system"]["percent"] << ": " << metadata["text settings"]["string"] << std::endl;
|
|
// std::cout << transform.position.x << ", " << transform.position.y << ", " << transform.position.z << std::endl;
|
|
try {
|
|
if ( !metadata["system"].isObject() ) {
|
|
this->removeChild(*particle);
|
|
particle->destroy();
|
|
delete particle;
|
|
*it = NULL;
|
|
continue;
|
|
}
|
|
if ( metadata["system"]["percent"] < 0.25f ) {
|
|
transform.position.y -= uf::physics::time::delta / 2.0f;
|
|
} else if ( metadata["system"]["percent"] < 0.5f ) {
|
|
transform.position.y += uf::physics::time::delta / 2.0f;
|
|
} else if ( metadata["system"]["percent"] < 1.0f ) {
|
|
} else {
|
|
this->removeChild(*particle);
|
|
particle->destroy();
|
|
delete particle;
|
|
*it = NULL;
|
|
continue;
|
|
}
|
|
metadata["system"]["percent"] = metadata["system"]["percent"].asFloat() + uf::physics::time::delta;
|
|
} catch ( ... ) {
|
|
if ( particle ) {
|
|
this->removeChild(*particle);
|
|
particle->destroy();
|
|
delete particle;
|
|
*it = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( auto* pointer : partyMemberButtons ) {
|
|
if ( !pointer ) continue;
|
|
if ( pointer->getUid() == 0 ) continue;
|
|
pod::Transform<>& transform = pointer->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = pointer->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
}
|
|
|
|
if ( partyMemberCommandCircle && partyMemberCommandCircle->getUid() != 0 ) {
|
|
pod::Transform<>& transform = partyMemberCommandCircle->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = partyMemberCommandCircle->getComponent<uf::Serializer>();
|
|
static float time = 0.0f;
|
|
float speed = 0.5f;
|
|
if ( metadata["hovered"].asBool() ) speed = 0.75f;
|
|
time += uf::physics::time::delta * speed;
|
|
transform.orientation = uf::quaternion::axisAngle( { 0.0f, 0.0f, 1.0f }, time );
|
|
metadata["color"][3] = alpha;
|
|
|
|
{
|
|
pod::Vector3f start = {
|
|
metadata["system"]["lerp"]["start"][0].asFloat(),
|
|
metadata["system"]["lerp"]["start"][1].asFloat(),
|
|
metadata["system"]["lerp"]["start"][2].asFloat(),
|
|
};
|
|
pod::Vector3f end = {
|
|
metadata["system"]["lerp"]["end"][0].asFloat(),
|
|
metadata["system"]["lerp"]["end"][1].asFloat(),
|
|
metadata["system"]["lerp"]["end"][2].asFloat(),
|
|
};
|
|
float delta = metadata["system"]["lerp"]["delta"].asFloat();
|
|
if ( delta >= 1 ) delta = 1;
|
|
else {
|
|
delta += uf::physics::time::delta * 8.0f;
|
|
transform.position = uf::vector::lerp( start, end, delta );
|
|
}
|
|
metadata["system"]["lerp"]["delta"] = delta;
|
|
}
|
|
}
|
|
|
|
ext::Gui::tick();
|
|
|
|
if ( stats.state != "open" ) {
|
|
if ( timer.elapsed().asDouble() >= timeout ) stats.state = "open";
|
|
return;
|
|
}
|
|
|
|
struct {
|
|
bool up = uf::Window::isKeyPressed("W");
|
|
bool down = uf::Window::isKeyPressed("S");
|
|
bool back = uf::Window::isKeyPressed("Escape");
|
|
bool select = uf::Window::isKeyPressed("Enter");
|
|
} keys;
|
|
|
|
if ( targetOptions ) {
|
|
bool delta = false;
|
|
bool invalid = false;
|
|
pod::Transform<>& transform = targetOptions->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = targetOptions->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
for ( uf::Entity* pointer : targetOptions->getChildren() ) {
|
|
uf::Serializer& cMetadata = pointer->getComponent<uf::Serializer>();
|
|
cMetadata["text settings"]["color"][3] = alpha;
|
|
}
|
|
static bool released = true;
|
|
if ( keys.up ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
--stats.targets.selection;
|
|
if ( stats.targets.selection < 0 ) { stats.targets.selection = stats.targets.current.size() - 1; }
|
|
} while ( std::find( stats.targets.invalids.begin(), stats.targets.invalids.end(), stats.targets.current[stats.targets.selection]["id"].asString() ) != stats.targets.invalids.end() );
|
|
}
|
|
}
|
|
else if ( keys.down ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
++stats.targets.selection;
|
|
if ( stats.targets.selection >= stats.targets.current.size() ) { stats.targets.selection = 0; }
|
|
} while ( std::find( stats.targets.invalids.begin(), stats.targets.invalids.end(), stats.targets.current[stats.targets.selection]["id"].asString() ) != stats.targets.invalids.end() );
|
|
}
|
|
}
|
|
else released = true;
|
|
if ( stats.targets.selection < 0 ) { stats.targets.selection = stats.targets.current.size() - 1; }
|
|
else if ( stats.targets.selection >= stats.targets.current.size() ) { stats.targets.selection = 0; }
|
|
if ( queuedAction == "analyze" ) keys.select = false;
|
|
if ( !released ) {
|
|
if ( invalid ) {
|
|
playSound(*this, "invalid select");
|
|
} else if ( delta ) {
|
|
playSound(*this, "menu flip");
|
|
renderTargetOptions(stats.targets.current);
|
|
}
|
|
} else if ( keys.back && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
renderTargetOptions(std::string(""));
|
|
if ( queuedAction == "analyze" ) {
|
|
renderSkillOptions(std::string(""));
|
|
renderCommandOptions(stats.actions.current);
|
|
} else renderSkillOptions(stats.currentMember);
|
|
timer.reset();
|
|
} else if ( keys.select && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
|
|
uf::Serializer payload;
|
|
payload["action"] = "member-attack";
|
|
payload["uid"] = stats.currentMember["uid"];
|
|
payload["target"] = Json::arrayValue;
|
|
payload["target"].append(stats.targets.current[stats.targets.selection]["uid"]);
|
|
payload["skill"] = stats.currentMember["skills"][stats.skill.selection];
|
|
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
postParseResult(result);
|
|
|
|
renderTargetOptions(std::string(""));
|
|
renderCommandOptions(std::string(""));
|
|
renderSkillOptions(std::string(""));
|
|
|
|
timer.reset();
|
|
stats.state = "waiting";
|
|
if ( result["invalid"].asBool() ) {
|
|
// stats.actions.invalids.push_back(action);
|
|
renderCommandOptions(stats.actions.current);
|
|
// this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else {
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
}
|
|
}
|
|
} else if ( battleOptions ) {
|
|
bool delta = false;
|
|
bool invalid = false;
|
|
pod::Transform<>& transform = battleOptions->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = battleOptions->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
for ( uf::Entity* pointer : battleOptions->getChildren() ) {
|
|
uf::Serializer& cMetadata = pointer->getComponent<uf::Serializer>();
|
|
cMetadata["text settings"]["color"][3] = alpha;
|
|
}
|
|
static bool released = true;
|
|
if ( keys.up ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
--stats.skill.selection;
|
|
if ( stats.skill.selection < 0 ) { stats.skill.selection = stats.currentMember["skills"].size()- 1; }
|
|
} while ( std::find( stats.skill.invalids.begin(), stats.skill.invalids.end(), stats.currentMember["skills"][stats.skill.selection].asString() ) != stats.skill.invalids.end() );
|
|
}
|
|
}
|
|
else if ( keys.down ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
++stats.skill.selection;
|
|
if ( stats.skill.selection >= stats.currentMember["skills"].size()) { stats.skill.selection = 0; }
|
|
} while ( std::find( stats.skill.invalids.begin(), stats.skill.invalids.end(), stats.currentMember["skills"][stats.skill.selection].asString() ) != stats.skill.invalids.end() );
|
|
}
|
|
}
|
|
else released = true;
|
|
if ( !released ) {
|
|
if ( invalid ) {
|
|
playSound(*this, "menu close");
|
|
} else if ( delta ) {
|
|
playSound(*this, "menu flip");
|
|
renderSkillOptions(stats.currentMember);
|
|
}
|
|
} else if ( keys.back && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
renderSkillOptions(std::string(""));
|
|
renderCommandOptions(stats.actions.current);
|
|
timer.reset();
|
|
} else if ( keys.select && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
bool allRanged = false;
|
|
renderSkillOptions(std::string(""));
|
|
renderCommandOptions(std::string(""));
|
|
|
|
|
|
uf::Serializer payload;
|
|
payload["action"] = "member-targets";
|
|
payload["uid"] = stats.currentMember["uid"];
|
|
payload["skill"] = stats.currentMember["skills"][stats.skill.selection];
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
|
|
std::string skillId = stats.currentMember["skills"][stats.skill.selection].asString();
|
|
uf::Serializer skillData = masterDataGet("Skill", skillId);
|
|
if ( result["range"].isNumeric() ) skillData["range"] = result["range"];
|
|
if ( skillData["range"].asInt64() > 1 ) {
|
|
uf::Serializer payload;
|
|
payload["action"] = "member-attack";
|
|
payload["uid"] = stats.currentMember["uid"];
|
|
payload["skill"] = stats.currentMember["skills"][stats.skill.selection];
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
postParseResult(result);
|
|
timer.reset();
|
|
stats.state = "waiting";
|
|
if ( result["invalid"].asBool() ) {
|
|
// stats.actions.invalids.push_back(action);
|
|
renderCommandOptions(stats.actions.current);
|
|
// this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else {
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
}
|
|
} else {
|
|
postParseResult(result);
|
|
renderTargetOptions(result["targets"]);
|
|
}
|
|
}
|
|
} else if ( commandOptions ) {
|
|
/*
|
|
bool delta = false;
|
|
bool invalid = false;
|
|
pod::Transform<>& transform = commandOptions->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = commandOptions->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
for ( uf::Entity* pointer : commandOptions->getChildren() ) {
|
|
uf::Serializer& cMetadata = pointer->getComponent<uf::Serializer>();
|
|
cMetadata["text settings"]["color"][3] = alpha;
|
|
}
|
|
static bool released = true;
|
|
if ( keys.up ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
--stats.actions.selection;
|
|
if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
|
|
} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), stats.actions.current[stats.actions.selection].asString() ) != stats.actions.invalids.end() );
|
|
}
|
|
}
|
|
else if ( keys.down ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
++stats.actions.selection;
|
|
if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
|
|
} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), stats.actions.current[stats.actions.selection].asString() ) != stats.actions.invalids.end() );
|
|
}
|
|
}
|
|
else released = true;
|
|
if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
|
|
else if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
|
|
if ( !released ) {
|
|
if ( invalid ) {
|
|
playSound(*this, "invalid select");
|
|
} else if ( delta ) {
|
|
playSound(*this, "menu flip");
|
|
renderCommandOptions(stats.actions.current);
|
|
}
|
|
} else if ( keys.back && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "invalid select");
|
|
timer.reset();
|
|
} else if ( keys.select && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
uf::Serializer payload;
|
|
std::string action = stats.actions.current[stats.actions.selection].asString();
|
|
payload["action"] = action;
|
|
if ( payload["action"] == "member-attack" ) {
|
|
payload["uid"] = stats.currentMember["uid"];
|
|
payload["skill"] = stats.currentMember["skills"][stats.skill.selection];
|
|
}
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
|
|
postParseResult(result);
|
|
timer.reset();
|
|
|
|
renderCommandOptions(std::string(""));
|
|
|
|
stats.state = "waiting";
|
|
if ( result["invalid"].asBool() ) {
|
|
stats.actions.invalids.push_back(action);
|
|
renderCommandOptions(stats.actions.current);
|
|
// this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else if ( payload["action"] == "member-attack" ) {
|
|
renderSkillOptions(std::string(""));
|
|
stats.state = "waiting";
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else if ( payload["action"] == "member-skill" ) {
|
|
renderSkillOptions(stats.currentMember);
|
|
} else if ( payload["action"] == "pass" ) {
|
|
stats.previousMember = stats.currentMember;
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
bool delta = false;
|
|
bool invalid = false;
|
|
pod::Transform<>& transform = commandOptions->getComponent<pod::Transform<>>();
|
|
uf::Serializer& metadata = commandOptions->getComponent<uf::Serializer>();
|
|
metadata["color"][3] = alpha;
|
|
for ( uf::Entity* pointer : commandOptions->getChildren() ) {
|
|
uf::Serializer& cMetadata = pointer->getComponent<uf::Serializer>();
|
|
cMetadata["text settings"]["color"][3] = alpha;
|
|
}
|
|
static bool released = true;
|
|
if ( keys.up ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
--stats.actions.selection;
|
|
if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
|
|
} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), getKeyFromIndex(stats.actions.current, stats.actions.selection) ) != stats.actions.invalids.end() );
|
|
}
|
|
}
|
|
else if ( keys.down ) {
|
|
if ( released ) {
|
|
delta = true;
|
|
released = false;
|
|
do {
|
|
++stats.actions.selection;
|
|
if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
|
|
} while ( std::find( stats.actions.invalids.begin(), stats.actions.invalids.end(), getKeyFromIndex(stats.actions.current, stats.actions.selection) ) != stats.actions.invalids.end() );
|
|
}
|
|
}
|
|
else released = true;
|
|
if ( stats.actions.selection < 0 ) { stats.actions.selection = stats.actions.current.size() - 1; }
|
|
else if ( stats.actions.selection >= stats.actions.current.size() ) { stats.actions.selection = 0; }
|
|
if ( stats.actions.current.size() == 0 ) released = true;
|
|
if ( !released ) {
|
|
if ( invalid ) {
|
|
playSound(*this, "invalid select");
|
|
} else if ( delta ) {
|
|
playSound(*this, "menu flip");
|
|
renderCommandOptions(stats.actions.current);
|
|
}
|
|
} else if ( keys.back && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "invalid select");
|
|
timer.reset();
|
|
} else if ( keys.select && timer.elapsed().asDouble() >= timeout ) {
|
|
playSound(*this, "menu close");
|
|
uf::Serializer payload;
|
|
std::string action = getKeyFromIndex(stats.actions.current, stats.actions.selection);
|
|
action = uf::string::extension(action);
|
|
payload["action"] = action;
|
|
payload["uid"] = stats.currentMember["uid"];
|
|
|
|
if ( payload["action"] == "member-attack" ) {
|
|
// payload["skill"] = stats.currentMember["skills"][0];
|
|
payload["skill"] = 0;
|
|
}
|
|
|
|
if ( payload["action"] == "analyze" ) {
|
|
payload["action"] = "member-targets";
|
|
}
|
|
uf::Serializer result = battleManager->callHook("world:Battle.Action.%UID%", payload)[0];
|
|
|
|
// std::cout << payload << ": " << result << std::endl;
|
|
|
|
postParseResult(result);
|
|
timer.reset();
|
|
|
|
renderCommandOptions(std::string(""));
|
|
queuedAction = action;
|
|
|
|
stats.state = "waiting";
|
|
if ( result["invalid"].asBool() ) {
|
|
stats.actions.invalids.push_back(action);
|
|
renderCommandOptions(stats.actions.current);
|
|
} else if ( payload["action"] == "member-attack" ) {
|
|
renderSkillOptions(std::string(""));
|
|
stats.state = "waiting";
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else if ( payload["action"] == "member-skill" ) {
|
|
renderSkillOptions(stats.currentMember);
|
|
} else if ( payload["action"] == "member-targets" ) {
|
|
renderTargetOptions(result["targets"]);
|
|
} else if ( payload["action"] == "pass" ) {
|
|
stats.previousMember = stats.currentMember;
|
|
this->queueHook("world:Battle.Turn.%UID%", "", timeout);
|
|
} else {
|
|
renderCommandOptions(stats.actions.current);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ext::GuiBattle::render() {
|
|
ext::Gui::render();
|
|
}
|
|
|
|
void ext::GuiBattle::destroy() {
|
|
ext::Gui::destroy();
|
|
} |