# Balazar Brothers
# Copyright (C) 2006 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 cerealizer, soya, tofu, soya.tofu4soya as tofu4soya

import balazar_brothers
from balazar_brothers.platform import Platform, MultiCharactersPlatform


P1 = soya.Point ()
V1 = soya.Vector()


class Action(tofu4soya.Action):
  def __init__(self):
    tofu4soya.Action.__init__(self)
cerealizer.register(Action)
    
class JumpAction(Action):
  def __init__(self):
    Action.__init__(self)
cerealizer.register(JumpAction)
    
class DieAction(Action):
  def __init__(self, dying_sens = None):
    Action.__init__(self)
    
    self.dying_sens = dying_sens
cerealizer.register(DieAction)
  
class QuitMenuAction(Action):
  def __init__(self):
    Action.__init__(self)
cerealizer.register(QuitMenuAction)
  

class State(tofu4soya.State):
  def __init__(self, mobile):
    tofu4soya.State.__init__(self)
cerealizer.register(State)
    
class JumpState(State):
  def __init__(self, mobile, platform):
    State.__init__(self, mobile)
    
    self.platform = platform
cerealizer.register(JumpState)
    
class DiscussionState(State):
  def __init__(self, mobile, gettext_key):
    State.__init__(self, mobile)
    
    self.gettext_key = gettext_key
cerealizer.register(DiscussionState)
    
class ReplyState(State):
  def __init__(self, mobile, gettext_key):
    State.__init__(self, mobile)
    
    self.gettext_key = gettext_key
cerealizer.register(ReplyState)
    
class DieState(State):
  def __init__(self, mobile, dying_sens = None):
    State.__init__(self, mobile)
    
    self.dying_sens = dying_sens
cerealizer.register(DieState)

class SoundState(State):
  def __init__(self, mobile, sound, gain = 1.0):
    State.__init__(self, mobile)
    
    self.sound = sound
    self.gain  = gain
cerealizer.register(SoundState)

class UpdateState(State):
  def __init__(self, mobile, score, keys):
    State.__init__(self, mobile)
    
    self.score = score
    self.keys  = keys
cerealizer.register(UpdateState)



