# LiveCheatMaker by agregen  v0.3.3b (2023-09-06)

init 999 python:
  config.console = True

  class _LiveCheatMaker (__builtins__['object']):
    version = "v0.3.3b"

    TOGGLE_KEY = ['0', 'o', 'k', 'K', 'm', 'M']
    BACKGROUND = "#111b"
    MARGIN     = 100, 100
    PADDING    = 10
    EDGESCROLL = 50, 500
    UI_LAYER = 'screens' # this is default, but some devs render stuff wherever :-(
    SHOW    = '_live_cheat_maker_menu'
    INPUT   = '_live_cheat_maker_input'
    CONFIRM = '_live_cheat_maker_confirm'
    BOOL_IS_INT = False

    EDIT = object()
    LOOKUP = object()
    BY_TYPE = object()

    import re, collections
    list    = __builtins__['list']
    dict    = __builtins__['dict']
    set     = __builtins__['set']
    str     = __builtins__['str']
    unicode = __builtins__.get('unicode', __builtins__['str'])

    def __init__ (_): # _ is short for self
      try: config.layers.insert(config.layers.index(_.UI_LAYER), 'live-cheat-maker')
      except: config.layers += ['live-cheat-maker'] # to hell with it, let's render wherever too
      _LiveCheatMaker.TOGGLE_KEY = (_.TOGGLE_KEY if _.is_list(_.TOGGLE_KEY) else [_.TOGGLE_KEY])
      config.keymap['live_cheat_maker'] = _.TOGGLE_KEY
      config.underlay.append( renpy.Keymap(live_cheat_maker=_.open) )
      try:    _.integer = (int, long)
      except: _.integer = (int,)
      _.number = (float,) + _.integer
      _.string = (_.str, _.unicode)
      _.editable = (bool,) + _.number + _.string
      _.scalar = (type(None),) + _.editable
      _.set_ = (_.set, set, frozenset)
      _.collection = (tuple, _.list, _.dict)
      _.MenuEntry = _.collections.namedtuple('MenuEntry', "caption action bar")
      _.Value = _.Value(_)
      _.Var = _.Var(_, _.Value)
      _.menu = _.CustomMenu(_, _.Var, _.Value, "game/-LiveCheatMaker.menu", title="Cheat menu "+_.version)
      _.reset()

    def menu_entry (_, caption, action=None, bar=None):
      return (_.menu_entry(*caption) if _.is_tuple(caption) else _.MenuEntry(_.escape(caption), action, bar))

    def open (_):
      _.path = []
      if renpy.can_rollback(): renpy.checkpoint()
      renpy.call_in_new_context(_.SHOW)

    Function = lambda _, *a, **kw:         [Function(*a, **kw), Jump(_.SHOW)]
    Nav      = lambda _, s=None:           _.Function(_.nav, s)
    SetVars  = lambda _, vars, value=None: _.Function(_.set_vars, vars, value)

    def Confirm (_, prompt, then, otherwise=None):
      def _confirm ():
        renpy.call_screen(next(s for s in ['confirm', 'yesno_prompt'] if renpy.has_screen(s)), # depends on Ren'Py version
                          message=_.escape(prompt), yes_action=then, no_action=otherwise or Return(False))
      def _set (): _.confirm = _confirm
      return [Function(_set), Jump(_.CONFIRM)]

    def Input (_, prompt, then, default="", convert=lambda x: x):
      def _input ():
        if hasattr(config, 'disable_input'): config.disable_input = False
        result = renpy.input(_.escape(prompt), default=default)
        try:
          then[0](*(list(then[1:]) + [convert(result)]))
        except Exception as e:
          print(_.type_(e) + ":", e)
          renpy.say(None, _.escape(_.split_caps( _.type_(e) ) + ": " + str(e)))
      def _set (): _.input = _input
      return [Function(_set), Jump(_.INPUT)]

    is_tuple   = lambda _, x: isinstance(x, tuple)
    is_list    = lambda _, x: isinstance(x, _.list)
    is_dict    = lambda _, x: isinstance(x, _.dict)
    is_set     = lambda _, x: isinstance(x, _.set_)
    is_scalar  = lambda _, x: isinstance(x, _.scalar)
    is_string  = lambda _, x: isinstance(x, _.string)
    is_number  = lambda _, x: isinstance(x, _.number)  and not _.is_bool(x)
    is_integer = lambda _, x: isinstance(x, _.integer) and not _.is_bool(x)
    is_float   = lambda _, x: isinstance(x, float)
    is_bool    = lambda _, x: isinstance(x, bool)

    type_ = lambda _, x: type(x).__name__
    # like repr() but doesn't hex-encode non-ASCII characters
    urepr = lambda _, x: (u'u"%s"' % _.re.sub(r'(["\\])', r'\\\1', x) if "'" in x and '"' not in x else
                          u"u'%s'" % _.re.sub(r"(['\\])", r'\\\1', x))
    repr_ = lambda _, x: (_.urepr if isinstance(x, _.unicode) else repr)(x)
    escape = lambda _, s: s and u"\u200B{#ZWSP}"+s.replace('[', "[[").replace('{', "{{")
    split_caps = lambda _, s: _.re.sub(r'(.)([A-Z])', r'\1 \2', s)   # 'ValueError' -> "Value Error"

    def reset (_):
      _.path, _.move, _.resume, _.Var.all_vars = [], None, None, False

    def goto (_, path):
      _.path, _.resume = path, None

    def nav (_, key=None):
      if key is not None:
        _.path += [key]
      elif _.path:
        _.path.pop()

    def menu_add_var (_, var, name):
      if not name: raise ValueError("Empty input!")
      _.menu.add_var(name, var)

    def menu_add_var_move (_, var, name):
      _.menu_add_var(var, name)
      _.resume, _.path, _.move = _.path, [_.EDIT], [_.menu, _.menu[-1]]

    def set_vars (_, vars, value=None):
      if value is not None:
        _.Var.set(value, *(x['var'] for x in vars))
      else: # set each separately
        for x in vars:
          if 'value' in x: _.Var.set(x['value'], x['var'])

    def read_vars (_, vars, s):
      for var in vars:
        _.Var.set(_.Value.parse(s, _.Var.get(var)), var)

    def add_item (_, path, kind, name):
      if not name: raise ValueError("Empty input!")
      _.menu.add(path[-1], kind, name)

    def rename_item (_, path, name):
      if not name: raise ValueError("Empty input!")
      if _.menu.rename(path[-2], path[-1], name):
        _.path[-1] = name
      _.move = None

    def delete_item (_, path):
      _.menu.remove(path[-2], path[-1])
      _.move = None
      _.nav()

    def item_value (_, path, vtype, s):
      _.menu.set_value(path[-1], s or None, vtype)

    def set_range (_, path, i, s):
      _.menu.set_range(path[-1], i, int(s))

    def unset_range (_, path):
      _.menu.set_range(path[-1], None)

    def set_move (_, move):
      _.move = move

    def toggle_all_vars (_):
      _.Var.all_vars = not _.Var.all_vars

    def move_to (_, path, after=None):
      if path[:len(_.move)] == _.move:
        print("Invalid move target!")
      elif after is not None and after not in _.menu.children(path[-1]):
        print("Invalid previous value!")
      else:
        _.menu.move(_.move, path, after)
        _.move = None
        if after is not None: _.nav()

    def _with_refresh (_, f):   # this workaround is required for screen refresh
      def g (*a, **kw):
        f(*a, **kw)
        _.refresh()
      return g

    def _bar_fix (_, bar):
      bar.changed = _._with_refresh(bar.changed)
      return bar

    def bar (_, item):
      _var, _range = item['var'], item.get('range')
      _value = _.Var.get(_var)
      if _range and _.is_integer(_value):
        (_min, _max), k, _parent = _range, _var[-1], _.Var.get(_var[:-1])
        return _._bar_fix((FieldValue if _.is_string(k) else DictValue)
                            (_parent, (k if _.is_string(k) else k[0]), _max-_min, offset=_min, step=1))

    def _edit_value (_, var, label=None, bar=None):
      _name, _value = _.Var.name(var), _.Var.get(var)
      _btn = lambda s, default="": (label or " = Edit", _.Input(s, default=default, then=(_.read_vars, [var])), bar)
      return (_btn("Set %s" % _name, _value)                                 if _.is_string(_value) else
              (label or " = Toggle", _.Function(_.Var.set, not _value, var)) if _.is_bool(_value) else
              _btn("Set %s (X or =X to set, +X or -X to modify)" % _name)    if _.is_number(_value) else
              label and (label, NullAction()))

    def lookup_menu (_):
      _var = ([]         if len(_.path) == 1 else
              _.path[3:] if _.path[1] is _.BY_TYPE else
              _.path[2:])
      _value = _.Var.get(_var)
      _sorted = lambda xs: sorted(xs, key=lambda s: (str(s).startswith('_'), s))
      _issubclass = lambda t, ts: issubclass(t, ts) and (_.BOOL_IS_INT or t is ts or t is not bool)
      _var_btn = lambda s, x: (("%s = %s" % (s, _.repr_(x)) if _.is_scalar(x) else "%s: %s" % (s, _typename( type(x) ))),
                               _.Nav(s))
      _kinds = _.collections.OrderedDict([(_.number, "<numbers>"), (_.string, "<strings>"), (_.collection+_.set_, "<collections>")])
      _typename = lambda t: _kinds.get(t) or ("{}" if t.__module__ == '__builtin__' else "{} ({})").format(t.__name__, t.__module__)
      _toggle_vars = [(("(Hide" if _.Var.all_vars else "(Show") + " unsaved variables)", _.Function(_.toggle_all_vars)), ""]
      if len(_.path) == 1:                                                # Lookup menu
        _search = "Type search term (start with double-quote to filter by prefix, end with double-quote to filter by suffix)"
        return "Lookup variable", [("Search",                    _.Input(_search, convert=_.unicode, then=[_.nav])),
                                   ("Search (case-insensitive)", _.Input(_search, convert=_.str, then=[_.nav])),
                                   ("By current value type",     _.Nav(_.BY_TYPE))]
      elif len(_.path) == 2 and _.path[1] is _.BY_TYPE:                   # List of types in store
        _types, _all_kinds = _.collections.Counter(type(_.Var[k]) for k in _.Var), tuple(_kinds.keys())
        _counts = {ts: sum(_types[t] for t in _types if _issubclass(t, ts)) for ts in _kinds}
        _builtin = lambda x: [t for t in _types if (t.__module__ == '__builtin__') is x and not _issubclass(t, _all_kinds)]
        _typelist = (list(_all_kinds) + sorted(_builtin(True), key=_typename) + sorted(_builtin(False), key=_typename))
        _types.update(_counts)
        return ("Lookup by current value type",
                _toggle_vars + [("%s: %d" % (_typename(t), _types[t]), _.Nav(t)) for t in _typelist if _types[t] > 0])
      elif len(_.path) == 3 and _.path[1] is _.BY_TYPE:                   # Browsing a type
        _check = ((lambda t: _issubclass(t, _.path[2])) if _.path[2] in _kinds else (lambda t: t is _.path[2]))
        _keys = [k for k in _.Var if _check( type(_.Var[k]) )]
        return "Lookup: " + _typename(_.path[2]), _toggle_vars + [_var_btn(k, _.Var[k]) for k in _sorted(_keys)]
      elif len(_.path) == 2 and _.is_string(_.path[1]):                   # Filtered store
        _keys = [k for k in _.Var if _.Value.match(_.path[1], k)]
        return "Search: " + _.repr_(_.path[1]), _toggle_vars + [_var_btn(k, _.Var[k]) for k in _sorted(_keys)]
      elif _.is_scalar(_value):                                           # Showing a single value
        _name = _.Var.name(_var)
        return _name, [
          "= " + _.repr_(_value),
          _._edit_value(_var),
        ] + ([] if not isinstance(_value, _.editable) else [
          (" + Add to menu",          _.Input("Name for "+_name, default=_name, then=(_.menu_add_var, _var))),
          (" + Add to menu and move", _.Input("Name for "+_name, default=_name, then=(_.menu_add_var_move, _var))),
        ])
      elif _.is_set(_value):
        return "%s: %s" % (_.Var.name(_var), _typename( type(_value) )), [_.repr_(x) for x in sorted(_value)]
      else:                                                               # Listing contents of an object or collection
        _attr_btn = lambda s: _var_btn(s, getattr(_value, s))
        return ("%s: %s" % (_.Var.name(_var), _typename( type(_value) )),
                [_attr_btn(s) for s in _sorted( _.Value.fields_(_value) )] if not isinstance(_value, _.collection) else
                [_var_btn([s], _value[s]) for s in sorted( _.Value.keys_(_value) )]
                  + [_attr_btn(s) for s in sorted( _.Value.namedtuple_fields(_value) )])

    def edit_menu (_):
      _path = [_.menu] + _.path[1:]
      _item, _not_root = _path[-1], len(_path) > 1
      _name, _kind = _.menu.name(_item), _.menu.kind(_item)
      _is_menu, _is_group, _is_var, _is_text = [_kind is x for x in [_.menu.MENU, _.menu.GROUP, _.menu.VAR, _.menu.TEXT]]
      _can_insert = lambda path: (path[:len(_.move)] != _.move and _.menu.may_contain(path[-1], _.move[-1]))
      _current = (_.Var.get(_item['var']) if _is_var else None)
      _move = [
        _is_var and "{1} = {0}".format(_current, _.Var.name(_item['var'])),
        _not_root and _.move != _path and (" # Move…" + (" (forget previous)" if _.move else ""), _.Function(_.set_move, _path)),
        _.move and _can_insert(_path) and (" # …insert as first child", _.Function(_.move_to, _path)),
        (_.move and len(_path) > 1 and _path != _.move and _can_insert(_path[:-1]) and
          (" # …insert after (as neighbour)", _.Function(_.move_to, _path[:-1], _path[-1]))),
      ]
      _rename_add_delete = [
        (not _is_menu or len(_path) > 1) and (" = Rename", _.Input("New name", default=_name, then=(_.rename_item, _path))),
        _is_menu and (" + Add submenu", _.Input("Submenu name", then=(_.add_item, _path, _.menu.MENU))),
        _is_menu and (" + Add group", _.Input("Group name", then=(_.add_item, _path, _.menu.GROUP))),
        (_is_menu or _is_group) and (" + Add text", _.Input("New text", then=(_.add_item, _path, _.menu.TEXT))),
        (_not_root and not _.menu.children(_item) and
          (" – Delete", _.Confirm("Delete “%s”?" % _name, then=_.Function(_.delete_item, _path)))),
      ]
      _value = lambda default=None: _item.get('value', default)
      _delta = lambda: (_.is_number(_current) if _is_var else _.is_list( _item.get('value', []) ))
      _oftype = lambda type_check: _is_group and (_value() is None or type_check( _value() ))
      _hint = lambda t: (" (+/-, 1/0, y/n, yes/no, on/off, true/false)" if t is bool or _.is_bool(_current) else "")
      _action_value = ([] if _is_menu or _is_text else
                       [b and (" => Set/unset action value = %s%s" % (_.Value.repr_(_value()), s),
                               _.Input("New value"+(_hint(t) or s), default=_.Value.str_( _value("") ), then=(_.item_value, _path, t)))
                        for b, s, t in [(_is_var,               "",              None),
                                        (_delta(),              " (+/– number)", _.list),
                                        (_oftype(_.is_string),  " (string)",     unicode),
                                        (_oftype(_.is_integer), " (integer)",    int),
                                        (_oftype(_.is_float),   " (fraction)",   float),
                                        (_oftype(_.is_bool),    " (boolean)",    bool)]])
      _range = lambda: _item.get('range', (None, None))
      _set_range = ([] if not _is_var or not (_current is None or _.is_integer(_current)) else
        [b and (" * Set range %s = %s" % (s, _range()[i]),
                _.Input("New range "+s, default=_range()[i] or "", then=(_.set_range, _path, i)))
         for b, i, s in [(True, 1, "maximum"), (_item.get('range'), 0, "minimum")]] + [
        (_item.get('range') and (" * Unset range", _.Confirm("Delete range?", then=_.Function(_.unset_range, _path)))),
      ])
      _title = "Edit: " + ("~ %s ~" if _is_group else "%s") % _.menu.show_path(_path) + ("" if not _.move else "\n (Move: %s)" % _.menu.show_path(_.move))
      return _title, _move + _rename_add_delete + _action_value + _set_range + [""] + _.custom_menu(_item, edit=True)

    def custom_menu (_, item, edit=False):
      _name, _kind = _.menu.name, _.menu.kind
      _val = lambda x: _.repr_( _.Var.get(x['var']) )
      _value_set = lambda x: ("%s = %s => %s" % (x['name'], _val(x), _.Value.repr_(x['value'])),
                              _.SetVars([x], x['value']),  _.bar(x))
      _nav = lambda x: ((x if not edit else ('“'+x+'”', _.Nav(x))) if _kind(x) is _.menu.TEXT else
                        ("> %s" % (_name(x),),           _.Nav(x)) if _kind(x) is _.menu.MENU else
                        ("~ %s ~" % (_name(x),),         _.Nav(x)) if _kind(x) is _.menu.GROUP else
                        ("%s = %s" %(_name(x), _val(x)), _.Nav(x)) if edit else
                        _value_set(x)                              if 'value' in x else
                        _._edit_value(x['var'], "%s = %s" % (_name(x), _val(x)), _.bar(x)))
      return [_nav(x) for x in _.menu.children(item)]

    def refresh (_):
      _back = (_.menu_entry("Back", _.Nav(), True) if not (_.resume and _.path and len(_.path) == 1) else
               _.menu_entry("Resume", _.Function(_.goto, _.resume), True))
      if not _.path:                                            # root menu
        _back = _.menu_entry("Close", [Function(_.reset), Return(True)], True)
        _title = _.menu[0]
        _items = [("Lookup variable", _.Nav(_.LOOKUP)), ("Edit menu", _.Nav(_.EDIT)), ""] + _.custom_menu(_.menu)
      elif _.path[0] is _.LOOKUP:                               # lookup menu
        _title, _items = _.lookup_menu()
      elif _.path[0] is _.EDIT:                                 # edit menu
        _title, _items = _.edit_menu()
      else:                                                     # custom menu
        _current = _.path[-1]
        _is_group, _vars = (_.menu.kind(_current) is _.menu.GROUP), _.menu.children(_current, _.menu.VAR)
        _title = ("~ %s ~" if _is_group else "%s") % ("Cheat menu > " + _.menu.show_path(_.path))
        _group = lambda: [(("~All~ => %s" % _.Value.repr_(_current['value']), _.SetVars(_vars, _current['value'])) if 'value' in _current else
                           ("~All~", _.Input("Input value for * %s *" % _.menu.name(_current), then=(_.read_vars, [x['var'] for x in _vars])))),
                          any('value' in x for x in _vars) and ("~All~ => *", _.SetVars(_vars)),  ""]
        _items = (_group() if _is_group and _vars else []) + _.custom_menu(_current)
      _.back, _.title, _.items = _back, _.escape(_title), [_.menu_entry(x) for x in _items if x not in (False, None)]


    class CustomMenu (__builtins__['list']):
      """Custom menu wrapper. Supported item kinds: submenu, variable, group of variables, text."""
      MENU  = 'menu'    # ["title", …children]
      TEXT  = 'text'    # "title"
      GROUP = 'group'   # {'name': "title", 'group': […children], 'value': value?}
      VAR   = 'var'     # {'name': "title", 'var': var, 'value': value?, 'range': [min, max]?}
      import json, os

      def __init__ (_, lcm, var, value, filename, title):
        _.SHOW, _.filename = lcm.SHOW, _.os.path.join(config.basedir, filename)
        _.is_list, _.is_string, _.is_number, _.type_ = lcm.is_list, lcm.is_string, lcm.is_number, lcm.type_
        _._var_name = var.name
        _._get_var = lambda x: x and var.get(x)
        _._parse = value.parse
        _ += [title]
        try:
          with open(_.filename) as fin:
            _ += _.json.load(fin)
        except Exception as e: print(_.type_(e) + ":", e)
        _._mess_with_strings(_)

      def _mess_with_strings (_, item): # hopefully this will prevent *some* issues with duplicate strings
        _list = _._list(item)
        for i, x in enumerate(_list):
          if _.kind(x) is _.TEXT:
            _list[i] = u''+x
          else:
            _._mess_with_strings(x)

      def save (_):
        if not _.children(_):
          _.os.remove(_.filename)
        else:
          with open(_.filename, 'w') as fout:
            fout.write("[" + ",\n ".join(_.json.dumps(x) for x in _.children(_)) + "]\n")

      kind = lambda _, item: (_.MENU if _.is_list(item) else
                              _.TEXT if _.is_string(item) else
                              _.GROUP if 'group' in item else _.VAR)

      may_contain = lambda _, x, y: (_.kind(x) is _.MENU or (_.kind(x) is _.GROUP and _.kind(y) in (_.VAR, _.TEXT)))

      children = lambda _, x, kind=None: ([y for y in _.children(x) if _.kind(y) is kind] if kind else
                                          (_._list(x)[1:] if _.kind(x) is _.MENU else _._list(x)))
      _shift = lambda _, x: (1 if _.kind(x) is _.MENU else 0)
      _index = lambda _, parent, item: next((i+_._shift(parent) for i, x in enumerate(_.children(parent)) if x is item),
                                            None)

      def _list (_, x):
        _kind = _.kind(x)
        return (x if _kind is _.MENU else x['group'] if _kind is _.GROUP else [])

      def name (_, x):
        _kind = _.kind(x)
        return ("Menu" if x is _ else x[0] if _kind is _.MENU else x if _kind is _.TEXT else x['name'])

      show_path = lambda _, path, sep=" > ": sep.join(_.name(x) for x in path)

      def rename (_, parent, item, name):
        """Returns True if menu item was replaced with name"""
        if _.kind(item) is not _.TEXT:
          item[0 if _.kind(item) is _.MENU else 'name'] = name
          _.save()
        else:
          _._list(parent)[ _._index(parent, item) ] = name
          _.save()
          return True

      def remove (_, parent, item):
        _._list(parent).pop( _._index(parent, item) )
        _.save()

      def add (_, item, kind, name, var=None):
        if (var is None) == (kind is _.VAR): raise ValueError("Var is supplied for variables!")
        _new = {_.MENU: [name], _.TEXT: unicode(name), _.GROUP: {'name': name, 'group': []}, _.VAR: {'name': name, 'var': var}}
        _._list(item).append(_new[kind])
        _.save()

      add_var = lambda _, name, var: _.add(_, _.VAR, name, var)

      def move (_, fro, to, after=None):
        _item, _parent, _child = to[-1], fro[-2], fro[-1]
        _._list(_parent).pop( _._index(_parent, _child) )
        _._list(_item).insert((_._index(_item, after)+1 if after else _._shift(_item)), _child)
        _.save()

      def set_value (_, item, s, vtype=None):
        if s is None:
          item.pop('value', None)
        else:
          v = _._get_var( item.get('var') )
          item['value'] = _._parse(('='+s if _.is_number(vtype or type(v)) else s), v, vtype)
        _.save()

      def set_range (_, item, i, n=0):
        if i is None:
          item.pop('range')
        else:
          item.setdefault('range', [0, 0])[i] = n
        _.save()


    class Var (object):
      """Variable name representation: foo['bar'].baz[1] <-> ['foo', ['bar'], 'baz', [1]]"""
      state = store
      import re
      from functools import reduce

      def __init__ (_, lcm, value):
        _.is_list, _.type_, _._update_value = lcm.is_list, lcm.type_, value.update
        _.all_vars = False

      _saved       = lambda _: [_.re.sub(r'^store\.', "", s) for s in renpy.game.log.freeze()]
      keys         = lambda _: (dir(_.state) if _.all_vars else [s for s in _._saved() if s in _])
      vals         = lambda _: [_[k] for k in _]
      __len__      = lambda _: len( _.keys() )          # len(_)
      __iter__     = lambda _: iter( _.keys() )         # for k in _
      __getitem__  = lambda _, k: getattr(_.state, k)   # _[k]
      __contains__ = lambda _, k: hasattr(_.state, k)   # if k in _

      name = lambda _, var: _.re.sub(r'^\.', "", "".join(repr(x) if _.is_list(x) else ".%s" % (x,) for x in var))

      def get (_, var):
        try:
          return _.reduce(lambda x, k: (x[ k[0] ] if _.is_list(k) else getattr(x, k)),
                          var, _.state)
        except Exception as e: print(_.type_(e) + ":", e, repr(var))

      def set (_, value, *vars):
        for var in vars:
          try:
            k, o, v = var[-1], _.get(var[:-1]), _._update_value(value, _.get(var))
            if _.is_list(k):
              o[ k[0] ] = v
            else:
              setattr(o, k, v)
          except Exception as e: print(_.type_(e) + ":", e, repr(var))


    class Value (object):
      """Value processing logic here"""
      def __init__ (_, lcm):
        _.number, _.str, _.list = lcm.number, lcm.str, lcm.list
        _.is_list, _.is_tuple, _.is_dict, _._repr = lcm.is_list, lcm.is_tuple, lcm.is_dict, lcm.repr_

      str_ = lambda _, x, f=unicode, sep="": (sep.join( map(str, x) ) if _.is_list(x) else f(x))
      repr_ = lambda _, x, sep=" ": _.str_(x, _._repr, sep)
      keys_ = lambda _, x: (x.keys() if _.is_dict(x) else range( len(x) ))
      fields_ = lambda _, x: [k for k in dir(x) if hasattr(x, k)] # check for descriptors (may be listed but missing)
      update = lambda _, new, old: (new if not _.is_list(new) else old + new[1]*(-1 if new[0] == '-' else 1))

      def namedtuple_fields (_, x):
        try:
          if _.is_tuple(x) and all(hasattr(x, s) for s in x._fields):
            return x._fields
        except: pass
        return []

      def match (_, match, s):
        if type(match) is _.str:
          s, match = s.lower(), match.lower()
        return match in ("", '"') or (s == match[1:-1]        if match[0] == match[-1] == '"' else
                                      s.startswith(match[1:]) if match[0] == '"' else
                                      s.endswith(match[:-1])  if match[-1] == '"' else
                                      match in s)

      def parse (_, s, v, vtype=None):
        vtype = vtype or type(v)
        if vtype is _.list:             # special mode for delta value input ['+'/'-', number]
          s = s.strip()
          if not s: raise ValueError("Empty input!")
          c, s = s[0], s[1:].strip()
          if c not in "+-": raise ValueError("+/- expected, got: " + repr(c))
          try:    return [c, int(s)]
          except: return [c, float(s)]
        if vtype in _.number:
          s = s.strip()
          if not s: raise ValueError("Empty input!")
          c, s = ((s[0], s[1:]) if s[0] in "+-=" else ('=', s))
          return (0 if c == '=' else v) + vtype(s) * (-1 if c == '-' else 1)
        elif vtype is bool:
          s = s.strip()
          if s.lower() not in ('+', '-', '1', '0', 'y', 'n', 'yes', 'no', 'on', 'off', 'true', 'false'):
            raise ValueError("Don't know how to parse as boolean: " + repr(s))
          return s.lower() in ('+', '1', 'y', 'yes', 'on', 'true')
        else:
          return vtype(s)


  _LiveCheatMaker = _LiveCheatMaker()


