add explanation in expr field in cards.html
This commit is contained in:
+41
-110
@@ -1686,6 +1686,8 @@ bool CardSpecial::evaluate_effect_arg2_condition(
|
||||
}
|
||||
|
||||
int32_t CardSpecial::evaluate_effect_expr(const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const {
|
||||
using ExprToken = CardDefinition::Effect::ExprToken;
|
||||
|
||||
auto log = this->server()->log_stack("evaluate_effect_expr: ");
|
||||
if (log.min_level == phosg::LogLevel::L_DEBUG) {
|
||||
log.debug_f(
|
||||
@@ -1699,75 +1701,65 @@ int32_t CardSpecial::evaluate_effect_expr(const AttackEnvStats& ast, const char*
|
||||
ast.print(stderr);
|
||||
}
|
||||
|
||||
// Note: This implementation is not based on the original code because the
|
||||
// original code was hard to follow - it used a look-behind approach with lots
|
||||
// of local variables instead of the look-ahead approach that this
|
||||
// implementation uses. Hopefully this implementation is easier to follow.
|
||||
vector<pair<ExpressionTokenType, int32_t>> tokens;
|
||||
while (expr) {
|
||||
ExpressionTokenType type;
|
||||
int32_t value = 0;
|
||||
expr = this->get_next_expr_token(expr, &type, &value);
|
||||
if (expr) {
|
||||
if (type == ExpressionTokenType::SPACE) {
|
||||
throw runtime_error("expression contains space token");
|
||||
}
|
||||
// Turn references into numbers, so only numbers and operators can appear
|
||||
// in the tokens vector
|
||||
if (type == ExpressionTokenType::REFERENCE) {
|
||||
if ((value == 1) || (value == 11)) {
|
||||
dice_roll.value_used_in_expr = true;
|
||||
}
|
||||
tokens.emplace_back(make_pair(ExpressionTokenType::NUMBER, ast.at(value)));
|
||||
} else {
|
||||
tokens.emplace_back(make_pair(type, value));
|
||||
// Note: This implementation is not based on the original code because the original code was hard to follow - it used
|
||||
// look-behind approach with lots of local variables instead of the look-ahead approach that this implementation
|
||||
// uses. Hopefully this implementation is easier to follow.
|
||||
auto tokens = ExprToken::parse(expr);
|
||||
for (auto& token : tokens) {
|
||||
if (token.type == ExprToken::Type::SPACE) {
|
||||
throw runtime_error("expression contains space token");
|
||||
}
|
||||
// Turn references into numbers, so only numbers and operators can appear in the tokens vector
|
||||
if (token.type == ExprToken::Type::REFERENCE) {
|
||||
if ((token.value == 1) || (token.value == 11)) {
|
||||
dice_roll.value_used_in_expr = true;
|
||||
}
|
||||
token.type = ExprToken::Type::NUMBER;
|
||||
token.value = ast.at(token.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Operators are evaluated left-to-right - there are no operator precedence
|
||||
// rules
|
||||
// Operators are evaluated left-to-right - there are no operator precedence rules
|
||||
int32_t value = 0;
|
||||
log.debug_f("value={} (start)", value);
|
||||
for (size_t token_index = 0; token_index < tokens.size(); token_index++) {
|
||||
auto token_type = tokens[token_index].first;
|
||||
int32_t token_value = tokens[token_index].second;
|
||||
if ((token_type == ExpressionTokenType::SPACE) || (token_type == ExpressionTokenType::REFERENCE)) {
|
||||
const auto& token = tokens[token_index];
|
||||
if ((token.type == ExprToken::Type::SPACE) || (token.type == ExprToken::Type::REFERENCE)) {
|
||||
throw logic_error("space or reference token present in expr evaluation phase 2");
|
||||
}
|
||||
if (token_type == ExpressionTokenType::NUMBER) {
|
||||
value = token_value;
|
||||
log.debug_f("value={} (token_type=NUMBER, token_value={})", value, token_value);
|
||||
if (token.type == ExprToken::Type::NUMBER) {
|
||||
value = token.value;
|
||||
log.debug_f("value={} (token_type=NUMBER, token_value={})", value, token.value);
|
||||
} else {
|
||||
if (token_index >= tokens.size() - 1) {
|
||||
throw runtime_error("no token on right side of binary operator");
|
||||
}
|
||||
token_index++;
|
||||
auto right_token_type = tokens[token_index].first;
|
||||
auto right_value = tokens[token_index].second;
|
||||
if (right_token_type != ExpressionTokenType::NUMBER) {
|
||||
const auto& right_token = tokens[token_index];
|
||||
if (right_token.type != ExprToken::Type::NUMBER) {
|
||||
// REFERENCE was converted to NUMBER after parsing, based on the attack env stats
|
||||
throw runtime_error("non-number, non-reference token on right side of operator");
|
||||
}
|
||||
switch (token_type) {
|
||||
case ExpressionTokenType::ROUND_DIVIDE:
|
||||
value = lround(static_cast<double>(value) / right_value);
|
||||
log.debug_f("value={} (token_type=ROUND_DIVIDE, right_token_value={})", value, right_value);
|
||||
switch (token.type) {
|
||||
case ExprToken::Type::ROUND_DIVIDE:
|
||||
value = lround(static_cast<double>(value) / right_token.value);
|
||||
log.debug_f("value={} (token_type=ROUND_DIVIDE, right_token_value={})", value, right_token.value);
|
||||
break;
|
||||
case ExpressionTokenType::SUBTRACT:
|
||||
value -= right_value;
|
||||
log.debug_f("value={} (token_type=SUBTRACT, right_token_value={})", value, right_value);
|
||||
case ExprToken::Type::SUBTRACT:
|
||||
value -= right_token.value;
|
||||
log.debug_f("value={} (token_type=SUBTRACT, right_token_value={})", value, right_token.value);
|
||||
break;
|
||||
case ExpressionTokenType::ADD:
|
||||
value += right_value;
|
||||
log.debug_f("value={} (token_type=ADD, right_token_value={})", value, right_value);
|
||||
case ExprToken::Type::ADD:
|
||||
value += right_token.value;
|
||||
log.debug_f("value={} (token_type=ADD, right_token_value={})", value, right_token.value);
|
||||
break;
|
||||
case ExpressionTokenType::MULTIPLY:
|
||||
value *= right_value;
|
||||
log.debug_f("value={} (token_type=MULTIPLY, right_token_value={})", value, right_value);
|
||||
case ExprToken::Type::MULTIPLY:
|
||||
value *= right_token.value;
|
||||
log.debug_f("value={} (token_type=MULTIPLY, right_token_value={})", value, right_token.value);
|
||||
break;
|
||||
case ExpressionTokenType::FLOOR_DIVIDE:
|
||||
value = floor(value / right_value);
|
||||
log.debug_f("value={} (token_type=FLOOR_DIVIDE, right_token_value={})", value, right_value);
|
||||
case ExprToken::Type::FLOOR_DIVIDE:
|
||||
value = floor(value / right_token.value);
|
||||
log.debug_f("value={} (token_type=FLOOR_DIVIDE, right_token_value={})", value, right_token.value);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid binary operator");
|
||||
@@ -2743,67 +2735,6 @@ void CardSpecial::get_effective_ap_tp(
|
||||
}
|
||||
}
|
||||
|
||||
const char* CardSpecial::get_next_expr_token(
|
||||
const char* expr, ExpressionTokenType* out_type, int32_t* out_value) const {
|
||||
switch (*expr) {
|
||||
case '\0':
|
||||
*out_type = ExpressionTokenType::SPACE;
|
||||
return nullptr;
|
||||
case ' ':
|
||||
*out_type = ExpressionTokenType::SPACE;
|
||||
return expr + 1;
|
||||
case '+':
|
||||
*out_type = ExpressionTokenType::ADD;
|
||||
return expr + 1;
|
||||
case '-':
|
||||
*out_type = ExpressionTokenType::SUBTRACT;
|
||||
return expr + 1;
|
||||
case '*':
|
||||
*out_type = ExpressionTokenType::MULTIPLY;
|
||||
return expr + 1;
|
||||
case '/':
|
||||
if (expr[1] == '/') {
|
||||
*out_type = ExpressionTokenType::FLOOR_DIVIDE;
|
||||
return expr + 2;
|
||||
} else {
|
||||
*out_type = ExpressionTokenType::ROUND_DIVIDE;
|
||||
return expr + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*expr >= 'a') && (*expr <= 'z')) {
|
||||
string token_buf;
|
||||
for (; (*expr >= 'a') && (*expr <= 'z'); expr++) {
|
||||
token_buf.push_back(*expr);
|
||||
}
|
||||
|
||||
*out_type = ExpressionTokenType::SPACE;
|
||||
*out_value = 0x27;
|
||||
|
||||
static const vector<const char*> tokens = {
|
||||
"f", "d", "ap", "tp", "hp", "mhp", "dm", "tdm", "tf", "ac", "php",
|
||||
"dc", "cs", "a", "kap", "ktp", "dn", "hf", "df", "ff", "ef", "bi",
|
||||
"ab", "mc", "dk", "sa", "gn", "wd", "tt", "lv", "adm", "ddm", "sat",
|
||||
"edm", "ldm", "rdm", "fdm", "ndm", "ehp"};
|
||||
for (size_t z = 0; z < tokens.size(); z++) {
|
||||
if (token_buf == tokens[z]) {
|
||||
*out_type = ExpressionTokenType::REFERENCE;
|
||||
*out_value = z;
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
if ((*expr >= '0') && (*expr <= '9')) {
|
||||
*out_type = ExpressionTokenType::NUMBER;
|
||||
*out_value = strtol(expr, const_cast<char**>(&expr), 10);
|
||||
return expr;
|
||||
}
|
||||
|
||||
throw runtime_error("invalid card effect expression");
|
||||
}
|
||||
|
||||
vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
uint16_t card_ref,
|
||||
uint8_t def_effect_index,
|
||||
|
||||
@@ -21,17 +21,6 @@ const InterferenceProbabilityEntry* get_interference_probability_entry(
|
||||
|
||||
class CardSpecial {
|
||||
public:
|
||||
enum class ExpressionTokenType {
|
||||
SPACE = 0, // Also used for end of string (get_next_expr_token returns null)
|
||||
REFERENCE = 1, // Reference to a value from the env stats (e.g. hp)
|
||||
NUMBER = 2, // Constant value (e.g. 2)
|
||||
SUBTRACT = 3, // "-" in input string
|
||||
ADD = 4, // "+" in input string
|
||||
ROUND_DIVIDE = 5, // "/" in input string
|
||||
FLOOR_DIVIDE = 6, // "//" in input string
|
||||
MULTIPLY = 7, // "*" in input string
|
||||
};
|
||||
|
||||
struct DiceRoll {
|
||||
uint8_t client_id;
|
||||
uint8_t unknown_a2;
|
||||
@@ -192,7 +181,6 @@ public:
|
||||
std::shared_ptr<const Card> card1, uint16_t default_card_id, std::shared_ptr<const Card> card2) const;
|
||||
static void get_effective_ap_tp(
|
||||
StatSwapType type, int16_t* effective_ap, int16_t* effective_tp, int16_t hp, int16_t ap, int16_t tp);
|
||||
const char* get_next_expr_token(const char* expr, ExpressionTokenType* out_type, int32_t* out_value) const;
|
||||
std::vector<std::shared_ptr<const Card>> get_targeted_cards_for_condition(
|
||||
uint16_t card_ref,
|
||||
uint8_t def_effect_index,
|
||||
|
||||
+163
-42
@@ -179,47 +179,61 @@ bool card_class_is_tech_like(CardClass cc, bool is_nte) {
|
||||
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (!is_nte && (cc == CardClass::BOSS_TECH));
|
||||
}
|
||||
|
||||
static const unordered_map<string, const char*> description_for_expr_token({
|
||||
{"f", "Number of FCs controlled by current SC"},
|
||||
{"d", "Die roll"},
|
||||
{"ap", "Attacker effective AP"},
|
||||
{"tp", "Attacker effective TP"},
|
||||
{"hp", "Current HP"},
|
||||
{"mhp", "Maximum HP"},
|
||||
{"dm", "Physical damage"},
|
||||
{"tdm", "Technique damage"},
|
||||
{"tf", "Number of SC\'s destroyed FCs"},
|
||||
{"ac", "Remaining ATK points"},
|
||||
{"php", "Maximum HP"},
|
||||
{"dc", "Die roll"},
|
||||
{"cs", "Card set cost"},
|
||||
{"a", "Number of FCs on all teams"},
|
||||
{"kap", "Action cards AP"},
|
||||
{"ktp", "Action cards TP"},
|
||||
{"dn", "Unknown: dn"},
|
||||
{"hf", "Number of item or creature cards in hand"},
|
||||
{"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", "Physical damage"},
|
||||
{"lv", "Dice boost"},
|
||||
{"adm", "SC attack damage"},
|
||||
{"ddm", "Attack bonus"},
|
||||
{"sat", "Number of Sword-type items on SC\'s team"},
|
||||
{"edm", "Target attack bonus"},
|
||||
{"ldm", "Last attack damage before defense"}, // Unused
|
||||
{"rdm", "Last attack damage"},
|
||||
{"fdm", "Final damage (after defense)"},
|
||||
{"ndm", "Unknown: ndm"}, // Unused
|
||||
{"ehp", "Attacker HP"},
|
||||
});
|
||||
struct ExprTokenDefinition {
|
||||
int32_t value;
|
||||
std::string token;
|
||||
std::string description;
|
||||
};
|
||||
static const vector<ExprTokenDefinition> expr_token_defs{
|
||||
{0x00, "f", "Number of FCs controlled by current SC"},
|
||||
{0x01, "d", "Die roll"},
|
||||
{0x02, "ap", "Attacker effective AP"},
|
||||
{0x03, "tp", "Attacker effective TP"},
|
||||
{0x04, "hp", "Current HP"},
|
||||
{0x05, "mhp", "Maximum HP"},
|
||||
{0x06, "dm", "Physical damage"},
|
||||
{0x07, "tdm", "Technique damage"},
|
||||
{0x08, "tf", "Number of SC\'s destroyed FCs"},
|
||||
{0x09, "ac", "Remaining ATK points"},
|
||||
{0x0A, "php", "Maximum HP"},
|
||||
{0x0B, "dc", "Die roll"},
|
||||
{0x0C, "cs", "Card set cost"},
|
||||
{0x0D, "a", "Number of FCs on all teams"},
|
||||
{0x0E, "kap", "Action cards AP"},
|
||||
{0x0F, "ktp", "Action cards TP"},
|
||||
{0x10, "dn", "Unknown: dn"},
|
||||
{0x11, "hf", "Number of item or creature cards in hand"},
|
||||
{0x12, "df", "Number of destroyed ally FCs (including SC\'s own)"},
|
||||
{0x13, "ff", "Number of ally FCs (including SC\'s own)"},
|
||||
{0x14, "ef", "Number of enemy FCs"},
|
||||
{0x15, "bi", "Number of Native FCs on either team"},
|
||||
{0x16, "ab", "Number of A.Beast FCs on either team"},
|
||||
{0x17, "mc", "Number of Machine FCs on either team"},
|
||||
{0x18, "dk", "Number of Dark FCs on either team"},
|
||||
{0x19, "sa", "Number of Sword-type items on either team"},
|
||||
{0x1A, "gn", "Number of Gun-type items on either team"},
|
||||
{0x1B, "wd", "Number of Cane-type items on either team"},
|
||||
{0x1C, "tt", "Physical damage"},
|
||||
{0x1D, "lv", "Dice boost"},
|
||||
{0x1E, "adm", "SC attack damage"},
|
||||
{0x1F, "ddm", "Attack bonus"},
|
||||
{0x20, "sat", "Number of Sword-type items on SC\'s team"},
|
||||
{0x21, "edm", "Target attack bonus"},
|
||||
{0x22, "ldm", "Last attack damage before defense"}, // Unused
|
||||
{0x23, "rdm", "Last attack damage"},
|
||||
{0x24, "fdm", "Final damage (after defense)"},
|
||||
{0x25, "ndm", "Unknown: ndm"}, // Unused
|
||||
{0x26, "ehp", "Attacker HP"},
|
||||
};
|
||||
const ExprTokenDefinition& def_for_expr_token(const std::string& token) {
|
||||
static unordered_map<std::string, const ExprTokenDefinition*> index;
|
||||
if (index.empty()) {
|
||||
for (const auto& def : expr_token_defs) {
|
||||
index.emplace(def.token, &def);
|
||||
}
|
||||
}
|
||||
return *index.at(token);
|
||||
}
|
||||
|
||||
static const vector<const char*> description_for_n_condition({
|
||||
/* n00 */ "Always true",
|
||||
@@ -485,6 +499,71 @@ string CardDefinition::Stat::str() const {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CardDefinition::Effect::ExprToken> CardDefinition::Effect::ExprToken::parse(const char* expr) {
|
||||
using T = ExprToken::Type;
|
||||
|
||||
std::vector<ExprToken> ret;
|
||||
while (*expr) {
|
||||
size_t ret_count = ret.size();
|
||||
switch (*expr) {
|
||||
case ' ':
|
||||
ret.emplace_back(ExprToken{.type = T::SPACE, .value = 0, .text = expr, .text_size = 1});
|
||||
break;
|
||||
case '+':
|
||||
ret.emplace_back(ExprToken{.type = T::ADD, .value = 0, .text = expr, .text_size = 1});
|
||||
break;
|
||||
case '-':
|
||||
ret.emplace_back(ExprToken{.type = T::SUBTRACT, .value = 0, .text = expr, .text_size = 1});
|
||||
break;
|
||||
case '*':
|
||||
ret.emplace_back(ExprToken{.type = T::MULTIPLY, .value = 0, .text = expr, .text_size = 1});
|
||||
break;
|
||||
case '/':
|
||||
ret.emplace_back(ExprToken{
|
||||
.type = ((expr[1] == '/') ? T::FLOOR_DIVIDE : T::ROUND_DIVIDE),
|
||||
.value = 0,
|
||||
.text = expr,
|
||||
.text_size = static_cast<size_t>((expr[1] == '/') ? 2 : 1)});
|
||||
break;
|
||||
|
||||
default:
|
||||
if ((*expr >= 'a') && (*expr <= 'z')) {
|
||||
string token_buf;
|
||||
for (const char* z = expr; (*z >= 'a') && (*z <= 'z'); z++) {
|
||||
token_buf.push_back(*z);
|
||||
}
|
||||
|
||||
try {
|
||||
const auto& def = def_for_expr_token(token_buf);
|
||||
ret.emplace_back(ExprToken{
|
||||
.type = T::REFERENCE,
|
||||
.value = def.value,
|
||||
.text = expr - token_buf.size(),
|
||||
.text_size = token_buf.size()});
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("unknown token in card effect expression: " + token_buf);
|
||||
}
|
||||
|
||||
} else if ((*expr >= '0') && (*expr <= '9')) {
|
||||
const char* end_ptr;
|
||||
int32_t value = strtol(expr, const_cast<char**>(&end_ptr), 10);
|
||||
ret.emplace_back(ExprToken{
|
||||
.type = T::NUMBER, .value = value, .text = expr, .text_size = static_cast<size_t>(end_ptr - expr)});
|
||||
|
||||
} else {
|
||||
throw runtime_error("invalid card effect expression");
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.size() != ret_count + 1) {
|
||||
throw std::logic_error("expr parsing step did not add exactly one token");
|
||||
}
|
||||
expr += ret.back().text_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CardDefinition::Effect::is_empty() const {
|
||||
return (this->effect_num == 0 &&
|
||||
this->type == ConditionType::NONE &&
|
||||
@@ -573,7 +652,49 @@ string CardDefinition::Effect::str(const char* separator, const TextSet* text_ar
|
||||
tokens.emplace_back(std::move(cmd_str));
|
||||
}
|
||||
if (!this->expr.empty()) {
|
||||
tokens.emplace_back("expr=" + this->expr.decode());
|
||||
std::string expr_decoded = this->expr.decode();
|
||||
std::vector<std::string> explanation_tokens;
|
||||
try {
|
||||
auto tokens = ExprToken::parse(expr_decoded.c_str());
|
||||
for (const auto& token : tokens) {
|
||||
switch (token.type) {
|
||||
case ExprToken::Type::REFERENCE:
|
||||
try {
|
||||
explanation_tokens.emplace_back(expr_token_defs.at(token.value).description);
|
||||
} catch (const out_of_range&) {
|
||||
explanation_tokens.emplace_back(std::format("<<invalid reference: {:X}>>", token.value));
|
||||
}
|
||||
break;
|
||||
case ExprToken::Type::NUMBER:
|
||||
explanation_tokens.emplace_back(std::format("{}", token.value));
|
||||
break;
|
||||
case ExprToken::Type::SUBTRACT:
|
||||
explanation_tokens.emplace_back("-");
|
||||
break;
|
||||
case ExprToken::Type::ADD:
|
||||
explanation_tokens.emplace_back("+");
|
||||
break;
|
||||
case ExprToken::Type::ROUND_DIVIDE:
|
||||
explanation_tokens.emplace_back("/ (round-divide)");
|
||||
break;
|
||||
case ExprToken::Type::FLOOR_DIVIDE:
|
||||
explanation_tokens.emplace_back("/ (floor-divide)");
|
||||
break;
|
||||
case ExprToken::Type::MULTIPLY:
|
||||
explanation_tokens.emplace_back("*");
|
||||
break;
|
||||
default:
|
||||
explanation_tokens.emplace_back("<<invalid token>>");
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
explanation_tokens.emplace_back(std::format("failed to parse expr: {}", e.what()));
|
||||
}
|
||||
if ((explanation_tokens.size() == 1) && (explanation_tokens.front() == expr_decoded)) {
|
||||
tokens.emplace_back(std::format("expr={}", expr_decoded));
|
||||
} else {
|
||||
tokens.emplace_back(std::format("expr={} ({})", expr_decoded, phosg::join(explanation_tokens, " ")));
|
||||
}
|
||||
}
|
||||
tokens.emplace_back(std::format("when={:02X}:{}", static_cast<uint8_t>(this->when), phosg::name_for_enum(this->when)));
|
||||
tokens.emplace_back("arg1=" + this->str_for_arg(this->arg1.decode()));
|
||||
|
||||
@@ -510,6 +510,27 @@ struct CardDefinition {
|
||||
} __packed_ws__(Stat, 4);
|
||||
|
||||
struct Effect {
|
||||
// Parsed version of expr (see below)
|
||||
struct ExprToken {
|
||||
enum class Type : uint8_t {
|
||||
SPACE = 0, // Also used for end of string (get_next_expr_token returns null)
|
||||
REFERENCE = 1, // Reference to a value from the env stats (e.g. hp)
|
||||
NUMBER = 2, // Constant value (e.g. 2)
|
||||
SUBTRACT = 3, // "-" in input string
|
||||
ADD = 4, // "+" in input string
|
||||
ROUND_DIVIDE = 5, // "/" in input string
|
||||
FLOOR_DIVIDE = 6, // "//" in input string
|
||||
MULTIPLY = 7, // "*" in input string
|
||||
UNKNOWN = 0xFF,
|
||||
};
|
||||
Type type = Type::UNKNOWN;
|
||||
int32_t value = 0;
|
||||
const char* text = nullptr;
|
||||
size_t text_size = 0;
|
||||
|
||||
static std::vector<ExprToken> parse(const char* expr);
|
||||
};
|
||||
|
||||
// effect_num is the 1-based index of this effect within the card definition (that is, .effects[0] should have
|
||||
// effect_num == 1 if it is used).
|
||||
/* 00 */ uint8_t effect_num;
|
||||
|
||||
+9
-5
@@ -2736,13 +2736,17 @@ Action a_generate_ep3_cards_html(
|
||||
deque<string> blocks;
|
||||
blocks.emplace_back("<html><head><title>Phantasy Star Online Episode III cards</title></head><body style=\"background-color:#222222; color: #EEEEEE\">");
|
||||
blocks.emplace_back("<table><tr><th style=\"text-align: left\">Legend:</th></tr><tr style=\"background-color: #663333\"><td>Card has no definition and is obviously incomplete</td></tr><tr style=\"background-color: #336633\"><td>Card is unobtainable in random draws but may be a quest or event reward</td></tr><tr style=\"background-color: #333333\"><td>Card is obtainable in random draws</td></tr></table><br /><br />");
|
||||
blocks.emplace_back("<table><tr><th rowspan=\"2\" style=\"text-align: left; padding: 4px\">ID</th>");
|
||||
|
||||
for (const auto& vi : version_infos) {
|
||||
blocks.emplace_back(std::format("<th colspan=\"{}\" style=\"text-align: left; padding: 4px\">{}</th>",
|
||||
vi.num_output_columns, vi.name));
|
||||
if (version_infos.size() > 1) {
|
||||
blocks.emplace_back("<table><tr><th rowspan=\"2\" style=\"text-align: left; padding: 4px\">ID</th>");
|
||||
for (const auto& vi : version_infos) {
|
||||
blocks.emplace_back(std::format("<th colspan=\"{}\" style=\"text-align: left; padding: 4px\">{}</th>",
|
||||
vi.num_output_columns, vi.name));
|
||||
}
|
||||
blocks.emplace_back("</tr><tr>");
|
||||
} else {
|
||||
blocks.emplace_back("<table><tr><th style=\"text-align: left; padding: 4px\">ID</th>");
|
||||
}
|
||||
blocks.emplace_back("</tr><tr>");
|
||||
for (const auto& vi : version_infos) {
|
||||
if (vi.show_small_column) {
|
||||
blocks.emplace_back("<th style=\"text-align: left; padding: 4px\">Small</th>");
|
||||
|
||||
Reference in New Issue
Block a user