#!/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: PersonajeJugador.py

"""Clase PersonajeJugador y derivadas segun razas."""

import logging
import sys
import Personaje
import FechaHoraMine
import CronoMine

log = logging.getLogger("PJ")

class PersonajeJugador(Personaje.Personaje):
    """Cualquier personaje controlado por un jugador.

    Contiene los atributos comunes de un personaje, y ademas los particulares de un
    personaje jugador.

    Los valores de los atributos de personaje (fuerza, destreza, etc.) tienen un rango
    que depende de los modificadores por raza y profesion:

     - Si el total de modificadores es 0, el minimo es 0 y el maximo 18.
     - Si es distinto de 0, dicho total se suma al minimo y al maximo (por ejemplo,
       si es +3 el rango sera de 3 a 21).
     - Aunque el mínimo sea 0, ningún atributo de un personaje real puede valer menos
       de 3. Si el total de modificadores no supera ese valor, habrá que invertir
       puntos obligatorios en subirlo a 3 (por ejemplo, si el modificador es -1, el
       rango ira de -1 a 17, pero es obligatorio invertir 4 puntos en subir el
       atributo hasta 3).
    """

    # Atributos públicos de la clase

    def __leer_clave(self): return self.__clave

    clave = property(__leer_clave,
    		     doc="""(string): la clave de acceso a este personaje.

		         Invariantes:
      			     clave != None
		         """)


    def __leer_es_maia(self): return self.__es_maia

    es_maia = property(__leer_es_maia,
                       doc="""(int): si vale true, el PJ es un Maia y tiene acceso a comandos
                           restringidos para masters.

			   Invariantes:
      			       es_maia in [0, 1]
			   """)

    def __leer_raza(self): return self.__raza

    raza = property(__leer_raza,
                    doc="""(Raza): raza del personaje.

		        Invariantes:
			    raza != None
		        """)

    def __leer_profesion(self): return self.__profesion

    profesion = property(__leer_profesion,
                         doc="""(Profesion): profesion del personaje.
			 
			     Invariantes:
      				 profesion != None
			     """)

    def __leer_sexo(self): return self.__sexo

    sexo = property(__leer_sexo,
                    doc="""(string): masculino o femenino.
		    
		        Invariantes:
      			    sexo in ["masculino", "femenino"]
		        """)

    def __leer_edad(self): return self.__edad 

    edad = property(__leer_edad,
                    doc="""(int): edad del personaje en años.
		    
		        Invariantes:
			    edad > 0
		        """)

    def __leer_altura(self): return self.__altura 

    altura = property(__leer_altura,
                      doc="""(float): altura del personaje en metros punto centimetros.
		      
		          Invariantes:
			      altura > 0.0
		          """)

    def __leer_peso(self): return self.__peso 

    peso = property(__leer_peso,
                    doc="""(int): peso del personaje en kilos.
		    
		        Invariantes:
			    peso > 0
		        """)

    def __leer_color_de_ojos(self): return self.__color_de_ojos 

    color_de_ojos = property(__leer_color_de_ojos,
                             doc="""(string): color de los ojos del personaje (en singular).
			     
			         Invariantes:
				     color_de_ojos != None
			         """)

    def __leer_color_de_pelo(self): return self.__color_de_pelo 

    color_de_pelo = property(__leer_color_de_pelo,
                             doc="""(string): color del pelo del personaje.
			     
			         Invariantes:
				     color_de_pelo != None
			         """)

    def __leer_silencio(self): return self.__silencio 

    silencio = property(__leer_silencio,
                        doc="""(string): vale 'si' o 'no'. Cuando vale 'si', el jugador
			    desactiva los mensajes de eventos que ocurran en la sala.
			
			    Invariantes:
			        silencio in ["si", "no"]
			    """)

    def __leer_hambre(self): return self.__hambre 

    hambre = property(__leer_hambre,
                      doc="""(int): nivel de hambre del personaje (de 0 = satisfecho
		          a 15 = muriendose de hambre).

			  Invariantes:
			      hambre in range(0, 16)
			  """)


    def __leer_mensaje_hambre(self):
	return self.__raza.niveles_hambre[int(self.__hambre)]["mensaje"]

    mensaje_hambre = property(__leer_mensaje_hambre,
                              doc="""(string): texto que refleja el hambre que tiene
			          el personaje.

				  Invariantes:
				      mensaje_hambre != None
				  """)


    def __leer_sed(self): return self.__sed 

    sed = property(__leer_sed,
                   doc="""(int): nivel de sed del personaje (de 0 = satisfecho
		          a 15 = muriendose de sed).

			  Invariantes:
			      sed in range(0, 16)
			  """)


    def __leer_mensaje_sed(self):
	return self.__raza.niveles_sed[int(self.__sed)]["mensaje"]

    mensaje_sed = property(__leer_mensaje_sed,
                           doc="""(string): texto que refleja el sed que tiene
			       el personaje.

			       Invariantes:
				   mensaje_sed != None
			       """)

    # Constructor

    def __init__(self, id, clave, es_maia, nombre, descripcion, experiencia, nivel, vida,
                 volumen, estado, aura, sala, controlador, atrib, idiomas, idioma_base,
                 dinero, raza, profesion, sexo, edad, altura, peso, color_de_ojos,
                 color_de_pelo):
        """Crea un nuevo personaje jugador a partir de sus atributos.

        Hay que crear el personaje cuando todas las propiedades ya estén totalmente
        definidas, para evitar tener personajes inconsistentes.

        Requiere:
          Todos los parámetros satisfacen las invariantes
        """
        Personaje.Personaje.__init__(self, id, sala, nombre, descripcion, experiencia,
		                     nivel, vida, volumen, estado, aura, controlador, atrib,
				     idiomas, idioma_base, dinero)

	# Atributos privados:
	#
	# - __crono_subir_hambre(CronoMine): cronometro que cada vez que vence, sube
	#                                    un nivel de hambre
	# - __crono_msg_hambre(CronoMine): cronometro que cada vez que vence, muestra
	#                                  un mensaje de hambre y produce daño
	# - __crono_subir_sed(CronoMine): cronometro que cada vez que vence, sube
	#                                 un nivel de sed
	# - __crono_msg_sed(CronoMine): cronometro que cada vez que vence, muestra
	#                               un mensaje de sed y produce daño
	#
	# Invariantes:
	#   self.__crono_subir_hambre != None
	#   self.__crono_subir_sed != None

        self.__clave = clave
        self.__es_maia = es_maia
        self.__raza = raza
        self.__profesion = profesion
        self.__sexo = sexo
        self.__edad = edad
        self.__altura = altura
        self.__peso = peso
        self.__color_de_ojos = color_de_ojos
        self.__color_de_pelo = color_de_pelo
        self.__silencio = 'no'
	self.__hambre = 0
	self.__sed = 0

	self.__crono_subir_hambre = None
	self.__crono_subir_sed = None
	self.__crono_msg_hambre = None
	self.__crono_msg_sed = None

	self.__efectos_hambre()
	self.__efectos_sed()

	# Dos opciones según el personaje venga ya controlado o no
	if self._Personaje__controlador == None:

	    import Mundo

	    # No controlado: arranca en el limbo y la sala pedida
	    # se guarda para cuando alguien lo saque.
	    self._Personaje__id_sala_original = sala.id
	    
	    self._Personaje__sala = Mundo.localizar_sala(Mundo.ID_SALA_LIMBO)
	    self._Personaje__sala.entrar_personaje(self)  # se asume que el limbo tiene
			 		                  # capacidad de volumen infinita

	    # Al estar en el limbo, los cronometros estan pausados
	    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()

	    self.actuar_global("ha sido creado")

	else:

	    import Mundo

	    # Controlado: se lleva a la sala pedida y se avisa al mundo.
	    Mundo.personaje_activado(self)
	    self.actuar_global("ha sido creado")
		
	    self._Personaje__sala = sala
	    for otro_personaje in self._Personaje__sala.personajes.values():
		if otro_personaje._Personaje__controlador != None:
		    otro_personaje._Personaje__controlador.entra_personaje(self)
	    self._Personaje__sala.entrar_personaje(self)  # se asume que la sala de entrada tiene
					                  # capacidad de volumen infinita


    # métodos normales

    def alimentar(self, puntos_nutritivos):
	"""Se reduce el nivel de hambre de un personaje.
	
	El nivel reducido depende de la raza (para un humano, 1 punto nutritivo =
	1 nivel de hambre). Si llega a 0, el exceso de puntos nutritivos se
	pierde.

	Requiere:
	    puntos_nutritivos > 0
	"""
        self.__hambre -= puntos_nutritivos / self.__raza.puntos_nutritivos
	if self.__hambre < 0:
	    self.__hambre = 0
	self.__efectos_hambre()


    def hidratar(self, puntos_hidratantes):
	"""Se reduce el nivel de sed de un personaje.
	
	El nivel reducido depende de la raza (para un humano, 1 punto hidratante =
	1 nivel de sed). Si llega a 0, el exceso de puntos hidratantes se
	pierde.

	Requiere:
	    puntos_hidratantes > 0
	"""
        self.__sed -= puntos_hidratantes / self.__raza.puntos_hidratantes
	if self.__sed < 0:
	    self.__sed = 0
	self.__efectos_sed()
	

    def preguntar(self, interlocutor, tema):
	"""Pregunta a un PNJ sobre un tema.

	Requiere:
	    interlocutor != None
	    tema != None
	"""
	interlocutor.personaje_pregunta(self, tema)


    def decir(self, interlocutor, tema):
	"""Habla a un PNJ sobre un tema.

	Requiere:
	    interlocutor != None
	    tema != None
	"""
	interlocutor.personaje_dice(self, tema)


    # Métodos privados

    def __efectos_hambre(self):
	"""Ejecuta los efectos correspondientes según nivel de hambre."""
	
	import Mundo

	# Iniciar el cronometro para subir al siguiente nivel de hambre
	tiempo_subir_hambre = self.__raza.tiempo_hambre
	self.__crono_subir_hambre = CronoMine.crear(Mundo.cronoMine_control,
	                                            FechaHoraMine.crear_rel(horas = tiempo_subir_hambre),
	                                            self.__vencido_crono_subir_hambre)

 	# Comprobar los datos del nivel actual de hambre
	hambre_int = int(self.__hambre)
	nivel_hambre = self.__raza.niveles_hambre[hambre_int]
	if nivel_hambre["horas"] > 0:
	    # Si hay efectos, enviar el mensaje, producir el daño e iniciar
	    # el cronometro para el siguiente mensaje.
	    self.actuar_uno_mismo(nivel_hambre["mensaje"])
	    self.danar(nivel_hambre["puntos_dano"])
	    self.__crono_msg_hambre = CronoMine.crear(Mundo.cronoMine_control,
	                                              FechaHoraMine.crear_rel(horas = nivel_hambre["horas"]),
	                                              self.__vencido_crono_msg_hambre)
	else:
	    # Si no, destruir el crono, si lo hay
	    if self.__crono_msg_hambre != None:
	        self.__crono_msg_hambre.parar()
	        self.__crono_msg_hambre = None


    def __vencido_crono_subir_hambre(self, crono):
	"""Realiza las acciones que ocurren al subir un nivel de hambre."""
	self.__hambre += 1
	if self.__hambre > 15:
	    self.__hambre = 15
	self.__efectos_hambre()
	log.info('El personaje ' + self.id + ' aumenta un nivel de hambre.')


    def __vencido_crono_msg_hambre(self, crono):
	"""Realiza las acciones que ocurren al mostrar un mensaje de hambre."""

	import Mundo

	hambre_int = int(self.__hambre)
	nivel_hambre = self.__raza.niveles_hambre[hambre_int]
	if nivel_hambre["horas"] > 0:
	    # Si hay efectos, enviar el mensaje, producir el daño e iniciar
	    # el cronometro para el siguiente mensaje.
	    self.actuar_uno_mismo(nivel_hambre["mensaje"])
	    self.danar(nivel_hambre["puntos_dano"])
	    self.__crono_msg_hambre = CronoMine.crear(Mundo.cronoMine_control,
	                                              FechaHoraMine.crear_rel(horas = nivel_hambre["horas"]),
	                                              self.__vencido_crono_msg_hambre)

    def __efectos_sed(self):
	"""Ejecuta los efectos correspondientes según nivel de sed."""
	
	import Mundo

	# Iniciar el cronometro para subir al siguiente nivel de sed
	tiempo_subir_sed = self.__raza.tiempo_sed
	self.__crono_subir_sed = CronoMine.crear(Mundo.cronoMine_control,
	                                         FechaHoraMine.crear_rel(horas = tiempo_subir_sed),
	                                         self.__vencido_crono_subir_sed)

 	# Comprobar los datos del nivel actual de sed
	sed = int(self.__sed)
	nivel_sed = self.__raza.niveles_sed[sed]
	if nivel_sed["horas"] > 0:
	    # Si hay efectos, enviar el mensaje, producir el daño e iniciar
	    # el cronometro para el siguiente mensaje.
	    self.__actuar_uno_mismo(nivel_sed["mensaje"])
	    self.__danar(nivel_sed["puntos_dano"])
	    self.__crono_msg_sed = CronoMine.crear(Mundo.cronoMine_control,
	                                           FechaHoraMine.crear_rel(horas = nivel_sed["horas"]),
	                                           self.__vencido_crono_msg_sed)
	else:
	    # Si no, destruir el crono, si lo hay
	    if self.__crono_msg_sed != None:
	        self.__crono_msg_sed.parar()
	        self.__crono_msg_sed = None


    def __vencido_crono_subir_sed(self, crono):
	"""Realiza las acciones que ocurren al subir un nivel de sed."""
	self.__sed += 1
	if self.__sed > 15:
	    self.__sed = 15
	self.__efectos_sed()
	log.info('El personaje ' + self.id + ' aumenta un nivel de sed.')


    def __vencido_crono_msg_sed(self, crono):
	"""Realiza las acciones que ocurren al mostrar un mensaje de sed."""
	
	import Mundo

	sed = int(self.__sed)
	nivel_sed = self.__raza.niveles_sed[sed]
	if nivel_sed["horas"] > 0:
	    # Si hay efectos, enviar el mensaje, producir el daño e iniciar
	    # el cronometro para el siguiente mensaje.
	    self.__actuar_uno_mismo(nivel_sed["mensaje"])
	    self.__danar(nivel_sed["puntos_dano"])
	    self.__crono_msg_sed = CronoMinecrear(Mundo.cronoMine_control,
	                                          FechaHoraMine.crear_rel(horas = nivel_sed["horas"]),
	                                          self.__vencido_crono_msg_sed)


