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,270 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/sound"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
// anvilInputSlot is the slot index of the input item in the anvil.
|
||||
anvilInputSlot = 0x1
|
||||
// anvilMaterialSlot is the slot index of the material in the anvil.
|
||||
anvilMaterialSlot = 0x2
|
||||
)
|
||||
|
||||
// handleCraftRecipeOptional handles the CraftRecipeOptional request action, sent when taking a result from an anvil
|
||||
// menu. It also contains information such as the new name of the item and the multi-recipe network ID.
|
||||
func (h *ItemStackRequestHandler) handleCraftRecipeOptional(a *protocol.CraftRecipeOptionalStackRequestAction, s *Session, filterStrings []string, co Controllable, tx *world.Tx) (err error) {
|
||||
// First check if there actually is an anvil opened.
|
||||
if !s.containerOpened.Load() {
|
||||
return fmt.Errorf("no anvil container opened")
|
||||
}
|
||||
|
||||
pos := *s.openedPos.Load()
|
||||
anvil, ok := tx.Block(pos).(block.Anvil)
|
||||
if !ok {
|
||||
return fmt.Errorf("no anvil container opened")
|
||||
}
|
||||
if index := int(a.FilterStringIndex); len(filterStrings) > 0 && (index < 0 || index >= len(filterStrings)) {
|
||||
return fmt.Errorf("filter string index %v is out of bounds", a.FilterStringIndex)
|
||||
}
|
||||
|
||||
input, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
|
||||
Container: protocol.FullContainerName{ContainerID: protocol.ContainerAnvilInput},
|
||||
Slot: anvilInputSlot,
|
||||
}, s, tx)
|
||||
if input.Empty() {
|
||||
return fmt.Errorf("no item in input input slot")
|
||||
}
|
||||
material, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
|
||||
Container: protocol.FullContainerName{ContainerID: protocol.ContainerAnvilMaterial},
|
||||
Slot: anvilMaterialSlot,
|
||||
}, s, tx)
|
||||
result := input
|
||||
|
||||
// The sum of the input's anvil cost as well as the material's anvil cost.
|
||||
anvilCost := input.AnvilCost()
|
||||
if !material.Empty() {
|
||||
anvilCost += material.AnvilCost()
|
||||
}
|
||||
|
||||
// The material input may be empty (if the player is only renaming, for example).
|
||||
var actionCost, renameCost, repairCount int
|
||||
if !material.Empty() {
|
||||
// First check if we are trying to repair the item with a material.
|
||||
if repairable, ok := input.Item().(item.Repairable); ok && repairable.RepairableBy(material) {
|
||||
result, actionCost, repairCount, err = repairItemWithMaterial(input, material, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, book := material.Item().(item.EnchantedBook)
|
||||
_, durable := input.Item().(item.Durable)
|
||||
|
||||
// Ensure that the input item is repairable, or the material item is an enchanted book. If not, this is an
|
||||
// invalid scenario, and we should return an error.
|
||||
enchantedBook := book && len(material.Enchantments()) > 0
|
||||
if !enchantedBook && (input.Item() != material.Item() || !durable) {
|
||||
return fmt.Errorf("input item is not repairable/same type or material item is not an enchanted book")
|
||||
}
|
||||
|
||||
// If the material is another durable item, we just need to increase the durability of the result by the
|
||||
// material's durability at 12%.
|
||||
if durable && !enchantedBook {
|
||||
result, actionCost = repairItemWithDurable(input, material, result)
|
||||
}
|
||||
|
||||
// Merge enchantments on the material item onto the result item.
|
||||
var hasCompatible, hasIncompatible bool
|
||||
result, hasCompatible, hasIncompatible, actionCost = mergeEnchantments(input, material, result, actionCost, enchantedBook)
|
||||
|
||||
// If we don't have any compatible enchantments and the input item isn't durable, then this is an invalid
|
||||
// scenario, and we should return an error.
|
||||
if !durable && hasIncompatible && !hasCompatible {
|
||||
return fmt.Errorf("no compatible enchantments but have incompatible ones")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a filter string, then the client is intending to rename the item.
|
||||
if len(filterStrings) > 0 {
|
||||
renameCost = 1
|
||||
actionCost += renameCost
|
||||
result = result.WithCustomName(filterStrings[int(a.FilterStringIndex)])
|
||||
}
|
||||
|
||||
// Calculate the total cost. (action cost + anvil cost)
|
||||
cost := actionCost + anvilCost
|
||||
if cost <= 0 {
|
||||
return fmt.Errorf("no action was taken")
|
||||
}
|
||||
|
||||
// If our only action was renaming, the cost should never exceed 40.
|
||||
if renameCost == actionCost && renameCost > 0 && cost >= 40 {
|
||||
cost = 39
|
||||
}
|
||||
|
||||
// We can bypass the "impossible cost" limit if we're in creative mode.
|
||||
c := co.GameMode().CreativeInventory()
|
||||
if cost >= 40 && !c {
|
||||
return fmt.Errorf("impossible cost")
|
||||
}
|
||||
|
||||
// Ensure we have enough levels (or if we're in creative mode, ignore the cost) to perform the action.
|
||||
level := co.ExperienceLevel()
|
||||
if level < cost && !c {
|
||||
return fmt.Errorf("not enough experience")
|
||||
} else if !c {
|
||||
co.SetExperienceLevel(level - cost)
|
||||
}
|
||||
|
||||
// If we had a result item, we need to calculate the new anvil cost and update it on the item.
|
||||
if !result.Empty() {
|
||||
updatedAnvilCost := result.AnvilCost()
|
||||
if !material.Empty() && updatedAnvilCost < material.AnvilCost() {
|
||||
updatedAnvilCost = material.AnvilCost()
|
||||
}
|
||||
if renameCost != actionCost || renameCost == 0 {
|
||||
updatedAnvilCost = updatedAnvilCost*2 + 1
|
||||
}
|
||||
result = result.WithAnvilCost(updatedAnvilCost)
|
||||
}
|
||||
|
||||
// If we're not in creative mode, we have a 12% chance of the anvil degrading down one state. If that is the case, we
|
||||
// need to play the related sound and update the block state. Otherwise, we play a regular anvil use sound.
|
||||
if !c && rand.Float64() < 0.12 {
|
||||
damaged := anvil.Break()
|
||||
if _, ok := damaged.(block.Air); ok {
|
||||
tx.PlaySound(pos.Vec3Centre(), sound.AnvilBreak{})
|
||||
} else {
|
||||
tx.PlaySound(pos.Vec3Centre(), sound.AnvilUse{})
|
||||
}
|
||||
defer tx.SetBlock(pos, damaged, nil)
|
||||
} else {
|
||||
tx.PlaySound(pos.Vec3Centre(), sound.AnvilUse{})
|
||||
}
|
||||
|
||||
h.setItemInSlot(protocol.StackRequestSlotInfo{
|
||||
Container: protocol.FullContainerName{ContainerID: protocol.ContainerAnvilInput},
|
||||
Slot: anvilInputSlot,
|
||||
}, item.Stack{}, s, tx)
|
||||
if repairCount > 0 {
|
||||
h.setItemInSlot(protocol.StackRequestSlotInfo{
|
||||
Container: protocol.FullContainerName{ContainerID: protocol.ContainerAnvilMaterial},
|
||||
Slot: anvilMaterialSlot,
|
||||
}, material.Grow(-repairCount), s, tx)
|
||||
} else {
|
||||
h.setItemInSlot(protocol.StackRequestSlotInfo{
|
||||
Container: protocol.FullContainerName{ContainerID: protocol.ContainerAnvilMaterial},
|
||||
Slot: anvilMaterialSlot,
|
||||
}, item.Stack{}, s, tx)
|
||||
}
|
||||
return h.createResults(s, tx, result)
|
||||
}
|
||||
|
||||
// repairItemWithMaterial is a helper function that repairs an item stack with a given material stack. It returns the new item
|
||||
// stack, the cost, and the repaired items count.
|
||||
func repairItemWithMaterial(input item.Stack, material item.Stack, result item.Stack) (item.Stack, int, int, error) {
|
||||
// Calculate the durability delta using the maximum durability and the current durability.
|
||||
delta := min(input.MaxDurability()-input.Durability(), input.MaxDurability()/4)
|
||||
if delta <= 0 {
|
||||
return item.Stack{}, 0, 0, fmt.Errorf("input item is already fully repaired")
|
||||
}
|
||||
|
||||
// While the durability delta is more than zero and the repaired count is under the material count, increase
|
||||
// the durability of the result by the durability delta.
|
||||
var cost, count int
|
||||
for ; delta > 0 && count < material.Count(); count, delta = count+1, min(result.MaxDurability()-result.Durability(), result.MaxDurability()/4) {
|
||||
result = result.WithDurability(result.Durability() + delta)
|
||||
cost++
|
||||
}
|
||||
return result, cost, count, nil
|
||||
}
|
||||
|
||||
// repairItemWithDurable is a helper function that repairs an item with another durable item stack.
|
||||
func repairItemWithDurable(input item.Stack, durable item.Stack, result item.Stack) (item.Stack, int) {
|
||||
durability := input.Durability() + durable.Durability() + input.MaxDurability()*12/100
|
||||
if durability > input.MaxDurability() {
|
||||
durability = input.MaxDurability()
|
||||
}
|
||||
|
||||
// Ensure the durability is higher than the input's current durability.
|
||||
var cost int
|
||||
if durability > input.Durability() {
|
||||
result = result.WithDurability(durability)
|
||||
cost += 2
|
||||
}
|
||||
return result, cost
|
||||
}
|
||||
|
||||
// mergeEnchantments merges the enchantments of the material item stack onto the result item stack and returns the result
|
||||
// item stack, booleans indicating whether the enchantments had any compatible or incompatible enchantments, and the cost.
|
||||
func mergeEnchantments(input item.Stack, material item.Stack, result item.Stack, cost int, enchantedBook bool) (item.Stack, bool, bool, int) {
|
||||
var hasCompatible, hasIncompatible bool
|
||||
for _, enchant := range material.Enchantments() {
|
||||
// First ensure that the enchantment type is compatible with the input item.
|
||||
enchantType := enchant.Type()
|
||||
compatible := enchantType.CompatibleWithItem(input.Item())
|
||||
if _, ok := input.Item().(item.EnchantedBook); ok {
|
||||
compatible = true
|
||||
}
|
||||
|
||||
// Then ensure that each input enchantment is compatible with this material enchantment. If one is not compatible,
|
||||
// increase the cost by one.
|
||||
for _, otherEnchant := range input.Enchantments() {
|
||||
if otherType := otherEnchant.Type(); enchantType != otherType && !enchantType.CompatibleWithEnchantment(otherType) {
|
||||
compatible = false
|
||||
cost++
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the enchantment if it isn't compatible with enchantments on the input item.
|
||||
if !compatible {
|
||||
hasIncompatible = true
|
||||
continue
|
||||
}
|
||||
hasCompatible = true
|
||||
|
||||
resultLevel := enchant.Level()
|
||||
levelCost := resultLevel
|
||||
|
||||
// Check if we have an enchantment of the same type on the input item.
|
||||
if existingEnchant, ok := input.Enchantment(enchantType); ok {
|
||||
if existingEnchant.Level() > resultLevel || (existingEnchant.Level() == resultLevel && resultLevel == enchantType.MaxLevel()) {
|
||||
// The result level is either lower than the existing enchantment's level or is higher than the maximum
|
||||
// level, so skip this enchantment.
|
||||
hasIncompatible = true
|
||||
continue
|
||||
} else if existingEnchant.Level() == resultLevel {
|
||||
// If the input level is equal to the material level, increase the result level by one.
|
||||
resultLevel++
|
||||
}
|
||||
// Update the level cost. (result level - existing level)
|
||||
levelCost = resultLevel - existingEnchant.Level()
|
||||
}
|
||||
|
||||
// Now calculate the rarity cost. This is just the application cost of the rarity, however if the
|
||||
// material is an enchanted book, then the rarity cost gets halved. If the new rarity cost is under one,
|
||||
// it is set to one.
|
||||
rarityCost := enchantType.Rarity().Cost()
|
||||
if enchantedBook {
|
||||
rarityCost = max(1, rarityCost/2)
|
||||
}
|
||||
|
||||
// Update the result item with the new enchantment.
|
||||
result = result.WithEnchantments(item.NewEnchantment(enchantType, resultLevel))
|
||||
|
||||
// Update the cost appropriately.
|
||||
cost += rarityCost * levelCost
|
||||
if input.Count() > 1 {
|
||||
cost = 40
|
||||
}
|
||||
}
|
||||
return result, hasCompatible, hasIncompatible, cost
|
||||
}
|
||||
Reference in New Issue
Block a user