Author Topic: General Discussion  (Read 3788127 times)

0 Members and 8 Guests are viewing this topic.

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #240 on: February 05, 2013, 01:41:24 PM »
That reminds me, this bar doesn't need such high sensitivity, just like fee for an entrance. I think bar with step 10 or at least 5 would be better, if you have time for such purely cosmetic things.

 Well, it took me a while to figure out bars but they are very easy to understand as soon as you implement them once correctly (if RenPy documentation has a weakness, it's lack of practical examples).

 I'll do that next time I see code for that screen.
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #241 on: February 05, 2013, 02:11:34 PM »
@Xela
Yes, it is definitely possible to keep the default behaviour of the character class for regular attributes and modify it for stats. That's what I did in Pytherworld. The most simple way to do it is like this:
Code: [Select]
class Girl(object):
    def __init__(self):
        self.__dict__["stats"] = {"health":100, "fatigue":0, "charisma":5}
       
    def __getattr__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        if name in self.stats:
            # get a gamestat   
            val = self.stats[name]
        elif name in self.__dict__:
            # get a regular attribute of this class   
            val = self.__dict__[name]
        else:
            # raise an error if name is neither a gamestat nor an attribute   
            msg = "'%s' is neither a gamestat nor an attribute of %s"
            raise AttributeError(msg % (name, self.__class__.__name__))
        return val
           
    def __setattr__(self, name, value):
        '''Assigns a value to a game attribute or a python attribute.
        '''
        if name in self.stats:
            # set a gamestat   
            self.stats[name] = value
        else:
            # set a regular attribute of this class 
            self.__dict__[name] = value

The code above works, so you could drop it into the sGirl class if you want to. If you do, just make sure the definition of the stats dictionary is the first line in the __init__ function, or you'll get infinite recursion.

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #242 on: February 05, 2013, 02:36:06 PM »
@Xela
Yes, it is definitely possible to keep the default behaviour of the character class for regular attributes and modify it for stats. That's what I did in Pytherworld. The most simple way to do it is like this:
Code: [Select]
class Girl(object):
    def __init__(self):
        self.__dict__["stats"] = {"health":100, "fatigue":0, "charisma":5}
       
    def __getattr__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        if name in self.stats:
            # get a gamestat   
            val = self.stats[name]
        elif name in self.__dict__:
            # get a regular attribute of this class   
            val = self.__dict__[name]
        else:
            # raise an error if name is neither a gamestat nor an attribute   
            msg = "'%s' is neither a gamestat nor an attribute of %s"
            raise AttributeError(msg % (name, self.__class__.__name__))
        return val
           
    def __setattr__(self, name, value):
        '''Assigns a value to a game attribute or a python attribute.
        '''
        if name in self.stats:
            # set a gamestat   
            self.stats[name] = value
        else:
            # set a regular attribute of this class 
            self.__dict__[name] = value

The code above works, so you could drop it into the sGirl class if you want to. If you do, just make sure the definition of the stats dictionary is the first line in the __init__ function, or you'll get infinite recursion.

Blah, I didn't know about infinite recursion :( So basically if I want to use more dictionaries in __setattr__ and __getattr__ like for max and min stats and another one containing a second variable that will keep track of stat values that are added by items and traits, I need to create a 'matrix' dictionary (dict or dicts?) as first line of __init__?

Or it might be simpler to add more key:value entries, like maxhealth=100, minhealth=0, modhealth=30, health=100.

Also a number of Structure methods will be rendered obsolete:

        def has(self,par): return self.__dict__.has_key(par)
        def set(self,par,value): self.__dict__[par] = value
        def mod(self,par,value): self.__dict__[par] += value

And those were used in several places if I recall it correctly.

Maybe a slightly different approach:

Code: [Select]
class Girl(object):
    def __init__(self):
        self.__dict__["stats"] = {"List":["fatigue","charisma","etc"]}
       
    def __getattr__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        if name in self.stats['List']:
            # get a gamestat   
            val = self.__dict__[name]
            # but here we can do cool stuff to stats!
        elif name in self.__dict__:
            # get a regular attribute of this class   
            val = self.__dict__[name]
        else:
            # raise an error if name is neither a gamestat nor an attribute   
            msg = "'%s' is neither a gamestat nor an attribute of %s"
            raise AttributeError(msg % (name, self.__class__.__name__))
        return val

Will this code cause infinite recursions?
« Last Edit: February 05, 2013, 02:55:02 PM by Xela »
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #243 on: February 05, 2013, 03:18:10 PM »
I'm not really sure what the problem is you are describing, but maybe you will solve it yourself if I explain a litte bit what the code above does.

Basically, Python syntax is just a nice way to call all those special methods. Writing
Code: [Select]
char.health = 100 is equivalent to
Code: [Select]
char.__setattr__("health", 100)
Likewise, writing 
Code: [Select]
current_health = char.health is equivalent to
Code: [Select]
current_health = char.__getattr__("health")
So, keeping that in mind, look at the constructor (__init__ method) of sGirl. I'm sure, somewhere you define an attribute, eg.
Code: [Select]
self.name = "unnamed"which Python will translate to
Code: [Select]
self.__setattr__("name", "unnamed")
This call to __setattr__ will execute the line
Code: [Select]
if name in self.stats: which Python will translate to
Code: [Select]
if name in self.__getattr__("stats")
Now __getattr__ is called, which also tries to access the stats attribute in the line
Code: [Select]
if name in self.stats:, triggering yet another call to __getattr__, which also tries to access the stats attribute, triggering yet another call to __getattr__, ...
et voilĂ , infinite recursion.

If however the first line of the constructor defines the stats attribute by accessing the internal dictionary of the instance, self.__dict__, subsequent attribute access via self.stats will work. This works because self.__dict__ is NOT translated to self.__getattr__("__dict__"), but instead just returns the internal dictionary of the instance, which contains its namespace.

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #244 on: February 05, 2013, 03:28:13 PM »
I'm not really sure what the problem is you are describing, but maybe you will solve it yourself if I explain a litte bit what the code above does.

Basically, Python syntax is just a nice way to call all those special methods. Writing
Code: [Select]
char.health = 100 is equivalent to
Code: [Select]
char.__setattr__("health", 100)
Likewise, writing 
Code: [Select]
current_health = char.health is equivalent to
Code: [Select]
current_health = char.__getattr__("health")
So, keeping that in mind, look at the constructor (__init__ method) of sGirl. I'm sure, somewhere you define an attribute, eg.
Code: [Select]
self.name = "unnamed"which Python will translate to
Code: [Select]
self.__setattr__("name", "unnamed")
This call to __setattr__ will execute the line
Code: [Select]
if name in self.stats: which Python will translate to
Code: [Select]
if name in self.__getattr__("stats")
Now __getattr__ is called, which also tries to access the stats attribute in the line
Code: [Select]
if name in self.stats:, triggering yet another call to __getattr__, which also tries to access the stats attribute, triggering yet another call to __getattr__, ...
et voilĂ , infinite recursion.

If however the first line of the constructor defines the stats attribute by accessing the internal dictionary of the instance, self.__dict__, subsequent attribute access via self.stats will work. This works because self.__dict__ is NOT translated to self.__getattr__("__dict__"), but instead just returns the internal dictionary of the instance, which contains its namespace.

I knew all of that but:

        def has(self,par): return self.__dict__.has_key(par)
        def set(self,par,value): self.__dict__[par] = value
        def mod(self,par,value): self.__dict__[par] += value

Accesses classes namespace directly, does it not? So __getattr__, __setattr__ is bypassed here, also in your code, stats and their values are no longer in __dict__, they are in in __dict__['stats'] which presents a problem. Correct me if I am wrong here.

It will prolly be more useful if you explain what causes recursion and I'll try to find a way to code what I need bypassing that.
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #245 on: February 05, 2013, 03:39:46 PM »
Quote
It will prolly be more useful if you explain what causes recursion
I actually just did in my previous post...

Will this code cause infinite recursions?
Easiest way to find out is copy&paste the class definition into IDLE (comes preinstalled with your Python distribution, except for some Linux distris) and create an instance of the class ;-)
Without defining a __setattr__ it's hard, but still possible, to cause infinite recursion.


