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 }