-2

I am developing a little console game where you go through a dungeon. I'm trying to make a potion system for it, the idea is that when you use a potion, it changes a specific stat of the player.

The stats of the player are stored in a static class called Stats. I want to create different potions from the same class, and change the stat the potion acts on using its constructor method. The way I want this to work is that, when a new potion instance is created, I pass to the constructor a reference to the stat variable, and the constructor will store that reference in a variable, to use it when the potion is used.

I tried using delegates with getters and setters, but it didn't work because they only work with functions. I can solve this problem making a potion id system, or learning to proper use pointers, but I prefer to use only safe code.

My question is: There is a way in c# to store a reference to a variable in another variable?

The Stats class:

static class Stats{
    public static int health = 10,
                      strenght = 5,
                      defense = 20;
}

The potion class:

class Potion {
    int statRef; //This is the "reference holder" variable I was asking about.
    int magnitude;
        public Potion(ref int stat, int _magnitude)
        {
             magnitude = _magnitude;
             statRef = stat; //Here I want to save the reference to the stat to the "reference holder"
        }

        public void UsePotion()
        {
            statRef += magnitude; //Here I want to change the referenced variable's value.
        }

}

The main program:

class Program{
    static class Main(string[] args)
    {
       Potion lifePotion = new Potion(Stats.life, 5);
       Potion strenghtPotion = new Potion(Stats.strenght, 5);
       Potion defensePotion = new Potion(Stats.defense, 10);
      
       lifePotion.UsePotion();
       strenghtPotion.UsePotion();
       defensePotion.UsePotion();

      Console.WriteLine(Stats.health);
      Console.WriteLine(Stats.strenght);
      Console.WriteLine(Stats.defense);

    }
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • Hi, please read https://stackoverflow.com/help/minimal-reproducible-example and https://stackoverflow.com/help/how-to-ask. Show what you tried and create a psuedo-example of what you are trying to accomplish in code. I.e. you should be able to describe your issue with code. – Connor Low Mar 03 '21 at 17:26
  • 2
    Part of the issue is that you're using a static class. There are many ways to solve the problem so it's too broad a question, but you'll want to use class instances. Here's one idea: Write a class with each stat as a property. Create one instance with the *base* values. Then, create an instance that's a copy of that, with the effects of each active potion applied for your *effective* stats. Each time through the game loop (or however often you need), replace that copy with a new copy of the base stats and reapply the active potions. – madreflection Mar 03 '21 at 20:11
  • 2
    This is a classic X,Y problem: https://meta.stackexchange.com/a/66378/568190 you don't actually want to use pointers at all, not in the sense that you are thinking about them. You need to think more _object oriented_ – Chris Schaller Mar 04 '21 at 03:34
  • the reference to `Stats` should _passed in_ to UsePotion, it probably shouldn't be static, but that's a different discussion. – Chris Schaller Mar 04 '21 at 03:37

2 Answers2

1

Note that a class is a reference type. So, variables of a class type automatically contain references and you can assign the same reference to another variable. You can only create an object (i.e., an instance of a class) if the class is not static. Then you can assign it to a variable.

The fields or properties must not be static. Non static members are called instance members. Each instance (object) has its own copy of the fields and properties that must be accessed through a variable name. Static members, in contrast, are shared among all objects of this type and must be accesses through the class name.

Stats stats1 = new Stats();
Stats stats2 = stats1;

now both variables reference the same Stats. If you make the change

stats1.health = 5;

then stats2.health is also 5 since both reference the same object. But of course, you can create independent Stats objects:

Stats stats1 = new Stats();
Stats stats2 = new Stats();

Now changes to stats1 do not affect stats2.


Note that an important idea of Object-Oriented Programming (OOP) is that objects should hide their internal state, i.e., their fields. From outside the state should only be accessible through methods. These methods ensure that the state is manipulated in an adequate manner. E.g., it could be ensured that the health stays within a valid range.

Properties are specialized methods allowing to manipulate the state of fields. They usually consist of a pair of get and set methods and can be accessed like a field.

Example:

class Stats
{
    private int _health = 10;
    public int Health
    {
        get { // called when reading the value: int h = stats1.Health;
            return _health;
        } 
        set { // called when setting the value: stats1.Health = 5;
            if (value < 0) {
                _health = 0;
            } else if (value > 100) {
                _health = 100;
            } else {
                _health = value;
            }
        }
    }
}

If a property has no such logic, you can use an auto implemented property. It automatically creates an invisible backing field (like _health) and returns and sets its value.

public int Health { get; set; }

Let us put the things together. Simple example of Stats class:

class Stats
{
    public int Health { get; set; } = 10;
    public int Strength { get; set; } = 5;
    public int Defense { get; set; } = 20;
}

Now you can reference a Stats object in the Potion class.

Because you want to have different kinds of potions, you can use inheritance (another important concept of OOP) to achieve this.

You can declare an abstract base class, i.e., a class that cannot be instantiated and can itself contain abstract members, i.e., members that have still to be defined in derived classes.

abstract class Potion
{
     // This is the "reference holder" variable you were asking about.
    protected Stats _stats;

