After playing with the name generator I discussed in the previous post, I decided to tackle powers and abilities next. While having a cool name generator is neat on it’s own, I really wanted to be able to use this tool in future game design projects. In order to do that, I needed to understand how to combine all these attributes into a single weapon with various bonuses, weaknesses, and powers.
At this point, there is no game engine defined that would give these numbers meaning, so I had to come up with something. I’m still not sure how the final game would look and feel, but I do know that I would want to keep it simple, so I came up with a list of powers your items may have. I talked about that in the introduction to the No Rogues Allowed Random Weapon Generator, but I’ll list everything here again for clarity.
- Hands – 1 or 2 handed weapon
- Multiplier – Applies to all rolls with this weapon
- Attack – Base Attack
- Defense – Bonus to Defense (blocking with the weapon)
- Magic – Bonus to Magic
- Flaming/Freezing/Socking – Damage Modifiers
- Holy/Evil – Damage Modifiers
- Sneak – Fleeing Combat
- Crit – Critical Hit modifier
I wasn’t quite sure how all these modifiers and values would add up, but I knew that I wanted each attributed from our name generator to apply some sort of bonus (or negative) to one or more of these categories. This way, each name piece is more that just a fancy title. Each weapon becomes more unique with additional attributes.
Disclaimer: All code shared here is my own and is provided with no guarantees that it will work. I assume no responsibility for damages incurred should you use my code in your projects. Also, feel free to use it however you see fit.
Storing the Values
My first inclination was to use multidimensional arrays. Each inner array would be one of the attributes and it would contain the attributes it adjusted… but this could get out of hand really fast.
$weapons = [ 'sword' => [ 'hands' => 1, 'attack' => 3, 'defense' => 0, 'magic' => 0, ], 'great axe' => [ 'hands' => 2, 'attack' => 9, 'defense' => 2, 'magic' => 0, ], ];
Having nested arrays like this buried in code somewhere also makes it very difficult to adjust (tweak) these numbers later on. My second thought was comma-separated (CSV) files. These are super easy to edit with any text editor and it would allow me to quickly expand the list as needed.
"sword",1,1,3,0,0,0,0,0,0,0,0,1 "great axe",2,1,9,2,0,0,0,0,0,0,-1,1
In the early 90’s, many of my favorite games used files like these (flat files) to store in-game elements like phrases or descriptions. It was fun to edit these files and watch the changes appear as I played. My experiences with that tarnished my young-developer mind and for many years after I refused to learn proper data-storage techniques and built many, many applications with flat-file “databases”.
Hopefully you see where this is all headed. I’m a web developer after all. I use SQL every single day of my life and this is the perfect sort of data storage solution we need for something like this. It’s fast, flexible, and even has some built-in features we can use to our advantage later. I went with mySQL – MariaDB to be exact – and whipped up a simple table structure:
CREATE TABLE `weapon_type` ( `id` int(11) NOT NULL AUTO_INCREMENT, `word` varchar(64) COLLATE utf8_unicode_ci NOT NULL, `hands` int(1) NOT NULL DEFAULT '1', `attack` int(3) DEFAULT '0', `defense` int(3) DEFAULT '0', `magic` int(3) DEFAULT '0', `flaming` double(5,2) DEFAULT '1.00', `freezing` double(5,2) DEFAULT '1.00', `shocking` double(5,2) DEFAULT '1.00', `holy` double(5,2) DEFAULT '1.00', `evil` double(5,2) DEFAULT '1.00', `sneak` double(5,2) DEFAULT '1.00', `crit` double(5,2) DEFAULT '1.00', `multiplier` double(5,2) DEFAULT '1.00', PRIMARY KEY (`id`), UNIQUE KEY `word` (`word`) )
Why Int AND Double?
Because they are different things. Remember, I’m not a game developer. I’m reinventing the wheel here. I’ve never tried to design a game like this with stats and rolls. I’ve never researched it. I’m just doing what makes sense to me and this is what I came up with.
Hands – INT – I hope this is self-explanatory, but weapons will never take up a part of your hand. Only 1 or 2.
Attack/Defense/Magic – INT – These are base values that will be used to “roll” your damage, etc. Think of these like dice. An Attack of 6 is like rolling a six sided die.
Flaming/Freezing/Shocking/Holy/Evil – DOUBLE – These are multipliers. They are applied after your base roll is calculated. If I want an attribute to add 20% damage against enemies weak to fire, I put 1.2 in the Flaming column. After my base attack is calculated, I can multiply the result by 1.2 to adjust for the weakness. I don’t need crazy precision here. I’m good with just two decimal places.
Crit/Sneak – DOUBLE – As above, these values are percentage modifiers when checking your chance to score a critical hit, or to flee combat.
Multiplier – DOUBLE – This value is a final adjustment to the total damage inflicted by the weapon.
Yes, yes, but WHY?
Because I don’t know what I’m doing, and using doubles as modifiers made sense to me. It gives me the ability to have negative modifiers as well. If I want your sword to be rusted, I can give it a .8 modifier and reduce the total damage by 20%. It also makes the math (and thus the code) simpler in the next step where we have to add all these things up. But first, let me show you an example of the current prefix table.
Things here are a little different. Where I gave all the multipliers in my weapons table a default value of 1, here everything gets a zero. Remember when I said this makes the code easier? I’ll show you why.
//////////////////////////////////////////////////////////////////////////////// // fuction sumWeaponAttributes() //////////////////////////////////////////////////////////////////////////////// // Adds attributes for a weapon // takes two associative arrays, sums their contents based on key // returns associative array //////////////////////////////////////////////////////////////////////////////// function sumWeaponAttributes($weapon, $array) { foreach($array as $key=>$value) { if (array_key_exists($key, $weapon)) { $weapon[$key] += $value; } } return $weapon; }
Every time I add a new weapon attribute… element, prefix, adjective, noun… I can run them through this simple function that sums the totals together. That Dagger you received with a base modifier of 1.00 turns into a much weaker weapon when it becomes a Rusted Dagger and the -.80 is applied.
My initial model for this was much more complicated where I was going to sum all of the int columns, but multiply all the other columns based on an assumed starting value. This would have worked, but it would have been much more complicated and far less flexible. To explain the function above, when we retrieve our weapons and attributes from the database, we are using PDO to fetch the results as associative arrays.
// prefix if ((rand(0, 100) * $level) >= 75) { // 25% of all weapons $sql = $pdo->query("SELECT * FROM weapon_prefix WHERE active = 1 ORDER BY RAND() LIMIT 1"); $weapon_prefix = $sql->fetchAll(PDO::FETCH_ASSOC); $weapon = sumWeaponAttributes($weapon, $weapon_prefix[0]); $weapon['str'] += 3; }
This is a powerful tool for us because it means that we haven’t constrained ourselves in our code. If I had written a function that summed certain columns and multiplied the rest, the code would have required that all those values existed. What if later on down the road I decide that I want to add a poison attribute? I’d have to go back through the code and update all my functions that deal with weapon generation.
With this method, all I need to do is add that column to the tables in the database, and it would be taken care of for us! Conversely, we could also choose to remove one of these columns and it wouldn’t change our code at all.
Up Next, Putting it all Together
I think we’ll stop here for now. There are some things in the code above that I haven’t talked about yet, and I will save those for another post. All these little details will hopefully make sense as I explain how we are getting these attributes from the database and using them in our final weapon. Until then, go play with the weapon generator a bit. Maybe try to break it?
Pingback: My Code Quality – Roguelike Design part 3 – Chevee Dodd
Pingback: Creating Better Items – Roguelike Design part 4 – Chevee Dodd