label _live_cheat_maker_menu:
  $ _LiveCheatMaker.refresh()
  $ renpy.call_screen('_live_cheat_maker')
  return

label _live_cheat_maker_input:
  $ _LiveCheatMaker.input()
  jump _live_cheat_maker_menu

label _live_cheat_maker_confirm:
  $ _LiveCheatMaker.confirm()
  jump _live_cheat_maker_menu


screen _live_cheat_maker():
  layer 'live-cheat-maker'
  $ _title, _back, _pad = _LiveCheatMaker.title, _LiveCheatMaker.back, _LiveCheatMaker.PADDING
  frame:
    style_prefix 'choice'
    background _LiveCheatMaker.BACKGROUND
    margin _LiveCheatMaker.MARGIN
    padding _pad, _pad
    for k in _LiveCheatMaker.TOGGLE_KEY:
      key k action [Function(_LiveCheatMaker.reset), Return(True)]

    viewport:
      vbox:
        style 'menu'
        spacing 2

        text _title style 'menu_caption'
        textbutton _back.caption action _back.action xfill True text_xalign 0.0 text_italic True
        text "" style 'menu_caption' # vertical whitespace

        viewport:
          mousewheel True
          draggable True
          edgescroll _LiveCheatMaker.EDGESCROLL # scroll when a list edge is hovered
          scrollbars 'vertical'
          vscrollbar_unscrollable 'hide' # unfortunately this only sets transparency, layout isn't affected
          vscrollbar_xsize 12 # this might conflict with game theme, but if width isn't set at all, menu'd be unusable

          vbox:
            style 'menu'
            spacing 2

            for x in _LiveCheatMaker.items:
              if x.action:
                textbutton x.caption action x.action xfill True text_xalign 0.0
                if x.bar:
                  bar value x.bar xfill True ysize 24
              else:
                text x.caption style 'menu_caption'
