from .utils import post_output, _getattr
[docs]class Ability:
"""Super class for all abilitis. Most abilities have two uses, they either have a direct effect or add
a Modifier to either an entity or the user of the ability. But really, you have full control over the effect
of the ability and you have access to the user casting and its target so go nuts. Similarly to modifiers, entities
can have an unlimited number of abilities but they can never have the same abiltiy twice.
Parameters
-----------
name : Optional[str]
A name for the ability, if none is provided the library will fallback to the docstring of the class and
then to the name of the class itself.
description : Optional[str]
A description for the ability, if none is provided the library will fall back to the default of the docstring
of the effect function
cost : Optional[Union[Callable[[Entity, Entity], int], int]]
An optional cost for using the ability, this can be a function that returns and int or it can simply be an
int. By default, the ability will cost 0 energy.
Attributes
-----------
name : str
Name of the ability
description : Optional[str]
Optional description of the ability
"""
def __init__(self, **kwargs):
self.name = _getattr(self, "name", kwargs, self.__doc__ if self.__doc__ else self.__class__.__name__)
self.description = _getattr(self, "description", kwargs, self.effect.__doc__)
self.cost = _getattr(self, "cost", kwargs, 0)
def __hash__(self):
return hash(self.__class__.__name__)
def __eq__(self, other : "Ability"):
if not isinstance(other, type(self)):
return NotImplemented
return self.__class__.__name__ == other.__class__.__name__
def __repr__(self):
return f"<{self.__class__.__name__}>"
def __str__(self):
return self.name
def cast(self, user : "Entity", target : "Entity"):
"""Method that verifies if all conditions have been met."""
if not self.costing(user, target):
return
post_output(f"{user.name} casts {self.name} on {target.name}")
self.effect(user, target)
def calculate_cost(self, user : "Entity", target : "Entity"):
"""Private method to calculate the cost
Parameters
-----------
user : Entity
The Entity that used the ability.
target : Entity
This is the Entity the ability is aimed at, if the ability is self-cast (user uses ability on
himself) then it will be the same entity as the `user`.
"""
if callable(self.cost):
int_cost = self.cost(user, target)
else:
int_cost = self.cost
return int(int_cost)
def costing(self, user : "Entity", target : "Entity") -> bool:
"""Abstract method that does the logic part of the cost. This allow for flexibility on how you want your
cost system to work wether it rage or mana or whatever other custom cost system you may create your
class with. This method must return true if the user has enough resource and false if it doesn't.
Parameters
-----------
user : Entity
The Entity that used the ability.
target : Entity
This is the Entity the ability is aimed at, if the ability is self-cast (user uses ability on
himself) then it will be the same entity as the `user`.
"""
int_cost = self.calculate_cost(user, target)
if user.can_cast(int_cost):
user.energy -= int_cost
return True
return False
[docs] def effect(self, user : "Entity", target : "Entity"):
"""The method that defines what happens to the target when the ability is cast on it. This can range from
directly affecting things like health or energy but it can also do more complex things like add a modifier.
Parameters
-----------
user : Entity
The Entity that used the ability. This is made available to allow you to make changes to the user in
the cases of a successful cast. For example if you want to restore health for damage dealt or
restore energy of the ability successfully lands.
target : Entity
This is the Entity the ability is aimed at, if the ability is self-cast (user uses ability on
himself) then it will be the same entity as the `user`.
"""
raise NotImplementedError
[docs] def cost(self, user : "Entity", target : "Entity") -> int:
"""Method to implement if you want to give your ability a dynamic costing.
Parameters
-----------
user : Entity
The Entity that used the ability. This is made available to allow you to create a dynamic cost
the is based on certain items or stats of the user.
target : Entity
This is the Entity the ability is aimed at, if the ability is self-cast (user uses ability on
himself) then it will be the same entity as the `user`.
Returns
--------
int
The final, calculated cost of casting this ability
"""
return 0
@classmethod
def add(cls, **kwargs):
"""Decorator function to allow you to decorate a function in order to transform it into an Ability
subclass. Takes the same arguments as the class itself."""
def decorator(func):
new_class = type(func.__name__, (cls,), {
"cost": kwargs.get("cost", 0),
"effect": func,
"name": kwargs.get("name", func.__name__),
"description": kwargs.get("description", func.__doc__)
})
return new_class
return decorator