Logo Search packages:      
Sourcecode: balazar3 version File versions  Download package

base.py

# -*- coding: utf-8 -*-
# Balazar in the Rancid Skull Dungeon
# Copyright (C) 2008 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 3 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, see <http://www.gnu.org/licenses/>.

import struct, math, random, copy
import cerealizer
import balazar3, balazar3.globdef as globdef

if globdef.DRIVER == "3d":
  import balazar3.tofu.tofu4soya as tofu
else:
  import balazar3.tofu as tofu

from balazar3.tofu.sides import *
from balazar3.rooms import Room




class BaseMainLoop(tofu.MainLoop):
  @side("single", "server")
  def main_loop(self):
    import balazar3.dungeon
    
    balazar3.dungeon.load_dungeon()
    
    tofu.MainLoop.main_loop(self)
    
    balazar3.dungeon.DUNGEON.save()
    

class PlayerID(tofu.PlayerID):
  def __init__(self, filename, password, character_name = "balazar"):
    tofu.PlayerID.__init__(self, filename, password)
    self.character_name = character_name
    
  def dumps(self):
    return "%s%s\n%s" % (tofu.PlayerID.dumps(self), len(self.character_name), self.character_name)
  
  @classmethod
  def loads(Class, s):
    self = tofu.PlayerID.loads(s)
    length = int(s.readline())
    self.character_name = s.read(length)
    return self
  
class Player(tofu.Player):
  def __init__(self, player_id):
    tofu.Player.__init__(self, player_id)
    
    self.character_name = player_id.character_name
    
    level     = balazar3.game.Level.get("0_0")
    character = self.mobile = balazar3.game.Balazar()
    character.level = level
    character.set_display_name(self.filename)
    self.add_mobile(character)
    
  def killed(self):
    self.logout(1)
    
  def login(self, sock, udp_address):
    if not self.mobiles:
      self.mobile.level = balazar3.game.Level.get("0_0")
      self.mobile.i = 2.5
      self.mobile.j = 4.6
      self.mobile.angle = 180.0
      self.mobile.current_action = ACTION_STOP_MOVING
      
      if self.mobile.life <= 0.0: # Hero is dead
        self.mobile.curse = min(self.mobile.curse + 0.3, 0.999 + int(self.mobile.curse))
        self.mobile.items[0].set_equiped(self.mobile, 1)
      #else: # Hero has gone outside the dungeon
      
      self.mobile.life = 1.0
      self.add_mobile(self.mobile)
      
    tofu.Player.login(self, sock, udp_address)
    
    
class BaseLevel(tofu.Level):
  def __init__(self, filename):
    tofu.Level.__init__(self)
    
    I, J = filename.split("_")
    self.I          = int(I)
    self.J          = int(J)
    self.filename   = filename
    
    # XXX create a random level
    print "* Balazar 3 * New level %s..." % filename

    if   filename == "0_0":
      self.room = Room.get("17")
    else:
      import balazar3.dungeon
      balazar3.dungeon.DUNGEON.random_level(self)

  def __getstate__(self):
    return (self._filename, self.uid, self.round, self.active, self.mobiles, self.room.name, self.I, self.J)
  
  def __setstate__(self, state):
    self._filename, self.uid, self.round, self.active, self.mobiles, room_name, self.I, self.J = state
    self.room          = Room.get(room_name)
    self.state_counter = 0
        
ACTION_MOVE_LEFT         = "<"
ACTION_MOVE_LEFT_DOWN    = "1"
ACTION_MOVE_LEFT_UP      = "7"
ACTION_MOVE_RIGHT        = ">"
ACTION_MOVE_RIGHT_DOWN   = "3"
ACTION_MOVE_RIGHT_UP     = "9"
ACTION_MOVE_UP           = "^"
ACTION_MOVE_DOWN         = "v"
ACTIONS_MOVE = set([
  ACTION_MOVE_LEFT,
  ACTION_MOVE_LEFT_DOWN,
  ACTION_MOVE_LEFT_UP,
  ACTION_MOVE_RIGHT,
  ACTION_MOVE_RIGHT_DOWN,
  ACTION_MOVE_RIGHT_UP,
  ACTION_MOVE_UP,
  ACTION_MOVE_DOWN,
])
ACTION_TURN_LEFT         = "("
ACTION_TURN_RIGHT        = ")"
ACTION_GO_FRONT          = "-"
ACTION_GO_BACK           = "_"
ACTION_STOP_MOVING       = " "
ACTION_GOTO              = "*"
ACTION_GOTO_STRIKE       = "%"
ACTION_STRIKE            = "/"
ACTION_STRIKE_ONE        = "|"
ACTION_HIT               = ","
ACTION_DIE               = "."
ACTION_CHAT              = "C"
ACTION_EQUIP             = "E"
ACTION_UNEQUIP           = "e"
ACTION_DROP              = "d"
ACTION_DROP_ALL          = "D"
ACTION_USE               = "U"
ACTION_GRAB              = "G"

