# Balazar # Copyright (C) 2003-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 math, random, types import tofu, soya, soya.tofu4soya, soya.sdlconst as sdlconst import balazar from balazar.character import * from balazar.character import _P, _V # constants of action detected on keyboard/joystick CONTROL_UNKNOWN = 0 CONTROL_UP = 1 CONTROL_DOWN = 2 CONTROL_LEFT = 3 CONTROL_RIGHT = 4 CONTROL_JUMP = 5 CONTROL_STRIKE = 6 CONTROL_ITEM = 7 CONTROL_QUIT = 8 CONTROL_PAUSE = 9 CONTROL_MENU = 10 CONTROL_LOOK_AT_LEFT = 11 CONTROL_LOOK_AT_RIGHT = 12 CONTROL_SUICIDE = 13 CONTROL_SCREENSHOT = 14 class Controls: def __init__(self): self.control_names = { CONTROL_UP : "Advance", CONTROL_DOWN : "Go back", CONTROL_LEFT : "Turn left", CONTROL_RIGHT : "Turn right", CONTROL_JUMP : "Jump", CONTROL_STRIKE : "Strike", CONTROL_ITEM : "Use item", CONTROL_QUIT : "Quit", CONTROL_PAUSE : "Pause", CONTROL_MENU : "Menu / inventory", CONTROL_LOOK_AT_LEFT : "Look at left", CONTROL_LOOK_AT_RIGHT: "Look at right", CONTROL_SUICIDE : "Suicide", CONTROL_SCREENSHOT : "Take screenshot", } self.control_2_events = { CONTROL_UP : [sdlconst.K_UP ], CONTROL_DOWN : [sdlconst.K_DOWN ], CONTROL_LEFT : [sdlconst.K_LEFT ], CONTROL_RIGHT : [sdlconst.K_RIGHT], CONTROL_JUMP : [sdlconst.K_LSHIFT, sdlconst.K_RSHIFT , sdlconst.K_0], CONTROL_STRIKE : [sdlconst.K_LCTRL , sdlconst.K_RCTRL , sdlconst.K_1], CONTROL_ITEM : [sdlconst.K_LALT , sdlconst.K_RALT , sdlconst.K_DELETE, sdlconst.K_4], CONTROL_QUIT : [sdlconst.K_q , sdlconst.K_ESCAPE , sdlconst.K_3], CONTROL_PAUSE : [sdlconst.K_p , sdlconst.K_5], CONTROL_MENU : [sdlconst.K_RETURN, sdlconst.K_2], CONTROL_LOOK_AT_LEFT : [sdlconst.K_w , sdlconst.K_6], CONTROL_LOOK_AT_RIGHT: [sdlconst.K_x , sdlconst.K_7], CONTROL_SUICIDE : [sdlconst.K_k], CONTROL_SCREENSHOT : [sdlconst.K_c], } self.changed() def changed(self): self.event_2_control = dict([(event, control) for (control, events) in self.control_2_events.items() for event in events]) def assign_event_to_control(self, event, control): # First, remove the event (if it was previously linked to another control) if self.event_2_control.has_key(event): self.control_2_events[self.event_2_control[event]].remove(event) self.control_2_events[control].append(event) self.changed() def get_events_for_control(self, control): return self.control_2_events[control] def get_control_for_event(self, event): return self.event_2_control.get(event, CONTROL_UNKNOWN) def get_control(self, sdl_event): if (sdl_event[0] == sdlconst.KEYDOWN) or (sdl_event[0] == sdlconst.KEYUP): return self.event_2_control.get(sdl_event[1], CONTROL_UNKNOWN) elif (sdl_event[0] == sdlconst.JOYBUTTONDOWN) or (sdl_event[0] == sdlconst.JOYBUTTONUP): # HACK ! Joystick button #n is considered as the same as key "n" return self.event_2_control.get(sdlconst.K_0 + sdl_event[1], CONTROL_UNKNOWN) elif sdl_event[0] == sdlconst.JOYAXISMOTION: if sdl_event[1] == 0: if sdl_event[2] < 0: return CONTROL_LEFT elif sdl_event[2] > 0: return CONTROL_RIGHT elif sdl_event[1] == 1: if sdl_event[2] < 0: return CONTROL_UP elif sdl_event[2] > 0: return CONTROL_DOWN return CONTROL_UNKNOWN def control_label(self, control): return u"%s : %s" % (self.control_name(control), u", ".join([self.event_name(event) for event in self.get_events_for_control(control)])) def controls(self): return CONTROLS.control_2_events.keys() def control_name(self, control): return _(self.control_names[control]) def event_name(self, event): const_2_name = dict([(value, name) for (name, value) in sdlconst.__dict__.items()]) return _(const_2_name[event][2:].lower()) CONTROLS = Controls() class StackController(soya.tofu4soya.LocalController): def __init__(self, mobile, controllers = None): soya.tofu4soya.LocalController.__init__(self, mobile) self.controllers = controllers or [] self.uncancelable_controller = None def __getstate__(self): state = self.__dict__.copy() state["controllers" ] = [] state["uncancelable_controller"] = None return state def animate(self, controller): if not self.uncancelable_controller in self.controllers: self.controllers.append(controller) self.uncancelable_controller = controller return 1 def animate_and_cancel(self, controller): self.controllers *= 0 self.controllers.append(controller) self.uncancelable_controller = controller return 1 def append(self, controller): if not self.uncancelable_controller in self.controllers: self.controllers.append(controller) def append_and_cancel(self, controller): if not self.uncancelable_controller in self.controllers: self.controllers *= 0 self.controllers.append(controller) def cancel(self, controller): try: self.controllers.remove(controller) except: return 0 if self.uncancelable_controller is controller: self.uncancelable_controller = None return 1 def big_round(self): pass def begin_round(self): while self.controllers: action = self.controllers[-1] if action is None: self.mobile.doer.do_action(None) return elif isinstance(action, tofu.Action): del self.controllers[-1] self.mobile.doer.do_action(action) return elif isinstance(action, soya.tofu4soya.LocalController): r = action.begin_round() if r: return else: del self.controllers[-1] else: # A generator / a pseudo-generator try: action = action.next() except StopIteration: if self.uncancelable_controller is self.controllers[-1]: self.uncancelable_controller = None del self.controllers[-1] else: if action is None: self.mobile.doer.do_action(None) return elif isinstance(action, tofu.Action): self.mobile.doer.do_action(action) return elif isinstance(action, types.GeneratorType): self.controllers.append(action) def relation_changed(self, character): pass GUARD_CONTROL_2_ACTION = { (1, 0) : ACTION_TURN_LEFT, (0, 1) : ACTION_TURN_RIGHT, } NORMAL_CONTROL_2_ACTION = { (0, 0, 1, 0) : ACTION_ADVANCE, (1, 0, 1, 0) : ACTION_ADVANCE_LEFT, (0, 1, 1, 0) : ACTION_ADVANCE_RIGHT, (1, 0, 0, 0) : ACTION_TURN_LEFT, (0, 1, 0, 0) : ACTION_TURN_RIGHT, (0, 0, 0, 1) : ACTION_GO_BACK, (1, 0, 0, 1) : ACTION_GO_BACK_LEFT, (0, 1, 0, 1) : ACTION_GO_BACK_RIGHT, } class HumanController(soya.tofu4soya.LocalController): def __init__(self, mobile): soya.tofu4soya.LocalController.__init__(self, mobile) self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0 self.look_key_down = 0 self.strike_key_down = 0 self.current_deplacement = ACTION_WAIT def big_round(self): pass def begin_round(self): for event in soya.process_event(): event_type = event[0] if (event_type == sdlconst.KEYDOWN) or (event_type == sdlconst.KEYUP) or (event_type == sdlconst.JOYBUTTONDOWN) or (event_type == sdlconst.JOYBUTTONUP) or (event_type == sdlconst.JOYAXISMOTION): if event_type == sdlconst.JOYAXISMOTION: if event[1] == 0: if event[2] < 0: event_type = sdlconst.KEYDOWN; control = CONTROL_LEFT elif event[2] > 0: event_type = sdlconst.KEYDOWN; control = CONTROL_RIGHT else: event_type = sdlconst.KEYUP if self.left_key_down: control = CONTROL_LEFT else: control = CONTROL_RIGHT elif event[1] == 1: if event[2] < 0: event_type = sdlconst.KEYDOWN; control = CONTROL_UP elif event[2] > 0: event_type = sdlconst.KEYDOWN; control = CONTROL_DOWN else: event_type = sdlconst.KEYUP if self.up_key_down : control = CONTROL_UP else: control = CONTROL_DOWN else: control = CONTROLS.get_control(event) if control != CONTROL_UNKNOWN: if (event_type == sdlconst.KEYDOWN) or (event_type == sdlconst.JOYBUTTONDOWN): for active_widget in tofu.GAME_INTERFACE.active_widgets: if active_widget.key_down(control): break else: if control == CONTROL_QUIT: import balazar.game_interface d = balazar.game_interface.QuitMenu(self.mobile).activate(1) #tofu.GAME_INTERFACE.end_game() # Quit the game elif control == CONTROL_JUMP: self.mobile.doer.do_action(Action(ACTION_JUMP)) elif control == CONTROL_LEFT: self.left_key_down = 1 elif control == CONTROL_RIGHT: self.right_key_down = 1 elif control == CONTROL_UP: self.up_key_down = 1 elif control == CONTROL_DOWN: self.down_key_down = 1 elif control == CONTROL_STRIKE: self.strike_key_down = 1 if self.mobile.weapon: self.mobile.doer.do_action(Action(ACTION_GUARD)) elif control == CONTROL_MENU: import balazar.game_interface, balazar.inventory if not tofu.GAME_INTERFACE.active_widgets: balazar.game_interface.Bubble(soya.root_widget, self.mobile, balazar.inventory.Inventory(self.mobile)).set_focus(1) elif control == CONTROL_SUICIDE: self.mobile.doer.do_action(Action(ACTION_KILLED)) elif control == CONTROL_LOOK_AT_LEFT: self.look_key_down = 1 elif control == CONTROL_LOOK_AT_RIGHT: self.look_key_down = 1 elif control == CONTROL_PAUSE: # Hack !! remove Idler.render, because it would render an empty scene. soya.IDLER.render = lambda : None tofu.GAME_INTERFACE.end_game("paused") # Quit the game, and relaunch it after the pause elif control == CONTROL_SCREENSHOT: import tempfile screenshot_file = tempfile.mktemp(".jpeg") soya.screenshot(screenshot_file) print "* Balazar * Screenshot saved as %s" % screenshot_file else: # Up if control == CONTROL_JUMP: self.mobile.doer.do_action(Action(ACTION_STOP_JUMPING)) elif control == CONTROL_LEFT: self.left_key_down = 0 elif control == CONTROL_RIGHT: self.right_key_down = 0 elif control == CONTROL_UP: self.up_key_down = 0 elif control == CONTROL_DOWN: self.down_key_down = 0 elif control == CONTROL_STRIKE: self.strike_key_down = 0 self.auto_strike() elif control == CONTROL_LOOK_AT_LEFT: if (self.look_key_down == 1) and not tofu.GAME_INTERFACE.traveling.is_in_fight_mode(): tofu.GAME_INTERFACE.traveling.lateral_angle -= math.pi / 2.0 if tofu.GAME_INTERFACE.traveling.lateral_angle < -math.pi: tofu.GAME_INTERFACE.traveling.lateral_angle += 2 * math.pi self.look_key_down = 0 elif control == CONTROL_LOOK_AT_RIGHT: if (self.look_key_down == 1) and not tofu.GAME_INTERFACE.traveling.is_in_fight_mode(): tofu.GAME_INTERFACE.traveling.lateral_angle += math.pi / 2.0 if tofu.GAME_INTERFACE.traveling.lateral_angle > math.pi: tofu.GAME_INTERFACE.traveling.lateral_angle -= 2 * math.pi self.look_key_down = 0 if self.look_key_down and not tofu.GAME_INTERFACE.traveling.is_in_fight_mode(): from balazar.game_interface import CameraTraveling traveling = tofu.GAME_INTERFACE.traveling if self.left_key_down: self.look_key_down = 2 traveling.lateral_angle -= math.pi / 2.0 if traveling.lateral_angle < -math.pi: traveling.lateral_angle += 2 * math.pi self.left_key_down = 0 elif self.right_key_down: self.look_key_down = 2 traveling.lateral_angle += math.pi / 2.0 if traveling.lateral_angle > math.pi: traveling.lateral_angle -= 2 * math.pi self.right_key_down = 0 elif self.up_key_down: self.look_key_down = 2 if isinstance(tofu.GAME_INTERFACE.camera.travelings[-1], CameraTraveling): tofu.GAME_INTERFACE.camera.travelings[-1].look_toward(1) else: if traveling.distance > 4.0: traveling.distance -= 0.5 traveling.top_view -= 0.0283 elif traveling.offset_y2 < 4.0: traveling.offset_y2 += 0.1 elif self.down_key_down: self.look_key_down = 2 if isinstance(tofu.GAME_INTERFACE.camera.travelings[-1], CameraTraveling): tofu.GAME_INTERFACE.camera.travelings[-1].look_toward(-1) else: if traveling.offset_y2 > 1.0: traveling.offset_y2 -= 0.1 elif traveling.distance < 18.0: traveling.distance += 0.5 traveling.top_view += 0.0283 self.mobile.doer.do_action(None) else: if self.strike_key_down: action = GUARD_CONTROL_2_ACTION .get((self.left_key_down, self.right_key_down), ACTION_WAIT) else: action = NORMAL_CONTROL_2_ACTION.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT) if action == self.current_deplacement: if self.strike_key_down and (not self.mobile.current_action) and self.mobile.weapon: # The strike key has been pressed while another action was doing self.mobile.doer.do_action(Action(ACTION_GUARD)) else: self.mobile.doer.do_action(None) else: self.current_deplacement = action self.mobile.doer.do_action(Action(action)) return 1 def find_fight_target(self): target = None best_dist = 10000.0 for s in self.mobile.level: if isinstance(s, base.Strikeable) or (isinstance(s, Character) and (not s is self.mobile) and (s.life > 0.0) and self.mobile.is_enemy(s)): dist = self.mobile.distance_to(s) if isinstance(s, Character): dist = dist / 2.0 # Characters are targetted prioritarily than mushrooms ! elif dist > 15.0: continue # Too far away if dist < best_dist: best_dist = dist target = s return target def auto_strike(self): if not self.mobile.current_animation.startswith("combat"): target = self.find_fight_target() if target and isinstance(target, Character): tofu.GAME_INTERFACE.traveling.set_enemy(target) if self.left_key_down or self.right_key_down or self.up_key_down or self.down_key_down: if self.left_key_down : self.mobile.doer.do_action(FightAction(ACTION_FIGHT_LEFT , target)); return if self.right_key_down: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_RIGHT , target)); return if self.up_key_down : self.mobile.doer.do_action(FightAction(ACTION_FIGHT_CHARGE , target)); return if self.down_key_down : self.mobile.doer.do_action(FightAction(ACTION_FIGHT_SAGITTAL, target)); return if target: _P.clone(target) _P.convert_to(self.mobile) c = _P.x / abs(_P.z) if self.mobile.weapon: range = self.mobile.range + self.mobile.weapon.range else: range = self.mobile.range if (_P.z < -1.0 - range) and (-0.6 < c < 0.6): self.mobile.doer.do_action(FightAction(ACTION_FIGHT_CHARGE, target)) elif c < -0.6: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_LEFT , target)) elif c > 0.6: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_RIGHT, target)) elif not -1.0 < _P.y < 1.0: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_SAGITTAL, target)) elif c < 0.0: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_LEFT , target)) else: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_RIGHT, target)) else: self.mobile.doer.do_action(FightAction(ACTION_FIGHT_CHARGE, target)) def Wait(self, duration): yield Action(ACTION_WAIT) for i in range(duration - 1): yield None def Wander(self, duration): for i in range(4): p = soya.Point(self, (random.random() - 0.5) * 10.0, 2.0, (random.random() - 0.75) * 12.0) p.convert_to(self.level) if self.level.raypick_b(p, self.down, 5.0): yield GotoXZ(self, p, 2.0) break else: yield Wait(self, 100) def LookAt(self, target, limit = 0.3): action = -1 current_action = ACTION_WAIT while 1: tx, ty, tz = self.transform_point(target.x, target.y, target.z, target.parent) if tz < 0.0: txz = tx / tz if txz < -limit: action = ACTION_TURN_RIGHT elif txz > limit: action = ACTION_TURN_LEFT else: return else: if tx < 0.0: action = ACTION_TURN_LEFT else: action = ACTION_TURN_RIGHT if action == current_action: yield None else: yield Action(action) def Goto(self, target, limit = 1.0): action = -1 current_action = ACTION_WAIT while self.distance_to(target) > limit: tx, ty, tz = self.transform_point(target.x, target.y, target.z, target.parent) if tz < 0.0: txz = tx / tz if txz < -1.0: action = ACTION_TURN_RIGHT elif txz > 1.0: action = ACTION_TURN_LEFT elif self.on_ground and (ty > 3.0) and (tz > -10.0): yield Action(ACTION_JUMP) elif txz < -0.3: action = ACTION_ADVANCE_RIGHT elif txz > 0.3: action = ACTION_ADVANCE_LEFT else: action = ACTION_ADVANCE else: if tx < 0.0: action = ACTION_TURN_LEFT else: action = ACTION_TURN_RIGHT if action == current_action: yield None else: yield Action(action) def GotoXZ(self, target, limit = 1.0): action = -1 current_action = ACTION_WAIT while self.distance_to(target) > limit: tx, ty, tz = self.transform_point(target.x, self.y, target.z, target.parent) if tz < 0.0: txz = tx / tz if txz < -1.0: action = ACTION_TURN_RIGHT elif txz > 1.0: action = ACTION_TURN_LEFT elif txz < -0.3: action = ACTION_ADVANCE_RIGHT elif txz > 0.3: action = ACTION_ADVANCE_LEFT else: action = ACTION_ADVANCE else: if tx < 0.0: action = ACTION_TURN_LEFT else: action = ACTION_TURN_RIGHT if action == current_action: yield None else: yield Action(action) def Gotoward(self, target, duration, limit = 1.0): action = -1 current_action = ACTION_WAIT while (duration > 0) and (self.distance_to(target) > limit): duration -= 1 tx, ty, tz = self.transform_point(target.x, target.y, target.z, target.parent) if tz < 0.0: txz = tx / tz if txz < -1.0: action = ACTION_TURN_RIGHT elif txz > 1.0: action = ACTION_TURN_LEFT elif self.on_ground and (ty > 3.0) and (tz > -10.0): yield Action(ACTION_JUMP) elif txz < -0.3: action = ACTION_ADVANCE_RIGHT elif txz > 0.3: action = ACTION_ADVANCE_LEFT else: action = ACTION_ADVANCE else: if tx < 0.0: action = ACTION_TURN_LEFT else: action = ACTION_TURN_RIGHT if action == current_action: yield None else: yield Action(action) def Fly(self, target, duration = 50): action = -1 current_action = ACTION_WAIT for i in range(duration): tx, ty, tz = self.transform_point(target.x, target.y, target.z, target.parent) if tz > 0.0: txz = tx / tz if txz < -1.0: action = ACTION_TURN_RIGHT elif txz > 1.0: action = ACTION_TURN_LEFT elif txz < -0.3: action = ACTION_ADVANCE_RIGHT elif txz > 0.3: action = ACTION_ADVANCE_LEFT else: action = ACTION_ADVANCE else: if tx > 0.0: action = ACTION_ADVANCE_LEFT else: action = ACTION_TURN_RIGHT if action == current_action: yield None else: yield Action(action) def GrabItem(self, item): yield Goto(self, item) if not item.owner: # Else someone already grab it ! self.doer.do_action(ItemAction(ACTION_GRAB_ITEM, item))