init python early:
    # Import commonly used python modules
    import time
    import datetime
    import math
    import random
    import pygame
    import fnmatch
    import posixpath
    import re
    import string
    import functools
    import timeit as timeit_module
    from bisect import bisect
    from operator import itemgetter
    from operator import add as _add
    from collections import OrderedDict

    get_volume_preference = renpy.game.preferences.get_volume

    def num_to_word(n, readable=True):
        """Transcript numbers (integers) into readable words."""
        n = int(n)
        units = ("","one","two","three","four","five","six","seven","eight","nine")
        teens = ("","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen")
        tens = ("","ten","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety")
        thousands = ("","thousand","million","billion","trillion","quadrillion","quintillion","sextillion","septillion","octillion","nonillion","decillion","undecillion","duodecillion","tredecillion","quattuordecillion","sexdecillion","septendecillion","octodecillion","novemdecillion","vigintillion")

        output = []
        if n == 0:
            output.append("zero")
        else:
            s = str(n)
            groups = (len(s)+2)//3
            s = s.zfill(groups*3)

            for i in range(0, groups*3, 3):
                h,t,u = int(s[i]), int(s[i+1]), int(s[i+2])
                g = groups-(i//3+1)

                if h > 0:
                    output.append(units[h]+" hundred")
                if t > 1:
                    if u > 0:
                        output.append(tens[t]+"-"+units[u])
                    else:
                        output.append(tens[t])
                elif t == 1:
                    if u > 0:
                        output.append(teens[u])
                    else:
                        output.append(tens[t])
                else:
                    if u > 0:
                        output.append(units[u])

                if g > 0 and (h+t+u) > 0:
                    if i == (groups*3)-6:
                        output.append(thousands[g]+" and")
                    else:
                        output.append(thousands[g]+",")

        if readable:
            output = " ".join(output)
        return output

    def clamp(n, smallest, largest):
        return max(smallest, min(n, largest))

    def white_tint(image):
        return Transform( image, matrixcolor=TintMatrix((1.1, 1.1, 1.1)) )

    def gray_tint(image):
        return Transform( image, matrixcolor=SaturationMatrix(0.0) )

    def yellow_tint(image):
        return Transform( image, matrixcolor=TintMatrix((1.2, 1.1, 0.7)) )

    def image_hover(image, brightness=0.12):
        """Returns slightly brighter image used during hover events"""
        return Transform( image, matrixcolor=BrightnessMatrix(brightness) )

    def image_alpha(image, alpha=0.5):
        """Returns an image with changed alpha 0 - fully transparent 1 - fully visible"""
        return Transform( image, matrixcolor=OpacityMatrix(alpha) )

    def set_clipboard(txt):
        txt = str(txt)
        pygame.scrap.put(pygame.scrap.SCRAP_TEXT, txt.encode())

    def get_clipboard():
        clipboard = pygame.scrap.get(pygame.scrap.SCRAP_TEXT)
        if clipboard:
            return clipboard.decode()
        return None

    def evaluate(txt):
        try:
            return __import__('ast').literal_eval(txt)
        except Exception as e:
            print("Error evaluating data:")
            print(e)

    def reset_variables(*args):
        """Resets the given variables to their default values."""
        # Refer to renpy.ast.Default.set_default for implementation details
        defaults_set = renpy.store._defaults_set
        changed_set = renpy.store.__dict__.ever_been_changed
        for arg in args:
            if arg in defaults_set:
                if arg in changed_set:
                    defaults_set.remove(arg)
                    changed_set.remove(arg)
            elif config.developer:
                raise Exception("The variable `{}` was not previously set with a default value.".format(arg))
        renpy.execute_default_statement(False)

    def disable_game_menu():
        setattr(renpy.store, "_game_menu_screen", None)

    def enable_game_menu():
        setattr(renpy.store, "_game_menu_screen", "save_screen")

    def make_revertable(obj):
        if isinstance(obj, _list):
            return [make_revertable(x) for x in obj]
        elif isinstance(obj, _dict):
            return dict((make_revertable(k), make_revertable(v)) for (k,v) in obj.items())
        else:
            return obj

    def is_integer(s):
        s = str(s)
        if not s:
            return False
        if s[0] in ("-", "+"):
            # calling lstrip("0+-") would be faster but not exactly identical
            s = s[1:]
        if s.lstrip("0").isdigit():
            return True
        return False

    def timeit(func, loops=10000, args=(), kwargs={}):
        rv = timeit_module.timeit("func(*args, **kwargs)", number=loops, globals=dict(func=func, args=args, kwargs=kwargs))
        print(f"The task has taken {rv} seconds to finish")

    def autorange(func, args=(), kwargs={}):
        loops, time = timeit_module.Timer("func(*args, **kwargs)", globals=dict(func=func, args=args, kwargs=kwargs)).autorange()
        print(f"The task has taken {time/loops} seconds to finish ({loops} iterations in {time} seconds)")

    def list_swap_values(l, val1, val2):
        """Mutates the original list."""
        l[val1], l[val2] = l[val2], l[val1]

    def natsort_key(s, pattern=re.compile("([0-9]+)")):
        return [int(t) if t.isdigit() else t.lower() for t in pattern.split(str(s))]

    def tts(s):
        renpy.display.tts.tts(str(s))

    def is_in_lead(house):
        if isinstance(house, str):
            house = getattr(renpy.store, house)

        return (house == max(gryffindor, slytherin, ravenclaw, hufflepuff))

    def play_potion_return(who):
        if game.daytime:
            return

        for i in inventory.get_instances_of_type("potion"):
            if not who in i.usable_on:
                continue

            if not i.in_progress[who]:
                continue

            i.ret(who)

    def strip(s):
        # We need a custom strip implementation because we cannot tell
        # if the raw argument isn't encapsulated in double, or triple quotes
        if s.startswith(('"', "'")) and s.endswith(('"', "'")):
            return s[1:-1]
        return s

    def matches(s1, s2, filter=" "):
        return s1.replace(filter, "") == s2.replace(filter, "")

    def istype(inst, clss):
        if isinstance(clss, (list, tuple, set)):
            return type(inst) in clss
        return type(inst) is clss

    class IntLike(python_object):
        # Does not support rollback

        def __init__(self, callable):
            self._callable = callable

        def __call__(self):
            return self._callable()

        def __repr__(self):
            return repr(self())

        def __add__(self, value):
            raise Exception("IntLike does not support add")

        def __eq__(self, value):
            return self._callable() == value

        def __gt__(self, value):
            return self._callable() > value

        def __lt__(self, value):
            return self._callable() < value

        def __ge__(self, value):
            return self.__gt__(value) or self.__eq__(value)

        def __le__(self, value):
            return self.__lt__(value) or self.__eq__(value)

        def __len__(self):
            return len(self._callable())

    def execute_callbacks(callbacks):
        for callback in callbacks: callback()

    def extract_number(key):
        match = re.match(r'^(\d+)', key)
        if match:
            return int(match.group(1))
        return float('inf') # Return a large number for non-numeric keys