Concerning the Structure methods:
These methods should not be there anyway because reimplementing the wheel is bad and Python already provides nice mechanisms to do that.

Instead of char.has(par) write hasattr(char, par)
Instead of char.set(par, value) write setattr(char, par, value), or just char.dancing = value if you don't need dynamic access
Instead of char.mod(par, value) write char.dancing += value

If you often need to add a value to a class attribute and you only know the name of the attribute at runtime (dynamic attribute access), you can reimplement the mod method as follows:
Code: [Select]
def mod(self,par,value):
    oldval = getattr(self, par)
    newval = oldval += value
    setattr(self, par, newval)

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #246 on: February 05, 2013, 04:00:25 PM »
I actually just did in my previous post...
Easiest way to find out is copy&paste the class definition into IDLE (comes preinstalled with your Python distribution, except for some Linux distris) and create an instance of the class ;-)
Without defining a __setattr__ it's hard, but still possible, to cause infinite recursion.


Concerning the Structure methods:
These methods should not be there anyway because reimplementing the wheel is bad and Python already provides nice mechanisms to do that.

Instead of char.has(par) write hasattr(char, par)
Instead of char.set(par, value) write setattr(char, par, value), or just char.dancing = value if you don't need dynamic access
Instead of char.mod(par, value) write char.dancing += value