MESSAGE_LIFE             = "L"
MESSAGE_EXPERIENCE_CURSE = "X"
MESSAGE_SPEED            = "S"
MESSAGE_RESISTANCES      = "R"
MESSAGE_CHAT             = "C"
MESSAGE_EQUIPED          = "E"
MESSAGE_UNEQUIPED        = "e"
MESSAGE_USED             = "U"
MESSAGE_DROPPED          = "d"
MESSAGE_DROPPED_ALL      = "D"
MESSAGE_GRABBED          = "G"
MESSAGE_GAINED           = "g"

MESSAGE_OPENED           = "O"

ATTACK_BASH    = 1
ATTACK_SHARP   = 2
ATTACK_FIRE    = 3
ATTACK_ICE     = 4
ATTACK_SULPHUR = 5
ATTACK_HEAL    = 9

ANGLE_SPEED = 22.75
CHAR_RADIUS = 0.35
MOVE_DATA = {
  # ACTION_NAME:           (angle, speed_i, speed_j)
  ACTION_MOVE_UP:          (  0,  0.0,  1.0),
  ACTION_MOVE_LEFT_UP:     ( 45, -0.7,  0.7),
  ACTION_MOVE_LEFT:        ( 90, -1.0,  0.0),
  ACTION_MOVE_LEFT_DOWN:   (135, -0.7, -0.7),
  ACTION_MOVE_DOWN:        (180,  0.0, -1.0),
  ACTION_MOVE_RIGHT_DOWN:  (225,  0.7, -0.7),
  ACTION_MOVE_RIGHT:       (270,  1.0,  0.0),
  ACTION_MOVE_RIGHT_UP:    (315,  0.7,  0.7),
}

class Strikeable(object):
  def hit(self, by): pass


class Blocker(object):
  life            = 1.0
  blocking_radius = 0.7
  def block(self, blocked): pass


class BaseCharacter(tofu.Mobile, Strikeable, Blocker):
  hit_sound    = "couic1.wav"
  die_sound    = "couic2.wav"
  strike_sound = "coup.wav"
  def __init__(self, model_name = None):
    super(BaseCharacter, self).__init__()
    self.i              = 2.5
    self.j              = 4.6
    self.angle          = 180.0
    self.speed_i        = 0.0
    self.speed_j        = 0.0
    self.speed_angle    = 0.0
#    self._action_stack  = []
    self.current_action = ACTION_STOP_MOVING
    self.last_noticeable_round = 0
    self.last_important_round  = 0
    self.controller     = None
    self.animation_pos  = 0
    self.life           = 1.0
    self.goto_i         = 0.0
    self.goto_j         = 0.0
    self.goto_angle     = 0.0
    self.display_name   = u""
    
#  def _get_current_action(self):
#    if self._action_stack:
#        return self._action_stack[-1]
#    else:
#        return ACTION_STOP_MOVING

#  def _set_current_action(self, action):
#    self._action_stack = [action]

#  def _del_current_action(self):
#    self._action_stack = []

