# Balazar
# Copyright (C) 2003-2005 Jean-Baptiste LAMY
#
# 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

import math
import tofu, soya, soya.tofu4soya
import balazar.sound, balazar.base as base, balazar.item
from balazar.item import Item, Weapon

def noop(): pass

ACTION_WAIT          = 0
ACTION_ADVANCE       = 1
ACTION_ADVANCE_LEFT  = 2
ACTION_ADVANCE_RIGHT = 3
ACTION_TURN_LEFT     = 4
ACTION_TURN_RIGHT    = 5
ACTION_GO_BACK       = 6
ACTION_GO_BACK_LEFT  = 7
ACTION_GO_BACK_RIGHT = 8
ACTION_JUMP          = 9
ACTION_STOP_JUMPING  = 10
ACTION_HURTED        = 11
ACTION_KILLED        = 12
ACTION_GUARD         = 13
ACTION_WALKED_ON     = 14

class Action(soya.tofu4soya.Action):
  prioritary = 1
  def __init__(self, action):
    self.action = action
    
  def is_crucial(self): return 1
  

ACTION_EQUIP_ITEM   = 21
ACTION_UNEQUIP_ITEM = 22
ACTION_GRAB_ITEM    = 23
ACTION_DROP_ITEM    = 24
ACTION_USE_ITEM     = 25

class ItemAction(soya.tofu4soya.Action):
  prioritary = 1
  def __init__(self, action, item):
    self.action   = action
    self.item_uid = item.uid
    
    
ACTION_FIGHT_LEFT          = 31
ACTION_FIGHT_RIGHT         = 32
ACTION_FIGHT_SAGITTAL      = 33
ACTION_FIGHT_CHARGE        = 34
ACTION_WEAPON_HURTED_LEFT  = 38
ACTION_WEAPON_HURTED_RIGHT = 39

class FightAction(soya.tofu4soya.Action):
  prioritary = 1
  def __init__(self, action, target = None):
    self.action     = action
    self.target_uid = (target and target.uid) or 0
    
ACTION_DISCUSSING     = 51
ACTION_END_DISCUSSING = 52
ACTION_DISCUSSION     = 53

class DiscussionAction(soya.tofu4soya.Action):
  prioritary = 1
  def __init__(self, action, character, text_key = ""):
    self.action        = action
    self.character_uid = (character and character.uid) or 0
    self.text_key      = text_key
    

class State(soya.tofu4soya.CoordSystState):
  def __init__(self, mobile):
    soya.tofu4soya.CoordSystState.__init__(self, mobile)
    
    self.animation = "attente"
    
  def is_crucial(self): return 0


class SoundState(tofu.State):
  def __init__(self, mobile, sound, gain = 1.0):
    tofu.State.__init__(self)
    
    self.sound = sound
    self.gain  = gain
    
class ItemState(tofu.State):
  def __init__(self, mobile, action, item_uid):
    tofu.State.__init__(self)
    
    self.action   = action
    self.item_uid = item_uid
    
    
class NewItemState(tofu.State):
  def __init__(self, item):
    tofu.State.__init__(self)
    
    self.item = item
    

ADD_ECLAT = 200
ADD_STEP  = 201
ADD_BLOOD = 202

class AddState(soya.tofu4soya.CoordSystState):
  def __init__(self, mobile, element):
    soya.tofu4soya.CoordSystState.__init__(self, mobile)
    
    self.element = element


STATE_KILL_LIFE = 222
STATE_HEAL_LIFE = 223

class ValueState(tofu.State):
  def __init__(self, mobile, type, value):
    tofu.State.__init__(self)
    
    self.type  = type
    self.value = value
    
    
_CONTEXT = None
_P       = soya.Point()
_V       = soya.Vector()
_P2      = soya.Point()
_V2      = soya.Vector()

_TEMP = [
  (_P , _V ),
  (_P2, _V2),
  ]

