Added an ending system

Word count: 292 words
This commit is contained in:
Jacoder23 2024-04-05 17:26:40 +08:00
parent d0a3798dd2
commit 630be43744
4 changed files with 1009 additions and 23 deletions

View File

@ -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()]

446
game/achievements.rpy Normal file
View File

@ -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

View File

@ -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")

View File

@ -23,32 +23,78 @@ image reimu happy:
label start: label start:
scene bg room label presentation_begins:
scene bg auditorium
show yuuka happy: show yuuka happy:
xalign 0.2 xalign 0.5
yalign 0.99 yalign 0.99
python: # <- Remove
'''
show reimu happy: show reimu happy:
xalign 0.8 xalign 0.8
yalign 0.99 yalign 0.99
'''
yuuka "Good evening." yuuka "Good evening."
yuuka "You all may know me as the substitute this past week for Professor Okazaki." yuuka "You all may know me as the substitute this past week for Professor Okazaki."
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." 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 "But enough of the customary ribbing and teasing, I am here because Professor Okazaki is returning today." narrator "{i}A few nervous chuckles break throughout the auditorium.{/i}"
yuuka "But enough of the customary good-natured teasing, I am here because Professor Okazaki is returning today."
yuuka "As is stated in the faculty handbook she wrote and doodled over, because clearly not even she could have handwriting THAT horrendous," 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." yuuka "I must give a presentation to report to the faculty and administration exactly what I did as subsitute for Professor Okazaki."
yuuka "This is that presentation." 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 "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 through out this presentation. I will be brief." 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 return
label pause_loop:
window hide
$ renpy.pause()
jump pause_loop