"""Represents achievements
Achievements are badges that are assigned to the
player as they play the game. An achievement is
basically a condition that is met. When you meet
the condition you get the badge.
"""
import time
import pygame
import datetime
import sys
import os
import serge.serialize
import serge.common
import serge.events
import serge.actor
import serge.engine
import serge.blocks.actors
import serge.blocks.layout
import serge.blocks.utils
import serge.blocks.behaviours
import serge.blocks.visualblocks
# Events
E_ACHIEVEMENT_MET = 'achievement-met'
[docs]class DuplicateAchievement(Exception):
"""An achievement with this name already exists"""
[docs]class BadReport(Exception):
"""An error occurred while evaluating the report"""
[docs]class BadTestType(Exception):
"""The test type was not found"""
[docs]class BadCondition(Exception):
"""The condition was not valid"""
[docs]class Achievement(serge.serialize.Serializable):
"""Represents an achievement"""
index = 0
my_properties = (
serge.serialize.S('name', '', 'the name of the achievement'),
serge.serialize.S('description', '', 'the description of the achievement'),
serge.serialize.S('badge', '', 'the badge sprite name of the achievement'),
serge.serialize.B('secret', False, 'whether the achievement is secret or not'),
serge.serialize.S('test_type', '', 'the test type for the achievement'),
serge.serialize.O('condition', None, 'the condition test for the achievement'),
serge.serialize.S('condition_string', '', 'the text of the condition test for the achievement'),
serge.serialize.B('met', False, 'whether the achievement has been met'),
serge.serialize.F('time', 0, 'when the achievement was met'),
)
def __init__(self, name, description, badge, secret, test_type, condition=None, condition_string=None):
"""Initialise the Achievement"""
self.name = name
self.description = description
self.badge = badge
self.secret = secret
self.test_type = test_type
self.met = False
self.time = 0
self.index = self.__class__.index
self.__class__.index += 1
#
self.condition = condition
self.condition_string = condition_string
self.init()
[docs] def init(self):
"""Initialise the achievement from pickling"""
super(Achievement, self).init()
if self.condition is None:
if not self.condition_string:
raise BadCondition('Must specify condition or condition_string for achievement "%s"' % self.name)
else:
try:
self._condition = eval('lambda %s' % self.condition_string)
except Exception, err:
raise BadCondition('Cannot create lambda from "%s" for achievement "%s"' % (
self.condition_string, self.name))
else:
if self.condition_string:
raise BadCondition(
'Cannot specify both condition and condition_string for achievement "%s"' % self.name)
self._condition = self.condition
[docs] def makeReport(self, **kw):
"""Make a report on this achievement"""
# Short circuit if we are met
if self.met:
return False
try:
self.met = self._condition(**kw)
except Exception, err:
raise BadReport('Error evaluating achievement "%s" with %s: %s' % (self.name, kw, err))
if self.met:
self.time = time.time()
return True
else:
return False
[docs] def isMet(self):
"""Return True if the achievement was met"""
return self.met
[docs] def resetStatus(self):
"""Reset the status of the achievement"""
self.met = False
self.time = 0
[docs]class AchievementManager(serge.serialize.Serializable, serge.common.Loggable, serge.common.EventAware):
"""Manages all the achievements in the game"""
my_properties = (
serge.serialize.O('achievements', {}, 'the list of achievements'),
)
def __init__(self, ):
"""Initialise the AchievementManager"""
self.achievements = {}
self.init()
[docs] def init(self):
"""Initialise"""
super(AchievementManager, self).init()
self.addLogger()
self.initEvents()
for achievement in self.getAchievements():
achievement.init()
self.filename = None
[docs] def registerAchievement(self, achievement):
"""Register an achievement"""
existing = self.achievements.setdefault(achievement.test_type, {})
if achievement.name in existing:
raise DuplicateAchievement('An achievement called "%s" with test "%s" already exists' % (
achievement.name, achievement.test_type))
else:
self.log.info('Registering new achievement "%s" with test "%s"' % (achievement.name, achievement.test_type))
existing[achievement.name] = achievement
[docs] def safeRegisterAchievement(self, achievement):
"""Register an achievement and do not worry if it is already registered"""
try:
self.registerAchievement(achievement)
except DuplicateAchievement:
pass
[docs] def makeReport(self, test_type, **kw):
"""Make a report on achievements"""
try:
achievements = self.achievements[test_type].values()
except KeyError:
raise BadTestType('The test type "%s" was not found' % test_type)
#
for achievement in achievements:
if achievement.makeReport(**kw):
self.log.info('Achievement "%s" has been met' % achievement.name)
self.processEvent((E_ACHIEVEMENT_MET, achievement))
if self.filename:
self.saveAchievements()
[docs] def getAchievements(self):
"""Return the list of achievements"""
ret = []
for items in self.achievements.values():
ret.extend(items.values())
return sorted(ret, lambda a, b: cmp(a.index, b.index))
[docs] def initialiseFromFile(self, filename):
"""Initialise from the file"""
var = 'HOME' if not sys.platform.startswith('win') else 'HOMEPATH'
the_filename = os.path.join(os.getenv(var), filename)
if os.path.isfile(the_filename):
self.log.info('Loading achievements from %s' % the_filename)
new_manager = serge.serialize.Serializable.fromFile(the_filename)
self.achievements = new_manager.achievements
else:
self.log.info('New achievements file at %s' % the_filename)
self.filename = the_filename
[docs] def resetAchievements(self):
"""Reset all the achievements so that they have not been met"""
self.log.debug('Resetting status of achievements')
for achievements in self.achievements.values():
for achievement in achievements.values():
achievement.resetStatus()
if self.filename:
self.saveAchievements()
[docs] def saveAchievements(self):
"""Save achievements to a file"""
self.toFile(self.filename)
[docs]class AchievementBanner(serge.actor.MountableActor):
"""A banner to show an achievement"""
def __init__(self, tag, name, background_layer, foreground_layer, behaviours, theme):
"""Initialise the AchievementBanner"""
super(AchievementBanner, self).__init__(tag, name)
G = self.G = theme.getTheme('achievements').getProperty
self.background_layer = background_layer
self.foreground_layer = foreground_layer
self.hider = behaviours.assignBehaviour(
self, serge.blocks.behaviours.TimedCallback(G('banner-duration') * 1000, self.hideMe), 'hiding')
self.hider.pause()
[docs] def addedToWorld(self, world):
"""Added the banner to the world"""
super(AchievementBanner, self).addedToWorld(world)
G = self.G
#
# Create the widgets
bg = serge.actor.Actor('banner', 'background')
bg.visual = serge.blocks.visualblocks.Rectangle(G('banner-size'),
G('banner-backcolour'))
bg.setLayerName(self.background_layer)
self.mountActor(bg, (0, 0))
#
self.name = name = serge.blocks.actors.StringText('banner', 'banner-name', 'Name',
colour=G('banner-font-colour'),
font_size=G('banner-name-size'),
font_name=G('banner-font-name'), justify='left')
name.setLayerName(self.foreground_layer)
self.mountActor(name, G('banner-name-position'))
#
self.description = description = serge.blocks.actors.StringText(
'banner', 'banner-description',
'Description of the achievement as it\nis written down.',
colour=G('banner-font-colour'),
font_size=G('banner-description-size'),
font_name=G('banner-font-name'), justify='left'
)
description.setLayerName(self.foreground_layer)
self.mountActor(description, G('banner-description-position'))
#
graphic = serge.actor.Actor('banner', 'banner-graphic')
graphic.setSpriteName('achievement')
graphic.setLayerName(self.foreground_layer)
graphic.visual.setCell(1)
self.mountActor(graphic, G('banner-graphic-position'))
#
self.visible = False
[docs] def meetAchievement(self, achievement, arg):
"""An achievement is met"""
self.name.value = achievement.name
self.description.value = achievement.description
if not self.hider.isRunning():
self.hider.restart()
self.log.debug('Showing achievement')
self.visible = True
[docs] def hideMe(self, world, actor, interval):
"""Hide this object"""
self.hider.pause()
self.log.debug('Hiding achievement')
self.visible = False
[docs]class AchievementStatus(serge.actor.MountableActor):
"""A banner to show an achievement"""
def __init__(self, tag, name, background_layer, foreground_layer, achievement, G):
"""Initialise the AchievementStatus"""
super(AchievementStatus, self).__init__(tag, name)
self.G = G
self.background_layer = background_layer
self.foreground_layer = foreground_layer
self.achievement = achievement
[docs] def addedToWorld(self, world):
"""Added the banner to the world"""
super(AchievementStatus, self).addedToWorld(world)
G = self.G
#
# Create the widgets
bg = serge.actor.Actor('banner', 'background')
bg.visual = serge.blocks.visualblocks.Rectangle(G('banner-size'),
G('banner-backcolour'))
bg.setLayerName(self.background_layer)
self.mountActor(bg, (0, 0))
#
self.name = name = serge.blocks.actors.StringText(
'banner', 'banner-name', 'Name',
colour=G('banner-font-colour'),
font_size=G('banner-name-size'),
font_name=G('banner-font-name'), justify='left')
name.setLayerName(self.foreground_layer)
self.mountActor(name, G('banner-name-position'))
#
self.description = description = serge.blocks.actors.StringText(
'banner', 'banner-description',
'Description of the achievement as it\nis written down.',
colour=G('banner-font-colour'),
font_size=G('banner-description-size'),
font_name=G('banner-font-name'), justify='left')
description.setLayerName(self.foreground_layer)
self.mountActor(description, G('banner-description-position'))
#
self.graphic = graphic = serge.actor.Actor('banner', 'banner-graphic')
graphic.setSpriteName('achievement')
graphic.setLayerName(self.foreground_layer)
graphic.visual.setCell(1)
self.mountActor(graphic, G('banner-graphic-position'))
#
self.time = time = serge.blocks.actors.StringText('banner', 'banner-time',
'Time achieved', colour=G('time-colour'),
font_size=G('time-size'), font_name=G('banner-font-name'),
justify='left')
time.setLayerName(self.foreground_layer)
self.mountActor(time, G('time-position'))
#
self.updateAchievement()
[docs] def updateAchievement(self):
"""Update the achievement view"""
self.name.value = self.achievement.name
self.description.value = self.achievement.description
self.graphic.visual.setCell(1 if self.achievement.isMet() else 0)
if self.achievement.isMet():
self.time.value = 'Achieved at %s' % str(datetime.datetime.fromtimestamp(int(self.achievement.time)))
else:
self.time.value = ''
[docs]class AchievementsGrid(serge.blocks.actors.ScreenActor):
"""A grid to show achievements"""
def __init__(self, G):
"""Initialise the AchievementsGrid"""
super(AchievementsGrid, self).__init__('achievements', 'grid')
self.G = G
self.manager = getManager()
[docs] def addedToWorld(self, world):
"""Added the grid to the world"""
super(AchievementsGrid, self).addedToWorld(world)
G = self.G
#
# Logo
logo = serge.blocks.utils.addSpriteActorToWorld(
world, 'logo', 'logo', 'logo', 'ui', G('logo-position'))
#
# Bg
bg = serge.blocks.utils.addSpriteActorToWorld(
world, 'bg', 'bg', G('screen-background-sprite'), 'background', G('screen-background-position'))
#
# The grid
self.grid = grid = serge.blocks.utils.addActorToWorld(
world,
serge.blocks.layout.Grid(
'grid', 'grid', size=G('grid-size'),width=G('grid-width'),
height=G('grid-height')), center_position=G('grid-position'), layer_name='ui')
#
# Place things in grid
for achievement in self.manager.getAchievements():
grid.autoAddActor(AchievementStatus('status', 'status', 'background', 'ui', achievement, self.G))
#
world.linkEvent(serge.events.E_ACTIVATE_WORLD, self.updateAchievements)
#
self.back = serge.blocks.utils.addActorToWorld(
world,
serge.blocks.actors.StringText(
'back', 'back',
'Back', colour=G('back-colour'),
font_size=G('back-font-size'),
font_name=G('back-font-name')
),
center_position=G('back-position'), layer_name='ui')
self.back.linkEvent(serge.events.E_LEFT_CLICK, serge.blocks.utils.backToPreviousWorld(G('back-sound')))
[docs] def updateAchievements(self, obj, arg):
"""Update all achievements"""
for status in self.grid.getChildren():
status.updateAchievement()
[docs]def addAchievementsWorld(options, theme):
"""Add a world for the achievements"""
G = theme.getProperty
#
# The behaviour manager
engine = serge.engine.CurrentEngine()
serge.blocks.utils.createWorldsForEngine(engine, ['achievements-screen'])
world = engine.getWorld('achievements-screen')
manager = serge.blocks.behaviours.BehaviourManager('behaviours', 'behaviours')
world.addActor(manager)
#
# The screen actor
s = AchievementsGrid(theme.getTheme('achievements').getProperty)
s.options = options
world.addActor(s)
#
# Snap shots
if options.screenshot:
manager.assignBehaviour(None,
serge.blocks.behaviours.SnapshotOnKey(key=pygame.K_s, size=G('screenshot-size')
, overwrite=False, location='screenshots'), 'screenshot')
[docs]def addAchievementsBannerToWorld(world, front_layer, back_layer, theme, manager):
"""Add a banner for achievements to the world"""
banner = AchievementBanner('banner', 'banner', back_layer, front_layer, manager, theme)
world.addActor(banner)
banner.moveTo(*theme.getProperty('banner-position', 'achievements'))
getManager().linkEvent(E_ACHIEVEMENT_MET, banner.meetAchievement)
# A global achievements manager
_Manager = AchievementManager()
[docs]def getManager():
return _Manager
[docs]def initManager(name):
"""Initialise and return the manager"""
manager = getManager()
manager.initialiseFromFile('.%s.achievements' % name)
return manager