# Balazar
# Copyright (C) 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 random

import tofu, soya, soya.tofu4soya
import balazar.sound, balazar.base as base
from balazar.character import Character, _P, _V, ValueState, STATE_HEAL_LIFE, NewItemState, FallingItem

try: set
except:
  import sets
  set = sets.Set

TRAP_ACTION_OPEN  = 201
TRAP_ACTION_CLOSE = 202

class TrapState(tofu.State):
  def __init__(self, action):
    tofu.State.__init__(self)
    
    self.action = action

class IndicateState(tofu.State):
  def __init__(self, x, z):
    tofu.State.__init__(self)
    
    self.x = x
    self.z = z
    

class OpenableTrap(soya.tofu4soya.Mobile):
  open_sound  = "mecanism.wav"
  close_sound = ""
  
  def __init__(self):
    soya.tofu4soya.Mobile.__init__(self)
    
    self.opened             = 0
    self.bot                = 1
    self.close_duration     = 0
    self.autoclose_duration = 0
    
  def big_round(self):
    if not self.doer.remote:
      if self.opened and self.close_duration:
        self.close_duration -= 1
        if self.close_duration == 0: self.set_opened(0)
        
  def set_opened(self, opened):
    if opened != self.opened:
      if opened:
        self.doer.action_done(TrapState(TRAP_ACTION_OPEN))
        self.close_duration = self.autoclose_duration
      else:
        self.doer.action_done(TrapState(TRAP_ACTION_CLOSE))
        
  def set_state(self, state):
    if   state.action == TRAP_ACTION_OPEN:
      if not self.opened:
        if self.open_sound: balazar.sound.play(self.open_sound, self)
        self.opened = 1
        
    elif state.action == TRAP_ACTION_CLOSE:
      if self.opened:
        if self.close_sound: balazar.sound.play(self.close_sound, self)
        self.opened = 0
  
  
class TrappedTile(OpenableTrap, base.SensitiveFloor, base.Terraformer):
  open_sound       = "strike4.wav"
  terraform_radius =  3.0
  closed_tile_y    =  0.0
  opened_tile_y    = -0.5
  speed_y          =  0.02
  
  def __init__(self, typ = 1):
    OpenableTrap.__init__(self)
    
    self._need_push = None
    self.tile       = soya.Volume(self, soya.Shape.get("dalle1@grille%s" % typ))
    self.characters = set()
    self.typ        = typ
    
    self.autoclose_duration = 2
    
  def set_typ(self, typ): self.tile.shape = soya.Shape.get("dalle1@grille%s" % typ)
  
  def character_on(self, character):
    if character.ground.parent is self.tile:
      self.characters.add(character)
      
  def do_action(self, action):
    if not self._need_push is None:
      self.set_pushed(self._need_push)
      self._need_push = None
      
    if self.characters:
      self.characters.clear()
      if not self.opened: self.set_opened(1)
      else: self.close_duration = self.autoclose_duration
      
  def advance_time(self, proportion):
    OpenableTrap.advance_time(self, proportion)
    
    if   self.opened:
      if self.tile.y > self.opened_tile_y:
        self.tile.y -= proportion * self.speed_y
        if self.tile.y <= self.opened_tile_y:
          self.tile.y     = self.opened_tile_y
          self._need_push = 1
    else:
      if self.tile.y < self.closed_tile_y:
        self.tile.y += proportion * self.speed_y * 0.5
        if self.tile.y >= self.closed_tile_y:
          self.tile.y     = self.closed_tile_y
          self._need_push = 0
          
  def set_pushed(self, pushed): pass
  
  
class FallingVigie(TrappedTile, base.SensitiveFloor, base.Photographiable):
  open_sound       = ""
  radius           =   4.8
  closed_tile_y    =   0.0
  opened_tile_y    = -16.5
  speed_y          =   0.3
  
  def __init__(self):
    TrappedTile.__init__(self, 0)
    self.tile.shape = soya.Shape.get("vigie")
    
    
