Esta guía, sería una especie de continuación de la anterior que publiqué, que si aún la has leído, te recomiendo hacerlo. Esta guía fue escrita por Caba`drin en el foro de Taleworlds, link a la guía original: https://forums.taleworlds.com/index.php/topic,142422.0.html , que recomiendo leer si entendés inglés. Marcaré en color azul las notas o lo que sea que agregue yo mismo. Trataré de no modificar mucho.
Descarga el Module System, si aún no lo tienes
Recomiendo el 1.166 mejorado de Lav porque, principalmente, está mucho mejor explicado el archivo header_operations.py. A continuación explico la función de este archivo.
Lo básico
El archivo header_operations.py contiene una lista de todos los comandos y operaciones válidas que pueden usarse en el Module System. Incluye comentarios (Lo que sigue después de el signo #) que pueden dar una idea sobre el uso de cada operación, pero también identifica qué argumentos necesitan ser incluidos con cada operación. Cada operación del Module System tiene la siguiente forma:
1. Está encerrada en paréntesis: (operación)
2. Le sigue inmediatamente una coma: (operación),
3. Los argumentos dentro de la operación entre paréntesis están separados por una coma: (operación, argumento, argumento),
Adicionalmente, el archivo header_operations asigna un código numérico a cada una de las operaciones de la lista. Por ejemplo, call_script = 1. Cuando el juego da errores, están en forma de SCRIPT ERROR ON OPCODE ##:....El número de OPCODE proporcionado se refiere al número asignado a la operación en header_operations. Entonces, si el error fue con OPCODE1, la línea de código en cuestión utilizó la operación call_script.
Los elementos contenidos en los diversos archivos module están indexados, se les asigna un valor numérico ordinal según el orden en que se encuentran en el archivo. Estos índices comienzan con 0 y aumentan de a 1 para cada próximo valor. Por lo tanto, las tropas, items, triggers, scenes, etc, una vez compilados en archivos .txt, se denominan por su valor de índice. Entonces, si ves un código de error en "trigger 0", ese es el primer desencadenador en el archivo indicado, y si el error está en la línea 4, estás buscando la quinta operación dentro de ese trigger.
Terminología
Control y Operaciones Condicionales y Sintaxis
En general, es imperioso notar que el motor de juego trata cada operación condicional como un "SI" (No es el sí de afirmación, el el que se usaría en una frase como: "Si es una operación condicional pasará esto"(IF) y todo el código que sigue después de una operación condicional como el "ENTONCES" (THEN)en una declaración Si-Entonces(If-Then). A medida que el motor pasa por líneas de código, se detendrá cada vez que una operación condicional falle, y no leerá el resto del código.
Para aislar las declaraciones Si-Entonces (-Además (-Else)) para permitir la falla pero continuar procesando el resto del código, se debe usar un "bloque try" con (try_begin), (else_try) y (try_end). Esto se discute con más detalle abajo.
Otros temas
Si algún compañero modder del foro nota algún error que yo haya tenido al traducir la guía, o quiere agregar información, por favor comentelo en este tema, así hago las correcciones necesarias
Descarga el Module System, si aún no lo tienes
Recomiendo el 1.166 mejorado de Lav porque, principalmente, está mucho mejor explicado el archivo header_operations.py. A continuación explico la función de este archivo.
Lo básico
El archivo header_operations.py contiene una lista de todos los comandos y operaciones válidas que pueden usarse en el Module System. Incluye comentarios (Lo que sigue después de el signo #) que pueden dar una idea sobre el uso de cada operación, pero también identifica qué argumentos necesitan ser incluidos con cada operación. Cada operación del Module System tiene la siguiente forma:
1. Está encerrada en paréntesis: (operación)
2. Le sigue inmediatamente una coma: (operación),
3. Los argumentos dentro de la operación entre paréntesis están separados por una coma: (operación, argumento, argumento),
Adicionalmente, el archivo header_operations asigna un código numérico a cada una de las operaciones de la lista. Por ejemplo, call_script = 1. Cuando el juego da errores, están en forma de SCRIPT ERROR ON OPCODE ##:....El número de OPCODE proporcionado se refiere al número asignado a la operación en header_operations. Entonces, si el error fue con OPCODE1, la línea de código en cuestión utilizó la operación call_script.
Los elementos contenidos en los diversos archivos module están indexados, se les asigna un valor numérico ordinal según el orden en que se encuentran en el archivo. Estos índices comienzan con 0 y aumentan de a 1 para cada próximo valor. Por lo tanto, las tropas, items, triggers, scenes, etc, una vez compilados en archivos .txt, se denominan por su valor de índice. Entonces, si ves un código de error en "trigger 0", ese es el primer desencadenador en el archivo indicado, y si el error está en la línea 4, estás buscando la quinta operación dentro de ese trigger.
- Variables:
Variables locales - ":variable" - Las variables declaradas con : son variables locales. Solo existen en el bloque de código actual--encerrado por corchetes [ ]--y no se pueden referenciar en otros bloques de código a menos que se pasen como parámetros de script . Uno debe inicializarlas (a 0 u otro valor adecuado) antes de que se usen, porque las principales ya contienen un valor. Están encerradas por comillas " ". Ej: ":agent_id"
Variables globales - "$variable" - Las variables declaradas con $ son variables globales. Están disponibles para ser referenciadas en cualquier parte del código, desde cualquier module_*, una vez que están definidas. Su valor por defecto es 0. Están encerradas por comillas " ". Ej: "$g_talk_troop"
Registros - reg(#) O reg# - son variables utilizadas por el motor para almacenar bits de información temporalmente. Son de alcance global, pero se puede acceder por una variedad de diferentes scripts para su duración y se dejan para que otro bit de código las use. En el archivo header_common, los registros desde reg0 a reg65 están declarados.
Strings - s# - son 'registros' específicos, usados para guardar un string, en lugar de un número entero. Los strings que no están declarados en module_strings (strings rápidos) comienzan con el símbolo @. En el archivo header_common los strings desde s0 a s67 están declarados.
Posiciones - pos# - Al contrario que los dos "registros" anteriores, las posiciones almacenan más de una pieza de información; cada posición almacena las coordenadas X, Y, Z así como también las rotaciones en cada eje X, Y, y Z. En el archivo header_common las posiciones desde pos0 a pos64 están declaradas, con pos64 comenzando un rango no especificado de posiciones usadas por las torres de asedio.
Constantes - constante - son constantes que están declaradas en module_constants, mantienen el valor numérico asignado en ese archivo, y no se pueden editar, salvo editando el archivo module_constants. Se pueden llamar desde cualquier archivo module_* y cualquier bloque de código. Las constantes no están encerradas con comillas.
Las variables locales y globales, así como también los registros, están declaradas con la operación (assign,, ), . El valor de la variable puede ser el nombre de otra variable. Los strings y posiciones tienen sus propias operaciones, como se define en header_operations
- Prefijos del Module System:
Dentro del Module System (ej: module_mission_templates), uno se puede referir a código o información almacenada en otros archivos del Module System usando un prefijo antes de la etiqueta para el fragmento de código.
module_animations: "anim_"
module_factions: "fac_"
module_info_pages: "ip_"
module_items: "itm_"
module_map_icons: "icon_"
module_game_menus: "menu_"
module_meshes: "mesh_"
module_mission_templates: "mst_"
module_particle_systems: "psys_"
module_parties: "p_"
module_party_templates: "pt_"
module_pstfx_params: "pfx_"
module_presentations: "prsnt_"
module_quests: "qst_"
module_scene_props: "spr_"
module_scenes: "scn_"
module_scripts: "script_"
module_skills: "skl_"
module_sounds: "snd_"
module_strings: "str_"
module_tableau_materials: "tableau_"
module_troops: "trp_"
- Module_Scripts, Parámetros, Argumentos, etc:
La mayoría de los archivos module_* contienen código autónomo en su mayor parte. Los archivos como _skills, _troops, _items, etc. simplemente definen estos diversos elementos para el juego. module_game_menus contiene todos los menús, sus opciones, etc. Presentations tiene todos los datos de dibujos de presentación más complicados, etc.
Pero module_scripts es referenciado desde muchos de esos archivos--contiene funciones "comunes" para ser llamadas desde cualquier parte del código del juego. Puedes considerar (call_script, "script_scriptname",), como un Go to:, Jump, function call, lo que quieras.
Para facilitar este proceso, la operación call script permite pasar información del código actual al script sin usar variables globales. Estos argumentos o parámetros pueden ser variables locales generadas por el bit de código actual. El código del script en module_scripts comenzará almacenando los parámetros que se le pasaron en variables locales para usar en todo el script. Los scripts no pueden pasar la información al código desde el que se llamaron de la misma manera, aunque ... para hacerlo, normalmente se usan registros. En el código de llamada, una vez que el script se ha completado, estos registros se graban en variables locales para facilitar su uso. Se pueden pasar hasta 15 parámetros de script con una sola llamada.
Ejemplo:
- Código:
//...declaraciones...//
(assign, ":local_variable_1", 10),
(assign, ":local_variable_2", 5),
(call_script, "script_ejemplo_script", ":local_variable_1", ":local_variable_2"),
(assign, ":local_variable_3", reg0), #local_variable_3 = 15
//...más cosas, trabajando con local_variable_3...//
- Código:
# script_ejemplo_script
# Entrada: variable_1, variable_2
# Salida: Sum en reg0
("ejemplo_script", [
(store_script_param, ":num1", 1), #ahora num1 es 10
(store_script_param, ":num2", 2), #ahora num2 es 5
(store_add, ":sum", ":num1", ":num2"),
(assign, reg0, ":sum"),
]),
Terminología
- Tropa vs Agente:
Tropas se refiere a las entradas en module_troops según los nombres que tengan allí. "trp_player" es el jugador, "trp_npc##" para los compañeros, "trp_knight_#_##" para lords, y "trp_swadian_footman" etc, etc. Con soldados más genéricos, la entrada en module_troops se usa como una "plantilla" para crear múltiples instancias de ellos en cada parte, etc.
Alternativamente, agentes se refiere a los actores específicos, por número, en una scene (si esa escena es una batalla, una taberna o lo que sea). Los agentes son creados desde las "plantillas" en module_troops...para el jugador, compañeros, lords, siguen siendo únicas, pero con soldados genéricos se pueden crear múltiples agentes a partir de una sola "plantilla" de tropa. Cada agente tiene un número único para esa scene, pero cuando la scene finaliza, el agente ya no existe, por lo que su ID de agente no tiene sentido.
- Parties y Party Templates:
Una "party" es cualquier entidad del mapamundi, ya sea la party del jugador, los bandidos y sus guaridas, lords, o una ciudad/castillo/aldea. Los diferentes tipos de parties están separadas en categorías en el primer party slot "slot_party_type" (o party slot 0--ve la discusion sobre los slots más abajo), que contiene un número entero asociado con un cierto tipo. Esos tipos están definidos en module_constants y comienzan con el encabezado spt_* (para slot_party_type). Algunos ejemplos de los valores spt_* son spt_kingdom_hero_party (Para lords) y spt_castle (...para castillos).
Cada party tiene un número de ID al que se hace referencia en el código. Por ejemplo, la party del jugador es "p_main_party" que tiene un valor de índice o Party ID de 0. Ciertas parties tienen números de ID constantes o estáticos, como la party del jugador y todas las ciudades, castillos y aldeas. Esas parties con una ID estática están definidas en module_parties. Puedes ver su número de ID en el archivo ID_parties.py después de compilar tu módulo.
Para todas las parties NO definidas en module_parties, estas son creadas, dinámicamente, por el juego, basadas en un party_template, de una manera un tanto paralela a la división agente/tropa anterior. Estas plantillas de party se definen en module_party_templates y tienen nombres a los que se hace referencia con el prefijo "pt_ *". La plantilla contiene, principalmente, una lista de tropas que están contenidas en esa plantilla y un rango numérico que se usa para determinar cuántas de cada tipo de tropa se le dará a esa plantilla en el juego (el motor elige aleatoriamente un número dentro de ese rango). Una plantilla de party se puede usar para crear una nueva party con el comando. A esta nueva party se le asigna un número de ID y luego se puede clasificar con un valor de slot_party_type, etc. Las plantillas de party también se pueden usar con las parties ya existentes... se puede agregar una plantilla (las tropas definidas en esa plantilla) a otra party para "reforzar" una party existente de esta manera.
- Slots:
Los Slots son esencialmente formas de tener variables dispuestas por un objeto (party, tropa, agente, equipo, facción, scene, item, jugador, scene prop, o party template). Cada party, tropa, agente, item, etc. tiene una cantidad de "slots" (variables) asignados. Esa variable tiene un nombre común "slot_item_SLOTNAME" para cada versión del objeto asignada (items en este caso), que es útil, pero almacena información exclusiva para cada objeto. Donde parece una "constante" es el hecho de que el motor convierte el nombre de la variable (el slot) en un número en module_constants (slot_item_base_price = 53). Esto significa que el motor mira el slot-variable n° 53 asociado con un item dado para encontrar el precio base de ese item.
Entonces, cuando ves operaciones que usan slots como (item_get_slot, ": base_price", ": item_id", slot_item_base_price), puedes ver que necesitás especificar exactamente para qué item querés obtener el precio base. Pero, dado que la variable está ordenada, se la puede insertar en un ciclo try_for_ * y repetir a través de muchos ": item_id" para hacer cosas al precio base de cada elemento, por ejemplo.
Pódes acceder al precio de un artículo específico con la operación item_get_slot, cambiarlo a través de item_set_slot y probar la igualdad con item_slot_eq y similares.
Si conocés los lenguajes de programación tipo C, los slots funcionan de la siguiente manera:
slot_troop_spouse
spouse[troop1] = spousename
spouse[troop2] = spousename
spouse[trp_player] = spousename
que el motor lee como troop_variable_30[troop1], troop_variable30[trp_player] etc, ya que slot_troop_spouse se establece como el slot 30 en las constantes.
slot_item_base_price
baseprice[item1] = priceA
baseprice[item2] = priceB
baseprice[item3] = priceC
y el motor lo lee como item_variable_53[item1], item_variable_53[item2] etc... ya que está configurado como 53 en las constantes.
Antes de establecerse con una operación * _set_slot, el valor de todos los slot es 0.
Control y Operaciones Condicionales y Sintaxis
En general, es imperioso notar que el motor de juego trata cada operación condicional como un "SI" (No es el sí de afirmación, el el que se usaría en una frase como: "Si es una operación condicional pasará esto"(IF) y todo el código que sigue después de una operación condicional como el "ENTONCES" (THEN)en una declaración Si-Entonces(If-Then). A medida que el motor pasa por líneas de código, se detendrá cada vez que una operación condicional falle, y no leerá el resto del código.
Para aislar las declaraciones Si-Entonces (-Además (-Else)) para permitir la falla pero continuar procesando el resto del código, se debe usar un "bloque try" con (try_begin), (else_try) y (try_end). Esto se discute con más detalle abajo.
- Lista de Operaciones Condicionales:
(eq,, ), ¿Es Valor 1 = Valor 2?
(gt,, ), ¿Es Valor 1 > Valor 2? (Mayor)
(ge,, ), ¿Es Valor 1 >= Valor 2? (Mayor o igual)
(lt,, ), ¿Es Valor 1 < Valor 2? (Menor)
(le,, ), ¿Es Valor 1 <= Valor 2? (Menor o igual)
(neq,, ), ¿Es Valor 1 != Valor 2? (Diferente)
(is_between,, , ), ¿Es Inferior <= Valor < Superior? (¿Está el valor dentro del límite inferior y superior que establecimos en la Operación?)
Además, cualquier operación que incluya "_is_" se puede considerar una prueba de condición verdadero-falso.
Por ejemplo, (agent_is_alive,), realizará una prueba condicional para verificar si la ID del agente está viva. Si la prueba es verdadera, el código continúa. Si es falso, el código se detiene.
Para probar "FALSO" en lugar de "VERDADERO" en cualquier prueba condicional V/F "_is_", usa lo siguiente:
(neg|),
De nuestro ejemplo anterior, (neg|agent_is_alive,), el código solo continuará si el agente identificado por el número de ID está muerto (NO está vivo).
- Lista de Operaciones de Control:
(try_begin),
(try_end),
(else_try),
(try_for_range,, , ),
(try_for_range_backwards,, , ),
(try_for_parties,),
(try_for_agents,),
El destino es la variable que se repetirá en un bucle.
La iteración comenzará en el límite inferior e irá hasta el límite superior -1
- Boolean AND(Y/E) y OR(O/U):
Como, como se discutió anteriormente, el motor trata cada operación condicional como "SI" en un Si-Entonces y todo el código debajo de él como "ENTONCES", el boolean(Ni siquiera sé cómo traducir eso a español xDD) Y(AND) puede lograrse simplemente enhebrando múltiples operaciones condicionales una tras otra.
Por ejemplo, si uno quisiera localizar a un agente que está vivo Y a un caballo Y a un enemigo, el código sería simplemente:
(agent_is_alive,),
(neg|agent_is_human,),
(neg|agent_is_ally,),
Para verificar una condición U(OR) otra, se usa lo siguiente:
(this_or_next|),
Según nuestra lógica anterior, también se puede unir varias veces.
Por ejemplo, para ver si una variable es 1 O 5 y, si es así, continuar:
(this_or_next|eq, ":test_variable", 1),
(eq, ":test_variable", 5),
- Si-Entonces-Además:
Dado que todas las operaciones condicionales se aplican a cada línea de código debajo de ellas, debe haber una manera de separar partes de código para que la operación de condición solo se aplique a una sección y luego el código continúa independientemente del resultado de la prueba. Para hacer esto, uno usa un "bloque try", código encerrado por (try_begin) y (try_end). Uno puede pensar en ellos como creando una típica instrucción Si-Entonces, con las primeras líneas de código después de que (try_begin) sea el "Si" en forma de condiciones pruebas y las siguientes líneas son el "Entonces" con operaciones de consecuencia, que están cerradas por el "Fin si"(try_end)
Como en todos los buenos Si-Entonces, (else_try) emerge como la declaración "Además si". En cualquier punto en que una condición falle en la prueba antes de (else_try) el motor comenzará a buscar en el siguiente (else_try).
Ejeplo:
- Código:
#Codigo arriba, haciendo lo que sea
(try_begin),
(eq, 1, 1), #Verdadero, pasa a la siguiente línea
(gt, 2, 5), #Falso, ve a else try
#operaciones de consecuencia
(else_try),
(eq, 0, 1), #Falso, ve al próximo else try
#operaciones de consecuencia
(else_try),
(eq, ":favorite_lance", ":jousting"), # ¿Tal vez?
(assign, ":prisoners", 100),
(try_end),
#El código continúa, independientemente de si la lanza favorita=justas y los prisioneros se estableciron en 100
- Bucle Para-Siguiente:
El Module System de M&B tiene diferentes variaciones del ciclo Para-Siguiente (For-Next). El más básico se logra con un (try_for_range, ":iterador",, ), luego operaciones en bucle y un tope (try_end), en lugar de un "Iterador Siguiente" o lo que sea.
La iteración comenzará en el límite inferior e irá hasta el límite superior -1, realizando pasos enteros individuales, contados por la variable asignada como destino, en el ejemplo anterior de la variable local ":iterador". El ":iterador" se debe usar en el código que sigue en el bloque try_for_range, pero si no se puede modificar para omitir las iteraciones de enteros hacia el límite superior -1. Si el ":iterador" no se utiliza en el código que sigue a la variable ":unused"(":sinusar") DEBE utilizarse en su lugar.
Ejemplo:
- Código:
(try_for_range, ":i", 0, 10),
(store_add, ":count", ":i", 1),
(assign, reg0, ":count"),
(display_message, "@{reg0}"),
(try_end),
#Esto mostrará en la pantalla un conteo desde el 1 al 10.
También hay un (try_for_range_backwards, ":iterador",, ), donde la iteración comenzará en el límite superior -1 y la cuenta hacia atrás para el límite inferior. Ignora los comentarios en header_operations que dicen cambiar el orden de los límites inferior y superior. Están equivocados. Esto es útil para eliminar algo de una lista (miembros de una party, por ejemplo) sin estropear la indexación de esa lista.
Ten en cuenta que tanto el límite inferior como el límite superior no necesitan ser un "número simple"; pueden ser fácilmente variables que están establecidas anteriormente en el código. Ejemplos de esto en la siguiente sección.
Existen dos bucles más específicos (try_for_*): (try_for_agents,) y (try_for_parties, ). Estos recorrerán todos los agentes de una escena o todas las parties en el juego, respectivamente. El iterador representa el número de ID del agente (o de la party) y debe almacenarse en una variable local.
Ejemplo:
- Código:
(get_player_agent_no, ":player"),
(agent_get_team, ":playerteam", ":player"),
(try_for_agents, ":agent"), #Bucles a través de todos los agentes
(agent_is_alive, ":agent"), #Verifica si el agente está vivo
(agent_is_human, ":agent"), #Si está vivo, verifica si es un humano (no un caballo)
(agent_is_non_player, ":agent"), #Si está vivo y es humano, verifica que el agente no es el jugador
(agent_get_team, ":team", ":agent"), #Si está vivo, es humano y no es el jugador, obtiene el equipo del agente actual.
(eq, ":team", ":playerteam"), #Comprueba que el agente vivo, humano y no jugador esté en el equipo del jugador
#Las operaciones de consecuencia van aquí para hacer cosas con este agente vivo, humano, no jugador, aliado.
(try_end), #Ir al siguiente agente
- Romper el bucle:
Muchas veces es posible que desees recorrer todos los agentes para localizar a un agente en particular, o recorrer otro rango de cosas (items, por ejemplo) para localizar un arma en particular, y no hay necesidad de recorrer el resto--ya encontraste lo que necesitabas. Entra el salto del bucle. El Module System del M&B no tiene ninguna operación específica para lograr esto, pero hay algunos trucos simples para romper un ciclo de manera efectiva. El truco a usar depende de qué tipo de bucle del Module System (try_for_*) estés usando.
Método 1: Cambiando el final del bucle
-Rompiendo un bucle try_for_range
Puedes romper un ciclo try_for_range fácilmente cambiando el límite superior para que sea igual al límite inferior. De esta forma, cuando el motor intenta iterar a la siguiente instancia del ciclo, parece que el ciclo ya ha alcanzado la última iteración y está completo. El ciclo se cerrará sin ejecutar el código nuevamente.
Para hacer esto, el límite superior del bucle debe ser una variable definida antes de que comience el bucle, y debe ser una variable que pueda modificarse sin pérdida de datos--crea una nueva variable o una copia de otra si es necesario. Una vez que se ha ejecutado el código suficientes veces, se ha encontrado algo, etc, establecé la variable para el final del ciclo para que sea igual al límite inferior del ciclo (o bien asigna el valor de la variable o el valor constante con el que comenzó el ciclo).
Ejemplo:
- Código:
#Dentro de un ciclo mayor (try_for_agents)
(assign, ":end", "itm_glaive"), #Establece el límite superior del bucle
(try_for_range, ":item", "itm_jousting_lance",":end"), #Bucle a través de las lanzas en el juego
(agent_has_item_equipped, ":agent", ":item"), #Verifica si el agente tiene equipada la lanza actual
(agent_set_wielded_item, ":agent", ":item"), #Si la lanza actual está equipada, hace que el agente actual la use
(assign, ":end", "itm_jousting_lance"), #Se rompe el ciclo - hace que el límite superior sea igual al límite inferior - deja de mirar a través de las lanzas
(try_end),
#Continua dentro del ciclo try_for_agents
El mismo método se usa para un ciclo hacia atrás. Esta vez, sin embargo, el límite inferior es el "extremo del bucle", por lo que el límite inferior tendrá que ser la variable modificada, y se debe cambiar para que sea igual al límite superior.
Ejemplo:
- Código:
#Código arriba... establece la variable ":troop" a algún valor
(assign, ":array_begin", 0), #Establece el límite inferior del bucle
(try_for_range_backwards, ":i", ":array_begin", 10), #Bucle desde 0 a 10
(party_slot_eq, "p_main_party_backup", ":i", 0), #Comprueba si el slot de la "i" party para la copia de seguridad de la party del jugador es igual a 0
(party_set_slot, "p_main_party_backup", ":i", ":troop"), #Si lo hace, configura ese slot al valor de la variable de la tropa
(assign, ":array_begin", 10), #Se rompe el ciclo - hace que el límite inferior sea igual al límite superior - no verifica los otros slots
(try_end),
-Romper un bucle try_for_agents o try_for_parties
Con los ciclos try_for_agents y try_for_parties, no es posible cambiar el "límite superior" del ciclo. En cambio, se usa una prueba condicional (generalmente para la igualdad) al comienzo del código para ejecutar dentro del ciclo. Mientras sea verdadero, se ejecutará el código dentro del bucle. Una vez que se establezca como falso, el ciclo continuará, pero no pasará nada dentro del ciclo ya que la primera condición siempre falla: el ciclo terminará rápidamente.
Para hacer esto, configura una variable antes del ciclo con un valor dado (es más fácil crear una nueva variable con el valor 0). Luego, justo dentro del ciclo, configura una prueba de operación condicional para ese valor. Después del bloque de código que deseas ejecutar solo una vez, cambia el valor de la variable para que la prueba de condición falle la próxima vez que se ejecute el ciclo.
Ejemplo:
- Código:
#Código arriba... establece pos1 en algún valor
(assign, ":break_loop", 0), #Configura la variable para romper el ciclo y un valor para probar
(try_for_agents, ":agent"), #Bucle a traves de todos los agentes
(eq, ":break_loop", 0), #Verifica si lo que rompe el ciclo sigue siendo verdadero
(agent_is_alive, ":agent"), #Si es así, sigue adelante; verifica si el agente actual está vivo
(agent_is_non_player, ":agent"), #Si está vivo, comprueba que no es el jugador
(agent_is_human, ":agent"), #Si está vivo y no es el jugador, comprueba que es humano
(agent_get_position, pos0, ":agent"), #Si está vivo, es humano y no es el jugador, obtiene su ubicación y registro para pos0
(get_distance_between_positions_in_meters, ":distance_to_target_pos", pos0, pos1), #Ve qué tan lejos pos0 está de pos1 y registra eso en la variable local
(lt, ":distance_to_target_pos", 10), #Verifica si el agente vivo, no jugador, humano está dentro de 10 metros de pos1
(assign, ":agent_at_target", ":agent"), #Si es así, registra el ID de este agente actual en la variable local "agent_at_target"
(assign, ":break_loop", 1), #Se rompe el ciclo de agente - cambia la variable de prueba para que la prueba de igualdad falle en el siguiente intento
(try_end),
#El bucle se romperá la primera vez que se encuentre un agente vivo, humano y no jugador dentro de los 10 metros de la posición de destino pos1
#El código adicional ahora puede hacer cosas con el agente que se encontró
Otros temas
- Mission Template Triggers:
Un trigger en mission templates (y en module_triggers para el caso) tiene la siguiente forma
Primera parte: con qué frecuencia se verifica el trigger, ya sea en segundos* o como un evento hard-coded como "ti_on_agent_hit"
Segunda parte: la demora, en segundos*, entre leer el bloque de condiciones y ejecutar el bloque de consecuencias. Esto no funciona si se está utilizando un intervalo de evento "palabra"/hard-coded, como "ti_on_agent_hit"
Tercera parte: la demora de rearme, en segundos*, entre el momento en que finalizó el bloqueo de consecuencias y el momento en que se puede volver a verificar el activador. Un retraso especial de rearme "ti_once" se usa para los disparadores que solo deben completarse (pasan su condición y luego ejecutan sus consecuencias) una vez y luego nunca vuelven a disparar.
Cuarta parte: el bloque de condiciones, siempre se ejecuta cuando se llama al activador
Quinta parte: el bloque de consecuencias, solo se ejecuta si el bloque de condiciones no falla, y después de que el intervalo de retraso ha pasado.
*Nota, a diferencia de otros números en el Module System, los 3 intervalos retrasos de los triggers NO necesitan ser números enteros.
En la práctica, luce así:
- Código:
(10, 2, 60,
[
(eq, 1, 1),
],
[
(val_add, "$global_variable", 10),
]),
El bloque de condiciones es una prueba de igualdad simple 1 == 1, que siempre pasará, por lo que el bloque de consecuencias siempre se activará 2 segundos después de que se verifique el trigger. El bloque de consecuencias toma una variable global y la incrementa en 10.
Triggers Temporizados
Comprender el intervalo de verificación/demora/reajuste de triggers temporizados puede ser complicado, así que aquí hay una ilustración:
- Código:
(2, 1, 3, [<una condición que a veces falla, a veces pasa>],
[
#algunas consecuencias
]),
Este trigger, como todos los triggers temporizados con un intervalo de verificación >0, comenzará a verificarse durante el primer segundo de la misión:
- Código:
Second Event
1 Trigger verificado; la condición falla--aplica intervalo de verificación (+2 segundos)
3 Trigger verificado; la condición falla--aplica intervalo de verificación (+2 segundos)
5 Trigger verificado; la condición pasa--aplica retraso de consecuencia (+1 segundo)
6 Consecuencia disparada y completada--aplica intervalo de verificación (+2); aplica retraso de rearme (+3)
11 Trigger verificado; la condición falla--aplica intervalo de verificación (+2 segundos)
13 Trigger verificado; la condición pasa--aplica retraso de consecuencia (+1 segundo)
14 Consecuencia disparada y completada--aplica intervalo de verificación (+2); aplica retraso de rearme (+3)
19 Trigger verificado....
Si uno quiere un trigger completamente "programado", no se pueden usar las demoras de consecuencia y rearme.
Dos triggers del mismo tipo/intervalo [/ i]
Cuando hay dos triggers que tienen el mismo intervalo de comprobación (ya sea en segundos o en un evento ti_*), importa el orden en que aparecen en el mission template. El trigger que aparece primero en la plantilla se disparará primero, seguido por el siguiente trigger del mismo intervalo, y así sucesivamente. Eso significa que si tienes dos triggers que disparan ti_on_agent_spawn, el que aparece primero en el archivo se ejecutará antes que el segundo.
Triggers cerca del inicio de una misión
Lógicamente, el trigger ti_before_mission_start tiene lugar antes de que se configure la escena y antes de que los agentes se generen en la scene. A continuación, el spawneo tiene lugar antes que cualquier otro trigger se dispare--los triggers ti_on_agent_spawn son los únicos triggers que disparan en este punto. A continuación, se dispara ti_after_mission_start , así como cualquier trigger con un intervalo de comprobación de 0 (cada fotograma) cuando se inicia el temporizador de misión. Los triggers basados en eventos no se llamarán hasta que ocurra su evento ti_*; otros triggers temporizados comienzan a llamarse en algún lugar en el primer segundo de la misión, aunque después de ti_after_mission_start se activan y cada cuadro (intervalo de verificación 0) se dispara.
Si tienes triggers que no necesitan disparar tan cerca del inicio de la misión, agregua algo como
- Código:
(store_mission_timer_a, reg0),(gt, reg0, <second-to-start-checking>),
al bloque de condiciones. Ayudará a aliviar la carga de la CPU en el inicio de la misión.
Ni ti_before_mission_start ni ti_after_mission comienzan a necesitar "ti_once" en su retraso de reactivación ya que solo dispararán una vez.
En resumen, al comienzo de una misión tenemos:
ti_before_mission_start
--Spawn-- (ti_on_agent_spawn)
--El tiempo comienza--
ti_after_mission_start y triggers con intervalos de verificación de "0" (que dispara @ un tiempo de misión de aproximadamente 0.2 segundos)
--Triggers Temporizados (aún dentro del primer 1 segundo de la misión)
--Triggers basados en eventos
- Diálogos (por xenoargh):
La primera sección como esta:
- Código:
"[ ...algún código aquí... ]"
...es un bloque que existe para probar una condición o una serie de condiciones.
Su único propósito es regresar VERDADERO o FALSO. Y cada cuadro de diálogo se verifica hasta que se produce un resultado VERDADERO.
En segundo lugar, solo se puede acceder a las partes después del "inicio" SI se produjo el "inicio", su rama está activa, Y su propio bloque de condición es VERDADERO.
Entonces, si quieres algo que siempre sucede cuando el Generalissimo EvilGuy se encuentra con tu jugador un martes, pero quieres que haga otra cosa el miércoles, su "inicio" podría verse así:
- Código:
#Inicio del bloque de diálogo
[
#Mira en header_dialogs para saber lo que puede estar aquí además de "anyone". Todos los cuadros de diálogo deben tener un "inicio".
anyone, "start",
#Empieza el bloque de condición
[
(eq, "$g_talk_troop", "trp_generalissimo_evilguy"),#SOLO SE EJECUTA SI EVILGUY
(try_begin),
(eq, "$g_day_of_week", 2),#HACE ESTO EL MARTES
(assign, "$g_conversation_temp",1),
(str_store_string, s17, "@Este es mi inicio de conversación del martes"),
(else_try),
(eq, "$g_day_of_week", 3),#MIÉRCOLES
(assign, "$g_conversation_temp",2),
(str_store_string, s17, "@Este es mi inicio de conversación del miércoles"),
(else_try),
(assign, "$g_conversation_temp",3),#SI TODO ESO FALLA...
(str_store_string, s17, "@Esto es lo que digo en otro día de la semana"),
(try_end),
],
#La condición es VERDADERA; estamos hablando con EvilGuy, ahora necesitamos nuestro texto personalizado:
"s17",
#Ahora nosotros, el Jugador es el que dice algo, incluso si es algo prosaico como, "leave", para salir de este diálogo.
"player_response_or_responses_here",
#Consecuencias, si hay alguna:
[],
#Fin del bloque de diálogo
],
No me gusta hacer diálogos usando una tropa en ese primer bloque; hace que sea más fácil crear accidentalmente errores de lógica. Prefiero hacerlo en el bloque de Condiciones, así:
- Código:
[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),], "¿Qué puedo hacer por vos {playername}?", "castellan_talk",[]],
Eso significa que puedo tener múltiples salidas para esa Tropa, dependiendo del bloque de condiciones que se vuelva VERDADERO, así:
- Código:
[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 1),
], "¿Qué puedo hacer por vos {playername}?", "castellan_talk",[]],
[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 2),
], "Oh, es {playername}, ese diablo escuálido, ¡Viene a molestarme otra vez!", "castellan_talk",[]],
La única gran excepción a esto son encuentros de parties en los que puede estar hablando con cualquier tipo aleatorio en la party, por ejemplo, el inicio del saqueador:
- Código:
[party_tpl|pt_looters|auto_proceed,"start", [(eq,"$talk_context",tc_party_encounter),(encountered_party_is_attacker),], "{!}Warning: This line should never be displayed.", "looters_1",[
(str_store_string, s11, "@La bolsa o la vida, {compañero/niña}. Nada de movimientos bruscos o te asesino."),
(str_store_string, s12, "@Por suerte para ti, me pillaste de buen humor. Danos todo tu oro y tal vez te deje vivir."),
(str_store_string, s13, "@Esto es un robo, ¿eh? Te doy una oportunidad de entregar todo lo que tienes, o yo y mis compañeros te mataremos. ¿Entiendes?"),
(store_random_in_range, ":random", 11, 14),
(str_store_string_reg, s4, ":random"),
(play_sound, "snd_encounter_looters")
]],
Ten en cuenta que no hay dos sino cuatro cosas que deben ser VERDADERAS:
1. Es un encuentro de parties en el mapamundi.
2. La Party_Template es pt_looters.
3. $talk_context == tc_party_encounter.
4. encounter_party_is_attacker debe ser VERDADERO
Si algún compañero modder del foro nota algún error que yo haya tenido al traducir la guía, o quiere agregar información, por favor comentelo en este tema, así hago las correcciones necesarias
Última edición por Hernanxd16 el Lun Ene 29, 2018 2:08 pm, editado 1 vez