|
8 | 8 | # Python Imports |
9 | 9 | # Collections |
10 | 10 | from collections import defaultdict |
11 | | -# Contextlib |
12 | | -from contextlib import suppress |
13 | 11 | # Inspect |
14 | 12 | from inspect import signature |
15 | 13 | # WeakRef |
|
41 | 39 | from entities.classes import server_classes |
42 | 40 | from entities.constants import WORLD_ENTITY_INDEX |
43 | 41 | from entities.constants import DamageTypes |
| 42 | +from entities.datamaps import InputFunction |
44 | 43 | from entities.helpers import index_from_inthandle |
45 | 44 | from entities.helpers import index_from_pointer |
46 | 45 | from entities.helpers import wrap_entity_mem_func |
@@ -144,6 +143,17 @@ def __call__(cls, index, caching=None): |
144 | 143 | # We are done, let's return the instance |
145 | 144 | return obj |
146 | 145 |
|
| 146 | + @cached_property(unbound=True) |
| 147 | + def attributes(cls): |
| 148 | + """Returns all the attributes available for this class. |
| 149 | +
|
| 150 | + :rtype: dict |
| 151 | + """ |
| 152 | + attributes = {} |
| 153 | + for cls in reversed(cls.mro()): |
| 154 | + attributes.update(vars(cls)) |
| 155 | + return attributes |
| 156 | + |
147 | 157 | @property |
148 | 158 | def caching(cls): |
149 | 159 | """Returns whether this class is caching its instances by default. |
@@ -208,54 +218,57 @@ def __hash__(self): |
208 | 218 |
|
209 | 219 | def __getattr__(self, attr): |
210 | 220 | """Find if the attribute is valid and returns the appropriate value.""" |
211 | | - # Loop through all of the entity's server classes |
212 | | - for instance in self.server_classes.values(): |
213 | | - |
214 | | - try: |
215 | | - # Get the attribute's value |
216 | | - value = getattr(instance, attr) |
217 | | - except AttributeError: |
218 | | - continue |
| 221 | + # Try to resolve the dynamic attribute |
| 222 | + try: |
| 223 | + instance, value = self.dynamic_attributes[attr] |
| 224 | + except KeyError: |
| 225 | + raise AttributeError('Attribute "{0}" not found'.format(attr)) |
219 | 226 |
|
220 | | - # Is the value a dynamic function? |
221 | | - if isinstance(value, MemberFunction): |
| 227 | + # Is the attribute a property descriptor? |
| 228 | + try: |
| 229 | + value = value.__get__(instance) |
| 230 | + except AttributeError: |
| 231 | + pass |
222 | 232 |
|
223 | | - # Cache the value |
224 | | - with suppress(AttributeError): |
225 | | - object.__setattr__(self, attr, value) |
| 233 | + # Is the value a dynamic function? |
| 234 | + if isinstance(value, (MemberFunction, InputFunction)): |
226 | 235 |
|
227 | | - # Return the attribute's value |
228 | | - return value |
| 236 | + # Cache the value |
| 237 | + try: |
| 238 | + object.__setattr__(self, attr, value) |
| 239 | + except AttributeError: |
| 240 | + pass |
229 | 241 |
|
230 | | - # If the attribute is not found, raise an error |
231 | | - raise AttributeError('Attribute "{0}" not found'.format(attr)) |
| 242 | + return value |
232 | 243 |
|
233 | 244 | def __setattr__(self, attr, value): |
234 | 245 | """Find if the attribute is valid and sets its value.""" |
235 | 246 | # Is the given attribute a property? |
236 | | - if (attr in super().__dir__() and isinstance( |
237 | | - getattr(self.__class__, attr, None), property)): |
238 | | - |
239 | | - # Set the property's value |
240 | | - object.__setattr__(self, attr, value) |
241 | | - |
242 | | - # No need to go further |
243 | | - return |
| 247 | + try: |
| 248 | + setter = type(self).attributes[attr].__set__ |
244 | 249 |
|
245 | | - # Loop through all of the entity's server classes |
246 | | - for server_class, instance in self.server_classes.items(): |
| 250 | + # KeyError: |
| 251 | + # The attribute does not exist. |
| 252 | + # AttributeError: |
| 253 | + # The attribute is not a descriptor. |
| 254 | + except (KeyError, AttributeError): |
247 | 255 |
|
248 | | - # Does the current server class contain the given attribute? |
249 | | - if hasattr(server_class, attr): |
| 256 | + # Try to resolve a dynamic attribute |
| 257 | + try: |
| 258 | + self, setter = self.dynamic_attributes[attr] |
| 259 | + setter = setter.__set__ |
250 | 260 |
|
251 | | - # Set the attribute's value |
252 | | - setattr(instance, attr, value) |
| 261 | + # KeyError: |
| 262 | + # The attribute does not exist. |
| 263 | + # AttributeError: |
| 264 | + # The attribute is not a descriptor. |
| 265 | + except (KeyError, AttributeError): |
253 | 266 |
|
254 | | - # No need to go further |
255 | | - return |
| 267 | + # Set the attribute to the given value |
| 268 | + return object.__setattr__(self, attr, value) |
256 | 269 |
|
257 | | - # If the attribute is not found, just set the attribute |
258 | | - super().__setattr__(attr, value) |
| 270 | + # Set the attribute's value |
| 271 | + setter(self, value) |
259 | 272 |
|
260 | 273 | def __dir__(self): |
261 | 274 | """Return an alphabetized list of attributes for the instance.""" |
@@ -317,9 +330,21 @@ def server_classes(self): |
317 | 330 | """Yield all server classes for the entity.""" |
318 | 331 | return { |
319 | 332 | server_class: make_object(server_class, self.pointer) for |
320 | | - server_class in server_classes.get_entity_server_classes(self) |
| 333 | + server_class in reversed( |
| 334 | + server_classes.get_entity_server_classes(self) |
| 335 | + ) |
321 | 336 | } |
322 | 337 |
|
| 338 | + @cached_property |
| 339 | + def dynamic_attributes(self): |
| 340 | + """Returns the dynamic attributes for this entity.""" |
| 341 | + attributes = {} |
| 342 | + for cls, instance in self.server_classes.items(): |
| 343 | + attributes.update( |
| 344 | + {attr:(instance, getattr(cls, attr)) for attr in dir(cls)} |
| 345 | + ) |
| 346 | + return attributes |
| 347 | + |
323 | 348 | @cached_property |
324 | 349 | def properties(self): |
325 | 350 | """Iterate over all descriptors available for the entity.""" |
@@ -679,34 +704,44 @@ def take_damage( |
679 | 704 | if attacker_index is not None: |
680 | 705 |
|
681 | 706 | # Try to get the Entity instance of the attacker |
682 | | - with suppress(ValueError): |
| 707 | + try: |
683 | 708 | attacker = Entity(attacker_index) |
| 709 | + except ValueError: |
| 710 | + pass |
684 | 711 |
|
685 | 712 | # Was a weapon given? |
686 | 713 | if weapon_index is not None: |
687 | 714 |
|
688 | 715 | # Try to get the Weapon instance of the weapon |
689 | | - with suppress(ValueError): |
| 716 | + try: |
690 | 717 | weapon = Weapon(weapon_index) |
| 718 | + except ValueError: |
| 719 | + pass |
691 | 720 |
|
692 | 721 | # Is there a weapon but no attacker? |
693 | 722 | if attacker is None and weapon is not None: |
694 | 723 |
|
695 | 724 | # Try to get the attacker based off of the weapon's owner |
696 | | - with suppress(ValueError, OverflowError): |
| 725 | + try: |
697 | 726 | attacker_index = index_from_inthandle(weapon.owner_handle) |
698 | 727 | attacker = Entity(attacker_index) |
| 728 | + except (ValueError, OverflowError): |
| 729 | + pass |
699 | 730 |
|
700 | 731 | # Is there an attacker but no weapon? |
701 | 732 | if attacker is not None and weapon is None: |
702 | 733 |
|
703 | 734 | # Try to use the attacker's active weapon |
704 | | - with suppress(AttributeError): |
| 735 | + try: |
705 | 736 | weapon = attacker.active_weapon |
| 737 | + except AttributeError: |
| 738 | + pass |
706 | 739 |
|
707 | 740 | # Try to set the hitgroup |
708 | | - with suppress(AttributeError): |
| 741 | + try: |
709 | 742 | self.hitgroup = hitgroup |
| 743 | + except AttributeError: |
| 744 | + pass |
710 | 745 |
|
711 | 746 | # Get a TakeDamageInfo instance |
712 | 747 | take_damage_info = TakeDamageInfo() |
@@ -795,8 +830,10 @@ def _on_networked_entity_deleted(index): |
795 | 830 | for delay in _entity_delays.pop(index, ()): |
796 | 831 |
|
797 | 832 | # Cancel the delay... |
798 | | - with suppress(ValueError): |
| 833 | + try: |
799 | 834 | delay.cancel() |
| 835 | + except ValueError: |
| 836 | + pass |
800 | 837 |
|
801 | 838 | # Loop through all repeats... |
802 | 839 | for repeat in _entity_repeats.pop(index, ()): |
|
0 commit comments