Repository navigation
Weapon System
A weapon is a WeaponItem: an inventory item with a type, a tier and a set of statistics. The same class is used by the player (a weapon held in the inventory and swung with the attack key) and by enemies (a weapon handed to an attack component, see Melee Attacks and Ranged Attacks).
All of the classes are in components/loot/.
| Class | Kind | Responsibility |
|---|---|---|
WeaponType |
enum | the five kinds of weapon |
WeaponTier |
enum | the three tiers, each holding the statistics of every weapon type at that tier and a loot weight |
WeaponStats |
interface | what a block of weapon statistics can be asked for |
WeaponStatsPreset |
class | one weapon type's statistics at one tier |
WeaponItem |
class | the weapon itself: an Item with a type, a tier, statistics and a windup |
WeaponGenerator |
class | builds a WeaponItem from a type and a tier number |
WeaponUpgrader |
class | builds the next tier of a weapon |
WeaponUpgradeEffect |
class | the Upgrade Stone: upgrades the weapon the player is holding |
| Type | Used for |
|---|---|
SWORD |
melee |
DAGGER |
a small weapon that stacks to 20 |
AXE |
melee; the hardest hitting and the longest reach |
BOW |
ranged; fires projectiles |
NATURAL |
an enemy attack that is part of its body (a bite, a stomp, a gaze); see Natural Weapons below |
Attack components check the type when they are built:
-
MeleeAttackComponentrejects aBOW. -
RangedAttackComponentrejects aSWORD, aDAGGERand anAXE. - Both accept
NATURAL.
There are three tiers. Each tier holds one WeaponStatsPreset per weapon type, with these statistics: damage, attack speed, knockback, range and projectile count.
Tier 1 (loot weight 60)
| Type | Damage | Attack speed | Knockback | Range | Projectiles |
|---|---|---|---|---|---|
| Sword | 10 | 1.0 | 1.0 | 2.0 | 1 |
| Bow | 7 | 1.0 | 0.5 | 8.0 | 1 |
| Dagger | 3 | 1.0 | 0.5 | 1.0 | 1 |
| Axe | 12 | 1.0 | 1.3 | 2.3 | 1 |
Tier 2 (loot weight 30)
| Type | Damage | Attack speed | Knockback | Range | Projectiles |
|---|---|---|---|---|---|
| Sword | 20 | 1.2 | 1.5 | 2.5 | 1 |
| Bow | 4 | 1.2 | 0.75 | 10.0 | 5 |
| Dagger | 6 | 1.3 | 0.75 | 1.2 | 1 |
| Axe | 23 | 1.2 | 1.8 | 2.8 | 1 |
Tier 3 (loot weight 10)
| Type | Damage | Attack speed | Knockback | Range | Projectiles |
|---|---|---|---|---|---|
| Sword | 30 | 1.5 | 2.0 | 3.0 | 1 |
| Bow | 3 | 1.5 | 1.0 | 12.0 | 10 |
| Dagger | 9 | 1.6 | 1.0 | 1.4 | 1 |
| Axe | 34 | 1.5 | 2.3 | 3.3 | 1 |
Things to notice:
- A higher tier bow fires more projectiles, each doing less damage: 1 x 7, then 5 x 4, then 10 x 3.
- The axe hits harder, knocks back farther and reaches further than the sword at every tier.
- The loot weight makes a tier 1 weapon six times as likely to drop as a tier 3 one.
LootTablereads it throughgetLootWeight().
WeaponTier.fromTierNumber(n) finds a tier by its number and throws IllegalArgumentException for a number outside 1 to 3. getStats(type) throws for a null type and for NATURAL, which has no tier statistics.
WeaponItem extends Item with the item type WEAPON. It can be built three ways.
From a tier (what WeaponGenerator uses). The damage and the other statistics are looked up from the tier.
public WeaponItem(String name, WeaponType weaponType, WeaponTier weaponTier, int quantity, int maxQuantity, float windupDuration) {}
From a damage value, with no tier statistics. The tier is reported as 1, and attack speed, knockback and range are reported as 0.
public WeaponItem(String name, WeaponType weaponType, int damage, int quantity, int maxQuantity, float windupDuration) {}
public WeaponItem(String name, WeaponType weaponType, int damage, int quantity, int maxQuantity) {}
As a natural weapon, described below.
The constructors throw IllegalArgumentException for a null type, a null tier, or a damage of zero or below.
windupDuration is the delay, in seconds, between an attack being started and the hit landing. It belongs to the weapon because it matches the weapon's swing animation. An attack component reads it with getWindupDuration() and refuses a weapon whose windup is not shorter than the attack's cooldown. setWindupDuration changes it; the Minotaur's factory sets its axe's windup to 0.
new WeaponGenerator().generateWeapon(WeaponType.AXE, 2)
builds a tier 2 axe. The generator sets the name, the stack size, the windup and the sell price for each type:
| Type | Name | Stack size | Windup | Sell price |
|---|---|---|---|---|
| Sword | Basic Sword | 10 | 2 s | 10 x tier |
| Bow | Basic Bow | 10 | 3 s | 8 x tier |
| Dagger | Basic Dagger | 20 | 1 s | 6 x tier |
| Axe | Basic Axe | 2 | 3 s | 12 x tier |
It throws IllegalArgumentException for a null type, a tier of zero or below, a tier above 3, and for NATURAL.
Some enemies attack with their body and carry no weapon. So that these attacks can use the same attack components as an armed enemy, the factory builds a natural weapon:
WeaponItem fists = WeaponItem.natural("Cyclops Fists", config.baseAttack, config.melee.windup);A natural weapon has the type NATURAL, a quantity of 1, and exactly the damage and windup it is given. It has no tier statistics. It is never added to the enemy's inventory, so it never drops, and it is never offered as loot.
| Enemy | Natural weapons |
|---|---|
| Cyclops | Cyclops Fists, Cyclops Rock Throw, Cyclops Laser |
| Medusa | Medusa Bite, Medusa Gaze |
| Cerberus | Cerberus Bite |
| Zeus | Zeus Lightning (his sword is a real, generated weapon) |
WeaponUpgrader.upgrade(weapon) returns a new weapon one tier higher and leaves the original untouched.
- A dagger becomes a sword of the next tier. Every other type stays the same type.
- Upgrading a
nullweapon throwsIllegalArgumentException. - Upgrading a tier 3 weapon throws
IllegalStateException, with the weapon's type in the message.
WeaponUpgradeEffect is the Upgrade Stone's effect. It upgrades the weapon the player last held, and it upgrades the whole stack: 20 tier 1 daggers become 20 tier 2 swords. The stone is kept, not used up, when there is no weapon to upgrade, the weapon is already tier 3, or there is no room in the inventory for the upgraded weapons.
- Enemies take only the damage and the windup from the weapon. Range, knockback and cooldown are the enemy's own config values.
-
The player attacks through
WeaponAttackComponent, which swings whichever weapon is in the active inventory slot and reads the weapon's projectile count for a bow.
| Enemy | Generated weapon |
|---|---|
| Skeleton | sword, tier 1 |
| Ranged Skeleton | bow, tier 1 |
| Minotaur | axe, tier 2 |
| Centaur | bow, tier 1 |
| Zeus | sword, tier 2 |
| Harpy | sword, tier 1 |
| Ranged Harpy | bow, tier 1 |
A generated weapon sits in the enemy's inventory and is dropped when the enemy dies.
- Add a value to
WeaponType. - Add a
WeaponStatsPresetfor it to each tier inWeaponTier, and register it in the tier's constructor. - Add a case for it to
WeaponGenerator.generateWeapon, with its name, stack size, windup and sell price. - Decide which attack components accept it, and update their constructor checks.
- Add its tier values to
WeaponStatsPresetTest.
Ten test classes in components/loot/ cover the weapon system, with 107 tests in total.
| Test class | Tests | What it covers |
|---|---|---|
WeaponItemTest |
9 | Building a weapon item |
WeaponTierTest |
9 | Tier numbers and looking up statistics |
WeaponStatsPresetTest |
17 | The statistics of every type at every tier |
WeaponTierLootWeightTest |
3 | The loot weight of each tier |
WeaponGeneratorTest |
19 | Generating each weapon type |
WeaponGeneratorTierTest |
3 | Generated weapons carry the right tier |
WeaponAxeTest |
23 | The axe, end to end |
WeaponUpgraderTest |
7 | Upgrading a weapon |
WeaponUpgradeEffectTest |
13 | The Upgrade Stone |
WeaponInventoryIntegrationTest |
4 | Weapons in the inventory |
The axe was added this sprint, so it has one class that follows it through every part of the system.
Tests were used to ensure the following situations were working:
- An axe above the top tier, or at tier zero or below, is rejected
- Each generated axe has the tier it was asked for
- The tier 1 and tier 3 axe statistics are exposed through the item
- The axe hits harder than the sword at every tier
- The axe knocks back farther and reaches further than the sword at every tier
Tests were used to ensure the following situations were working:
- A tier 1 axe upgrades to a tier 2 axe, and an axe can be upgraded through every tier
- Upgrading a tier 3 axe is rejected, and the message names the weapon
- The upgraded axe has more damage and a higher sell price
- The original axe is left untouched
Tests were used to ensure the following situations were working:
-
MeleeAttackComponentaccepts an axe and reports its damage -
MeleeAttackComponentaccepts an axe with its windup set to zero, as the Minotaur's is -
MeleeAttackComponentrejects an axe whose windup is not below the cooldown -
RangedAttackComponentrejects an axe
Tests were used to ensure the following situations were working:
- Swinging an axe announces an axe swing carrying the axe's damage, and does not throw
- A tier 3 axe carries its tier 3 damage in the swing
- An axe does not announce a sword swing
- Swinging an axe does not spawn an arrow or a dagger
- The axe in the active inventory slot is the one that is swung
Tests were used to ensure the following situations were working:
- Two axes of the same tier stack, and a third starts a second stack
- Axes of different tiers do not stack
Tests were used to ensure the following situations were working:
-
WeaponItemTest: a weapon item stores its type and damage, and rejects a null type and a damage of zero or below -
WeaponTierTest: a tier is found by its number, a number outside 1 to 3 is rejected, and statistics are returned for each weapon type -
WeaponStatsPresetTest: every statistic of every weapon type at every tier matches the tables on this page -
WeaponTierLootWeightTest: each tier has its loot weight, and lower tiers are more likely -
WeaponGeneratorTestandWeaponGeneratorTierTest: each type is generated with the right name, statistics and tier, and a null type or an invalid tier is rejected -
WeaponUpgraderTest: a weapon upgrades by one tier, a dagger becomes a sword, and a null weapon or a tier 3 weapon is rejected -
WeaponUpgradeEffectTest: the Upgrade Stone upgrades the held weapon and the whole stack, and is kept when there is nothing to upgrade -
WeaponInventoryIntegrationTest: weapons are added to, stacked in and read back from the inventory
LootTableTest (13 tests) covers weapons being offered as loot by tier weight, with natural weapons left out.