#!/usr/bin/env python
# encoding: iso-8859-15
#
#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: Personaje.py

"""Clase Personaje."""

import logging
from random import choice, random
from math import floor
from utils import *
from string import *
import Idioma
import Raza
import FechaHoraMine

log = logging.getLogger("Personajes")

# Versión del sistema de personajes (se incrementa al cambiar cualquiera
# de Personaje, PersonajeJugador o PersonajeNoJugador)
VERSION = '1.5.7'

# Resultados de comparar otro personaje con este
COMP_INF_MAS_FUERTE   = 9
COMP_MUCHO_MAS_FUERTE = 8
COMP_BAST_MAS_FUERTE  = 7
COMP_ALGO_MAS_FUERTE  = 6
COMP_POR_EL_ESTILO    = 5
COMP_ALGO_MAS_DEBIL   = 4
COMP_BAST_MAS_DEBIL   = 3
COMP_MUCHO_MAS_DEBIL  = 2
COMP_INF_MAS_DEBIL    = 1

# Resultados de un ataque
ATAQUE_EXITO   = 1
ATAQUE_FALLADO = 2
ATAQUE_CRITICO = 3
ATAQUE_PIFIA   = 4

# Posiciones del cuerpo donde se pueden poner objetos
POSICIONES = [ 'cabeza', 'cuello', 'tronco', 'brazo-izq', 'brazo-der', 'mano-izq',
	       'mano-der', 'dos-manos', 'piernas', 'tobillo-izq', 'tobillo-der',
	       'pie-izq', 'pie-der' ]

# Dinero que porta un personaje nuevo si no se indica otra cosa
DINERO_INICIAL = 10


