1
\$\begingroup\$

Prologue

Im quite new to data oriented programming and my goal is to implement a runescape stats & damage mechanic.
This is quite a pretty complex topic : [Runescape-Mechanics][1] and i havent found any ECS related sources on that topic yet.

In the following example we see a bunch of items which modify the weares stats based on a few conditions. This happens in two different variants, either for the damage calculation only, or as a buff.

Brine sabre and Brackish blade increase damage against crabs.
or
Silverlight and Darklight increase ability damage by a scaling of 25-124% against most demons. The exact damage bonus is based on your base Attack and Strength, and the monster's base Defence.

The problem

Such a RPG system is very complex. There different damage types, different resistences and other stats. Having a strong OOP background and no real experience in DOP, i cant find a suitable architecture to fill those needs.

In my current approach every stat is a component. Items and Buffs are structs. A item "buffs" its owner and the buff modifies his stats. This works so far, but i have no idea how i could realise the damage calculation, while still keeping it that flexible as it is in runescape.
This little example would just be able to buff the stats... not deal, receive or modify the damage itself.


// The stats

public struct Health{
  float max;
  float value;
}

public struct MeeleDamage{
  float base;
  float value;
}

public struct MeeleResistence{
  float base;
  float value;
}

// Item & Buffs
public struct Item{
  string name;
  int amount;
  bool equipable;
  List<Buff> buffs;
}

public struct Buff{
  string name;
  float duration;
  Condition applyable;
  ToBuff stat;
  float value;
}

Its also important that an entity can also deal damage to multiple other entities in one frame. How would you implement such an complex mechanic ? Any examples are appreciated !

\$\endgroup\$
1
  • 1
    \$\begingroup\$ We have some past Q&A about similar topics under the effect-modifier tag, which might be useful to you as you puzzle this out. Keep in mind that ECS/DOP is a tool to use when it helps, not a straightjacket to confine you when it doesn't. You can have an ECS/DOP foundation that still includes more OOP style elements where that style might help you work more intuitively/efficiently. \$\endgroup\$
    – DMGregory
    Apr 11 at 22:52
0
\$\begingroup\$

Here's one way to approach it.

First throw all your base stats into one component as follows:

public struct BaseStats {
    float maxHp = 100;
    float meleeDmg = 40;
    float meleeResistance = 20;
    ...
}

This BaseStat component is complimented by a StatModifier component with multiplier and offset modifiers for each stat:

public struct StatModifier {
    float maxHPMult = 1.0;
    float maxHPOffset = 0.0;

    float meleeDmgMult = 1.0;
    float meleeDmgOffset = 0.0;
    
    float meleeResistanceMult = 1.0;
    float meleeResistanceOffset = 0.0;

    ...
}

Your player entity will have both of these components attached and each stat is evaluated in a System using the following equation:

stat = (base * multiplier) + offset;

All equip-able items will also have a StatModifer, and when that item is equipped the on the player, its stat multipliers and offsets are added to the player's StatModifier.


So say you have a ring item that:

  • adds +50 health
  • increases melee damage by 10%
  • reduces melee resistance by 5%

Its StatModifier component would be implemented as follows:

public struct StatModifier {
    float maxHPMult = 0.0;
    float maxHPOffset = 50.0;

    float meleeDmgMult = 0.1;
    float meleeDmgOffset = 0.0;
    
    float meleeResistanceMult = -0.05;
    float meleeResistanceOffset = 0.0;

    ...
}

With the ring equipped the player entity's StatModifier component is updated with the following values:

  playerStatModifier.maxHPMult == 1.0 // unchanged
  playerStatModifier.maxHpOffset == 50 // 0.0 + 50.0
  
  playerStatModifier.meleeDmgMult = 1.1 // (1.0 + 0.1) up 10%
  playerStatModifier.meleeDmgOffset = 0.0 // unchanged
  
  playerStatModifier.meleeResistanceMult = 0.95 // (1.0 - 0.05) down 5%
  playerStatModifier.meleeResistanceOffset = 0.0 // unchanged 
  

The cool thing about this setup is that the player's StatModifier component acts as an accumulator, allowing you to easily stack status effects as items are removed and added.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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