class Character(soya.tofu4soya.Mobile):
  range = 0.7
  couic_sound = "couic1.wav"
  die_sound   = "couic2.wav"
  move_speed  = 1.0
  
  def __init__(self):
    soya.tofu4soya.Mobile.__init__(self)
    
    self.lod                 = 0
    self.current_animation   = ""
    self.current_deplacement = ACTION_WAIT
    
    self.solid          = 0
    #self.perso.solid    = 0
    
    self.speed          = soya.Vector(self)
    
    self.radius         = 0.5 # We need radius * sqrt(2)/2 > max speed (here, 0.35)
    self.radius_y       = 1.0
    self.center         = soya.Point(self, 0.0, self.radius_y, 0.0)
    
    self.left   = soya.Vector(self, -1.0,  0.0,  0.0)
    #self.right  = soya.Vector(self,  1.0,  0.0,  0.0)
    self.down   = soya.Vector(self,  0.0, -1.0,  0.0)
    self.up     = soya.Vector(self,  0.0,  1.0,  0.0)
    self.front  = soya.Vector(self,  0.0,  0.0, -1.0)
    #self.back   = soya.Vector(self,  0.0,  0.0,  1.0)
    
    self.fight_skill = 2.0
    self.resistance  = 2.0
    
    self.life              = 1.0
    self.items             = []
    self.weapon            = None
    self.striking          = 0
    self.invincible        = 0
    self.jumping           = 0
    self.ground            = soya.Point()
    self.last_ground       = soya.Point()
    self.last_land_ground  = soya.Point()
    self.ground_dir        = soya.Vector()
    self.last_ground_dir   = soya.Vector()
    
    self.on_ground         = 0
    self.current_action    = None
    self.kill_rays         = ()
    
    self.relations         = {}
    
  def do_action(self, action):
    if   isinstance(action, FightAction):
      if not self.weapon: return
      if (not self.current_action) or not self.current_action.prioritary:
        action.duration = 0
        self.current_action = action
      return
      
    elif isinstance(action, ItemAction):
      if tofu.Unique.hasuid(action.item_uid):
        item = tofu.Unique.getbyuid(action.item_uid)
        if ((action.action == ACTION_EQUIP_ITEM) or (action.action == ACTION_UNEQUIP_ITEM) or (action.action == ACTION_USE_ITEM) or (action.action == ACTION_DROP_ITEM)) and not (self is item.owner):
          print "* Balazar * %s cannot equip / unequip / use / drop item %s: not owner !" % (self.uid, item.uid)
          return
        if ((action.action == ACTION_EQUIP_ITEM) or (action.action == ACTION_UNEQUIP_ITEM)) and not isinstance(item, balazar.item.EquipableItem):
          print "* Balazar * %s cannot equip / unequip item %s: not equipable !" % (self.uid, item.uid)
          return
        if (action.action == ACTION_USE_ITEM) and not isinstance(item, balazar.item.UseableItem):
          print "* Balazar * %s cannot use item %s: not useable !" % (self.uid, item.uid)
          return
        if (action.action == ACTION_EQUIP_ITEM) and (item.equiped):
          print "* Balazar * %s cannot equip item %s: already equiped !" % (self.uid, item.uid)
          return
        if  action.action == ACTION_EQUIP_ITEM: item.unequip_incompatible_items()
        if (action.action == ACTION_UNEQUIP_ITEM) and (not item.equiped):
          print "* Balazar * %s cannot unequip item %s: already unequiped !" % (self.uid, item.uid)
          return
        if (action.action == ACTION_DROP_ITEM) and not (self.on_ground):
          print "* Balazar * %s cannot drop item %s: not on ground !" % (self.uid, item.uid)
          return
        if (action.action == ACTION_DROP_ITEM) and item.equiped:
          self.doer.action_done(ItemState(self, ACTION_UNEQUIP_ITEM, action.item_uid)) # Unequip before dropping
        if (action.action == ACTION_GRAB_ITEM) and not (self.distance_to(item) < item.discussion_radius):
          print "* Balazar * %s cannot grab item %s: too far !" % (self.uid, item.uid)
          return
        self.doer.action_done(ItemState(self, action.action, action.item_uid))
      return
    
    elif isinstance(action, DiscussionAction):
      if   action.action == ACTION_DISCUSSING:
        if tofu.Unique.hasuid(action.character_uid):
          character = tofu.Unique.getbyuid(action.character_uid)
          character.controller.discussing(self)
          
      elif action.action == ACTION_END_DISCUSSING:
        if tofu.Unique.hasuid(action.character_uid):
          character = tofu.Unique.getbyuid(action.character_uid)
          character.controller.end_discussing(self)
          
      elif action.action == ACTION_DISCUSSION:
        if tofu.Unique.hasuid(action.character_uid):
          character = tofu.Unique.getbyuid(action.character_uid)
          # Ensure the discussion is valid (to avoid cheating in multiplayer mode)
          discussion_is_valid = getattr(character, "discussion_is_valid_%s" % action.text_key, None)
          if discussion_is_valid and not discussion_is_valid(self): return
          discussion_action = getattr(character, "discussion_action_%s" % action.text_key, None)
          if discussion_action: discussion_action(self)
      return
    
    if not self.level: return
    
    state = State(self)
    
    if action:
      if   action.action == ACTION_GUARD:
        if not self.current_action:
          self.strinking = 0
          self.current_action = action
          action.prioritary = 0
          
      elif action.action == ACTION_KILLED: self.hurt(0.5, self, can_resist = 0)
      
      if 0 <= action.action < 9: self.current_deplacement = action.action
      
    if   self.current_deplacement in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
      state.rotate_lateral( 4.0)
      state.animation = "tourneG"
    elif self.current_deplacement in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
      state.rotate_lateral(-4.0)
      state.animation = "tourneD"
      
    if   self.current_deplacement in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
      state.shift(0.0, 0.0, -0.25 * self.move_speed)
      state.animation = "marche"
    elif self.current_deplacement in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
      state.shift(0.0, 0.0, 0.15 * self.move_speed)
      state.animation = "recule"
      
    if self.on_ground == 2: # For mobile platform
      self.ground.convert_to(self.level)
      state.add_xyz(self.ground.x - self.last_ground.x, self.ground.y - self.last_ground.y, self.ground.z - self.last_ground.z)
      
      self.ground_dir.convert_to(state)
      self.ground_dir.y = 0.0
      axe   = self.last_ground_dir.cross_product(self.ground_dir)
      angle = self.last_ground_dir.angle_to     (self.ground_dir)
      
      if axe.length() > 0.001:
        state.rotate_axe(angle, axe)
        
        
    if self.current_action:
      getattr(self, "do_action_%s" % self.current_action.action, noop)(self.current_action, state)
      
    scene = self.level
    state_center = soya.Point(state, 0.0, self.radius_y, 0.0)
    global _CONTEXT
    context = _CONTEXT = scene.RaypickContext(state_center, self.radius_y + 0.1, _CONTEXT)

    # Fall
    if context.raypick(state_center, self.down, 0.1 + self.radius_y, 1, 0, self.ground, _V) and not self.jumping:
      if (not self.on_ground) and (self.speed.y < -0.15):
        self.doer.action_done(SoundState(self, "chute.wav"))
      self.speed.y = 0.0
      if self.ground.parent.is_inside(self.level.children[0]):
        state.y = self.level.transform_point(self.ground.x, self.ground.y, self.ground.z, self.ground.parent)[1]
        if isinstance(self.ground.parent, soya.Land):
          self.last_land_ground.move(self.ground)
          if   state.y > self.level.push_down_level + 0.4: state.y -= 0.4
          elif state.y > self.level.push_down_level:       state.y  = self.level.push_down_level
          
        self.on_ground = 1 # Not on a mobile platform (level.children[0] is the static part !!!)
        self.ground.parent = None # Avoid to store a useless parent
      else: # Possibly (not sure) on a mobile platform
        self.on_ground = 2
        self.last_ground.clone(self.ground)
        self.last_ground.convert_to(self.level)
        
        self.ground_dir.__init__(self, 0.0, 0.0, -1.0)
        self.ground_dir.convert_to(self.ground.parent)
        
        self.last_ground_dir.__init__(self, 0.0, 0.0, -1.0)
        self.last_ground_dir.convert_to(self.level)
        
        ground = self.ground.parent
        while ground:
          if isinstance(ground, base.SensitiveFloor):
            ground.character_on(self)
            break
          ground = ground.parent
          
        state.y = self.level.transform_point(self.ground.x, self.ground.y, self.ground.z, self.ground.parent)[1]
        
      if action and (action.action == ACTION_JUMP):
        self.jumping = 1
        self.speed.y = 0.4
        self.doer.action_done(SoundState(self, "jump1.wav"))
        
    else:
      self.on_ground = 0
      #self.speed.y = max(self.speed.y - 0.02, -0.25)
      if self.speed.y > -0.25: self.speed.y -= 0.02
      if not self.current_action: state.animation = "chute"
      if   self.speed.y < 0.0: self.jumping = 0
      elif action and (action.action == ACTION_STOP_JUMPING): self.speed.y = 0.0


    # Allow to fly :
    #if action and (action.action == ACTION_JUMP):
    #  self.jumping = 1
    #  self.speed.y = 0.4
    #  self.doer.action_done(SoundState(self, "jump1.wav"))
      
    state.y += self.speed.y
    
    for character in self.level.mobiles:
      if isinstance(character, Character) and (not character is self) and character.visible:
        d = character.distance_to(self) - 0.5 - self.radius - character.radius
        if d < 0.0:
          d /= 2.0
          state.add_xyz(d * (character.x - self.x), d * (character.y - self.y), d * (character.z - self.z))
          
    if context.raypick(state_center, self.left, self.radius, 0, 0, _P, _V):
      _V.convert_to(self.parent)
      _V.normalize()
      state.add_mul_vector(self.radius - state_center.distance_to(_P), _V)

    if context.raypick(state_center, self.front, self.radius, 0, 0, _P, _V):
      _V.convert_to(self.parent)
      _V.normalize()
      state.add_mul_vector(self.radius - state_center.distance_to(_P), _V)
      
    if context.raypick(state_center, self.up, self.radius_y, 1, 0, _P, _V):
      _V.convert_to(self.parent)
      _V.normalize()
      state.add_mul_vector(self.radius_y - state_center.distance_to(_P), _V)
      if self.speed.y > 0.0:
        self.speed.y = 0.0
        self.doer.action_done(SoundState(self, "chute.wav"))
        
    # Weapon collisions
    if self.striking and self.kill_rays:
      for mobile in self.level.mobiles:
        if isinstance(mobile, Character) and (not mobile.invincible) and mobile.visible:
          mobile.solid = 1 # Temporarily enable raypick on characters
          #mobile.perso.solid = 1
          if mobile.weapon and mobile.weapon.prot: mobile.weapon.prot.solid = 1 # Activate raypicking on protection shape
      self.solid = 0 # but not on self !
      
      self.level.children[0].children[0].solid = 0
      
      kill_context = scene.RaypickContext(self.kill_rays[0], self.range, _CONTEXT)
      res = [kill_context.raypick(kill_ray, kill_ray.dir, kill_ray.length, 1, 0, _TEMP[i][0], _TEMP[i][1])
             for (i, kill_ray) in enumerate(self.kill_rays)
             ]
      for i in range(len(res)):
        if self.weapon.hurt_weapon and res[i]:
          target = _TEMP[i][0].parent
          while target:
            if isinstance(target, Weapon):
              if target.owner and not target.owner.invincible:
                self.doer.action_done(SoundState(self, "weapon_hurted2.wav"))
                state2 = AddState(self, ADD_ECLAT)
                state2.move   (_TEMP[i][0])
                state2.look_at(_TEMP[i][1])
                self.doer.action_done(state2)
                self.weapon_hurted()
              break
            
            target = target.parent
          else: continue
          break
        
      else: # No weapon hurted => check hit
        for i in range(len(res)):
          if res[i]:
            target = _TEMP[i][0].parent
            while 1:
              if   isinstance(target, Character):
                if (not target.invincible) and self.is_enemy(target):
                  fighting = self.fight_skill
                  for item in self.items: fighting += item.fighting_bonus(target)
                  _TEMP[i][1].__imul__(-1)
                  target.hurt(fighting / 10.0, self, *_TEMP[i])
                break

              elif isinstance(target, base.Strikeable):
                fighting = self.fight_skill - target.resistance + random.uniform(-10.0, 10.0)
                for item in self.items: fighting += item.fighting_bonus(target)
                if not target.hurt(self, fighting):
                  self.weapon_hurted()
                break

              elif isinstance(target, soya.Land): break

              elif target is None:
                if self.weapon and (kill_ray in self.weapon.kill_rays):
                  self.doer.action_done(SoundState(self, "weapon_hurted2.wav"))
                  state2 = AddState(self, ADD_ECLAT)
                  state2.move   (_TEMP[i][0])
                  state2.look_at(_TEMP[i][1])
                  self.doer.action_done(state2)
                  self.weapon_hurted()
                break

              target = target.parent
            else: continue
            break

      for mobile in self.level.mobiles:
        if isinstance(mobile, Character):
          mobile.solid = 0
          if mobile.weapon and mobile.weapon.prot: mobile.weapon.prot.solid = 0
          
      self.level.children[0].children[0].solid = 1
      
    self.doer.action_done(state)
    
  def set_state(self, state):
    if   isinstance(state, SoundState): balazar.sound.play(state.sound, self, gain = state.gain)
    elif isinstance(state, ValueState):
      if   state.type == STATE_KILL_LIFE:
        self.life -= state.value
        if self.life > 0.0: # Else, dead
          if self.bot: self.invincible = 10
          else:        self.invincible = 40
        if tofu.GAME_INTERFACE and (not self.bot) and (not self.controller.remote):
          tofu.GAME_INTERFACE.life_bar.set_life(self.life)
          
      elif state.type == STATE_HEAL_LIFE:
        if self.life > 0.0: # Else, dead
          self.life = min(1.0, self.life + state.value)
          if tofu.GAME_INTERFACE:
            #heal = HealEffect(self.level)
            #heal.move(self)
            #heal.y += 5.0
            if (not self.bot) and (not self.controller.remote):
              tofu.GAME_INTERFACE.life_bar.set_life(self.life)
              
    elif isinstance(state, AddState):
      if   state.element == ADD_ECLAT:
        if not tofu.GAME_INTERFACE: return
        element = Eclat(self.level)
      if   state.element == ADD_STEP:
        if not tofu.GAME_INTERFACE: return
        element = Step(self.level)
      if   state.element == ADD_BLOOD:
        if not tofu.GAME_INTERFACE: return
        element = Blood(self.level)
      element.matrix = state.matrix
      
    elif isinstance(state, ItemState):
      if tofu.Unique.hasuid(state.item_uid):
        item = tofu.Unique.getbyuid(state.item_uid)
        if   state.action == ACTION_EQUIP_ITEM  : item.set_equiped(1)
        elif state.action == ACTION_UNEQUIP_ITEM: item.set_equiped(0)
        elif state.action == ACTION_USE_ITEM    : item.used_by(self)
        elif state.action == ACTION_GRAB_ITEM   :
          if item.parent: item.parent.remove(item)
          self.add_item(item)
          
        elif state.action == ACTION_DROP_ITEM:
          if not item.owner is self: raise "jiba"
          item.set_owner(None)
          try: self.items.remove(item)
          except: return
          
          speed = soya.Vector(self, (int(str(item.uid)[-2]) - 5) * 0.02, 0.2, -0.1)
          speed.convert_to(self.parent)
          f = FallingItem(self.parent, item, speed)
          f.move(soya.Point(self, 0.0, 2.0, 0.0))
          item.look_at(self.front)
          
          #if self.level.raypick(soya.Point(self, 0.0, 2.0, -1.5), self.down, -1.0, 1, 1, _P, _V):
          #  _P.convert_to(self.level)
          #  self.parent.add(item)
          #  item.set_ground_pos(self.front, _V)
          #  item.move(_P)
          #  item.y += 0.05
    else:
      #if self.distance_to(state) > 10.0: return
      
      soya.tofu4soya.Mobile.set_state(self, state)
      
      if tofu.GAME_INTERFACE and (self.on_ground == 1) and (self.y > self.level.step_level):
        if (state.animation != "attente") and (state.animation != "tourneG") and (state.animation != "tourneD") and ((soya.IDLER.big_round_count == 1) or (soya.IDLER.big_round_count == 5) or (soya.IDLER.big_round_count == 9) or (soya.IDLER.big_round_count == 13)):
          if self.level.children[0].raypick(self, self.up, 0.5, 1, 0, _P, _V):
            step = Step(self.level)
            step.move(_P)
            step.y += 0.02
            step.look_at  (self.front)
            step.look_at_y(_V)
            
      if self.current_animation != state.animation:
        if self.current_animation: self.perso.animate_clear_cycle(self.current_animation, 0.2)
        self.perso.animate_reset()
        self.perso.animate_blend_cycle(state.animation, 1.0, 0.2)
        self.current_animation = state.animation
        
  def control_owned(self):
    soya.tofu4soya.Mobile.control_owned(self)
    
    from balazar.controller import HumanController, StackController
    from balazar.camera import Traveling
    
    self.controller = StackController(self)
    self.controller.append(HumanController(self))
    
    traveling = tofu.GAME_INTERFACE.traveling = Traveling(self, soya.Vector(None, 0.0, 0.0, 2.0))
    tofu.GAME_INTERFACE.camera.add_traveling(traveling)
    tofu.GAME_INTERFACE.camera.zap()
    
    tofu.GAME_INTERFACE.hero = self
    
    tofu.GAME_INTERFACE.life_bar.set_life(self.life)
    
    self.advertise_country()
    
    if getattr(self, "is_new_player", 0):
      del self.is_new_player
      balazar.game_interface.StartDiscussion(self).activate(1)
      
  def advertise_country(self):
    country = self.level.get_country()

    if not country is tofu.GAME_INTERFACE.current_country:
      balazar.sound.play_music(country.music)
      
      if tofu.GAME_INTERFACE.current_country:
        d = balazar.game_interface.Discussion(self, u"", _("__welcome__%s" % country.name),
          choices = [balazar.game_interface.Discussion(self, _(u"Ok"))])
        balazar.game_interface.Bubble(soya.root_widget, self, balazar.game_interface.DiscussionWidget(d)).set_focus(1)
        
      tofu.GAME_INTERFACE.current_country = country
    
    
  def zap(self):
    for item in self.items: item.zap()
    # Disable interpolation
    self.state1.x = self.state2.x = self.x
    self.state1.y = self.state2.y = self.y
    self.state1.z = self.state2.z = self.z
    
  def next_action(self): return Action(ACTION_WAIT)
  
  def begin_round(self):
    soya.tofu4soya.Mobile.begin_round(self)
    
    if self.invincible:
      self.visible = self.invincible % 2
      self.invincible -= 1
      
  def big_round(self):
    if hasattr(self.controller, "big_round"): self.controller.big_round()
    
    if not self.doer.remote:
      if   self.y < -5.0:
        if self.life > 0.0:
          if self.hurt(((self.bot and 1.0) or 0.3), self, vector = 0, can_resist = 0): # Else dead
            if self.last_land_ground. y != 0.0:
              self.move(self.last_land_ground)
              self.zap()