If you often need to add a value to a class attribute and you only know the name of the attribute at runtime (dynamic attribute access), you can reimplement the mod method as follows:
Code: [Select]
def mod(self,par,value):
    oldval = getattr(self, par)
    newval = oldval += value
    setattr(self, par, newval)

Right, thanks for explanations, I'll simply do the thing I've always done: Trial and Error. I had a messed up day so I barely have strength to type, I'll play with this today and see if I can get it done tomorrow.
Like what we're doing?

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #247 on: February 06, 2013, 07:58:13 PM »
Ok... update time: Nothing has changed in the game...  ???

Yet, Sh!tzlloadz had changed in game code due to this:

Code: [Select]
        def __getattr__(self, key):
            if key in StatList:
                if self.__dict__['stats'][key] + self.__dict__['imod'][key] > self.__dict__['max'][key]:
                    val = self.__dict__['max'][key]
                elif self.__dict__['stats'][key] + self.__dict__['imod'][key] < self.__dict__['min'][key]:
                    val = self.__dict__['min'][key]
                else:
                    val = self.__dict__['stats'][key] + self.__dict__['imod'][key]
                   
            else:
                msg = "'%s' is neither a gamestat nor an attribute of %s"
                raise AttributeError(msg % (key, self.__class__.__name__))
            return val
           
        def __setattr__(self, key, value):
            if key in StatList:
                if self.__dict__['imod'][key] + value > self.__dict__['max'][key]:
                    if value > self.__dict__['max'][key]:
                        self.__dict__['stats'][key] = self.__dict__['max'][key]
                        return
                       
                if value < 0:
                    if self.__dict__['imod'][key] + value < self.__dict__['min'][key]:
                        if value < self.__dict__['min'][key]:
                            self.__dict__['stats'][key] = self.__dict__['min'][key]
                            return

                self.__dict__['stats'][key] = value
 
            else:
                self.__dict__[key] = value

As a result, decent amount of code issues had to be traced down and repaired (I even got 'Windows' Error (That I never knew existed) in RenPy 5 or 6 times), but if this actually works (and so far testing shows that, it does):

- Items issue has been solved (When a girl with 90 of any stats and max of that stat set to 100, equips an item that increases that stat by 50 for example, in the past, this action would result in that stat being set to 100. But a problem Rudi tracked down was the fact that taking this item off, would result in stat being set to 50 (100 max - 50 = 50)). This should be fixed now as well and girls stat will keep on increasing until max is reached even if item/items are still equipped.

