-
Notifications
You must be signed in to change notification settings - Fork 141
/
armour.go
249 lines (220 loc) · 8.06 KB
/
armour.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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"
)
// 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.canAdd = 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 defense
// reduction is decreased by the toughness armor 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().Handle.
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()
}