#  current_action = property(_get_current_action,
#                            _set_current_action,
#                            _del_current_action,"The current caracter state")

  def loaded(self):
    tofu.Mobile.loaded(self)
    self._init()
    
  def _init(self):
    self.set_model_name(self.model_name)
    if self.life <= 0.0: self.set_animation_name("mort2")
    else:                self.set_animation_name("attente")
    self.set_display_name(self.display_name)
    
  def __getstate__(self):
    return (self.uid, self.player_name, self.local, self.bot, self.level, self.display_name, self.i, self.j, self.angle, self.current_action, self.life, self.goto_i, self.goto_j, self.goto_angle, self.get_model_name())
  
  def __setstate__(self, state):
    self.uid, self.player_name, self.local, self.bot, self.level, self.display_name, self.i, self.j, self.angle, self.current_action, self.life, self.goto_i, self.goto_j, self.goto_angle, self.model_name = state
    self.speed_i        = 0.0
    self.speed_j        = 0.0
    self.speed_angle    = 0.0
    self.last_noticeable_round = 0
    self.last_important_round  = 0
    self.controller     = None
    self.animation_pos  = 0
    self.move_started   = 0
    #self.set_model_name(self.model_name)
    
  def get_network_state(self):
    if (self.current_action == ACTION_GOTO) or (self.current_action == ACTION_GOTO_STRIKE):
      return self.current_action + struct.pack("!ffffff", self.goto_i, self.goto_j, self.goto_angle, self.i, self.j, self.angle)
    else:
      return self.current_action + struct.pack("!fff", self.i, self.j, self.angle)
    
  def read_network_state(self, f):
    action = f.read(1)
    if (action == ACTION_GOTO) or (action == ACTION_GOTO_STRIKE):
      action += f.read(12)
    self.i, self.j, self.angle = struct.unpack("!fff", f.read(12))
    self.do_action(action)
    
  def set_controller(self, controller):
    self.controller = controller
    if controller: self.controller.character = self
    
  def generate_actions(self):
    if self.controller: self.controller.generate_actions()
    
  def do_action(self, action):
    if   action == ACTION_HIT:
      self.speed_i = self.speed_j = self.speed_angle = 0.0
      self.current_action = ACTION_HIT
      self.set_animation_name("couic")
      self.set_current_state_importance(2)
      
    elif action == ACTION_DIE:
      if self.current_action != ACTION_DIE:
        self.speed_i = self.speed_j = self.speed_angle = 0.0
        self.current_action = ACTION_DIE
        self.set_animation_name("mort")
        self.set_current_state_importance(2)
        
    elif action == ACTION_DROP_ALL:
      self.send_message(action)
      
    elif action.startswith(ACTION_CHAT):
      self.send_message(action)
      
    else:
      if (self.current_action != ACTION_HIT) and (self.current_action != ACTION_DIE):
        if (action in ACTIONS_MOVE) or (action == ACTION_GO_FRONT) or (action == ACTION_GO_BACK) or (action == ACTION_TURN_LEFT) or (action == ACTION_TURN_RIGHT):
          self.current_action = action
          self.speed_i = self.speed_j = self.speed_angle = 0.0
          self.move_started = 0
          self.set_current_state_importance(2)
          
        elif action == ACTION_STOP_MOVING:
          if (self.current_action == ACTION_STRIKE) or (self.current_action == ACTION_STRIKE_ONE):
            self.current_action = ACTION_STRIKE_ONE
            self.set_current_state_importance(2)
          else:
            self.current_action = ACTION_STOP_MOVING
            self.speed_i = self.speed_j = self.speed_angle = 0.0
            self.set_animation_name("attente")
            self.set_current_state_importance(2)
            
        elif (action == ACTION_STRIKE) or (action == ACTION_STRIKE_ONE):
          if (self.current_action != ACTION_STRIKE) and (self.current_action != ACTION_STRIKE_ONE):
            self.speed_i = self.speed_j = self.speed_angle = 0.0
            self.set_animation_name("frappe")
            self.set_current_state_importance(2)
          self.current_action = action
          
        elif (action.startswith(ACTION_GOTO)) or (action.startswith(ACTION_GOTO_STRIKE)):
          self.current_action = action[0]
          self.speed_i = self.speed_j = self.speed_angle = 0.0
          self.goto_i, self.goto_j, self.goto_angle = struct.unpack("!fff", action[1:])
          self.set_current_state_importance(2)
          
        elif action.startswith(ACTION_USE):
          item = tofu.Unique.undumpsuid(action[1:])
          if (isinstance(item, Usable) or isinstance(item, Limited)) and (item in self.items):
            if item.usable_by(self) or ((self.flag & FLAG_ITEM_LORE) and self.use_power(balazar3.game.ItemLore)): self.send_message(action)
            else: self.chat(_(u"__cannotuse__"))
            
        elif action.startswith(ACTION_EQUIP):
          item = tofu.Unique.undumpsuid(action[1:])
          if isinstance(item, Equipable) and (item in self.items):
            if item.usable_by(self) or ((self.flag & FLAG_ITEM_LORE) and self.use_power(balazar3.game.ItemLore)): self.send_message(action)
            else: self.chat(_(u"__cannotuse__"))
            
        elif action.startswith(ACTION_UNEQUIP):
          item = tofu.Unique.undumpsuid(action[1:])
          if isinstance(item, Equipable) and (item in self.items):
            self.send_message(action)
            
        elif action.startswith(ACTION_DROP):
          item = tofu.Unique.undumpsuid(action[1:])
          if isinstance(item, Dropable) and (item in self.items):
            self.send_message(action)
            
        elif action.startswith(ACTION_GRAB):
          item_on_ground = tofu.Unique.undumpsuid(action[1:])
          if item_on_ground:
            if isinstance(item_on_ground, BaseItemOnGround) and (abs(item_on_ground.i - self.i) < 0.8) and (abs(item_on_ground.j - self.j) < 0.8):
              self.send_message(action)
  
  def can_pass(self, speed_i, speed_j):
    if   speed_i > 0: collid_i = self.i + CHAR_RADIUS
    elif speed_i < 0: collid_i = self.i - CHAR_RADIUS
    else:             collid_i = self.i
    if   speed_j > 0: collid_j = self.j + CHAR_RADIUS
    elif speed_j < 0: collid_j = self.j - CHAR_RADIUS
    else:             collid_j = self.j
    bloc = self.level.room.blocs[int(collid_i + 1.0)][int(collid_j + 1.0)]
    
    if   bloc == 2: self.change_room(); return 0
    elif bloc == 3: self.exit_from_dungeon(); return 0
    elif bloc  > 0: return 0

    i = self.i + speed_i
    j = self.j + speed_j
    for mobile in self.level.mobiles:
      if (not mobile is self) and isinstance(mobile, Blocker) and (mobile.life > 0.0) and not (isinstance(mobile, BaseHero) and isinstance(self, BaseHero)):
        if (abs(mobile.i - i) < mobile.blocking_radius) and (abs(mobile.j - j) < mobile.blocking_radius):
          return 0
    return 1
  
  def change_room      (self): return 0
  def exit_from_dungeon(self): return 0
  
  
  def do_physics(self):
    if   self.current_action == ACTION_STOP_MOVING: return
    elif self.current_action in MOVE_DATA:
      angle, speed_i, speed_j = MOVE_DATA[self.current_action]
      rel_angle = (self.angle - angle) % 360
      if    20.0 <  rel_angle < 180.0:
        self.speed_angle = -ANGLE_SPEED
      elif 180.0 <= rel_angle < 340.0:
        self.speed_angle =  ANGLE_SPEED
      else:
        if not self.move_started:
          self.move_started = 1
          self.set_animation_name("marche")
          self.angle       =  angle
          self.speed_angle =   0.0
          
        speed_i *= self.move_speed
        speed_j *= self.move_speed
        
        if self.can_pass(speed_i, speed_j):
          self.speed_i = speed_i
          self.speed_j = speed_j
        else:
          self.speed_i = self.speed_j = 0
          
    elif self.current_action == ACTION_GO_FRONT:
      di, dj = angle2vector(self.angle)
      if   self.can_pass(self, di * self.move_speed, dj * self.move_speed):
        self.speed_i = di * self.move_speed
        self.speed_j = dj * self.move_speed
      elif abs(di) > abs(dj):
        self.speed_j = 0.0
        if self.can_pass(self, di * self.move_speed, 0.0):
          self.speed_i = di * self.move_speed
        else:
          self.speed_i = 0.0
      else:
        self.speed_i = 0.0
        if self.can_pass(self, 0.0, dj * self.move_speed):
          self.speed_j = dj * self.move_speed
        else:
          self.speed_j = 0.0
      if self.animation_name != "marche": self.set_animation_name("marche")
      
    elif self.current_action == ACTION_GO_BACK:
      di, dj = angle2vector(self.angle)
      if self.can_pass(self, -di * self.move_speed * 0.5, -dj * self.move_speed * 0.5):
        self.speed_i = -di * self.move_speed * 0.5
        self.speed_j = -dj * self.move_speed * 0.5
      else:
        self.speed_i = 0.0
        self.speed_j = 0.0
      if self.animation_name != "recule": self.set_animation_name("recule")
      
    elif self.current_action == ACTION_TURN_LEFT:
      self.speed_angle = -11.375
      
    elif self.current_action == ACTION_TURN_RIGHT:
      self.speed_angle =  11.375
      
    elif self.current_action == ACTION_HIT:
      if self.animation_pos == 6:
        self.current_action = ACTION_STOP_MOVING
        self.set_animation_name("attente")
        
    elif self.current_action == ACTION_DIE:
      if self.animation_name == "mort":
        if self.animation_pos == 9:
          self.set_animation_name("mort2")
      else:
        self.life -= 0.01
        if self.life < -1.0:
          if tofu.has_side("server") or tofu.has_side("single"):
            self.kill()
            
    elif self.current_action == ACTION_STRIKE:
      if   self.animation_pos == 1: tofu.MAIN_LOOP.play_sound(self.strike_sound)
      
    elif self.current_action == ACTION_STRIKE_ONE:
      if   self.animation_pos == 1: tofu.MAIN_LOOP.play_sound(self.strike_sound)
      elif self.animation_pos == 19:
        self.current_action = ACTION_STOP_MOVING
        self.set_animation_name("attente")
        
    elif (self.current_action == ACTION_GOTO) or (self.current_action == ACTION_GOTO_STRIKE):
      dangle = ((self.goto_angle - self.angle) % 360.0) - 180.0
      if   -160.0 < dangle <=   0.0: self.speed_angle =  22.75
      elif    0.0 < dangle <  160.0: self.speed_angle = -22.75
      else:
        di = self.goto_i - self.i
        dj = self.goto_j - self.j
        d = math.sqrt(di * di + dj * dj)
        if abs(di) > abs(dj):
          if di > 0:
            #can_pass = self.check_can_pass(self.i + CHAR_RADIUS, self.j) and self.level.can_pass(self, self.i + self.move_speed, self.j)
            can_pass = self.can_pass(self.move_speed, 0.0)
          else:
            #can_pass = self.check_can_pass(self.i - CHAR_RADIUS, self.j) and self.level.can_pass(self, self.i - self.move_speed, self.j)
            can_pass = self.can_pass(-self.move_speed, 0.0)
        else:
          if dj > 0:
            #can_pass = self.check_can_pass(self.i, self.j + CHAR_RADIUS) and self.level.can_pass(self, self.i, self.j + self.move_speed)
            can_pass = self.can_pass(0.0, self.move_speed)
          else:
            #can_pass = self.check_can_pass(self.i, self.j - CHAR_RADIUS) and self.level.can_pass(self, self.i, self.j - self.move_speed)
            can_pass = self.can_pass(0.0, -self.move_speed)
        if (((self.current_action == ACTION_GOTO       ) and (d > 0.2)) or
            ((self.current_action == ACTION_GOTO_STRIKE) and (d > 1.0))) and can_pass:
          self.speed_angle = 0
          self.angle = self.goto_angle
          self.speed_i = di * self.move_speed / d
          self.speed_j = dj * self.move_speed / d
          if self.animation_name != "marche": self.set_animation_name("marche")
        else:
          if (self.current_action == ACTION_GOTO_STRIKE) and (d < 1.1):
            self.do_action(ACTION_STRIKE)
          else:
            self.current_action = ACTION_STOP_MOVING
            self.speed_i = self.speed_j = self.speed_angle = 0.0
            if self.animation_name != "attente": self.set_animation_name("attente")
            
  def hit(self, by):
    if self.life > 0.0:
      physic = 0
      life = self.life
      for attack, damage in by.damages:
        life -= self.resistances[attack] * damage * 0.04
        if (attack == ATTACK_SHARP) or (attack == ATTACK_BASH): physic = 1
        
      self.angle = (by.angle + 180.0) % 360.0
      
      if isinstance(by, BaseHero):
        if life <= 0.0:
          by.add_experience_curse(self.experience_value, self.curse_value)
        if isinstance(by.weapon, Usable):
          by.send_message(MESSAGE_USED + by.weapon.dumpsuid())
          
      if life <= 0.0:
        life = 0.0
        self.send_action(ACTION_DIE)
        
      else:
        if physic:
          self.send_action(ACTION_HIT)
        else:
          self.set_current_state_importance(2) # self.angle has been modified
          
      self.send_message(MESSAGE_LIFE + str(attack) + struct.pack("!f", life))
      
  def do_collisions(self):
    if ((self.current_action == ACTION_STRIKE) or (self.current_action == ACTION_STRIKE_ONE)) and (self.animation_pos == 10):
      i, j = angle2vector(self.angle, 1.1)
      i += self.i
      j += self.j
      if self.damages[0][0] == ATTACK_ICE: radius = 6.0
      else:                                radius = 0.25
      for mobile in self.level.mobiles:
        # Equivalent to (but faster):
        #if (not mobile is self) and isinstance(mobile, Strikeable) and (math.sqrt((mobile.i - i) ** 2 + (mobile.j - j) ** 2) < 0.5):
        if (not mobile is self) and isinstance(mobile, Strikeable) and ((mobile.i - i) ** 2 + (mobile.j - j) ** 2 < radius):
          mobile.hit(self)
          
  def advance_time(self, proportion):
    tofu.Mobile.advance_time(self, proportion)
    
    if self.speed_angle:
      self.angle = (self.angle + self.speed_angle * proportion) % 360.0
      
    self.i += self.speed_i * proportion
    self.j += self.speed_j * proportion
    
  def set_display_name(self, name):
    self.display_name = name
    
  def do_message(self, message):
    if   message.startswith(MESSAGE_LIFE):
      old_life = self.life
      self.life = struct.unpack("!f", message[2:])[0]
      
      if old_life > self.life:
        if self.life: tofu.MAIN_LOOP.play_sound(self.hit_sound)
        else:         tofu.MAIN_LOOP.play_sound(self.die_sound)
      effect = int(message[1])
      if effect: self.create_effect(effect)
      
  def add_item(self, item):
    for i in range(len(self.items)):
      if not isinstance(self.items[i], Item):
        self.items.insert(i, item)
        break
    else: self.items.append(item)
    item.gained(self)
    