class Character(tofu4soya.Mobile, Platform):
  COUIC_SOUND      = "couic2.wav"
  ANIMATION_SUFFIX = ""
  
  def __init__(self):
    tofu4soya.Mobile.__init__(self)
    Platform.__init__(self)
    
    self.platform_position   = soya.Point(self, 0.0, 2.1, 0.0)
    self.center              = soya.Point(self, 0.0, 1.1, 0.0)
    
    self.power               = 8.0 # How far the character can jump ?
    self.speed               = 0.1
    self.platform            = None
    self._next_platform      = None
    self.front               = soya.Vector(self, 0.0, 0.0, -1.0)
    self.jump_proportion     = 0.0
    self.jump_start_position = soya.Point()
    self.current_rotation    = 0.0
    self.current_translation = soya.Vector(self, 0.0, 0.0, 0.0)
    self.current_animation   = ""
    self.dying               = 0
    self.wait_before_suicide = 20
    
    self.old_position        = soya.Point()
    self.old_front           = soya.Vector()
    self.current_discussion  = None
    
  def __getstate__(self):
    state = super(Character, self).__getstate__()
    state = state[0], state[1].copy()
    if self.platform and not (isinstance(self.platform, Character) and self.platform.player_name == self.player_name):
      state[1]["platform"] = self.level.children.index(self.platform)
    if self.platform:
      if isinstance(self.platform, Character):
        if self.platform.player_name == self.player_name: pass
        else:
          platform = self.platform
          while platform and isinstance(platform, Character) and platform.player_name != self.player_name: platform = platform.platform or platform.next_platform
          state[1]["platform"] = self.level.children.index(platform)
      else: state[1]["platform"] = self.level.children.index(self.platform)
    #else:             state["platform"] = None
    
    state[1]["characters_on"] = [character for character in self.characters_on if character.player_name == self.player_name]
    
    del state[1]["current_discussion" ]
    del state[1]["_next_platform"     ]
    del state[1]["old_position"       ]
    del state[1]["old_front"          ]
    del state[1]["current_translation"]
    del state[1]["jump_start_position"]
    state[1]["current_animation"] = ""
    return state
  
  def __setstate__(self, state):
    super(Character, self).__setstate__(state)
      
    self.current_discussion  = None
    self._next_platform      = -1
    self.old_position        = soya.Point()
    self.old_front           = soya.Vector()
    self.current_translation = soya.Vector(self, 0.0, 0.0, 0.0)
    self.jump_start_position = soya.Point()

  def loaded(self):
    if isinstance(self.platform, int):
      self.platform          = self.level.children[self.platform]
      if not self in self.platform.characters_on: # Else, already done by the server.
        while self.platform.characters_on and not isinstance(self.platform, MultiCharactersPlatform):
          self.platform = self.platform.characters_on[0]
          
  def received(self):
    tofu4soya.Mobile.received(self)
    
    if self.platform and not self in self.platform.characters_on:
      self.platform.add_character(self)
      
  def set_platform(self, platform, **kargs):
    if self.platform: self.platform.remove_character(self)
      
    self.platform = platform
    
    if platform:
      from balazar_brothers.level import Level
      level = platform
      while not isinstance(level, Level): level = level.parent
      
      self.move(platform.platform_position % level)
      self.look_at(soya.Vector(platform, 0.0, 0.0, -1.0) % level)
      platform.add_character(self, **kargs)
      
      self.play_animation("attente")
      self.dying = 0
      self.current_translation.set_xyz(0, 0, 0)
      self.store_old_pos()
      
      # Don't work with just self.store_old_pos(), WHY ?
      self.old_position.clone(self)
      
      if tofu.GAME_INTERFACE: tofu.GAME_INTERFACE.camera.zap()
      
  def get_next_platform(self):
    if self._next_platform == -1:
      self._next_platform = self.find_next_platform()
      if self._next_platform: self.wait_before_suicide = 20
    return self._next_platform
  def set_next_platform(self, platform):
    self._next_platform = platform
  next_platform = property(get_next_platform, set_next_platform)
  
  def find_next_platform(self, position = None, front = None, power = None, max_angle = 75.0, angle_factor = 0.07, exclude = []):
    if not position: position = self
    if not front   : front    = self.front
    if not power   : power    = self.power
    
    best_platform = None
    best_distance = power
    
    for platform in self.level.mobiles:
      if isinstance(platform, Platform) and (not platform is self) and (not platform is self.platform) and platform.suitable_for(self) and (not platform in exclude):
        if isinstance(self.platform, Character) and (self.platform.next_platform is platform) and (not self.platform.platform): continue
        if isinstance(platform, Character) and platform.dying: continue
        V1.set_start_end(position, platform.platform_position)
        if V1.y <= 0.0: V1.y /= 100.0 # Don't limit how far the character can fall down.
        
        angle = front.angle_to(V1)
        
        if (angle < max_angle) and V1.y < 2.5:
          if (V1.y > 1.0) and isinstance(platform, Character): continue
          distance = V1.length() + angle_factor * angle
          
          if distance < best_distance:
            best_distance = distance
            best_platform = platform
            
    if best_platform: best_platform.is_next_for_character(self)
    return best_platform
  
  def compute_trajectory(self, start_position, end_position, jump_proportion):
    self.current_translation.set_start_end(self, end_position)
    self.current_translation *= self.speed / (1.0 - jump_proportion)
    self.current_translation.y += -0.4 * (((jump_proportion - 4.0 * self.speed) * 2.0) - 1.0)
    
    V1.set_start_end(start_position, end_position)
    V1.y = 0.0
    self.current_rotation = min(self.front.angle_to(V1), 100.0 * self.speed)
    P1.clone(end_position)
    P1.convert_to(self)
    if P1.x > 0.0: self.current_rotation *= -1.0
    
  def can_kill    (self, character): return 1
  def want_to_kill(self, character): return self.can_kill(character)
  
  def die(self, dying_sens = None):
    if self.platform:
      self.platform.remove_character(self)
    if not self.doer.remote:
      if dying_sens: dying_sens = dying_sens % self.level
      self.doer.action_done(DieState(self, dying_sens))
      
  def kill(self):
    for character in self.characters_on: character.kill()
    
    tofu4soya.Mobile.kill(self)
    
  def start_discussion(self, gettext_key):
    if not self.doer.remote:
      self.doer.action_done(DiscussionState(self, gettext_key))
      
  def do_action(self, action):
    if not self.level: return
    
    if   isinstance(action, JumpAction):
      if self.current_discussion:
        self.doer.action_done(ReplyState(self, self.current_discussion.gettext_key))
      else:
        if not self.platform: return # The character can jump ONLY if he is on a platform !
        if self.next_platform:
          self.doer.action_done(JumpState(self, self.next_platform.uid))
        else:
          if self.wait_before_suicide:
            self.wait_before_suicide -= 1
          else:
            self.doer.action_done(DieState(self)) # No platform available => fall and die
            
    elif isinstance(action, DieAction):
      self.doer.action_done(DieState(self, action.dying_sens))
      
    elif isinstance(action, QuitMenuAction):
      self.start_discussion("__quit_None__")
      
  def set_state(self, state):
    if   isinstance(state, SoundState):
      balazar_brothers.sound.play(state.sound, self, gain = state.gain)
    elif isinstance(state, JumpState): # Start jumping
      balazar_brothers.sound.play((self.platform or Platform).JUMP_SOUND, self)

      if self.platform: self.jump_start_position.clone(self.platform.platform_position)
      else:             self.jump_start_position.clone(self)
      self.jump_start_position.convert_to(self.level)
      self.jump_proportion = 0.0
      self.next_platform   = tofu.Unique.getbyuid(state.platform)
      if self.platform:
        self.platform.remove_character(self)
        self.platform        = None
      self.play_animation("saute")
      
    elif isinstance(state, DieState):
      #if not self.dying:
      if (not self.dying) or (self.dying > 20):
        self.jump_start_position.clone(self)
        self.play_animation("couicavant")
        if self.platform:
          self.platform.remove_character(self)
          self.platform      = None
        self.next_platform = None
        self.dying         = 1
        if state.dying_sens:
          self.current_translation.parent = self
          self.current_translation.move(state.dying_sens)
          self.current_translation *= 0.2
          self.current_translation.convert_to(self.level)
          state.dying_sens.y = 0.0
          self.look_at(state.dying_sens)
        else:
          self.current_translation.__init__(self, 0.0, 2.0, -1.0)
          self.current_translation *= 0.2
          self.current_translation.convert_to(self.level)
        if self.characters_on:
          V1.__init__(self, 0.0, 0.0, -1.0)
          self.characters_on[0].look_at(V1)
          self.characters_on[0].old_front.move(V1)
    elif isinstance(state, UpdateState):
      self.score = state.score
      self.keys  = state.keys
      self.play_animation("bonus")
      
    elif isinstance(state, DiscussionState):
      discussion = self.get_discussion(state.gettext_key)
      if not self.controller.remote:
        if discussion.can_say(): discussion.show()
        
    elif isinstance(state, ReplyState):
      if   not self.current_discussion:                              discussion = self.get_discussion(state.gettext_key)
      elif self.current_discussion.gettext_key != state.gettext_key: discussion = self.get_discussion(state.gettext_key); self.current_discussion.hide()
      else:                                                          discussion = self.current_discussion
      for character in self.get_teamates(): character.current_discussion = None
      discussion.on_validate()
      
  def get_discussion(self, gettext_key):
    characters = { "owner" : self }
    if isinstance(self, Balazar):
      teamates = self.get_teamates()
      characters["red" ] = teamates[0]
      characters["blue"] = teamates[1]
      
    return balazar_brothers.discussion.Discussion(gettext_key, characters)
    
  def add_character(self, character, change_level = 0):
    Platform.add_character(self, character)
    animation = (self.current_animation or "attente").split("_")[0]
    if animation == "bonus": animation = "attente"
    self.play_animation(animation)
    
  def remove_character(self, character):
    Platform.remove_character(self, character)
    animation = (self.current_animation or "attente").split("_")[0]
    if animation == "bonus": animation = "attente"
    self.play_animation(animation)
    
  def play_animation(self, animation, fade_out = 0.2):
    if (animation == "attente") and (self.current_animation.startswith("bonus")) and (not self.characters_on): return
    if animation == "bonus" and self.characters_on: return
    
    if self.current_animation:
      self.perso.animate_clear_cycle(self.current_animation, fade_out)
      
    if   animation == "attente":
      if   self.characters_on:                   animation += "_portage"
      elif isinstance(self.platform, Character): animation += "_porte"
      
    elif animation == "saute":
      if self.characters_on: animation += "_portage"
      
    if not animation.endswith(self.ANIMATION_SUFFIX): animation += self.ANIMATION_SUFFIX
    
    self.current_animation = animation
    self.perso.animate_reset()
    self.perso.animate_blend_cycle(animation)
    
  def begin_round(self):
    if self.platform:
      if not self.current_animation: self.play_animation("attente")
      if self.old_position.parent:
        self.move(self.old_position)
        self.old_front.convert_to(self.level)
        self.old_front.y = 0.0
        self.look_at(self.old_front)
        
        self.rotate_lateral(self.current_rotation)
        
    self.current_rotation = 0.0
    
    tofu4soya.Mobile.begin_round(self)
    
    if self.dying:
      if self.characters_on:
        self.dying += 0.25
        if self.dying == 5.0:
          balazar_brothers.sound.play("brother_surf.wav", self, gain = 3.0)
          if not self.characters_on[0].characters_on:
            self.characters_on[0].play_animation("freresurf")
      else:
        if self.dying == 10.0: balazar_brothers.sound.play(self.COUIC_SOUND, self)
        self.dying += 1
      if (not self.doer.remote) and self.y < -50.0: self.kill()
        
    elif not self.platform:
      if self.jump_proportion < 0.96: # Jumping
        self.compute_trajectory(self.jump_start_position, self.next_platform.platform_position, self.jump_proportion)
        
        if (self.jump_proportion > 0.8) and not self.current_animation.startswith("attente"):
          self.play_animation("attente", 0.05)
          
        
      else: # Jump is ended.
        self.platform = self.next_platform
        self.platform.add_character(self)
        if not self.platform: return # Occurs if the platform is already occuped by someone you cannot kill. Implies that the character is currently dying.
        balazar_brothers.sound.play(self.platform.FALL_SOUND, self)
        
        self.current_translation.set_xyz(0, 0, 0)
        
        self.old_position.parent = None
        
        #self.play_animation("attente")
        
    if self.platform: self.store_old_pos()
    
  def store_old_pos(self):
    if isinstance(self.platform.platform_position, soya.CoordSyst): platform_coordsyst = self.platform.platform_position
    else:                                                           platform_coordsyst = self.platform.platform_position.parent
    self.old_position.clone(self)
    self.old_position.convert_to(platform_coordsyst)
    self.old_front.__init__(self, 0.0, 0.0, -1.0)
    self.old_front.convert_to(platform_coordsyst)
    self._next_platform = -1 # Invalid the next platform => will be recomputed when needed.
    
  def advance_time(self, proportion):
    if self.dying:
      if self.characters_on: proportion = proportion * 0.5
    
    if not self.platform:
      if self.dying:
        if self.dying > 0:
          if self.dying <  30:
            if self.dying <  8: self.turn_vertical(-10.0 * proportion * .5)
            #self.shift(0.0, 0.0, -0.3 * proportion)
          #self.y -= 0.02 * proportion * max(0,  self.dying - 10)
          self.current_translation.y -= 0.02 * proportion
          
      else:
        self.jump_proportion += proportion * self.speed
      
    self.rotate_lateral(proportion * self.current_rotation)
    self.add_mul_vector(proportion, self.current_translation)
        
    soya.World.advance_time(self, proportion)
