Jump to content

Search the Community

Showing results for tags 'quest'.



More search options

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Metin2 Zone
    • Community
    • Presentations and farewells
  • Private Servers
    • Server Presentations
    • Project showcase
  • General
    • General Discussions
    • Partnerships and Cooperation
    • Services and trading
    • Helps and questions
    • Reviews and advice
    • Offtopic
  • Technical
    • Programming
    • Metin2 Server Modding
    • Web Development
    • Security
    • Operating Systems
    • Computing
  • Art
    • Design and graphic section
    • Gallery of images and videos
    • 3D Modelling
    • Mapping
  • Downloads
    • Client and Server files
    • 3D Models
    • Metin2 Images
    • Maps
    • Translation
  • Archive
    • Offline Servers
    • Temas Links Caidos

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Email


Sitio web


Jabber


Skype


Discord


Location


Intereses

Found 84 results

  1. Buenas noches, me encontré con un problema que había tenido hace mucho tiempo y al retomarlo me encontré que casi no hay guías y si las hay no son bien explicadas a mi parecer le faltan cosas a las guías por lo tanto hoy les traigo en lo que pueda bien explicado el tema de usar una quest via python para las funciones de los botones del inventario les dejo una imagen para mayor guia. Bien empecemos: Lo primero que tenemos que hacer es abrir el game.py que esta en root Buscamos: def __ServerCommand_Build(self): Y agregamos a la lista el boton que usaremos para la quest: "boton_quest" :self.boton_quest, Luego Bajamos a las ultimas lineas del archivo game.py y agregamos la funcion del botón: def boton_quest(self, id): constInfo.boton_quest= int(id) Bien, una vez tengamos hecho lo anterior vamos al archivo uiinventory.py Buscamos lo siguiente: def LoadWindow(self): debajo agregamos nuestro botón: self.nombrequest= self.GetChild("nombrequeapareceraencliente") Luego buscamos en este mismo archivo lo siguiente: # MallButton Creamos el evento de la función self.nombrequest.SetEvent(ui.mem_func(self._nombrequest)) Luego buscamos: def ClickMallButton(self) Abajo ponemos la funcion del boton con la unión de la llamada del botón def _nombrequest(self): event.QuestButtonClick(constInfo.boton_quest) Luego de esto compilamos el archivo root y vamos a decompilar el locale Buscamos el archivo llamado inventoriywindows.py que se encuentra en el directorio /ui Buscamos la linea: "name" : "InventoryWindow" Dentro agregamos nuesto botón (que contendrá su ubicación, icono y conexion con el uiinventory.py llamado = nombrequeapareceraencliente): { "name" : "nombrequeapareceraencliente", "type" : "button", "x" : 10, "y" : 20+34+34+34-13, "tooltip_text" : "nombrequeapareceraencliente", "default_image" : "locale/es/botones/img.tga", "over_image" : "locale/es/botones/img2.tga", "down_image" : "locale/es/botones/img3.tga", }, Por último lo único que tenemos que hacer es en la quest que usaremos necesitamos indicarle que quest usara y que boton quedando algo asi: quest myquest begin state start begin when login begin cmdchat("boton_quest"..q.getcurrentquestindex()) end when button or info begin say_title ( "Nuestra quest funciona con boton :D" ) end end end Aquí el nombre "boton_quest" tiene que ser el nombre que usamos en el archivo game.py al principio para indicarle que funcion usara el boton. cmdchat("boton_quest "..q.getcurrentquestindex()) Eso seria todo, cualquier consulta o algo que no se entienda o explique mal hagan me lo saber y lo corrijo, espero haber ayudado, suerte
  2. ¡Hola a todos! Hoy he llegado a los 700 likes. Gracias a todos los que han apoyado mis posts. Saludos a @Kronixer™, @Pegasus mis socios con los que trabajo También a Askira, Nazox, el crack Break, a mi amigo Deiklo, Dur, Viksant, Mau, al admin Peluche, Enju, Delaro, Mix (Legion), Master (Yelson) y todos los que no recuerdo en este momento xD El regalo de hoy es una quest que consiste en agregar un set de armas (skins) en un cofre. En esta quest podrás: - Agregar de la manera más sencilla los sets de armas (skins) que quieras, sin preocuparte por la programación. - Elegir un arma de uno o varios sets de armas (skins), según como lo hayas configurado. - Ver el nombre del set junto con el tipo de arma (espada, daga, dos manos, etc. los que quieras agregar. Se puede poner garras también) - Al elegir el tipo de arma, te mostrará el ícono y te pedirá confirmar. Una vez confirmado, te entrega el objeto y se borra el cofre. - Agregar uno o varios sets en un solo baúl. En ese caso te pedirá elegir según el nombre del set. No tengo fotos pero sé que funciona porque yo lo hice xD weapon_skin.quest quest weapon_skin begin state start begin function bug_control() return pc.count_item(item.vnum) < 1 end function data() return { [1] = { title = "Prueba", --Nombre del set {40159, "Espada"}, --vnum y tipo de arma {40160, "Daga"}, {40161, "Arco"}, {40162, "Dos manos"}, {40163, "Campana"}, {40164, "Fan"}, {40165, "Garras"} }, [2] = { title = "Nemere", {40400, "Espada"}, {40403, "Daga"}, {40404, "Arco"}, {40402, "Dos manos"}, {40405, "Campana"}, {40406, "Fan"}, {40407, "Garras"} }, [3] = { title = "Arrador", {40666, "Espada"}, {40668, "Daga"}, {40669, "Arco"}, {40667, "Dos manos"}, {40670, "Campana"}, {40671, "Fan"}, {40672, "Garras"} }, [4] = { title = "Hongos", {40300, "Espada"}, {40303, "Daga"}, {40304, "Arco"}, {40302, "Dos manos"}, {40305, "Campana"}, {40306, "Fan"}, {40307, "Garras"} }, [5] = { title = "Submarinas", {19960, "Espada"}, {19963, "Daga"}, {19964, "Arco"}, {19962, "Dos manos"}, {19965, "Campana"}, {19966, "Fan"}, {19967, "Garras"} }, [6] = { title = "Cristal", {40408, "Espada"}, {40411, "Daga"}, {40412, "Arco"}, {40410, "Dos manos"}, {40413, "Campana"}, {40414, "Fan"}, {40415, "Garras"} }, [7] = { title = "Jotun", {61110, "Espada"}, {61113, "Daga"}, {61114, "Arco"}, {61112, "Dos manos"}, {61115, "Campana"}, {61116, "Fan"}, {61117, "Garras"} }, [8] = { title = "Hidra", {40590, "Espada"}, {40592, "Daga"}, {40593, "Arco"}, {40591, "Dos manos"}, {40594, "Campana"}, {40595, "Fan"}, {40596, "Garras"} }, [9] = { title = "Zodíaco", {40316, "Espada"}, {40319, "Daga"}, {40320, "Arco"}, {40318, "Dos manos"}, {40321, "Campana"}, {40322, "Fan"}, {40323, "Garras"} } } end when 30335.use or --vnum del cofre 30339.use or 8011.use or 8012.use or 8013.use or 8014.use or 8015.use or 8016.use or 8017.use or 8018.use begin if weapon_skin.bug_control() then return end local list = { [30335] = {1}, --vnum del cofre y seguido el id del set. En este caso muestra el set 1. [30339] = {2, 3, 4, 5, 6, 7, 8, 9}, --vnum del cofre y seguido el id del set. En este caso muestra varios sets. El personaje solo podrá elegir uno. [8011] = {2}, [8012] = {3}, [8013] = {8}, [8014] = {9}, [8015] = {7}, [8016] = {6}, [8017] = {5}, [8018] = {4} } if list[item.vnum] != nil then weapon_skin.giveSkinSet(list[item.vnum]) end end function giveSkinSet(set_elements) local data = weapon_skin.data() local id_set_selected = 1 if table.getn(set_elements) > 1 then local table_names_set = {} for i = 1, table.getn(set_elements) do if data[set_elements[i]] != nil then if data[set_elements[i]].title != nil then table.insert(table_names_set, data[set_elements[i]].title) end end end table.insert(table_names_set, "Cerrar") say_title(item_name(item.vnum)) say() id_set_selected = select_table(table_names_set) if id_set_selected == table.getn(table_names_set) then return end end say_title(item_name(item.vnum)) say() if data[set_elements[id_set_selected]].title != nil then say_title_center(data[set_elements[id_set_selected]].title) end local table_names_weapon = {} for i = 2, table.getn(data[set_elements[id_set_selected]]) do table_names_weapon[i-1] = data[set_elements[id_set_selected]][i-1][2] end table.insert(table_names_weapon, "Cerrar") local weapon_selected = select_table(table_names_weapon) if weapon_selected == table.getn(table_names_weapon) then return end say_title(item_name(item.vnum)) say("Recibirás: ") say_item_vnum(item.vnum) if select("Confirmar", "Cancelar") == 1 then pc.give_item2(data[set_elements[s1]][weapon_selected][1]) pc.remove_item(item.vnum) end end end end ¿Cómo agregar un skin de set de arma? 1. Ten listado el vnum del cofre y los vnum de las armas que pertenecen a ese set. 2. Agrega un nuevo elemento a la tabla data. function data() return { [1] = { title = "Prueba", --Nombre del set {40159, "Espada"}, --vnum y tipo de arma {40160, "Daga"}, {40161, "Arco"}, {40162, "Dos manos"}, {40163, "Campana"}, {40164, "Fan"}, {40165, "Garras"} }, --agrego la coma al final --Aquí voy a agregar este nuevo set [10] = { title = "Set Zone", {11111, "Espada"}, --Si tienes una espada de sura, solo agrega una línea abajo así: {tu_vnum, "Espada Sura"} {11112, "Daga"}, {11113, "Arco"}, {11114, "Dos manos"}, {11115, "Campana"}, {11116, "Fan"}, {11117, "Garras"} }, -- Ten en cuenta el número que agregaste, en este caso yo agregué el 10. No puedes repetir números en la tabla y no necesariamente deben ser en orden. 3. Agrega el cofre when 30335.use or --vnum del cofre 30339.use or 8011.use or 8012.use or 8013.use or 8014.use or 8015.use or 8016.use or 8017.use or 8018.use or --añado el or al final -- Agregando mi cofre de Zone 22222.use -- begin 4. Poner el set o los sets en el cofre local list = { [30335] = {1}, --vnum del cofre y seguido el id del set. En este caso muestra el set 1. [30339] = {2, 3, 4, 5, 6, 7, 8, 9}, --vnum del cofre y seguido el id del set. En este caso muestra varios sets. El personaje solo podrá elegir uno. [8011] = {2}, [8012] = {3}, [8013] = {8}, [8014] = {9}, [8015] = {7}, [8016] = {6}, [8017] = {5}, [8018] = {4}, --pongo la coma --Agregando mi set de Zone [22222] = {10} --el 10 es el id de la tabla del set que agregué } 5. Compilar Hay que compilar necesariamente porque el when item_vnum.use es estático y no le echo mano para que sea dinámico xD Repasemos cómo se vería (sin fotos xD) Cofre con un solo set: Al darle clic al cofre, va a mostrar un say donde muestra el nombre del set y los botones "Espada", "Daga", "Arco", "Dos manos", etc. El personaje elije el arma que desea y le aparece una nueva ventana donde sale el ícono del objeto y un botón que dice confirmar. Al confirmar, el cofre será borrado y el skin será dado. Cofre con varios sets: Al darle clic al cofre, mostrará varios botones con el nombre de los sets. Al elegir alguno de ellos, pasará lo mismo que he mencionado arriba. Espero que sea de alguna utilidad y saludos a todos! weapon_skin.quest
  3. Hola a todos! He creado un evento llamado Super Metines, que consiste en invocar metines en un mapa, con la diferencia de que cada metin tiene en las mismas coordenadas 5 veces del mismo. Así como se ve en la imagen: [Hidden Content] [Hidden Content] - Un pergamino aparece en el GM y tiene la opción de elegir con botones la cantidad de metines que va a lanzar. - El mapa seleccionado es el desierto. - Puedes agregar los vnums de los metines que quieras, todos caerán aleatorios. - Puedes modificar la cantidad de metines superpuestos, por defecto está en 5. - Una vez lanzada una oleada, aparecerá un anuncio a todos los personajes. - Una vez lanzada una oleada, le aparecerá al GM las coordenadas de los metines invocados. Cualquier personalización te la puedo hacer a pedido. Precio de la quest al privado o mensaje a mi Discord: Camilo#0869 Saludos!
  4. Hola muchachos, estoy buscando ayuda para el final de la quest de guerra de imperios Quisiera que la quest terminara cuando alguno de los dos reinos "llegara a una puntuacion de 250" (kills - puntos), que saliera el anuncio de que "gano x reino" se le diera un premio a todos los qe entraron de ese reino. y los sacara a todos . actualmente da el recuento de los puntos todo IMAGEN DE GUERRA perfecto pero sale "el tiempo ha terminado" pero no dice quien gano ni los saca ni nada. De antemano agradezco al que me pueda colaborar quest warp_empire begin state start begin when 9004.chat."GM: Guerra de Reinos" with pc.is_gm() begin say_title(""..pc.get_name()..":") say("") local se = select ("Activar evento","Desactivar evento","Salir") if se == 1 then game.set_event_flag("Guerra_Jinno", 1) game.set_event_flag("Guerra_Shinso", 1) game.set_event_flag("Jinno", 1) game.set_event_flag("Shinso", 1) game.set_event_flag("Jinno_kill", 1) game.set_event_flag("Shinso_kill", 1) notice_all("Guerra de Reinos activada") elseif se == 2 then game.set_event_flag("Guerra_Jinno", 0) game.set_event_flag("Guerra_Shinso", 0) game.set_event_flag("Jinno", 0) game.set_event_flag("Shinso", 0) game.set_event_flag("Jinno_kill", 0) game.set_event_flag("Shinso_kill", 0) notice_all("Guerra de Reinos desactivada") else return end end when 9004.chat."Acerca de la guerra de Imperios" begin if game.get_event_flag("Guerra_Jinno") == 0 and pc.get_empire() == 3 then say_title("El evento:") say("Actualmente esta desactivado") say("") end if game.get_event_flag("Guerra_Jinno") == 1 and pc.get_empire() == 3 then say_title("El evento:") say("Actualmente esta activo") local temo1 = select ("Quiero entrar", "Salir") if temo1 == 1 then pc.warp(25600, 51200) else return end end if game.get_event_flag("Guerra_Shinso") == 0 and pc.get_empire() == 1 then say_title("El evento:") say("Actualmente esta desactivado") say("") end if game.get_event_flag("Guerra_Shinso") == 1 and pc.get_empire() == 1 then say_title("El evento:") say("Actualmente esta activo") local temo2 = select ("Quiero entrar", "Salir") if temo2 == 1 then pc.warp(25600, 51200) else return end end end when login with pc.get_map_index() == 103 begin if pc.get_empire() == 1 then game.set_event_flag("Shinso", game.get_event_flag("Shinso")+1) chat(""..game.get_event_flag("Shinso").." en el evento hay esos shinsos") elseif pc.get_empire() == 3 then game.set_event_flag("Jinno", game.get_event_flag("Jinno")+1) chat(""..game.get_event_flag("Jinno").." en el evento hay esos Jinno") end if game.get_event_flag("Shinso") == 2 then notice_all("El reino shinso ya alcanso su maximo numero de jugadores, esta listo para comenzar") notice_all("Las puertas para este imperio han sido bloqueadas") game.set_event_flag("Guerra_Shinso", 0) elseif game.get_event_flag("Jinno") == 2 then notice_all("El reino Jinno ya alcanso su maximo numero de jugadores, esta listo para comenzar") notice_all("Las puertas para este imperio han sido bloqueadas") game.set_event_flag("Guerra_Jinno", 0) end end when kill with npc.is_pc() and pc.get_map_index() == 103 begin loop_timer("Tiempo", 20*1) if pc.get_empire() == 3 then game.set_event_flag("Jinno_kill", game.get_event_flag("Jinno_kill")+1) notice_all("Jinno lleva:"..game.get_event_flag("Jinno_kill").." puntos") elseif pc.get_empire() == 1 then game.set_event_flag("Shinso_kill", game.get_event_flag("Shinso_kill")+1) notice_all("shinso lleva:"..game.get_event_flag("Shinso_kill").." puntos") end end when Tiempo.timer begin chat("El tiempo ha acabado") if game.get_event_flag("Jinno_kill") > game.get_event_flag("Chunjo_kill") > game.get_event_flag("Shinso_kill") then -------- Aqui shinso pierde notice_all("Shinso ha sido eliminado") elseif game.get_event_flag("Chunjo_kill") > game.get_event_flag("Shinso_kill") > game.get_event_flag("Jinno_kill") then ------- Aqui jinno pierde notice_all("Jinno ha sido eliminado") end end end end
  5. Bueno. aquí les dejo la carpeta Spain. osea la carpeta de locale con map, quest etc. tiene las quest originales limpias sin editar, [Hidden Content] Post Original
  6. Buenas tardes, nesecito una quest para activar evento de un NPC por un tiempo limite, ej: yo uso /e cajas_luz_luna 1-0 para activar y dezactivar, me gustaria ponerlo en un npc para activar y dezactivar, ademas cuando la activo que se pueda introducir durante cuanto tiempo. Ej: TITULO:EVENTO CAJAS LUZ LUNA El evento se encuentra: (el estado del evento Activado o dezactivado) Que desea hacer? Boton [Activar] Botonc [Dezactivar] Boton [Cerrar] Y cuando le de a [Activar] TITULO:EVENTO CAJAS LUZ LUNA Introduce el tiempo que desas que dure el evento en hora. [ Espacio para introducir numero (Las horas de duracion] Debajo boton [Activar o continuar] Gracias, espero que me explicado bien
  7. Hola a todos En esta ocasión voy a explicarles con pleno detalle cómo hice el editor de ítems. Aclaro que la versión que tengo posteada es antigua en cuanto a código, y la versión que les voy a enseñar ahora es basada en la experiencia que he adquirido luego. Esto quiere decir, código limpio, modular, más desacoplado, más cohesivo, mejores nombres de variables, todo en inglés, mejor estructurado, más dinámico, más fácil de leer y modificar, más actualizado en cuanto a tipos de objetos y bonus, convertido en librería, mejor dicho, toda una obra de arte. Verán la forma de programación más profesional que jamás hayan visto, y lo mejor de todo, esto es gratis. Esta quest no fue hecha una vez en tres días, sino fue la experiencia de una persona que lleva más de 5000 horas de programación en lua y quests, con una carrera profesional en este tipo de temas y varios años de conocimiento sobre el juego y su software. Esto va a ser extenso pero a la vez va a estar muy enriquecido. Les cuento que la primera vez que la empecé a hacer, no sabía cómo hacerla, solo empecé. ¿Alguna vez les ha pasado? lo importante es escribir la primera línea de código, nunca te quedes pensando cómo hacer algo o nunca lo harás; este es mi primer consejo. Cuando empecé a hacerla, ya había creado el bloque quest, state y empezaba mi primer when, fue el npc donde iba a hacer el take. Con mi experiencia en quest pude saber hacia dónde me tenía que dirigir luego. Aquí vamos. Primero que todo debemos saber cómo es que se supone que debería funcionar. Obviamente ustedes ya la vieron y la metieron en su sv. Pero pónganse en mis zapatos, que yo solo tenía una idea en la cabeza, no tenía ninguna quest para guiarme. ¿Cómo es que se hace para llevar una idea a que se haga real? Lo único que pensé fue, bueno, quiero que algo agarre cierto objeto equipable que se le pueda meter bonus, me muestre los bonus y valores que pueda elegir y ya, cuando mire el objeto ya tenga esos bonus puestos tal cual el orden en que los elegí. Esto lo vi posible porque sé el alcance que tienen las quests, aunque sean limitadas. En este punto sabía que un ítem se puede seleccionar cuando "algo" agarre el id de él (ojo, el id, no el vnum). La diferencia entre el id de un objeto y el vnum, es que el id representa la identidad de un objeto, y ese id es único en cada objeto. Mientras que el vnum es la "plantilla" de ese objeto, de manera que cada vez que crees un objeto con el vnum, estarás sacando el objeto de la plantilla de los ítems que es el item_proto. Proto significa base. Los objetos ya creados aparecen en la tabla player.item. Sigo... Una vez un NPC captura el id de un objeto con el disparador take (los login, logout, take, .chat, unmount, etc son triggers pero traducidos son disparadores), ya puedo modificar todos sus atributos que están en las columnas de la tabla player.item. Con la tabla player.item_proto puedo saber si el objeto es un arma o una armadura, y si tiene media o no; ya que ese atributo está desde la base. Para saber esto debes haber estudiado muy bien el item_proto ya que es lo más básico en el desarrollo de servidores. También en la tabla player.item_proto puedo ver los bonus base que el objeto ya tiene, para no tener que repetirlos. Otra cosa de las quests, es que tienen unas funciones listadas en quest/quest_functions. Ahí hay muchas que son útiles para modificar atributos del ítem y lo hace desde el source, o sea que la modificación se hace de inmediato. Las vamos a ver más adelante. Luego necesito mostrar los bonus que están disponibles para ese objeto, que son los mismos bonus que me salen cuando cambio los bonus. Estos bonus están en player.item_attr. Esto también es básico cuando estás aprendiendo estos temas. Aquí se complica un poco las cosas... pero, les digo de una vez que no pensé en nada de eso cuando empecé a hacer la quest. Iniciemos Esta quest será modular, es decir, utilizaré funciones que se conectarán las unas con las otras para lograr nuestro resultado. Es la mejor manera que he encontrado para que cualquier otra persona que vea la quest pueda entenderla muy rápido. item_editor_camilo.quest quest item_editor_camilo begin state start begin when 11000.take or 11002.take or 11004.take begin item_editor_camilo_lib.startItemEditor() end end end Si no entiendes lo de los bloques o no tienes un entorno preparado para programar quests, lee y haz los pasos de este post: "cuando" 11000 (guardián de la ciudad) "tome" "empieza" Ahí estamos diciendo literalmente que cuando el NPC de vnum 11000, 11002 o 11004 tome un objeto (es arrastrado hacia él), entonces empiece a ejecutar lo que viene abajo y "termine" en donde dice end. item_editor_camilo_lib.startItemEditor() En esta instrucción estamos llamando a una función de una tabla que no hemos creado. Vamos a crearla. item_editor_camilo_lib.lua item_editor_camilo_lib = {} item_editor_camilo_lib.startItemEditor = function() chat("Esto debería mostrar cuando ponga un objeto.") end Para esto crearás un archivo llamado item_editor_camilo_lib.lua. En verdad el nombre no importa pero la extensión sí, no la cambies, tiene que ser lua. Un archivo es .quest cuando el código empieza por quest [nombre] begin. Un archivo es .lua cuando no empiece por quest. En este caso no empieza por quest. Este archivo por el momento no tiene conexión con nada. Nuestro archivo padre se llama questlib.lua que ya está configurado desde la fuente para que sea la librería papá. Allí se pone todo tipo de código suelto, funciones, inicializar variables, llamar a otros archivos, etc. Conectaremos el questlib.lua con nuestra librería que acabamos de crear. Para ello ponemos esta línea en el questlib.lua: dofile(get_locale_base_path().."/quest/system/_item_editor_camilo/item_editor_camilo_lib.lua") Esta instrucción si la traduces al español lo dice todo pero igual te lo voy a explicar. Aquí el questlib.lua se extiende hacia ese archivo que declaras en esa ruta, por lo tanto, lo ejecuta y lo tendrá en cuenta cuando llamen a alguna función o variable que esté declarada allí. Ese get_locale_base_path() devuelve locale/xxxx. En xxxx va el country o país de tus files, sean turkey, germany, spanish, etc. Ese get_locale_base_path() no lo toques, solo modifica lo que está en verde. Y si no te funciona así, pon la ruta completa desde el /usr. Lo más probable es que la ruta que tengas no sea la misma mía pero sí te recomiendo que sigas el mismo patrón que las otras quests. Por ejemplo, si en tu carpeta quest hay una carpeta system o source donde meten todas las quests, métela ahí. Si en tu carpeta quest ponen las quest en carpetas que empiezan en "_", crea una carpeta nueva con el mismo patrón. Te recomiendo crear una carpeta donde estará el editor, no combines esta quest y librería con varios archivos de otras cosas. En ese archivo lib que creamos he hecho las funciones de una de las maneras; hay otra manera, como están en questlib.lua pero nosotros somos más sofisticados y las hemos guardado en una tabla. Prueba la quest y revisa si en el juego te funciona para ver si todo está bien. Si te funciona, sigamos. Vamos a hacer que el NPC determine si el objeto tiene media y habilidad. La verdad no nos importa si es arma, armadura, acc, etc. item_editor_camilo_lib.startItemEditor = function() local _, item_table = mysql_direct_query(string.format("SELECT addon_type FROM player.item_proto WHERE vnum = %s;", item.vnum)) if tonumber(item_table[1].addon_type) == -1 then --aquí haríamos lo que haríamos si el objeto tuviera media else syschat("Este objeto no se puede editar.") end end Un momento... ¿Qué acabo de hacer? Bueno, primero que todo vamos a tener que usar la maravillosa función de mysql_direct_query que les dejaré como adjunto al final de este post para que la metan en el src. La saqué de [Hidden Content] Solo hay que meterla y ya, ya les explicaré cómo funciona. Uso del string.format: resulta que cuando creamos los chat o los say o derivados textos que se muestran en el personaje, ponemos variables entre esos textos y no tienen un formato fácil de leer. A mí me gusta mucho usar el string.format. Sin string.format: chat("Soy "..pc.get_name().." y soy nivel "..pc.level) --Esto muestra: --Soy Camilo y soy nivel 120 Con string.format: chat(string.format("Soy %s y soy nivel %s", pc.get_name(), pc.level)) --Esto muestra: --Soy Camilo y soy nivel 120 Son dos formas de crear un string (cadena de texto) pero una es más fácil de leer y modificar que la otra. Ese %s es el que determina la variable, y esa "s" se refiere a que es string. La verdad hay un %d que se refiere a double (números decimales) pero no la uso porque finalmente así sea número va a mezclarse con un string y todo será un string. Así que para todas será siempre %s. Por si te enredas con ese código, míralo de esta manera: local texto_presentacion = string.format("Soy %s y soy nivel %s", pc.get_name(), pc.level) chat(texto_presentacion) --Esto muestra: --Soy Camilo y soy nivel 120 Así se entiende mejor, ¿verdad? Mira ese item.vnum, te preguntarás que por qué item.vnum y no item.get_vnum(). La respuesta está en el questlib.lua. Hay tablas donde asignan nuevos valores a estas funciones, miren: Así que si pones por ejemplo item.type, será lo mismo que poner item.get_type(). Por qué funciona eso así? por unas estructuras llamadas metatables. Lo mismo funciona para algunas funciones de pc y npc. Solo debes revisar. Ahora vamos por entender la query. Query significa consulta, refiriéndonos a la base de datos. Una "búsqueda" en la base de datos. Hay un lenguaje llamado SQL que también se estudia y todo, pero es más básico que los lenguajes que conocemos. SELECT addon_type FROM player.item_proto WHERE vnum = %s; Aquí le estoy diciendo en español "seleccione" addon_type [columna] "de" player.item_proto[tabla] "donde" vnum = xxxx; No es el objetivo enseñar SQL porque hay tutoriales en internet pero haré lo posible para explicar de pasada y quede algo en la mente. Ese player.item_proto lo pongo porque necesito seleccionar una base de datos. Recuerden que las bases de datos son account, common, information_schema, log, mysql, etc. Es decir, las que aparecen debajo de tu conexión. El mismo ícono indica una base de datos. La forma sería base_de_datos.nombre_tabla El resultado de esta consulta obviamente será un solo registro o resultado porque solo hay un vnum único en item_proto. Ya sabemos el tipo de resultado que puede arrojar esa consulta. Ahora si el número el diferente de -1 pues deducimos que no es un arma de media y listo. local _, item_table = mysql_direct_query(... ¿Qué significa ese "_" y por qué hay dos variables que se asignan luego de usar la función mysql_direct_query? Si ven la función por source, verán que retorna dos variables. Dada la estructura de la tabla que retorna, no se puede obtener la cantidad de registros usando el típico table.getn(). La primera variable es la cantidad de registros obtenidos de la consulta, y la segunda variable es la tabla obtenida de la consulta. Puse un "_" porque no voy a utilizar la cantidad de registros en ninguna parte. Es obligatorio poner las dos variables o sino habría un problema de redundancia. La estructura de las tablas obtenidas de mysql_direct_query es: nombre_tabla[num_registro][nombre_columna] Las típicas funciones de mysql_query tienen otra estructura. Entonces para obtener el -1 debemos hacer esto: item_table[1].addon_type --o que es lo mismo: item_table[1]['addon_type'] Explicación de esa forma de acceder al dato: Resulta que si tienes una variable que no tiene espacios puedes hacer como en la primera opción. Pero si tienes una variable con espacios, no puedes hacer esto: item_table[1].addon type --error En cambio sí puedes hacer esto: item_table[1]['addon type'] --aunque obviamente en las columnas de la db no pueden haber espacios Aclaro que solo en dado caso de que la variable tenga espacios o uses variables dentro de ella, que ya lo veremos más adelante. Cuando recibimos ese -1 puede ser un -1 propiamente, o puede ser un "-1". Me ha pasado de las dos formas. Así que es mejor asegurarnos. Hay que poner el tonumber() así como está en el código, para garantizar que estamos comparando con un número. No es lo mismo "-1" que -1. El primero es un string y el segundo es un number, no se pueden comparar. Como opcional ponemos que en dado caso que no sea un objeto con media o habilidad, le decimos que no se puede editar el objeto. Pasemos a la siguiente función: item_editor_camilo_lib.startItemEditor = function() local _, item_table = mysql_direct_query(string.format("SELECT addon_type FROM player.item_proto WHERE vnum = %s;", item.vnum)) if tonumber(item_table[1].addon_type) == -1 then --aquí haríamos lo que haríamos si el objeto tuviera media item_editor_camilo_lib.switchingMeanSkill() else syschat("Este objeto no se puede editar.") end end Para llamar a otra función hay que seleccionar la tabla y luego llamar a la función con el punto. item_editor_camilo_lib.switchingMeanSkill() switchingMeanSkill significa cambiar media y habilidad. Vamos a crearla: item_editor_camilo_lib.switchingMeanSkill = function() say_title_center("Editor de ítems By Camilo[ENTER]") say_item_vnum(item.vnum) say("Ingresa la media que quieres sacar") local mean_chosen = tonumber(input()) if mean_chosen == nil or mean_chosen < 0 or math.mod(mean_chosen, 1) != 0 then return end say_title_center("Editor de ítems By Camilo[ENTER]") say_item_vnum(item.vnum) say("Ingresa la habilidad que quieres sacar") local skill_chosen = tonumber(input()) if skill_chosen == nil or skill_chosen < 0 or math.mod(skill_chosen, 1) != 0 then return end end Le vamos a mostrar al personaje el menú cuando le pongamos el objeto de media. Decoramos con say_title_center. Si no tienes esa función, dejo todo adjunto al final. También decoramos con say_item_vnum poniendo el ícono del objeto en el menú. Acá le juego a un método: la persona selecciona un valor de media y un valor de habilidad, y el primero que caiga mayor o igual, se queda. Voy a explicar esto muy bien porque vi que muchas personas no lo entendieron. Si yo pongo media 50 y habilidad 25, cuando caiga 25 de habilidad se va a detener ahí. Si cae 26 o 27 de habilidad también se detiene ahí. Si cae 52 de media primero que 25 de habilidad, pues quedan esos 52 de media y se detiene el dopador. Nota: en la parte de los bonus de la media y habilidad es un dopador, no es un editor, ya que estamos haciendo cambios realmente. Le pediremos a la persona que digite un número. Esto se hace con input() y lo convertimos a número con tonumber(). Hacemos la validación. La validación es un filtro. Si el texto entregado por la persona no nos sirve en nuestras operaciones, no debemos dejarlo pasar. Si la persona dejó en blanco el espacio ponemos: if mean_chosen == nil Si la persona puso media negativa tampoco lo dejemos pasar. Y si la persona puso números decimales tampoco lo dejemos pasar; yo lo hago con math.mod, explico: Hay una operación matemática que hace parte de la aritmética que se llama módulo (representado en programación por "%") donde el resultado es el residuo de la división de dos números. Sabemos que un número par que se divide entre 2, el resultado será un número entero. Esto se llama ser divisible. Si el 7 se divide entre 3 da un número decimal. Entonces el 7 y el 3 no son divisibles, o sea que el residuo no es 0. Si yo tomo un número y lo divido entre 1, siempre dará el mismo número. Si el número 2 lo divido entre 1 me da 2. El residuo es 0 porque el número es entero. Si el número 2.1 lo divido entre 1 me da 2.1. El residuo no es 0 ya que es decimal. Esto quiere decir que si un número dividido entre 1 me da decimal, el residual no es 0. No había otra manera sencilla de decirle a lua si un número es decimal porque no existe una función "is_integer" o "es_entero" o algo así. Entonces se me ocurrió poner: math.mod(numero, 1) != 0 para definir si el número es decimal. Regla importante sobre el return: Si el return está en una función, no se terminará la quest ahí. Solo devolvería algo. Si llegáramos a poner sentencias debajo de nuestro bloque if: item_editor_camilo_lib.startItemEditor = function() local _, item_table = mysql_direct_query(string.format("SELECT addon_type FROM player.item_proto WHERE vnum = %s;", item.vnum)) if tonumber(item_table[1].addon_type) == -1 then item_editor_camilo_lib.switchingMeanSkill() else syschat("Este objeto no se puede editar.") end --lo que esté de aquí para abajo se ejecutaría end y también: quest item_editor_camilo begin state start begin when 11000.take or 11002.take or 11004.take begin item_editor_camilo_lib.startItemEditor() --se ejecutaría de aquí para bajo si hubiese algo... end end end Así que pensemos esto: si retornamos en una función, se devolverá un dato. Si retornamos en un bloque when, se detendrá la quest. Ahora vamos a meter la segunda parte de nuestra función: item_editor_camilo_lib.switchingMeanSkill = function() say_title_center("Editor de ítems By Camilo[ENTER]") say_item_vnum(item.vnum) say("Ingresa la media que quieres sacar") local mean_chosen = tonumber(input()) if mean_chosen == nil or mean_chosen < 0 or math.mod(mean_chosen, 1) != 0 then return end say_title_center("Editor de ítems By Camilo[ENTER]") say_item_vnum(item.vnum) say("Ingresa la habilidad que quieres sacar") local skill_chosen = tonumber(input()) if skill_chosen == nil or skill_chosen < 0 or math.mod(skill_chosen, 1) != 0 then return end --Lo nuevo agregado de aquí para abajo local switch_max_amount = 1000000 local mean_calculated, skill_calculated, switchs = get_mean_skill(mean_chosen, skill_chosen, switch_max_amount) if switchs <= switch_max_amount then if mean_calculated == 0 then item.set_value(0, 71, skill_calculated) item_editor_camilo_lib.addAttr(4) elseif skill_calculated == 0 then item.set_value(0, 72, mean_calculated) item_editor_camilo_lib.addAttr(4) else item.set_value(0, 72, mean_calculated) item.set_value(1, 71, skill_calculated) item_editor_camilo_lib.addAttr(3) end else syschat("No dopó. Inténtalo de nuevo") end end La función get_mean_skill es de source y la he creado a partir del source game en item_addon.cpp. Ese archivo define la distribución de la media y la habilidad y devuelve la media y habilidad que haya salido. En los archivos adjuntos estará la función get_mean_skill que debes agregarla al source game en questlua_global.cpp. Toda función que no tenga prefijo o no haga parte de una agrupación debe ir en questlua_global. Si fuera pc.get algo, iría en questlua_pc.cpp. Si fuera item.set algo, iría en questlua_item.cpp. Entiendes el patrón? Nota: debes fijarte cómo se declara la función, si es con ALUA o con int; siempre sigue el patrón... Aparte de agregarla en source game, debes agregar el nombre en quest_functions. Voy a mostrarles un poco la función. No hay que programar nada, solo explico un poco: Parte de questlua_global.cpp ALUA(_get_mean_skill) { if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3)) { return 0; } int mean = (int)lua_tonumber(L, 1); int skill = (int)lua_tonumber(L, 2); int switch_max = (int)lua_tonumber(L, 3); int iSkillBonus = 0; int iNormalHitBonus = 0; int switching = 0; for (int i = 0; i < switch_max; i++) { iSkillBonus = MINMAX(-30, (int)(gauss_random(0, 5) + 0.5f), 30); iNormalHitBonus = 0; if (abs(iSkillBonus) <= 20) iNormalHitBonus = -2 * iSkillBonus + abs(number(-8, 8) + number(-8, 8)) + number(1, 4); else iNormalHitBonus = -2 * iSkillBonus + number(1, 5); switching = i+1; if (iNormalHitBonus >= mean || iSkillBonus >= skill) break; } lua_pushnumber(L, iNormalHitBonus); lua_pushnumber(L, iSkillBonus); lua_pushnumber(L, switching); return 3; } Si no ponemos un límite en cambios, se iría a ciclo infinito y se paraliza el juego si una persona pone una media inexistente, por ejemplo 61 o mayor. switch_max es el máximo de cambios que le pondremos a la función. Es el 3° parámetro de la función. La función recibe tres parámetros, la media mínima que debe salir, la habilidad mínima que debe salir y la cantidad máxima de cambios que debe hacer el ciclo. Lo que está dentro del ciclo es muy parecido a la función de addon_type.cpp. La diferencia es que cuento cada cambio y rompo el ciclo cuando me salga la media o habilidad. El retorno se hace así como lo puse allí, con los lua_push y la cantidad de valores retornados los debo poner en al final, en este caso return 3. Devuelvo la media que salió, la habilidad que salió y la cantidad de veces que se hizo. La cantidad de veces serían los cambios. Recuerden que hay una línea que se debe agregar en ese mismo archivo para que pueda funcionar. Esto está en el archivo adjunto. Muchos me preguntan que cuál es la máxima media y la máxima habilidad. Esta función se probó millones de veces; la media nunca pasó de 60, y la habilidad nunca pasó de 27. En la tercera variable puse switchs, esos son los cambios que se hicieron. Ahora le digo al sistema que si son menores o iguales a la cantidad de cambios máximos, entonces siga. Pero si no, que diga que no dopó. Como máxima cantidad de cambios que hará el source pongo 1000000, es un buen valor. ¿Qué sucede si pongo 10 millones, por ejemplo? El servidor mientras realiza ese proceso le dará prioridad y el juego se quedará quieto. En cambio si pongo 1000000 demorará milisegundos y pasará desapercibido. Variables globales vs locales: Las variables locales mueren cuando se termina un bloque en la jerarquía del when o function. Mueren quiero decir que se vacían, o sea que no se podrán usar luego en otros bloques de esa jerarquía. Las variables globales reservan los datos y cada vez que haces rel q se cargan de nuevo. Si vas a usar una variable global te debes asegurar de que no se interfiera con otra con el mismo nombre. Cuando uses variables locales asegúrate que solo las vayas a usar en ese bloque. No dejes variables con valores almacenados y no los vayas a usar, no llenes el sv de basura. Las variables globales se crean normal, solo no hay que poner el "local". Pondré el max_switch_amount como variable global en el archivo adjunto para que puedan ver. Ahora tengo que validar si la media es 0 o la habilidad es 0 porque en los bonus no se muestra cuando un bonus tiene valor 0. Esta parte es fácil, si alguno de los dos es 0, entonces los bonus que agrego sería 4. Si los dos bonus son diferentes de 0, entonces agrego 3 bonus más. Esto es para que me queden los 5 bonus. La parte del item.set_value. Esta función es de source game. Aparece en questlua_item.cpp. Funciona así: item.set_value(num_bonus, id_bonus, valor_bonus) Al ítem seleccionado (sabemos que el NPC lo seleccionó y cada vez que hagas item. algo, hará algo con ese ítem) se le asignará un bonus. num_bonus me refiero a los números entre 0 y 4 de los bonus. Siendo 0 el primer bonus, me refiero al orden. id_bonus es el bonus número de bonus que aparece declarado en source game en constants.cpp. Si vas allí, busca: const TApplyInfo aApplyInfo[MAX_APPLY_NUM] = En esa tabla aparecerán los nombres de los bonus, antecedidos por POINT_, y en comentario aparece el id. Para el 71 es SKILL_DAMAGE_BONUS. Para el 72 es NORMAL_HIT_DAMAGE_BONUS. En el questlib.lua aparecen los bonus pero están incompletos porque hace años no se toca la tabla apply. Por eso en los archivos adjuntos voy a agregar los demás bonus que salen en los objetos. item_editor_camilo_lib.addAttr(4) Esta función sería la que agregaría esos siguientes bonus. Para que no se vea abrumador, no pondré la función completa. La pondré por partes. item_editor_camilo_lib.addAttr = function(bonus_count) available_attr = item_editor_camilo_lib.getAvailableAttr() end Esta nueva función es la de agregar bonus. Ese Attr significa Attribute o atributos. El parámetro que recibe se llama bonus_count y es el que nos va a decir cuántos bonus se le van a agregar al ítem. Para agregar esos bonus primero necesitamos saber cuáles son los bonus que podemos agregar al objeto porque no podemos meter HP en una arma, por ejemplo. Creamos la función getAvailableAttr() item_editor_camilo_lib.getAvailableAttr = function() local id_types_base = item_editor_camilo_lib.getApplyTypeBase() local r, item_attr_table = mysql_direct_query(string.format("SELECT apply as name_apply, prob, lv1, lv2, lv3, lv4, lv5 FROM player.item_attr WHERE %s > 0;", item_editor_camilo_lib.getNameSubtype(item.type, item.sub_type))) local available_attr = {} for i = 1, r do local name_apply = item_attr_table[i].name_apply local id_apply = apply[name_apply] if not item_editor_camilo_lib.isAttrIncluded(id_apply, id_types_base) then table.insert(available_attr, {}) local current_pos = table.getn(available_attr) available_attr[current_pos][0] = id_apply available_attr[current_pos][1] = name_apply available_attr[current_pos][2] = apply_human[id_apply][2] available_attr[current_pos][3] = {} for j = 1, 5 do available_attr[current_pos][3][j] = item_attr_table[i]["lv"..j] end available_attr[current_pos][4] = item_attr_table[i].prob end end return available_attr end Esto se ve complejo pero vamos a desglosarlo. Necesitamos quitar de los bonus disponibles (aquellos que tienen un 5 en la tabla player.item_attr en la db según el tipo de ítem) los que ya tiene de base el ítem. Si el armadura tiene HP de base, no dejaremos que salga HP en bonus, por ejemplo. La idea es guardar esos types de bonus (o sea, el id) en una tabla para luego comparar y quitarlos de la tabla de disponibles. Creamos la función: item_editor_camilo_lib.getApplyTypeBase = function() local _, apply_type_base = mysql_direct_query(string.format("SELECT applytype0, applytype1, applytype2 FROM player.item_proto WHERE vnum = %s;", tonumber(item.vnum))) local id_types_base = {} for i = 0, 2 do if tonumber(apply_type_base[1]["applytype"..i]) > 0 then table.insert(id_types_base, apply_type_base[1]["applytype"..i]) end end return id_types_base end La query lo que hace es recoger los type que están en player.item_proto de ese objeto. No hay nada raro ahí. Les mostraré en el caso de un Brazalete de plata +9 para que el ejemplo sirva. Ya que este brazalete tiene HP de base y en bonus también tiene disponible el HP. La tabla arroja el type 7 que es velocidad de ataque y el 1 que es HP. Recuerda revisar constants.cpp para ver los ids o types de los bonus. Ahora viene la parte de meter una variable en la tabla que llegó de la query. No podía poner esto: apply_type_base[1].applytype..i Aunque pensarás, bueno, podríamos hacer esto: apply_type_base[1].applytype0 apply_type_base[1].applytype1 apply_type_base[1].applytype2 Pero tendríamos que hacer tres if evaluando si el número es mayor a 0 porque sería inútil llenar la tabla de ceros. Finalmente nos queda: apply_type_base[1]["applytype"..i] Lo demás ya lo expliqué más arriba. Bien, para insertar en una tabla debemos poner table.insert(nombre_tabla, valor_para_meter) Cuando insertamos en una tabla, se agrega un nuevo valor en la tabla, o sea, si está vacía, se agregará el valor en el primer índice. En lua los índices empiezan en 1. Si hacemos 3 inserts, la tabla queda así: [1] = valor1, [2] = valor2, [3] = valor3 Retornamos la tabla. La idea de retornar las tablas es que se puedan reutilizar en otras ocasiones. Ya si quisieras crear otra quest donde necesites obtener los bonus base del equipamento, solo llamas a la función y ya. local r, item_attr_table = mysql_direct_query(string.format("SELECT apply as name_apply, lv1, lv2, lv3, lv4, lv5 FROM player.item_attr WHERE %s > 0;", item_editor_camilo_lib.getNameSubtype(item.type, item.sub_type))) Vamos por esta parte: Esta query es la tabla player.item_attr pero lo que nos importa es sacar los bonus de cierto tipo de equipo. Es decir, arma, armadura, ciertos acc, etc. Revisa la tabla player.item_proto, vas a ver que hay columnas que representan el tipo de equipamento, y en esa columna donde esté el 5 es porque admite el respectivo bonus. Ejemplo para armas, aquí quiere decir que en las armas sale CON (vit), INT, STR, DEX, CAST_SPEED (velocidad de hechizo) y así... La prob no la tendremos en cuenta que que este es un editor, no un dopador. Sobre los lvs: En el source game item_attribute.cpp está la lógica de cómo funciona esto, pero no lo voy a explicar ahora porque esto es un editor, solo mostraremos los 5 valores y el que se seleccione, ese queda. En la query le estamos diciendo al sistema que seleccione la columna apply. ¿Qué es ese "as"? "as" significa "como". Seleccione "apply" como "name_apply", es decir, le estoy cambiando el nombre. ¿Por qué hago esto? porque si no lo hago, habrá redundancia con otro apply (una tabla) que está en el questlib que utilizaremos ahorita. Lo explico mejor: Ya sabemos que hay una columna llamada apply, que termina siendo una tabla aquí en lua. También sabemos que hay una tabla llamada apply que está en questlib.lua y que contiene los nombres de los bonus con sus ids. Al llamar a apply, el sistema a cuál de los dos tomará? ... Eso se llama redundancia. Seguro el apply nuevo de la query reemplaza al que ya teníamos, y perderíamos los datos. Ahora sí, en la query le decimos al sistema que seleccione apply con el nuevo nombre de name_apply, además seleccione la columna lv1, lv2... lv5 de la tabla player.item_attr donde [tipo de equipamento] sea mayor a 0. Cuando obtengamos la tabla de esa consulta, la columna no será apply sino name_apply. Al ejecutar la query, poniendo en %s weapon, obtenemos: Ahora se va viendo la forma. En el %s va el tipo de equipamento. Revisa en player.item_attr las columnas. En mi caso tengo weapon (arma), wrist (brazalete), foots (zapatos), neck (collar), head (casco), shield (escudo), ear (pendientes), pendant (talismán) y glove (guantes). Vayamos a esta parte de esa línea: item_editor_camilo_lib.getNameSubtype(item.type, item.sub_type) Aquí debemos crear una función donde obtengamos ese tipo de equipamento. Ya sabemos que lo obtendremos dando el type y el subtype. Si es un arma, el type es 1. Si es cualquier otro equipamento el type es 2. Dentro del type 2 hay varios subtypes que determina si es brazalete, zapatos, casco, etc. item_editor_camilo_lib.getNameSubtype = function(attr_type, attr_subtype) if attr_type == 1 then return "weapon" elseif attr_type == 2 then local name_subtypes = { [0] = "body", [1] = "head", [2] = "shield", [3] = "wrist", [4] = "foots", [5] = "neck", [6] = "ear", [7] = "pendant", [8] = "glove" } return name_subtypes[attr_subtype] end end Esta estructura hasta el día de hoy es la más eficiente para mí. Diferencia entre hacer varios if y hacer if, elseif, else: Si pongo varios if, se ejecutarán todos. Si pongo un if con varios elseif, se ejecutará solo el que cumpla, en orden. Por lo tanto, el programa haría menos instrucciones si lo haces con elseif, para este caso. Ejemplo con solo if: local level = pc.level if level > 15 then chat("Soy mayor que 15") end if level > 30 then chat("Soy mayor que 30") end if level > 50 then chat("Soy mayor que 50") end Va a mostrar: Soy mayor que 15 Soy mayor que 30 Soy mayor que 50 Dado que es la misma variable, conviene usar if, elseif, end para que muestre solo un resultado, así: local level = pc.level if level > 15 then chat("Soy mayor que 15") elseif level > 30 then chat("Soy mayor que 30") elseif level > 50 then chat("Soy mayor que 50") end Si eres nivel 51, de todas maneras te dirá Soy mayor que 15 pero en este caso de niveles es mejor hacerlo desde el más alto hasta el más bajo: local level = pc.level if level > 50 then chat("Soy mayor que 50") elseif level > 30 then chat("Soy mayor que 30") elseif level > 15 then chat("Soy mayor que 15") end Soy mayor que 50 Ahora sí. Aunque en nuestro caso da igual porque retornamos directamente el valor en cada if o elseif, pero se ve mejor programado. Hay veces que es necesario utilizar la estructura [clave] = valor como en este caso con los subtypes. local name_subtypes = { [0] = "body", [1] = "head", [2] = "shield", [3] = "wrist", [4] = "foots", [5] = "neck", [6] = "ear", [7] = "pendant", [8] = "glove" } Esto es mejor que hacer esto: local name_subtypes = { {0, "body"}, {1, "head"}, {2, "shield"}, {3, "wrist"}, {4, "foots"}, {5, "neck"}, {6, "ear"}, {7, "pendant"}, {8, "glove"} } ¿Por qué? porque para obtener el nombre del subtipo es mejor hacerlo en una línea que hacerlo en un ciclo. En la primera parte sería muy fácil, solo haces esto: name_subtypes[attr_subtype] En la segunda parte tendrías que hacer esto: for i = 0, table.getn(name_subtypes) do if attr_subtype == name_subtypes[i] then return attr_subtype end end La razón es porque, si le tienes un id a cada objeto de tu tabla, solo accedes a él normalmente con tabla[id]. En cambio si no tienes un id para tus objetos en la tabla, tienes que recorrerla y comparar hasta encontrar la coincidencia. Dejo aquí el croquis de donde vamos: item_editor_camilo_lib.getAvailableAttr = function() local id_types_base = item_editor_camilo_lib.getApplyTypeBase() local r, item_attr_table = mysql_direct_query(string.format("SELECT apply as name_apply, prob, lv1, lv2, lv3, lv4, lv5 FROM player.item_attr WHERE %s > 0;", item_editor_camilo_lib.getNameSubtype(item.type, item.sub_type))) --Vamos aquí local available_attr = {} for i = 1, r do local name_apply = item_attr_table[i].name_apply local id_apply = apply[name_apply] if not item_editor_camilo_lib.isAttrIncluded(id_apply, id_types_base) then table.insert(available_attr, {}) local current_pos = table.getn(available_attr) available_attr[current_pos][0] = id_apply available_attr[current_pos][1] = name_apply available_attr[current_pos][2] = apply_human[id_apply][2] available_attr[current_pos][3] = {} for j = 1, 5 do available_attr[current_pos][3][j] = item_attr_table[i]["lv"..j] end available_attr[current_pos][4] = item_attr_table[i].prob end end return available_attr end Con la tabla de los tipos de bonus base y los tipos de bonus que pueden salir por cada equipo, sacaremos los tipos de bonus disponibles en nuestro ítem y los meteremos en una tabla. Vamos de 1 hasta r (recuerden que es la cantidad de registros que arroja la tabla de la query) e insertaremos en orden los bonus así: Posición 0: el id, es decir, el número que aparece en constants.cpp Posición 1: el nombre del apply, o sea, el nombre en mayúscula en inglés separado de guiones bajos que está en constants.cpp Posición 2: el nombre que se mostrará a la persona en el juego, yo lo llamo humano, o sea, leíble para los humanos. Posición 3: los valores lv1 hasta 5 Para el nombre es muy fácil: local name_apply = item_attr_table[i].name_apply Yo lo expliqué cuando había un solo registro, que era poniendo [1]. Para cuando son varios registros estamos usando este ciclo for con la variable de control i. Para el id_apply: ahora sí iremos a la tabla apply del questlib.lua. Si la revisas, te fijas que en las [clave] = valor, en clave está el nombre del tipo de bonus que está en constants.cpp Solo accediendo a la posición del bonus obtendremos el id, es muy sencillo. Lo importante de este ciclo es filtrar esos tipos de bonus base, así que debemos identificarlos. if not item_editor_camilo_lib.isAttrIncluded(id_apply, apply_type_base) then Cada iteración del ciclo hará referencia a un bonus. En cada bonus evalúa si el ítem ya lo tiene incluido o no en el item_proto. Literalmente dice: si no está el atributo incluido, entonces En los parámetros pongo el id_apply y id_types_base porque voy a tomar el bonus actual y lo voy a comparar con los bonus de esa tabla; si lo encuentra entonces retornará true. Si no, entonces hay que agregarlo a nuestra tabla de disponibles. Veamos cómo se hace eso: item_editor_camilo_lib.isAttrIncluded = function(id_apply, table_attr_base) for _, id in pairs(table_attr_base) do if tonumber(id) == tonumber(id_apply) then return true end end return false end Creamos la función isAttrIncluded. Uso del pairs: En los ciclos no siempre se recorre desde un valor hasta cierto otro valor contando de 1 en 1 o de 2 en 2. A veces necesitamos recorrer cada elemento existente. Digamos que tenemos una tabla así: local tabla = { [1] = "uno", [2] = "dos", [3] = "tres", ["uno"] = "UNO", [100] = "cien" } Si ponemos un for así: for i = 1, table.getn(tabla) do --pues tiene 5 elementos chat(tabla[i]) end La salida sería: uno dos tres --aquí daría error, no existe la posición 4 --aquí error, no existe la posición 5 Faltaría el UNO y el cien además. Para mostrar solo los números haríamos esto: for k, v in ipairs(tabla) do --key: value --clave: valor chat(k..": "..v) end La salida sería: 1: uno 2: dos 3: tres 100: cien Así es como funciona el ipairs. Ahora falta otro, el pairs. for k, v in pairs(tabla) do --key: value --clave: valor chat(k..": "..v) end La salida sería: 1: uno 2: dos 3: tres uno: UNO 100: cien Ven la diferencia? realmente no sé si el orden sea ese pero lo importante es que pairs muestra tanto posiciones con texto como números, y el ipairs muestra solo posiciones de números. Yo puse pairs en la quest pero da igual porque no hay posiciones en texto. Cuando pongo if alguna_variable then, estoy diciendo que si la variable es nula o tiene algo que no sea false, entrará al if. La única razón para que no entre es que el valor de la variable sea false. if not item_editor_camilo_lib.isAttrIncluded(id_apply, id_types_base) then Si el actual bonus del ciclo no se encontró en la tabla de bonus base del objeto, entonces retona false y entrará al if. La estructura de mi tabla de disponibles será así: [1] = { --ese 1 es la posición que se agrega automáticamente por hacer el table.insert. [0] = id_apply, --ej: el 1 [1] = name_apply, --el nombre del POINT, el que está en constants.cpp, ej: "MAX_HP" [2] = nombre_humano, --quiero decir, el bonus que lo pueda leer un humano y lo entienda, ej: "Máx. HP" [3] = { [1] = value_bonus1, --estos son los levels de player.item_attr [2] = value_bonus2, [3] = value_bonus3, [4] = value_bonus4, [5] = value_bonus5 } } Lo hago poniendo el index en orden porque será útil más adelante. Se preguntarán que por qué no pongo simplemente: available_attr[i][0] = id_apply, por ejemplo. Pues intentaré explicarlo como pueda. Como hay un if que me restringe algunos bonus, si yo pongo un if, mi tabla ya no estará en orden. --vamos con la i = 1 --este bonus no lo tiene, ok, agregado [1] = {--[[todo lo del bonus aquí--]]}, -- i = 2 --este bonus no lo tiene, ok, agregado [2] = {--[[todo lo del bonus aquí--]]}, -- i = 3 --este bonus sí está en la tabla, no entra al if, no se agrega -- i = 4 --este bonus no lo tiene, ok, agregado [4] = {--[[todo lo del bonus aquí--]]}, --El orden quedó 1, 2, 4... y no nos servirá para nuestro ciclo de más adelante En vez de la i puse que se insertara la nueva tabla en la última posición y fue sencillo: table.insert(available_attr, {}) local current_pos = table.getn(available_attr) available_attr[current_pos][0] = id_apply Obtengo el tamaño de mi tabla, y obviamente ese tamaño será la última posición -1. Ahora la parte del apply_human. ¿Qué significa eso? available_attr[current_pos][2] = apply_human[id_apply][2] Es una tabla global (creada en el questlib.lua) hecha por mí y adjunta abajo al final, en donde defino nuevamente los bonus del apply pero en una estructura diferente y con los nombres que aparecen en el locale o muy parecidos. Es decir, en vez de que salga "MAX_HP", que salga "Máx. HP". Otro ejemplo, en vez de que salga "CRITICAL_PCT", salga "Probabilidad de golpes críticos +x%". El % también lo incluyo. Estos textos no aparecen en los bonus del ítem obviamente, recuerden que esto es quest, solo aparecerá en la pantalla negra en los botones. Se puede crear una tabla dentro de otra. En cada bonus creé esa estructura que puse arriba. También hice una nueva para los levels, ya que son 5, y son en orden, la mejor manera era crear una tabla. Ya tenemos los bonus disponibles. Ahora sí volvamos a addAttr item_editor_camilo_lib.addAttr = function(bonus_count) available_attr = item_editor_camilo_lib.getAvailableAttr() item_editor_camilo_lib.makeSelectUI() --vamos a crear ahora el select end He creado la variable available_attr como global ya que la usaré en otra función sin tener que enviarla por parámetro. Crearemos el select_table. La diferencia entre un select y un select_table es que en el select envías los textos separados por coma, mientras que en el select_table envías una tabla. Las tablas son dinámicas. --Si quisieras poner los números del 1 al 5 en texto en un select, lo harías así: local sel = select("Uno", "Dos", "Tres", "Cuatro", "Cinco") --Podría cambiar los números sin problema al ponerlo en variables local num1, num2, num3, num4, num5 = "uno", "dos", "tres", "cuatro", "cinco" local sel = select(num1, num2, num3, num4, num5) --Pero si quisiera variar la cantidad de textos que hay en el select, ahí ya no se puede. Si solo necesito 3 textos, qué hago con los otros dos que me sobran? --A ese problema nos enfrentamos, pues cada equipamento tiene distintas cantidades de tipos de bonus que pueden salir. Unos tienen 14, otros 15, otros 16, etc. --La solución es crear una tabla y usar select_table. local tabla = {"uno", "dos", "tres", "cuatro", "cinco"} local sel = select_table(tabla) --Ahora quiero dejar solo tres números. local tabla = {"uno", "dos", "tres"} --ya sea que lo haya cambiado con un insert o asignaciones, lo que sea. local sel = select_table(tabla) Ahora sí vamos con el makeSelectUI item_editor_camilo_lib.makeSelectUI = function() type_select_ui, value_select_ui = {}, {} for i = 1, table.getn(available_attr) do type_select_ui[i] = available_attr[i][2] for j = 1, table.getn(available_attr[i][3]) do table.insert(value_select_ui, {}) value_select_ui[i][j] = available_attr[i][3][j] end table.insert(value_select_ui[i], "Volver") end table.insert(type_select_ui, "Cerrar") end Creamos dos tablas, una para los select_table de los nombres en humano de los bonus y otra para los valores en el select_table. Aquí no hay mucha ciencia, solo tomamos el available_attr que es global, y agarramos la pos 2 que es el nombre humano y la pos 3 que son los valores de ese bonus. Hay que darle un valor añadido y es, una vez llenado los valores de la página del bonus, agregarle el botón de "Volver". También una vez agregados todos los bonus, agregar el botón de "Cerrar". Vamos a la última parte: item_editor_camilo_lib.addAttr = function(bonus_count) available_attr = item_editor_camilo_lib.getAvailableAttr() item_editor_camilo_lib.makeSelectUI() --Vamos de aquí para abajo. La función anterior no retorna porque las tablas las dejé globales. local type_saved, value_saved = {}, {} local type_support = {} for i = 1, table.getn(available_attr) do type_support[i] = available_attr[i][0] end table.insert(type_support, "") for i = 6-bonus_count, 5 do say_title(string.format("Elige el %s° bonus", i)) local sel_type = select_table(type_select_ui) if sel_type != table.getn(type_select_ui) then say_title("Elige el valor") local sel_value = select_table(value_select_ui[sel_type]) if sel_value != 6 then table.insert(type_saved, type_support[sel_type]) table.insert(value_saved, value_select_ui[sel_type][sel_value]) table.remove(type_select_ui, sel_type) table.remove(value_select_ui, sel_type) table.remove(type_support, sel_type) else i = i - 1 end else return end end local count = 1 for i = math.abs(bonus_count-5), 4 do item.set_value(i, type_saved[count], value_saved[count]) count = count + 1 end syschat("Hecho!") end Esas tablas type_saved y value_saved son los bonus y los valores del bonus que selecciona la persona. Saved significa guardados o salvados. local type_support = {} for i = 1, table.getn(available_attr) do type_support[i] = available_attr[i][0] end table.insert(type_support, "") Creé una tabla de soporte simplemente por cuestiones de conversión. Necesitaba obtener el id del bonus pero el type_select me da el nombre humano, y el select_table me da la pos de ese nombre humano pero no el id del bonus. Así que hice una tabla del mismo tamaño del select porque también necesitaba eliminarla para cuando la persona seleccionara un bonus. Me explico, cada vez que la persona elije un bonus hay que quitarlo para que no aparezca en las demás páginas porque no se debe repetir. Es por eso que más abajo aparece table.remove. La tabla de soporte tiene esta estructura: type_support = { [1] = 1, --id del bonus [2] = 2, ---... --no siempre será en orden, ya que hay bonus que se saltan } Puse: table.insert(type_saved, type_support[sel_type]) y no: table.insert(type_saved, available_attr[sel_type][0]) porque available_attr como es global, no puedo eliminarle bonus porque puede que interfiera con otro personaje. Así que creo la de soporte para que se le pueda eliminar los bonus. En verdad puse variables globales en varias partes cuando no debería porque era para explicar lo de global y local. Lo explicaré mejor. Si es una variable que cambiará por cada personaje, entonces ponla local. Si la variable se usará por otros personajes , por ejemplo en un evento, entonces ponla global. Explico de nuevo. En este caso el available_attr que lo había puesto global, imagina que dos personas estén usando al tiempo el editor. Puede llegar un momento en que al crear la tabla support de alguno de los dos, tome el available_attr de la otra persona y sean diferentes. En el archivo adjunto no habrá variables globales a excepción de switch_max_amount ya que ese valor no cambia. La llamaría una constante. En esta parte: for i = 6-bonus_count, 5 do es muy sencillo, tienes que tener en cuenta que como esto es la interfaz, esto será equivalente a las páginas de bonus que le mostrarás al personaje. Si hubieras puesto: bonus_count = 5 -> 6-5 = 1 hasta 5. Página 1 hasta la 5 bonus_count = 4 -> 6-4 = 2 hasta 5. Página 2 hasta la 5. Esto en caso de que hubiera salido algún valor 0 de media o habilidad. bonus_count = 3 -> 6-3 = 3 hasta 5. Página 3 hasta la 5. Esto en caso de que salga valores de media y habilidad diferentes de 0. Para cuando le dan al último botón siempre uso mi viejo truco de poner: if sel != table.getn(tabla) then return end En este caso en el sel_type pongo que si le da a "Cerrar", cancele toda la operación. En el caso del sel_value pongo diferente de 6 porque siempre los levels van a ser 5, y el botón 6 es "Volver", así que le resto 1 a mi variable de control para que me devuelva a la página anterior. Ya en la última parte es necesario hacer un truco para sincronizar la cantidad de bonus con las posiciones donde se van a poner. for i = math.abs(bonus_count-5), 4 do Si bonus_count = 5 -> de 0 hasta el 4. Ahí están los 5 bonus. Si bonus_count = 4 -> de 1 hasta el 4. Como ya tiene un valor de media o habilidad diferente de 0, se agregarán los bonus del segundo al quinto. Si bonus_count = 3 -> de 2 hasta el 4. La media y habilidad es diferente de 0, ya tiene dos bonus, se agregan los otros 3. Ese math.abs es una función que ya viene con lua y en todos los lenguajes de programación. En matemáticas el absoluto es volver positivo todo valor. Si no hubiera puesto el absoluto, miren lo que hubiera ocurrido: Si bonus_count = 5 -> de 0 hasta el 4. Aquí está bien porque da 0 Si bonus_count = 4 -> de -1 hasta el 4. Del -1 al 4 hay 6, serían 6 bonus. Si bonus_count = 3 -> de -2 hasta el 4. Del -2 al 4 hay 7, serían 7 bonus. En esta línea: item.set_value(i, type_saved[count], value_saved[count]) Podemos ver que la i debe empezar en 0, y las tablas type_saved y value_saved en 1. Ya saben por qué. Con ese local count = 1 lo hago. Y lo aumento en 1 en cada iteración y ya está. La estructura de type_saved es: type_saved = {1, 18, 17, 16, 22} La estructura de value_saved es: value_saved = {2000, 10, 10, 10, 20} Donde el 1 corresponde con el 2000, el 18 con el 10 y así. Al final pongo "Hecho!" para comprobar que se terminó el editor. Los resultados son los mismos que en el post original del editor. (dejo todo en el adjunto) Muchos éxitos para todos! Post original de la quest: item_editor_camilo.rar
  8. ¡Hola a todos! Esta es una guía para crear quest como un profesional. ¿Has editado una quest desde bloc de notas y parece muy difícil? realmente lo es. De hecho, yo no puedo hacer quest ahí ¿Has editado quest por Notepad++ y parece muy difícil? al principio cuando era nuevo y no configuraba Notepad++ sí. Ahora que tengo experiencia puedo decirte que existen ciertas configuraciones y reglas para facilitar la programación de quests. Esta guía tiene el objetivo de facilitar la programación de quests, a tal nivel que, ahorrarás bastante tiempo haciéndolas sin cometer errores porque los verás al instante. Empecemos: 1. Configurar Notepad++ Abre Notepad++ y haz lo siguiente: 1.1 Haremos que al darle a Nuevo, nos cree un archivo con la codificación correcta (ANSI) para que el servidor lea las tildes. Además, el nuevo archivo tendrá por defecto el lenguaje Lua donde resaltará las palabras reservadas (como if, for, etc) Procedimiento: Configuración -> Preferencias -> Archivo nuevo -> Codificación: ANSI, Lenguaje: Lua 1.2 Dejaremos por defecto el tamaño del tabulador en 4. Procedimiento: Configuración -> Preferencias -> Lenguaje -> Tamaño: 4 1.3 Para que se vean los tabuladores y espacios. Ver los espacios nos ayuda a diferenciar de los tabuladores cuando son muy reducidos. Los tabuladores nos ayudarán a indentar el código (ya lo veremos más adelante) Procedimiento: Vista -> Mostrar símbolo -> Mostrar espacios y tabulaciones Finalmente verás los espacios y tabuladores así: 1.4 Crearemos una extensión del lenguaje Lua. Como sabemos, el lenguaje Quest es un derivado del lenguaje Lua y cuando abrimos un archivo .quest no lo va a reconocer como Lua. Procedimiento: Configuración -> Configurador de estilo... -> Lenguaje: Lua -> Ext. Usuario: quest Nota: no cierres la ventana aún 1.5 Vamos a agregar la palabra reservada with a nuestro lenguaje quest. La palabra with está dentro de los conjuntos if, else, elseif, then, etc. Solo que esas están en Lua, y with está en quest porque se usa en el bloque when. Lo que hacemos es meter los conjuntos de palabras reservadas del mismo tipo de Lua en quest. Procedimiento: Configuración -> Configurador de estilo... -> Lenguaje: Lua -> Estilo: INSTRUCTION WORD: with, Tipo de fuente: Negrita 1.6 Vamos a hacer lo mismo que el paso anterior pero con las funciones. Procedimiento: Ir al FTP ir a la carpeta quest y copiar el contenido que tengas en quest_functions. Luego: Configuración -> Configurador de estilo... -> Lenguaje: Lua -> Estilo: FUNC1: (pegar todas las funciones de quest_functions), Tipo de fuente: Negrita 1.7 Haremos lo mismo pero con las palabras reservadas de quest. Procedimiento: Configuración -> Configurador de estilo... -> Estilo: FUNC2: quest begin state end when, Tipo de fuente: Negrita 2. Hacer una quest profesional probando este entorno de desarrollo mejorado Cuando hablo de indentar me refiero a jerarquizar el código. Es decir, un programa quest tiene una estructura de bloques. El bloque más poderoso, más sobresaliente, es el bloque quest. Bloque quest: Pero necesita un state para funcionar, y además tiene un state que se ejecuta por defecto que es el start. ¿Por qué puse los tabs y por qué ahí? Porque el bloque quest y state no van en la misma jerarquía. Tú no puedes poner primero el state y luego el quest. Quest identifica la quest, y state identifica el estado, o sea, el conjunto de disparadores (when) que afectan al personaje. Si el personaje está en otro state, los when de los otros state no funcionarán. Vamos a crear un when: Cada vez que abrimos un bloque, las siguientes líneas van con un tab nuevo. ¿Cuáles son los bloques? aquí los dejo a continuación: No es difícil, apenas son el quest, state, when, function (que son típicos de Quest), y los if-elseif, if, for (hay otro que es repeat pero lo omitiré) que son de Lua. Todo bloque termina en end, y ese end va en la misma jerarquía que el bloque obviamente. ¿Por qué indentar? Después de ver tantas quest me di cuenta que podías buscar un error durante 4 horas y no encontrarlo cuando era un end que faltaba o que estaba de más. Indentar las quest te hace tener el código más ordenado y así vas a tener éxito haciendo esto, lo digo por experiencia. Tips que nadie te los dirá: - El bloque function puede ir en cualquier state y lo puedes llamar sin problema. Si function está en state start y el personaje está en state run, puedes llamarlo desde run y funciona. - Regla de los say, select: no debes tener ciertos caracteres especiales como tildes como último caracter y tampoco te pases de 49 caracteres (50 ya te da un salto de línea). - Regla del with. El with sirve para separar las instrucciones de disparadores de las otras. Es decir, when login or levelup with pc.level > 10 begin, lo que está antes del with son disparadores (es decir, se activan una vez suceda) y se ponen antes del with. Nunca va un and porque es imposible que sucedan dos disparadores al mismo tiempo. Luego lo que va después del with son instrucciones en Lua normales, allí puedes jugar con las funciones sin problema. - Abreviaciones básicas: en la parte anterior puse pc.level, esto es porque en el questlib.lua hay unas asignaciones. Puedes ver que pc.level = pc.get_level(), lo cual quiere decir que cuando pones pc.level hará referencia a lo que está después del igual. Y así con otras funciones. Puedes probar tu nuevo entorno en Notepad++ y estarás confiado si te quedó bien una función si te aparece de color morado. Esto fue todo por hoy. Ofrezco mis servicios de quest/lua y tengo una buena promoción de una misión de caza súper buena con un buscador de ítems en cofres :3 [Hidden Content] ¡Que tengan muchos éxitos!
  9. ¡Hola mi gente! Soy yo de nuevo [ADM]Dark me pidió que le arreglara un bug y casualmente en mi servidor tenía ese mismo bug, así que le di manos a la obra y lo he logrado resolver. Voy a ayudarles a reparar la boda de sus servidores. Quest: Había un problema hablando con la anciana y era que en el when había una función que no tenemos. Así que debes cambiar pc.is_engage_or_married() por (pc.is_engage() or pc.is_married()). Actualmente está así: when oldwoman.chat.gameforge.marriage_manage._10_npcChat with not pc.is_engaged_or_married() begin Y debe quedar más o menos así: when oldwoman.chat.gameforge.marriage_manage._10_npcChat with not (pc.is_engaged() or pc.is_married()) begin En tu quest probablemente tengas 9006 en vez de oldwoman, y "Quiero casarme" en vez de chat.gameforge... pero es lo mismo, no te asustes, solo cambia la función que te puse arriba. Source: Es muy probable que tengas un error de nil en la función npc.lock (la quest usa esto para que se bloquee el npc mientras otro pj lo usa). La solución no es quitarlo, por supuesto. Debes ir al source de server y abrir el archivo questlua_npc.cpp. Busca este bloque: int npc_lock(lua_State* L) { CQuestManager& q = CQuestManager::instance(); LPCHARACTER ch = q.GetCurrentCharacterPtr(); LPCHARACTER npc = q.GetCurrentNPCCharacterPtr(); if (!npc || npc->IsPC()) { lua_pushboolean(L, TRUE); return 1; } if (npc->GetQuestNPCID() == 0 || npc->GetQuestNPCID() == ch->GetPlayerID()) { npc->SetQuestNPCID(ch->GetPlayerID()); lua_pushboolean(L, TRUE); } else { lua_pushboolean(L, FALSE); } return 1; } En donde dice TRUE, cámbialo por true; y donde dice FALSE, cámbialo por false. C++ toma el valor del booleano solo en minúscula. (Tú mismo te darás cuenta por el cambio de color) Debe quedar así: int npc_lock(lua_State* L) { CQuestManager& q = CQuestManager::instance(); LPCHARACTER ch = q.GetCurrentCharacterPtr(); LPCHARACTER npc = q.GetCurrentNPCCharacterPtr(); if (!npc || npc->IsPC()) { lua_pushboolean(L, true); return 1; } if (npc->GetQuestNPCID() == 0 || npc->GetQuestNPCID() == ch->GetPlayerID()) { npc->SetQuestNPCID(ch->GetPlayerID()); lua_pushboolean(L, true); } else { lua_pushboolean(L, false); } return 1; } Cliente: También hubo un problema al hacer el confirm. El confirm es el cuadro que le va a llegar a la novia cuando se le pide el matrimonio y ella elige si aceptar o rechazar. El error es que a una función se le está enviando un parámetro pero debería enviar dos. Ve al root/game.py y busca este bloque: # QUEST_CONFIRM def BINARY_OnQuestConfirm(self, msg, timeout, pid): confirmDialog = uiCommon.QuestionDialogWithTimeLimit() confirmDialog.SetText1(msg) confirmDialog.Open(timeout) confirmDialog.SetAcceptEvent(lambda answer=True, pid=pid: net.SendQuestConfirmPacket(answer, pid) or self.confirmDialog.Hide()) confirmDialog.SetCancelEvent(lambda answer=False, pid=pid: net.SendQuestConfirmPacket(answer, pid) or self.confirmDialog.Hide()) self.confirmDialog = confirmDialog # END_OF_QUEST_CONFIRM El error es que a la función Open (de la clase QuestionDialogWithTimeLimit) recibe solo un parámetro cuando debería recibir dos. Aquí les muestro: def Open(self, msg, timeout): self.SetCenterPosition() self.SetTop() self.Show() self.SetText1(msg) self.endTime = app.GetTime() + timeout uicommon.py Ven que recibe msg y timeout? ok, entonces la solución es: # QUEST_CONFIRM def BINARY_OnQuestConfirm(self, msg, timeout, pid): confirmDialog = uiCommon.QuestionDialogWithTimeLimit() confirmDialog.SetText1(msg) confirmDialog.Open(msg, timeout) #fix bug confirmDialog.SetAcceptEvent(lambda answer=True, pid=pid: net.SendQuestConfirmPacket(answer, pid) or self.confirmDialog.Hide()) confirmDialog.SetCancelEvent(lambda answer=False, pid=pid: net.SendQuestConfirmPacket(answer, pid) or self.confirmDialog.Hide()) self.confirmDialog = confirmDialog # END_OF_QUEST_CONFIRM Agregarle el msg de primero. Y recuerden tener el index del mapa de bodas bien puesto. Foto de prueba para no morir en el olvido: Esto es todo, que disfruten casándose con sus chamanas :v ¡Éxitos para todos!
  10. Hola! ¡Al grano! Crear un archivo llamado borrar.sh y agregar este contenido. #!/bin/sh echo Por favor, introduce nombre archivos quest a borrar sin .quest read NOMBRE find . -type f -name "$NOMBRE*" echo borrar S/N read borrar if [ $borrar = "s" ]; then find . -type f -name "$NOMBRE*" -exec rm -v {} \; fi Después meterlo en la carpeta quest/object Para ejecutarlo debes ir a la consola y poner: cd ruta/quest/object && sh borrar.sh o si ya estás en object solo pones: sh borrar.sh Nota: ese archivo me lo encontré en los files Ethernia, creo. No es mío. Explicación: Si pensabas que eliminar una quest era borrar el archivo de quest pues ¡It's wrong! Si eliminabas la quest desde la carpeta object buscando cada when tal vez se te hacía fácil pero imagínate una quest bien compleja... buscando cada when. Para que tenga sentido los comandos que te doy, voy a explicarte cómo funciona bien el árbol de las quest compiladas Cuando haces ./qc nombre_quest, se crean ciertos archivos en la carpeta quest/object. Míralo de esta forma, quest es un bloque que en jerarquía es el mayor de todos y se almacena en quest/object en varios archivos que mencionaré a continuación. quest nombre_quest begin end Luego el siguiente nivel de jerarquía es state y un archivo se almacena en quest/object/state/nombre_quest quest nombre_quest begin state start begin end end Ese archivo describe los id de cada state (el state start es obligatorio y siempre entra a ese state primero antes que a otro, por lo tanto el id siempre será 0) y cada función. Así para el ejemplo de la quest de arriba: nombre_quest={["start"]=0} Si la quest tiene otro state, se agrega separado por coma y se le asigna un id aleatorio, así: nombre_quest={["start"]=0, ["state_dos"]=12434543, ["state_tres"]=3453434} Si la quest tiene funciones, se agregan así. nombre_quest={["start"]=0, ["state_dos"]=12434543, ["state_tres"]=3453434,nombre_funcion= function() todo_el_contenido_aquí} Después el siguiente nivel de jerarquía es when y cada when se almacena en un archivo diferente en quest/object/ Si el when está llamando a un objeto, entonces se crea una carpeta. Por ejemplo: quest nombre_quest begin state start begin when 101.kill begin end when 20095.chat("Hola") begin end when mi_timer.timer begin end when kill begin end end end Voy a explicar cada caso por cada when en orden Caso 1: se crea el archivo quest/object/101/kill/nombre_quest.start. El kill está llamando al objeto 101 que es el perro salvaje. Caso 2: se crea el archivo quest/object/20095/chat/nombre_quest.start. El chat está llamando al objeto 20095, obviamente chat solo está configurado para mob tipo NPC. Caso 3: se crea el archivo quest/object/mi_timer/timer/nombre_quest.start. El timer está llamando a mi_timer que fue un timer creado en un when así: timer("mi_timer", tiempo_en_segundos) Estos tres casos tienen algo en común y es que cuando el when llama a un objeto se crea una carpeta con el nombre del objeto o el id, luego adentro crea otra carpeta con la acción (kill, chat, timer, loop_timer, server_timer o server_loop_timer) ¿Qué sucede si la acción no llama a un objeto? Se crea el archivo en una carpeta llamada notarget en quest/object. Caso 4: se crea el archivo quest/object/notarget/kill/nombre_quest.start Como ya se han dado cuenta, el archivo que se crea es de extensión .state El árbol de archivos y carpetas tienen una estructura específica para describir las quest una vez se compilan. ¿Qué contenido traen los archivos nombre_quest.state? Adivinaste. El mismo contenido del when que pertenecen a ese state. El árbol queda así para la quest de ejemplo: Puede ser más complejo meter más state, complicar el when poniendo muchos or combinando acciones llamando a objetos y otras no, pero no es el objetivo profundizar esto. La conclusión es simple, todos los archivos creados tienen algo en común: el nombre Por eso el archivo en bash (líneas de comandos para la consola) borra lo que digites sin el .quest para que se borre también el state. Si has llegado hasta aquí no has perdido el tiempo. Ahora entiendes un poco más cómo está estructurado esto. Si te preguntas por qué hay un perro que dropea barras de oro y no encuentras el archivo en quest, puede que no esté allí pero sí esté compilado en object. Anexo: Si al hacer una acción implica dos when, siempre hará el primero e ignorará a los demás, ejemplo: when 101.kill begin chat("Rojo") end when kill begin chat("Negro") end Si matas un perro te va a decir Rojo Si cambiamos el orden de los when, pasará esto: when kill begin chat("Negro") end when 101.kill begin chat("Rojo") end Al matar un perro va a decir Negro Saludos!
  11. ¡Hola a todos de nuevo! Este será un tutorial sobre los timers en una dung. Ojo, solo en las dungs. Tutorial nivel intermedio Existen dos tipos de timers: Timers globales: Estos timers son a nivel de la dung. Tenemos: server_timer() server_loop_timer() Timers personales: Estos timers son a nivel de personaje. Tenemos: timer() loop_timer() server_timer() server_timer() es generalmente utilizado para tiempos globales en las dungs. Un tiempo global quiero decir, por ejemplo, cuando entras a la dung y te dice: "Te quedan 45 minutos para matar al jefe". Y es fácil de identificarlo porque ese tiempo es el mismo para todos. server_timer() es una función y también es un disparador (when) que se activa cuando pasa el tiempo determinado. Pero no siempre se usa para poner un anuncio de tiempo, sino que hay muchas funciones que se pueden usar. Y ojo a lo siguiente que es donde más se cometen errores: Al ser una función global, no puedes poner funciones personales Funciones que NO puedes poner en un server_timer(): - say(), chat(), syschat(), notice() o derivadas. - pc.mount(), y cualquiera que tenga pc... ya que "pc" es "personal character", no es global - game.drop_item() porque el objeto cae bajo el pj Funciones que puedes usar dentro de un server_timer(): - d.notice(), notice_all(), notice_multiline(), - clear_server_timer() - game.set_event_flag(), game.get_event_flag() - server_loop_timer() - Todas las que sean de dung, o sea, las que empiecen por d., ejemplo d.count_monster() - Todas las que no tengan que ver con afectar a algún carácter, ejemplo tonumber(), table.getn(), string.len(), etc. Estructura de un server_timer() Para crearlo: En el nombre, es el mismo nombre que le vas a poner al when. En tiempo, procura ponerlo en esta estructura, 60*60*2 (2 horas), por ejemplo. Si el server_timer no está dentro de otro, se pone en el 3° parámetro el index del mapa. Yo pongo d.get_map_index() pero es igual que con pc.get_map_index(). Esto mismo se aplica para la función clear_server_timer(). server_timer("nombre", tiempo, d.get_map_index()) Si el server_timer está dentro de otro, se pone get_server_timer_arg(), así: server_timer("nombre", tiempo, get_server_timer_arg()) Cuerpo de un server_timer(): when nombre.server_timer begin if d.select(get_server_timer_arg()) then -- aquí pones tu contenido con las funciones válidas end end En nombre, puse el mismo de cuando lo creé. Luego, en vez de poner use, chat, y esas cosas, puse server_timer. Sí o sí para dungs hay que poner ese bloque if así tal cual está. Esto es para identificar la dung. Pongo los dos casos a continuación: when login begin server_timer("nombre", 60, d.get_map_index()) --cuando pongo un server_timer() en un when normal end when nombre.server_timer begin if d.select(get_server_timer_arg()) then server_timer("nombre2", 30, get_server_timer_arg()) --cuando pongo un server_timer() en un when server_timer end end Parar un server_timer: En el caso de que esté en un when distinto a un server_timer: clear_server_timer("nombre", d.get_map_index()) En el caso de que esté dentro de un when server_timer: clear_server_timer("nombre", get_server_timer_arg()) Cuándo se detiene un server_timer? - Cuando necesitas parar un server_loop_timer - Cuando necesites detenerlo y crear otro con diferente tiempo. Como en el caso de Catacumbas cuando destruyes a Caronte, te crea un nuevo tiempo para matar a Azrael. - Cuando terminas la dung y antes de transportar a todos, limpias todos los server_timer. Cómo crear un server_timer imitando un ciclo: when login begin server_timer("nombre", 60, d.get_map_index()) end when nombre.server_timer begin if d.select(get_server_timer_arg()) then server_timer("nombre2", 30, get_server_timer_arg()) end end when nombre2.server_timer begin if d.select(get_server_timer_arg()) then server_timer("nombre", 1, get_server_timer_arg()) --llamo al server_timer anterior en 30 seg, y éste llamará de nuevo al server_timer y así... end end Esta estructura sirve como ciclo, en este caso para que cada 30 segundos haga algo. La siguiente estructura es para anunciar los tiempos de duración de la dung: when login begin server_timer("nombre", 10, d.get_map_index()) --en 10 segundos empezará a avisar que faltan 60 min end when nombre.server_timer begin if d.select(get_server_timer_arg()) then d.notice("Quedan 60 minutos") server_timer("nombre2", 60*30, get_server_timer_arg()) --en 30 min avisará que quedan 30 min end end when nombre2.server_timer begin if d.select(get_server_timer_arg()) then d.notice("Quedan 30 minutos") server_timer("nombre3", 60*20, get_server_timer_arg()) --en 20 min avisará que quedan 10 min end end when nombre3.server_timer begin if d.select(get_server_timer_arg()) then d.notice("Quedan 10 minutos") server_timer("nombre4", 60*5, get_server_timer_arg()) --en 5 min avisará que quedan 5 min end end when nombre4.server_timer begin if d.select(get_server_timer_arg()) then d.notice("Quedan 5 minutos") server_timer("nombre5", 60*5, get_server_timer_arg()) --en 5 min transportará a todos end end when nombre5.server_timer begin if d.select(get_server_timer_arg()) then d.exit_all() end end server_loop_timer() server_loop_timer() es igual al server_timer() pero cíclico. Loop significa ciclo, repeticiones cada cierto tiempo. Mientras que server_timer() hace algo una vez en x tiempo, server_loop_timer() hace algo cada x tiempo (en segundos) hasta que lo detengas forzosamente. Las funciones que se utilizan son las mismas que en el server_timer(). Estructura de un server_loop_timer() Para crearlo: Se crea exactamente igual que el server_timer(), solo cambia el tiempo. Si pones 5, cada 5 segundos hará lo que pongas dentro del when. Cuerpo de un server_loop_timer(): La única diferencia es que el when es el mismo. Veamos: when login begin server_loop_timer("nombre", 5, d.get_map_index()) end when nombre.server_timer begin --aquí se pone server_timer if d.select(get_server_timer_arg()) then -- aquí pones lo que va a hacer cada 5 seg end end Los server_loop_timer se detienen igual que un server_timer. Combinar server_loop_timer con server_timer: El tiempo de un server_loop_timer debe ser mayor al de un server_timer anidado. Anidado significa que está dentro, o sea, el server_timer dentro del server_loop_timer. Veamos qué sucede si NO cumplo con esta regla: when login begin server_loop_timer("nombre", 5, d.get_map_index()) end when nombre.server_timer begin if d.select(get_server_timer_arg()) then server_timer("nombre2", 10, get_server_timer_arg()) end end El ciclo cada 5 segundos va a ejecutar un ciclo de 10 segundos. Entra al ciclo, ejecuta el server_timer(), y en el segundo 5 vuelve a entrar al ciclo, impidiéndole cumplir sus 10 segundos para entrar al server_timer. Así que la regla es no poner un server_timer() con un tiempo mayor al del server_loop_timer(). Pero toda regla tiene sus excepciones, así que vamos con esta: Si el ciclo tiene una condición que permite que el server_timer() cumpla con su tiempo, entonces sí es válido. Como en el siguiente ejemplo: when login begin server_loop_timer("nombre", 5, d.get_map_index()) end when nombre.server_timer begin if d.select(get_server_timer_arg()) then if d.getf("bloqueo") == 0 then server_timer("nombre2", 10, get_server_timer_arg()) d.setf("bloqueo", 1) end end end Puse un d.getf() que cuando entra y ejecuta el server_timer(), solo lo hace una vez porque le cambié el valor al d.getf() al final. Es claro que el código así como lo tenemos no va a funcionar, tendríamos que hacer que el personaje logre cambiar de nuevo el valor del d.getf("bloqueo") a 0 para que se ejecute de nuevo el ciclo (si es lo que buscamos). Digamos que, cuando mate al jefe, ponga d.setf("bloqueo", 0) y se ejecutará de nuevo el ciclo. timer() Los timer() se usan cuando necesitas ejecutar funciones a nivel de personaje. La ventaja es que son más sencillas de usar, incluso puedes usar cualquier función, hasta las globales. Crear un timer: timer("nombre", tiempo) Cuerpo de un timer: when login begin timer("nombre", 10) end when nombre.timer begin -- end En el when solo pones nombre.timer Ya no hay que poner el if Detener un timer: cleartimer("nombre") Prácticamente no se usa en las dungs porque generalmente cualquier acción en las dungs afectan a todos los personajes que están ahí. Por lo tanto, si usas un timer() en una dung, y el personaje se desconecta antes de pase el tiempo, no se ejecutará el when timer. El timer() se muere cuando lo detengas o cuando el personaje se desconecta. A diferencia del server_timer o server_loop_timer que se sigue ejecutando sin importar si los personajes se desconectaron. Un ejemplo crítico para esto, es que mates un jefe y te ponga un timer() donde los lleve a todos a una sala, pues si el pj se desconecta antes de que ocurra el timer, nadie se transportará a la sala. Los timer() se pueden anidar en los server_timer() y server_loop_timer(). Así que la regla es, si las funciones son a nivel personaje debes usar timer(), si son a nivel global (a nivel dung, de hecho) se usan los server_timer() y server_loop_timer(). Un posible uso es que cuando mates a un jefe, en un determinado tiempo te de algo (a ti solo) pero no le veo mucho sentido. loop_timer() Llegamos al nivel más complejo del tutorial. Un loop_timer() es como un server_loop_timer() pero a nivel personaje. Crear un loop_timer: loop_timer("nombre", tiempo) Cuerpo de un loop_timer: when login begin loop_timer("nombre", 5) end when nombre.timer begin -- end Detener un loop_timer: Se detiene igual que un timer. Un loop_timer famoso es el de cuando el personaje se muere y haga algo when login begin loop_timer("muerto", 5) end when muerto.timer begin if pc.get_hp() <= 0 then chat("Se murióooo (8)") end end El tiempo del loop_timer() debe ser menor al tiempo en que hace respawn el pj o sino no aparece cuando le de al botón de respawn cuando ya pueda aparecer. Cuando el pj muere, en pocos segundos aparecerá en el chat ese mensaje, y si sigue muerto, cada 5 segundos seguirá apareciendo. Pero bueno, en una dung no le veo mucho sentido usar un loop_timer() independiente porque vuelvo y repito, las funciones de las dung afectan a todos los personajes salvo la creatividad que quieras darle que necesite de algo más exclusivo... Combinar loop_timer() con server_timer() y server_loop_timer(): El objetivo es usar funciones personales con funciones globales, así hacemos más diversa la dung. El problema está en que es no es tan sencillo combinarlas y menos cuando vamos a meter un loop_timer(), así que vamos a ver lo que yo llamo semáforos, que básicamente es usar los d.getf() y d.setf() como banderas. Vamos a hacer que el personaje cuando muera (loop_timer() porque la muerte es personal y además debe evaluar cada ciertos segundos porque no es when) lo lleve a city: when login begin loop_timer("muerto", 3) end when muerto.timer begin if pc.get_hp() <= 0 then timer("enviar_a_city", 1) end end when enviar_a_city.timer begin warp_to_village() end Puse un timer de 1 segundo para que al morir no transporte de una vez (se ve horrible). Ahora haré que cuando no hayan monstruos, invoque otros monstruos más después de 60 segundos: when login begin d.spawn_mob(blablabla) --aquí invoco los primeros mobs loop_timer("muerto", 3) server_loop_timer("timer_inv", 3, d.get_map_index()) --ciclo cada 3 segundos para evaluar si hay monstruos end when muerto.timer begin if pc.get_hp() <= 0 then timer("enviar_a_city", 1) end end when enviar_a_city.timer begin warp_to_village() end when timer_inv.server_timer begin if d.select(get_server_timer_arg()) then if d.count_monster() <= 0 and d.getf("bloqueo") == 0 then --si no hay monstruos, entonces... y de una vez pongo mi bloqueo que expliqué antes server_timer("invocar", 60, get_server_timer_arg()) --en 60 seg invocará de nuevo d.setf("bloqueo", 1) --fin bloqueo end end end when invocar.server_timer begin d.spawn_mob(blablabla) --aquí invoca de nuevo d.setf("bloqueo", 0) --pongo bloqueo en 0 para que pueda volver a invocar por el server_loop_timer end Ahora buscaré la manera de hacer que en cada oleada se monten automáticamente los personajes en alguna montura. Aquí aplicaré una función personal (pc.mount()) pero a todos. when login begin d.spawn_mob(blablabla) loop_timer("muerto", 3) server_loop_timer("timer_inv", 3, d.get_map_index()) end when muerto.timer begin if d.getf("montar_todos") == 1 then --esta es la bandera que les decía d.setf("montar_todos", 0) --toca bloquearla para que no se repita de nuevo hasta que se lance de nuevo la oleada quest.montarTodos() --aquí va a la function... end if pc.get_hp() <= 0 then timer("enviar_a_city", 1) end end when enviar_a_city.timer begin warp_to_village() end when timer_inv.server_timer begin if d.select(get_server_timer_arg()) then if d.count_monster() <= 0 and d.getf("bloqueo") == 0 then server_timer("invocar", 60, get_server_timer_arg()) d.setf("bloqueo", 1) end end end when invocar.server_timer begin d.spawn_mob(blablabla) o d.setf("bloqueo", 0) d.setf("montar_todos", 1) --pongo en 1 esta bandera para que arriba en el loop_timer entre a la condición que hace que todos monten end function montarTodos() -- aquí se usa el partyMembers y todo eso de las dungs... pc.mount() --lo que quiero que entiendan acá es que ese pc.mount() se aplicará en todos los pjs con q.begin_other_pc_block() y q.end_other_pc_block() end El resumen de esta parte es que utilicé los d.setf y d.getf para poder activar y desactivar las entradas a ciertas condiciones de los server_timer o server_loop_timer. Así que si necesitas usar una función personal en un server_loop_timer debes activar una bandera, en el loop_timer poner la condición, desactivar la bandera y luego hacer las funciones. Notas: - Se puede usar funciones dungeon (las que empiezan por d.) dentro de cualquier when (timers globales, personales, use, chat, todo eso) - Las líneas de código se ejecutan inmediatamente. No hay que esperar x tiempo del timer para que se ejecute las siguientes líneas. - El /rel q, mata todos los timers del mapa. Así que al testear debes cambiar de pj. Y bueno, esto solo se entiende practicando xD Si no entendiste ni madres, estás bien. Probablemente en dos semanas no entenderé lo que puse acá Esto lo apliqué en mi quest de "Arena Mob" que es una mazmorra infinita. Y al agregarle complejidad a la mazmo, había funciones que sí o sí debía poner en el server_loop_timer algunas funciones que eran personales, como pc.give_item2(), pues al pasar de cada ciertas oleadas tenía que dar unos premios y no podía usar pc.give_item2() en el server_loop_timer, así que me tocó en el loop_timer. Tampoco quería que reclamaran los premios dándole clic al NPC, yo quería que se dieran automáticamente y ahí está la complejidad. Arena Mob Consiste en una mazmorra de gremio donde se entra en grupo (la idea es que vayan los 8 mejores del gremio) y deben vencer las oleadas de monstruos que aparecen. Una vez dentro del mapa, aparecerá en anuncio que el gremio x se está enfrentando a la Arena Mob. Personaje que muere, será transportado a la ciudad y aparecerá un anuncio diciendo que x persona del gremio y murió en la oleada z. En cada oleada los monstruos se vuelven más fuertes y más resistentes. Cada ciertas oleadas todos los personajes que sigan vivos en la dung van a recibir un premio. En cada ciertas oleadas aparecerá un jefe un poco más fuerte. Cuando destruyan la oleada, pueden hacer aparecer otra inmediatamente, pero si no lo hacen, en 60 segundos aparecerá. Cuando muera el último personaje, aparecerá en anuncio que el gremio x terminó la Arena Mob en la oleada y. Luego, hay un ranking donde aparecerá la lista de gremios que llegaron a la oleada más alta. Precio de la quest: 15 usd Discord: Camilo#0869 Un gusto ayudarlos, ¡saludos amigos!
  12. --------------------------------------------------- UPDATE! He puesto multilanguage la quest. En el momento está en español e inglés pero está en variables de translate, así que se puede usar en cualquier servidor internacional, solo se agregan las variables de los otros idiomas. Esta quest es para que se pueda pescar todo lo que quieras en el mar sin tener que modificar ningún archivo en la base! Otra actualización que hice fue que, mientras el evento esté activo y yo como GM agregue o elimine un pez (objeto) va a avisar a todos en un anuncio. ¿La quieres? ------------------------------------------------------- ¡Hola a todos! He sacado de mi baúl de quests un evento de pesca El evento consiste en pescar objetos que el GM agregue a la lista de peces. Saludos a mi amigo @Anthony's que aparece en el vídeo. Vídeo: Instrucciones: El GM activa el evento y asigna la duración. Hay un menú donde aparecen todos los objetos posibles para pescar, y al seleccionar alguno, se puede elegir como modelo para agregar el objeto que se quiera. Una vez agregado, queda en la lista de Objetos especiales que se puede ver en el Pescador y en el NPC donde se activa el evento. Los objetos agregados se pueden eliminar desde la lista de Objetos especiales. Se puede terminar el evento forzadamente. FAQ: ¿Si agrego un objeto, se pueden seguir pescando los peces normales? Sí ¿Qué sucede cuando agrego un pez? ¿en dónde queda? Queda arriba del pez modelo, o sea, del pez elegido. Si elegiste Pez pequeño como modelo, en el fishing.txt va a quedar tu nuevo objeto arriba de esa línea. ¿Si se termina el evento, no saldrán los objetos especiales? Por supuesto que no. Y tampoco se eliminarán los objetos especiales sino que seguirán guardados en la lista. ¿Por qué al pescar objetos con socket aparece una rozadura en un socket? Ya no recomiendo esto, por favor no poner objetos equipables para pescar ¿Si elimino un pez de los normales, lo puedo volver a agregar después? No. Hay que hacer una copia de fishing.txt ¿Por qué vi un tiempo de 30 segundos cuando se iba a activar el evento? Porque era para hacer pruebas, eso ya no estará. ¿Por qué hay que tomar como modelo otro objeto? Porque los parámetros de los peces son muy complejos, y para más facilidad solo se copia y se pega la línea. ¿Cuánto cuesta? Escríbeme para hacer un trato. El servicio cuenta desde la instalación hasta las actualizaciones que haga del evento. Esto fue todo amigos Discord: Camilo#0869
  13. Holaaaaaaaaaaaaaa Vengo a compartir un programa que hice en Python sencillo, que consiste en "traducir" el locale_string.txt. Contexto: cuando descargas una base turca, por ejemplo, viene el locale_string.txt en turco, y no puedes pasar tu locale_string.txt traducido porque resulta que la base turca tiene algunas líneas diferentes que dependen de sistemas, y si pones tu locale_string.txt traducido, te va a dar error. Lo mostraré mejor: Este es el locale_string.txt turco. " %s 군주 후보에서 삭제하였습니다"; "%s Savas arasindan ?kartildi "; " %s 군주로 입명 했습니다."; "%s ?parator aday?olarak g?terildi. "; " %s 군주르 제거 하였습니다.."; "%s ?paratorluk g?evini b?akt? "; "´?¸? °?·ˇAß(?˘°i,±ł??,≫oAˇ)?ˇ´A °ł?I≫oAˇ?≫ ≫c?eC? ?o ??˝?´?´?."; "Bir market a汚k iken baka bir market a?mazs?."; " %s 군주를 제거 할수 없습니다."; "%s ?paratoru g?evi b?akamaz. "; "Kendi marketinizden herhangi bir ey sat? alamazs??."; "Kendi marketinizden herhangi bir ey sat? alamazs??."; "20?? łE?≫ ??°uC??ⓒ ≫oAˇ?≫ ?­?o°ˇ ??˝?´?´?"; "Envanterinizdeki Yang maksimum seviyeye ulam詰. L?fen envanterdeki Yang'lar?k??lere d?淆t?? tekrar deneyiniz."; Este es el locale_string.txt tuyo, el traducido: " %s 군주 후보에서 삭제하였습니다"; "%s is deleted as Emperor Candidate."; " %s 군주로 입명 했습니다."; "%s nominated as Emperor."; " %s 군주르 제거 하였습니다.."; "The %s Emperor gets driven out."; " %s 군주를 제거 할수 없습니다."; "The %s Emperor cannot be driven out."; " %s 는 군주 후보가 아닙니다."; "%s is no Candidate to become Emperor."; " %s 는 군주로 입명할수 없습니다 ."; "%s cannot be nominated as Emperor."; " (만료일 : %d년 %d월 %d일)"; "(Procedure: %d y- %d m - %d d)"; Si te das cuenta, el 4° texto del locale_string.txt turco no existe en el locale_string.txt traducido. ¿Qué hubiese pasado si lo reemplazas en el servidor? probablemente te da error o se pierde ese texto para el sistema que pertenece ese locale. Aquí te lo explico mejor. Turco vs Inglés Por eso he creado un programa en Python que compara las líneas de tu locale_string.txt con las del locale_string.txt del otro servidor y reemplaza el texto. Dejo el código de Python aquí para las personas dev: (más abajo dejo el programa completo) compare_locale_string.py import array def getArrayFile(path): content = [] with open(path) as f: line = f.readline() content.append(line) while line: line = f.readline() content.append(line) f.close() return content print("You should have your locale_string within this directory") print("Type name file translated locale_string.txt (ex. locale_string_en.txt)") other_file = input() locale_string_content = getArrayFile("locale_string.txt") other_file_content = getArrayFile(other_file) count_1 = 1 for i in other_file_content: if '"' in i: count_2 = 1 for j in locale_string_content: if i == j: locale_string_content[count_2] = other_file_content[count_1] break count_2 = count_2 + 1 count_1 = count_1 + 1 with open('new_locale_string.txt', 'w') as f: for i in locale_string_content: f.write(i) print("New file: new_locale_string.txt") Requisitos: debes tener instalado Python versión 3+ Programa completo: archivo adjunto al final ¿Cómo se usa? Luego de tener Python 3+ instalado, vas a hacer esto: 1. Descomprime el rar y pon la carpeta en un lugar donde la identifiques 2. Abrir cmd.exe y poner cd ruta/de/la/carpeta/compare_locale_string 3. Pon py compare_locale_string.py 4. La consola te pedirá el nombre del archivo tuyo. Yo dejé un locale_string_en.txt ahí. 5. Abrir el archivo generado new_locale_string.txt y verificar que todo esté bien. El resultado de las primeras líneas de mi archivo new_locale_string.txt: Saludos amigos! compare_locale_string.rar
  14. Buenas una pregunta al traducir el locale_string se puede traducir las dos lineas o siempre las segundas. es q las letras chino o japones salen para traducir no se si traducir el idioma japones al español se dañe algo . no se si me hago entender bien o siempre es traducir la segunda lineas. locale_string.txt
  15. caanmasu

    RETO quest #1

    ¡Bienvenidos nuevamente, cracks! Espero que se encuentren muy bien porque voy a lanzar un reto. Antes de eso, voy a presentarme de nuevo: soy Camilo Martínez, y conozco Metin2 como desde 2009. Nunca fui bueno, así que me dio la curiosidad de saber cómo se montaba ese juego localmente para acabar el juego y de paso volverme GM. Luego de aprender lo básico de los servidores, me especialicé en quest/lua. Entre esos años han pasado muchas cosas y ahora no puedo dar detalles. Mi experiencia con las quest: llevo 3 años programando quests. He visto todos los posts de quest de este foro y he resuelto todas las quest (para mí mismo) que han pedido. También he analizado y creado quests muy grandes turcas e incluso he aprendido de trabajos muy bien elaborados. Empecé con un say("Hola mundo") y ahora puedo crear mundos con este lenguaje. He aportado quests en este foro que han llegado a tener buena fama, e incluso las he visto en servidores. En total diría que he analizado más de 1000 quests con sus correcciones. Mi valor agregado con las quest aparte de crear cosas exclusivas es personalizarlas, es decir, que solo cambiando números como "nivel_mision = 50" puedas cambiarla según tus necesidades sin tener que modificar código abajo. También me enfoqué en crearlas de manera profesional, y hasta acá llega mi avance. Mi objetivo del reto es obviamente compartir conocimiento para usar este exquisito programa creado por todos para sus servidores o lo que quieran si así lo desean. Les estaré ayudando, dando pistas y trucos, corrigiendo errores. Yo no daré la solución sino ustedes. No hay fecha de caducidad porque esto va a ser para ustedes, y quizá pueda haber premios luego... Este post puede quedar muerto, no pasa nada. La intención es lo importante. Lo que deben hacer es comentar haciendo preguntas, poniendo código en lua, pidiendo pistas, etc. Así es como veo la participación. Reto #1 Elaborar una quest donde: - Un GM desde un NPC pueda dar un objeto X y una cantidad Y a todos los personajes que se encuentren en el mapa donde estás. ¡Let's gooo!
  16. Buenas Alguno sabe de como implementar el Baúl de Aprendiz I cuando creo Nueva cuenta no sale.
  17. Buenas Alguno alguno sabe como puedo resolver. SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv30 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv40 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv50 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv60 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv70 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv80 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv85 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv90 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv92 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv94 (4) SYSERR: May 8 12:52:41 :: ReadQuestCategoryToDict: QUEST couldnt find QuestIndex for name Quest: collect_quest_lv96
  18. Hola comunidad de Metin2Zone quien me podria hacer el favor y me podria editar esta quest para que escoja si quiere corporal o mental etc, y si es posible que pida 50kk de yang por hacerte las p muchisimas gracias y disculpen si los incomodo when 50512.use begin if pc.get_level() < 50 then say_title("Debe De Ser Nivel 50 Para Poder Subir Sus Habilidades Ha Perfect") return end say_title ("Subir Ha Perfect Master") say ("") say ("¿Husted Desea Subir Sus Habilidades Ha Perfect?") say ("") a = select ("Subir A Perfect" , "Cerrar") if a == 2 then elseif a == 1 then pc.set_skill_level (1,59) pc.set_skill_level (2,59) pc.set_skill_level (3,59) pc.set_skill_level (4,59) pc.set_skill_level (5,59) pc.set_skill_level (6,59) pc.set_skill_level (16,59) pc.set_skill_level (17,59) pc.set_skill_level (18,59) pc.set_skill_level (19,59) pc.set_skill_level (20,59) pc.set_skill_level (21,59) pc.set_skill_level (31,59) pc.set_skill_level (32,59) pc.set_skill_level (33,59) pc.set_skill_level (34,59) pc.set_skill_level (35,59) pc.set_skill_level (36,59) pc.set_skill_level (46,59) pc.set_skill_level (47,59) pc.set_skill_level (48,59) pc.set_skill_level (49,59) pc.set_skill_level (50,59) pc.set_skill_level (51,59) pc.set_skill_level (61,59) pc.set_skill_level (62,59) pc.set_skill_level (63,59) pc.set_skill_level (64,59) pc.set_skill_level (65,59) pc.set_skill_level (66,59) pc.set_skill_level (76,59) pc.set_skill_level (77,59) pc.set_skill_level (78,59) pc.set_skill_level (79,59) pc.set_skill_level (80,59) pc.set_skill_level (81,59) pc.set_skill_level (91,59) pc.set_skill_level (92,59) pc.set_skill_level (93,59) pc.set_skill_level (94,59) pc.set_skill_level (95,59) pc.set_skill_level (96,59) pc.set_skill_level (106,59) pc.set_skill_level (107,59) pc.set_skill_level (108,59) pc.set_skill_level (109,59) pc.set_skill_level (110,59) pc.set_skill_level (111,59) pc . remove_item("50512",1) elseif a == 2 then end end end end
  19. Hola Comunidad de metin2zone, me e encontrado un archivito en otro foro (Just4Metin-Donici Cătălin) y me vi en la necesidad de compartirlo :D porque esta muy PRO! Bueno dejo el tuto :D depronto este postiado pero aqui lo revivimos :v aclaro el tuto no es mio. 1-Vamos a la carpeta de las quest usr/home/game/share/locale/xxxx/quest Alli buscamos el siguiente archivo Questlib.lua, bajamos y pegamos las siguientes lineas 2- nos devolvemos a usr/home/game/share/locale/xxxx/quest y buscamos el archivo quest_functions y pegamos las siguientes lineas y listo con eso ya tenemos la funcion de la quest de colores :D say_rosu(“rojo”) say_verde(“verde”) say_portocaliu(“naranja”) say_negru(“negro”) say_alb(“blanco”) say_galben(“amarillo”) say_verde(“verde claro”) Aclaro la guia funciona perfectamente :D
  20. Aqui traigo esta quest que encontre por ahi, que cambie tu nick, a algunos les servira, a otros no, igualmente aqui la tiene ^-^ quest cambio_nombre begin state start begin when item que desea usar.use begin if pc.is_married() then say("Usted no puede cambiarse su nombre si está casado.") say("") return end if pc.is_polymorphed() then say("Usted no puede cambiar su nombre si está transformado.") say("") return end if pc.has_guild() then say("Usted no puede cambiar su nombre si está en un gremio. ") say("") return end if party.is_party() then say("Usted no puede cambiar su nombre si está en grupo.") say("") return end if pc.get_level() < 35 then say("Necesitas ser nivel 35 o mas para cambiar tu nombre!") say("") return end if get_time() < pc.getqf("next_time") then say("No puede utilizarce ahora.") say("") if is_test_server() == true then say("Puede irse") say("") else return end end say("Introdusca su nombre nuevo") ; local name = pc.name ; local str = input() ; if string.len(str) > 16 then say("El nombre es demasiado largo, intentelo de nuevo.") say("") return end local ret = pc.change_name(str) ; if ret == 0 then say("Debe relogear despues de cambiar su nombre.") say("please re-log in.") say("") char_log(0, "CHANGE_NAME", "HAVE NOT RE-LOGIN") elseif ret == 1 then say("Ha ocurrido un problema.") say("Por favor uselo de nuevo.") say("") char_log(0, "CHANGE_NAME", "ITEM USE PROBLEM") elseif ret == 2 then say("Este nombre no está disponible.") say("Por favor ingrese otro nombre.") say("") char_log(0, "CHANGE_NAME", "CAN NOT USE NAME") elseif ret == 3 then say("Este nombre no está disponible.") say("Por favor ingrese otro nombre.") say("") char_log(0, "CHANGE_NAME", "ALREADY USING NAME") elseif ret == 4 then say("Ha cambiado su nombre con exito.") say("Por favor, inicie sesión de nuevo.") say("") item.remove() ; pc.setqf("next_time", get_time() + time_hour_to_sec(24*15)) char_log(0, "CHANGE_NAME", "SUCCESS: from "..name.." to "..str) else say("Error desconocido.") say("") char_log(0, "CHANGE_NAME", "UNKNOWN NAME") end end end end si yasé que ya estaba por ahi, pero solo le puse las lineas al español(que dificil no? XD)
  21. Alguien tiene esta quest. traducida? Yo la estoy traduciendo pero no sé si es así tal cual. Gracias Por su atención. gmpanel.quest
  22. wenas a todos!! He estado haciendo una quest para los que les da pereza andar con comandos de mutear y esas cosas, tambien puse todos lo que se me ocurrieron, si se me paso alguno, decirlo y lo añado. Para poder usar la parte de banear gente, hace falta tener lo del mysql para quest, podeis encontrarlo aqui , no se si funcionara la quest sin eso... Click aqui para descargar la quest Descripcion de opciones: Halloween: Lo que hace es invocar unos npcs para halloween, deben ser configurados en la quest. Como usar el comando "/m" Dia/noche: creo que no hace falta explicacion xD Rates: para poner cualquier rate para reinos o por separado. Pronto rates para gremios xD Bloquear chat: como su nombre dice, es para silenciar el chat, para quitar el silencio o ver la lista de silenciados. Navidad: Para activar/desactivar cualquier evento de navidad, como la nieva, el arbol de navidad, los villancicos, Santa Claus, venta de fuegos artificiales, etc Banear: Banea/Desbanear la cuenta de la persona que quieras, pero necesitas implementar mysql para quest, aqui el post. Teleport: Para que te teletrasportes hacia una persona, o para traerlo hacia ti. Recordad: No os olvideis de poner vuestros datos y esas cosas en las funciones de mysql_query() al final de la quest hay 4 funciones parecidas a la siguiente, en "root" es el usuario del navicat(suele ser root), en PassNavicat, pos eso, la pass xD, en account no tocarle, y en ipNavicat, es la ip del server, los datos esos, van entre comillas como estan puestos ahy, si las quitais os dara error. mysql_query("Update account.account set account.status = 'OK' WHERE account.id ="..id.account_id[1].."" ,"root","PassNavicat", "account", "IPNavicat") Si teneis alguna duda, preguntaros, y recordad que nunca esta de mas dar las gracias xD Saludos!!
  23. Hola amigos soy novato en esto me gustaría si me pueden ayudar tengo files rain cliente omega me podrían dar las quest del oficial y decirme como borrar quest que ya tengo es que cuando lo ago me da error muchas gracias.
  24. Hola gente, hace unos dias pasaba por UJ y me encontre con esto, ustedes diran "hab a p wao ._." pero lo que hace esto es que tambien te sube las habilidades secundarias como Poliformismo, las habilidades de monta, etc. Creditos: Clawdi
  25. ¡Hola a todos! Les traigo una quest de cacería personalizada hecha por mí. Mi objetivo, aparte de entregarles algo de calidad, es pedirles una colaboración monetaria para un proyecto de emprendimiento que voy a realizar. Mi misión de caza está valuada en 15 USD la quest + 5 USD los datos (toda la parte de los niveles, los jefes, recompensas, etc). El método de pago es PayPal. La diferencia de mi misión con las otras de caza es que es personalizada. Primero unos gif y pantallazos para que no te pierdas: Aceptar la misión [Hidden Content] Contenido de la misión [Hidden Content] En batalla [Hidden Content] Recompensa [Hidden Content] Esa fue solo la misión de nivel 1. Luego la misión de nivel 10 es esta: No me dejó subir la otra imagen... Recompensas misión nivel 10. [Hidden Content] Detalles de la misión El personaje cuando empieza, le aparece un pergamino donde hay que aceptar que vas a hacer las misiones. Luego te envía a la primera misión. El sistema asigna los monstruos según lo que tengas en tu quest. El sistema solo asigna uno por cada categoría para matar. Por eso vas a ver que en monstruos hay como 20 pero solo aparece 1 en la misión, al azar. Cada vez que mates a uno, aparecerá en el chat y te mostrará cuántos llevas y cuántos te quedan. Cuando ya hayas alcanzado el máximo, no volverá a aparecer. Cuando completas la cantidad de monstruos de la categoría, te aparece en blanco, sino, en rojo. Para las recompensas, recibes exp, yang e ítems, dependiendo de cómo lo tengas en tu quest. Yo puse que a partir de la de nivel 10 aparezcan ítems, por eso no los vieron en el gif. Si el Yang se pasa de 2kkk cuando terminas la misión, no te deja recibir recompensas sino que te toca vaciar un poco tu Yang para poder recibir toda la recompensa. Las misiones son continuas, una vez terminas la misión, te aparece la siguiente siempre y cuando cumplas con el nivel requerido. Cuando se acabe la última misión y se reclama la recompensa ya no vuelve a salir el pergamino. Cómo personalizar: El [1] es el nivel de la misión. Más arriba están las categorías, dejé que 1 fuera metines, 2 monstruos normales y 3 jefes. Así que aquí puedes ver que en metines pide 1 metin de dolor. En monstruos normales pide perros... En recompensas puse que diera exp, yang e ítems. Pon true la variable current_level_quest para dar el % de exp según el nivel del personaje. Pon false si quieres que de el % de exp según el nivel de la misión. En exp_perc pues el % de exp. Al final de las tablas nunca va la coma, cuidado allí. Si no quieres dar exp, pon 0 en exp_perc. Si no quieres dar yang pon 0 en yang. Si no quieres dar ítems déjalo así como lo tengo allí. Todos estos valores se pueden cambiar. Yo los hice según mi jugabilidad pero creo que está bien. La última misión: Aquí tomé los metines de Bosque Encantado. Los monstruos del Templo Ochao y Bosque Encantado. En jefes puse Meley y Jotun. Recompensas allí pueden verlo. Cómo obtener esta misión de caza? hagamos un trato por privado aquí en Zone o Discord: Camilo#0869 Cualquier consulta puedes escribirme al Discord Camilo#0869, yo ayudo mucho con quest/lua, así que no lo dudes. ¡Muchas gracias por llegar hasta acá!
×
×
  • Create New...