I asked a question in the forum a couple of weeks ago, but no one could answer my question exactly, so I began developing a module that solved the problem.
What is the problem? I want to used Object Oriented Programming with Ren'py, so that Ren'py automatically saves every attribute of both classes and instances and, if possible, support rollback that way.
It can be saved. It can be rollbacked (at least in the more or less simplest examples; I couldn't test everything because I don't know how rollback works and I don't have the time nor the objective to do it).
So, here it is. I leave the module to you; just follow the instructions at the beggining of the code.
Code: Select all
__author__ = "Alvaro Parafita (parafita.alvaro@gmail.com)"
"""
Creates a base class to ensure that all attributes of a user-defined
class are in a dictionary so that ren'py can store them accordingly.
It also enables rollback with complex variables
Usage:
* Copy this module as 'oop.py' in your game folder
* Copy this snippet in any .rpy script file:
init -999:
$ from oop import *
* Make all of your classes inherit from OOP
* When you need to use lists, tuples, dicts or sets use the ones defined in oop.py
Those are created calling List(), Tuple(), Dict(), Set() (passing as argument the values you want to add; check the module for more info on this)
These classes have all methods that list, tuple, dict or set use.
Caution:
* Don't create or delete classes during the game.
They must be defined in a init block.
* This only saves lists, tuples, dicts and sets. For any other structure, implement it yourself inheriting from OOP.
* Be careful with rollback. It's supposed to work but it could to some weird behaviour (I can't test every single case).
* This solution is meant to be functional, not optimal.
I didn't have more time to optimize anything (specially the structure definitions),
so it's sure to be potentially more optimal. Feel free to edit the code according to your needs.
"""
import renpy.store as store
ins_ids_expression = "__oop_ins_ids_%s" #%class name
cls_attr_expression = "__oop_cls_attr_%s_%s" #%(class_name, attr_name)
ins_attr_expression = "__oop_ins_attr_%s_%d_%s" #%(class_name, ins_ids, attr_name)
class OOPType(type):
def __new__(cls, name, parents, dct, *args, **kwargs):
store.__setattr__(ins_ids_expression%name, 0)
return super(OOPType, cls).__new__(cls, name, parents, dct)
def __getattr__(cls, name):
return store.__getattribute__(cls_attr_expression%(cls.__name__, name))
def __setattr__(cls, name, value):
store.__setattr__(cls_attr_expression%(cls.__name__, name), value)
def __delattr__(cls, name):
store.__delattr__(cls_attr_expression%(cls.__name__, name))
class OOP(object):
__metaclass__ = OOPType
def __new__(cls, *args, **kwargs):
ins = super(OOP, cls).__new__(cls)
name = cls.__name__
ids = store.__getattribute__(ins_ids_expression%name)
store.__setattr__(ins_ids_expression%name, ids + 1)
ins.__referrer__ = (name, ids)
ins.__oopattrs__ = set() #It can be a normal set cause it will only store strings
return ins
def __getattr__(self, name):
cls, ids = self.__referrer__
try:
return store.__getattribute__(ins_attr_expression%(cls, ids, name))
except:
raise AttributeError(name)
def __setattr__(self, name, value):
if name == "__referrer__":
super(OOP, self).__setattr__(name, value)
else:
cls, ids = self.__referrer__
store.__setattr__(ins_attr_expression%(cls, ids, name), value)
if name != '__oopattrs__':
#It's a set, so we can't add it more than once
self.__oopattrs__.add(name)
def __delattr__(self, name):
cls, ids = self.__referrer__
store.__delattr__(ins_attr_expression%(cls, ids, name))
self.__oopattrs__.remove(name)
def __del__(self):
cls, ids = self.__referrer__
if '__oopattrs__' not in store.__dict__:
#We're exiting the program, not deleting conciously the object
#Nothing must be done
return
for name in list(self.__getattr__('__oopattrs__')):
self.__delattr__(name)
last_ids = store.__getattribute__(ins_ids_expression%cls)
if (last_ids-1) == ids:
store.__setattr__(ins_ids_expression%cls, last_ids-1)
_ooplist_items_expression = "item%d" #%index
class List(OOP):
def __init__(self, original=[]):
self._ooplist_num_items = 0
for x in original:
self.append(x)
def __len__(self):
return self._ooplist_num_items
def __getitem__(self, key):
if type(key).__name__ == 'slice':
start, stop, step = key.start, key.stop, key.step
start = start if start else 0
stop = stop if stop else len(self)
step = step if step else 1
return List([ self[i] for i in range(start, stop, step) ])
else:
if key<0:
key = len(self) - key
try:
return self.__getattr__(_ooplist_items_expression%key)
except:
raise IndexError(key)
def __setitem__(self, key, value):
if type(key).__name__ == 'slice':
if key.step:
raise Error("No step allowed here!")
start, stop = key.start, key.stop
start = start if start else 0
stop = stop if stop else len(self)
for i in range(stop - start):
self.pop(start)
try:
#Object is iterable?
for x in value.__reversed__():
#We get them reversed because the inserting would be reversed otherwise
self.insert(start, x)
except:
#Object is not iterable
self.insert(start, value)
else:
if key<0:
key = len(self) - key
if key >= len(self):
for i in range(len(self), key):
self.__setattr__(_ooplist_items_expression%i, None)
self.__setattr__(_ooplist_items_expression%key, value)
self._ooplist_num_items = key + 1
else:
self.__setattr__(_ooplist_items_expression%key, value)
def __iter__(self):
return [self[i] for i in range(0, len(self))].__iter__()
def __reversed__(self):
return [self[i] for i in range(0, len(self))].__reversed__()
def append(self, item):
length = len(self)
self._ooplist_num_items += 1
self[length] = item
def extend(self, l):
for item in l:
self.append(item)
def insert(self, index, item):
length = len(self)
self._ooplist_num_items += 1
for i in range(length-1, index-1, -1):
self[i+1] = self[i]
self[index] = item
def remove(self, item):
self.pop(self.index(item))
def pop(self, i=-1):
i = len(self)-1 if i==-1 else i
for i in range(i+1, len(self)):
self[i-1] = self[i]
self.__delattr__(_ooplist_items_expression%(len(self)-1))
self._ooplist_num_items -= 1
def index(self, item):
for n, x in enumerate(self):
if x == item:
return n
raise Exception("No such item")
def count(self, item):
n = 0
for x in self:
if x == item:
n += 1
return n
def sort(self, cmp=None, key=None, reverse=False):
values = list(self)
values.sort(cmp=cmp, key=key, reverse=reverse)
self[:] = values
def reverse(self):
values = list(self)
values.reverse()
self[:] = values
def __str__(self):
return str(list(self))
def __repr__(self):
return repr(list(self))
_ooptuple_items_expression = "item%d" #%index
class Tuple(OOP):
"""
Tuple class
Usage:
Create a Tuple object calling the class
and passing as parameters all objects you want in the tuple.
If you want to pass a tuple, call it like this: Tuple(*t)
"""
def __init__(self, *args):
self._ooptuple_num_items = len(args)
for n, x in enumerate(args):
self.__setattr__(_ooptuple_items_expression%n, x)
def __len__(self):
return self._ooptuple_num_items
def __getitem__(self, key):
if type(key).__name__ == 'slice':
start, stop, step = key.start, key.stop, key.step
start = start if start else 0
stop = stop if stop else len(self)
step = step if step else 1
return Tuple(*[ self[i] for i in range(start, stop, step) ])
else:
if key<0:
key = len(self) - key
try:
return self.__getattr__(_ooptuple_items_expression%key)
except:
raise IndexError(key)
def __iter__(self):
return (self[i] for i in range(0, len(self))).__iter__()
def __reversed__(self):
return (self[i] for i in range(0, len(self))).__reversed__()
def index(self, item):
for n, x in enumerate(self):
if x == item:
return n
raise Exception("No such item")
def count(self, item):
n = 0
for x in self:
if x == item:
n += 1
return n
def sort(self, cmp=None, key=None, reverse=False):
values = list(self)
values.sort(cmp=cmp, key=key, reverse=reverse)
return Tuple(*values) #It returns it because Tuples are inmutable
def reverse(self):
values = list(self)
values.reverse()
return Tuple(*values) #It returns it because Tuples are inmutable
def __str__(self):
return str(tuple(self))
def __repr__(self):
return repr(tuple(self))
class Dict(OOP):
def __init__(self, **kwargs):
for k,v in kwargs.iteritems():
self[k] = v
def __getitem__(self, key):
try:
return self.__getattr__(key)
except:
raise KeyError(key)
def __setitem__(self, key, value):
self.__setattr__(key, value)
def __delitem__(self, key):
self.__delattr__(key)
def __iter__(self):
return list(self.__oopattrs__).__iter__()
def __len__(self):
return len(self.__oopattrs__)
def clear(self):
for k in self:
del self[k]
def copy(self):
return Dict(**{k:self[k] for k in self})
def get(self, key, default=None):
try:
return self[key]
except:
return default
def has_key(self, key):
return key in self
def items(self):
return Tuple(*[(k,self[k]) for k in self])
def iteritems(self):
return self.items().__iter__()
def iterkeys(self):
return self.__oopattrs__.__iter__()
def itervalues(self):
return [self[k] for k in self].__iter__()
def keys(self):
return Tuple(*self.__oopattrs__)
def pop(self, key, default=None):
if key in self:
v = self[key]
del self[key]
return v
else:
return default
def popitem(self):
import random
return self.pop(self.keys()[random.randint(0, len(self)-1)])
def setdefault(self, key, default=None):
if key not in self:
self[key] = default
return self[key]
def update(self, other=None, **kwargs):
if not other:
other = []
if type(other).__name__.lower() != 'dict':
it = {k: v for (k,v) in other}.iteritems()
else:
it = other.iteritems()
for k,v in it:
self[k] = v
for k,v in kwargs.iteritems():
self[k] = v
def values(self):
return Tuple(*[self[k] for k in self])
def __str__(self):
return str(dict(self))
def __repr__(self):
return repr(dict(self))
class Set(List):
"""
Set is in fact a List.
If you want optimization, feel free to work with this class
(and change the inheritance to OOP)
"""
def add(self, item):
if item not in self:
self.append(item)
Happy renpying