class ManualGridOpener(TrappedTile):
  def __init__(self, typ = 1):
    TrappedTile.__init__(self, typ)
    self.grids = [] # [(grid, on_push, on_unpush),...]
    
  def add_grid(self, grid, on_push = 1, on_unpush = None):
    self.grids.append((grid, on_push, on_unpush))
    if hasattr(grid, "typ"): self.set_typ(grid.typ)
    else:                    self.set_typ(0)
    
  def set_pushed(self, pushed):
    for grid__on_push__on_unpush in self.grids:
      opened = grid__on_push__on_unpush[2 - pushed]
      if not opened is None:
        grid__on_push__on_unpush[0].set_opened(opened)

        
class GridOpener(TrappedTile):
  def __init__(self, typ = 1, on_push = 1, on_unpush = None, autoclose_duration = 2):
    TrappedTile.__init__(self, typ)
    self.actions            = (on_unpush, on_push)
    self.on_unpush          = on_unpush
    self.autoclose_duration = autoclose_duration
    
  def set_pushed(self, pushed):
    action = self.actions[pushed]
    if not action is None:
      for grid in self.level.mobiles:
        if isinstance(grid, Grid) and grid.typ == self.typ:
          grid.set_opened(action)
          
          
class LifeAltar(TrappedTile):
  open_sound       = "strike4.wav"
  terraform_radius =  4.0
  closed_tile_y    =  1.0
  opened_tile_y    =  0.5
  
  def __init__(self):
    TrappedTile.__init__(self, 0)
    
    self.shape              = soya.Shape.get("altar1")
    self.autoclose_duration = 2
    self.gloom              = Gloom(self)
    self.gloom.y            = 3.0
    
  def set_pushed(self, pushed):
    self.gloom.add_life = pushed * 5.0
    for character in self.characters:
      character.doer.action_done(ValueState(character, STATE_HEAL_LIFE, 1.0))
      
      
class Gloom(soya.Particles):
  def __init__(self, parent = None, nb_particles = 7):
    soya.Particles.__init__(self, parent, soya.Material.get("x_lumiere_1"), nb_particles)
    self.auto_generate_particle = 1
    self.set_colors((0.3, 0.1, 0.2, 1.0), (0.8, 0.2, 0.3, 1.0), (0.8, 0.2, 0.3, 1.0), (0.6, 0.2, 0.3, 1.0), (0.0, 0.0, 0.0, 1.0))
    self.set_colors((0.3, 0.1, 0.2, 1.0), (0.6, 0.2, 0.3, 1.0), (0.6, 0.2, 0.3, 1.0), (0.6, 0.2, 0.3, 1.0), (0.0, 0.0, 0.0, 1.0))
    self.set_sizes((0.2, 0.2), (3.2, 3.2), (0.2, 0.2))
    self.max_particles_per_round = 400
    self.add_life = 0.0
    self.lit      = 0
    
  def generate(self, index):
    sx = random.uniform(-0.005, 0.005)
    sy = random.uniform(-0.0  , 0.005)
    sz = random.uniform(-0.005, 0.005)
    self.set_particle(index, self.add_life + random.uniform(1.0 , 3.0), 10.0 * sx, 10.0 * sy, 10.0 * sz, sx, sy, sz)
    

      