class Personaje(object):
    """Cualquier personaje, tanto jugador como no jugador.

    Un personaje puede ser controlado por cualquier clase, siempre y cuando ésta implemente
    el interfaz Controlador, para que pueda responder a los eventos que genera un personaje.
    Los personajes se mueven automaticamente al limbo al dejar de ser controlados, y se recuperan
    al controlarlos otra vez. Tambien se mantiene una lista de personajes activos en Mundo.

    Los personajes se pueden agrupar en compañias, indicando que participan juntos en acciones
    como movimiento entre salas, combates y algunas acciones (buscar, levantar algo, etc.). La
    gestión de esto se deja al controlador.

    Cuando se salva un personaje con Pickle, su controlador NO se salva. Al ser restaurado
    más tarde, hay que volver a colocarle un controlador. 
    """

    # Atributos públicos de la clase

    def __leer_id(self): return self.__id

    id = property(__leer_id,
                  doc="""(string): identifica univocamente al personaje.

                      No puede contener espacios ni símbolos raros.

                      Invariantes:
                          id != None
                      """)

    def __leer_version(self): return self.__version

    version = property(__leer_version,
                       doc="""(string): versión del sistema de personajes.
                           
                           Invariantes:
                               version != None
                           """)

    def __leer_nombre(self): return self.__nombre

    nombre = property(__leer_nombre,
                      doc="""(string): también identifica univocamente, pero este ya puede contener
                          espacios, porque es como se muestra en pantalla.

                          Invariantes:
                              nombre != None
                          """)

    def __leer_descripcion(self): return self.__descripcion 

    descripcion = property(__leer_descripcion,
                           doc="""({String:any}[]): descripcion del personaje.

			       Consiste en una lista de items, y cada item es un conjunto de
			       propiedades en forma de diccionario:
				- "dificultad" (int): dificultad para percibir el item
						      (0 = automatico, 100 = imposible)
				- "texto" (string): el texto del item
				- "personaje_mira" (guion): el guion que se ejecuta cuando
				                            un personaje mira el item

			       Invariantes:
				   descripcion != None
				   len(descripcion) >= 0
				   descripcion[i]["dificultad"] in range(0,100)
				   descripcion[i]["texto"] != None
			       """)

    def __leer_experiencia(self): return self.__experiencia 

    experiencia = property(__leer_experiencia,
                           doc="""(int): puntos de experiencia acumulados.
                               
                               Invariantes:
                                   experiencia >= 0
                               """)
                             

    def __leer_nivel(self): return self.__nivel

    nivel = property(__leer_nivel,
                     doc="""(int): poder del personaje.

                         Cada 1000 puntos de experiencia incrementan un nivel. El ajuste se hace
                         automáticamente (al asignar experiencia se actualiza el nivel y viceversa).

                         Invariantes:
                             nivel >= 1
                         """)

    def __leer_vida(self): return self.__vida

    vida = property(__leer_vida,
                    doc="""(int): puntos de vida que aguanta antes de morir.

                        Invariantes:
                            vida > 0
                        """)

    def __leer_volumen(self): return self.__volumen

    volumen = property(__leer_volumen,
                       doc="""(float): tamaño que ocupa, para ver si cabe o no en las salas
                           (1 es igual al tamaño de un humano)

                           Invariantes:
                               volumen > 0.0
                           """)

    def __leer_estado(self): return self.__estado
    def __poner_estado(self, estado): self.__estado = estado

    estado = property(__leer_estado,
                      doc="""(string): puede ser "vivo" o "muerto".

                          Invariantes:
                              estado in ("vivo", "muerto")
                          """)

    def __leer_aura(self): return self.__aura
    def __poner_aura(self, aura): self.__aura = aura

    aura = property(__leer_aura,
                    doc="""(int): alineamiento del personaje (0=Morgoth, 50=neutral, 100=un Valar)

                        Invariantes:
                            aura in range(0,100)
                        """)

    def __leer_sala(self): return self.__sala

    sala = property(__leer_sala,
                    doc="""(Sala): sala actual en la que se encuentra el personaje.

                        Invariantes:
                            sala != None
                            self in sala.personajes.values()
                        """)

    def __leer_controlador(self): return self.__controlador
    def __poner_controlador(self, controlador): self.__controlador = controlador

    controlador = property(__leer_controlador, __poner_controlador,
                           doc="""(Controlador): la entidad que controla a este personaje,
                               o None si está inactivo.

                               Invariantes:
                                   controlador != None <-> ( sala.id != Mundo.ID_SALA_LIMBO and
                                                             self in Mundo.pjs_activos.values() )
                                   controlador == None <-> ( sala.id == Mundo.ID_SALA_LIMBO and
                                                             self not in Mundo.pjs_activos.values() )
                                   controlador == None -> ( siguiente == None and anterior == None )
                               """)

    def __leer_atrib(self): return self.__atrib

    atrib = property(__leer_atrib,
                     doc="""({String:int}): atributos del personaje.
                     
                         Cada atributo tiene un nombre (un string que hace de índice) y un valor
                         entero. Los atributos son fuerza, destreza, constitucion, inteligencia,
                         sabiduria y carisma.

                         Invariantes:
                             atrib != None
                             len(atrib) == 6
                         """)

    def __leer_idiomas(self): return self.__idiomas

    idiomas = property(__leer_idiomas,
                       doc="""({Idioma:int}): lista de los idiomas que conoce.
                       
                           Cada idioma es el índice de un diccionario que apunta al nivel que tiene
                           el personaje en ese idioma.

                           Invariantes:
                               idiomas != None
                               len(idiomas) == len(Idioma.lista)
                           """)

    def __leer_idioma_base(self): return self.__idioma_base

    idioma_base = property(__leer_idioma_base,
                           doc="""(Idioma): el idioma que habla por defecto.
                           
                               Comienza siendo Idioma.oestron.

                               Invariantes:
                                   idioma_base != None
                                   idioma_base in Idioma.lista
                               """)

    def __leer_id_sala_original(self): return self.__id_sala_original

    id_sala_original = property(__leer_id_sala_original,
                                doc="""(string): solo mientras esta en el limbo, id de la sala
                                    donde estaba antes.
                                    """)

    def __leer_siguiente(self): return self.__siguiente

    siguiente = property(__leer_siguiente,
                         doc="""(Personaje): el personaje que va detrás de el en la compañia
                             (None si no está en ninguna compañia o va el último).

                             Invariantes:
                                 siguiente != None -> siguiente.anterior == self
                             """)

    def __leer_anterior(self): return self.__anterior

    anterior = property(__leer_anterior,
                        doc="""(Personaje): el personaje que va delante de el en la compañia
                            (None si no está en ninguna compañía o va el primero).

                            Invariantes:
                                anterior != None -> anterior.siguiente == self
                            """)

    def __leer_mochila(self): return self.__mochila

    mochila = property(__leer_mochila,
                       doc="""(Objeto[][]): lista de todos los objetos que va cargando el personaje.
                       
                           Es lista doble porque si hay varios objetos con el mismo id, van agrupados
                           en forma de lista de instancias.
                
                           Invariantes:
                               mochila != None
                               mochila[i] != None
                               len(mochila[i] > 0)
                               mochila[i][j] != None
                            """)

    def __leer_cuerpo(self): return self.__cuerpo

    cuerpo = property(__leer_cuerpo,
                      doc="""({String:Objeto}): mapa con las partes del cuerpo del PJ, indicando
                          el objeto que lleva puesto en cada parte, o None si no lleva nada.
                          
                          Las partes o posiciones son:

                          cabeza, cuello, tronco, brazo-izq, brazo-der, mano-izq, mano-der, dos-manos,
                          piernas, tobillo-izq, tobillo-der, pie-izq y pie-der.
                
                          Invariantes:
                              cuerpo != None
                              len(cuerpo) == len(Personaje.POSICIONES)
                          """)

    def __leer_ids_amigos(self): return self.__ids_amigos

    ids_amigos = property(__leer_ids_amigos,
                          doc="""(Diccionario): Se trata de un diccionario de la forma {id:int}
                              en donde id es un string que identifica a un personaje e int es el
                              grado de confianza, siendo 0 nada, 1 toda (de momento).

                              Invariantes:
                                  ids_amigos != None
                              """)

    def __leer_salas_visitadas(self): return self.__salas_visitadas

    salas_visitadas = property(__leer_salas_visitadas,
                               doc="""(Diccionario): Un diccionario que tiene como claves todas las
                                   salas que el personaje ha ido visitando en su vida (el valor mapeado
                                   no es relevante, y vale siempre 'si').

                                   Habrá que recortar esto de alguna manera, para que no se vuelva
                                   gigantescamente grande.

                                   Invariantes:
                                       salas_visitadas != None
                                   """)


    def __leer_dinero(self): return self.__dinero

    dinero = property(__leer_dinero,
                      doc="""(int): Cantidad de monedas que lleva encima el personaje.

		          Invariantes:
			      dinero >= 0
			  """)

    def __leer_es_jugador(self):
        from PersonajeJugador import PersonajeJugador
        return isinstance(self, PersonajeJugador)

    es_jugador = property(__leer_es_jugador,
	                  doc="""(bool): Dice si el personaje es jugador o no.""")


    # Constructor

    def __init__(self, id, sala, nombre, descripcion, experiencia, nivel, vida,
                 volumen, estado, aura, controlador, atrib, idiomas, idioma_base,
		 dinero):
        """Método para crear una nueva instancia de esta clase.

        Personaje es una clase abstracta, luego no puede ser directamente
        instanciada. Este método sólo puede ser llamado desde subclases.

        Requiere:
          Todos los parámetros satisfacen las invariantes
        """
	
	# Atributos privados:
	#
	# __max_vida (int): valor inicial de la propiedad vida, nunca podrá superar este valor
	# __tiradas ({string:int}): nombres y resultados de las tiradas realizadas por este personaje
	# __competiciones ({string:int}): nombres y resultados de las competiciones realizadas por este
	#                                 personaje
	# __tasaciones ({string:(int, int, FechaHoraMine)}): tasaciones de objetos realizadas.
	#       La clave es el id de un objeto, que apunta a una tupla (valor, beneficio, fecha)

        self.__id = id
        self.__version = VERSION
        self.__sala = sala

        self.__nombre = nombre
        self.__descripcion = descripcion
        self.__experiencia = experiencia
        self.__nivel = nivel
        self.__vida = vida
        self.__max_vida = vida
        self.__volumen = volumen
        self.__estado = estado
        self.__aura = aura
        self.__controlador = controlador
        self.__atrib = atrib
        self.__idiomas = idiomas
        self.__idioma_base = idioma_base
	self.__dinero = dinero

        self.__siguiente = None
        self.__anterior = None

	self.__tiradas = {}
	self.__competiciones = {}
	self.__tasaciones = {}

        self.__mochila = []
        self.__cuerpo = {}
        for posicion in POSICIONES:
            self.__cuerpo[posicion] = None

        self.__ids_amigos = {}
        self.__salas_visitadas = {}


    # Métodos especiales

    def __del__(self):
        """Realiza ajustes automáticos al ser destruido"""
        if self.__controlador != None:
            self.liberar()
        if self.__sala != None:
            if self in self.__sala.personajes.values():
                self.__sala.salir_personaje(self)
                for otro_personaje in self.__sala.personajes.values():
                    if otro_personaje.__controlador != None:
                        otro_personaje.__controlador.sale_personaje(self, "el vacío")
            self.__sala = None
	if self.es_jugador:
            self._PersonajeJugador__crono_subir_hambre.parar()
            self._PersonajeJugador__crono_subir_hambre = None
            self._PersonajeJugador__crono_subir_sed.parar()
            self._PersonajeJugador__crono_subir_sed = None
            if self._PersonajeJugador__crono_msg_hambre != None:
                self._PersonajeJugador__crono_msg_hambre.parar()
                self._PersonajeJugador__crono_msg_hambre = None
            if self._PersonajeJugador__crono_msg_sed != None:
                self._PersonajeJugador__crono_msg_sed.parar()
                self._PersonajeJugador__crono_msg_sed = None


    def __getstate__(self):
        """Método llamado para preguntar qué hay que salvar en un pickle."""
        atributos = self.__dict__.copy()

        # El controlador no se salva.
        del atributos["_Personaje__controlador"]

        # Los idiomas se sustituyen por su nombre (si guardamos la instancia
        # de idioma, luego al cargar la instancia cargada no coincide con la
        # instancia que se ha definido en el Idioma.py)
        idiomas_nombres = {}
        for idioma in self.__idiomas.keys():
            idiomas_nombres[idioma.nombre] = self.__idiomas[idioma]

        atributos["_Personaje__idiomas"] = idiomas_nombres

        atributos["_Personaje__idioma_base"] = self.__idioma_base.nombre
        
	if self.es_jugador:
            # Lo mismo con la raza (solo PJ)
            atributos["_PersonajeJugador__raza"] = self._PersonajeJugador__raza.nombre

        return atributos


    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("_Personaje__version"):
            raise ValueError("Cargado personaje " + self.__id + " obsoleto: sin versión" +
                             " cuando se esperaba " + VERSION)

        assert(VERSION == "1.5.7")
        while self.__version != "1.5.7":

            # La versión 1.5.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.

            if self.__version == "1.5.1":
                # Versión 1.5.1 -> 1.5.2: añadir __max_vida
                self.__max_vida = self.__vida
                self.__version = "1.5.2"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.2")
            elif self.__version == "1.5.2":
                # Versión 1.5.2 -> 1.5.3: añadir __hambre y __sed, la raza ahora
                # es solo el nombre y no la instancia entera (sólo PJs).
		if self.es_jugador:
                    self._PersonajeJugador__hambre = 0
                    self._PersonajeJugador__sed = 0
                    self._PersonajeJugador__crono_subir_hambre = FechaHoraMine.crear_rel(horas = self._PersonajeJugador__raza.tiempo_hambre)
                    self._PersonajeJugador__crono_subir_sed = FechaHoraMine.crear_rel(horas = self._PersonajeJugador__raza.tiempo_sed)
                    self._PersonajeJugador__crono_msg_hambre = None
                    self._PersonajeJugador__crono_msg_sed = None
                    self._PersonajeJugador__raza = self._PersonajeJugador__raza.nombre
                self.__version = "1.5.3"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.3")
            if self.__version == "1.5.3":
                # Versión 1.5.3 -> 1.5.4: añadir __tiradas y __competiciones
                self.__tiradas = {}
                self.__competiciones = {}
                self.__version = "1.5.4"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.4")
	    elif self.__version == "1.5.4":
		# Versión 1.5.4 -> 1.5.5: añadir suceso personaje_mira a los items de descripción.
		for item in self.__descripcion:
		    item["personaje_mira"] = None
                self.__version = "1.5.5"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.5")
            if self.__version == "1.5.5":
                # Versión 1.5.5 -> 1.5.6: añadir __dinero y __tasaciones
                self.__dinero = DINERO_INICIAL
		self.__tasaciones = {}
                self.__version = "1.5.6"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.6")
	    elif self.__version == "1.5.6":
		# Versión 1.5.6 -> 1.5.7: añadir contadores y etiquetas (sólo PNJs)
		if not self.es_jugador:
		    self.__contadores = {}
		    self.__etiquetas = {}
                self.__version = "1.5.7"
                log.info("Convertido personaje " + self.__id + " a versión 1.5.7")
	    else:
	        raise ValueError("Cargado personaje " + self.__id + " con versión desconocida: " +
			         self.__version + " cuando se esperaba " + VERSION)

        # Recuperar el controlador
	if not self.es_jugador:

            # PNJ: se crea el controlador al momento
            self.controlador_automatico()

        else:

            # PJ: el controlador se restaura a None
            self.__controlador = None

        # Recuperar los idiomas, buscando por nombre.
        idiomas_instancias = {}
        for idioma_nombre in atributos["_Personaje__idiomas"].keys():
            idioma = Idioma.buscar(idioma_nombre)
            idiomas_instancias[idioma] = atributos["_Personaje__idiomas"][idioma_nombre]

        self.__idiomas = idiomas_instancias

        idioma_base = Idioma.buscar(atributos["_Personaje__idioma_base"])
        self.__idioma_base = idioma_base
        
        # Lo mismo con la raza (solo PJ)
	if self.es_jugador:
            self._PersonajeJugador__raza = Raza.buscar(self._PersonajeJugador__raza)


    # métodos normales

    def cambiar_experiencia(self, puntos):
        """Modifica el valor de la propiedad experiencia, sumando los puntos indicados.
        
        El nivel es ajustado correspondientemente.
        """
        self.__experiencia += puntos
        self.__nivel = self.__experiencia / 1000


    def cambiar_nivel(self, nuevo_nivel):
        """Fuerza el nivel de un personaje, y resetea los puntos de experiencia al mínimo
        para ese nivel.
        
        No tengo muy claro si tiene mucho sentido esto, se podría usar por ejemplo si te
        lanzan una maldición que hace que bajes un nivel y pierdas la experiencia
        acumulada. No sé, no sé...
        """
        self.__nivel = nivel
        self.__experiencia = nivel * 1000


    def cambiar_vida(self, puntos):
        """Modifica el valor de la propiedad vida.

        El valor del parámetro puntos es sumado al de la propiedad. Puede ser negativo. Si el
        resultado es menor que 0 o mayor que el máximo, se trunca.
        """
        self.__vida += puntos
        if self.__vida < 0:
            self.__vida = 0
        if self.__vida > self.__max_vida:
            self.__vida = self.__max_vida


    def atributo(self, nombre_atributo):
        """Devuelve el valor del atributo con el nombre indicado.

        Es útil para requisitos.

        Requiere:
            nombre_atributo in self.__atrib.keys()
        """
        return self.__atrib[nombre_atributo]


    def cambiar_atributo(self, nombre_atributo, niveles):
        """Modifica el valor del atributo con el nombre indicado.

        El valor del parámetro niveles es sumado al del valor del atributo. Puede ser negativo.
        Si el resultado es menor que 0 o mayor que el máximo, se trunca. <-%% ojo, sin hacer

        Requiere:
            nombre_atributo in self.__atrib.keys()
        """
        self.__atrib[nombre_atributo] += niveles
        #%% OJO, comprobar los máximos...


    def idioma(self, idioma):
        """Devuelve el valor del idioma con el nombre indicado.

        Es útil para requisitos.

        Requiere:
            idioma in self.__idiomas.keys()
        """
        return self.__idiomas[idioma]


    def cambiar_idioma(self, idioma, niveles):
        """Modifica el valor del idioma con el nombre indicado.

        El valor del parámetro niveles es sumado al del idioma. Puede ser negativo. Si el
        resultado es menor que 0 o mayor que el máximo, se trunca.
        """
        self.__idiomas[idioma] += niveles
        #%% OJO, comprobar los máximos...


    def cambiar_dinero(self, monedas):
        """Modifica el valor de la propiedad dinero, sumando las monedas indicadas.

	Si el resultado es menor que cero, se trunca.
        """
        self.__dinero += monedas
	if self.__dinero < 0:
	    self.__dinero = 0


    def pagar(self, otro_personaje, monedas):
	"""Transfiere una cantidad de dinero a otro personaje."""
	self.cambiar_dinero(-monedas)
	otro_personaje.cambiar_dinero(monedas)
	if otro_personaje.controlador != None:
	    otro_personaje.controlador.paga_personaje(self, monedas)


    def volumen_total(self):
        """El total de volumen que ocupa el personaje y todos los objetos que carga.
        Asegura:
          return > 0.0
        """
        total = self.__volumen
        for objeto in self.__mochila:
            for instancia in objeto:
                total += instancia.volumen
        for objeto in self.__cuerpo.values():
            if objeto != None:
                total += objeto.volumen
        return total
        

    def color_texto(self):
        """Color con el que se muestran los mensajes que dice este personaje"""
            
	if not self.es_jugador:
            return chr(27) + "[1;" + str(35) + "m"
        else:
            base = 0
            for letra in self.__nombre:
                base = base + ord(letra)
            return color(31 + base % 7)

        
    def mover(self, sala_destino, automatico = 0):
        """Mover al personaje a otra sala.
        
        Se avisa a la sala sala actual y a la nueva (y a todos los personajes en ambas).

        Si el personaje está en una compañía, pero no es el lider, la abandona. Si es
        el líder, los personajes que le siguen se mueven también con él, con movimiento
        automático. Todos los personajes con movimiento automático producen un evento
        a su controlador.

        Si la sala destino se llena, los que vengan detrás en compañia la abandonan.

	Se olvidan todas las tiradas y competiciones memorizadas.

	Se comprueba si lleva algún objeto propiedad de otro, y se toman en su caso
	las acciones correspondientes.

	El parametro automatico es solo para uso interno.
        
        Requiere:
            sala_destino != None
            sala_destino.volumen_ocupado() + self.volumen_total() <= sala_destino.volumen
            
        Asegura:
            self.sala == sala_destino
        """
	import Mundo
	if self.__sala.id != Mundo.ID_SALA_LIMBO and \
	   sala_destino.id != Mundo.ID_SALA_LIMBO:

	    # Comprobar si está intentando robar algo. En un futuro, esto se hará
	    # mediante guiones o algo así.
	    objeto_ajeno = self.buscar_objeto_ajeno()
	    if objeto_ajeno != None:
		self.actuar_uno_mismo("No puedes salir llevando %s. No es de tu propiedad." % 
				      objeto_ajeno.nombre)
		if automatico:
		    raise ExcepcionDeJuego("Alguien que te sigue no puede mover.")
		else:
		    raise ExcepcionDeJuego("No se ha podido realizar el movimiento.")

	sala_origen = self.__sala

        try:

            if not automatico:
                if self.__anterior != None:
                    self.no_seguir();

            self.__sala.salir_personaje(self)
            for otro_personaje in self.__sala.personajes.values():
                if otro_personaje.controlador != None:
                    otro_personaje.controlador.sale_personaje(self, sala_destino.nombre)

            self.__sala = sala_destino

            for otro_personaje in self.__sala.personajes.values():
                if otro_personaje.controlador != None:
                    otro_personaje.controlador.entra_personaje(self)
            self.__sala.entrar_personaje(self)

            if automatico:
                if self.__controlador != None:
                   self.__controlador.movimiento_automatico()

            if self.__siguiente != None:
                if sala_destino.volumen_ocupado() + self.__siguiente.volumen_total() <= sala_destino.volumen:
                    self.__siguiente.mover(sala_destino, 1)
                else:
                    self.__siguiente.actuar_uno_mismo(
                        "Intentas seguir a " + self.__id + " pero el lugar a donde se dirige está " +
                        "al límite de su capacidad y no cabes. Ya no sigues a " + self.__id + ".")
                    self.__siguiente.no_seguir()

	    self.__tiradas = {}
	    self.__competiciones = {}

        except:

            # Si hay algún error en los avisos, intentamos mover el personaje
            # a un estado consistente (cumpliendo todas las invariantes)
            # antes de cascar.
            if self.__sala != sala_origen:
                if self.__sala != None: self.__sala.salir_personaje(self)
                self.__sala = sala_origen

            if not self in self.__sala.personajes.values():
                self.__sala.entrar_personaje(self)

            raise


    def liberar(self):
        """Libera al personaje del control de un jugador.

        El personaje se mueve al limbo, y se guarda la sala original para restaurarla
        al volver a controlarlo. Se retira también de la lista de pjs activos y se sale
        de la compañía, si está en una.

        Requiere:
          self.controlador != None

        Asegura:
          self.controlador == None
        """
        import Mundo

        log.info("Enviando al limbo a " + self.__id)

        if self.__siguiente != None:
            self.__siguiente.no_seguir()

        self.__id_sala_original = self.__sala.id
        self.mover(Mundo.localizar_sala(Mundo.ID_SALA_LIMBO))  # se asume que el limbo tiene
                                                               # capacidad de volumen infinita
        self.__controlador = None

        if self.es_jugador:
            Mundo.personaje_desactivado(self)
            self.actuar_global("ha salido de Minë")

            # Los cronometros de hambre y sed se detienen, se guarda el tiempo
            # que les queda para restaurarlos despues por donde iban.
            self._PersonajeJugador__crono_subir_hambre.pausar()
            self._PersonajeJugador__crono_subir_sed.pausar()
            if self._PersonajeJugador__crono_msg_hambre != None:
                self._PersonajeJugador__crono_msg_hambre.pausar()
            if self._PersonajeJugador__crono_msg_sed != None:
                self._PersonajeJugador__crono_msg_sed.pausar()

        # Si hay algún error en actuar_global, no hace falta restaurar nada, el
        # personaje queda en el limbo.


    def controlar(self, controlador):
        """Pasa a ser controlado por un jugador.

        El personaje se mueve a su sala original y se apunta a la lista de pjs activos.

        Requiere:
          self.controlador == None
          controlador != None

        Asegura:
          self.controlador = controlador
        """
        import Mundo

	if self.es_jugador:
            log.info("Trayendo desde el limbo a " + self.__id)

            id_sala_original = self.__id_sala_original

            try:

                # Aquí tengo un dilema gordo: en teoría debería comprobar el volumen ocupado de la
                # sala y no dejarle entrar si está llena. Pero es una putada que alguien no pueda
                # recuperar su personaje porque en la sala donde lo dejó ha entrado un montón de
                # gente. Como será un caso muy poco frecuente, haremos la vista gorda y permitiremos
                # que haya más gente en la sala de la que cabe, espero que no falle nada (durará
                # sólo un ratito hasta que alguien salga)...
                self.mover(Mundo.localizar_sala(self.__id_sala_original))
                del self.__id_sala_original

                self.__controlador = controlador

                if self.es_jugador:
                    Mundo.personaje_activado(self)
                    self.actuar_global("ha entrado en Minë")

                    # Restaurar los cronometros de hambre y sed
                    self._PersonajeJugador__crono_subir_hambre.reanudar()
                    self._PersonajeJugador__crono_subir_sed.reanudar()
                    if self._PersonajeJugador__crono_msg_hambre != None:
                        self._PersonajeJugador__crono_msg_hambre.reanudar()
                    if self._PersonajeJugador__crono_msg_sed != None:
                        self._PersonajeJugador__crono_msg_sed.reanudar()

            except:
                # Si casca algo, volvemos al limbo
                self.__id_sala_original = id_sala_original
                self.mover(Mundo.localizar_sala(Mundo.ID_SALA_LIMBO))  # se asume que el limbo tiene
                self.__controlador = None                              # capacidad de volumen infinita
		if self.es_jugador:
                    Mundo.personaje_desactivado(self)
                raise
        else:
            self.__controlador = controlador
                    
                
    def seguir(self, lider):
        """Entrar en una compañía, siguiendo a otro personaje que hace de lider.
        
        Si el lider ya tenía algún seguidor, éste se coloca en medio de los dos.
        Seguirá en la companía hasta que se llame explícitamente a no_seguir, o
        se mueva de sala por voluntad propia.

        Si este personaje ya era lider de otra compañia, se mezclan ambas,
        colocandose la de éste completa justo detras del lider.

	Los controladores de los personajes implicados reciben eventos indicando
	lo que ocurre.
        
        Requiere:
            anterior == None
	    lider != self

        Asegura:
            anterior = lider
        """
        # Localizar al ultimo personaje de la compañia actual (que puede
        # perfectamente ser este personaje si le sigue nadie)
        ultimo = self
        while ultimo.siguiente != None:
            ultimo = ultimo.siguiente

	# Localizar al personaje que está siguiendo actualmente al líder,
	# si es que lo hay
	seguidor = lider.__siguiente

        try:

	    # Seguir al lider
	    self.__anterior = lider
	    self.actuar_uno_mismo("Ahora estás siguiendo a %s" % lider.id)
	    lider.__siguiente = self
	    self.actuar_individual(lider, "te está siguiendo")
	    log.info("%s sigue a %s" % (self.id, lider.id))

	    # Poner a seguidor detras de último, si lo hay
	    if seguidor != None:
		seguidor.__anterior = ultimo
		seguidor.actuar_uno_mismo("Ahora estás siguiendo a %s" % ultimo.id)
		ultimo.__siguiente = seguidor
		seguidor.actuar_individual(ultimo, "te está siguiendo")
	        log.info("%s sigue a %s" % (seguidor.id, ultimo.id))

	except:
	    # Si casca algo, dejar las cosas como estaban
	    self.__anterior = None
	    lider.__siguiente = seguidor
	    if seguidor != None:
		seguidor.__anterior = lider
	        ultimo.__siguiente = None
	    raise


    def no_seguir(self):
        """Abandonar una compañia.
        
        Requiere:
            anterior != None

        Asegura:
            anterior == None
        """
	lider = self.__anterior
	seguidor = self.__siguiente

	try:

	    # Dejar de seguir al líder
	    self.__anterior = None
	    self.__siguiente = None
	    self.actuar_uno_mismo("Ya no sigues a %s" % lider.id)
	    self.actuar_individual(lider, "ya no te sigue")

	    # Poner a nuestro antiguo seguidor (si lo hay)
	    # detrás del líder
	    lider.__siguiente = seguidor
	    if seguidor == None:
		lider.actuar_uno_mismo("Ahora no te sigue nadie")
	    else:
		seguidor.actuar_individual_personaje(lider, "te está siguiendo")
		seguidor.__anterior = lider
		seguidor.actuar_uno_mismo("Ahora estás siguiendo a %s" % lider.id)

	except:
	    # Si casca algo, dejar las cosas como estaban
	    self.__anterior = lider
	    lider.__siguiente = self
	    self.__siguente = seguidor
	    if seguidor != None:
		seguidor.__anterior = self
	    raise

        
    def mensaje_filtrado(self, mensaje, idioma, oyente):
        """Filtra un mensaje de texto según los niveles de un idioma.

        Tiene en cuenta el nivel de este idioma que tiene este personaje (el hablante)
        y otro (el oyente). Cuanto más bajos sean los niveles menos se parece el
        mensaje filtrado al original.
        """

        if idioma == None:

            mensaje_filtrado = mensaje[:]

        else:

            nivel_hablante = self.__idiomas.get(idioma)
            if nivel_hablante == None: nivel_hablante = 0

            nivel_oyente = oyente.idiomas.get(idioma)
            if nivel_oyente == None: nivel_oyente = 0

            total_filtro = nivel_hablante + nivel_oyente #por ejemplo

            if nivel_hablante == 0 or nivel_oyente == 0:
                total_filtro = total_filtro - 2000
            if nivel_hablante == 5 and nivel_oyente == 5:
                total_filtro = total_filtro + 1000

            # El siguiente cálculo es el 'bonus de compatibilidad por raza'.
	    if self.es_jugador and oyente.es_jugador:
                if self.raza == oyente.raza:
                    total_filtro = total_filtro + 0.5

            # Si escribimos pocas palabras no habrá filtro porque simulamos
            # hablar despacio, además, un 'buenos días' no merece filtro.
            if (len(split(mensaje)) <= 3 and len(mensaje) <= 10):

                mensaje_filtrado = mensaje[:]

            else:

                mensaje_filtrado = ""
                for i in range(0, len(mensaje)):
                    if tirar_dados(2) > total_filtro + 2:
                        mensaje_filtrado += chr(choice(idioma.lista_letras))
                    else:
                        mensaje_filtrado += mensaje[i]

        return mensaje_filtrado


    def hablar(self, texto, idioma=Idioma.oestron):
        """Dice un mensaje en voz alta de forma que lo oigan todos los que estan en la sala."""
        if self.__sala != None:
            for personaje in [p for p in self.__sala.personajes.values()
                              if p.controlador != None]:
                msg_f = self.mensaje_filtrado(texto, idioma, personaje)
                personaje.controlador.habla_personaje(self, msg_f)
