################################################################################
# copyright 2008 Gabriel Pettier <gabriel.pettier@gmail.com> #
# #
# This file is part of UltimateSmashFriends #
# #
# UltimateSmashFriends 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. #
# #
# UltimateSmashFriends 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 UltimateSmashFriends. If not, see <http://www.gnu.org/licenses/>.#
################################################################################
'''
This module manage controls, mainly on the keyboard, and either trigger
player's animations, or pass events to the menu system.
'''
# standards import
import os
import pygame
from pygame import locals as pygame_locals
from pygame.locals import (
KEYDOWN,
KEYUP,
MOUSEMOTION,
MOUSEBUTTONUP,
MOUSEBUTTONDOWN,
)
# my imports
from usf import CONFIG
from usf import game
[docs]def key_shield(the_key, player, game_instance):
""" activate shield if asked by the player, and if possible, return
True, if the shield was activated
"""
if ("_SHIELD" in the_key and
player.entity_skin.current_animation in (
'static', 'static_upgraded',
'walk', 'walk_upgraded')):
player.shield['on'] = True
player.entity_skin.change_animation(
'static',
game_instance,
params={'entity': player, 'anim_name': 'static'})
player.set_walking_vector((0, player.walking_vector[1]))
return True
return False
[docs]def key_down_left(the_key, player, game_instance):
""" manage incidence on walk animation if the player push down his left key
"""
if "_LEFT" in the_key:
if player.on_ground:
player.entity_skin.change_animation(
'walk',
game_instance,
params={'entity': player, 'anim_name': 'walk'})
player.set_walking_vector([CONFIG.general.WALKSPEED,
player.walking_vector[1]])
player.set_reversed(True)
return True
return False
[docs]def key_down_right(the_key, player, game_instance):
""" manage incidence on walk animation if the player push down his right key
"""
if "_RIGHT" in the_key:
if player.on_ground:
player.entity_skin.change_animation(
'walk',
game_instance,
params={'entity': player, 'anim_name': 'walk'})
player.set_walking_vector([CONFIG.general.WALKSPEED,
player.walking_vector[1]])
player.set_reversed(False)
return True
return False
[docs]def key_up_left(player, game_instance):
""" manage incidence on walk animation if the player release his left key
"""
if player.reversed:
player.set_walking_vector([0, player.walking_vector[1]])
if (player.entity_skin.current_animation in ('walk', 'walk_upgraded')):
player.entity_skin.change_animation("static", game_instance,
params={'entity': player})
[docs]def key_up_right(player, game_instance):
""" manage incidence on walk animation if the player release his right key
"""
if not player.reversed:
player.set_walking_vector([0, player.walking_vector[1]])
if (player.entity_skin.current_animation in ('walk', 'walk_upgraded')):
player.entity_skin.change_animation("static", game_instance,
params={'entity': player})
[docs]class Sequence(object):
"""
Used to bind a character animation to a sequence of key of a player.
condition allow to restrict the avaiability of the animation to certain
current animation (the player can only double jump if he is already jumping
for example)
"""
def __init__(self, keys, action, condition=None):
self.keys = keys
self.action = action
self.condition = condition
[docs] def compare_local(self, seq, player, index):
"""
compare a sequence of keys events against our sequence starting from
index
"""
current = player.entity_skin.current_animation.replace('_upgraded', '')
# if conditions are matched, and the sequence tested is as least as
# long as our
if ((not self.condition or current in self.condition)
and len(seq) - index >= len(self.keys)):
# test keys are the same, up to length of our combination
i, j = 0, None
for i, j in enumerate(self.keys):
# not the good one? stop here
if seq[i + index][0] != j:
return False
# all keys are good but sequence was already validated? exit
if seq[i + index][2]:
return False
# first time this sequence is met, validate!
else:
seq[i + index][2] = True
return True
[docs] def compare(self, seq, player):
"""Compare a sequence of key events against our sequence
"""
for i in enumerate(seq):
if self.compare_local(seq, player, i[0]):
return True
return False
def __str__(self):
"""return a string representation of the sequence
"""
return [str(i) for i in self.keys]
[docs]def get_key_code(name):
""" return key code for the key associated with the keyboard config
name
"""
try:
return pygame_locals.__dict__[getattr(CONFIG.keyboard, name)]
except KeyError:
return int(getattr(CONFIG.keyboard, name))
[docs]def reverse_keymap(keycode):
"""
provide a name of key for a codekey, should not break on weird
international keyboards
"""
if 'world' in pygame.key.name(keycode):
return str(keycode)
else:
name = pygame.key.name(keycode)
if len(name) == 1:
return 'K_'+name
else:
if '[' in name:
return 'K_KP'+name[1]
else:
return 'K_'+name.upper().replace(
'LEFT ','L').replace('RIGHT ', 'R')
[docs]class Controls (object):
"""
Catch pygame keyboard events and decides of the interaction with the game,
game menu and entity instances. Key configuration are taken from
sequences.cfg. This class can update and save configuration.
"""
def __init__(self):
#loaders.load_keys()
self.player_sequences = [[], [], [], []]
self.keys = {}
self.sequences = []
self.load_keys()
self.load_sequences()
[docs] def load_keys(self):
""" load player keys from configuration
"""
self.keys = dict(
[[get_key_code(key), key]
for key in CONFIG.keyboard.options])
[docs] def load_sequences(self):
""" construct player combo sequences, using config (sequences.cfg) and
known player keys
"""
sequences_file = open(os.path.join(
CONFIG.system_path,
'sequences'+os.extsep+'cfg'), 'r')
lines = sequences_file.readlines()
sequences_file.close()
for i in lines:
if i == '\n' or i[0] == '#':
continue
if i[0] == '=':
condition = i.split('=')[1].split('\n')[0].split(',')
continue
tmp = i.replace(' ', '').split('\n')[0].split(':')
seq = tmp[0].split('+')
act = tmp[1]
self.sequences.append(Sequence(seq, act, condition))
[docs] def test_sequences(self, game_instance, numplayer):
"""
test all sequences against player state/sequence
if a sequence is recognized, the animation is applied
"""
player = game_instance.players[numplayer]
sequence = self.player_sequences[numplayer]
for i in self.sequences:
if i.compare(sequence, player):
player.entity_skin.change_animation(
i.action,
game_instance,
params={'entity': player, 'anim_name': i.action})
[docs] def handle_game_key_down(self, key, game_instance):
""" manage key_down events
"""
ret = 'game'
if key in self.keys:
the_key = self.keys[key]
if the_key == "QUIT":
ret = "menu"
elif the_key == "TOGGLE_FULLSCREEN":
pygame.display.toggle_fullscreen()
elif the_key == "VALIDATE":
pass
else:
numplayer = int(the_key.split('_')[0][-1]) - 1
keyname = the_key.split('_')[1]
if numplayer >= len(game_instance.players):
return
player = game_instance.players[numplayer]
if not player.ai:
self.player_sequences[numplayer].append(
[keyname, game_instance.gametime, False])
# the player can't do anything if the shield is on
if not player.shield['on']:
if not key_shield(the_key, player, game_instance):
if not key_down_left(the_key, player,
game_instance):
key_down_right(the_key, player, game_instance)
#test sequences
self.test_sequences(game_instance, numplayer)
return ret
[docs] def handle_game_key_up(self, key, game_instance):
""" manage key up events
"""
if key in self.keys:
the_key = self.keys[key]
if the_key == "VALIDATE":
return
numplayer = int(self.keys[key].split('_')[0][-1]) - 1
if numplayer >= len(game_instance.players):
return
player = game_instance.players[numplayer]
if not player.ai:
if "_SHIELD" in the_key:
player.shield['on'] = False
elif "_LEFT" in the_key:
key_up_left(player, game_instance)
elif "_RIGHT" in the_key:
key_up_right(player, game_instance)
[docs] def handle_game_key(self, state, key, game_instance):
""" Call handle_game_key_down of handle_game_key_up whether if the key
event is DOWN or UP
"""
if state is KEYDOWN:
return self.handle_game_key_down(key, game_instance)
elif state is KEYUP:
self.handle_game_key_up(key, game_instance)
return "game"
[docs] def poll(self, game_instance, state):
"""
This function manages key events aquiered from local keyboard or sent
by clients in the case of a networkk game.
"""
# if this is a network server game
if isinstance(game_instance, game.NetworkServerGame):
while True:
k = game_instance.server.fetch()
if not k:
break
else:
self.handle_game_key("game", k, game_instance)
else:
pygame.event.pump()
for event in pygame.event.get([KEYDOWN, KEYUP]):
state = self.handle_game_key(
event.type, event.key, game_instance)
# eliminate unwanted events that fill the pool and break the
# controls.
pygame.event.clear([MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN])
# clean sequences from outdated keys
for index, sequence in enumerate(self.player_sequences):
limit_time = game_instance.gametime - .5
# clean the entire sequence if the last key is outdated.
if sequence != [] and sequence[-1][1] < limit_time:
self.player_sequences[index] = []
return state