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