4ba9c42829
En 3.4.3 `item_template` ya no existe: los datos del juego viven en ficheros
DB2 del cliente, y aquí acore_world está vacío. Se extraen a `home_item_data`
(44873 ítems): nombre es/en, calidad, nivel de objeto, tipo de inventario y
displayId.
No hay ninguna librería en PyPI que lea WDC4 (wdbx, dbc-extract3, wow-db2 no
existen), así que el lector va incluido en sql/db2/:
- `wdc_db2.py`: WDC3/WDC4 con registros fijos (bitpacked, common y pallet).
- `itemsparse.py`: ItemSparse es una tabla *sparse*, que es otro mundo: los
locstring van inline y terminados en \0, así que desplazan todo lo que va
detrás y los offsets de field_storage_info no valen. Hay que recorrer los
campos en orden, y el fichero no dice cuáles son strings: eso sale de las
definiciones oficiales de WoWDBDefs (ItemSparse.dbd, incluido), casando el
layout_hash del propio fichero (D532973D = 3.4.3).
- `db2_to_sql.py`: genera sql/item_data.sql. Regenerable y multi-locale: basta
con dejar el ItemSparse.db2 de cada idioma en /root/<locale>/.
Verificado: los 3068 ítems de la tienda están cubiertos; 2949 nombres (96%)
coinciden literalmente con el catálogo antiguo y los 119 restantes son
revisiones de traducción de Blizzard, no fallos del parser ("Bordón de Luz" ->
"Batuta de Luz"). El ítem 45327 sale q4 / ilvl 238 / "Siren's Cry", idéntico a
lo que devuelve wowhead. La distribución de calidad es realista (53
legendarios, 23 artefactos).
Ojo: la tabla está cargada pero TODAVÍA no la usa ningún código de la web.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
121 lines
4.9 KiB
Python
121 lines
4.9 KiB
Python
#!/usr/bin/env python
|
|
"""Genera web-next/sql/item_data.sql a partir de los DB2 del cliente 3.4.3.
|
|
|
|
USO: /root/NovaWoW/.venv/bin/python /root/db2_to_sql.py
|
|
(el python del sistema no tiene pip/venv; usa SIEMPRE el del proyecto)
|
|
|
|
En 3.4.3 no existe `item_template`: los datos van en DB2 del cliente.
|
|
|
|
Locales: los nombres salen de <LOCALE>/ItemSparse.db2. Basta con dejar el
|
|
ItemSparse.db2 de cada idioma en /root/<locale>/ — el resto de tablas
|
|
(ItemAppearance, ItemModifiedAppearance) NO dependen del idioma y se leen del
|
|
primer locale que exista. Si falta un locale, su columna queda a NULL.
|
|
|
|
Ficheros que necesita:
|
|
/root/esES/ItemSparse.db2, ItemAppearance.db2, ItemModifiedAppearance.db2
|
|
/root/enUS/ItemSparse.db2 (opcional; para name_en)
|
|
/root/ItemSparse.dbd (definiciones oficiales de WoWDBDefs)
|
|
/root/wdc_db2.py, /root/itemsparse.py
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
import wdc_db2
|
|
from itemsparse import ItemSparse
|
|
|
|
DBD = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ItemSparse.dbd')
|
|
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'item_data.sql')
|
|
# (columna_sql, carpeta). Añadir aquí si aparecen más idiomas.
|
|
LOCALES = [('name_es', '/root/esES'), ('name_en', '/root/enUS')]
|
|
|
|
|
|
def load_names(folder):
|
|
path = f'{folder}/ItemSparse.db2'
|
|
if not os.path.exists(path):
|
|
print(f' (sin {path}: esa columna queda NULL)', file=sys.stderr)
|
|
return {}
|
|
t = ItemSparse(path, DBD)
|
|
return {i: r for i, r in t.rows}
|
|
|
|
|
|
def esc(s):
|
|
return s.replace('\\', '\\\\').replace("'", "''")
|
|
|
|
|
|
per_locale = {}
|
|
for col, folder in LOCALES:
|
|
print(f'{col}: leyendo {folder}', file=sys.stderr)
|
|
per_locale[col] = load_names(folder)
|
|
|
|
base_folder = next((f for _c, f in LOCALES if os.path.exists(f'{f}/ItemAppearance.db2')), None)
|
|
if not base_folder:
|
|
sys.exit('falta ItemAppearance.db2 en algún locale')
|
|
|
|
# displayId: ItemModifiedAppearance.ItemID -> ItemAppearanceID -> ItemDisplayInfoID.
|
|
# No depende del idioma. Solo lo tienen los equipables.
|
|
ia = {rid: row for rid, row in wdc_db2.DB2(f'{base_folder}/ItemAppearance.db2').rows}
|
|
display = {}
|
|
for _rid, row in wdc_db2.DB2(f'{base_folder}/ItemModifiedAppearance.db2').rows:
|
|
item_id, app_id, order = row[1], row[3], row[4]
|
|
if app_id in ia:
|
|
prev = display.get(item_id)
|
|
if prev is None or order < prev[0]:
|
|
display[item_id] = (order, ia[app_id][1])
|
|
display = {k: v[1] for k, v in display.items()}
|
|
print(f'displayId: {len(display)} ítems equipables', file=sys.stderr)
|
|
|
|
name_cols = [c for c, _f in LOCALES]
|
|
entries = sorted({e for m in per_locale.values() for e in m})
|
|
|
|
with open(OUT, 'w', encoding='utf-8') as out:
|
|
cols_sql = ',\n'.join(
|
|
f" `{c}` varchar(255) DEFAULT NULL COMMENT 'nombre en {c[-2:]}'" for c in name_cols)
|
|
out.write(f"""-- Datos de ítems del cliente 3.4.3, extraídos de los DB2. NO editar a mano:
|
|
-- regenerar con `/root/NovaWoW/.venv/bin/python /root/db2_to_sql.py`.
|
|
-- En 3.4.3 no existe `item_template` (los datos viven en DB2 del cliente).
|
|
-- Parseado con las definiciones oficiales de WoWDBDefs (layout D532973D).
|
|
-- Los nombres son los de Blizzard en 3.4.3: ~4% difieren de los del catálogo
|
|
-- antiguo de la tienda por revisiones de traducción de Blizzard.
|
|
|
|
DROP TABLE IF EXISTS `home_item_data`;
|
|
CREATE TABLE `home_item_data` (
|
|
`entry` int unsigned NOT NULL COMMENT 'item id de WoW',
|
|
{cols_sql},
|
|
`quality` tinyint unsigned NOT NULL COMMENT '0 pobre .. 6 artefacto, 7 reliquia',
|
|
`item_level` smallint unsigned NOT NULL,
|
|
`inventory_type` tinyint unsigned NOT NULL COMMENT '0 = no equipable',
|
|
`display_id` int unsigned DEFAULT NULL COMMENT 'ItemDisplayInfoID; NULL si no es equipable',
|
|
PRIMARY KEY (`entry`),
|
|
KEY `idx_name_es` (`name_es`(64)),
|
|
KEY `idx_quality` (`quality`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
""")
|
|
|
|
rows = []
|
|
for entry in entries:
|
|
names = []
|
|
any_name = False
|
|
for c in name_cols:
|
|
r = per_locale[c].get(entry)
|
|
n = (r or {}).get('Display_lang') or ''
|
|
names.append(f"'{esc(n)}'" if n else 'NULL')
|
|
any_name = any_name or bool(n)
|
|
if not any_name:
|
|
continue # ítems internos sin nombre en ningún idioma
|
|
ref = next((per_locale[c][entry] for c in name_cols if entry in per_locale[c]), {})
|
|
rows.append('(%d,%s,%d,%d,%d,%s)' % (
|
|
entry, ','.join(names), ref.get('OverallQualityID', 0) or 0,
|
|
ref.get('ItemLevel', 0) or 0, ref.get('InventoryType', 0) or 0,
|
|
display.get(entry, 'NULL')))
|
|
|
|
cols = ','.join(f'`{c}`' for c in name_cols)
|
|
for i in range(0, len(rows), 500):
|
|
out.write(f'INSERT INTO `home_item_data` (`entry`,{cols},`quality`,`item_level`,`inventory_type`,`display_id`) VALUES\n')
|
|
out.write(',\n'.join(rows[i:i + 500]))
|
|
out.write(';\n')
|
|
|
|
print(f'escritas {len(rows)} filas en {OUT}', file=sys.stderr)
|