diff --git a/api/schema.sql b/api/schema.sql index c5103206..82455437 100644 --- a/api/schema.sql +++ b/api/schema.sql @@ -625,5 +625,28 @@ $migracion$; CREATE INDEX IF NOT EXISTS idx_game_map_tile_overrides_map ON game_map_tile_overrides(map_num, status); + +-- Objetos y NPCs colocados en un tile respecto del mapa original. +-- +-- Un tile tiene como maximo un objeto y un NPC, igual que el modelo del juego +-- (tile.objInfo y tile.npcIndex). Viven en una tabla propia y no como capas de +-- grafico porque no pertenecen a ninguna capa: pintar el piso no deberia +-- borrar el objeto que hay encima, y viceversa. +-- +-- kind 'obj' referencia game_objects y kind 'npc' referencia game_npcs. +CREATE TABLE IF NOT EXISTS game_map_tile_entities ( + map_num INTEGER NOT NULL CHECK (map_num > 0), + x INTEGER NOT NULL CHECK (x > 0), + y INTEGER NOT NULL CHECK (y > 0), + kind TEXT NOT NULL CHECK (kind IN ('obj', 'npc')), + entity_id INTEGER NOT NULL CHECK (entity_id > 0), + status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published')), + updated_by_account_id UUID REFERENCES accounts(id) ON DELETE SET NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (map_num, x, y, kind, status) +); + +CREATE INDEX IF NOT EXISTS idx_game_map_tile_entities_map + ON game_map_tile_entities(map_num, status); CREATE INDEX IF NOT EXISTS idx_game_uploaded_graphics_created_at ON game_uploaded_graphics(created_at DESC); diff --git a/api/src/jsons/npcs.json b/api/src/jsons/npcs.json new file mode 100644 index 00000000..0918a2f8 --- /dev/null +++ b/api/src/jsons/npcs.json @@ -0,0 +1,13780 @@ +{ + "1": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! Bienvenido a nuestra humilde aldea.", + "drop": [], + "gold": 0, + "name": "Aldeano", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 18, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "2": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Gallo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 5, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "3": { + "hp": 35, + "def": 0, + "exp": 15, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Cerdo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 35, + "idBody": 6, + "idHead": 0, + "maxHit": 2, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 15, + "magicResistance": 0 + }, + "4": { + "hp": 65, + "def": 2, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Vaca", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 65, + "idBody": 7, + "idHead": 0, + "maxHit": 5, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 25, + "poderEvasion": 15, + "magicResistance": 0 + }, + "5": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola, hijo mío! Soy el Sacerdote de estas tierras. Si quieres que te resucite escribe /RESUCITAR, o haz doble click sobre mí.", + "drop": [], + "gold": 0, + "name": "Sacerdote", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 49, + "idHead": 3, + "maxHit": 0, + "minHit": 0, + "npcType": 1, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "6": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Daré cárcel a todos los criminales!", + "drop": [], + "gold": 0, + "name": "Guardia Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 15, + "idHead": 0, + "maxHit": 250, + "minHit": 115, + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "7": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido, rata de alcantarilla, soy el Herrero de Ullathorpe...tengo algunas armas que pueden ayudarte en tus viajes.", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1, + "item": 15 + }, + { + "cant": 1, + "item": 619 + }, + { + "cant": 1, + "item": 165 + }, + { + "cant": 1, + "item": 365 + }, + { + "cant": 1, + "item": 366 + }, + { + "cant": 1, + "item": 367 + }, + { + "cant": 1, + "item": 164 + }, + { + "cant": 1, + "item": 9 + }, + { + "cant": 1, + "item": 398 + }, + { + "cant": 1, + "item": 2 + }, + { + "cant": 1, + "item": 753 + }, + { + "cant": 1, + "item": 3 + }, + { + "cant": 1, + "item": 19 + }, + { + "cant": 1, + "item": 401 + }, + { + "cant": 1, + "item": 399 + }, + { + "cant": 1, + "item": 124 + }, + { + "cant": 1, + "item": 990 + }, + { + "cant": 1, + "item": 123 + }, + { + "cant": 1, + "item": 159 + }, + { + "cant": 1, + "item": 129 + }, + { + "cant": 1, + "item": 126 + }, + { + "cant": 1, + "item": 403 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "8": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hooooola jovencito, tengo las mejores provisiones de Ullathorpe!!", + "drop": [], + "gold": 0, + "name": "Thorkis", + "objs": [ + { + "cant": 10000, + "item": 1 + }, + { + "cant": 1000, + "item": 158 + }, + { + "cant": 1000, + "item": 22 + }, + { + "cant": 1000, + "item": 43 + }, + { + "cant": 1000, + "item": 160 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 504, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola, pequeño aventurero, tengo aquí unas mercaderías para saciar tu hambre y sed!.", + "drop": [], + "gold": 0, + "name": "Ralph", + "objs": [ + { + "cant": 1000, + "item": 1 + }, + { + "cant": 1000, + "item": 158 + }, + { + "cant": 1000, + "item": 43 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 504, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "10": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡¿Hola, os gustaria oír una hermosa melodía?!.", + "drop": [], + "gold": 0, + "name": "Bardo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 17, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "11": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! Bienvenido a nuestra humilde aldea.", + "drop": [], + "gold": 0, + "name": "Aldeana", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 19, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "12": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! Soy el Sastre de Ullathorpe, Bienvenido!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1, + "item": 31 + }, + { + "cant": 1, + "item": 32 + }, + { + "cant": 1, + "item": 35 + }, + { + "cant": 1, + "item": 641 + }, + { + "cant": 1, + "item": 501 + }, + { + "cant": 1, + "item": 135 + }, + { + "cant": 1, + "item": 238 + }, + { + "cant": 1, + "item": 239 + }, + { + "cant": 1, + "item": 381 + }, + { + "cant": 1, + "item": 357 + }, + { + "cant": 1, + "item": 986 + }, + { + "cant": 1, + "item": 519 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 50, + "idHead": 9, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "13": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Ullathorpe ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "14": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, tengo las mejores Pociones de la Alquimia.", + "drop": [], + "gold": 0, + "name": "Lilith", + "objs": [ + { + "cant": 1000, + "item": 36 + }, + { + "cant": 1000, + "item": 37 + }, + { + "cant": 1000, + "item": 38 + }, + { + "cant": 1000, + "item": 39 + }, + { + "cant": 1000, + "item": 166 + }, + { + "cant": 1000, + "item": 645 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 200, + "idHead": 77, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "15": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Qué tal amigo! Entra, pues tengo las mejores provisiones de Nix!", + "drop": [], + "gold": 0, + "name": "Thorkis", + "objs": [ + { + "cant": 1000, + "item": 139 + }, + { + "cant": 1000, + "item": 26 + }, + { + "cant": 1000, + "item": 43 + }, + { + "cant": 1000, + "item": 160 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 504, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "16": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Centinela", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 540, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "17": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Conejo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 11, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "18": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Pavo Real", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 20, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "19": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenida Viajera! Tengo hermosas vestimentas para ofrecerte...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 80, + "idHead": 70, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "20": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Acérquese! Tengo pequeñas prendas que serán de su agrado...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1, + "item": 240 + }, + { + "cant": 1, + "item": 486 + }, + { + "cant": 1, + "item": 507 + }, + { + "cant": 1, + "item": 502 + }, + { + "cant": 1, + "item": 707 + }, + { + "cant": 1, + "item": 930 + }, + { + "cant": 1, + "item": 671 + }, + { + "cant": 1, + "item": 972 + }, + { + "cant": 1, + "item": 524 + }, + { + "cant": 1, + "item": 525 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 180, + "idHead": 303, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "21": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! Soy el Sastre de Nix, Bienvenido!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 31 + }, + { + "cant": 1000, + "item": 32 + }, + { + "cant": 1000, + "item": 35 + }, + { + "cant": 1000, + "item": 45 + }, + { + "cant": 1000, + "item": 80 + }, + { + "cant": 1000, + "item": 501 + }, + { + "cant": 1000, + "item": 135 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 50, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "22": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenida Viajera! Entra a mi tienda, ¡Tengo las mejores prendas de Nix!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 80, + "idHead": 71, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "23": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Acérquese! Tengo pequeñas prendas que serán de su agrado...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 240 + }, + { + "cant": 1000, + "item": 486 + }, + { + "cant": 1000, + "item": 507 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 707 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 180, + "idHead": 404, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "24": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, bienvenido a la cadena de finanzas Goliath. Somos la cadena más grande en Argentum, contamos con bancos en las ciudades más importantes.", + "drop": [], + "gold": 0, + "name": "Banquero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 201, + "idHead": 503, + "maxHit": 0, + "minHit": 0, + "npcType": 4, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 603, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "25": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenido a la Sastrería de Banderbill! ¿En qué lo puedo ayudar?", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 501 + }, + { + "cant": 1000, + "item": 135 + }, + { + "cant": 1000, + "item": 238 + }, + { + "cant": 1000, + "item": 239 + }, + { + "cant": 1000, + "item": 381 + }, + { + "cant": 1000, + "item": 196 + }, + { + "cant": 1000, + "item": 357 + }, + { + "cant": 1000, + "item": 986 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 56, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "26": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Adelante hermosa niña! En mi tienda encontrarás vestidos hechos para tí!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 121, + "idHead": 173, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "27": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven y mira estas prendas que he confeccionado para nuestra raza!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 486 + }, + { + "cant": 1000, + "item": 507 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 707 + }, + { + "cant": 1000, + "item": 671 + }, + { + "cant": 1000, + "item": 670 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 159, + "idHead": 305, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "28": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Entrad Valiente Viajero! En la Sastrería de Arkhein están las mejores ropas del continente.", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 45 + }, + { + "cant": 1000, + "item": 80 + }, + { + "cant": 1000, + "item": 641 + }, + { + "cant": 1000, + "item": 501 + }, + { + "cant": 1000, + "item": 135 + }, + { + "cant": 1000, + "item": 238 + }, + { + "cant": 1000, + "item": 239 + }, + { + "cant": 1000, + "item": 381 + }, + { + "cant": 1000, + "item": 196 + }, + { + "cant": 1000, + "item": 357 + }, + { + "cant": 1000, + "item": 873 + }, + { + "cant": 1000, + "item": 986 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 56, + "idHead": 15, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "29": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven y mira estas vestimentas, seguro te encantarán!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 80, + "idHead": 173, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "30": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Aquí encontrarás las mejores prendas para enanos ¡Y a muy buen precio!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 486 + }, + { + "cant": 1000, + "item": 507 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 707 + }, + { + "cant": 1000, + "item": 671 + }, + { + "cant": 1000, + "item": 670 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 159, + "idHead": 404, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "31": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven aquí! Te puedo ofrecer las mejores ropas de la isla...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 31 + }, + { + "cant": 1000, + "item": 32 + }, + { + "cant": 1000, + "item": 35 + }, + { + "cant": 1000, + "item": 135 + }, + { + "cant": 1000, + "item": 238 + }, + { + "cant": 1000, + "item": 239 + }, + { + "cant": 1000, + "item": 381 + }, + { + "cant": 1000, + "item": 196 + }, + { + "cant": 1000, + "item": 986 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "32": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven y mira estas vestimentas, seguro te encantarán!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 118, + "idHead": 75, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "33": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola muchacho!, si deseas vender o comprar madera escribe /COMERCIAR.", + "drop": [], + "gold": 0, + "name": "Leñador", + "objs": [ + { + "cant": 1000, + "item": 127 + }, + { + "cant": 1000, + "item": 1005 + }, + { + "cant": 1000, + "item": 198 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 1, + "idHead": 4, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "34": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Usted sabe que no hay persona noble o respetable en estas tierras que no tenga su propia vivienda. ¡acérquese y sea dueño de una de las exclusivas casas de Ullathorpe. ¡Será admirado por toda la población!", + "drop": [], + "gold": 0, + "name": "Cullighan", + "objs": [ + { + "cant": 0, + "item": 54 + }, + { + "cant": 0, + "item": 65 + }, + { + "cant": 0, + "item": 69 + }, + { + "cant": 0, + "item": 73 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 240, + "idHead": 33, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "35": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¿Ha descubierto que la ciudad de Nix es la más hermosa de todo Argentum, verdad? ¡Pues qué mejor que ser un terrateniente de este paraíso! ¡Acércate que haremos negocios!", + "drop": [], + "gold": 0, + "name": "Moraday", + "objs": [ + { + "cant": 0, + "item": 87 + }, + { + "cant": 0, + "item": 91 + }, + { + "cant": 0, + "item": 95 + }, + { + "cant": 0, + "item": 99 + }, + { + "cant": 1, + "item": 103 + }, + { + "cant": 1, + "item": 107 + }, + { + "cant": 0, + "item": 111 + }, + { + "cant": 0, + "item": 115 + }, + { + "cant": 0, + "item": 119 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 239, + "idHead": 35, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "36": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "orcoszhh zson todsh orcshhh..hip..", + "drop": [], + "gold": 0, + "name": "Astor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 285, + "idHead": 2140, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "37": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido, rata de alcantarilla, soy el Herrero de Nix, tengo algunas armas que pueden ayudarte en tus viajes.", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 15 + }, + { + "cant": 1000, + "item": 619 + }, + { + "cant": 1000, + "item": 165 + }, + { + "cant": 1000, + "item": 3 + }, + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 398 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "38": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven, pasa, que he fabricado las más poderosas armas de Banderbill para tí!", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 164 + }, + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 19 + }, + { + "cant": 1000, + "item": 123 + }, + { + "cant": 1000, + "item": 401 + }, + { + "cant": 1000, + "item": 124 + }, + { + "cant": 1000, + "item": 478 + }, + { + "cant": 1000, + "item": 990 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "39": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! confeccionamos las mejores ropas de la zona.", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 507 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 707 + }, + { + "cant": 1000, + "item": 671 + }, + { + "cant": 1000, + "item": 670 + }, + { + "cant": 1000, + "item": 524 + }, + { + "cant": 1000, + "item": 370 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 135, + "idHead": 304, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "40": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola! Bienvenido a la Taberna de Arkhein, ¿Qué desea que le sirva?", + "drop": [], + "gold": 0, + "name": "Tolloz", + "objs": [ + { + "cant": 1000, + "item": 21 + }, + { + "cant": 1000, + "item": 23 + }, + { + "cant": 1000, + "item": 24 + }, + { + "cant": 1000, + "item": 28 + }, + { + "cant": 1000, + "item": 29 + }, + { + "cant": 1000, + "item": 26 + }, + { + "cant": 1000, + "item": 25 + }, + { + "cant": 1000, + "item": 160 + }, + { + "cant": 1000, + "item": 42 + }, + { + "cant": 1000, + "item": 161 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "41": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenido al gremio de pescadores!", + "drop": [], + "gold": 0, + "name": "Pescador", + "objs": [ + { + "cant": 1000, + "item": 138 + }, + { + "cant": 1000, + "item": 139 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 3, + "idHead": 2, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "42": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola aventurero! Si estais hambriento habeis venido al lugar apropiador, tengo todo tipo de frutas.", + "drop": [], + "gold": 0, + "name": "Antogne", + "objs": [ + { + "cant": 1000, + "item": 1 + }, + { + "cant": 1000, + "item": 158 + }, + { + "cant": 1000, + "item": 22 + }, + { + "cant": 1000, + "item": 754 + }, + { + "cant": 1000, + "item": 29 + }, + { + "cant": 1000, + "item": 43 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "43": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¿¡Estáis sediento y hambriento!?", + "drop": [], + "gold": 0, + "name": "Sorks", + "objs": [ + { + "cant": 1000, + "item": 139 + }, + { + "cant": 1000, + "item": 754 + }, + { + "cant": 1000, + "item": 43 + }, + { + "cant": 1000, + "item": 160 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "44": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola muchacho!, bienvenido a mi humilde Carpintería.", + "drop": [], + "gold": 0, + "name": "Carpintero", + "objs": [ + { + "cant": 1000, + "item": 478 + }, + { + "cant": 1000, + "item": 663 + }, + { + "cant": 1000, + "item": 479 + }, + { + "cant": 1000, + "item": 664 + }, + { + "cant": 1000, + "item": 665 + }, + { + "cant": 1000, + "item": 480 + }, + { + "cant": 1000, + "item": 551 + }, + { + "cant": 1000, + "item": 552 + }, + { + "cant": 1000, + "item": 553 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 1, + "idHead": 2, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "45": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Huye de aquí mientras puedas!", + "drop": [], + "gold": 0, + "name": "Espíritu", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 8, + "idHead": 514, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "46": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a mi pequeña tienda de Ullathorpe... Tengo algunos hechizos que podrían interesarte.", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 363 + }, + { + "cant": 1000, + "item": 199 + }, + { + "cant": 1000, + "item": 200 + }, + { + "cant": 1000, + "item": 197 + }, + { + "cant": 1000, + "item": 658 + }, + { + "cant": 1000, + "item": 659 + }, + { + "cant": 1000, + "item": 540 + }, + { + "cant": 1000, + "item": 208 + }, + { + "cant": 1000, + "item": 469 + }, + { + "cant": 1000, + "item": 696 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "47": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a mi pequeña tienda de Nix... Tengo algunos hechizos que podrían interesarte.", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 201 + }, + { + "cant": 1000, + "item": 204 + }, + { + "cant": 1000, + "item": 364 + }, + { + "cant": 1000, + "item": 205 + }, + { + "cant": 1000, + "item": 380 + }, + { + "cant": 1000, + "item": 661 + }, + { + "cant": 1000, + "item": 383 + }, + { + "cant": 1000, + "item": 540 + }, + { + "cant": 1000, + "item": 208 + }, + { + "cant": 1000, + "item": 469 + }, + { + "cant": 1000, + "item": 696 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "48": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo los mejores utensillos para mineros de todo Argentum.", + "drop": [], + "gold": 0, + "name": "Minero", + "objs": [ + { + "cant": 1000, + "item": 187 + }, + { + "cant": 1000, + "item": 389 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 1, + "idHead": 2, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "49": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Daré cárcel a todos los criminales!", + "drop": [], + "gold": 0, + "name": "Guardia Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 15, + "idHead": 0, + "maxHit": 250, + "minHit": 115, + "npcType": 2, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "51": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a mi pequeña Tienda de Banderbill... Tengo algunos hechizos que podrían interesarte.", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 394 + }, + { + "cant": 1000, + "item": 203 + }, + { + "cant": 1000, + "item": 207 + }, + { + "cant": 1000, + "item": 657 + }, + { + "cant": 1000, + "item": 206 + }, + { + "cant": 1000, + "item": 361 + }, + { + "cant": 1000, + "item": 319 + }, + { + "cant": 1000, + "item": 531 + }, + { + "cant": 1000, + "item": 318 + }, + { + "cant": 1000, + "item": 537 + }, + { + "cant": 1000, + "item": 662 + }, + { + "cant": 1000, + "item": 540 + }, + { + "cant": 1000, + "item": 208 + }, + { + "cant": 1000, + "item": 469 + }, + { + "cant": 1000, + "item": 696 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "52": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "De lejos pude ver vuestro blazón, seguro busca una de las moradas más lujosas de todo el Argentum. En la ciudad real de Banderbill es donde deben habitar la flor y la nata de la nobleza ¡Venga y hagamos negocios!", + "drop": [], + "gold": 0, + "name": "Rantarisk", + "objs": [ + { + "cant": 0, + "item": 266 + }, + { + "cant": 0, + "item": 270 + }, + { + "cant": 0, + "item": 274 + }, + { + "cant": 0, + "item": 278 + }, + { + "cant": 0, + "item": 282 + }, + { + "cant": 0, + "item": 286 + }, + { + "cant": 0, + "item": 290 + }, + { + "cant": 0, + "item": 294 + }, + { + "cant": 0, + "item": 298 + }, + { + "cant": 1, + "item": 323 + }, + { + "cant": 1, + "item": 327 + }, + { + "cant": 1, + "item": 331 + }, + { + "cant": 0, + "item": 335 + }, + { + "cant": 0, + "item": 339 + }, + { + "cant": 0, + "item": 343 + }, + { + "cant": 0, + "item": 347 + }, + { + "cant": 0, + "item": 351 + }, + { + "cant": 0, + "item": 355 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 241, + "idHead": 32, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "53": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola, buen viajero! Dispongo de todo tipo de provisiones... ¿Qué deseas?", + "drop": [], + "gold": 0, + "name": "Roksar", + "objs": [ + { + "cant": 1000, + "item": 28 + }, + { + "cant": 1000, + "item": 29 + }, + { + "cant": 1000, + "item": 26 + }, + { + "cant": 1000, + "item": 25 + }, + { + "cant": 1000, + "item": 27 + }, + { + "cant": 1000, + "item": 42 + }, + { + "cant": 1000, + "item": 161 + }, + { + "cant": 1000, + "item": 533 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 202, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "54": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Acércate, tengo algunas armaduras que podrían protegerte.", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1, + "item": 30 + }, + { + "cant": 1, + "item": 668 + }, + { + "cant": 1, + "item": 195 + }, + { + "cant": 1, + "item": 243 + }, + { + "cant": 1, + "item": 356 + }, + { + "cant": 1, + "item": 854 + }, + { + "cant": 1, + "item": 359 + }, + { + "cant": 1, + "item": 667 + }, + { + "cant": 1, + "item": 360 + }, + { + "cant": 1, + "item": 648 + }, + { + "cant": 1, + "item": 390 + }, + { + "cant": 1, + "item": 392 + }, + { + "cant": 1, + "item": 391 + }, + { + "cant": 1, + "item": 393 + }, + { + "cant": 1, + "item": 497 + }, + { + "cant": 1, + "item": 500 + }, + { + "cant": 1, + "item": 404 + }, + { + "cant": 1, + "item": 128 + }, + { + "cant": 1, + "item": 130 + }, + { + "cant": 1, + "item": 132 + }, + { + "cant": 1, + "item": 131 + }, + { + "cant": 1, + "item": 405 + }, + { + "cant": 1, + "item": 1003 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "55": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven, pasa, que he fabricado las más poderosas armas de Arkhein para tí!", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 164 + }, + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 19 + }, + { + "cant": 1000, + "item": 123 + }, + { + "cant": 1000, + "item": 401 + }, + { + "cant": 1000, + "item": 478 + }, + { + "cant": 1000, + "item": 990 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "56": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Ven, pasa, que he fabricado, aquí en Lindos, poderosas armas para tí!", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 19 + }, + { + "cant": 1000, + "item": 123 + }, + { + "cant": 1000, + "item": 401 + }, + { + "cant": 1000, + "item": 125 + }, + { + "cant": 1000, + "item": 753 + }, + { + "cant": 1000, + "item": 404 + }, + { + "cant": 1000, + "item": 990 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "57": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Sea bienvenido a la famosa Taberna de Arghal!, Aquí podrá saciarse con la mejor comida y la mejor bebida de toda la zona.", + "drop": [], + "gold": 0, + "name": "Bhoemond", + "objs": [ + { + "cant": 1000, + "item": 21 + }, + { + "cant": 1000, + "item": 23 + }, + { + "cant": 1000, + "item": 24 + }, + { + "cant": 1000, + "item": 28 + }, + { + "cant": 1000, + "item": 29 + }, + { + "cant": 1000, + "item": 26 + }, + { + "cant": 1000, + "item": 25 + }, + { + "cant": 1000, + "item": 160 + }, + { + "cant": 1000, + "item": 42 + }, + { + "cant": 1000, + "item": 161 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 100, + "idHead": 17, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "58": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenido al gremio de pescadores!", + "drop": [], + "gold": 0, + "name": "Pescador", + "objs": [ + { + "cant": 1000, + "item": 138 + }, + { + "cant": 1000, + "item": 139 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 3, + "idHead": 101, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "59": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido, neófito a ésta, a mi pequeña escuela de Magia, ubicada en el corazón mismo de la ciudad de Arghal.", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 199 + }, + { + "cant": 1000, + "item": 200 + }, + { + "cant": 1000, + "item": 201 + }, + { + "cant": 1000, + "item": 202 + }, + { + "cant": 1000, + "item": 204 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 34, + "idHead": 20, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "60": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola amigo! Soy el Maestro de armas de la Milicia de Banderbill, si deseas que traiga una criatura sólo debes pedirmelo. (Escribe /ENTRENAR)", + "drop": [], + "gold": 0, + "name": "O'Riley de Banderbill", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 57, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 3, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "61": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a mi pequeña tienda de Arkhein... Tengo algunos hechizos que podrían interesarte.", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 395 + }, + { + "cant": 1000, + "item": 203 + }, + { + "cant": 1000, + "item": 531 + }, + { + "cant": 1000, + "item": 318 + }, + { + "cant": 1000, + "item": 537 + }, + { + "cant": 1000, + "item": 662 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "62": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Si está buscando la paz y la tranquilidad en estas tierras ha llegado al lugar indicado... adquiera una de nuestras exclusivas residencias, y cambie su vida para siempre.", + "drop": [], + "gold": 0, + "name": "Guldorf", + "objs": [ + { + "cant": 0, + "item": 420 + }, + { + "cant": 0, + "item": 424 + }, + { + "cant": 0, + "item": 428 + }, + { + "cant": 0, + "item": 432 + }, + { + "cant": 0, + "item": 436 + }, + { + "cant": 0, + "item": 440 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 234, + "idHead": 36, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "63": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Ven, acércate, que tengo para ofrecerte las mejores prendas masculinas de todo Arghal...", + "drop": [], + "gold": 0, + "name": "Steve", + "objs": [ + { + "cant": 1000, + "item": 31 + }, + { + "cant": 1000, + "item": 32 + }, + { + "cant": 1000, + "item": 35 + }, + { + "cant": 1000, + "item": 641 + }, + { + "cant": 1000, + "item": 501 + }, + { + "cant": 1000, + "item": 135 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 342, + "idHead": 22, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "64": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenida jovencita! Soy quien hace los mejores atuendos femeninos de todo Arghal, ven y prueba mi mercadería.", + "drop": [], + "gold": 0, + "name": "Virgin", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 80, + "idHead": 70, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "65": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Gallo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 5, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "66": { + "hp": 35, + "def": 0, + "exp": 15, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Cerdo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 35, + "idBody": 6, + "idHead": 0, + "maxHit": 2, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "67": { + "hp": 45, + "def": 2, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Vaca", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45, + "idBody": 7, + "idHead": 0, + "maxHit": 3, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "68": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Acérquese pequeño vistante de Arghal... Tengo prendas a su medida.", + "drop": [], + "gold": 0, + "name": "Odkon", + "objs": [ + { + "cant": 1000, + "item": 240 + }, + { + "cant": 1000, + "item": 486 + }, + { + "cant": 1000, + "item": 507 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 707 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 371, + "idHead": 404, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "69": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Ven por aquí joven, las artes mágicas superiores te están llamando...", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 530 + }, + { + "cant": 1000, + "item": 396 + }, + { + "cant": 1000, + "item": 397 + }, + { + "cant": 1000, + "item": 470 + }, + { + "cant": 1000, + "item": 471 + }, + { + "cant": 1000, + "item": 319 + }, + { + "cant": 1000, + "item": 673 + }, + { + "cant": 1000, + "item": 394 + }, + { + "cant": 1000, + "item": 395 + }, + { + "cant": 1000, + "item": 538 + }, + { + "cant": 1000, + "item": 536 + }, + { + "cant": 1000, + "item": 532 + }, + { + "cant": 1000, + "item": 542 + }, + { + "cant": 1000, + "item": 540 + }, + { + "cant": 1000, + "item": 208 + }, + { + "cant": 1000, + "item": 469 + }, + { + "cant": 1000, + "item": 696 + }, + { + "cant": 1000, + "item": 1053 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "70": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Acércate, tengo algunas armaduras que podrían protegerte.", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 30 + }, + { + "cant": 1000, + "item": 668 + }, + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 1001 + }, + { + "cant": 1000, + "item": 1002 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "71": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tened aquí las armas más poderosas de estas tierras... ¡Pasad y ved!", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 164 + }, + { + "cant": 1000, + "item": 19 + }, + { + "cant": 1000, + "item": 123 + }, + { + "cant": 1000, + "item": 401 + }, + { + "cant": 1000, + "item": 125 + }, + { + "cant": 1000, + "item": 753 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "72": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Saludos ciudadano, soy Tancredo de Hauteville, nuevo soberano de Banderbill. Mi deber es mantener el nuevo orden del Imperio. Si deseas unirte a mis filas y ayudarme di /enlistar o /recompensa para subir de rango.", + "drop": [], + "gold": 0, + "name": "Tancredo de Hauteville, soberano de Banderbill", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 409, + "idHead": 510, + "maxHit": 0, + "minHit": 0, + "npcType": 5, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "73": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Si tienes agallas como para abandonar la ciudad y enfrentar el peligro de los bosques, deberás comprar una de las armaduras que he fabricado.", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 30 + }, + { + "cant": 1000, + "item": 668 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 39, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "74": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Ven, tengo resistentes armaduras para ofrecerte.", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 360 + }, + { + "cant": 1000, + "item": 648 + }, + { + "cant": 1000, + "item": 1003 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "75": { + "hp": 30, + "def": 0, + "exp": 55, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Cuervo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 30, + "idBody": 68, + "idHead": 0, + "maxHit": 10, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 25, + "poderEvasion": 50, + "magicResistance": 0 + }, + "76": { + "hp": 5, + "def": 0, + "exp": 5, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Hormiga", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5, + "idBody": 70, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 3, + "magicResistance": 0 + }, + "77": { + "hp": 15, + "def": 0, + "exp": 5, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Rata", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 71, + "idHead": 0, + "maxHit": 4, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 20, + "magicResistance": 0 + }, + "78": { + "hp": 600, + "def": 0, + "exp": 550, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Oso Pardo Domesticado", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 73, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 65, + "magicResistance": 0 + }, + "79": { + "hp": 315, + "def": 0, + "exp": 350, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Tortuga de Mar", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 315, + "idBody": 74, + "idHead": 0, + "maxHit": 50, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 45, + "magicResistance": 0 + }, + "80": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡¡Hola buen amigo, compro y vendo todo tipo de mercancías!!", + "drop": [], + "gold": 0, + "name": "Mercader", + "objs": [ + { + "cant": 1000, + "item": 534 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 3, + "idHead": 2, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "81": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Sacerdote Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 101, + "idHead": 4, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 25, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "82": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Sacerdote Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 101, + "idHead": 5, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 25, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "83": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Druida Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 56, + "idHead": 101, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "84": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Druida Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 56, + "idHead": 102, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "85": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Druida Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 56, + "idHead": 103, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "86": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Mago Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 130, + "idHead": 6, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "87": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ohhh hijo mío!! que la llama de la virtud arda en tu corazón!!!", + "drop": [], + "gold": 0, + "name": "Mago Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 128, + "idHead": 6, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "88": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Ely", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 143, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "spells": [ + { + "idSpell": 5, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "89": { + "hp": 15000, + "def": 0, + "exp": 9000, + "defM": 0, + "drop": [], + "gold": 1000, + "name": "Fuego Fatuo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15000, + "idBody": 144, + "idHead": 0, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 1000, + "poderEvasion": 500, + "magicResistance": 0 + }, + "90": { + "hp": 29550, + "def": 0, + "exp": 2000, + "defM": 0, + "drop": [], + "gold": 2000, + "name": "Morgolock's Soul", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 29550, + "idBody": 144, + "idHead": 0, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 1500, + "magicResistance": 0 + }, + "91": { + "hp": 40, + "def": 0, + "exp": 75, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Gran Águila", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 40, + "idBody": 59, + "idHead": 0, + "maxHit": 15, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 25, + "poderEvasion": 160, + "magicResistance": 0 + }, + "92": { + "hp": 400, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Elemental de Agua", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 400, + "idBody": 541, + "idHead": 0, + "maxHit": 120, + "minHit": 100, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 235, + "poderEvasion": 70, + "magicResistance": 0 + }, + "93": { + "hp": 200, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Elemental de Fuego", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 200, + "idBody": 153, + "idHead": 0, + "maxHit": 120, + "minHit": 100, + "spells": [ + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 235, + "poderEvasion": 70, + "magicResistance": 0 + }, + "94": { + "hp": 200, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Elemental de Tierra", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 200, + "idBody": 543, + "idHead": 0, + "maxHit": 120, + "minHit": 100, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 235, + "poderEvasion": 70, + "magicResistance": 0 + }, + "95": { + "hp": 515, + "def": 0, + "exp": 550, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Jabalí", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 515, + "idBody": 150, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 130, + "poderEvasion": 70, + "magicResistance": 0 + }, + "96": { + "hp": 800, + "def": 0, + "exp": 600, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Tigre de Bengala", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 800, + "idBody": 147, + "idHead": 0, + "maxHit": 70, + "minHit": 30, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 700, + "poderEvasion": 45, + "magicResistance": 0 + }, + "97": { + "hp": 30, + "def": 0, + "exp": 20, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Pato", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 30, + "idBody": 88, + "idHead": 0, + "maxHit": 5, + "minHit": 3, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 10, + "magicResistance": 0 + }, + "98": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Si deseas unirte a la Legión Oscura escribe /enlistar o /recompensa para subir de rango", + "drop": [], + "gold": 0, + "name": "Demonio Legionario", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 405, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 5, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "99": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¿Estás buscando los hechizos más poderosos? A buen puerto has llegado...", + "drop": [], + "gold": 0, + "name": "Mago", + "objs": [ + { + "cant": 1000, + "item": 542 + }, + { + "cant": 1000, + "item": 532 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "100": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Daré cárcel a todos los criminales y los perseguiré hasta el fin!", + "drop": [], + "gold": 0, + "name": "Guardia Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 15, + "idHead": 0, + "maxHit": 250, + "minHit": 115, + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "101": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "En estas tierras demoníacas están las mejores residencias para los miembros de la Legión Oscura... Si no eres legionario puedes también vivir acá, ¡pero os aseguro que los augura un infierno! jajaja!", + "drop": [], + "gold": 0, + "name": "Vendedor de propiedades.", + "objs": [ + { + "cant": 0, + "item": 583 + }, + { + "cant": 0, + "item": 587 + }, + { + "cant": 0, + "item": 591 + }, + { + "cant": 0, + "item": 595 + }, + { + "cant": 0, + "item": 599 + }, + { + "cant": 0, + "item": 603 + }, + { + "cant": 0, + "item": 607 + }, + { + "cant": 0, + "item": 611 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 170, + "idHead": 34, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "102": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Daré cárcel a todos los criminales!", + "drop": [], + "gold": 0, + "name": "Guardia Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 15, + "idHead": 0, + "maxHit": 250, + "minHit": 115, + "npcType": 2, + "magicDef": 0, + "movement": 11, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "103": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola, pequeño amigo! ¿Estás buscando algo para llenar tu estómago? Creo que puedo ayudarte...", + "drop": [], + "gold": 0, + "name": "Carahuel", + "objs": [ + { + "cant": 1000, + "item": 1 + }, + { + "cant": 1000, + "item": 29 + }, + { + "cant": 1000, + "item": 158 + }, + { + "cant": 1000, + "item": 754 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 309, + "idHead": 474, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "104": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola joven! Si deseas apostar escribe /APOSTAR X", + "drop": [], + "gold": 0, + "name": "Timbero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 47, + "idHead": 9, + "maxHit": 0, + "minHit": 0, + "npcType": 7, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "105": { + "hp": 9550, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "¡El mal prevalecerá sobre estas tierras!", + "drop": [], + "gold": 0, + "name": "Mago del Caos", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 9550, + "idBody": 129, + "idHead": 202, + "maxHit": 100, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 8, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "106": { + "hp": 3500, + "def": 10, + "exp": 0, + "defM": 0, + "desc": "Ningún criminal escapará de mi puntería.", + "drop": [], + "gold": 0, + "name": "Arquera Real", + "objs": [ + { + "cant": 10, + "item": 551 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 3500, + "idBody": 131, + "idHead": 172, + "maxHit": 50, + "minHit": 30, + "spells": [ + { + "idSpell": 35, + "cooldownSeconds": 0 + } + ], + "npcType": 2, + "magicDef": 0, + "movement": 5, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1500, + "poderEvasion": 190, + "magicResistance": 0 + }, + "107": { + "hp": 49000, + "def": 30, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Golem Oscuro", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 49000, + "idBody": 141, + "idHead": 0, + "maxHit": 450, + "minHit": 300, + "npcType": 8, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 50, + "magicResistance": 0 + }, + "108": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola amigo! Escribe /ENTRENAR para divertirte con estas almas malvadas que hemos domado para tu diversión en estos festejos.", + "drop": [], + "gold": 0, + "name": "O'Riley de Arkhein", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 57, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 3, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "109": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Bienvenidos a la Gran Sastrería de la Ciudad Perdida! Aquí encontrarán las ropas más exóticas del mundo...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 515 + }, + { + "cant": 1000, + "item": 490 + }, + { + "cant": 1000, + "item": 381 + }, + { + "cant": 1000, + "item": 196 + }, + { + "cant": 1000, + "item": 357 + }, + { + "cant": 1000, + "item": 519 + }, + { + "cant": 1000, + "item": 986 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 130, + "idHead": 406, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "110": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Entra! Jamás verás prendas más bellas que las que tengo para ofrecerte...", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 124, + "idHead": 474, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "111": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Ven, tengo resistentes armaduras para ofrecerte.", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 360 + }, + { + "cant": 1000, + "item": 648 + }, + { + "cant": 1000, + "item": 1003 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "112": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Con estas manos he construído algunas armas que te podrán servir en tus aventuras... ¡paga por ellas y serán tuyas!", + "drop": [], + "gold": 0, + "name": "Herrero", + "objs": [ + { + "cant": 1000, + "item": 15 + }, + { + "cant": 1000, + "item": 619 + }, + { + "cant": 1000, + "item": 165 + }, + { + "cant": 1000, + "item": 9 + }, + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 125 + }, + { + "cant": 1000, + "item": 389 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 16, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "113": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Tengo a la venta las mejores armaduras de la isla!", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 360 + }, + { + "cant": 1000, + "item": 648 + }, + { + "cant": 1000, + "item": 484 + }, + { + "cant": 1000, + "item": 1004 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "114": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a la Sastrería Real.", + "drop": [], + "gold": 0, + "name": "SR", + "objs": [ + { + "cant": 1000, + "item": 675 + }, + { + "cant": 1000, + "item": 676 + }, + { + "cant": 1000, + "item": 517 + }, + { + "cant": 1000, + "item": 521 + }, + { + "cant": 1000, + "item": 372 + }, + { + "cant": 1000, + "item": 1039 + }, + { + "cant": 1000, + "item": 492 + }, + { + "cant": 1000, + "item": 549 + }, + { + "cant": 1000, + "item": 679 + }, + { + "cant": 1000, + "item": 629 + }, + { + "cant": 1000, + "item": 680 + }, + { + "cant": 1000, + "item": 1040 + }, + { + "cant": 1000, + "item": 521 + }, + { + "cant": 1000, + "item": 681 + }, + { + "cant": 1000, + "item": 682 + }, + { + "cant": 1000, + "item": 687 + }, + { + "cant": 1000, + "item": 688 + }, + { + "cant": 1000, + "item": 691 + }, + { + "cant": 1000, + "item": 694 + }, + { + "cant": 1000, + "item": 1038 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 174, + "idHead": 171, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "115": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡AQUÍ OBTENDRÁS TU ARMADURA LEGIONARIA!", + "drop": [], + "gold": 0, + "name": "SC", + "objs": [ + { + "cant": 1000, + "item": 677 + }, + { + "cant": 1000, + "item": 678 + }, + { + "cant": 1000, + "item": 518 + }, + { + "cant": 1000, + "item": 369 + }, + { + "cant": 1000, + "item": 523 + }, + { + "cant": 1000, + "item": 1041 + }, + { + "cant": 1000, + "item": 379 + }, + { + "cant": 1000, + "item": 558 + }, + { + "cant": 1000, + "item": 634 + }, + { + "cant": 1000, + "item": 683 + }, + { + "cant": 1000, + "item": 684 + }, + { + "cant": 1000, + "item": 1042 + }, + { + "cant": 1000, + "item": 685 + }, + { + "cant": 1000, + "item": 686 + }, + { + "cant": 1000, + "item": 689 + }, + { + "cant": 1000, + "item": 690 + }, + { + "cant": 1000, + "item": 701 + }, + { + "cant": 1000, + "item": 695 + }, + { + "cant": 1000, + "item": 1037 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 69, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "116": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Si estás buscando las mejores armaduras de estas tierras, has llegado al lugar correcto...", + "drop": [], + "gold": 0, + "name": "Armaduras", + "objs": [ + { + "cant": 1000, + "item": 672 + }, + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 360 + }, + { + "cant": 1000, + "item": 648 + }, + { + "cant": 1000, + "item": 356 + }, + { + "cant": 1000, + "item": 854 + }, + { + "cant": 1000, + "item": 195 + }, + { + "cant": 1000, + "item": 243 + }, + { + "cant": 1000, + "item": 1004 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "117": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Aquí en la Ciudad Perdida encontrarán las mejores ropas pequeñas de todo el mundo!", + "drop": [], + "gold": 0, + "name": "Sastre", + "objs": [ + { + "cant": 1000, + "item": 671 + }, + { + "cant": 1000, + "item": 670 + }, + { + "cant": 1000, + "item": 672 + }, + { + "cant": 1000, + "item": 525 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 136, + "idHead": 402, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "118": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Esto no es digno de un soberano! ¡Exijo me devuelvan mi trono y me saquen de esta pocilga!...¡Maldito Tancredo, me las pagarás!", + "drop": [], + "gold": 0, + "name": "Alarnick, antiguo soberano de Banderbill", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 348, + "idHead": 101, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "119": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola, hijo mío! Soy el sacerdote de estas tierras. Si eres newbie te podré resucitar, sólo necesitas tipear /RESUCITAR o hacer doble click sobre mi cabeza.", + "drop": [], + "gold": 0, + "name": "Sacerdote", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 49, + "idHead": 3, + "maxHit": 0, + "minHit": 0, + "npcType": 9, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "120": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido amigo, vendo ropajes para MUJERES HUMANAS.", + "drop": [], + "gold": 0, + "name": "Ireth", + "objs": [ + { + "cant": 1000, + "item": 885 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 254, + "idHead": 71, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "121": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¿Buscas ropas para HOMBRES HUMANOS? Entonces has hecho bien en acudir a mí.", + "drop": [], + "gold": 0, + "name": "Isaac", + "objs": [ + { + "cant": 1000, + "item": 882 + }, + { + "cant": 1000, + "item": 887 + }, + { + "cant": 1000, + "item": 888 + }, + { + "cant": 1000, + "item": 889 + }, + { + "cant": 1000, + "item": 890 + }, + { + "cant": 1000, + "item": 897 + }, + { + "cant": 1000, + "item": 903 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 240, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "122": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Poseo una gran cantidad de ropajes para los astutos GNOMOS, ¿Deseas algo en particular?", + "drop": [], + "gold": 0, + "name": "Nelvi", + "objs": [ + { + "cant": 1000, + "item": 908 + }, + { + "cant": 1000, + "item": 910 + }, + { + "cant": 1000, + "item": 911 + }, + { + "cant": 1000, + "item": 912 + }, + { + "cant": 1000, + "item": 913 + }, + { + "cant": 1000, + "item": 915 + }, + { + "cant": 1000, + "item": 916 + }, + { + "cant": 1000, + "item": 917 + }, + { + "cant": 1000, + "item": 918 + }, + { + "cant": 1000, + "item": 919 + }, + { + "cant": 1000, + "item": 920 + }, + { + "cant": 1000, + "item": 921 + }, + { + "cant": 1000, + "item": 922 + }, + { + "cant": 1000, + "item": 923 + }, + { + "cant": 1000, + "item": 924 + }, + { + "cant": 1000, + "item": 927 + }, + { + "cant": 1000, + "item": 928 + }, + { + "cant": 1000, + "item": 929 + }, + { + "cant": 1000, + "item": 930 + }, + { + "cant": 1000, + "item": 932 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 294, + "idHead": 403, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "123": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido, vendo ropas para los hermosos GNOMOS de estas tierras.", + "drop": [], + "gold": 0, + "name": "Aile", + "objs": [ + { + "cant": 1000, + "item": 934 + }, + { + "cant": 1000, + "item": 937 + }, + { + "cant": 1000, + "item": 939 + }, + { + "cant": 1000, + "item": 940 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 288, + "idHead": 473, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "124": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo para ofrecerte los mejores ropajes para ELFOS.", + "drop": [], + "gold": 0, + "name": "Varinya", + "objs": [ + { + "cant": 1000, + "item": 943 + }, + { + "cant": 1000, + "item": 946 + }, + { + "cant": 1000, + "item": 947 + }, + { + "cant": 1000, + "item": 952 + }, + { + "cant": 1000, + "item": 954 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 234, + "idHead": 172, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "125": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Solo tengo ropajes para ENANOS, si no las puedes usar, ¡vete de aqui!", + "drop": [], + "gold": 0, + "name": "Torke", + "objs": [ + { + "cant": 1000, + "item": 958 + }, + { + "cant": 1000, + "item": 959 + }, + { + "cant": 1000, + "item": 960 + }, + { + "cant": 1000, + "item": 961 + }, + { + "cant": 1000, + "item": 962 + }, + { + "cant": 1000, + "item": 963 + }, + { + "cant": 1000, + "item": 964 + }, + { + "cant": 1000, + "item": 965 + }, + { + "cant": 1000, + "item": 966 + }, + { + "cant": 1000, + "item": 967 + }, + { + "cant": 1000, + "item": 968 + }, + { + "cant": 1000, + "item": 969 + }, + { + "cant": 1000, + "item": 970 + }, + { + "cant": 1000, + "item": 971 + }, + { + "cant": 1000, + "item": 972 + }, + { + "cant": 1000, + "item": 974 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 306, + "idHead": 371, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "126": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Vendo ropas para DROWS, de hermosas telas y refinados detalles.", + "drop": [], + "gold": 0, + "name": "Gourk", + "objs": [ + { + "cant": 1000, + "item": 978 + }, + { + "cant": 1000, + "item": 979 + }, + { + "cant": 1000, + "item": 980 + }, + { + "cant": 1000, + "item": 981 + }, + { + "cant": 1000, + "item": 982 + }, + { + "cant": 1000, + "item": 984 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 264, + "idHead": 203, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "127": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Por el Señor Oscuro, aniquilaré a quienes lo deshonran!", + "drop": [], + "gold": 0, + "name": "Guardia Infernal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 170, + "idHead": 201, + "maxHit": 250, + "minHit": 115, + "npcType": 8, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "128": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "El pueblo de Ullathorpe en contra del alcalde... ¡Todos aquellos que estén en contra se presenten en el castillo del rey en Banderbill!", + "drop": [], + "gold": 0, + "name": "Nelmasil", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 410, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "129": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Todos arderán! arderán en el infierno y luciernagas saldrán de sus ojos!", + "drop": [], + "gold": 0, + "name": "Gael Azrraed", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 75, + "idHead": 2070, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "130": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Aprovechad que la pesca es buena a estas horas... Si deseas pescar, puedes comprarle una caña al comerciante que se encuentra al oeste del muelle.", + "drop": [], + "gold": 0, + "name": "Altair de Oteren", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 75, + "idHead": 60, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "131": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hey tú, ¿qué me andas mirando? He matado Medusas en peligrosos Dungeons desde que era un niño...", + "drop": [], + "gold": 0, + "name": "Linderoth", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 285, + "idHead": 2280, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "132": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡¡¿EH, EH?!! ¡no! ¡Yo no he hecho nada! ¡Vete si no quieres problemas!", + "drop": [], + "gold": 0, + "name": "Sebastien", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 31, + "idHead": 2154, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "133": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Shhh, ten cuidado, planeo raptar al Demonio. Buscaré refuerzos y la ciudad será asediada.", + "drop": [], + "gold": 0, + "name": "Anthares", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 378, + "idHead": 2050, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "134": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Estoy hasta la coronilla de que se escape mi vaca! ya recibirá su merecido cuando la encuentre.", + "drop": [], + "gold": 0, + "name": "Vuragon", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 501, + "idHead": 60, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "135": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Maldito seas Tancredo de Hauteville! Me obligan limpiar esta mugrosa ciudad por haber hecho algo que ¡¡¡no hice!!!", + "drop": [], + "gold": 0, + "name": "Belial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 641, + "idHead": 63, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "136": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¿Qué miras tú? ¿Acaso quieres sufrir?", + "drop": [], + "gold": 0, + "name": "Efral", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 442, + "idHead": 69, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "137": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "El Rey es avaro y sus terratenientes también, dame algo por favor, no seas como él...", + "drop": [], + "gold": 0, + "name": "Manrique", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 411, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "138": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Cuando hayas caido en combate, puedes acercarte al sacerdote de la ciudad para que te devuelva a la vida.", + "drop": [], + "gold": 0, + "name": "Abel, el monje pensador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 381, + "idHead": 2050, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "139": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Que la Diosa naturaleza te guíe.", + "drop": [], + "gold": 0, + "name": "Clipsa", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 357, + "idHead": 67, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "140": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "orcoszhh zson todsh orcshhh..hip..", + "drop": [], + "gold": 0, + "name": "Astor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 412, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "141": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Podrash caminar de norte a shur, *hip* podrash encontrar sherveza de la mejor *hip* pero ninguna como la del meshon *hip*", + "drop": [], + "gold": 0, + "name": "Zairot", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 416, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "142": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Déjenme tarnquilo... hip hip... descargaré lo que he... hip hip... bebido", + "drop": [], + "gold": 0, + "name": "Onkin", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 424, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "143": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "En las batallas más escalofriantes, más sangrientas... Un bardo siempre está presente.", + "drop": [], + "gold": 0, + "name": "Fethresor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 418, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "144": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Mis melodías podrían hacer que hasta el más hábil guerrero, caiga a mis pies...", + "drop": [], + "gold": 0, + "name": "Whyx", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 419, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "145": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Cuando te encuentres solo, perdido, sin saber para dónde correr... Respira profundo y susurra mi melodía.", + "drop": [], + "gold": 0, + "name": "Iklot", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 420, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "146": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola! Poseo las mejores y más extrañas bebidas de estas tierras, ¿Qué desea que le sirva?", + "drop": [], + "gold": 0, + "name": "Unurbar Gorog", + "objs": [ + { + "cant": 1000, + "item": 498 + }, + { + "cant": 1000, + "item": 522 + }, + { + "cant": 1000, + "item": 550 + }, + { + "cant": 1000, + "item": 160 + }, + { + "cant": 1000, + "item": 557 + }, + { + "cant": 1000, + "item": 612 + }, + { + "cant": 1000, + "item": 42 + }, + { + "cant": 1000, + "item": 622 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 372, + "idHead": 304, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "147": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "¡Hola forastero! Pronto se celebrará un nuevo aniversario de la creación de la Cerveza. ¿Acudirás a los festejos?", + "drop": [], + "gold": 0, + "name": "Dugh Dhail", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 410, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "148": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Atención! Si eres criminal e ingresas aquí seras atacado por los guardias reales.", + "drop": [], + "gold": 0, + "name": "Guardia Imperial", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 15, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "149": { + "hp": 1000, + "def": 30, + "exp": 0, + "defM": 0, + "desc": "¡Atención! Si eres ciudadano e ingresas aquí seras atacado por los guardias de la Legión.", + "drop": [], + "gold": 0, + "name": "Guardia Infernal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 170, + "idHead": 201, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 190, + "magicResistance": 0 + }, + "150": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Nix ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "151": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Banderbill ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "152": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Lindos ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "153": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Arghal ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "154": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, tengo las mejores Pociones de la Alquimia para principiantes.", + "drop": [], + "gold": 0, + "name": "Lilith", + "objs": [ + { + "cant": 1000, + "item": 855 + }, + { + "cant": 1000, + "item": 856 + }, + { + "cant": 1000, + "item": 857 + }, + { + "cant": 1000, + "item": 858 + }, + { + "cant": 1000, + "item": 166 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 200, + "idHead": 77, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "155": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Si estás buscando túnicas y armaduras para principiantes, has llegado al lugar indicado.", + "drop": [], + "gold": 0, + "name": "Armaduras Newbie", + "objs": [ + { + "cant": 1000, + "item": 1044 + }, + { + "cant": 1000, + "item": 1045 + }, + { + "cant": 1000, + "item": 1046 + }, + { + "cant": 1000, + "item": 1047 + }, + { + "cant": 1000, + "item": 1048 + }, + { + "cant": 1000, + "item": 1051 + }, + { + "cant": 1000, + "item": 1052 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "156": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola novato, tengo algunas armas que pueden ayudarte en tus aventuras.", + "drop": [], + "gold": 0, + "name": "Armas Newbie", + "objs": [ + { + "cant": 1000, + "item": 861 + }, + { + "cant": 1000, + "item": 859 + }, + { + "cant": 1000, + "item": 860 + }, + { + "cant": 1000, + "item": 564 + }, + { + "cant": 1000, + "item": 565 + }, + { + "cant": 1000, + "item": 562 + }, + { + "cant": 1000, + "item": 561 + }, + { + "cant": 1000, + "item": 563 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "157": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido a mi pequeña tienda. Tengo algunos hechizos que podrían interesarte.", + "drop": [], + "gold": 0, + "name": "Magia Newbie", + "objs": [ + { + "cant": 1000, + "item": 199 + }, + { + "cant": 1000, + "item": 197 + }, + { + "cant": 1000, + "item": 204 + }, + { + "cant": 1000, + "item": 205 + }, + { + "cant": 1000, + "item": 202 + }, + { + "cant": 1000, + "item": 200 + }, + { + "cant": 1000, + "item": 201 + }, + { + "cant": 1000, + "item": 363 + }, + { + "cant": 1000, + "item": 364 + }, + { + "cant": 1000, + "item": 862 + }, + { + "cant": 1000, + "item": 863 + }, + { + "cant": 1000, + "item": 864 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 46, + "idHead": 110, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "500": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 12 + }, + { + "cant": 2, + "item": 12 + }, + { + "cant": 15, + "item": 12 + }, + { + "cant": 70, + "item": 12 + } + ], + "gold": 0, + "name": "Murciélago", + "objs": [], + "snd1": 125, + "snd2": 125, + "maxHp": 15, + "idBody": 9, + "idHead": 0, + "maxHit": 4, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 30, + "poderEvasion": 10, + "magicResistance": 0 + }, + "501": { + "hp": 75, + "def": 8, + "exp": 100, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 414 + }, + { + "cant": 3, + "item": 27 + }, + { + "cant": 1, + "item": 1002 + }, + { + "cant": 1, + "item": 394 + } + ], + "gold": 0, + "name": "Lobo", + "objs": [], + "snd1": 35, + "snd2": 9, + "maxHp": 75, + "idBody": 10, + "idHead": 0, + "maxHit": 18, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 80, + "poderEvasion": 25, + "magicResistance": 0 + }, + "502": { + "hp": 250, + "def": 20, + "exp": 175, + "defM": 0, + "drop": [ + { + "cant": 21, + "item": 12 + }, + { + "cant": 1, + "item": 45 + }, + { + "cant": 50, + "item": 12 + }, + { + "cant": 30, + "item": 38 + } + ], + "gold": 0, + "name": "Zombie", + "objs": [], + "snd1": 0, + "snd2": 33, + "maxHp": 250, + "idBody": 196, + "idHead": 507, + "maxHit": 12, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 20, + "magicResistance": 0 + }, + "503": { + "hp": 50, + "def": 10, + "exp": 47, + "defM": 0, + "drop": [ + { + "cant": 8, + "item": 12 + }, + { + "cant": 8, + "item": 12 + }, + { + "cant": 1, + "item": 363 + }, + { + "cant": 2, + "item": 2 + }, + { + "cant": 1, + "item": 197 + } + ], + "gold": 0, + "name": "Esqueleto", + "objs": [], + "snd1": 168, + "snd2": 168, + "maxHp": 50, + "idBody": 12, + "idHead": 0, + "maxHit": 8, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 30, + "poderEvasion": 30, + "magicResistance": 0 + }, + "504": { + "hp": 22, + "def": 0, + "exp": 15, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 38 + }, + { + "cant": 1, + "item": 200 + }, + { + "cant": 1, + "item": 202 + } + ], + "gold": 0, + "name": "Serpiente", + "objs": [], + "snd1": 127, + "snd2": 128, + "maxHp": 22, + "idBody": 13, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 0, + "magicResistance": 0 + }, + "505": { + "hp": 110, + "def": 15, + "exp": 95, + "defM": 0, + "drop": [ + { + "cant": 15, + "item": 12 + }, + { + "cant": 22, + "item": 12 + }, + { + "cant": 1, + "item": 15 + }, + { + "cant": 1, + "item": 132 + }, + { + "cant": 10, + "item": 39 + } + ], + "gold": 0, + "name": "Goblin", + "objs": [], + "snd1": 140, + "snd2": 140, + "maxHp": 110, + "idBody": 14, + "idHead": 0, + "maxHit": 12, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 60, + "poderEvasion": 40, + "magicResistance": 0 + }, + "506": { + "hp": 15, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 26 + }, + { + "cant": 5, + "item": 12 + }, + { + "cant": 50, + "item": 12 + } + ], + "gold": 0, + "name": "Gallo Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 5, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 5, + "magicResistance": 0 + }, + "507": { + "hp": 350, + "def": 34, + "exp": 355, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 7, + "item": 38 + }, + { + "cant": 5, + "item": 37 + }, + { + "cant": 1, + "item": 395 + } + ], + "gold": 0, + "name": "Lord Zombie", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 350, + "idBody": 197, + "idHead": 508, + "maxHit": 25, + "minHit": 10, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 35, + "magicResistance": 0 + }, + "508": { + "hp": 150, + "def": 19, + "exp": 155, + "defM": 0, + "drop": [ + { + "cant": 25, + "item": 12 + }, + { + "cant": 1, + "item": 132 + }, + { + "cant": 1, + "item": 126 + }, + { + "cant": 1, + "item": 380 + } + ], + "gold": 0, + "name": "Esqueleto Guerrero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 150, + "idBody": 393, + "idHead": 0, + "maxHit": 18, + "minHit": 8, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 60, + "poderEvasion": 50, + "magicResistance": 0 + }, + "509": { + "hp": 5000, + "def": 50, + "exp": 8500, + "defM": 0, + "drop": [ + { + "cant": 1000, + "item": 12 + }, + { + "cant": 1, + "item": 239 + }, + { + "cant": 4700, + "item": 12 + }, + { + "cant": 2, + "item": 981 + }, + { + "cant": 1, + "item": 481 + } + ], + "gold": 0, + "name": "Dragón Rojo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5000, + "idBody": 41, + "idHead": 0, + "maxHit": 300, + "minHit": 250, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 380, + "poderEvasion": 250, + "magicResistance": 0 + }, + "510": { + "hp": 1000, + "def": 0, + "exp": 1450, + "defM": 0, + "drop": [ + { + "cant": 175, + "item": 12 + }, + { + "cant": 30, + "item": 38 + }, + { + "cant": 1, + "item": 164 + }, + { + "cant": 110, + "item": 480 + }, + { + "cant": 1, + "item": 206 + } + ], + "gold": 0, + "name": "Araña Gigante", + "objs": [], + "snd1": 29, + "snd2": 0, + "maxHp": 1000, + "idBody": 42, + "idHead": 0, + "maxHit": 100, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "magicResistance": 0 + }, + "511": { + "hp": 2750, + "def": 10, + "exp": 4110, + "defM": 0, + "drop": [ + { + "cant": 350, + "item": 12 + }, + { + "cant": 55, + "item": 37 + }, + { + "cant": 1, + "item": 401 + }, + { + "cant": 1, + "item": 697 + } + ], + "gold": 0, + "name": "Ogro Esclavo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2750, + "idBody": 205, + "idHead": 0, + "maxHit": 190, + "minHit": 70, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 210, + "magicResistance": 0 + }, + "512": { + "hp": 75, + "def": 8, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Lobo", + "objs": [], + "snd1": 35, + "snd2": 9, + "maxHp": 75, + "idBody": 10, + "idHead": 0, + "maxHit": 20, + "minHit": 10, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 80, + "poderEvasion": 25, + "magicResistance": 0 + }, + "513": { + "hp": 4500, + "def": 30, + "exp": 7288, + "defM": 0, + "drop": [ + { + "cant": 900, + "item": 12 + }, + { + "cant": 145, + "item": 37 + }, + { + "cant": 1, + "item": 990 + }, + { + "cant": 1, + "item": 698 + }, + { + "cant": 3, + "item": 359 + } + ], + "gold": 0, + "name": "Cíclope Guardián", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 4500, + "idBody": 206, + "idHead": 0, + "maxHit": 250, + "minHit": 100, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 260, + "poderEvasion": 245, + "magicResistance": 0 + }, + "514": { + "hp": 5000, + "def": 0, + "exp": 3410, + "defM": 0, + "drop": [ + { + "cant": 3000, + "item": 12 + }, + { + "cant": 3, + "item": 697 + }, + { + "cant": 2, + "item": 698 + }, + { + "cant": 1, + "item": 699 + } + ], + "gold": 0, + "name": "Serpiente de Anthares", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5000, + "idBody": 207, + "idHead": 0, + "maxHit": 45, + "minHit": 10, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 150, + "magicResistance": 0 + }, + "515": { + "hp": 1300, + "def": 10, + "exp": 1880, + "defM": 0, + "drop": [ + { + "cant": 175, + "item": 12 + }, + { + "cant": 30, + "item": 38 + }, + { + "cant": 50, + "item": 37 + }, + { + "cant": 310, + "item": 480 + }, + { + "cant": 2, + "item": 697 + } + ], + "gold": 0, + "name": "Gorila Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1300, + "idBody": 213, + "idHead": 0, + "maxHit": 130, + "minHit": 90, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 160, + "poderEvasion": 70, + "magicResistance": 0 + }, + "516": { + "hp": 90, + "def": 15, + "exp": 45, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Goblin", + "objs": [], + "snd1": 140, + "snd2": 140, + "maxHp": 90, + "idBody": 14, + "idHead": 0, + "maxHit": 12, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 50, + "poderEvasion": 40, + "magicResistance": 0 + }, + "517": { + "hp": 4200, + "def": 45, + "exp": 6804, + "defM": 0, + "drop": [ + { + "cant": 1200, + "item": 12 + }, + { + "cant": 1550, + "item": 12 + }, + { + "cant": 175, + "item": 37 + }, + { + "cant": 1, + "item": 206 + }, + { + "cant": 1, + "item": 531 + } + ], + "gold": 0, + "name": "Djinn de Viento", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 4200, + "idBody": 215, + "idHead": 0, + "maxHit": 140, + "minHit": 75, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 250, + "magicResistance": 0 + }, + "518": { + "hp": 2000, + "def": 50, + "exp": 2900, + "defM": 0, + "drop": [ + { + "cant": 150, + "item": 12 + }, + { + "cant": 120, + "item": 37 + }, + { + "cant": 1, + "item": 361 + }, + { + "cant": 1, + "item": 206 + } + ], + "gold": 0, + "name": "Dragón de las Profundidades", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2000, + "idBody": 218, + "idHead": 0, + "maxHit": 150, + "minHit": 90, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 250, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "519": { + "hp": 1000, + "def": 0, + "exp": 1450, + "defM": 0, + "drop": [ + { + "cant": 175, + "item": 12 + }, + { + "cant": 30, + "item": 38 + }, + { + "cant": 10, + "item": 36 + }, + { + "cant": 365, + "item": 480 + } + ], + "gold": 0, + "name": "Gusano de Arena", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 216, + "idHead": 0, + "maxHit": 100, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "magicResistance": 0 + }, + "520": { + "hp": 320, + "def": 25, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Esqueleto Guerrero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 320, + "idBody": 393, + "idHead": 0, + "maxHit": 94, + "minHit": 55, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 130, + "poderEvasion": 100, + "magicResistance": 0 + }, + "521": { + "hp": 800, + "def": 0, + "exp": 600, + "defM": 0, + "drop": [ + { + "cant": 20, + "item": 414 + }, + { + "cant": 23, + "item": 27 + }, + { + "cant": 1, + "item": 30 + }, + { + "cant": 1, + "item": 1004 + } + ], + "gold": 0, + "name": "Tigre Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 800, + "idBody": 147, + "idHead": 0, + "maxHit": 70, + "minHit": 30, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 700, + "poderEvasion": 45, + "magicResistance": 0 + }, + "522": { + "hp": 350, + "def": 30, + "exp": 395, + "defM": 0, + "drop": [ + { + "cant": 114, + "item": 12 + }, + { + "cant": 1, + "item": 3 + }, + { + "cant": 10, + "item": 39 + }, + { + "cant": 2, + "item": 990 + }, + { + "cant": 2, + "item": 359 + } + ], + "gold": 0, + "name": "Orco", + "objs": [], + "snd1": 2036, + "snd2": 2046, + "maxHp": 350, + "idBody": 185, + "idHead": 0, + "maxHit": 35, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 75, + "magicResistance": 0 + }, + "523": { + "hp": 350, + "def": 30, + "exp": 395, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 3 + } + ], + "gold": 44, + "name": "Orco", + "objs": [], + "snd1": 2036, + "snd2": 2046, + "maxHp": 350, + "idBody": 185, + "idHead": 0, + "maxHit": 35, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 75, + "magicResistance": 0 + }, + "524": { + "hp": 1000, + "def": 30, + "exp": 2000, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 183 + }, + { + "cant": 1, + "item": 183 + }, + { + "cant": 1, + "item": 183 + }, + { + "cant": 1, + "item": 183 + }, + { + "cant": 1, + "item": 183 + } + ], + "gold": 0, + "name": "Jefe Orco", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 43, + "idHead": 0, + "maxHit": 400, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 200, + "magicResistance": 0 + }, + "525": { + "hp": 40, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 139 + }, + { + "cant": 5, + "item": 544 + } + ], + "gold": 0, + "name": "Águila", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 40, + "idBody": 59, + "idHead": 0, + "maxHit": 15, + "minHit": 4, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 25, + "poderEvasion": 160, + "magicResistance": 0 + }, + "526": { + "hp": 3000, + "def": 10, + "exp": 1450, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 174 + }, + { + "cant": 1, + "item": 174 + }, + { + "cant": 1, + "item": 174 + }, + { + "cant": 1, + "item": 174 + }, + { + "cant": 1, + "item": 174 + } + ], + "gold": 0, + "name": "Araña Malaheing", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 3000, + "idBody": 42, + "idHead": 0, + "maxHit": 100, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 150, + "magicResistance": 0 + }, + "527": { + "hp": 5000, + "def": 20, + "exp": 8500, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 178 + }, + { + "cant": 1, + "item": 178 + }, + { + "cant": 1, + "item": 178 + }, + { + "cant": 1, + "item": 178 + }, + { + "cant": 1, + "item": 178 + } + ], + "gold": 0, + "name": "Dragón Rojo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5000, + "idBody": 41, + "idHead": 0, + "maxHit": 300, + "minHit": 250, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 250, + "magicResistance": 0 + }, + "528": { + "hp": 75, + "def": 0, + "exp": 51, + "defM": 0, + "drop": [ + { + "cant": 5, + "item": 12 + }, + { + "cant": 10, + "item": 12 + }, + { + "cant": 50, + "item": 12 + } + ], + "gold": 0, + "name": "Anfisbena", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 75, + "idBody": 203, + "idHead": 0, + "maxHit": 5, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 30, + "poderEvasion": 0, + "magicResistance": 0 + }, + "529": { + "hp": 32, + "def": 0, + "exp": 32, + "defM": 0, + "drop": [ + { + "cant": 2, + "item": 12 + }, + { + "cant": 30, + "item": 12 + }, + { + "cant": 1, + "item": 2 + }, + { + "cant": 1, + "item": 164 + } + ], + "gold": 0, + "name": "Escorpión", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 32, + "idBody": 51, + "idHead": 0, + "maxHit": 3, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 30, + "poderEvasion": 10, + "magicResistance": 0 + }, + "530": { + "hp": 200, + "def": 0, + "exp": 266, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 1, + "item": 641 + }, + { + "cant": 10, + "item": 38 + }, + { + "cant": 1, + "item": 873 + } + ], + "gold": 0, + "name": "Bandido", + "objs": [], + "snd1": 46, + "snd2": 0, + "maxHp": 200, + "idBody": 47, + "idHead": 2, + "maxHit": 39, + "minHit": 15, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 70, + "poderEvasion": 50, + "magicResistance": 0 + }, + "531": { + "hp": 850, + "def": 0, + "exp": 1500, + "defM": 0, + "drop": [ + { + "cant": 200, + "item": 12 + }, + { + "cant": 10, + "item": 38 + }, + { + "cant": 1, + "item": 366 + }, + { + "cant": 1, + "item": 367 + }, + { + "cant": 1, + "item": 356 + } + ], + "gold": 0, + "name": "Asesino", + "objs": [], + "snd1": 58, + "snd2": 117, + "maxHp": 850, + "idBody": 48, + "idHead": 201, + "maxHit": 139, + "minHit": 15, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 200, + "magicResistance": 0 + }, + "532": { + "hp": 400, + "def": 0, + "exp": 550, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 415 + }, + { + "cant": 10, + "item": 38 + }, + { + "cant": 50, + "item": 480 + } + ], + "gold": 0, + "name": "Osezno", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 400, + "idBody": 236, + "idHead": 0, + "maxHit": 70, + "minHit": 55, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 230, + "poderEvasion": 60, + "magicResistance": 0 + }, + "533": { + "hp": 50, + "def": 2, + "exp": 41, + "defM": 0, + "drop": [ + { + "cant": 4, + "item": 12 + }, + { + "cant": 53, + "item": 12 + }, + { + "cant": 250, + "item": 12 + }, + { + "cant": 1000, + "item": 12 + } + ], + "gold": 0, + "name": "Cuervo Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 50, + "idBody": 68, + "idHead": 0, + "maxHit": 6, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 20, + "poderEvasion": 25, + "magicResistance": 0 + }, + "534": { + "hp": 400, + "def": 5, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 1, + "item": 478 + }, + { + "cant": 1, + "item": 479 + }, + { + "cant": 15, + "item": 38 + }, + { + "cant": 500, + "item": 480 + } + ], + "gold": 0, + "name": "Hormiga Gigante", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 400, + "idBody": 69, + "idHead": 0, + "maxHit": 100, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 100, + "magicResistance": 0 + }, + "535": { + "hp": 5, + "def": 0, + "exp": 5, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 12 + }, + { + "cant": 10, + "item": 12 + }, + { + "cant": 200, + "item": 12 + } + ], + "gold": 0, + "name": "Hormiga Marabunta", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5, + "idBody": 70, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 5, + "poderEvasion": 3, + "magicResistance": 0 + }, + "536": { + "hp": 15, + "def": 0, + "exp": 16, + "defM": 0, + "drop": [ + { + "cant": 2, + "item": 12 + }, + { + "cant": 10, + "item": 28 + }, + { + "cant": 10, + "item": 534 + }, + { + "cant": 100, + "item": 12 + } + ], + "gold": 0, + "name": "Rata Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15, + "idBody": 71, + "idHead": 0, + "maxHit": 4, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 20, + "magicResistance": 0 + }, + "537": { + "hp": 1500, + "def": 0, + "exp": 2520, + "defM": 0, + "drop": [ + { + "cant": 360, + "item": 12 + }, + { + "cant": 500, + "item": 12 + }, + { + "cant": 100, + "item": 37 + }, + { + "cant": 1, + "item": 205 + }, + { + "cant": 10000, + "item": 12 + } + ], + "gold": 0, + "name": "Beholder", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1500, + "idBody": 72, + "idHead": 0, + "maxHit": 300, + "minHit": 45, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 200, + "magicResistance": 0 + }, + "538": { + "hp": 600, + "def": 0, + "exp": 850, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 415 + }, + { + "cant": 26, + "item": 12 + }, + { + "cant": 50, + "item": 480 + }, + { + "cant": 1, + "item": 479 + }, + { + "cant": 100, + "item": 38 + } + ], + "gold": 0, + "name": "Oso Pardo", + "objs": [], + "snd1": 138, + "snd2": 139, + "maxHp": 600, + "idBody": 73, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 65, + "magicResistance": 0 + }, + "539": { + "hp": 550, + "def": 0, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 404 + }, + { + "cant": 175, + "item": 12 + }, + { + "cant": 20, + "item": 38 + }, + { + "cant": 1, + "item": 203 + } + ], + "gold": 0, + "name": "Tortuga Gigante", + "objs": [], + "snd1": 143, + "snd2": 83, + "maxHp": 550, + "idBody": 74, + "idHead": 0, + "maxHit": 50, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 45, + "magicResistance": 0 + }, + "540": { + "hp": 210, + "def": 0, + "exp": 245, + "defM": 0, + "drop": [ + { + "cant": 23, + "item": 12 + }, + { + "cant": 150, + "item": 12 + }, + { + "cant": 1, + "item": 197 + }, + { + "cant": 1, + "item": 204 + } + ], + "gold": 0, + "name": "Duende", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 210, + "idBody": 75, + "idHead": 0, + "maxHit": 32, + "minHit": 8, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 80, + "poderEvasion": 60, + "magicResistance": 0 + }, + "541": { + "hp": 2250, + "def": 10, + "exp": 3600, + "defM": 0, + "drop": [ + { + "cant": 512, + "item": 12 + }, + { + "cant": 20, + "item": 398 + }, + { + "cant": 120, + "item": 38 + }, + { + "cant": 150, + "item": 37 + }, + { + "cant": 1, + "item": 698 + } + ], + "gold": 0, + "name": "Ogro", + "objs": [], + "snd1": 114, + "snd2": 140, + "maxHp": 2250, + "idBody": 76, + "idHead": 0, + "maxHit": 232, + "minHit": 88, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 260, + "poderEvasion": 230, + "magicResistance": 0 + }, + "542": { + "hp": 2000000, + "def": 0, + "exp": 840000, + "defM": 0, + "drop": [ + { + "cant": 65000, + "item": 12 + }, + { + "cant": 5000, + "item": 194 + }, + { + "cant": 1, + "item": 402 + }, + { + "cant": 1, + "item": 532 + }, + { + "cant": 3, + "item": 481 + } + ], + "gold": 0, + "name": "Gran Dragón Rojo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2000000, + "idBody": 82, + "idHead": 0, + "maxHit": 5000, + "minHit": 1000, + "spells": [ + { + "idSpell": 15, + "cooldownSeconds": 0 + }, + { + "idSpell": 25, + "cooldownSeconds": 0 + } + ], + "npcType": 6, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 400, + "poderEvasion": 320, + "magicResistance": 0 + }, + "543": { + "hp": 15000, + "def": 0, + "exp": 22500, + "defM": 0, + "drop": [ + { + "cant": 6000, + "item": 12 + }, + { + "cant": 10000, + "item": 12 + }, + { + "cant": 1, + "item": 126 + }, + { + "cant": 3, + "item": 129 + }, + { + "cant": 1, + "item": 495 + } + ], + "gold": 0, + "name": "Demonio", + "objs": [], + "snd1": 0, + "snd2": 52, + "maxHp": 15000, + "idBody": 83, + "idHead": 0, + "maxHit": 350, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 230, + "poderEvasion": 220, + "magicResistance": 0 + }, + "544": { + "hp": 500, + "def": 0, + "exp": 200, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 80, + "item": 12 + }, + { + "cant": 150, + "item": 12 + }, + { + "cant": 50, + "item": 37 + }, + { + "cant": 5000, + "item": 12 + } + ], + "gold": 0, + "name": "Unicornio", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 500, + "idBody": 183, + "idHead": 0, + "maxHit": 7, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 10, + "magicResistance": 0 + }, + "545": { + "hp": 600, + "def": 0, + "exp": 850, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 10, + "item": 38 + }, + { + "cant": 30, + "item": 414 + }, + { + "cant": 1, + "item": 1002 + }, + { + "cant": 1, + "item": 394 + } + ], + "gold": 0, + "name": "Licántropo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 212, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 65, + "magicResistance": 0 + }, + "546": { + "hp": 250, + "def": 20, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Zombie", + "objs": [], + "snd1": 0, + "snd2": 33, + "maxHp": 250, + "idBody": 196, + "idHead": 507, + "maxHit": 50, + "minHit": 23, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 20, + "magicResistance": 0 + }, + "547": { + "hp": 1000, + "def": 0, + "exp": 1450, + "defM": 0, + "drop": [ + { + "cant": 175, + "item": 12 + }, + { + "cant": 25, + "item": 38 + }, + { + "cant": 15, + "item": 36 + }, + { + "cant": 20, + "item": 39 + }, + { + "cant": 5, + "item": 159 + } + ], + "gold": 0, + "name": "Tarántula", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1000, + "idBody": 36, + "idHead": 0, + "maxHit": 100, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "magicResistance": 0 + }, + "548": { + "hp": 6000, + "def": 0, + "exp": 9000, + "defM": 0, + "drop": [ + { + "cant": 2100, + "item": 12 + }, + { + "cant": 30, + "item": 36 + }, + { + "cant": 30, + "item": 39 + }, + { + "cant": 1, + "item": 537 + }, + { + "cant": 1, + "item": 496 + } + ], + "gold": 0, + "name": "Leviathan", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 6000, + "idBody": 99, + "idHead": 0, + "maxHit": 250, + "minHit": 60, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 180, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "549": { + "hp": 1900, + "def": 30, + "exp": 2880, + "defM": 0, + "drop": [ + { + "cant": 500, + "item": 12 + }, + { + "cant": 650, + "item": 12 + }, + { + "cant": 1, + "item": 205 + }, + { + "cant": 300, + "item": 38 + }, + { + "cant": 500, + "item": 37 + } + ], + "gold": 0, + "name": "Orco Brujo", + "objs": [], + "snd1": 2036, + "snd2": 2046, + "maxHp": 1900, + "idBody": 186, + "idHead": 0, + "maxHit": 120, + "minHit": 75, + "spells": [ + { + "idSpell": 8, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 180, + "magicResistance": 0 + }, + "550": { + "hp": 4200, + "def": 30, + "exp": 6300, + "defM": 0, + "drop": [ + { + "cant": 1000, + "item": 12 + }, + { + "cant": 1, + "item": 659 + }, + { + "cant": 5000, + "item": 12 + }, + { + "cant": 15000, + "item": 12 + } + ], + "gold": 0, + "name": "Bruja", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 4200, + "idBody": 125, + "idHead": 271, + "maxHit": 140, + "minHit": 75, + "spells": [ + { + "idSpell": 8, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 250, + "magicResistance": 0 + }, + "551": { + "hp": 3000, + "def": 30, + "exp": 4200, + "defM": 0, + "drop": [ + { + "cant": 1500, + "item": 12 + }, + { + "cant": 2000, + "item": 12 + }, + { + "cant": 2, + "item": 524 + }, + { + "cant": 1, + "item": 206 + }, + { + "cant": 5, + "item": 660 + } + ], + "gold": 0, + "name": "Mago Malvado", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 3000, + "idBody": 135, + "idHead": 401, + "maxHit": 126, + "minHit": 75, + "spells": [ + { + "idSpell": 8, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 150, + "magicResistance": 0 + }, + "552": { + "hp": 32000, + "def": 30, + "exp": 60160, + "defM": 0, + "drop": [ + { + "cant": 2000, + "item": 12 + }, + { + "cant": 2750, + "item": 12 + }, + { + "cant": 10000, + "item": 21 + }, + { + "cant": 2, + "item": 403 + }, + { + "cant": 1, + "item": 485 + } + ], + "gold": 0, + "name": "Golem", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 32000, + "idBody": 141, + "idHead": 0, + "maxHit": 600, + "minHit": 300, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 50, + "magicResistance": 0 + }, + "553": { + "hp": 7200, + "def": 20, + "exp": 11520, + "defM": 0, + "drop": [ + { + "cant": 1200, + "item": 12 + }, + { + "cant": 100, + "item": 38 + }, + { + "cant": 7500, + "item": 12 + }, + { + "cant": 1, + "item": 206 + }, + { + "cant": 1, + "item": 531 + } + ], + "gold": 0, + "name": "Medusa", + "objs": [], + "snd1": 345, + "snd2": 345, + "maxHp": 7200, + "idBody": 151, + "idHead": 0, + "maxHit": 185, + "minHit": 125, + "spells": [ + { + "idSpell": 15, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 160, + "magicResistance": 0 + }, + "554": { + "hp": 2000, + "def": 30, + "exp": 3000, + "defM": 0, + "drop": [ + { + "cant": 1000, + "item": 12 + }, + { + "cant": 2000, + "item": 12 + }, + { + "cant": 5, + "item": 196 + }, + { + "cant": 1, + "item": 380 + }, + { + "cant": 5, + "item": 660 + } + ], + "gold": 0, + "name": "Liche", + "objs": [], + "snd1": 10, + "snd2": 149, + "maxHp": 2000, + "idBody": 149, + "idHead": 0, + "maxHit": 250, + "minHit": 120, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 190, + "magicResistance": 0 + }, + "555": { + "hp": 1750, + "def": 30, + "exp": 2625, + "defM": 0, + "drop": [ + { + "cant": 800, + "item": 12 + }, + { + "cant": 100, + "item": 38 + }, + { + "cant": 2, + "item": 132 + }, + { + "cant": 5, + "item": 126 + }, + { + "cant": 3, + "item": 130 + } + ], + "gold": 0, + "name": "Lord Orco", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1750, + "idBody": 187, + "idHead": 0, + "maxHit": 200, + "minHit": 200, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 170, + "poderEvasion": 100, + "magicResistance": 0 + }, + "556": { + "hp": 50, + "def": 5, + "exp": 25, + "defM": 0, + "drop": [ + { + "cant": 15, + "item": 12 + }, + { + "cant": 20, + "item": 12 + }, + { + "cant": 100, + "item": 12 + }, + { + "cant": 250, + "item": 12 + }, + { + "cant": 750, + "item": 12 + } + ], + "gold": 0, + "name": "Quark", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 50, + "idBody": 146, + "idHead": 0, + "maxHit": 10, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 45, + "poderEvasion": 50, + "magicResistance": 0 + }, + "557": { + "hp": 515, + "def": 0, + "exp": 340, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 27 + }, + { + "cant": 50, + "item": 12 + }, + { + "cant": 20, + "item": 38 + } + ], + "gold": 0, + "name": "Jabalí Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 515, + "idBody": 150, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 130, + "poderEvasion": 70, + "magicResistance": 0 + }, + "558": { + "hp": 45000, + "def": 10, + "exp": 45000, + "defM": 0, + "drop": [], + "gold": 25000, + "name": "Dark mOrGoLoCk", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45000, + "idBody": 56, + "idHead": 101, + "maxHit": 400, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 300, + "magicResistance": 0 + }, + "559": { + "hp": 45000, + "def": 10, + "exp": 45000, + "defM": 0, + "drop": [], + "gold": 25000, + "name": "Dark NuKeR", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45000, + "idBody": 93, + "idHead": 102, + "maxHit": 400, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 300, + "magicResistance": 0 + }, + "560": { + "hp": 45000, + "def": 10, + "exp": 45000, + "defM": 0, + "drop": [], + "gold": 25000, + "name": "Dark Valkira", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45000, + "idBody": 104, + "idHead": 72, + "maxHit": 400, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 300, + "magicResistance": 0 + }, + "561": { + "hp": 45000, + "def": 10, + "exp": 45000, + "defM": 0, + "drop": [], + "gold": 25000, + "name": "Dark Chatillon", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45000, + "idBody": 140, + "idHead": 9, + "maxHit": 400, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 300, + "magicResistance": 0 + }, + "562": { + "hp": 45000, + "def": 10, + "exp": 45000, + "defM": 0, + "drop": [], + "gold": 25000, + "name": "Dark Jennifer", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 45000, + "idBody": 64, + "idHead": 72, + "maxHit": 400, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 300, + "magicResistance": 0 + }, + "564": { + "hp": 32, + "def": 0, + "exp": 32, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Escorpión", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 32, + "idBody": 51, + "idHead": 0, + "maxHit": 3, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 30, + "poderEvasion": 10, + "magicResistance": 0 + }, + "565": { + "hp": 22, + "def": 0, + "exp": 15, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Serpiente", + "objs": [], + "snd1": 127, + "snd2": 128, + "maxHp": 22, + "idBody": 13, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 0, + "magicResistance": 0 + }, + "566": { + "hp": 10, + "def": 0, + "exp": 12, + "defM": 0, + "drop": [], + "gold": 0, + "name": "Murciélago", + "objs": [], + "snd1": 125, + "snd2": 125, + "maxHp": 10, + "idBody": 9, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 15, + "poderEvasion": 10, + "magicResistance": 0 + }, + "567": { + "hp": 310, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 75, + "item": 12 + }, + { + "cant": 150, + "item": 12 + }, + { + "cant": 5, + "item": 199 + } + ], + "gold": 0, + "name": "Duende Mágico", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 310, + "idBody": 75, + "idHead": 0, + "maxHit": 10, + "minHit": 2, + "spells": [ + { + "idSpell": 2, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 80, + "poderEvasion": 60, + "magicResistance": 0 + }, + "568": { + "hp": 4000, + "def": 0, + "exp": 6000, + "defM": 0, + "drop": [ + { + "cant": 5000, + "item": 12 + }, + { + "cant": 1, + "item": 474 + }, + { + "cant": 1, + "item": 475 + }, + { + "cant": 1, + "item": 476 + } + ], + "gold": 0, + "name": "Galeón Pirata", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 4000, + "idBody": 86, + "idHead": 0, + "maxHit": 250, + "minHit": 200, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 150, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "569": { + "hp": 7000, + "def": 50, + "exp": 10500, + "defM": 0, + "drop": [ + { + "cant": 3500, + "item": 12 + }, + { + "cant": 1, + "item": 474 + }, + { + "cant": 1, + "item": 475 + } + ], + "gold": 0, + "name": "Galeón Fantasmal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 7000, + "idBody": 487, + "idHead": 0, + "maxHit": 350, + "minHit": 150, + "spells": [ + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 450, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "573": { + "hp": 10, + "def": 0, + "exp": 100, + "defM": 0, + "desc": "¡Odio los leñadores!", + "drop": [ + { + "cant": 15, + "item": 58 + } + ], + "gold": 0, + "name": "Árbol Encantado", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 10, + "idBody": 25, + "idHead": 0, + "maxHit": 2, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 500, + "poderEvasion": 10, + "magicResistance": 0 + }, + "574": { + "hp": 1500, + "def": 0, + "exp": 900, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 58 + }, + { + "cant": 100, + "item": 58 + }, + { + "cant": 500, + "item": 58 + }, + { + "cant": 3000, + "item": 58 + }, + { + "cant": 10000, + "item": 58 + } + ], + "gold": 0, + "name": "Ucorno", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1500, + "idBody": 25, + "idHead": 0, + "maxHit": 90, + "minHit": 85, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 100, + "magicResistance": 0 + }, + "575": { + "hp": 1500, + "def": 0, + "exp": 100, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 58 + }, + { + "cant": 1, + "item": 208 + }, + { + "cant": 5, + "item": 696 + } + ], + "gold": 0, + "name": "Árbol de Jungla", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1500, + "idBody": 25, + "idHead": 0, + "maxHit": 250, + "minHit": 150, + "npcType": 0, + "magicDef": 0, + "movement": 2, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 100, + "magicResistance": 0 + }, + "576": { + "hp": 10, + "def": 0, + "exp": 100, + "defM": 0, + "drop": [ + { + "cant": 15, + "item": 58 + } + ], + "gold": 0, + "name": "Árbol de Jungla", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 10, + "idBody": 25, + "idHead": 0, + "maxHit": 2, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 500, + "poderEvasion": 10, + "magicResistance": 0 + }, + "577": { + "hp": 700, + "def": 0, + "exp": 500, + "defM": 0, + "desc": "Soy un esclavo del mi señor del mal", + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 100, + "item": 12 + }, + { + "cant": 1, + "item": 645 + } + ], + "gold": 0, + "name": "Alma Putrefacta", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 700, + "idBody": 145, + "idHead": 514, + "maxHit": 100, + "minHit": 90, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 140, + "magicResistance": 0 + }, + "578": { + "hp": 35, + "def": 0, + "exp": 15, + "defM": 0, + "desc": "Oink Oink", + "drop": [ + { + "cant": 1, + "item": 27 + }, + { + "cant": 10, + "item": 12 + } + ], + "gold": 0, + "name": "Cerdo Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 35, + "idBody": 6, + "idHead": 0, + "maxHit": 2, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 15, + "magicResistance": 0 + }, + "579": { + "hp": 65, + "def": 2, + "exp": 25, + "defM": 0, + "desc": "Muuuuu", + "drop": [ + { + "cant": 5, + "item": 27 + }, + { + "cant": 50, + "item": 12 + } + ], + "gold": 0, + "name": "Vaca Salvaje", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 65, + "idBody": 7, + "idHead": 0, + "maxHit": 5, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 25, + "poderEvasion": 15, + "magicResistance": 0 + }, + "582": { + "hp": 3000, + "def": 30, + "exp": 4740, + "defM": 0, + "drop": [ + { + "cant": 500, + "item": 12 + }, + { + "cant": 50, + "item": 38 + }, + { + "cant": 1, + "item": 159 + }, + { + "cant": 2, + "item": 243 + }, + { + "cant": 3, + "item": 393 + } + ], + "gold": 0, + "name": "Enano de las Profundidades", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 3000, + "idBody": 54, + "idHead": 304, + "maxHit": 165, + "minHit": 93, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 140, + "magicResistance": 0 + }, + "583": { + "hp": 10000, + "def": 30, + "exp": 10000, + "defM": 0, + "drop": [ + { + "cant": 10000, + "item": 12 + }, + { + "cant": 15000, + "item": 12 + }, + { + "cant": 2, + "item": 361 + }, + { + "cant": 3, + "item": 206 + }, + { + "cant": 3, + "item": 400 + } + ], + "gold": 0, + "name": "Liche Infernal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 10000, + "idBody": 149, + "idHead": 0, + "maxHit": 250, + "minHit": 120, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 23, + "cooldownSeconds": 0 + }, + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 190, + "magicResistance": 0 + }, + "584": { + "hp": 14000, + "def": 0, + "exp": 21000, + "defM": 0, + "drop": [ + { + "cant": 8000, + "item": 12 + }, + { + "cant": 16000, + "item": 12 + }, + { + "cant": 3, + "item": 126 + }, + { + "cant": 5, + "item": 129 + }, + { + "cant": 2, + "item": 495 + } + ], + "gold": 0, + "name": "Demonio Infernal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 14000, + "idBody": 83, + "idHead": 0, + "maxHit": 350, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 230, + "poderEvasion": 220, + "magicResistance": 0 + }, + "585": { + "hp": 5000, + "def": 50, + "exp": 7900, + "defM": 0, + "drop": [ + { + "cant": 4000, + "item": 12 + }, + { + "cant": 6000, + "item": 12 + }, + { + "cant": 10000, + "item": 12 + }, + { + "cant": 50000, + "item": 12 + }, + { + "cant": 1, + "item": 402 + } + ], + "gold": 0, + "name": "Dragón Rojo Infernal", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5000, + "idBody": 41, + "idHead": 0, + "maxHit": 300, + "minHit": 250, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 380, + "poderEvasion": 250, + "magicResistance": 0 + }, + "586": { + "hp": 42000, + "def": 40, + "exp": 78960, + "defM": 0, + "drop": [ + { + "cant": 15000, + "item": 12 + }, + { + "cant": 1000, + "item": 38 + }, + { + "cant": 2000, + "item": 37 + }, + { + "cant": 100000, + "item": 12 + }, + { + "cant": 2, + "item": 485 + } + ], + "gold": 0, + "name": "Golem Infernal", + "objs": [], + "snd1": 113, + "snd2": 113, + "maxHp": 42000, + "idBody": 181, + "idHead": 0, + "maxHit": 600, + "minHit": 300, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 300, + "poderEvasion": 50, + "magicResistance": 0 + }, + "587": { + "hp": 3500, + "def": 30, + "exp": 5740, + "defM": 0, + "drop": [ + { + "cant": 2500, + "item": 12 + }, + { + "cant": 200, + "item": 37 + }, + { + "cant": 1, + "item": 361 + }, + { + "cant": 2, + "item": 530 + }, + { + "cant": 3, + "item": 699 + } + ], + "gold": 0, + "name": "Astaroth", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 3500, + "idBody": 204, + "idHead": 0, + "maxHit": 126, + "minHit": 75, + "spells": [ + { + "idSpell": 8, + "cooldownSeconds": 0 + }, + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 15, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 150, + "magicResistance": 0 + }, + "588": { + "hp": 600, + "def": 30, + "exp": 100, + "defM": 0, + "drop": [ + { + "cant": 500, + "item": 12 + }, + { + "cant": 100, + "item": 38 + }, + { + "cant": 1, + "item": 239 + }, + { + "cant": 1, + "item": 205 + } + ], + "gold": 0, + "name": "Elemental de Fuego", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 153, + "idHead": 0, + "maxHit": 250, + "minHit": 75, + "spells": [ + { + "idSpell": 8, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 70, + "magicResistance": 0 + }, + "589": { + "hp": 7000, + "def": 30, + "exp": 10920, + "defM": 0, + "drop": [ + { + "cant": 10000, + "item": 12 + }, + { + "cant": 15000, + "item": 12 + }, + { + "cant": 2, + "item": 361 + }, + { + "cant": 1, + "item": 536 + }, + { + "cant": 1, + "item": 532 + } + ], + "gold": 0, + "name": "Elemental de Fuego Anciano", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 7000, + "idBody": 394, + "idHead": 0, + "maxHit": 140, + "minHit": 75, + "spells": [ + { + "idSpell": 15, + "cooldownSeconds": 0 + }, + { + "idSpell": 25, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 250, + "magicResistance": 0 + }, + "590": { + "hp": 435, + "def": 10, + "exp": 550, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 150, + "item": 12 + }, + { + "cant": 300, + "item": 12 + }, + { + "cant": 600, + "item": 12 + }, + { + "cant": 1200, + "item": 12 + } + ], + "gold": 0, + "name": "Blattodea", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 435, + "idBody": 231, + "idHead": 0, + "maxHit": 90, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 50, + "magicResistance": 0 + }, + "591": { + "hp": 700, + "def": 10, + "exp": 1100, + "defM": 0, + "drop": [ + { + "cant": 125, + "item": 12 + }, + { + "cant": 200, + "item": 12 + }, + { + "cant": 300, + "item": 12 + }, + { + "cant": 500, + "item": 12 + }, + { + "cant": 1500, + "item": 12 + } + ], + "gold": 0, + "name": "Artopleura", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 700, + "idBody": 229, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 80, + "magicResistance": 0 + }, + "592": { + "hp": 650, + "def": 0, + "exp": 700, + "defM": 0, + "drop": [ + { + "cant": 75, + "item": 12 + }, + { + "cant": 1, + "item": 619 + }, + { + "cant": 1, + "item": 202 + } + ], + "gold": 0, + "name": "Araña de los Rincones", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 650, + "idBody": 230, + "idHead": 0, + "maxHit": 75, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 110, + "poderEvasion": 70, + "magicResistance": 0 + }, + "593": { + "hp": 700, + "def": 0, + "exp": 1050, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 1, + "item": 19 + }, + { + "cant": 1, + "item": 125 + }, + { + "cant": 1, + "item": 873 + } + ], + "gold": 0, + "name": "Pirata", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 700, + "idBody": 386, + "idHead": 509, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 85, + "magicResistance": 0 + }, + "594": { + "hp": 550, + "def": 0, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 150, + "item": 12 + }, + { + "cant": 200, + "item": 12 + }, + { + "cant": 600, + "item": 12 + } + ], + "gold": 0, + "name": "Escarabajo Gigante", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 550, + "idBody": 391, + "idHead": 0, + "maxHit": 80, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 45, + "magicResistance": 0 + }, + "595": { + "hp": 600, + "def": 0, + "exp": 900, + "defM": 0, + "drop": [ + { + "cant": 70, + "item": 12 + }, + { + "cant": 140, + "item": 12 + }, + { + "cant": 200, + "item": 12 + } + ], + "gold": 0, + "name": "Tentáculo Putrefacto", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 392, + "idHead": 0, + "maxHit": 80, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 80, + "poderEvasion": 100, + "magicResistance": 0 + }, + "596": { + "hp": 400, + "def": 5, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 10, + "item": 38 + } + ], + "gold": 0, + "name": "Espíritu chocarrero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 400, + "idBody": 219, + "idHead": 0, + "maxHit": 130, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 150, + "magicResistance": 0 + }, + "597": { + "hp": 435, + "def": 10, + "exp": 455, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 65, + "item": 12 + }, + { + "cant": 1, + "item": 380 + } + ], + "gold": 0, + "name": "Protector de los muertos", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 435, + "idBody": 426, + "idHead": 0, + "maxHit": 90, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 50, + "magicResistance": 0 + }, + "598": { + "hp": 650, + "def": 0, + "exp": 700, + "defM": 0, + "drop": [ + { + "cant": 75, + "item": 12 + }, + { + "cant": 2, + "item": 978 + }, + { + "cant": 2, + "item": 697 + }, + { + "cant": 1, + "item": 531 + } + ], + "gold": 0, + "name": "Hada Oscura", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 650, + "idBody": 427, + "idHead": 511, + "maxHit": 75, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 110, + "poderEvasion": 70, + "magicResistance": 0 + }, + "599": { + "hp": 550, + "def": 0, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 75, + "item": 12 + }, + { + "cant": 12, + "item": 38 + }, + { + "cant": 4, + "item": 398 + }, + { + "cant": 1, + "item": 30 + } + ], + "gold": 0, + "name": "Engendro de las profundidades", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 550, + "idBody": 387, + "idHead": 0, + "maxHit": 80, + "minHit": 35, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 45, + "magicResistance": 0 + }, + "600": { + "hp": 700, + "def": 10, + "exp": 900, + "defM": 0, + "drop": [ + { + "cant": 125, + "item": 12 + }, + { + "cant": 50, + "item": 551 + }, + { + "cant": 50, + "item": 552 + } + ], + "gold": 0, + "name": "Ave de las profundidades", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 700, + "idBody": 425, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 80, + "magicResistance": 0 + }, + "601": { + "hp": 600, + "def": 10, + "exp": 850, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 20, + "item": 414 + }, + { + "cant": 5, + "item": 1002 + } + ], + "gold": 0, + "name": "Licántropo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 212, + "idHead": 0, + "maxHit": 100, + "minHit": 65, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 65, + "magicResistance": 0 + }, + "602": { + "hp": 800, + "def": 15, + "exp": 1150, + "defM": 0, + "drop": [ + { + "cant": 150, + "item": 12 + }, + { + "cant": 275, + "item": 12 + }, + { + "cant": 10, + "item": 36 + } + ], + "gold": 0, + "name": "Piraña", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 800, + "idBody": 433, + "idHead": 0, + "maxHit": 75, + "minHit": 110, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 70, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "603": { + "hp": 1300, + "def": 25, + "exp": 1950, + "defM": 0, + "drop": [ + { + "cant": 230, + "item": 12 + }, + { + "cant": 2, + "item": 208 + }, + { + "cant": 1, + "item": 542 + } + ], + "gold": 0, + "name": "Sirena", + "objs": [], + "snd1": 143, + "snd2": 54, + "maxHp": 1300, + "idBody": 434, + "idHead": 0, + "maxHit": 110, + "minHit": 85, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "604": { + "hp": 600, + "def": 15, + "exp": 550, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 20, + "item": 38 + }, + { + "cant": 4, + "item": 36 + } + ], + "gold": 0, + "name": "Manta raya", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 600, + "idBody": 407, + "idHead": 0, + "maxHit": 55, + "minHit": 45, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 120, + "poderEvasion": 45, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "605": { + "hp": 2100, + "def": 20, + "exp": 2730, + "defM": 0, + "drop": [ + { + "cant": 550, + "item": 12 + }, + { + "cant": 15, + "item": 36 + }, + { + "cant": 15, + "item": 39 + }, + { + "cant": 13500, + "item": 12 + } + ], + "gold": 0, + "name": "Lutra", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2100, + "idBody": 435, + "idHead": 0, + "maxHit": 115, + "minHit": 60, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 150, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "606": { + "hp": 2500, + "def": 15, + "exp": 3750, + "defM": 0, + "drop": [ + { + "cant": 150, + "item": 12 + }, + { + "cant": 25, + "item": 38 + }, + { + "cant": 5, + "item": 39 + }, + { + "cant": 2100, + "item": 12 + } + ], + "gold": 0, + "name": "Tiburón", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2500, + "idBody": 436, + "idHead": 0, + "maxHit": 175, + "minHit": 100, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 180, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "607": { + "hp": 2700, + "def": 35, + "exp": 4050, + "defM": 0, + "drop": [ + { + "cant": 1000, + "item": 12 + }, + { + "cant": 1640, + "item": 12 + }, + { + "cant": 2, + "item": 125 + }, + { + "cant": 1, + "item": 474 + } + ], + "gold": 0, + "name": "Capitán Pirata", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2700, + "idBody": 428, + "idHead": 512, + "maxHit": 250, + "minHit": 130, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 190, + "magicResistance": 0 + }, + "608": { + "hp": 2000, + "def": 15, + "exp": 3500, + "defM": 0, + "drop": [ + { + "cant": 400, + "item": 12 + }, + { + "cant": 50, + "item": 37 + }, + { + "cant": 130, + "item": 38 + }, + { + "cant": 1, + "item": 361 + } + ], + "gold": 0, + "name": "Guardián de las Profundidades", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2000, + "idBody": 218, + "idHead": 0, + "maxHit": 300, + "minHit": 100, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 210, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "609": { + "hp": 7000, + "def": 30, + "exp": 9800, + "defM": 0, + "drop": [ + { + "cant": 10000, + "item": 12 + }, + { + "cant": 19050, + "item": 12 + }, + { + "cant": 1, + "item": 657 + }, + { + "cant": 1, + "item": 542 + }, + { + "cant": 1, + "item": 537 + } + ], + "gold": 0, + "name": "Elemental de Agua Anciano", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 7000, + "idBody": 542, + "idHead": 0, + "maxHit": 250, + "minHit": 150, + "spells": [ + { + "idSpell": 31, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 250, + "poderEvasion": 250, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "610": { + "hp": 6250, + "def": 15, + "exp": 10500, + "defM": 0, + "drop": [ + { + "cant": 1200, + "item": 12 + }, + { + "cant": 110, + "item": 38 + }, + { + "cant": 1, + "item": 405 + }, + { + "cant": 1, + "item": 206 + }, + { + "cant": 1, + "item": 475 + } + ], + "gold": 0, + "name": "Kraken", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 6250, + "idBody": 408, + "idHead": 0, + "maxHit": 220, + "minHit": 95, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 170, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "611": { + "hp": 750, + "def": 0, + "exp": 800, + "defM": 0, + "drop": [ + { + "cant": 100, + "item": 12 + }, + { + "cant": 18, + "item": 38 + }, + { + "cant": 30, + "item": 37 + }, + { + "cant": 10, + "item": 36 + }, + { + "cant": 10, + "item": 39 + } + ], + "gold": 0, + "name": "Fango", + "objs": [], + "snd1": 114, + "snd2": 180, + "maxHp": 750, + "idBody": 403, + "idHead": 0, + "maxHit": 110, + "minHit": 50, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 70, + "magicResistance": 0 + }, + "612": { + "hp": 550, + "def": 10, + "exp": 1000, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 619 + }, + { + "cant": 1, + "item": 365 + } + ], + "gold": 0, + "name": "Cocodrilo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 550, + "idBody": 208, + "idHead": 0, + "maxHit": 100, + "minHit": 75, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 220, + "poderEvasion": 60, + "magicResistance": 0 + }, + "613": { + "hp": 1350, + "def": 5, + "exp": 1900, + "defM": 0, + "drop": [ + { + "cant": 150, + "item": 12 + }, + { + "cant": 23, + "item": 37 + }, + { + "cant": 1, + "item": 658 + }, + { + "cant": 1, + "item": 697 + }, + { + "cant": 1, + "item": 698 + } + ], + "gold": 0, + "name": "Unicornio", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1350, + "idBody": 183, + "idHead": 0, + "maxHit": 115, + "minHit": 60, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "magicResistance": 0 + }, + "614": { + "hp": 1500, + "def": 15, + "exp": 2300, + "defM": 0, + "drop": [ + { + "cant": 500, + "item": 12 + }, + { + "cant": 115, + "item": 480 + }, + { + "cant": 3, + "item": 479 + }, + { + "cant": 200, + "item": 551 + }, + { + "cant": 6, + "item": 665 + } + ], + "gold": 0, + "name": "Elfo Guardián", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1500, + "idBody": 402, + "idHead": 0, + "maxHit": 115, + "minHit": 60, + "spells": [ + { + "idSpell": 44, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 135, + "poderEvasion": 150, + "magicResistance": 0 + }, + "615": { + "hp": 2500, + "def": 15, + "exp": 3100, + "defM": 0, + "drop": [ + { + "cant": 1000, + "item": 12 + }, + { + "cant": 2, + "item": 1005 + }, + { + "cant": 300, + "item": 38 + }, + { + "cant": 1, + "item": 203 + } + ], + "gold": 0, + "name": "Árbol capturador de Almas", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 2500, + "idBody": 401, + "idHead": 0, + "maxHit": 150, + "minHit": 100, + "spells": [ + { + "idSpell": 24, + "cooldownSeconds": 0 + }, + { + "idSpell": 45, + "cooldownSeconds": 0 + } + ], + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 60, + "magicResistance": 0 + }, + "616": { + "hp": 35, + "def": 0, + "exp": 60, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 500, + "item": 12 + }, + { + "cant": 5000, + "item": 12 + } + ], + "gold": 0, + "name": "Sapo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 35, + "idBody": 432, + "idHead": 0, + "maxHit": 7, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 40, + "poderEvasion": 35, + "magicResistance": 0 + }, + "617": { + "hp": 200, + "def": 5, + "exp": 175, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 12 + }, + { + "cant": 10, + "item": 855 + }, + { + "cant": 20, + "item": 858 + }, + { + "cant": 1, + "item": 126 + } + ], + "gold": 0, + "name": "Pequeño Escarabajo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 200, + "idBody": 533, + "idHead": 0, + "maxHit": 14, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 75, + "poderEvasion": 25, + "magicResistance": 0 + }, + "618": { + "hp": 100, + "def": 8, + "exp": 80, + "defM": 0, + "drop": [ + { + "cant": 65, + "item": 12 + }, + { + "cant": 24, + "item": 856 + }, + { + "cant": 1, + "item": 208 + }, + { + "cant": 1, + "item": 696 + } + ], + "gold": 0, + "name": "Pequeño Duende", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 100, + "idBody": 75, + "idHead": 0, + "maxHit": 17, + "minHit": 10, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 90, + "poderEvasion": 100, + "magicResistance": 0 + }, + "619": { + "hp": 278, + "def": 5, + "exp": 417, + "defM": 0, + "drop": [ + { + "cant": 40, + "item": 12 + }, + { + "cant": 85, + "item": 12 + }, + { + "cant": 20, + "item": 857 + }, + { + "cant": 1, + "item": 658 + }, + { + "cant": 1, + "item": 205 + } + ], + "gold": 0, + "name": "Hada del Bosque", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 278, + "idBody": 532, + "idHead": 0, + "maxHit": 23, + "minHit": 15, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 150, + "magicResistance": 0 + }, + "620": { + "hp": 125, + "def": 4, + "exp": 75, + "defM": 0, + "drop": [ + { + "cant": 12, + "item": 12 + }, + { + "cant": 110, + "item": 12 + }, + { + "cant": 1, + "item": 366 + }, + { + "cant": 1, + "item": 30 + } + ], + "gold": 0, + "name": "Pequeña Araña", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 125, + "idBody": 534, + "idHead": 0, + "maxHit": 8, + "minHit": 3, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 50, + "poderEvasion": 20, + "magicResistance": 0 + }, + "621": { + "hp": 150, + "def": 4, + "exp": 135, + "defM": 0, + "drop": [ + { + "cant": 20, + "item": 12 + }, + { + "cant": 15, + "item": 480 + }, + { + "cant": 30, + "item": 855 + }, + { + "cant": 20, + "item": 1009 + } + ], + "gold": 0, + "name": "Piraña", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 150, + "idBody": 433, + "idHead": 0, + "maxHit": 16, + "minHit": 10, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 75, + "poderEvasion": 25, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "622": { + "hp": 200, + "def": 5, + "exp": 160, + "defM": 0, + "drop": [ + { + "cant": 40, + "item": 12 + }, + { + "cant": 25, + "item": 480 + }, + { + "cant": 150, + "item": 480 + }, + { + "cant": 1, + "item": 664 + }, + { + "cant": 1, + "item": 665 + } + ], + "gold": 0, + "name": "Pequeño Dragón Mágico", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 200, + "idBody": 537, + "idHead": 0, + "maxHit": 17, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 84, + "poderEvasion": 27, + "tierraInvalida": 1, + "magicResistance": 0 + }, + "623": { + "hp": 210, + "def": 3, + "exp": 120, + "defM": 0, + "drop": [ + { + "cant": 50, + "item": 12 + }, + { + "cant": 75, + "item": 12 + }, + { + "cant": 1, + "item": 205 + }, + { + "cant": 3, + "item": 123 + } + ], + "gold": 0, + "name": "Mini Elemental de Fuego", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 210, + "idBody": 535, + "idHead": 0, + "maxHit": 13, + "minHit": 5, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 45, + "poderEvasion": 30, + "magicResistance": 0 + }, + "624": { + "hp": 300, + "def": 7, + "exp": 500, + "defM": 0, + "drop": [ + { + "cant": 25, + "item": 12 + }, + { + "cant": 25, + "item": 12 + }, + { + "cant": 1, + "item": 204 + }, + { + "cant": 3, + "item": 990 + } + ], + "gold": 0, + "name": "Pequeño Dragón Rojo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 300, + "idBody": 536, + "idHead": 0, + "maxHit": 13, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 40, + "poderEvasion": 50, + "magicResistance": 0 + }, + "625": { + "hp": 22, + "def": 3, + "exp": 30, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 38 + }, + { + "cant": 1, + "item": 200 + }, + { + "cant": 1, + "item": 202 + } + ], + "gold": 0, + "name": "Víbora", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 22, + "idBody": 13, + "idHead": 0, + "maxHit": 6, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 35, + "poderEvasion": 20, + "magicResistance": 0 + }, + "626": { + "hp": 75, + "def": 2, + "exp": 70, + "defM": 0, + "drop": [ + { + "cant": 6, + "item": 12 + }, + { + "cant": 10, + "item": 480 + }, + { + "cant": 1, + "item": 479 + } + ], + "gold": 0, + "name": "Goblin Travieso", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 75, + "idBody": 14, + "idHead": 0, + "maxHit": 9, + "minHit": 2, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 58, + "poderEvasion": 15, + "magicResistance": 0 + }, + "627": { + "hp": 130, + "def": 10, + "exp": 100, + "defM": 0, + "drop": [ + { + "cant": 41, + "item": 12 + }, + { + "cant": 41, + "item": 12 + }, + { + "cant": 1, + "item": 364 + }, + { + "cant": 2, + "item": 19 + }, + { + "cant": 1, + "item": 470 + } + ], + "gold": 0, + "name": "Protector de almas", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 130, + "idBody": 426, + "idHead": 0, + "maxHit": 23, + "minHit": 15, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 69, + "poderEvasion": 55, + "magicResistance": 0 + }, + "628": { + "hp": 95, + "def": 2, + "exp": 95, + "defM": 0, + "drop": [ + { + "cant": 5, + "item": 166 + }, + { + "cant": 1, + "item": 201 + }, + { + "cant": 1, + "item": 396 + } + ], + "gold": 0, + "name": "Tarántula venenosa", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 95, + "idBody": 538, + "idHead": 0, + "maxHit": 15, + "minHit": 6, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 63, + "poderEvasion": 32, + "magicResistance": 0 + }, + "629": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los guerreros se especializan en el combate cuerpo a cuerpo por lo que las criaturas más adecuadas para ellos son aquellas que no poseen demasiada evasión y poder de ataque.", + "drop": [], + "gold": 0, + "name": "Guerrero tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 54, + "idHead": 301, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "630": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los guerreros prefieren entrenar en el desierto al este de la ciudad.", + "drop": [], + "gold": 0, + "name": "Guerrero tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 54, + "idHead": 370, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "631": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los cazadores se especializan en el combate a distancia, por lo que las criaturas más indicadas para ellos, son las que no tengan un gran poder de evasión.", + "drop": [], + "gold": 0, + "name": "Cazador tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 155, + "idHead": 303, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "632": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los cazadores, prefieren entrenar en el bosque, al oeste de la ciudad", + "drop": [], + "gold": 0, + "name": "Cazador tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 58, + "idHead": 4, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "633": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los piratas pueden combatir tanto cuerpo a cuerpo como a distancia, por lo tanto, las criaturas más adecuadas para ellos, son aquellas que no posean gran poder de evasión ni de ataque.", + "drop": [], + "gold": 0, + "name": "Pirata tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 161, + "idHead": 380, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "634": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Por sus características, dentro de esta isla, los piratas, pueden entrenar tanto en el desierto (cuerpo a cuerpo) como en el bosque (a distancia), al este u oeste de la ciudad.", + "drop": [], + "gold": 0, + "name": "Pirata tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 161, + "idHead": 305, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "635": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los bandidos se especializan en el combate cuerpo a cuerpo, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean un gran poder de evasión ni de ataque.", + "drop": [], + "gold": 0, + "name": "Bandido tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 321, + "idHead": 9, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "636": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los bandidos, prefieren entrenar en el desierto, al este de la ciudad.", + "drop": [], + "gold": 0, + "name": "Bandido tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 321, + "idHead": 72, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "637": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los ladrones se destacan en el arte de ocultarse y el combate a distancia, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean gran poder de evasión.", + "drop": [], + "gold": 0, + "name": "Ladron tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 337, + "idHead": 75, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "638": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los ladrones, prefieren entrenar en el bosque, al oeste de la ciudad", + "drop": [], + "gold": 0, + "name": "Ladron tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 337, + "idHead": 37, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "639": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los magos se especializan en ataques mágicos, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean defensa mágica.", + "drop": [], + "gold": 0, + "name": "Mago tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 159, + "idHead": 407, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "640": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los magos, prefieren entrenar en el bosque, al norte de la ciudad", + "drop": [], + "gold": 0, + "name": "Mago tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 353, + "idHead": 109, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "641": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los bardos se especializan en conjurar hechizos y evadir golpes, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean defensa mágica.", + "drop": [], + "gold": 0, + "name": "Bardo Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 130, + "idHead": 220, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "642": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los bardos, prefieren entrenar en el bosque, al norte de la ciudad", + "drop": [], + "gold": 0, + "name": "Bardo Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 319, + "idHead": 179, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "643": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los clérigos combinan la magia con el combate cuerpo a cuerpo, por lo tanto, las criaturas más adecuadas para ellos, son las que no posean defensa mágica o posean bajo ataque y evasión.", + "drop": [], + "gold": 0, + "name": "Clérigo Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 314, + "idHead": 22, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "644": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Por sus características, dentro de esta isla, los clérigos, pueden entrenar tanto en el desierto (cuerpo a cuerpo) como en el bosque (magia), al este o norte de la ciudad.", + "drop": [], + "gold": 0, + "name": "Clérigo Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 314, + "idHead": 112, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "645": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los druidas se especializan en ataques mágicos, por lo tanto, las criaturas más adecuadas para esta clase, son las que no posean defensa mágica. También pueden entrenar con mascotas al domar", + "drop": [], + "gold": 0, + "name": "Druida Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 56, + "idHead": 284, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "646": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla, los druidas, prefieren entrenar en el bosque, al norte de la ciudad", + "drop": [], + "gold": 0, + "name": "Druida Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 56, + "idHead": 115, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "647": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los paladines combinan el golpe cuerpo a cuerpo con la magia, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean un gran poder de evasión ni de ataque.", + "drop": [], + "gold": 0, + "name": "Paladín Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 45, + "idHead": 30, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "648": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla los paladines prefieren entrenar en el desierto al este de la ciudad.", + "drop": [], + "gold": 0, + "name": "Paladín Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 54, + "idHead": 382, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "649": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los asesinos combinan el golpe cuerpo a cuerpo con la magia, por lo tanto, las criaturas más adecuadas para esta clase, son aquellas que no posean un gran poder de evasión ni de ataque.", + "drop": [], + "gold": 0, + "name": "Asesino Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 48, + "idHead": 288, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "650": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Debido a sus características, dentro de esta isla los asesinos prefieren entrenar en el desierto al este de la ciudad.", + "drop": [], + "gold": 0, + "name": "Asesino Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 539, + "idHead": 309, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "651": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Se considera Herrero al trabajador que tenga 100 skills en la habilidad Herrería. Los mismos se encargan de construir armas y armaduras para los aventureros de estas tierras.", + "drop": [], + "gold": 0, + "name": "Herrero Tutor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "652": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Se considera Minero al trabajador que tenga 100 skills en la habilidad Minería. Los mismos se encargan de extraer minerales y transformarlos en lingotes para el uso de los herreros.", + "drop": [], + "gold": 0, + "name": "Minero Tutor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 100, + "idHead": 109, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "653": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Se considera Carpintero al trabajador que tenga 100 skills en la habilidad Carpintería. Los mismos se encargan de construir todo tipo de objetos que requieran madera.", + "drop": [], + "gold": 0, + "name": "Carpintero Tutor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 373, + "idHead": 308, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "654": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Se considera Leñador al trabajador que tenga 100 skills en la habilidad Tala. Los mismos se encargan de extraer leña común y leña élfica para el uso de los carpinteros.", + "drop": [], + "gold": 0, + "name": "Leñador Tutor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 382, + "idHead": 216, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "655": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Se considera Pescador al trabajador que tenga 100 skills en la habilidad Pesca. Los mismos se encargan de extraer peces de los ríos y mares para el venderselos al mejor postor.", + "drop": [], + "gold": 0, + "name": "Pescador Tutor", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 301, + "idHead": 410, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "656": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Los trabajadores pueden combatir tanto cuerpo a cuerpo como a distancia, por lo tanto, las criaturas más adecuadas para ellos, son aquellas que no posean gran poder de evasión ni de ataque.", + "drop": [], + "gold": 0, + "name": "Trabajador Tutor I", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 348, + "idHead": 172, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "657": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Por sus características, dentro de esta isla, los trabajadores, pueden entrenar tanto en el desierto (cuerpo a cuerpo) como en el bosque (a distancia), al este u oeste de la ciudad.", + "drop": [], + "gold": 0, + "name": "Trabajador Tutor II", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 376, + "idHead": 372, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "900": { + "hp": 8000, + "def": 10, + "exp": 10000, + "defM": 0, + "drop": [], + "gold": 5000, + "name": "Sacerdote Pretoriano", + "objs": [ + { + "cant": 1, + "item": 986 + }, + { + "cant": 300, + "item": 37 + }, + { + "cant": 300, + "item": 38 + }, + { + "cant": 1, + "item": 361 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 8000, + "idBody": 38, + "idHead": 74, + "maxHit": 140, + "minHit": 70, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 10, + "cooldownSeconds": 0 + }, + { + "idSpell": 39, + "cooldownSeconds": 0 + }, + { + "idSpell": 5, + "cooldownSeconds": 0 + }, + { + "idSpell": 36, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 120, + "magicResistance": 0 + }, + "901": { + "hp": 15000, + "def": 20, + "exp": 5000, + "defM": 0, + "drop": [], + "gold": 5000, + "name": "Guerrero Pretoriano", + "objs": [ + { + "cant": 1, + "item": 129 + }, + { + "cant": 1, + "item": 195 + }, + { + "cant": 1, + "item": 131 + }, + { + "cant": 1, + "item": 128 + }, + { + "cant": 30, + "item": 36 + }, + { + "cant": 30, + "item": 39 + }, + { + "cant": 300, + "item": 38 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 15000, + "idBody": 45, + "idHead": 7, + "maxHit": 200, + "minHit": 180, + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 600, + "poderEvasion": 250, + "magicResistance": 0 + }, + "902": { + "hp": 5000, + "def": 10, + "exp": 10000, + "defM": 0, + "drop": [], + "gold": 10000, + "name": "Mago Pretoriano", + "objs": [ + { + "cant": 1, + "item": 238 + }, + { + "cant": 400, + "item": 37 + }, + { + "cant": 1, + "item": 660 + }, + { + "cant": 1, + "item": 662 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 5000, + "idBody": 49, + "idHead": 104, + "maxHit": 60, + "minHit": 30, + "spells": [ + { + "idSpell": 25, + "cooldownSeconds": 0 + }, + { + "idSpell": 38, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 80, + "magicResistance": 0 + }, + "903": { + "hp": 10000, + "def": 10, + "exp": 5000, + "defM": 0, + "drop": [], + "gold": 5000, + "name": "Cazador Pretoriano", + "objs": [ + { + "cant": 1, + "item": 665 + }, + { + "cant": 125, + "item": 553 + }, + { + "cant": 1, + "item": 648 + }, + { + "cant": 1, + "item": 131 + }, + { + "cant": 1, + "item": 128 + }, + { + "cant": 20, + "item": 36 + }, + { + "cant": 20, + "item": 39 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 10000, + "idBody": 110, + "idHead": 301, + "maxHit": 150, + "minHit": 100, + "spells": [ + { + "idSpell": 37, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 350, + "poderEvasion": 220, + "magicResistance": 0 + }, + "904": { + "hp": 12000, + "def": 30, + "exp": 35000, + "defM": 0, + "drop": [], + "gold": 30000, + "name": "Rey Pretoriano", + "objs": [ + { + "cant": 1, + "item": 646 + }, + { + "cant": 1, + "item": 403 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 12000, + "idBody": 63, + "idHead": 14, + "maxHit": 50, + "minHit": 30, + "spells": [ + { + "idSpell": 3, + "cooldownSeconds": 0 + }, + { + "idSpell": 10, + "cooldownSeconds": 0 + }, + { + "idSpell": 30, + "cooldownSeconds": 0 + }, + { + "idSpell": 31, + "cooldownSeconds": 0 + }, + { + "idSpell": 1, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 1000, + "poderEvasion": 270, + "magicResistance": 0 + }, + "905": { + "hp": 8000, + "def": 10, + "exp": 10000, + "defM": 0, + "drop": [], + "gold": 5000, + "name": "Bardo Pretoriano", + "objs": [ + { + "cant": 1, + "item": 525 + }, + { + "cant": 1, + "item": 132 + }, + { + "cant": 300, + "item": 37 + }, + { + "cant": 300, + "item": 38 + }, + { + "cant": 3, + "item": 696 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 8000, + "idBody": 38, + "idHead": 74, + "maxHit": 140, + "minHit": 70, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 10, + "cooldownSeconds": 0 + }, + { + "idSpell": 39, + "cooldownSeconds": 0 + }, + { + "idSpell": 5, + "cooldownSeconds": 0 + }, + { + "idSpell": 36, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 120, + "magicResistance": 0 + }, + "906": { + "hp": 8000, + "def": 10, + "exp": 10000, + "defM": 0, + "drop": [], + "gold": 5000, + "name": "Sacerdote Pretoriano", + "objs": [ + { + "cant": 1, + "item": 986 + }, + { + "cant": 300, + "item": 37 + }, + { + "cant": 300, + "item": 38 + }, + { + "cant": 1, + "item": 361 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 8000, + "idBody": 38, + "idHead": 74, + "maxHit": 140, + "minHit": 70, + "spells": [ + { + "idSpell": 9, + "cooldownSeconds": 0 + }, + { + "idSpell": 10, + "cooldownSeconds": 0 + }, + { + "idSpell": 39, + "cooldownSeconds": 0 + }, + { + "idSpell": 5, + "cooldownSeconds": 0 + }, + { + "idSpell": 36, + "cooldownSeconds": 0 + } + ], + "npcType": 10, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 120, + "magicResistance": 0 + }, + "9000": { + "hp": 1, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "E2E Dummy Melee", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9001": { + "hp": 1, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "E2E Dummy Range", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9002": { + "hp": 1, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "E2E Dummy Spell", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 1, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9003": { + "hp": 100, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "E2E Dummy Support", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 100, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9004": { + "hp": 5, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [], + "gold": 0, + "name": "E2E Dummy Kill", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9005": { + "hp": 100, + "def": 0, + "exp": 15, + "defM": 0, + "drop": [ + { + "cant": 10, + "item": 38 + }, + { + "cant": 1, + "item": 200 + }, + { + "cant": 1, + "item": 202 + } + ], + "gold": 0, + "name": "Serpiente", + "objs": [], + "snd1": 127, + "snd2": 128, + "maxHp": 100, + "idBody": 13, + "idHead": 0, + "maxHit": 1, + "minHit": 1, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 10, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9006": { + "hp": 5, + "def": 0, + "exp": 10, + "defM": 0, + "drop": [], + "gold": 10, + "name": "E2E Dummy Party Reward", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 5, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9007": { + "hp": 5, + "def": 0, + "exp": 0, + "defM": 0, + "drop": [ + { + "cant": 30, + "item": 12 + } + ], + "gold": 0, + "name": "E2E Dummy Party Gold Drop", + "objs": [], + "snd1": 127, + "snd2": 128, + "maxHp": 5, + "idBody": 5, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 0, + "magicDef": 0, + "movement": 0, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9008": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo tunicas, ropas y vestimentas organizadas por tier.", + "drop": [], + "gold": 0, + "name": "Mercader de Tunicas", + "objs": [ + { + "cant": 1000, + "item": 958 + }, + { + "cant": 1000, + "item": 32 + }, + { + "cant": 1000, + "item": 922 + }, + { + "cant": 1000, + "item": 135 + }, + { + "cant": 1000, + "item": 45 + }, + { + "cant": 1000, + "item": 641 + }, + { + "cant": 1000, + "item": 501 + }, + { + "cant": 1000, + "item": 502 + }, + { + "cant": 1000, + "item": 930 + }, + { + "cant": 1000, + "item": 239 + }, + { + "cant": 1000, + "item": 238 + }, + { + "cant": 1000, + "item": 196 + }, + { + "cant": 1000, + "item": 670 + }, + { + "cant": 1000, + "item": 381 + }, + { + "cant": 1000, + "item": 671 + }, + { + "cant": 1000, + "item": 357 + }, + { + "cant": 1000, + "item": 972 + }, + { + "cant": 1000, + "item": 986 + }, + { + "cant": 1000, + "item": 690 + }, + { + "cant": 1000, + "item": 614 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 50, + "idHead": 9, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9009": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo armaduras organizadas por tier para guerreros y aventureros.", + "drop": [], + "gold": 0, + "name": "Mercader de Armaduras", + "objs": [ + { + "cant": 1000, + "item": 30 + }, + { + "cant": 1000, + "item": 668 + }, + { + "cant": 1000, + "item": 359 + }, + { + "cant": 1000, + "item": 667 + }, + { + "cant": 1000, + "item": 360 + }, + { + "cant": 1000, + "item": 648 + }, + { + "cant": 1000, + "item": 195 + }, + { + "cant": 1000, + "item": 243 + }, + { + "cant": 1000, + "item": 500 + }, + { + "cant": 1000, + "item": 496 + }, + { + "cant": 1000, + "item": 495 + }, + { + "cant": 1000, + "item": 356 + }, + { + "cant": 1000, + "item": 854 + }, + { + "cant": 1000, + "item": 390 + }, + { + "cant": 1000, + "item": 392 + }, + { + "cant": 1000, + "item": 497 + }, + { + "cant": 1000, + "item": 391 + }, + { + "cant": 1000, + "item": 393 + }, + { + "cant": 1000, + "item": 1063 + }, + { + "cant": 1000, + "item": 1064 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 93, + "idHead": 6, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9010": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo armas cuerpo a cuerpo y a distancia organizadas por tier.", + "drop": [], + "gold": 0, + "name": "Mercader de Armas", + "objs": [ + { + "cant": 1000, + "item": 15 + }, + { + "cant": 1000, + "item": 3 + }, + { + "cant": 1000, + "item": 9 + }, + { + "cant": 1000, + "item": 165 + }, + { + "cant": 1000, + "item": 365 + }, + { + "cant": 1000, + "item": 164 + }, + { + "cant": 1000, + "item": 19 + }, + { + "cant": 1000, + "item": 2 + }, + { + "cant": 1000, + "item": 398 + }, + { + "cant": 1000, + "item": 401 + }, + { + "cant": 1000, + "item": 399 + }, + { + "cant": 1000, + "item": 366 + }, + { + "cant": 1000, + "item": 367 + }, + { + "cant": 1000, + "item": 619 + }, + { + "cant": 1000, + "item": 123 + }, + { + "cant": 1000, + "item": 159 + }, + { + "cant": 1000, + "item": 124 + }, + { + "cant": 1000, + "item": 990 + }, + { + "cant": 1000, + "item": 559 + }, + { + "cant": 1000, + "item": 403 + }, + { + "cant": 1000, + "item": 129 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 199, + "idHead": 505, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9012": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Tengo cascos, sombreros y escudos organizados por tier.", + "drop": [], + "gold": 0, + "name": "Mercader de Cascos y Escudos", + "objs": [ + { + "cant": 1000, + "item": 661 + }, + { + "cant": 1000, + "item": 1001 + }, + { + "cant": 1000, + "item": 404 + }, + { + "cant": 1000, + "item": 383 + }, + { + "cant": 1000, + "item": 132 + }, + { + "cant": 1000, + "item": 1002 + }, + { + "cant": 1000, + "item": 130 + }, + { + "cant": 1000, + "item": 128 + }, + { + "cant": 1000, + "item": 662 + }, + { + "cant": 1000, + "item": 1003 + }, + { + "cant": 1000, + "item": 131 + }, + { + "cant": 1000, + "item": 405 + }, + { + "cant": 1000, + "item": 1004 + } + ], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 38, + "idHead": 7, + "maxHit": 0, + "minHit": 0, + "npcType": 10, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9013": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Bienvenido al Mercado Global. Aquí podrás publicar, comprar y reclamar subastas.", + "drop": [], + "gold": 0, + "name": "Banquero", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 49, + "idHead": 81, + "maxHit": 0, + "minHit": 0, + "npcType": 12, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 603, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9014": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Banderbill", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9015": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Arkhein", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9016": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Lindos", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9017": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Nix", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9018": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Ciudad Abandonada", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9019": { + "hp": 0, + "def": 0, + "exp": 0, + "defM": 0, + "desc": "Hola, soy el gobernador de Arkhein ¡Bienvenido a nuestra pequeña aldea! Escribe /asignarhogar para obtener la ciudadanía.", + "drop": [], + "gold": 0, + "name": "Gobernador", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 0, + "idBody": 4, + "idHead": 0, + "maxHit": 0, + "minHit": 0, + "npcType": 11, + "magicDef": 0, + "movement": 1, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 0, + "poderEvasion": 0, + "magicResistance": 0 + }, + "9020": { + "desc": "Tengo pasajes para viajar entre los puertos del continente.", + "name": "Capitán del Puerto de Arghal", + "objs": [ + { + "cant": 1000, + "item": 1054 + }, + { + "cant": 1000, + "item": 1055 + }, + { + "cant": 1000, + "item": 1056 + }, + { + "cant": 1000, + "item": 1057 + }, + { + "cant": 1000, + "item": 1058 + }, + { + "cant": 1000, + "item": 1065 + } + ], + "idBody": 428, + "idHead": 512, + "npcType": 10, + "movement": 1 + }, + "9570": { + "hp": 6000, + "def": 10, + "exp": 7800, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 1062 + } + ], + "gold": 4500, + "name": "Lobo Invernal", + "objs": [], + "snd1": 35, + "snd2": 9, + "maxHp": 6000, + "idBody": 551, + "idHead": 0, + "maxHit": 120, + "minHit": 90, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 150, + "poderEvasion": 70, + "magicResistance": 0 + }, + "9571": { + "hp": 15000, + "def": 20, + "exp": 22500, + "defM": 0, + "drop": [ + { + "cant": 1, + "item": 416 + } + ], + "gold": 9000, + "name": "Oso Polar", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 15000, + "idBody": 552, + "idHead": 0, + "maxHit": 150, + "minHit": 120, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 200, + "poderEvasion": 100, + "magicResistance": 0 + }, + "9572": { + "hp": 3000, + "def": 5, + "exp": 3600, + "defM": 0, + "drop": [], + "gold": 2500, + "name": "Pingüino", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 3000, + "idBody": 553, + "idHead": 0, + "maxHit": 80, + "minHit": 60, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 1, + "soundClose": 0, + "poderAtaque": 100, + "poderEvasion": 70, + "magicResistance": 0 + }, + "9573": { + "hp": 28000, + "def": 35, + "exp": 65000, + "defM": 0, + "drop": [], + "gold": 14000, + "name": "Golem de Hielo", + "objs": [], + "snd1": 0, + "snd2": 0, + "maxHp": 28000, + "idBody": 554, + "idHead": 0, + "maxHit": 420, + "minHit": 280, + "npcType": 0, + "magicDef": 0, + "movement": 3, + "aguaValida": 0, + "soundClose": 0, + "poderAtaque": 260, + "poderEvasion": 100, + "magicResistance": 0 + } +} \ No newline at end of file diff --git a/api/src/jsons/objs.json b/api/src/jsons/objs.json new file mode 100644 index 00000000..bdaecb84 --- /dev/null +++ b/api/src/jsons/objs.json @@ -0,0 +1 @@ +{"1":{"name":"Manzana Roja","grhIndex":"506","objType":1},"2":{"name":"Espada Larga","grhIndex":"504","objType":2,"minHit":2,"maxHit":7},"3":{"name":"Hacha","grhIndex":"505","objType":2,"minHit":1,"maxHit":4},"4":{"name":"Árbol","grhIndex":"7000","objType":4},"5":{"name":"Árbol","grhIndex":"7001","objType":4},"6":{"name":"Árbol","grhIndex":"7002","objType":4},"7":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"8":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"9":{"name":"Horquilla","grhIndex":"508","objType":2,"minHit":1,"maxHit":3},"10":{"name":"Cofre Abierto","grhIndex":"502","objType":7},"11":{"name":"Cofre Cerrado","grhIndex":"503","objType":7},"12":{"name":"Monedas de Oro","grhIndex":"511","objType":5},"13":{"name":"Cartel Armas","grhIndex":"500","objType":8},"14":{"name":"Llave","grhIndex":"509","objType":9},"15":{"name":"Daga","grhIndex":"510","objType":2,"minHit":1,"maxHit":2,"apu":1},"16":{"name":"Puerta de madera abierta 2","grhIndex":"5599","objType":6},"17":{"name":"Puerta de madera cerrada 2","grhIndex":"5600","objType":6},"18":{"name":"Cartel Comida","grhIndex":"512","objType":8},"19":{"name":"Espada Dos Manos","grhIndex":"5748","objType":2,"minHit":4,"maxHit":9},"20":{"name":"Lápida","grhIndex":"513","objType":8},"21":{"name":"Porción de Tarta","grhIndex":"515","objType":1},"22":{"name":"Frutas del Bosque","grhIndex":"516","objType":1},"23":{"name":"Pan de Trigo","grhIndex":"517","objType":1},"24":{"name":"Pan de Maíz","grhIndex":"518","objType":1},"25":{"name":"Pastel","grhIndex":"519","objType":1},"26":{"name":"Pollo","grhIndex":"520","objType":1},"27":{"name":"Chuleta","grhIndex":"521","objType":1},"28":{"name":"Queso de Cabra","grhIndex":"522","objType":1},"29":{"name":"Sandía","grhIndex":"523","objType":1},"30":{"name":"Armadura de Cuero","grhIndex":"526","objType":3,"minDef":7,"maxDef":12},"31":{"name":"Vestimentas Comunes","grhIndex":"527","objType":3},"32":{"name":"Vestimentas Comunes","grhIndex":"528","objType":3,"minDef":1,"maxDef":2},"33":{"name":"Cartel Sastre","grhIndex":"530","objType":8},"34":{"name":"Foro de Ullathorpe","grhIndex":"529","objType":10},"35":{"name":"Vestimentas Comunes","grhIndex":"535","objType":3,"minDef":1,"maxDef":2},"36":{"name":"Poción Amarilla","grhIndex":"540","objType":11},"37":{"name":"Poción Azul","grhIndex":"541","objType":11},"38":{"name":"Poción Roja","grhIndex":"542","objType":11},"39":{"name":"Poción Verde","grhIndex":"543","objType":11},"40":{"name":"Atlas Argentum","grhIndex":"538","objType":12},"41":{"name":"Libro Antiguo","grhIndex":"539","objType":12},"42":{"name":"Vino","grhIndex":"536","objType":13},"43":{"name":"Botella de Agua","grhIndex":"537","objType":13},"44":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"45":{"name":"Ropa de Clan","grhIndex":"544","objType":3,"minDef":3,"maxDef":5},"46":{"name":"Árbol","grhIndex":"641","objType":4},"47":{"name":"Árbol","grhIndex":"642","objType":4},"48":{"name":"Árbol","grhIndex":"641","objType":4},"49":{"name":"Árbol","grhIndex":"644","objType":4},"50":{"name":"Árbol","grhIndex":"647","objType":4},"51":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"52":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"53":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"54":{"name":"Llave Ullathorpe Casa Nro 1","grhIndex":"509","objType":9},"55":{"name":"Puerta de madera abierta","grhIndex":"5599","objType":6},"56":{"name":"Puerta de madera cerrada","grhIndex":"5600","objType":6},"57":{"name":"Puerta de madera cerrada","grhIndex":"5600","objType":6},"58":{"name":"Leña","grhIndex":"550","objType":14},"59":{"name":"Cartel Pociones","grhIndex":"546","objType":8},"60":{"name":"Casa Número 1","grhIndex":"547","objType":8},"61":{"name":"Cartel Carpintería","grhIndex":"548","objType":8},"62":{"name":"Cartel Propiedades","grhIndex":"549","objType":8},"63":{"name":"Fogata","grhIndex":"1521","objType":15},"64":{"name":"Manzana Roja","grhIndex":"506","objType":1},"65":{"name":"Llave Ullathorpe Casa Nro 2","grhIndex":"509","objType":9},"66":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"67":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"68":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"69":{"name":"Llave Ullathorpe Casa Nro 3","grhIndex":"509","objType":9},"70":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"71":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"72":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"73":{"name":"Llave Ullathorpe Casa Nro 4","grhIndex":"509","objType":9},"74":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"75":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"76":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"77":{"name":"Casa Número 2","grhIndex":"553","objType":8},"78":{"name":"Casa Número 3","grhIndex":"552","objType":8},"79":{"name":"Casa Número 4","grhIndex":"551","objType":8},"80":{"name":"Ropa de Clan","grhIndex":"554","objType":3,"minDef":3,"maxDef":5},"81":{"name":"Sandía","grhIndex":"523","objType":1},"82":{"name":"Casa Número 5","grhIndex":"555","objType":8},"83":{"name":"Casa Número 6","grhIndex":"556","objType":8},"84":{"name":"Casa Número 7","grhIndex":"567","objType":8},"85":{"name":"Casa Número 8","grhIndex":"568","objType":8},"86":{"name":"Casa Número 9","grhIndex":"569","objType":8},"87":{"name":"Llave Nix casa Nro 1","grhIndex":"509","objType":9},"88":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"89":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"90":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"91":{"name":"Llave Nix Casa Nro 2","grhIndex":"509","objType":9},"92":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"93":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"94":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"95":{"name":"Llave Nix Casa Nro 3","grhIndex":"509","objType":9},"96":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"97":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"98":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"99":{"name":"Llave Nix Casa Nro 4","grhIndex":"509","objType":9},"100":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"101":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"102":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"103":{"name":"Llave Nix Casa Nro 5","grhIndex":"509","objType":9},"104":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"105":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"106":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"107":{"name":"Llave Nix Casa Nro 6","grhIndex":"509","objType":9},"108":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"109":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"110":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"111":{"name":"Llave Nix Casa Nro 7","grhIndex":"509","objType":9},"112":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"113":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"114":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"115":{"name":"Llave Nix Casa Nro 8","grhIndex":"509","objType":9},"116":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"117":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"118":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"119":{"name":"Llave Nix Casa Nro 9","grhIndex":"509","objType":9},"120":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"121":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"122":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"123":{"name":"Espada Vikinga","grhIndex":"561","objType":2,"minHit":5,"maxHit":15},"124":{"name":"Katana","grhIndex":"562","objType":2,"minHit":8,"maxHit":16},"125":{"name":"Sable","grhIndex":"22723","objType":2,"minHit":5,"maxHit":8},"126":{"name":"Hacha Larga de Guerra","grhIndex":"564","objType":2,"minHit":10,"maxHit":18},"127":{"name":"Hacha de Leñador","grhIndex":"565","objType":2},"128":{"name":"Escudo de Hierro","grhIndex":"557","objType":16,"minDef":3,"maxDef":4},"129":{"name":"Hacha de Guerra Dos Filos","grhIndex":"566","objType":2,"minHit":9,"maxHit":18},"130":{"name":"Escudo de Plata","grhIndex":"4860","objType":16,"minDef":2,"maxDef":5},"131":{"name":"Casco de Hierro Completo","grhIndex":"558","objType":17,"minDef":15,"maxDef":20},"132":{"name":"Casco de Hierro","grhIndex":"559","objType":17,"minDef":4,"maxDef":7},"133":{"name":"Escudo Imperial","grhIndex":"560","objType":16,"minDef":1,"maxDef":4},"134":{"name":"Cartel Armaduras","grhIndex":"570","objType":8},"135":{"name":"Túnica Roja Burda","grhIndex":"657","objType":3,"minDef":2,"maxDef":5},"136":{"name":"Ramitas","grhIndex":"572","objType":14},"137":{"name":"Foro","grhIndex":"529","objType":10},"138":{"name":"Caña de Pescar","grhIndex":"574","objType":2},"139":{"name":"Pescado","grhIndex":"573","objType":1},"140":{"name":"Llave Nix Mansión","grhIndex":"509","objType":9},"141":{"name":"Puerta de madera abierta","grhIndex":"9199","objType":6},"142":{"name":"Puerta de madera cerrada","grhIndex":"9193","objType":6},"143":{"name":"Puerta de madera cerrada","grhIndex":"9193","objType":6},"144":{"name":"Teleport a Dungeon Newbie","grhIndex":"669","objType":19},"145":{"name":"Teleport a Nix","grhIndex":"669","objType":19},"146":{"name":"Teleport a Ullathorpe","grhIndex":"669","objType":19},"147":{"name":"Árbol","grhIndex":"7001","objType":4},"148":{"name":"Árbol de la Jungla","grhIndex":"7002","objType":4},"149":{"name":"Árbol","grhIndex":"7000","objType":4},"150":{"name":"Árbol","grhIndex":"7222","objType":4},"151":{"name":"Árbol","grhIndex":"7223","objType":4},"152":{"name":"Árbol","grhIndex":"7224","objType":4},"153":{"name":"Árbol","grhIndex":"7225","objType":4},"154":{"name":"Árbol","grhIndex":"7226","objType":4},"155":{"name":"Cama","grhIndex":"575","objType":20},"156":{"name":"Cama","grhIndex":"576","objType":20},"157":{"name":"Copa de Plata","grhIndex":"579","objType":21},"158":{"name":"Banana","grhIndex":"581","objType":1},"159":{"name":"Hacha de Bárbaro","grhIndex":"583","objType":2,"minHit":8,"maxHit":15},"160":{"name":"Cerveza","grhIndex":"585","objType":13},"161":{"name":"Jugo de Frutas","grhIndex":"584","objType":13},"162":{"name":"Silla","grhIndex":"586","objType":20},"163":{"name":"Cuchara","grhIndex":"587","objType":20},"164":{"name":"Espada Corta","grhIndex":"588","objType":2,"minHit":4,"maxHit":5,"apu":1},"165":{"name":"Daga +1","grhIndex":"4999","objType":2,"minHit":3,"maxHit":4,"apu":1},"166":{"name":"Poción Violeta","grhIndex":"601","objType":11},"167":{"name":"Mueble Rústico","grhIndex":"593","objType":20},"168":{"name":"Silla","grhIndex":"598","objType":20},"169":{"name":"Yunque","grhIndex":"600","objType":27},"171":{"name":"Puerta","grhIndex":"5599","objType":6},"172":{"name":"Puerta","grhIndex":"5600","objType":6},"173":{"name":"Puerta","grhIndex":"5600","objType":6},"174":{"name":"Llave Catacumbas de Ullathorpe","grhIndex":"509","objType":9},"175":{"name":"Puerta","grhIndex":"5599","objType":6},"176":{"name":"Puerta","grhIndex":"5600","objType":6},"177":{"name":"Puerta","grhIndex":"5600","objType":6},"178":{"name":"Llave Dungeon Maleheing","grhIndex":"509","objType":9},"179":{"name":"Puerta","grhIndex":"5599","objType":6},"180":{"name":"Puerta","grhIndex":"5600","objType":6},"181":{"name":"Puerta","grhIndex":"5600","objType":6},"182":{"name":"Llave Dungeon Maleheing","grhIndex":"509","objType":9},"183":{"name":"Llave Fuerte","grhIndex":"509","objType":9},"184":{"name":"Puerta","grhIndex":"5592","objType":6},"185":{"name":"Puerta","grhIndex":"5593","objType":6},"186":{"name":"Puerta","grhIndex":"5593","objType":6},"187":{"name":"Piquete de Minero","grhIndex":"608","objType":2},"188":{"name":"Yacimiento de Hierro","grhIndex":"8616","objType":22},"189":{"name":"Yacimiento de Hierro","grhIndex":"8617","objType":22},"190":{"name":"Yacimiento de Hierro","grhIndex":"8618","objType":22},"191":{"name":"Yacimiento de Hierro","grhIndex":"8619","objType":22},"192":{"name":"Minerales de Hierro","grhIndex":"604","objType":23},"193":{"name":"Minerales de Plata","grhIndex":"632","objType":23},"194":{"name":"Minerales de Oro","grhIndex":"631","objType":23},"195":{"name":"Armadura de Placas Completa","grhIndex":"605","objType":3,"minDef":27,"maxDef":35},"196":{"name":"Túnica de Mago","grhIndex":"606","objType":3,"minDef":7,"maxDef":11},"197":{"name":"Flecha Mágica","grhIndex":"609","objType":24},"198":{"name":"Serrucho","grhIndex":"607","objType":2},"199":{"name":"Dardo Mágico","grhIndex":"609","objType":24},"200":{"name":"Antídoto Mágico","grhIndex":"609","objType":24},"201":{"name":"Curar Heridas Leves","grhIndex":"609","objType":24},"202":{"name":"Toxina","grhIndex":"609","objType":24},"203":{"name":"Curar Heridas Graves","grhIndex":"609","objType":24},"204":{"name":"Flecha Eléctrica","grhIndex":"609","objType":24},"205":{"name":"Proyectil Mágico","grhIndex":"609","objType":24},"206":{"name":"Paralizar","grhIndex":"609","objType":24},"207":{"name":"Remover Parálisis","grhIndex":"609","objType":24},"208":{"name":"Flauta Mágica","grhIndex":"686","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":5},"209":{"name":"Cartel Mineros","grhIndex":"610","objType":8},"210":{"name":"Casa Número 11","grhIndex":"611","objType":8},"211":{"name":"Casa Número 12","grhIndex":"612","objType":8},"212":{"name":"Casa Número 13","grhIndex":"613","objType":8},"213":{"name":"Casa Número 14","grhIndex":"614","objType":8},"214":{"name":"Casa Número 15","grhIndex":"615","objType":8},"215":{"name":"Casa Número 16","grhIndex":"616","objType":8},"216":{"name":"Casa Número 17","grhIndex":"617","objType":8},"217":{"name":"Casa Número 18","grhIndex":"618","objType":8},"218":{"name":"Casa Número 19","grhIndex":"619","objType":8},"219":{"name":"Casa Número 10","grhIndex":"620","objType":8},"220":{"name":"Casa Número 20","grhIndex":"621","objType":8},"221":{"name":"Cartel Hechizos","grhIndex":"622","objType":8},"222":{"name":"Cartel Pescadores","grhIndex":"623","objType":8},"223":{"name":"Cartel","grhIndex":"625","objType":8},"224":{"name":"Cartel","grhIndex":"624","objType":8},"225":{"name":"Cartel Newbie Dungeon","grhIndex":"626","objType":8},"226":{"name":"Cartel","grhIndex":"628","objType":8},"227":{"name":"Cartel Bosque Dorck","grhIndex":"627","objType":8},"228":{"name":"Yacimiento de Oro","grhIndex":"8608","objType":22},"229":{"name":"Yacimiento de Oro","grhIndex":"8609","objType":22},"230":{"name":"Yacimiento de Oro","grhIndex":"8610","objType":22},"231":{"name":"Yacimiento de Oro","grhIndex":"8611","objType":22},"232":{"name":"Yacimiento de Plata","grhIndex":"8612","objType":22},"233":{"name":"Yacimiento de Plata","grhIndex":"8613","objType":22},"234":{"name":"Yacimiento de Plata","grhIndex":"8614","objType":22},"235":{"name":"Yacimiento de Plata","grhIndex":"8615","objType":22},"236":{"name":"Atlas Argentum","grhIndex":"538","objType":12},"237":{"name":"Costurero","grhIndex":"983","objType":2},"238":{"name":"Túnica Azul","grhIndex":"656","objType":3,"minDef":7,"maxDef":11},"239":{"name":"Túnica Roja","grhIndex":"657","objType":3,"minDef":5,"maxDef":10},"240":{"name":"Ropa de Enano (E/G)","grhIndex":"658","objType":3},"241":{"name":"Puerta de madera abierta","grhIndex":"8685","objType":6},"242":{"name":"Puerta de madera cerrada","grhIndex":"8684","objType":6},"243":{"name":"Armadura de Placas Completa (E/G)","grhIndex":"659","objType":3,"minDef":27,"maxDef":35},"244":{"name":"Puerta de madera abierta","grhIndex":"9199","objType":6},"245":{"name":"Puerta de madera cerrada","grhIndex":"9193","objType":6},"246":{"name":"Puerta de madera abierta","grhIndex":"9200","objType":6},"247":{"name":"Puerta de madera cerrada","grhIndex":"9194","objType":6},"248":{"name":"Puerta de madera abierta","grhIndex":"9201","objType":6},"249":{"name":"Puerta de madera cerrada","grhIndex":"9195","objType":6},"250":{"name":"Puerta de madera abierta","grhIndex":"9202","objType":6},"251":{"name":"Puerta de madera cerrada","grhIndex":"9196","objType":6},"252":{"name":"Puerta de madera abierta","grhIndex":"9203","objType":6},"253":{"name":"Puerta de madera cerrada","grhIndex":"9197","objType":6},"254":{"name":"Puerta de madera abierta","grhIndex":"9204","objType":6},"255":{"name":"Puerta de madera cerrada","grhIndex":"9198","objType":6},"256":{"name":"Foro","grhIndex":"529","objType":10},"257":{"name":"Foro","grhIndex":"529","objType":10},"258":{"name":"Cartel Banco","grhIndex":"670","objType":8},"259":{"name":"Cartel Provisiones","grhIndex":"672","objType":8},"260":{"name":"Cartel Sacerdote","grhIndex":"673","objType":8},"261":{"name":"Cartel Mesón Hostigado","grhIndex":"671","objType":8},"262":{"name":"Cartel","grhIndex":"719","objType":8},"263":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"264":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"265":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"266":{"name":"Llave Banderbill Casa Nro 1","grhIndex":"509","objType":9},"267":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"268":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"269":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"270":{"name":"Llave Banderbill Casa Nro 2","grhIndex":"509","objType":9},"271":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"272":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"273":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"274":{"name":"Llave Banderbill Casa Nro 3","grhIndex":"509","objType":9},"275":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"276":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"277":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"278":{"name":"Llave Banderbill Casa Nro 4","grhIndex":"509","objType":9},"279":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"280":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"281":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"282":{"name":"Llave Banderbill Casa Nro 5","grhIndex":"509","objType":9},"283":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"284":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"285":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"286":{"name":"Llave Banderbill Casa Nro 6","grhIndex":"509","objType":9},"287":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"288":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"289":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"290":{"name":"Llave Banderbill Casa Nro 7","grhIndex":"509","objType":9},"291":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"292":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"293":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"294":{"name":"Llave Banderbill Casa Nro 8","grhIndex":"509","objType":9},"295":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"296":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"297":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"298":{"name":"Llave Banderbill Casa Nro 9","grhIndex":"509","objType":9},"299":{"name":"Puerta abierta","grhIndex":"9199","objType":6},"300":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"301":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"302":{"name":"Llave del Gran Consejo","grhIndex":"509","objType":9},"303":{"name":"Puerta abierta","grhIndex":"9199","objType":6},"304":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"305":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"306":{"name":"Llave Bóveda del Gran Consejo","grhIndex":"509","objType":9},"307":{"name":"Puerta abierta","grhIndex":"9199","objType":6},"308":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"309":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"310":{"name":"Llave Bóveda del Gran Consejo","grhIndex":"509","objType":9},"311":{"name":"Puerta abierta","grhIndex":"9199","objType":6},"312":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"313":{"name":"Puerta cerrada","grhIndex":"9193","objType":6},"314":{"name":"Llave del la Bóveda Goliath","grhIndex":"509","objType":9},"315":{"name":"Cartel Muelles","grhIndex":"675","objType":8},"316":{"name":"Cartel Goliath","grhIndex":"676","objType":8},"317":{"name":"Cartel Palacio Real","grhIndex":"677","objType":8},"318":{"name":"Invisibilidad","grhIndex":"609","objType":24},"319":{"name":"Resucitar","grhIndex":"609","objType":24},"320":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"321":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"322":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"323":{"name":"Llave Banderbill Casa Nro 10","grhIndex":"509","objType":9},"324":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"325":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"326":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"327":{"name":"Llave Banderbill Casa Nro 11","grhIndex":"509","objType":9},"328":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"329":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"330":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"331":{"name":"Llave Banderbill Casa Nro 12","grhIndex":"509","objType":9},"332":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"333":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"334":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"335":{"name":"Llave Banderbill Casa Nro 13","grhIndex":"509","objType":9},"336":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"337":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"338":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"339":{"name":"Llave Banderbill Casa Nro 14","grhIndex":"509","objType":9},"340":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"341":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"342":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"343":{"name":"Llave Banderbill Casa Nro 15","grhIndex":"509","objType":9},"344":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"345":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"346":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"347":{"name":"Llave Banderbill Casa Nro 16","grhIndex":"509","objType":9},"348":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"349":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"350":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"351":{"name":"Llave Banderbill Casa Nro 17","grhIndex":"509","objType":9},"352":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"353":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"354":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"355":{"name":"Llave Banderbill Casa Nro 18","grhIndex":"509","objType":9},"356":{"name":"Armadura de las Sombras","grhIndex":"680","objType":3,"minDef":30,"maxDef":37},"357":{"name":"Túnica de Monje","grhIndex":"693","objType":3,"minDef":15,"maxDef":20},"358":{"name":"Teleport a Banderbill","grhIndex":"669","objType":19},"359":{"name":"Cota de Mallas","grhIndex":"682","objType":3,"minDef":15,"maxDef":22},"360":{"name":"Armadura de Cazador","grhIndex":"683","objType":3,"minDef":25,"maxDef":30},"361":{"name":"Tormenta de Fuego","grhIndex":"609","objType":24},"362":{"name":"Tambor","grhIndex":"684","objType":18},"363":{"name":"Ataque de Hambre","grhIndex":"609","objType":24},"364":{"name":"Terrible Hambre de Igôr","grhIndex":"609","objType":24},"365":{"name":"Daga +2","grhIndex":"710","objType":2,"minHit":3,"maxHit":6,"apu":1},"366":{"name":"Daga +3","grhIndex":"711","objType":2,"minHit":4,"maxHit":7,"apu":1},"367":{"name":"Daga +4","grhIndex":"5595","objType":2,"minHit":5,"maxHit":8,"apu":1},"368":{"name":"Lápida","grhIndex":"513","objType":8},"369":{"name":"Armadura Legionaria","grhIndex":"691","objType":3,"minDef":35,"maxDef":38},"370":{"name":"Capucha de Cazador","grhIndex":"467","objType":17,"minDef":15,"maxDef":20},"371":{"name":"Tumba de Nebeth","grhIndex":"513","objType":8},"372":{"name":"Manto de Sacerdote del Imperio","grhIndex":"759","objType":3,"minDef":6,"maxDef":12},"373":{"name":"Puerta abierta","grhIndex":"9201","objType":6},"374":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"375":{"name":"Puerta cerrada","grhIndex":"9195","objType":6},"376":{"name":"Llave Isla Beleta","grhIndex":"509","objType":9},"377":{"name":"Foro","grhIndex":"529","objType":10},"378":{"name":"Teleport","grhIndex":"669","objType":19},"379":{"name":"Armadura Legionaria (E/G)","grhIndex":"695","objType":3,"minDef":7,"maxDef":13},"380":{"name":"Invocación de Ultratumba","grhIndex":"609","objType":24},"381":{"name":"Túnica Antigua","grhIndex":"571","objType":3,"minDef":6,"maxDef":15},"383":{"name":"Sombrero Mágico","grhIndex":"18994","objType":17,"minDef":1,"maxDef":1,"resistenciaMagica":3},"384":{"name":"Yunque","grhIndex":"600","objType":27},"385":{"name":"Fragua","grhIndex":"722","objType":28},"386":{"name":"Lingote de Hierro","grhIndex":"724","objType":29},"387":{"name":"Lingote de Plata","grhIndex":"725","objType":29},"388":{"name":"Lingote de Oro","grhIndex":"723","objType":29},"389":{"name":"Martillo de Herrero","grhIndex":"726","objType":2},"390":{"name":"Armadura de Placas Completa +1","grhIndex":"3226","objType":3,"minDef":40,"maxDef":45},"391":{"name":"Armadura de Placas Completa +2","grhIndex":"3253","objType":3,"minDef":45,"maxDef":50},"392":{"name":"Armadura de Placas Completa +1 (E/G)","grhIndex":"3156","objType":3,"minDef":40,"maxDef":45},"393":{"name":"Armadura de Placas Completa +2 (E/G)","grhIndex":"3155","objType":3,"minDef":45,"maxDef":50},"394":{"name":"Llamado a la Naturaleza","grhIndex":"609","objType":24},"395":{"name":"Llamado Nigromante","grhIndex":"609","objType":24},"396":{"name":"Celeridad","grhIndex":"609","objType":24},"397":{"name":"Torpeza","grhIndex":"609","objType":24},"398":{"name":"Garrote","grhIndex":"714","objType":2,"minHit":2,"maxHit":5},"399":{"name":"Cimitarra","grhIndex":"709","objType":2,"minHit":7,"maxHit":15},"400":{"name":"Vara de Mago","grhIndex":"715","objType":2,"minHit":5,"maxHit":15,"magicDamageBonus":5},"401":{"name":"Martillo de Guerra","grhIndex":"708","objType":2,"minHit":4,"maxHit":9},"402":{"name":"Espada Mata Dragones","grhIndex":"716","objType":2,"minHit":1,"maxHit":2},"403":{"name":"Espada de Plata","grhIndex":"713","objType":2,"minHit":11,"maxHit":19},"404":{"name":"Escudo de Tortuga","grhIndex":"712","objType":16,"minDef":1,"maxDef":2},"405":{"name":"Casco de Plata","grhIndex":"717","objType":17,"minDef":20,"maxDef":25},"406":{"name":"Gema Lunar","grhIndex":"700","objType":29},"407":{"name":"Gema Azul","grhIndex":"701","objType":29},"408":{"name":"Gema Naranja","grhIndex":"702","objType":29},"409":{"name":"Gema Celeste","grhIndex":"703","objType":29},"410":{"name":"Gema Lila","grhIndex":"704","objType":29},"411":{"name":"Gema Roja","grhIndex":"705","objType":29},"412":{"name":"Gema Verde","grhIndex":"706","objType":29},"413":{"name":"Gema Violeta","grhIndex":"707","objType":29},"414":{"name":"Piel Lobo","grhIndex":"697","objType":30},"415":{"name":"Piel Oso Pardo","grhIndex":"699","objType":30},"416":{"name":"Piel Oso Polar","grhIndex":"698","objType":30},"417":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"418":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"419":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"420":{"name":"Llave Lindos Casa Nro 1","grhIndex":"509","objType":9},"421":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"422":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"423":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"424":{"name":"Llave Lindos Casa Nro 2","grhIndex":"509","objType":9},"425":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"426":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"427":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"428":{"name":"Llave Lindos Casa Nro 3","grhIndex":"509","objType":9},"429":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"430":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"431":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"432":{"name":"Llave Lindos Casa Nro 4","grhIndex":"509","objType":9},"433":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"434":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"435":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"436":{"name":"Llave Lindos Casa Nro 5","grhIndex":"509","objType":9},"437":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"438":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"439":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"440":{"name":"Llave Lindos Casa Nro 6","grhIndex":"509","objType":9},"441":{"name":"Puerta de madera abierta","grhIndex":"9203","objType":6},"442":{"name":"Puerta de madera cerrada","grhIndex":"9197","objType":6},"443":{"name":"Puerta de madera cerrada","grhIndex":"9197","objType":6},"444":{"name":"Llave Abadía de Lindos","grhIndex":"509","objType":9},"445":{"name":"Puerta de madera abierta","grhIndex":"9203","objType":6},"446":{"name":"Puerta de madera cerrada","grhIndex":"9197","objType":6},"447":{"name":"Puerta de madera cerrada","grhIndex":"9197","objType":6},"448":{"name":"Llave Vault de la Abadía de Lindos","grhIndex":"509","objType":9},"449":{"name":"Puerta de madera abierta","grhIndex":"9202","objType":6},"450":{"name":"Puerta de madera cerrada","grhIndex":"9196","objType":6},"451":{"name":"Puerta de madera cerrada","grhIndex":"9196","objType":6},"452":{"name":"Llave Prisión de Banderbill","grhIndex":"509","objType":9},"453":{"name":"Foro","grhIndex":"529","objType":10},"454":{"name":"Foro","grhIndex":"529","objType":10},"455":{"name":"Foro","grhIndex":"529","objType":10},"456":{"name":"Teleport a Lindos","grhIndex":"669","objType":19},"457":{"name":"Cartel","grhIndex":"718","objType":8},"458":{"name":"Cartel","grhIndex":"720","objType":8},"459":{"name":"Cartel","grhIndex":"721","objType":8},"460":{"name":"Daga (Newbie)","grhIndex":"510","objType":2,"minHit":1,"maxHit":3,"apu":1},"461":{"name":"Poción Roja (Newbie)","grhIndex":"542","objType":11},"462":{"name":"Poción Verde (Newbie)","grhIndex":"543","objType":11},"463":{"name":"Vestimentas Comunes (Newbie)","grhIndex":"527","objType":3,"minDef":1,"maxDef":2},"464":{"name":"Vestimentas Comunes (Newbie)","grhIndex":"528","objType":3},"465":{"name":"Vestimentas Comunes (Newbie)","grhIndex":"535","objType":3,"maxDef":1},"466":{"name":"Harapos de Mendigo (E/G) (Newbie)","grhIndex":"15249","objType":3},"467":{"name":"Manzana Roja (Newbie)","grhIndex":"506","objType":1},"468":{"name":"Botella de Agua (Newbie)","grhIndex":"537","objType":13},"469":{"name":"Laúd","grhIndex":"685","objType":18,"magicDamageBonus":3},"470":{"name":"Fuerza","grhIndex":"609","objType":24},"471":{"name":"Debilidad","grhIndex":"609","objType":24},"472":{"name":"Palmera","grhIndex":"735","objType":4},"473":{"name":"Flor","grhIndex":"731","objType":30},"474":{"name":"Barca","grhIndex":"749","objType":31,"minHit":10,"maxHit":25,"minDef":5,"maxDef":10},"475":{"name":"Galera","grhIndex":"5587","objType":31,"minHit":25,"maxHit":35,"minDef":10,"maxDef":20},"476":{"name":"Galeón","grhIndex":"5594","objType":31,"minHit":45,"maxHit":55,"minDef":15,"maxDef":25},"477":{"name":"Fragata Fantasmal","grhIndex":"749","objType":31},"478":{"name":"Arco Simple","grhIndex":"750","objType":2,"minHit":1,"maxHit":3,"proyectil":1},"479":{"name":"Arco Compuesto","grhIndex":"751","objType":2,"minHit":7,"maxHit":9,"proyectil":1},"480":{"name":"Flecha","grhIndex":"752","objType":32,"minHit":1,"maxHit":1},"481":{"name":"Armadura de Dragón","grhIndex":"756","objType":3,"minDef":60,"maxDef":65},"483":{"name":"Armadura de Dragón (E/G)","grhIndex":"758","objType":3,"minDef":60,"maxDef":65},"484":{"name":"Manto de Noble","grhIndex":"760","objType":3,"minDef":10,"maxDef":15},"485":{"name":"Armadura Legendaria","grhIndex":"761","objType":3,"minDef":55,"maxDef":60},"486":{"name":"Ropa de Estilo (E/G)","grhIndex":"762","objType":3,"minDef":1,"maxDef":1},"489":{"name":"Armadura de Placas Azul","grhIndex":"765","objType":3,"minDef":40,"maxDef":45},"490":{"name":"Ropa de Minero","grhIndex":"766","objType":3,"minDef":1,"maxDef":2},"491":{"name":"Escudo Real RM","grhIndex":"560","objType":16,"minDef":1,"maxDef":1},"492":{"name":"Armadura Real (E/G)","grhIndex":"768","objType":3,"minDef":7,"maxDef":13},"494":{"name":"Escudo Legionario RM","grhIndex":"13255","objType":16,"minDef":1,"maxDef":1},"495":{"name":"Armadura Escarlata","grhIndex":"771","objType":3,"minDef":35,"maxDef":38},"496":{"name":"Armadura de la Ciénaga","grhIndex":"772","objType":3,"minDef":35,"maxDef":38},"497":{"name":"Armadura de Placas de Gala","grhIndex":"773","objType":3,"minDef":42,"maxDef":47},"498":{"name":"Sidra","grhIndex":"20509","objType":13},"500":{"name":"Armadura Bruñida (E/G)","grhIndex":"776","objType":3,"minDef":35,"maxDef":38},"501":{"name":"Ropa de Campesino","grhIndex":"777","objType":3,"minDef":3,"maxDef":5},"502":{"name":"Ropa de Campesino (E/G)","grhIndex":"778","objType":3,"minDef":3,"maxDef":5},"507":{"name":"Ropa Violeta (E/G)","grhIndex":"783","objType":3,"minDef":2,"maxDef":4},"515":{"name":"Ropa Legendaria","grhIndex":"792","objType":3,"minDef":30,"maxDef":35},"517":{"name":"Túnica de Mago del Imperio","grhIndex":"794","objType":3,"minDef":5,"maxDef":12},"518":{"name":"Túnica de Mago Oscuro","grhIndex":"795","objType":3,"minDef":5,"maxDef":12},"519":{"name":"Túnica Legendaria","grhIndex":"796","objType":3,"minDef":30,"maxDef":35},"521":{"name":"Armadura Real","grhIndex":"690","objType":3,"minDef":7,"maxDef":13},"522":{"name":"Hipocrás","grhIndex":"20507","objType":13},"523":{"name":"Manto de Sacerdote Demoníaco","grhIndex":"800","objType":3,"minDef":6,"maxDef":12},"524":{"name":"Túnica Roja Especial (E/G)","grhIndex":"801","objType":3,"minDef":10,"maxDef":20},"525":{"name":"Túnica Combinada (E/G)","grhIndex":"802","objType":3,"minDef":15,"maxDef":25},"528":{"name":"Túnica Negra","grhIndex":"806","objType":3,"minDef":15,"maxDef":20},"530":{"name":"Inmovilizar","grhIndex":"609","objType":24},"531":{"name":"Descarga Eléctrica","grhIndex":"609","objType":24},"532":{"name":"Apocalipsis","grhIndex":"609","objType":24},"533":{"name":"Odre","grhIndex":"850","objType":34},"534":{"name":"Odre Vacía","grhIndex":"849","objType":33},"535":{"name":"Tumba de Gulfas Morgolock","grhIndex":"513","objType":8},"536":{"name":"Invocar Elemental de Fuego","grhIndex":"609","objType":24},"537":{"name":"Invocar Elemental de Agua","grhIndex":"609","objType":24},"538":{"name":"Invocar Elemental de Tierra","grhIndex":"609","objType":24},"539":{"name":"Implorar Ayuda","grhIndex":"609","objType":24},"540":{"name":"Flauta","grhIndex":"686","objType":18,"magicDamageBonus":3},"541":{"name":"Ceguera","grhIndex":"609","objType":24},"542":{"name":"Aturdir","grhIndex":"609","objType":24},"543":{"name":"Red de Pesca","grhIndex":"844","objType":2},"544":{"name":"Pez Dorado","grhIndex":"845","objType":1},"545":{"name":"Pez Espada","grhIndex":"846","objType":1},"546":{"name":"Merluza","grhIndex":"847","objType":1},"547":{"name":"Cartel Dungeon Veriil","grhIndex":"863","objType":8},"548":{"name":"Cartel Arghâl","grhIndex":"864","objType":8},"549":{"name":"Túnica de Mago del Imperio (E/G)","grhIndex":"799","objType":3,"minDef":5,"maxDef":12},"550":{"name":"Porrón de cerveza","grhIndex":"20508","objType":13},"551":{"name":"Flecha +1","grhIndex":"755","objType":32,"minHit":2,"maxHit":3},"552":{"name":"Flecha +2","grhIndex":"754","objType":32,"minHit":3,"maxHit":4},"553":{"name":"Flecha +3","grhIndex":"753","objType":32,"minHit":3,"maxHit":5},"554":{"name":"Sangre","grhIndex":"264","objType":35},"555":{"name":"Sangre","grhIndex":"263","objType":35},"556":{"name":"Espada Mata Dragones NatOs","grhIndex":"716","objType":2,"minHit":1000,"maxHit":1000},"557":{"name":"Vino de canela","grhIndex":"20504","objType":13},"558":{"name":"Túnica de Mago Oscuro (E/G)","grhIndex":"774","objType":3,"minDef":5,"maxDef":12},"559":{"name":"Daga de Plata","grhIndex":"711","objType":2,"minHit":6,"maxHit":9,"apu":1},"560":{"name":"Gema Sagrada Recuperada por Shadow VX","grhIndex":"705","objType":29},"561":{"name":"Hacha de Leñador (Newbie)","grhIndex":"565","objType":2},"562":{"name":"Piquete de Minero (Newbie)","grhIndex":"608","objType":2,"minHit":1,"maxHit":2},"563":{"name":"Caña de pescar (Newbie)","grhIndex":"574","objType":2,"minHit":1,"maxHit":2},"564":{"name":"Serrucho (Newbie)","grhIndex":"607","objType":2,"minHit":1,"maxHit":2},"565":{"name":"Martillo de Herrero (Newbie)","grhIndex":"726","objType":2,"minHit":1,"maxHit":2},"566":{"name":"Flores","grhIndex":"867","objType":30},"567":{"name":"Flores","grhIndex":"868","objType":30},"568":{"name":"Flores","grhIndex":"869","objType":30},"569":{"name":"Flores","grhIndex":"870","objType":30},"570":{"name":"Flores","grhIndex":"871","objType":30},"571":{"name":"Flores","grhIndex":"872","objType":30},"572":{"name":"Flores","grhIndex":"873","objType":30},"573":{"name":"Flores","grhIndex":"874","objType":30},"574":{"name":"Cartel","grhIndex":"875","objType":8},"575":{"name":"Foro","grhIndex":"529","objType":10},"576":{"name":"Foro","grhIndex":"529","objType":10},"577":{"name":"Cartel","grhIndex":"876","objType":8},"578":{"name":"Cartel","grhIndex":"877","objType":8},"579":{"name":"Cartel","grhIndex":"878","objType":8},"580":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"581":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"582":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"583":{"name":"Llave Arkhein Casa Nro 1","grhIndex":"509","objType":9},"584":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"585":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"586":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"587":{"name":"Llave Arkhein Casa Nro 2","grhIndex":"509","objType":9},"588":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"589":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"590":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"591":{"name":"Llave Arkhein Casa Nro 3","grhIndex":"509","objType":9},"592":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"593":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"594":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"595":{"name":"Llave Arkhein Casa Nro 4","grhIndex":"509","objType":9},"596":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"597":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"598":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"599":{"name":"Llave Arkhein Casa Nro 5","grhIndex":"509","objType":9},"600":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"601":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"602":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"603":{"name":"Llave Arkhein Casa Nro 6","grhIndex":"509","objType":9},"604":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"605":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"606":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"607":{"name":"Llave Arkhein Casa Nro 7","grhIndex":"509","objType":9},"608":{"name":"Puerta de madera abierta","grhIndex":"5592","objType":6},"609":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"610":{"name":"Puerta de madera cerrada","grhIndex":"5593","objType":6},"611":{"name":"Llave Arkhein Casa Nro 8","grhIndex":"509","objType":9},"612":{"name":"Cerveza de los enanos","grhIndex":"20506","objType":13},"613":{"name":"Gema Sagrada","grhIndex":"700","objType":29},"614":{"name":"Túnica de Archimago","grhIndex":"806","objType":3,"minDef":30,"maxDef":35},"615":{"name":"Túnica de Archimago (E/G)","grhIndex":"657","objType":3,"minDef":35,"maxDef":40},"616":{"name":"Armadura de Guerrero Campeón","grhIndex":"756","objType":3,"minDef":80,"maxDef":85},"617":{"name":"Armadura de Guerrero Campeón (E/G)","grhIndex":"758","objType":3,"minDef":60,"maxDef":65},"618":{"name":"Monedas de Oro","grhIndex":"511","objType":29},"619":{"name":"Daga Envenenada","grhIndex":"510","objType":2,"minHit":4,"maxHit":7,"apu":1},"620":{"name":"Lápida","grhIndex":"513","objType":8},"621":{"name":"Cartel","grhIndex":"500","objType":8},"622":{"name":"Barrilito de cerveza","grhIndex":"20505","objType":13},"623":{"name":"Foro","grhIndex":"529","objType":10},"624":{"name":"Foro","grhIndex":"529","objType":10},"625":{"name":"Escudo AlejoLp","grhIndex":"560","objType":16,"minDef":10,"maxDef":12},"626":{"name":"No ves nada interesante.","grhIndex":"145","objType":12},"627":{"name":"No ves nada interesante.","grhIndex":"275","objType":12},"628":{"name":"Túnica de Archimago Imperial","grhIndex":"687","objType":3,"minDef":35,"maxDef":35},"629":{"name":"Armadura Nobilísima","grhIndex":"688","objType":3,"minDef":15,"maxDef":25},"630":{"name":"Túnica Celestial","grhIndex":"767","objType":3,"minDef":40,"maxDef":40},"632":{"name":"Armadura de Guerrero Real (E/G)","grhIndex":"776","objType":3,"minDef":55,"maxDef":55},"633":{"name":"Túnica Marrón de Mago Imperial (E/G)","grhIndex":"802","objType":3,"minDef":35,"maxDef":35},"634":{"name":"Túnica Egregia de Mago Oscuro","grhIndex":"692","objType":3,"minDef":15,"maxDef":20},"636":{"name":"Túnica de Líder del Caos (E/G)","grhIndex":"657","objType":3,"minDef":35,"maxDef":35},"637":{"name":"Túnica de líder del Caos","grhIndex":"806","objType":3,"minDef":40,"maxDef":40},"638":{"name":"Armadura de Guardián del Demonio","grhIndex":"756","objType":3,"minDef":55,"maxDef":55},"639":{"name":"Armadura de Guardián del Demonio (E/G)","grhIndex":"758","objType":3,"minDef":55,"maxDef":55},"640":{"name":"Túnica de Archimago del Caos","grhIndex":"657","objType":3,"minDef":55,"maxDef":55},"641":{"name":"Ropa de Pordiosero","grhIndex":"654","objType":3,"minDef":3,"maxDef":5},"642":{"name":"Tumba de Calixto","grhIndex":"513","objType":8},"643":{"name":"Tumba de Lobonegro","grhIndex":"513","objType":8},"644":{"name":"Tumba de Almendra","grhIndex":"513","objType":8},"645":{"name":"Poción Negra","grhIndex":"53","objType":11},"646":{"name":"Túnica Real Pretoriana","grhIndex":"689","objType":3},"647":{"name":"Cartel","grhIndex":"500","objType":8},"648":{"name":"Armadura de Cazador (E/G)","grhIndex":"1100","objType":3,"minDef":25,"maxDef":30},"649":{"name":"Cartel","grhIndex":"500","objType":8},"650":{"name":"Túnica Pretoriana","grhIndex":"657","objType":3},"651":{"name":"Portal Mágico","grhIndex":"308","objType":30},"652":{"name":"Piedra de Arghâl","grhIndex":"705","objType":29},"653":{"name":"Reja","grhIndex":"12402","objType":35},"654":{"name":"Reja","grhIndex":"12408","objType":35},"655":{"name":"Reja","grhIndex":"12410","objType":35},"656":{"name":"Túnica Pretoriana (E/G)","grhIndex":"657","objType":3},"657":{"name":"Desaturdir","grhIndex":"609","objType":24},"658":{"name":"Vara de Fresno","grhIndex":"932","objType":2,"minHit":1,"maxHit":1,"magicDamageBonus":3},"659":{"name":"Bastón Nudoso","grhIndex":"959","objType":2,"minHit":1,"maxHit":1,"magicDamageBonus":5},"660":{"name":"Báculo Engarzado","grhIndex":"986","objType":2,"minHit":1,"maxHit":1,"magicDamageBonus":7},"661":{"name":"Sombrero de Aprendiz","grhIndex":"1013","objType":17,"minDef":1,"maxDef":3,"resistenciaMagica":1},"662":{"name":"Sombrero de Mago","grhIndex":"1018","objType":17,"minDef":1,"maxDef":1,"resistenciaMagica":5},"663":{"name":"Arco Simple Reforzado","grhIndex":"1024","objType":2,"minHit":4,"maxHit":7,"proyectil":1},"664":{"name":"Arco Compuesto Reforzado","grhIndex":"1025","objType":2,"minHit":5,"maxHit":10,"proyectil":1},"665":{"name":"Arco de Cazador","grhIndex":"1023","objType":2,"minHit":9,"maxHit":12,"proyectil":1},"666":{"name":"Objetos Voodoo Satánicos","grhIndex":"740","objType":8},"667":{"name":"Cota de Mallas (E/G)","grhIndex":"1127","objType":3,"minDef":15,"maxDef":22},"668":{"name":"Armadura de Cuero (E/G)","grhIndex":"1154","objType":3,"minDef":7,"maxDef":12},"669":{"name":"Ropa Real","grhIndex":"1181","objType":3,"minDef":20,"maxDef":20},"670":{"name":"Túnica de Mago (E/G)","grhIndex":"1208","objType":3,"minDef":7,"maxDef":11},"671":{"name":"Túnica de Monje (E/G)","grhIndex":"1235","objType":3,"minDef":15,"maxDef":20},"672":{"name":"Armadura de Elite (E/G)","grhIndex":"1262","objType":3,"minDef":30,"maxDef":35},"673":{"name":"Mimetismo","grhIndex":"609","objType":24},"674":{"name":"Sangre de Liche","grhIndex":"264","objType":35},"675":{"name":"Vestimenta Imperial","grhIndex":"4924","objType":3,"minDef":1,"maxDef":3},"676":{"name":"Vestimenta Imperial (E/G)","grhIndex":"4925","objType":3,"minDef":1,"maxDef":3},"677":{"name":"Vestimenta Legionaria","grhIndex":"4926","objType":3,"minDef":1,"maxDef":3},"678":{"name":"Vestimenta Legionaria (E/G)","grhIndex":"4927","objType":3,"minDef":1,"maxDef":3},"679":{"name":"Túnica Conspicua de Mago Imperial","grhIndex":"1181","objType":3,"minDef":15,"maxDef":20},"680":{"name":"Armadura de Gran Sacerdote Imperial","grhIndex":"4922","objType":3,"minDef":15,"maxDef":22},"681":{"name":"Armadura Nobilísima (E/G)","grhIndex":"4921","objType":3,"minDef":15,"maxDef":25},"682":{"name":"Túnica Conspicua de Mago Imperial (E/G)","grhIndex":"4920","objType":3,"minDef":15,"maxDef":20},"683":{"name":"Armadura Lóbrega","grhIndex":"4919","objType":3,"minDef":15,"maxDef":25},"684":{"name":"Armadura de Gran Sacerdote Demoníaco","grhIndex":"4918","objType":3,"minDef":15,"maxDef":22},"685":{"name":"Armadura Lóbrega (E/G)","grhIndex":"4917","objType":3,"minDef":15,"maxDef":25},"686":{"name":"Túnica Egregia de Mago Oscuro (E/G)","grhIndex":"4916","objType":3,"minDef":15,"maxDef":20},"687":{"name":"Túnica Palatina","grhIndex":"687","objType":3,"minDef":30,"maxDef":35},"688":{"name":"Pequeña Túnica Palatina (E/G)","grhIndex":"4915","objType":3,"minDef":30,"maxDef":35},"689":{"name":"Túnica Avernal","grhIndex":"806","objType":3,"minDef":30,"maxDef":35},"690":{"name":"Pequeña Túnica Avernal (E/G)","grhIndex":"4914","objType":3,"minDef":30,"maxDef":35},"691":{"name":"Armadura de Ilustre Caballero","grhIndex":"4913","objType":3,"minDef":35,"maxDef":40},"692":{"name":"Teleport a Nemahuak","grhIndex":"669","objType":19},"694":{"name":"Armadura de Ilustre Caballero (E/G)","grhIndex":"4912","objType":3,"minDef":35,"maxDef":40},"695":{"name":"Armadura de Pequeño Pandemonium (E/G)","grhIndex":"4910","objType":3,"minDef":35,"maxDef":40},"696":{"name":"Laúd Mágico","grhIndex":"685","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":4},"697":{"name":"Anillo Mágico","grhIndex":"4900","objType":18,"resistenciaMagica":3},"698":{"name":"Anillo de Resistencia Mágica","grhIndex":"4901","objType":18,"resistenciaMagica":5},"699":{"name":"Anillo de Disolución Mágica","grhIndex":"4902","objType":18,"resistenciaMagica":7},"700":{"name":"Anillo de los Dioses","grhIndex":"4903","objType":18,"minDefMag":200,"maxDefMag":210},"701":{"name":"Armadura Infame","grhIndex":"4911","objType":3,"minDef":35,"maxDef":40},"703":{"name":"Tumba de Cronos","grhIndex":"513","objType":8},"705":{"name":"El Ojo del Demiurgo","grhIndex":"609","objType":24},"706":{"name":"Cartel de Advertencia","grhIndex":"500","objType":8},"707":{"name":"Vestimenta de Noble (E/G)","grhIndex":"13282","objType":3,"minDef":3,"maxDef":5},"708":{"name":"Escudo Infernal","grhIndex":"13255","objType":16,"minDef":1,"maxDef":4},"709":{"name":"Columna Egipcia","grhIndex":"1460","objType":20},"710":{"name":"Copa de Oro","grhIndex":"54","objType":21},"711":{"name":"Tumba de Valkira","grhIndex":"513","objType":8},"712":{"name":"Tumba de Jennifer","grhIndex":"513","objType":8},"713":{"name":"Tumba de Jareth","grhIndex":"513","objType":8},"714":{"name":"Tumba de Gold Fire","grhIndex":"513","objType":8},"715":{"name":"Tumba de Amenakhte","grhIndex":"513","objType":8},"716":{"name":"Rueca","grhIndex":"14","objType":20},"717":{"name":"Antorcha","grhIndex":"25","objType":20},"718":{"name":"Salamandra","grhIndex":"30","objType":20},"719":{"name":"Lancero","grhIndex":"31","objType":20},"720":{"name":"Viga","grhIndex":"32","objType":20},"721":{"name":"Marmita","grhIndex":"36","objType":20},"722":{"name":"Caldero","grhIndex":"37","objType":20},"723":{"name":"Humo Verde","grhIndex":"49","objType":20},"724":{"name":"Mesa","grhIndex":"50","objType":20},"725":{"name":"Maceta","grhIndex":"51","objType":20},"726":{"name":"Biblioteca","grhIndex":"52","objType":20},"727":{"name":"Biblioteca","grhIndex":"492","objType":20},"728":{"name":"Biblioteca","grhIndex":"534","objType":20},"729":{"name":"Hogar","grhIndex":"60","objType":20},"730":{"name":"Cántaro","grhIndex":"99","objType":20},"731":{"name":"Mueble con Espejo","grhIndex":"309","objType":20},"732":{"name":"Mesa Circular","grhIndex":"311","objType":20},"733":{"name":"Butaca","grhIndex":"312","objType":20},"734":{"name":"Jarra de Losa","grhIndex":"310","objType":21},"735":{"name":"Grilletes","grhIndex":"313","objType":20},"736":{"name":"Candelabro Simple","grhIndex":"314","objType":21},"737":{"name":"Cómoda","grhIndex":"493","objType":20},"738":{"name":"Carcaj","grhIndex":"494","objType":7},"739":{"name":"Baúl Abierto","grhIndex":"496","objType":7},"740":{"name":"Baúl Cerrado","grhIndex":"495","objType":7},"741":{"name":"Cuadro","grhIndex":"497","objType":20},"742":{"name":"Cuadro","grhIndex":"498","objType":20},"743":{"name":"Cuadro","grhIndex":"532","objType":20},"744":{"name":"Cuadro","grhIndex":"533","objType":20},"745":{"name":"Escritorio","grhIndex":"499","objType":20},"746":{"name":"Farol","grhIndex":"507","objType":20},"747":{"name":"Cama Doble","grhIndex":"524","objType":20},"748":{"name":"Sillón","grhIndex":"525","objType":20},"749":{"name":"Carretilla","grhIndex":"577","objType":20},"750":{"name":"Carretilla","grhIndex":"578","objType":20},"751":{"name":"Barril Pequeño","grhIndex":"580","objType":20},"752":{"name":"Barril Pequeño","grhIndex":"582","objType":20},"753":{"name":"Mayal Triple","grhIndex":"590","objType":2,"minHit":1,"maxHit":5},"754":{"name":"Rábano","grhIndex":"591","objType":1},"755":{"name":"Tinaja","grhIndex":"592","objType":20},"756":{"name":"Mueble Rústico","grhIndex":"594","objType":20},"757":{"name":"Balde","grhIndex":"595","objType":20},"758":{"name":"Reloj de Arena","grhIndex":"596","objType":20},"759":{"name":"Vasija","grhIndex":"597","objType":20},"760":{"name":"Mesa con Mantel","grhIndex":"642","objType":20},"761":{"name":"Cantero","grhIndex":"648","objType":20},"762":{"name":"Cantero","grhIndex":"649","objType":20},"763":{"name":"Carreta","grhIndex":"650","objType":20},"764":{"name":"Carreta","grhIndex":"651","objType":20},"765":{"name":"Carreta","grhIndex":"652","objType":20},"766":{"name":"Arbolito","grhIndex":"653","objType":20},"767":{"name":"Margarita","grhIndex":"729","objType":30},"768":{"name":"Hongo","grhIndex":"730","objType":30},"769":{"name":"Hongos","grhIndex":"732","objType":30},"770":{"name":"Algas","grhIndex":"733","objType":30},"771":{"name":"Enorme Tinaja","grhIndex":"727","objType":20},"772":{"name":"Mesa de Alquimia","grhIndex":"739","objType":20},"773":{"name":"Pianola","grhIndex":"747","objType":20},"774":{"name":"Cepo","grhIndex":"748","objType":20},"775":{"name":"Hipocampo","grhIndex":"848","objType":1},"776":{"name":"Atril","grhIndex":"879","objType":20},"777":{"name":"Grilletes","grhIndex":"1472","objType":20},"778":{"name":"Restos de Brazos","grhIndex":"1473","objType":20},"779":{"name":"Mesa de Carpintero","grhIndex":"1474","objType":20},"780":{"name":"Mesa de Comedor","grhIndex":"1475","objType":20},"781":{"name":"Órgano","grhIndex":"1477","objType":20},"782":{"name":"Potro","grhIndex":"1479","objType":20},"783":{"name":"Potro","grhIndex":"1480","objType":20},"784":{"name":"Tesoro","grhIndex":"1478","objType":20},"785":{"name":"Utencillos","grhIndex":"1481","objType":20},"786":{"name":"Ventana","grhIndex":"1482","objType":20},"787":{"name":"Ventana","grhIndex":"1483","objType":20},"788":{"name":"Sillón","grhIndex":"1484","objType":20},"789":{"name":"Sillón","grhIndex":"1485","objType":20},"790":{"name":"Sillón Azul","grhIndex":"1485","objType":20},"791":{"name":"Sillón Azul Doble","grhIndex":"1485","objType":20},"792":{"name":"Escudo de Armas","grhIndex":"1488","objType":20},"793":{"name":"Escudo de Armas","grhIndex":"1489","objType":20},"794":{"name":"Arbolito en Maceta","grhIndex":"1490","objType":20},"795":{"name":"Arbolito en Maceta","grhIndex":"1491","objType":20},"796":{"name":"Arbolito en Maceta","grhIndex":"1492","objType":20},"797":{"name":"Hoguera","grhIndex":"1496","objType":20},"798":{"name":"Desperdicios","grhIndex":"1497","objType":20},"799":{"name":"Banqueta","grhIndex":"1498","objType":20},"800":{"name":"Armario","grhIndex":"1499","objType":20},"801":{"name":"Estantes","grhIndex":"1501","objType":20},"802":{"name":"Barriles","grhIndex":"1502","objType":20},"803":{"name":"Utensillos","grhIndex":"1503","objType":20},"804":{"name":"Mesa Real","grhIndex":"1504","objType":20},"805":{"name":"Silla Real","grhIndex":"2130","objType":20},"806":{"name":"Silla Real","grhIndex":"2131","objType":20},"807":{"name":"Silla Real","grhIndex":"2132","objType":20},"808":{"name":"Silla Real","grhIndex":"2133","objType":20},"809":{"name":"Hongos Amarillos","grhIndex":"2134","objType":30},"810":{"name":"Hongos Azules","grhIndex":"2135","objType":30},"811":{"name":"Hongos Rojos","grhIndex":"2136","objType":30},"812":{"name":"Hongos Violetas","grhIndex":"2137","objType":30},"813":{"name":"Candelabro de Pie","grhIndex":"2172","objType":20},"814":{"name":"Relicario","grhIndex":"2173","objType":20},"815":{"name":"Altar","grhIndex":"2174","objType":20},"816":{"name":"Vitreaux","grhIndex":"2175","objType":20},"817":{"name":"Trofeo de Pesca","grhIndex":"2138","objType":20},"818":{"name":"Trofeo de Pesca","grhIndex":"2139","objType":20},"819":{"name":"Boya","grhIndex":"2634","objType":20},"820":{"name":"Hojitas","grhIndex":"1524","objType":20},"821":{"name":"Estalagmitas","grhIndex":"1525","objType":20},"822":{"name":"Estalagmitas","grhIndex":"1526","objType":20},"823":{"name":"Estalagmitas","grhIndex":"1527","objType":20},"824":{"name":"Restos Mortales","grhIndex":"1528","objType":20},"825":{"name":"Árbol","grhIndex":"1529","objType":4},"826":{"name":"Árbol","grhIndex":"1530","objType":4},"827":{"name":"Árbol","grhIndex":"1531","objType":4},"828":{"name":"Árbol","grhIndex":"1532","objType":4},"829":{"name":"Árbol","grhIndex":"1533","objType":4},"830":{"name":"Árbol","grhIndex":"1534","objType":4},"831":{"name":"Arbusto","grhIndex":"1535","objType":30},"832":{"name":"Arbusto","grhIndex":"1536","objType":20},"833":{"name":"Taza","grhIndex":"1537","objType":20},"834":{"name":"Fósil","grhIndex":"1538","objType":20},"835":{"name":"Fósil","grhIndex":"1539","objType":20},"836":{"name":"Cuadro","grhIndex":"2619","objType":20},"837":{"name":"Cuadro","grhIndex":"2620","objType":20},"838":{"name":"Cuadro","grhIndex":"2621","objType":20},"839":{"name":"Cuadro","grhIndex":"2622","objType":20},"840":{"name":"Cuadro","grhIndex":"2623","objType":20},"841":{"name":"Cuadro","grhIndex":"2624","objType":20},"842":{"name":"Cuadro","grhIndex":"2625","objType":20},"843":{"name":"Cuadro","grhIndex":"2626","objType":20},"844":{"name":"Cuadro","grhIndex":"2627","objType":20},"845":{"name":"Cuadro","grhIndex":"2628","objType":20},"846":{"name":"Cuadro","grhIndex":"2629","objType":20},"847":{"name":"Estandarte Legionario","grhIndex":"3300","objType":20},"848":{"name":"Estandarte Real","grhIndex":"3877","objType":20},"849":{"name":"Tonel","grhIndex":"12429","objType":20},"850":{"name":"Arco de Flores","grhIndex":"95","objType":30},"851":{"name":"Laureles","grhIndex":"55","objType":17},"852":{"name":"Tonel","grhIndex":"4488","objType":20},"853":{"name":"Tonel","grhIndex":"4489","objType":20},"854":{"name":"Armadura de las Sombras (E/G)","grhIndex":"22039","objType":3,"minDef":30,"maxDef":37},"855":{"name":"Poción Amarilla (Newbie)","grhIndex":"540","objType":11},"856":{"name":"Poción Azul (Newbie)","grhIndex":"541","objType":11},"857":{"name":"Poción Roja (Newbie)","grhIndex":"542","objType":11},"858":{"name":"Poción Verde (Newbie)","grhIndex":"543","objType":11},"859":{"name":"Arco (Newbie)","grhIndex":"750","objType":2,"minHit":1,"maxHit":2,"proyectil":1},"860":{"name":"Flecha (Newbie)","grhIndex":"752","objType":32,"minHit":1,"maxHit":1},"861":{"name":"Espada (Newbie)","grhIndex":"504","objType":2,"minHit":1,"maxHit":3},"862":{"name":"Bastón de Mago (Newbie)","grhIndex":"932","objType":2,"minHit":1,"maxHit":1},"863":{"name":"Flauta Mágica (Newbie)","grhIndex":"686","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":1},"864":{"name":"Laúd Mágico (Newbie)","grhIndex":"685","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":1},"865":{"name":"Alforja","grhIndex":"388","objType":37},"866":{"name":"Mochila de Viajero","grhIndex":"403","objType":37,"minHit":1,"maxHit":1},"867":{"name":"Túnica de Gnomo (E/G)","grhIndex":"5326","objType":3},"868":{"name":"Tumba de Chatillon","grhIndex":"513","objType":8},"869":{"name":"Tumba de Nightsorrow","grhIndex":"513","objType":8},"870":{"name":"Tumba de Shep","grhIndex":"513","objType":8},"871":{"name":"Tumba de Patrick","grhIndex":"513","objType":8},"872":{"name":"Tumba de EL OSO","grhIndex":"513","objType":8},"873":{"name":"Guantes de Hurto","grhIndex":"1469","objType":18,"minHit":1,"maxHit":2},"874":{"name":"Cuerno de llamado Armada","grhIndex":"11980","objType":26},"875":{"name":"Cuerno de llamado Legionario","grhIndex":"11979","objType":26},"876":{"name":"Vestimenta de Game Master","grhIndex":"1470","objType":3},"877":{"name":"Túnica de Gran Hechicero (E/G)","grhIndex":"15519","objType":3,"minDef":15,"maxDef":20},"882":{"name":"Anillas Livianas de Aventurero (H/E/EO)","grhIndex":"16167","objType":3,"minDef":3,"maxDef":8},"883":{"name":"Cota de Anillas Compuesta (H/E/EO)","grhIndex":"15627","objType":3,"minDef":18,"maxDef":25},"885":{"name":"Armadura Liviana de Hierro (H/E/EO)","grhIndex":"15789","objType":3,"minDef":10,"maxDef":15},"887":{"name":"Cotas de Manto Marrón (H/E/EO)","grhIndex":"16221","objType":3,"minDef":17,"maxDef":25},"888":{"name":"Brial del Bosque (H/E/EO)","grhIndex":"16275","objType":3,"minDef":2,"maxDef":4},"889":{"name":"Anillas Con Manto Verde (H/E/EO)","grhIndex":"15600","objType":3,"minDef":13,"maxDef":16},"890":{"name":"Armadura de Cuero de Oso Pardo (H/E/EO)","grhIndex":"15762","objType":3,"minDef":10,"maxDef":20},"897":{"name":"Cota de Piquero (H/E/EO)","grhIndex":"16113","objType":3,"minDef":12,"maxDef":22},"903":{"name":"Armadura de Campeón (H/E/EO)","grhIndex":"15681","objType":3,"minDef":25,"maxDef":40},"905":{"name":"Suprema Armadura Dorada (H/E/EO)","grhIndex":"15708","objType":3,"minDef":35,"maxDef":45},"906":{"name":"Manto del Dragón (H/E/EO)","grhIndex":"16329","objType":3,"minDef":63,"maxDef":68},"908":{"name":"Vestimentas de Pescador (E/G)","grhIndex":"15276","objType":3,"maxDef":1},"910":{"name":"Atuendo de Gnomo (E/G)","grhIndex":"14655","objType":3,"minDef":1,"maxDef":1},"911":{"name":"Vestimenta Bordo de Poblador (E/G)","grhIndex":"15141","objType":3,"minDef":1,"maxDef":2},"912":{"name":"Vestimenta Verde de Poblador (E/G)","grhIndex":"15087","objType":3,"minDef":1,"maxDef":3},"913":{"name":"Ropaje de Ermitaño (E/G)","grhIndex":"14736","objType":3,"minDef":1,"maxDef":2},"915":{"name":"Ropaje de Aldeano (E/G)","grhIndex":"15006","objType":3,"minDef":1,"maxDef":2},"916":{"name":"Vestimenta Elegante (E/G)","grhIndex":"15033","objType":3,"minDef":1,"maxDef":3},"917":{"name":"Vestimenta de Minero (E/G)","grhIndex":"15114","objType":3,"minDef":1,"maxDef":3},"918":{"name":"Gabán de Cuero Pequeño (E/G)","grhIndex":"14574","objType":3,"minDef":4,"maxDef":5},"919":{"name":"Alifafe de Serpiente (E/G)","grhIndex":"14979","objType":3,"minDef":2,"maxDef":2},"920":{"name":"Ropaje de Herrero (E/G)","grhIndex":"15060","objType":3,"minDef":2,"maxDef":3},"921":{"name":"Túnica Sírica Elegante (E/G)","grhIndex":"15168","objType":3,"minDef":1,"maxDef":5},"922":{"name":"Traje de Capitán Pirata (E/G)","grhIndex":"15195","objType":3,"minDef":2,"maxDef":5},"923":{"name":"Traje de Noble (E/G)","grhIndex":"15222","objType":3,"minDef":3,"maxDef":5},"924":{"name":"Vestimenta de Explorador (E/G)","grhIndex":"14520","objType":3,"minDef":3,"maxDef":6},"927":{"name":"Alifafe de Lobo (E/G)","grhIndex":"14682","objType":3,"minDef":5,"maxDef":7},"928":{"name":"Barragán de Cazador (E/G)","grhIndex":"14628","objType":3,"minDef":6,"maxDef":9},"929":{"name":"Barragán Compuesto (E/G)","grhIndex":"14709","objType":3,"minDef":10,"maxDef":15},"930":{"name":"Toga de Aprendiz (E/G)","grhIndex":"15465","objType":3,"minDef":5,"maxDef":10},"932":{"name":"Sotana de Gran Hechicero (E/G)","grhIndex":"15492","objType":3,"minDef":8,"maxDef":12},"934":{"name":"Cotas Livianas (E/G)","grhIndex":"14871","objType":3,"minDef":10,"maxDef":15},"937":{"name":"Cota de Peto Púrpura (E/G)","grhIndex":"14790","objType":3,"minDef":12,"maxDef":22},"939":{"name":"Túnica de Gala Roja (E/G)","grhIndex":"15546","objType":3,"minDef":19,"maxDef":23},"940":{"name":"Cota de Mallas Enteriza (E/G)","grhIndex":"14898","objType":3,"minDef":20,"maxDef":26},"943":{"name":"Sobrevesta Élfica (H/E/EO)","grhIndex":"16356","objType":3},"946":{"name":"Vestimentas de Poblador Élfico (H/E/EO)","grhIndex":"16625","objType":3,"minDef":1,"maxDef":1},"947":{"name":"Hábito Liviano de Ritual (H/E/EO)","grhIndex":"16679","objType":3,"minDef":2,"maxDef":4},"952":{"name":"Ropa de Explorador Élfico (H/E/EO)","grhIndex":"16544","objType":3,"minDef":4,"maxDef":5},"954":{"name":"Hábito de Ritual de Hombre (H/E/EO)","grhIndex":"16490","objType":3,"minDef":11,"maxDef":20},"958":{"name":"Vestimenta Común (E/G)","grhIndex":"17139","objType":3,"minDef":1,"maxDef":2},"959":{"name":"Ropa de Carpintero Enano (E/G)","grhIndex":"17220","objType":3,"maxDef":1},"960":{"name":"Ropa de Leñador (E/G)","grhIndex":"17085","objType":3,"minDef":3,"maxDef":4},"961":{"name":"Ropa de Minero (E/G)","grhIndex":"16977","objType":3,"maxDef":2},"962":{"name":"Túnica Elegante (E/G)","grhIndex":"17166","objType":3,"minDef":1,"maxDef":3},"963":{"name":"Ropaje de Luchador de Las Minas (E/G)","grhIndex":"17247","objType":3,"minDef":7,"maxDef":12},"964":{"name":"Ropa de Viajero (E/G)","grhIndex":"16896","objType":3,"minDef":2,"maxDef":3},"965":{"name":"Antigua Armadura Dorada (E/G)","grhIndex":"16869","objType":3,"minDef":3,"maxDef":5},"966":{"name":"Alifafe de Pirata (E/G)","grhIndex":"17193","objType":3,"minDef":4,"maxDef":16},"967":{"name":"Ropas de Buscador (E/G)","grhIndex":"17301","objType":3,"minDef":4,"maxDef":17},"968":{"name":"Armadura Grabada Completa (E/G)","grhIndex":"16842","objType":3,"minDef":5,"maxDef":19},"969":{"name":"Túnica Dorada de Gala (E/G)","grhIndex":"16950","objType":3,"minDef":16,"maxDef":18},"970":{"name":"Armadura de Placas Livianas Thor (E/G)","grhIndex":"16761","objType":3,"minDef":18,"maxDef":22},"971":{"name":"Armadura Metal Oscuro (E/G)","grhIndex":"16923","objType":3,"minDef":19,"maxDef":23},"972":{"name":"Túnica de Mahal (E/G)","grhIndex":"17004","objType":3,"minDef":20,"maxDef":25},"974":{"name":"Capa de Monje Cocida con Mithril (E/G)","grhIndex":"17058","objType":3,"minDef":23,"maxDef":35},"975":{"name":"Cota de Mallas Larga (E/G)","grhIndex":"17031","objType":3,"minDef":28,"maxDef":40},"976":{"name":"Armadura Pesada de Hierro y Bronce (E/G)","grhIndex":"16788","objType":3,"minDef":30,"maxDef":50},"978":{"name":"Harapos de la Cueva (EO)","grhIndex":"17382","objType":3,"maxDef":1},"979":{"name":"Armadura Maghtere Roja (EO)","grhIndex":"17355","objType":3,"minDef":10,"maxDef":20},"980":{"name":"Armadura de Combate Liviana (EO)","grhIndex":"16706","objType":3,"minDef":14,"maxDef":20},"981":{"name":"Piwafwi de Fuego (EO)","grhIndex":"17436","objType":3,"minDef":16,"maxDef":20},"982":{"name":"Armadura Melee de Explorador (EO)","grhIndex":"17463","objType":3,"minDef":18,"maxDef":22},"984":{"name":"Piwafwi Alto Narbondel (EO)","grhIndex":"17517","objType":3,"minDef":20,"maxDef":25},"986":{"name":"Túnica de Druida","grhIndex":"681","objType":3,"minDef":20,"maxDef":25},"987":{"name":"Estatua de Otto Wallace","grhIndex":"0","objType":8},"988":{"name":"Teleport a Arghal","grhIndex":"669","objType":19},"989":{"name":"Estatua del Panda","grhIndex":"0","objType":8},"990":{"name":"Mazo del Juicio","grhIndex":"20233","objType":2,"minHit":5,"maxHit":16},"991":{"name":"Vestimentas Olímpicas (EO)","grhIndex":"20239","objType":3,"minDef":1,"maxDef":1},"994":{"name":"Vestimentas Olímpicas (E)","grhIndex":"20320","objType":3,"minDef":1,"maxDef":1},"996":{"name":"Vestimentas Olímpicas (H)","grhIndex":"20374","objType":3,"minDef":1,"maxDef":1},"997":{"name":"Antorcha Olímpica","grhIndex":"20405","objType":20},"999":{"name":"Vestimentas Olímpicas (G)","grhIndex":"20433","objType":3,"minDef":1,"maxDef":1},"1000":{"name":"Antorcha Olímpica de Mano","grhIndex":"20460","objType":2,"minHit":1,"maxHit":8},"1001":{"name":"Casco de Águila","grhIndex":"20238","objType":17,"minDef":1,"maxDef":5},"1002":{"name":"Casco de Lobo","grhIndex":"20066","objType":17,"minDef":2,"maxDef":4,"resistenciaMagica":1},"1003":{"name":"Casco de Oso","grhIndex":"20067","objType":17,"minDef":3,"maxDef":5,"resistenciaMagica":5},"1004":{"name":"Casco de Tigre","grhIndex":"20068","objType":17,"minDef":6,"maxDef":9,"resistenciaMagica":7},"1005":{"name":"Hacha de leña Elfica","grhIndex":"603","objType":2},"1006":{"name":"Leña Elfica","grhIndex":"1999","objType":14},"1007":{"name":"Invocar Mascota","grhIndex":"609","objType":24},"1008":{"name":"Árbol élfico","grhIndex":"18802","objType":4},"1009":{"name":"Cuchillas","grhIndex":"2341","objType":2,"minHit":7,"maxHit":16,"proyectil":1},"1010":{"name":"Sombrero Pirata","grhIndex":"22034","objType":17},"1011":{"name":"Farola","grhIndex":"2639","objType":20},"1012":{"name":"Teleport","grhIndex":"669","objType":19},"1013":{"name":"Teleport","grhIndex":"669","objType":19},"1014":{"name":"Teleport","grhIndex":"669","objType":19},"1015":{"name":"Teleport","grhIndex":"669","objType":19},"1016":{"name":"Teleport","grhIndex":"669","objType":19},"1017":{"name":"Teleport","grhIndex":"669","objType":19},"1018":{"name":"Teleport","grhIndex":"669","objType":19},"1019":{"name":"Cartel Asesino","grhIndex":"21969","objType":8},"1020":{"name":"Cartel Bandido","grhIndex":"21970","objType":8},"1021":{"name":"Cartel Bardo","grhIndex":"21971","objType":8},"1022":{"name":"Cartel Clérigo","grhIndex":"21972","objType":8},"1023":{"name":"Cartel Druida","grhIndex":"21973","objType":8},"1024":{"name":"Cartel Guerrero","grhIndex":"21974","objType":8},"1025":{"name":"Cartel Cazador","grhIndex":"21975","objType":8},"1026":{"name":"Cartel Ladrón","grhIndex":"21976","objType":8},"1027":{"name":"Cartel Mago","grhIndex":"21977","objType":8},"1028":{"name":"Cartel Paladín","grhIndex":"21978","objType":8},"1029":{"name":"Cartel Pirata","grhIndex":"21979","objType":8},"1030":{"name":"Cartel Trabajador","grhIndex":"21980","objType":8},"1031":{"name":"Cartel Gremio de Trabajadores","grhIndex":"4909","objType":8},"1032":{"name":"Cartel Leñadores","grhIndex":"3716","objType":8},"1033":{"name":"Cartel","grhIndex":"4986","objType":8},"1034":{"name":"Cartel","grhIndex":"4986","objType":8},"1035":{"name":"Cartel Nemahuak","grhIndex":"4923","objType":8},"1036":{"name":"Guantes de Lucha","grhIndex":"22033","objType":18,"minHit":2,"maxHit":5},"1037":{"name":"Escudo Legionario","grhIndex":"23069","objType":16,"minDef":1,"maxDef":1},"1038":{"name":"Escudo Real","grhIndex":"23068","objType":16,"minDef":1,"maxDef":1},"1039":{"name":"Manto de Sacerdote del Imperio (E/G)","grhIndex":"23150","objType":3,"minDef":6,"maxDef":12},"1040":{"name":"Armadura de Gran Sacerdote Imperial (E/G)","grhIndex":"23096","objType":3,"minDef":15,"maxDef":22},"1041":{"name":"Manto de Sacerdote Demoníaco (E/G)","grhIndex":"23177","objType":3,"minDef":6,"maxDef":12},"1042":{"name":"Armadura de Gran Sacerdote Demoníaco (E/G)","grhIndex":"23123","objType":3,"minDef":15,"maxDef":22},"1043":{"name":"No ves nada interesante.","grhIndex":"0","objType":19},"1044":{"name":"Túnica (H/E/EO) (Newbie)","grhIndex":"606","objType":3},"1045":{"name":"Túnica (E/G) (Newbie)","grhIndex":"1208","objType":3},"1046":{"name":"Armadura de Cuero (H/E/EO) (Newbie)","grhIndex":"526","objType":3,"maxDef":1},"1047":{"name":"Armadura de Cuero (E/G) (Newbie)","grhIndex":"1154","objType":3,"maxDef":1},"1048":{"name":"Escudo (Newbie)","grhIndex":"712","objType":16,"maxDef":1},"1049":{"name":"Laúd Élfico","grhIndex":"1523","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":6},"1050":{"name":"Flauta Élfica","grhIndex":"1522","objType":18,"minHit":1,"maxHit":1,"magicDamageBonus":7},"1051":{"name":"Casco Simple (Newbie)","grhIndex":"559","objType":17,"minDef":1,"maxDef":1},"1052":{"name":"Capucha de Cazador (Newbie)","grhIndex":"467","objType":17,"minDef":1,"maxDef":1},"1053":{"name":"Detectar Invisibilidad","grhIndex":"609","objType":24},"1059":{"name":"Árbol Nevado","grhIndex":"50083","objType":4},"1060":{"name":"Árbol Nevado","grhIndex":"50084","objType":4},"1061":{"name":"Árbol Nevado","grhIndex":"50085","objType":4},"1062":{"name":"Piel de lobo invernal","grhIndex":"52395","objType":14},"1063":{"name":"Equipo invernal (H-E-EO/H)","grhIndex":"52218","objType":3,"minDef":20,"maxDef":20},"1064":{"name":"Equipo invernal (E/G-H/M)","grhIndex":"52219","objType":3,"minDef":20,"maxDef":20}} \ No newline at end of file diff --git a/api/src/repositories/gameNpcs.ts b/api/src/repositories/gameNpcs.ts index 6585d85d..8d8a66d7 100644 --- a/api/src/repositories/gameNpcs.ts +++ b/api/src/repositories/gameNpcs.ts @@ -11,6 +11,13 @@ import { const CURRENT_EXP_MULTIPLIER = 5; const CURRENT_GOLD_MULTIPLIER = 3; +/** + * Tamano de pagina cuando se pide el catalogo entero. Es un techo, no un + * objetivo: el catalogo real ronda los 340 NPCs y esto solo evita que un + * `all=true` se convierta en una consulta sin limite. + */ +const FULL_CATALOG_PAGE_SIZE = 10_000; + type GameNpcRow = { id: number; name: string; @@ -87,31 +94,34 @@ const gameNpcSchema = z }) .catchall(z.unknown()); +/** + * Bandera booleana que llega como query string ("true", "1", ausente...). + * + * Lo que no se reconoce vale `false`, igual que en el catalogo de objetos: la + * ruta envuelve los errores de parseo en un 500, asi que un `?all=quizas` + * contestaria una falla del servidor por una query mal escrita. + */ +const queryFlagSchema = z.preprocess((value) => { + const raw = Array.isArray(value) ? value[0] : value; + + return raw === true || raw === "true" || raw === "1"; +}, z.boolean()); + const listFiltersSchema = z.object({ search: z.string().trim().optional(), npcType: z.coerce.number().int().optional(), - hostileOnly: z.preprocess((value) => { - const raw = Array.isArray(value) ? value[0] : value; - if (raw === true || raw === "true" || raw === "1") { - return true; - } - if ( - raw === false || - raw === "false" || - raw === "0" || - raw == null || - raw === "" - ) { - return false; - } - return raw; - }, z.boolean().optional()), + hostileOnly: queryFlagSchema, sortBy: z .enum(["id", "expPerHp", "expReward", "goldReward", "maxHp"]) .optional(), sortDirection: z.enum(["asc", "desc"]).optional(), limit: z.coerce.number().int().min(1).max(200).optional(), page: z.coerce.number().int().min(1).optional(), + /** + * Devuelve el catalogo completo en una sola pagina. El editor visual lo + * necesita para buscar y filtrar del lado del cliente sobre los 340 NPCs. + */ + all: queryFlagSchema, }); function isHostileNpc(data: GameNpcRecordData): boolean { @@ -305,8 +315,8 @@ export async function listGameNpcs(filters: unknown) { const parsed = listFiltersSchema.parse(filters ?? {}); const values: Array = []; const conditions: string[] = []; - const pageSize = parsed.limit ?? 100; - const page = parsed.page ?? 1; + const pageSize = parsed.all ? FULL_CATALOG_PAGE_SIZE : (parsed.limit ?? 100); + const page = parsed.all ? 1 : (parsed.page ?? 1); const offset = (page - 1) * pageSize; if (parsed.search) { diff --git a/api/src/repositories/gameObjects.ts b/api/src/repositories/gameObjects.ts index 78b2cb32..d4d60051 100644 --- a/api/src/repositories/gameObjects.ts +++ b/api/src/repositories/gameObjects.ts @@ -25,8 +25,23 @@ const listFiltersSchema = z.object({ objType: z.coerce.number().int().optional(), limit: z.coerce.number().int().min(1).max(200).optional(), page: z.coerce.number().int().min(1).optional(), + /** + * Devuelve el catalogo completo en una sola pagina. El editor visual lo + * necesita para buscar y filtrar del lado del cliente sobre 1.062 objetos. + */ + all: z.preprocess((value) => { + const raw = Array.isArray(value) ? value[0] : value; + return raw === true || raw === "true" || raw === "1"; + }, z.boolean()), }); +/** + * Tamano de pagina cuando se pide el catalogo entero. Es un techo, no un + * objetivo: el catalogo real ronda los mil objetos y esto solo evita que un + * `all=true` se convierta en una consulta sin limite. + */ +const FULL_CATALOG_PAGE_SIZE = 10_000; + let seedPromise: Promise | null = null; async function insertRevision(client: PoolClient, entityId: number, checksum: string): Promise { @@ -97,6 +112,7 @@ function toGameObjectSummary(row: GameObjectRow) { id: row.id, name: row.name, objType: row.obj_type, + grhIndex: Number(row.data.grhIndex ?? 0), version: Number(row.version), updatedAt: row.updated_at.toISOString(), }; @@ -107,8 +123,8 @@ export async function listGameObjects(filters: unknown) { const parsed = listFiltersSchema.parse(filters ?? {}); const values: Array = []; const conditions: string[] = []; - const pageSize = parsed.limit ?? 100; - const page = parsed.page ?? 1; + const pageSize = parsed.all ? FULL_CATALOG_PAGE_SIZE : (parsed.limit ?? 100); + const page = parsed.all ? 1 : (parsed.page ?? 1); const offset = (page - 1) * pageSize; if (parsed.search) { diff --git a/api/src/repositories/worldBuilder.ts b/api/src/repositories/worldBuilder.ts index 8fd58c95..4402a924 100644 --- a/api/src/repositories/worldBuilder.ts +++ b/api/src/repositories/worldBuilder.ts @@ -1,4 +1,7 @@ import crypto from "crypto"; +import { existsSync } from "fs"; +import fs from "fs/promises"; +import path from "path"; import { z } from "zod"; import pool from "../db"; import { validatePngUpload } from "../lib/pngValidation"; @@ -12,6 +15,18 @@ export const UPLOADED_GRAPHIC_INDEX_START = 1_000_000; /** Los mapas del juego son de 100x100. */ export const MAP_SIZE = 100; +/** Un tile admite un objeto y un NPC, como el modelo del juego. */ +export const TILE_ENTITY_KINDS = ["obj", "npc"] as const; + +export type TileEntityKind = (typeof TILE_ENTITY_KINDS)[number]; + +export type TileEntityPlacement = { + x: number; + y: number; + kind: TileEntityKind; + entityId: number; +}; + export type UploadedGraphic = { grhIndex: number; checksum: string; @@ -192,6 +207,13 @@ export const paintTilesSchema = z.object({ tiles: z.array(tilePaintSchema).min(1).max(500), }); +export const tileEntitySchema = z.object({ + x: z.coerce.number().int().min(1).max(MAP_SIZE), + y: z.coerce.number().int().min(1).max(MAP_SIZE), + kind: z.enum(TILE_ENTITY_KINDS), + entityId: z.coerce.number().int().positive(), +}); + export type TilePaint = z.infer; export type MapTileOverride = { @@ -203,6 +225,10 @@ export type MapTileOverride = { status: "draft" | "published"; }; +export type MapTileEntity = TileEntityPlacement & { + status: "draft" | "published"; +}; + /** * Pinta tiles como BORRADOR. No los ve ningun jugador hasta publicar. * @@ -311,11 +337,122 @@ export async function listMapOverrides( })); } +/** + * Coloca un objeto o un NPC en un tile como BORRADOR. + * + * El id referenciado tiene que existir en la tabla correspondiente, igual que + * un grafico referenciado tiene que existir antes de pintarlo. + */ +export async function placeTileEntity( + mapNum: number, + placement: TileEntityPlacement, + accountId: string, +): Promise<{ placed: boolean }> { + const client = await pool.connect(); + + try { + await client.query("BEGIN"); + + const kind = placement.kind; + const tableName = kind === "obj" ? "game_objects" : "game_npcs"; + const exists = await client.query( + `SELECT 1 FROM ${tableName} WHERE id = $1 LIMIT 1`, + [placement.entityId], + ); + + if (exists.rowCount === 0) { + throw new Error( + `El ${kind === "obj" ? "objeto" : "NPC"} ${placement.entityId} no existe.`, + ); + } + + await client.query( + `INSERT INTO game_map_tile_entities + (map_num, x, y, kind, entity_id, status, updated_by_account_id, updated_at) + VALUES ($1, $2, $3, $4, $5, 'draft', $6, NOW()) + ON CONFLICT (map_num, x, y, kind, status) DO UPDATE + SET entity_id = EXCLUDED.entity_id, + updated_by_account_id = EXCLUDED.updated_by_account_id, + updated_at = NOW()`, + [ + mapNum, + placement.x, + placement.y, + kind, + placement.entityId, + accountId, + ], + ); + + await client.query("COMMIT"); + + return { placed: true }; + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } +} + +/** Quita el objeto o NPC colocado en un tile (borrador). */ +export async function removeTileEntity( + mapNum: number, + x: number, + y: number, + kind: TileEntityKind, +): Promise { + const result = await pool.query( + `DELETE FROM game_map_tile_entities + WHERE map_num = $1 AND x = $2 AND y = $3 AND kind = $4 AND status = 'draft'`, + [mapNum, x, y, kind], + ); + + return (result.rowCount ?? 0) > 0; +} + +/** + * Entidades de un mapa. + * + * Misma regla que los tiles: un jugador comun recibe solo lo publicado y un + * admin recibe ademas sus borradores, con el borrador pisando a lo publicado. + */ +export async function listMapTileEntities( + mapNum: number, + includeDrafts = false, +): Promise { + const query = includeDrafts + ? `SELECT DISTINCT ON (x, y, kind) x, y, kind, entity_id, status + FROM game_map_tile_entities + WHERE map_num = $1 + ORDER BY x, y, kind, status ASC` + : `SELECT x, y, kind, entity_id, status + FROM game_map_tile_entities + WHERE map_num = $1 AND status = 'published' + ORDER BY y, x, kind`; + + const result = await pool.query<{ + x: number; + y: number; + kind: string; + entity_id: number; + status: string; + }>(query, [mapNum]); + + return result.rows.map((row) => ({ + x: row.x, + y: row.y, + kind: row.kind as TileEntityKind, + entityId: row.entity_id, + status: row.status as "draft" | "published", + })); +} + /** Publica los borradores de un mapa: a partir de aca los ven los jugadores. */ export async function publishMap( mapNum: number, accountId: string, -): Promise<{ published: number }> { +): Promise<{ published: number; publishedEntities: number }> { const client = await pool.connect(); try { @@ -340,9 +477,30 @@ export async function publishMap( [mapNum], ); + const entitiesResult = await client.query( + `INSERT INTO game_map_tile_entities + (map_num, x, y, kind, entity_id, status, updated_by_account_id, updated_at) + SELECT map_num, x, y, kind, entity_id, 'published', $2, NOW() + FROM game_map_tile_entities + WHERE map_num = $1 AND status = 'draft' + ON CONFLICT (map_num, x, y, kind, status) DO UPDATE + SET entity_id = EXCLUDED.entity_id, + updated_by_account_id = EXCLUDED.updated_by_account_id, + updated_at = NOW()`, + [mapNum, accountId], + ); + + await client.query( + `DELETE FROM game_map_tile_entities WHERE map_num = $1 AND status = 'draft'`, + [mapNum], + ); + await client.query("COMMIT"); - return { published: result.rowCount ?? 0 }; + return { + published: result.rowCount ?? 0, + publishedEntities: entitiesResult.rowCount ?? 0, + }; } catch (error) { await client.query("ROLLBACK"); throw error; @@ -354,13 +512,21 @@ export async function publishMap( /** Descarta los borradores sin tocar lo que ya esta publicado. */ export async function discardDrafts( mapNum: number, -): Promise<{ discarded: number }> { +): Promise<{ discarded: number; discardedEntities: number }> { const result = await pool.query( `DELETE FROM game_map_tile_overrides WHERE map_num = $1 AND status = 'draft'`, [mapNum], ); - return { discarded: result.rowCount ?? 0 }; + const entitiesResult = await pool.query( + `DELETE FROM game_map_tile_entities WHERE map_num = $1 AND status = 'draft'`, + [mapNum], + ); + + return { + discarded: result.rowCount ?? 0, + discardedEntities: entitiesResult.rowCount ?? 0, + }; } /** @@ -369,20 +535,30 @@ export async function discardDrafts( */ export async function revertMap( mapNum: number, -): Promise<{ reverted: number }> { +): Promise<{ reverted: number; revertedEntities: number }> { const result = await pool.query( `DELETE FROM game_map_tile_overrides WHERE map_num = $1`, [mapNum], ); - return { reverted: result.rowCount ?? 0 }; + const entitiesResult = await pool.query( + `DELETE FROM game_map_tile_entities WHERE map_num = $1`, + [mapNum], + ); + + return { + reverted: result.rowCount ?? 0, + revertedEntities: entitiesResult.rowCount ?? 0, + }; } -/** Cuantos tiles tiene el mapa en cada estado, para mostrar en la UI. */ +/** Cuantos tiles y entidades tiene el mapa en cada estado, para mostrar en la UI. */ export async function getMapStatus(mapNum: number): Promise<{ mapNum: number; draft: number; published: number; + draftEntities: number; + publishedEntities: number; }> { const result = await pool.query<{ status: string; count: string }>( `SELECT status, COUNT(*)::text AS count @@ -392,14 +568,27 @@ export async function getMapStatus(mapNum: number): Promise<{ [mapNum], ); + const entitiesResult = await pool.query<{ status: string; count: string }>( + `SELECT status, COUNT(*)::text AS count + FROM game_map_tile_entities + WHERE map_num = $1 + GROUP BY status`, + [mapNum], + ); + const counts = new Map( result.rows.map((row) => [row.status, Number(row.count)]), ); + const entityCounts = new Map( + entitiesResult.rows.map((row) => [row.status, Number(row.count)]), + ); return { mapNum, draft: counts.get("draft") ?? 0, published: counts.get("published") ?? 0, + draftEntities: entityCounts.get("draft") ?? 0, + publishedEntities: entityCounts.get("published") ?? 0, }; } @@ -417,3 +606,100 @@ export async function clearTile( return (result.rowCount ?? 0) > 0; } + +/** + * Ruta al directorio fuente de un mapa dentro del proyecto. + * + * Los mapas editables viven como archivos (terrain.json con paleta + filas, + * specials.json, npcs.json, meta.json) y la API los re-exporta para el + * frontend. Si la copia fuente no existe, el mapa no es editable. + */ +function resolveMapsSourceDir(): string { + const candidates = [ + path.resolve(__dirname, ".."), + path.resolve(__dirname, "..", "..", "src"), + ]; + + for (const candidate of candidates) { + if (existsSync(path.join(candidate, "mapas_source"))) { + return path.join(candidate, "mapas_source"); + } + } + + return path.join(candidates[0], "mapas_source"); +} + +export type TerrainPaletteEntry = { + id: number; + graphics: Array; + blocked: boolean; +}; + +/** Cuantos graficos subidos entran en la paleta del editor. */ +const MAX_PALETTE_UPLOADED_GRAPHICS = 500; + +export type TerrainPaletteResult = { + mapNum: number; + palette: TerrainPaletteEntry[]; + uploadedGraphics: UploadedGraphic[]; +}; + +/** + * Paleta de tiles del mapa actual. + * + * Cada entrada es un tile de la paleta del mapa fuente (terrain.json), con los + * graficos de sus capas y si bloquea. Los graficos subidos por administradores + * se suman al final: son tiles disponibles para pintar igual que los + * originales, con la diferencia de que cada imagen es un tile completo. + */ +export async function getMapTerrainPalette( + mapNum: number, +): Promise { + const mapsSourceDir = resolveMapsSourceDir(); + const terrainPath = path.join( + mapsSourceDir, + `mapa_${mapNum}`, + "terrain.json", + ); + + if (!existsSync(terrainPath)) { + throw new Error(`El mapa ${mapNum} no tiene paleta fuente.`); + } + + const terrain = JSON.parse(await fs.readFile(terrainPath, "utf8")) as { + palette?: Record; + }; + + const palette: TerrainPaletteEntry[] = []; + + for (const [id, entry] of Object.entries(terrain.palette ?? {})) { + const parsedId = Number.parseInt(id, 10); + + if (!Number.isInteger(parsedId) || parsedId <= 0) { + continue; + } + + const rawGraphics = Array.isArray(entry.graphics) + ? entry.graphics + : [entry.graphics]; + const graphics = rawGraphics.map((grh) => { + const parsed = Number(grh); + + return Number.isInteger(parsed) && parsed > 0 ? parsed : null; + }); + + palette.push({ + id: parsedId, + graphics, + blocked: Boolean(entry.blocked), + }); + } + + palette.sort((left, right) => left.id - right.id); + + return { + mapNum, + palette, + uploadedGraphics: await listGraphics(MAX_PALETTE_UPLOADED_GRAPHICS), + }; +} diff --git a/api/src/server.ts b/api/src/server.ts index d6f97469..fab5b69f 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -100,12 +100,17 @@ import { discardDrafts, getGraphicContent, getMapStatus, + getMapTerrainPalette, listGraphics, listMapOverrides, + listMapTileEntities, paintTiles, paintTilesSchema, + placeTileEntity, publishMap, + removeTileEntity, revertMap, + tileEntitySchema, uploadGraphic, } from "./repositories/worldBuilder"; import { MAX_PNG_BYTES } from "./lib/pngValidation"; @@ -942,6 +947,7 @@ app.get("/maps/:mapNum/overrides", async (request, response) => { mapNum, includeDrafts, overrides: await listMapOverrides(mapNum, includeDrafts), + entities: await listMapTileEntities(mapNum, includeDrafts), }); } catch (error) { const message = @@ -950,6 +956,62 @@ app.get("/maps/:mapNum/overrides", async (request, response) => { } }); +/** + * Overrides y entidades de un mapa para el editor visual. + * + * Existe aparte del endpoint publico porque el editor necesita que "sin + * permiso" sea un error explicito: la ruta publica degrada a lo publicado y el + * editor mostraria un mapa sin borradores como si estuviera todo bien. + */ +app.get( + "/admin/game-data/maps/:mapNum/overrides", + async (request, response) => { + try { + const authorized = await requireAdminEmailSession( + request, + response, + ); + if (!authorized) return; + + const mapNum = Number.parseInt(request.params.mapNum ?? "", 10); + + if (!Number.isInteger(mapNum) || mapNum <= 0) { + response.status(400).json({ error: "Numero de mapa invalido." }); + return; + } + + response.json({ + mapNum, + includeDrafts: true, + overrides: await listMapOverrides(mapNum, true), + entities: await listMapTileEntities(mapNum, true), + }); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unexpected error"; + response.status(400).json({ error: message }); + } + }, +); + +/** + * Responde si la sesion actual puede usar el modo construccion. + * + * El frontend lo necesita para no ofrecer el editor a quien no puede entrar: + * la sesion publica no expone si la cuenta es admin de game-data, y adivinarlo + * desde el cliente significaria filtrar el email de admin al navegador. + */ +app.get("/admin/game-data/session", async (request, response) => { + const authorized = await requireAdminEmailSession(request, response); + + if (!authorized) return; + + response.json({ + isGameDataAdmin: true, + accountId: authorized.session.account._id, + }); +}); + /** Publica los borradores de un mapa. A partir de aca los ven los jugadores. */ app.post("/admin/game-data/maps/:mapNum/publish", async (request, response) => { try { @@ -1039,6 +1101,117 @@ app.get("/admin/game-data/maps/:mapNum/status", async (request, response) => { } }); +/** + * Paleta de tiles disponibles para el mapa actual: las entradas de la paleta + * fuente (terrain.json) mas los graficos subidos por administradores. + */ +app.get( + "/admin/game-data/maps/:mapNum/terrain", + async (request, response) => { + try { + const authorized = await requireAdminEmailSession( + request, + response, + ); + if (!authorized) return; + + const mapNum = Number.parseInt(request.params.mapNum ?? "", 10); + + if (!Number.isInteger(mapNum) || mapNum <= 0) { + response.status(400).json({ error: "Numero de mapa invalido." }); + return; + } + + response.json(await getMapTerrainPalette(mapNum)); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unexpected error"; + response + .status(message.startsWith("El mapa") ? 404 : 400) + .json({ error: message }); + } + }, +); + +/** Coloca un objeto o un NPC en un tile, como borrador. */ +app.put( + "/admin/game-data/maps/:mapNum/entities", + async (request, response) => { + try { + const authorized = await requireAdminEmailSession( + request, + response, + ); + if (!authorized) return; + + const mapNum = Number.parseInt(request.params.mapNum ?? "", 10); + + if (!Number.isInteger(mapNum) || mapNum <= 0) { + response.status(400).json({ error: "Numero de mapa invalido." }); + return; + } + + const parsed = tileEntitySchema.safeParse(request.body); + + if (!parsed.success) { + response + .status(400) + .json({ error: JSON.stringify(parsed.error.issues) }); + return; + } + + response.json( + await placeTileEntity( + mapNum, + parsed.data, + authorized.session.account._id, + ), + ); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unexpected error"; + response.status(400).json({ error: message }); + } + }, +); + +/** Quita el objeto o NPC colocado en un tile (solo borradores). */ +app.delete( + "/admin/game-data/maps/:mapNum/entities/:x/:y/:kind", + async (request, response) => { + try { + const authorized = await requireAdminEmailSession( + request, + response, + ); + if (!authorized) return; + + const mapNum = Number.parseInt(request.params.mapNum ?? "", 10); + const x = Number.parseInt(request.params.x ?? "", 10); + const y = Number.parseInt(request.params.y ?? "", 10); + const kind = request.params.kind ?? ""; + + if ( + !Number.isInteger(mapNum) || + !Number.isInteger(x) || + !Number.isInteger(y) || + (kind !== "obj" && kind !== "npc") + ) { + response.status(400).json({ error: "Parametros invalidos." }); + return; + } + + response.json({ + removed: await removeTileEntity(mapNum, x, y, kind), + }); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unexpected error"; + response.status(400).json({ error: message }); + } + }, +); + app.get( "/internal/game-data/objects", requireAuth, diff --git a/api/src/tests/world-builder.integration.test.ts b/api/src/tests/world-builder.integration.test.ts new file mode 100644 index 00000000..19ebebaf --- /dev/null +++ b/api/src/tests/world-builder.integration.test.ts @@ -0,0 +1,226 @@ +import assert from "node:assert/strict"; +import { beforeAll, test } from "vitest"; +import { getMapTerrainPalette } from "../repositories/worldBuilder"; +import { + API_AUTH, + ensureApiReady, + requestJson, +} from "./helpers/api"; + +beforeAll(async () => { + await ensureApiReady(); +}); + +test("world builder admin endpoints are forbidden without the admin proxy token", async () => { + const [entities, terrain, status] = await Promise.all([ + requestJson<{ error?: string }>( + "/admin/game-data/maps/1/entities", + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${API_AUTH}`, + }, + body: JSON.stringify({ + x: 1, + y: 1, + kind: "obj", + entityId: 1, + }), + }, + ), + requestJson<{ error?: string }>( + "/admin/game-data/maps/1/terrain", + { + headers: { + Authorization: `Bearer ${API_AUTH}`, + }, + }, + ), + requestJson<{ error?: string }>( + "/admin/game-data/maps/1/status", + { + headers: { + Authorization: `Bearer ${API_AUTH}`, + }, + }, + ), + ]); + + assert.equal(entities.status, 403); + assert.equal(terrain.status, 403); + assert.equal(status.status, 403); +}); + +test("map overrides endpoint returns entities alongside tiles", async () => { + const response = await requestJson<{ + mapNum?: number; + overrides?: unknown[]; + entities?: unknown[]; + error?: string; + }>("/maps/1/overrides"); + + assert.equal(response.status, 200); + assert.equal(response.data.mapNum, 1); + assert.equal(Array.isArray(response.data.overrides), true); + assert.equal(Array.isArray(response.data.entities), true); +}); + +test("admin map overrides endpoint is forbidden without the admin proxy token", async () => { + const response = await requestJson<{ error?: string }>( + "/admin/game-data/maps/1/overrides", + { + headers: { + Authorization: `Bearer ${API_AUTH}`, + }, + }, + ); + + assert.equal(response.status, 403); +}); + +test("game data session endpoint is forbidden without the admin proxy token", async () => { + const response = await requestJson<{ + isGameDataAdmin?: boolean; + error?: string; + }>("/admin/game-data/session", { + headers: { + Authorization: `Bearer ${API_AUTH}`, + }, + }); + + // El frontend usa este endpoint para decidir si muestra el editor: si + // dejara de exigir el token, cualquier cuenta veria el modo construccion. + assert.equal(response.status, 403); + assert.notEqual(response.data.isGameDataAdmin, true); +}); + +test("terrain palette keeps one graphic per layer and lists uploaded graphics", async () => { + const result = await getMapTerrainPalette(1); + + assert.equal(result.mapNum, 1); + assert.ok(result.palette.length > 0); + // El nombre del campo es parte del contrato: el editor lo lee para la + // pestaña de graficos subidos y un `?? []` esconderia el error. + assert.equal(Array.isArray(result.uploadedGraphics), true); + + for (const entry of result.palette) { + assert.equal(Number.isInteger(entry.id), true); + assert.ok(entry.id > 0); + assert.equal(typeof entry.blocked, "boolean"); + assert.equal(Array.isArray(entry.graphics), true); + assert.ok(entry.graphics.length >= 1); + assert.ok(entry.graphics.length <= 4); + + for (const graphic of entry.graphics) { + assert.ok( + graphic === null || + (Number.isInteger(graphic) && graphic > 0), + `grafico invalido en la entrada ${entry.id}`, + ); + } + } + + // Una entrada de la paleta es el tile completo: si el arreglo se aplanara a + // un solo grafico, pintar dejaria las capas de arriba del tile anterior. + assert.ok( + result.palette.some((entry) => entry.graphics.length > 1), + "la paleta del mapa 1 tiene tiles de varias capas", + ); +}); + +test("objects internal endpoint supports all=true for the editor catalog", async () => { + const response = await requestJson<{ + objects?: Array<{ id: number; name: string; objType: number }>; + pagination?: { total?: number; totalPages?: number }; + error?: string; + }>("/internal/game-data/objects?all=true", { + headers: { + Authorization: API_AUTH, + }, + }); + + assert.equal(response.status, 200); + assert.equal(Array.isArray(response.data.objects), true); + assert.ok((response.data.objects?.length ?? 0) > 900); + assert.equal(response.data.pagination?.totalPages, 1); +}); + +test("npcs internal endpoint supports all=true for the editor catalog", async () => { + const response = await requestJson<{ + npcs?: Array<{ id: number; name: string }>; + pagination?: { total?: number; totalPages?: number }; + error?: string; + }>("/internal/game-data/npcs?all=true", { + headers: { + Authorization: API_AUTH, + }, + }); + + assert.equal(response.status, 200); + assert.equal(Array.isArray(response.data.npcs), true); + assert.ok((response.data.npcs?.length ?? 0) > 300); + assert.equal(response.data.pagination?.totalPages, 1); +}); + +test("catalog flags accept the shapes a query string can produce", async () => { + // `?all=1` y `?all=false` llegan como texto: el editor pide el catalogo + // completo por query string, y un flag mal interpretado le devuelve 100 + // objetos sin ningun error visible. + const [numeric, falsy] = await Promise.all([ + requestJson<{ + objects?: unknown[]; + pagination?: { totalPages?: number }; + }>("/internal/game-data/objects?all=1", { + headers: { + Authorization: API_AUTH, + }, + }), + requestJson<{ + objects?: unknown[]; + pagination?: { totalPages?: number }; + }>("/internal/game-data/objects?all=false", { + headers: { + Authorization: API_AUTH, + }, + }), + ]); + + assert.equal(numeric.status, 200); + assert.equal(numeric.data.pagination?.totalPages, 1); + assert.ok((numeric.data.objects?.length ?? 0) > 900); + + assert.equal(falsy.status, 200); + assert.equal(falsy.data.objects?.length, 100); + assert.ok((falsy.data.pagination?.totalPages ?? 0) > 1); +}); + +test("unrecognized catalog flags default to false instead of failing", async () => { + // Las rutas de catalogo envuelven cualquier error en un 500, asi que un flag + // que no se reconoce tiene que valer `false` y no reventar: un `?all=quizas` + // es una query mal escrita del cliente, no una falla del servidor. + const [objects, npcs, hostile] = await Promise.all([ + requestJson<{ objects?: unknown[] }>( + "/internal/game-data/objects?all=quizas", + { headers: { Authorization: API_AUTH } }, + ), + requestJson<{ npcs?: unknown[] }>( + "/internal/game-data/npcs?all=quizas", + { headers: { Authorization: API_AUTH } }, + ), + requestJson<{ pagination?: { total?: number } }>( + "/internal/game-data/npcs?hostileOnly=quizas", + { headers: { Authorization: API_AUTH } }, + ), + ]); + + assert.equal(objects.status, 200); + assert.equal(objects.data.objects?.length, 100); + + assert.equal(npcs.status, 200); + assert.equal(npcs.data.npcs?.length, 100); + + // Sin filtrar: el catalogo entero, no solo los hostiles. + assert.equal(hostile.status, 200); + assert.ok((hostile.data.pagination?.total ?? 0) > 300); +}); \ No newline at end of file diff --git a/docs/licensing-notes.md b/docs/licensing-notes.md new file mode 100644 index 00000000..0aa7191c --- /dev/null +++ b/docs/licensing-notes.md @@ -0,0 +1,32 @@ +# Notas de licencia + +## Referencia conceptual: AO-object-editor + +Este modo construccion se inspira en el proyecto de referencia: + +- Repositorio: https://github.com/elpatoenlasolas/AO-object-editor +- Licencia del repositorio: **sin licencia declarada** (sin LICENSE, sin README + de licencia al momento de escribir estas notas). + +Por ese motivo todo el codigo de esta implementacion es **original**: no se +copio codigo, texto, assets ni estructura interna del repositorio de +referencia. Se uso unicamente como fuente de informacion conceptual sobre el +flujo de edicion de mapas (paleta de terreno, catalogo de objetos y NPCs, +pintado de tiles, vista previa en vivo, publicacion de cambios). + +Cualquier coincidencia en nombres de archivos o de componentes es generica +(editor, paleta, browser) y no deriva de la obra de referencia. + +## Datos de juego (api/src/jsons) + +- `objs.json`: copiado de `frontend/public/init/objs.json`, que es parte del + propio repositorio (datos del juego Argentum Online original). +- `npcs.json`: datos del juego Argentum Online original, obtenidos del branch + `fix/npcs-json-seed` del PR #98 del repositorio principal, que restaura el + archivo faltante del seed. Los datos de juego de Argentum Online son de + codigo abierto (GPL) desde sus origenes. + +## Dependencias + +No se agregaron dependencias nuevas; se usan las ya presentes +(pixi.js, next, react) con sus licencias existentes. diff --git a/docs/modo-construccion-guide.md b/docs/modo-construccion-guide.md new file mode 100644 index 00000000..433b770e --- /dev/null +++ b/docs/modo-construccion-guide.md @@ -0,0 +1,157 @@ +# Modo construccion — guia de prueba manual y mensaje de PR + +## Guia de prueba manual (paso a paso) + +Requisitos locales: + +1. PostgreSQL local con la base `aoweb` y `schema.sql` aplicado. +2. API en `http://127.0.0.1:3001` con variables de entorno: + - `GAME_DATA_ADMIN_EMAIL=admin@local.test` + - `GAME_DATA_ADMIN_ACCOUNT_ID=` + - `GAME_DATA_ADMIN_PROXY_TOKEN=admin-proxy-token` +3. Frontend en `http://localhost:3000` con: + - `API_BASE_URL=http://127.0.0.1:3001` + - `GAME_DATA_ADMIN_PROXY_TOKEN=admin-proxy-token` + +Casos: + +### 1. Acceso y carga + +- Iniciar sesion con la cuenta admin. +- El menu superior muestra el link "Construccion" (solo con sesion iniciada). +- Entrar a `/construccion`: + - Se ven la barra de herramientas, el panel izquierdo con pestanas + Terreno / Objetos / NPCs, el lienzo con el mapa 1 centrado y la tira de + recientes vacia. + - El panel "Terreno" muestra la paleta del mapa (entradas con numero y + punto rojo si estan bloqueadas). + +### 2. Pincel de terreno + +- En el panel Terreno, elegir un tile cualquiera. Hacer clic en el lienzo: + el tile cambia y aparece el marcador "Aplicando cambios...". +- Arrastrar el mouse sobre varios tiles: se pinta en lote (una sola llamada). +- Verificar en el panel izquierdo "Actualizando..." y que el contador de + Borradores en la barra suba. +- Probar zoom con la rueda (se ancla al cursor) y paneo con + Shift+click o boton central. + +### 3. Objetos + +- Pestana "Objetos": buscar "espada" o por id. Cada item muestra miniatura, + nombre y tipo. +- Seleccionar uno y hacer clic en el lienzo: aparece el objeto anclado al + piso del tile (con la posicion del juego). +- El item queda en "Recientes"; hacer clic en el chip vuelve a seleccionar + la herramienta. + +### 4. NPCs + +- Pestana "NPCs": se ven los NPCs con su personaje (cuerpo y cabeza). +- Seleccionar uno y colocarlo: se renderiza con un tinte violeta + (se distingue de los objetos). +- En "Recientes" aparece con la miniatura de su cabeza. + +### 5. Borrar + +- Herramienta "Borrar": clic sobre un tile editado quita el override (las + cuatro capas) o el borrador de la entidad. +- Nota: los cambios ya publicados no se borran con esta herramienta; se + revierten con el boton "Revertir". + +### 6. Publicar, descartar y revertir + +- "Publicar": los borradores pasan a publicados; el contador de Publicados + sube y Borradores baja. Recargar la pagina: los cambios persisten. +- Sin sesion de admin (o visitante anonimo), `/maps/1/overrides` sigue + devolviendo solo lo publicado (vista de jugador). +- "Revertir": restaura el ultimo estado publicado (quita overrides y + entidades publicados). +- "Descartar borradores": elimina los borradores sin tocar lo publicado + (deshabilitado si no hay borradores). + +### 7. Grilla y bloqueo + +- Alternar "Grilla" y "Bloqueo" (botones arriba a la derecha del lienzo). +- El overlay de bloqueo muestra en rojo los tiles bloqueados del mapa base y + en rojo mas intenso los bloqueados por override. + +### 8. Subir PNG + +- En el panel Terreno, "Subir PNG nuevo", elegir un PNG de 32x32 o multiplo + de 32 (los de otro tamano se rechazan con mensaje claro). +- El grafico aparece en la seccion "Subidos" y se puede pintar en el mapa. + +### 9. Cambio de mapa + +- En la barra, cambiar el numero de mapa: el lienzo, la paleta, el estado y + los recientes de ese mapa se recargan. + +### 10. Seguridad + +- Con sesion de un usuario NO admin: el proxy devuelve 403 y la pagina + muestra el error ("El modo construccion no esta habilitado." / error de la + API) sin romper el resto del sitio. +- Sin sesion: 401. + +## Mensaje del PR + +```markdown +## Que incluye + +Implementacion del modo construccion (issue #29): un editor visual de mapas +con paleta de terreno, catalogo de objetos y NPCs, pintado de tiles, vista +previa en vivo y publicacion de cambios. + +### Cliente (`frontend/`) + +- Pagina `/construccion` con lienzo PixiJS (zoom con rueda, paneo con + Shift+click o boton central, grilla, overlay de tiles bloqueados). +- Paleta de terreno por mapa (terrain.json + graficos subidos), con subida de + PNG nuevos. +- Catalogos de objetos (con tipo y grhIndex) y de NPCs (cuerpo/cabeza) con + busqueda, filtro por tipo y lista virtualizada. +- Herramientas de pincel de terreno (lote de hasta 500 tiles), colocacion de + objetos/NPCs, borrado, y acciones Publicar / Descartar borradores / Revertir. +- Tira de elementos recientes (persistida en localStorage). +- Proxy `app/api/editor/[...path]` que reenvia al API con la sesion del + usuario y el token de proxy de admin (`GAME_DATA_ADMIN_PROXY_TOKEN`, + ya previsto como build arg en el Dockerfile). El token nunca llega al + navegador. + +### API (`api/`) + +- Tabla `game_map_tile_entities` para objetos/NPCs colocados, con estado + draft/published (mismo modelo que los overrides de tiles). +- Endpoints admin nuevos: `GET /admin/game-data/maps/:n/overrides` + (con borradores), `PUT/DELETE /admin/game-data/maps/:n/entities`, + `GET /admin/game-data/maps/:n/terrain`. +- `publish`/`discard`/`revert`/`status` extendidos para incluir entidades. +- `GET /internal/game-data/objects|npcs?all=true` para catalogo completo. +- `grhIndex` agregado al resumen de objetos. +- Restaurados los seeds faltantes `api/src/jsons/objs.json` y + `api/src/jsons/npcs.json` (cierra #83): sin ellos los endpoints internos + devuelven 500 y el CI falla. +- Tests de integracion nuevos: 4 casos en `world-builder.integration.test.ts`. + +### Documentacion + +- `docs/licensing-notes.md`: notas de licencia (el proyecto de referencia + no declara licencia; todo el codigo es original). + +## Como probar + +Ver `docs/modo-construccion-guide.md` (paso a paso) o en sintesis: + +1. Levantar API con las env `GAME_DATA_ADMIN_*` y frontend con + `API_BASE_URL` apuntando a la API. +2. Entrar a `/construccion` con la cuenta admin. +3. Pintar terreno, colocar un objeto y un NPC, y publicar; verificar que un + jugador anonimo ve los cambios en el juego. + +## Nota sobre PRs existentes + +Los seeds `objs.json`/`npcs.json` tambien estan en los PRs #96, #98 y #100 +(issue #83). Si se mergean antes, este PR conserva los archivos tal cual y el +conflicto, si aparece, se resuelve manteniendo el contenido identico. +``` diff --git a/frontend/app/api/editor/[...path]/route.ts b/frontend/app/api/editor/[...path]/route.ts new file mode 100644 index 00000000..795c2374 --- /dev/null +++ b/frontend/app/api/editor/[...path]/route.ts @@ -0,0 +1,109 @@ +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { getApiBaseUrlCandidates } from "@/lib/api-base-url"; +import { AUTH_COOKIE_NAME } from "@/lib/auth-session"; + +type RouteContext = { + params: Promise<{ path: string[] }>; +}; + +/** + * Proxy hacia los endpoints de administracion del modo construccion. + * + * El editor visual (app/construccion) opera contra /admin/game-data/* de la + * API. En produccion el cliente solo habla con Next, asi que este handler + * reenvia la peticion con la sesion del usuario y el token de proxy de admin. + * + * El token de proxy no sale nunca del servidor: el navegador solo aporta la + * cookie de sesion, y la API exige ambos para autorizar. + */ +export async function GET(_request: Request, context: RouteContext) { + return handleEditorProxy("GET", _request, context); +} + +export async function PUT(request: Request, context: RouteContext) { + return handleEditorProxy("PUT", request, context); +} + +export async function POST(request: Request, context: RouteContext) { + return handleEditorProxy("POST", request, context); +} + +export async function DELETE(request: Request, context: RouteContext) { + return handleEditorProxy("DELETE", request, context); +} + +async function handleEditorProxy( + method: string, + request: Request, + context: RouteContext, +): Promise { + const { path } = await context.params; + const pathname = path.map(encodeURIComponent).join("/"); + + if (!pathname) { + return NextResponse.json({ error: "Ruta invalida." }, { status: 400 }); + } + + const token = (await cookies()).get(AUTH_COOKIE_NAME)?.value?.trim(); + + if (!token) { + return NextResponse.json( + { error: "Tu sesion no es valida o ya vencio." }, + { status: 401 }, + ); + } + + const adminProxyToken = process.env.GAME_DATA_ADMIN_PROXY_TOKEN?.trim(); + + if (!adminProxyToken) { + return NextResponse.json( + { error: "El modo construccion no esta habilitado." }, + { status: 403 }, + ); + } + + const headers = new Headers(); + headers.set("Authorization", `Bearer ${token}`); + headers.set("x-game-data-admin-token", adminProxyToken); + + const incomingContentType = request.headers.get("content-type"); + + if (incomingContentType) { + headers.set("Content-Type", incomingContentType); + } + + let body: BodyInit | null = null; + + if (method !== "GET" && method !== "HEAD") { + body = await request.arrayBuffer(); + } + + for (const apiBaseUrl of getApiBaseUrlCandidates()) { + try { + const response = await fetch( + `${apiBaseUrl}/admin/game-data/${pathname}${new URL(request.url).search}`, + { + method, + headers, + body, + cache: "no-store", + }, + ); + + const payload = await response.json().catch(() => null); + + return NextResponse.json(payload, { status: response.status }); + } catch (error) { + console.error( + `No se pudo reenviar /admin/game-data/${pathname} desde ${apiBaseUrl}:`, + error, + ); + } + } + + return NextResponse.json( + { error: "No se pudo conectar con el servidor." }, + { status: 502 }, + ); +} \ No newline at end of file diff --git a/frontend/app/construccion/page.tsx b/frontend/app/construccion/page.tsx new file mode 100644 index 00000000..399d29f0 --- /dev/null +++ b/frontend/app/construccion/page.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { Suspense, useState } from "react"; +import dynamic from "next/dynamic"; +import Link from "next/link"; +import { EditorStoreProvider, useEditorStore } from "../../lib/editor/editorStore"; +import { useGameDataAdmin } from "../../lib/editor/useGameDataAdmin"; +import EditorToolbar from "../../components/editor/EditorToolbar"; +import RecentsStrip from "../../components/editor/RecentsStrip"; +import TerrainPalette from "../../components/editor/TerrainPalette"; +import ObjectsBrowser from "../../components/editor/ObjectsBrowser"; +import NpcsBrowser from "../../components/editor/NpcsBrowser"; + +const EditorCanvas = dynamic( + () => import("../../components/editor/EditorCanvas"), + { + ssr: false, + loading: () => ( +
+ Cargando lienzo del editor... +
+ ), + }, +); + +type PanelTab = "terrain" | "objects" | "npcs"; + +function EditorPanels() { + const { isLoading, loadError } = useEditorStore(); + const [activeTab, setActiveTab] = useState("terrain"); + + const tabs: Array<{ key: PanelTab; label: string }> = [ + { key: "terrain", label: "Terreno" }, + { key: "objects", label: "Objetos" }, + { key: "npcs", label: "NPCs" }, + ]; + + return ( + + ); +} + +function ConstruccionEditor() { + return ( +
+
+
+

+ Modo construccion +

+

+ Pinta terreno, coloca objetos y NPCs, y publica los + cambios del mapa. +

+
+
+ + + +
+ +
+ +
+
+ + +
+ ); +} + +export default function ConstruccionPage() { + const adminState = useGameDataAdmin(); + + if (adminState === "loading") { + return ( +
+ Verificando permisos... +
+ ); + } + + // El editor no se monta sin permiso: si lo hiciera, cada panel pediria su + // catalogo para recibir un 403 y la pantalla quedaria vacia sin explicar + // por que. + if (adminState === "denied") { + return ( +
+

+ Modo construccion +

+

+ Esta seccion es solo para las cuentas con permiso de + edicion de mapas. Si deberias tenerlo, pedile a un + administrador que agregue tu correo a la lista. +

+
+ + Ingresar con otra cuenta + + + Volver al inicio + +
+
+ ); + } + + return ( + + + + + + ); +} \ No newline at end of file diff --git a/frontend/components/AppChrome.tsx b/frontend/components/AppChrome.tsx index 0c5a3932..25576dbf 100644 --- a/frontend/components/AppChrome.tsx +++ b/frontend/components/AppChrome.tsx @@ -11,9 +11,11 @@ import { LogIn, LogOut, MessageCircle, + Hammer, } from "lucide-react"; import { useEffect, useState } from "react"; import type { AuthErrorResponse, AuthSession } from "@/lib/auth"; +import { useGameDataAdmin } from "@/lib/editor/useGameDataAdmin"; type AppChromeProps = { children: React.ReactNode; @@ -49,6 +51,10 @@ export default function AppChrome({ children }: AppChromeProps) { const pathname = usePathname(); const router = useRouter(); const [session, setSession] = useState(null); + // El editor de mapas es solo para administradores, asi que el enlace no se + // muestra a cualquier cuenta con sesion: entrar solo para encontrarse con + // una pantalla sin permiso no le sirve a nadie. + const isGameDataAdmin = useGameDataAdmin(session !== null) === "allowed"; useEffect(() => { let cancelled = false; @@ -129,6 +135,19 @@ export default function AppChrome({ children }: AppChromeProps) { ); })} + {isGameDataAdmin ? ( + + + Construccion + + ) : null}
diff --git a/frontend/components/CharacterSpritePreview.tsx b/frontend/components/CharacterSpritePreview.tsx index 19fb6c06..d2e52e00 100644 --- a/frontend/components/CharacterSpritePreview.tsx +++ b/frontend/components/CharacterSpritePreview.tsx @@ -3,7 +3,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { Application, - Assets, Container, Rectangle, Sprite, @@ -11,13 +10,11 @@ import { } from "pixi.js"; import type { BodiesDB, - DirectionalGraphicData, GraphicsDB, HeadsDB, HelmetsDB, ShieldsDB, WeaponsDB, - GraphicData, } from "../types/game"; import { loadBodiesDB, @@ -27,6 +24,11 @@ import { loadShieldsDB, loadWeaponsDB, } from "../utils/gameLoader"; +import { + loadGraphicTexture, + resolveDirectionalGraphicFrame, + resolveGraphicFrame, +} from "../lib/graphicTextures"; type CharacterSpritePreviewProps = { bodyId: number; @@ -49,7 +51,6 @@ const VIEW_HEIGHT = 138; const HEAD_VIEW_WIDTH = 72; const HEAD_VIEW_HEIGHT = 72; const FRONT_DIRECTION = "2"; -const baseTexturePromiseCache = new Map>(); function roundToEven(value: number) { const rounded = Math.round(value); @@ -57,14 +58,6 @@ function roundToEven(value: number) { return rounded % 2 === 0 ? rounded : rounded + 1; } -function getGraphicImagePaths(imageFile: string | number) { - return [ - `/graphics/${imageFile}.png`, - `/static/graphics/${imageFile}.png`, - `/static/graficosbk/${imageFile}.png`, - ]; -} - function getBodySpritePosition(width: number, height: number): SpritePosition { return { x: 16 - Math.floor((width * 16) / 32), @@ -122,96 +115,6 @@ function getHelmetSpritePosition( }; } -function resolveGraphicFrame( - graphicsDB: GraphicsDB, - graphicId: number, - direction: string, -) { - const graphic = graphicsDB[graphicId.toString()]; - - if (!graphic) { - return null; - } - - if (graphic.numFile && graphic.numFrames <= 1) { - return graphic; - } - - const frameId = - (graphic.numFrames > 1 ? graphic.frames?.["1"] : undefined) ?? - graphic.frames?.[direction] ?? - graphic.frames?.["1"] ?? - Object.values(graphic.frames ?? {})[0]; - - if (!frameId) { - return null; - } - - return graphicsDB[frameId.toString()] ?? null; -} - -function resolveDirectionalGraphicFrame( - graphicsDB: GraphicsDB, - directionalData: DirectionalGraphicData | undefined, - direction: string, -) { - if (!directionalData) { - return null; - } - - const graphicId = - directionalData[direction as keyof DirectionalGraphicData]; - - if (!graphicId) { - return null; - } - - return resolveGraphicFrame(graphicsDB, graphicId, direction); -} - -async function loadBaseTexture(imageFile: string | number): Promise { - const candidatePaths = getGraphicImagePaths(imageFile); - const cacheKey = candidatePaths.join("|"); - const cachedPromise = baseTexturePromiseCache.get(cacheKey); - - if (cachedPromise) { - return cachedPromise; - } - - const loadPromise = (async () => { - let lastError: unknown; - - for (const candidatePath of candidatePaths) { - try { - const texture = await Assets.load(candidatePath); - texture.source.scaleMode = "nearest"; - return texture; - } catch (error) { - lastError = error; - } - } - - throw lastError ?? new Error("Failed to load base texture"); - })(); - - baseTexturePromiseCache.set(cacheKey, loadPromise); - return loadPromise; -} - -async function loadGraphicTexture(graphicData: GraphicData): Promise { - const baseTexture = await loadBaseTexture(graphicData.numFile); - - return new Texture({ - source: baseTexture.source, - frame: new Rectangle( - graphicData.sX, - graphicData.sY, - graphicData.width, - graphicData.height, - ), - }); -} - function getVisibleAlphaBounds( pixels: Uint8ClampedArray, width: number, diff --git a/frontend/components/editor/EditorCanvas.tsx b/frontend/components/editor/EditorCanvas.tsx new file mode 100644 index 00000000..71409ea5 --- /dev/null +++ b/frontend/components/editor/EditorCanvas.tsx @@ -0,0 +1,1189 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Application, Container, Graphics, Sprite, Texture } from "pixi.js"; +import type { + BodiesDB, + GraphicsDB, + MapData, + ObjectsDB, +} from "../../types/game"; +import { + getMapDimensions, + getTileAt, + loadMapData, + loadObjectsDB, +} from "../../utils/gameLoader"; +import { + getSharedBodiesDB, + getSharedGraphicsDB, + loadGraphicTexture, + resolveGraphicFrame, +} from "../../lib/graphicTextures"; +import { getBottomAnchoredGraphicPosition } from "../game/rendering/characterLayout"; +import type { MapTileEntity, TilePaint } from "../../lib/editor/editorApi"; +import { + clearTileOverride, + paintTiles, + placeTileEntity, + removeTileEntity, +} from "../../lib/editor/editorApi"; +import { useEditorStore } from "../../lib/editor/editorStore"; + +const TILE_SIZE = 32; +const MIN_ZOOM = 0.25; +const MAX_ZOOM = 4; +const Z_LAYER_FLOOR = 0; +const Z_LAYER_BELOW = 2; +const Z_LAYER_OBJECT = 3; +const Z_LAYER_ENTITY = 4; +/** La capa 4 son techos y copas: va por encima de lo que se coloca abajo. */ +const Z_LAYER_ROOF = 5; +/** La grilla y el bloqueo van sobre cualquier tile (su zIndex es y * 10). */ +const Z_OVERLAY = 100_000; + +const LAYER_ORDER = [1, 2, 3, 4] as const; +const LAYER_Z_INDEX: Record = { + 1: Z_LAYER_FLOOR, + 2: Z_LAYER_BELOW, + 3: Z_LAYER_OBJECT, + 4: Z_LAYER_ROOF, +}; +const ENTITY_KINDS = ["obj", "npc"] as const; +/** El server acepta como maximo 500 tiles por lote. */ +const PAINT_BATCH_SIZE = 500; +/** Cuanto se espera antes de mandar el trazo, para no pedir por cada tile. */ +const PAINT_DEBOUNCE_MS = 400; + +/** + * Texturas por indice de grafico, compartidas entre renders y entre mapas. + * + * Sin este cache cada cambio de un tile volveria a resolver y recortar las + * texturas de todo el mapa. Esta a nivel de modulo porque los graficos del + * juego se repiten entre mapas y no cambian nunca. + */ +const textureCache = new Map(); +const texturePromiseCache = new Map>(); + +function tileKey(x: number, y: number): string { + return `${x},${y}`; +} + +/** + * Si los tiles cargados son de este mapa. + * + * Cambiar de mapa deja un render con el numero nuevo y los tiles viejos + * todavia en memoria, y ahi `getTileAt` accede a un mapa que no existe. + */ +function hasMapTiles( + mapData: MapData | null, + mapNum: number, +): mapData is MapData { + return Boolean(mapData && mapData[mapNum]); +} + +async function resolveTexture( + graphicsDB: GraphicsDB, + grhIndex: number, +): Promise { + const graphic = resolveGraphicFrame(graphicsDB, grhIndex, "2"); + + if (!graphic) { + return null; + } + + try { + return await loadGraphicTexture(graphic); + } catch { + return null; + } +} + +/** Deja la textura de un grafico en el cache. Un indice se resuelve una vez. */ +function ensureTexture( + graphicsDB: GraphicsDB, + grhIndex: number, +): Promise { + if (textureCache.has(grhIndex)) { + return Promise.resolve(); + } + + const inFlight = texturePromiseCache.get(grhIndex); + + if (inFlight) { + return inFlight; + } + + const pending = resolveTexture(graphicsDB, grhIndex).then((texture) => { + textureCache.set(grhIndex, texture); + texturePromiseCache.delete(grhIndex); + }); + + texturePromiseCache.set(grhIndex, pending); + + return pending; +} + +/** Un sprite del tile, ya resuelto a indice de grafico y orden de dibujo. */ +type TileSprite = { + grhIndex: number; + zIndex: number; + /** Los graficos altos se anclan al borde inferior del tile. */ + isAnchored: boolean; + tint?: number; +}; + +type TileSpec = { + x: number; + y: number; + sprites: TileSprite[]; + /** Como se ve el tile. Si no cambio, no hace falta redibujarlo. */ + signature: string; +}; + +type EditorCanvasProps = { + width?: number; + height?: number; +}; + +/** + * Lienzo del editor visual: mapa base, tiles editados y objetos/NPCs + * colocados, con camara (zoom + paneo), grilla, overlay de bloqueo y + * resaltado del tile bajo el cursor. El click pinta o coloca; arrastrando + * se pinta de a varios tiles en un solo lote. + */ +export default function EditorCanvas({ + width = 960, + height = 640, +}: EditorCanvasProps) { + const { + mapNum, + tool, + overrides, + entities, + npcs, + refreshMapData, + refreshStatus, + } = useEditorStore(); + const hostRef = useRef(null); + const appRef = useRef(null); + const worldRef = useRef(null); + const tileContainersRef = useRef>(new Map()); + const renderedSignaturesRef = useRef>(new Map()); + const renderedMapRef = useRef(null); + const [isPixiReady, setIsPixiReady] = useState(false); + const [graphicsDB, setGraphicsDB] = useState(null); + const [bodiesDB, setBodiesDB] = useState(null); + const [objectsDB, setObjectsDB] = useState(null); + const [mapData, setMapData] = useState(null); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + const [cursorTile, setCursorTile] = useState<{ + x: number; + y: number; + } | null>(null); + const [zoom, setZoom] = useState(1); + const [showGrid, setShowGrid] = useState(true); + const [showBlocked, setShowBlocked] = useState(true); + const [isPanning, setIsPanning] = useState(false); + const [isApplying, setIsApplying] = useState(false); + const [applyError, setApplyError] = useState(null); + const hoverHighlightRef = useRef(null); + const gridLayerRef = useRef(null); + const blockedLayerRef = useRef(null); + const cameraRef = useRef({ x: 0, y: 0, zoom: 1 }); + const pointerTileRef = useRef<{ x: number; y: number } | null>(null); + const panStartRef = useRef<{ x: number; y: number } | null>(null); + const isDrawingRef = useRef(false); + const lastAppliedTileRef = useRef(null); + const pendingRef = useRef<{ + timer: number | null; + tiles: Map; + needsRefresh: boolean; + /** Colocaciones y borrados ya pedidos, que el refresco tiene que esperar. */ + inFlight: Array>; + }>({ timer: null, tiles: new Map(), needsRefresh: false, inFlight: [] }); + const applyingRef = useRef(false); + const flushRef = useRef<(() => Promise) | null>(null); + + // Que borradores tiene cada tile, para que el borrador pida solo los + // borrados que existen en vez de seis peticiones por tile. + const draftLayersByTile = useMemo(() => { + const byTile = new Map(); + + for (const entry of overrides) { + if (entry.status !== "draft") { + continue; + } + + const key = tileKey(entry.x, entry.y); + byTile.set(key, [...(byTile.get(key) ?? []), entry.layer]); + } + + return byTile; + }, [overrides]); + + const draftEntitiesByTile = useMemo(() => { + const byTile = new Map(); + + for (const entry of entities) { + if (entry.status !== "draft") { + continue; + } + + const key = tileKey(entry.x, entry.y); + byTile.set(key, [...(byTile.get(key) ?? []), entry.kind]); + } + + return byTile; + }, [entities]); + + // Carga de recursos base. + useEffect(() => { + let cancelled = false; + + Promise.all([ + getSharedGraphicsDB(), + loadObjectsDB(), + getSharedBodiesDB(), + ]) + .then(([graphics, objects, bodies]) => { + if (!cancelled) { + setGraphicsDB(graphics); + setObjectsDB(objects); + setBodiesDB(bodies); + } + }) + .catch((error) => { + console.error("Error cargando recursos del editor:", error); + }); + + return () => { + cancelled = true; + }; + }, []); + + // Carga del mapa base. + useEffect(() => { + let cancelled = false; + + setMapData(null); + setDimensions({ width: 0, height: 0 }); + + loadMapData(mapNum) + .then((data) => { + if (cancelled) { + return; + } + + setMapData(data); + setDimensions(getMapDimensions(data, mapNum)); + }) + .catch((error) => { + console.error(`Error cargando mapa ${mapNum}:`, error); + }); + + return () => { + cancelled = true; + }; + }, [mapNum]); + + // Inicializacion de PixiJS. + useEffect(() => { + let disposed = false; + const host = hostRef.current; + const tileContainers = tileContainersRef.current; + const renderedSignatures = renderedSignaturesRef.current; + + if (!host) { + return; + } + + void (async () => { + const app = new Application(); + await app.init({ + width, + height, + antialias: false, + backgroundColor: 0x0c0a09, + resolution: Math.min(window.devicePixelRatio || 1, 2), + autoDensity: true, + }); + + if (disposed) { + app.destroy(undefined, { children: true }); + return; + } + + host.innerHTML = ""; + host.appendChild(app.canvas); + app.canvas.style.width = `${width}px`; + app.canvas.style.height = `${height}px`; + + const world = new Container(); + // Los tiles se redibujan de a uno cuando cambian, asi que el orden + // de insercion no alcanza: sin esto un tile reconstruido taparia a + // los de abajo con su copa de arbol. + world.sortableChildren = true; + app.stage.addChild(world); + + const gridLayer = new Graphics(); + const blockedLayer = new Graphics(); + const hoverHighlight = new Graphics(); + hoverHighlight.visible = false; + gridLayer.zIndex = Z_OVERLAY; + blockedLayer.zIndex = Z_OVERLAY + 1; + hoverHighlight.zIndex = Z_OVERLAY + 2; + world.addChild(gridLayer); + world.addChild(blockedLayer); + world.addChild(hoverHighlight); + + appRef.current = app; + worldRef.current = world; + gridLayerRef.current = gridLayer; + blockedLayerRef.current = blockedLayer; + hoverHighlightRef.current = hoverHighlight; + cameraRef.current = { x: 0, y: 0, zoom: 1 }; + // Un estado y no solo el ref: el render depende de que Pixi este + // listo y los refs no vuelven a disparar el efecto. + setIsPixiReady(true); + })(); + + return () => { + disposed = true; + setIsPixiReady(false); + + if (appRef.current) { + appRef.current.destroy(undefined, { children: true }); + appRef.current = null; + } + + if (host) { + host.innerHTML = ""; + } + + worldRef.current = null; + tileContainers.clear(); + renderedSignatures.clear(); + }; + }, [width, height]); + + const applyCamera = useCallback(() => { + const world = worldRef.current; + + if (!world) { + return; + } + + const { x, y, zoom: nextZoom } = cameraRef.current; + world.position.set(x, y); + world.scale.set(nextZoom); + }, []); + + // Camara inicial: centra el mapa. + useEffect(() => { + if (!dimensions.width || !dimensions.height) { + return; + } + + const mapPixels = { + width: dimensions.width * TILE_SIZE, + height: dimensions.height * TILE_SIZE, + }; + const initialZoom = Math.min( + 1, + Math.max( + MIN_ZOOM, + Math.min(width / mapPixels.width, height / mapPixels.height), + ), + ); + cameraRef.current = { + x: (width - mapPixels.width * initialZoom) / 2, + y: (height - mapPixels.height * initialZoom) / 2, + zoom: initialZoom, + }; + setZoom(initialZoom); + applyCamera(); + }, [dimensions, width, height, applyCamera]); + + const clearTileContainers = useCallback(() => { + for (const container of tileContainersRef.current.values()) { + container.destroy({ children: true }); + } + + tileContainersRef.current.clear(); + renderedSignaturesRef.current.clear(); + }, []); + + const drawGrid = useCallback(() => { + const gridLayer = gridLayerRef.current; + + if (!gridLayer || !dimensions.width) { + return; + } + + gridLayer.clear(); + + if (!showGrid) { + return; + } + + gridLayer + .rect(0, 0, dimensions.width * TILE_SIZE, dimensions.height * TILE_SIZE) + .stroke({ color: 0x57534e, alpha: 0.35, width: 1 }); + + for (let x = 1; x < dimensions.width; x += 1) { + gridLayer.moveTo(x * TILE_SIZE, 0); + gridLayer.lineTo(x * TILE_SIZE, dimensions.height * TILE_SIZE); + } + + for (let y = 1; y < dimensions.height; y += 1) { + gridLayer.moveTo(0, y * TILE_SIZE); + gridLayer.lineTo(dimensions.width * TILE_SIZE, y * TILE_SIZE); + } + + gridLayer.stroke({ color: 0x57534e, alpha: 0.35, width: 1 }); + }, [dimensions, showGrid]); + + const drawBlocked = useCallback(() => { + const blockedLayer = blockedLayerRef.current; + + if (!blockedLayer || !hasMapTiles(mapData, mapNum) || !dimensions.width) { + return; + } + + blockedLayer.clear(); + + if (!showBlocked) { + return; + } + + const overrideByTile = new Map(); + + for (const entry of overrides) { + if (entry.blocked !== null) { + overrideByTile.set(tileKey(entry.x, entry.y), entry.blocked); + } + } + + for (let y = 1; y <= dimensions.height; y += 1) { + for (let x = 1; x <= dimensions.width; x += 1) { + const baseTile = getTileAt(mapData, mapNum, x, y); + const overridden = overrideByTile.has(tileKey(x, y)); + const blocked = + (overridden + ? overrideByTile.get(tileKey(x, y)) + : Boolean(baseTile?.blocked)) ?? false; + + if (!blocked) { + continue; + } + + blockedLayer + .rect((x - 1) * TILE_SIZE, (y - 1) * TILE_SIZE, TILE_SIZE, TILE_SIZE) + .fill({ color: 0xef4444, alpha: overridden ? 0.28 : 0.14 }); + } + } + }, [dimensions, mapData, mapNum, overrides, showBlocked]); + + /** + * Que hay que dibujar en cada tile: mapa base, mas los overrides del + * editor, mas los objetos y NPCs colocados. + */ + const buildTileSpecs = useCallback((): TileSpec[] => { + if (!mapData || !objectsDB || !bodiesDB || !hasMapTiles(mapData, mapNum)) { + return []; + } + + const overrideByKey = new Map( + overrides.map((entry) => [ + `${entry.x},${entry.y},${entry.layer}`, + entry, + ]), + ); + const entityByKey = new Map( + entities.map((entry) => [ + `${entry.x},${entry.y},${entry.kind}`, + entry, + ]), + ); + const npcById = new Map(npcs.map((entry) => [entry.id, entry])); + const specs: TileSpec[] = []; + + for (let y = 1; y <= dimensions.height; y += 1) { + for (let x = 1; x <= dimensions.width; x += 1) { + const baseTile = getTileAt(mapData, mapNum, x, y); + const graphics: Record = { + ...(baseTile?.graphics ?? {}), + }; + + for (const layer of LAYER_ORDER) { + const override = overrideByKey.get(`${x},${y},${layer}`); + + if (!override) { + continue; + } + + if (override.grhIndex == null) { + delete graphics[String(layer)]; + } else { + graphics[String(layer)] = override.grhIndex; + } + } + + const sprites: TileSprite[] = []; + + for (const layer of LAYER_ORDER) { + const grhIndex = Number(graphics[String(layer)] ?? 0); + + if (!grhIndex) { + continue; + } + + sprites.push({ + grhIndex, + zIndex: LAYER_Z_INDEX[layer], + isAnchored: layer >= 3, + }); + } + + // Objetos del mapa base (objInfo). + const baseObjIndex = baseTile?.objInfo?.objIndex ?? 0; + + if (baseObjIndex > 0) { + const grhIndex = Number( + objectsDB[baseObjIndex.toString()]?.grhIndex ?? 0, + ); + + if (grhIndex) { + sprites.push({ + grhIndex, + zIndex: Z_LAYER_OBJECT, + isAnchored: true, + }); + } + } + + // Entidades colocadas desde el editor. + for (const kind of ENTITY_KINDS) { + const entity = entityByKey.get(`${x},${y},${kind}`); + + if (!entity) { + continue; + } + + let grhIndex = 0; + + if (kind === "obj") { + grhIndex = Number( + objectsDB[entity.entityId.toString()]?.grhIndex ?? 0, + ); + } else { + const npcEntry = npcById.get(entity.entityId); + const bodyData = npcEntry + ? bodiesDB[npcEntry.idBody.toString()] + : undefined; + grhIndex = Number(bodyData?.["2"] ?? 0); + } + + if (!grhIndex) { + continue; + } + + sprites.push({ + grhIndex, + zIndex: Z_LAYER_ENTITY, + isAnchored: true, + // Los NPCs colocados van tenidos: en el editor hay que + // poder distinguirlos del decorado del mapa base. + tint: kind === "obj" ? undefined : 0xd8b4fe, + }); + } + + specs.push({ + x, + y, + sprites, + signature: sprites + .map( + (sprite) => + `${sprite.grhIndex}:${sprite.zIndex}:${sprite.tint ?? ""}`, + ) + .join(","), + }); + } + } + + return specs; + }, [ + bodiesDB, + dimensions, + entities, + mapData, + mapNum, + npcs, + objectsDB, + overrides, + ]); + + // Render del mapa base + overrides + entidades. + // + // Solo se reconstruyen los tiles cuya firma cambio: pintar un tile no puede + // costar volver a resolver las texturas de los diez mil del mapa. + useEffect(() => { + if ( + !isPixiReady || + !hasMapTiles(mapData, mapNum) || + !graphicsDB || + !objectsDB || + !bodiesDB || + !dimensions.width + ) { + return; + } + + const world = worldRef.current; + + if (!world) { + return; + } + + if (renderedMapRef.current !== mapNum) { + clearTileContainers(); + renderedMapRef.current = mapNum; + } + + const specs = buildTileSpecs(); + const changed = specs.filter( + (spec) => + renderedSignaturesRef.current.get(tileKey(spec.x, spec.y)) !== + spec.signature, + ); + + if (changed.length === 0) { + return; + } + + let cancelled = false; + + void (async () => { + const missing = new Set(); + + for (const spec of changed) { + for (const sprite of spec.sprites) { + if (!textureCache.has(sprite.grhIndex)) { + missing.add(sprite.grhIndex); + } + } + } + + if (missing.size > 0) { + // En paralelo y una sola vez por indice: en serie, la primera + // carga del mapa son decenas de miles de esperas encadenadas. + await Promise.all( + Array.from(missing, (grhIndex) => + ensureTexture(graphicsDB, grhIndex), + ), + ); + } + + if (cancelled || worldRef.current !== world) { + return; + } + + for (const spec of changed) { + const key = tileKey(spec.x, spec.y); + const previous = tileContainersRef.current.get(key); + + if (previous) { + previous.destroy({ children: true }); + tileContainersRef.current.delete(key); + } + + if (spec.sprites.length > 0) { + const container = buildTileContainer(spec); + world.addChild(container); + tileContainersRef.current.set(key, container); + } + + renderedSignaturesRef.current.set(key, spec.signature); + } + })(); + + return () => { + cancelled = true; + }; + }, [ + bodiesDB, + buildTileSpecs, + clearTileContainers, + dimensions, + graphicsDB, + isPixiReady, + mapData, + mapNum, + objectsDB, + ]); + + const flushPending = useCallback(async () => { + const pending = pendingRef.current; + + if (pending.timer !== null) { + window.clearTimeout(pending.timer); + pending.timer = null; + } + + const tiles = Array.from(pending.tiles.values()); + + if ( + tiles.length === 0 && + !pending.needsRefresh && + pending.inFlight.length === 0 + ) { + return; + } + + // Si hay un apply en curso, reprogramar en vez de descartar los tiles. + if (applyingRef.current) { + pending.timer = window.setTimeout(() => { + pending.timer = null; + void flushRef.current?.(); + }, 200); + return; + } + + const inFlight = pending.inFlight; + pending.inFlight = []; + pending.tiles.clear(); + pending.needsRefresh = false; + applyingRef.current = true; + setIsApplying(true); + + try { + // Los errores de cada pedido ya se reportan donde se lanzo: aca solo + // interesa no refrescar antes de que terminen, porque el mapa + // volveria sin la entidad y el tile se veria vacio. + if (inFlight.length > 0) { + await Promise.allSettled(inFlight); + } + + for (let i = 0; i < tiles.length; i += PAINT_BATCH_SIZE) { + await paintTiles(mapNum, tiles.slice(i, i + PAINT_BATCH_SIZE)); + } + + await refreshMapData(); + await refreshStatus(); + setApplyError(null); + } catch (error) { + setApplyError( + error instanceof Error + ? error.message + : "No se pudieron guardar los cambios.", + ); + } finally { + applyingRef.current = false; + setIsApplying(false); + } + }, [mapNum, refreshMapData, refreshStatus]); + + useEffect(() => { + flushRef.current = flushPending; + }, [flushPending]); + + const scheduleFlush = useCallback(() => { + const pending = pendingRef.current; + + if (pending.timer !== null) { + window.clearTimeout(pending.timer); + } + + pending.timer = window.setTimeout(() => { + pending.timer = null; + void flushRef.current?.(); + }, PAINT_DEBOUNCE_MS); + }, []); + + /** + * Borra las ediciones sin publicar del tile. + * + * La API borra el borrador, no lo publicado: el borrador deshace lo que + * todavia no vio nadie. Para deshacer algo ya publicado hay que revertir el + * mapa desde la barra de herramientas. + */ + const eraseTile = useCallback( + async (x: number, y: number) => { + const key = tileKey(x, y); + const layers = draftLayersByTile.get(key) ?? []; + const kinds = draftEntitiesByTile.get(key) ?? []; + + if (layers.length === 0 && kinds.length === 0) { + return; + } + + // Se registra antes de esperarlo: soltar el boton dispara el flush, + // y el refresco tiene que esperar este borrado para no traer el mapa + // sin aplicar. + const request = Promise.all([ + ...layers.map((layer) => + clearTileOverride(mapNum, x, y, layer), + ), + ...kinds.map((kind) => removeTileEntity(mapNum, x, y, kind)), + ]); + + pendingRef.current.inFlight.push(request); + pendingRef.current.needsRefresh = true; + scheduleFlush(); + + await request; + }, + [draftEntitiesByTile, draftLayersByTile, mapNum, scheduleFlush], + ); + + const applyToolToTile = useCallback( + async (x: number, y: number) => { + if (!tool) { + return; + } + + const key = tileKey(x, y); + + // Arrastrar dentro del mismo tile no repite el trabajo: antes cada + // pointermove volvia a pedirle lo mismo al servidor. + if (lastAppliedTileRef.current === key) { + return; + } + + lastAppliedTileRef.current = key; + + try { + if (tool.kind === "erase") { + await eraseTile(x, y); + return; + } + + if (tool.kind === "object" || tool.kind === "npc") { + // Igual que el borrado: registrado antes de esperarlo, para + // que el refresco no se adelante a la colocacion. + const request = placeTileEntity(mapNum, { + x, + y, + kind: tool.kind === "object" ? "obj" : "npc", + entityId: + tool.kind === "object" + ? tool.object.id + : tool.npc.id, + }); + + pendingRef.current.inFlight.push(request); + pendingRef.current.needsRefresh = true; + scheduleFlush(); + + await request; + return; + } + + // Terreno: una entrada de la paleta es el tile completo, con un + // grafico por capa. Se acumula y se envia en lote. + // + // Las capas que la entrada no menciona quedan como estaban: una + // entrada de una sola capa es un piso, no un tile vacio. + const pending = pendingRef.current; + + tool.graphics.slice(0, LAYER_ORDER.length).forEach( + (grhIndex, index) => { + const layer = index + 1; + + pending.tiles.set(`${x},${y},${layer}`, { + x, + y, + layer, + grhIndex, + // El bloqueo es del tile, no de la capa: va con el piso. + blocked: layer === 1 ? tool.blocked : null, + }); + }, + ); + + scheduleFlush(); + } catch (error) { + setApplyError( + error instanceof Error + ? error.message + : "No se pudo aplicar la herramienta.", + ); + } + }, + [eraseTile, mapNum, scheduleFlush, tool], + ); + + // Interacciones de camara y pintado. + useEffect(() => { + const host = hostRef.current; + + if (!host) { + return; + } + + const stopDrawing = () => { + panStartRef.current = null; + setIsPanning(false); + + if (isDrawingRef.current) { + isDrawingRef.current = false; + lastAppliedTileRef.current = null; + void flushRef.current?.(); + } + }; + + const handleWheel = (event: WheelEvent) => { + event.preventDefault(); + + const camera = cameraRef.current; + const factor = event.deltaY < 0 ? 1.12 : 1 / 1.12; + const nextZoom = Math.min( + MAX_ZOOM, + Math.max(MIN_ZOOM, camera.zoom * factor), + ); + const rect = host.getBoundingClientRect(); + const pointerX = event.clientX - rect.left; + const pointerY = event.clientY - rect.top; + const worldX = (pointerX - camera.x) / camera.zoom; + const worldY = (pointerY - camera.y) / camera.zoom; + + camera.x = pointerX - worldX * nextZoom; + camera.y = pointerY - worldY * nextZoom; + camera.zoom = nextZoom; + setZoom(nextZoom); + applyCamera(); + }; + + const handlePointerDown = (event: PointerEvent) => { + const isMiddleButton = event.button === 1; + const isShiftPan = event.button === 0 && event.shiftKey; + + if (isMiddleButton || isShiftPan) { + event.preventDefault(); + panStartRef.current = { x: event.clientX, y: event.clientY }; + setIsPanning(true); + return; + } + + if (event.button === 0 && pointerTileRef.current && tool) { + isDrawingRef.current = true; + // Un click nuevo sobre el mismo tile si tiene que aplicar. + lastAppliedTileRef.current = null; + void applyToolToTile( + pointerTileRef.current.x, + pointerTileRef.current.y, + ); + } + }; + + const handlePointerMove = (event: PointerEvent) => { + const rect = host.getBoundingClientRect(); + const camera = cameraRef.current; + const worldX = (event.clientX - rect.left - camera.x) / camera.zoom; + const worldY = (event.clientY - rect.top - camera.y) / camera.zoom; + const tileX = Math.floor(worldX / TILE_SIZE) + 1; + const tileY = Math.floor(worldY / TILE_SIZE) + 1; + const inBounds = + tileX >= 1 && + tileX <= dimensions.width && + tileY >= 1 && + tileY <= dimensions.height; + const previous = pointerTileRef.current; + const hasChangedTile = + previous?.x !== (inBounds ? tileX : undefined) || + previous?.y !== (inBounds ? tileY : undefined); + + pointerTileRef.current = inBounds ? { x: tileX, y: tileY } : null; + + // Solo al cruzar de tile: un setState por pixel de movimiento + // vuelve a renderizar el editor entero sesenta veces por segundo. + if (hasChangedTile) { + setCursorTile(pointerTileRef.current); + } + + const hover = hoverHighlightRef.current; + + if (hover && hasChangedTile) { + if (pointerTileRef.current) { + hover.visible = true; + hover.clear(); + hover + .rect( + (tileX - 1) * TILE_SIZE, + (tileY - 1) * TILE_SIZE, + TILE_SIZE, + TILE_SIZE, + ) + .fill({ color: 0xfbbf24, alpha: 0.25 }) + .stroke({ color: 0xfbbf24, alpha: 0.8, width: 1 }); + } else { + hover.visible = false; + } + } + + if (panStartRef.current) { + camera.x += event.clientX - panStartRef.current.x; + camera.y += event.clientY - panStartRef.current.y; + panStartRef.current = { x: event.clientX, y: event.clientY }; + applyCamera(); + return; + } + + if (isDrawingRef.current && pointerTileRef.current) { + void applyToolToTile( + pointerTileRef.current.x, + pointerTileRef.current.y, + ); + } + }; + + const handlePointerLeave = () => { + pointerTileRef.current = null; + setCursorTile(null); + + const hover = hoverHighlightRef.current; + + if (hover) { + hover.visible = false; + } + + stopDrawing(); + }; + + host.addEventListener("wheel", handleWheel, { passive: false }); + host.addEventListener("pointerdown", handlePointerDown); + host.addEventListener("pointermove", handlePointerMove); + host.addEventListener("pointerleave", handlePointerLeave); + // En la ventana y no en el lienzo: soltar el boton afuera tambien + // termina el trazo, si no queda pintando al volver a entrar. + window.addEventListener("pointerup", stopDrawing); + window.addEventListener("pointercancel", stopDrawing); + + return () => { + host.removeEventListener("wheel", handleWheel); + host.removeEventListener("pointerdown", handlePointerDown); + host.removeEventListener("pointermove", handlePointerMove); + host.removeEventListener("pointerleave", handlePointerLeave); + window.removeEventListener("pointerup", stopDrawing); + window.removeEventListener("pointercancel", stopDrawing); + }; + }, [applyCamera, applyToolToTile, dimensions, tool]); + + // Limpieza del timer de pintado al desmontar. + useEffect(() => { + const pending = pendingRef.current; + + return () => { + if (pending.timer !== null) { + window.clearTimeout(pending.timer); + } + }; + }, []); + + useEffect(() => { + drawGrid(); + }, [drawGrid]); + + useEffect(() => { + drawBlocked(); + }, [drawBlocked]); + + return ( +
+
+ +
+ Mapa {mapNum} + {cursorTile ? ` - Tile ${cursorTile.x}, ${cursorTile.y}` : ""} + + Zoom {Math.round(zoom * 100)}% + +
+ +
+ + +
+ + {applyError ? ( +
+ {applyError} + +
+ ) : isApplying ? ( +
+ Aplicando cambios... +
+ ) : isPanning ? ( +
+ Arrastrando mapa (Shift+click o boton central) +
+ ) : null} +
+ ); +} + +function buildTileContainer(spec: TileSpec): Container { + const container = new Container(); + container.sortableChildren = true; + container.zIndex = spec.y * 10; + + for (const item of spec.sprites) { + const texture = textureCache.get(item.grhIndex); + + if (!texture) { + continue; + } + + const sprite = new Sprite(texture); + sprite.x = (spec.x - 1) * TILE_SIZE; + sprite.y = (spec.y - 1) * TILE_SIZE; + + if (item.isAnchored) { + const position = getBottomAnchoredGraphicPosition( + texture.width, + texture.height, + ); + sprite.x += position.x; + sprite.y += position.y; + } + + sprite.zIndex = item.zIndex; + + if (item.tint !== undefined) { + sprite.tint = item.tint; + } + + container.addChild(sprite); + } + + return container; +} diff --git a/frontend/components/editor/EditorToolbar.tsx b/frontend/components/editor/EditorToolbar.tsx new file mode 100644 index 00000000..39931410 --- /dev/null +++ b/frontend/components/editor/EditorToolbar.tsx @@ -0,0 +1,363 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + createTerrainBrush, + useEditorStore, +} from "../../lib/editor/editorStore"; +import { + discardMapDrafts, + publishMapChanges, + revertMapChanges, +} from "../../lib/editor/editorApi"; + +type EditorAction = "publish" | "discard" | "revert"; + +/** + * Barra de herramientas del editor: seleccion de herramienta, accion de + * borrado y acciones de publicar / descartar / revertir cambios. + */ +export default function EditorToolbar() { + const { + mapNum, + setMapNum, + tool, + setTool, + refreshMapData, + refreshStatus, + status, + terrain, + objects, + npcs, + } = useEditorStore(); + const [isBusy, setIsBusy] = useState(null); + const [pendingAction, setPendingAction] = useState( + null, + ); + const [error, setError] = useState(null); + + const draftTiles = status?.draft ?? 0; + const draftEntities = status?.draftEntities ?? 0; + const publishedTiles = status?.published ?? 0; + const publishedEntities = status?.publishedEntities ?? 0; + // Las entidades cuentan igual que los tiles: un mapa cuyo unico cambio es un + // NPC colocado sigue teniendo trabajo sin publicar. + const draftTotal = draftTiles + draftEntities; + const hasAnything = + draftTotal + publishedTiles + publishedEntities > 0; + + const runAction = async (action: EditorAction) => { + setIsBusy(action); + setError(null); + + try { + if (action === "publish") { + await publishMapChanges(mapNum); + } else if (action === "discard") { + await discardMapDrafts(mapNum); + } else { + await revertMapChanges(mapNum); + } + + await refreshMapData(); + await refreshStatus(); + } catch (actionError) { + setError( + actionError instanceof Error + ? actionError.message + : "Ocurrio un error.", + ); + } finally { + setIsBusy(null); + } + }; + + const toolButtons: Array<{ + key: string; + label: string; + icon: string; + isActive: boolean; + onClick: () => void; + }> = [ + { + key: "terrain", + label: "Terreno", + icon: "◫", + isActive: tool?.kind === "terrain", + onClick: () => { + const firstEntry = terrain?.palette.find((entry) => + entry.graphics.some( + (graphic) => + typeof graphic === "number" && graphic > 0, + ), + ); + + if (firstEntry) { + setTool({ + kind: "terrain", + ...createTerrainBrush(firstEntry), + }); + } + }, + }, + { + key: "object", + label: "Objeto", + icon: "▦", + isActive: tool?.kind === "object", + onClick: () => { + const firstObject = objects[0]; + + if (firstObject) { + setTool({ kind: "object", object: firstObject }); + } + }, + }, + { + key: "npc", + label: "NPC", + icon: "◉", + isActive: tool?.kind === "npc", + onClick: () => { + const firstNpc = npcs[0]; + + if (firstNpc) { + setTool({ kind: "npc", npc: firstNpc }); + } + }, + }, + { + key: "erase", + label: "Borrar", + icon: "⌫", + isActive: tool?.kind === "erase", + onClick: () => setTool({ kind: "erase" }), + }, + ]; + + return ( +
+
+ + { + const nextValue = Number(event.target.value); + + if (Number.isInteger(nextValue) && nextValue > 0) { + setMapNum(nextValue); + } + }} + className="w-20 rounded-lg border border-white/10 bg-stone-950/60 px-2 py-1 text-xs text-stone-200 focus:border-amber-400/50 focus:outline-none" + /> +
+ +
+ +
+ {toolButtons.map((button) => ( + + ))} +
+ +
+ +
+ + + +
+ +
+ + Publicados:{" "} + + {publishedTiles} + + + + Borradores:{" "} + + {draftTiles} + + + + Entidades:{" "} + + {publishedEntities} + + {draftEntities > 0 ? ( + + {" "} + (+{draftEntities}) + + ) : null} + +
+ + {error ? ( +

+ {error} +

+ ) : null} + + {pendingAction ? ( + setPendingAction(null)} + onConfirm={() => { + const action = pendingAction; + setPendingAction(null); + void runAction(action); + }} + /> + ) : null} +
+ ); +} + +/** + * Confirmacion de las tres acciones que no se pueden deshacer. + * + * Publicar cambia lo que ven los jugadores y revertir borra tambien lo ya + * publicado, asi que ninguna de las dos deberia depender de no errarle al boton. + */ +function ConfirmDialog({ + action, + mapNum, + draftTiles, + draftEntities, + publishedTiles, + publishedEntities, + onCancel, + onConfirm, +}: { + action: EditorAction; + mapNum: number; + draftTiles: number; + draftEntities: number; + publishedTiles: number; + publishedEntities: number; + onCancel: () => void; + onConfirm: () => void; +}) { + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onCancel(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => window.removeEventListener("keydown", handleKeyDown); + }, [onCancel]); + + const copy = { + publish: { + title: `Publicar los cambios del mapa ${mapNum}`, + body: `Los jugadores van a ver ${draftTiles} tiles y ${draftEntities} entidades en cuanto entren al mapa. Publicar no se puede deshacer desde aca: para volver atras hay que revertir el mapa entero.`, + confirmLabel: "Publicar", + confirmClass: + "border-emerald-400/50 bg-emerald-400/15 text-emerald-200 hover:bg-emerald-400/25", + }, + discard: { + title: "Descartar los borradores", + body: `Se van a borrar ${draftTiles} tiles y ${draftEntities} entidades sin publicar. Lo ya publicado no se toca.`, + confirmLabel: "Descartar", + confirmClass: + "border-red-400/50 bg-red-400/15 text-red-200 hover:bg-red-400/25", + }, + revert: { + title: `Revertir el mapa ${mapNum} a su estado original`, + body: `Se borra todo lo pintado en este mapa: ${publishedTiles} tiles y ${publishedEntities} entidades publicadas, mas ${draftTiles} tiles y ${draftEntities} entidades en borrador. No se puede deshacer.`, + confirmLabel: "Revertir todo", + confirmClass: + "border-red-400/50 bg-red-400/15 text-red-200 hover:bg-red-400/25", + }, + }[action]; + + return ( +
+
+

+ {copy.title} +

+

+ {copy.body} +

+
+ + +
+
+
+ ); +} diff --git a/frontend/components/editor/GraphicPreview.tsx b/frontend/components/editor/GraphicPreview.tsx new file mode 100644 index 00000000..12e106cd --- /dev/null +++ b/frontend/components/editor/GraphicPreview.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import type { GraphicsDB } from "../../types/game"; +import { + getSharedGraphicsDB, + loadGraphicImage, + resolveGraphicFrame, +} from "../../lib/graphicTextures"; + +type GraphicPreviewProps = { + grhIndex: number; + size?: number; + scale?: number; + className?: string; +}; + +/** + * Miniatura de un grafico del motor: resuelve el frame en el catalogo y recorta + * el sprite del PNG fuente sobre un canvas 2D. + * + * Deliberadamente no usa PixiJS. Una `Application` por miniatura significa un + * contexto WebGL por miniatura, y el navegador mantiene vivos apenas unos + * quince: la paleta de un mapa tiene cientos de entradas y las ultimas + * quedarian en blanco. + */ +export default function GraphicPreview({ + grhIndex, + size = 48, + scale = 2, + className = "", +}: GraphicPreviewProps) { + const canvasRef = useRef(null); + const [graphicsDB, setGraphicsDB] = useState(null); + + useEffect(() => { + let cancelled = false; + + getSharedGraphicsDB() + .then((db) => { + if (!cancelled) { + setGraphicsDB(db); + } + }) + .catch(() => { + // Sin catalogo de graficos la miniatura queda vacia. + }); + + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + const canvas = canvasRef.current; + + if (!canvas) { + return; + } + + const context = canvas.getContext("2d"); + + if (!context) { + return; + } + + const ratio = Math.min(window.devicePixelRatio || 1, 2); + canvas.width = Math.round(size * ratio); + canvas.height = Math.round(size * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + context.clearRect(0, 0, size, size); + + if (!graphicsDB || grhIndex <= 0) { + return; + } + + const graphic = resolveGraphicFrame(graphicsDB, grhIndex, "2"); + + if (!graphic) { + return; + } + + let cancelled = false; + + void loadGraphicImage(graphic.numFile) + .then((image) => { + if (cancelled) { + return; + } + + const frameWidth = Math.max(1, graphic.width); + const frameHeight = Math.max(1, graphic.height); + // El sprite se agranda hasta `scale` pero nunca desborda el + // recuadro: los graficos del juego van de 32x32 a varios tiles. + const drawScale = Math.min( + scale, + size / frameWidth, + size / frameHeight, + ); + const drawWidth = Math.max(1, Math.round(frameWidth * drawScale)); + const drawHeight = Math.max( + 1, + Math.round(frameHeight * drawScale), + ); + + context.imageSmoothingEnabled = false; + context.drawImage( + image, + graphic.sX, + graphic.sY, + frameWidth, + frameHeight, + Math.round((size - drawWidth) / 2), + Math.round((size - drawHeight) / 2), + drawWidth, + drawHeight, + ); + }) + .catch(() => { + // Un grafico que no resuelve deja el recuadro vacio. + }); + + return () => { + cancelled = true; + }; + }, [graphicsDB, grhIndex, scale, size]); + + return ( + + ); +} diff --git a/frontend/components/editor/NpcsBrowser.tsx b/frontend/components/editor/NpcsBrowser.tsx new file mode 100644 index 00000000..7b0f1fce --- /dev/null +++ b/frontend/components/editor/NpcsBrowser.tsx @@ -0,0 +1,158 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import type { BodiesDB, HeadsDB } from "../../types/game"; +import type { EditorNpc } from "../../lib/editor/editorApi"; +import { useEditorStore } from "../../lib/editor/editorStore"; +import { + getSharedBodiesDB, + getSharedHeadsDB, + resolveCharacterThumbnailGrh, +} from "../../lib/graphicTextures"; +import GraphicPreview from "./GraphicPreview"; +import VirtualizedList from "./VirtualizedList"; + +const ITEM_HEIGHT = 76; + +/** + * Catalogo de NPCs del juego. + * + * Las miniaturas resuelven el grafico del NPC contra los catalogos de cuerpos y + * cabezas y lo dibujan en un canvas 2D. No usan el renderizador de personajes + * del cliente a proposito: crea una `Application` de PixiJS -y con ella un + * contexto WebGL- por fila, y el navegador mantiene vivos apenas unos quince. + */ +export default function NpcsBrowser() { + const { npcs, tool, setTool, addRecent } = useEditorStore(); + const [search, setSearch] = useState(""); + const [bodiesDB, setBodiesDB] = useState(null); + const [headsDB, setHeadsDB] = useState(null); + const normalizedSearch = search.trim().toLowerCase(); + + useEffect(() => { + let cancelled = false; + + Promise.all([getSharedBodiesDB(), getSharedHeadsDB()]) + .then(([bodies, heads]) => { + if (!cancelled) { + setBodiesDB(bodies); + setHeadsDB(heads); + } + }) + .catch(() => { + // Sin catalogos las miniaturas quedan vacias, la lista sirve igual. + }); + + return () => { + cancelled = true; + }; + }, []); + + const filteredNpcs = useMemo(() => { + if (!normalizedSearch) { + return npcs; + } + + return npcs.filter( + (entry) => + entry.name.toLowerCase().includes(normalizedSearch) || + String(entry.id).includes(normalizedSearch), + ); + }, [normalizedSearch, npcs]); + + const selectedId = tool?.kind === "npc" ? tool.npc.id : null; + + const handleSelect = async (entry: EditorNpc) => { + // La herramienta se activa ya: pintar no tiene por que esperar a que + // carguen los catalogos. + setTool({ kind: "npc", npc: entry }); + + try { + // Los catalogos se piden aca en vez de leerse del estado. Un click + // antes de que terminen de cargar resolveria el grafico en 0, y ese + // 0 queda guardado en los recientes: la miniatura quedaria vacia + // para siempre, incluso despues de que los catalogos lleguen. + const [bodies, heads] = await Promise.all([ + getSharedBodiesDB(), + getSharedHeadsDB(), + ]); + + addRecent({ + kind: "npc", + id: entry.id, + // El grafico, no `idHead`: son numeraciones distintas y los + // recientes dibujan la miniatura con este valor. + grhIndex: resolveCharacterThumbnailGrh( + bodies, + heads, + entry.idBody, + entry.idHead, + ), + name: entry.name, + }); + } catch { + // Sin catalogos no hay grafico que guardar, y un reciente que se ve + // vacio es peor que no tenerlo. + } + }; + + return ( +
+ setSearch(event.target.value)} + placeholder="Buscar NPC por nombre o id..." + className="w-full rounded-lg border border-white/10 bg-stone-950/60 px-3 py-2 text-xs text-stone-200 placeholder:text-stone-500 focus:border-amber-400/50 focus:outline-none" + /> + + {filteredNpcs.length === 0 ? ( +

+ Ningun NPC coincide con la busqueda. +

+ ) : ( + entry.id} + renderItem={(entry) => { + const isSelected = selectedId === entry.id; + + return ( + + ); + }} + itemHeight={ITEM_HEIGHT} + className="min-h-0 flex-1 rounded-lg" + /> + )} +
+ ); +} diff --git a/frontend/components/editor/ObjectsBrowser.tsx b/frontend/components/editor/ObjectsBrowser.tsx new file mode 100644 index 00000000..e2107ffb --- /dev/null +++ b/frontend/components/editor/ObjectsBrowser.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { getObjectType, OBJECT_TYPES } from "../../data/objectTypes"; +import type { EditorObject } from "../../lib/editor/editorApi"; +import { useEditorStore } from "../../lib/editor/editorStore"; +import GraphicPreview from "./GraphicPreview"; +import VirtualizedList from "./VirtualizedList"; + +const ITEM_HEIGHT = 56; + +/** + * Catalogo de objetos del juego con busqueda y filtro por tipo. + * + * Al seleccionar un objeto se activa la herramienta de colocacion y se suma + * al historial de recientes. + */ +export default function ObjectsBrowser() { + const { objects, tool, setTool, addRecent } = useEditorStore(); + const [search, setSearch] = useState(""); + const [objTypeFilter, setObjTypeFilter] = useState(null); + const normalizedSearch = search.trim().toLowerCase(); + + // Un recorrido por tipo y no un filter por chip: son 37 chips sobre mil + // objetos, y se recalculaba entero en cada tecla de la busqueda. + const countsByType = useMemo(() => { + const counts = new Map(); + + for (const entry of objects) { + counts.set(entry.objType, (counts.get(entry.objType) ?? 0) + 1); + } + + return counts; + }, [objects]); + + const filteredObjects = useMemo(() => { + let result = objects; + + if (objTypeFilter !== null) { + result = result.filter((entry) => entry.objType === objTypeFilter); + } + + if (normalizedSearch) { + result = result.filter( + (entry) => + entry.name.toLowerCase().includes(normalizedSearch) || + String(entry.id).includes(normalizedSearch), + ); + } + + return result; + }, [normalizedSearch, objTypeFilter, objects]); + + const selectedId = + tool?.kind === "object" ? tool.object.id : null; + + const handleSelect = (entry: EditorObject) => { + setTool({ kind: "object", object: entry }); + addRecent({ + kind: "object", + id: entry.id, + grhIndex: entry.grhIndex, + name: entry.name, + }); + }; + + return ( +
+ setSearch(event.target.value)} + placeholder="Buscar objeto por nombre o id..." + className="w-full rounded-lg border border-white/10 bg-stone-950/60 px-3 py-2 text-xs text-stone-200 placeholder:text-stone-500 focus:border-amber-400/50 focus:outline-none" + /> + + {objects.length > 0 ? ( +
+ + {OBJECT_TYPES.map((entry) => { + const count = countsByType.get(entry.id) ?? 0; + + if (count === 0) { + return null; + } + + return ( + + ); + })} +
+ ) : null} + + {filteredObjects.length === 0 ? ( +

+ Ningun objeto coincide con la busqueda. +

+ ) : ( + entry.id} + renderItem={(entry) => { + const type = getObjectType(entry.objType); + const isSelected = selectedId === entry.id; + + return ( + + ); + }} + itemHeight={ITEM_HEIGHT} + className="min-h-0 flex-1 rounded-lg" + /> + )} +
+ ); +} \ No newline at end of file diff --git a/frontend/components/editor/RecentsStrip.tsx b/frontend/components/editor/RecentsStrip.tsx new file mode 100644 index 00000000..9c391914 --- /dev/null +++ b/frontend/components/editor/RecentsStrip.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { findTerrainBrush, useEditorStore } from "../../lib/editor/editorStore"; +import GraphicPreview from "./GraphicPreview"; + +const KIND_LABELS: Record = { + terrain: "Terreno", + object: "Objeto", + npc: "NPC", +}; + +/** + * Tira de elementos recientes del editor. Un clic vuelve a seleccionar la + * herramienta correspondiente. + */ +export default function RecentsStrip() { + const { recents, objects, npcs, terrain, setTool } = useEditorStore(); + + if (recents.length === 0) { + return ( +
+ Los elementos que uses apareceran aca para acceso rapido. +
+ ); + } + + return ( +
+ + Recientes + + {recents.map((entry) => { + const matchedObject = + entry.kind === "object" + ? objects.find((object) => object.id === entry.id) + : undefined; + const matchedNpc = + entry.kind === "npc" + ? npcs.find((npc) => npc.id === entry.id) + : undefined; + // El pincel de terreno se rearma desde la paleta del mapa: los + // recientes guardan el id, no las capas, y pintar solo el + // grafico representativo dejaria el tile a medio cambiar. + const matchedBrush = + entry.kind === "terrain" + ? findTerrainBrush(terrain, entry.id) + : null; + const isAvailable = + entry.kind === "terrain" + ? matchedBrush !== null + : entry.kind === "object" + ? matchedObject !== undefined + : matchedNpc !== undefined; + + return ( + + ); + })} +
+ ); +} \ No newline at end of file diff --git a/frontend/components/editor/TerrainPalette.tsx b/frontend/components/editor/TerrainPalette.tsx new file mode 100644 index 00000000..b441b61e --- /dev/null +++ b/frontend/components/editor/TerrainPalette.tsx @@ -0,0 +1,222 @@ +"use client"; + +import { useMemo, useRef, useState } from "react"; +import { + createTerrainBrush, + createUploadedGraphicBrush, + useEditorStore, + type TerrainBrush, +} from "../../lib/editor/editorStore"; +import { uploadGraphicPng } from "../../lib/editor/editorApi"; +import { invalidateSharedGraphicsDB } from "../../lib/graphicTextures"; +import GraphicPreview from "./GraphicPreview"; +import VirtualizedList from "./VirtualizedList"; + +/** Alto de una celda: miniatura de 56px mas la etiqueta y el borde. */ +const CELL_HEIGHT = 84; +const COLUMNS = 3; + +type PaletteTab = "terrain" | "uploaded"; + +/** + * Paleta de terreno del mapa: entradas de terrain.json mas los graficos + * subidos por administradores. Permite subir PNGs nuevos al modo construccion. + * + * Las dos fuentes van en pestañas y no una debajo de la otra: la paleta de un + * mapa pasa las ochocientas entradas y hacen falta dos grillas virtualizadas + * independientes, no una sola con dos encabezados pegajosos. + */ +export default function TerrainPalette() { + const { terrain, tool, setTool, addRecent, refreshMapData } = + useEditorStore(); + const fileInputRef = useRef(null); + const [isUploading, setIsUploading] = useState(false); + const [uploadError, setUploadError] = useState(null); + const [tab, setTab] = useState("terrain"); + + const selectedPaletteId = tool?.kind === "terrain" ? tool.paletteId : null; + + const terrainBrushes = useMemo( + () => (terrain?.palette ?? []).map(createTerrainBrush), + [terrain], + ); + + const uploadedBrushes = useMemo( + () => + (terrain?.uploadedGraphics ?? []).map((graphic) => + createUploadedGraphicBrush(graphic.grhIndex), + ), + [terrain], + ); + + const handleSelect = (brush: TerrainBrush) => { + setTool({ kind: "terrain", ...brush }); + addRecent({ + kind: "terrain", + id: brush.paletteId, + grhIndex: brush.grhIndex, + name: `Tile ${brush.paletteId}`, + }); + }; + + const handleFile = async (file: File) => { + setIsUploading(true); + setUploadError(null); + + try { + const bytes = await file.arrayBuffer(); + const uploaded = await uploadGraphicPng(bytes); + // El catalogo de graficos mezcla los indices subidos al construirse, + // asi que sin descartarlo la miniatura del PNG nuevo saldria vacia + // hasta recargar la pagina. + invalidateSharedGraphicsDB(); + await refreshMapData(); + setTab("uploaded"); + handleSelect(createUploadedGraphicBrush(uploaded.grhIndex)); + } catch (error) { + setUploadError( + error instanceof Error + ? error.message + : "No se pudo subir el grafico.", + ); + } finally { + setIsUploading(false); + + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + } + }; + + if (!terrain) { + return ( +
+ Sin paleta de terreno para este mapa. +
+ ); + } + + const brushes = tab === "terrain" ? terrainBrushes : uploadedBrushes; + + return ( +
+
+ { + const file = event.target.files?.[0]; + + if (file) { + void handleFile(file); + } + }} + /> + +
+ + {uploadError ? ( +

+ {uploadError} +

+ ) : null} + +
+ setTab("terrain")} + label={`Terreno (${terrainBrushes.length})`} + /> + setTab("uploaded")} + label={`Subidos (${uploadedBrushes.length})`} + /> +
+ + {brushes.length === 0 ? ( +

+ {tab === "uploaded" + ? "Todavia no hay graficos subidos." + : "Este mapa no tiene tiles en su paleta."} +

+ ) : ( + brush.paletteId} + renderItem={(brush) => ( +
+ +
+ )} + /> + )} +
+ ); +} + +function TabButton({ + isActive, + onClick, + label, +}: { + isActive: boolean; + onClick: () => void; + label: string; +}) { + return ( + + ); +} diff --git a/frontend/components/editor/VirtualizedList.tsx b/frontend/components/editor/VirtualizedList.tsx new file mode 100644 index 00000000..6a831e7f --- /dev/null +++ b/frontend/components/editor/VirtualizedList.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useEffect, useRef, useState, type ReactNode } from "react"; + +type VirtualizedListProps = { + items: T[]; + getItemKey: (item: T, index: number) => string | number; + renderItem: (item: T, index: number) => ReactNode; + itemHeight?: number; + /** Items por fila. Con 1 es una lista; con mas, una grilla virtualizada. */ + columns?: number; + overscan?: number; + className?: string; +}; + +/** + * Lista (o grilla) virtualizada sin dependencias: solo renderiza las filas + * visibles. Los catalogos del editor superan los mil elementos y la paleta de + * un mapa las ochocientas entradas, y el navegador no debe crear un nodo DOM + * -ni una miniatura- por cada uno. + */ +export default function VirtualizedList({ + items, + getItemKey, + renderItem, + itemHeight = 72, + columns = 1, + overscan = 6, + className = "", +}: VirtualizedListProps) { + const viewportRef = useRef(null); + const [scrollTop, setScrollTop] = useState(0); + const [viewportHeight, setViewportHeight] = useState(0); + + useEffect(() => { + const viewport = viewportRef.current; + + if (!viewport) { + return; + } + + const updateHeight = () => { + setViewportHeight(viewport.clientHeight); + }; + + updateHeight(); + + // El alto no depende solo de la ventana: filtrar reacomoda los chips de + // arriba y el viewport cambia de alto sin ningun resize del navegador. + const observer = new ResizeObserver(updateHeight); + observer.observe(viewport); + + return () => { + observer.disconnect(); + }; + }, []); + + const columnCount = Math.max(1, Math.floor(columns)); + const rowCount = Math.ceil(items.length / columnCount); + const totalHeight = rowCount * itemHeight; + const startRow = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan); + const visibleRowCount = + Math.ceil(Math.max(viewportHeight, itemHeight) / itemHeight) + + overscan * 2; + const endRow = Math.min(rowCount, startRow + visibleRowCount); + const visibleRows: Array<{ row: number; startIndex: number }> = []; + + for (let row = startRow; row < endRow; row += 1) { + visibleRows.push({ row, startIndex: row * columnCount }); + } + + return ( +
setScrollTop(event.currentTarget.scrollTop)} + > +
+ {visibleRows.map(({ row, startIndex }) => ( +
+ {Array.from( + { length: columnCount }, + (_, column) => startIndex + column, + ) + .filter((index) => index < items.length) + .map((index) => ( +
+ {renderItem(items[index], index)} +
+ ))} +
+ ))} +
+
+ ); +} diff --git a/frontend/data/objectTypes.ts b/frontend/data/objectTypes.ts new file mode 100644 index 00000000..92c81378 --- /dev/null +++ b/frontend/data/objectTypes.ts @@ -0,0 +1,137 @@ +/** + * Catalogo de tipos de objeto del juego. + * + * Los tipos se derivan del propio objs.json del juego (ver public/init/objs.json), + * no de una convencion externa: cada tipo enumera los ids que le pertenecen y la + * etiqueta se deduce del nombre mas repetido de sus objetos. + */ +export type ObjectTypeId = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 37; + +export type ObjectType = { + id: number; + label: string; + color: string; +}; + +const OBJECT_TYPE_COLORS: Record = { + "1": "#fbbf24", // Comida + "2": "#ef4444", // Armas + "3": "#3b82f6", // Armaduras + "4": "#22c55e", // Arboles + "5": "#eab308", // Dinero + "6": "#a16207", // Puertas + "7": "#f97316", // Cofres + "8": "#94a3b8", // Carteles + "9": "#facc15", // Llaves + "10": "#d946ef", // Foros + "11": "#a855f7", // Pociones + "12": "#8b5cf6", // Libros + "13": "#14b8a6", // Bebidas + "14": "#b45309", // Lena + "15": "#f97316", // Fogata + "16": "#6366f1", // Escudos + "17": "#06b6d4", // Cascos + "18": "#ec4899", // Instrumentos + "19": "#0ea5e9", // Teleports + "20": "#d97706", // Muebles + "21": "#78716c", // Decoracion + "22": "#84cc16", // Yacimientos + "23": "#10b981", // Minerales + "24": "#c026d3", // Magia + "26": "#f59e0b", // Cuerno de clan + "27": "#64748b", // Yunque + "28": "#ef4444", // Fragua + "29": "#22d3ee", // Lingotes y gemas + "30": "#a3e635", // Pieles y flores + "31": "#38bdf8", // Barcos + "32": "#fb923c", // Flechas + "33": "#fcd34d", // Odre vacia + "34": "#fde68a", // Odre + "35": "#e7e5e4", // Misc + "37": "#fbbf24", // Mochilas +}; + +const OBJECT_TYPE_LABELS: Record = { + "1": "Comida", + "2": "Armas", + "3": "Armaduras", + "4": "Arboles", + "5": "Dinero", + "6": "Puertas", + "7": "Cofres", + "8": "Carteles", + "9": "Llaves", + "10": "Foros", + "11": "Pociones", + "12": "Libros", + "13": "Bebidas", + "14": "Lena", + "15": "Fogatas", + "16": "Escudos", + "17": "Cascos", + "18": "Instrumentos", + "19": "Teleports", + "20": "Muebles", + "21": "Decoracion", + "22": "Yacimientos", + "23": "Minerales", + "24": "Magia", + "26": "Cuerno de clan", + "27": "Yunques", + "28": "Fraguas", + "29": "Lingotes y gemas", + "30": "Pieles y flores", + "31": "Barcos", + "32": "Flechas", + "33": "Odres vacias", + "34": "Odres", + "35": "Misc", + "37": "Mochilas", +}; + +export const OBJECT_TYPES: ObjectType[] = Object.keys(OBJECT_TYPE_LABELS).map( + (key) => ({ + id: Number(key), + label: OBJECT_TYPE_LABELS[key], + color: OBJECT_TYPE_COLORS[key] ?? "#78716c", + }), +); + +export function getObjectType(id: number): ObjectType | null { + return OBJECT_TYPES.find((entry) => entry.id === id) ?? null; +} \ No newline at end of file diff --git a/frontend/lib/editor/editorApi.ts b/frontend/lib/editor/editorApi.ts new file mode 100644 index 00000000..ee5b4a72 --- /dev/null +++ b/frontend/lib/editor/editorApi.ts @@ -0,0 +1,350 @@ +/** + * Cliente del editor visual. Todas las peticiones pasan por el proxy de Next + * (app/api/editor/[...path]) que agrega la sesion y el token de admin; el + * navegador nunca ve esos secretos. + */ + +export type EditorObject = { + id: number; + name: string; + objType: number; + grhIndex: number; + version: number; + updatedAt: string; +}; + +export type EditorNpc = { + id: number; + name: string; + npcType: number; + idHead: number; + idBody: number; + movement: number; + version: number; + updatedAt: string; +}; + +export type TerrainPaletteEntry = { + id: number; + graphics: Array; + blocked: boolean; +}; + +export type TerrainPalette = { + mapNum: number; + palette: TerrainPaletteEntry[]; + uploadedGraphics: Array<{ + grhIndex: number; + width: number; + height: number; + byteSize: number; + createdAt: string; + }>; +}; + +export type MapTileOverride = { + x: number; + y: number; + layer: number; + grhIndex: number | null; + blocked: boolean | null; + status: "draft" | "published"; +}; + +export type MapTileEntity = { + x: number; + y: number; + kind: "obj" | "npc"; + entityId: number; + status: "draft" | "published"; +}; + +export type MapOverridesResponse = { + mapNum: number; + includeDrafts: boolean; + overrides: MapTileOverride[]; + entities: MapTileEntity[]; +}; + +export type MapStatus = { + mapNum: number; + draft: number; + published: number; + draftEntities: number; + publishedEntities: number; +}; + +export type TilePaint = { + x: number; + y: number; + layer: number; + grhIndex: number | null; + blocked?: boolean | null; +}; + +export type EntityPlacement = { + x: number; + y: number; + kind: "obj" | "npc"; + entityId: number; +}; + +export type UploadedGraphic = { + grhIndex: number; + width: number; + height: number; + byteSize: number; + deduped: boolean; + url: string; +}; + +export type PaginatedResponse = { + items: T[]; + pagination: { + page: number; + pageSize: number; + total: number; + totalPages: number; + }; +}; + +async function requestJson( + path: string, + init?: RequestInit, +): Promise<{ status: number; ok: boolean; data: T }> { + const response = await fetch(path, init); + // Un cuerpo vacio o no-JSON no debe convertirse en un TypeError al leer + // `data.error`: todos los llamadores esperan un objeto. + const data = ((await response.json().catch(() => null)) ?? {}) as T; + return { status: response.status, ok: response.ok, data }; +} + +function editorPath(path: string): string { + return `/api/editor/${path.replace(/^\/+/, "")}`; +} + +export async function listEditorObjects(): Promise { + const response = await requestJson<{ + objects?: EditorObject[]; + error?: string; + }>(editorPath("objects?all=true")); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudieron cargar los objetos."); + } + + return response.data.objects ?? []; +} + +export async function listEditorNpcs(): Promise { + const response = await requestJson<{ npcs?: EditorNpc[]; error?: string }>( + editorPath("npcs?all=true"), + ); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudieron cargar los NPCs."); + } + + return response.data.npcs ?? []; +} + +export async function getMapTerrainPalette(mapNum: number): Promise { + const response = await requestJson( + editorPath(`maps/${mapNum}/terrain`), + ); + + if (!response.ok) { + throw new Error( + (response.data as { error?: string }).error ?? + "No se pudo cargar la paleta de tiles.", + ); + } + + return response.data as TerrainPalette; +} + +export async function getMapStatus(mapNum: number): Promise { + const response = await requestJson( + editorPath(`maps/${mapNum}/status`), + ); + + if (!response.ok) { + throw new Error( + (response.data as { error?: string }).error ?? + "No se pudo cargar el estado del mapa.", + ); + } + + return response.data as MapStatus; +} + +export async function paintTiles( + mapNum: number, + tiles: TilePaint[], +): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/tiles`), + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ tiles }), + }, + ); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudieron pintar los tiles."); + } +} + +export async function clearTileOverride( + mapNum: number, + x: number, + y: number, + layer: number, +): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/tiles/${x}/${y}/${layer}`), + { method: "DELETE" }, + ); + + if (!response.ok) { + throw new Error( + response.data.error ?? "No se pudo limpiar el tile.", + ); + } +} + +export async function placeTileEntity( + mapNum: number, + placement: EntityPlacement, +): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/entities`), + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(placement), + }, + ); + + if (!response.ok) { + throw new Error( + response.data.error ?? "No se pudo colocar el objeto o NPC.", + ); + } +} + +export async function removeTileEntity( + mapNum: number, + x: number, + y: number, + kind: "obj" | "npc", +): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/entities/${x}/${y}/${kind}`), + { method: "DELETE" }, + ); + + if (!response.ok) { + throw new Error( + response.data.error ?? "No se pudo quitar el objeto o NPC.", + ); + } +} + +export async function publishMapChanges(mapNum: number): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/publish`), + { method: "POST" }, + ); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudo publicar el mapa."); + } +} + +export async function discardMapDrafts(mapNum: number): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/discard`), + { method: "POST" }, + ); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudieron descartar los borradores."); + } +} + +export async function revertMapChanges(mapNum: number): Promise { + const response = await requestJson<{ error?: string }>( + editorPath(`maps/${mapNum}/revert`), + { method: "POST" }, + ); + + if (!response.ok) { + throw new Error(response.data.error ?? "No se pudo revertir el mapa."); + } +} + +export async function uploadGraphicPng( + pngBytes: ArrayBuffer, +): Promise { + const response = await requestJson( + editorPath("graphics"), + { + method: "POST", + headers: { "Content-Type": "image/png" }, + body: pngBytes, + }, + ); + + if (!response.ok) { + throw new Error( + (response.data as { error?: string }).error ?? + "No se pudo subir el grafico.", + ); + } + + return response.data as UploadedGraphic; +} + +/** + * Ediciones del mapa para el editor. + * + * Usa la ruta de admin y no la publica a proposito: la publica degrada a lo + * publicado cuando falta permiso o la API no responde, y el editor abriria un + * mapa sin borradores como si estuviera todo en orden. + */ +export async function getMapOverrides(mapNum: number): Promise { + const response = await requestJson( + editorPath(`maps/${mapNum}/overrides`), + ); + + if (!response.ok) { + throw new Error( + (response.data as { error?: string }).error ?? + "No se pudieron cargar las ediciones del mapa.", + ); + } + + return response.data as MapOverridesResponse; +} + +/** + * Si la cuenta actual puede usar el modo construccion. + * + * La sesion publica no dice si la cuenta es admin de game-data, y compararlo en + * el cliente exigiria mandarle el email de admin al navegador. Preguntar a la + * API deja el dato del lado del servidor. + */ +export async function isGameDataAdmin(): Promise { + try { + const response = await requestJson<{ isGameDataAdmin?: boolean }>( + editorPath("session"), + ); + + return response.ok && response.data.isGameDataAdmin === true; + } catch { + // Sin red se asume que no hay permiso: es el caso seguro. + return false; + } +} \ No newline at end of file diff --git a/frontend/lib/editor/editorStore.tsx b/frontend/lib/editor/editorStore.tsx new file mode 100644 index 00000000..7a47f422 --- /dev/null +++ b/frontend/lib/editor/editorStore.tsx @@ -0,0 +1,359 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from "react"; +import type { + EditorNpc, + EditorObject, + MapStatus, + MapTileEntity, + MapTileOverride, + TerrainPalette, + TerrainPaletteEntry, +} from "./editorApi"; +import { + getMapOverrides, + getMapStatus, + getMapTerrainPalette, + listEditorNpcs, + listEditorObjects, +} from "./editorApi"; +import { UPLOADED_GRAPHIC_INDEX_START } from "../../utils/gameLoader"; + +/** + * Pincel de terreno. + * + * Una entrada de la paleta describe el tile completo, no un solo grafico: lleva + * un grafico por capa y si bloquea el paso. Pintar solo la capa 1 dejaria el + * arbol de la capa 3 encima del piso nuevo. + */ +export type TerrainBrush = { + paletteId: number; + /** Grafico por capa, desde la capa 1. `null` vacia esa capa. */ + graphics: Array; + blocked: boolean; + /** Grafico representativo, para miniaturas y recientes. */ + grhIndex: number; +}; + +/** + * Pincel de una entrada de la paleta del mapa. + * + * Las capas que la entrada no menciona quedan fuera del pincel y no se tocan al + * pintar; los `null` internos si vacian esa capa, porque forman parte de como se + * ve el tile (un piso con arbol en la capa 3 y nada en la 2). + */ +export function createTerrainBrush(entry: TerrainPaletteEntry): TerrainBrush { + const representative = entry.graphics.find( + (graphic): graphic is number => + typeof graphic === "number" && graphic > 0, + ); + + return { + paletteId: entry.id, + graphics: entry.graphics, + blocked: entry.blocked, + grhIndex: representative ?? 0, + }; +} + +/** + * Pincel de un grafico subido. Cada PNG subido es un tile entero, asi que ocupa + * la capa del piso y no bloquea por si mismo. + */ +export function createUploadedGraphicBrush(grhIndex: number): TerrainBrush { + return { + paletteId: grhIndex, + graphics: [grhIndex], + blocked: false, + grhIndex, + }; +} + +/** + * Rearma un pincel a partir de su id. Lo necesitan los recientes y la barra de + * herramientas, que guardan el id y no el pincel entero. + */ +export function findTerrainBrush( + terrain: TerrainPalette | null, + paletteId: number, +): TerrainBrush | null { + const entry = terrain?.palette.find( + (candidate) => candidate.id === paletteId, + ); + + if (entry) { + return createTerrainBrush(entry); + } + + // Un grafico subido se puede pintar igual aunque no venga en la paleta de + // este mapa: la lista de subidos tiene tope y el indice ya alcanza. + if (paletteId >= UPLOADED_GRAPHIC_INDEX_START) { + return createUploadedGraphicBrush(paletteId); + } + + return null; +} + +export type EditorTool = + | ({ kind: "terrain" } & TerrainBrush) + | { kind: "object"; object: EditorObject } + | { kind: "npc"; npc: EditorNpc } + | { kind: "erase" }; + +export type RecentsEntry = { + kind: "terrain" | "object" | "npc"; + id: number; + grhIndex: number; + name: string; +}; + +type EditorStoreValue = { + mapNum: number; + setMapNum: (mapNum: number) => void; + objects: EditorObject[]; + npcs: EditorNpc[]; + terrain: TerrainPalette | null; + status: MapStatus | null; + overrides: MapTileOverride[]; + entities: MapTileEntity[]; + tool: EditorTool | null; + setTool: (tool: EditorTool | null) => void; + recents: RecentsEntry[]; + addRecent: (entry: RecentsEntry) => void; + refreshMapData: () => Promise; + refreshStatus: () => Promise; + isLoading: boolean; + loadError: string | null; +}; + +const RECENTS_STORAGE_KEY = "editor.recents.v1"; +const DEFAULT_MAP_NUM = 1; + +const EditorStoreContext = createContext(null); + +function readRecentsFromStorage(): RecentsEntry[] { + if (typeof window === "undefined") { + return []; + } + + try { + const raw = window.localStorage.getItem(RECENTS_STORAGE_KEY); + + if (!raw) { + return []; + } + + const parsed = JSON.parse(raw) as unknown; + + if (!Array.isArray(parsed)) { + return []; + } + + return parsed.filter( + (entry): entry is RecentsEntry => + typeof entry === "object" && + entry !== null && + ["terrain", "object", "npc"].includes( + (entry as RecentsEntry).kind, + ) && + typeof (entry as RecentsEntry).id === "number" && + typeof (entry as RecentsEntry).grhIndex === "number", + ); + } catch { + return []; + } +} + +function writeRecentsToStorage(recents: RecentsEntry[]): void { + try { + window.localStorage.setItem(RECENTS_STORAGE_KEY, JSON.stringify(recents)); + } catch { + // El almacenamiento puede estar lleno o bloqueado; se ignora. + } +} + +export function EditorStoreProvider({ + initialMapNum = DEFAULT_MAP_NUM, + children, +}: { + initialMapNum?: number; + children: ReactNode; +}) { + const [mapNum, setMapNumState] = useState(initialMapNum); + const [objects, setObjects] = useState([]); + const [npcs, setNpcs] = useState([]); + const [terrain, setTerrain] = useState(null); + const [status, setStatus] = useState(null); + const [overrides, setOverrides] = useState([]); + const [entities, setEntities] = useState([]); + const [tool, setTool] = useState(null); + const [recents, setRecents] = useState(() => + readRecentsFromStorage(), + ); + const [isLoading, setIsLoading] = useState(true); + const [loadError, setLoadError] = useState(null); + const refreshTokenRef = useRef(0); + + const refreshMapData = useCallback(async () => { + const token = ++refreshTokenRef.current; + setIsLoading(true); + setLoadError(null); + + try { + const [overridesData, statusData, terrainData] = await Promise.all([ + getMapOverrides(mapNum), + getMapStatus(mapNum), + getMapTerrainPalette(mapNum), + ]); + + if (refreshTokenRef.current !== token) { + return; + } + + setOverrides(overridesData.overrides ?? []); + setEntities(overridesData.entities ?? []); + setStatus(statusData); + setTerrain(terrainData); + } catch (error) { + if (refreshTokenRef.current !== token) { + return; + } + + setLoadError( + error instanceof Error + ? error.message + : "No se pudo cargar el mapa.", + ); + } finally { + if (refreshTokenRef.current === token) { + setIsLoading(false); + } + } + }, [mapNum]); + + const refreshStatus = useCallback(async () => { + try { + const statusData = await getMapStatus(mapNum); + setStatus(statusData); + } catch { + // El estado se refresca solo; si falla se conserva el anterior. + } + }, [mapNum]); + + const setMapNum = useCallback((nextMapNum: number) => { + setMapNumState(nextMapNum); + setOverrides([]); + setEntities([]); + setStatus(null); + setTerrain(null); + setTool(null); + }, []); + + const addRecent = useCallback((entry: RecentsEntry) => { + setRecents((current) => { + const withoutDuplicate = current.filter( + (existing) => + existing.kind !== entry.kind || existing.id !== entry.id, + ); + const next = [entry, ...withoutDuplicate].slice(0, 12); + writeRecentsToStorage(next); + return next; + }); + }, []); + + useEffect(() => { + let cancelled = false; + + Promise.all([listEditorObjects(), listEditorNpcs()]) + .then(([objectsData, npcsData]) => { + if (cancelled) { + return; + } + + setObjects(objectsData); + setNpcs(npcsData); + }) + .catch((error) => { + if (!cancelled) { + setLoadError( + error instanceof Error + ? error.message + : "No se pudo cargar el catalogo.", + ); + } + }); + + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + void refreshMapData(); + }, [mapNum, refreshMapData]); + + const value = useMemo( + () => ({ + mapNum, + setMapNum, + objects, + npcs, + terrain, + status, + overrides, + entities, + tool, + setTool, + recents, + addRecent, + refreshMapData, + refreshStatus, + isLoading, + loadError, + }), + [ + addRecent, + entities, + isLoading, + loadError, + mapNum, + npcs, + objects, + overrides, + recents, + refreshMapData, + refreshStatus, + setMapNum, + status, + terrain, + tool, + ], + ); + + return ( + + {children} + + ); +} + +export function useEditorStore(): EditorStoreValue { + const context = useContext(EditorStoreContext); + + if (!context) { + throw new Error( + "useEditorStore debe usarse dentro de EditorStoreProvider", + ); + } + + return context; +} \ No newline at end of file diff --git a/frontend/lib/editor/useGameDataAdmin.ts b/frontend/lib/editor/useGameDataAdmin.ts new file mode 100644 index 00000000..333fe4f8 --- /dev/null +++ b/frontend/lib/editor/useGameDataAdmin.ts @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { isGameDataAdmin } from "./editorApi"; + +export type GameDataAdminState = "loading" | "allowed" | "denied"; + +/** + * Si la cuenta puede editar mapas. + * + * El permiso lo decide la API: el token del proxy vive solo en el servidor, asi + * que el navegador no tiene forma de saberlo por su cuenta y hay que preguntar. + * Es un gate de interfaz para no mostrar un editor que va a responder 403, no + * una barrera de seguridad: cada endpoint del editor valida por su cuenta. + * + * @param enabled Cuando es `false` no se pregunta nada y el estado es `denied`. + * Sirve para no gastar una peticion si ya se sabe que no hay sesion. + */ +export function useGameDataAdmin(enabled = true): GameDataAdminState { + const [state, setState] = useState( + enabled ? "loading" : "denied", + ); + + useEffect(() => { + if (!enabled) { + setState("denied"); + return; + } + + let cancelled = false; + setState("loading"); + + void isGameDataAdmin().then((allowed) => { + if (!cancelled) { + setState(allowed ? "allowed" : "denied"); + } + }); + + return () => { + cancelled = true; + }; + }, [enabled]); + + return state; +} diff --git a/frontend/lib/graphicTextures.ts b/frontend/lib/graphicTextures.ts new file mode 100644 index 00000000..9cb34c81 --- /dev/null +++ b/frontend/lib/graphicTextures.ts @@ -0,0 +1,263 @@ +import { Assets, Rectangle, Texture } from "pixi.js"; +import type { + BodiesDB, + DirectionalGraphicData, + GraphicData, + GraphicsDB, + HeadsDB, +} from "../types/game"; +import { + loadBodiesDB, + loadGraphicsDB, + loadHeadsDB, + UPLOADED_GRAPHIC_INDEX_START, +} from "../utils/gameLoader"; +import { getApiBaseUrl } from "./api-base-url"; + +/** + * Resolucion de graficos compartida entre el juego y el editor visual. + * + * Los graficos subidos desde el modo construccion no estan horneados en + * public/, se sirven desde la API. Se distinguen por el rango de indice. + */ +export function getGraphicImagePaths( + imageFile: string | number, +): string[] { + if (Number(imageFile) >= UPLOADED_GRAPHIC_INDEX_START) { + return [`${getApiBaseUrl()}/game-data/graphics/${imageFile}.png`]; + } + + return [ + `/graphics/${imageFile}.png`, + `/static/graphics/${imageFile}.png`, + `/static/graficosbk/${imageFile}.png`, + ]; +} + +/** + * Cachea un catalogo del juego para todo el proceso. + * + * Los `load*DB` descargan y descomprimen en cada llamada, asi que una pantalla + * con una miniatura por fila reconstruiria el catalogo cientos de veces. Un + * fallo no queda cacheado: se vuelve a intentar en la siguiente llamada. + */ +function shareCatalog(load: () => Promise): { + get: () => Promise; + invalidate: () => void; +} { + let pending: Promise | null = null; + + return { + get: () => { + if (!pending) { + pending = load().catch((error: unknown) => { + pending = null; + throw error; + }); + } + + return pending; + }, + invalidate: () => { + pending = null; + }, + }; +} + +const graphicsCatalog = shareCatalog(loadGraphicsDB); +const bodiesCatalog = shareCatalog(loadBodiesDB); +const headsCatalog = shareCatalog(loadHeadsDB); + +/** Catalogo de graficos compartido por todo el proceso. */ +export function getSharedGraphicsDB(): Promise { + return graphicsCatalog.get(); +} + +/** + * Descarta el catalogo compartido. Hay que llamarlo tras subir un grafico: los + * indices subidos se mezclan al construir el catalogo y de otro modo el nuevo + * no existiria hasta recargar la pagina. + */ +export function invalidateSharedGraphicsDB(): void { + graphicsCatalog.invalidate(); +} + +/** Catalogo de cuerpos compartido, para resolver el grafico de un NPC. */ +export function getSharedBodiesDB(): Promise { + return bodiesCatalog.get(); +} + +/** Catalogo de cabezas compartido, para resolver el grafico de un NPC. */ +export function getSharedHeadsDB(): Promise { + return headsCatalog.get(); +} + +/** Los sprites mirando hacia el jugador. */ +const FRONT_DIRECTION = "2"; + +/** + * Grafico con el que se reconoce a un personaje en una lista. + * + * Un NPC no tiene grhIndex propio: se dibuja combinando cuerpo y cabeza. Para + * una miniatura alcanza la cabeza de frente, y cuando no tiene (bestias, + * criaturas) el cuerpo es justamente lo que lo identifica. + */ +export function resolveCharacterThumbnailGrh( + bodiesDB: BodiesDB | null, + headsDB: HeadsDB | null, + idBody: number, + idHead: number, +): number { + const head = headsDB?.[String(idHead)]?.[FRONT_DIRECTION] ?? 0; + + if (head > 0) { + return head; + } + + return bodiesDB?.[String(idBody)]?.[FRONT_DIRECTION] ?? 0; +} + +const imagePromiseCache = new Map>(); + +/** + * Carga la imagen fuente de un grafico como `HTMLImageElement`, para recortarla + * en un canvas 2D. + * + * Las miniaturas del editor no usan PixiJS a proposito: cada `Application` toma + * un contexto WebGL, y el navegador solo mantiene una decena y media vivos. La + * paleta de un mapa tiene cientos de entradas. + */ +export function loadGraphicImage( + imageFile: string | number, +): Promise { + const candidatePaths = getGraphicImagePaths(imageFile); + const cacheKey = candidatePaths.join("|"); + const cached = imagePromiseCache.get(cacheKey); + + if (cached) { + return cached; + } + + const loadPromise = (async () => { + let lastError: unknown; + + for (const candidatePath of candidatePaths) { + try { + return await loadImageElement(candidatePath); + } catch (error) { + lastError = error; + } + } + + throw lastError ?? new Error(`No se pudo cargar ${imageFile}.png`); + })(); + + imagePromiseCache.set(cacheKey, loadPromise); + + return loadPromise; +} + +function loadImageElement(source: string): Promise { + return new Promise((resolve, reject) => { + const image = new Image(); + image.decoding = "async"; + image.onload = () => resolve(image); + image.onerror = () => reject(new Error(`No se pudo cargar ${source}`)); + image.src = source; + }); +} + +const baseTexturePromiseCache = new Map>(); + +export async function loadBaseTexture( + imageFile: string | number, +): Promise { + const candidatePaths = getGraphicImagePaths(imageFile); + const cacheKey = candidatePaths.join("|"); + const cachedPromise = baseTexturePromiseCache.get(cacheKey); + + if (cachedPromise) { + return cachedPromise; + } + + const loadPromise = (async () => { + let lastError: unknown; + + for (const candidatePath of candidatePaths) { + try { + const texture = await Assets.load(candidatePath); + texture.source.scaleMode = "nearest"; + return texture; + } catch (error) { + lastError = error; + } + } + + throw lastError ?? new Error("Failed to load base texture"); + })(); + + baseTexturePromiseCache.set(cacheKey, loadPromise); + return loadPromise; +} + +export async function loadGraphicTexture( + graphicData: GraphicData, +): Promise { + const baseTexture = await loadBaseTexture(graphicData.numFile); + + return new Texture({ + source: baseTexture.source, + frame: new Rectangle( + graphicData.sX, + graphicData.sY, + graphicData.width, + graphicData.height, + ), + }); +} + +export function resolveGraphicFrame( + graphicsDB: GraphicsDB, + graphicId: number, + direction: string, +): GraphicData | null { + const graphic = graphicsDB[graphicId.toString()]; + + if (!graphic) { + return null; + } + + if (graphic.numFile && graphic.numFrames <= 1) { + return graphic; + } + + const frameId = + (graphic.numFrames > 1 ? graphic.frames?.["1"] : undefined) ?? + graphic.frames?.[direction] ?? + graphic.frames?.["1"] ?? + Object.values(graphic.frames ?? {})[0]; + + if (!frameId) { + return null; + } + + return graphicsDB[frameId.toString()] ?? null; +} + +export function resolveDirectionalGraphicFrame( + graphicsDB: GraphicsDB, + directionalData: DirectionalGraphicData | undefined, + direction: string, +): GraphicData | null { + if (!directionalData) { + return null; + } + + const graphicId = directionalData[direction as keyof DirectionalGraphicData]; + + if (!graphicId) { + return null; + } + + return resolveGraphicFrame(graphicsDB, graphicId, direction); +} diff --git a/frontend/utils/gameLoader.ts b/frontend/utils/gameLoader.ts index 3de8bdf9..f26b355d 100644 --- a/frontend/utils/gameLoader.ts +++ b/frontend/utils/gameLoader.ts @@ -806,15 +806,19 @@ async function applyMapOverrides( continue; } - if (override.grhIndex != null) { - const graphics = (tile.graphics ?? {}) as Record< - string, - number - >; + const graphics = (tile.graphics ?? {}) as Record; + + if (override.grhIndex == null) { + // Un grafico nulo es "esta capa quedo vacia", no "sin cambios": + // el editor lo usa para borrar, y si no se aplicara el jugador + // seguiria viendo el arbol que el admin ya saco. + delete graphics[String(override.layer)]; + } else { graphics[String(override.layer)] = override.grhIndex; - tile.graphics = graphics; } + tile.graphics = graphics; + if (override.blocked != null) { // El cliente representa el bloqueo como numero (1 / 0), no como // booleano, que es lo que devuelve la API.