#       elif 0 and not self.bot:
#         if   self.x < -balazar.level.BRIDGE_DEMI_DIM - 0.0:
#           p = self % self.get_root()
#           new_level = self.level.get_by_pos(self.level.X - 1, self.level.Z)
#           p.y = p.y - self.level.bridge_0_1.y + new_level.bridge_2_1.y

#           self.level.remove_mobile(self)
#           p.convert_to(new_level)
#           self.move(p)

#           new_level.add_mobile(self)
#           self.last_land_ground.move(self)
#           self.current_deplacement = ACTION_WAIT
#           return

#         elif self.x > balazar.level.LAND_DIM + balazar.level.BRIDGE_DEMI_DIM + 0.0:
#           p = self % self.get_root()
#           new_level = self.level.get_by_pos(self.level.X + 1, self.level.Z)
#           p.y = p.y - self.level.bridge_2_1.y + new_level.bridge_0_1.y

#           self.level.remove_mobile(self)
#           p.convert_to(new_level)
#           self.move(p)

#           new_level.add_mobile(self)
#           self.last_land_ground.move(self)
#           self.current_deplacement = ACTION_WAIT
#           return

#         elif self.z < -balazar.level.BRIDGE_DEMI_DIM - 0.0:
#           p = self % self.get_root()
#           new_level = self.level.get_by_pos(self.level.X, self.level.Z - 1)
#           p.y = p.y - self.level.bridge_1_0.y + new_level.bridge_1_2.y