class BaseMonster(BaseCharacter):
  def __init__(self, model_name = None):
    super(BaseMonster, self).__init__(model_name)
    
    self.bot = 1
    import balazar3.ai
    self.set_controller(getattr(balazar3.ai, "%sController" % self.__class__.__name__)())
    
  def loaded(self):
    BaseCharacter.loaded(self)
    
    if self.local:
      import balazar3.ai
      self.set_controller(getattr(balazar3.ai, "%sController" % self.__class__.__name__)())

FLAG_ITEM_LORE = 1
FLAG_DODGE     = 2

class BaseHero(BaseCharacter):
  non_targetable   = 0
  experience_value = 0.3
  curse_value      = 0.5
  
  def __init__(self):
    super(BaseHero, self).__init__()
    
    self.experience  = 1.4
    self.curse       = 0.0
    self.items       = [balazar3.game.Club(), balazar3.game.LifePotion()]
    #self.items       = [balazar3.game.Club(), balazar3.game.TeleportShort(), balazar3.game.TeleportLong(), balazar3.game.TeleportShield(), balazar3.game.IceWand(), balazar3.game.IceStaff(), balazar3.game.FireWand(), balazar3.game.FireStaff(), balazar3.game.LifePotion()]
    #self.items       = [balazar3.game.Club(), balazar3.game.LifePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion(), balazar3.game.ExperiencePotion()]
    self.items[0].set_equiped(self, 1)
    self.keys        = {}
    self.flag        = 0
    self.resistances = self.__class__.resistances.copy()
    self._init()
    
  def __getstate__(self):
    return BaseCharacter.__getstate__(self) + (self.move_speed, self.resistances, self.experience, self.curse, self.items, self.keys, self.flag)
  
  def __setstate__(self, state):
    self.move_speed, self.resistances, self.experience, self.curse, self.items, self.keys, self.flag = state[-7:]
    BaseCharacter.__setstate__(self, state[:-7])
    
  def loaded(self):
    for item in self.items: item.loaded()
    self.calc_damage()
    
    BaseCharacter.loaded(self)
    
  def discard(self):
    BaseCharacter.discard(self)

    for item in self.items: item.discard()
    
  def begin_round(self):
    super(BaseHero, self).begin_round()
    
    if self.non_targetable: self.non_targetable -= 1
    
  @side("single", "server")
  def change_room(self):
    if tofu.has_side("server"):
      self.send_action(ACTION_STOP_MOVING)
      
    if   self.i <= 1: dI = -1; dJ =  0
    elif self.i >= 4: dI = +1; dJ =  0
    elif self.j <= 1: dI =  0; dJ = -1
    elif self.j >= 4: dI =  0; dJ = +1
    
    self.i = min(5.0, max(0.0, self.i - 5.0 * dI))
    self.j = min(5.0, max(0.0, self.j - 5.0 * dJ))
    
    new_level = balazar3.game.Level.get("%s_%s" % (self.level.I + dI, self.level.J + dJ))
    self.level.remove_mobile(self)
    new_level.add_mobile(self)
    
    self.non_targetable = 30
    
    if tofu.has_side("single"):
      tofu.MAIN_LOOP.reset()
      
    return 1
  
  @side("single")
  def exit_from_dungeon(self):
    import os
    
    self.level.remove_mobile(self)
    Player.get(self.player_name).remove_mobile(self)
    import balazar3.dungeon
    balazar3.dungeon.DUNGEON.delete()
    return 0
  
  @side("server")
  def exit_from_dungeon(self):
    self.level.remove_mobile(self)
    Player.get(self.player_name).remove_mobile(self)
    return 0
  
  def chat(self, text):
    self.send_action(ACTION_CHAT + text.encode("utf8"))
    
  def do_message(self, message):
    if   message.startswith(MESSAGE_LIFE):
      super(BaseHero, self).do_message(message)
      self.update_life()
    elif message.startswith(MESSAGE_EXPERIENCE_CURSE):
      old_experience = self.experience
      self.experience, self.curse = struct.unpack("!ff", message[1:])
      if int(self.experience) > int(old_experience): tofu.MAIN_LOOP.play_sound("bonus.wav")
      self.update_experience_curse()
    elif message.startswith(MESSAGE_EQUIPED):
      if self.local: tofu.MAIN_LOOP.play_sound("menu1.wav")
      item = tofu.Unique.undumpsuid(message[1:])
      item.set_equiped(self, 1)
    elif message.startswith(MESSAGE_UNEQUIPED):
      if self.local: tofu.MAIN_LOOP.play_sound("menu1.wav")
      item = tofu.Unique.undumpsuid(message[1:])
      item.set_equiped(self, 0)
      if isinstance(item, Weapon): self.items[0].set_equiped(self, 1)
      else:                        item         .set_equiped(self, 0)
    elif message.startswith(MESSAGE_USED):
      item = tofu.Unique.undumpsuid(message[1:])
      item.used(self)
    elif message.startswith(MESSAGE_DROPPED):
      if self.local: tofu.MAIN_LOOP.play_sound("depose.wav")
      item = tofu.Unique.undumpsuid(message[1:])
      item.dropped(self)
    elif message.startswith(MESSAGE_GRABBED):
      item_on_ground = tofu.Unique.undumpsuid(message[1:])
      if item_on_ground.level:
        if self.local: tofu.MAIN_LOOP.play_sound("menu1.wav")
        self.add_item(item_on_ground.item)
        item_on_ground.item = None # Needed, because without this, the remove_mobile would discard the item_on_ground, and thus the item itself !
        if tofu.has_side("server") or tofu.has_side("single"):
          self.level.remove_mobile(item_on_ground)
          
    elif message.startswith(MESSAGE_SPEED):
      self.move_speed = struct.unpack("!f", message[1:])[0]
    elif message.startswith(MESSAGE_RESISTANCES):
      self.resistances[ATTACK_BASH], self.resistances[ATTACK_SHARP], self.resistances[ATTACK_FIRE], self.resistances[ATTACK_ICE], self.resistances[ATTACK_SULPHUR] = struct.unpack("!fffff", message[1:])
    elif message.startswith(MESSAGE_GAINED):
      power = cerealizer.loads(message[1:])
      tofu.Unique._alls[power.uid] = power # Hack ! Needed, so as the server assign this UID to the object, without creating a new one.
      self.add_power(power)
    elif message == MESSAGE_DROPPED_ALL:
      to_drops = [item for item in self.items if isinstance(item, Dropable)]
      if to_drops:
        angle  = 0.0
        dangle = 6.2834 / len(to_drops)
        for item in to_drops:
          item.dropped(self, self.i + 0.7 * math.cos(angle), self.j + 0.7 * math.sin(angle))
          angle += dangle
    else:
      super(BaseHero, self).do_message(message)
      
  def init_interface(self):
    self.update_life()
    self.update_experience_curse()

  def set_move_speed(self, move_speed):
    self.send_message(MESSAGE_SPEED + struct.pack("!f", move_speed))
    
  def set_resistances(self, bash, sharp, fire, ice, sulphur):
    self.send_message(MESSAGE_RESISTANCES + struct.pack("!fffff", bash, sharp, fire, ice, sulphur))
    
  def add_experience_curse(self, experience, curse):
    old_experience = self.experience
    old_curse      = self.curse
    experience += self.experience
    curse      += self.curse
    
    s = []
    if int(experience) != int(old_experience):
      s.append(_(u"__level_up__") % int(experience))
      power = self.random_power()
      if power:
        d = power.description()
        d = d[:d.find(u"(")]
        s.append(_(u"__power_up__") % d)
        self.send_message(MESSAGE_GAINED + power.dumps())
        
    if int(curse) != int(old_curse):
      h = random.randint(0, 6)
      if   h == 0:
        self.set_move_speed(self.move_speed - 0.01)
        text = _(u"__curse_slow__")
      elif h == 1:
        self.life *= 0.4
        self.send_message(MESSAGE_LIFE + "0" + struct.pack("!f", self.life))
        text = _(u"__curse_sick__")
      elif h == 2:
        experience = int(experience)
        text = _(u"__curse_dumb__")
      elif h == 3:
        self.set_resistances(self.resistances[ATTACK_BASH], self.resistances[ATTACK_SHARP], self.resistances[ATTACK_FIRE] + 0.1, self.resistances[ATTACK_ICE], self.resistances[ATTACK_SULPHUR])
        text = _(u"__curse_fire__")
      elif h == 4:
        self.set_resistances(self.resistances[ATTACK_BASH], self.resistances[ATTACK_SHARP], self.resistances[ATTACK_FIRE], self.resistances[ATTACK_ICE] + 0.1, self.resistances[ATTACK_SULPHUR])
        text = _(u"__curse_ice__")
      elif h == 5:
        self.set_resistances(self.resistances[ATTACK_BASH], self.resistances[ATTACK_SHARP], self.resistances[ATTACK_FIRE], self.resistances[ATTACK_ICE], self.resistances[ATTACK_SULPHUR] + 0.1)
        text = _(u"__curse_sulphur__")
      elif h == 6:
        self.set_resistances(self.resistances[ATTACK_BASH] + 0.02, self.resistances[ATTACK_SHARP] + 0.02, self.resistances[ATTACK_FIRE], self.resistances[ATTACK_ICE], self.resistances[ATTACK_SULPHUR])
        text = _(u"__curse_weak__")
        
      s.append(u"%s %s" % (_(u"__curse_up__") % int(curse), text))

    if s: self.chat(u"\n".join(s))
      
    self.send_message(MESSAGE_EXPERIENCE_CURSE + struct.pack("!ff", experience, curse))
    
  def random_power(self):
    owned_powers = set()
    for item in self.items:
      if isinstance(item, Power):
        owned_powers.add(item.__class__)
        owned_powers.update(item.deprecates)
    powers = [power for power in self.possible_powers - owned_powers
              if power.requires.issubset(owned_powers)
              ]
    
    if powers:
      return random.choice(powers)()

  def add_power(self, power):
    # Remove power included in the new one (e.g. Heavy Basher 1 when you get the 2).
    for item in self.items[:]:
      if item.__class__ in power.deprecates:
        self.remove_item(item)
        
    self.items.append(power)
    power.gained(self)
    
  def remove_item(self, item):
    self.items.remove(item)
    item.lost(self)
  remove_power = remove_item
        
  def use_power(self, Power):
    for item in self.items:
      if isinstance(item, Power):
        self.use(item)
        return 1
      
  def get_info(self):
    return (
      _(u"__level_curse__") % (int(self.experience), int(self.curse)) + u" " +
      _(u"__speed__"      ) % (u"%s%%" % int(round(1000 * self.move_speed))) + u"\n" +
      _(u"__attack__"     ) % u", ".join([u"%s +%s"  % (_(u"__attack_%s__" % attack), damage) for attack, damage in self.damages]) + u"\n" +
      _(u"__resistance__" ) % u", ".join([u"%s %i%%" % (_(u"__attack_%s__" % attack), 100 - round(100 * self.resistances[attack])) for attack in range(1, 6)])
      )
  def calc_damage(self, weapon = None):
    if not weapon:
      for item in self.items:
        if isinstance(item, Weapon) and item.equiped:
          self.weapon = item
          break
    self.strike_sound = self.weapon.strike_sound
    self.damages = copy.deepcopy(self.weapon.damages)
    for item in self.items: item.calc_damage(self)
    
  def use    (self, item          ): self.send_action(ACTION_USE     + item          .dumpsuid())
  def equip  (self, item          ): self.send_action(ACTION_EQUIP   + item          .dumpsuid())
  def unequip(self, item          ): self.send_action(ACTION_UNEQUIP + item          .dumpsuid())
  def drop   (self, item          ): self.send_action(ACTION_DROP    + item          .dumpsuid())
  def grab   (self, item_on_ground): self.send_action(ACTION_GRAB    + item_on_ground.dumpsuid())
  
  def hit(self, by):
    if (self.flag & FLAG_DODGE) and self.use_power(DodgeTool):
      return
    BaseCharacter.hit(self, by)
    
  def kill(self):
    self.send_action(ACTION_DROP_ALL)
    
    BaseCharacter.kill(self)
    
