################################################################################
# copyright 2008-2011 Gabriel Pettier <gabriel.pettier@gmail.com> #
# #
# This file is part of ultimate-smash-friends #
# #
# ultimate-smash-friends 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. #
# #
# ultimate-smash-friends 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 #
# ultimate-smash-friends. If not, see <http://www.gnu.org/licenses/>. #
################################################################################
'''
This module allow to create timed and condition based events, to create
complexe effects and behaviours in the game.
'''
import random
import math
import logging
from usf import CONFIG
SIZE = (CONFIG.general.WIDTH,
CONFIG.general.HEIGHT)
[docs]class TimedEvent (object):
"""
An event allow to define a function to be executed later and/or during a
certain period of time.
"""
def __init__(self, manager, period, params=dict()):
"""
Action must be a callable, it will be called every frames in the
period.
Condition must be a callable, when it return false the event will die.
Period must be a period of time defined as a tupple of value, if the
first value is None, then it will replaced by current time and if the
second is None, the event will happen until dying.
"""
self.params = params
self.period = period[0], period[1] or None
self.done = False
self.event_manager = manager
[docs] def update(self, deltatime, gametime):
"""
This method make the event up to date, by executing the various
functions of the event.
"""
if self.period[1] is not None and gametime > self.period[1]:
self.done = True
elif gametime > self.period[0]:
if not self.condition():
self.done = True
else:
self.execute(deltatime)
[docs] def backup(self):
"""
This method return the state of the event in a dict, to restore latter
"""
backup = {}
for k in self.__dict__:
backup[k] = self.__dict__[k]
return backup
[docs] def restore(self, backup):
"""
This method restore the event in a previous state from a backup()
"""
self.__dict__.update(backup)
[docs] def execute(self, deltatime):
"""
This method must be overriden, it will be called every frame by the
event.
"""
raise NotImplementedError
[docs] def condition(self):
"""
This method must be overriden, it will be called every frame by the
event, to verify if the event must continue.
"""
raise NotImplementedError
[docs] def delete(self):
"""
you can override this one if you want special behaviour
"""
pass
[docs] def del_(self):
"""
please don't override this method if you want a special behaviour,
override "delete" method instead.
"""
#logging.info(str(self.__class__) + ' event deleted')
self.delete()
[docs]class HealEvent(TimedEvent):
"""
Event used to timely drop a player's percentage to zero.
"""
[docs] def execute(self, deltatime):
# at this rate it should take 5 seconds to go from 100% to 0%
self.params['player'].add_percents(-deltatime*2)
[docs] def condition(self):
return self.params['player'].percents > 0
[docs]class ShieldUpdateEvent(TimedEvent):
"""
This event, launched for one character, update the energy of it's shield at
every loop, depending on if it's on or off.
"""
[docs] def execute(self, deltatime):
if self.params['player'].shield['on']:
self.params['player'].shield['power'] -= deltatime/10
elif self.params['player'].shield['power'] < 1:
self.params['player'].shield['power'] += deltatime/10
if self.params['player'].shield['power'] <= 0:
self.params['player'].shield['on'] = False
[docs] def condition(self):
return True
[docs]class DelItemEvent(TimedEvent):
"""
Event used to timely delete an item, because it was used, or because it got
a timeout.
"""
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
return True
[docs] def delete(self):
if self.params is not None and 'entity' in self.params:
target = self.params['entity']
else:
try:
target = self.target
except AttributeError:
logging.error(
'DelItemEvent no entity in params, and no self.target!!!')
target.set_lives(0)
[docs]class BombExplode(TimedEvent):
"""
This Event timely trigger the bomb explostion.
"""
[docs] def execute(self, deltatime):
self.params['entity'].set_gravity(False)
self.params['entity'].entity_skin.change_animation(
'explode',
self.params['world'])
self.done = True
[docs] def condition(self):
return True
[docs]class DropRandomItem(TimedEvent):
"""
Add a random item in game.
"""
[docs] def execute(self, deltatime):
try:
self.params['world'].add_item(
random.sample(['heal', 'bomb'], 1)[0],
place=(random.random()*SIZE[0], 50))
self.done = True
except:
raise
[docs] def condition(self):
return True
[docs]class ItemShower(TimedEvent):
"""
Add periodicaly an item into game.
"""
[docs] def execute(self, deltatime):
if 'freq' in self.params:
freq = self.params['freq']
else:
freq = 2
try:
self.elapsed += deltatime
except AttributeError:
self.elapsed = deltatime
if self.elapsed >= freq:
try:
self.params['world'].add_item(
random.sample(
[
'heal',
'invincibility',
'trunk',
'bomb',
],
1)[0],
place=(random.random()*SIZE[0], 50))
self.elapsed -= freq
except:
raise
[docs] def condition(self):
return True
[docs]class ThrowBomb(TimedEvent):
"""
Launch a bomb in front of the player.
"""
[docs] def execute(self, deltatime):
self.done = True
self.params['world'].add_item(
'bomb',
place=(self.params['entity'].rect[0:2]),
reverse=self.params['entity'].reversed,
vector=[1000, -1000])
[docs] def condition(self):
return True
[docs]class ThrowFireBall(TimedEvent):
"""
Launch a fireball in front of the player.
"""
[docs] def execute(self, deltatime):
self.done = True
entity = self.params['entity']
place = list(entity.place[0:2])
place[0] += -100 if entity.reversed else entity.rect[2]
#place[1] -= entity.rect[3]/3
self.params['world'].add_item(
'fireball',
place=place,
reverse=entity.reversed,
upgraded=entity.upgraded,
bullet=True)
[docs] def condition(self):
return True
[docs]class Gost(TimedEvent):
"""
Chasing 'AI' for a little chasing gost.
"""
[docs] def execute(self, deltatime):
self.target_player = None
for pla in (
player
for player in self.params['world'].players
if player is not self.params['entity']):
if (self.target_player is None or
self.params['entity'].dist(pla) <
self.target_player.dist(self.params['entity'])):
self.target_player = pla
self.params['entity'].set_vector([
(self.target_player.place[0] - self.params['entity'].place[0]) * 3,
(self.target_player.place[1] - self.params['entity'].place[1]) * 3])
[docs] def condition(self):
return True
[docs]class ThrowMiniGost(TimedEvent):
"""
Insert a little chasing gost in game.
"""
[docs] def execute(self, deltatime):
self.done = True
self.params['world'].add_item(
'mini_gost',
place=(self.params['entity'].rect[0:2]),
vector=[1000, 50])
[docs] def condition(self):
return True
[docs]class LaunchBullet(TimedEvent):
"""
Launch a fireball in front of the player.
"""
[docs] def execute(self, deltatime):
self.done = True
entity = self.params['entity']
place = entity.place[0:2]
place[0] += entity.reversed and -entity.rect[2] or entity.rect[2]
place[1] += entity.rect[3]/2
self.params['world'].add_item(
'bullet',
place=place,
reverse=entity.reversed,
upgraded=entity.upgraded,
bullet=True)
[docs] def condition(self):
return True
[docs]class InvinciblePlayer(TimedEvent):
"""
The target player is invincible and half invisible during this event.
"""
def __init__(self, manager, period, params=dict()):
# if we find another invincibility event on the same player we
# just extend it's time to our limit and we are done.
super(InvinciblePlayer, self).__init__(manager, period, params)
invincibilities = list(
self.event_manager.get_events(cls=InvinciblePlayer,
params={'player': self.params['player']}))
if len(invincibilities) != 0:
invincibilities[0].period = self.period
self.done = True
else:
self.params['player'].set_invincible(True)
[docs] def execute(self, deltatime):
self.params['player'].set_invincible(True)
self.params['player'].set_lighten(not self.params['player'].lighten)
[docs] def condition(self):
return True
[docs] def delete(self):
self.params['player'].set_lighten(False)
self.params['player'].set_invincible(False)
[docs]class VectorEvent(TimedEvent):
"""
This event is used to assign time-based vector (accelerations) to a player.
Used by animations.
"""
def __init__(self, manager, period, params=dict()):
#logging.debug("vector Event created "+ str(self.period))
super(VectorEvent, self).__init__(manager, period, params)
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
"""
Return false so the addition is only performed once.
"""
return (self.params['entity'].entity_skin.current_animation in
(self.params['anim_name'],
self.params['anim_name']+'_upgraded'))
[docs] def delete(self):
"""
Add the vector to the player vector.
"""
#logging.debug("vector Event applied")
if self.condition():
self.params['entity'].set_vector([
self.params['vector'][0],
-self.params['vector'][1]])
[docs]class UpgradePlayer(TimedEvent):
"""
This event will upgrade the player to his upper state, that state ends when
the player dies.
"""
def __init__(self, manager, period, params=dict()):
super(UpgradePlayer, self).__init__(manager, period, params)
self.params['player'].set_upgraded(True)
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
return False
[docs]class DropPlayer(TimedEvent):
"""
This event is called to drop a player in game.
"""
def __init__(self, manager, period, params=dict()):
super(DropPlayer, self).__init__(manager, period, params)
#logging.debug("inserting player in game")
self.params['entity'].set_vector([0, 0])
self.params['entity'].set_walking_vector([0, 0])
self.params['entity'].set_percents(0)
self.params['entity'].set_upgraded(False)
entry = random.choice(self.params['world'].level.entrypoints)
self.params['entity'].set_place(entry)
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
return True
[docs] def delete(self):
self.params['entity'].set_visible(True)
self.params['entity'].set_present(True)
self.params['entity'].entity_skin.change_animation(
'static',
self.params['world'])
self.event_manager.add_event(
'InvinciblePlayer',
(None, self.params['gametime'] + 3),
params = {
'player':
self.params['entity'],
'world':
self.params['world']})
[docs]class PlayerOut(TimedEvent):
"""
This event is called when a player is hitting the level border
"""
def __init__(self, manager, period, params=dict()):
super(PlayerOut, self).__init__(manager, period, params)
self.params['entity'].set_lives(self.params['entity'].lives - 1)
self.params['entity'].set_present(False)
if self.params['entity'].lives > 0:
self.event_manager.add_event(
'DropPlayer',
(None, self.params['gametime'] + 1),
params = self.params)
self.coords = self.params['entity'].place
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
return True
[docs]class PlayerStaticOnGround(TimedEvent):
"""
This event will set the player on static animation when he touch the ground
"""
[docs] def execute(self, deltatime):
pass
[docs] def condition(self):
"""
Return false if the player is on ground so the event effect occure.
"""
return not self.params['entity'].on_ground
[docs] def delete(self):
# don't apply if the animation was changed meanwhile
if (self.params['entity'].entity_skin.current_animation in
(self.params['anim_name'],
self.params['anim_name']+'_upgraded')):
if tuple(self.params['entity'].walking_vector) != (0, 0):
anim = 'walk'
else:
anim = 'static'
self.params['entity'].entity_skin.change_animation(
anim+self.params['entity'].upgraded*'_upgraded',
self.params['world'],
params={'entity': self.params['entity']})
[docs]class Bounce(TimedEvent):
"""
This event will make the entity bounce when touching the ground
"""
[docs] def execute(self, deltatime):
if self.params['entity'].on_ground:
self.params['entity'].vector[1] *= -1
[docs] def condition(self):
return True
[docs]class BlobSpecial(TimedEvent):
""" A boomerang like attack with the eye of blob
"""
def __init__(self, manager, period, params=dict()):
super(BlobSpecial, self).__init__(manager, period, params)
self.entity = self.params['entity']
self.entity_life = self.entity.percents
try:
self.target = min([
(self.entity.dist(e), e) for e in self.params['world'].players
if e is not self.entity and
((e.place[0] < self.entity.place[0]) ==\
self.entity.reversed)])[1]
except ValueError:
# no suitable target, abort
self.done = True
self.angle = 0
[docs] def execute(self, deltatime):
try:
self.eye
except AttributeError:
self.eye = self.params['world'].add_item('blob-eye',
upgraded=self.entity.upgraded, physics=False,
reverse=self.entity.reversed)
self.angle += deltatime * 2 * 3.14159 # enought precision here
center = (
(self.entity.place[0] + self.target.place[0]) / 2,
(self.entity.place[1] + self.target.place[1]) / 2)
dx_ = self.target.place[0] - self.entity.place[0]
dy_ = self.target.place[1] - self.entity.place[1]
xx_ = - math.cos(self.angle) * dx_ / 2 + center[0]
yy_ = math.sin(self.angle) * dy_ / 2 + center[1] + dy_ * (
self.angle / (2 * 3.14159) * (1 if self.angle < 3.14159 else
-1))
self.eye.set_place((xx_, yy_))
[docs] def condition(self):
if (self.angle <= (2 * 3.14159)
and self.entity_life == self.entity.percents):
#and
# "special" in self.entity.entity_skin.current_animation
return True
else:
return False
[docs] def delete(self):
try:
self.eye.set_lives(0)
except AttributeError:
# happens if there was no suitable target and event was aborted
pass
[docs]class XeonCharge(TimedEvent):
""" a charge attack for Xeon
"""
def __init__(self, manager, period, params=dict()):
super(XeonCharge, self).__init__(manager, period, params)
self.size = 0
self.entity = self.params['entity']
self.world = self.params['world']
self.entity_life = self.entity.percents
[docs] def execute(self, deltatime):
self.size += deltatime
[docs] def condition(self):
if (self.size <= 1700 and
self.entity_life == self.entity.percents and
"special2" in self.entity.entity_skin.current_animation):
return True
else:
return False
[docs] def delete(self):
if self.entity_life == self.entity.percents:
size = min(int(self.size / (1.7 / 7)), 6)
self.world.add_item(
"xeon-charge",
upgraded=self.entity.upgraded,
animation=str(size),
reverse=self.entity.reversed,
place=[self.entity.place[0] + (-100 if self.entity.reversed
else 100), self.entity.place[1]],
vector=[300, 0],
bullet=True)
# This list is used to cast an event by name. This is usefull since events are
# configured in players/items xml files.
EVENT_NAMES = {
'BlobSpecial': BlobSpecial,
'BombExplode': BombExplode,
'Bounce': Bounce,
'DelItemEvent': DelItemEvent,
'DropPlayer': DropPlayer,
'DropRandomItem': DropRandomItem,
'Gost': Gost,
'HealEvent': HealEvent,
'InvinciblePlayer': InvinciblePlayer,
'ItemShower': ItemShower,
'LaunchBullet': LaunchBullet,
'PlayerOut': PlayerOut,
'PlayerStaticOnGround': PlayerStaticOnGround,
'ShieldUpdateEvent': ShieldUpdateEvent,
'ThrowBomb': ThrowBomb,
'ThrowFireBall': ThrowFireBall,
'ThrowMiniGost': ThrowMiniGost,
'UpgradePlayer': UpgradePlayer,
'VectorEvent': VectorEvent,
'XeonCharge': XeonCharge,
}