github.com/df-mc/dragonfly@v0.9.13/server/item/inventory/armour.go (about)

     1  package inventory
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/item"
     6  	"github.com/df-mc/dragonfly/server/item/enchantment"
     7  	"github.com/df-mc/dragonfly/server/world"
     8  	"math"
     9  	"math/rand"
    10  )
    11  
    12  // Armour represents an inventory for armour. It has 4 slots, one for a helmet, chestplate, leggings and
    13  // boots respectively. NewArmour() must be used to create a valid armour inventory.
    14  // Armour inventories, like normal Inventories, are safe for concurrent usage.
    15  type Armour struct {
    16  	inv *Inventory
    17  }
    18  
    19  // NewArmour returns an armour inventory that is ready to be used. The zero value of an inventory.Armour is
    20  // not valid for usage.
    21  // The function passed is called when a slot is changed. It may be nil to not call anything.
    22  func NewArmour(f func(slot int, before, after item.Stack)) *Armour {
    23  	inv := New(4, f)
    24  	inv.canAdd = canAddArmour
    25  	return &Armour{inv: inv}
    26  }
    27  
    28  // canAddArmour checks if the item passed can be worn as armour in the slot passed.
    29  func canAddArmour(s item.Stack, slot int) bool {
    30  	if s.Empty() {
    31  		return true
    32  	}
    33  	switch slot {
    34  	case 0:
    35  		if h, ok := s.Item().(item.HelmetType); ok {
    36  			return h.Helmet()
    37  		}
    38  	case 1:
    39  		if c, ok := s.Item().(item.ChestplateType); ok {
    40  			return c.Chestplate()
    41  		}
    42  	case 2:
    43  		if l, ok := s.Item().(item.LeggingsType); ok {
    44  			return l.Leggings()
    45  		}
    46  	case 3:
    47  		if b, ok := s.Item().(item.BootsType); ok {
    48  			return b.Boots()
    49  		}
    50  	}
    51  	return false
    52  }
    53  
    54  // Set sets all individual pieces of armour in one go. It is equivalent to calling SetHelmet, SetChestplate, SetLeggings
    55  // and SetBoots sequentially.
    56  func (a *Armour) Set(helmet, chestplate, leggings, boots item.Stack) {
    57  	a.SetHelmet(helmet)
    58  	a.SetChestplate(chestplate)
    59  	a.SetLeggings(leggings)
    60  	a.SetBoots(boots)
    61  }
    62  
    63  // SetHelmet sets the item stack passed as the helmet in the inventory.
    64  func (a *Armour) SetHelmet(helmet item.Stack) {
    65  	_ = a.inv.SetItem(0, helmet)
    66  }
    67  
    68  // Helmet returns the item stack set as helmet in the inventory.
    69  func (a *Armour) Helmet() item.Stack {
    70  	i, _ := a.inv.Item(0)
    71  	return i
    72  }
    73  
    74  // SetChestplate sets the item stack passed as the chestplate in the inventory.
    75  func (a *Armour) SetChestplate(chestplate item.Stack) {
    76  	_ = a.inv.SetItem(1, chestplate)
    77  }
    78  
    79  // Chestplate returns the item stack set as chestplate in the inventory.
    80  func (a *Armour) Chestplate() item.Stack {
    81  	i, _ := a.inv.Item(1)
    82  	return i
    83  }
    84  
    85  // SetLeggings sets the item stack passed as the leggings in the inventory.
    86  func (a *Armour) SetLeggings(leggings item.Stack) {
    87  	_ = a.inv.SetItem(2, leggings)
    88  }
    89  
    90  // Leggings returns the item stack set as leggings in the inventory.
    91  func (a *Armour) Leggings() item.Stack {
    92  	i, _ := a.inv.Item(2)
    93  	return i
    94  }
    95  
    96  // SetBoots sets the item stack passed as the boots in the inventory.
    97  func (a *Armour) SetBoots(boots item.Stack) {
    98  	_ = a.inv.SetItem(3, boots)
    99  }
   100  
   101  // Boots returns the item stack set as boots in the inventory.
   102  func (a *Armour) Boots() item.Stack {
   103  	i, _ := a.inv.Item(3)
   104  	return i
   105  }
   106  
   107  // DamageReduction returns the amount of damage that is reduced by the Armour for
   108  // an amount of damage and damage source. The value returned takes into account
   109  // the armour itself and its enchantments.
   110  func (a *Armour) DamageReduction(dmg float64, src world.DamageSource) float64 {
   111  	var (
   112  		original                 = dmg
   113  		defencePoints, toughness float64
   114  		enchantments             []item.Enchantment
   115  	)
   116  
   117  	for _, it := range a.Items() {
   118  		enchantments = append(enchantments, it.Enchantments()...)
   119  		if armour, ok := it.Item().(item.Armour); ok {
   120  			defencePoints += armour.DefencePoints()
   121  			toughness += armour.Toughness()
   122  		}
   123  	}
   124  
   125  	dmg -= dmg * enchantment.ProtectionFactor(src, enchantments)
   126  	if src.ReducedByArmour() {
   127  		// Armour in Bedrock edition reduces the damage taken by 4% for each effective armour point. Effective
   128  		// armour point decreases as damage increases, with 1 point lost for every 2 HP of damage. The defense
   129  		// reduction is decreased by the toughness armor value. Effective armour points will at minimum be 20% of
   130  		// armour points.
   131  		dmg -= dmg * 0.04 * math.Max(defencePoints*0.2, defencePoints-dmg/(2+toughness/4))
   132  	}
   133  	return original - dmg
   134  }
   135  
   136  // HighestEnchantmentLevel looks up the highest level of an item.EnchantmentType
   137  // that any of the Armour items have and returns it, or 0 if none of the items
   138  // have the enchantment.
   139  func (a *Armour) HighestEnchantmentLevel(t item.EnchantmentType) int {
   140  	lvl := 0
   141  	for _, it := range a.Items() {
   142  		if e, ok := it.Enchantment(t); ok && e.Level() > lvl {
   143  			lvl = e.Level()
   144  		}
   145  	}
   146  	return lvl
   147  }
   148  
   149  // DamageFunc is a function that deals d damage points to an item stack s. The
   150  // resulting item.Stack is returned. Depending on the game mode of a player,
   151  // damage may not be dealt at all.
   152  type DamageFunc func(s item.Stack, d int) item.Stack
   153  
   154  // Damage deals damage (hearts) to Armour. The resulting item damage depends on the
   155  // dmg passed and the DamageFunc used.
   156  func (a *Armour) Damage(dmg float64, f DamageFunc) {
   157  	armourDamage := int(math.Max(math.Floor(dmg/4), 1))
   158  	for slot, it := range a.Slots() {
   159  		_ = a.inv.SetItem(slot, f(it, armourDamage))
   160  	}
   161  }
   162  
   163  // ThornsDamage checks if any of the Armour items are enchanted with Thorns. If
   164  // this is the case and the Thorns enchantment activates (15% chance per level),
   165  // a random Armour piece is damaged. The damage to be dealt to the attacker is
   166  // returned.
   167  func (a *Armour) ThornsDamage(f DamageFunc) float64 {
   168  	slots := a.Slots()
   169  	dmg := 0.0
   170  
   171  	for _, i := range slots {
   172  		thorns, _ := i.Enchantment(enchantment.Thorns{})
   173  		if level := float64(thorns.Level()); rand.Float64() < level*0.15 {
   174  			// 15%/level chance of Thorns activation per item. Total damage from
   175  			// normal thorns armour (max Thorns III) should never exceed 4.0 in
   176  			// total.
   177  			dmg = math.Min(dmg+float64(1+rand.Intn(4)), 4.0)
   178  		}
   179  	}
   180  	if highest := a.HighestEnchantmentLevel(enchantment.Thorns{}); highest > 10 {
   181  		// When we find an armour piece with thorns XI or above, the logic
   182  		// changes: We have to find the armour piece with the highest level
   183  		// of thorns and subtract 10 from its level to calculate the final
   184  		// damage.
   185  		dmg = float64(highest - 10)
   186  	}
   187  	if dmg > 0 {
   188  		// Deal 2 damage to one random thorns item. Bedrock Edition and Java Edition
   189  		// both have different behaviour here and neither seem to match the expected
   190  		// behaviour. Java Edition deals 2 damage to a random thorns item for every
   191  		// Thorns armour item worn, while Bedrock Edition deals 1 additional damage
   192  		// for every Thorns item and another 2 for every Thorns item when it
   193  		// activates.
   194  		slot := rand.Intn(len(slots))
   195  		_ = a.Inventory().SetItem(slot, f(slots[slot], 2))
   196  	}
   197  	return dmg
   198  }
   199  
   200  // KnockBackResistance returns the combined knock back resistance of all Armour
   201  // items. A value of 0 means normal knock back force, while a value of 1 means
   202  // all knock back is ignored.
   203  func (a *Armour) KnockBackResistance() float64 {
   204  	resistance := 0.0
   205  	for _, i := range a.Items() {
   206  		if a, ok := i.Item().(item.Armour); ok {
   207  			resistance += a.KnockBackResistance()
   208  		}
   209  	}
   210  	return resistance
   211  }
   212  
   213  // Slots returns all items (including) air of the armour inventory in the order of helmet, chestplate, leggings,
   214  // boots.
   215  func (a *Armour) Slots() []item.Stack {
   216  	return a.inv.Slots()
   217  }
   218  
   219  // Items returns a slice of all non-empty armour items equipped.
   220  func (a *Armour) Items() []item.Stack {
   221  	return a.inv.Items()
   222  }
   223  
   224  // Clear clears the armour inventory, removing all items currently present.
   225  func (a *Armour) Clear() []item.Stack {
   226  	return a.inv.Clear()
   227  }
   228  
   229  // String converts the armour to a readable string representation.
   230  func (a *Armour) String() string {
   231  	return fmt.Sprintf("(helmet: %v, chestplate: %v, leggings: %v, boots: %v)", a.Helmet(), a.Chestplate(), a.Leggings(), a.Boots())
   232  }
   233  
   234  // Inventory returns the underlying Inventory instance.
   235  func (a *Armour) Inventory() *Inventory {
   236  	return a.inv
   237  }
   238  
   239  // Handle assigns a Handler to an Armour inventory so that its methods are called for the respective events. Nil may be
   240  // passed to set the default NopHandler.
   241  // Handle is the equivalent of calling (*Armour).Inventory().Handle.
   242  func (a *Armour) Handle(h Handler) {
   243  	a.inv.Handle(h)
   244  }
   245  
   246  // Close closes the armour inventory, removing the slot change function.
   247  func (a *Armour) Close() error {
   248  	return a.inv.Close()
   249  }