#           self.level.remove_mobile(self)
#           p.convert_to(new_level)
#           self.move(p)

#           new_level.add_mobile(self)
#           self.last_land_ground.move(self)
#           self.current_deplacement = ACTION_WAIT
#           return

#         elif self.z > balazar.level.LAND_DIM + balazar.level.BRIDGE_DEMI_DIM + 0.0:
#           p = self % self.get_root()
#           new_level = self.level.get_by_pos(self.level.X, self.level.Z + 1)
#           p.y = p.y - self.level.bridge_1_2.y + new_level.bridge_1_0.y

#           self.level.remove_mobile(self)
#           p.convert_to(new_level)
#           self.move(p)

#           new_level.add_mobile(self)
#           self.last_land_ground.move(self)
#           self.current_deplacement = ACTION_WAIT
#           return
      
    if tofu.GAME_INTERFACE:
      if self.lod: # Check LODs
        if self.distance_to(tofu.GAME_INTERFACE.camera) < 15.0:
          self.lod = 0
          try:
            self.perso.detach("perso_poor")
            self.perso.attach("perso")
          except: pass
      else:
        if self.distance_to(tofu.GAME_INTERFACE.camera) > 17.0:
          self.lod = 1
          try:
            self.perso.detach("perso")
            self.perso.attach("perso_poor")
          except: pass
    else:
      self.perso.detach("perso")
      self.perso.attach("perso_poor")
      
  def loaded(self):
    soya.tofu4soya.Mobile.loaded(self)
    
    self.current_animation = ""
    if self.on_ground == 2: self.on_ground = 1
    
    self.lod = 0
    try:
      self.perso.detach("perso_poor")
      self.perso.attach("perso")
    except: pass
    
    for item in self.items: item.loaded()
    
  def received(self):
    soya.tofu4soya.Mobile.received(self)
    
    self.current_animation = ""
    if self.on_ground == 2: self.on_ground = 1
    
    self.lod = 0
    try:
      self.perso.detach("perso_poor")
      self.perso.attach("perso")
    except: pass
    
    for item in self.items: item.received()
    
  def add_item(self, item):
    self.items.append(item)
    item.set_owner(self)
    item.zap()

  def get_item(self, Item):
    for item in self.items:
      if isinstance(item, Item): return item
      
  def is_enemy(self, other): return self.get_relation(other) < 0.0
  
  def change_relation(self, other, delta_relation):
    self.set_relation(other, self.get_relation(other) + delta_relation)
    self.controller.relation_changed(other)
    
  def set_relation(self, other, relation):
    if isinstance(other, Character):
      self ._set_relation(other, relation)
      other._set_relation(self , relation)
      self.controller.relation_changed(other)
    else:
      self.relations[other] = relation
      
  def _set_relation(self, other, relation):
    self.relations[other.race, other.uid] = relation
    
  def get_relation(self, other):
    if isinstance(other, Character): return min(self._get_relation(other), other._get_relation(self))
    else:
      if self.relations.has_key(other): return self.relations[other]
      return self.relations.get(None, 0.2)
    
  def _get_relation(self, other):
    if self.relations.has_key((other.race, other.uid)): return self.relations[other.race, other.uid]
    if self.relations.has_key(other.race): return self.relations[other.race]
    return self.relations.get(None, 0.2)
  
  def hurt(self, life, caster, localisation = None, vector = None, can_resist = 1):
    if self.life > 0.0:
      if can_resist:
        resistance = self.resistance
        for item in self.items: resistance += item.resistance_bonus(caster)
        life = life / resistance
        
      state = AddState(self, ADD_BLOOD)
      state.move(localisation or self.center)
      self.doer.action_done(state)
      
      if self.life - life <= 0.0:
        self.doer.action_done(ValueState(self, STATE_KILL_LIFE, life)) # Do this AFTER the if !!! (because it can change self.life immediately)
        self.doer.action_done(SoundState(self, self.die_sound))
        
        action = self.current_action = FightAction(ACTION_KILLED)
        action.duration = 0
        
        return 0
      
      else:
        self.doer.action_done(ValueState(self, STATE_KILL_LIFE, life)) # Do this AFTER the if !!! (because it can change self.life immediately)
        self.doer.action_done(SoundState(self, self.couic_sound))

        if not (self.current_action and self.current_action.action == ACTION_KILLED):
          action = self.current_action = FightAction(ACTION_HURTED)
          action.duration = 0
          
          #if (self.life > 0.5) and (self.life - life <= 0.5): length = 0.5
          #else:                                               length = 0.1
          length = 0.25
          if vector:
            vector %= self.level
            vector.set_length(length)
            action.vector_x = vector.x
            action.vector_z = vector.z
          elif vector == 0:
            action.vector_x = 0.0
            action.vector_z = 0.0
          else:
            _V.parent = self
            _V.set_xyz(0.0, 0.0, length)
            _V.convert_to(self.level)
            action.vector_x = _V.x
            action.vector_z = _V.z
            
        self.striking = 0
        
        return 1
      
  def weapon_hurted(self):
    if self.current_action and (ACTION_HURTED <= self.current_action.action <= ACTION_KILLED): return
    
    action = FightAction(ACTION_WEAPON_HURTED_LEFT)
    if   self.current_animation == "combat0": action.action = ACTION_WEAPON_HURTED_LEFT
    elif self.current_animation == "combat1": action.action = ACTION_WEAPON_HURTED_RIGHT
    else:
      _P.clone(self.weapon)
      _P.convert_to(self)
      if _P.x < 0.0: action.action = ACTION_WEAPON_HURTED_LEFT
      else:          action.action = ACTION_WEAPON_HURTED_RIGHT
    action.duration = 0
    self.current_action = action
    self.striking = 0
    
  def do_action_38(self, action, state): # ACTION_WEAPON_HURTED_LEFT
    state.animation = "combat0c"
    
    if action.duration == 0: state.y += 0.1
    action.duration += 1
    if action.duration > 12: self.current_action = None
    
  def do_action_39(self, action, state): # ACTION_WEAPON_HURTED_RIGHT
    state.animation = "combat1c"
    
    if action.duration == 0: state.y += 0.1
    action.duration += 1
    if action.duration > 12: self.current_action = None
    
  def do_action_11(self, action, state): # ACTION_HURTED
    state.animation = "couic"
    
    if action.duration == 0: state.y += 0.1
    action.duration += 1
    if action.duration > 7:
      action.vector_x /= 2.0
      action.vector_z /= 2.0
      if action.duration > 9: self.current_action = None
      
    state.add_xyz(action.vector_x, 0.0, action.vector_z)
    
  def do_action_12(self, action, state): # ACTION_KILLED
    state.animation = "mort"
    
    if action.duration == 0:
      #print "throwing items of", self.uid
      for item in self.items[:]:
        if self.bot or item.equiped:
          self.doer.action_done(ItemState(self, ACTION_DROP_ITEM, item.uid))
    action.duration += 1
    
    state.matrix = self.matrix
    self.speed.y = 0.0
    if   action.duration < self.die_duration1: state.shift(0.0, 0.0, 0.04)
    elif action.duration > self.die_duration2: self.kill()

  def killed(self):
    if tofu.GAME_INTERFACE:
      tofu.GAME_INTERFACE.close_all_widgets()
      
    soya.tofu4soya.Mobile.killed(self)
    
    #for item in self.items:
      
  def do_action_13(self, action, state): # ACTION_GUARD
    state.animation = "garde"
    
  def strike_rot(self, target, state):
    _P.clone(target)
    _P.convert_to(self)
    if   _P.x < -0.2: state.rotate_lateral( 4.0)
    elif _P.x >  0.2: state.rotate_lateral(-4.0)
    
    
