Skip to content

Commit

Permalink
feat: rework block breaking system (#84)
Browse files Browse the repository at this point in the history
* feat: use attributes for custom-block-breaking system

* feat: implement BlockyModifiers

* feat: create datapack to disable

* fix: handle break modifier when breaking vanilla noteblock due to datapack

* refactor: code cleanup of old system code

* refactor: more code cleanup

* fix: workflow java version
  • Loading branch information
Boy0000 authored May 17, 2024
1 parent d01156c commit 0fa9749
Show file tree
Hide file tree
Showing 15 changed files with 304 additions and 425 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
java-version: 21
cache: gradle

- name: Build
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mineinabyss.blocky

import com.github.shynixn.mccoroutine.bukkit.launch
import com.mineinabyss.blocky.assets_generation.ResourcepackGeneration
import com.mineinabyss.blocky.components.core.BlockyFurniture
import com.mineinabyss.blocky.components.features.blocks.BlockyDirectional
import com.mineinabyss.blocky.helpers.gearyInventory
Expand All @@ -27,24 +28,20 @@ import org.bukkit.command.TabCompleter
import org.bukkit.entity.Player
import org.bukkit.inventory.EquipmentSlot

@OptIn(UnsafeAccessors::class)
class BlockyCommandExecutor : IdofrontCommandExecutor(), TabCompleter {
override val commands = commands(blocky.plugin) {
("blocky")(desc = "Commands related to Blocky-plugin") {
"reload" {
action {
blocky.plugin.createBlockyContext()
blocky.plugin.runStartupFunctions()
blocky.plugin.launch {
blocky.prefabQuery.entities().forEach { prefabs.loader.reload(it) }
}
blocky.plugin.launch { blocky.prefabQuery.entities().forEach { prefabs.loader.reload(it) } }
ResourcepackGeneration().generateDefaultAssets()
sender.success("Blocky has been reloaded!")

}
}
"give" {
val type by optionArg(options = blockPrefabs
.filter { it.directional?.isParentBlock != false }
.map { it.prefabKey.toString() }) {
parseErrorMessage = { "No such block: $passed" }
}
Expand Down
59 changes: 59 additions & 0 deletions src/main/kotlin/com/mineinabyss/blocky/BlockyDatapacks.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.mineinabyss.blocky

import com.mineinabyss.idofront.messaging.broadcast
import com.mineinabyss.idofront.messaging.broadcastVal
import com.mineinabyss.idofront.messaging.logVal
import io.papermc.paper.datapack.Datapack
import io.papermc.paper.datapack.DatapackManager
import io.papermc.paper.datapack.PaperDatapack
import kotlinx.serialization.json.*
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.tags.BlockTags
import net.minecraft.world.level.block.NoteBlock
import org.bukkit.Bukkit
import org.bukkit.Tag

object BlockyDatapacks {

private val defaultWorld = Bukkit.getWorlds().first()
private val blockyDatapack = defaultWorld.worldFolder.resolve("datapacks/blocky")

fun generateDatapack() {
blockyDatapack.resolve("data").mkdirs()
writeMcMeta()
generateMineableTag()

Bukkit.getDatapackManager().packs.firstOrNull { it.name == "file/blocky" }?.isEnabled = true
}

fun writeMcMeta() {
runCatching {
val packMeta = blockyDatapack.resolve("pack.mcmeta")
packMeta.writeText(buildJsonObject {
putJsonObject("pack") {
put("description", "Datapack for Blocky")
put("pack_format", 26)
}
}.toString())
}.onFailure { it.printStackTrace() }
}

private fun generateMineableTag() {
runCatching {
val tagFile = blockyDatapack.resolve("data/minecraft/tags/blocks/mineable/axe.json")
tagFile.parentFile.mkdirs()
tagFile.createNewFile()

val tagObject = buildJsonObject {
put("replace", true)
putJsonArray("values") {
BuiltInRegistries.BLOCK.tags.toList().find { it.first == BlockTags.MINEABLE_WITH_AXE }?.second?.forEach {
if (it.registeredName != "minecraft:note_block") add(it.registeredName)
}
}
}

tagFile.writeText(tagObject.toString())
}.onFailure { it.printStackTrace() }
}
}
28 changes: 1 addition & 27 deletions src/main/kotlin/com/mineinabyss/blocky/BlockyPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import org.bukkit.block.data.BlockData
import org.bukkit.plugin.java.JavaPlugin

var prefabMap = mapOf<BlockData, PrefabKey>()
var registryTagMap = mapOf<ResourceLocation, IntArrayList>()

class BlockyPlugin : JavaPlugin() {
override fun onLoad() {
Expand All @@ -41,6 +40,7 @@ class BlockyPlugin : JavaPlugin() {

override fun onEnable() {
createBlockyContext()
BlockyDatapacks.generateDatapack()

if (Plugins.isEnabled("WorldEdit")) {
WorldEdit.getInstance().blockFactory.register(WorldEditSupport.BlockyInputParser())
Expand All @@ -64,7 +64,6 @@ class BlockyPlugin : JavaPlugin() {
BlockyGenericListener(),
BlockyFurnitureListener(),
BlockyMiddleClickListener(),
BlockyNMSListener(),
)

blocky.config.run {
Expand All @@ -87,34 +86,9 @@ class BlockyPlugin : JavaPlugin() {
if (!disableCustomSounds) listeners(BlockySoundListener())
}

geary {
on(GearyPhase.ENABLE) {
runStartupFunctions()
}
}
}

fun runStartupFunctions() {
registryTagMap = createTagRegistryMap()
ResourcepackGeneration().generateDefaultAssets()
}

private fun createTagRegistryMap(): Map<ResourceLocation, IntArrayList> {

return BuiltInRegistries.BLOCK.tags.map { pair ->
pair.first.location to IntArrayList(pair.second.size()).apply {
// If the tag is MINEABLE_WITH_AXE, don't add noteblock, if it's MINEABLE_WITH_PICKAXE, don't add petrified oak slab
pair.second.filter {
it.value().descriptionId != when (pair.first.location) {
BlockTags.MINEABLE_WITH_AXE.location -> "block.minecraft.note_block"
BlockTags.MINEABLE_WITH_PICKAXE.location -> "block.minecraft.petrified_oak_slab"
else -> it.value().descriptionId
}
}.forEach { add(BuiltInRegistries.BLOCK.getId(it.value())) }
}
}.toList().toMap()
}


fun createBlockyContext() {
DI.remove<BlockyContext>()
Expand Down
5 changes: 0 additions & 5 deletions src/main/kotlin/com/mineinabyss/blocky/api/BlockyBlocks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,4 @@ object BlockyBlocks {
//TODO Handle light via packets for blocks
return true
}

fun removeBlockyBlock(location: Location): Boolean {
location.blockyBlock ?: return false
return attemptBreakBlockyBlock(location.block)
}
}
Original file line number Diff line number Diff line change
@@ -1,74 +1,81 @@
package com.mineinabyss.blocky.components.features

import com.mineinabyss.blocky.components.core.BlockyInfo
import com.mineinabyss.blocky.components.features.mining.BlockyMining
import com.mineinabyss.blocky.api.BlockyFurnitures.prefabKey
import com.mineinabyss.blocky.components.features.mining.ToolType
import com.mineinabyss.blocky.helpers.GenericHelpers
import com.mineinabyss.blocky.helpers.gearyInventory
import com.mineinabyss.geary.papermc.tracking.blocks.helpers.toGearyOrNull
import com.mineinabyss.geary.prefabs.PrefabKey
import com.mineinabyss.idofront.messaging.broadcastVal
import com.mineinabyss.idofront.serialization.DurationSerializer
import com.mineinabyss.idofront.serialization.SerializableItemStack
import com.mineinabyss.idofront.serialization.toSerializable
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.Material
import org.bukkit.attribute.AttributeModifier
import org.bukkit.block.Block
import org.bukkit.entity.Player
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.EquipmentSlotGroup
import java.util.UUID
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Serializable
@SerialName("blocky:breaking")
data class BlockyBreaking(
val baseDuration: @Serializable(DurationSerializer::class) Duration = 3.seconds,
val hardness: Double,
val modifiers: BlockyModifiers = BlockyModifiers()
) {
private fun defaultBlockHardness(block: Block): Double {
return when (block.type) {
Material.NOTE_BLOCK -> 0.8
else -> 1.0
}
}

fun calculateBreakTime(block: Block, player: Player, hand: EquipmentSlot, heldItem: ItemStack?): Duration {
val itemInHand = heldItem ?: ItemStack(Material.AIR)
var duration = baseDuration
if (block.toGearyOrNull()?.get<BlockyInfo>()?.isUnbreakable == true) return Duration.INFINITE
/**
* Calculates the AttributeModifier that would correctly change the breaking-speed based on the BlockyBreaking-hardness
* This method takes into account the base-value of the PLAYER_BLOCK_BREAKING_SPEED attribute, as well as any existing modifiers
* It then handles it based on the default hardness of the block
*/
fun createBreakingModifier(player: Player, block: Block): AttributeModifier {
return AttributeModifier.deserialize(
mapOf(
"slot" to EquipmentSlotGroup.HAND,
"uuid" to UUID.nameUUIDFromBytes(block.toString().toByteArray()).toString(),
"name" to "blocky:custom_break_speed",
"operation" to AttributeModifier.Operation.MULTIPLY_SCALAR_1.ordinal,
"amount" to (defaultBlockHardness(block) / hardness) - 1 + player.blockStateModifiers()
)
)
}

if (modifiers.heldItems.isNotEmpty()) {
val heldPrefab = player.gearyInventory?.get(hand)?.prefabs?.first()?.get<PrefabKey>()
val modifier = modifiers.heldItems.firstOrNull {
it.item.prefab?.let { p -> p == heldPrefab?.full } ?: false
//TODO This could be improved. isSimilar cares about durability which we don't want though
|| (it.item.type == itemInHand.type)
}
if (modifier != null) duration = maxOf(duration - modifier.value, Duration.ZERO)
} else if (modifiers.heldTypes.isNotEmpty()) {
val heldTypes = player.gearyInventory?.get(hand)?.get<BlockyMining>()?.toolTypes ?: setOf(GenericHelpers.vanillaToolTypes(itemInHand))
val modifier = modifiers.heldTypes.firstOrNull { it.toolType in heldTypes }
if (modifier != null) duration = maxOf(duration - modifier.value, Duration.ZERO)
}
private fun Player.blockStateModifiers(): Double {
var modifier = 0.0

//TODO: state modifiers
/*if (player.activePotionEffects.any { it.type == PotionEffectType.FAST_DIGGING })
duration = duration.times(0.2 * (player.activePotionEffects.first { it.type == PotionEffectType.FAST_DIGGING }.amplifier))
if (player.isInWater && player.)*/
modifier += modifiers.heldTypes.find { it.toolType.contains(inventory.itemInMainHand) }?.value ?: 0.0
modifier += modifiers.heldItems.find {
if (it.item.prefab != null) it.item.prefab == inventory.itemInMainHand.toSerializable().prefab
else it.item.type == inventory.itemInMainHand.type
}?.value ?: 0.0

return duration
return modifier
}

@Serializable
@SerialName("blocky:modifier")
data class BlockyModifiers(
val heldItems: Set<BlockySerializableItemModifier> = setOf(),
val heldTypes: Set<BlockyToolModifier> = setOf(),
val states: Set<BlockyStateModifier> = setOf(),
) {
@Serializable data class BlockySerializableItemModifier(val item: SerializableItemStack, val value: @Serializable(DurationSerializer::class) Duration)
@Serializable data class BlockyToolModifier(val toolType: ToolType, val value: @Serializable(DurationSerializer::class) Duration)
@Serializable data class BlockyStateModifier(val state: BlockyStateType, val value: @Serializable(DurationSerializer::class) Duration, val operation: Operation = Operation.SUBTRACT)
@Serializable
data class BlockySerializableItemModifier(
val item: SerializableItemStack,
val value: Double
)

enum class Operation {
ADD, SUBTRACT, MULTIPLY, DIVIDE
}
enum class BlockyStateType {
HASTE, MINING_FATIGUE, IN_WATER, IN_WATER_NO_AFFINITY, NOT_ON_GROUND, IS_SNEAKING
}
@Serializable
data class BlockyToolModifier(
val toolType: ToolType,
val value: Double
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.mineinabyss.blocky.components.features.mining

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.Material
import org.bukkit.Tag
import org.bukkit.inventory.ItemStack

/**
* Lets you define a component that affects the mining-speed of custom blocks
Expand All @@ -13,5 +16,17 @@ import kotlinx.serialization.Serializable
data class BlockyMining(val toolTypes: Set<ToolType> = setOf(ToolType.ANY))

enum class ToolType {
PICKAXE, AXE, SHOVEL, HOE, SWORD, SHEARS, ANY
PICKAXE, AXE, SHOVEL, HOE, SWORD, SHEARS, ANY;

fun contains(itemStack: ItemStack): Boolean {
return when(this) {
PICKAXE -> Tag.ITEMS_PICKAXES.isTagged(itemStack.type)
AXE -> Tag.ITEMS_AXES.isTagged(itemStack.type)
SHOVEL -> Tag.ITEMS_SHOVELS.isTagged(itemStack.type)
HOE -> Tag.ITEMS_HOES.isTagged(itemStack.type)
SWORD -> Tag.ITEMS_SWORDS.isTagged(itemStack.type)
SHEARS -> itemStack.type == Material.SHEARS
ANY -> !itemStack.isEmpty
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mineinabyss.blocky.components.features.mining

import com.mineinabyss.geary.papermc.tracking.entities.toGearyOrNull
import com.mineinabyss.idofront.messaging.broadcastVal
import com.mineinabyss.idofront.serialization.AttributeModifierSerializer
import kotlinx.serialization.Serializable
import org.bukkit.attribute.Attribute
import org.bukkit.attribute.AttributeModifier
import org.bukkit.entity.Player

@Serializable
data class PlayerMiningAttribute(val modifier: @Serializable(AttributeModifierSerializer::class) AttributeModifier) {
fun addTransientModifier(player: Player) {
player.getAttribute(Attribute.PLAYER_BLOCK_BREAK_SPEED)?.addTransientModifier(modifier)
}

fun removeModifier(player: Player) {
player.getAttribute(Attribute.PLAYER_BLOCK_BREAK_SPEED)?.removeModifier(modifier)
player.toGearyOrNull()?.remove<PlayerMiningAttribute>()
}
}

val Player.miningAttribute get() = toGearyOrNull()?.get<PlayerMiningAttribute>()
Loading

0 comments on commit 0fa9749

Please sign in to comment.