class BaseController(object):
  def __init__(self): pass
  
  def generate_actions(self): pass


def angle2vector(angle, d = 1.0):
  angle /= 57.295779513082323
  return -d * math.sin(angle), d * math.cos(angle)
  
def vector2angle(di, dj):
  d = math.sqrt(di * di + dj * dj)
  if d == 0.0: d = 0.0001
  if di > 0: return 360.0 - math.acos(dj / d) * 57.295779513082323
  return math.acos(dj / d) * 57.295779513082323


class BaseTrap(tofu.Mobile):
  def __init__(self):
    tofu.Mobile.__init__(self)
    
    self.i             = 2.5
    self.j             = 2.5
    self.animation_pos = 0
    
  def loaded(self):
    tofu.Mobile.loaded(self)
    self._init()
    
    
class BaseChest(BaseTrap, Strikeable, Blocker):
  blocking_radius = 0.68
  def __init__(self):
    super(BaseChest, self).__init__()
    
    self.opened = 0
    self.bot    = 1
    self._init()
    
  def _init(self):
    self.set_model_name("coffre1")
    if self.opened: self.set_animation_name("ouvert")
    else:           self.set_animation_name("ferme")
    
  def __getstate__(self):
    return (self.uid, self.player_name, self.local, self.bot, self.level, self.i, self.j, self.opened)
  
  def __setstate__(self, state):
    self.uid, self.player_name, self.local, self.bot, self.level, self.i, self.j, self.opened = state
    self.last_noticeable_round = 0
    self.last_important_round  = 0
    
  def hit(self, by):
    if isinstance(by, BaseHero) and not self.opened:
      self.open()
      
  def open(self):
    self.send_message(MESSAGE_OPENED)
    
    j = self.j - 0.6
    item_on_ground = balazar3.game.ItemOnGround(self.get_item(), self.i, j)
    self.level.add_mobile(item_on_ground)

  def do_physics(self):
    if (self.animation_name == "ouvre") and (self.animation_pos == 9):
      self.set_animation_name("ouvert")
      
  def do_message(self, message):
    if message == MESSAGE_OPENED:
      self.opened = 1
      self.set_animation_name("ouvre")
      tofu.MAIN_LOOP.play_sound("mecanism.wav")
      
