Files
voxelforge/core/include/vf/Equipment.h
T

168 lines
6.5 KiB
C++

#pragma once
#include <array>
#include <cstdint>
#include <vector>
#include "ContainerMenu.h"
#include "Damage.h"
#include "ItemStack.h"
namespace vf {
// What a player is wearing and holding. The armour half deliberately reuses
// Damage.h's Armor and ArmorSet instead of inventing a second description of a
// breastplate: the whole point is that EquipmentStats hands the combat code
// exactly the values it already knows how to consume. The hand half is made of
// MetaStacks, the same stacks a ContainerMenu moves around.
//
// ItemId has no wearable entries at all, which is why armour cannot be a
// MetaStack here. equip() therefore refuses every stack aimed at an armour
// slot, and equipArmor() is the way in.
enum class EquipSlot : uint8_t { Head, Chest, Legs, Feet, MainHand, OffHand, Count };
const char* equipSlotName(EquipSlot slot);
bool isArmorSlot(EquipSlot slot);
bool isHandSlot(EquipSlot slot);
Armor::Slot armorSlotFor(EquipSlot slot);
EquipSlot equipSlotFor(Armor::Slot slot);
// One line of the "hover the item and read what it does" tooltip, and the unit
// the stats sum works in.
struct AttributeModifier {
enum class Attribute : uint8_t {
AttackDamage,
AttackSpeed,
AttackReach,
MiningSpeed,
MovementSpeed,
ArmorPoints,
ArmorToughness,
Count
};
enum class Operation : uint8_t { Add, MultiplyTotal };
Attribute attribute = Attribute::AttackDamage;
Operation operation = Operation::Add;
float amount = 0.0f;
EquipSlot slot = EquipSlot::MainHand;
};
const char* attributeName(AttributeModifier::Attribute attribute);
// Base values a bare player has before anything is worn or held. Named for the
// unarmed case rather than "base", because CombatStats already has fields with
// those names and the two must stay visibly distinct.
constexpr float unarmedAttackDamage = 1.0f;
constexpr float unarmedAttackSpeed = 4.0f;
constexpr float unarmedAttackReach = 3.0f;
// The damage and swing speed a tool grants when held, keyed off the item
// tables so a new tool tier is picked up without touching this module.
float weaponAttackDamage(ItemId item);
float weaponAttackSpeed(ItemId item);
// Reach and armour weight are this engine's own rules, not ported constants: a
// long hafted weapon reaches further, and every armour point costs a sliver of
// movement speed. They are called out here so nobody mistakes them for vanilla.
float weaponAttackReach(ItemId item);
float armorWeightPenalty(int armorPoints);
struct EquipmentStats {
// Ready to be assigned onto CombatStats::armor.
ArmorSet armor;
int armorPoints = 0;
float armorToughness = 0.0f;
float attackDamage = unarmedAttackDamage;
float attackSpeed = unarmedAttackSpeed;
float attackReach = unarmedAttackReach;
// Tool tier times Efficiency: the one attribute that draws on both the item
// and an enchantment without colliding with Damage.h's combat maths.
float miningSpeed = 1.0f;
float movementSpeed = 1.0f;
// The held weapon's enchantments, handed to Damage.h's meleeAttackDamage
// untouched. Sharpness and the Protection family are deliberately absent
// from attackDamage and from armorPoints: Damage.h already owns those
// formulas, and summing them here as well would count them twice.
EnchantSet weaponEnchants;
std::vector<AttributeModifier> modifiers;
// Writes the derived values onto an existing CombatStats without touching
// health, absorption or the invulnerability window.
void applyTo(CombatStats& stats) const;
};
class Equipment {
public:
Equipment() = default;
// Hand slots only. Returns false for an armour slot or an empty stack,
// which is what "only a matching item goes on" means with this item table.
bool equip(EquipSlot slot, const MetaStack& stack);
bool equipArmor(const Armor& piece);
// Places a piece in the slot its own Armor::Slot names, so a helmet cannot
// be forced onto the legs.
bool equipArmorAt(EquipSlot slot, const Armor& piece);
MetaStack unequip(EquipSlot slot);
Armor unequipArmor(EquipSlot slot);
bool accepts(EquipSlot slot, const MetaStack& stack) const;
// Reading by slot is safe for every EquipSlot: an armour slot answers with
// an empty stack and a hand slot with an empty piece, so callers iterating
// the whole enum need no special cases. Writing goes through the named
// accessors below, where the slot is fixed at compile time.
const MetaStack& hand(EquipSlot slot) const;
const Armor& armorPiece(EquipSlot slot) const;
const MetaStack& mainHand() const { return hands[0]; }
const MetaStack& offHand() const { return hands[1]; }
MetaStack& mainHandStack() { return hands[0]; }
MetaStack& offHandStack() { return hands[1]; }
const ArmorSet& armor() const { return worn; }
ArmorSet& armor() { return worn; }
EquipmentStats stats() const;
// Wears the armour by one point per four damage, honouring Unbreaking and
// dropping a piece that hits zero. Delegates to Damage.h's wearArmor so the
// wear curve stays in one place. Returns the durability spent.
int wearFromHit(float incomingDamage, uint32_t& rng);
// Wears the held weapon or tool. Returns what happened to it, including the
// break that empties the slot.
WearResult useHeld(EquipSlot slot, int amount, uint32_t& rng);
// Experience picked up while wearing Mending gear repairs it, gear first
// and the held tool after. Returns the durability restored.
int absorbExperience(int experience);
int wornPieces() const;
bool empty() const;
private:
std::array<MetaStack, 2> hands;
ArmorSet worn;
};
// Reads the player's hands out of an open menu: the selected hotbar slot is the
// main hand, the menu's off-hand slot is the off hand. This is the seam between
// the click layer and the combat layer, and it copies stacks rather than
// aliasing them so a half-finished drag cannot arm the player.
void loadHandsFromMenu(Equipment& gear, const ContainerMenu& menu, int selectedHotbarSlot);
void storeHandsIntoMenu(Equipment& gear, ContainerMenu& menu, int selectedHotbarSlot);
// Full melee damage against a target, combining the equipment's own attack
// value with the enchantment maths Damage.h already owns.
float equippedMeleeDamage(const Equipment& gear, const CombatStats& attacker,
const EffectSet& attackerEffects, bool undeadTarget,
bool arthropodTarget, bool critical);
} // namespace vf