When you first start programming, the problem you're trying to solve is "how do I write code that'll do ______?". Initially you're happy just to get something working, who cares what the code looks like. But when you try to develop a complete game this indifference can become a problem. As your code gets messier and messier, the project becomes harder to work on - you're more likely to write code that has bugs and they become harder to track down and fix. Eventually it'll get to the point where you're wasting a lot of time, don't feel productive, and are more likely to stop working on the project.
As a more experienced programmer, instead of asking "how do I do ______?", you should start asking "what's the best way to do ______?". In this post we'll look at different ways to write a simple function to see how certain ways are better than others.
Suppose you have a proc that's called when one mob deals damage to another and you're not sure if it should belong to the mob dealing damage or the mob taking damage. There are many ways you could look at this. For instance, you could argue that the proc should belong to the mob that's performing the action. The attacker is doing the action, so it should look like this:
mob
proc
attack(mob/target, damage)
target.health -= damage
That makes sense, but you could also argue that the mob being attacked is the one who's really performing the action. The attacker just generates a damage value, the real work is done by the mob being attacked because there may be a lot of things to take into account - armor, elemental resistances, chance to evade, etc.
So, you could also implement it like this:
mob
proc
attack(mob/target, damage)
target.take_damage(src, damage)
proc
take_damage(mob/attacker, damage)
health -= damage
That also makes sense, but you may have some situations where the attacker is more actively involved. The chance for the attack to hit depends on the attacker and the target, so who does that belong to?
It's very easy to get stuck on an issue like this. You want to do things the right way but you keep going in circles and can't bring yourself to implement the feature at all. While it's good to think about code before you write it, if you spend too much time thinking and not enough time coding, you'll just be wasting time. At some point it becomes better to just pick one way to implement it, write the code, and see how it turns out.
In this case, we'll arbitrarily decide to go with the first method and have everything handled by a proc belonging to the attacker. Here's the code we end up with:
mob
proc
attack(mob/target, damage)
var/hit = rand(speed + target.speed) <= speed
if(hit)
damage = (damage + power) * (speed / target.speed) - armor
else
damage = 0
target.health -= damage
First we determine if the attack hits or not. If it does, we apply some modifications to the damage value based on the attacker and defender's stats (if the attack didn't hit, the damage is zero).
Do you see any problems with that code?
The problem is on the line after the if statement. The end of that line should be target.armor instead of armor because we're reducing damage based on the target's armor value, not the attackers. But, because the proc to attack a mob belongs to the mob, forgetting to type "target." makes it reference the attacker's variable. Since they're both mobs this var exists and the code compiles but is not correct.
You can waste time going in circles like that and completely miss an alternative. Even worse, the alternative can make itself obvious if you just picked a method and went with it. If you had gone ahead and just implemented the proc either way, you'd have more easily come up with a preferred option:
proc
attack(mob/attacker, mob/defender, damage)
var/hit = rand(attacker.speed + defender.speed) <= attacker.speed
if(hit)
damage = (damage + attacker.power) * (attacker.speed / defender.speed) - defender.armor
else
damage = 0
defender.health -= damage
By making it a global proc you're forcing yourself to specify if the var belongs to the attacker or defender. You're less likely to make mistakes and are more likely to catch them because you have to explicitly state which mob each var belongs to.
There are two lessons to take away from this:
1. Know that your code will change so don't think that you're going to get everything right on the first try. Think about your code before writing it, but don't spend too much energy trying to get it right the first time. Write code so you can easily change it and get it right on the second try, but this also requires that you do take a stab at things and make a first attempt.
2. Base your work on concrete results, not theoretical results. Taking a blind stab at implementing a feature is a concrete step. It gives you something to analyze, something to improve, and something that works in the meantime.
How often do you see BYOND developers making concrete progress? How often do you see BYOND developers refine their mob icon while talking theoretically about different ways the game might work? How often do you want to strangle people and say "just make the game already!"
Note: my answers to those questions were: "rarely", "often", and "often".
At first I was confused at this, but I eventually got it. I never knew you could put those arguments (correct words?) in a var and then check it in an if statement, like your first lesson, much easier to the eyes.
Thanks for this Forum, glad you made this one too. :)