26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
250 lines
8.1 KiB
Go
250 lines
8.1 KiB
Go
package inventory
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/df-mc/dragonfly/server/item"
|
|
"github.com/df-mc/dragonfly/server/item/enchantment"
|
|
"github.com/df-mc/dragonfly/server/world"
|
|
"math"
|
|
"math/rand/v2"
|
|
)
|
|
|
|
// Armour represents an inventory for armour. It has 4 slots, one for a helmet, chestplate, leggings and
|
|
// boots respectively. NewArmour() must be used to create a valid armour inventory.
|
|
// Armour inventories, like normal Inventories, are safe for concurrent usage.
|
|
type Armour struct {
|
|
inv *Inventory
|
|
}
|
|
|
|
// NewArmour returns an armour inventory that is ready to be used. The zero value of an inventory.Armour is
|
|
// not valid for usage.
|
|
// The function passed is called when a slot is changed. It may be nil to not call anything.
|
|
func NewArmour(f func(slot int, before, after item.Stack)) *Armour {
|
|
inv := New(4, f)
|
|
inv.validator = canAddArmour
|
|
return &Armour{inv: inv}
|
|
}
|
|
|
|
// canAddArmour checks if the item passed can be worn as armour in the slot passed.
|
|
func canAddArmour(s item.Stack, slot int) bool {
|
|
if s.Empty() {
|
|
return true
|
|
}
|
|
switch slot {
|
|
case 0:
|
|
if h, ok := s.Item().(item.HelmetType); ok {
|
|
return h.Helmet()
|
|
}
|
|
case 1:
|
|
if c, ok := s.Item().(item.ChestplateType); ok {
|
|
return c.Chestplate()
|
|
}
|
|
case 2:
|
|
if l, ok := s.Item().(item.LeggingsType); ok {
|
|
return l.Leggings()
|
|
}
|
|
case 3:
|
|
if b, ok := s.Item().(item.BootsType); ok {
|
|
return b.Boots()
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Set sets all individual pieces of armour in one go. It is equivalent to calling SetHelmet, SetChestplate, SetLeggings
|
|
// and SetBoots sequentially.
|
|
func (a *Armour) Set(helmet, chestplate, leggings, boots item.Stack) {
|
|
a.SetHelmet(helmet)
|
|
a.SetChestplate(chestplate)
|
|
a.SetLeggings(leggings)
|
|
a.SetBoots(boots)
|
|
}
|
|
|
|
// SetHelmet sets the item stack passed as the helmet in the inventory.
|
|
func (a *Armour) SetHelmet(helmet item.Stack) {
|
|
_ = a.inv.SetItem(0, helmet)
|
|
}
|
|
|
|
// Helmet returns the item stack set as helmet in the inventory.
|
|
func (a *Armour) Helmet() item.Stack {
|
|
i, _ := a.inv.Item(0)
|
|
return i
|
|
}
|
|
|
|
// SetChestplate sets the item stack passed as the chestplate in the inventory.
|
|
func (a *Armour) SetChestplate(chestplate item.Stack) {
|
|
_ = a.inv.SetItem(1, chestplate)
|
|
}
|
|
|
|
// Chestplate returns the item stack set as chestplate in the inventory.
|
|
func (a *Armour) Chestplate() item.Stack {
|
|
i, _ := a.inv.Item(1)
|
|
return i
|
|
}
|
|
|
|
// SetLeggings sets the item stack passed as the leggings in the inventory.
|
|
func (a *Armour) SetLeggings(leggings item.Stack) {
|
|
_ = a.inv.SetItem(2, leggings)
|
|
}
|
|
|
|
// Leggings returns the item stack set as leggings in the inventory.
|
|
func (a *Armour) Leggings() item.Stack {
|
|
i, _ := a.inv.Item(2)
|
|
return i
|
|
}
|
|
|
|
// SetBoots sets the item stack passed as the boots in the inventory.
|
|
func (a *Armour) SetBoots(boots item.Stack) {
|
|
_ = a.inv.SetItem(3, boots)
|
|
}
|
|
|
|
// Boots returns the item stack set as boots in the inventory.
|
|
func (a *Armour) Boots() item.Stack {
|
|
i, _ := a.inv.Item(3)
|
|
return i
|
|
}
|
|
|
|
// DamageReduction returns the amount of damage that is reduced by the Armour for
|
|
// an amount of damage and damage source. The value returned takes into account
|
|
// the armour itself and its enchantments.
|
|
func (a *Armour) DamageReduction(dmg float64, src world.DamageSource) float64 {
|
|
var (
|
|
original = dmg
|
|
defencePoints, toughness float64
|
|
enchantments []item.Enchantment
|
|
)
|
|
|
|
for _, it := range a.Items() {
|
|
enchantments = append(enchantments, it.Enchantments()...)
|
|
if armour, ok := it.Item().(item.Armour); ok {
|
|
defencePoints += armour.DefencePoints()
|
|
toughness += armour.Toughness()
|
|
}
|
|
}
|
|
|
|
dmg -= dmg * enchantment.ProtectionFactor(src, enchantments)
|
|
if src.ReducedByArmour() {
|
|
// Armour in Bedrock edition reduces the damage taken by 4% for each effective armour point. Effective
|
|
// armour point decreases as damage increases, with 1 point lost for every 2 HP of damage. The defence
|
|
// reduction is decreased by the toughness armour value. Effective armour points will at minimum be 20% of
|
|
// armour points.
|
|
dmg -= dmg * 0.04 * math.Max(defencePoints*0.2, defencePoints-dmg/(2+toughness/4))
|
|
}
|
|
return original - dmg
|
|
}
|
|
|
|
// HighestEnchantmentLevel looks up the highest level of an item.EnchantmentType
|
|
// that any of the Armour items have and returns it, or 0 if none of the items
|
|
// have the enchantment.
|
|
func (a *Armour) HighestEnchantmentLevel(t item.EnchantmentType) int {
|
|
lvl := 0
|
|
for _, it := range a.Items() {
|
|
if e, ok := it.Enchantment(t); ok && e.Level() > lvl {
|
|
lvl = e.Level()
|
|
}
|
|
}
|
|
return lvl
|
|
}
|
|
|
|
// DamageFunc is a function that deals d damage points to an item stack s. The
|
|
// resulting item.Stack is returned. Depending on the game mode of a player,
|
|
// damage may not be dealt at all.
|
|
type DamageFunc func(s item.Stack, d int) item.Stack
|
|
|
|
// Damage deals damage (hearts) to Armour. The resulting item damage depends on the
|
|
// dmg passed and the DamageFunc used.
|
|
func (a *Armour) Damage(dmg float64, f DamageFunc) {
|
|
armourDamage := int(math.Max(math.Floor(dmg/4), 1))
|
|
for slot, it := range a.Slots() {
|
|
_ = a.inv.SetItem(slot, f(it, armourDamage))
|
|
}
|
|
}
|
|
|
|
// ThornsDamage checks if any of the Armour items are enchanted with thorns. If
|
|
// this is the case and the thorns enchantment activates (15% chance per level),
|
|
// a random Armour piece is damaged. The damage to be dealt to the attacker is
|
|
// returned.
|
|
func (a *Armour) ThornsDamage(f DamageFunc) float64 {
|
|
slots := a.Slots()
|
|
dmg := 0.0
|
|
|
|
for _, i := range slots {
|
|
thorns, _ := i.Enchantment(enchantment.Thorns)
|
|
if level := float64(thorns.Level()); rand.Float64() < level*0.15 {
|
|
// 15%/level chance of thorns activation per item. Total damage from
|
|
// normal thorns armour (max thorns III) should never exceed 4.0 in
|
|
// total.
|
|
dmg = math.Min(dmg+float64(1+rand.IntN(4)), 4.0)
|
|
}
|
|
}
|
|
if highest := a.HighestEnchantmentLevel(enchantment.Thorns); highest > 10 {
|
|
// When we find an armour piece with thorns XI or above, the logic
|
|
// changes: We have to find the armour piece with the highest level
|
|
// of thorns and subtract 10 from its level to calculate the final
|
|
// damage.
|
|
dmg = float64(highest - 10)
|
|
}
|
|
if dmg > 0 {
|
|
// Deal 2 damage to one random thorns item. Bedrock Edition and Java Edition
|
|
// both have different behaviour here and neither seem to match the expected
|
|
// behaviour. Java Edition deals 2 damage to a random thorns item for every
|
|
// thorns armour item worn, while Bedrock Edition deals 1 additional damage
|
|
// for every thorns item and another 2 for every thorns item when it
|
|
// activates.
|
|
slot := rand.IntN(len(slots))
|
|
_ = a.Inventory().SetItem(slot, f(slots[slot], 2))
|
|
}
|
|
return dmg
|
|
}
|
|
|
|
// KnockBackResistance returns the combined knock back resistance of all Armour
|
|
// items. A value of 0 means normal knock back force, while a value of 1 means
|
|
// all knock back is ignored.
|
|
func (a *Armour) KnockBackResistance() float64 {
|
|
resistance := 0.0
|
|
for _, i := range a.Items() {
|
|
if a, ok := i.Item().(item.Armour); ok {
|
|
resistance += a.KnockBackResistance()
|
|
}
|
|
}
|
|
return resistance
|
|
}
|
|
|
|
// Slots returns all items (including) air of the armour inventory in the order of helmet, chestplate, leggings,
|
|
// boots.
|
|
func (a *Armour) Slots() []item.Stack {
|
|
return a.inv.Slots()
|
|
}
|
|
|
|
// Items returns a slice of all non-empty armour items equipped.
|
|
func (a *Armour) Items() []item.Stack {
|
|
return a.inv.Items()
|
|
}
|
|
|
|
// Clear clears the armour inventory, removing all items currently present.
|
|
func (a *Armour) Clear() []item.Stack {
|
|
return a.inv.Clear()
|
|
}
|
|
|
|
// String converts the armour to a readable string representation.
|
|
func (a *Armour) String() string {
|
|
return fmt.Sprintf("(helmet: %v, chestplate: %v, leggings: %v, boots: %v)", a.Helmet(), a.Chestplate(), a.Leggings(), a.Boots())
|
|
}
|
|
|
|
// Inventory returns the underlying Inventory instance.
|
|
func (a *Armour) Inventory() *Inventory {
|
|
return a.inv
|
|
}
|
|
|
|
// Handle assigns a Handler to an Armour inventory so that its methods are called for the respective events. Nil may be
|
|
// passed to set the default NopHandler.
|
|
// Handle is the equivalent of calling (*Armour).Inventory().H.
|
|
func (a *Armour) Handle(h Handler) {
|
|
a.inv.Handle(h)
|
|
}
|
|
|
|
// Close closes the armour inventory, removing the slot change function.
|
|
func (a *Armour) Close() error {
|
|
return a.inv.Close()
|
|
}
|