     // Protected means private and visible to derived classes.
    protected int _magnitude;

    public Potion(Stats stats, int magnitude)
    {
        _stats = stats; // Save the reference to the stat to the "reference holder"
        _magnitude = magnitude;
    }

    public abstract void UsePotion();
}

Now the derived LifePotion class as an example

class LifePotion : Potion // Inherits Potion.
{
    public LifePotion(Stats stats, int magnitude)
       : base(stats, magnitude) // Calls the base constructor.
    {
    }

    public override void UsePotion()
    {
        _stats.Health += _magnitude; // Change a property of the referenced variable.
    }
}

Repeat the same for StrenghtPotion and DefensePotion classes with UsePotion setting the Strength and Defense properties.

The adapted main program

class Program{
    static class Main(string[] args)
    {
        var stats = new Stats();
        Potion lifePotion = new LifePotion(stats, 5);
        Potion strenghtPotion = new StrengthPotion(stats, 5);
        Potion defensePotion = new DefensePotion(stats, 10);

        lifePotion.UsePotion();
        strenghtPotion.UsePotion();
        defensePotion.UsePotion();

        Console.WriteLine(stats.Health);
        Console.WriteLine(stats.Strength);
        Console.WriteLine(stats.Defense);
    }
}

Note that you can override ToString in a class and provide your own implementation. Add this to the Stats class:

public override string ToString()
{
    return $"Health = {Health}, Strength = {Strength}, Defense = {Defense}";
}

Then you can print the health like this in the main routine:

Console.WriteLine(stats); // Prints: Health = 15, Strength = 10, Defense = 30
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
-2

C# is an object-oriented programming language. That means it is designed to use “objects”, or in-memory instances of classes, that are responsible for maintaining their own state. Now you don't have to do this, but the more you stray from this design the less the language supports you, and the more work you have to do yourself.

Your design is not object-oriented. There probably isn't a “stats” that wanders around in your game, statting things. It probably isn't static either. Static classes are for concepts that can't change. For example, Math.Sin is static; its meaning can't change and my Math.Sin is your Math.Sin.

Instead of static Stats wandering around, your game probably has characters or Mooks. So make a class for them:

public class Mook
{
    public string Name { get; }
    public int Strength { get; private set; }

    public Mook(string name, int strength)
    {
        Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name)) : name;
        Strength = strength;
    }
}

Now you can create instances of Mooks:

var player = new Mook("Link", 10);
var monster = new Mook("Orc", 11);

Mooks can do things, like attack or drink potions. Potions can do things, like modifying your strength. Each class is responsible only for its own internal state; you don't have potions changing Mooks, only the Mook themselves can do that. Classes do things that change themselves through methods. If you want a Mook to drink a potion you have to create a method inside your Mook class to do that:

    public void Drink(Potion potion)
    {
        switch (potion.Sipped())
        {
            case PotionEffect.ModifyStrength:
                Strength += potion.Modifier;
                break;
        }
    }

Potions don't decide what happens outside themselves, only the class using the potion does. To track the possible effects of a potion, create an enum:

public enum PotionEffect
{
    Nothing,
    ModifyStrength
}

Potions are other objects, so you need to create another class. Remember, each class is responsible for maintaining its own state:

public class Potion
{
    public PotionEffect Effect { get; }
    public int Modifier { get; }
    public int Doses { get; private set; }

    public Potion(PotionEffect defaultEffect, int modifier, int doses)
    {
        Effect = defaultEffect;
        Modifier = modifier;
        Doses = doses;
    }

    public PotionEffect Sipped()
    {
        if (Doses <= 0)
            return PotionEffect.Nothing;

        Doses--;
        return Effect;
    }
}

Now you can create potions:

var strengthPotion = new Potion(PotionEffect.ModifyStrength, +1, 10);

And have mooks drink them:

player.Drink(strengthPotion);
monster.Drink(strengthPotion);
Dour High Arch
  • 21,513
  • 29
  • 75
  • 90
  • Thanks for the help! Now I understand what the people means by "you have to be more object-oriented'. I haven't too much experience in the object oriented programming, I only learnt how to program in C#, but I never learnt how to proper design a oop system. I'll have to refactorize all the program, but this time It will be more 'object-oriented'. Thanks again!! – Benicio Dominguez Mar 15 '21 at 22:52