diff --git a/game/scripts/gui/main_menu.rpy b/game/scripts/gui/main_menu.rpy index d14c5d1b..3a5f5e3b 100644 --- a/game/scripts/gui/main_menu.rpy +++ b/game/scripts/gui/main_menu.rpy @@ -171,7 +171,7 @@ screen game_menu(title, scroll=None, yinitial=0.0): label title anchor (0.5, 0.5) align (0.86, 0.15) - if main_menu: + if main_menu and not title == "Updater": key "game_menu" action ShowMenu("main_menu") style game_menu_outer_frame is empty: @@ -220,6 +220,7 @@ screen navigation(title=None): default show_quick_start = False default show_dev_start = False + default is_sensitive = not bool(title == "Updater") key "keydown_K_LSHIFT" action SetLocalVariable("show_quick_start", True) key "keyup_K_LSHIFT" action SetLocalVariable("show_quick_start", False) @@ -243,37 +244,38 @@ screen navigation(title=None): if main_menu: if not title: - if (updater.can_update() or config.developer) and getattr(store, "UPDATE_URL", None): - if new_version: - textbutton "Update available" action install_updates() style_prefix "update_available" + + if not renpy.mobile: + if UPDATE_VER: + textbutton "Install updates" action InstallUpdates() style_prefix "update_available" else: - textbutton "Check for updates" action Function(check_updates, 300) + textbutton "Check for updates" action CheckUpdates(300) if show_quick_start: - textbutton _("Quick Start") action Start("start_quick") + textbutton _("Quick Start") action Start("start_quick") sensitive is_sensitive elif show_dev_start: - textbutton _("Developer Start") action Start("start_dev") + textbutton _("Developer Start") action Start("start_dev") sensitive is_sensitive else: - textbutton _("Start") action Start() + textbutton _("Start") action Start() sensitive is_sensitive else: - textbutton _("Return") action Return() + textbutton _("Return") action Return() sensitive is_sensitive else: - textbutton _("Return") action Return() - textbutton _("History") action ShowMenu("history") - textbutton _("Save") action ShowMenu("save") + textbutton _("Return") action Return() sensitive is_sensitive + textbutton _("History") action ShowMenu("history") sensitive is_sensitive + textbutton _("Save") action ShowMenu("save") sensitive is_sensitive - textbutton _("Load") action ShowMenu("load") - textbutton _("Preferences") action ShowMenu("preferences") + textbutton _("Load") action ShowMenu("load") sensitive is_sensitive + textbutton _("Preferences") action ShowMenu("preferences") sensitive is_sensitive if main_menu: - textbutton _("Mods") sensitive bool(mods_list) action ShowMenu("mods") - textbutton _("Credits") action Jump("credits") + textbutton _("Mods") sensitive (bool(mods_list) and is_sensitive) action ShowMenu("mods") + textbutton _("Credits") action Jump("credits") sensitive is_sensitive if not renpy.mobile: - textbutton _("Quit") action Quit(confirm=not main_menu) + textbutton _("Quit") action Quit(confirm=not main_menu) sensitive is_sensitive else: - textbutton _("Help") action ShowMenu("help") - textbutton _("Main Menu") action MainMenu() + textbutton _("Help") action ShowMenu("help") sensitive is_sensitive + textbutton _("Quit to menu") action MainMenu() sensitive is_sensitive style navigation_vbox: xsize 250 diff --git a/game/scripts/gui/preferences.rpy b/game/scripts/gui/preferences.rpy index 7570a8d9..ecaa178f 100644 --- a/game/scripts/gui/preferences.rpy +++ b/game/scripts/gui/preferences.rpy @@ -62,6 +62,7 @@ screen preferences_general(): if not renpy.mobile: textbutton _("Tooltips") action settings.Toggle("tooltip") textbutton _("System Cursor") action Preference("system cursor", "toggle") + textbutton _("Automatic Updates") action settings.Toggle("updates") default trans = config.intra_transition diff --git a/game/scripts/options.rpy b/game/scripts/options.rpy index f58aa1ad..b5c9e6cb 100644 --- a/game/scripts/options.rpy +++ b/game/scripts/options.rpy @@ -22,6 +22,7 @@ init python: settings.default("tutorials", True) settings.default("preserve_aspect_ratio", True) settings.default("animations", True) + settings.default("updates", True) # Configuration # https://www.renpy.org/doc/html/config.html @@ -35,10 +36,10 @@ define config.console = True # Game version and naming define config.version = "1.43.0" define compatible_version = 1.43 -define config.name = "WT Silver" +define config.name = "Witch Trainer Silver" # Application window settings -define config.window_title = "Witch Trainer: Silver (v{}) ({}) ({}-bit)".format(config.version, get_renderer(), renpy.bits) +define config.window_title = "{} (v{}) ({}) ({}-bit)".format(config.name, config.version, get_renderer(), renpy.bits) define config.window_icon = "gui/icon.webp" define config.screen_width = 1080 define config.screen_height = 600 @@ -89,7 +90,7 @@ init -1: define config.late_images_scan = True # Saving and loading -define config.save_directory = "WTS" +define config.save_directory = "Witch Trainer Silver" define config.has_autosave = True define config.autosave_on_quit = True define config.autosave_on_choice = True @@ -123,10 +124,10 @@ define config.exit_replay_transition = None define config.say_attribute_transition = d3 # Garbage Collector -define config.manage_gc = True -define config.gc_thresholds = (25000, 10, 10) -define config.idle_gc_count = 10000 -define config.gc_print_unreachable = False +# define config.manage_gc = True +# define config.gc_thresholds = (25000, 10, 10) +# define config.idle_gc_count = 10000 +# define config.gc_print_unreachable = False ################################################ ## Build configuration ## @@ -140,33 +141,20 @@ init python: build.include_old_themes = False build.exclude_empty_directories = True - build.archive("scripts", "all") - build.archive("characters", "all") - build.archive("images", "all") - build.archive("sounds", "all") - build.archive("music", "all") - build.archive("gui", "all") - build.archive("old", "all") + build.classify("**.rpy", "renpy") + build.classify("**.rpyc", "all") + build.classify("game/images.whitespace", "all") + build.classify("**.webp", "all") + build.classify("**.webm", "all") + build.classify("**.ogg", "all") - build.classify("game/scripts/**.rpy", "scripts") - build.classify("game/scripts/**.rpyc", "scripts") - build.classify("old-game/scripts/**.rpy", "old") - build.classify("old-game/scripts/**.rpyc", "old") - build.classify("game/images.whitespace", "scripts") - build.classify("game/characters/**.webp", "characters") - build.classify("game/images/**.webp", "images") - build.classify("game/images/**.webm", "images") - build.classify("game/sounds/**.ogg", "sounds") - build.classify("game/music/**.ogg", "music") - - build.classify("game/gui/**", "gui") - build.classify("game/interface/**.webp", "gui") - build.classify("android-icon_*.png", "android gui") - build.classify("android-presplash.jpg", "android gui") - build.classify("icon.icns", "mac gui") - build.classify("icon.ico", "windows gui") - build.classify("game/presplash_*.png", "mac windows linux gui") - build.classify("game/outfits/**", "mac windows linux") + build.classify("game/gui/**", "all") + build.classify("android-icon_*.png", "android") + build.classify("android-presplash.jpg", "android") + build.classify("icon.icns", "mac") + build.classify("icon.ico", "windows") + build.classify("game/presplash_*.png", "renpy") + build.classify("game/outfits/**", "all") build.classify("**.py", None) build.classify("**.txt", None) diff --git a/game/scripts/utility/server.rpy b/game/scripts/utility/server.rpy deleted file mode 100644 index 24da3af3..00000000 --- a/game/scripts/utility/server.rpy +++ /dev/null @@ -1,54 +0,0 @@ -# This file can be safely deleted to permanently disable update checks. - -init python: - import requests - import binascii - - UPDATE_URL = "" - LOGO_URL = "" - - new_version = None - - def check_updates(interval=3600*6): - if not UPDATE_URL: - return - - global new_version - new_version = updater.UpdateVersion(binascii.unhexlify(UPDATE_URL), check_interval=interval) - - def install_updates(): - if not UPDATE_URL: - return None - - return updater.Update(binascii.unhexlify(UPDATE_URL), patch=True) - - def fetch_update_logo(url): - if not (updater.can_update() or config.developer) or new_version is None or not LOGO_URL: - return Null() - - filename = "logo_{}.png".format(new_version) - path = os.path.join(config.basedir, "updates/{}".format(filename)) - - # Read file if exists - if os.path.isfile(path): - with open(path, "rb") as f: - data = f.read() - return im.Data(data, path) - - # Fetch file if doesn't exist - try: - url = binascii.unhexlify(url) - response = requests.get(url, timeout=5) - data = response.content - except: - return Null() - - if not data: - return Null() - - with open(path, "wb") as f: - f.write(data) - - return im.Data(data, path) - - check_updates() diff --git a/game/scripts/utility/updater.rpy b/game/scripts/utility/updater.rpy index f95128d0..2cd6c723 100644 --- a/game/scripts/utility/updater.rpy +++ b/game/scripts/utility/updater.rpy @@ -1,3 +1,122 @@ +init python: + import binascii + import requests + + UPDATE_URL = binascii.unhexlify("687474703a2f2f3135332e39322e3232312e3234362f757064617465732e6a736f6e") + UPDATE_VER = "" + + @renpy.pure + class CheckUpdates(Action): + def __init__(self, interval=3600*6, simulate=None, onetime=False, autostart=True, **kwargs): + self.url = UPDATE_URL + self.interval = interval + self.simulate = simulate + self.onetime = onetime + self.autostart = autostart + self.kwargs = kwargs + + def __call__(self): + # Since source files are publicly available, + # we need to forbid updater from affecting + # GIT-supplied versions of the game to avoid bugs. + + if (not updater.can_update() or config.developer) and not self.simulate: + return + + check = True + url = self.url + + if self.onetime and url in updater.checked: + check = False + + if time.time() < persistent._update_last_checked.get(url, 0) + self.interval: + check = False + + if check: + updater.checked.add(url) + persistent._update_last_checked[url] = time.time() + updater.Updater(url, check_only=True, simulate=self.simulate, **self.kwargs) + + global UPDATE_VER + UPDATE_VER = persistent._update_version.get(url, "") + + if self.autostart: + renpy.invoke_in_new_context(updater.update, self.url, simulate=self.simulate, **self.kwargs) + + @renpy.pure + class InstallUpdates(Action): + def __init__(self, simulate=None, **kwargs): + self.url = UPDATE_URL + self.simulate = simulate + self.kwargs = kwargs + + def __call__(self): + renpy.invoke_in_new_context(updater.update, self.url, simulate=self.simulate, **self.kwargs) + + def version_float(): + control, major, minor = config.version.split(" ")[0].split(".") + return float("{}.{}{}".format(control, major, minor)) + + def version_patch(): + if renpy.is_init_phase(): + # Don't update save files from when game recovers from a crash. + return + + latest = version_float() + + if not hasattr(renpy.store, "version"): + if hasattr(renpy.store, "save_internal_version"): + raise Exception("Loaded save file is incompatible. (Save Version: {}, Game Version: {})".format(getattr(renpy.store, "save_internal_version")), latest) + raise Exception("Loaded save file is incompatible. (Save Version: Unknown, Game Version: {})".format(latest)) + + current = version + + if current > latest: + raise Exception("Loaded save file is incompatible. (Save Version: {}, Game Version: {})".format(current, latest)) + + if current < latest: + setattr(renpy.store, "version", latest) + message = "Have fun!" + + achievements.attempt_repair() + + renpy.call_in_new_context("modal_popup", "Update Successful", "\nYour save file has been successfully updated to version {{b}}{}{{/b}}.\n\n{}".format(config.version, message), None, "Hurray!") + renpy.block_rollback() + return + + def version_logo(): + url = UPDATE_URL + filename = "logo_{}.webp".format(UPDATE_VER) + path = os.path.join(config.basedir, "update/{}".format(filename)) + + # Read file if exists + if os.path.isfile(path): + with open(path, "rb") as f: + data = f.read() + return im.Data(data, path) + + # Fetch file if doesn't exist + url = url.split("updates.json")[0] + "logo.webp" + + try: + response = requests.get(url, timeout=5) + response.raise_for_status() + data = response.content + + with open(path, "wb") as f: + f.write(data) + + return im.Data(data, path) + except: + path = os.path.join(config.basedir, "update/generic.webp") + + with open(path, "rb") as f: + data = f.read() + + return Fixed(im.Data(data, path), Text(UPDATE_VER, size=96, align=(0.5, 0.8), color="#000000", outlines=[( 1, "#ffffff", 0, 0 )]), fit_first=True) + + config.after_load_callbacks.append(version_patch) + define update_message_list = [ "Spinning up disks", "Lubricating Hermione", @@ -72,7 +191,7 @@ screen updater: tag menu default msg = renpy.random.choice(update_message_list) - default logo = fetch_update_logo(LOGO_URL) # http://update.silverstudiogames.org/logo.webp + default logo = version_logo() use game_menu(_("Updater"), scroll="viewport"): @@ -138,8 +257,6 @@ screen updater: style updater_text: xalign 0.5 -style updater_button - style updater_button_text is gui_button_text style updater_label is gui_label: xsize 209 @@ -163,43 +280,8 @@ style update_available_button_text: hover_color "#fff" xalign 0.5 - -init python early: - - def version_float(): - control, major, minor = config.version.split(" ")[0].split(".") - return float("{}.{}{}".format(control, major, minor)) - - def version_patch(): - if renpy.is_init_phase(): - # Don't update save files from when game recovers from a crash. - return - - latest = version_float() - - if not hasattr(renpy.store, "version"): - if hasattr(renpy.store, "save_internal_version"): - raise Exception("Loaded save file is incompatible. (Save Version: {}, Game Version: {})".format(getattr(renpy.store, "save_internal_version")), latest) - raise Exception("Loaded save file is incompatible. (Save Version: Unknown, Game Version: {})".format(latest)) - - current = version - - if current > latest: - raise Exception("Loaded save file is incompatible. (Save Version: {}, Game Version: {})".format(current, latest)) - - if current < latest: - setattr(renpy.store, "version", latest) - message = "Have fun!" - - achievements.attempt_repair() - - renpy.call_in_new_context("modal_popup", "Update Successful", "\nYour save file has been successfully updated to version {{b}}{}{{/b}}.\n\n{}".format(config.version, message), None, "Hurray!") - renpy.block_rollback() - return - -init python: - config.after_load_callbacks.append(version_patch) - label before_main_menu(): - # Add screen + python: + if settings.get("updates"): + CheckUpdates(onetime=True, autostart=False)() return diff --git a/updates/.gitignore b/update/.gitignore similarity index 83% rename from updates/.gitignore rename to update/.gitignore index 5e7d2734..1cb60dae 100644 --- a/updates/.gitignore +++ b/update/.gitignore @@ -2,3 +2,4 @@ * # Except this file !.gitignore +!generic.webp diff --git a/update/generic.webp b/update/generic.webp new file mode 100644 index 00000000..9d8428eb --- /dev/null +++ b/update/generic.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33f6a96278428c4c01ea6025de11825b9c9593359b7246fb1f3d6be729d55d63 +size 242670