more Ep3 structures
This commit is contained in:
+738
-13
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "Compression.hh"
|
||||
@@ -11,36 +12,760 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
static const vector<const char*> name_for_card_type({
|
||||
"HunterSC",
|
||||
"ArkzSC",
|
||||
"Item",
|
||||
"Creature",
|
||||
"Action",
|
||||
"Assist",
|
||||
});
|
||||
|
||||
static const unordered_map<uint8_t, const char*> description_for_when({
|
||||
{0x01, "Set"}, // TODO: Is 01 this, or "Permanent"?
|
||||
{0x02, "Attack"},
|
||||
{0x03, "??? (TODO)"},
|
||||
{0x04, "Before turn"},
|
||||
{0x05, "Destroyed"},
|
||||
{0x0A, "Permanent"}, // only used on Tollaw; could be same as 01
|
||||
{0x0B, "Battle"},
|
||||
{0x0C, "Opponent destroyed"}, // TODO: but this is also used for some support things like Shifta, and for Snatch, which also applies when opponents are not destroyed
|
||||
{0x0D, "Attack lands"},
|
||||
{0x0E, "Before attack phase"},
|
||||
{0x16, "Battle end"},
|
||||
{0x17, "Each defense"},
|
||||
{0x20, "Each attack"},
|
||||
{0x22, "Act phase"},
|
||||
{0x27, "Move phase"},
|
||||
{0x29, "Set and act phases"},
|
||||
{0x33, "Defense phase"},
|
||||
{0x3D, "Battle"}, // TODO: how is this different from 3D and 0B?
|
||||
{0x3E, "Battle"}, // TODO: how is this different from 3D and 0B?
|
||||
{0x3F, "Each defense"}, // TODO: how is this different from 17?
|
||||
{0x46, "On specific turn"},
|
||||
});
|
||||
|
||||
static const unordered_map<string, const char*> description_for_expr_token({
|
||||
{"f", "Number of FCs controlled by current SC"},
|
||||
{"d", "Die roll"},
|
||||
{"ap", "Attacker AP"}, // Unused
|
||||
{"tp", "Attacker TP"},
|
||||
{"hp", "Attacker HP"}, // TODO: How is this different from ehp?
|
||||
{"mhp", "Attacker maximum HP"},
|
||||
{"dm", "Unknown: dm"}, // Unused
|
||||
{"tdm", "Unknown: tdm"}, // Unused
|
||||
{"tf", "Number of SC\'s destroyed FCs"},
|
||||
{"ac", "Remaining ATK points"},
|
||||
{"php", "Unknown: php"}, // Unused
|
||||
{"dc", "Unknown: dc"}, // Unused
|
||||
{"cs", "Unknown: cs"}, // Unused
|
||||
{"a", "Unknown: a"}, // Unused
|
||||
{"kap", "Action cards AP"},
|
||||
{"ktp", "Action cards TP"},
|
||||
{"dn", "Unknown: dn"}, // Unused
|
||||
{"hf", "Unknown: hf"}, // Unused
|
||||
{"df", "Number of destroyed ally FCs (including SC\'s own)"},
|
||||
{"ff", "Number of ally FCs (including SC\'s own)"},
|
||||
{"ef", "Number of enemy FCs"},
|
||||
{"bi", "Number of Native FCs on either team"},
|
||||
{"ab", "Number of A.Beast FCs on either team"},
|
||||
{"mc", "Number of Machine FCs on either team"},
|
||||
{"dk", "Number of Dark FCs on either team"},
|
||||
{"sa", "Number of Sword-type items on either team"},
|
||||
{"gn", "Number of Gun-type items on either team"},
|
||||
{"wd", "Number of Cane-type items on either team"},
|
||||
{"tt", "Unknown: tt"}, // Unused
|
||||
{"lv", "Dice bonus"},
|
||||
{"adm", "Attack damage"},
|
||||
{"ddm", "Defending damage"},
|
||||
{"sat", "Number of Sword-type items on SC\'s team"},
|
||||
{"edm", "Defending damage"}, // TODO: How is this different from ddm?
|
||||
{"ldm", "Unknown: ldm"}, // Unused
|
||||
{"rdm", "Defending damage"}, // TODO: How is this different from ddm/edm?
|
||||
{"fdm", "Final damage (after defense)"},
|
||||
{"ndm", "Unknown: ndm"}, // Unused
|
||||
{"ehp", "Attacker HP"},
|
||||
});
|
||||
|
||||
// Arguments are encoded as 3-character null-terminated strings (why?!), and are
|
||||
// used for adding conditions to effects (e.g. making them only trigger in
|
||||
// certain situations) or otherwise customizing their results.
|
||||
// Argument meanings:
|
||||
// a01 = ???
|
||||
// cXY/CXY = linked items (require item with cYX/CYX to be equipped as well)
|
||||
// dXY = roll one die; require result between X and Y inclusive
|
||||
// e00 = effect lasts while equipped? (in contrast to tXX)
|
||||
// hXX = require HP >= XX
|
||||
// iXX = require HP <= XX
|
||||
// nXX = require condition XX (see description_for_n_condition)
|
||||
// oXX = seems to be "require previous random-condition effect to have happened"
|
||||
// TODO: this is used as both o01 (recovery) and o11 (reflection)
|
||||
// conditions - why the difference?
|
||||
// pXX = who to target (see description_for_p_target)
|
||||
// rXX = randomly pass with XX% chance (if XX == 00, 100% chance?)
|
||||
// sXY = require card cost between X and Y ATK points (inclusive)
|
||||
// tXX = lasts XX turns, or activate after XX turns
|
||||
|
||||
static const vector<const char*> description_for_n_condition({
|
||||
/* n00 */ "Always true",
|
||||
/* n01 */ "??? (TODO)",
|
||||
/* n02 */ "Destroyed with a single attack?",
|
||||
/* n03 */ "Unknown", // Unused
|
||||
/* n04 */ "Attack has Pierce",
|
||||
/* n05 */ "Attack has Rampage",
|
||||
/* n06 */ "Native attribute",
|
||||
/* n07 */ "A.Beast attribute",
|
||||
/* n08 */ "Machine attribute",
|
||||
/* n09 */ "Dark attribute",
|
||||
/* n10 */ "Sword-type item",
|
||||
/* n11 */ "Gun-type item",
|
||||
/* n12 */ "Cane-type item",
|
||||
/* n13 */ "Guard item",
|
||||
/* n14 */ "Story Character",
|
||||
/* n15 */ "Attacker does not use action cards",
|
||||
/* n16 */ "Aerial attribute",
|
||||
/* n17 */ "Same AP as opponent",
|
||||
/* n18 */ "Opponent is SC",
|
||||
/* n19 */ "Has Paralyzed condition",
|
||||
/* n20 */ "Has Frozen condition",
|
||||
});
|
||||
|
||||
static const vector<const char*> description_for_p_target({
|
||||
/* p00 */ "Unknown: p00", // Unused; probably invalid
|
||||
/* p01 */ "SC / FC who set the card",
|
||||
/* p02 */ "Attacking SC / FC",
|
||||
/* p03 */ "Unknown: p03", // Unused
|
||||
/* p04 */ "Unknown: p04", // Unused
|
||||
/* p05 */ "Unknown: p05", // Unused
|
||||
/* p06 */ "??? (TODO)",
|
||||
/* p07 */ "??? (TODO; Weakness)",
|
||||
/* p08 */ "FC's owner SC",
|
||||
/* p09 */ "Unknown: p09", // Unused
|
||||
/* p10 */ "All ally FCs",
|
||||
/* p11 */ "All ally FCs", // TODO: how is this different from p10?
|
||||
/* p12 */ "All non-aerial FCs on both teams",
|
||||
/* p13 */ "All FCs on both teams that are Frozen",
|
||||
/* p14 */ "All FCs on both teams that have <= 3 HP",
|
||||
/* p15 */ "All FCs except SCs", // TODO: used during attacks only?
|
||||
/* p16 */ "All FCs except SCs", // TODO: used during attacks only? how is this different from p15?
|
||||
/* p17 */ "This card",
|
||||
/* p18 */ "SC who equipped this card",
|
||||
/* p19 */ "Unknown: p19", // Unused
|
||||
/* p20 */ "Unknown: p20", // Unused
|
||||
/* p21 */ "Unknown: p21", // Unused
|
||||
/* p22 */ "All characters (SCs & FCs) including this card", // TODO: But why does Shifta apply only to allies then?
|
||||
/* p23 */ "All characters (SCs & FCs) except this card",
|
||||
/* p24 */ "All FCs on both teams that have Paralysis",
|
||||
/* p25 */ "Unknown: p25", // Unused
|
||||
/* p26 */ "Unknown: p26", // Unused
|
||||
/* p27 */ "Unknown: p27", // Unused
|
||||
/* p28 */ "Unknown: p28", // Unused
|
||||
/* p29 */ "Unknown: p29", // Unused
|
||||
/* p30 */ "Unknown: p30", // Unused
|
||||
/* p31 */ "Unknown: p31", // Unused
|
||||
/* p32 */ "Unknown: p32", // Unused
|
||||
/* p33 */ "Unknown: p33", // Unused
|
||||
/* p34 */ "Unknown: p34", // Unused
|
||||
/* p35 */ "All characters (SCs & FCs) within range", // Used for Explosion effect
|
||||
/* p36 */ "All ally SCs within range, but not the caster", // Resta
|
||||
/* p37 */ "All FCs or all opponent FCs (TODO)", // TODO: when to use which selector? is a3 involved here somehow?
|
||||
/* p38 */ "All allies except items within range (and not this card)",
|
||||
/* p39 */ "All FCs that cost 4 or more points",
|
||||
/* p40 */ "All FCs that cost 3 or fewer points",
|
||||
/* p41 */ "Unknown: p41", // Unused
|
||||
/* p42 */ "Attacker during defense phase", // Only used by TP Defense
|
||||
/* p43 */ "Owner SC of defending FC during attack",
|
||||
/* p44 */ "SC\'s own creature FCs within range",
|
||||
/* p45 */ "Both attacker and defender", // Used for Snatch, which moves EXP from one to the other
|
||||
/* p46 */ "All SCs & FCs one space left or right of this card",
|
||||
/* p47 */ "FC\'s owner Boss SC", // Only used for Gibbles+ which explicitly mentions Boss SC, so it looks like this is p08 but for bosses
|
||||
/* p48 */ "Everything within range, including this card\'s user", // Madness
|
||||
/* p49 */ "All ally FCs within range except this card",
|
||||
});
|
||||
|
||||
struct Ep3AbilityDescription {
|
||||
uint8_t command;
|
||||
bool has_expr;
|
||||
const char* name;
|
||||
const char* description;
|
||||
};
|
||||
|
||||
static const std::vector<Ep3AbilityDescription> name_for_effect_command({
|
||||
{0x00, false, nullptr, nullptr},
|
||||
{0x01, true, "AP Boost", "Temporarily increase AP by N"},
|
||||
{0x02, false, "Rampage", "Rampage"},
|
||||
{0x03, true, "Multi Strike", "Duplicate attack N times"},
|
||||
{0x04, true, "Damage Modifier 1", "Set attack damage / AP to N after action cards applied (step 1)"},
|
||||
{0x05, false, "Immobile", "Give Immobile condition"},
|
||||
{0x06, false, "Hold", "Give Hold condition"},
|
||||
{0x07, false, nullptr, nullptr},
|
||||
{0x08, true, "TP Boost", "Add N TP temporarily during attack"},
|
||||
{0x09, true, "Give Damage", "Cause direct N HP loss"},
|
||||
{0x0A, false, "Guom", "Give Guom condition"},
|
||||
{0x0B, false, "Paralyze", "Give Paralysis condition"},
|
||||
{0x0C, false, nullptr, nullptr},
|
||||
{0x0D, false, "A/H Swap", "Swap AP and HP temporarily"},
|
||||
{0x0E, false, "Pierce", "Attack SC directly even if they have items equipped"},
|
||||
{0x0F, false, nullptr, nullptr},
|
||||
{0x10, true, "Heal", "Increase HP by N"},
|
||||
{0x11, false, "Return to Hand", "Return card to hand"},
|
||||
{0x12, false, nullptr, nullptr},
|
||||
{0x13, false, nullptr, nullptr},
|
||||
{0x14, false, "Acid", "Give Acid condition"},
|
||||
{0x15, false, nullptr, nullptr},
|
||||
{0x16, true, "Mighty Knuckle", "Temporarily increase AP by N, and set ATK dice to zero"},
|
||||
{0x17, true, "Unit Blow", "Temporarily increase AP by N * number of this card set within phase"},
|
||||
{0x18, false, "Curse", "Give Curse condition"},
|
||||
{0x19, false, "Combo (AP)", "Temporarily increase AP by number of this card set within phase"},
|
||||
{0x1A, false, "Pierce/Rampage Block", "Block attack if Pierce/Rampage (?)"},
|
||||
{0x1B, false, "Ability Trap", "Temporarily disable opponent abilities"},
|
||||
{0x1C, false, "Freeze", "Give Freeze condition"},
|
||||
{0x1D, false, "Anti-Abnormality", "Cure all conditions"},
|
||||
{0x1E, false, nullptr, nullptr},
|
||||
{0x1F, false, "Explosion", "Damage all SCs and FCs by number of this same card set * 2"},
|
||||
{0x20, false, nullptr, nullptr},
|
||||
{0x21, false, nullptr, nullptr},
|
||||
{0x22, false, nullptr, nullptr},
|
||||
{0x23, false, "Return to Deck", "Cancel discard and move to bottom of deck instead"},
|
||||
{0x24, false, "Aerial", "Give Aerial status"},
|
||||
{0x25, true, "AP Loss", "Make attacker temporarily lose N AP during defense"},
|
||||
{0x26, true, "Bonus From Leader", "Gain AP equal to the number of cards of type N on the field"},
|
||||
{0x27, false, "Free Maneuver", "Enable movement over occupied tiles"},
|
||||
{0x28, false, "Haste", "Make move actions free"},
|
||||
{0x29, true, "Clone", "Make setting this card free if at least one card of type N is already on the field"},
|
||||
{0x2A, true, "DEF Disable by Cost", "Disable use of any defense cards costing between (N / 10) and (N % 10) points, inclusive"},
|
||||
{0x2B, true, "Filial", "Increase controlling SC\'s HP by N when this card is destroyed"},
|
||||
{0x2C, true, "Snatch", "Steal N EXP during attack"},
|
||||
{0x2D, true, "Hand Disrupter", "DIscard N cards from hand immediately"},
|
||||
{0x2E, false, "Drop", "Give Drop condition"},
|
||||
{0x2F, false, "Action Disrupter", "Destroy all action cards used by attacker"},
|
||||
{0x30, true, "Set HP", "Set HP to N (?) (TODO)"},
|
||||
{0x31, false, "Native Shield", "Block attacks from Native creatures"},
|
||||
{0x32, false, "A.Beast Shield", "Block attacks from A.Beast creatures"},
|
||||
{0x33, false, "Machine Shield", "Block attacks from Machine creatures"},
|
||||
{0x34, false, "Dark Shield", "Block attacks from Dark creatures"},
|
||||
{0x35, false, "Sword Shield", "Block attacks from Sword items"},
|
||||
{0x36, false, "Gun Shield", "Block attacks from Gun items"},
|
||||
{0x37, false, "Cane Shield", "Block attacks from Cane items"},
|
||||
{0x38, false, nullptr, nullptr},
|
||||
{0x39, false, nullptr, nullptr},
|
||||
{0x3A, false, "Defender", "Make attacks go to setter of this card instead of original target"},
|
||||
{0x3B, false, "Survival Decoys", "Redirect damage for multi-sided attack"},
|
||||
{0x3C, true, "Give/Take EXP", "Give N EXP, or take if N is negative"},
|
||||
{0x3D, false, nullptr, nullptr},
|
||||
{0x3E, false, "Death Companion", "If this card has 1 or 2 HP, set its HP to N"},
|
||||
{0x3F, true, "EXP Decoy", "If defender has EXP, lose EXP instead of getting damage when attacked"},
|
||||
{0x40, true, "Set MV", "Set MV to N"},
|
||||
{0x41, true, "Group", "Temporarily increase AP by N * number of this card on field, excluding itself"},
|
||||
{0x42, false, "Berserk", "User of this card receives the same damage as target, and isn't helped by target's defense cards"},
|
||||
{0x43, false, "Guard Creature", "Attacks on controlling SC damage this card instead"},
|
||||
{0x44, false, "Tech", "Technique cards cost 1 fewer ATK point"},
|
||||
{0x45, false, "Big Swing", "Increase all attacking ATK costs by 1"},
|
||||
{0x46, false, nullptr, nullptr},
|
||||
{0x47, false, "Shield Weapon", "Limit attacker\'s choice of target to guard items"},
|
||||
{0x48, false, "ATK Dice Boost", "Increase ATK dice roll by 1"},
|
||||
{0x49, false, nullptr, nullptr},
|
||||
{0x4A, false, "Major Pierce", "If SC has over half of max HP, attacks target SC instead of equipped items"},
|
||||
{0x4B, false, "Heavy Pierce", "If SC has 3 or more items equipped, attacks target SC instead of equipped items"},
|
||||
{0x4C, false, "Major Rampage", "If SC has over half of max HP, attacks target SC and all equipped items"},
|
||||
{0x4D, false, "Heavy Rampage", "If SC has 3 or more items equipped, attacks target SC and all equipped items"},
|
||||
{0x4E, true, "AP Growth", "Permanently increase AP by N"},
|
||||
{0x4F, true, "TP Growth", "Permanently increase TP by N"},
|
||||
{0x50, true, "Reborn", "If any card of type N is on the field, this card goes to the hand when destroyed instead of being discarded"},
|
||||
{0x51, true, "Copy", "Temporarily set AP/TP to N percent (or 100% if N is 0) of opponent\'s values"},
|
||||
{0x52, false, nullptr, nullptr},
|
||||
{0x53, true, "Misc. Guards", "Add N to card's defense value"},
|
||||
{0x54, true, "AP Override", "Set AP to N temporarily"},
|
||||
{0x55, true, "TP Override", "Set TP to N temporarily"},
|
||||
{0x56, false, "Return", "Return card to hand on destruction instead of discarding"},
|
||||
{0x57, false, "A/T Swap Perm", "Permanently swap AP and TP"},
|
||||
{0x58, false, "A/H Swap Perm", "Permanently swap AP and HP"},
|
||||
{0x59, true, "Slayers/Assassins", "Temporarily increase AP during attack"},
|
||||
{0x5A, false, "Anti-Abnormality", "Remove all conditions"},
|
||||
{0x5B, false, "Fixed Range", "Use SC\'s range instead of weapon or attack card ranges"},
|
||||
{0x5C, false, "Elude", "SC does not lose HP when equipped items are destroyed"},
|
||||
{0x5D, false, "Parry", "Forward attack to a random FC within one tile of original target, excluding attacker and original target"},
|
||||
{0x5E, false, "Block Attack", "Completely block attack"},
|
||||
{0x5F, false, nullptr, nullptr},
|
||||
{0x60, false, nullptr, nullptr},
|
||||
{0x61, true, "Combo (TP)", "Gain TP equal to the number of cards of type N on the field"},
|
||||
{0x62, true, "Misc. AP Bonuses", "Temporarily increase AP by N"},
|
||||
{0x63, true, "Misc. TP Bonuses", "Temporarily increase TP by N"},
|
||||
{0x64, false, nullptr, nullptr},
|
||||
{0x65, true, "Misc. Defense Bonuses", "Decrease damage by N"},
|
||||
{0x66, true, "Mostly Halfguards", "Reduce damage from incoming attack by N"},
|
||||
{0x67, false, "Periodic Field", "Swap immunity to tech or physical attacks"},
|
||||
{0x68, false, "Unlimited Summoning", "Allow unlimited summoning"},
|
||||
{0x69, false, nullptr, nullptr},
|
||||
{0x6A, true, "MV Bonus", "Increase MV by N"},
|
||||
{0x6B, true, "Forward Damage", "Give N damage back to attacker during defense (?) (TODO)"},
|
||||
{0x6C, true, "Weak Spot / Influence", "Temporarily decrease AP by N"},
|
||||
{0x6D, true, "Damage Modifier 2", "Set attack damage / AP after action cards applied (step 2)"},
|
||||
{0x6E, true, "Weak Hit Block", "Block all attacks of N damage or less"},
|
||||
{0x6F, true, "AP Silence", "Temporarily decrease AP of opponent by N"},
|
||||
{0x70, true, "TP Silence", "Temporarily decrease TP of opponent by N"},
|
||||
{0x71, false, "A/T Swap", "Temporarily swap AP and TP"},
|
||||
{0x72, true, "Halfguard", "Halve damage from attacks that would inflict N or more damage"},
|
||||
{0x73, false, nullptr, nullptr},
|
||||
{0x74, true, "Rampage AP Loss", "Temporarily reduce AP by N"},
|
||||
{0x75, false, nullptr, nullptr},
|
||||
{0x76, false, "Reflect", "Generate reverse attack"},
|
||||
});
|
||||
|
||||
void Ep3CardStats::Stat::decode_code() {
|
||||
this->type = static_cast<Type>(this->code / 1000);
|
||||
int16_t value = this->code - (this->type * 1000);
|
||||
if (value != 999) {
|
||||
switch (this->type) {
|
||||
case Type::BLANK:
|
||||
this->stat = 0;
|
||||
break;
|
||||
case Type::STAT:
|
||||
case Type::PLUS_STAT:
|
||||
case Type::EQUALS_STAT:
|
||||
this->stat = value;
|
||||
break;
|
||||
case Type::MINUS_STAT:
|
||||
this->stat = -value;
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid card stat type");
|
||||
}
|
||||
} else {
|
||||
this->stat = 0;
|
||||
this->type = static_cast<Type>(this->type + 4);
|
||||
}
|
||||
}
|
||||
|
||||
string Ep3CardStats::Stat::str() const {
|
||||
switch (this->type) {
|
||||
case Type::BLANK:
|
||||
return "";
|
||||
case Type::STAT:
|
||||
return string_printf("%hhd", this->stat);
|
||||
case Type::PLUS_STAT:
|
||||
return string_printf("+%hhd", this->stat);
|
||||
case Type::MINUS_STAT:
|
||||
return string_printf("-%d", -this->stat);
|
||||
case Type::EQUALS_STAT:
|
||||
return string_printf("=%hhd", this->stat);
|
||||
case Type::UNKNOWN:
|
||||
return "?";
|
||||
case Type::PLUS_UNKNOWN:
|
||||
return "+?";
|
||||
case Type::MINUS_UNKNOWN:
|
||||
return "-?";
|
||||
case Type::EQUALS_UNKNOWN:
|
||||
return "=?";
|
||||
default:
|
||||
return string_printf("[%02hhX %02hhX]", this->type, this->stat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Ep3CardStats::Effect::is_empty() const {
|
||||
return (this->command == 0 &&
|
||||
this->expr.is_filled_with(0) &&
|
||||
this->when == 0 &&
|
||||
this->arg1.is_filled_with(0) &&
|
||||
this->arg2.is_filled_with(0) &&
|
||||
this->arg3.is_filled_with(0) &&
|
||||
this->unknown_a3.is_filled_with(0));
|
||||
}
|
||||
|
||||
string Ep3CardStats::Effect::str_for_arg(const std::string& arg) {
|
||||
if (arg.empty()) {
|
||||
return arg;
|
||||
}
|
||||
if (arg.size() != 3) {
|
||||
return arg + "/(invalid)";
|
||||
}
|
||||
size_t value;
|
||||
try {
|
||||
value = stoul(arg.c_str() + 1, nullptr, 10);
|
||||
} catch (const invalid_argument&) {
|
||||
return arg + "/(invalid)";
|
||||
}
|
||||
|
||||
switch (arg[0]) {
|
||||
case 'a':
|
||||
return arg + "/(unknown)";
|
||||
case 'C':
|
||||
case 'c':
|
||||
return string_printf("%s/Req. linked item (%zu=>%zu)", arg.c_str(), value / 10, value % 10);
|
||||
case 'd':
|
||||
return string_printf("%s/Req. die roll in [%zu, %zu]", arg.c_str(), value / 10, value % 10);
|
||||
case 'e':
|
||||
return arg + "/While equipped";
|
||||
case 'h':
|
||||
return string_printf("%s/Req. HP >= %zu", arg.c_str(), value);
|
||||
case 'i':
|
||||
return string_printf("%s/Req. HP <= %zu", arg.c_str(), value);
|
||||
case 'n':
|
||||
try {
|
||||
return string_printf("%s/Req. condition: %s", arg.c_str(), description_for_n_condition.at(value));
|
||||
} catch (const out_of_range&) {
|
||||
return arg + "/(unknown)";
|
||||
}
|
||||
case 'o':
|
||||
return arg + "/Req. prev effect conditions passed";
|
||||
case 'p':
|
||||
try {
|
||||
return string_printf("%s/Target: %s", arg.c_str(), description_for_p_target.at(value));
|
||||
} catch (const out_of_range&) {
|
||||
return arg + "/(unknown)";
|
||||
}
|
||||
case 'r':
|
||||
return string_printf("%s/Req. random with %zu%% chance", arg.c_str(), value == 0 ? 100 : value);
|
||||
case 's':
|
||||
return string_printf("%s/Req. cost in [%zu, %zu]", arg.c_str(), value / 10, value % 10);
|
||||
case 't':
|
||||
return string_printf("%s/Turns: %zu", arg.c_str(), value);
|
||||
default:
|
||||
return arg + "/(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
string Ep3CardStats::Effect::str() const {
|
||||
string cmd_str = string_printf("%02hhX", this->command);
|
||||
try {
|
||||
const char* name = name_for_effect_command.at(this->command).name;
|
||||
if (name) {
|
||||
cmd_str += ':';
|
||||
cmd_str += name;
|
||||
}
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
string when_str = string_printf("%02hhX", this->when);
|
||||
try {
|
||||
const char* name = description_for_when.at(this->when);
|
||||
if (name) {
|
||||
when_str += ':';
|
||||
when_str += name;
|
||||
}
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
string expr_str = this->expr;
|
||||
if (!expr_str.empty()) {
|
||||
expr_str = ", expr=" + expr_str;
|
||||
}
|
||||
|
||||
string arg1str = this->str_for_arg(this->arg1);
|
||||
string arg2str = this->str_for_arg(this->arg2);
|
||||
string arg3str = this->str_for_arg(this->arg3);
|
||||
string a3str = format_data_string(this->unknown_a3.data(), sizeof(this->unknown_a3));
|
||||
return string_printf("(cmd=%s%s, when=%s, arg1=%s, arg2=%s, arg3=%s, a3=%s)",
|
||||
cmd_str.c_str(), expr_str.c_str(), when_str.c_str(), arg1str.data(),
|
||||
arg2str.data(), arg3str.data(), a3str.c_str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Ep3CardStats::decode_range() {
|
||||
// If the cell representing the FC is nonzero, the card has a range from a
|
||||
// list of constants. Otherwise, its range is already defined in the range
|
||||
// array and should be left alone.
|
||||
uint8_t index = (this->range[4] >> 8) & 0xF;
|
||||
if (index != 0) {
|
||||
this->range.clear(0);
|
||||
switch (index) {
|
||||
case 1: // Single cell in front of FC
|
||||
this->range[3] = 0x00000100;
|
||||
break;
|
||||
case 2: // Cell in front of FC and the front-left and front-right (Slash)
|
||||
this->range[3] = 0x00001110;
|
||||
break;
|
||||
case 3: // 3 cells in a line in front of FC
|
||||
this->range[1] = 0x00000100;
|
||||
this->range[2] = 0x00000100;
|
||||
this->range[3] = 0x00000100;
|
||||
break;
|
||||
case 4: // All 8 cells around FC
|
||||
this->range[3] = 0x00001110;
|
||||
this->range[4] = 0x00001010;
|
||||
this->range[5] = 0x00001110;
|
||||
break;
|
||||
case 5: // 2 cells in a line in front of FC
|
||||
this->range[2] = 0x00000100;
|
||||
this->range[3] = 0x00000100;
|
||||
break;
|
||||
case 6: // Entire field (renders as "A")
|
||||
for (size_t x = 0; x < 6; x++) {
|
||||
this->range[x] = 0x000FFFFF;
|
||||
}
|
||||
break;
|
||||
case 7: // Superposition of 4 and 5 (unused)
|
||||
this->range[2] = 0x00000100;
|
||||
this->range[3] = 0x00001110;
|
||||
this->range[4] = 0x00001010;
|
||||
this->range[5] = 0x00001110;
|
||||
break;
|
||||
case 8: // All 8 cells around FC and FC's cell
|
||||
this->range[3] = 0x00001110;
|
||||
this->range[4] = 0x00001110;
|
||||
this->range[5] = 0x00001110;
|
||||
break;
|
||||
case 9: // No cells
|
||||
break;
|
||||
// The table in the DOL file only appears to contain 9 entries; there are
|
||||
// some pointers immediately after. So probably if a card specified A-F,
|
||||
// its range would be filled in with garbage in the original game.
|
||||
default:
|
||||
throw runtime_error("invalid fixed range index");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string name_for_rarity(uint8_t rarity) {
|
||||
static const vector<const char*> names({
|
||||
"N1",
|
||||
"R1",
|
||||
"S",
|
||||
"E",
|
||||
"N2",
|
||||
"N3",
|
||||
"N4",
|
||||
"R2",
|
||||
"R3",
|
||||
"R4",
|
||||
"SS",
|
||||
"D1",
|
||||
"D2",
|
||||
"INVIS",
|
||||
});
|
||||
try {
|
||||
return names.at(rarity - 1);
|
||||
} catch (const out_of_range&) {
|
||||
return string_printf("(%02hhX)", rarity);
|
||||
}
|
||||
}
|
||||
|
||||
string name_for_target_mode(uint8_t target_mode) {
|
||||
static const vector<const char*> names({
|
||||
"NONE",
|
||||
"SINGLE",
|
||||
"MULTI",
|
||||
"SELF",
|
||||
"TEAM",
|
||||
"ALL",
|
||||
"MULTI-ALLY",
|
||||
"ALL-ALLY",
|
||||
"ALL-ATTACK",
|
||||
"OWN-FCS",
|
||||
});
|
||||
try {
|
||||
return names.at(target_mode);
|
||||
} catch (const out_of_range&) {
|
||||
return string_printf("(%02hhX)", target_mode);
|
||||
}
|
||||
}
|
||||
|
||||
string string_for_colors(const parray<uint8_t, 8>& colors) {
|
||||
string ret;
|
||||
for (size_t x = 0; x < 8; x++) {
|
||||
if (colors[x]) {
|
||||
ret += '0' + colors[x];
|
||||
}
|
||||
}
|
||||
if (ret.empty()) {
|
||||
return "none";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string string_for_assist_turns(uint8_t turns) {
|
||||
if (turns == 90) {
|
||||
return "ONCE";
|
||||
} else if (turns == 99) {
|
||||
return "FOREVER";
|
||||
} else {
|
||||
return string_printf("%hhu", turns);
|
||||
}
|
||||
}
|
||||
|
||||
string string_for_range(const parray<be_uint32_t, 6>& range) {
|
||||
string ret;
|
||||
for (size_t x = 0; x < 6; x++) {
|
||||
ret += string_printf("%05" PRIX32 "/", range[x].load());
|
||||
}
|
||||
while (starts_with(ret, "00000/")) {
|
||||
ret = ret.substr(6);
|
||||
}
|
||||
if (!ret.empty()) {
|
||||
ret.resize(ret.size() - 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string Ep3CardStats::str() const {
|
||||
string type_str;
|
||||
try {
|
||||
type_str = name_for_card_type.at(this->type);
|
||||
} catch (const out_of_range&) {
|
||||
type_str = string_printf("%02hhX", this->type);
|
||||
}
|
||||
string rarity_str = name_for_rarity(this->rarity);
|
||||
string target_mode_str = name_for_target_mode(this->target_mode);
|
||||
string range_str = string_for_range(this->range);
|
||||
string assist_turns_str = string_for_assist_turns(this->assist_turns);
|
||||
string hp_str = this->hp.str();
|
||||
string ap_str = this->ap.str();
|
||||
string tp_str = this->tp.str();
|
||||
string mv_str = this->mv.str();
|
||||
string left_str = string_for_colors(this->left_colors);
|
||||
string right_str = string_for_colors(this->right_colors);
|
||||
string top_str = string_for_colors(this->top_colors);
|
||||
string effects_str;
|
||||
for (size_t x = 0; x < 3; x++) {
|
||||
if (this->effects[x].is_empty()) {
|
||||
continue;
|
||||
}
|
||||
if (!effects_str.empty()) {
|
||||
effects_str += ", ";
|
||||
}
|
||||
effects_str += this->effects[x].str();
|
||||
}
|
||||
return string_printf(
|
||||
"[Card: %04" PRIX32 " name=%s type=%s-%02hhX rare=%s cost=%hhX+%hhX "
|
||||
"target=%s range=%s assist_turns=%s cannot_move=%s cannot_attack=%s "
|
||||
"hidden=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s top=%s a2=%08" PRIX32 " "
|
||||
"assist_effect=[%hu, %hu] a3=[%hu, %hu] has_effects=%s effects=[%s]]",
|
||||
this->card_id.load(),
|
||||
this->name.data(),
|
||||
type_str.c_str(),
|
||||
this->subtype,
|
||||
rarity_str.c_str(),
|
||||
this->self_cost,
|
||||
this->ally_cost,
|
||||
target_mode_str.c_str(),
|
||||
range_str.c_str(),
|
||||
assist_turns_str.c_str(),
|
||||
this->cannot_move ? "true" : "false",
|
||||
this->cannot_attack ? "true" : "false",
|
||||
this->hide_in_deck_edit ? "true" : "false",
|
||||
hp_str.c_str(),
|
||||
ap_str.c_str(),
|
||||
tp_str.c_str(),
|
||||
mv_str.c_str(),
|
||||
left_str.c_str(),
|
||||
right_str.c_str(),
|
||||
top_str.c_str(),
|
||||
this->unknown_a2.load(),
|
||||
this->assist_effect[0].load(),
|
||||
this->assist_effect[1].load(),
|
||||
this->unknown_a3[0].load(),
|
||||
this->unknown_a3[1].load(),
|
||||
this->has_effects ? "true" : "false",
|
||||
effects_str.c_str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ep3DataIndex::Ep3DataIndex(const string& directory) {
|
||||
static constexpr bool debug_enabled = false;
|
||||
|
||||
unordered_map<uint32_t, vector<string>> card_tags;
|
||||
if (debug_enabled) {
|
||||
unordered_map<uint32_t, string> card_text;
|
||||
try {
|
||||
string data = prs_decompress(load_file(directory + "/cardtext.mnr"));
|
||||
StringReader r(data);
|
||||
|
||||
while (!r.eof()) {
|
||||
uint32_t card_id = stoul(r.get_cstr());
|
||||
|
||||
// Most cards have multiple pages, but we only care about the first page
|
||||
// (for now)
|
||||
string text = r.get_cstr();
|
||||
|
||||
// Preprocess text: first, delete all color markers
|
||||
size_t offset = text.find("\tC");
|
||||
while (offset != string::npos) {
|
||||
text = text.substr(0, offset) + text.substr(offset + 3);
|
||||
offset = text.find("\tC");
|
||||
}
|
||||
// Preprocess text: delete all initial lines that don't start with \t
|
||||
offset = text.find('\t');
|
||||
if (offset == string::npos) {
|
||||
text.clear();
|
||||
} else {
|
||||
text = text.substr(offset);
|
||||
}
|
||||
// Preprocess text: merge lines that don't begin with \t
|
||||
for (offset = 0; offset < text.size(); offset++) {
|
||||
if (text[offset] == '\n' && text[offset + 1] != '\t') {
|
||||
text = text.substr(0, offset) + text.substr(offset + 1);
|
||||
offset--;
|
||||
}
|
||||
}
|
||||
|
||||
// Split text into tags
|
||||
vector<string> tags;
|
||||
auto lines = split(text, '\n');
|
||||
for (const auto& line : lines) {
|
||||
if (line[0] == '\t' && line[1] == 'D') {
|
||||
tags.emplace_back("D: " + line.substr(2));
|
||||
} else if (line[0] == '\t' && line[1] == 'S') {
|
||||
tags.emplace_back("S: " + line.substr(2));
|
||||
}
|
||||
}
|
||||
|
||||
if (!card_text.emplace(card_id, move(text)).second) {
|
||||
throw runtime_error("duplicate card text id");
|
||||
}
|
||||
if (!card_tags.emplace(card_id, move(tags)).second) {
|
||||
throw logic_error("duplicate card tags id");
|
||||
}
|
||||
|
||||
r.go((r.where() + 0x3FF) & (~0x3FF));
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to load card text: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this->compressed_card_definitions = load_file(directory + "/cardupdate.mnr");
|
||||
string data = prs_decompress(this->compressed_card_definitions);
|
||||
// There's a footer after the card definitions, but we ignore it
|
||||
if (data.size() % sizeof(Ep3CardDefinition) != sizeof(Ep3CardDefinitionsFooter)) {
|
||||
if (data.size() % sizeof(Ep3CardStats) != sizeof(Ep3CardStatsFooter)) {
|
||||
throw runtime_error(string_printf(
|
||||
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
|
||||
data.size(), sizeof(Ep3CardDefinition), data.size() % sizeof(Ep3CardDefinition)));
|
||||
data.size(), sizeof(Ep3CardStats), data.size() % sizeof(Ep3CardStats)));
|
||||
}
|
||||
const auto* defs = reinterpret_cast<const Ep3CardDefinition*>(data.data());
|
||||
size_t max_cards = data.size() / sizeof(Ep3CardDefinition);
|
||||
const auto* stats = reinterpret_cast<const Ep3CardStats*>(data.data());
|
||||
size_t max_cards = data.size() / sizeof(Ep3CardStats);
|
||||
for (size_t x = 0; x < max_cards; x++) {
|
||||
// The last card entry has the build date and some other metadata (and
|
||||
// isn't a real card, obviously), so skip it. Seems like the card ID is
|
||||
// always a large number that won't fit in a uint16_t, so we use that to
|
||||
// determine if the entry is a real card or not.
|
||||
if (defs[x].card_id & 0xFFFF0000) {
|
||||
if (stats[x].card_id & 0xFFFF0000) {
|
||||
continue;
|
||||
}
|
||||
shared_ptr<Ep3CardDefinition> shared_def(new Ep3CardDefinition(defs[x]));
|
||||
if (!this->card_definitions.emplace(shared_def->card_id, shared_def).second) {
|
||||
shared_ptr<CardEntry> entry(new CardEntry({stats[x], {}}));
|
||||
if (!this->card_definitions.emplace(entry->stats.card_id, entry).second) {
|
||||
throw runtime_error(string_printf(
|
||||
"duplicate card id: %08" PRIX32, shared_def->card_id.load()));
|
||||
"duplicate card id: %08" PRIX32, entry->stats.card_id.load()));
|
||||
}
|
||||
|
||||
// TODO: remove debugging code here
|
||||
// string a2str = format_data_string(defs[x].unknown_a2.data(), sizeof(defs[x].unknown_a2));
|
||||
// string a4str = format_data_string(defs[x].unknown_a4.data(), sizeof(defs[x].unknown_a4));
|
||||
// fprintf(stderr, "[debug] %-20s = %04X %s %04X %s\n", defs[x].name.data(), defs[x].unused.load(), a2str.c_str(), defs[x].unknown_a3.load(), a4str.c_str());
|
||||
entry->stats.hp.decode_code();
|
||||
entry->stats.ap.decode_code();
|
||||
entry->stats.tp.decode_code();
|
||||
entry->stats.mv.decode_code();
|
||||
entry->stats.decode_range();
|
||||
|
||||
if (debug_enabled) {
|
||||
string card_str = entry->stats.str();
|
||||
try {
|
||||
string tags_str = join(card_tags.at(stats[x].card_id), ", ");
|
||||
fprintf(stderr, "%s tags: [%s]\n", card_str.c_str(), tags_str.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
fprintf(stderr, "%s\n", card_str.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(INFO, "Indexed %zu Episode 3 card definitions", this->card_definitions.size());
|
||||
@@ -140,7 +865,7 @@ const string& Ep3DataIndex::get_compressed_card_definitions() const {
|
||||
return this->compressed_card_definitions;
|
||||
}
|
||||
|
||||
shared_ptr<const Ep3CardDefinition> Ep3DataIndex::get_card_definition(
|
||||
shared_ptr<const Ep3DataIndex::CardEntry> Ep3DataIndex::get_card_definition(
|
||||
uint32_t id) const {
|
||||
return this->card_definitions.at(id);
|
||||
}
|
||||
|
||||
+113
-15
@@ -12,17 +12,77 @@
|
||||
|
||||
|
||||
|
||||
struct Ep3CardDefinition {
|
||||
// Note: Much of the structures and enums here are based on the card list file,
|
||||
// and comparing the card text with the data in the file. Some inferences may be
|
||||
// incorrect here, since Episode 3's card text is wrong in various places.
|
||||
|
||||
struct Ep3CardStats {
|
||||
enum Rarity : uint8_t {
|
||||
N1 = 0x01,
|
||||
R1 = 0x02,
|
||||
S = 0x03,
|
||||
E = 0x04,
|
||||
N2 = 0x05,
|
||||
N3 = 0x06,
|
||||
N4 = 0x07,
|
||||
R2 = 0x08,
|
||||
R3 = 0x09,
|
||||
R4 = 0x0A,
|
||||
SS = 0x0B,
|
||||
D1 = 0x0C,
|
||||
D2 = 0x0D,
|
||||
INVIS = 0x0E,
|
||||
};
|
||||
|
||||
enum Type : uint8_t {
|
||||
SC_HUNTERS = 0x00, // No subtypes
|
||||
SC_ARKZ = 0x01, // No subtypes
|
||||
ITEM = 0x02, // Subtype 01 = sword, 02 = gun, 03 = cane. TODO: there are many more subtypes than those 3
|
||||
CREATURE = 0x03, // No subtypes (TODO: Where are attributes stored then?)
|
||||
ACTION = 0x04, // TODO: What do the subtypes mean? Are they actually flags instead?
|
||||
ASSIST = 0x05, // No subtypes
|
||||
};
|
||||
|
||||
struct Stat {
|
||||
enum Type : uint8_t {
|
||||
BLANK = 0,
|
||||
STAT = 1,
|
||||
PLUS_STAT = 2,
|
||||
MINUS_STAT = 3,
|
||||
EQUALS_STAT = 4,
|
||||
UNKNOWN = 5,
|
||||
PLUS_UNKNOWN = 6,
|
||||
MINUS_UNKNOWN = 7,
|
||||
EQUALS_UNKNOWN = 8,
|
||||
};
|
||||
be_uint16_t code;
|
||||
uint8_t type;
|
||||
uint8_t stat;
|
||||
Type type;
|
||||
int8_t stat;
|
||||
|
||||
void decode_code();
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Effect {
|
||||
uint8_t command;
|
||||
ptext<char, 0x0F> expr; // May be blank if the command doesn't use it
|
||||
uint8_t when;
|
||||
ptext<char, 4> arg1;
|
||||
ptext<char, 4> arg2;
|
||||
ptext<char, 4> arg3;
|
||||
parray<uint8_t, 3> unknown_a3;
|
||||
|
||||
bool is_empty() const;
|
||||
static std::string str_for_arg(const std::string& arg);
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
be_uint32_t card_id;
|
||||
parray<uint8_t, 0x40> jp_name;
|
||||
uint8_t type;
|
||||
uint8_t cost;
|
||||
be_uint16_t unused;
|
||||
int8_t type; // Type enum. If <0, then this is the end of the card list
|
||||
uint8_t self_cost; // ATK dice points required
|
||||
uint8_t ally_cost; // ATK points from allies required; PBs use this
|
||||
uint8_t unused_a0; // Always 0
|
||||
Stat hp;
|
||||
Stat ap;
|
||||
Stat tp;
|
||||
@@ -30,16 +90,49 @@ struct Ep3CardDefinition {
|
||||
parray<uint8_t, 8> left_colors;
|
||||
parray<uint8_t, 8> right_colors;
|
||||
parray<uint8_t, 8> top_colors;
|
||||
parray<be_uint32_t, 8> range;
|
||||
parray<uint8_t, 0x10> unknown_a2;
|
||||
parray<be_uint32_t, 6> range;
|
||||
be_uint32_t unused_a1; // Always 0
|
||||
// Target modes:
|
||||
// 00 = no targeting (used for defense cards, mags, shields, etc.)
|
||||
// 01 = single enemy
|
||||
// 02 = multiple enemies (with range)
|
||||
// 03 = self (assist)
|
||||
// 04 = team (assist)
|
||||
// 05 = everyone (assist)
|
||||
// 06 = multiple allies (with range); only used by Shifta
|
||||
// 07 = all allies including yourself; see Anti, Resta, Leilla
|
||||
// 08 = all (attack); see e.g. Last Judgment, Earthquake
|
||||
// 09 = your own FCs but not SCs; see Traitor
|
||||
uint8_t target_mode;
|
||||
uint8_t assist_turns; // 90 = once, 99 = forever
|
||||
uint8_t cannot_move; // 0 for SC and creature cards; 1 for everything else
|
||||
uint8_t cannot_attack; // 1 for shields, mags, defense actions, and assist cards
|
||||
uint8_t unused_a2; // Always 0
|
||||
uint8_t hide_in_deck_edit; // 0 = player can use this card (appears in deck edit)
|
||||
uint8_t subtype; // e.g. gun, sword, etc. (used for checking if SCs can use it)
|
||||
uint8_t rarity; // Rarity enum
|
||||
be_uint32_t unknown_a2;
|
||||
// These two fields seem to always contain the same value, and are always 0
|
||||
// for non-assist cards and nonzero for assists. Each assist card has a unique
|
||||
// value here and no effects, which makes it look like this is how assist
|
||||
// effects are implemented. There seems to be some 1k-modulation going on here
|
||||
// too; most cards are in the range 101-174 but a few have e.g. 1150, 2141. A
|
||||
// few pairs of cards have the same effect, which makes it look like some
|
||||
// other fields are also involved in determining their effects (see e.g. Skip
|
||||
// Draw / Skip Move, Dice Fever / Dice Fever +, Reverse Card / Rich +).
|
||||
parray<be_uint16_t, 2> assist_effect;
|
||||
parray<be_uint16_t, 2> unknown_a3;
|
||||
ptext<char, 0x14> name;
|
||||
ptext<char, 0x0B> jp_short_name;
|
||||
ptext<char, 0x07> short_name;
|
||||
be_uint16_t unknown_a3; // Could be has_abilities?
|
||||
parray<uint8_t, 0x60> unknown_a4;
|
||||
} __attribute__((packed));
|
||||
be_uint16_t has_effects; // 1 if any of the following structs are not blank
|
||||
Effect effects[3];
|
||||
|
||||
struct Ep3CardDefinitionsFooter {
|
||||
void decode_range();
|
||||
std::string str() const;
|
||||
} __attribute__((packed)); // 0x128 bytes in total
|
||||
|
||||
struct Ep3CardStatsFooter {
|
||||
be_uint32_t num_cards1;
|
||||
be_uint32_t unknown_a1;
|
||||
be_uint32_t num_cards2;
|
||||
@@ -222,20 +315,25 @@ class Ep3DataIndex {
|
||||
public:
|
||||
explicit Ep3DataIndex(const std::string& directory);
|
||||
|
||||
const std::string& get_compressed_card_definitions() const;
|
||||
std::shared_ptr<const Ep3CardDefinition> get_card_definition(uint32_t id) const;
|
||||
struct CardEntry {
|
||||
Ep3CardStats stats;
|
||||
std::vector<std::string> text;
|
||||
};
|
||||
|
||||
struct MapEntry {
|
||||
Ep3Map map;
|
||||
std::string compressed_data;
|
||||
};
|
||||
|
||||
const std::string& get_compressed_card_definitions() const;
|
||||
std::shared_ptr<const CardEntry> get_card_definition(uint32_t id) const;
|
||||
|
||||
const std::string& get_compressed_map_list() const;
|
||||
std::shared_ptr<const MapEntry> get_map(uint32_t id) const;
|
||||
|
||||
private:
|
||||
std::string compressed_card_definitions;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Ep3CardDefinition>> card_definitions;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
|
||||
|
||||
std::string compressed_map_list;
|
||||
std::map<uint32_t, std::shared_ptr<MapEntry>> maps;
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user