64 lines
5.9 KiB
C++
64 lines
5.9 KiB
C++
#include "combat_runtime.hpp"
|
|
#include "combat_slice.hpp"
|
|
#include "skill_runtime_data.hpp"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
namespace combat_runtime {
|
|
using J=nlohmann::json;
|
|
using namespace combat_slice;
|
|
static int64_t fixed(double n){return std::llround(n*1000);}
|
|
static int ticks(double seconds){return static_cast<int>(std::ceil(seconds*20-1e-8));}
|
|
const J& definitions(){static const J data=[](){
|
|
auto d=J::parse(kSkillRuntimeJson);
|
|
if(d.at("protocol")!=1||d.at("logicVersion")!=1||d.at("runtimeEnabled")!=true||d.at("skills").size()!=25||d.at("clock").at("tickMs")!=50)throw std::runtime_error("Invalid runtime skill config");
|
|
std::set<std::string> ids;
|
|
for(const auto& t:d.at("templates")){
|
|
if(!ids.insert(t.at("id")).second)throw std::runtime_error("Duplicate skill template");std::set<std::string> actions;
|
|
for(const auto& a:t.at("actions")){std::string type=a.at("type");if(!actions.insert(a.at("id")).second||(type!="damage"&&type!="heal"&&type!="apply_status")||a.at("trigger")!="delivery_hit")throw std::runtime_error("Unsupported action");}
|
|
}
|
|
ids.clear();for(const auto& s:d.at("skills"))if(!ids.insert(s.at("id")).second)throw std::runtime_error("Duplicate skill binding");
|
|
return d;}();return data;
|
|
}
|
|
static Skill resolve(const J& binding,const J& p,int tier,int64_t attack){
|
|
const auto& plan=definitions();J t;for(const auto& v:plan["templates"])if(v["id"]==binding["template"])t=v;if(t.is_null())throw std::runtime_error("Missing skill template");
|
|
const bool ability=binding["slot"]=="ability";const auto& data=ability?p.at("ability"):p;
|
|
auto timing=binding.value("timing",t["timing"]);Skill s;s.id=binding["id"];s.ability=ability;s.projectile=t["delivery"]=="projectile";s.release=timing["releaseTicks"];s.recovery=timing["recoveryTicks"];
|
|
s.cooldown=ticks(data.at("cooldown"));s.range=static_cast<int>(fixed(data.at("range")));s.value=attack;
|
|
if(ability){s.targets=data.at(tier==2?"t2Targets":"targets");s.radius=data.at(tier==2?"t2Radius":"radius");s.duration=ticks(data.at(tier==2?"t2Duration":"duration"));}
|
|
for(const auto& action:t.at("actions")){
|
|
Effect e;e.id=action.at("id");std::string type=action.at("type");e.duration=s.duration;
|
|
if(type=="damage"){e.action=Action::Damage;e.value=attack;if(action.value("scale","")=="ability_value")e.value=attack*std::llround(data.at(tier==2?"t2":"t1").get<double>()*10000)/10000;}
|
|
else if(type=="heal"){e.action=Action::Heal;e.value=fixed(data.at(tier==2?"t2":"t1"));}
|
|
else if(action.at("statusId")=="shield_main"){e.action=Action::Shield;e.value=fixed(data.at(tier==2?"t2":"t1"));}
|
|
else if(action.at("statusId")=="slow_main"){e.action=Action::Slow;e.value=std::llround(data.at(tier==2?"t2":"t1").get<double>()*10000);e.bossScale=static_cast<int>(std::llround(data.at("bossControlScale").get<double>()*10000));}
|
|
else throw std::runtime_error("Unsupported status");
|
|
s.actions.push_back(e);
|
|
}
|
|
if(s.actions.empty())throw std::runtime_error("Empty skill actions");s.action=s.actions[0].action;
|
|
if(s.release+s.recovery>s.cooldown)throw std::runtime_error("Skill duration exceeds cooldown");return s;
|
|
}
|
|
J simulate(const J& gameplay,const J& save,const J& stage){
|
|
const auto& plan=definitions();std::vector<Unit> units;
|
|
for(int slot=0;slot<stage.at("lanes").get<int>()*2;slot++){
|
|
int pet=save.at("formation").at(slot);if(pet<0)continue;const auto& p=gameplay.at("pets").at(pet);Unit u;u.id=pet;u.art=pet;u.team=0;u.slot=slot;u.lane=slot/2;u.z=slot%2==0?0:-2000;u.tier=save.at("stages").at(pet);u.element=p.at("element");
|
|
auto& economy=gameplay.at("economy");int64_t growth=10000+std::llround(economy.at("levelGrowth").get<double>()*10000)*(save.at("level").get<int>()-1);
|
|
if(u.tier==2)growth=growth*std::llround(economy.at("evolveGrowth").get<double>()*10000)/10000;
|
|
u.hp=u.maxHp=fixed(p.at("hp"))*growth/10000;int64_t attack=fixed(p.at("attack"))*growth/10000;
|
|
for(const auto& binding:plan["skills"])if(binding["actorKind"]=="pet"&&binding["actorId"]==pet){bool ability=binding["slot"]=="ability";(ability?u.ability:u.basic)=resolve(binding,p,u.tier,attack);}
|
|
if(u.basic.id.empty()||u.ability.id.empty())throw std::runtime_error("Missing pet binding");units.push_back(u);
|
|
}
|
|
for(int index=0;index<9;index++){
|
|
int type=stage.at("enemyIds").at(index);auto d=gameplay.at("enemies").at(type);Unit u;u.id=100+index;u.art=type;u.team=1;u.lane=stage.at("enemyLanes").at(index);u.boss=d.at("boss");u.z=11000;u.spawned=false;
|
|
u.spawnTick=ticks(index/3*stage.at("waveGap").get<double>()+index%3*stage.at("spawnGap").get<double>());u.speed=static_cast<int>(std::llround(d.at("speed").get<double>()*50));u.leak=d.at("leak");
|
|
u.hp=u.maxHp=fixed((d.at("hp").get<double>()+index*d.at("hpStep").get<double>())*stage.at("hpScale").get<double>());u.defense=fixed(d.at("defense"));u.element=stage.at("element");d["range"]=1;
|
|
for(const auto& binding:plan["skills"])if(binding["actorKind"]=="enemy"&&binding["actorId"]==type)u.basic=resolve(binding,d,1,fixed(d.at("attack").get<double>()*stage.at("attackScale").get<double>()));
|
|
if(u.basic.id.empty())throw std::runtime_error("Missing enemy binding");units.push_back(u);
|
|
}
|
|
Settings settings;settings.maxTicks=ticks(stage.at("timeout"));settings.durability=stage.at("durability");settings.elements=stage.at("elementsEnabled");settings.projectileSpeed=plan["projectile"]["speedMmPerTick"];settings.projectileLifetime=plan["projectile"]["maxLifetimeTicks"];
|
|
settings.advantage=static_cast<int>(std::llround(gameplay["elements"]["advantage"].get<double>()*10000));settings.disadvantage=static_cast<int>(std::llround(gameplay["elements"]["disadvantage"].get<double>()*10000));
|
|
Battle sim(std::move(units),settings);auto trace=sim.run();trace["kind"]="authoritative_battle";trace["protocol"]=1;trace["logicVersion"]=1;trace["rulesVersion"]=3;trace["duration"]=(trace["lastTick"].get<int>()+1)*.05;trace["initialDurability"]=settings.durability;return trace;
|
|
}
|
|
}
|