#DOWN = soya.Vector(None, 0.0, -1.0, 0.0)

class FallingItem(soya.World):
  def __init__(self, parent = None, item = None, speed = None):
    soya.World.__init__(self, parent)
    
    self.item  = item
    self.speed = speed or soya.Vector()
    self.solid = 0
    self.add(item)
    item.set_ground_pos()
    item.set_xyz(0.0, 0.0, 0.0)
    
  def big_round(self):
    if self.y < -5.0: self.parent.remove(self)
    
  def begin_round(self):
    if self.speed.y > -0.23: self.speed.y -= 0.02
    self.add_vector(self.speed)
    
    if self.parent.raypick(self, self.speed, 0.35, 1, 1, _P, _V):
      self.parent.add(self.item)
      self.parent.remove(self)
      self.item.set_ground_pos(_P, None, _V)
      
  #def advance_time(self, proportion):
  #  self.add_mul_vector(proportion, self.speed)
    
    
import random

class Blood(soya.Particles):
  def __init__(self, parent = None, material = None, nb_particles = 20):
    soya.Particles.__init__(self, parent, material or soya.PARTICLE_DEFAULT_MATERIAL, nb_particles)
    self.auto_generate_particle = 1
    self.set_colors((1.0, 0.1, 0.1, 1.0), (1.0, 0.1, 0.1, 1.0), (0.8, 0.1, 0.1, 1.0), (0.4, 0.0, 0.0, 1.0))
    self.set_sizes((0.7, 0.7))
    self.max_particles_per_round = 2
    self.life = 11
    self.lit  = 0
    
  def generate(self, index):
    angle = random.uniform(0.0, 6.2834)
    sx = math.cos(angle)
    sz = math.sin(angle)
    l = 0.06 / math.sqrt(sx * sx + 1.0 + sz * sz)
    self.set_particle(index, random.uniform(0.5, 1.5), sx * l, 3 * l, sz * l, 0.0, -0.01, 0.0)
    
  def begin_round(self):
    soya.Particles.begin_round(self)
    if self.life == 1:
      self.auto_generate_particle = 0
      self.removable = 1
      self.life = 0
    else: self.life -= 1

