41 |
|
|
42 |
Contiene los siguientes atributos: |
Contiene los siguientes atributos: |
43 |
|
|
44 |
- __ruta_conversacion (path): la conversacion que es capaz de mantener el PNJ |
- __id_conversacion: nombre de la conversacion que es capaz de mantener el PNJ |
45 |
- __personaje (Personaje): el personaje que controlamos |
- __personaje (Personaje): el personaje que controlamos |
46 |
- estado (string): el estado en el que se encuentra el interfaz |
- estado (string): el estado en el que se encuentra el interfaz |
47 |
- sub_estado (int): algunos estados tienen fases intermedias. Si no, None. |
- sub_estado (int): algunos estados tienen fases intermedias. Si no, None. |
54 |
|
|
55 |
- personaje: se modifica tambi�n al personaje para que tenga a este interfaz como |
- personaje: se modifica tambi�n al personaje para que tenga a este interfaz como |
56 |
controlador |
controlador |
57 |
|
|
58 |
Invariantes: |
Invariantes: |
59 |
_conversacion != None |
_conversacion != None |
60 |
__personaje != None |
__personaje != None |
61 |
len(_visitas) >= 0 |
len(_visitas) >= 0 |
62 |
len(cronos) >= 0 |
len(cronos) >= 0 |
63 |
len(_fragmentos) >= 0 |
len(_fragmentos) >= 0 |
64 |
self.visita[i].activo <--> len(self._fragmentos) > 0 |
self.visita[i].activo <--> len(self._fragmentos) > 0 |
65 |
""" |
""" |
66 |
|
|
67 |
VERSION_DESC_CONVERSACIONES = '2.0.0' |
VERSION_DESC_CONVERSACIONES = '2.0.0' |
68 |
|
|
69 |
|
|
70 |
DIR_DESC_CONVERSACIONES = 'conversaciones' |
DIR_DESC_CONVERSACIONES = 'conversaciones' |
71 |
|
DIR_DESC_GENERICAS = 'generico' |
72 |
DIR_DESC_ESPECIALES = 'modulos_especiales' |
DIR_DESC_ESPECIALES = 'modulos_especiales' |
73 |
EXT_DESC_CONVERSACIONES = '.xml' |
EXT_DESC_CONVERSACIONES = '.xml' |
74 |
|
|
83 |
self.__temporizador = None |
self.__temporizador = None |
84 |
self.__interlocutor = None |
self.__interlocutor = None |
85 |
self.__velocidad = 0.05 # TO DO: parametrizar la velocidad de reaccion del PNJ |
self.__velocidad = 0.05 # TO DO: parametrizar la velocidad de reaccion del PNJ |
86 |
self.__keywords = [] # TO DO: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs |
self.__keywords = [] # TO DO: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs |
87 |
|
|
88 |
self.__ruta_conversacion = os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_CONVERSACIONES) |
self.id_conversacion = id_conversacion |
89 |
self.__ruta_especiales = os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_ESPECIALES) |
self.__guion_activo = None |
|
self.__guion_activo = None |
|
90 |
|
|
91 |
self.__temas = {} |
self.__temas = {} |
92 |
self.__temas_especiales = {} |
self.__temas_especiales = {} |
93 |
for tema in InterfazPNJHablante.TEMAS_ESPECIALES: |
self.__temas_genericos = {} |
94 |
if not self.__encuentra_tema_especial(tema): |
|
95 |
trazar("No esta definido el tema especial de tipo " + tema, "pnj") |
for tema in InterfazPNJHablante.TEMAS_ESPECIALES: |
96 |
else: |
if not self.__encuentra_tema_especial(tema): |
97 |
trazar("Inicializado el tema " + tema, "pnj") |
trazar("No esta definido el tema especial de tipo " + tema, "pnj") |
98 |
|
else: |
99 |
|
trazar("Inicializado el tema " + tema, "pnj") |
100 |
|
|
101 |
|
def __ruta_conversacion(self, id_conversacion=None): |
102 |
|
if not id_conversacion: id_conversacion = self.id_conversacion |
103 |
|
return os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_CONVERSACIONES) |
104 |
|
|
105 |
|
def __ruta_especial(self, id_conversacion=None): |
106 |
|
if not id_conversacion: id_conversacion = self.id_conversacion |
107 |
|
return os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_ESPECIALES) |
108 |
|
|
109 |
def __setattr__(self, name, value): |
def __setattr__(self, name, value): |
110 |
"""Realiza ajustes autom�ticos al modificar cualquier par�metro""" |
"""Realiza ajustes autom�ticos al modificar cualquier par�metro""" |
111 |
|
|
119 |
else: |
else: |
120 |
# En otro caso, asignar y ya esta |
# En otro caso, asignar y ya esta |
121 |
self.__dict__[name] = value |
self.__dict__[name] = value |
122 |
|
|
123 |
# Mé�todos privados |
# Mé�todos privados |
124 |
|
|
125 |
def __encuentra_tema(self, nombretema, ruta = None): |
def __encuentra_tema(self, nombretema, ruta = None): |
126 |
"""Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas.""" |
"""Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas.""" |
127 |
|
|
128 |
tema = self.__temas.get(nombretema, None) |
tema = self.__temas.get(nombretema, None) |
129 |
if tema: return tema |
if tema: return tema |
130 |
|
|
131 |
if not ruta: ruta = self.__ruta_conversacion |
if not ruta: ruta = self.__ruta_conversacion() |
132 |
try: |
try: |
133 |
self.__cargar_modulo(nombretema) |
self.__temas[nombretema] = self.__cargar_modulo(nombretema) |
134 |
except IOError: |
except IOError: |
135 |
return None |
return None |
136 |
|
|
137 |
return self.__temas.get(nombretema, None) |
return self.__temas.get(nombretema, None) |
138 |
|
|
139 |
def __encuentra_tema_especial(self, nombretema, ruta = None): |
def __encuentra_tema_especial(self, nombretema, ruta = None): |
140 |
"""Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas.""" |
"""Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas.""" |
141 |
|
|
142 |
tema = self.__temas_especiales.get(nombretema, None) |
tema = self.__temas_especiales.get(nombretema, None) |
143 |
if tema: |
if tema: |
144 |
return tema |
return tema |
145 |
|
|
146 |
if not ruta: ruta = self.__ruta_especiales |
if not ruta: ruta = self.__ruta_especial() |
147 |
try: |
try: |
148 |
self.__cargar_modulo(nombretema, ruta, especial=1) |
self.__temas_especiales[nombretema] = self.__cargar_modulo(nombretema, ruta) |
149 |
|
except IOError: |
150 |
|
return None |
151 |
|
|
152 |
|
return self.__temas_especiales.get(nombretema, None) |
153 |
|
|
154 |
|
def __encuentra_tema_generico(self, nombretema, ruta = None): |
155 |
|
"""Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas.""" |
156 |
|
|
157 |
|
tema = self.__temas_genericos.get(nombretema, None) |
158 |
|
if tema: |
159 |
|
return tema |
160 |
|
|
161 |
|
if not ruta: ruta = self.__ruta_especial(InterfazPNJHablante.DIR_DESC_GENERICAS) |
162 |
|
try: |
163 |
|
self.__temas_genericos[nombretema] = self.__cargar_modulo(nombretema, ruta) |
164 |
except IOError: |
except IOError: |
165 |
return None |
return None |
166 |
|
|
167 |
return self.__temas.get(nombretema, None) |
return self.__temas_genericos.get(nombretema, None) |
168 |
|
|
169 |
|
|
170 |
def __cargar_modulo(self, id_modulo, ruta=None, especial=0): |
def __cargar_modulo(self, id_modulo, ruta=None): |
171 |
#archivo = os.path.join(Modulo.DIR_DESC_MODULOS, "prueba" + Modulo.EXT_DESC_MODULOS) |
#archivo = os.path.join(Modulo.DIR_DESC_MODULOS, "prueba" + Modulo.EXT_DESC_MODULOS) |
172 |
if not ruta: ruta = self.__ruta_conversacion |
if not ruta: ruta = self.__ruta_conversacion() |
173 |
archivo = os.path.join(ruta, id_modulo + InterfazPNJHablante.EXT_DESC_CONVERSACIONES) |
archivo = os.path.join(ruta, id_modulo + InterfazPNJHablante.EXT_DESC_CONVERSACIONES) |
174 |
arbol_xml = minidom.parse(archivo) |
arbol_xml = minidom.parse(archivo) |
175 |
elem_desc_modulo = arbol_xml.documentElement |
elem_desc_modulo = arbol_xml.documentElement |
176 |
macros = [ ("$interlocutor", "Personaje"), |
macros = [ ("$interlocutor", "Personaje"), |
177 |
("$tutora", "Personaje"), |
("$pnj", "Personaje"), |
178 |
("$sala", "Sala")]#, |
("$sala", "Sala")]#, |
179 |
# ("$tema", "Modulo") ] |
# ("$tema", "Modulo") ] |
180 |
|
|
181 |
macros_extra = {} |
macros_extra = {} |
182 |
if not especial: |
return Modulo(elem_desc_modulo, macros, macros_extra) |
183 |
self.__temas[id_modulo] = Modulo(elem_desc_modulo, macros, macros_extra) |
|
184 |
else: |
if not especial: |
185 |
self.__temas_especiales[id_modulo] = Modulo(elem_desc_modulo, macros, macros_extra) |
self.__temas[id_modulo] = Modulo(elem_desc_modulo, macros, macros_extra) |
186 |
|
else: |
187 |
|
self.__temas_especiales[id_modulo] = Modulo(elem_desc_modulo, macros, macros_extra) |
188 |
return |
return |
189 |
|
|
190 |
|
|
191 |
def __fallar(self, tema, macros): |
def __fallar(self, nombretema, macros): |
192 |
|
tema = self.__temas_especiales[nombretema] |
193 |
guion_equivocado = tema.mejor_guion() |
guion_equivocado = tema.mejor_guion() |
194 |
if not guion_equivocado: raise Exception("Error en modulo de conversacion: no quedan guiones activos en el tema por defecto.") |
if not guion_equivocado: |
195 |
|
tema = self.__encuentra_tema_generico(nombretema) |
196 |
|
guion_equivocado = tema.mejor_guion() |
197 |
|
if not guion_equivocado: |
198 |
|
raise Exception("Error en modulo de conversacion: no quedan guiones activos en el tema por defecto.") |
199 |
guion_equivocado.ejecutar(macros) |
guion_equivocado.ejecutar(macros) |
200 |
self.__reanudar_iniciativa() |
self.__reanudar_iniciativa() |
201 |
|
|
202 |
|
|
203 |
def __temporizar_nueva_iniciativa(self): |
def __temporizar_nueva_iniciativa(self): |
204 |
"""Establece el tiempo necesario para activar la iniciativa""" |
"""Establece el tiempo necesario para activar la iniciativa""" |
205 |
from mundo import Mundo |
from mundo import Mundo |
206 |
try: |
try: |
207 |
if self.__temporizador: |
if self.__temporizador: |
208 |
self.__temporizador.parar() |
self.__temporizador.parar() |
209 |
activacion = FechaHoraMineRel( segundos = (random() + 0.5) * self.__velocidad * FechaHoraMine.SEGUNDOS_POR_HORA ) |
activacion = FechaHoraMineRel( segundos = (random() + 0.5) * self.__velocidad * FechaHoraMine.SEGUNDOS_POR_HORA ) |
210 |
trazar("Activada iniciativa para dentro de %s horas Mine" % repr(activacion), "pnj", 2) |
trazar("Activada iniciativa para dentro de %s horas Mine" % repr(activacion), "pnj", 2) |
211 |
self.__temporizador = CronoMine(Mundo.cronoMine_control, activacion, lambda x: self.__disparar_iniciativa()) |
self.__temporizador = CronoMine(Mundo.cronoMine_control, activacion, lambda x: self.__disparar_iniciativa()) |
212 |
except: |
except: |
213 |
escribir("Error inesperado al temporizar la iniciativa") |
escribir("Error inesperado al temporizar la iniciativa") |
214 |
|
|
215 |
|
|
216 |
def __pausar_iniciativa(self): |
def __pausar_iniciativa(self): |
217 |
if self.__temporizador: |
if self.__temporizador: |
218 |
try: |
try: |
219 |
trazar("Pausando la iniciativa del pnj", "pnj", 2) |
trazar("Pausando la iniciativa del pnj", "pnj", 2) |
220 |
# TO DO: comprobar que no se pausa repetidas veces. |
# TO DO: comprobar que no se pausa repetidas veces. |
221 |
self.__temporizador.pausar() |
self.__temporizador.pausar() |
222 |
except: |
except: |
223 |
escribir("Error inesperado al pausar la iniciativa") |
escribir("Error inesperado al pausar la iniciativa") |
224 |
|
|
225 |
def __reanudar_iniciativa(self): |
def __reanudar_iniciativa(self): |
226 |
if self.__temporizador: |
if self.__temporizador: |
227 |
try: |
try: |
228 |
trazar("Reanudando la iniciativa del pnj", "pnj", 2) |
trazar("Reanudando la iniciativa del pnj", "pnj", 2) |
229 |
self.__temporizador.reanudar() |
self.__temporizador.reanudar() |
230 |
except: |
except: |
231 |
escribir("Error inesperado al reanudar la iniciativa") |
escribir("Error inesperado al reanudar la iniciativa") |
232 |
|
|
233 |
def __disparar_iniciativa(self): |
def __disparar_iniciativa(self): |
234 |
"""Activa el modulo de iniciativa propia de la conversacion""" |
"""Activa el modulo de iniciativa propia de la conversacion""" |
235 |
trazar("Activando iniciativa del pnj", "pnj") |
trazar("Activando iniciativa del pnj", "pnj") |
236 |
iniciativa = self.__encuentra_tema_especial("iniciativa_propia") |
iniciativa = self.__encuentra_tema_especial("iniciativa_propia") |
237 |
if iniciativa: |
if iniciativa: |
238 |
macros = { "$interlocutor": self.__interlocutor, |
macros = { "$interlocutor": self.__interlocutor, |
239 |
"$tutora": self.__personaje, |
"$pnj": self.__personaje, |
240 |
"$sala": self.__personaje.sala } |
"$sala": self.__personaje.sala } |
241 |
iniciativa.ejecutar(macros) |
iniciativa.ejecutar(macros) |
242 |
self.__temporizador = None |
self.__temporizador = None |
243 |
self.__temporizar_nueva_iniciativa() |
self.__temporizar_nueva_iniciativa() |
244 |
else: |
else: |
245 |
self.__temporizador = None |
self.__temporizador = None |
246 |
|
|
247 |
|
|
248 |
# ------------------------------------------------- |
# ------------------------------------------------- |
252 |
def conversa_personaje(self, personaje, comando, nombretema): |
def conversa_personaje(self, personaje, comando, nombretema): |
253 |
"""Metodo llamado cuando otro personaje habla con este PNJ""" |
"""Metodo llamado cuando otro personaje habla con este PNJ""" |
254 |
|
|
255 |
|
# LOGICA PARA CANCELAR +HABLAR CUANDO HABLA CON OTRO PNJ DE MAYOR CARISMA |
256 |
|
# if visita.activo: |
257 |
|
# if visita.pj_activo.atrib["carisma"] > personaje.atrib["carisma"] : #visita.pj_activo.aura > personaje.controlador._DialogoNormal__personaje.aura: |
258 |
|
# personaje.controlador.conn().send(visita.pnj.nombre +" est� hablando con " + visita.pj_activo.nombre) |
259 |
|
# return |
260 |
|
# else: |
261 |
|
# self.anular_conversacion() |
262 |
|
|
263 |
self.__interlocutor = personaje |
self.__interlocutor = personaje |
264 |
self.__pausar_iniciativa() |
self.__pausar_iniciativa() |
265 |
macros = { "$interlocutor": personaje, |
macros = { "$interlocutor": personaje, |
266 |
"$tutora": self.__personaje, |
"$pnj": self.__personaje, |
267 |
"$sala": personaje.sala } |
"$sala": personaje.sala } |
268 |
|
|
269 |
if nombretema in InterfazPNJHablante.TEMAS_ESPECIALES or \ |
if nombretema in InterfazPNJHablante.TEMAS_ESPECIALES or \ |
270 |
not self.__encuentra_tema(nombretema): |
not self.__encuentra_tema(nombretema): |
271 |
trazar("No existe el tema %s" % nombretema, "pnj") |
trazar("No existe el tema %s" % nombretema, "pnj") |
272 |
self.__fallar(self.__temas_especiales["tema_no_encontrado"], macros) |
self.__fallar("tema_no_encontrado", macros) |
273 |
else: |
else: |
274 |
tema = self.__temas[nombretema] |
tema = self.__temas[nombretema] |
275 |
guion = tema.mejor_guion() |
guion = tema.mejor_guion() |
276 |
if guion: |
if guion: |
277 |
# Nueva conversacion con exito |
# Nueva conversacion con exito |
278 |
guion.ejecutar(macros) |
guion.ejecutar(macros) |
279 |
self.__reanudar_iniciativa() |
self.__reanudar_iniciativa() |
280 |
self.__temporizar_nueva_iniciativa() |
self.__temporizar_nueva_iniciativa() |
281 |
else: |
else: |
282 |
trazar("El tema %s está agotado" % nombretema, "pnj") |
trazar("El tema %s está agotado" % nombretema, "pnj") |
283 |
self.__fallar(self.__temas_especiales["tema_agotado"], macros) |
self.__fallar("tema_agotado", macros) |
284 |
|
|
285 |
trazar("Ejecutado el tema " + nombretema, "pnj") |
trazar("Ejecutado el tema " + nombretema, "pnj") |
286 |
#self._modulo.ejecutar(None) |
#self._modulo.ejecutar(None) |
287 |
|
|
288 |
return |
return |
289 |
|
|
290 |
visita = self._visitas[0] |
visita = self._visitas[0] |
291 |
if visita.activo: |
if visita.activo: |
292 |
if visita.pj_activo.atrib["carisma"] > personaje.atrib["carisma"] : #visita.pj_activo.aura > personaje.controlador._DialogoNormal__personaje.aura: |
if visita.pj_activo.atrib["carisma"] > personaje.atrib["carisma"] : #visita.pj_activo.aura > personaje.controlador._DialogoNormal__personaje.aura: |
293 |
personaje.controlador.conn().send(visita.pnj.nombre +" est� hablando con " + visita.pj_activo.nombre) |
personaje.controlador.conn().send(visita.pnj.nombre +" est� hablando con " + visita.pj_activo.nombre) |
294 |
return |
return |
295 |
else: |
else: |
296 |
self.anular_conversacion() |
self.anular_conversacion() |
297 |
respuesta = visita.responder(personaje, comando, nombretema.lower()) |
respuesta = visita.responder(personaje, comando, nombretema.lower()) |
298 |
escribir("Tema " + visita.estado()) |
escribir("Tema " + visita.estado()) |
299 |
#print respuesta[len(respuesta)-1] |
#print respuesta[len(respuesta)-1] |
300 |
if visita.estado() == Visita.ENCONTRADO or visita.estado() == Visita.OFFTOPIC: |
if visita.estado() == Visita.ENCONTRADO or visita.estado() == Visita.OFFTOPIC: |
301 |
visita.activar(respuesta) |
visita.activar(respuesta) |
302 |
|
|
303 |
nombre = visita.pnj.nombre[0].lower() + visita.pnj.nombre[1:] |
nombre = visita.pnj.nombre[0].lower() + visita.pnj.nombre[1:] |
304 |
if comando == "decir": |
if comando == "decir": |
305 |
visita.pj_activo.actuar("dice a " + nombre + " algo acerca de '" + nombretema + "'") |
visita.pj_activo.actuar("dice a " + nombre + " algo acerca de '" + nombretema + "'") |
306 |
personaje.controlador.conn().send("Dices a " + nombre + " algo sobre '" + nombretema + "'") |
personaje.controlador.conn().send("Dices a " + nombre + " algo sobre '" + nombretema + "'") |
307 |
else: |
else: |
308 |
visita.pj_activo.actuar("pregunta a " + nombre + " acerca de '" + nombretema + "'") |
visita.pj_activo.actuar("pregunta a " + nombre + " acerca de '" + nombretema + "'") |
309 |
personaje.controlador.conn().send("Preguntas a " + nombre + " sobre '" + nombretema + "'") |
personaje.controlador.conn().send("Preguntas a " + nombre + " sobre '" + nombretema + "'") |
310 |
|
|
311 |
|
|
312 |
def controlar_personaje(self, personaje): |
def controlar_personaje(self, personaje): |
313 |
"""Pasar a estar controlando un personaje. |
"""Pasar a estar controlando un personaje. |
314 |
|
|
315 |
Requiere: |
Requiere: |
316 |
self.__personaje == None |
self.__personaje == None |
317 |
personaje != None |
personaje != None |
318 |
""" |
""" |
319 |
self.__personaje = personaje |
self.__personaje = personaje |
320 |
|
|
321 |
|
|
322 |
|
|
323 |
def anular_conversacion(self): |
def anular_conversacion(self): |
324 |
self._fragmentos = [] |
self._fragmentos = [] |
325 |
self._visitas[0].activo = None |
self._visitas[0].activo = None |
326 |
for crono in self.cronos: |
for crono in self.cronos: |
327 |
# Anula los cronos que no se han ejecutado a�n, asignandoles la funci�n nula |
# Anula los cronos que no se han ejecutado a�n, asignandoles la funci�n nula |
328 |
crono.vencimiento_func = lambda x: None |
crono.vencimiento_func = lambda x: None |
329 |
|
|
330 |
|
|
343 |
"""Si no quedan personajes, desactiva el temporizador de iniciativa""" |
"""Si no quedan personajes, desactiva el temporizador de iniciativa""" |
344 |
|
|
345 |
if personaje == self.__interlocutor: |
if personaje == self.__interlocutor: |
346 |
self.__interlocutor = None |
self.__interlocutor = None |
347 |
if self.__guion_activo: |
if self.__guion_activo: |
348 |
trazar("Cancelando la conversacion por abandono del interlocutor", "pnj") |
trazar("Cancelando la conversacion por abandono del interlocutor", "pnj") |
349 |
self.__guion_activo.cancelar() |
self.__guion_activo.cancelar() |
350 |
|
|
351 |
pjs = [ personaje for personaje in self.__personaje.sala.personajes.values() \ |
pjs = [ personaje for personaje in self.__personaje.sala.personajes.values() \ |
352 |
if isinstance(personaje.controlador, InterfazPNJHablante) ] |
if isinstance(personaje.controlador, InterfazPNJHablante) ] |
353 |
trazar("Quedan %s personajes jugadores en la sala" % len(pjs), "pnj", 2) |
trazar("Quedan %s personajes jugadores en la sala" % len(pjs), "pnj", 2) |
354 |
if len(pjs) == 1: |
if len(pjs) == 1: |
355 |
trazar("Desactivando la iniciativa del pnj", "pnj", 2) |
trazar("Desactivando la iniciativa del pnj", "pnj", 2) |
356 |
if self.__temporizador: |
if self.__temporizador: |
357 |
try: |
try: |
358 |
self.__temporizador.parar() |
self.__temporizador.parar() |
359 |
self.__temporizador = None |
self.__temporizador = None |
360 |
except: |
except: |
361 |
escribir("Error inesperado al pausar la iniciativa") |
escribir("Error inesperado al pausar la iniciativa") |
362 |
|
|
363 |
|
|
364 |
def habla_personaje(self, personaje, mensaje): |
def habla_personaje(self, personaje, mensaje): |
366 |
pass |
pass |
367 |
# TO DO: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs |
# TO DO: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs |
368 |
for palabra in mensaje.split(): |
for palabra in mensaje.split(): |
369 |
if palabra in self.__keywords: |
if palabra in self.__keywords: |
370 |
self.__reaccionar(palabra) |
self.__reaccionar(palabra) |
371 |
|
|
372 |
|
|
373 |
|
|