class Tool(tofu.Unique):
  def __init__(self):
    tofu.Unique.__init__(self)
  
  def usable_by(self, character): return 1
  
  def description(self): return _(u"__%s__" % self.__class__.__name__)
  
  def calc_damage(self, character): pass
  
  def gained(self, character): pass
  def lost  (self, character): pass
  
class Dropable(object):
  def dropped(self, character, i = None, j = None):
    if isinstance(self, Equipable) and self.equiped: self.set_equiped(character, 0)
    character.items.remove(self)
    
    if tofu.has_side("server") or tofu.has_side("single"):
      item_on_ground = balazar3.game.ItemOnGround(self, i or character.i, j or character.j)
      character.level.add_mobile(item_on_ground)
      
class Equipable(object):
  def __init__(self):
    super(Equipable, self).__init__()
    self.equiped = 0
    
  def set_equiped(self, character, equiped):
    if        equiped and not self.equiped: self.on_equiped  (character)
    elif self.equiped and not      equiped: self.on_unequiped(character)

  def on_equiped  (self, character): self.equiped = 1
  def on_unequiped(self, character): self.equiped = 0
    
class Weapon(Equipable):
  strike_sound = "coup.wav"
  def dropped(self, character, i = None, j = None):
    if self.equiped: character.equip(character.items[0])
    super(Weapon, self).dropped(character, i, j)
    
  def lost  (self, character):
    if self.equiped: character.equip(character.items[0])
    
  def on_equiped(self, character):
    for item in character.items:
      if isinstance(item, Weapon) and item.equiped: item.set_equiped(character, 0)
    super(Weapon, self).on_equiped(character)
    character.weapon = self
    character.calc_damage(self)
    
  def description(self):
    return "%s (%s)" % (_(u"__%s__" % self.__class__.__name__), u", ".join([u"%s +%s"  % (_(u"__attack_%s__" % attack), damage) for attack, damage in self.damages]))
  
