# Balazar
# Copyright (C) 2004-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 soya, soya.tofu4soya as tofu4soya
from soya import Point, Vector

from balazar.controller import StackController, Goto, GotoXZ, Gotoward, LookAt, GrabItem, Fly, Wait, Wander
from balazar.character  import *
from balazar.character  import _P, _V
from balazar.item       import Item, Weapon, Scepter
from balazar.trap       import LifeAltar
import balazar.globdef  as globdef

import balazar.base  as base


class AIController(StackController):
  vision               = 50.0
  aggressivity         = 0.0
  dodge                = 0.05
  dodge_while_striking = 0
  
  def __init__(self, mobile = None):
    StackController.__init__(self, mobile)
    
    self.check_time = 0
    self.enemy      = None
    
  def big_round(self):
    pass
  
  def begin_round(self):
    if self.enemy and (not self.uncancelable_controller) and (self.dodge_while_striking or (not self.mobile.current_animation.startswith("combat"))):
      if random.random() < self.dodge:
        if self.enemy.current_animation and self.enemy.current_animation.startswith("combat") and (self.mobile.distance_to(self.enemy) < 3.0):
          self.animate(self.attack_dodge())
          
    if not self.controllers: self.check()
    else:
      self.check_time -= 1
      if (not self.uncancelable_controller) and (self.check_time <= 0):
        self.check() # Cancel current action, because it is taking too much time.
        
    StackController.begin_round(self)
    
    
  def check(self):
    self.check_time = 400
    self.enemy      = None
    
    interests = map(lambda o: (self.interest(o), o), self.mobile.level)
    
    if interests:
      a = max(interests)
      b = min(interests)
      
      if a[0] > -b[0]: o = a
      else:            o = b
      if abs(o[0]) > 0.2:
        self.append_and_cancel(self.deal_with(*o))
        return
      
    # Nothing of interest
    if random.random() < 0.6: self.append_and_cancel(Wait  (self.mobile, 200))
    else:                     self.append_and_cancel(Wander(self.mobile, 200))
    
  def relation_changed(self, character):
    self.check_time = 2
    
  def interest(self, o, d = None):
    """Returns a value in the range [-1.0, 1.0] indicating if the AI is interested
by O (where 1.0 means very interested, 0.0 means ignore it, and -1.0 means go away
from it)."""
    if o is self.mobile: return 0.0
    if d is None: d = 1.0 + self.mobile.distance_to(o) / self.vision
    
    if isinstance(o, Item):
      if o.owner:
        if not o.owner is self.mobile:
          # XXX Steal ?
          pass
        return 0.0
      else:
        incomp = 0
        for item in self.mobile.items:
          if item.incompatible_with(o): incomp += item.price
        #return (0.1 + max(0.0, o.blessed) - incomp) / 3.0 / d
        return (10.0 + o.price - incomp) / 30.0 / d
      
    if isinstance(o, Character):
      if o.life <= 0.0: return 0.0
      return min(0.0, self.mobile.get_relation(o) / d)
    
    if isinstance(o, LifeAltar):
      if self.mobile.life > 0.5: return 0.0
      return 3.0 * (1.0 - self.mobile.life) / d
    
    return 0.0 # Unidentifiable or useless thing
  
  def deal_with(self, interest, o):
    if interest < 0.0:
      if isinstance(o, Character): yield self.deal_with_character(o)
      else:                        yield Fly     (self.mobile, o)
    elif isinstance(o, Item):      yield GrabItem(self.mobile, o)
    elif isinstance(o, LifeAltar):
      yield Goto(self.mobile, soya.Point(o, 0.0, 2.0, 0.0))
      yield Action(ACTION_WAIT)
      while self.mobile.life < 0.5: yield None
    else:                          yield Goto    (self.mobile, o)
    
  def deal_with_character(self, enemy):
    self.enemy = enemy
    while enemy.life > 0.0:
      a = self.aggressivity + self.mobile.life - enemy.life
      
      if a > 0.0:
        yield self.attack(enemy)
        #yield self.attack_jump(enemy)
        #yield self.attack_jump_over2(enemy)
        #yield self.attack_back (enemy)
        #yield self.attack_side (enemy)
        #yield self.attack_basic(enemy)
      else:
        yield Fly(self.mobile, enemy)
        break
      
  def attack_dodge(self):
    yield Action(ACTION_JUMP)
    yield Action(ACTION_GO_BACK)
    for i in range(10): yield None
    
  def attack_basic(self, enemy):
    yield Goto(self.mobile, enemy, self.mobile.range + ((self.mobile.weapon and self.mobile.weapon.range) or 0.0))
    #yield LookAt(self.mobile, enemy, 3.0)
    
    _P.clone(enemy)
    _P.convert_to(self.mobile)
    if -1.0 < _P.y < 1.0:
      if _P.x < 0.0: yield self.mobile.strike_left (enemy)
      else:          yield self.mobile.strike_right(enemy)
    else:
      yield self.mobile.strike_sagittal(enemy)
      
  def attack_side(self, enemy):
    _P.clone(self.mobile)
    _P.convert_to(enemy)
    if _P.x < 0.0: x = -6.0
    else:          x =  6.0
    yield GotoXZ(self.mobile, Point(enemy, x, 0.0, 2.0), 1.5)
    
    self.attack_basic(enemy)
    
  def attack_back(self, enemy):
    _P.clone(self.mobile)
    _P.convert_to(enemy)
    if _P.z < abs(_P.x):
      if _P.x < 0.0: x = -6.0
      else:          x =  6.0
      yield GotoXZ(self.mobile, Point(enemy, x, 0.0, 2.0), 1.5)
      
    yield GotoXZ(self.mobile, Point(enemy, 0.0, 0.0, 3.0), 1.5)
    
    yield self.attack_basic(enemy)
    
  def attack_jump_over(self, enemy):
    yield Goto(self.mobile, enemy, 5.0)
    yield LookAt(self.mobile, enemy)
    
    yield Action(ACTION_JUMP)
    
    yield GotoXZ(self.mobile, Point(enemy, 0.0, 0.0, 3.0), 3.0)
    yield Action(ACTION_STOP_JUMPING)
    yield GotoXZ(self.mobile, Point(enemy, 0.0, 0.0, 3.0), 1.0)
    
    yield LookAt(self.mobile, enemy, 3.0)
    
    yield self.attack_basic(enemy)
    
  def attack_jump_over2(self, enemy):
    yield Goto(self.mobile, enemy, 5.0)
    yield LookAt(self.mobile, enemy)
    
    yield Action(ACTION_JUMP)
    yield Action(ACTION_TURN_LEFT)
    for i in range(13): yield Action(ACTION_TURN_LEFT)
    yield Action(ACTION_STOP_JUMPING)
    yield Action(ACTION_GO_BACK)
    for i in range(25):
      if self.mobile.on_ground: break
      yield None
      
    yield self.attack_basic(enemy)
    
  def attack_jump(self, enemy):
    yield LookAt(self.mobile, enemy, 2.0)
    yield Action(ACTION_GO_BACK)
    for i in range(1000):
      if self.mobile.distance_to(enemy) > 6.5: break
      yield None
    yield Action(ACTION_JUMP)
    
    yield self.mobile.strike_sagittal(enemy)
    yield self.attack_basic(enemy)
    
    
class NPC(Character):
  Controller = AIController
  def __init__(self):
    Character.__init__(self)
    
    self.bot = 1
    
  def next_action(self):
    if self.bot:
      assert globdef.mode != "--client"
      self.controller = self.Controller(self)
    return None
  
  def add_item(self, item, auto_equip = 1):
    Character.add_item(self, item)
    
    if (not self.doer.remote) and auto_equip: # Else, game not started
      if isinstance(item, Weapon):
        if (not self.weapon) or (item.price >= self.weapon.price):
          self.doer.do_action(ItemAction(ACTION_EQUIP_ITEM, item))
          
  def auto_equip_items(self):
    best_weapon = None
    for item in self.items:
      if   isinstance(item, Weapon) and ((not best_weapon) or (best_weapon.price < item.price)):
        best_weapon = item
      elif isinstance(item, Scepter): item.set_equiped(1)
    if best_weapon: best_weapon.set_equiped(1)
        
      
class Monster(NPC):
  pass