cerealizer.register(Character)
    


class Balazar(Character):
  def __init__(self):
    Character.__init__(self)
    
    self.start_jumping       = 0
    self.previous_level_name = ""
    
    self.score = 0
    self.keys  = 0
    
  def want_to_kill(self, character):
    if isinstance(character, Balazar) and (character.player_name == self.player_name): return 0
    return Character.want_to_kill(self, character)
  
  def control_owned(self):
    soya.tofu4soya.Mobile.control_owned(self)
    
    tofu.GAME_INTERFACE.camera.zap()
    self.arrow.visible = 1
    
  def next_action(self):
    if self.start_jumping:
      self.start_jumping = 0
      return JumpAction()
    
  def get_teamates(self):
    teamates = [None, None]
    if (not self.controller.remote) and tofu.GAME_INTERFACE.traveling.characters: # Client controlling this character
      teamates[0] = tofu.GAME_INTERFACE.traveling.characters[ 0]
      teamates[1] = tofu.GAME_INTERFACE.traveling.characters[-1]
      
    elif self.controller.remote and not self.doer.remote: # Server
      player = tofu.Player.get(self.player_name)
      teamates[0] = player.mobiles[ 0]
      teamates[1] = player.mobiles[-1]
      
    else:
      level = self.level
      if not level:
        level = self.platform
        while not isinstance(level, balazar_brothers.level.Level): level = level.parent
      if level:
        for i in level.recursive():
          if isinstance(i, Character):
            if i.player_name == self.player_name:
              if   isinstance(i, RedBalazar ): teamates[0] = i
              elif isinstance(i, BlueBalazar): teamates[1] = i
              
    return teamates

  def added_into(self, level):
    super(Balazar, self).added_into(level)
    
    if level and self.platform and self.platform.discussion and tofu.GAME_INTERFACE.traveling.characters:
      self.start_discussion(self.platform.discussion)