class Usable(object):
  def used(self, character): pass
  
class Limited(object):
  nb_use = 1
  def used(self, character):
    if isinstance(self, Usable): super(Limited, self).used(character)
    self.nb_use -= 1
    if self.nb_use == 0:
      character.remove_item(self)
      
  def on_equiped(self, character):
    super(Limited, self).on_equiped(character)
    if not isinstance(self, Usable): self.nb_use -= 1 # Else, it is the number of use that is limited (e.g. a weapon)
    
  def on_unequiped(self, character):
    super(Limited, self).on_unequiped(character)
    if self.nb_use == 0:
      character.remove_item(self)
      
  def description(self):
    s = super(Limited, self).description()
    return s + _(u"__item_nb_use__") % self.nb_use
  
class Item(Tool): pass

class Power(Tool):
  requires = deprecates = set()

class DodgeTool(Equipable): # Tag class for FLAG_DODGE
  def on_equiped(self, character):
    super(DodgeTool, self).on_equiped(character)
    character.flag |=  FLAG_DODGE
    
  def on_unequiped(self, character):
    super(DodgeTool, self).on_unequiped(character)
    character.flag &= ~FLAG_DODGE



  
class BaseItemOnGround(tofu.Mobile):
  def __init__(self, item, i, j):
    tofu.Mobile.__init__(self)
    self.bot  = 1
    self.item = item
    self.i    = i
    self.j    = j
    
  def __getstate__(self):
    return (self.uid, self.level, self.player_name, self.local, self.bot, self.item, self.i, self.j)
  
  def __setstate__(self, state):
    self.uid, self.level, self.player_name, self.local, self.bot, self.item, self.i, self.j = state
    self.last_noticeable_round = 0
    self.last_important_round  = 0
    
  def loaded(self):
    tofu.Mobile.loaded(self)
    if self.item: self.item.loaded()
    
  def discard(self):
    tofu.Mobile.discard(self)
    if self.item: self.item.discard()

Generated by  Doxygen 1.6.0   Back to index