Ring 70058: usable from level 1, tooltip says unlimited and no cooldown
The item_proto limit (LEVEL 30) kept new characters from using the warp ring. ring-warp/install.sh now clears it in share/conf/item_proto.txt and the player.item_proto mirror. The client proto gets the same limit through a new item_limits_server.tsv fed to locale_proto_patch, and the zh itemdesc overlay replaces the "50 uses" text. Verified on 203: a fresh level-1 character opened the warp menu four times in a row (one real warp in between), ring still in the bag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
01cc252a1d
commit
f345bd5d3b
@@ -1,5 +1,8 @@
|
||||
// Offline CP936 name overlay for the 40250 item/mob proto format.
|
||||
// Runtime loading remains the original 40250 path and binary format.
|
||||
// An optional item limits table (vnum, limit type 0, value 0, limit type 1, value 1) mirrors the few
|
||||
// item_proto.txt limit changes made on our server (tools/server/*), so the tooltip states what the
|
||||
// server enforces; only aLimits of the listed items may change.
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "GameLib/ItemData.h"
|
||||
#include "UserInterface/StdAfx.h"
|
||||
@@ -7,6 +10,7 @@
|
||||
#include "EterBase/lzo.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
@@ -67,9 +71,53 @@ std::map<DWORD, std::string> read_names(const char* path) {
|
||||
return names;
|
||||
}
|
||||
|
||||
using Limits = std::array<CItemData::TItemLimit, CItemData::ITEM_LIMIT_MAX_NUM>;
|
||||
|
||||
std::map<DWORD, Limits> read_limits(const char* path) {
|
||||
std::map<DWORD, Limits> limits;
|
||||
if (!path) return limits;
|
||||
std::ifstream stream(path);
|
||||
if (!stream) throw std::runtime_error(std::string("cannot read ") + path);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
unsigned long vnum, type0, type1;
|
||||
long value0, value1;
|
||||
if (std::sscanf(line.c_str(), "%lu %lu %ld %lu %ld", &vnum, &type0, &value0, &type1, &value1) != 5)
|
||||
throw std::runtime_error("bad item limits row: " + line);
|
||||
Limits& row = limits[static_cast<DWORD>(vnum)];
|
||||
row[0].bType = static_cast<BYTE>(type0);
|
||||
row[0].lValue = value0;
|
||||
row[1].bType = static_cast<BYTE>(type1);
|
||||
row[1].lValue = value1;
|
||||
}
|
||||
return limits;
|
||||
}
|
||||
|
||||
template <class Record>
|
||||
void apply_limits(Record&, const std::map<DWORD, Limits>&, std::size_t&) {}
|
||||
|
||||
template <>
|
||||
void apply_limits<Item>(Item& record, const std::map<DWORD, Limits>& limits, std::size_t& changed) {
|
||||
const auto found = limits.find(record.dwVnum);
|
||||
if (found == limits.end()) return;
|
||||
std::memcpy(record.aLimits, found->second.data(), sizeof(record.aLimits));
|
||||
++changed;
|
||||
}
|
||||
|
||||
template <class Record>
|
||||
bool limits_field(std::size_t, DWORD, const std::map<DWORD, Limits>&) { return false; }
|
||||
|
||||
template <>
|
||||
bool limits_field<Item>(std::size_t offset, DWORD vnum, const std::map<DWORD, Limits>& limits) {
|
||||
return limits.count(vnum) && offset >= offsetof(Item, aLimits) &&
|
||||
offset < offsetof(Item, aLimits) + sizeof(Item::aLimits);
|
||||
}
|
||||
|
||||
template <class Record>
|
||||
std::vector<BYTE> patch(const char* source, const char* names_path,
|
||||
std::array<DWORD, 4> key, const char* magic, bool versioned) {
|
||||
std::array<DWORD, 4> key, const char* magic, bool versioned,
|
||||
const std::map<DWORD, Limits>& limits = {}) {
|
||||
const auto input = read_file(source);
|
||||
const std::size_t count_offset = versioned ? 12 : 4;
|
||||
const std::size_t payload_offset = versioned ? 20 : 12;
|
||||
@@ -89,10 +137,12 @@ std::vector<BYTE> patch(const char* source, const char* names_path,
|
||||
std::vector<BYTE> plain(decoded.GetBuffer(), decoded.GetBuffer() + decoded.GetSize());
|
||||
const auto original = plain;
|
||||
const auto names = read_names(names_path);
|
||||
std::size_t translated = 0, missing = 0, too_long = 0;
|
||||
std::size_t translated = 0, missing = 0, too_long = 0, limited = 0;
|
||||
for (DWORD i = 0; i < count; ++i) {
|
||||
Record record;
|
||||
std::memcpy(&record, plain.data() + std::size_t(i) * sizeof(Record), sizeof(Record));
|
||||
apply_limits(record, limits, limited);
|
||||
std::memcpy(plain.data() + std::size_t(i) * sizeof(Record), &record, sizeof(Record));
|
||||
const auto found = names.find(record.dwVnum);
|
||||
if (found == names.end()) { ++missing; continue; }
|
||||
if (found->second.size() >= sizeof(record.szLocaleName)) { ++too_long; continue; }
|
||||
@@ -101,13 +151,16 @@ std::vector<BYTE> patch(const char* source, const char* names_path,
|
||||
std::memcpy(plain.data() + std::size_t(i) * sizeof(Record), &record, sizeof(Record));
|
||||
++translated;
|
||||
}
|
||||
// No item stats, VNUM, canonical name, or mob gameplay fields may change.
|
||||
// No item stats, VNUM, canonical name, or mob gameplay fields may change (but the listed item limits).
|
||||
for (DWORD i = 0; i < count; ++i) {
|
||||
const std::size_t start = std::size_t(i) * sizeof(Record);
|
||||
const std::size_t name_start = start + offsetof(Record, szLocaleName);
|
||||
const std::size_t name_end = name_start + sizeof(Record::szLocaleName);
|
||||
DWORD vnum;
|
||||
std::memcpy(&vnum, plain.data() + start + offsetof(Record, dwVnum), sizeof(vnum));
|
||||
for (std::size_t byte = start; byte < start + sizeof(Record); ++byte) {
|
||||
if ((byte < name_start || byte >= name_end) && plain[byte] != original[byte])
|
||||
if ((byte < name_start || byte >= name_end) && !limits_field<Record>(byte - start, vnum, limits) &&
|
||||
plain[byte] != original[byte])
|
||||
throw std::runtime_error("non-name proto data changed");
|
||||
}
|
||||
}
|
||||
@@ -124,19 +177,22 @@ std::vector<BYTE> patch(const char* source, const char* names_path,
|
||||
std::memcmp(verified.GetBuffer(), plain.data(), plain.size()) != 0)
|
||||
throw std::runtime_error("encoded proto failed round-trip verification");
|
||||
std::cout << magic << " count=" << count << " translated=" << translated
|
||||
<< " missing=" << missing << " too_long=" << too_long << '\n';
|
||||
<< " missing=" << missing << " too_long=" << too_long;
|
||||
if (limited) std::cout << " limits=" << limited;
|
||||
std::cout << '\n';
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 7) {
|
||||
std::cerr << "usage: locale_proto_patch item_proto mob_proto item_names_gbk mob_names_gbk out_item out_mob\n";
|
||||
if (argc != 7 && argc != 8) {
|
||||
std::cerr << "usage: locale_proto_patch item_proto mob_proto item_names_gbk mob_names_gbk out_item out_mob"
|
||||
" [item_limits]\n";
|
||||
return 2;
|
||||
}
|
||||
try {
|
||||
static CLZO lzo;
|
||||
const auto item = patch<Item>(argv[1], argv[3], item_key, "MIPX", true);
|
||||
const auto item = patch<Item>(argv[1], argv[3], item_key, "MIPX", true, read_limits(argc == 8 ? argv[7] : nullptr));
|
||||
const auto mob = patch<Mob>(argv[2], argv[4], mob_key, "MMPT", false);
|
||||
write_file(argv[5], item);
|
||||
write_file(argv[6], mob);
|
||||
|
||||
Reference in New Issue
Block a user