class Grid(OpenableTrap):
  def __init__(self, typ = 1, autoclose_duration = 0, size = "8_8"):
    OpenableTrap.__init__(self)
    self.shape              = soya.Shape.get("grille_%s@grille%s" % (size, typ))
    self.herse              = soya.Volume(self, soya.Shape.get("herse_%s@grille%s" % (size, typ)))
    self.typ                = typ
    self.autoclose_duration = autoclose_duration
    
  def _added_into_level(self, level):
    if level:             level.camera_can_pass_through.append(self.herse)
    elif self.level: self.level.camera_can_pass_through.remove(self.herse)
    
    soya.tofu4soya.Mobile._added_into_level(self, level)
    
  def do_action(self, action):
    if (not self.opened) and (self.herse.y > 0.0):
      for mobile in self.level.mobiles:
        if isinstance(mobile, Character) and (not mobile.invincible):
          _P.clone(mobile)
          _P.convert_to(self.herse)
          if (-5.0 < _P.x < 5.0) and (-3.0 < _P.y < 0.0) and (-1.5 < _P.z < 1.5):
            mobile.hurt(0.3, self, can_resist = 0)
            
  def advance_time(self, proportion):
    OpenableTrap.advance_time(self, proportion)
    
    if   self.opened:
      if self.herse.y < 8.0:
        self.herse.y += proportion * 0.05
        if self.herse.y >= 8.0: self.herse.y = 8.0
    else:
      if self.herse.y > 0.0:
        self.herse.y -= proportion * 0.5
        if self.herse.y <= 0.0:
          balazar.sound.play("lock-1.wav", self)
          self.herse.y = 0.0
          
          
class NoMonsterGrid(Grid):
  """A grid that auto-open when there is no monster."""
  def __init__(self):
    Grid.__init__(self, 3)
    
  def next_action(self):
    if self.level.nb_monster:
      if self.herse.y == 8.0: self.set_opened(0)
    else:
      self.set_opened(1)
      
      
class MiamGrid(Grid):
  def __init__(self, typ = 0, size = "8_8"):
    Grid.__init__(self, typ, 0, size)
    self.active  = 0
    self.opened  = 1
    self.herse.y = 8.0
    
  def big_round(self):
    if not self.doer.remote:
      if (self.active <= 0) and (self.herse.y == 0.0): self.set_opened(1)

      if self.active <= 0:
        for mobile in self.level.mobiles:
          if isinstance(mobile, Character) and mobile.distance_to(self) < 5.5:
            self.active = 1
            break

      if self.herse.y == 0.0:
        self.active -= 1
        
  def next_action(self):
    if self.active > 0:
      if self.opened:
        if self.herse.y == 8.0: self.set_opened(0)
        
  def set_state(self, state):
    if   state.action == TRAP_ACTION_OPEN:
      if not self.opened:
        balazar.sound.play("mecanism.wav", self, gain = 0.5)
        self.opened = 1
        
    elif state.action == TRAP_ACTION_CLOSE:
      if self.opened:
        self.opened = 0
        
  def advance_time(self, proportion):
    soya.World.advance_time(self, proportion)
    
    if   self.opened:
      if self.herse.y < 8.0:
        self.herse.y += proportion * 0.3
        if self.herse.y > 8.0: self.herse.y = 8.0
    else:
      if self.herse.y > 0.0:
        self.herse.y -= proportion * 0.5
        if self.herse.y < 0.0:
          balazar.sound.play("lock-1.wav", self, gain = 0.5)
          self.herse.y = 0.0
          
          
