diff --git a/.github/workflows/gradle-ci.yml b/.github/workflows/gradle-ci.yml
index 36d11db..5cf235f 100644
--- a/.github/workflows/gradle-ci.yml
+++ b/.github/workflows/gradle-ci.yml
@@ -1,13 +1,12 @@
name: Java CI with Gradle
on:
- push:
- branches:
- - master
- paths-ignore:
- - '**.md'
pull_request:
+concurrency:
+ cancel-in-progress: true
+ group: ci-${{ github.ref }}
+
jobs:
build:
runs-on: ubuntu-latest
@@ -21,14 +20,5 @@ jobs:
java-version: 17
cache: gradle
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
-
- name: Build
run: gradle build
-
- - name: Upload build
- uses: actions/upload-artifact@v1
- with:
- name: build
- path: build/libs
diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml
index 67136f0..94c8176 100644
--- a/.github/workflows/publish-packages.yml
+++ b/.github/workflows/publish-packages.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- master
+ - develop
paths-ignore:
- '**.md'
@@ -14,42 +15,27 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Set up JDK
- uses: actions/setup-java@v3
+ - uses: MineInAbyss/publish-action@master
with:
- distribution: temurin
- java-version: 17
- cache: gradle
-
- - name: Set env variable from latest maven version
- run: >
- echo "RELEASE_VERSION=$( \
- curl https://repo.mineinabyss.com/releases/com/mineinabyss/looty/maven-metadata.xml | \
- grep -oP '(?!)[\d\.]*(?=)' \
- )" >> $GITHUB_ENV
-
- - name: Run gradle build and publish
- run: gradle build publish dokkaHtml -PmineinabyssMavenUsername=${{ secrets.MAVEN_PUBLISH_USERNAME }} -PmineinabyssMavenPassword=${{ secrets.MAVEN_PUBLISH_PASSWORD }}
-
- - name: Publish documentation to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/dokka/htmlMultiModule
- force_orphan: true
-
- - name: Get version from gradle
- shell: bash
- id: extract_version
- run: |
- version=`gradle properties --console=plain -q | grep "^version:" | awk '{printf $2}'`
- echo "::set-output name=version::$version"
-
- - name: Create GitHub Release for shaded idofront platfrom
- uses: marvinpinto/action-automatic-releases@latest
- with:
- repo_token: "${{ secrets.GITHUB_TOKEN }}"
- prerelease: false
- automatic_release_tag: v${{ steps.extract_version.outputs.version }}
- files: |
- build/libs/*[0-9].jar
+ maven-metadata-url: https://repo.mineinabyss.com/releases/com/mineinabyss/looty/maven-metadata.xml
+ pages-path: build/dokka/htmlMultiModule/
+ dokka: dokkaHtmlMultiModule
+ maven-username: ${{ secrets.MAVEN_PUBLISH_USERNAME }}
+ maven-password: ${{ secrets.MAVEN_PUBLISH_PASSWORD }}
+ release-files: |
+ ${{ github.workspace }}/publish/*.jar
+
+ deploy:
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ if: ${{ github.ref == 'refs/heads/master' }}
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v1
diff --git a/.gitignore b/.gitignore
index 73eae65..feaa6d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ out
eclipse
*.ipr
*.iws
+
+logs
diff --git a/README.md b/README.md
index 89243e0..4611dfa 100644
--- a/README.md
+++ b/README.md
@@ -7,49 +7,72 @@
[![Contribute](https://shields.io/badge/Contribute-e57be5?logo=github%20sponsors&style=flat&logoColor=white)](https://wiki.mineinabyss.com/contribute)
-## Overview
-
-Looty is a PaperMC plugin that acts as a link between ItemStacks and ECS entities from our Entity Component System (
-ECS) [Geary](https://github.com/MineInAbyss/Geary). It lets you easily persist components on ItemStacks and have systems
-iterate over them.
-
-Looty also provides the same powerful configuration system as Geary, which lets you quickly create fancy custom items
-for Minecraft servers. It even updates item data like models or lore update automatically.
+Looty is a [Paper](https://papermc.io/) plugin for creating custom items with config files. We use [Geary](https://github.com/MineInAbyss/geary-papermc) to break down items into small components. We provide many components to modify vanilla behaviour, for new game features check out [Geary-addons](https://github.com/MineInAbyss/Geary-addons).
## Features
-### Modular behaviours
-
-ECS allows us to deconstruct complex item behaviours into individual components or actions. It makes code easier to
-maintain and behaviours more reusable!
-
-### Easy serialization
-
-Thanks to kotlinx.serialization all our components are automatically serializable without reflection! Looty will
-automatically save persisting components to the item itself.
-
-### Prefabs and config files
-
-Looty uses the same prefab system as Geary. It also adds item-related events so you can configure Geary actions to fire
-on left click, or when an item is equipped.
-
-Coders can focus on coding interesting components and systems while designers can tweak numbers and combine things
-together without messing with your precious code!
-
-### Item tracking
-
-Looty will automatically keep track of item entities for you. You can go between the two using: `gearyOrNull(itemStack)`
-and `gearyEntity.get()`. Looty essentially ensures the ItemStack component on this entity always references
-the true ItemStack (i.e. modifying it modifies the item in inventory.)
-
-Currently, we plan on keeping track of items in player inventories and when thrown on the ground. We may also allow
-specifically marked mobs to keep track of custom items in their inventory (doing this for all mobs would likely be
-unnecessarily slow).
-
-## Biggest issues
-
-- Some more caching and optimizations should be done for the item tracking system. We haven't tested it large-scale yet.
-- There are many design questions regarding how the config system should work and how to handle some complex behaviours.
- Things may change.
-- There is no data migration for items, though using prefabs, most item components can be static and not serialized to
- the item itself. This means once the prefab config is updated, the item itself works again.
+- Automatically updates item name, lore, and custom models when the config changes
+- Support for custom recipes
+- Write configs in yaml, json, and more.
+
+## Usage
+Coming soon
+
+## Examples
+
+### Custom item from our server
+
+`star-compass.yml`
+```yaml
+# Make items in the same inventory share one entity
+- !
+
+# Specify item name, model, and lore,
+# if this ever changes, Looty will update existing items
+- !
+ item:
+ type: PAPER
+ customModelData: 125
+ displayName: Star Compass
+ lore:
+ - Points towards the center of the Abyss
+
+# Add a recipe using other custom items
+- !
+ discoverRecipes: true
+ recipes:
+ - !
+ items:
+ - prefab: mineinabyss:star_compass_needle
+ - prefab: mineinabyss:titanjaw_pearl
+
+
+# A component provided another plugin
+- !
+
+```
+
+### Custom recipe for a vanilla item
+
+`lead.yml`
+```yaml
+- !
+ discoverRecipes: true
+ result:
+ type: LEAD
+ amount: 2
+ removeRecipes:
+ - minecraft:lead
+ group: "looty:lead"
+ recipes:
+ - !
+ items:
+ S:
+ prefab: mineinabyss:silkfang_silk
+ Z:
+ prefab: mineinabyss:abyssal_snail_gunk
+ configuration: |-
+ |ZZ |
+ |ZS |
+ | Z|
+```
diff --git a/build.gradle.kts b/build.gradle.kts
index 6b059c5..8176685 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,10 +3,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val idofrontVersion: String by project
val gearyVersion: String by project
+@Suppress("DSL_SCOPE_VIOLATION")
plugins {
- kotlin("jvm")
- kotlin("plugin.serialization")
- id("com.mineinabyss.conventions.kotlin")
+ alias(libs.plugins.kotlinx.serialization)
+ id("com.mineinabyss.conventions.kotlin.jvm")
id("com.mineinabyss.conventions.papermc")
id("com.mineinabyss.conventions.nms")
id("com.mineinabyss.conventions.copyjar")
@@ -15,12 +15,13 @@ plugins {
}
repositories {
+ maven("https://repo.mineinabyss.com/snapshots")
maven("https://jitpack.io")
}
dependencies {
// Other plugins
- compileOnly(lootyLibs.geary.papermc.core)
+ compileOnly(myLibs.geary.papermc)
// From Geary
diff --git a/gradle.properties b/gradle.properties
index ca9da8c..720ee27 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,3 @@
group=com.mineinabyss
-version=0.9
-kotlinVersion=1.7.20
-idofrontVersion=0.15.3
-serverVersion=1.19.2-R0.1-SNAPSHOT
+version=0.10
+idofrontVersion=0.18.14
diff --git a/gradle/lootyLibs.versions.toml b/gradle/lootyLibs.versions.toml
deleted file mode 100644
index 35c844c..0000000
--- a/gradle/lootyLibs.versions.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-[versions]
-geary = "0.21.2"
-
-[libraries]
-geary-papermc-core = { module = "com.mineinabyss:geary-papermc-core", version.ref = "geary" }
diff --git a/gradle/myLibs.versions.toml b/gradle/myLibs.versions.toml
new file mode 100644
index 0000000..0682a39
--- /dev/null
+++ b/gradle/myLibs.versions.toml
@@ -0,0 +1,5 @@
+[versions]
+gearyPaper = "0.24-SNAPSHOT"
+
+[libraries]
+geary-papermc = { module = "com.mineinabyss:geary-papermc", version.ref = "gearyPaper" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ae04661..fae0804 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/settings.gradle.kts b/settings.gradle.kts
index fc27ff7..d711ced 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,3 +1,5 @@
+rootProject.name = "looty"
+
pluginManagement {
repositories {
gradlePluginPortal()
@@ -5,13 +7,6 @@ pluginManagement {
maven("https://repo.papermc.io/repository/maven-public/")
}
- plugins {
- val kotlinVersion: String by settings
- kotlin("jvm") version kotlinVersion
- kotlin("plugin.serialization") version kotlinVersion
- kotlin("kapt") version kotlinVersion
- }
-
val idofrontVersion: String by settings
resolutionStrategy {
eachPlugin {
@@ -30,9 +25,7 @@ dependencyResolutionManagement {
versionCatalogs {
create("libs").from("com.mineinabyss:catalog:$idofrontVersion")
- create("lootyLibs").from(files("gradle/lootyLibs.versions.toml"))
+ create("myLibs").from(files("gradle/myLibs.versions.toml"))
}
}
-
-rootProject.name = "looty"
diff --git a/src/main/kotlin/com/mineinabyss/looty/Helpers.kt b/src/main/kotlin/com/mineinabyss/looty/Helpers.kt
deleted file mode 100644
index d4f9498..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/Helpers.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.mineinabyss.looty
-
-import com.mineinabyss.geary.datatypes.GearyEntity
-import com.mineinabyss.geary.papermc.store.encodeComponentsTo
-import com.mineinabyss.idofront.items.editItemMeta
-import com.mineinabyss.idofront.messaging.broadcast
-import com.mineinabyss.looty.config.lootyConfig
-import com.mineinabyss.looty.ecs.components.LootyType
-import org.bukkit.inventory.ItemStack
-
-internal fun debug(message: Any?) {
- if (lootyConfig.debug) broadcast(message)
-}
-
-fun GearyEntity.encodeComponentsTo(lootyType: LootyType): ItemStack =
- lootyType.item.toItemStack().editItemMeta {
- encodeComponentsTo(persistentDataContainer)
- }
diff --git a/src/main/kotlin/com/mineinabyss/looty/LootyCommands.kt b/src/main/kotlin/com/mineinabyss/looty/LootyCommands.kt
index 7ff9a6c..3abce86 100644
--- a/src/main/kotlin/com/mineinabyss/looty/LootyCommands.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/LootyCommands.kt
@@ -1,135 +1,16 @@
package com.mineinabyss.looty
-import com.mineinabyss.geary.helpers.listComponents
-import com.mineinabyss.geary.papermc.globalContextMC
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.idofront.commands.arguments.intArg
-import com.mineinabyss.idofront.commands.arguments.optionArg
-import com.mineinabyss.idofront.commands.arguments.stringArg
import com.mineinabyss.idofront.commands.execution.IdofrontCommandExecutor
-import com.mineinabyss.idofront.commands.extensions.actions.playerAction
-import com.mineinabyss.idofront.config.config
-import com.mineinabyss.idofront.messaging.error
-import com.mineinabyss.idofront.messaging.info
-import com.mineinabyss.looty.ecs.queries.LootyTypeQuery
-import com.mineinabyss.looty.ecs.queries.LootyTypeQuery.key
-import com.mineinabyss.looty.tracking.toGearyOrNull
-import org.bukkit.Material
-import org.bukkit.command.Command
-import org.bukkit.command.CommandSender
-import org.bukkit.command.TabCompleter
-import org.bukkit.inventory.ItemStack
+import com.mineinabyss.looty.config.looty
-class LootyCommands : IdofrontCommandExecutor(), TabCompleter {
- override val commands = commands(looty) {
+class LootyCommands : IdofrontCommandExecutor() {
+ override val commands = commands(looty.plugin) {
"looty" {
"reload" {
action {
- looty.config = config("config") { looty.fromPluginPath(loadDefault = true) }
+ looty.configController.reload()
}
}
- "item" {
- //TODO more efficient way of finding the right types
- val type by optionArg(options = LootyTypeQuery.map { it.key.toString() }) {
- parseErrorMessage = { "No such item: $passed" }
- }
- val amount by intArg { default = 1 }
-
- playerAction {
- val slot = player.inventory.firstEmpty()
- if (slot == -1) {
- player.error("No empty slots in inventory")
- return@playerAction
- }
-
- val item = LootyFactory.createFromPrefab(PrefabKey.of(type))
- if (item == null) {
- player.error("$type exists but is not an item.")
- return@playerAction
- }
- item.amount = amount
- player.inventory.addItem(item)
- }
- }
-
- "debug" {
- "stone" {
- playerAction {
- player.inventory.itemInMainHand.toGearyOrNull(player)?.get()?.type = Material.STONE
- }
- }
- "pdc" {
- playerAction {
- sender.info(player.inventory.itemInMainHand.itemMeta!!.persistentDataContainer.keys)
- }
- }
- "components" {
- playerAction {
- sender.info(player.inventory.itemInMainHand.toGearyOrNull(player)?.listComponents())
- }
- //TODO print static and serialized on separate lines
- }
- "component" {
-// "add" {
-// action {
-// val player = sender as? Player ?: return@action
-// runCatching {
-// globalContextMC.formats.getFormat("json").decodeFromString(
-// PolymorphicSerializer(GearyComponent::class),
-// arguments.joinToString(" ")
-// )
-// }.onSuccess {
-// player.inventory.itemInMainHand.toGearyOrNull(player)
-// ?.set(it, it::class)
-// }.onFailure {
-// player.info(it.message)
-// }
-// }
-// }
-
- "remove" {
- val name by stringArg()
- playerAction {
- runCatching {
- globalContextMC.serializers.getClassFor(name)
- }.onSuccess {
- player.inventory.itemInMainHand.toGearyOrNull(player)
- ?.remove(it)
- }.onFailure {
- player.info(it.message)
- }
- }
- }
- }
- }
- }
- }
-
-
- override fun onTabComplete(
- sender: CommandSender,
- command: Command,
- alias: String,
- args: Array
- ): List {
- if (command.name != "looty") return emptyList()
- return when (args.size) {
- 2 -> when (args[0]) {
- "item" -> {
- LootyTypeQuery
- .filter {
- val arg = args[1].lowercase()
- it.key.key.startsWith(arg) || it.key.full.startsWith(arg)
- }
- .map { it.key.toString() }
- }
- else -> emptyList()
- }
- 3 -> when (args[0]) {
- "item" -> listOf("1", "64")
- else -> emptyList()
- }
- else -> emptyList()
}
}
}
diff --git a/src/main/kotlin/com/mineinabyss/looty/LootyFactory.kt b/src/main/kotlin/com/mineinabyss/looty/LootyFactory.kt
deleted file mode 100644
index 92f9e3d..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/LootyFactory.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.mineinabyss.looty
-
-import com.mineinabyss.geary.components.RegenerateUUIDOnClash
-import com.mineinabyss.geary.datatypes.GearyEntity
-import com.mineinabyss.geary.datatypes.GearyEntityType
-import com.mineinabyss.geary.helpers.NO_ENTITY
-import com.mineinabyss.geary.helpers.addParent
-import com.mineinabyss.geary.helpers.entity
-import com.mineinabyss.geary.helpers.toGeary
-import com.mineinabyss.geary.papermc.globalContextMC
-import com.mineinabyss.geary.papermc.store.*
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.idofront.nms.aliases.NMSItemStack
-import com.mineinabyss.looty.config.lootyConfig
-import com.mineinabyss.looty.ecs.components.LootyType
-import com.mineinabyss.looty.ecs.components.PlayerInstancedItem
-import com.mineinabyss.looty.migration.custommodeldata.CustomItem
-import com.mineinabyss.looty.migration.custommodeldata.CustomModelDataToPrefabMap
-import net.minecraft.world.item.Items
-import org.bukkit.Material
-import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers
-import org.bukkit.inventory.ItemStack
-import org.bukkit.persistence.PersistentDataContainer
-import java.util.*
-
-/**
- * Many helper functions related to creating Looty items.
- */
-object LootyFactory {
- /** Creates an ItemStack from a [prefabKey], encoding relevant information to it. */
- fun createFromPrefab(
- prefabKey: PrefabKey,
- ): ItemStack? {
- val item = ItemStack(Material.AIR)
- updateItemFromPrefab(item, prefabKey)
- return item.takeIf { it.type != Material.AIR }
- }
-
- fun updateItemFromPrefab(item: ItemStack, prefabKey: PrefabKey) {
- val prefab = prefabKey.toEntityOrNull() ?: return
- prefab.get()?.item?.toItemStack(item)
- item.editMeta {
- it.persistentDataContainer.encodePrefabs(listOf(prefabKey))
- }
- }
-
- sealed class ItemState {
- abstract val entity: GearyEntity
-
- class Loaded(override val entity: GearyEntity, val slot: Int, val pdc: PersistentDataContainer) : ItemState()
- class Empty : ItemState() {
- override val entity: GearyEntity = NO_ENTITY
- }
- class NotLoaded(val slot: Int, val pdc: PersistentDataContainer) : ItemState() {
- override val entity: GearyEntity = NO_ENTITY
- }
- }
-
- private fun updateOldLootyItem(pdc: PersistentDataContainer, prefabs: Set, item: NMSItemStack) {
- val tag = item.tag ?: return
- if (!tag.contains("CustomModelData")) return
- if (prefabs.isEmpty()) {
- val prefab = CustomModelDataToPrefabMap[CustomItem(
- CraftMagicNumbers.getMaterial(item.item),
- tag.getInt("CustomModelData")
- )] ?: return
- pdc.encodeComponents(setOf(), GearyEntityType())
- pdc.encodePrefabs(listOf(prefab))
- }
- }
-
- //TODO maybe if the prefab has PlayerInstancedItem added to it, we should remove id?
- fun getItemState(pdc: PersistentDataContainer?, slot: Int, item: NMSItemStack): ItemState {
- if (pdc == null || item.item == Items.AIR || !pdc.hasComponentsEncoded) return ItemState.Empty()
- val prefabs = pdc.decodePrefabs()
- if (lootyConfig.migrateByCustomModelData) {
- updateOldLootyItem(pdc, prefabs, item)
- }
- if (prefabs.size == 1) {
- val prefab = prefabs.first().toEntityOrNull() ?: return ItemState.Empty()
- if (prefab.has()) {
- pdc.remove()
- return ItemState.Loaded(prefab, slot, pdc)
- } else if(pdc.decode() == null) {
- return ItemState.NotLoaded(slot, pdc)
- }
- }
- val uuid = pdc.decode()
- if (uuid != null) {
- val entity = globalContextMC.uuid2entity[uuid] ?: return ItemState.NotLoaded(slot, pdc)
- return ItemState.Loaded(entity, slot, pdc)
- }
- return ItemState.Empty()
- }
-
- //TODO return the instance of prefab for PlayerInstanced
- /** Gets or creates a [GearyEntity] based on a given item and the context it is in. */
- fun loadItem(holder: GearyEntity, pdc: PersistentDataContainer/*, cache: PlayerItemCache*/): GearyEntity {
- val decoded = pdc.decodeComponents()
-
- // Attempt to load player-instanced item into a component on the player
- val prefabs = decoded.type.prefabs
- if (prefabs.size == 1) {
- val prefab = prefabs.first().toGeary()
- if (prefab.has()) return prefab
-// cache.getInstance(prefab)?.let { return it }
- }
-
- // If the item wasn't already loaded or slots didn't match, create a new entity
- return entity {
- addParent(holder)
- add()
- loadComponentsFrom(decoded)
- getOrSetPersisting { UUID.randomUUID() }
-// addSlotTypeComponent(itemLocation, this)
- encodeComponentsTo(pdc)
-// debug("Loaded item ${get()} in slot ${itemLocation.slot}")
- debug("Loaded new instance of prefab ${get()} on $holder")
- }
- }
-
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/LootyPlugin.kt b/src/main/kotlin/com/mineinabyss/looty/LootyPlugin.kt
index c7e4be7..3c0a136 100644
--- a/src/main/kotlin/com/mineinabyss/looty/LootyPlugin.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/LootyPlugin.kt
@@ -1,45 +1,58 @@
package com.mineinabyss.looty
-import com.mineinabyss.geary.addon.autoscan
-import com.mineinabyss.geary.papermc.dsl.gearyAddon
-import com.mineinabyss.idofront.config.IdofrontConfig
-import com.mineinabyss.idofront.config.config
+import com.mineinabyss.geary.autoscan.autoscan
+import com.mineinabyss.geary.modules.geary
+import com.mineinabyss.idofront.di.DI
import com.mineinabyss.idofront.platforms.Platforms
+import com.mineinabyss.idofront.plugin.listeners
import com.mineinabyss.idofront.plugin.service
import com.mineinabyss.idofront.serialization.SerializablePrefabItemService
-import com.mineinabyss.looty.config.LootyConfig
-import com.mineinabyss.looty.ecs.systems.*
+import com.mineinabyss.looty.config.LootyModule
+import com.mineinabyss.looty.features.backpack.BackpackListener
+import com.mineinabyss.looty.features.food.FoodConsumptionListener
+import com.mineinabyss.looty.features.holdsentity.SpawnHeldPrefabSystem
+import com.mineinabyss.looty.features.nointeraction.DisableItemInteractionsSystem
+import com.mineinabyss.looty.features.recipes.ItemRecipes
+import com.mineinabyss.looty.features.recipes.PotionMixRecipeSystem
+import com.mineinabyss.looty.features.wearables.WearableItemSystem
import org.bukkit.plugin.java.JavaPlugin
-/** Gets [Geary] via Bukkit once, then sends that reference back afterwards */
-val looty: LootyPlugin by lazy { JavaPlugin.getPlugin(LootyPlugin::class.java) }
-
class LootyPlugin : JavaPlugin() {
- lateinit var config: IdofrontConfig
override fun onLoad() {
Platforms.load(this, "mineinabyss")
}
override fun onEnable() {
- config = config("config") { fromPluginPath(loadDefault = true) }
+ DI.add(LootyModule(this))
//Reset to avoid duplicates and clear mixes that have been removed
- looty.server.potionBrewer.resetPotionMixes()
- gearyAddon {
- autoscan("com.mineinabyss") {
+ server.potionBrewer.resetPotionMixes()
+
+ geary {
+ autoscan(classLoader, "com.mineinabyss.looty") {
all()
}
+ install(ItemRecipes)
- service(LootySerializablePrefabItemService)
-
- LootyCommands() //Register commands
+ geary.pipeline.addSystems(
+ DisableItemInteractionsSystem(),
+ PotionMixRecipeSystem(),
+ )
}
+ service(LootySerializablePrefabItemService())
+ LootyCommands() //Register commands
+
+ listeners(
+ WearableItemSystem(),
+ BackpackListener(),
+ FoodConsumptionListener(),
+ SpawnHeldPrefabSystem(),
+ )
}
override fun onDisable() {
super.onDisable()
server.scheduler.cancelTasks(this)
-// Engine.forEach { it.clear() }
}
}
diff --git a/src/main/kotlin/com/mineinabyss/looty/SerializablePrefabItemStack.kt b/src/main/kotlin/com/mineinabyss/looty/SerializablePrefabItemStack.kt
index e6533ac..7f0d2ea 100644
--- a/src/main/kotlin/com/mineinabyss/looty/SerializablePrefabItemStack.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/SerializablePrefabItemStack.kt
@@ -1,34 +1,13 @@
package com.mineinabyss.looty
-import com.mineinabyss.geary.papermc.GearyMCContext
-import com.mineinabyss.geary.papermc.GearyMCContextKoin
-import com.mineinabyss.geary.papermc.store.decodePrefabs
+import com.mineinabyss.geary.papermc.tracking.items.gearyItems
import com.mineinabyss.geary.prefabs.PrefabKey
import com.mineinabyss.idofront.serialization.SerializablePrefabItemService
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
import org.bukkit.inventory.ItemStack
-import org.bukkit.inventory.RecipeChoice
-@Serializable
-@SerialName("looty:item")
-object LootySerializablePrefabItemService : SerializablePrefabItemService, GearyMCContext by GearyMCContextKoin() {
+class LootySerializablePrefabItemService : SerializablePrefabItemService {
override fun encodeFromPrefab(item: ItemStack, prefabName: String) {
- LootyFactory.updateItemFromPrefab(item, PrefabKey.of(prefabName)) //TODO encode
+ val result = gearyItems.createItem(PrefabKey.of(prefabName), item)
+ require(result != null) { "Failed to create serializable ItemStack from $prefabName, does the prefab exist and have a geary:set.item component?" }
}
}
-
-data class LootyRecipeChoice(
- val item: ItemStack
-) : RecipeChoice, GearyMCContext by GearyMCContextKoin() {
- val prefabs = item.itemMeta.persistentDataContainer.decodePrefabs()
-
- override fun test(itemStack: ItemStack): Boolean {
- return itemStack.isSimilar(item) || itemStack.itemMeta.persistentDataContainer.decodePrefabs()
- .containsAll(prefabs)
- }
-
- override fun clone(): RecipeChoice = copy()
-
- override fun getItemStack(): ItemStack = item
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/config/LootyConfig.kt b/src/main/kotlin/com/mineinabyss/looty/config/LootyConfig.kt
index 20f7360..c9db6df 100644
--- a/src/main/kotlin/com/mineinabyss/looty/config/LootyConfig.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/config/LootyConfig.kt
@@ -1,9 +1,7 @@
package com.mineinabyss.looty.config
-import com.mineinabyss.looty.looty
import kotlinx.serialization.Serializable
-val lootyConfig get() = looty.config.data
@Serializable
data class LootyConfig(
val debug: Boolean = false,
diff --git a/src/main/kotlin/com/mineinabyss/looty/config/LootyModule.kt b/src/main/kotlin/com/mineinabyss/looty/config/LootyModule.kt
new file mode 100644
index 0000000..7a77a96
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/config/LootyModule.kt
@@ -0,0 +1,14 @@
+package com.mineinabyss.looty.config
+
+import com.mineinabyss.idofront.config.config
+import com.mineinabyss.idofront.di.DI
+import com.mineinabyss.looty.LootyPlugin
+
+val looty by DI.observe()
+
+class LootyModule(
+ val plugin: LootyPlugin
+) {
+ val configController = config("config") { plugin.fromPluginPath(loadDefault = true) }
+ val config: LootyConfig by configController
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/LootyType.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/LootyType.kt
deleted file mode 100644
index 8c476f7..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/LootyType.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.mineinabyss.looty.ecs.components
-
-import com.mineinabyss.idofront.serialization.SerializableItemStack
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-/**
- * A component describing what the ItemStack for a looty item should look like
- */
-@Serializable
-@SerialName("looty:type")
-class LootyType(val item: SerializableItemStack)
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PickedUpItemData.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/PickedUpItemData.kt
deleted file mode 100644
index 868f21b..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PickedUpItemData.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.mineinabyss.looty.ecs.components
-
-import org.bukkit.inventory.ItemStack
-
-/**
- * A class that stores an item with components properly encoded that was picked up by players in creative mode.
- *
- * When the player goes to put it back, the data gets copied over. We can't do this when the item is picked up because
- * clients basically have full control over creative inventory.
- */
-class PickedUpItemData(
- val item: ItemStack
-)
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItem.kt
deleted file mode 100644
index 6b6fd62..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItem.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.mineinabyss.looty.ecs.components
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-/**
- * > looty:player_instanced_item
- *
- * Indicates a Looty item entity should exist once per player instead of once for each ItemStack in the inventory.
- */
-@Serializable
-@SerialName("looty:player_instanced_item")
-class PlayerInstancedItem
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItems.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItems.kt
deleted file mode 100644
index 5f0535d..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerInstancedItems.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.mineinabyss.looty.ecs.components
-
-import com.mineinabyss.geary.datatypes.GearyEntity
-import com.mineinabyss.geary.datatypes.setBit
-import com.mineinabyss.geary.datatypes.toIntArray
-import com.mineinabyss.geary.datatypes.unsetBit
-import com.mineinabyss.geary.helpers.addParent
-import com.mineinabyss.geary.helpers.entity
-import com.mineinabyss.geary.helpers.toGeary
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.geary.prefabs.helpers.addPrefab
-import com.mineinabyss.looty.debug
-import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap
-
-class PlayerInstancedItems(val parent: GearyEntity) {
- /** Map of prefab entity to its instance on the player */
- private val prefab2InstanceMap: Long2LongOpenHashMap = Long2LongOpenHashMap()
- private val instance2PrefabMap: Long2LongOpenHashMap = Long2LongOpenHashMap()
-
- private val slots: Long2LongOpenHashMap = Long2LongOpenHashMap()
-
- fun GearyEntity.pair(): Pair {
- val id = id.toLong()
- val read = prefab2InstanceMap[id]
- return if (read != 0L) id to read else instance2PrefabMap[id] to id
- }
-
- fun setSlot(entity: GearyEntity, slot: Int) {
- val (prefab, instance) = entity.pair()
- // If entity not present
- if (prefab == 0L) {
- load(entity, slot)
- return
- }
- val curr = slots[instance]
- slots[instance] = curr.or(1L shl slot)
- }
-
- fun unsetSlot(entity: GearyEntity, slot: Int, removeEntity: Boolean): Boolean {
- val (_, instance) = entity.pair()
- val update = slots[instance].unsetBit(slot)
- if (update == 0L) {
- remove(entity, removeEntity)
- return true
- }
- slots[instance] = update
- return false
- }
-
- fun getSlots(entity: GearyEntity): IntArray {
- val (_, instance) = entity.pair()
- val slots = slots[instance]
- return slots.toIntArray()
- }
-
- fun setSlots(entity: GearyEntity, newSlots: IntArray) {
- val (_, instance) = entity.pair()
- var slotLong = 0L
- for (slot in newSlots) slotLong = slotLong.setBit(slot)
- slots[instance] = slotLong
- }
-
- operator fun get(entity: GearyEntity): GearyEntity? {
- val id = entity.id.toLong()
- return (prefab2InstanceMap[id].takeIf { it != 0L }
- ?: instance2PrefabMap[id].takeIf { it != 0L })?.toGeary()
- }
-
- operator fun contains(entity: GearyEntity): Boolean {
- val id = entity.id.toLong()
- return prefab2InstanceMap[id] != 0L || slots.containsKey(id)
- }
-
- fun load(prefab: GearyEntity, vararg addToSlots: Int): GearyEntity {
- //TODO perhaps we want to disallow adding to no slots
- val prefabId = prefab.id.toLong()
-
- val instance = prefab2InstanceMap.getOrPut(prefabId) {
- entity {
- addPrefab(prefab)
- addParent(parent)
- debug("Loaded prefab ${prefab.get()} on $parent")
- }.id.toLong()
- }
- instance2PrefabMap[instance] = prefabId
- setSlots(prefab, addToSlots)
- return instance.toGeary()
- }
-
- fun remove(entity: GearyEntity, removeEntity: Boolean): Boolean {
- val (prefab, instance) = entity.pair()
- if (prefab == 0L) return false
- prefab2InstanceMap.remove(prefab)
- instance2PrefabMap.remove(instance)
- slots.remove(instance) != 0L
- if (removeEntity) instance.toGeary().removeEntity()
- return true
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerItemCache.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerItemCache.kt
deleted file mode 100644
index ab4b4b9..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/PlayerItemCache.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.mineinabyss.looty.ecs.components
-
-import com.mineinabyss.geary.datatypes.GearyEntity
-import com.mineinabyss.geary.helpers.NO_ENTITY
-import com.mineinabyss.geary.helpers.toGeary
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.idofront.nms.aliases.NMSItemStack
-import com.mineinabyss.looty.debug
-import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack
-import org.bukkit.inventory.ItemStack
-
-// TODO bad pattern, passing entity into component, move into event
-class PlayerItemCache(parent: GearyEntity) {
- private val entities = ULongArray(64)
- private val cachedItems = Array(64) { null }
- private val playerInstanced = PlayerInstancedItems(parent)
-
- fun swap(firstSlot: Int, secondSlot: Int) {
- val firstEntity = entities[firstSlot].toGeary()
- val secondEntity = entities[secondSlot].toGeary()
- if (firstEntity in playerInstanced) {
- playerInstanced.setSlot(firstEntity, secondSlot)
- playerInstanced.unsetSlot(firstEntity, firstSlot, false)
- }
- if (secondEntity in playerInstanced) {
- playerInstanced.setSlot(secondEntity, firstSlot)
- playerInstanced.unsetSlot(secondEntity, secondSlot, false)
- }
- entities[firstSlot] = secondEntity.id
- entities[secondSlot] = firstEntity.id
- debug("Swapped ${firstEntity.get()} in $firstSlot and ${secondEntity.get()} in $secondSlot")
- }
-
- fun move(oldSlot: Int, newSlot: Int) {
- val entity = entities[oldSlot].takeIf { it != 0uL }?.toGeary() ?: return
- if (entity in playerInstanced) {
- playerInstanced.setSlot(entity, newSlot)
- playerInstanced.unsetSlot(entity, oldSlot, false)
- }
- entities[newSlot] = entity.id
- entities[oldSlot] = 0uL
- debug("Moved ${entity.get()} from $oldSlot to $newSlot")
- }
-
- /**
- * @return Whether the entity would be removed if [removeEntity] were true.
- */
- fun remove(slot: Int, removeEntity: Boolean): Boolean {
- val entity = entities[slot].toGeary()
- if (entity.id == 0uL) return false
- entities[slot] = 0uL
- cachedItems[slot] = null
- return if (entity.has())
- playerInstanced.unsetSlot(entity, slot, removeEntity)
- else if (removeEntity) {
- entity.removeEntity()
- true
- } else false
- }
-
- operator fun set(slot: Int, entity: GearyEntity) {
- entities[slot] = entity.id
- if (entity.has())
- playerInstanced.setSlot(entity, slot)
- }
-
- operator fun get(slot: Int): GearyEntity = entities[slot].toGeary()
-
- fun getInstance(prefab: GearyEntity) = playerInstanced[prefab]
-
- fun updateItem(slot: Int, item: NMSItemStack) {
- // Get the instance of the prefab if the entity is a prefab
- val cached = cachedItems[slot]
- if(cached === item) return
- cachedItems[slot] = item
- val entity = entities[slot].toGeary()
- if(entity == NO_ENTITY) return
- val prefab = playerInstanced[entity]
- (prefab ?: entity).set(CraftItemStack.asCraftMirror(item))
- }
-
- fun getItem(slot: Int): NMSItemStack? {
- return cachedItems[slot]
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/inventory/SlotType.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/components/inventory/SlotType.kt
deleted file mode 100644
index 8fcc6e8..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/inventory/SlotType.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.mineinabyss.looty.ecs.components.inventory
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-object SlotType {
- @Serializable
- @SerialName("looty:slot.held")
- object Held
-
- @Serializable
- @SerialName("looty:slot.offhand")
- object Offhand
-
- @Serializable
- @SerialName("looty:slot.hotbar")
- object Hotbar
-
- @Serializable
- @SerialName("looty:slot.equipped")
- object Equipped
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/queries/LootyTypeQuery.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/queries/LootyTypeQuery.kt
deleted file mode 100644
index 8dccedb..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/queries/LootyTypeQuery.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.mineinabyss.looty.ecs.queries
-
-import com.mineinabyss.geary.datatypes.family.family
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.geary.prefabs.configuration.components.Prefab
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.geary.systems.query.GearyQuery
-import com.mineinabyss.looty.ecs.components.LootyType
-
-object LootyTypeQuery : GearyQuery() {
- val TargetScope.key by get()
- val TargetScope.type by get()
- val TargetScope.isPrefab by family { has() }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ApplyLootyTypeToItemStackSystem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ApplyLootyTypeToItemStackSystem.kt
deleted file mode 100644
index fd34a37..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ApplyLootyTypeToItemStackSystem.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.mineinabyss.looty.ecs.systems
-
-import com.mineinabyss.geary.annotations.AutoScan
-import com.mineinabyss.geary.annotations.Handler
-import com.mineinabyss.geary.systems.GearyListener
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.looty.ecs.components.LootyType
-import org.bukkit.inventory.ItemStack
-
-/**
- * Updates an entity's ItemStack to the one specified in LootyType when it is first set.
- */
-@AutoScan
-class ApplyLootyTypeToItemStackSystem : GearyListener() {
- val TargetScope.item by onFirstSet()
- val TargetScope.lootyType by onSet()
-
- @Handler
- fun TargetScope.updateItem() {
- lootyType.item.toItemStack(item)
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ItemRecipeSystem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ItemRecipeSystem.kt
deleted file mode 100644
index 9a36e54..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/ItemRecipeSystem.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.mineinabyss.looty.ecs.systems
-
-import com.mineinabyss.geary.annotations.AutoScan
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.geary.systems.RepeatingSystem
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.idofront.recipes.register
-import com.mineinabyss.looty.LootyFactory
-import com.mineinabyss.looty.ecs.components.RegisterRecipeComponent
-import com.mineinabyss.looty.looty
-import org.bukkit.Bukkit
-import org.bukkit.NamespacedKey
-import org.bukkit.event.EventHandler
-import org.bukkit.event.Listener
-import org.bukkit.event.player.PlayerJoinEvent
-import org.bukkit.inventory.ItemStack
-
-@AutoScan
-class ItemRecipeSystem : RepeatingSystem(), Listener {
- private val TargetScope.recipes by get()
- private val TargetScope.prefabKey by get()
- private val registeredRecipes = mutableSetOf()
- private val discoveredRecipes = mutableSetOf()
-
- override fun TargetScope.tick() {
- val result: ItemStack? = recipes.result?.toItemStackOrNull()
- ?: LootyFactory.createFromPrefab(this.prefabKey)
-
- if (result != null) {
- recipes.removeRecipes.forEach {
- Bukkit.removeRecipe(NamespacedKey.fromString(it)!!)
- }
-
- recipes.recipes.forEachIndexed { i, recipe ->
- val key = NamespacedKey(prefabKey.namespace, "${prefabKey.key}$i")
- registeredRecipes += key
- // Register recipe only if not present
- Bukkit.getRecipe(key) ?: recipe.toRecipe(key, result, recipes.group).register()
- if (recipes.discoverRecipes) discoveredRecipes += key
- }
- entity.remove()
- } else looty.logger.warning("Recipe ${prefabKey.key} is missing result item")
- }
-
- @EventHandler
- fun PlayerJoinEvent.showRecipesOnJoin() {
- player.discoverRecipes(discoveredRecipes)
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PeriodicSaveSystem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PeriodicSaveSystem.kt
deleted file mode 100644
index 2d76a82..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PeriodicSaveSystem.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.mineinabyss.looty.ecs.systems
-
-import com.mineinabyss.geary.annotations.AutoScan
-import com.mineinabyss.geary.components.relations.Persists
-import com.mineinabyss.geary.papermc.store.encode
-import com.mineinabyss.geary.papermc.store.encodeComponentsTo
-import com.mineinabyss.geary.systems.RepeatingSystem
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.geary.systems.accessors.building.flatten
-import com.mineinabyss.idofront.items.editItemMeta
-import org.bukkit.inventory.ItemStack
-import kotlin.time.Duration.Companion.seconds
-
-@AutoScan
-class PeriodicSaveSystem : RepeatingSystem(interval = 5.seconds) {
- private val TargetScope.persisting by getRelations().flatten()
- private val TargetScope.item by get()
-
- override fun TargetScope.tick() {
- val forceSave = every(iterations = 100)
-
- if (forceSave) {
- entity.encodeComponentsTo(item)
- return
- }
-
- item.editItemMeta {
- persisting.forEach {
- val newHash = it.targetData.hashCode()
- if (newHash != it.data.hash) {
- it.data.hash = newHash
- persistentDataContainer.encode(it.targetData)
- }
- }
- }
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PotionMixRecipeSystem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PotionMixRecipeSystem.kt
deleted file mode 100644
index 71698e8..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/PotionMixRecipeSystem.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.mineinabyss.looty.ecs.systems
-
-import com.mineinabyss.geary.annotations.AutoScan
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.geary.systems.RepeatingSystem
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.looty.LootyFactory
-import com.mineinabyss.looty.ecs.components.RegisterPotionMixComponent
-import com.mineinabyss.looty.looty
-import org.bukkit.NamespacedKey
-
-@AutoScan
-class PotionMixRecipeSystem : RepeatingSystem() {
- private val TargetScope.prefabKey by get()
- private val TargetScope.potionmixes by get()
-
- override fun TargetScope.tick() {
- val result = potionmixes.result?.toItemStackOrNull()
- ?: LootyFactory.createFromPrefab(this.prefabKey)
-
- if (result != null) {
- potionmixes.potionmixes.forEachIndexed { i, potionmix ->
- val key = NamespacedKey(prefabKey.namespace, "${prefabKey.key}$i")
- looty.server.potionBrewer.removePotionMix(key)
- looty.server.potionBrewer.addPotionMix(potionmix.toPotionMix(key, result))
- }
- entity.remove()
- } else looty.logger.warning("PotionMix $prefabKey is missing result item")
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/tracking/ItemTrackerSystem.kt b/src/main/kotlin/com/mineinabyss/looty/ecs/systems/tracking/ItemTrackerSystem.kt
deleted file mode 100644
index 8f1b637..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/systems/tracking/ItemTrackerSystem.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-@file:Suppress("UNREACHABLE_CODE")
-
-package com.mineinabyss.looty.ecs.systems.tracking
-
-import com.mineinabyss.geary.annotations.AutoScan
-import com.mineinabyss.geary.annotations.Handler
-import com.mineinabyss.geary.datatypes.forEachBit
-import com.mineinabyss.geary.datatypes.pop1
-import com.mineinabyss.geary.datatypes.setBit
-import com.mineinabyss.geary.helpers.toGeary
-import com.mineinabyss.geary.papermc.GearyMCContext
-import com.mineinabyss.geary.papermc.GearyMCContextKoin
-import com.mineinabyss.geary.papermc.access.toGeary
-import com.mineinabyss.geary.systems.GearyListener
-import com.mineinabyss.geary.systems.RepeatingSystem
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.idofront.nms.aliases.NMSItemStack
-import com.mineinabyss.idofront.nms.aliases.toNMS
-import com.mineinabyss.idofront.nms.nbt.fastPDC
-import com.mineinabyss.idofront.time.ticks
-import com.mineinabyss.looty.LootyFactory
-import com.mineinabyss.looty.LootyFactory.ItemState.*
-import com.mineinabyss.looty.debug
-import com.mineinabyss.looty.ecs.components.PlayerItemCache
-import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap
-import org.bukkit.entity.Player
-
-/**
- * ItemStack instances are super disposable, they don't represent real items. Additionally, tracking items is
- * very inconsistent, so we must cache all components from an item, then periodically check to ensure these items
- * are still there, alongside all the item movement events available to us.
- *
- * ## Process:
- * - An Inventory component stores a cache of items, which we read and compare to actual items in the inventory.
- * - We go through geary items in the inventory and ensure the right items match our existing slots.
- * - If an item is a mismatch, we add it to a list of mismatches
- * - If an item isn't in our cache, we check the mismatches or deserialize it into the cache.
- * - All valid items get re-serialized TODO in the future there should be some form of dirty tag so we aren't unnecessarily serializing things
- */
-@AutoScan
-class ItemTrackerSystem : RepeatingSystem(interval = 1.ticks) {
- private val TargetScope.player by get()
- private val TargetScope.itemCache by get()
-
- override fun TargetScope.tick() {
- refresh(player, itemCache)
- }
-
-
- @AutoScan
- class TrackOnLogin : GearyListener() {
- val TargetScope.player by onSet()
-
- @Handler
- fun TargetScope.track() {
- entity.set(PlayerItemCache(entity))
- }
- }
-
- companion object : GearyMCContext by GearyMCContextKoin() {
- // Avoids bukkit items since ItemMeta does a lot of copying which adds overhead
- fun refresh(player: Player, cache: PlayerItemCache) {
- val nmsInv = player.toNMS().inventory
- var slot = 0
-
- // Map of removed items looking for a possible new position to their slots
- val toRemove = Long2LongOpenHashMap()
- // Set of new items looking for a possible old item to fill their heart :)
-// val toMatch = mutableSetOf()
- val toMatch = Array(64) { null }
- val toAdd = mutableSetOf()
-
- fun attemptMove(loaded: Loaded): Boolean {
- val id = loaded.entity.id.toLong()
- if (id !in toRemove) return false
- val oldSlots = toRemove[id]
- val popped = oldSlots.pop1()
- val oldSlot = (oldSlots xor popped).countTrailingZeroBits()
- // If old slot is looking for the entity in our current slot
- val oldSlotLoading = toMatch[oldSlot]
- if (oldSlotLoading?.entity == cache[loaded.slot]) {
- cache.swap(oldSlot, loaded.slot)
- toMatch[oldSlot] = null
- } else cache.move(oldSlot, loaded.slot)
- if (popped == 0L) toRemove.remove(id)
- toRemove[id] = popped
- return true
- }
-
- fun calculateForItem(item: NMSItemStack, slot: Int) {
-// val currItem = cache.getItem(slot)
-// if (currItem == item && item != ItemStack.EMPTY) return
- val pdc = item.fastPDC
- // Get uuid or prefab entity
- val itemState = LootyFactory.getItemState(pdc, slot, item)
- val currEntity = cache[slot]
- // Based on them, check whether the item has changed
- when (itemState) {
- is Empty -> {}
- is Loaded ->
- if (currEntity != itemState.entity) toMatch[slot] = itemState
- else cache.updateItem(slot, item) // Update ItemStack component to always lead to an up-to-date reference
- is NotLoaded -> toAdd += itemState
- }
- if (currEntity != itemState.entity) {
- val currId = currEntity.id.toLong()
- if (currId != 0L) toRemove[currId] = toRemove[currId].setBit(slot)
- }
- }
-
- nmsInv.compartments.forEach { comp ->
- comp.forEach { item ->
- calculateForItem(item, slot++)
- }
- }
-
- // Consider cursor item as last slot
- calculateForItem(player.toNMS().containerMenu.carried, 63)
-
- // Try to match any changes with removed items, otherwise load them and update cache
- for (loaded in toMatch) {
- if (loaded == null) continue
- if (!attemptMove(loaded))
- cache[loaded.slot] = LootyFactory.loadItem(player.toGeary(), loaded.pdc)
- }
-
- // remove anything left in toRemove
- toRemove.forEach { (entityId, slot) ->
- val entity = entityId.toGeary()
- // If no other entity replaced a slot, we remove it now
- slot.forEachBit {
- if (cache[it] == entity && cache.remove(it, true))
- //TODO display entity name
- debug("Removed $entity from ${player.name}")
- }
- }
-
- // Add queued up items
- toAdd.forEach {
- cache[it.slot] = LootyFactory.loadItem(player.toGeary(), it.pdc)
- }
-
- // Set held item
-// if (context.inventory.heldItemSlot == context.slot)
-// entity.add()
- }
- }
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/backpack/Backpack.kt b/src/main/kotlin/com/mineinabyss/looty/features/backpack/Backpack.kt
new file mode 100644
index 0000000..163a40f
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/backpack/Backpack.kt
@@ -0,0 +1,13 @@
+package com.mineinabyss.looty.features.backpack
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+@SerialName("looty:backpack")
+class Backpack(
+ val canOpenInInventory: Boolean = true,
+ val canOpenInChest: Boolean = true,
+ val canOpenInEnderChest: Boolean = true,
+ val canOpenInBarrels: Boolean = true
+)
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackContents.kt b/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackContents.kt
new file mode 100644
index 0000000..b073f16
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackContents.kt
@@ -0,0 +1,10 @@
+package com.mineinabyss.looty.features.backpack
+
+import com.mineinabyss.idofront.serialization.ItemStackSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.bukkit.inventory.ItemStack
+
+@Serializable
+@SerialName("looty:backpack_contents")
+class BackpackContents(val contents: List<@Serializable(ItemStackSerializer::class) ItemStack> = emptyList())
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackListener.kt b/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackListener.kt
new file mode 100644
index 0000000..4242138
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/backpack/BackpackListener.kt
@@ -0,0 +1,99 @@
+package com.mineinabyss.looty.features.backpack
+
+import com.mineinabyss.geary.papermc.tracking.entities.toGearyOrNull
+import com.mineinabyss.geary.papermc.tracking.items.inventory.toGeary
+import com.mineinabyss.idofront.entities.rightClicked
+import net.kyori.adventure.text.Component
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.EventPriority
+import org.bukkit.event.Listener
+import org.bukkit.event.block.BlockPlaceEvent
+import org.bukkit.event.entity.PlayerDeathEvent
+import org.bukkit.event.inventory.InventoryClickEvent
+import org.bukkit.event.inventory.InventoryCloseEvent
+import org.bukkit.event.inventory.InventoryType
+import org.bukkit.event.player.PlayerDropItemEvent
+import org.bukkit.event.player.PlayerInteractEvent
+import org.bukkit.event.player.PlayerSwapHandItemsEvent
+import org.bukkit.inventory.EquipmentSlot
+import org.bukkit.inventory.ItemStack
+
+class BackpackListener : Listener {
+
+ private fun isBackpack(player: Player, slot: EquipmentSlot) = getBackpack(player, slot)?.has() == true
+ private fun getBackpack(player: Player, slot: EquipmentSlot) = player.inventory.toGeary()?.get(slot)
+ private fun getBackpack(player: Player, slot: Int) = player.inventory.toGeary()?.get(slot)
+ private fun BackpackContents.openBackpack(player: Player, title: Component) {
+ val inventory = Bukkit.createInventory(player, InventoryType.CHEST, title)
+ inventory.contents = this.contents.toTypedArray()
+ player.openInventory(inventory)
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
+ fun PlayerInteractEvent.onBackpackOpen() {
+ if (!rightClicked || player.isSneaking) return
+ val (item, hand) = (item ?: return) to (hand ?: return)
+ val backpack = getBackpack(player, hand) ?: return
+ if (!backpack.has()) return
+ val title = if (item.itemMeta.hasDisplayName()) item.itemMeta.displayName()!! else item.displayName()
+ backpack.getOrSetPersisting { BackpackContents() }.openBackpack(player, title)
+ isCancelled = true
+ }
+
+ @EventHandler
+ fun InventoryClickEvent.onOpenInInventory() {
+ if (!click.isRightClick || slotType == InventoryType.SlotType.OUTSIDE) return
+ val player = whoClicked as? Player ?: return
+ val gearyEntity = getBackpack(player, slot) ?: return
+ val backpack = gearyEntity.get() ?: return
+
+ val contents = gearyEntity.getOrSetPersisting { BackpackContents() }
+ val title =
+ if (currentItem?.itemMeta?.hasDisplayName() == true) currentItem?.itemMeta?.displayName()!! else currentItem?.displayName()
+ ?: Component.text("Backpack")
+
+ when (clickedInventory?.type ?: return) {
+ InventoryType.PLAYER -> {
+ if (backpack.canOpenInInventory) {
+ isCancelled = true
+ player.updateInventory()
+ contents.openBackpack(player, title)
+ }
+ }
+
+ else -> return
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ fun InventoryCloseEvent.onCloseBackpack() {
+ val player = player as Player
+ val backpack = getBackpack(player, EquipmentSlot.HAND) ?: getBackpack(player, EquipmentSlot.OFF_HAND) ?: return
+ backpack.setPersisting(BackpackContents(inventory.contents.toList().map { it ?: ItemStack(Material.AIR) }))
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ fun PlayerDropItemEvent.onDropBackpack() {
+ if (itemDrop.toGearyOrNull()?.has() == true) player.closeInventory()
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ fun PlayerSwapHandItemsEvent.onSwapBackpack() {
+ if (isBackpack(player, EquipmentSlot.HAND) || isBackpack(
+ player,
+ EquipmentSlot.OFF_HAND
+ )
+ ) player.closeInventory()
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ fun PlayerDeathEvent.onDeath() = player.closeInventory()
+
+ @EventHandler(ignoreCancelled = true)
+ fun BlockPlaceEvent.onPlaceBackpack() {
+ if (isBackpack(player, EquipmentSlot.HAND) || isBackpack(player, EquipmentSlot.OFF_HAND)) isCancelled = true
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/food/Food.kt b/src/main/kotlin/com/mineinabyss/looty/features/food/Food.kt
new file mode 100644
index 0000000..5a9ca4c
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/food/Food.kt
@@ -0,0 +1,26 @@
+package com.mineinabyss.looty.features.food
+
+import com.mineinabyss.idofront.serialization.PotionEffectSerializer
+import com.mineinabyss.idofront.serialization.SerializableItemStack
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.bukkit.potion.PotionEffect
+
+/**
+ * Lets an item have custom food properties.
+ *
+ * @param hunger The amount of hunger this item restores.
+ * @param saturation The amount of saturation this item gives.
+ * @param replacement The item to replace with after consuming. If null it will subtract one from the stack.
+ * @param effectChance The chance of effects being applied.
+ * @param effectList The effects this item can give.
+ */
+@Serializable
+@SerialName("looty:food")
+class Food(
+ val hunger: Int,
+ val saturation: Double,
+ val replacement: SerializableItemStack? = null,
+ val effectChance: Double = 1.0,
+ val effectList: List<@Serializable(with = PotionEffectSerializer::class) PotionEffect> = emptyList(),
+)
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/food/FoodConsumptionListener.kt b/src/main/kotlin/com/mineinabyss/looty/features/food/FoodConsumptionListener.kt
new file mode 100644
index 0000000..db888a3
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/food/FoodConsumptionListener.kt
@@ -0,0 +1,39 @@
+package com.mineinabyss.looty.features.food
+
+import com.mineinabyss.geary.papermc.tracking.items.inventory.toGeary
+import org.bukkit.GameMode
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerItemConsumeEvent
+import org.bukkit.inventory.EquipmentSlot
+import kotlin.random.Random
+
+class FoodConsumptionListener : Listener {
+ @EventHandler
+ fun PlayerItemConsumeEvent.onConsumeFood() {
+ val gearyInventory = player.inventory.toGeary() ?: return
+
+ val entity = if (hand == EquipmentSlot.HAND)
+ gearyInventory.itemInMainHand
+ else gearyInventory.itemInOffhand ?: return
+
+ val gearyFood = entity?.get() ?: return
+
+ val replacement = gearyFood.replacement?.toItemStack()
+ isCancelled = true // Cancel vanilla behaviour
+
+ if (player.gameMode != GameMode.CREATIVE) {
+ if (replacement != null) {
+ if (player.inventory.firstEmpty() != -1) player.inventory.addItem(replacement)
+ else player.world.dropItemNaturally(player.location, replacement)
+ }
+ item.subtract()
+
+ if (gearyFood.effectList.isNotEmpty() && Random.nextDouble(0.0, 1.0) <= gearyFood.effectChance)
+ player.addPotionEffects(gearyFood.effectList)
+ }
+
+ player.foodLevel += minOf(gearyFood.hunger, 20)
+ player.saturation += minOf(gearyFood.saturation, 20.0).toFloat()
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/HoldsEntity.kt b/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/HoldsEntity.kt
new file mode 100644
index 0000000..6fd9d7b
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/HoldsEntity.kt
@@ -0,0 +1,13 @@
+package com.mineinabyss.looty.features.holdsentity
+
+import com.mineinabyss.geary.prefabs.PrefabKey
+import com.mineinabyss.idofront.serialization.SerializableItemStack
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+@SerialName("looty:holds_prefab")
+class HoldsEntity(
+ val prefabKey: PrefabKey,
+ val emptiedItem: SerializableItemStack? = null
+)
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/SpawnHeldPrefabSystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/SpawnHeldPrefabSystem.kt
new file mode 100644
index 0000000..10d4ebd
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/holdsentity/SpawnHeldPrefabSystem.kt
@@ -0,0 +1,22 @@
+package com.mineinabyss.looty.features.holdsentity
+
+import com.mineinabyss.geary.papermc.tracking.entities.helpers.spawnFromPrefab
+import com.mineinabyss.geary.papermc.tracking.items.inventory.toGeary
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.Action
+import org.bukkit.event.player.PlayerInteractEvent
+import org.bukkit.inventory.EquipmentSlot
+
+class SpawnHeldPrefabSystem : Listener {
+ @EventHandler(ignoreCancelled = true) // Fires after the onPickupMob thus it places it aswell
+ fun PlayerInteractEvent.onEmptyMobzyBucket() {
+ if (action != Action.RIGHT_CLICK_BLOCK || hand != EquipmentSlot.HAND) return
+ val heldEntity = player.inventory.toGeary()?.itemInMainHand?.get() ?: return
+ val block = clickedBlock?.getRelative(blockFace) ?: return
+
+ block.location.toCenterLocation().spawnFromPrefab(heldEntity.prefabKey)
+ player.inventory.setItemInMainHand(heldEntity.emptiedItem?.toItemStack())
+ isCancelled = true // Cancel vanilla behaviour
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractions.kt b/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractions.kt
new file mode 100644
index 0000000..d64861e
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractions.kt
@@ -0,0 +1,8 @@
+package com.mineinabyss.looty.features.nointeraction
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+@SerialName("looty:disable_item_interactions")
+class DisableItemInteractions
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractionsSystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractionsSystem.kt
new file mode 100644
index 0000000..9e24dcc
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/nointeraction/DisableItemInteractionsSystem.kt
@@ -0,0 +1,25 @@
+package com.mineinabyss.looty.features.nointeraction
+
+import com.mineinabyss.geary.annotations.Handler
+import com.mineinabyss.geary.autoscan.AutoScan
+import com.mineinabyss.geary.datatypes.family.family
+import com.mineinabyss.geary.papermc.bridge.components.Interacted
+import com.mineinabyss.geary.systems.GearyListener
+import com.mineinabyss.geary.systems.accessors.EventScope
+import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.building.map
+import org.bukkit.event.Cancellable
+import org.bukkit.event.Event
+
+@AutoScan
+class DisableItemInteractionsSystem : GearyListener() {
+ private val EventScope.bukkit by get().map { it as? Cancellable }
+ private val EventScope.interacted by family { has() }
+ private val TargetScope.noVanilla by family { has() }
+
+ @Handler
+ fun onPlace(event: EventScope) {
+ event.bukkit?.isCancelled = true
+ }
+}
+
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/DenyInVanillaRecipe.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/DenyInVanillaRecipe.kt
new file mode 100644
index 0000000..d34c5ee
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/DenyInVanillaRecipe.kt
@@ -0,0 +1,12 @@
+package com.mineinabyss.looty.features.recipes
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+/**
+ * A component to indicate that an ItemStack should not be allowed in vanilla crafting recipes.
+ * Meaning if a GearyItem has a base-material of PAPER and this component, it cannot be used to craft books.
+ */
+@Serializable
+@SerialName("looty:deny_in_vanilla_recipes")
+class DenyInVanillaRecipes
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipeQuery.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipeQuery.kt
new file mode 100644
index 0000000..8a38230
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipeQuery.kt
@@ -0,0 +1,44 @@
+package com.mineinabyss.looty.features.recipes
+
+import com.mineinabyss.geary.papermc.tracking.items.gearyItems
+import com.mineinabyss.geary.prefabs.PrefabKey
+import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.query.Query
+import com.mineinabyss.idofront.recipes.register
+import com.mineinabyss.looty.config.looty
+import org.bukkit.Bukkit
+import org.bukkit.NamespacedKey
+import org.bukkit.inventory.ItemStack
+
+class ItemRecipeQuery : Query() {
+ private val TargetScope.recipes by get()
+ private val TargetScope.prefabKey by get()
+
+ fun TargetScope.registerRecipes(): Set {
+ val discoveredRecipes = mutableSetOf()
+ val result: ItemStack? = recipes.result?.toItemStackOrNull() ?: gearyItems.createItem(prefabKey)
+
+ if (result == null) {
+ looty.plugin.logger.warning("Recipe ${prefabKey.key} is missing result item")
+ return emptySet()
+ }
+
+ recipes.removeRecipes.forEach {
+ runCatching {
+ Bukkit.removeRecipe(NamespacedKey.fromString(it)!!)
+ }.onFailure { it.printStackTrace() }
+ }
+
+ recipes.recipes.forEachIndexed { i, recipe ->
+ runCatching {
+ val key = NamespacedKey(prefabKey.namespace, "${prefabKey.key}$i")
+ // Register recipe only if not present
+ Bukkit.getRecipe(key) ?: recipe.toRecipe(key, result, recipes.group).register()
+ if (recipes.discoverRecipes) discoveredRecipes += key
+ }.onFailure {
+ looty.plugin.logger.warning("Failed to register recipe ${prefabKey.key} #$i, ${it.message}")
+ }
+ }
+ return discoveredRecipes
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipes.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipes.kt
new file mode 100644
index 0000000..ddfa47d
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/ItemRecipes.kt
@@ -0,0 +1,30 @@
+package com.mineinabyss.looty.features.recipes
+
+import com.mineinabyss.geary.addons.GearyPhase
+import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault
+import com.mineinabyss.geary.modules.geary
+import com.mineinabyss.geary.papermc.gearyPaper
+import com.mineinabyss.idofront.plugin.listeners
+
+interface ItemRecipes {
+ val query: ItemRecipeQuery
+
+ companion object : GearyAddonWithDefault {
+ override fun default() = object : ItemRecipes {
+ override val query = ItemRecipeQuery()
+ }
+
+ override fun ItemRecipes.install() {
+ geary.pipeline.intercept(GearyPhase.ENABLE) {
+ val autoDiscoveredRecipes = query.run {
+ flatMap { it.registerRecipes() }
+ }
+
+ gearyPaper.plugin.listeners(
+ RecipeDiscoverySystem(autoDiscoveredRecipes),
+ RecipeCraftingSystem(),
+ )
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/PotionMixRecipeSystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/PotionMixRecipeSystem.kt
new file mode 100644
index 0000000..e76b33d
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/PotionMixRecipeSystem.kt
@@ -0,0 +1,31 @@
+package com.mineinabyss.looty.features.recipes
+
+import com.mineinabyss.geary.annotations.Handler
+import com.mineinabyss.geary.papermc.tracking.items.gearyItems
+import com.mineinabyss.geary.prefabs.PrefabKey
+import com.mineinabyss.geary.systems.GearyListener
+import com.mineinabyss.geary.systems.RepeatingSystem
+import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.looty.config.looty
+import org.bukkit.NamespacedKey
+
+/**
+ * This system is implemented separate from idofront recipes since they are handled differently by Minecraft.
+ */
+class PotionMixRecipeSystem : GearyListener() {
+ private val TargetScope.prefabKey by onSet()
+ private val TargetScope.potionmixes by onSet()
+
+ @Handler
+ fun TargetScope.registerPotionMix() {
+ val result = potionmixes.result?.toItemStackOrNull() ?: gearyItems.createItem(prefabKey)
+
+ if (result != null) {
+ potionmixes.potionmixes.forEachIndexed { i, potionmix ->
+ val key = NamespacedKey(prefabKey.namespace, "${prefabKey.key}$i")
+ looty.plugin.server.potionBrewer.removePotionMix(key)
+ looty.plugin.server.potionBrewer.addPotionMix(potionmix.toPotionMix(key, result))
+ }
+ } else looty.plugin.logger.warning("PotionMix $prefabKey is missing result item")
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeCraftingSystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeCraftingSystem.kt
new file mode 100644
index 0000000..0c69200
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeCraftingSystem.kt
@@ -0,0 +1,30 @@
+package com.mineinabyss.looty.features.recipes
+
+import com.mineinabyss.geary.papermc.datastore.decodePrefabs
+import org.bukkit.Keyed
+import org.bukkit.event.EventHandler
+import org.bukkit.event.EventPriority
+import org.bukkit.event.Listener
+import org.bukkit.event.inventory.PrepareItemCraftEvent
+
+class RecipeCraftingSystem : Listener {
+ /**
+ * Prevents custom items being usable in vanilla recipes based on their material,
+ * when they have a [DenyInVanillaRecipes] component, by setting result to null.
+ */
+ @EventHandler(priority = EventPriority.HIGHEST)
+ fun PrepareItemCraftEvent.onCraftWithCustomItem() {
+ // Ensure this only cancels vanilla recipes
+ if (recipe == null || (recipe as? Keyed)?.key()?.namespace() != "minecraft") return
+
+ if (inventory.matrix.any {
+ it?.itemMeta?.persistentDataContainer
+ ?.decodePrefabs()
+ ?.firstOrNull()
+ ?.toEntityOrNull()
+ ?.has() == true
+ }) {
+ inventory.result = null
+ }
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeDiscoverySystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeDiscoverySystem.kt
new file mode 100644
index 0000000..12bc1d0
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/RecipeDiscoverySystem.kt
@@ -0,0 +1,15 @@
+package com.mineinabyss.looty.features.recipes
+
+import org.bukkit.NamespacedKey
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerJoinEvent
+
+class RecipeDiscoverySystem(
+ val discoveredRecipes: List
+) : Listener {
+ @EventHandler
+ fun PlayerJoinEvent.showRecipesOnJoin() {
+ player.discoverRecipes(discoveredRecipes)
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterPotionMixComponent.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/SetPotionMixes.kt
similarity index 76%
rename from src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterPotionMixComponent.kt
rename to src/main/kotlin/com/mineinabyss/looty/features/recipes/SetPotionMixes.kt
index c8f0b20..a26b938 100644
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterPotionMixComponent.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/SetPotionMixes.kt
@@ -1,4 +1,4 @@
-package com.mineinabyss.looty.ecs.components
+package com.mineinabyss.looty.features.recipes
import com.mineinabyss.idofront.serialization.SerializableItemStack
import com.mineinabyss.idofront.serialization.recipes.PotionMixRecipeIngredients
@@ -6,8 +6,8 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
-@SerialName("looty:potion_mixes")
-class RegisterPotionMixComponent(
+@SerialName("looty:set.potion_mixes")
+class SetPotionMixes(
val result: SerializableItemStack? = null,
val potionmixes: List = emptyList(),
)
diff --git a/src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterRecipeComponent.kt b/src/main/kotlin/com/mineinabyss/looty/features/recipes/SetRecipes.kt
similarity index 81%
rename from src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterRecipeComponent.kt
rename to src/main/kotlin/com/mineinabyss/looty/features/recipes/SetRecipes.kt
index 5b2e2de..58aabb0 100644
--- a/src/main/kotlin/com/mineinabyss/looty/ecs/components/RegisterRecipeComponent.kt
+++ b/src/main/kotlin/com/mineinabyss/looty/features/recipes/SetRecipes.kt
@@ -1,4 +1,4 @@
-package com.mineinabyss.looty.ecs.components
+package com.mineinabyss.looty.features.recipes
import com.mineinabyss.idofront.serialization.SerializableItemStack
import com.mineinabyss.idofront.serialization.recipes.SerializableRecipeIngredients
@@ -6,8 +6,8 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
-@SerialName("looty:recipes")
-class RegisterRecipeComponent(
+@SerialName("looty:set.recipes")
+class SetRecipes(
val recipes: List,
val discoverRecipes: Boolean = false,
val group: String = "",
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/wearables/Hat.kt b/src/main/kotlin/com/mineinabyss/looty/features/wearables/Hat.kt
new file mode 100644
index 0000000..2f89f57
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/wearables/Hat.kt
@@ -0,0 +1,16 @@
+package com.mineinabyss.looty.features.wearables
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.bukkit.Sound
+
+/**
+ * Lets an item be worn as a hat.
+ *
+ * @param sound The sound to play when equipped.
+ */
+@Serializable
+@SerialName("looty:hat")
+class Hat(
+ val sound: Sound = Sound.ITEM_ARMOR_EQUIP_NETHERITE
+)
diff --git a/src/main/kotlin/com/mineinabyss/looty/features/wearables/WearableItemSystem.kt b/src/main/kotlin/com/mineinabyss/looty/features/wearables/WearableItemSystem.kt
new file mode 100644
index 0000000..04adc11
--- /dev/null
+++ b/src/main/kotlin/com/mineinabyss/looty/features/wearables/WearableItemSystem.kt
@@ -0,0 +1,81 @@
+package com.mineinabyss.looty.features.wearables
+
+import com.mineinabyss.geary.helpers.with
+import com.mineinabyss.geary.papermc.tracking.items.inventory.toGeary
+import com.mineinabyss.idofront.entities.rightClicked
+import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryCrafting
+import org.bukkit.entity.ArmorStand
+import org.bukkit.entity.EntityType
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.inventory.InventoryClickEvent
+import org.bukkit.event.inventory.InventoryType
+import org.bukkit.event.player.PlayerInteractAtEntityEvent
+import org.bukkit.event.player.PlayerInteractEvent
+import org.bukkit.inventory.EquipmentSlot
+
+class WearableItemSystem : Listener {
+ @EventHandler
+ fun InventoryClickEvent.shiftClickToWear() {
+ if (!click.isShiftClick) return
+ if (inventory !is CraftInventoryCrafting) return // Only support shift clicking when only player inventory is open
+ val player = inventory.holder as? Player ?: return
+ player.inventory.toGeary()?.get(slot)?.with { _: Hat ->
+ if (player.inventory.helmet == null) {
+ player.inventory.helmet = currentItem
+ currentItem = null
+ isCancelled = true
+ }
+ }
+ }
+
+ @EventHandler
+ fun InventoryClickEvent.clickToWear() {
+ // Check that helmet slot was clicked
+ if (slotType !== InventoryType.SlotType.ARMOR) return
+ if (rawSlot != 5) return
+
+ val player = inventory.holder as? Player ?: return
+ val entity = player.inventory.toGeary()?.itemOnCursor ?: return
+ val hat = entity.get() ?: return
+
+
+ // swap the items from cursor to helmet slot
+ val currItem = currentItem
+ currentItem = cursor
+ view.cursor = currItem
+ isCancelled = true
+ player.playSound(player.location, hat.sound, 1f, 1f)
+ }
+
+ @EventHandler
+ fun PlayerInteractEvent.rightClickToWear() {
+ if (hand == EquipmentSlot.OFF_HAND) return //the event is called twice, on for each hand. We want to ignore the offhand call
+ if (!rightClicked) return //only do stuff when player rightclicks
+ if (player.inventory.helmet !== null) return // don't equip if we are wearing a helmet
+
+ val entityInMainHand = player.inventory.toGeary()?.itemInMainHand ?: return
+ val hat = entityInMainHand.get() ?: return
+
+ player.inventory.helmet = player.inventory.itemInMainHand
+ player.inventory.setItemInMainHand(null)
+ player.playSound(player.location, hat.sound, 1f, 1f)
+ }
+
+ @EventHandler
+ fun PlayerInteractAtEntityEvent.rightClickArmorStand() {
+ val item = player.inventory.itemInMainHand
+ val armorstand = rightClicked as? ArmorStand ?: return
+
+ if (hand != EquipmentSlot.HAND) return
+ if (rightClicked.type != EntityType.ARMOR_STAND) return
+ val gearyItem = player.inventory.toGeary()?.itemInMainHand ?: return
+ val hat = gearyItem.get() ?: return
+
+ armorstand.equipment.helmet = item
+ item.subtract(1)
+ player.playSound(rightClicked.location, hat.sound, 1f, 1f)
+
+ }
+}
diff --git a/src/main/kotlin/com/mineinabyss/looty/migration/custommodeldata/CustomModelDataToPrefabMap.kt b/src/main/kotlin/com/mineinabyss/looty/migration/custommodeldata/CustomModelDataToPrefabMap.kt
deleted file mode 100644
index d1cf0c4..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/migration/custommodeldata/CustomModelDataToPrefabMap.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.mineinabyss.looty.migration.custommodeldata
-
-import com.mineinabyss.geary.prefabs.PrefabKey
-import com.mineinabyss.looty.ecs.queries.LootyTypeQuery
-import org.bukkit.Material
-
-data class CustomItem(
- val material: Material,
- val customModelData: Int
-)
-
-/**
- * Assists in migrating old items which only have custom model data to define their item type.
- */
-object CustomModelDataToPrefabMap {
- private val map = mutableMapOf()
-
- init {
- LootyTypeQuery.run {
- forEach {
- val item = it.type.item
- map[CustomItem(item.type ?: return@forEach, item.customModelData ?: return@forEach)] = it.key
- }
- }
- }
-
- operator fun get(item: CustomItem) = map[item]
-}
diff --git a/src/main/kotlin/com/mineinabyss/looty/tracking/BukkitItemAssociations.kt b/src/main/kotlin/com/mineinabyss/looty/tracking/BukkitItemAssociations.kt
deleted file mode 100644
index 55e02a3..0000000
--- a/src/main/kotlin/com/mineinabyss/looty/tracking/BukkitItemAssociations.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.mineinabyss.looty.tracking
-
-import com.mineinabyss.geary.datatypes.GearyEntity
-import com.mineinabyss.geary.papermc.access.toGeary
-import com.mineinabyss.geary.papermc.globalContextMC
-import com.mineinabyss.geary.papermc.store.decode
-import com.mineinabyss.geary.papermc.store.decodePrefabs
-import com.mineinabyss.idofront.nms.nbt.fastPDC
-import com.mineinabyss.looty.ecs.components.PlayerItemCache
-import org.bukkit.entity.Player
-import org.bukkit.inventory.ItemStack
-import org.bukkit.inventory.meta.ItemMeta
-import org.bukkit.persistence.PersistentDataContainer
-import java.util.*
-
-/**
- * Returns a Geary entity if this item has a UUID encoded.
- *
- * Use [toGearyOrNull] to support player-instanced items.
- * Otherwise, this function specifically ignores them.
- */
-fun ItemStack.toGearyFromUUIDOrNull(): GearyEntity? {
- if (!hasItemMeta()) return null
- return itemMeta.toGearyFromUUIDOrNull()
-}
-
-fun ItemMeta.toGearyFromUUIDOrNull(): GearyEntity? {
- return persistentDataContainer.toGearyFromUUIDOrNull()
-}
-
-fun PersistentDataContainer.toGearyFromUUIDOrNull(): GearyEntity? {
- val uuid = decode() ?: return null
- return globalContextMC.uuid2entity[uuid]
-}
-
-/**
- * Returns a Geary entity if this item has a UUID encoded.
- *
- * Otherwise, returns the [player]-instanced entity based on the
- * first prefab encoded on this item.
- *
- * Use [toGearyFromUUIDOrNull] if you wish to ignore player-instanced items.
- */
-fun ItemStack.toGearyOrNull(player: Player): GearyEntity? {
- val pdc = fastPDC ?: return null
-
- // If a UUID is encoded, just return the item
- pdc.decode()?.let { return globalContextMC.uuid2entity[it] }
-
- // If no UUID, try to read as a player-instanced item
- val prefab = pdc.decodePrefabs().firstOrNull()?.toEntityOrNull() ?: return null
- return player.toGeary().get()?.getInstance(prefab)
-}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 9656b09..44b5a3e 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -2,7 +2,7 @@ name: Looty
version: ${plugin_version}
author: Offz
main: com.mineinabyss.looty.LootyPlugin
-api-version: 1.16
+api-version: "1.20"
depend: [ Geary ]
prefix: Looty