parent
d0a3798dd2
commit
630be43744
|
@ -0,0 +1,492 @@
|
||||||
|
################################################################################
|
||||||
|
##
|
||||||
|
## Achievements for Ren'Py by Feniks (feniksdev.itch.io / feniksdev.com)
|
||||||
|
##
|
||||||
|
################################################################################
|
||||||
|
## This file contains code for an achievement system in Ren'Py. It is designed
|
||||||
|
## as a wrapper to the built-in achievement system, so it hooks into the
|
||||||
|
## Steam backend as well if you set up your achievement IDs the same as in
|
||||||
|
## the Steam backend.
|
||||||
|
##
|
||||||
|
## If you use this code in your projects, credit me as Feniks @ feniksdev.com
|
||||||
|
##
|
||||||
|
## If you'd like to see how to use this tool, check the other file,
|
||||||
|
## achievements.rpy!
|
||||||
|
##
|
||||||
|
## Leave a comment on the tool page on itch.io or an issue on the GitHub
|
||||||
|
## if you run into any issues.
|
||||||
|
################################################################################
|
||||||
|
init -50 python:
|
||||||
|
import datetime, time
|
||||||
|
from re import sub as re_sub
|
||||||
|
|
||||||
|
TAG_ALPHABET = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
def get_random_screen_tag(k=4):
|
||||||
|
"""Generate a random k-letter word out of alphabet letters."""
|
||||||
|
|
||||||
|
# Shuffle the list and pop k items from the front
|
||||||
|
alphabet = list(store.TAG_ALPHABET)
|
||||||
|
renpy.random.shuffle(alphabet)
|
||||||
|
## Add the time onto the end so there are no duplicates
|
||||||
|
return ''.join(alphabet[:k] + [str(time.time())])
|
||||||
|
|
||||||
|
|
||||||
|
class Achievement():
|
||||||
|
"""
|
||||||
|
A class with information on in-game achievements which can be extended
|
||||||
|
to use with other systems (e.g. Steam achievements).
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
-----------
|
||||||
|
name : string
|
||||||
|
The human-readable name of this achievement. May have spaces,
|
||||||
|
apostrophes, dashes, etc.
|
||||||
|
id : string
|
||||||
|
The code-friendly name of this achievement (which can be used for
|
||||||
|
things like the Steam backend). Should only include letters,
|
||||||
|
numbers, and underscores. If not provided, name will be sanitized
|
||||||
|
for this purpose.
|
||||||
|
description : string
|
||||||
|
A longer description for this achievement. Optional.
|
||||||
|
unlocked_image : Displayable
|
||||||
|
A displayable to use when this achievement is unlocked.
|
||||||
|
locked_image : Displayable
|
||||||
|
A displayable to use when this achievement is locked. If not
|
||||||
|
provided, requires an image named "locked_achievement" to be
|
||||||
|
declared somewhere.
|
||||||
|
stat_max : int
|
||||||
|
If provided, an integer corresponding to the maximum progress of
|
||||||
|
an achievement, if the achievement can be partially completed
|
||||||
|
(e.g. your game has 24 chapters and you want this to tick up
|
||||||
|
after every chapter, thus, stat_max is 24). The achievement is
|
||||||
|
unlocked when it reaches this value.
|
||||||
|
stat_progress : int
|
||||||
|
The current progress for the stat.
|
||||||
|
stat_modulo : int
|
||||||
|
The formula (stat_progress % stat_modulo) is applied whenever
|
||||||
|
achievement progress is updated. If the result is 0, the
|
||||||
|
progress is shown to the user. By default this is 0 so all updates
|
||||||
|
to stat_progress are shown. Useful if, for the supposed 24-chapter
|
||||||
|
game progress stat, you only wanted to show updates every time the
|
||||||
|
player got through a quarter of the chapters. In this case,
|
||||||
|
stat_modulo would be 6 (24//4).
|
||||||
|
hidden : bool
|
||||||
|
True if this achievement's description and name should be hidden
|
||||||
|
from the player.
|
||||||
|
hide_description : bool
|
||||||
|
True if this achievement's description should be hidden from the
|
||||||
|
player. Can be set separately from hidden, e.g. with hidden=True
|
||||||
|
and hide_description=False, the player will see the name but not
|
||||||
|
the description.
|
||||||
|
timestamp : Datetime
|
||||||
|
The time this achievement was unlocked at.
|
||||||
|
"""
|
||||||
|
## A list of all the achievements that exist in this game,
|
||||||
|
## to loop over in the achievements screen.
|
||||||
|
all_achievements = [ ]
|
||||||
|
achievement_dict = dict()
|
||||||
|
def __init__(self, name, id=None, description=None, unlocked_image=None,
|
||||||
|
locked_image=None, stat_max=None, stat_modulo=None, hidden=False,
|
||||||
|
stat_update_percent=1, hide_description=None):
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
# Try to sanitize the name for an id, if possible
|
||||||
|
self.id = id or re_sub(r'\W+', '', name)
|
||||||
|
|
||||||
|
self._description = description or ""
|
||||||
|
self.unlocked_image = unlocked_image or None
|
||||||
|
self.locked_image = locked_image or "locked_achievement"
|
||||||
|
|
||||||
|
self.stat_max = stat_max
|
||||||
|
self.stat_modulo = stat_modulo
|
||||||
|
if stat_update_percent != 1 and stat_modulo != 0:
|
||||||
|
raise Exception("Achievement {} has both stat_update_percent and stat_modulo set. Please only set one.".format(self.name))
|
||||||
|
## Figure out the modulo based on the percent
|
||||||
|
if stat_update_percent > 1:
|
||||||
|
## Basically, if stat_max % stat_modulo == 0, then it updates.
|
||||||
|
## So if it updates every 10%, then stat_max / stat_modulo = 10
|
||||||
|
self.stat_modulo = int(stat_max * (stat_update_percent / 100.0))
|
||||||
|
|
||||||
|
self.hidden = hidden
|
||||||
|
if hide_description is None:
|
||||||
|
self.hide_description = hidden
|
||||||
|
else:
|
||||||
|
self.hide_description = hide_description
|
||||||
|
|
||||||
|
# Add to list of all achievements
|
||||||
|
self.all_achievements.append(self)
|
||||||
|
# Add to the dictionary for a quick lookup
|
||||||
|
self.achievement_dict[self.id] = self
|
||||||
|
|
||||||
|
# Register with backends
|
||||||
|
achievement.register(self.id, stat_max=stat_max,
|
||||||
|
stat_modulo=stat_modulo or None)
|
||||||
|
|
||||||
|
def get_timestamp(self, format="%b %d, %Y @ %I:%M %p"):
|
||||||
|
"""
|
||||||
|
Return the timestamp when this achievement was granted,
|
||||||
|
using the provided string format.
|
||||||
|
"""
|
||||||
|
if self.has():
|
||||||
|
return datetime.datetime.fromtimestamp(
|
||||||
|
self._timestamp).strftime(format)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _timestamp(self):
|
||||||
|
if store.persistent.achievement_timestamp is not None:
|
||||||
|
return store.persistent.achievement_timestamp.get(self.id, None)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timestamp(self):
|
||||||
|
"""Return the timestamp when this achievement was granted."""
|
||||||
|
if self.has():
|
||||||
|
try:
|
||||||
|
ts = datetime.datetime.fromtimestamp(self._timestamp)
|
||||||
|
except TypeError:
|
||||||
|
if config.developer:
|
||||||
|
print("WARNING: Could not find timestamp for achievement with ID {}".format(self.id))
|
||||||
|
return ""
|
||||||
|
return __("Unlocked ") + ts.strftime(__(
|
||||||
|
"%b %d, %Y @ %I:%M %p{#achievement_timestamp}"))
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@_timestamp.setter
|
||||||
|
def _timestamp(self, value):
|
||||||
|
"""Set the timestamp for this achievement."""
|
||||||
|
if store.persistent.achievement_timestamp is not None:
|
||||||
|
store.persistent.achievement_timestamp[self.id] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def idle_img(self):
|
||||||
|
"""Return the idle image based on its locked status."""
|
||||||
|
if self.has():
|
||||||
|
return self.unlocked_image
|
||||||
|
else:
|
||||||
|
return self.locked_image
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
Returns the name of the achievement based on whether it's
|
||||||
|
hidden or not.
|
||||||
|
"""
|
||||||
|
if self.hidden and not self.has():
|
||||||
|
return _("???{#hidden_achievement_name}")
|
||||||
|
else:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
"""
|
||||||
|
Returns the description of the achievement based on whether it's
|
||||||
|
hidden or not.
|
||||||
|
"""
|
||||||
|
if self.hide_description and not self.has():
|
||||||
|
if self.hide_description is True:
|
||||||
|
return _("???{#hidden_achievement_description}")
|
||||||
|
else:
|
||||||
|
return self.hide_description
|
||||||
|
else:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stat_progress(self):
|
||||||
|
"""Return this achievement's progress stat."""
|
||||||
|
return self.get_progress()
|
||||||
|
|
||||||
|
def add_progress(self, amount=1):
|
||||||
|
"""
|
||||||
|
Increment the progress towards this achievement by amount.
|
||||||
|
"""
|
||||||
|
self.progress(min(self.stat_progress+amount, self.stat_max))
|
||||||
|
|
||||||
|
## Wrappers for various achievement functionality
|
||||||
|
def clear(self):
|
||||||
|
"""Clear this achievement from memory."""
|
||||||
|
return achievement.clear(self.id)
|
||||||
|
|
||||||
|
def get_progress(self):
|
||||||
|
"""Return this achievement's progress."""
|
||||||
|
return achievement.get_progress(self.id)
|
||||||
|
|
||||||
|
def grant(self):
|
||||||
|
"""
|
||||||
|
Grant the player this achievement, and show a popup if this is
|
||||||
|
the first time they've gotten it.
|
||||||
|
"""
|
||||||
|
has_achievement = self.has()
|
||||||
|
x = achievement.grant(self.id)
|
||||||
|
if not has_achievement:
|
||||||
|
# First time this was granted
|
||||||
|
self.achievement_popup()
|
||||||
|
# Save the timestamp
|
||||||
|
self._timestamp = time.time()
|
||||||
|
# Callback
|
||||||
|
if myconfig.ACHIEVEMENT_CALLBACK is not None:
|
||||||
|
renpy.run(myconfig.ACHIEVEMENT_CALLBACK, self)
|
||||||
|
# Double check achievement sync
|
||||||
|
achievement.sync()
|
||||||
|
return x
|
||||||
|
|
||||||
|
def has(self):
|
||||||
|
"""Return True if the player has achieved this achievement."""
|
||||||
|
return achievement.has(self.id)
|
||||||
|
|
||||||
|
def progress(self, complete):
|
||||||
|
"""
|
||||||
|
A plugin to the original Achievement class. Sets the current
|
||||||
|
achievement progress to "complete".
|
||||||
|
"""
|
||||||
|
has_achievement = self.has()
|
||||||
|
x = achievement.progress(self.id, complete)
|
||||||
|
if not has_achievement and self.has():
|
||||||
|
# First time this was granted
|
||||||
|
self.achievement_popup()
|
||||||
|
# Save the timestamp
|
||||||
|
self._timestamp = time.time()
|
||||||
|
# Callback
|
||||||
|
if myconfig.ACHIEVEMENT_CALLBACK is not None:
|
||||||
|
renpy.run(myconfig.ACHIEVEMENT_CALLBACK, self)
|
||||||
|
return x
|
||||||
|
|
||||||
|
def achievement_popup(self):
|
||||||
|
"""
|
||||||
|
A function which shows an achievement screen to the user
|
||||||
|
to indicate they were granted an achievement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if renpy.is_init_phase():
|
||||||
|
## This is init time; we don't show a popup screen
|
||||||
|
return
|
||||||
|
elif not self.has():
|
||||||
|
# Don't have this achievement, so it doesn't get a popup.
|
||||||
|
return
|
||||||
|
elif not myconfig.SHOW_ACHIEVEMENT_POPUPS:
|
||||||
|
# Popups are disabled
|
||||||
|
return
|
||||||
|
|
||||||
|
if achievement.steamapi and not myconfig.INGAME_POPUP_WITH_STEAM:
|
||||||
|
# Steam is detected and popups shouldn't appear in-game.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Otherwise, show the achievement screen
|
||||||
|
for i in range(10):
|
||||||
|
if store.onscreen_achievements.get(i, None) is None:
|
||||||
|
store.onscreen_achievements[i] = True
|
||||||
|
break
|
||||||
|
# Generate a random tag for this screen
|
||||||
|
tag = get_random_screen_tag(6)
|
||||||
|
## Play a sound, if provided
|
||||||
|
if myconfig.ACHIEVEMENT_SOUND:
|
||||||
|
renpy.music.play(myconfig.ACHIEVEMENT_SOUND,
|
||||||
|
channel=myconfig.ACHIEVEMENT_CHANNEL)
|
||||||
|
renpy.show_screen('achievement_popup', a=self, tag=tag, num=i,
|
||||||
|
_tag=tag)
|
||||||
|
|
||||||
|
def AddProgress(self, amount=1):
|
||||||
|
"""Add amount of progress to this achievement."""
|
||||||
|
return Function(self.add_progress, amount=amount)
|
||||||
|
|
||||||
|
def Progress(self, amount):
|
||||||
|
"""Set this achievement's progress to amount."""
|
||||||
|
return Function(self.progress, amount)
|
||||||
|
|
||||||
|
def Toggle(self):
|
||||||
|
"""
|
||||||
|
A developer action to easily toggle the achieved status
|
||||||
|
of a particular achievement.
|
||||||
|
"""
|
||||||
|
return [SelectedIf(self.has()),
|
||||||
|
If(self.has(),
|
||||||
|
Function(self.clear),
|
||||||
|
Function(self.grant))]
|
||||||
|
|
||||||
|
def Grant(self):
|
||||||
|
"""
|
||||||
|
An action to easily achieve a particular achievement.
|
||||||
|
"""
|
||||||
|
return Function(self.grant)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset(self):
|
||||||
|
"""
|
||||||
|
A class method which resets all achievements and clears all their
|
||||||
|
progress.
|
||||||
|
"""
|
||||||
|
for achievement in self.all_achievements:
|
||||||
|
achievement.clear()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def Reset(self):
|
||||||
|
"""
|
||||||
|
A class method which resets all achievements and clears all their
|
||||||
|
progress. This is a button action rather than a function.
|
||||||
|
"""
|
||||||
|
return Function(self.reset)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def num_earned(self):
|
||||||
|
"""
|
||||||
|
A class property which returns the number of unlocked achievements.
|
||||||
|
"""
|
||||||
|
return len([a for a in self.all_achievements if a.has()])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def num_total(self):
|
||||||
|
"""
|
||||||
|
A class property which returns the total number of achievements.
|
||||||
|
"""
|
||||||
|
return len(self.all_achievements)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkedAchievement():
|
||||||
|
"""
|
||||||
|
A class which can be used as part of an achievement callback to
|
||||||
|
trigger an achievement when some subset of achievements is unlocked.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
-----------
|
||||||
|
links : dict
|
||||||
|
A dictionary of the form {achievement.id : [list of final
|
||||||
|
achievement ids]}. This is a reverse of the dictionary passed in
|
||||||
|
to the constructor and is used to look up what final achievements
|
||||||
|
are tied to a given achievement.
|
||||||
|
final_to_list : dict
|
||||||
|
A dictionary of the form {final_achievement.id : [list of
|
||||||
|
achievement ids to check]}. This is the same as the dictionary
|
||||||
|
passed in to the constructor, and is used to look up what
|
||||||
|
achievements are needed to unlock a given final achievement.
|
||||||
|
unlock_after_all : string
|
||||||
|
If this is set to an achievement ID, then that achievement is
|
||||||
|
unlocked after all other achievements are unlocked.
|
||||||
|
"""
|
||||||
|
def __init__(self, **links):
|
||||||
|
"""
|
||||||
|
Create a LinkedAchievement to be used as a callback.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
----------
|
||||||
|
links : dict
|
||||||
|
A dictionary of the form {final_achievement.id : [list of
|
||||||
|
achievement ids to check]}. When all of the achievements in the
|
||||||
|
list are unlocked, the final achievement is unlocked.
|
||||||
|
"""
|
||||||
|
## links comes in the form of
|
||||||
|
## {final_achievement.id : [list of achievement ids to check]}
|
||||||
|
self.links = dict()
|
||||||
|
|
||||||
|
values = links.values()
|
||||||
|
if len(values) == 1 and 'all' in values:
|
||||||
|
## Special case for an achievement that's achieved after
|
||||||
|
## getting all achievements
|
||||||
|
self.unlock_after_all = ''.join(links.keys())
|
||||||
|
self.final_to_list = links
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.unlock_after_all = False
|
||||||
|
|
||||||
|
## Reverse-engineer a dictionary which corresponds to the things
|
||||||
|
## that are checked, and what they tie back to.
|
||||||
|
for final_achievement, check_achievements in links.items():
|
||||||
|
for check_achievement in check_achievements:
|
||||||
|
if check_achievement == final_achievement:
|
||||||
|
continue
|
||||||
|
if check_achievement not in links:
|
||||||
|
self.links[check_achievement] = [final_achievement]
|
||||||
|
else:
|
||||||
|
self.links[check_achievement].append(final_achievement)
|
||||||
|
|
||||||
|
self.final_to_list = links
|
||||||
|
|
||||||
|
def __call__(self, the_achievement):
|
||||||
|
"""
|
||||||
|
A method which is called when an achievement is unlocked.
|
||||||
|
It checks if the achievement is part of a list of achievements
|
||||||
|
which are needed to unlock a given final achievement, and if the
|
||||||
|
conditions needed to unlock that final achievement are met.
|
||||||
|
If so, it grants that achievement.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-----------
|
||||||
|
the_achievement : Achievement
|
||||||
|
The achievement which was just granted.
|
||||||
|
"""
|
||||||
|
if self.unlock_after_all:
|
||||||
|
## This unlocks after all achievements are earned
|
||||||
|
if all([a.has() for a in Achievement.all_achievements
|
||||||
|
if a.id != self.unlock_after_all]):
|
||||||
|
fa = Achievement.achievement_dict.get(self.unlock_after_all)
|
||||||
|
if fa is not None:
|
||||||
|
fa.grant()
|
||||||
|
return
|
||||||
|
|
||||||
|
## Find which final achievements this is attached to
|
||||||
|
final_achievements = self.links.get(the_achievement.id, None)
|
||||||
|
if not final_achievements:
|
||||||
|
return
|
||||||
|
|
||||||
|
## Otherwise, see if this was the last achievement which was needed
|
||||||
|
## to unlock a given final_achievement.
|
||||||
|
for final_achievement in final_achievements:
|
||||||
|
lst = self.final_to_list.get(final_achievement, None)
|
||||||
|
if lst is None:
|
||||||
|
continue
|
||||||
|
## Check if all the achievements in the list are unlocked
|
||||||
|
if all([achievement.has(a) for a in lst]):
|
||||||
|
fa = Achievement.achievement_dict.get(final_achievement)
|
||||||
|
if fa is not None:
|
||||||
|
fa.grant()
|
||||||
|
return
|
||||||
|
|
||||||
|
## Note: DO NOT change these configuration values in this block! See
|
||||||
|
## `achievements.rpy` for how to change them. This is just for setup so they
|
||||||
|
## exist in the game, and then you can modify them with `define` in a different
|
||||||
|
## file.
|
||||||
|
init -999 python in myconfig:
|
||||||
|
_constant = True
|
||||||
|
## This is a configuration value which determines whether the in-game
|
||||||
|
## achievement popup should appear when Steam is detected. Since Steam
|
||||||
|
## already has its own built-in popup, you may want to set this to False
|
||||||
|
## if you don't want to show the in-game popup alongside it.
|
||||||
|
## The in-game popup will still work on non-Steam builds, such as builds
|
||||||
|
## released DRM-free on itch.io.
|
||||||
|
INGAME_POPUP_WITH_STEAM = True
|
||||||
|
## The length of time the in-game popup spends hiding itself (see
|
||||||
|
## transform achievement_popout in achievements.rpy).
|
||||||
|
ACHIEVEMENT_HIDE_TIME = 1.0
|
||||||
|
## True if the game should show in-game achievement popups when an
|
||||||
|
## achievement is earned. You can set this to False if you just want an
|
||||||
|
## achievement gallery screen and don't want any popups.
|
||||||
|
SHOW_ACHIEVEMENT_POPUPS = True
|
||||||
|
## A callback, or list of callbacks, which are called when an achievement
|
||||||
|
## is granted. It is called with one argument, the achievement which
|
||||||
|
## was granted. It is only called if the achievement has not previously
|
||||||
|
## been earned.
|
||||||
|
ACHIEVEMENT_CALLBACK = None
|
||||||
|
## A sound to play when the achievement is granted
|
||||||
|
ACHIEVEMENT_SOUND = None
|
||||||
|
ACHIEVEMENT_CHANNEL = "audio"
|
||||||
|
|
||||||
|
## Track the time each achievement was earned at
|
||||||
|
default persistent.achievement_timestamp = dict()
|
||||||
|
## Tracks the number of onscreen achievements, for offsetting when
|
||||||
|
## multiple achievements are earned at once
|
||||||
|
default onscreen_achievements = dict()
|
||||||
|
## Required for older Ren'Py versions so the vpgrid doesn't complain about
|
||||||
|
## uneven numbers of achievements, but True by default in later Ren'Py versions.
|
||||||
|
define config.allow_underfull_grids = True
|
||||||
|
|
||||||
|
# This, coupled with the timer on the popup screen, ensures that the achievement
|
||||||
|
# is properly hidden before another achievement can be shown in that "slot".
|
||||||
|
# If this was done as part of the timer in the previous screen, then it would
|
||||||
|
# consider that slot empty during the 1 second the achievement is hiding itself.
|
||||||
|
# That's why this timer is 1 second long.
|
||||||
|
screen finish_animating_achievement(num):
|
||||||
|
timer myconfig.ACHIEVEMENT_HIDE_TIME:
|
||||||
|
action [SetDict(onscreen_achievements, num, None), Hide()]
|
||||||
|
|
|
@ -0,0 +1,446 @@
|
||||||
|
################################################################################
|
||||||
|
##
|
||||||
|
## Achievements for Ren'Py by Feniks (feniksdev.itch.io / feniksdev.com)
|
||||||
|
##
|
||||||
|
################################################################################
|
||||||
|
## This file contains code for an achievement system in Ren'Py. It is designed
|
||||||
|
## as a wrapper to the built-in achievement system, so it hooks into the
|
||||||
|
## Steam backend as well if you set up your achievement IDs the same as in
|
||||||
|
## the Steam backend.
|
||||||
|
##
|
||||||
|
## You don't have to register the achievements or anything with the backend,
|
||||||
|
## or worry about syncing - that's all automatically done for you when the
|
||||||
|
## achievements are declared and granted.
|
||||||
|
##
|
||||||
|
## To get started, declare a few achievements below. Some samples are included.
|
||||||
|
## You may also replace the `image locked_achievement` image with something
|
||||||
|
## appropriate - this image is used as the default "Locked" image for all your
|
||||||
|
## achievements unless you specify something else.
|
||||||
|
##
|
||||||
|
## Then you can make a button to go to your achievement gallery screen, e.g.
|
||||||
|
# textbutton _("Achievements") action ShowMenu("achievement_gallery")
|
||||||
|
## This will show the achievement gallery screen, declared below. You can
|
||||||
|
## further customize it however you like.
|
||||||
|
## If you click on an achievement in the gallery during development (this will
|
||||||
|
## not happen in a release build), it will toggle the achievement on/off.
|
||||||
|
## This will also let you see the achievement popup screen, similarly declared
|
||||||
|
## below. It can be customized however you like.
|
||||||
|
##
|
||||||
|
## If you use this code in your projects, credit me as Feniks @ feniksdev.com
|
||||||
|
##
|
||||||
|
## Leave a comment on the tool page on itch.io or an issue on the GitHub
|
||||||
|
## if you run into any issues.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## CONFIGURATION
|
||||||
|
################################################################################
|
||||||
|
## This is a configuration value which determines whether the in-game
|
||||||
|
## achievement popup should appear when Steam is detected. Since Steam
|
||||||
|
## already has its own built-in popup, you may want to set this to False
|
||||||
|
## if you don't want to show the in-game popup alongside it.
|
||||||
|
## The in-game popup will still work on non-Steam builds, such as builds
|
||||||
|
## released DRM-free on itch.io.
|
||||||
|
define myconfig.INGAME_POPUP_WITH_STEAM = True
|
||||||
|
## The length of time the in-game popup spends hiding itself (see
|
||||||
|
## transform achievement_popout below).
|
||||||
|
define myconfig.ACHIEVEMENT_HIDE_TIME = 1.0
|
||||||
|
## True if the game should show in-game achievement popups when an
|
||||||
|
## achievement is earned. You can set this to False if you just want an
|
||||||
|
## achievement gallery screen and don't want any popups.
|
||||||
|
define myconfig.SHOW_ACHIEVEMENT_POPUPS = True
|
||||||
|
## This can be set to a sound that plays when the achievement popup appears.
|
||||||
|
## None does not play a sound.
|
||||||
|
define myconfig.ACHIEVEMENT_SOUND = None # "audio/sfx/achievement.ogg"
|
||||||
|
## If the sound plays, this sets the channel it will play on. The audio
|
||||||
|
## channel plays on the sfx mixer, and can play overlapping sounds if multiple
|
||||||
|
## achievements are earned at once.
|
||||||
|
define myconfig.ACHIEVEMENT_CHANNEL = "audio"
|
||||||
|
|
||||||
|
## A callback, or list of callbacks, which are called when an achievement
|
||||||
|
## is granted. It is called with one argument, the achievement which
|
||||||
|
## was granted. It is only called if the achievement has not previously
|
||||||
|
## been earned. See the README for more information.
|
||||||
|
define myconfig.ACHIEVEMENT_CALLBACK = [
|
||||||
|
## This first example is an achievement which unlocks after two other
|
||||||
|
## achievements have been granted ("hidden_achievement" and
|
||||||
|
## "hidden_description").
|
||||||
|
#LinkedAchievement(hidden3=['hidden_achievement', 'hidden_description']),
|
||||||
|
## The second example is an achievement which unlocks after all achievements
|
||||||
|
## have been granted. This is a special case.
|
||||||
|
# LinkedAchievement(platinum_achievement='all'),
|
||||||
|
]
|
||||||
|
|
||||||
|
init python:
|
||||||
|
## This is a built-in configuration value. It will set the position of
|
||||||
|
## the Steam popup. You can change this to any of the following:
|
||||||
|
## "top_left", "top_right", "bottom_left", "bottom_right"
|
||||||
|
## You may want to use this to ensure any Steam notifications don't conflict
|
||||||
|
## with the position of the built-in notification, if you're using both.
|
||||||
|
achievement.steam_position = None
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## DEFINING ACHIEVEMENTS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
define kicked_out = Achievement(
|
||||||
|
name=_("Kicked Out"),
|
||||||
|
id="kicked_out",
|
||||||
|
description=_("You left willingly and easily."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
define scare_tactics = Achievement(
|
||||||
|
name=_("Scare Tactics"),
|
||||||
|
id="scare_tactics",
|
||||||
|
description=_("You frightened your students off."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
define very_loud_quitting = Achievement(
|
||||||
|
name=_("Very Loud Quitting"),
|
||||||
|
id="very_loud_quitting",
|
||||||
|
description=_("You incited all the faculty into quitting."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
define she_has_your_back = Achievement(
|
||||||
|
name=_("She Has Your Back"),
|
||||||
|
id="she_has_your_back",
|
||||||
|
description=_("Either Reimu, Marisa, or Alice had your back."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
define they_have_your_back = Achievement(
|
||||||
|
name=_("They Have Your Back"),
|
||||||
|
id="they_have_your_back",
|
||||||
|
description=_("Your class had your back."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
define hedgehog_dilemma_solved = Achievement(
|
||||||
|
name=_("Hedgehog's Dilemma Solved"),
|
||||||
|
id="hedgehog_dilemma_solved",
|
||||||
|
description=_("You were proud and stubborn until the end."),
|
||||||
|
locked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ## Replace this with whatever locked image you want to use as the default
|
||||||
|
# ## for a locked achievement.
|
||||||
|
# image locked_achievement = Text("?")
|
||||||
|
|
||||||
|
# ## Example 1 ###################################################################
|
||||||
|
# ## This is how you declare achievements. You will use `define` and NOT
|
||||||
|
# ## `default`, so you can update the achievements later (you wouldn't want the
|
||||||
|
# ## description to be tied to a specific save file, for example).
|
||||||
|
# ## The order you declare achievements in is the order they will appear in the
|
||||||
|
# ## achievement gallery, by default.
|
||||||
|
# define sample_achievement = Achievement(
|
||||||
|
# ## The human-readable name, as it'll appear in the popup and in the gallery.
|
||||||
|
# name=_("Sample Achievement"),
|
||||||
|
# ## The id is used for Steam integration, and should match whatever ID
|
||||||
|
# ## you have set up in the Steam backend (if using).
|
||||||
|
# id="sample_achievement",
|
||||||
|
# ## Description.
|
||||||
|
# description=_("This is a sample achievement."),
|
||||||
|
# ## The image used in the popup and in the gallery once this achievement
|
||||||
|
# ## is unlocked.
|
||||||
|
# unlocked_image="gui/window_icon.png",
|
||||||
|
# ## By default all achievements use the "locked_achievement" image (declared
|
||||||
|
# ## above), but if you wanted to provide a different image, this is how
|
||||||
|
# ## you would specify it. It's used in the achievement gallery when the
|
||||||
|
# ## achievement is locked.
|
||||||
|
# locked_image="locked_achievement",
|
||||||
|
# ## All achievements are hidden=False by default, but you can change it to
|
||||||
|
# ## hidden=True if you'd like the title/description to show as ??? in the
|
||||||
|
# ## achievement gallery. See Examples 3 and 4 for examples of this.
|
||||||
|
# hidden=False,
|
||||||
|
# )
|
||||||
|
# ## You can grant an achievement in-game with `$ sample_achievement.grant()`
|
||||||
|
|
||||||
|
# ## Example 2 ###################################################################
|
||||||
|
# define progress_achievement = Achievement(
|
||||||
|
# name=_("Progress Achievement"),
|
||||||
|
# id="progress_achievement",
|
||||||
|
# description=_("This is an achievement with a progress bar."),
|
||||||
|
# unlocked_image=Transform("gui/window_icon.png", matrixcolor=InvertMatrix()),
|
||||||
|
# ## To record progress, you need to specify a stat_max. This means you can
|
||||||
|
# ## show a progress bar with % completion towards the achievement. It is
|
||||||
|
# ## useful if, for example, you have an achievement counting how many
|
||||||
|
# ## chapters the player has completed which unlocks when they have seen all
|
||||||
|
# ## the chapters.
|
||||||
|
# stat_max=12,
|
||||||
|
# ## You can also provide a stat_modulo, which means the achievement is only
|
||||||
|
# ## updated in the Steam backend every time the stat reaches a multiple of
|
||||||
|
# ## the modulo.
|
||||||
|
# ## Alternatively, this system also lets you set stat_update_percent instead,
|
||||||
|
# ## so if you want it to update every 10% it progresses, you can set
|
||||||
|
# # stat_update_percent=10
|
||||||
|
# ## This is most useful for achievements with a large number of steps,
|
||||||
|
# ## like a general % completion achievement. Maybe there are 600 things to
|
||||||
|
# ## complete for the achievement, but obviously 0.1% increments are pretty
|
||||||
|
# ## meaningless so you can either set stat_modulo=6 or stat_update_percent=1
|
||||||
|
# ## and it will update Steam every 6 steps or every 1%.
|
||||||
|
# )
|
||||||
|
# ## To update progress towards completion of this achievement, you can use
|
||||||
|
# # $ progress_achievement.add_progress(1)
|
||||||
|
# ## where 1 is how much progress is added to the stat (so, the first time it
|
||||||
|
# ## is called for the above example it'd be 1/12, the second it'd be 2/12, etc).
|
||||||
|
# ##
|
||||||
|
# ## Alternatively, you can directly set the progress like:
|
||||||
|
# # $ progress_achievement.progress(5)
|
||||||
|
# ## This will directly set progress to 5, making the above example 5/12 for
|
||||||
|
# ## example. This can be useful if you're doing something like using a set to
|
||||||
|
# ## track unique progress towards the achievement e.g.
|
||||||
|
# # $ persistent.seen_endings.add("end1")
|
||||||
|
# # $ ending_achievement.progress(len(persistent.seen_endings))
|
||||||
|
# ## This will prevent the achievement from being added to multiple times if the
|
||||||
|
# ## player sees the same ending multiple times.
|
||||||
|
|
||||||
|
# ## Example 3 ###################################################################
|
||||||
|
# ## This achievement is "hidden", that is, its name and description appear as
|
||||||
|
# ## ??? in the achievement gallery until it is unlocked.
|
||||||
|
# define hidden_achievement = Achievement(
|
||||||
|
# name=_("Hidden Achievement"),
|
||||||
|
# id="hidden_achievement",
|
||||||
|
# description=_("This hidden achievement hides both the name and description."),
|
||||||
|
# unlocked_image=Transform("gui/window_icon.png", matrixcolor=BrightnessMatrix(-1.0)),
|
||||||
|
# hidden=True, ## The important bit that hides the name and description
|
||||||
|
# )
|
||||||
|
|
||||||
|
# ## Example 4 ###################################################################
|
||||||
|
# define hidden_description = Achievement(
|
||||||
|
# name=_("Hidden Description"),
|
||||||
|
# id="hidden_description",
|
||||||
|
# description=_("This hidden achievement hides only the description."),
|
||||||
|
# unlocked_image=Transform("gui/window_icon.png", matrixcolor=SepiaMatrix()),
|
||||||
|
# hide_description=True, ## The important bit that hides only the description
|
||||||
|
# )
|
||||||
|
|
||||||
|
# ## Example 5 ###################################################################
|
||||||
|
# ## This achievement unlocks automatically when the other two hidden achievements
|
||||||
|
# ## are unlocked. This is set up via myconfig.ACHIEVEMENT_CALLBACK earlier in
|
||||||
|
# ## the file.
|
||||||
|
# define hidden_double_unlock = Achievement(
|
||||||
|
# name=_("You found it"),
|
||||||
|
# id="hidden3",
|
||||||
|
# description=_("This achievement unlocks automatically when the other two hidden achievements are unlocked."),
|
||||||
|
# unlocked_image=Transform("gui/window_icon.png", matrixcolor=ContrastMatrix(0.0)),
|
||||||
|
# hidden=True,
|
||||||
|
# ## Besides just setting hide_description=True to set it to "???", you can
|
||||||
|
# ## optionally provide your own custom description here, which is only
|
||||||
|
# ## shown until the achievement is unlocked.
|
||||||
|
# hide_description=_("Try unlocking the other two hidden achievements before this one."),
|
||||||
|
# )
|
||||||
|
# ## Example 6 ###################################################################
|
||||||
|
# ## This -2 makes sure it's declared before the other achievements. This is
|
||||||
|
# ## so it shows up first in the list even though it's defined all the way down
|
||||||
|
# ## here.
|
||||||
|
# define -2 all_achievements = Achievement(
|
||||||
|
# name=_("Platinum Achievement"),
|
||||||
|
# id="platinum_achievement",
|
||||||
|
# description=_("Congrats! You unlocked every achievement!"),
|
||||||
|
# unlocked_image=Transform("gui/window_icon.png", matrixcolor=BrightnessMatrix(1.0)),
|
||||||
|
# hide_description=_("Get all other achievements."),
|
||||||
|
# )
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## SCREENS
|
||||||
|
################################################################################
|
||||||
|
## POPUP SCREEN
|
||||||
|
################################################################################
|
||||||
|
## A screen which shows a popup for an achievement the first time
|
||||||
|
## it is obtained. You may modify this however you like.
|
||||||
|
## The relevant information is:
|
||||||
|
## a.name = the human-readable name of the achievement
|
||||||
|
## a.description = the description
|
||||||
|
## a.unlocked_image = the image of the achievement, now that it's unlocked
|
||||||
|
## a.timestamp = the time the achievement was unlocked at
|
||||||
|
screen achievement_popup(a, tag, num):
|
||||||
|
|
||||||
|
zorder 190
|
||||||
|
|
||||||
|
## Allows multiple achievements to be slightly offset from each other.
|
||||||
|
## This number should be at least as tall as one achievement.
|
||||||
|
default achievement_yoffset = num*170
|
||||||
|
|
||||||
|
frame:
|
||||||
|
style_prefix 'achieve_popup'
|
||||||
|
## The transform that makes it pop out
|
||||||
|
at achievement_popout()
|
||||||
|
## Offsets the achievement down if there are multiple
|
||||||
|
yoffset achievement_yoffset
|
||||||
|
has hbox
|
||||||
|
add a.unlocked_image:
|
||||||
|
## Make sure the image is within a certain size. Useful because
|
||||||
|
## often popups are smaller than the full gallery image.
|
||||||
|
## In this case it will not exceed 95 pixels tall but will retain
|
||||||
|
## its dimensions.
|
||||||
|
fit "contain" ysize 95 align (0.5, 0.5)
|
||||||
|
vbox:
|
||||||
|
text a.name
|
||||||
|
text a.description size 25
|
||||||
|
|
||||||
|
## Hide the screen after 5 seconds. You can change the time but shouldn't
|
||||||
|
## change the action.
|
||||||
|
timer 5.0 action [Hide("achievement_popup"),
|
||||||
|
Show('finish_animating_achievement', num=num, _tag=tag+"1")]
|
||||||
|
|
||||||
|
style achieve_popup_frame:
|
||||||
|
is confirm_frame
|
||||||
|
align (0.0, 0.0)
|
||||||
|
style achieve_popup_hbox:
|
||||||
|
spacing 10
|
||||||
|
style achieve_popup_vbox:
|
||||||
|
spacing 2
|
||||||
|
|
||||||
|
|
||||||
|
## A transform that pops the achievement out from the left side of
|
||||||
|
## the screen and bounces it slightly into place, then does the
|
||||||
|
## reverse when the achievement is hidden.
|
||||||
|
transform achievement_popout():
|
||||||
|
## The `on show` event occurs when the screen is first shown.
|
||||||
|
on show:
|
||||||
|
## Align it off-screen at the left. Note that no y information is
|
||||||
|
## given, as that is handled on the popup screen.
|
||||||
|
xpos 0.0 xanchor 1.0
|
||||||
|
## Ease it on-screen
|
||||||
|
easein_back 1.0 xpos 0.0 xanchor 0.0
|
||||||
|
## The `on hide, replaced` event occurs when the screen is hidden.
|
||||||
|
on hide, replaced:
|
||||||
|
## Ease it off-screen again.
|
||||||
|
## This uses the hide time above so it supports displaying multiple
|
||||||
|
## achievements at once.
|
||||||
|
easeout_back myconfig.ACHIEVEMENT_HIDE_TIME xpos 0.0 xanchor 1.0
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## ACHIEVEMENT GALLERY SCREEN
|
||||||
|
################################################################################
|
||||||
|
## The screen displaying a list of the achievements the player has earned.
|
||||||
|
## Feel free to update the styling for this however you like; this is just one
|
||||||
|
## way to display the various information.
|
||||||
|
screen ending_gallery():
|
||||||
|
tag menu
|
||||||
|
|
||||||
|
add VBox(Transform("#292835", ysize=110), "#21212db2") # Background
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
## Version 1 ###############################################################
|
||||||
|
## If you're using a default template/typical Ren'Py layout, uncomment
|
||||||
|
## the following:
|
||||||
|
# use game_menu(_("Achievement Gallery"), scroll='viewport'):
|
||||||
|
############################################################################
|
||||||
|
## Version 2 ###############################################################
|
||||||
|
## Otherwise, if you'd like this to be independent of the game menu,
|
||||||
|
## use the following:
|
||||||
|
if main_menu:
|
||||||
|
textbutton _("Return") action Return() align (1.0, 1.0)
|
||||||
|
|
||||||
|
viewport:
|
||||||
|
mousewheel True draggable True pagekeys True
|
||||||
|
scrollbars "vertical"
|
||||||
|
xalign 0.5 yalign 0.5
|
||||||
|
xsize int(config.screen_width*0.6) ysize int(config.screen_height*0.7)
|
||||||
|
xfill False yfill False
|
||||||
|
has vbox
|
||||||
|
spacing 20
|
||||||
|
############################################################################
|
||||||
|
## Version 3 ###############################################################
|
||||||
|
## You might also consider a vpgrid layout like so:
|
||||||
|
# textbutton _("Return") action Return() align (1.0, 1.0)
|
||||||
|
# vpgrid:
|
||||||
|
# cols 2
|
||||||
|
# mousewheel True draggable True pagekeys True
|
||||||
|
# scrollbars "vertical"
|
||||||
|
# xalign 0.5 yalign 0.5
|
||||||
|
# xsize 1500 ysize int(config.screen_height*0.7)
|
||||||
|
# yspacing 70 xspacing 50
|
||||||
|
############################################################################
|
||||||
|
## This list contains every achievement you declared. You can also
|
||||||
|
## create your own lists to iterate over, if desired. That would be
|
||||||
|
## useful if you wanted to group achievements by category, for example.
|
||||||
|
for a in Achievement.all_achievements:
|
||||||
|
button:
|
||||||
|
style_prefix 'achievement'
|
||||||
|
## During development, you can click on achievements in the
|
||||||
|
## gallery and they will toggle on/off.
|
||||||
|
if config.developer:
|
||||||
|
action a.Toggle()
|
||||||
|
has hbox
|
||||||
|
if a.idle_img:
|
||||||
|
fixed:
|
||||||
|
align (0.5, 0.5)
|
||||||
|
xysize (155, 155)
|
||||||
|
add a.idle_img fit "scale-down" ysize 155 align (0.5, 0.5)
|
||||||
|
else:
|
||||||
|
null width -10
|
||||||
|
vbox:
|
||||||
|
label a.name
|
||||||
|
text a.description
|
||||||
|
if a.has():
|
||||||
|
## There are two ways to display the timestamp. The
|
||||||
|
## first is automatically formatted like
|
||||||
|
## Unlocked Sep 14, 2023 @ 6:45 PM
|
||||||
|
text a.timestamp size 22
|
||||||
|
## If you want to format it yourself, you can use
|
||||||
|
## the get_timestamp method:
|
||||||
|
# text __("Achieved at ") + a.get_timestamp(__("%H:%M on %Y/%m/%d"))
|
||||||
|
## The above example would display the timestamp like:
|
||||||
|
## Achieved at 18:45 on 2023/09/14
|
||||||
|
## See https://strftime.org/ for formatting
|
||||||
|
## Note also the double underscores for translation.
|
||||||
|
elif a.stat_max:
|
||||||
|
# Has a bar to show stat progress.
|
||||||
|
## NOTE: If you don't want to show the progress *bar*,
|
||||||
|
## you can remove this entire block (or potentially just
|
||||||
|
## keep the text and not the bar if you like).
|
||||||
|
fixed:
|
||||||
|
fit_first True
|
||||||
|
bar value a.stat_progress range a.stat_max:
|
||||||
|
style 'achievement_bar'
|
||||||
|
text "[a.stat_progress]/[a.stat_max]":
|
||||||
|
style_suffix "progress_text"
|
||||||
|
|
||||||
|
## So there's a bit of space at the bottom after scrolling all the way.
|
||||||
|
null height 100
|
||||||
|
|
||||||
|
## A header that shows how many achievements you've earned, out of
|
||||||
|
## the total number of achievements in the game. Feel free to remove
|
||||||
|
## or relocate this.
|
||||||
|
label __("Endings: ") + "{earned}/{total}".format(
|
||||||
|
earned=Achievement.num_earned(), total=Achievement.num_total()):
|
||||||
|
text_size 52 xalign 0.5 text_color "#f93c3e" top_padding 15
|
||||||
|
|
||||||
|
## This is an example of a button you might have during development which
|
||||||
|
## will reset all achievement progress at once. It can also be provided
|
||||||
|
## to players if you'd like them to be able to reset their achievement
|
||||||
|
## progress.
|
||||||
|
if main_menu:
|
||||||
|
textbutton "Reset Progress" action Achievement.Reset() align (1.0, 0.0)
|
||||||
|
|
||||||
|
style achievement_button:
|
||||||
|
size_group 'achievement'
|
||||||
|
xmaximum 750
|
||||||
|
style achievement_label:
|
||||||
|
padding (2, 2)
|
||||||
|
style achievement_label_text:
|
||||||
|
size 40 color "#ff8335"
|
||||||
|
style achievement_hbox:
|
||||||
|
spacing 10
|
||||||
|
style achievement_vbox:
|
||||||
|
spacing 2
|
||||||
|
style achievement_bar:
|
||||||
|
xmaximum 600
|
|
@ -27,6 +27,8 @@ screen main_menu():
|
||||||
|
|
||||||
textbutton _("Load") action ShowMenu("load")
|
textbutton _("Load") action ShowMenu("load")
|
||||||
|
|
||||||
|
textbutton _("Endings") action ShowMenu("ending_gallery")
|
||||||
|
|
||||||
textbutton _("Preferences") action ShowMenu("preferences")
|
textbutton _("Preferences") action ShowMenu("preferences")
|
||||||
|
|
||||||
textbutton _("About") action ShowMenu("about")
|
textbutton _("About") action ShowMenu("about")
|
||||||
|
|
|
@ -23,32 +23,78 @@ image reimu happy:
|
||||||
|
|
||||||
label start:
|
label start:
|
||||||
|
|
||||||
scene bg room
|
label presentation_begins:
|
||||||
|
|
||||||
show yuuka happy:
|
scene bg auditorium
|
||||||
xalign 0.2
|
|
||||||
yalign 0.99
|
|
||||||
|
|
||||||
show reimu happy:
|
|
||||||
xalign 0.8
|
|
||||||
yalign 0.99
|
|
||||||
|
|
||||||
yuuka "Good evening."
|
show yuuka happy:
|
||||||
|
xalign 0.5
|
||||||
yuuka "You all may know me as the substitute this past week for Professor Okazaki."
|
yalign 0.99
|
||||||
|
|
||||||
yuuka "I've been teaching Botany, a topic on which I am incredibly overqualified for, to idiots younger than you but still very much like you."
|
python: # <- Remove
|
||||||
|
'''
|
||||||
|
show reimu happy:
|
||||||
|
xalign 0.8
|
||||||
|
yalign 0.99
|
||||||
|
'''
|
||||||
|
|
||||||
yuuka "But enough of the customary ribbing and teasing, I am here because Professor Okazaki is returning today."
|
yuuka "Good evening."
|
||||||
|
|
||||||
yuuka "As is stated in the faculty handbook she wrote and doodled over, because clearly not even she could have handwriting THAT horrendous,"
|
yuuka "You all may know me as the substitute this past week for Professor Okazaki."
|
||||||
|
|
||||||
yuuka "I must give a presentation to report to the faculty and administration exactly what I did as subsitute for Professor Okazaki."
|
yuuka "I've been teaching Botany, a topic on which I am {sc}incredibly{/sc} overqualified for, to idiots younger than you, but still very much similar to you."
|
||||||
|
|
||||||
yuuka "This is that presentation."
|
narrator "{i}A few nervous chuckles break throughout the auditorium.{/i}"
|
||||||
|
|
||||||
yuuka "You might also be wondering why I've invited select students to attend this presentation."
|
yuuka "But enough of the customary good-natured teasing, I am here because Professor Okazaki is returning today."
|
||||||
|
|
||||||
yuuka "I will be getting to that, please do not leave your seats through out this presentation. I will be brief."
|
yuuka "As is stated in the faculty handbook she wrote and doodled over, because clearly not even she could have handwriting THAT horrendous,"
|
||||||
|
|
||||||
|
yuuka "I must give a presentation to report to the faculty and administration exactly what I did as subsitute for Professor Okazaki."
|
||||||
|
|
||||||
return
|
yuuka "This is that presentation: a postmortem to my short time here, a retrospective of sorts."
|
||||||
|
|
||||||
|
yuuka "You might also be wondering why I've invited select students to attend this presentation."
|
||||||
|
|
||||||
|
yuuka "I will be getting to that, please do not leave your seats throughout this presentation. This will be brief."
|
||||||
|
|
||||||
|
jump intro
|
||||||
|
|
||||||
|
label intro:
|
||||||
|
scene bg entrance with fade
|
||||||
|
|
||||||
|
show yuuka happy:
|
||||||
|
xalign 0.5
|
||||||
|
yalign 0.99
|
||||||
|
|
||||||
|
yuuka "{i}There I was, one week ago, naive, unsure of what I was looking at.{/i}" (cb_name="")
|
||||||
|
|
||||||
|
yuuka "What the hell am I looking at?"
|
||||||
|
|
||||||
|
jump ending
|
||||||
|
|
||||||
|
label ending:
|
||||||
|
|
||||||
|
narrator "This is the part where the ending goes."
|
||||||
|
|
||||||
|
narrator "You understand?"
|
||||||
|
|
||||||
|
$ kicked_out.grant()
|
||||||
|
# $ scare_tactics.grant()
|
||||||
|
# $ very_loud_quitting.grant()
|
||||||
|
# $ she_has_your_back.grant()
|
||||||
|
# $ they_have_your_back.grant()
|
||||||
|
# $ hedgehog_dilemma_solved.grant()
|
||||||
|
|
||||||
|
show screen ending_gallery
|
||||||
|
|
||||||
|
jump pause_loop
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
label pause_loop:
|
||||||
|
|
||||||
|
window hide
|
||||||
|
$ renpy.pause()
|
||||||
|
|
||||||
|
jump pause_loop
|
Loading…
Reference in New Issue