feat(vf): add AttributeModifier and EquipmentStats
This commit is contained in:
1 parent
32b7d0075f
commit
24a8171dab
1 file changed
+167
@@ -0,0 +1,167 @@
|
||||
#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
|
||||
Reference in new issue
Block a user