1

I'm creating a game (well, a plugin) where each player has a list of skills, each of which has an unique type object, each of which has a list of actions that need to be ran when a player executes his skills. I drew a diagram to show my classes: https://i.stack.imgur.com/NOdDF.jpg

And here's a quick mockup in pseudo-python:

class Player:

    def __init__(self):
        self.skills = []

    def execute_actions(self, **event_args):
        for skill in self.skills:
            skill.execute_actions(player=self, **event_args)


class Skill:

    def __init__(self, type_object, level):
        self.type_object = type_object
        self.level = level

    @property
    def name(self):
        return self.type_object.name

    def execute_actions(self, **event_args):
        if self.level > 0:
            self.type_object.execute_actions(skill=self, **event_args)

    # more type_object properties


class SkillType:

    def __init__(self, name, max_level):
        self.name = name
        self.max_level = max_level
        self.actions = []

    def execute_actions(self, **event_args):
        for action in self.actions:
            if action.event == event_args['event_name']:
                action(**event_args)


class Action:

    def __init__(self, event, callback, group=None, cooldown=None):
        self.event = event
        self.callback = callback
        self.group = group
        self._cooldown = cooldown

   @property
   def cooldown(self):
       if self._cooldown is not None:
           return self._cooldown
       if self.group is not None:
           return self.group.get('cooldown', None)
       return None

# Usage

player = Player()

fireball = SkillType('Fireball', 5)

def fireball_deal_damage(skill, player, target, **event_args):
    player.damage(target, skill.level)

fireball.actions.append(
    Action('player_attack', fireball_deal_damage, cooldown=5)
)

player.skills.append(Skill(fireball, 1))

So, these skill types and actions are actually parsed from JSON:

// skills.json
[
  {
    "name": "Fireball",
    "max_level": 5,
    "actions": [
      {
        "event": "player_attack",
        "action": "deal_damage",
        "data": {"amount_base": 3, "amount_per_level": 1},
        "cooldown": 5
      }
    ]
  }
]

The action above has a cooldown of five, which is easy enough to implement, just give each action a dictionary of type {Skill: time_when_last_used} so it follows each Skill instance's cooldown for that particular action.

The problem is, sometimes I need to have multiple actions that all use the same cooldown (maybe the fireball spell needs to deal damage and ignite the enemy, so that's two separate actions), so I came up with the idea of "groups":

// skills.json
[
  {
    "name": "Fireball",
    "max_level": 5,
    "actions": [
      {
        "event": "player_attack",
        "action": "deal_damage",
        "data": {"amount_base": 3, "amount_per_level": 1},
        "group": 0
      },
      {
        "event": "player_attack",
        "action": "ignite",
        "data": {"duration_base": 0, "duration_per_level": 1},
        "group": 0
      }
    ],
    "groups": [
      {  // Group 0
        "cooldown": 5
      }
  }
]

Now some actions will use this group cooldown, so it's no longer as simple as giving each action instance a dictionary. Yet some actions will still use an individual cooldown rather than a "group" cooldown. How would I go on about implementing this?

2 Answers 2

1

Your action group model has significantly altered your original skill action design. By trying to shoehorn the old cooldown model onto the new design, you're just begging for complexity problems.

Re-examine your new skill action model, try to see if it is going to need further changes, and try to freeze it.

Only then, design an action cooldown model that actually fits the skill action model.

I know, stepping backwards is a real pain in the neck for the present, but stepping backwards is better then stepping off a cliff.

3
  • I don't think I follow here. What do you suggest as the alternative design choice? Jan 19, 2018 at 23:16
  • @MarkusMeskanen Not sure if that is the intended meaning, but for one thing it seems additional complexity that grouped cooldowns and non-grouped cooldowns are handled and modeled differently. If you need grouped cooldowns, then modeling such that every cooldown is grouped (even if the group has only one member) seems the simpler model and results in simpler code. What I also understood from his answer is that finish the skill action model first, then design an action cooldown model. Don't start with a finished model for both, change one and then change the other.
    – kutschkem
    Mar 21, 2018 at 13:48
  • 1
    @MarkusMeskanen For example, what if while designing the actions, you find that they have to use the same cooldown, but different times. Or the time depends on outside conditions (can only use the curse again when the cursed monster has died)... All that will change the cooldown model again towards a mix of the old and new model or something else entirely.
    – kutschkem
    Mar 21, 2018 at 13:52
0

Rather than Action having a data member cooldown, why not have a method on_cooldown(time_of_use, time_of_last_use). Then the Skill can hold it's last use time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.