cerealizer.register(Balazar)
      
      
class BlueBalazar(Balazar):
  ANIMATION_SUFFIX = "_blu"
  
  def __init__(self):
    Balazar.__init__(self)
    
    self.perso = soya.Cal3dVolume(self, soya.Cal3dShape.get("balazar"), ["perso"])
    
    self.arrow = Arrow(self, (0.3, 0.0, 1.0, 1.0))
    self.arrow.visible = 0
    
  def control_owned(self):
    Balazar.control_owned(self)

    for i in tofu.GAME_INTERFACE.traveling.characters[:]:
      if isinstance(i, self.__class__): tofu.GAME_INTERFACE.traveling.characters.remove(i)
    tofu.GAME_INTERFACE.traveling.characters.insert(1, self)
    
    from balazar_brothers.game_interface import ImageLabel
    
    tofu.GAME_INTERFACE.score_label.character = self
    tofu.GAME_INTERFACE.keys_label .character = self
    
    if self.platform.discussion: self.start_discussion(self.platform.discussion)
    
  def have_said__quit_None_blue__(self, discussion):
    if not self.controller.remote: tofu.GAME_INTERFACE.end_game()
cerealizer.register(BlueBalazar)
    
    
class RedBalazar(Balazar):
  ANIMATION_SUFFIX = "_red"
  
  def __init__(self):
    Balazar.__init__(self)
    
    self.perso = soya.Cal3dVolume(self, soya.Cal3dShape.get("balazar@rouge"), ["perso"])
    
    self.arrow = Arrow(self, (1.0, 0.2, 0.2, 1.0))
    self.arrow.visible = 0
    
  def control_owned(self):
    Balazar.control_owned(self)
    
    for i in tofu.GAME_INTERFACE.traveling.characters[:]:
      if isinstance(i, self.__class__): tofu.GAME_INTERFACE.traveling.characters.remove(i)
    tofu.GAME_INTERFACE.traveling.characters.insert(0, self)
    
  def have_said__quit_None_red__(self, discussion):
    if not self.doer.remote: tofu.Player.get(self.player_name).restart_world()
