CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/main.py
2025-03-03 23:00:33 +01:00

604 lines
18 KiB
Python

# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import renpy.display
import renpy.style
import renpy.sl2
import renpy.test
import renpy.game as game
import os
import sys
import time
import zipfile
import gc
import __main__
last_clock = time.time()
def log_clock(s):
global last_clock
now = time.time()
s = "{} took {:.2f}s".format(s, now - last_clock)
renpy.display.log.write(s)
if renpy.android and not renpy.config.log_to_stdout:
print(s)
# Pump the presplash window to prevent marking
# our process as unresponsive by OS
renpy.display.presplash.pump_window()
last_clock = now
def reset_clock():
global last_clock
last_clock = time.time()
def run(restart):
"""
This is called during a single run of the script. Restarting the script
will cause this to change.
"""
reset_clock()
# Reset the store to a clean version of itself.
renpy.python.clean_stores()
log_clock("Cleaning stores")
# Init translation.
renpy.translation.init_translation()
log_clock("Init translation")
# Rebuild the various style caches.
renpy.style.build_styles() # @UndefinedVariable
log_clock("Build styles")
renpy.sl2.slast.load_cache()
log_clock("Load screen analysis")
# Analyze the screens.
renpy.display.screen.analyze_screens()
log_clock("Analyze screens")
if not restart:
renpy.sl2.slast.save_cache()
log_clock("Save screen analysis")
# Prepare the screens.
renpy.display.screen.prepare_screens()
log_clock("Prepare screens")
if not restart:
renpy.pyanalysis.save_cache()
log_clock("Save pyanalysis.")
renpy.game.script.save_bytecode()
log_clock("Save bytecode.")
# Handle arguments and commands.
if not renpy.arguments.post_init():
renpy.exports.quit()
if renpy.config.clear_lines:
renpy.scriptedit.lines.clear()
# Sleep to finish the presplash.
renpy.display.presplash.sleep()
# Re-Initialize the log.
game.log = renpy.python.RollbackLog()
# Switch contexts, begin logging.
game.contexts = [ renpy.execution.Context(True) ]
# Jump to an appropriate start label.
if game.script.has_label("_start"):
start_label = '_start'
else:
start_label = 'start'
game.context().goto_label(start_label)
try:
renpy.exports.log("--- " + time.ctime())
renpy.exports.log("")
except:
pass
# Note if this is a restart.
renpy.store._restart = restart
# We run until we get an exception.
renpy.display.interface.enter_context()
log_clock("Running {}".format(start_label))
renpy.execution.run_context(True)
def load_rpe(fn):
zfn = zipfile.ZipFile(fn)
autorun = zfn.read("autorun.py")
zfn.close()
sys.path.insert(0, fn)
exec autorun in dict()
def choose_variants():
if "RENPY_VARIANT" in os.environ:
renpy.config.variants = list(os.environ["RENPY_VARIANT"].split()) + [ None ]
return
renpy.config.variants = [ None ]
if renpy.android: # @UndefinedVariable
renpy.config.variants.insert(0, 'mobile')
renpy.config.variants.insert(0, 'android')
import android # @UnresolvedImport
import math
import pygame_sdl2 as pygame
from jnius import autoclass # @UnresolvedImport
# Manufacturer/Model-specific variants.
try:
Build = autoclass("android.os.Build")
manufacturer = Build.MANUFACTURER
model = Build.MODEL
print("Manufacturer", manufacturer, "model", model)
if manufacturer == "Amazon" and model.startswith("AFT"):
print("Running on a Fire TV.")
renpy.config.variants.insert(0, "firetv")
except:
pass
# Are we running on OUYA or Google TV or something similar?
package_manager = android.activity.getPackageManager()
if package_manager.hasSystemFeature("android.hardware.type.television"):
print("Running on a television.")
renpy.config.variants.insert(0, "tv")
renpy.config.variants.insert(0, "small")
return
# Otherwise, a phone or tablet.
renpy.config.variants.insert(0, 'touch')
pygame.display.init()
info = renpy.display.get_info()
diag = math.hypot(info.current_w, info.current_h) / android.get_dpi()
print("Screen diagonal is", diag, "inches.")
if diag >= 6:
renpy.config.variants.insert(0, 'tablet')
renpy.config.variants.insert(0, 'medium')
else:
renpy.config.variants.insert(0, 'phone')
renpy.config.variants.insert(0, 'small')
elif renpy.ios:
renpy.config.variants.insert(0, 'ios')
renpy.config.variants.insert(0, 'touch')
from pyobjus import autoclass # @UnresolvedImport @Reimport
UIDevice = autoclass("UIDevice")
idiom = UIDevice.currentDevice().userInterfaceIdiom
print("iOS device idiom", idiom)
# idiom 0 is iPhone, 1 is iPad. We assume any bigger idiom will
# be tablet-like.
if idiom >= 1:
renpy.config.variants.insert(0, 'tablet')
renpy.config.variants.insert(0, 'medium')
else:
renpy.config.variants.insert(0, 'phone')
renpy.config.variants.insert(0, 'small')
elif renpy.emscripten:
import emscripten, re
# web
renpy.config.variants.insert(0, 'web')
# mobile
userAgent = emscripten.run_script_string(r'''navigator.userAgent''')
mobile = re.search('Mobile|Android|iPad|iPhone', userAgent)
if mobile:
renpy.config.variants.insert(0, 'mobile')
# Reserve android/ios for when the OS API is exposed
#if re.search('Android', userAgent):
# renpy.config.variants.insert(0, 'android')
#if re.search('iPad|iPhone', userAgent):
# renpy.config.variants.insert(0, 'ios')
# touch
touch = emscripten.run_script_int(r'''
('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0)''')
if touch == 1:
# mitigate hybrids (e.g. ms surface) by restricting touch to mobile
if mobile:
renpy.config.variants.insert(0, 'touch')
# large/medium/small
# tablet/phone
# screen.width/height is auto-adjusted by browser,
# so it can be used as a physical sizereference
# (see also window.devicePixelRatio)
# e.g. Galaxy S5:
# - physical / OpenGL: 1080x1920
# - web screen: 360x640 w/ devicePixelRatio=3
ref_width = emscripten.run_script_int(r'''screen.width''')
ref_height = emscripten.run_script_int(r'''screen.height''')
# medium reference point: ipad 1024x768, ipad pro 1336x1024 (browser "pixels")
if mobile:
if (ref_width < 768 or ref_height < 768):
renpy.config.variants.insert(0, 'small')
renpy.config.variants.insert(0, 'phone')
else:
renpy.config.variants.insert(0, 'medium')
renpy.config.variants.insert(0, 'tablet')
else:
renpy.config.variants.insert(0, 'large')
else:
renpy.config.variants.insert(0, 'pc')
renpy.config.variants.insert(0, 'large')
def main():
log_clock("Bootstrap to the start of init.init")
renpy.game.exception_info = 'Before loading the script.'
# Get ready to accept new arguments.
renpy.arguments.pre_init()
# Init the screen language parser.
renpy.sl2.slparser.init()
# Init the config after load.
renpy.config.init()
# Set up variants.
choose_variants()
renpy.display.touch = "touch" in renpy.config.variants
log_clock("Early init")
# Note the game directory.
game.basepath = renpy.config.gamedir
renpy.config.searchpath = [ renpy.config.gamedir ]
# Find the common directory.
commondir = __main__.path_to_common(renpy.config.renpy_base) # E1101 @UndefinedVariable
if os.path.isdir(commondir):
renpy.config.searchpath.append(commondir)
renpy.config.commondir = commondir
else:
renpy.config.commondir = None
# Add path from env variable, if any
if "RENPY_SEARCHPATH" in os.environ:
renpy.config.searchpath.extend(os.environ["RENPY_SEARCHPATH"].split("::"))
if renpy.android:
renpy.config.searchpath = [ ]
renpy.config.commondir = None
if "ANDROID_PUBLIC" in os.environ:
android_game = os.path.join(os.environ["ANDROID_PUBLIC"], "game")
print("Android searchpath: ", android_game)
if os.path.exists(android_game):
renpy.config.searchpath.insert(0, android_game)
# Load Ren'Py extensions.
for dir in renpy.config.searchpath: # @ReservedAssignment
for fn in os.listdir(dir):
if fn.lower().endswith(".rpe"):
load_rpe(dir + "/" + fn)
# The basename is the final component of the path to the gamedir.
for i in sorted(os.listdir(renpy.config.gamedir)):
if not i.endswith(".rpa"):
continue
i = i[:-4]
renpy.config.archives.append(i)
renpy.config.archives.reverse()
# Initialize archives.
renpy.loader.index_archives()
# Start auto-loading.
renpy.loader.auto_init()
log_clock("Loader init")
# Initialize the log.
game.log = renpy.python.RollbackLog()
# Initialize the store.
renpy.store.store = sys.modules['store']
# Set up styles.
game.style = renpy.style.StyleManager() # @UndefinedVariable
renpy.store.style = game.style
# Run init code in its own context. (Don't log.)
game.contexts = [ renpy.execution.Context(False) ]
game.contexts[0].init_phase = True
renpy.execution.not_infinite_loop(60)
# Load the script.
renpy.game.exception_info = 'While loading the script.'
renpy.game.script = renpy.script.Script()
if renpy.session.get("compile", False):
renpy.game.args.compile = True
# Set up error handling.
renpy.exports.load_module("_errorhandling")
if renpy.exports.loadable("tl/None/common.rpym") or renpy.exports.loadable("tl/None/common.rpymc"):
renpy.exports.load_module("tl/None/common")
renpy.config.init_system_styles()
renpy.style.build_styles() # @UndefinedVariable
log_clock("Loading error handling")
# If recompiling everything, remove orphan .rpyc files.
# Otherwise, will fail in case orphan .rpyc have same
# labels as in other scripts (usually happens on script rename).
if (renpy.game.args.command == 'compile') and not (renpy.game.args.keep_orphan_rpyc): # @UndefinedVariable
for (fn, dn) in renpy.game.script.script_files:
if dn is None:
continue
if not os.path.isfile(os.path.join(dn, fn + ".rpy")):
try:
name = os.path.join(dn, fn + ".rpyc")
os.rename(name, name + ".bak")
except OSError:
# This perhaps shouldn't happen since either .rpy or .rpyc should exist
pass
# Update script files list, so that it doesn't contain removed .rpyc's
renpy.loader.cleardirfiles()
renpy.game.script.scan_script_files()
# Load all .rpy files.
renpy.game.script.load_script() # sets renpy.game.script.
log_clock("Loading script")
if renpy.game.args.command == 'load-test': # @UndefinedVariable
start = time.time()
for i in range(5):
print(i)
renpy.game.script = renpy.script.Script()
renpy.game.script.load_script()
print(time.time() - start)
sys.exit(0)
renpy.game.exception_info = 'After loading the script.'
# Find the save directory.
if renpy.config.savedir is None:
renpy.config.savedir = __main__.path_to_saves(renpy.config.gamedir) # E1101 @UndefinedVariable
if renpy.game.args.savedir: # @UndefinedVariable
renpy.config.savedir = renpy.game.args.savedir # @UndefinedVariable
# Init preferences.
game.persistent = renpy.persistent.init()
game.preferences = game.persistent._preferences
for i in renpy.game.persistent._seen_translates: # @UndefinedVariable
if i in renpy.game.script.translator.default_translates:
renpy.game.seen_translates_count += 1
if game.persistent._virtual_size:
renpy.config.screen_width, renpy.config.screen_height = game.persistent._virtual_size
# Init save locations and loadsave.
renpy.savelocation.init()
# We need to be 100% sure we kill the savelocation thread.
try:
# Init save slots.
renpy.loadsave.init()
log_clock("Loading save slot metadata.")
# Load persistent data from all save locations.
renpy.persistent.update()
game.preferences = game.persistent._preferences
log_clock("Loading persistent")
# Clear the list of seen statements in this game.
game.seen_session = { }
# Initialize persistent variables.
renpy.store.persistent = game.persistent
renpy.store._preferences = game.preferences
renpy.store._test = renpy.test.testast._test
if renpy.parser.report_parse_errors():
raise renpy.game.ParseErrorException()
renpy.game.exception_info = 'While executing init code:'
for _prio, node in game.script.initcode:
if isinstance(node, renpy.ast.Node):
renpy.game.context().run(node)
else:
# An init function.
node()
renpy.game.exception_info = 'After initialization, but before game start.'
# Check if we should simulate android.
renpy.android = renpy.android or renpy.config.simulate_android # @UndefinedVariable
# Re-set up the logging.
renpy.log.post_init()
# Run the post init code, if any.
for i in renpy.game.post_init:
i()
renpy.game.script.report_duplicate_labels()
# Sort the images.
renpy.display.image.image_names.sort()
game.persistent._virtual_size = renpy.config.screen_width, renpy.config.screen_height
log_clock("Running init code")
renpy.pyanalysis.load_cache()
log_clock("Loading analysis data")
# Analyze the script and compile ATL.
renpy.game.script.analyze()
renpy.atl.compile_all()
log_clock("Analyze and compile ATL")
# Index the archive files. We should not have loaded an image
# before this point. (As pygame will not have been initialized.)
# We need to do this again because the list of known archives
# may have changed.
renpy.loader.index_archives()
log_clock("Index archives")
# Check some environment variables.
renpy.game.less_memory = "RENPY_LESS_MEMORY" in os.environ
renpy.game.less_mouse = "RENPY_LESS_MOUSE" in os.environ
renpy.game.less_updates = "RENPY_LESS_UPDATES" in os.environ
renpy.dump.dump(False)
renpy.game.script.make_backups()
log_clock("Dump and make backups.")
# Initialize image cache.
renpy.display.im.cache.init()
log_clock("Cleaning cache")
# Make a clean copy of the store.
renpy.python.make_clean_stores()
log_clock("Making clean stores")
gc.collect()
if renpy.config.manage_gc:
gc.set_threshold(*renpy.config.gc_thresholds)
gc_debug = int(os.environ.get("RENPY_GC_DEBUG", 0))
if renpy.config.gc_print_unreachable:
gc_debug |= gc.DEBUG_SAVEALL
gc.set_debug(gc_debug)
log_clock("Initial gc.")
# Start debugging file opens.
renpy.debug.init_main_thread_open()
# (Perhaps) Initialize graphics.
if not game.interface:
renpy.display.core.Interface()
log_clock("Creating interface object")
# Start things running.
restart = None
while True:
if restart:
renpy.display.screen.before_restart()
try:
try:
run(restart)
finally:
restart = (renpy.config.end_game_transition, "_invoke_main_menu", "_main_menu")
renpy.persistent.update(True)
except game.FullRestartException as e:
restart = e.reason
finally:
# Flush any pending interface work.
renpy.display.interface.finish_pending()
# Give Ren'Py a couple of seconds to finish saving.
renpy.loadsave.autosave_not_running.wait(3.0)
finally:
gc.set_debug(0)
renpy.loader.auto_quit()
renpy.savelocation.quit()
renpy.translation.write_updated_strings()
# This is stuff we do on a normal, non-error return.
if not renpy.display.error.error_handled:
renpy.display.render.check_at_shutdown()