allow creating items by name instead of by data
This commit is contained in:
+2
-32
@@ -1176,24 +1176,9 @@ static void server_command_item(
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(s, l);
|
||||
|
||||
string data = parse_data_string(encode_sjis(args));
|
||||
if (data.size() < 2) {
|
||||
send_text_message(c, u"$C6Item codes must be\n2 bytes or more");
|
||||
return;
|
||||
}
|
||||
if (data.size() > 16) {
|
||||
send_text_message(c, u"$C6Item codes must be\n16 bytes or fewer");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerInventoryItem item;
|
||||
item.data = item_for_string(encode_sjis(args));
|
||||
item.data.id = l->generate_item_id(c->lobby_client_id);
|
||||
if (data.size() <= 12) {
|
||||
memcpy(item.data.data1.data(), data.data(), data.size());
|
||||
} else {
|
||||
memcpy(item.data.data1.data(), data.data(), 12);
|
||||
memcpy(item.data.data2.data(), data.data() + 12, data.size() - 12);
|
||||
}
|
||||
|
||||
l->add_item(item, c->area, c->x, c->z);
|
||||
send_drop_stacked_item(l, item.data, c->area, c->x, c->z);
|
||||
@@ -1223,24 +1208,9 @@ static void proxy_command_item(
|
||||
|
||||
bool set_drop = (!args.empty() && (args[0] == u'!'));
|
||||
|
||||
string data = parse_data_string(encode_sjis(args));
|
||||
if (data.size() < 2) {
|
||||
send_text_message(session.client_channel, u"$C6Item codes must be\n2 bytes or more");
|
||||
return;
|
||||
}
|
||||
if (data.size() > 16) {
|
||||
send_text_message(session.client_channel, u"$C6Item codes must be\n16 bytes or fewer");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerInventoryItem item;
|
||||
item.data = item_for_string(encode_sjis(set_drop ? args.substr(1) : args));
|
||||
item.data.id = random_object<uint32_t>();
|
||||
if (data.size() <= 12) {
|
||||
memcpy(item.data.data1.data(), data.data(), data.size());
|
||||
} else {
|
||||
memcpy(item.data.data1.data(), data.data(), 12);
|
||||
memcpy(item.data.data2.data(), data.data() + 12, data.size() - 12);
|
||||
}
|
||||
|
||||
if (set_drop) {
|
||||
session.next_drop_item = item;
|
||||
|
||||
+305
-32
@@ -4,6 +4,8 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string S_RANK_NAME_CHARACTERS("\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", 0x20);
|
||||
|
||||
ItemData::ItemData() {
|
||||
this->clear();
|
||||
}
|
||||
@@ -50,17 +52,54 @@ uint32_t ItemData::primary_identifier() const {
|
||||
// The game treats any item starting with 04 as Meseta, and ignores the rest
|
||||
// of data1 (the value is in data2)
|
||||
if (this->data1[0] == 0x04) {
|
||||
return 0x00040000;
|
||||
return 0x040000;
|
||||
}
|
||||
if (this->data1[0] == 0x03 && this->data1[1] == 0x02) {
|
||||
return 0x00030200; // Tech disk (data1[2] is level, so omit it)
|
||||
return 0x030200; // Tech disk (data1[2] is level, so omit it)
|
||||
} else if (this->data1[0] == 0x02) {
|
||||
return 0x00020000 | (this->data1[1] << 8); // Mag
|
||||
return 0x020000 | (this->data1[1] << 8); // Mag
|
||||
} else {
|
||||
return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2];
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::is_wrapped() const {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
return this->data1[4] & 0x40;
|
||||
case 2:
|
||||
return this->data2[2] & 0x40;
|
||||
case 3:
|
||||
return !this->is_stackable() && (this->data1[3] & 0x40);
|
||||
case 4:
|
||||
return false;
|
||||
default:
|
||||
throw runtime_error("invalid item data");
|
||||
}
|
||||
}
|
||||
|
||||
void ItemData::unwrap() {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
this->data1[4] &= 0xBF;
|
||||
break;
|
||||
case 2:
|
||||
this->data2[2] &= 0xBF;
|
||||
break;
|
||||
case 3:
|
||||
if (!this->is_stackable()) {
|
||||
this->data1[3] &= 0xBF;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid item data");
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::is_stackable() const {
|
||||
return this->max_stack_size() > 1;
|
||||
}
|
||||
@@ -1364,6 +1403,236 @@ const unordered_map<uint32_t, ItemNameInfo> name_info_for_primary_identifier({
|
||||
{0x031903, "Team Points 10000"},
|
||||
});
|
||||
|
||||
ItemData::ItemData(const string& orig_description) {
|
||||
this->data1d.clear(0);
|
||||
this->id = 0xFFFFFFFF;
|
||||
this->data2d = 0;
|
||||
|
||||
string desc = tolower(orig_description);
|
||||
if (ends_with(desc, " meseta")) {
|
||||
this->data1[0] = 0x04;
|
||||
this->data2d = stol(desc, nullptr, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
if (starts_with(desc, "disk:")) {
|
||||
auto tokens = split(desc, ' ');
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("invalid tech disk name");
|
||||
}
|
||||
if (!starts_with(tokens[0], "disk:") || !starts_with(tokens[1], "lv.")) {
|
||||
throw runtime_error("invalid tech disk format");
|
||||
}
|
||||
uint8_t tech = technique_for_name(tokens[0].substr(5));
|
||||
uint8_t level = stoul(tokens[1].substr(3), nullptr, 10);
|
||||
this->data1[0] = 0x03;
|
||||
this->data1[1] = 0x02;
|
||||
this->data1[2] = level;
|
||||
this->data1[4] = tech;
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_wrapped = starts_with(desc, "wrapped ");
|
||||
if (is_wrapped) {
|
||||
desc = desc.substr(8);
|
||||
}
|
||||
|
||||
uint8_t weapon_special = 0;
|
||||
for (const auto& it : name_for_weapon_special) {
|
||||
if (!it.second) {
|
||||
continue;
|
||||
}
|
||||
string prefix = tolower(it.second);
|
||||
prefix += ' ';
|
||||
if (starts_with(desc, prefix)) {
|
||||
weapon_special = it.first;
|
||||
desc = desc.substr(prefix.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static map<string, uint32_t> primary_identifier_for_name;
|
||||
if (primary_identifier_for_name.empty()) {
|
||||
for (const auto& it : name_info_for_primary_identifier) {
|
||||
primary_identifier_for_name.emplace(tolower(it.second.name), it.first);
|
||||
}
|
||||
}
|
||||
auto name_it = primary_identifier_for_name.lower_bound(desc);
|
||||
// Look up to 3 places before the lower bound. We have to do this to catch
|
||||
// cases like Sange vs. Sange & Yasha - if the input is like "Sange 0/...",
|
||||
// then we'll see Sange & Yasha first, which we should skip.
|
||||
size_t lookback = 0;
|
||||
while (lookback < 4) {
|
||||
if (name_it != primary_identifier_for_name.end() &&
|
||||
desc.starts_with(name_it->first)) {
|
||||
break;
|
||||
} else if (name_it == primary_identifier_for_name.begin()) {
|
||||
throw runtime_error("no such item");
|
||||
} else {
|
||||
name_it--;
|
||||
lookback++;
|
||||
}
|
||||
}
|
||||
if (lookback >= 4) {
|
||||
throw runtime_error("item not found");
|
||||
}
|
||||
|
||||
desc = desc.substr(name_it->first.size());
|
||||
if (starts_with(desc, " ")) {
|
||||
desc = desc.substr(1);
|
||||
}
|
||||
|
||||
uint32_t primary_identifier = name_it->second;
|
||||
this->data1[0] = (primary_identifier >> 16) & 0xFF;
|
||||
this->data1[1] = (primary_identifier >> 8) & 0xFF;
|
||||
this->data1[2] = primary_identifier & 0xFF;
|
||||
|
||||
if (this->data1[0] == 0x00) {
|
||||
// Weapons: add special, grind and percentages (or name, if S-rank)
|
||||
this->data1[4] = weapon_special | (is_wrapped ? 0x40 : 0x00);
|
||||
|
||||
auto tokens = split(desc, ' ');
|
||||
for (auto& token : tokens) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (starts_with(token, "+")) {
|
||||
token = token.substr(1);
|
||||
this->data1[3] = stoul(token, nullptr, 10);
|
||||
|
||||
} else if (this->is_s_rank_weapon()) {
|
||||
if (token.size() > 8) {
|
||||
throw runtime_error("s-rank name too long");
|
||||
}
|
||||
|
||||
uint8_t char_indexes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
for (size_t z = 0; z < token.size(); z++) {
|
||||
char ch = toupper(token[z]);
|
||||
size_t pos = S_RANK_NAME_CHARACTERS.find(ch);
|
||||
if (pos == string::npos) {
|
||||
throw runtime_error(string_printf("s-rank name contains invalid character %02hhX (%c)", ch, ch));
|
||||
}
|
||||
char_indexes[z] = pos;
|
||||
}
|
||||
|
||||
this->data1w[3] = (char_indexes[1] & 0x1F) | ((char_indexes[0] & 0x1F) << 5);
|
||||
this->data1w[4] = (char_indexes[4] & 0x1F) | ((char_indexes[3] & 0x1F) << 5) | ((char_indexes[2] & 0x1F) << 10);
|
||||
this->data1w[5] = (char_indexes[7] & 0x1F) | ((char_indexes[6] & 0x1F) << 5) | ((char_indexes[5] & 0x1F) << 10);
|
||||
|
||||
} else {
|
||||
auto p_tokens = split(token, '/');
|
||||
if (p_tokens.size() > 5) {
|
||||
throw runtime_error("invalid bonuses token");
|
||||
}
|
||||
uint8_t bonus_index = 0;
|
||||
for (size_t z = 0; z < p_tokens.size(); z++) {
|
||||
int8_t bonus_value = stol(p_tokens[z], nullptr, 10);
|
||||
if (bonus_value == 0) {
|
||||
continue;
|
||||
}
|
||||
if (bonus_index >= 3) {
|
||||
throw runtime_error("weapon has too many bonuses");
|
||||
}
|
||||
this->data1[6 + (2 * bonus_index)] = z + 1;
|
||||
this->data1[7 + (2 * bonus_index)] = static_cast<uint8_t>(bonus_value);
|
||||
bonus_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (this->data1[0] == 0x01) {
|
||||
if (this->data1[1] == 0x03) { // Unit
|
||||
static const unordered_map<string, uint16_t> modifiers({
|
||||
{"--", 0xFFFC},
|
||||
{"-", 0xFFFE},
|
||||
{"", 0x0000},
|
||||
{"+", 0x0002},
|
||||
{"++", 0x0004},
|
||||
});
|
||||
uint16_t modifier = modifiers.at(desc);
|
||||
this->data1[7] = modifier & 0xFF;
|
||||
this->data1[8] = (modifier >> 8) & 0xFF;
|
||||
|
||||
} else { // Armor/shield
|
||||
for (const auto& token : split(desc, ' ')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
} else if (!starts_with(token, "+")) {
|
||||
throw runtime_error("invalid armor/shield modifier");
|
||||
}
|
||||
if (ends_with(token, "def")) {
|
||||
this->data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||
} else if (ends_with(token, "evp")) {
|
||||
this->data1w[4] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||
} else {
|
||||
this->data1[5] = stoul(token.substr(1), nullptr, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_wrapped) {
|
||||
this->data1[4] |= 0x40;
|
||||
}
|
||||
|
||||
} else if (this->data1[0] == 0x02) {
|
||||
for (const auto& token : split(desc, ' ')) {
|
||||
if (starts_with(token, "pb:")) { // Photon blasts
|
||||
auto pb_tokens = split(token.substr(3), ',');
|
||||
if (pb_tokens.size() > 3) {
|
||||
throw runtime_error("too many photon blasts specified");
|
||||
}
|
||||
static const unordered_map<string, uint8_t> name_to_pb_num({
|
||||
{"f", 0},
|
||||
{"e", 1},
|
||||
{"g", 2},
|
||||
{"p", 3},
|
||||
{"l", 4},
|
||||
{"m&y", 5},
|
||||
});
|
||||
for (const auto& pb_token : pb_tokens) {
|
||||
this->add_mag_photon_blast(name_to_pb_num.at(pb_token));
|
||||
}
|
||||
} else if (ends_with(token, "%")) { // Synchro
|
||||
this->data2[0] = stoul(token.substr(0, token.size() - 1), nullptr, 10);
|
||||
} else if (ends_with(token, "iq")) { // IQ
|
||||
this->data2[1] = stoul(token.substr(0, token.size() - 2), nullptr, 10);
|
||||
} else if (!token.empty() && isdigit(token[0])) { // Stats
|
||||
auto s_tokens = split(token, '/');
|
||||
if (s_tokens.size() != 4) {
|
||||
throw runtime_error("incorrect stat count");
|
||||
}
|
||||
this->data1w[2] = stoul(s_tokens[0], nullptr, 10);
|
||||
this->data1w[3] = stoul(s_tokens[1], nullptr, 10);
|
||||
this->data1w[4] = stoul(s_tokens[2], nullptr, 10);
|
||||
this->data1w[5] = stoul(s_tokens[3], nullptr, 10);
|
||||
} else { // Color
|
||||
this->data2[3] = mag_color_for_name.at(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_wrapped) {
|
||||
this->data2[2] |= 0x40;
|
||||
}
|
||||
|
||||
} else if (this->data1[0] == 0x03) {
|
||||
if (this->max_stack_size() > 1) {
|
||||
if (starts_with(desc, "x")) {
|
||||
this->data1[5] = stoul(desc.substr(1), nullptr, 10);
|
||||
} else {
|
||||
this->data1[5] = 1;
|
||||
}
|
||||
} else if (!desc.empty()) {
|
||||
throw runtime_error("item cannot be stacked");
|
||||
}
|
||||
|
||||
if (is_wrapped) {
|
||||
this->data1[3] |= 0x40;
|
||||
}
|
||||
} else {
|
||||
throw logic_error("invalid item class");
|
||||
}
|
||||
}
|
||||
|
||||
string ItemData::hex() const {
|
||||
return string_printf("%02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX (%08" PRIX32 ") %02hhX%02hhX%02hhX%02hhX",
|
||||
this->data1[0], this->data1[1], this->data1[2], this->data1[3],
|
||||
@@ -1402,6 +1671,10 @@ string ItemData::name(bool include_color_codes) const {
|
||||
if ((this->data1[0] == 0x02) && (this->data2[2] & 0x40)) {
|
||||
ret_tokens.emplace_back("Wrapped");
|
||||
}
|
||||
// And tools can be wrapped if they aren't stackable
|
||||
if ((this->data1[0] == 0x03) && !this->is_stackable() && (this->data1[3] & 0x40)) {
|
||||
ret_tokens.emplace_back("Wrapped");
|
||||
}
|
||||
|
||||
// Add the item name. Technique disks are special because the level is part of
|
||||
// the primary identifier, so we manually generate the name instead of looking
|
||||
@@ -1434,7 +1707,7 @@ string ItemData::name(bool include_color_codes) const {
|
||||
ret_tokens.emplace_back(string_printf("+%hhu", this->data1[3]));
|
||||
}
|
||||
|
||||
if (this->is_s_rank_weapon() && (this->data1[6] & 0x18)) {
|
||||
if (this->is_s_rank_weapon()) {
|
||||
// S-rank (has name instead of percent bonuses)
|
||||
uint8_t char_indexes[8] = {
|
||||
static_cast<uint8_t>((this->data1w[3] >> 5) & 0x1F),
|
||||
@@ -1446,11 +1719,10 @@ string ItemData::name(bool include_color_codes) const {
|
||||
static_cast<uint8_t>((this->data1w[5] >> 5) & 0x1F),
|
||||
static_cast<uint8_t>(this->data1w[5] & 0x1F),
|
||||
};
|
||||
const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
|
||||
|
||||
string name;
|
||||
for (size_t x = 0; x < 8; x++) {
|
||||
char ch = translation_table[char_indexes[x]];
|
||||
char ch = S_RANK_NAME_CHARACTERS[char_indexes[x]];
|
||||
if (ch == 0) {
|
||||
break;
|
||||
}
|
||||
@@ -1552,33 +1824,12 @@ string ItemData::name(bool include_color_codes) const {
|
||||
token += pb_names[x];
|
||||
}
|
||||
ret_tokens.emplace_back(std::move(token));
|
||||
}
|
||||
|
||||
static const vector<const char*> mag_colors({
|
||||
/* 00 */ "red",
|
||||
/* 01 */ "blue",
|
||||
/* 02 */ "yellow",
|
||||
/* 03 */ "green",
|
||||
/* 04 */ "purple",
|
||||
/* 05 */ "black",
|
||||
/* 06 */ "white",
|
||||
/* 07 */ "cyan",
|
||||
/* 08 */ "brown",
|
||||
/* 09 */ "orange",
|
||||
/* 0A */ "light blue",
|
||||
/* 0B */ "olive",
|
||||
/* 0C */ "light cyan",
|
||||
/* 0D */ "dark purple",
|
||||
/* 0E */ "grey",
|
||||
/* 0F */ "light grey",
|
||||
/* 10 */ "pink",
|
||||
/* 11 */ "dark cyan",
|
||||
/* 12 */ "costume color",
|
||||
});
|
||||
try {
|
||||
ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(this->data2[3])));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", this->data2[3]));
|
||||
}
|
||||
try {
|
||||
ret_tokens.emplace_back(string_printf("(%s)", name_for_mag_color.at(this->data2[3])));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", this->data2[3]));
|
||||
}
|
||||
|
||||
// For tools, add the amount (if applicable)
|
||||
@@ -1603,3 +1854,25 @@ string ItemData::name(bool include_color_codes) const {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ItemData item_for_string(const string& desc) {
|
||||
try {
|
||||
return ItemData(desc);
|
||||
} catch (const exception&) {
|
||||
string data = parse_data_string(desc);
|
||||
if (data.size() < 2) {
|
||||
throw runtime_error("item code too short");
|
||||
}
|
||||
if (data.size() > 16) {
|
||||
throw runtime_error("item code too long");
|
||||
}
|
||||
ItemData ret;
|
||||
if (data.size() <= 12) {
|
||||
memcpy(ret.data1.data(), data.data(), data.size());
|
||||
} else {
|
||||
memcpy(ret.data1.data(), data.data(), 12);
|
||||
memcpy(ret.data2.data(), data.data() + 12, data.size() - 12);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,38 @@ struct ItemMagStats {
|
||||
};
|
||||
|
||||
struct ItemData { // 0x14 bytes
|
||||
// QUICK ITEM FORMAT REFERENCE
|
||||
// data1/0 data1/4 data1/8 data2
|
||||
// Weapon: 00ZZZZGG SS00AABB AABBAABB 00000000
|
||||
// Armor: 0101ZZ00 FFTTDDDD EEEE0000 00000000
|
||||
// Shield: 0102ZZ00 FFTTDDDD EEEE0000 00000000
|
||||
// Unit: 0103ZZ00 FF0000RR RR000000 00000000
|
||||
// Mag: 02ZZLLWW HHHHIIII JJJJKKKK YYQQPPVV
|
||||
// Tool: 03ZZZZFF 00CC0000 00000000 00000000
|
||||
// Meseta: 04000000 00000000 00000000 MMMMMMMM
|
||||
// A = attribute type (for S-ranks, custom name)
|
||||
// B = attribute amount (for S-ranks, custom name)
|
||||
// C = stack size (for tools)
|
||||
// D = DEF bonus
|
||||
// E = EVP bonus
|
||||
// F = flags (40=present; for tools, unused if item is stackable)
|
||||
// G = weapon grind
|
||||
// H = mag DEF
|
||||
// I = mag POW
|
||||
// J = mag DEX
|
||||
// K = mag MIND
|
||||
// L = mag level
|
||||
// M = meseta amount
|
||||
// P = mag flags (40=present, 04=has left pb, 02=has right pb, 01=has center pb)
|
||||
// Q = mag IQ
|
||||
// R = unit modifier (little-endian)
|
||||
// S = weapon flags (80=unidentified, 40=present) and special (low 6 bits)
|
||||
// T = slot count
|
||||
// V = mag color
|
||||
// W = photon blasts
|
||||
// Y = mag synchro
|
||||
// Z = item ID
|
||||
|
||||
union {
|
||||
parray<uint8_t, 12> data1;
|
||||
parray<le_uint16_t, 6> data1w;
|
||||
@@ -60,6 +92,7 @@ struct ItemData { // 0x14 bytes
|
||||
} __attribute__((packed));
|
||||
|
||||
ItemData();
|
||||
explicit ItemData(const std::string& orig_description);
|
||||
ItemData(const ItemData& other);
|
||||
ItemData& operator=(const ItemData& other);
|
||||
|
||||
@@ -72,6 +105,9 @@ struct ItemData { // 0x14 bytes
|
||||
std::string name(bool include_color_codes) const;
|
||||
uint32_t primary_identifier() const;
|
||||
|
||||
bool is_wrapped() const;
|
||||
void unwrap();
|
||||
|
||||
bool is_stackable() const;
|
||||
size_t stack_size() const;
|
||||
size_t max_stack_size() const;
|
||||
@@ -104,3 +140,5 @@ struct ItemData { // 0x14 bytes
|
||||
|
||||
static bool compare_for_sort(const ItemData& a, const ItemData& b);
|
||||
} __attribute__((packed));
|
||||
|
||||
ItemData item_for_string(const std::string& desc);
|
||||
|
||||
+3
-8
@@ -74,14 +74,9 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
|
||||
}
|
||||
armor.data.data1[5]++;
|
||||
|
||||
} else if ((item.data.data1[0] == 0x02) && (item.data.data2[2] & 0x40)) {
|
||||
// Unwrap mag present
|
||||
item.data.data2[2] &= 0xBF;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if ((item.data.data1[0] != 0x02) && (item.data.data1[4] & 0x40)) {
|
||||
// Unwrap non-mag present
|
||||
item.data.data1[4] &= 0xBF;
|
||||
} else if (item.data.is_wrapped()) {
|
||||
// Unwrap present
|
||||
item.data.unwrap();
|
||||
should_delete_item = false;
|
||||
|
||||
} else if (item_identifier == 0x003300) {
|
||||
|
||||
+16
@@ -225,6 +225,7 @@ enum class Behavior {
|
||||
FORMAT_ITEMRT_REL,
|
||||
SHOW_EP3_DATA,
|
||||
DESCRIBE_ITEM,
|
||||
ENCODE_ITEM,
|
||||
PARSE_OBJECT_GRAPH,
|
||||
REPLAY_LOG,
|
||||
CAT_CLIENT,
|
||||
@@ -255,6 +256,7 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML) ||
|
||||
(b == Behavior::DESCRIBE_ITEM) ||
|
||||
(b == Behavior::ENCODE_ITEM) ||
|
||||
(b == Behavior::PARSE_OBJECT_GRAPH) ||
|
||||
(b == Behavior::REPLAY_LOG) ||
|
||||
(b == Behavior::CAT_CLIENT) ||
|
||||
@@ -435,6 +437,8 @@ int main(int argc, char** argv) {
|
||||
behavior = Behavior::SHOW_EP3_DATA;
|
||||
} else if (!strcmp(argv[x], "describe-item")) {
|
||||
behavior = Behavior::DESCRIBE_ITEM;
|
||||
} else if (!strcmp(argv[x], "encode-item")) {
|
||||
behavior = Behavior::ENCODE_ITEM;
|
||||
} else if (!strcmp(argv[x], "parse-object-graph")) {
|
||||
behavior = Behavior::PARSE_OBJECT_GRAPH;
|
||||
} else if (!strcmp(argv[x], "replay-log")) {
|
||||
@@ -1130,6 +1134,18 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::ENCODE_ITEM: {
|
||||
ItemData item(input_filename);
|
||||
string desc = item.name(false);
|
||||
log_info("Data: %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
|
||||
item.data1[0], item.data1[1], item.data1[2], item.data1[3],
|
||||
item.data1[4], item.data1[5], item.data1[6], item.data1[7],
|
||||
item.data1[8], item.data1[9], item.data1[10], item.data1[11],
|
||||
item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
|
||||
log_info("Description: %s", desc.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::SHOW_EP3_DATA: {
|
||||
config_log.info("Collecting Episode 3 data");
|
||||
Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT);
|
||||
|
||||
+1
-14
@@ -761,22 +761,9 @@ Proxy session commands:\n\
|
||||
throw runtime_error("proxy session is not game leader");
|
||||
}
|
||||
|
||||
string data = parse_data_string(command_args, nullptr, ParseDataFlags::ALLOW_FILES);
|
||||
if (data.size() < 2) {
|
||||
throw runtime_error("data too short");
|
||||
}
|
||||
if (data.size() > 16) {
|
||||
throw runtime_error("data too long");
|
||||
}
|
||||
|
||||
PlayerInventoryItem item;
|
||||
item.data = item_for_string(command_args);
|
||||
item.data.id = random_object<uint32_t>();
|
||||
if (data.size() <= 12) {
|
||||
memcpy(item.data.data1.data(), data.data(), data.size());
|
||||
} else {
|
||||
memcpy(item.data.data1.data(), data.data(), 12);
|
||||
memcpy(item.data.data2.data(), data.data() + 12, data.size() - 12);
|
||||
}
|
||||
|
||||
if (command_name == "set-next-item") {
|
||||
session->next_drop_item = item;
|
||||
|
||||
@@ -545,3 +545,106 @@ uint8_t technique_for_name(const string& name) {
|
||||
uint8_t technique_for_name(const u16string& name) {
|
||||
return technique_for_name(encode_sjis(name));
|
||||
}
|
||||
|
||||
const vector<const char*> name_for_mag_color({
|
||||
/* 00 */ "red",
|
||||
/* 01 */ "blue",
|
||||
/* 02 */ "yellow",
|
||||
/* 03 */ "green",
|
||||
/* 04 */ "purple",
|
||||
/* 05 */ "black",
|
||||
/* 06 */ "white",
|
||||
/* 07 */ "cyan",
|
||||
/* 08 */ "brown",
|
||||
/* 09 */ "orange",
|
||||
/* 0A */ "light-blue",
|
||||
/* 0B */ "olive",
|
||||
/* 0C */ "light-cyan",
|
||||
/* 0D */ "dark-purple",
|
||||
/* 0E */ "grey",
|
||||
/* 0F */ "light-grey",
|
||||
/* 10 */ "pink",
|
||||
/* 11 */ "dark-cyan",
|
||||
/* 12 */ "costume",
|
||||
});
|
||||
|
||||
const unordered_map<string, uint8_t> mag_color_for_name({
|
||||
{"red", 0x00},
|
||||
{"blue", 0x01},
|
||||
{"yellow", 0x02},
|
||||
{"green", 0x03},
|
||||
{"purple", 0x04},
|
||||
{"black", 0x05},
|
||||
{"white", 0x06},
|
||||
{"cyan", 0x07},
|
||||
{"brown", 0x08},
|
||||
{"orange", 0x09},
|
||||
{"light-blue", 0x0A},
|
||||
{"olive", 0x0B},
|
||||
{"light-cyan", 0x0C},
|
||||
{"dark-purple", 0x0D},
|
||||
{"grey", 0x0E},
|
||||
{"light-grey", 0x0F},
|
||||
{"pink", 0x10},
|
||||
{"dark-cyan", 0x11},
|
||||
{"costume-color", 0x12},
|
||||
});
|
||||
|
||||
uint8_t drop_area_for_name(const std::string& name) {
|
||||
static const unordered_map<string, uint8_t> areas({
|
||||
{"forest1", 0},
|
||||
{"forest2", 1},
|
||||
{"dragon", 2},
|
||||
{"caves1", 2},
|
||||
{"cave1", 2},
|
||||
{"caves2", 3},
|
||||
{"cave2", 3},
|
||||
{"caves3", 4},
|
||||
{"cave3", 4},
|
||||
{"derolle", 5},
|
||||
{"mines1", 5},
|
||||
{"mine1", 5},
|
||||
{"mines2", 6},
|
||||
{"mine2", 6},
|
||||
{"volopt", 7},
|
||||
{"ruins1", 7},
|
||||
{"ruin1", 7},
|
||||
{"ruins2", 8},
|
||||
{"ruin2", 8},
|
||||
{"ruins3", 9},
|
||||
{"ruin3", 9},
|
||||
{"darkfalz", 9},
|
||||
|
||||
{"vrtemplealpha", 0},
|
||||
{"vrtemplebeta", 1},
|
||||
{"barbaray", 2},
|
||||
{"vrspaceshipalpha", 2},
|
||||
{"vrspaceshipbeta", 3},
|
||||
{"goldragon", 5},
|
||||
{"centralcontrolarea", 4},
|
||||
{"cca", 4},
|
||||
{"jungleareanorth", 5},
|
||||
{"junglenorth", 5},
|
||||
{"jungleareaeast", 5},
|
||||
{"jungleeast", 5},
|
||||
{"mountain", 6},
|
||||
{"seaside", 7},
|
||||
{"galgryphon", 8},
|
||||
{"seabedupper", 8},
|
||||
{"seabedlower", 9},
|
||||
{"olgaflow", 9},
|
||||
{"seasidenight", 7},
|
||||
{"tower", 9},
|
||||
|
||||
{"cratereast", 2},
|
||||
{"craterwest", 3},
|
||||
{"cratersouth", 4},
|
||||
{"craternorth", 5},
|
||||
{"craterinterior", 6},
|
||||
{"subdesert1", 7},
|
||||
{"subdesert2", 8},
|
||||
{"subdesert3", 9},
|
||||
{"saintmillion", 9},
|
||||
});
|
||||
return areas.at(tolower(name));
|
||||
}
|
||||
|
||||
@@ -74,3 +74,8 @@ const char* name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
char char_for_language_code(uint8_t language);
|
||||
|
||||
extern const std::vector<const char*> name_for_mag_color;
|
||||
extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
|
||||
|
||||
uint8_t drop_area_for_name(const std::string& name);
|
||||
|
||||
Reference in New Issue
Block a user