cerealizer.register(RedBalazar)

    
import soya.opengl as opengl

class Arrow(soya.PythonCoordSyst):
  def __init__(self, parent = None, color = (1.0, 1.0, 1.0, 1.0)):
    soya.PythonCoordSyst.__init__(self, parent)
    
    self.color = color
    
  def batch(self):
    if self.color[3] < 1.0: return 2, self, None
    else:                   return 1, self, None

  def advance_time(self, proportion):
    character = self.parent
    if character.platform and character.next_platform:
      self.look_at(character.next_platform)
      
  def render(self):
    character = self.parent
    if character.platform and character.next_platform:
      P1.clone(character.next_platform.platform_position)
      P1.convert_to(self)
      
    elif not character.platform:
      if character.next_platform:
        P1.clone(character.next_platform.platform_position)
        P1.convert_to(self)
      else: return
    else: return
    
    soya.DEFAULT_MATERIAL.activate()
    opengl.glDisable(opengl.GL_TEXTURE_2D)
    opengl.glDisable(opengl.GL_CULL_FACE)
    opengl.glDisable(opengl.GL_LIGHTING)
    opengl.glColor4f(*self.color)
    #opengl.glBegin(opengl.GL_QUADS)
    opengl.glBegin(opengl.GL_LINES)

    dy = 0.0
    for i in range(6):
      f1 = (i + 0.0) / 6.0
      f2 = (i + 1.0) / 6.0

      z1 = P1.z * f1
      z2 = P1.z * f2

      y1 = P1.y * f1 + dy; dy -= 0.6 * (2.0 * f1 - 1.0)
      y2 = P1.y * f2 + dy#; dy -= 0.6 * (2.0 * f2 - 1.0)

      if i == 5:
        opengl.glVertex3f(-0.15, y1, z1)
        opengl.glVertex3f(-0.4, y1, z1)
        opengl.glVertex3f( 0.15, y1, z1)
        opengl.glVertex3f( 0.4, y1, z1)
        
        opengl.glVertex3f(-0.4, y1, z1)
        opengl.glVertex3f(-0.0, y2, z2)
        opengl.glVertex3f( 0.0, y2, z2)
        opengl.glVertex3f( 0.4, y1, z1)
      else:
        opengl.glVertex3f(-0.15, y1, z1)
        opengl.glVertex3f(-0.15, y2, z2)
        opengl.glVertex3f( 0.15, y2, z2)
        opengl.glVertex3f( 0.15, y1, z1)

    opengl.glEnd()
    
    opengl.glEnable(opengl.GL_CULL_FACE)
    opengl.glEnable(opengl.GL_LIGHTING)
    #opengl.glEnable(opengl.GL_TEXTURE_2D)
cerealizer.register(Arrow)
      
