Subclassing an entity - C++ signature error

Please post any questions about developing your plugin here. Please use the search function before posting!
arawra
Senior Member
Posts: 190
Joined: Fri Jun 21, 2013 6:51 am

Subclassing an entity - C++ signature error

Postby arawra » Tue Jan 21, 2014 6:59 am

Syntax: Select all

class dndPlayer(PlayerEntity):

def __init__(self, userid):

# Since PlayerEntity requires an index for instantiation, we need to get the index
index = index_from_userid(userid)

# Call the "super" class (which is PlayerEntity in this instance) __init__ method with the index
super(dndPlayer, self).__init__(index)
self.index = index

self.playerinfo = playerinfo_from_userid(userid)

# Set the player's name and last connected values
# "steamid" and "name" are both properties of PlayerEntity
PlayerDataDictionary[self.steamid].name = self.name


Image
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Tue Jan 21, 2014 7:30 am

A couple things with this. First, currently we do not have the ability to instantiate some of the objects with all the types we used to. We are still working on figuring out whether we want to handle things the same and how to handle the objects in general. At some point, inheriting will work perfectly fine.

However, having said that, I believe I am the one that gave you that code a while back. I failed to realize, however, that we would have to override __new__, as well as __init__. Once these other internal questions/issues are resolved, the following should work:

Syntax: Select all

class MyPlayerObject(PlayerEntity):
def __new__(cls, userid):
'''Override __new__ to convert the userid to the player's index before returning an instance'''

# Get the player's index
index = index_from_userid(userid)

# Return the object using the super class' __new__
return super(MyPlayerObject, cls).__new__(cls, index)

def __init__(self, userid):
'''Call the super class' __init__ using the object's stored index'''
super(MyPlayerObject, self).__init__(self.index)
PlayerEntity, and thus any class that inherits from it, will already have the index and PlayerInfo stored as properties, so there will be no reason for you to get those values and store them. That all will happen internally. Note also that the value passed to both methods will be the userid. You only need to handle changing it to the index in __new__. Once the super class' __new__ is called, that index is stored as the property "index". This is why, in the overriding __init__, while userid is the value passed, the value we then pass to the super class' __init__ is the object's "index" property. If you mistakenly pass in the userid, you will encounter issues, so make sure you have exactly what I have there at the very top of your __init__ method.

Satoon
arawra
Senior Member
Posts: 190
Joined: Fri Jun 21, 2013 6:51 am

Postby arawra » Tue Jan 21, 2014 4:04 pm

I dont understand the native methods __new__() and __init__ all that well, but I wish to.

I've worked with __init__() on my own a little bit. I understand that its probably shorthand for initialization of the object, and it runs whenever you create a new object. I am not sure if it works the same way for __new__() and if it does, which is called first? I would assume __new__() because it seems as if we're recreating the object with the parent class of PlayerEntity, but I don't understand why we're making calls to the parent twice? My best guess is that it needs to go into memory (possibly on the heap?) before all attributes can be set?
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Tue Jan 21, 2014 4:41 pm

I will attempt to do my best to explain. Yes, __new__ gets called when the object is created, and allows us to manipulate object creation. __new__ is also a "class" method, while __init__ is an "instance" method.

When you override a method, and then call super(<class>, <instance>).__<method>__(), Python goes step by step through the hierarchy of the class to see which class in the hierarchy has a method of that name. So, for the current example, we call __new__ and in that method, we call the super class' __new__. Since PlayerEntity is the class that we are inheriting from, Python first checks that class. PlayerEntity does not directly have a __new__ built into it. So, Python checks PlayerEntity's inherited class BaseEntity. BaseEntity does in fact have a __new__ method, so that is the method that gets called. BaseEntity's __new__ method is designed to help us create any BaseEntity object, verify that the given index is of a valid edict, and stores the entity's index and edict as private attributes (which are made available to us via public properties):

Syntax: Select all

class BaseEntity(object):
'''Class used to interact directly with entities'''

def __new__(cls, index, *entities):
'''Override the __new__ class method to verify the given index
is of the correct entity type and add the index attribute'''

# Get the given indexes edict
edict = CEdict(index)

# Is the edict valid?
if edict.is_free() or not edict.is_valid():

# If not raise an error
raise ValueError(
'Index "{0}" is not a proper entity index'.format(index))

# Create the object
self = object.__new__(cls)

# Set the entity's base attributes
self._index = index
self._edict = edict
self._entities = frozenset(list(entities) + ['entity'])

# Return the instance
return self

@property
def index(self):
'''Returns the entity's index'''
return self._index

@property
def edict(self):
'''Returns the entity's edict instance'''
return self._edict

@property
def entities(self):
'''Returns the set of entity names to use for the instance'''
return self._entities

