#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/ext/vulkan/graphic.h>
#include <uf/ext/vulkan/device.h>
#include <uf/ext/vulkan/swapchain.h>
#include <uf/ext/vulkan/vulkan.h>
#include <uf/utils/image/image.h>
#include <uf/utils/audio/audio.h>

#include "../world.h"
#include "../gui/battle.h"
#include <uf/engine/asset/asset.h>

namespace {
	ext::World* world;
	ext::Gui* gui;
	std::string previousMusic;

	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 + "%";
	}

}

namespace {
	void playSound( ext::HousamoBattle& entity, const std::string& id, const std::string& key ) {
		ext::World& world = entity.getRootParent<ext::World>();
		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";
		}

		uf::Asset& assetLoader = world.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 ) {
		ext::World& world = entity.getRootParent<ext::World>();
		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::HousamoBattle& entity, const std::string& key ) {
		ext::World& world = entity.getRootParent<ext::World>();
		uf::Serializer& metadata = entity.getComponent<uf::Serializer>();
		uf::Serializer& masterdata = world.getComponent<uf::Serializer>();

		std::string url = "./data/audio/ui/" + key + ".ogg";

		uf::Asset& assetLoader = world.getComponent<uf::Asset>();
		assetLoader.cache(url, "asset:Cache.SFX." + std::to_string(entity.getUid()));
	}
	void playMusic( ext::HousamoBattle& entity, const std::string& filename ) {
		ext::World& world = entity.getRootParent<ext::World>();
		uf::Asset& assetLoader = world.getComponent<uf::Asset>();
		uf::Serializer& masterdata = world.getComponent<uf::Serializer>();

		uf::Audio& audio = world.getComponent<uf::Audio>();
		if ( audio.playing() ) {
			uf::Serializer& metadata = world.getComponent<uf::Serializer>();
			metadata["previous bgm"]["filename"] = audio.getFilename();
			metadata["previous bgm"]["timestamp"] = audio.getTime();
		}

		assetLoader.load(filename, "asset:Load." + std::to_string(world.getUid()));
	}

	uf::Audio sfx;
	uf::Audio voice;
	std::vector<ext::Housamo*> transients;
}
void ext::HousamoBattle::initialize() {	
	uf::Object::initialize();

	gui = NULL;
	transients.clear();

	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;
		ext::World& world = this->getRootParent<ext::World>();
		uf::Serializer& metadata = this->getComponent<uf::Serializer>();
		uf::Serializer& masterdata = world.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;
		
		ext::World& world = this->getRootParent<ext::World>();
		uf::Serializer& masterdata = world.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*  = this->getRootParent<ext::World>().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);
	*/
		ext::World& world = this->getRootParent<ext::World>();
		world.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
			{
				ext::World& world = this->getRootParent<ext::World>();
				std::size_t uid = metadata["battle"]["enemy"]["uid"].asUInt64();
				uf::Entity*  = world.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
			{
				ext::World& world = this->getRootParent<ext::World>();
				std::size_t uid = metadata["battle"]["enemy"]["uid"].asUInt64();
				uf::Entity*  = world.findByUid(uid);
				if (  ) {
					uf::Object& parent = ->getParent<uf::Object>();
					->destroy();
					parent.removeChild(*);
				}
			}
		}

		// clear battle data on player
		{
			ext::World& world = this->getRootParent<ext::World>();
			std::size_t uid = metadata["battle"]["player"]["uid"].asUInt64();
			uf::Entity*  = world.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();
}