#               else:
#                       self.controlador.conn().send('Dices: '+ self.__personaje.color_texto() + msg_f)


    def susurrar(self, oyente, texto, idioma=Idioma.oestron):
        """Dice un mensaje en voz baja de forma que lo oiga sólo una persona de la sala."""
        msg_f = self.mensaje_filtrado(texto, idioma, oyente)
	if oyente.controlador != None:
	    oyente.controlador.susurra_personaje(self, msg_f)


    def gritar(self, texto, idioma=Idioma.oestron):
        """Dice un mensaje en voz muy alta de forma que lo oigan todos los jugadores conectados en
        la sala del que grita y circundantes.
        ."""
        #Generamos la lista de ids de salas en donde se oirá el grito.
        salidas_gritar = []
        for salida in self.__sala.salidas[:]:
            salidas_gritar.append(salida["id"])
        salidas_gritar.append(self.__sala.id)
        
        import Mundo
        for sala in Mundo.salas:
            if sala.id in salidas_gritar:
                for personaje in [ p for p in sala.personajes.values()
                                   if p != self and p.controlador != None ]:
                    msg_f = self.mensaje_filtrado(texto, idioma, personaje)
                    personaje.controlador.grita_personaje(self, msg_f)


    def actuar(self, texto):
        """Muestra lo que hace el personaje, de forma novelada, para que
        lo vean los personajes que estan en la sala."""
        if self.__sala != None:
            for personaje in [ p for p in self.__sala.personajes.values()
                               if p != self and p.controlador != None]:
                personaje.controlador.actua_personaje(self, texto)


    def actuar_individual(self, espectador, texto):
        """Muestra lo que hace el personaje, solo para que lo perciba
        otro personaje."""
	if espectador.controlador != None:
	    espectador.controlador.actua_individual_personaje(self, texto)


    def actuar_uno_mismo(self, texto):
        """Muestra lo que hace el personaje, para si mismo."""
	if self.controlador != None:
	    self.controlador.actua_uno_mismo(texto)


    def actuar_global(self, texto):
        """Muestra lo que hace el personaje, de forma novelada, para que
        lo vean TODOS LOS JUGADORES CONECTADOS."""
        import Mundo
        for sala in Mundo.salas:
            for personaje in [ p for p in sala.personajes.values()
                               if p != self and p.controlador != None ]:
                personaje.controlador.actua_global_personaje(self, texto)


    def describir_entorno(self, suceso):
        """El personaje describe un fenomeno ambiental, independiente de
        el mismo, para que lo vean los personajes que estan en la sala
        (incluido el personaje que lo describe)."""
        if self.__sala != None:
            for personaje in [ p for p in self.__sala.personajes.values()
                               if p.controlador != None ]:
                personaje.controlador.suceso_entorno(suceso)


    def comparacion(self, otro_personaje):
        """Compara este personaje con otro, a efectos de combate.

        Devuelve un número del 1 al 9, siendo
         1 = el otro es infinitamente más débil que este
         5 = los dos son más o menos iguales
         9 = el otro es infinitamente más fuerte que este
        """
        media_este = (self.__atrib["fuerza"] +
                      self.__atrib["destreza"] +
                      self.__atrib["inteligencia"] +
                      self.__vida / 2) / 4

        media_otro = (otro_personaje.atrib["fuerza"] +
                      otro_personaje.atrib["destreza"] +
                      otro_personaje.atrib["inteligencia"] +
                      otro_personaje.vida / 2) / 4

        diferencia = media_otro - media_este

        if diferencia > 40:
            result = COMP_INF_MAS_FUERTE
        elif diferencia > 20:
            result = COMP_MUCHO_MAS_FUERTE
        elif diferencia > 10:
            result = COMP_BAST_MAS_FUERTE
        elif diferencia > 5:
            result = COMP_ALGO_MAS_FUERTE
        elif diferencia > -5:
            result = COMP_POR_EL_ESTILO
        elif diferencia > -10:
            result = COMP_ALGO_MAS_DEBIL
        elif diferencia > -20:
            result = COMP_BAST_MAS_DEBIL
        elif diferencia > -40:
            result = COMP_MUCHO_MAS_DEBIL
        else:
            result = COMP_INF_MAS_DEBIL

        return result


    def generar_dificultad(self, atributo, modif_atributo):
        """Calcula la dificultad de una prueba que el personaje impone a otros,
        basandose en un atributo (pero que no es una competicion).

        La dificultad sera tal que otro personaje con un atributo igual que el tendra
        un 50% de probabilidades de superar la prueba.

        Ejemplos de prueba: esconder un objeto, atrancar una puerta, etc.
        """

        nivel_atrib = self.__atrib[atributo] + modif_atributo
        n_dados = floor(nivel_atrib / 6) + 1
        rango = rango_dados(n_dados)
        dificultad = nivel_atrib + rango[(len(rango)-1) / 2]

        return dificultad


    def tirar_atributo(self, id_tirada, atributo, modif_atributo):
        """Realiza una tirada de datos basada en un atributo.
	
	El personaje recuerda el resultado de la tirada por su id, hasta que se mueva a otra sala.

	id_tirada -- nombre de la tirada para recordarlo en resultado_tirada
	atributo -- nombre del atributo sobre el que se realiza la tirada
	modif_atributo -- modificador positivo o negativo que se añade al atributo antes de tirar

	Devuelve el número obtenido en la tirada.
	"""

        nivel_atrib = self.__atrib[atributo] + modif_atributo
        n_dados = int(floor(nivel_atrib / 6) + 1)
        tirada = nivel_atrib + tirar_dados(n_dados)

        self.__tiradas[id_tirada] = tirada

	return tirada


    def resultado_tirada(self, id_tirada, dificultad, modif_dificultad=0):
        """Devuelve el numero de exitos o fallos de una tirada contra una dificultad."""
        tirada = self.__tiradas[id_tirada]
        diferencia = tirada - (dificultad + modif_dificultad)

        # esto no me convence: la distribucion de las tiradas es muy rara,
        # y ademas no tiene sentido si la dificultad es cero
        #if diferencia >= 0:
        #    return floor(diferencia / dificultad) + 1
        #else:
        #    return floor(diferencia / dificultad) - 1
        return diferencia


    def prueba(self, atributo, modif_atributo, dificultad, modif_dificultad):
        """Realiza una tirada de dados para superar una pueba usando un atributo.

        atributo -- nombre del atributo a usar (fuerza, destreza, etc.)
        modif_atributo -- numero que hay que sumar o restar al atributo
        dificultad -- número que indica la dificultad de la prueba
        modif_dificultad -- numero que hay que sumar o restar a la dificultad

        Devuelve el nº de exitos (>0) si la prueba ha sido superada o el nº de
        fallos (<0) si no lo ha sido.
        """
	self.tirar_atributo("prueba", atributo, modif_atributo)
	return self.resultado_tirada("prueba", dificultad, modif_dificultad)


    def tirar_competicion(self, id_competicion, oponente, atrib_este, atrib_oponente, bonus=0, malus=0):
	"""Realiza una competición entre un atributo de este personaje y un atributo del oponente.

	El personaje recuerda el resultado de la competición por su id, hasta que se mueva a 
	otra sala.

	id_competicion -- nombre de la competición para recordarlo en
			  resultado_competicion. Si es None, no se guarda.
	oponente -- el personaje oponente
	atrib_este -- nombre del atributo sobre el que compite este personaje
	atrib_oponente -- nombre del atributo sobre el que compite el oponente
	bonus -- modificador positivo o negativo que se añade al atributo de este personaje
	malus -- modificador positivo o negativo que se añade al atributo del oponente

	Devuelve la diferencia de tiradas obtenida en la competición.
	"""
        tirada_este = self.tirar_atributo("competicion", atrib_este, 0)
        tirada_oponente = oponente.tirar_atributo("competicion", atrib_oponente, 0)
	diferencia = tirada_este - tirada_oponente

        if id_competicion != None:
	    self.__competiciones[id_competicion] = diferencia

	return diferencia


    def resultado_competicion(self, id_competicion):
	"""Devuelve el número de éxitos o fallos de una competición."""
	diferencia = self.__competiciones[id_competicion]
	return diferencia


    def danar(self, dano):
        """Pierde puntos de vida como consecuencia de un ataque o de otro evento.

        Si el total llega a cero, se pasa el estado a "muerto".
        """
        self.__vida -= dano
        if self.__vida <= 0:
            #self.__estado = "muerto"
            self.resucitar()


    def resucitar(self):
	"""ESTO ES TOTALMENTE PROVISIONAL, HASTA QUE IMPLEMENTEMOS EL SISTEMA DE MUERTE DE VERDAD."""
	self.actuar_uno_mismo("Estas bastante MUERTO")
	self.actuar_uno_mismo("Pero como todavía no está hecho el código de morirse, pues vamos " + \
	                      "y te resucitamos por todo el morro")
	self.actuar_uno_mismo("Hale, a seguir tirando")
	self.actuar("tiembla un poco, gimotea, y cae al suelo, MUERTO.")
	self.actuar("De pronto, se levanta, se sacude el polvo, dice 'vaya, que chungo', y " + \
	            "se encuentra de nuevo tan campante. ¿Pero esto qué es?")
	self.__vida = self.__atrib["constitucion"] * 2
	if getattr(self, "alimentar", None) != None:
	    self.alimentar(10000)


    def ataque_fisico(self, oponente):
        """Este personaje intenta golpear a un oponente con un arma.

        Realiza las tiradas necesarias y devuelve el resultado del ataque:
          ATAQUE_EXITO -- el oponente ha sido dañado
          ATAQUE_FALLADO -- el personaje no ha podido golpear, no ocurre ningún efecto
          ATAQUE_CRITICO -- el personaje da un golpe impresionante que causa doble daño
          ATAQUE_PIFIA -- el personaje falla estrepitosamente y se daña a sí mismo

        El daño se resta en esta función, y si el oponente muere, se aumentan
        también los puntos de experiencia.
        """

        result = None

        dados_ataque = tirar_dados(2)
        dados_defensa = tirar_dados(2)

        tirada_ataque = dados_ataque + self.__atrib["fuerza"]
        tirada_defensa = dados_defensa + oponente.atrib["destreza"]

        if dados_ataque == 12:

            result = ATAQUE_CRITICO
            oponente.danar(self.__atrib["fuerza"] / 5 * 2)

        elif dados_ataque == 1:

            result = ATAQUE_PIFIA
            self.danar(1)

        elif tirada_ataque > tirada_defensa:

            result = ATAQUE_EXITO
            oponente.danar(self.__atrib["fuerza"] / 5)

        else:

            result = ATAQUE_FALLADO

        if oponente.estado == "muerto":
            self.__experiencia += oponente.experiencia / 5

        # El oponente se da cuenta de que le estamos atacando, tanto si
        # le llegamos a golpear como si no.
        if oponente.controlador != None:
            oponente.controlador.ataca_personaje(self, result)

        return result


    def ataque_magico(self, oponente):
        """Este personaje intenta lanzar un hechizo de ataque a un oponente.

        Realiza las tiradas necesarias y devuelve el resultado del ataque magico:
          ATAQUE_EXITO -- el oponente ha sido dañado
          ATAQUE_FALLADO -- el hechizo no ha funcionado, no ocurre ningún efecto

        El daño se resta en esta función, y si el oponente muere, se aumentan
        también los puntos de experiencia.
        """

        result = None

        dados_ataque = tirar_dados(2)

        tirada_ataque = dados_ataque + self.__atrib["sabiduria"]
        tirada_defensa = oponente.atrib["inteligencia"] + oponente.atrib["sabiduria"]

        if tirada_ataque > tirada_defensa:

            result = ATAQUE_EXITO
            oponente.danar( int(random() * self.__atrib["sabiduria"] / 2) + 2 )

        else:

            result = ATAQUE_FALLADO

        if oponente.estado == "muerto":
            self.__experiencia += oponente.experiencia / 5

        # El oponente se da cuenta de que le estamos atacando, tanto si
        # le llegamos a golpear como si no.
        if oponente.controlador != None:
            oponente.controlador.ataca_personaje(self, result)

        return result


    def tasar_objeto(self, objeto):
	"""Devuelve el valor que este personaje atribuye al objeto.

	La fórmula parte del valor base del objeto, y puede añadir modificadores
	basados en:
	    - la sala donde se encuentran (que puede tener tablas de modificadores
	      por tipo de objeto)
	    - un modificador aleatorio por el humor del personaje en el día :)

	También calcula un beneficio que el personaje espera obtener si lo
	compra y luego lo vende.

	El valor tasado y el beneficio se recuerdan durante todo un día, para
	evitar obtener precios diferentes al preguntar varias veces seguidas.
	"""
	ahora = FechaHoraMine.ahora()

	# Primero limpiamos la lista de tasaciones antiguas
	for id in self.__tasaciones.keys()[:]:
	    (valor, beneficio, fecha) = self.__tasaciones[id]
	    if fecha.anno() != ahora.anno() or \
	       fecha.mes() != ahora.mes() or \
	       fecha.dia() != ahora.dia():
		   del self.__tasaciones[id]
	
	# Comprobamos si ya se ha "tasado" este objeto hoy. Si no, pues
	# aplicamos la fórmula.
	if self.__tasaciones.has_key(objeto.id):

	    (valor, beneficio, fecha) = self.__tasaciones[objeto.id]

	else:

	    valor = objeto.valor

	    #%% De momento no se hace lo del modificador por tipo de objeto...

	    # Modificador aleatorio de + - 10%
	    valor = valor * (random() * 0.2 + 0.9)

            # Beneficio aleatorio de entre un 10% y un 30%
	    beneficio = random() * 0.2 + 0.1

	    valor = int(round(valor))
	    beneficio = int(round(beneficio))
	    self.__tasaciones[objeto.id] = (valor, beneficio, ahora)

	return (valor, beneficio)

    
    def precio_compra_objeto(self, objeto, vendedor):
	"""Devuelve el precio al que este personaje está dispuesto a comprar
	ese objeto a ese vendedor.

	La fórmula parte del valor tasado del objeto, y puede añadir modificadores
	basados en:
	    - el carisma del personaje vendedor
	    - la relación de confianza entre ambos
	    - un descuento por beneficio
	"""
	(precio, beneficio) = self.tasar_objeto(objeto)

	# Si el vendedor gana una competición de carisma contra la inteligencia
	# de este, consigue un precio de compra 10% mayor.
	if self.tirar_competicion(None, vendedor,
		                  "inteligencia", "carisma") <= 0:
	    precio = precio * 1.1

	#%% de momento, no hay fórmula de confianza

	# Al comprar, le reducimos la mitad del beneficio (al vender le sumaremos
	# la otra mitad).
	precio = precio - (beneficio / 2)

	return int(round(precio))

    
    def precio_venta_objeto(self, objeto, comprador):
	"""Devuelve el precio al que este personaje está dispuesto a vender
	ese objeto a ese comprador.

	La fórmula es equivalente a la de compra, cambiando de signo los
	modificadores.
	"""
	(precio, beneficio) = self.tasar_objeto(objeto)

	# Si el comprador gana una competición de carisma contra la inteligencia
	# de este, consigue un precio de venta 10% menor.
	if self.tirar_competicion(None, comprador,
		                  "inteligencia", "carisma") <= 0:
	    precio = precio * 0.9

	#%% de momento, no hay fórmula de confianza

	# Al vender, le sumamos la mitad del beneficio (al comprar le restamos
	# la otra mitad).
	precio = precio + (beneficio / 2)

	return int(round(precio))


    def mirar_objeto(self, objeto):
	"""Examina la descripción de un objeto, y ejecuta todos los guiones asociados.

	Por cada item de descripción, ejecuta el guion si existe. Al final devuelve una
	lista con todos los items cuyo guion ha devuelto un resultado de éxito.

	Requiere:
	    objeto != None
	"""
	return objeto.personaje_mira(self, self.sala)


    def mirar_personaje(self, personaje):
	"""Examina la descripción de otro personaje, y ejecuta todos los guiones asociados.

	Por cada item de descripción, ejecuta el guion si existe. Al final devuelve una
	lista con todos los items cuyo guion ha devuelto un resultado de éxito.

	Requiere:
	    objeto != None
	"""
	return personaje.personaje_mira(self, self.sala)

    
    def mirar_sala(self, sala):
	"""Examina la descripción, salidas y objetos de una sala, y ejecuta todos los guiones asociados.

	Por cada item de descripción, salida u objeto, ejecuta el guion si existe. Al final devuelve
	tres listas, con todos los items, salidas y objetos cuyo guion ha devuelto un resultado
	de éxito.

	Requiere:
	    sala != None
	"""
	return sala.personaje_mira(self)


    def coger(self, id_objeto, n_orden=1, n_instancia=1, cantidad=1, sigilo=0):
        """Extrae una o mas instancias de un objeto de la sala y las mete en la mochila.

        Genera un suceso de actuar, salvo si se indica con sigilo.

        - id_objeto es el identificador del objeto. Puede haber varios objetos distintos
          con el mismo id
        - n_orden sirve para distinguir el objeto si hay mas de uno con el mismo id
        - n_instancia es para elegir una instancia concreta, si hay mas de una
        - cantidad es el numero de instancias a extraer
        - sigilo indica si se hace intentando que los otros personajes de la sala no
          se den cuenta

        Devuelve el nombre completo del objeto cogido.

        Lanza ExcepcionDeJuego si no se puede coger (el id no existe, se piden mas
        instancias de las que hay, etc.)

	Si una o más instancias del objeto pertenece a un personaje distinto,
	toma las acciones correspondientes.

        Requiere:
            id_objeto != None
            n_orden >= 1
            n_instancia >= 1
            cantidad >= 1

        Asegura:
            return != None
        """
	#%% PARCHE provisional: cuando los parámetros provienen de un guion, vienen con tipo float
	#%% pero esto produce problemas. De momento, con las prisas, lo arreglo aquí...
	n_orden = int(n_orden)
	n_instancia = int(n_instancia)
	cantidad = int(cantidad)
	
        n_objeto = self.__sala.buscar_objeto(id_objeto, n_orden)
        objetos = self.__sala.coger(n_objeto, n_instancia, cantidad)
        self.meter_en_mochila(objetos)

        if not sigilo:
            #%% aquí habría que hacer una tirada en algún sitio...
            self.actuar("ha cogido " + objetos[0].nombre)

	for objeto in objetos:
	    if objeto.propietario != None and \
	       objeto.propietario != self.__id.split("%")[0]:

  	        #%%PROVISIONAL: aquí lo que hay que hacer en realidad
		#es lanzar un suceso sobre el propietario
		if self.__controlador != None:
		    self.__controlador.suceso_entorno("Este objeto es propiedad de " +
		                                      objeto.propietario + ".")

		    for id in self.__sala.personajes:
			personaje = self.__sala.personajes[id]
			if personaje.id.split("%")[0] == objeto.propietario:
			    self.__controlador.suceso_entorno(
			        "Su valor es de " + 
				str(personaje.precio_venta_objeto(objeto, self)) +
				" monedas.")

        return objetos[0].nombre


    def dejar(self, id_objeto, n_orden=1, n_instancia=1, cantidad=1, sigilo=0):
        """Saca una o más instancias de un objeto de la mochila y las suelta en la sala.

        Resto de información del método igual que coger().
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objetos = self.sacar_de_mochila(n_objeto, n_instancia, cantidad)
        self.__sala.dejar(objetos)

        if not sigilo:
            #%% aquí habría que hacer una tirada en algún sitio...
            self.actuar("ha dejado " + objetos[0].nombre)

        return objetos[0].nombre



    def esconder(self, id_objeto, n_orden=1, n_instancia=1, cantidad=1, sigilo=1):
        """Saca un objeto de la mochila y lo suelta en la sala, dejándolo escondido.

        La dificultad para encontrarlo dependerá de la inteligencia del personaje.

        Resto de información del método igual que coger().
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objetos = self.sacar_de_mochila(n_objeto, n_instancia, cantidad)
        dificultad = self.generar_dificultad("inteligencia", 0)
        self.__sala.dejar(objetos, dificultad)

        if not sigilo:
            #%% aquí habría que hacer una tirada en algún sitio...
            self.__personaje.actuar("ha escondido algo")

        return objeto.nombre


    def dar(self, receptor, id_objeto, n_orden=1, n_instancia=1, cantidad=1, sigilo=0):
        """Saca una o más instancias de un objeto de la mochila y las mete en la
        mochila de otro personaje.

        El otro personaje sólo aceptará el objeto si el personaje que da está en la lista
        de confianza del receptor (salvo si el que da es un Maia). 
        
        Genera un suceso de actuar, salvo si se indica con sigilo, y otro de actuar
        individual.

	Si el objeto es propiedad del que lo lleva, el propietario desaparece. Si
	es propiedad de un tercero, no se puede dar.

        - receptor es el personaje que recibe el objeto

        Resto de información del método igual que coger().
        """
	# Comprobar que el personaje que da el objeto se encuentra en la lista
	# de confianza del receptor (salvo que el que da sea un Maia...), y que
	# la confianza es >= 0.
	if not self.__es_maia:
	    try:
		confianza = receptor.ids_amigos[self.__id]
		if confianza < 1:
		    raise KeyError
	    except KeyError, e:
		self.actuar_individual(receptor, "te ha intentado dar " + id_objeto + " pero te has negado.")
		raise ExcepcionDeJuego(receptor.nombre + " no acepta tu objeto.")

        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objetos = self.sacar_de_mochila(n_objeto, n_instancia, cantidad)

	# Ver si alguno de los objetos tiene dueño
	for objeto in objetos:
	    if objeto.propietario != None:
		if objeto.propietario == self.__id.split("%")[0]:
		    objeto.cambiar_propietario(None)
		else:
		    self.meter_en_mochila(objetos)
		    raise ExcepcionDeJuego("No puedes dar " + objeto.nombre + 
					   ". No es de tu propiedad.")

	receptor.meter_en_mochila(objetos)

	if not sigilo:
	    #%% aquí habría que hacer una tirada en algún sitio...
	    self.__personaje.actuar("ha dado " + objetos[0].nombre + " a " + receptor.nombre)

	msg = "te ha dado " + objetos[0].nombre
	if cantidad > 1:
	    msg += ' (' + str(cantidad) + ' unidades)'
	self.actuar_individual(receptor, msg)

	return objetos[0].nombre


    def comprar(self, id_objeto, n_orden=1, n_instancia=1, cantidad=1):
	"""Este personaje compra un objeto a su propietario.

	El objeto debe estar ya en posesión del personaje comprador (o sea, éste).
	Si este personaje tiene suficiente dinero, se le transfiere al propietario 
	una cantidad igual al precio de venta de ese objeto, y a cambio el propietario
	le transfiere la propiedad del objeto.

        - id_objeto es el identificador del objeto. Puede haber varios objetos distintos
          con el mismo id
        - n_orden sirve para distinguir el objeto si hay mas de uno con el mismo id
        - n_instancia es para elegir una instancia concreta, si hay mas de una
        - cantidad es el numero de instancias a extraer

	Genera un suceso actuar_uno_mismo.

	Requiere:
            id_objeto != None
            n_orden >= 1
            n_instancia >= 1
            cantidad >= 1
	"""
	n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
	instancias = self.__mochila[n_objeto]

        if (n_instancia + cantidad - 1) > len(instancias):
            raise ExcepcionDeJuego("No tienes " + str(n_instancia + cantidad - 1) +
                                   " unidades de ese objeto en la mochila, solo tienes " + 
                                   str(len(instancias)))

        objetos_comprados = []
        for i in range(0, cantidad):
	    objeto = instancias[n_instancia - 1 + i]
            objetos_comprados.append(objeto)

        # Buscar al propietario en la sala actual.
	id_propietario = objetos_comprados[0].propietario
	if id_propietario == None:
	    raise ExcepcionDeJuego("No es necesario comprar %s. No tiene propietario." %
	                           (id_objeto))
	if id_propietario == self.id.split("%")[0]:
	    raise ExcepcionDeJuego("No es necesario comprar %s. Ya es de tu propiedad." %
	                           (id_objeto))

	propietario = None
	for personaje in self.__sala.personajes.values():
	    if personaje != self and personaje.controlador != None and personaje.id.lower().split("%")[0] == id_propietario.lower():
		propietario = personaje
		break

	if propietario == None:
	    raise ExcepcionDeJuego("El propietario de %s (%s) no se encuentra en la sala" %
	                           (id_objeto, id_propietario))

	#%%PROVISIONAL: esto deberá hacerse mediante guiones, cuando estén hechos
	precio = propietario.precio_venta_objeto(objetos_comprados[0], self) * cantidad
	if self.dinero < precio:
	    raise ExcepcionDeJuego("No tienes suficiente dinero. " + 
	                           "El precio son %d monedas y tú sólo tienes %d." %
	                            (precio, self.dinero))

	self.pagar(propietario, precio)
	for objeto in objetos_comprados:
	    objeto.cambiar_propietario(None)

	self.actuar_uno_mismo("Has comprado %s a %s por %d monedas." %
	                      (id_objeto, id_propietario, precio))


    def comer(self, id_objeto, n_orden=1, n_instancia=1, sigilo=0):
        """Come una instancia de un objeto de la mochila.

        Genera un suceso de actuar, salvo si se indica con sigilo, y un suceso personaje_come
        en el objeto comido.

        - id_objeto es el identificador del objeto. Puede haber varios objetos distintos
          con el mismo id
        - n_orden sirve para distinguir el objeto si hay mas de uno con el mismo id
        - n_instancia es para elegir una instancia concreta, si hay mas de una
        - sigilo indica si se hace intentando que los otros personajes de la sala no
          se den cuenta

        Devuelve el nombre completo del objeto cogido.

        Lanza ExcepcionDeJuego si no se puede coger (el id no existe, se piden mas
        instancias de las que hay, algún requisito lo impide, etc.)

        Requiere:
            id_objeto != None
            n_orden >= 1
            n_instancia >= 1

        Asegura:
            return != None
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objeto = self.acceder_a_mochila(n_objeto, n_instancia)

	if objeto.propietario != None and \
	   objeto.propietario != self.__id.split("%")[0]:
	       raise ExcepcionDeJuego("No puedes usar este objeto. No es de tu propiedad.")
	   
        result = objeto.personaje_come(self)
        if result == 0:
            raise ExcepcionDeJuego("No puedes comer este objeto.")
        else:
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("se come " + objeto.nombre)

        return objeto.nombre


    def beber(self, id_objeto, n_orden=1, n_instancia=1, sigilo=0):
        """Bebe una instancia de un objeto de la mochila.

        Resto de información del método igual que comer().
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objeto = self.acceder_a_mochila(n_objeto, n_instancia)

	if objeto.propietario != None and \
	   objeto.propietario != self.__id.split("%")[0]:
	       raise ExcepcionDeJuego("No puedes usar este objeto. No es de tu propiedad.")

        result = objeto.personaje_bebe(self)
        if result == 0:
            raise ExcepcionDeJuego("No puedes beber este objeto.")
        else:
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("se bebe " + objeto.nombre)

        return objeto.nombre


    def usar(self, id_objeto, n_orden=1, n_instancia=1, sigilo=0):
        """Usa una instancia de un objeto de la mochila.

        Resto de información del método igual que comer().
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objeto = self.acceder_a_mochila(n_objeto, n_instancia)

	if objeto.propietario != None and \
	   objeto.propietario != self.__id.split("%")[0]:
	       raise ExcepcionDeJuego("No puedes usar este objeto. No es de tu propiedad.")

        result = objeto.personaje_usa(self)
        if result == 0:
            raise ExcepcionDeJuego("No puedes usar este objeto.")
        else:
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("usa " + objeto.nombre)

        return objeto.nombre


    def usar_con(self, id_objeto, n_orden=1, n_instancia=1, 
                 id_objeto2=None, n_orden2=1, n_instancia2=1,
                 sigilo=0):
        """Usa una instancia de un objeto de la mochila, con otra instancia de objeto,
        que puede estar en la mochila o en la sala.

        Resto de información del método igual que comer().
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objeto = self.acceder_a_mochila(n_objeto, n_instancia)

	if objeto.propietario != None and \
	   objeto.propietario != self.__id.split("%")[0]:
	       raise ExcepcionDeJuego("No puedes usar este objeto. No es de tu propiedad.")

        try:
            n_objeto2 = self.buscar_en_mochila(id_objeto2, n_orden2)
            objeto2 = self.acceder_a_mochila(n_objeto2, n_instancia2)
        except ExcepcionDeJuego:
            n_objeto2 = self.__sala.buscar_objeto(id_objeto2, n_orden2)
            objeto2 = self.__sala.acceder_a_objeto(n_objeto2, n_instancia2)

        result = objeto.personaje_usa_con(self, objeto2)
        if result == 0:
            raise ExcepcionDeJuego("No puedes usar este objeto.")
        else:
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("usa " + objeto.nombre + " con " + objeto2.nombre)

        return objeto.nombre


    def meter_en_mochila(self, objetos):
        """Introducir una o varias instancias de un objeto en la mochila del personaje.

        Requiere:
            objetos != None
            len(objetos) >= 1
        """
        for objeto in objetos:
            objeto.contener(self)

        for instancias in self.__mochila:
            if instancias[0].id == objetos[0].id:
                instancias.extend(objetos)
                break
        else:
            self.__mochila.append(objetos)


    def buscar_en_mochila(self, id_objeto, n_orden=1):
        """Localiza un objeto dentro de la mochila del personaje.

        - id_objeto es el identificador del objeto. Puede haber varios objetos distintos
          con el mismo id
        - n_orden sirve para distinguir el objeto si hay mas de uno con el mismo id

        Devuelve el indice dentro de la mochila que apunta al objeto buscado.

        Lanza ExcepcionDeJuego si no se encuentra.

        Requiere:
            id_objeto != None
            n_orden >= 1

        Asegura:
            return in range(0, len(self.__mochila))
        """
        encontrados = 0
        n_objeto = -1

        for instancias in self.__mochila:
            n_objeto = n_objeto + 1
            if normalizar_id(id_objeto) == extraer_id(instancias[0].nombre):
                encontrados = encontrados + 1
                if encontrados == n_orden:
                    break
                    
        if encontrados == 0:
            raise ExcepcionDeJuego("No tienes en la mochila ningún objeto con identificativo " +
                                   id_objeto)
        elif encontrados < n_orden:
            raise ExcepcionDeJuego("No tienes en la mochila suficientes objetos con identificativo " +
                                   id_objeto + ", solo tienes " + str(encontrados))
        else:
            return n_objeto


    def objeto_en_mochila(self, id_objeto):
	"""Pregunta si existe en la mochila un objeto con el id indicado."""
	try:
	    indice = self.buscar_en_mochila(id_objeto)
	    return True
	except ExcepcionDeJuego:
	    return False


    def acceder_a_mochila(self, n_objeto, n_instancia=1):
        """Accede a una instancia de un objeto para trabajar con ella, pero sin sacarla de la mochila.
        
        - n_objeto es el número de orden del objeto dentro de la mochila
        - n_instancia es el número de orden de la instancia a extraer

        Devuelve la instancia extraida.

        Lanza ExcepcionDeJuego si no puede acceder (por ejemplo, se ha pedido un número de instancia
        mayor que las que hay).

        Requiere:
            n_objeto in range(0, len(self.mochila))
            n_instancia >= 1

        Asegura:
            return != None
        """
        instancias = self.__mochila[n_objeto]
        if (n_instancia) > len(instancias):
            raise ExcepcionDeJuego("No tienes " + str(n_instancia) +
                                   " unidades de ese objeto en la mochila, solo tienes " + 
                                   str(len(instancias)))

        objeto = instancias[n_instancia-1]

        return objeto


    def sacar_de_mochila(self, n_objeto, n_instancia=1, cantidad=1):
        """Sacar una o varias instancias de un objeto de la mochila del personaje.
        
        - n_objeto es el número de orden del objeto dentro de la mochila
        - n_instancia es el número de orden de la primera instancia a extraer
        - cantidad es el número de instancias a extraer

        Devuelve una lista con las instancias extraidas.

        Lanza ExcepcionDeJuego si no puede sacarlas (por ejemplo, se han pedido
        más instancias de las que hay).

        Requiere:
            n_objeto in range(0, len(self.mochila))
            n_instancia >= 1
            cantidad >= 1

        Asegura:
            return != None
            len(return) == cantidad
        """
        instancias = self.__mochila[n_objeto]

        if (n_instancia + cantidad - 1) > len(instancias):
            raise ExcepcionDeJuego("No tienes " + str(n_instancia + cantidad - 1) +
                                   " unidades de ese objeto en la mochila, solo tienes " + 
                                   str(len(instancias)))

        objetos_sacados = []

        for i in range(0, cantidad):
            objetos_sacados.append(instancias[n_instancia-1])
            instancias[n_instancia-1].activar()
            instancias[n_instancia-1].liberar()
            del instancias[n_instancia-1]

        if len(instancias) == 0:
            del self.__mochila[n_objeto]

        return objetos_sacados


