up3
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
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
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user