The "entities" property (and _entities private attribute) are used by BaseEntity to know which folders to use when checking properties, keyvalues, offsets, and functions (all of which use in the ../data/source-python/ directory).

In PlayerEntity, again, we do not override __new__, but instead use __init__. We only use this to store the PlayerInfo object of the player (after verifying it is a valid PlayerInfo object) and make sure the "entities" property is only entity and player:

Syntax: Select all

class PlayerEntity(BaseEntity, _PlayerWeapons):
'''Class used to interact directly with players'''

def __init__(self, index):
'''Override the __init__ method to set the
"entities" attribute and set the PlayerInfo'''

# Set the player's info attribute
self._info = CPlayerInfo(self.edict)

# Is the IPlayerInfo instance valid?
if self.info is None:

raise ValueError(
'Invalid IPlayerInfo instance for index "{0}"'.format(index))

# Set the entities attribute
self._entities = frozenset(['entity', 'player'])

@property
def info(self):
'''Returns the player's IPlayerInfo instance'''
return self._info

If you do not call the super __new__ method after getting the index, the object will not be created properly, and will give you errors. If you do not call the super __init__ method, the PlayerInfo will not be stored and the entities property will not be the correct value. With the way things are setup in PlayerEntity/BaseEntity, you "must" override both methods.

Having said all of this, I now realize how easy it would be for me to just override __new__ within PlayerEntity instead of __init__. I am going to test my changes before I commit them, but possibly here soon, you will not have to override __init__ and call the super() method.

I hope you understood that, if not, please feel free to ask questions.

Satoon
arawra
Senior Member
Posts: 190
Joined: Fri Jun 21, 2013 6:51 am

Postby arawra » Tue Jan 21, 2014 10:06 pm

It definitely makes more sense than when I first read the code.

If I understand your changes you wish to apply, PlayerEntity will inherit all properties and attributes from BaseEntity without having to call on them both?
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Tue Jan 21, 2014 10:23 pm

Yes, that is how inheritance works. As long as you do not override the inherited class' attributes, properties, and methods, those items will be exactly as the would be for the parent class. As another example, both BaseEntity and PlayerEntity have an "instances" property. We use this property to know which other values to check against when __getattr__ is called.

Another tidbit, in case you did not know, but __getattr__ is only called when the attribute is not known by the instance. __getattribute__ is called first, and if the attribute is known to the instance, it returns as normal. If the attribute is not found in __getattribute__, __getattr__ is called. You should almost never override __getattribute__, as you will have issues doing so (most notably running an infinite loop).

Back on topic, though, the "instances" property of BaseEntity only checks the entity's CEdict instance to see if CEdict has an attribute by that name. This allows us to use BaseEntity (and PlayerEntity) to directly call CEdict methods (<instance>.<method>) instead of having to use <instance>.edict.<method>.

Syntax: Select all

@property
def instances(self):
'''Yields the entity's edict instance'''
yield self.edict

For PlayerEntity, we also have the player's CPlayerInfo instance that we want to check against. So, we override the "instances" property in PlayerEntity to add that object as well:

Syntax: Select all

@property
def instances(self):
'''Yields the player's IPlayerInfo and Edict instances'''
yield self.info
yield self.edict

Satoon
arawra
Senior Member
Posts: 190
Joined: Fri Jun 21, 2013 6:51 am

Postby arawra » Wed Jan 22, 2014 4:43 am

What is the CEdict class/object and which properties does it contain that we would want to access/change?
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Wed Jan 22, 2014 4:56 pm

http://www.sourcepython.com/showwiki.php?title=Wiki:CEdict

Methods of a class are also attributes. You can use the BaseEntity class to then call all of the CEdict methods by using the same names.

Satoon
arawra
Senior Member
Posts: 190
Joined: Fri Jun 21, 2013 6:51 am

Postby arawra » Wed Jan 22, 2014 6:22 pm

I can't think of anyway to do this, but is it possible to iterate over CEdict to retrieve all props? I remember doing that in ES:P to get some names of things...
Omega_K2
Senior Member
Posts: 227
Joined: Sat Jul 07, 2012 3:05 am
Location: Europe
Contact:

Postby Omega_K2 » Thu Jan 23, 2014 12:43 am

use dir(your_python_object), returns all props and functions

If you don't understand class inheritance and the special methods __new__ and __init__ I suggest you read up a bit on the programming theory regarding classes first.
As for python special methods, go into the docs:
http://docs.python.org/3/reference/datamodel.html
Libraries: k2tools
Plugins (any): GSRPG (soon) | Pretty Status List | MySQLAds (soon)
Plugins (game-specific): None atm

If you happen to find a bug or need help, either post in the release threads or contact me in IRC gamesurge.net:6667 / #sourcepython

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 21 guests