#%%    def sacar_de_mochila(self, n_objeto, n_instancia):
#%%     """Sacar un objeto de la mochila del personaje.
#%%
#%%     n_objeto es el numero de orden dentro de la mochila, y n_instancia es el
#%%     numero de la instancia, ambos empezando por cero.
#%%
#%%     Requiere:
#%%         n_objeto in range(0, len(self.mochila)-1)
#%%         n_instancia in range(0, len(self.mochila[n_objeto])-1)
#%%
#%%     Asegura:
#%%         return != None
#%%     """
#%%     objeto = self.__mochila[n_objeto][n_instancia]
#%%
#%%     del self.__mochila[n_objeto][n_instancia]
#%%     if len(self.__mochila[n_objeto]) == 0:
#%%         del self.__mochila[n_objeto]
#%%
#%%     return objeto


    def reservar_posicion(self, posicion):
        """Quitar todos los objetos necesarios para poder ponerse uno nuevo en
        la posicion indicada.

        Normalmente solo habra que quitar el objeto que ya este en esa posicion,
        si hay alguno. Pero con las posiciones "mano-izq", "mano-der" y "dos-manos"
        puede haber que quitar mas de uno.

        Devuelve una lista con todos los objetos que se haya tenido que quitar.

        Requiere:
            posicion in Personaje.POSICIONES

        Asegura:
            return != None
        """
        objetos_quitados = []

        if self.__cuerpo[posicion] != None:
            objetos_quitados.append(self.__cuerpo[posicion])
            self.quitarse(posicion)

        if posicion == "dos-manos":
            if self.__cuerpo["mano-izq"] != None:
                objetos_quitados.append(self.__cuerpo["mano-izq"])
                self.quitarse("mano-izq")
            if self.__cuerpo["mano-der"] != None:
                objetos_quitados.append(self.__cuerpo["mano-der"])
                self.quitarse("mano-der")

        if posicion == "mano-izq" or posicion == "mano-der":
            if self.__cuerpo["dos-manos"] != None:
                objetos_quitados.append(self.__cuerpo["dos-manos"])
                self.quitarse("dos-manos")

        return objetos_quitados


    def ponerse(self, posicion, id_objeto, n_orden=1, n_instancia=1, sigilo=0):
        """Ponerse un objeto en una posicion del cuerpo.

        Si ya hay algún objeto en esa posición, se lo quita y lo guarda en la mochila.

        Genera un suceso de actuar_uno_mismo, uno de actuar (salvo si se indica sigilo)
        y uno de personaje_pone_XXXX en el objeto puesto.

        - posicion es la parte del cuerpo donde se pone
        - id_objeto es el identificador del objeto. Puede haber varios objetos distintos
          con el mismo id
        - n_orden sirve para distinguir el objeto si hay mas de uno con el mismo id
        - n_instancia es para elegir una instancia concreta, si hay mas de una
        - sigilo indica si se hace intentando que los otros personajes de la sala no
          se den cuenta

        Devuelve el nombre completo del objeto puesto.

        Lanza ExcepcionDeJuego si no se puede coger (el id no existe, se piden mas
        instancias de las que hay, algún requisito lo impide, etc.)

        Requiere:
            posicion in Personaje.POSICIONES
            id_objeto != None
            n_orden >= 1
            n_instancia >= 1

        Asegura:
            self.cuerpo[posicion] == objeto
            return != None
        """
        n_objeto = self.buscar_en_mochila(id_objeto, n_orden)
        objeto = self.acceder_a_mochila(n_objeto, n_instancia)

	if objeto.propietario != None and \
	   objeto.propietario != self.__id.split("%")[0]:
	       raise ExcepcionDeJuego("No puedes usar este objeto. No es de tu propiedad.")

        suceso = getattr(objeto, "personaje_pone_" + posicion)
        result = suceso(self)
        if result == 0:
            raise ExcepcionDeJuego("No te puedes poner este objeto en esa posicion.")
        else:
	    objetos_quitados = self.reservar_posicion(posicion)
	    if len(objetos_quitados) > 0:
		self.meter_en_mochila(objetos_quitados)

            objeto = self.sacar_de_mochila(n_objeto, n_instancia, 1)[0]
            objeto.contener(self)
            self.__cuerpo[posicion] = objeto
            self.actuar_uno_mismo("Te pones " + objeto.nombre + " en " + posicion)
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("se pone " + objeto.nombre + " en " + posicion)

        return objeto.nombre


    def quitarse(self, posicion, sigilo=0):
        """Quitarse el objeto que esté en una posición del cuerpo y meterlo en la mochila.

        Genera un suceso de actuar_uno_mismo, uno de actuar (salvo si se indica sigilo)
        y uno de personaje_quita_XXXX en el objeto quitado.

        Devuelve el nombre completo del objeto quitado.

        Lanza ExcepcionDeJuego si no hay ningún objeto en la posición indicada o algún
        requisito lo impide.

        Requiere:
            posicion in Personaje.POSICIONES

        Asegura:
            self.cuerpo[posicion] == None
            return != None
        """
        if self.__cuerpo[posicion] == None:
            raise ExcepcionDeJuego("No llevas nada puesto en " + posicion)

        objeto = self.__cuerpo[posicion]

        suceso = getattr(objeto, "personaje_quita_" + posicion)
        result = suceso(self)
        if result == 0:
            raise ExcepcionDeJuego("No te puedes quitar este objeto de esa posicion.")
        else:
            del self.__cuerpo[posicion]
            objeto.liberar()
            self.meter_en_mochila([objeto])
            self.actuar_uno_mismo("Te quitas " + objeto.nombre + " de " + posicion)
            if not sigilo:
                #%% aquí habría que hacer una tirada en algún sitio...
                self.actuar("se quita " + objeto.nombre + " de " + posicion)

        return objeto.nombre


    def objeto_puesto(self, id_objeto):
	"""Pregunta si el personaje lleva puesto, en cualquier posición, un objeto con
	el id indicado."""
	for pos in self.cuerpo:
	    obj = self.cuerpo[pos]
	    if obj != None and obj.id == id_objeto:
		return True
	return False


    def objeto_desaparece(self, objeto):
	"""Un objeto contenido en este personaje ha desaparecido.

	Hay que eliminarlo de la mochila o del cuerpo, donde esté. No ocurre ningún suceso
	en particular. Este método será llamado sólo desde el propio objeto, a través del 
	interfaz Contenedor.
	"""
	objeto.liberar()

	encontrado = False
	for i, instancias in izip(self.__mochila):
	    for j, instancia in izip(instancias):
		if instancia == objeto:
		    del instancias[j]
		    if len(instancias) == 0:
			del self.__mochila[i]
		    encontrado = True
		    break

	if not encontrado:
	    for parte in self.__cuerpo.keys():
		if self.__cuerpo[parte] == objeto:
		    del self.__cuerpo[parte]
		    break


    def objeto_aparece(self, objeto):
	"""Un objeto ha aparecido en este personaje (transportado automáticamente).

	Hay que añadir el obeto a la mochila. No ocurre ningún suceso en particular.
	Este método será llamado sólo desde el propio objeto, a través del
	interfaz Contenedor.
	"""
	objeto.contener(self)
        
	for instancias in self.__mochila:
	    if instancias[0].id == objeto.id:
		instancias.append(objeto)
		break
	else:
	    self.__mochila.append([objeto])


    def objeto_reemplazado(self, objeto_antiguo, objeto_nuevo):
        """Un objeto contenido en este personaje ha sido destruido, y reemplazado por otro.

	Hay que eliminar el antiguo de la mochila o del cuerpo, donde esté, y añadir el nuevo
	en el mismo lugar donde estaba el antiguo. No ocurre ningún suceso en particular. Este
	método será llamado sólo desde el propio objeto, a través del interfaz Contenedor.
	"""
	objeto_antiguo.liberar()

	encontrado = False
	for i, instancias in izip(self.__mochila):
	    for j, instancia in izip(instancias):
		if instancia == objeto_antiguo:
		    del instancias[j]
		    if len(instancias) == 0:
			del self.__mochila[i]
		    encontrado = True
		    break

	if not encontrado:
	    for parte in self.__cuerpo.keys():
		if self.__cuerpo[parte] == objeto_antiguo:
		    del self.__cuerpo[parte]
		    break
	else:
	    parte = None

        objeto_nuevo.contener(self)
        
        if parte == None:
            for instancias in self.__mochila:
                if instancias[0].id == objeto_nuevo.id:
                    instancias.append(objeto_nuevo)
                    break
            else:
                self.__mochila.append([objeto_nuevo])
        else:
            self.__cuerpo[parte] = objeto_nuevo


    def buscar_objeto_ajeno(self):
	"""Busca en la mochila algún objeto propiedad de otro personaje.

	Devuelve None si no lleva, el objeto en cuestión si lleva uno, o el primero
	que haya si lleva más de uno.
	"""
	for objeto in self.__mochila:
	    instancia = objeto[0]

	    if instancia.propietario != None \
	       and instancia.propietario != self.__id.split("%")[0]:
		return instancia

	return None


    def personaje_mira(self, observador, sala):
	"""Responde al suceso de que otro personaje mira la descripción de éste.

	Por cada item de descripcion, chequea los requisitos y realiza las acciones
	correspondientes. Al final devuelve una lista con todos los items de descripción
	cuyo guion de respuesta devuelto un resultado de éxito.

	Requiere:
	    observador != None
	    sala != None
	"""
	items_devueltos = []

	for item in self.__descripcion:
	    if item["personaje_mira"] == None:
		items_devueltos.append(item)
	    else:
		respuesta = item["personaje_mira"]
		result =  respuesta.ejecutar(macros_valores = {"$observador": observador,
							       "$sala": sala,
							       "$personaje": self})

		if result == None:  # Si ninguna acción acepta o cancela explícitamente el
		    result = 1      # suceso, se asume que se acepta

		if result:
		    items_devueltos.append(item)

	return items_devueltos


# Código para pruebas unitarias del módulo.

if (__name__ == '__main__'):
    import Mundo
    sala = Mundo.localizar_sala(Mundo.ID_SALA_LIMBO)
    pers = Personaje("UnPersonaje", sala)
    print ''
    print 'Creado personaje'
    print ' nombre = ' + str(pers.id)
    print ' nombre_largo = ' + str(pers.nombre)
    print ' experiencia = ' + str(pers.experiencia)
    print ' nivel = ' + str(pers.nivel)
    print ' atributos =', pers.atrib
    print ' vida = ' + str(pers.vida)
    print ' sala = ' + pers.sala.nombre
    print ' idiomas =' , pers.idiomas