class Chest(OpenableTrap, base.Strikeable, base.Terraformer):
  radius           = 3.0
  terraform_radius = 3.0
  open_sound       = "porte-2.wav"
  treasure         = None
  monsters         = []
  
  def __init__(self, typ = 1, treasure = None, autoclose_duration = 200):
    OpenableTrap.__init__(self)
    self.shape = soya.Shape.get("coffre1@typ%s" % typ)
    self.couvercle = soya.Volume(self, soya.Shape.get("coffre_couvercle1@typ%s" % typ))
    self.couvercle.set_xyz(0.0, 2.0, 1.0)

    self.typ     = typ
    self.opening = 0.0
    self.autoclose_duration = autoclose_duration
    if typ == 1: self.scale(0.6, 0.6, 0.6)
    if not treasure is None: self.treasure = treasure
    
  def hurt(self, caster, damage):
    if not self.opening: self.set_opened(1)
    
  def set_opened(self, opened):
    if opened and (not self.opened) and self.monsters:
      Monster = random.choice(self.monsters)
      if Monster:
        m = Monster()
        m.move(self)
        self.level.add_mobile(m)
        
    OpenableTrap.set_opened(self, opened)
    
  def big_round(self):
    if not self.doer.remote:
      if self.opened and (not self.opening):
        if (self.close_duration == self.autoclose_duration):
          if self.treasure != 0:
            item = random.choice(self.treasure or ((self.typ == 2) and balazar.item.BIG_TREASURE) or balazar.item.SMALL_TREASURE)
            if item is balazar.item.Map: item = item(self.level.get_country().name)
            else:                        item = item()
            self.doer.action_done(NewItemState(item))
            
        self.close_duration -= 1
        if self.close_duration == 0: self.set_opened(0)
        
  def set_state(self, state):
    if isinstance(state, NewItemState):
      speed = soya.Vector(self, 0.0, 0.2, -0.1)
      speed.convert_to(self.level)
      speed.set_length(0.2236)
      f = FallingItem(self.level, state.item, speed)
      f.move(soya.Point(self, 0.0, 2.0, 0.0))
      
    elif state.action == TRAP_ACTION_OPEN:
      if not self.opened:
        if self.open_sound: balazar.sound.play(self.open_sound, self)
        self.opened  = 1
        self.opening = 120
        
    elif state.action == TRAP_ACTION_CLOSE:
      if self.opened:
        if self.close_sound: balazar.sound.play(self.close_sound, self)
        self.opened  = 0
        self.opening = -120
        
  def advance_time(self, proportion):
    soya.World.advance_time(self, proportion)
    
    if self.opening:
      if   self.opening > 0.0:
        self.couvercle.rotate_vertical( 3.0 * proportion)
        self.opening -= 3.0 * proportion
        if self.opening < 0.0: self.opening = 0.0
        
      elif self.opening < 0.0:
        self.couvercle.rotate_vertical(-3.0 * proportion)
        self.opening += 3.0 * proportion
        if self.opening > 0.0: self.opening = 0.0
    
    
    
class Mill(OpenableTrap, base.Terraformer):
  radius           = 8.0
  terraform_radius = 8.0
  
  def __init__(self, parent):
    OpenableTrap.__init__(self)
    self.shape = soya.Shape.get("pont_monte1")
    
  def advance_time(self, proportion):
    soya.World.advance_time(self, proportion)
    
    if self.opened:
      self.rotate_lateral(proportion * -1.0)
      

class RotatingBridgeTile(OpenableTrap, base.SensitiveFloor):
  open_sound  = "delectation.wav"
  
  def __init__(self):
    OpenableTrap.__init__(self)
    self.shape = soya.Shape.get("pont_trocon1")
    self.angle = 0.0
    
  def set_state(self, state):
    if   state.action == TRAP_ACTION_OPEN:
      if not self.opened:
        self.angle = 180.0
    OpenableTrap.set_state(self, state)
    
  def advance_time(self, proportion):
    soya.World.advance_time(self, proportion)
    
    if self.angle:
      if self.angle > proportion * 5.0:
        self.turn_vertical(proportion * 5.0)
        self.angle -= proportion * 5.0
      else:
        self.turn_vertical(self.angle)
        self.angle = 0.0
        self.set_opened(0)
        
  def character_on(self, character):
    if not self.opened:
      if random.random() < 0.08:
        self.set_opened(1)

    
    
class IndicatorPlatForm(soya.tofu4soya.Mobile, base.Terraformer):
  radius           = 8.0
  terraform_radius = 4.0
  
  def __init__(self, parent):
    soya.tofu4soya.Mobile.__init__(self)
    
    self.bot     = 1
    self.shape   = soya.Shape.get("arrow_platform")
    self.vector  = None
    self.vector0 = soya.Vector(self, 0.0, 0.0, -1.0)
    
  def rotate_toward(self, x, z):
    self.doer.action_done(IndicateState(x, z))
    
  def set_state(self, state):
    self.vector = soya.Vector(self.parent, state.x, 0.0, state.z)
    
  def advance_time(self, proportion):
    soya.World.advance_time(self, proportion)
    
    if self.vector:
      self.rotate_lateral(proportion * -8.0)
      
      if self.vector.distance_to(self.vector0) < 0.05:
        self.vector = None
      
