sql: datos de ítems del cliente 3.4.3 extraídos de los DB2 (es/en)
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>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user