#!/usr/bin/env python # #Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media] #Copyright (C) 2002 Pablo Ruiz Múzquiz # # #This program is free software; you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by #the Free Software Foundation; either version 2 of the License, or #(at your option) any later version. # #This program is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. # #You should have received a copy of the GNU General Public License #along with this program; if not, write to the Free Software #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # #Fichero: objeto.py """Clase Objeto.""" from xml.dom import minidom import random import sys import string import os import cPickle from personaje import * from personajejugador import * from personajenojugador import * from interfazpnj import * class Objeto(object): """Un objeto dentro del juego. Al instanciar un objeto, se crea nuevo y se lee desde el fichero de descripcion de objeto, poniendolo en su estado inicial. Esta instancia podrá pasar a manos de pjs y pnjs, y se puede salvar a disco junto con la sala o el personaje que lo contiene. """ # Version del sistema de descripciones de objeto (se incrementa cada vez que cambia # el formato de los ficheros de desc_objetos) VERSION_DESC_OBJETO = '1.0' # Version del sistema de objetos (se incrementa cada vez que varian los atributos # de la clase Objeto) VERSION_OBJETO = '1.0.1' DIR_DESC_OBJETOS = 'desc_objetos' EXT_DESC_OBJETOS = '.xml' TIPOS_DE_MATERIAL = [ 'madera', 'metal', 'cristal', 'piel', 'tejido', 'piedra', 'organico', 'cera', 'liquido', 'barro' ] CATEGORIAS = [ 'mobiliario', 'arma', 'recipiente', 'joya', 'armadura', 'herramienta', 'vestimenta', 'escritura', 'recipìente', 'comestible', 'accesorio' ] USOS = [ 'comer', 'beber', 'poner', 'lanzar', 'usar' ] POSICIONES = [ 'cabeza', 'cuello', 'tronco', 'brazo-izq', 'brazo-der', 'mano-izq', 'mano-der', 'dos-manos', 'piernas', 'tobillo-izq', 'tobillo-der', 'pie-izq', 'pie-der' ] # Atributos públicos de la clase def __leer_id(self): return self.__id id = property(__leer_id, doc="""(string): identifica univocamente al objeto. No puede contener espacios. Invariantes: id != None """) def __leer_version(self): return self.__version version = property(__leer_version, doc="""(int): la versión del sistema de objetos. Invariantes: version != None """) def __leer_autor(self): return self.__autor autor = property(__leer_autor, doc="""autor (string): identifica al autor del objeto. Invariantes: autor != None """) def __leer_comentario(self): return self.__comentario comentario = property(__leer_comentario, doc="""(string): un metacomentario acerca del objeto. Invariantes: comentario != None """) def __leer_area(self): return self.__area area = property(__leer_area, doc="""(string): el area a la que pertenece el objeto, si es que lo necesita. Invariantes: area != None """) def __leer_nombre(self): return self.__nombre nombre = property(__leer_nombre, doc="""(string): Una breve frase informando de qué es el objeto. Invariantes: nombre != None """) def __leer_descripcion(self): return self.__descripcion descripcion = property(__leer_descripcion, doc="""({String:any}[]): descripción del objeto. Es una lista de diccionarios, conteniendo cada uno: - "dificultad" (int): de 1 a 100 - "texto" (string): el contenido del item de descripcion Invariantes: descripcion != None len(descripcion) >= 0 descripcion[i]["dificultad"] in range(0,100) descripcion[i]["texto"] != None """) def __leer_tipo(self): return self.__tipo tipo = property(__leer_tipo, doc="""(string): el tipo de material del objeto. Invariantes: tipo != None tipo in TIPOS_DE_MATERIAL """) def __leer_categoria(self): return self.__categoria categoria = property(__leer_categoria, doc="""(string): el tipo de uso generico del objeto. Invariantes: categoria != None categoria in CATEGORIAS """) def __leer_peso(self): return self.__peso peso = property(__leer_peso, doc="""(float): peso en kilos. Invariantes: peso > 0 """) def __leer_volumen(self): return self.__volumen volumen = property(__leer_volumen, doc="""(float): volumen en relación a 1 unidad de persona. Invariantes: volumen > 0 """) def __leer_cargas(self): return self.__cargas cargas = property(__leer_cargas, doc="""(int): el número de veces que puede ser usado antes de destruirse o volverse inservible. El valor 0 indica que no tiene limite. Invariantes: cargas >= 0 """) def __leer_valor(self): return self.__valor valor = property(__leer_valor, doc="""(int): en monedas de estaño. Invariantes: valor > 0 """) def __leer_estado(self): return self.__estado estado = property(__leer_estado, doc="""(int): de 0 a 100. Invariantes: estado in range(0,100) """) def __leer_aura(self): return self.__aura aura = property(__leer_aura, doc="""(int): de 0 a 100. Invariantes: aura in range(0,100) """) def __leer_usos(self): return self.__usos usos = property(__leer_usos, doc="""({string:any}[]): lista de usos del objeto. Cada uso es un diccionario: - "tipo" (string): el tipo de uso ("comer", "poner", "lanzar", etc.) - "posicion" (string): si el uso es "poner", en que parte del cuerpo se pone - "gasto" (int): el numero de cargas que consume cada uso - "msg" (string): el mensaje que aparece cuando se usa - "duracion" (int): el numero de segundos que dura el efecto (0 = indeterminado) - "requisitos" ({string:any}[]): lista de requisitos del uso, siendo cada requisito un diccionario: - "atributo" (string): el atributo a comprobar - "valor" (int): el valor mínimo que deberá tener el atributo - "msg" (string): el mensaje que sale si el atributo no llega al mínimo - "efectos" ({string:any}[]): lista de efectos que produce el uso, siendo cada efecto un diccionario: - "atributo" (string): el atributo a modificar - "valor" (int): el número de puntos que sube o baja (si es negativo) - "duracion" (int): duración del efecto en segundos (0 = ilimitado) - "msg" (string): el mensaje que sale cuando ocurre el efecto Invariantes: usos != None len(usos) >= 0 usos[i]["tipo"] in USOS usos[i]["posicion"] in POSICIONES usos[i]["gasto"] >= 0 usos[i]["msg"] != None usos[i]["duracion"] >= 0 usos[i]["requisitos"] != None usos[i]["requisitos"][j]["atributo"] != None usos[i]["requisitos"][j]["valor"] in range(0,100) usos[i]["requisitos"][j]["msg"] != None usos[i]["efectos"] != None usos[i]["efectos"][k]["atributo"] != None usos[i]["efectos"][k]["duracion"] >= 0 usos[i]["efectos"][k]["msg"] != None """) # Constructor def __init__(self, id, conservar_arbol_xml = 0): """Crea un objeto nuevo a partir de un fichero de descripcion de objeto. Requiere: id != None id corresponde con un fichero existente """ # Atributos privados: # # __fecha_fichero (int): fecha de la ultima modificacion del fichero de descripción # # Invariantes: # __fecha_fichero >= 0 self.__id = id self.__inicializar_objeto(conservar_arbol_xml) def __inicializar_objeto(self,conservar_arbol_xml): """Rellena todos los datos del objeto a partir de su fichero de descripcion.""" archivo = os.path.join(Objeto.DIR_DESC_OBJETOS, self.__id + Objeto.EXT_DESC_OBJETOS) self.__fecha_fichero = os.path.getmtime(archivo) arbol_xml = minidom.parse(archivo) elem_objeto = arbol_xml.documentElement self.__parsear_objeto(elem_objeto) elem_id = elem_objeto.getElementsByTagName('id')[0] self.__parsear_id(elem_id) elem_nombre = elem_objeto.getElementsByTagName('nombre')[0] self.__parsear_nombre(elem_nombre) elem_descripcion = elem_objeto.getElementsByTagName('descripcion')[0] self.__parsear_descripcion(elem_descripcion) elem_propiedades = elem_objeto.getElementsByTagName('propiedades')[0] self.__parsear_propiedades(elem_propiedades) elem_usos = elem_objeto.getElementsByTagName('usos')[0] self.__parsear_propiedades(elem_usos) #self.__iniciar_parseador(doc_xml) #self.__parsear_objeto(doc_xml) #self.__parsear_id(doc_xml) #self.__parsear_nombre(doc_xml) #self.__parsear_descripcion(doc_xml) #self.__parsear_propiedades(doc_xml) #self.__parsear_usos(doc_xml) if conservar_arbol_xml: self.__arbol_xml = arbol_xml def __parsear_objeto(self, elem_objeto): """Extrae la información del elemento objeto: autor, version, comentario, area.""" version_desc_objeto = extraer_atributo_xml(elem_objeto,'version') if version_desc_objeto != Objeto.VERSION_DESC_OBJETO: raise ValueError("Version incorrecta del fichero descriptor de objeto " + self.__id + ": " + version_desc_objeto + " cuando se esperaba " + Objeto.VERSION_DESC_OBJETO) self.__version = Objeto.VERSION_OBJETO self.__autor = extraer_atributo_xml(elem_objeto,'autor') if elem_objeto.hasAttribute('area'): self.__area = extraer_atributo_xml(elem_objeto,'area') else: self.__area = "" if elem_objeto.hasAttribute('comentario'): self.__comentario = extraer_atributo_xml(elem_objeto,'comentario') else: self.__comentario = "" def __parsear_id(self, elem_id): """Extrae el id del objeto""" id_fichero = extraer_contenido_xml(elem_id) if id_fichero != self.__id: raise ValueError("El id no coincide con el nombre del fichero") def __parsear_nombre(self, elem_nombre): """Extrae el nombre del objeto""" self.__nombre = extraer_contenido_xml(elem_nombre) def __parsear_descripcion(self, elem_descripcion): """Extrae el texto y la dificultad asociada de los items de descripcion del objeto""" self.__descripcion = [] elems_item = elem_descripcion.getElementsByTagName('item') for elem_item in elems_item: texto_item = extraer_contenido_xml(elem_item) texto_item = " ".join(texto_item.split()) # elimina todos los espacios redundantes if elem_item.hasAttribute('dificultad'): try: dif_item = int(extraer_atributo_xml(elem_item,'dificultad')) except ValueError: raise ValueError("Error en objeto " + self.__id + ". La dificultad de la descripción tiene que ser un número.") if not dif_item in range(0, 101): raise ValueError("Error en objeto " + self.__id + ". La dificultad de la descripción tiene que ser de 0 a 100.") else: dif_item = 0 self.__descripcion.append({"dificultad": dif_item, "texto": texto_item}) def __parsear_propiedades(self, elem_propiedades): """Extrae la información de las propiedades generales: tipo, categoria, peso, volumen, cargas, valor, estado, aura.""" if elem_propiedades.hasAttribute('tipo'): self.__tipo = extraer_atributo_xml(elem_propiedades,'tipo') else: self.__tipo="" if elem_propiedades.hasAttribute('categoria'): self.__categoria = extraer_atributo_xml(elem_propiedades,'categoria') else: self.__categoria="" if elem_propiedades.hasAttribute('peso'): self.__peso = extraer_atributo_xml(elem_propiedades,'peso') else: self.__peso="" if elem_propiedades.hasAttribute('volumen'): self.__volumen = float(extraer_atributo_xml(elem_propiedades,'volumen')) else: self.__volumen=0 if elem_propiedades.hasAttribute('cargas'): self.__cargas = int(extraer_atributo_xml(elem_propiedades,'cargas')) else: self.__cargas = 1 if elem_propiedades.hasAttribute('valor'): self.__valor = int(extraer_atributo_xml(elem_propiedades,'valor')) else: self.__valor = 1 if elem_propiedades.hasAttribute('estado'): self.__estado = int(extraer_atributo_xml(elem_propiedades,'estado')) else: self.__estado = 0 if elem_propiedades.hasAttribute('aura'): self.__aura = int(extraer_atributo_xml(elem_propiedades,'aura')) else: self.__aura = 1 def __parsear_usos(self, elem_usos): """Extrae toda la información de los usos del objeto.""" limpiar_nodos_text_xml(elem_usos) self.__usos = [] for nodo_uso in elem_usos.childNodes[:]: uso_tipo = extraer_atributo_xml(nodo_uso,'tipo') if nodo_uso.hasAttribute('posicion'): uso_posicion = extraer_atributo_xml(nodo_uso,'posicion') else: uso_posicion = '' if nodo_uso.hasAttribute('gasto'): uso_gasto = int(extraer_atributo_xml(nodo_uso,'gasto')) else: uso_gasto = 0 if nodo_uso.hasAttribute('msg'): uso_msg = extraer_atributo_xml(nodo_uso,'msg') else: if uso_tipo == 'comer': uso_msg = 'Te comes ' + self.__nombre elif uso_tipo == 'beber': uso_msg = 'Bebes ' + self.__nombre elif uso_tipo == 'poner': uso_msg = 'Te pones ' + self.__nombre + ' en ' + uso_posicion elif uso_tipo == 'lanzar': uso_msg = 'Arrojas ' + self.__nombre else: uso_msg = 'Usas ' + self.__nombre if nodo_uso.hasAttribute('duracion'): uso_duracion = int(extraer_atributo_xml(nodo_uso,'duracion')) else: uso_duracion = 0 #Siguiente nivel del árbol limpiar_nodos_text_xml(nodo_uso) uso_requisitos = [] uso_efectos = [] for nodo_requi_efe in nodo_uso.childNodes[:]: nombre_item = extraer_nombre_xml(nodo_requi_efe) if nombre_item == 'requisito': requisito_atrib = extraer_atributo_xml(nodo_requi_efe,'atributo') requisito_valor = int(extraer_atributo_xml(nodo_requi_efe,'valor')) if nodo_requi_efe.hasAttribute('msg'): requisito_msg = extraer_atributo_xml(nodo_requi_efe,'msg') else: requisito_msg = 'No tienes suficiente ' + requisito_atrib uso_requisitos.append( {"atributo":requisito_atrib, "valor":requisito_valor, "msg":requisito_msg} ) else: efecto_atrib = extraer_atributo_xml(nodo_requi_efe,'atributo') efecto_valor = int(extraer_atributo_xml(nodo_requi_efe,'valor')) if nodo_requi_efe.hasAttribute('msg'): efecto_msg = extraer_atributo_xml(nodo_requi_efe,'msg') else: efecto_msg = "" uso_efectos.append( {"atributo":efecto_atrib, "valor":efecto_valor, "msg":efecto_msg} ) self.__usos.append( {"tipo":uso_tipo, "posicion":uso_posicion, "gasto":uso_gasto, "msg":uso_msg, "duracion":uso_duracion, "requisitos":uso_requisitos, "efectos":uso_efectos} ) # Métodos especiales def __setstate__(self, atributos): """Método llamado para restaurar los atributos provinientes de un pickle.""" self.__dict__ = atributos # Verificar la versión. if not self.__dict__.has_key("_Objeto__version"): raise ValueError("Cargado objeto " + self.id + " obsoleto: sin versión," + " cuando se esperaba " + Objeto.VERSION_OBJETO) assert(Objeto.VERSION_OBJETO == "1.0.1") while self.__version != "1.0.1": # La versión 1.0.1 produce una ruptura con las anteriores ya que se migra al formato # de clases de Python 2.2, y no es compatible con el anterior. Hay que borrar todas # las salas, personajes y objetos. # escribir("Cargado objeto " + self.__id + " con versión desconocida: " + self.__version + ", cuando se esperaba " + Objeto.VERSION_OBJETO) # Métodos normales def activar(self): """Realiza algunos chequeos periodicos de integridad del objeto. Los chequeos se pueden hacer, por ejemplo, al coger el objeto. """ archivo = os.path.join(Objeto.DIR_DESC_OBJETOS, self.__id + Objeto.EXT_DESC_OBJETOS) if self.__fecha_fichero < os.path.getmtime(archivo): # Si se modifica el fichero descriptor, recargarlo de nuevo # reseteando completamente el objeto escribir("Recargando objeto " + self.__id + " desde el fichero de descripcion") self.__inicializar_objeto() # Código para pruebas unitarias del módulo. if (__name__ == '__main__'): print "No hay prueba unitaria de módulo. prueba con objetotester.py"