engine/ext/scenes/worldscape/housamo/battle.cpp
2020-09-26 00:00:00 -05:00

1518 lines
57 KiB
C++

#include "battle.h"
#include ".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/graphic/mesh.h>
#include <uf/utils/window/window.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/renderer/renderer.h>
#include <uf/utils/image/image.h>
#include <uf/utils/audio/audio.h>
#include "../gui/battle.h"
#include <uf/engine/asset/asset.h>
namespace {
ext::Gui* gui;
std::string previousMusic;
uf::Serializer masterTableGet( const std::string& table ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& mastertable = scene.getComponent<uf::Serializer>();
return mastertable["system"]["mastertable"][table];
}
uf::Serializer masterDataGet( const std::string& table, const std::string& key ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& mastertable = scene.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 + "%";
}
}
namespace {
void playSound( ext::HousamoBattle& entity, const std::string& id, const std::string& key ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& masterdata = scene.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";
}
uf::Asset& assetLoader = scene.getComponent<uf::Asset>();
assetLoader.cache(url, "asset:Cache.Voice." + std::to_string(entity.getUid()));
}
void playSound( ext::HousamoBattle& entity, std::size_t uid, const std::string& key ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& pMetadata = entity.getComponent<uf::Serializer>();
uf::Serializer& masterdata = scene.getComponent<uf::Serializer>();
uf::Entity* = scene.findByUid(uid);
if ( ! ) return;
uf::Serializer& metadata = ->getComponent<uf::Serializer>();
std::string id = metadata[""]["id"].asString();
playSound( entity, id, key );
}
void playSound( ext::HousamoBattle& entity, const std::string& key ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& metadata = entity.getComponent<uf::Serializer>();
uf::Serializer& masterdata = scene.getComponent<uf::Serializer>();
std::string url = "./data/audio/ui/" + key + ".ogg";
uf::Asset& assetLoader = scene.getComponent<uf::Asset>();
assetLoader.cache(url, "asset:Cache.SFX." + std::to_string(entity.getUid()));
}
void playMusic( ext::HousamoBattle& entity, const std::string& filename ) {
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Asset& assetLoader = scene.getComponent<uf::Asset>();
uf::Serializer& masterdata = scene.getComponent<uf::Serializer>();
uf::Audio& audio = scene.getComponent<uf::Audio>();
if ( audio.playing() ) {
uf::Serializer& metadata = scene.getComponent<uf::Serializer>();
metadata["previous bgm"]["filename"] = audio.getFilename();
metadata["previous bgm"]["timestamp"] = audio.getTime();
}
assetLoader.load(filename, "asset:Load." + std::to_string(scene.getUid()));
}
uf::Audio sfx;
uf::Audio voice;
std::vector<ext::Housamo*> transients;
}
void ext::HousamoBattle::initialize() {
uf::Object::initialize();
gui = NULL;
transients.clear();
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& metadata = this->getComponent<uf::Serializer>();
// std::vector<ext::Housamo>& transients = this->getComponent<std::vector<ext::Housamo>>();
this->m_name = "Battle Manager";
// asset loading
this->addHook( "asset:Cache.Voice.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& metadata = this->getComponent<uf::Serializer>();
uf::Serializer& masterdata = scene.getComponent<uf::Serializer>();
std::string filename = json["filename"].asString();
if ( uf::string::extension(filename) != "ogg" ) return "false";
if ( filename == "" ) return "false";
if ( voice.playing() ) voice.stop();
voice = uf::Audio();
voice.load(filename);
voice.setVolume(masterdata["volumes"]["voice"].asFloat());
voice.play();
return "true";
});
this->addHook( "asset:Cache.SFX.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
uf::Scene& scene = uf::scene::getCurrentScene();
uf::Serializer& masterdata = scene.getComponent<uf::Serializer>();
std::string filename = json["filename"].asString();
if ( uf::string::extension(filename) != "ogg" ) return "false";
if ( filename == "" ) return "false";
if ( sfx.playing() ) sfx.stop();
sfx = uf::Audio();
sfx.load(filename);
sfx.setVolume(masterdata["volumes"]["sfx"].asFloat());
sfx.play();
return "true";
});
this->addHook( "asset:Music.Load.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
std::string filename = "";
if ( json["music"].isArray() ) {
int ri = floor(rand() % json["music"].size());
filename = json["music"][ri].asString();
} else if ( json["music"].isString() ) {
filename = json["music"].asString();
}
if ( filename != "" ) playMusic(*this, filename);
return "true";
});
// bind events
this->addHook( "world:Battle.Start.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
metadata["battle"] = json["battle"];
{
uf::Serializer payload;
payload["music"] = metadata["battle"]["music"];
this->queueHook( "asset:Music.Load.%UID%", payload );
}
// generate battle data
uf::Serializer actions; actions.readFromFile("./data/entities/gui/battle/actions.json");
metadata["actions"] = actions;
if ( metadata["actions"].isNull() ) {
metadata["actions"]["00.member-attack"] = "攻撃";
metadata["actions"]["01.member-skill"] = "スキル";
metadata["actions"]["02.inventory"] = "アイテム";
metadata["actions"]["03.talk"] = "話し掛ける";
metadata["actions"]["04.transients"] = "転光生";
metadata["actions"]["05.escape"] = "脱走";
metadata["actions"]["06.analyze"] = "Analyze";
metadata["actions"]["07.pass"] = "Pass";
}
// spawn transients accordingly
for ( auto& id : metadata["battle"]["player"]["party"] ) {
uint64_t uid = transients.size();
auto& member = metadata["battle"]["player"]["transients"][id.asString()];
member["uid"] = uid;
ext::Housamo* transient = new ext::Housamo;
transients.push_back(transient);
this->addChild(*transient);
uf::Serializer& pMetadata = transient->getComponent<uf::Serializer>();
pMetadata[""] = member;
pMetadata[""]["uid"] = uid;
pMetadata[""]["type"] = "player";
pMetadata[""]["initialized"] = false;
transient->initialize();
metadata["battle"]["transients"][std::to_string(uid)] = pMetadata[""];
}
for ( auto& id : metadata["battle"]["enemy"]["party"] ) {
uint64_t uid = transients.size();
auto& member = metadata["battle"]["enemy"]["transients"][id.asString()];
member["uid"] = uid;
ext::Housamo* transient = new ext::Housamo;
transients.push_back(transient);
this->addChild(*transient);
uf::Serializer& pMetadata = transient->getComponent<uf::Serializer>();
pMetadata[""] = member;
pMetadata[""]["uid"] = uid;
pMetadata[""]["type"] = "enemy";
pMetadata[""]["initialized"] = false;
transient->initialize();
metadata["battle"]["transients"][std::to_string(uid)] = pMetadata[""];
}
this->queueHook("world:Battle.Gui.%UID%");
uf::Serializer payload;
for ( auto& member : metadata["battle"]["transients"] ) {
for ( auto& skillId : member["skills"] ) {
uf::Serializer skillData = masterDataGet("Skill", skillId.asString());
if ( skillData["type"].asInt64() != 16 ) continue;
for ( auto& status : skillData["statuses"] ) {
std::string target = status["target"].asString();
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
float r = (rand() % 100) / 100.0;
if ( statusData["proc"].isNumeric() ) {
// failed
if ( r > statusData["proc"].asFloat() / 100.0f ) {
target = "";
}
}
// apply status
uf::Serializer targets;
if ( target == "self" ) {
targets.append(member["uid"].asString());
} else if ( target == "enemy" ) {
for ( auto& tmember : metadata["battle"]["transients"] ) {
if ( tmember["type"] == member["type"] ) continue;
targets.append(tmember["uid"].asString());
}
} else if ( target == "ally" ) {
for ( auto& tmember : metadata["battle"]["transients"] ) {
if ( tmember["type"] != member["type"] ) continue;
targets.append(tmember["uid"].asString());
}
}
for ( auto& target : targets ) {
uf::Serializer statusPayload;
statusPayload["id"] = statusId;
statusPayload["modifier"] = status["modifier"];
statusPayload["turns"] = status["turns"].isNumeric() ? status["turns"] : statusData["turns"];
metadata["battle"]["transients"][target.asString()]["statuses"].append(statusPayload);
uf::Serializer cardData = masterDataGet( "Card", metadata["battle"]["transients"][target.asString()]["id"].asString() );
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
payload["message"] = payload["message"].asString() + "\nApplied "+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + " to " + charaData["name"].asString();
}
}
}
}
payload["battle"] = metadata["battle"];
return payload;
});
this->addHook( "world:Battle.Gui.%UID%", [&](const std::string& event)->std::string{
ext::Gui* guiManager = (ext::Gui*) this->getRootParent().findByName("Gui Manager");
ext::Gui* guiMenu = new ext::GuiBattle;
guiManager->addChild(*guiMenu);
guiMenu->load("./scenes/world/gui/battle/menu.json");
guiMenu->initialize();
uf::Serializer payload;
payload["battle"] = metadata["battle"];
guiMenu->queueHook("world:Battle.Start.%UID%", payload);
gui = guiMenu;
return "true";
});
// json.action and json.uid
this->addHook( "world:Battle.Action.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
std::string hookName = "";
uf::Serializer payload;
std::string action = json["action"].asString();
// dead check
struct {
struct {
int dead = 0;
int total = 0;
} player, enemy;
} counts;
for ( auto& member : metadata["battle"]["transients"] ) {
bool dead = member["hp"].asInt64() <= 0;
if ( member["type"] == "player" ) {
++counts.player.total;
if ( dead ) ++counts.player.dead;
} else {
++counts.enemy.total;
if ( dead ) ++counts.enemy.dead;
}
}
if ( counts.player.total > 0 && counts.player.total == counts.player.dead ) {
hookName = "world:Battle.End.%UID%";
payload["won"] = false;
} else if ( counts.enemy.total > 0 && counts.enemy.total == counts.enemy.dead ) {
hookName = "world:Battle.End.%UID%";
payload["won"] = true;
} else if ( action == "turn-start" ) {
payload = json;
hookName = "world:Battle.Turn.%UID%";
} else if ( action == "member-turn" ) {
payload = json;
hookName = "world:Battle.Get.%UID%";
} else if ( action == "member-skill" ) {
payload = json;
hookName = "world:Battle.Get.%UID%";
// test
} else if ( action == "member-attack" ) {
payload = json;
hookName = "world:Battle.Attack.%UID%";
metadata["battle"]["turn"]["decrement"] = 1.0f;
} else if ( action == "member-targets" ) {
payload = json;
hookName = "world:Battle.GetTargets.%UID%";
} else if ( action == "enemy-attack" ) {
payload = this->callHook("world:Battle.AI.%UID%", json)[0];
hookName = "world:Battle.Attack.%UID%";
metadata["battle"]["turn"]["decrement"] = 1.0f;
} else if ( action == "escape" ) {
payload = json;
hookName = "world:Battle.Escape.%UID%";
metadata["battle"]["turn"]["decrement"] = 1.0f;
} else if ( action == "talk" ) {
payload = json;
hookName = "world:Battle.Talk.%UID%";
metadata["battle"]["turn"]["decrement"] = 1.0f;
} else if ( action == "pass" ) {
payload["uid"] = json["uid"];
payload["message"] = "Waiting...";
payload["timeout"] = 2.0f;
// take half
metadata["battle"]["turn"]["decrement"] = 0.5f;
} else if ( action == "force-pass" ) {
payload["uid"] = json["uid"];
payload["message"] = "Immobilized...";
payload["timeout"] = 2.0f;
} else if ( action == "analyze" ) {
payload = json;
hookName = "world:Battle.Get.%UID%";
} else {
payload["message"] = "Invalid command.";
payload["timeout"] = 2.0f;
payload["invalid"] = true;
}
// this->callHook("world:Battle.AI.%UID%", json)[0];
{
auto& turnState = metadata["battle"]["turn"];
float decrement = turnState["decrement"].asFloat();
turnState["counter"] = turnState["counter"].asFloat() - decrement;
turnState["decrement"] = 0;
}
{
for ( auto it = metadata["battle"]["transients"].begin(); it != metadata["battle"]["transients"].end(); ++it ) {
std::string key = it.key().asString();
if ( key == "" || (*it)["type"].isNull() ) metadata["battle"].removeMember(key);
}
}
/* remove dead */ if ( false ) {
for ( auto it = metadata["battle"]["transients"].begin(); it != metadata["battle"]["transients"].end(); ++it ) {
std::string key = it.key().asString();
if ( metadata["battle"]["transients"][key]["hp"].asInt64() > 0 ) continue;
// metadata["battle"]["transients"].removeMember(key);
}
// std::cout << metadata["battle"]["transients"] << std::endl;
}
if ( gui ) {
uf::Serializer payload;
payload["battle"] = metadata["battle"];
gui->queueHook("world:Battle.Update.%UID%", payload);
}
if ( hookName == "" ) return payload;
uf::Serializer result;
result = this->callHook(hookName, payload)[0];
return result;
});
this->addHook( "world:Battle.AI.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
// default to attack
std::string queuedSkillId = "0";
if ( json["uid"] == "" ) {
json["skill"] = "0";
return json;
}
auto& member = metadata["battle"]["transients"][json["uid"].asString()];
// deduce member state
struct {
std::vector<std::string> ailments;
int ndas = 0;
float factor = 0.0f;
} stats;
for ( auto& status : member["statuses"] ) {
std::string id = status["id"].asString();
if ( id == "0" ) continue;
stats.ailments.push_back(id);
if ( id == "6" ) ++stats.ndas;
}
uf::Serializer possibilities;
possibilities = Json::arrayValue;
for ( auto& skillId : member["skills"] ) {
uf::Serializer skillData = masterDataGet("Skill", skillId.asString());
// not recarms
std::cout << skillData["name"] << ": ";
if ( skillId == "81" || skillId == "82" ) {
std::cout << "\tBad skill ID" << std::endl;
continue;
}
// invalid skill type
if ( skillData["type"].asInt() < 1 || skillData["type"].asInt() > 15 ) {
std::cout << "\tBad skill type" << std::endl;
continue;
}
// no MP
if ( skillData["mp"].asFloat() > member["mp"].asFloat() ) {
std::cout << "\tNot enough MP" << std::endl;
continue;
}
// no HP
if ( skillData["hp%"].asFloat() / 100.0f * member["max hp"].asFloat() >= member["hp"].asFloat() ) {
std::cout << "\tNot enough HP" << std::endl;
continue;
}
// do not use same skills
if ( std::find(member["used skills"].begin(), member["used skills"].end(), skillId.asString()) != member["used skills"].end() ) {
std::cout << "\tRecently Used" << std::endl;
continue;
}
// grants additional turns
if ( skillData["turns+"].isNumeric() ) {
queuedSkillId = skillId.asString();
std::cout << "\tCan gain turns" << std::endl;
break;
// try and heal if under 30%
} else if ( member["hp"].asFloat() / member["max hp"].asFloat() < 0.3f ) {
if ( skillData["type"] != "14" ) continue;
if ( stats.factor < skillData["power"].asFloat() ) {
queuedSkillId = skillId.asString();
stats.factor = skillData["power"].asFloat();
std::cout << "\tCan heal" << std::endl;
}
// remove debuffs
} else if ( skillId == "200" ) {
if ( stats.ndas * 75.0f > stats.factor ) {
queuedSkillId = skillId.asString();
stats.factor = stats.ndas * 75.0f;
std::cout << "\tCan Dekunda" << std::endl;
}
continue;
} else {
// remove negative ailment (fear, etc)
for ( auto& status : skillData["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
// remove statuses
if ( statusData["type"] == "remove" && std::find( stats.ailments.begin(), stats.ailments.end(), statusId ) != stats.ailments.end() ) {
queuedSkillId = skillId.asString();
stats.factor = 150.0f;
std::cout << "\tCan remove status " << statusData["name"] << std::endl;
}
}
std::cout << "\tRandom" << std::endl;
// no heal spells
if ( skillData["type"] == "14" ) continue;
// let RNG pick
possibilities.append(skillId);
}
}
// let RNG pick
std::cout << queuedSkillId << ": " << possibilities << std::endl;
if ( queuedSkillId == "0" && possibilities.size() > 0 ) {
possibilities.append("0");
int ri = floor(rand() % possibilities.size());
queuedSkillId = possibilities[ri].asString();
auto queuedSkillData = masterDataGet( "Skill", queuedSkillId );
std::cout << "Picked " << queuedSkillData["name"].asString() << std::endl;
}
json["skill"] = queuedSkillId;
return json;
});
this->addHook( "world:Battle.GetTargets.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
uf::Serializer payload;
payload["targets"] = Json::arrayValue;
auto& member = metadata["battle"]["transients"][json["uid"].asString()];
if ( json["skill"].isString() ) {
std::string skillId = json["skill"].asString();
if ( json["skill"] == "0" ) {
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( statusData["skill"] != skillId ) continue;
if ( statusData["range"].isNumeric() ) json["range"] = statusData["range"];
}
} else if ( !json["type"].isString() ) {
uf::Serializer skillData = masterDataGet( "Skill", skillId );
json["type"] = skillData["target"].asString();
if ( json["type"] == "" ) json["type"] = "enemy";
}
} else {
json["type"] = "all";
}
if ( json["type"].isString() ) {
if ( json["type"] == "self" ) {
payload["targets"].append( metadata["battle"]["transients"][json["uid"].asString()] );
} else {
for ( auto& m : metadata["battle"]["transients"] ) {
if ( json["type"] == "ally" && m["type"] != member["type"] ) continue;
if ( json["type"] == "enemy" && m["type"] == member["type"] ) continue;
payload["targets"].append( m );
}
}
}
return payload;
});
this->addHook( "world:Battle.Escape.%UID%", [&](const std::string& event)->std::string{
uf::Serializer payload;
float r = (rand() % 100) / 100.0;
if ( r > 0.9 ) {
payload["message"] = "Cannot escape!";
payload["timeout"] = 2.0f;
payload["invalid"] = true;
} else {
payload["message"] = "Escaped!";
payload["timeout"] = 2.0f;
this->queueHook("world:Battle.End.%UID%", payload, 2.0f);
}
return payload;
});
this->addHook( "world:Battle.Talk.%UID%", [&](const std::string& event)->std::string{
uf::Serializer payload;
float r = (rand() % 100) / 100.0;
payload["message"] = "";
payload["timeout"] = 2.0f;
payload["invalid"] = true;
uf::Serializer target;
std::string name;
for ( auto& member : metadata["battle"]["transients"] ) {
if ( member["type"] != "enemy" ) continue;
target = member;
size_t uid = member["uid"].asUInt64();
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
name = charaData["name"].asString();
break;
}
if ( target ) {
playSound(*this, target["id"].asString(), r > 0.5 ? "turn" : "battlestart");
payload["message"] = ""+colorString("FF0000") + "" + name + ""+colorString("FFFFFF") + " speaks.";
}
return payload;
});
this->addHook( "world:Battle.OnHit.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
uint64_t uid = json["uid"].asUInt64();
ext::Housamo* transient = transients.at(uid);
uf::Serializer& member = transient->getComponent<uf::Serializer>();
uint64_t entid = metadata["battle"]["enemy"]["uid"].asUInt64();
uf::Entity* = scene.findByUid(entid);
if ( ! ) return "false";
uf::Serializer& hMetadata = ->getComponent<uf::Serializer>();
uint64_t phase = json["phase"].asUInt64();
// start color
pod::Vector4f color = { 1, 1, 1, 0 };
if ( phase == 0 ) {
color = (member[""]["type"] == "enemy") ? pod::Vector4f{ 1.0f, 0, 0, 0.6f } : pod::Vector4f{ 1.0f, 1.0f, 1.0f, 0.6f };
}
hMetadata["color"][0] = color[0];
hMetadata["color"][1] = color[1];
hMetadata["color"][2] = color[2];
hMetadata["color"][3] = color[3];
return "true";
});
this->addHook( "world:Battle.Turn.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
bool found = false;
uint64_t turnPlayer = json["uid"].asUInt64();
std::string name = "";
auto& turnState = metadata["battle"]["turn"];
{
if ( turnState["counter"].asFloat() <= 0 ) {
if ( turnState["phase"] == "player" ) turnState["phase"] = "enemy";
else turnState["phase"] = "player";
int counter = 0;
for ( auto& member : metadata["battle"]["transients"] ) {
if ( member["type"] != turnState["phase"] ) continue;
bool locked = false;
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( statusData["lock"].asBool() ) locked = true;
}
if ( locked ) continue;
++counter;
}
turnState["counter"] = counter;
turnState["start of turn"] = true;
if ( gui ) {
uf::Serializer payload;
payload["battle"] = metadata["battle"];
gui->queueHook("world:Battle.TurnStart.%UID%", payload);
}
} else {
turnState["start of turn"] = false;
}
}
std::function<bool()> getTurnPlayer = [&]()->bool{
for ( auto& member : metadata["battle"]["transients"] ) {
if ( member["hp"].asInt64() <= 0 ) continue;
bool locked = false;
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( statusData["lock"].asBool() ) locked = true;
}
if ( locked ) continue;
if ( turnPlayer > member["uid"].asUInt64() ) continue;
if ( member["type"] != turnState["phase"] ) continue;
turnPlayer = member["uid"].asUInt64();
return true;
}
return false;
};
uf::Serializer payload;
payload["message"] = "An error occurred!";
if ( !getTurnPlayer() ) {
turnPlayer = 0;
if ( !getTurnPlayer() ) {
std::cout << metadata["battle"]["transients"] << std::endl;
payload["end"] = true;
return payload;
}
}
payload["uid"] = turnPlayer;
auto& member = metadata["battle"]["transients"][payload["uid"].asString()];
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
name = charaData["name"].asString();
payload["message"] = "What will "+colorString("FF0000") + "" + name + ""+colorString("FFFFFF") + " do?";
//
if ( member["type"] == "enemy" ) {
payload["message"] = ""+colorString("FF0000") + "" + name + ""+colorString("FFFFFF") + "'s turn...";
payload["timeout"] = 2.0f;
}
bool skipped = false;
// tick down statuses
if ( turnState["start of turn"].asBool() ) {
/* remove dead */ if ( false ) {
for ( auto it = metadata["battle"]["transients"].begin(); it != metadata["battle"]["transients"].end(); ++it ) {
std::string key = it.key().asString();
if ( metadata["battle"]["transients"][key]["hp"].asInt64() > 0 ) continue;
// metadata["battle"]["transients"].removeMember(key);
}
// std::cout << metadata["battle"]["transients"] << std::endl;
}
for ( auto& member : metadata["battle"]["transients"] ) {
// clear OPT used skills
member["used skills"] = Json::arrayValue;
if ( member["type"] != turnState["phase"] ) continue;
auto& statuses = member["statuses"];
// std::cout << "Before: " << member["uid"].asString() << ": " << statuses << std::endl;
for ( int i = 0; i < statuses.size(); ++i ) {
auto& status = statuses[i];
uf::Serializer statusData = masterDataGet("Status", status["id"].asString());
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
uf::Serializer cardData = masterDataGet( "Card", member["id"].asString() );
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
if ( statusData["hp"].isNumeric() ) {
int64_t hp = statusData["hp"].asInt64();
member["hp"] = member["hp"].asInt64() + hp;
payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + charaData["name"].asString() + ""+colorString("FFFFFF") + " gained "+colorString("00FF00") + ""+ std::to_string(hp) +" HP"+colorString("FFFFFF") + " from "+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + "";
}
if ( statusData["hp%"].isNumeric() ) {
int64_t hp = member["max hp"].asFloat() * (statusData["hp%"].asInt64() / 100.0f);
if ( member["max hp"].asInt64() < member["hp"].asInt64() + hp ) hp = member["max hp"].asInt64() - member["hp"].asInt64();
member["hp"] = member["hp"].asInt64() + hp;
if ( hp > 0 ) payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + charaData["name"].asString() + ""+colorString("FFFFFF") + " gained "+colorString("00FF00") + ""+ std::to_string(hp) +" HP"+colorString("FFFFFF") + " from "+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + "";
}
if ( statusData["mp"].isNumeric() ) {
int64_t mp = statusData["mp"].asInt64();
member["mp"] = member["mp"].asInt64() + mp;
if ( member["max mp"].asInt64() < member["mp"].asInt64() + mp ) mp = member["max mp"].asInt64() - member["mp"].asInt64();
payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + charaData["name"].asString() + ""+colorString("FFFFFF") + " gained "+colorString("0000FF") + ""+ std::to_string(mp) +" MP"+colorString("FFFFFF") + " from "+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + "";
}
if ( statusData["lock"].isNumeric() ) {
skipped = true;
}
}
uf::Serializer newStatuses;
for ( int i = statuses.size() - 1; i >= 0; --i ) {
auto& status = statuses[i];
uf::Serializer statusData = masterDataGet("Status", status["id"].asString());
if ( !status["turns"].isNull() ) {
status["turns"] = status["turns"].asInt64() - 1;
if ( status["turns"].asInt64() < 0 ) {
if ( statusData["id"] != "0" ) payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + " wore off.";
} else {
newStatuses.append(status);
}
} else {
newStatuses.append(status);
}
}
statuses = newStatuses;
// std::cout << "After: " << member["uid"].asString() << ": " << statuses << std::endl;
}
}
{
for ( auto it = metadata["battle"]["transients"].begin(); it != metadata["battle"]["transients"].end(); ++it ) {
std::string key = it.key().asString();
if ( key == "" || (*it)["type"].isNull() ) metadata["battle"].removeMember(key);
}
}
if ( gui ) {
uf::Serializer payload;
payload["battle"] = metadata["battle"];
gui->queueHook("world:Battle.Update.%UID%", payload);
}
if ( skipped ) payload["skipped"] = true;
/*
std::vector<std::string> list = {
"member-attack",
"member-skill",
"inventory",
"talk",
"transients",
"escape",
"pass",
"analyze",
};
payload["actions"]["list"] = Json::arrayValue;
payload["actions"]["names"]["member-attack"] = "攻撃";
payload["actions"]["names"]["member-skill"] = "スキル";
payload["actions"]["names"]["inventory"] = "アイテム";
payload["actions"]["names"]["talk"] = "話し掛ける";
payload["actions"]["names"]["transients"] = "転光生";
payload["actions"]["names"]["escape"] = "脱走";
payload["actions"]["names"]["analyze"] = "Analyze";
payload["actions"]["names"]["pass"] = "Pass";
for ( auto& x : list ) payload["actions"]["list"].append(x);
*/
payload["actions"] = metadata["actions"];
payload[""] = member;
return payload;
});
this->addHook( "world:Battle.Attack.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
std::string skillId = json["skill"].asString();
uf::Serializer skillData = masterDataGet("Skill", skillId);
// ext::Housamo* transient = transients.at(uid);
auto& member = metadata["battle"]["transients"][json["uid"].asString()];
uf::Serializer payload;
payload["message"] = "";
// modify skill data based on statuses
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( statusData["skill"] != skillId ) continue;
if ( statusData["range"].isNumeric() ) skillData["range"] = statusData["range"];
}
// select random target
if ( json["target"].isNull() ) {
if ( skillData["target"].isNull() ) skillData["target"] = "enemy";
if ( skillData["target"] == "self" ) {
json["target"].append( member["uid"].asUInt64() );
} else if ( skillData["range"].isNumeric() && skillData["range"].asInt64() > 1 ) {
json["target"] = Json::arrayValue;
for ( auto& m : metadata["battle"]["transients"] ) {
if ( skillData["target"] == "ally" && m["type"] != member["type"] ) continue;
if ( skillData["target"] != "ally" && m["type"] == member["type"] ) continue;
json["target"].append( m["uid"].asUInt64() );
}
} else {
std::vector<uint64_t> targets;
for ( auto& m : metadata["battle"]["transients"] ) {
if ( skillData["target"] == "ally" && m["type"] != member["type"] ) continue;
if ( skillData["target"] != "ally" && m["type"] == member["type"] ) continue;
targets.push_back( m["uid"].asUInt64() );
}
int ri = floor(rand() % targets.size());
json["target"] = Json::arrayValue;
json["target"].append(targets.at(ri));
}
}
uf::Serializer elementData;
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
std::string elementId;
float basePower = member["type"] == "enemy" ? 1.2f : 1.0f;
if ( skillId != "0" ) {
payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + charaData["name"].asString() + ""+colorString("FFFFFF") + ": "+colorString("7777FF") + "" + skillData["name"].asString() + colorString("FFFFFF");
elementId = skillData["type"].asString();
elementData = masterDataGet("Element", elementId);
} else {
payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "" + charaData["name"].asString() + ""+colorString("FFFFFF") + ": "+colorString("7777FF") + "攻撃" + colorString("FFFFFF");
elementId = "0";
if ( cardData["weapon_type"] == "1" ) elementId = "1"; // slash
if ( cardData["weapon_type"] == "2" ) elementId = "3"; // strike
if ( cardData["weapon_type"] == "3" ) elementId = "2"; // pierce
elementData = masterDataGet("Element", elementId);
}
struct {
float damage = 0.0f;
} delays;
struct {
bool endTurn = false;
int loseTurn = 0;
} turnEffects;
std::function<void(uf::Serializer&)> showMessage = [&]( uf::Serializer& json ) {
uf::Serializer payload;
payload["color"] = json["color"];
payload["target"]["uid"] = json["uid"];
payload["target"]["damage"] = json["message"];
gui->queueHook("world:Battle.Damage.%UID%", payload, delays.damage);
delays.damage += 0.25f;
};
{
int64_t mp = member["mp"].asInt64();
int64_t hp = member["hp"].asInt64();
int64_t mpCost = skillData["mp"].asInt64();
int64_t hpCost = skillData["hp%"].asInt64();
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( hpCost > 0 && statusData["cost"].isNumeric() ) {
hpCost -= statusData["cost"].asInt64();
}
}
hpCost = member["max hp"].asFloat() * ( hpCost / 100.0f );
if ( mp < mpCost ) {
payload["message"] = "Not enough MP to use `"+colorString("FF0000") + ""+skillData["name"].asString()+""+colorString("FFFFFF") + "`.";
payload["timeout"] = 2.0f;
if ( member["type"] != "enemy" ) {
payload["invalid"] = true;
}
return payload;
} else if ( mpCost > 0 ) {
metadata["battle"]["transients"][member["uid"].asString()]["mp"] = mp - mpCost;
}
if ( hp < hpCost ) {
payload["message"] = "Not enough HP to use `"+colorString("FF0000") + ""+skillData["name"].asString()+""+colorString("FFFFFF") + "`.";
payload["timeout"] = 2.0f;
if ( member["type"] != "enemy" ) {
payload["invalid"] = true;
return payload;
} else {
skillId = "0";
skillData = masterDataGet("Skill", skillId);
}
} else if ( hpCost > 0 ) {
metadata["battle"]["transients"][member["uid"].asString()]["hp"] = hp - hpCost;
{
uf::Serializer payload;
payload["uid"] = member["uid"];
payload["message"] = hpCost;
payload["color"] = "FF0000";
showMessage( payload );
}
}
}
if ( skillData["turns+"].isNumeric() ) {
member["used skills"].append(skillId);
payload["message"] = payload["message"].asString() + "\nGained "+colorString("FF0000") + ""+skillData["turns+"].asString()+""+colorString("FFFFFF") + " additional turns!";
metadata["battle"]["turn"]["counter"] = metadata["battle"]["turn"]["counter"].asFloat() + skillData["turns+"].asFloat();
}
for ( auto& targetId : json["target"] ) {
uf::Serializer target = metadata["battle"]["transients"][targetId.asString()];
float power = basePower;
float morePower = 0;
float probability = skillData["proc"].isNumeric() ? skillData["proc"].asInt64() / 100.0f : 1.0f;
// modify probability from statuses
for ( auto& status : metadata["battle"]["transients"][json["uid"].asString()]["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( statusData["proc+"].isNumeric() ) {
float moreProb = statusData["proc+"].asFloat() / 100.0f;
probability += moreProb;
payload["message"] = payload["message"].asString() + "\n"+colorString("FF00FF") + "" + std::to_string((int)(moreProb*100)) + "%"+colorString("FF0000") + " more probability"+colorString("FFFFFF") + "";
}
}
// find attack modifying statuses on attacker
for ( auto& status : metadata["battle"]["transients"][json["uid"].asString()]["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( !statusData["ailments"].isNull() ) continue;
if ( statusData["power"].isNumeric() ) {
float adjust = statusData["power"].asFloat() / 100.0f;
morePower += adjust;
// payload["message"] = payload["message"].asString() + "\n"+colorString("FF00FF") + "" + std::to_string((int)(adjust*100)) + "%"+colorString("FF0000") + " more power"+colorString("FFFFFF") + "";
}
}
// find defense modifying statuses on target
for ( auto& status : metadata["battle"]["transients"][targetId.asString()]["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( statusData["defense"].isNumeric() ) {
float adjust = statusData["defense"].asFloat() / 100.0f;
morePower -= adjust;
// payload["message"] = payload["message"].asString() + "\n"+colorString("FF00FF") + "" + std::to_string((int)(adjust*100)) + "%"+colorString("FF0000") + " more defense"+colorString("FFFFFF") + "";
}
}
// if ( morePower != 0 ) payload["message"] = payload["message"].asString() + "\n"+colorString("FF00FF") + "" + std::to_string((int)(morePower*100)) + "%"+colorString("FF0000") + " power modifier"+colorString("FFFFFF") + "";
float r = (rand() % 100) / 100.0;
// missed
if ( r > probability ) {
uf::Serializer payload;
payload["target"]["uid"] = target["uid"];
payload["target"]["damage"] = "Miss";
gui->queueHook("world:Battle.Damage.%UID%", payload);
continue;
}
bool repel = false;
std::function<void(const std::string&)> affinityCheck = [&]( const std::string& targetId ) {
auto& target = metadata["battle"]["transients"][targetId];
uf::Serializer cardData = masterDataGet("Card", target["id"].asString());
uf::Serializer affinity = cardData["affinity"][elementId];
// affinity change check
for ( auto& status : target["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( !statusData["affinity"].isNull() ) {
if ( !statusData["affinity"][elementId].isNull() ) affinity = statusData["affinity"][elementId];
// payload["message"] = payload["message"].asString() + "\naffinity modifier."+colorString("FFFFFF") + "";
}
}
if ( !affinity.isNull() ) {
std::string affinityStr = affinity.asString();
if ( repel && affinityStr == "repel" ) affinityStr = "null";
if ( affinityStr == "weak" ) { payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "Weak!\n"; morePower *= 2.0f; }
else if ( affinityStr == "strong" ) { payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "Strong!\n"; morePower *= 0.5f; }
else if ( affinityStr == "drain" ) { turnEffects.endTurn = true; payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "Drain!\n"; morePower *= -1.0f; }
else if ( affinityStr == "null" ) { turnEffects.loseTurn = 2; payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "Nulled!\n"; morePower *= 0; }
else if ( affinityStr == "repel" ) { turnEffects.endTurn = true; payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + "Repelled!\n"; repel = true;
// check again
affinityCheck(json["uid"].asString());
} else {
affinityStr = "";
}
if ( affinityStr != "" ) {
uf::Serializer payload;
payload["uid"] = targetId;
payload["message"] = affinityStr;
payload["color"] = "FF0000";
showMessage( payload );
}
}
}; affinityCheck(target["uid"].asString());
power += morePower;
// damage calc
float damage = 0.0f; {
float calcDamage = 0.0f;
// normal attack
if ( elementData["type"] == "Physical" ) {
if ( skillData["hp%"].isNumeric() ) {
// max hp * power
calcDamage = (member["max hp"].asFloat() * skillData["power"].asFloat() * 0.00114f );
} else {
// ( lv + str ) * power / 15
calcDamage = (member["lv"].asFloat() + member["str"].asFloat()) * skillData["power"].asFloat() / 15.0f;
}
} else if ( elementData["type"] == "Magic" ) {
if ( member["lv"].asFloat() < 30 ) {
calcDamage *= skillData["power"].asFloat() * member["lv"].asFloat() * ( 2 * member["mag"].asFloat() + 70.0f ) / 1000.0f;
} else if ( member["lv"].asFloat() < 160 ) {
calcDamage = 3.0f * skillData["power"].asFloat() * ( 2 * member["mag"].asFloat() + 70.0f - ( 0.4f * member["lv"].asFloat() ) ) / 100.0f;
} else {
calcDamage = 3.0f * skillData["power"].asFloat() * ( 2 * member["mag"].asFloat() + 5 ) / 100.0f;
}
} else {
std::cout << elementData << ": " << skillData << std::endl;
}
damage = (int) (calcDamage * power);
}
if ( skillData["hits"].isArray() ) {
float low = skillData["hits"][0].asFloat();
float high = skillData["hits"][1].asFloat();
int hits = low + r * (high - low);
damage *= hits;
}
// hama/mudo instakill
bool instakill = false;
if ( elementId == "12" || elementId == "13" ) {
instakill = true;
}
bool critted = false;
std::function<void(const std::string&)> applyDamage = [&]( const std::string& targetId ) {
auto& target = metadata["battle"]["transients"][targetId];
uf::Serializer cardData = masterDataGet("Card", target["id"].asString());
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
int curHp = target["hp"].asFloat();
bool isCrit = ((rand() % 100) / 100.0) < 0.1;
if ( damage == 0 ) isCrit = false;
float oldDamage = damage;
if ( isCrit ) {
damage *= 1.5;
critted = true;
}
// is a heal
if ( elementId == "14" ) {
float power = skillData["power"].asFloat() / 100.0f * (morePower == 0 ? 1 : morePower);
std::cout << target["max hp"].asFloat() << ", " << power << std::endl;
damage = -1 * std::abs(target["max hp"].asFloat() * power);
}
if ( instakill ) damage = curHp;
target["hp"] = (int) (curHp -= damage);
if ( target["hp"].asFloat() > target["max hp"].asFloat() ) target["hp"] = target["max hp"].asFloat();
std::cout << power << ", " << damage << ", " << morePower << (isCrit ? " / crit" : "") << std::endl;
{
uf::Serializer payload;
payload["uid"] = target["uid"];
payload["crit"] = isCrit;
payload["message"] = "";
if ( damage > 0 ) {
payload["color"] = "FF0000";
payload["message"] = (int) damage;
if ( skillData["drains"].asBool() ) {
float ratio = damage / target["max hp"].asFloat();
int mpDrain = target["max mp"].asFloat() * ratio;
if ( target["mp"].asFloat() > mpDrain ) mpDrain = target["mp"].asFloat();
target["mp"] = target["mp"].asFloat() - mpDrain;
payload["message"] = payload["message"].asString() + "\n " + colorString("0000FF") + std::to_string(mpDrain);
showMessage( payload );
payload["uid"] = member["uid"];
payload["color"] = "00FF00";
}
// heal
} else if ( damage < 0 ) {
payload["message"] = (int) -damage;
payload["color"] = "00FF00";
}
showMessage( payload );
}
damage = oldDamage;
if ( curHp <= 0 ) {
payload["message"] = payload["message"].asString() + "\n"+colorString("FF0000") + ""+ charaData["name"].asString() +" "+colorString("FFFFFF") + "died!";
target["hp"] = 0;
uf::Serializer statusPayload;
statusPayload["id"] = "21";
target["statuses"] = Json::arrayValue;
target["statuses"].append(statusPayload);
}
}; applyDamage(repel ? json["uid"].asString() : target["uid"].asString());
// counter statuses
if ( !repel ) {
for ( auto& status : metadata["battle"]["transients"][target["uid"].asString()]["statuses"] ) {
std::string statusId = status["id"].asString();
if ( statusId != "19" ) continue;
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( !statusData["type"].isNull() && elementData["type"] != statusData["type"] ) continue;
if ( !statusData["attributes"].isNull() ) {
bool found = false;
for ( auto& value : statusData["attributes"] ) {
if ( value == elementData["id"] ) found = true;
}
if ( !found ) continue;
}
if ( statusData["proc"].isNumeric() ) {
float r = (rand() % 100) / 100.0;
if ( r <= statusData["proc"].asFloat() / 100.0f ) {
applyDamage(json["uid"].asString());
payload["message"] = payload["message"].asString() + "\nCountered!";
}
}
}
}
// apply status
{
for ( auto& status : skillData["statuses"] ) {
std::string target = status["target"].asString();
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
if ( skillData["type"].asInt64() == 16 ) continue;
float r = (rand() % 100) / 100.0;
if ( repel ) {
if ( target == "enemy" ) target = "self";
else target = "";
}
if ( statusData["proc"].isNumeric() ) {
// failed
if ( r > statusData["proc"].asFloat() / 100.0f ) {
target = "";
}
}
uf::Serializer targets;
if ( target == "self" ) {
targets.append(json["uid"].asString());
// } else if ( target == "enemy" || target == "ally" ) {
} else {
targets.append(targetId.asString());
}
for ( auto& target : targets ) {
// remove status(es)
if ( status["type"] == "remove" ) {
for ( auto& statusC : metadata["battle"]["transients"][target.asString()]["statuses"] ) {
if ( statusC["id"] == statusId ) {
statusC = Json::nullValue;
statusC["id"] = 0;
statusC["turns"] = -1;
}
}
continue;
}
uf::Serializer statusPayload;
statusPayload["id"] = statusId;
statusPayload["modifier"] = status["modifier"];
statusPayload["turns"] = status["turns"].isNumeric() ? status["turns"] : statusData["turns"];
metadata["battle"]["transients"][target.asString()]["statuses"].append(statusPayload);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
uf::Serializer cardData = masterDataGet( "Card", metadata["battle"]["transients"][target.asString()]["id"].asString() );
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
// payload["message"] = payload["message"].asString() + "\nApplied "+colorString("FF0000") + "" + statusData["name"].asString() + ""+colorString("FFFFFF") + " to " + charaData["name"].asString();
{
uf::Serializer payload;
payload["uid"] = targetId;
payload["message"] = skillData["name"].asString();
payload["color"] = "9900FF";
showMessage( payload );
}
}
}
}
if ( damage > 0 ) {
float r = (rand() % 100) / 100.0;
// if ( r < 0.5 ) {
if ( r < 1.0 ) {
playSound(*this, member["id"].asString(), r > 0.25 ? "attack" : "skill");
} else {
playSound(*this, target["id"].asString(), r > 0.75 ? "damagedsmall" : "damagedlarge");
}
for ( int i = 0; i < 16; ++i ) {
uf::Serializer payload;
payload["uid"] = target["uid"];
payload["phase"] = i % 2 == 0 ? 0 : 1;
this->queueHook("world:Battle.OnHit.%UID%", payload, 0.05f * i);
}
}
if ( critted ) {
uf::Serializer payload;
payload["uid"] = json["uid"];
payload["id"] = member["id"];
gui->queueHook("world:Battle.OnCrit.%UID%", payload, 0.1f);
}
}
/* heal self */
if ( skillData["add hp"].isNumeric() ) {
int heal = skillData["add hp"].asFloat() / 100.0f * member["max hp"].asFloat();
if ( heal + member["hp"].asFloat() >= member["max hp"].asFloat() ) heal = member["max hp"].asFloat() - member["hp"].asFloat();
member["hp"] = member["hp"].asFloat() + heal;
uf::Serializer payload;
payload["uid"] = member["uid"];
payload["message"] = "";
payload["color"] = "00FF00";
payload["message"] = (int) heal;
showMessage( payload );
}
if ( turnEffects.endTurn ) {
turnEffects.loseTurn = 99;
}
if ( turnEffects.loseTurn > 0 ) {
auto& turnState = metadata["battle"]["turn"];
float decrement = turnState["decrement"].asFloat();
turnState["counter"] = turnState["counter"].asFloat() - turnEffects.loseTurn;
turnState["decrement"] = 0;
}
payload["uid"] = json["uid"];
payload["target"] = json["target"];
payload[""]["id"] = member["id"];
payload[""]["skill"] = skillId;
payload["timeout"] = 2.0f;
return payload;
});
this->addHook( "world:Battle.Get.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
auto& member = metadata["battle"]["transients"][json["uid"].asString()];
/*
ext::Housamo* transient = transients.at(uid);
uf::Serializer& member = transient->getComponent<uf::Serializer>();
*/
std::string message;
uf::Serializer cardData = masterDataGet("Card", member["id"].asString());
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].asString());
uf::Serializer affinity = cardData["affinity"];
std::string name = charaData["name"].asString();
if ( member["type"] == "player" ) message += colorString("00FF00");
else if ( member["type"] == "enemy" ) message += colorString("FF0000");
else message += colorString("AAAAAA");
message += name + colorString("FFFFFF") + ": " + member["type"].asString();
message += "\n" + colorString("FF0000") + "HP: " + member["hp"].asString();
message += "\n" + colorString("0000FF") + "MP: " + member["mp"].asString();
message += "\n" + colorString("9900FF") + "Ailments" + colorString("FFFFFF") + ":";
for ( auto& status : member["statuses"] ) {
std::string statusId = status["id"].asString();
uf::Serializer statusData = masterDataGet("Status", statusId);
for ( auto it = status["modifier"].begin(); it != status["modifier"].end(); ++it ) {
statusData[it.key().asString()] = *it;
}
if ( statusId == "0" ) continue;
// affinity change check
for ( auto it = statusData["affinity"].begin(); it != statusData["affinity"].end(); ++it ) {
affinity[it.key().asString()] = *it;
}
message += "\n\t" + statusData["name"].asString() + (status["turns"].isNull() ? "" : " (Turns: " + status["turns"].asString() + ")" );
}
message += "\n" + colorString("9900FF") + "Affinities" + colorString("FFFFFF") + ":";
for ( auto it = affinity.begin(); it != affinity.end(); ++it ) {
std::string elementId = it.key().asString();
std::string value = it->asString();
uf::Serializer elementData = masterDataGet("Element", elementId);
message += "\n\t" + elementData["name"].asString() + ": " + value;
}
uf::Serializer payload;
payload[""] = member;
// payload["timeout"] = 0.1f;
payload["message"] = message;
return payload;
});
this->addHook( "world:Battle.End.%UID%", [&](const std::string& event)->std::string{
uf::Serializer json = event;
// play music
/*
std::cout << "Previous BGM: " << previousMusic << std::endl;
playMusic(*this, previousMusic);
*/
uf::Scene& scene = uf::scene::getCurrentScene();
scene.callHook("world:Music.LoadPrevious.%UID%");
gui->callHook("menu:Close.%UID%");
// cleanup
for ( ext::Housamo* pointer : transients ) {
pointer->destroy();
this->removeChild(*pointer);
delete pointer;
}
transients.clear();
if ( json["won"].asBool() ) {
// play death sound
// playSound(*this, metadata["battle"]["enemy"]["uid"].asUInt64(), "killed");
// kill
{
uf::Scene& scene = uf::scene::getCurrentScene();
std::size_t uid = metadata["battle"]["enemy"]["uid"].asUInt64();
uf::Entity* = scene.findByUid(uid);
if ( ) {
uf::Object& parent = ->getParent<uf::Object>();
->destroy();
parent.removeChild(*);
}
}
}
{
uf::Serializer payload;
payload["battle"] = metadata["battle"];
this->callHook("world:Battle.End", payload);
}
uf::Serializer payload;
payload["end"] = true;
return payload;
/*
if ( json["action"].asString() == "killed" ) {
// play death sound
playSound(*this, metadata["battle"]["enemy"]["uid"].asUInt64(), "killed");
// kill
{
uf::Scene& scene = uf::scene::getCurrentScene();
std::size_t uid = metadata["battle"]["enemy"]["uid"].asUInt64();
uf::Entity* = scene.findByUid(uid);
if ( ) {
uf::Object& parent = ->getParent<uf::Object>();
->destroy();
parent.removeChild(*);
}
}
}
// clear battle data on player
{
uf::Scene& scene = uf::scene::getCurrentScene();
std::size_t uid = metadata["battle"]["player"]["uid"].asUInt64();
uf::Entity* = scene.findByUid(uid);
if ( ) {
uf::Serializer& pMetadata = ->getComponent<uf::Serializer>();
pMetadata["system"].removeMember("battle");
}
}
uf::Serializer payload;
payload["end"] = true;
return payload;
return "true";
*/
});
}
void ext::HousamoBattle::tick() {
uf::Object::tick();
}
void ext::HousamoBattle::destroy() {
uf::Object::destroy();
}
void ext::HousamoBattle::render() {
uf::Object::render();
}