#include "combat_runtime.hpp" #include "combat_slice.hpp" #include "skill_runtime_data.hpp" #include #include #include #include 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(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 ids; for(const auto& t:d.at("templates")){ if(!ids.insert(t.at("id")).second)throw std::runtime_error("Duplicate skill template");std::set 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(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()*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()*10000);e.bossScale=static_cast(std::llround(data.at("bossControlScale").get()*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 units; for(int slot=0;slot()*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()*10000)*(save.at("level").get()-1); if(u.tier==2)growth=growth*std::llround(economy.at("evolveGrowth").get()*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()+index%3*stage.at("spawnGap").get());u.speed=static_cast(std::llround(d.at("speed").get()*50));u.leak=d.at("leak"); u.hp=u.maxHp=fixed((d.at("hp").get()+index*d.at("hpStep").get())*stage.at("hpScale").get());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()*stage.at("attackScale").get())); 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(std::llround(gameplay["elements"]["advantage"].get()*10000));settings.disadvantage=static_cast(std::llround(gameplay["elements"]["disadvantage"].get()*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()+1)*.05;trace["initialDurability"]=settings.durability;return trace; } }