- mod method is no longer needed. So if you type chr.charisma += 10, game automatically checks if charisma (or any other stat) is within max and min parameters. Modders can still use chr.mod('charisma', 10) if they so desire.

- It is now also possible to intercept other attributes and do cool stuff with them as well.

@Rudi:

Code: [Select]
        elif name in self.__dict__:
            # get a regular attribute of this class   
            val = self.__dict__[name]

This is about as useful as Alkion's/PyTFall's:

Code: [Select]
do_nothing():
    pass

function :)

In any case, modifying classes behavior might be messy work, but it's usefulness can hardly be ignored, thanks for the idea.
« Last Edit: February 06, 2013, 08:41:41 PM by Xela »
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #248 on: February 07, 2013, 02:29:37 AM »
You're right. Well, at least it doesn't break anything  :D

EDIT: After rereading the docs on attribute access (http://docs.python.org/2/reference/datamodel.html), I believe that the following is a less error-prone implementation of the desired behaviour. The desired behaviour is to intercept any attribute access to character stats and keep the behaviour unchanged for any other attribute. With the previous implementation, which used __getattr__, a character stat could only be accessed if the instance, its class or any superclass did not provide an attribute with the same name.
Code: [Select]
class Girl(object):
    def __init__(self):
        statsdict = {"health":100, "fatigue":0, "charisma":5}
        object.__setattr__(self, "stats", statsdict)
        object.__init__(self)
       
    def __getattribute__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        statsdict = object.__getattribute__(self, "stats")
        if name in statsdict:
            # get a gamestat   
            val = statsdict[name]
        else:
            # get a regular attribute or raise an error
            val = object.__getattribute__(self, name)
        return val
           
    def __setattr__(self, name, value):
        '''Assigns a value to a game attribute or a python attribute.
        '''
        if name in self.stats:
            # set a gamestat   
            self.stats[name] = value
        else:
            # set a regular attribute
            object.__setattr__(self, name, value)
@Xela
Should you want to adapt this code for PyTFall, please note that you should replace any reference to object with the superclass of your sGirl class: Structure.
 
« Last Edit: February 07, 2013, 03:20:27 AM by rudistoned »

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #249 on: February 07, 2013, 07:04:20 AM »
You're right. Well, at least it doesn't break anything  :D

EDIT: After rereading the docs on attribute access (http://docs.python.org/2/reference/datamodel.html), I believe that the following is a less error-prone implementation of the desired behaviour. The desired behaviour is to intercept any attribute access to character stats and keep the behaviour unchanged for any other attribute. With the previous implementation, which used __getattr__, a character stat could only be accessed if the instance, its class or any superclass did not provide an attribute with the same name.
Code: [Select]
class Girl(object):
    def __init__(self):
        statsdict = {"health":100, "fatigue":0, "charisma":5}
        object.__setattr__(self, "stats", statsdict)
        object.__init__(self)
       
    def __getattribute__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        statsdict = object.__getattribute__(self, "stats")
        if name in statsdict:
            # get a gamestat   
            val = statsdict[name]
        else:
            # get a regular attribute or raise an error
            val = object.__getattribute__(self, name)
        return val
           
    def __setattr__(self, name, value):
        '''Assigns a value to a game attribute or a python attribute.
        '''
        if name in self.stats:
            # set a gamestat   
            self.stats[name] = value
        else:
            # set a regular attribute
            object.__setattr__(self, name, value)
@Xela
Should you want to adapt this code for PyTFall, please note that you should replace any reference to object with the superclass of your sGirl class: Structure.

I tried messing with __getattribute__ already, didn't do much for me, and I tried a number of different approaches.

With your code, I get error here

statsdict = Structure.__getattribute__(self, "stats")

AttributeError: 'sGirl' object has no attribute 'stats'

while importing stuff into the class from xml. It may not be worth spending time making it less errorprone, I understand what errors can occur so I can fix them even if they appear.
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #250 on: February 07, 2013, 08:32:46 AM »
Sounds like the XML loader does not call the constructor. I had the same problem because pickle doesn't call the constructor either and after unpickling an instance of my character class it had no stats attribute. I think you can avoid the error message like this and I also think it fixes the problem, but I never made sure it really does. Btw, if __getattribute__ is defined like this, the constructor does not need to have, and should not have the initial values for the statsdict.
Code: [Select]
    def __getattribute__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        try:
            statsdict = object.__getattribute__(self, "stats")
        except AttributeError:
            # must create self.stats if it does not yet exist
            statsdict = {"health":100, "fatigue":0, "charisma":5}
            object.__setattr__(self, "stats", statsdict)
            statsdict = object.__getattribute__(self, "stats")
        if name in statsdict:
            # get a gamestat   
            val = statsdict[name]
        else:
            # get a regular attribute or raise an error
            val = object.__getattribute__(self, name)
        return val

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #251 on: February 07, 2013, 10:05:53 AM »
Sounds like the XML loader does not call the constructor. I had the same problem because pickle doesn't call the constructor either and after unpickling an instance of my character class it had no stats attribute. I think you can avoid the error message like this and I also think it fixes the problem, but I never made sure it really does. Btw, if __getattribute__ is defined like this, the constructor does not need to have, and should not have the initial values for the statsdict.
Code: [Select]
    def __getattribute__(self, name):
        '''Returns a value for a game attribute or a python attribute.
        '''
        try:
            statsdict = object.__getattribute__(self, "stats")
        except AttributeError:
            # must create self.stats if it does not yet exist
            statsdict = {"health":100, "fatigue":0, "charisma":5}
            object.__setattr__(self, "stats", statsdict)
            statsdict = object.__getattribute__(self, "stats")
        if name in statsdict:
            # get a gamestat   
            val = statsdict[name]
        else:
            # get a regular attribute or raise an error
            val = object.__getattribute__(self, name)
        return val

== recursion error. Or some other error that python/renpy cannot trace and just hangs. I also tried to do a number of things that should have worked (your approach looks solid, at least to me) but resulted in same. Basically catching this error would cost me another 3 - 4 hours.

It might also be a part of parser or our custom importing functions (but less likely). Like I said, not worth it. __getattr__ seems to be working marvelously.
« Last Edit: February 07, 2013, 10:14:54 AM by Xela »
Like what we're doing?

Offline rudistoned

  • Full Member
  • ***
  • Posts: 229
Re: PyTFall Dev Thread: Writers needed!
« Reply #252 on: February 07, 2013, 10:25:43 AM »
Strange. Well, you're right, __getattr__ will get the job done and it's quite unlikely it will cause problems.

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #253 on: February 07, 2013, 04:42:37 PM »
First tests of stripjob are successful, since it is a pretty complicated job by design with a lot depending on it, I would say logic for it is still only 70% complete, but there has been a considerable amount of progress and debugging.
Like what we're doing?

Offline Xela

  • Global Moderator
  • *****
  • Posts: 6893
  • "It's like hunting cows"
Re: PyTFall Dev Thread: Writers needed!
« Reply #254 on: February 08, 2013, 05:13:02 PM »
Still working on strip method. Making sure it has effect on all stats it is supposed to, improving logic and client's satisfaction that it needs to relay to bar job and whore job and so on. I would say the damn thing is about 90% done, it can obviously be improved indefinitely to make texts better and more sensible, adding hooks and forks but I think I am fairly close to reaching the limit for SimBro version. Most time doesn't really do into coding but into figuring out how much of each stat game has to mod.

I am expecting a weekend without to many time consuming tasks in rl,  so plan is to get a good amount of work done for the project tomorrow and the day after.

=============================================
Edit:

I am wrapping up strip job (still needs a bit of work here and there but some other stuff needs to be taken care of first) and starting with more brothel logic, service(bar/cleaning/waitress) job.


This is what strip job currently looks like:


I tried to post code for the job but got this instead  ::)

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
  The following error or errors occurred while posting this message:
  The message exceeds the maximum allowed length (20000 characters). 
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Odd since it isn't really that big... maybe a lot of spaces or something.
« Last Edit: February 09, 2013, 10:31:33 AM by Xela »
Like what we're doing?