# 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 self.append_and_cancel(self.do_nothing()) def do_nothing(self): if random.random() < 0.6: yield Wait (self.mobile, 200) else: yield Wander(self.mobile, 200) def relation_changed(self, character): self.check_time = 2 def discussing(self, hero): if not self.enemy: self.append_and_cancel(Wait(self.mobile, 10000)) self.append(LookAt(self.mobile, hero)) self.check_time = 10000 def end_discussing(self, hero = None): 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 self.enemy = None 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) + 2.0) if self.mobile.distance_to(enemy) > self.mobile.range + ((self.mobile.weapon and self.mobile.weapon.range) or 0.0): yield LookAt(self.mobile, enemy, 1.6) yield self.mobile.strike_charge(enemy) else: _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) def discussion_action_combat(self, hero): hero.set_relation (self , -1.0) hero.change_relation(self.race, -0.5) class Monster(NPC): pass