add card list HTML generator
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
## Episode 3
|
||||
|
||||
- Make disconnecting during a tournament match cause you to forfeit the match
|
||||
- Enforce tournament deck restrictions (e.g. rarity checks, No Assist option) when populating COMs at tournament start time
|
||||
- Enforce tournament deck restrictions (e.g. rank checks, No Assist option) when populating COMs at tournament start time
|
||||
- Spectator teams
|
||||
- Spectator teams sometimes stop receiving commands during live battles?
|
||||
- It may be possible to send spectators back to the waiting room after a non-tournament battle by sending 6xB4x05 with environment 0x19, then 6xB4x3B again; try this
|
||||
|
||||
+52
-32
@@ -727,29 +727,43 @@ string CardDefinition::Effect::str_for_arg(const string& arg) {
|
||||
}
|
||||
}
|
||||
|
||||
string CardDefinition::Effect::str() const {
|
||||
uint8_t type = static_cast<uint8_t>(this->type);
|
||||
string cmd_str = string_printf("%02hhX", type);
|
||||
try {
|
||||
const char* name = description_for_condition_type.at(type).name;
|
||||
if (name) {
|
||||
cmd_str += ':';
|
||||
cmd_str += name;
|
||||
string CardDefinition::Effect::str(const char* separator) const {
|
||||
vector<string> tokens;
|
||||
tokens.emplace_back(string_printf("%hhu:", this->effect_num));
|
||||
{
|
||||
uint8_t type = static_cast<uint8_t>(this->type);
|
||||
string cmd_str = string_printf("cmd=%02hhX", type);
|
||||
try {
|
||||
const char* name = description_for_condition_type.at(type).name;
|
||||
if (name) {
|
||||
cmd_str += ':';
|
||||
cmd_str += name;
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
tokens.emplace_back(std::move(cmd_str));
|
||||
}
|
||||
|
||||
string expr_str = this->expr;
|
||||
if (!expr_str.empty()) {
|
||||
expr_str = ", expr=" + expr_str;
|
||||
if (!this->expr.empty()) {
|
||||
tokens.emplace_back("expr=" + string(this->expr));
|
||||
}
|
||||
tokens.emplace_back(string_printf("when=%02hhX", this->when));
|
||||
tokens.emplace_back(this->str_for_arg(this->arg1));
|
||||
tokens.emplace_back(this->str_for_arg(this->arg2));
|
||||
tokens.emplace_back(this->str_for_arg(this->arg3));
|
||||
{
|
||||
uint8_t type = static_cast<uint8_t>(this->apply_criterion);
|
||||
string cond_str = string_printf("cond=%02hhX", type);
|
||||
try {
|
||||
const char* name = name_for_criterion_code(this->apply_criterion);
|
||||
cond_str += ':';
|
||||
cond_str += name;
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
tokens.emplace_back(std::move(cond_str));
|
||||
}
|
||||
tokens.emplace_back(string_printf("a2=%02hhX", this->unknown_a2));
|
||||
|
||||
string arg1str = this->str_for_arg(this->arg1);
|
||||
string arg2str = this->str_for_arg(this->arg2);
|
||||
string arg3str = this->str_for_arg(this->arg3);
|
||||
return string_printf("((%hhu) cmd=%s%s, when=%02hhX, arg1=%s, arg2=%s, arg3=%s, cond=%02hhX, a2=%02hhX)",
|
||||
this->effect_num, cmd_str.c_str(), expr_str.c_str(), this->when, arg1str.data(),
|
||||
arg2str.data(), arg3str.data(), static_cast<uint8_t>(this->apply_criterion), this->unknown_a2);
|
||||
return join(tokens, separator);
|
||||
}
|
||||
|
||||
bool CardDefinition::is_sc() const {
|
||||
@@ -837,13 +851,13 @@ void CardDefinition::decode_range() {
|
||||
}
|
||||
}
|
||||
|
||||
string name_for_rarity(CardRarity rarity) {
|
||||
string name_for_rank(CardRank rank) {
|
||||
static const vector<const char*> names(
|
||||
{"N1", "R1", "S", "E", "N2", "N3", "N4", "R2", "R3", "R4", "SS", "D1", "D2"});
|
||||
try {
|
||||
return names.at(static_cast<uint8_t>(rarity) - 1);
|
||||
return names.at(static_cast<uint8_t>(rank) - 1);
|
||||
} catch (const out_of_range&) {
|
||||
return string_printf("(%02hhX)", static_cast<uint8_t>(rarity));
|
||||
return string_printf("(%02hhX)", static_cast<uint8_t>(rank));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +896,7 @@ string string_for_colors(const parray<uint8_t, 8>& colors) {
|
||||
}
|
||||
}
|
||||
if (ret.empty()) {
|
||||
return "none";
|
||||
return "(none)";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1011,7 +1025,7 @@ string CardDefinition::str(bool single_line) const {
|
||||
} catch (const invalid_argument&) {
|
||||
card_class_str = string_printf("%04hX", this->be_card_class.load());
|
||||
}
|
||||
string rarity_str = name_for_rarity(this->rarity);
|
||||
string rank_str = name_for_rank(this->rank);
|
||||
string target_mode_str = name_for_target_mode(this->target_mode);
|
||||
string assist_turns_str = string_for_assist_turns(this->assist_turns);
|
||||
string hp_str = this->hp.str();
|
||||
@@ -1031,7 +1045,7 @@ string CardDefinition::str(bool single_line) const {
|
||||
} else if (!effects_str.empty()) {
|
||||
effects_str += ", ";
|
||||
}
|
||||
effects_str += this->effects[x].str();
|
||||
effects_str += this->effects[x].str(single_line ? ", " : "\n ");
|
||||
}
|
||||
if (!single_line && effects_str.empty()) {
|
||||
effects_str = " (none)";
|
||||
@@ -1052,7 +1066,7 @@ string CardDefinition::str(bool single_line) const {
|
||||
if (single_line) {
|
||||
string range_str = string_for_range(this->range);
|
||||
return string_printf(
|
||||
"[Card: %04" PRIX32 " name=%s type=%s usable_condition=%s rare=%s "
|
||||
"[Card: %04" PRIX32 " name=%s type=%s usable_condition=%s rank=%s "
|
||||
"cost=%s target=%s range=%s assist_turns=%s cannot_move=%s "
|
||||
"cannot_attack=%s cannot_drop=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s "
|
||||
"top=%s class=%s assist_ai_params=[target=%s priority=%hhu effect=%hhu] drop_rates=[%s, %s] effects=[%s]]",
|
||||
@@ -1060,7 +1074,7 @@ string CardDefinition::str(bool single_line) const {
|
||||
this->en_name.data(),
|
||||
type_str.c_str(),
|
||||
criterion_str.c_str(),
|
||||
rarity_str.c_str(),
|
||||
rank_str.c_str(),
|
||||
cost_str.c_str(),
|
||||
target_mode_str.c_str(),
|
||||
range_str.c_str(),
|
||||
@@ -1105,23 +1119,29 @@ string CardDefinition::str(bool single_line) const {
|
||||
Card: %04" PRIX32 " \"%s\"\n\
|
||||
Type: %s, class: %s\n\
|
||||
Usability condition: %s\n\
|
||||
Rarity: %s\n\
|
||||
Rank: %s\n\
|
||||
Cost: %s\n\
|
||||
Target mode: %s\n\
|
||||
Range:%s\n\
|
||||
Assist turns: %s\n\
|
||||
Capabilities: %s move, %s attack\n\
|
||||
HP: %s, AP: %s, TP: %s, MV: %s\n\
|
||||
Left colors: %s; right colors: %s; top colors: %s\n\
|
||||
Colors:\n\
|
||||
Left: %s\n\
|
||||
Right: %s\n\
|
||||
Top: %s\n\
|
||||
Assist AI parameters: [target %s, priority %hu, effect %hu]\n\
|
||||
Drop rates: [%s, %s] (%s drop)\n\
|
||||
Drop rates:\n\
|
||||
%s\n\
|
||||
%s\n\
|
||||
%s\n\
|
||||
Effects:%s",
|
||||
this->card_id.load(),
|
||||
this->en_name.data(),
|
||||
type_str.c_str(),
|
||||
card_class_str.c_str(),
|
||||
criterion_str.c_str(),
|
||||
rarity_str.c_str(),
|
||||
rank_str.c_str(),
|
||||
cost_str.c_str(),
|
||||
target_mode_str.c_str(),
|
||||
range_str.c_str(),
|
||||
@@ -1140,7 +1160,7 @@ Card: %04" PRIX32 " \"%s\"\n\
|
||||
static_cast<uint8_t>(this->assist_ai_params % 100),
|
||||
drop0_str.c_str(),
|
||||
drop1_str.c_str(),
|
||||
this->cannot_drop ? "cannot" : "can",
|
||||
this->cannot_drop ? "Forbidden" : "Permitted",
|
||||
effects_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
+21
-22
@@ -102,7 +102,7 @@ enum class CriterionCode : uint8_t {
|
||||
|
||||
const char* name_for_criterion_code(CriterionCode code);
|
||||
|
||||
enum class CardRarity : uint8_t {
|
||||
enum class CardRank : uint8_t {
|
||||
N1 = 0x01,
|
||||
R1 = 0x02,
|
||||
S = 0x03,
|
||||
@@ -114,14 +114,14 @@ enum class CardRarity : uint8_t {
|
||||
R3 = 0x09,
|
||||
R4 = 0x0A,
|
||||
SS = 0x0B,
|
||||
// Cards with the D1 or D2 rarities are considered never usable by the player,
|
||||
// Cards with the D1 or D2 ranks are considered never usable by the player,
|
||||
// and are automatically removed from player decks before battle and when
|
||||
// loading the deckbuilder. Cards with the D1 rarity appear in the deckbuilder
|
||||
// but are grayed out (and cannot be added to decks); cards with the D2 rarity
|
||||
// loading the deckbuilder. Cards with the D1 rank appear in the deckbuilder
|
||||
// but are grayed out (and cannot be added to decks); cards with the D2 rank
|
||||
// don't appear in the deckbuilder at all.
|
||||
D1 = 0x0C,
|
||||
D2 = 0x0D,
|
||||
// The D3 rarity is referenced in a few places, including the function that
|
||||
// The D3 rank is referenced in a few places, including the function that
|
||||
// determines whether or not a card can appear in post-battle draws, and the
|
||||
// function that determines whether a card should appear in the deckbuilder.
|
||||
// In these cases, it prevents the card from appearing.
|
||||
@@ -497,7 +497,7 @@ struct CardDefinition {
|
||||
|
||||
bool is_empty() const;
|
||||
static std::string str_for_arg(const std::string& arg);
|
||||
std::string str() const;
|
||||
std::string str(const char* separator = ", ") const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 0000 */ be_uint32_t card_id;
|
||||
@@ -525,7 +525,7 @@ struct CardDefinition {
|
||||
// random assist.
|
||||
/* 0091 */ uint8_t cannot_drop;
|
||||
/* 0092 */ CriterionCode usable_criterion;
|
||||
/* 0093 */ CardRarity rarity;
|
||||
/* 0093 */ CardRank rank;
|
||||
/* 0094 */ be_uint16_t unused4;
|
||||
// The card class is used for checking attributes (e.g. item types). It's
|
||||
// stored big-endian here, so there's a helper function (card_class()) that
|
||||
@@ -552,7 +552,7 @@ struct CardDefinition {
|
||||
// card can transform into this card if any of the following are true:
|
||||
// - type is SC_HUNTERS or SC_ARKZ
|
||||
// - card_class is BOSS_ATTACK_ACTION (0x23) or BOSS_TECH (0x24)
|
||||
// - rarity is E, D1, or D2
|
||||
// - rank is E, D1, or D2
|
||||
// - cannot_drop is 1 (specifically 1; other nonzero values here don't
|
||||
// prevent the card from appearing in post-battle draws)
|
||||
// If none of these conditions apply, the logic below is used.
|
||||
@@ -653,7 +653,7 @@ struct CardDefinition {
|
||||
// effect. Therefore, the final probability that a card will transform into a
|
||||
// VIP card is P(activate) * P(vip), and the final probability of transforming
|
||||
// into a rarer card is P(activate) * P(rare).
|
||||
// ====== Card rarities N4-N1 ====== ====== Card rarities R4-R1 ======
|
||||
// ======== Card rank N4-N1 ======== ======== Card rank R4-R1 ========
|
||||
// Count P(activate) P(rare) P(vip) P(activate) P(rare) P(vip)
|
||||
// 0-4 0% 0% 0% 0% 0% 0%
|
||||
// 5-10 1.923077% 55% 0.5% 2.0408163% 55% 0.5%
|
||||
@@ -665,9 +665,9 @@ struct CardDefinition {
|
||||
// 53-99 5% 90% 0.33333334% 5.263158% 90% 0.4347826%
|
||||
//
|
||||
// If a transformation occurs, the card transforms to a card of a different
|
||||
// rarity. First, the game consults the following table to determine the
|
||||
// rarity of the resulting card (original card's rarity on the left, new
|
||||
// card's rarity across the top):
|
||||
// rank. First, the game consults the following table to determine the rank of
|
||||
// the resulting card (original card's rank on the left, new card's rank
|
||||
// across the top):
|
||||
// N4 N3 N2 N1 R4 R3 R2 R1 S SS
|
||||
// N4 => 60 30 10
|
||||
// N3 => 60 30 10
|
||||
@@ -682,16 +682,15 @@ struct CardDefinition {
|
||||
// card transforms, there is a 900/1001 chance of becoming another R1, a
|
||||
// 100/1001 chance of becoming an S, and a 1/1001 chance of becoming an SS.
|
||||
//
|
||||
// Once a rarity is chosen, the game puts all possible cards into buckets
|
||||
// based on how many of that card the player already has, then chooses a
|
||||
// random card out of bucket 0, then bucket 1, etc. all the way up to bucket
|
||||
// 49 (or 2 if the final rarity is S or SS). The first drawn card that is the
|
||||
// final rarity is the card that the original card transforms into. Notably,
|
||||
// this logic means that cards are more likely to transform into cards that
|
||||
// the player doesn't already have, or only has few copies of. Also notably,
|
||||
// it is impossible for a card to transform into another card that the player
|
||||
// already has 50 or more copies of, or an S or SS card that the player
|
||||
// already has 3 copies of.
|
||||
// Once a rank is chosen, the game puts all possible cards into buckets based
|
||||
// on how many of that card the player already has, then chooses a random card
|
||||
// out of bucket 0, then bucket 1, etc. all the way up to bucket 49 (or 2 if
|
||||
// the final rank is S or SS). The first drawn card that is the final rank is
|
||||
// the card that the original card transforms into. Notably, this logic means
|
||||
// that cards are more likely to transform into cards that the player doesn't
|
||||
// already have, or only has few copies of. Also notably, it is impossible for
|
||||
// a card to transform into another card that the player already has 50 or
|
||||
// more copies of, or an S or SS card that the player already has 3 copies of.
|
||||
//
|
||||
// One curiosity about the above procedure is that the buckets can only hold
|
||||
// 400 cards each for the N ranks, 300 each for the R ranks, and 100 each for
|
||||
|
||||
@@ -2507,11 +2507,11 @@ void RulerServer::register_player(
|
||||
this->set_card_action_metadatas[client_id] = set_card_action_metadatas;
|
||||
}
|
||||
|
||||
void RulerServer::replace_D1_D2_rarity_cards_with_Attack(
|
||||
void RulerServer::replace_D1_D2_rank_cards_with_Attack(
|
||||
parray<le_uint16_t, 0x1F>& card_ids) const {
|
||||
for (size_t z = 0; z < card_ids.size(); z++) {
|
||||
auto ce = this->definition_for_card_id(card_ids[z]);
|
||||
if (ce && ((ce->def.rarity == CardRarity::D1) || (ce->def.rarity == CardRarity::D2))) {
|
||||
if (ce && ((ce->def.rank == CardRank::D1) || (ce->def.rank == CardRank::D2))) {
|
||||
card_ids[z] = 0x008A; // Attack action card
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,8 +197,7 @@ public:
|
||||
std::shared_ptr<DeckEntry> deck_entry,
|
||||
std::shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
|
||||
std::shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas);
|
||||
void replace_D1_D2_rarity_cards_with_Attack(
|
||||
parray<le_uint16_t, 0x1F>& card_ids) const;
|
||||
void replace_D1_D2_rank_cards_with_Attack(parray<le_uint16_t, 0x1F>& card_ids) const;
|
||||
AttackMedium get_attack_medium(const ActionState& pa) const;
|
||||
void set_client_team_id(uint8_t client_id, uint8_t team_id);
|
||||
int32_t set_cost_for_card(uint8_t client_id, uint16_t card_ref) const;
|
||||
|
||||
@@ -1991,7 +1991,7 @@ void Server::handle_CAx14_update_deck_during_setup(const string& data) {
|
||||
throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error));
|
||||
}
|
||||
if (!(this->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||
this->ruler_server->replace_D1_D2_rarity_cards_with_Attack(entry.card_ids);
|
||||
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
||||
}
|
||||
*this->deck_entries[in_cmd.client_id] = in_cmd.entry;
|
||||
this->presence_entries[in_cmd.client_id].player_present = true;
|
||||
|
||||
+108
@@ -294,6 +294,7 @@ enum class Behavior {
|
||||
CONVERT_ITEMRT_REL_TO_JSON,
|
||||
SHOW_EP3_MAPS,
|
||||
SHOW_EP3_CARDS,
|
||||
GENERATE_EP3_CARDS_HTML,
|
||||
DESCRIBE_ITEM,
|
||||
ENCODE_ITEM,
|
||||
PARSE_OBJECT_GRAPH,
|
||||
@@ -589,6 +590,8 @@ int main(int argc, char** argv) {
|
||||
behavior = Behavior::SHOW_EP3_MAPS;
|
||||
} else if (!strcmp(argv[x], "show-ep3-cards")) {
|
||||
behavior = Behavior::SHOW_EP3_CARDS;
|
||||
} else if (!strcmp(argv[x], "generate-ep3-cards-html")) {
|
||||
behavior = Behavior::GENERATE_EP3_CARDS_HTML;
|
||||
} else if (!strcmp(argv[x], "describe-item")) {
|
||||
behavior = Behavior::DESCRIBE_ITEM;
|
||||
} else if (!strcmp(argv[x], "encode-item")) {
|
||||
@@ -1661,6 +1664,111 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::GENERATE_EP3_CARDS_HTML: {
|
||||
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr", "system/ep3/card-text.mnrd");
|
||||
struct CardInfo {
|
||||
shared_ptr<const Episode3::CardIndex::CardEntry> ce;
|
||||
Image small_image;
|
||||
Image medium_image;
|
||||
Image large_image;
|
||||
|
||||
bool is_empty() const {
|
||||
return (this->ce == nullptr) && (this->small_image.get_width() == 0) && (this->medium_image.get_width() == 0) && (this->large_image.get_width() == 0);
|
||||
}
|
||||
};
|
||||
vector<CardInfo> infos;
|
||||
for (uint32_t card_id : card_index.all_ids()) {
|
||||
if (infos.size() <= card_id) {
|
||||
infos.resize(card_id + 1);
|
||||
}
|
||||
infos[card_id].ce = card_index.definition_for_id(card_id);
|
||||
}
|
||||
for (const auto& filename : list_directory_sorted("system/ep3/cardtex")) {
|
||||
if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) {
|
||||
size_t card_id = stoull(filename.substr(2, 3), nullptr, 10);
|
||||
if (infos.size() <= card_id) {
|
||||
infos.resize(card_id + 1);
|
||||
}
|
||||
auto& info = infos[card_id];
|
||||
Image img("system/ep3/cardtex/" + filename);
|
||||
if (filename[0] == 'C') {
|
||||
info.large_image = Image(512, 399);
|
||||
info.large_image.blit(img, 0, 0, 512, 399, 0, 0);
|
||||
} else if (filename[0] == 'L') {
|
||||
info.medium_image = Image(184, 144);
|
||||
info.medium_image.blit(img, 0, 0, 184, 144, 0, 0);
|
||||
} else if (filename[0] == 'M') {
|
||||
info.small_image = Image(58, 43);
|
||||
info.small_image.blit(img, 0, 0, 58, 43, 0, 0);
|
||||
}
|
||||
fprintf(stderr, "... %s (%04zX)\r", filename.c_str(), card_id);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "... Loaded card images\n");
|
||||
|
||||
deque<string> blocks;
|
||||
blocks.emplace_back("<html><head><title>Phantasy Star Online Episode III cards</title></head><body>");
|
||||
blocks.emplace_back("<table><tr><th style=\"text-align: left\">Legend:</th></tr><tr style=\"background-color: #FFC0C0\"><td>Card has no definition and is obviously incomplete</td></tr><tr style=\"background-color: #C0EEC0\"><td>Card is unobtainable in random draws but may be a quest or event reward</td></tr><tr style=\"background-color: #FFFFFF\"><td>Card is obtainable in random draws</td></tr></table><br /><br />");
|
||||
blocks.emplace_back("<table><tr><th style=\"text-align: left\">ID</th><th style=\"text-align: left\">Large</th><th style=\"text-align: left\">Medium</th><th style=\"text-align: left\">Small</th><th style=\"text-align: left\">Text</th><th style=\"text-align: left\">Disassembly</th></tr>");
|
||||
bool gray = false;
|
||||
for (size_t card_id = 0; card_id < infos.size(); card_id++) {
|
||||
const auto& entry = infos[card_id];
|
||||
if (entry.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* background_color;
|
||||
if (!entry.ce) {
|
||||
background_color = gray ? "#EEC0C0" : "#FFC0C0";
|
||||
} else if (entry.ce->def.cannot_drop ||
|
||||
((entry.ce->def.rank == Episode3::CardRank::D1) || (entry.ce->def.rank == Episode3::CardRank::D2) || (entry.ce->def.rank == Episode3::CardRank::D3)) ||
|
||||
((entry.ce->def.card_class() == Episode3::CardClass::BOSS_ATTACK_ACTION) || (entry.ce->def.card_class() == Episode3::CardClass::BOSS_TECH)) ||
|
||||
((entry.ce->def.drop_rates[0] == 6) && (entry.ce->def.drop_rates[1] == 6))) {
|
||||
background_color = gray ? "#C0EEC0" : "#C0FFC0";
|
||||
} else {
|
||||
background_color = gray ? "#EEEEEE" : "#FFFFFF";
|
||||
}
|
||||
|
||||
gray = !gray;
|
||||
blocks.emplace_back(string_printf("<tr style=\"background-color: %s\">", background_color));
|
||||
blocks.emplace_back(string_printf("<td><pre>%04zX</pre></td><td>", card_id));
|
||||
if (entry.large_image.get_width() > 0) {
|
||||
blocks.emplace_back("<img src=\"");
|
||||
blocks.emplace_back(entry.large_image.png_data_url());
|
||||
blocks.emplace_back("\" />");
|
||||
}
|
||||
blocks.emplace_back("</td><td>");
|
||||
if (entry.medium_image.get_width() > 0) {
|
||||
blocks.emplace_back("<img src=\"");
|
||||
blocks.emplace_back(entry.medium_image.png_data_url());
|
||||
blocks.emplace_back("\" />");
|
||||
}
|
||||
blocks.emplace_back("</td><td>");
|
||||
if (entry.small_image.get_width() > 0) {
|
||||
blocks.emplace_back("<img src=\"");
|
||||
blocks.emplace_back(entry.small_image.png_data_url());
|
||||
blocks.emplace_back("\" />");
|
||||
}
|
||||
blocks.emplace_back("</td><td>");
|
||||
if (entry.ce) {
|
||||
blocks.emplace_back("<pre>");
|
||||
blocks.emplace_back(entry.ce->text);
|
||||
blocks.emplace_back("</pre></td><td><pre>");
|
||||
blocks.emplace_back(entry.ce->def.str(false));
|
||||
blocks.emplace_back("</pre>");
|
||||
} else {
|
||||
blocks.emplace_back("</td><td><pre>Definition is missing</pre>");
|
||||
}
|
||||
blocks.emplace_back("</td></tr>");
|
||||
fprintf(stderr, "... %04zX/%04zX\r", card_id, infos.size());
|
||||
}
|
||||
blocks.emplace_back("</table></body></html>");
|
||||
fprintf(stderr, "... Constructed HTML file\n");
|
||||
|
||||
save_file("cards.html", join(blocks, ""));
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::SHOW_EP3_MAPS: {
|
||||
config_log.info("Collecting Episode 3 data");
|
||||
Episode3::MapIndex map_index("system/ep3");
|
||||
|
||||
@@ -273,14 +273,14 @@ struct PSOGCEp3CharacterFile {
|
||||
/* 19428 */ be_uint32_t save_count;
|
||||
// This is an array of 1000 bits, represented here as 128 bytes, the last few
|
||||
// of which are unused. Each bit corresponds to a card ID with the bit's
|
||||
// index; if the bit is set, then the card's rarity is replaced with D2 if its
|
||||
// original rarity is S, SS, E, or D2, or with D1 if the original rarity is
|
||||
// any other value. Upon receiving a B8 command (new card definitions), the
|
||||
// game updates this array of bits based on which cards in the received update
|
||||
// have D1 or D2 rarities. This could have been used by Sega to persist part
|
||||
// of the online updates into offline play, but there's no indication that
|
||||
// they ever used this functionality.
|
||||
/* 1942C */ parray<uint8_t, 0x80> card_rarity_override_flags;
|
||||
// index; if the bit is set, then the card's rank is replaced with D2 if its
|
||||
// original rank is S, SS, E, or D2, or with D1 if the original rank is any
|
||||
// other value. Upon receiving a B8 command (new card definitions), the game
|
||||
// updates this array of bits based on which cards in the received update
|
||||
// have D1 or D2 ranks. This could have been used by Sega to persist part of
|
||||
// the online updates into offline play, but there's no indication that they
|
||||
// ever used this functionality.
|
||||
/* 1942C */ parray<uint8_t, 0x80> card_rank_override_flags;
|
||||
/* 194AC */ be_uint32_t round2_seed;
|
||||
/* 194B0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
+1
-1
@@ -170,7 +170,7 @@ Server commands:\n\
|
||||
dice separately\n\
|
||||
overall-time-limit=N: Set battle time limit (in multiples of 5 minutes)\n\
|
||||
phase-time-limit=N: Set phase time limit (in seconds)\n\
|
||||
allowed-cards=ALL/N/NR/NRS: Set rarities of allowed cards\n\
|
||||
allowed-cards=ALL/N/NR/NRS: Set ranks of allowed cards\n\
|
||||
deck-shuffle=ON/OFF: Enable/disable deck shuffle\n\
|
||||
deck-loop=ON/OFF: Enable/disable deck loop\n\
|
||||
hp=N: Set Story Character initial HP\n\
|
||||
|
||||
Reference in New Issue
Block a user