3

I'm developing a RPG-game. After searching about various ways to save item information in RPG-game, I determined to save information in JSON files. The problem is that in my game, some equipment's stat depends on the character condition.

class Equipment {
    String name;
    double strength;
    double agility;
Equipment(String name) {
    this.name = name;
}

checkCondition(Character character) {
    switch (this.name) {
        case "itemA" -> {
            strength = 20;
                  //This is what I mean by "condition dependent item"
            agility = character.strength > 30 ? 20 : 10;
        }


        case "itemB" -> strength = 40;
    }
}

}

As you can see, "itemA"'s stat is dependent to characters' strength.

{
    "name": "itemB",
    "strength": 40,
    "agility": 0
}

This is how I planned to save the items, but "itemA" doesn't seem to fit in this form. Please let me know you have an idea to solve this problem.

DMGregory
  • 134,153
  • 22
  • 242
  • 357
evol1102
  • 51
  • 1
  • Are you rolling your item stats or do you have an fixed amount of items you can identify by id? – Zibelas Dec 18 '23 at 12:24
  • @Zibelas I'm not sure what you mean by 'rolling item stats', but what I'm trying to do is to store information of fixed amount of items(itemA, itemB, ... , itemN) – evol1102 Dec 18 '23 at 13:23
  • 2
    I've tagged your question [tag:effect-modifier] which is our tag for systems like this where an item applies modifications to another item, like a character, possibly subject to special conditions. A lot of similar considerations come up, whether those modifiers are buffs, potions, equipment, cards, etc. So you may want to take a read through other Q&A with that tag to get some inspirations. What's a way to implement a flexible buff/debuff system? is the most extensive so far, so it's a particularly good one to look at. – DMGregory Dec 18 '23 at 14:18
  • Your first mistake was to store the stat as a stateful field, rather than calculating it on the fly whenever it is requested. This mistake was already present before you even considered persisting the information to an external source. – Flater Dec 19 '23 at 00:35
  • Additionally, while it doesn't strictly cause the problem at hand, the implementation of the checkCondition method is a textbook OCP violation which should ideally be refactored to not explicitly list every possible string value that could be used for the item's name, instead relying on either inheritance or composition to figure out how to calculate stats based on the item's type. – Flater Dec 19 '23 at 00:36
  • Thirdly, I doubt your equipment has strength or agility. These values (and the calculation of the logic) belong to the character, not the character's equipment. The equipment can yield a stat modifier. You might be using the strength/agility fields as modifiers to apply to the character (it's unclear from your question), but at the very least the naming leaves something to be desired. – Flater Dec 19 '23 at 00:41
  • @Flater Thank you for your advice, however I couldn't understand the meaning of "Your first mistake was to store the stat as a stateful field, ~ whenever it is requested.". Could you explain it in more detail? – evol1102 Dec 19 '23 at 04:09
  • 1
    @evol1102: If I write down the current date on a piece of paper, that's problematic. Because now I have to worry if the piece of paper is still up to date, and I have to make sure to update the piece of paper at the right time because otherwise I'm going to end up doing the wrong thing because an older date is still written down. Instead of writing it down, it would be better if I looked it up from a reliable source instead (which for your example means doing that calculation whenever you want to access the value, instead of doing it pre-emptively and storing it in a field). – Flater Dec 19 '23 at 08:12
  • @Flater it sounds like you have some thoughts to share that might be better presented in an answer, free from the limitations of a comment thread. – DMGregory Dec 19 '23 at 17:12
  • @DMGregory: I don't think I can write a conclusive answer that doesn't redevelop OP's solution from the ground up and/or boils down to advising OP to "look up tutorials on this". I'm pointing out a complexity, but providing a final answer is decidedly more difficult. – Flater Dec 20 '23 at 01:12

1 Answers1

6

I wouldn't do this based on item name. Instead, I'd implement a system of modifiers to describe what an item does, something like...

{
    name: "itemA",
    displayName: "Slippers of Mighty Grace",
    effects: [
        {
            type: "statCheck",
            stat: "strength",
            threshold: 31,
            effectsIfMet: [
                {
                    type: "statBoost",
                    stat: "agility",
                    value: 20
                }
            ],
            effectsIfNotMet: [
                {
                    type: "statBoost",
                    stat: "agility",
                    value: 10
                }
            ]
        }
    ]
}

When you load this item from JSON, you string together a matching pattern of polymorphic effect nodes, each with their own Apply method, something like this:

public class StatCheckEffect implements ItemEffect {
    public Stat stat;
    public int threshold;
public ItemEffect[] effectsIfMet;
public ItemEffect[] effectsIfNotMet;

public void Apply(Character char) {
    if (char.GetStat(stat) >= threshold) {
        for(ItemEffect effect : effectsIfMet)
            effect.Apply(char);
    } else {
        for(ItemEffect effect : effectsIfNotMet)
            effect.Apply(char);
    }
}

}

public class StatBoostEffect implements ItemEffect { public Stat stat; public int value;

public void Apply(Character char) {
    char.ModifyStat(stat, value);
}

}

When the item is equipped, you run the Apply() methods on all its top-level effects. When the character's status changes in a way that might affect equipment properties, you reset the character's stats to base and re-run the Apply() methods of all equipped gear to find the new net result. (More sophisticated caching to avoid redundant processing is possible, but do it the simple way first to check if you really need the added complexity)

DMGregory
  • 134,153
  • 22
  • 242
  • 357
  • This is exactly what I was looking for! But, I have question about "you reset the character's stats to base and re-run the Apply()". When I reset the character's stats and re-run the Apply() methods, isn't the results always same?(Since char.GetStat(stat) would be the initialization value of a character) – evol1102 Dec 18 '23 at 15:24
  • Here I'm assuming this is being done in response to a change in the character's base stats, like if they've leveled up and their strength is 1 point higher now, or a change to other character properties that might be used to trigger conditions on items. – DMGregory Dec 18 '23 at 15:27
  • Oh, now I understand. Thank you very much for you kindness :D – evol1102 Dec 18 '23 at 15:34
  • The only problem with rerunning the apply could be if there are cursed items that lower stats if you are over a threshold. Curse lowers the attributes and Bonus falls off, no bonus removes the curse and that lets you apply the Bonus again. – Zibelas Dec 18 '23 at 15:53
  • 2
    If you have issues like that, I'd recommend separating "base stat" and "modified stat" and have conditions only ever trigger off of "base stat", but that's a whole extra set of design problems that merits a separate question, I'd say. – DMGregory Dec 18 '23 at 17:37