# Funciones públicas

def modif_atrib_total(atributo, raza, profesion):
    """Modificador total de un atributo para una raza y profesión.

    Requiere:
      atributo != None
      raza != None
      profesion != None
      atributo in raza.modif_atrib.keys()
      atributo in profesion.modif_atrib.keys()
    """
    return raza.modif_atrib[atributo] + profesion.modif_atrib[atributo]


def min_atrib(atributo, raza, profesion):
    """El valor menor que puede tener un atributo para una raza y profesión.

    Requiere:
      atributo != None
      raza != None
      profesion != None
      atributo in raza.modif_atrib.keys()
      atributo in profesion.modif_atrib.keys()
    """
    return max(3, modif_atrib_total(atributo, raza, profesion))


def max_atrib(atributo, raza, profesion):
    """El valor mayor que puede tener un atributo para una raza y profesión.

    Requiere:
      atributo != None
      raza != None
      profesion != None
      atributo in raza.modif_atrib.keys()
      atributo in profesion.modif_atrib.keys()
    """
    return 18 + modif_atrib_total(atributo, raza, profesion)


def ptos_obligatorios(raza, profesion):
    """La cantidad de puntos que hay que gastar obligatoriamente en atributos
       para una determinada raza y profesión.

    Requiere:
      raza != None
      profesion != None
    """
    total = 0
    for atributo in raza.modif_atrib.keys():
	modif = modif_atrib_total(atributo, raza, profesion)
	if modif < 3:
	    total += (3 - modif)
    return total


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

if (__name__ == '__main__'):
    print "No hay prueba unitaria de módulo."