class Eclat(soya.Volume):
  def __init__(self, parent = None):
    soya.Volume.__init__(self, parent, soya.Shape.get("eclat"))
    self.solid = 0
    self.angle = 0.0
    
  def advance_time(self, proportion):
    self.angle += 0.08 * proportion
    f = math.cos(self.angle)
    if f < 0.0:
      if self.parent: self.parent.remove(self)
    else:
      self.set_scale_factors(f, f, f)
      
      
class Step(soya.Volume):
  def __init__(self, parent = None):
    soya.Volume.__init__(self, parent, soya.Shape.get("pas"))
    self.solid    = 0
    
  def big_round(self):
    if (not tofu.GAME_INTERFACE) or (not tofu.GAME_INTERFACE.camera.is_in_frustum(self)):
      self.parent.remove(self)
      
      
class HealEffect(soya.Particles):
  def __init__(self, parent = None, nb_particles = 35):
    soya.Particles.__init__(self, parent, soya.PARTICLE_DEFAULT_MATERIAL, nb_particles)
    self.auto_generate_particle = 1
    self.set_colors((1.0, 0.2, 0.8, 0.4), (1.0, 0.2, 0.8, 1.0), (1.0, 0.2, 0.2, 1.0), (0.8, 0.2, 0.1, 1.0), (0.3, 0.1, 0.0, 1.0))
    self.set_sizes((1.0, 1.0))
    self.max_particles_per_round = 2
    self.life = 60
    self.lit  = 0
    
  def generate(self, index):
    angle = random.uniform(0.0, 6.2834)
    sx = math.cos(angle)
    sy = -7.0
    sz = math.sin(angle)
    l = 0.15 / math.sqrt(sx * sx + sy * sy + sz * sz)
    self.set_particle(index, random.uniform(0.6, 1.5), sx * l, sy * l, sz * l, 0.0, 0.0, 0.0)
    
  def begin_round(self):
    soya.Particles.begin_round(self)
    if   self.life >  0: self.life -= 1
    elif self.life == 0:
      self.auto_generate_particle = 0
      self.removable = 1
      self.life = -1

