├── src └── main │ ├── resources │ ├── assets │ │ └── yigd │ │ │ ├── models │ │ │ ├── item │ │ │ │ ├── grave.json │ │ │ │ ├── grave_key.json │ │ │ │ └── death_scroll.json │ │ │ └── block │ │ │ │ └── grave.json │ │ │ ├── icon.png │ │ │ ├── textures │ │ │ ├── block │ │ │ │ └── grave.png │ │ │ ├── item │ │ │ │ ├── grave_key.png │ │ │ │ └── death_scroll.png │ │ │ └── gui │ │ │ │ ├── grave_overview.png │ │ │ │ └── sprites │ │ │ │ ├── slot.png │ │ │ │ ├── exp_orb.png │ │ │ │ ├── rob_btn.png │ │ │ │ ├── drop_icon.png │ │ │ │ ├── hide_status.png │ │ │ │ ├── locked_btn.png │ │ │ │ ├── restore_btn.png │ │ │ │ ├── scroll_bar.png │ │ │ │ ├── show_status.png │ │ │ │ ├── window_bg.png │ │ │ │ ├── claimed_grave.png │ │ │ │ ├── trashcan_icon.png │ │ │ │ ├── unlocked_btn.png │ │ │ │ ├── destroyed_grave.png │ │ │ │ ├── drop_icon_cross.png │ │ │ │ ├── unclaimed_grave.png │ │ │ │ ├── scroll_bar_pressed.png │ │ │ │ ├── claimed_grave_cross.png │ │ │ │ ├── destroyed_grave_cross.png │ │ │ │ ├── enchanted_book_cross.png │ │ │ │ ├── trashcan_icon_cross.png │ │ │ │ ├── unclaimed_grave_cross.png │ │ │ │ ├── slot.png.mcmeta │ │ │ │ ├── window_bg.png.mcmeta │ │ │ │ ├── scroll_bar.png.mcmeta │ │ │ │ └── scroll_bar_pressed.png.mcmeta │ │ │ └── blockstates │ │ │ └── grave.json │ ├── data │ │ ├── yigd │ │ │ ├── tags │ │ │ │ ├── item │ │ │ │ │ ├── loss_immune.json │ │ │ │ │ ├── natural_vanishing.json │ │ │ │ │ ├── grave_incompatible.json │ │ │ │ │ ├── natural_soulbound.json │ │ │ │ │ └── soulbindable.json │ │ │ │ ├── block │ │ │ │ │ ├── replace_soft_whitelist.json │ │ │ │ │ ├── replace_grave_blacklist.json │ │ │ │ │ └── keep_strict_blacklist.json │ │ │ │ └── enchantment │ │ │ │ │ ├── vanishing.json │ │ │ │ │ ├── death_sight.json │ │ │ │ │ └── soulbound.json │ │ │ ├── custom │ │ │ │ ├── grave_areas.json │ │ │ │ ├── graveyard.json │ │ │ │ └── grave_shape.json │ │ │ ├── recipes │ │ │ │ └── grave.json │ │ │ ├── enchantment │ │ │ │ ├── soulbound.json │ │ │ │ └── death_sight.json │ │ │ └── loot_table │ │ │ │ └── blocks │ │ │ │ └── grave.json │ │ ├── c │ │ │ └── tags │ │ │ │ └── block │ │ │ │ └── unbreakable.json │ │ ├── ftbchunks │ │ │ └── tags │ │ │ │ └── block │ │ │ │ ├── edit_whitelist.json │ │ │ │ └── interact_whitelist.json │ │ ├── minecraft │ │ │ └── tags │ │ │ │ ├── block │ │ │ │ ├── dragon_immune.json │ │ │ │ ├── wither_immune.json │ │ │ │ └── mineable │ │ │ │ │ └── pickaxe.json │ │ │ │ └── enchantment │ │ │ │ ├── non_treasure.json │ │ │ │ └── tradeable.json │ │ ├── botania │ │ │ └── tags │ │ │ │ └── block │ │ │ │ └── gaia_break_blacklist.json │ │ ├── inventorytabs │ │ │ └── tags │ │ │ │ └── block │ │ │ │ └── mod_compat_blacklist.json │ │ └── twilightforest │ │ │ └── tags │ │ │ └── block │ │ │ └── common_protections.json │ ├── META-INF │ │ └── accesstransformer.cfg │ └── yigd.mixins.json │ ├── java │ └── com │ │ └── b1n_ry │ │ └── yigd │ │ ├── data │ │ ├── ListMode.java │ │ ├── GraveStatus.java │ │ ├── DeathContext.java │ │ ├── DirectionalPos.java │ │ ├── GraveItem.java │ │ ├── GraveyardData.java │ │ └── TimePoint.java │ │ ├── config │ │ ├── ClaimPriority.java │ │ ├── DropType.java │ │ ├── ExpDropBehaviour.java │ │ ├── GraveRenderingConfig.java │ │ ├── CommandConfig.java │ │ ├── ExpConfig.java │ │ ├── MapEntryConfig.java │ │ ├── InventoryConfig.java │ │ ├── YigdConfig.java │ │ ├── RespawnConfig.java │ │ ├── CompatConfig.java │ │ ├── ExtraFeaturesConfig.java │ │ └── GraveConfig.java │ │ ├── util │ │ ├── GraveItemModificationConsumer.java │ │ ├── DropRule.java │ │ ├── YigdTags.java │ │ ├── GraveOverrideAreas.java │ │ ├── YigdResourceHandler.java │ │ └── GraveCompassHelper.java │ │ ├── mixin │ │ ├── EndPlatformFeatureMixin.java │ │ ├── LevelChunkMixin.java │ │ ├── CompassModelMixin.java │ │ ├── DedicatedServerMixin.java │ │ ├── AbstractConfigScreenMixin.java │ │ ├── CompassItemMixin.java │ │ └── RegistryDataLoaderMixin.java │ │ ├── networking │ │ ├── packets │ │ │ ├── RequestKeyC2SPacket.java │ │ │ ├── DeleteGraveC2SPacket.java │ │ │ ├── RequestCompassC2SPacket.java │ │ │ ├── GraveOverviewRequestC2SPacket.java │ │ │ ├── GraveSelectionRequestC2SPacket.java │ │ │ ├── LockGraveC2SPacket.java │ │ │ ├── UpdateConfigC2SPacket.java │ │ │ ├── SyncConfigS2CPacket.java │ │ │ ├── PlayerSelectionS2CPacket.java │ │ │ ├── RobGraveC2SPacket.java │ │ │ ├── RestoreGraveC2SPacket.java │ │ │ ├── GraveSelectionS2CPacket.java │ │ │ └── GraveOverviewS2CPacket.java │ │ ├── LightPlayerData.java │ │ ├── ClientPacketHandler.java │ │ ├── PacketInitializer.java │ │ └── LightGraveData.java │ │ ├── client │ │ └── YigdClient.java │ │ ├── compat │ │ ├── InvModCompat.java │ │ ├── misc_compat_mods │ │ │ └── TwilightCompat.java │ │ ├── CompatComponent.java │ │ ├── TravelersBackpackCompat.java │ │ └── CosmeticArmorCompat.java │ │ ├── events │ │ ├── YigdClientEventHandler.java │ │ └── ServerEventHandler.java │ │ ├── item │ │ └── GraveKeyItem.java │ │ ├── components │ │ ├── EffectComponent.java │ │ ├── ExpComponent.java │ │ └── RespawnComponent.java │ │ ├── DeathHandler.java │ │ └── Yigd.java │ └── templates │ └── META-INF │ └── neoforge.mods.toml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitattributes ├── .gitignore ├── settings.gradle ├── .github └── workflows │ └── build.yml ├── LICENSE ├── gradle.properties ├── README.md ├── gradlew.bat ├── CHANGELOG.md └── gradlew /src/main/resources/assets/yigd/models/item/grave.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "yigd:block/grave" 3 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/item/loss_immune.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | 4 | ] 5 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/item/natural_vanishing.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | 4 | ] 5 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.client.renderer.LevelRenderer renderBuffers -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/item/grave_incompatible.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | 5 | ] 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/data/yigd/custom/grave_areas.json: -------------------------------------------------------------------------------- 1 | { 2 | "default_drop_rule": "PUT_IN_GRAVE", 3 | "values": [ 4 | 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/block/replace_soft_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | "#minecraft:replaceable" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/main/resources/data/c/tags/block/unbreakable.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/icon.png -------------------------------------------------------------------------------- /src/main/resources/data/ftbchunks/tags/block/edit_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/block/dragon_immune.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/block/wither_immune.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/botania/tags/block/gaia_break_blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/block/mineable/pickaxe.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/ftbchunks/tags/block/interact_whitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/data/inventorytabs/tags/block/mod_compat_blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/enchantment/vanishing.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "minecraft:vanishing_curse" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/ListMode.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | public enum ListMode { 4 | WHITELIST, 5 | BLACKLIST 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/data/twilightforest/tags/block/common_protections.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "yigd:grave" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/ClaimPriority.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | public enum ClaimPriority { 4 | INVENTORY, 5 | GRAVE 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/DropType.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | public enum DropType { 4 | ON_GROUND, 5 | IN_INVENTORY 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/models/item/grave_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "minecraft:item/generated", 3 | "textures": { 4 | "layer0": "yigd:item/grave_key" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/block/grave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/block/grave.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/models/item/death_scroll.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "minecraft:item/generated", 3 | "textures": { 4 | "layer0": "yigd:item/death_scroll" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/item/grave_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/item/grave_key.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/grave_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/grave_overview.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/slot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/slot.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/item/death_scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/item/death_scroll.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/exp_orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/exp_orb.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/rob_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/rob_btn.png -------------------------------------------------------------------------------- /src/main/resources/data/yigd/custom/graveyard.json: -------------------------------------------------------------------------------- 1 | { 2 | "point2point": false, 3 | "dimension": "minecraft:overworld", 4 | "use_closest": false, 5 | "coordinates": [ 6 | 7 | ] 8 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/drop_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/drop_icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/hide_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/hide_status.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/locked_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/locked_btn.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/restore_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/restore_btn.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/show_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/show_status.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/window_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/window_bg.png -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/item/natural_soulbound.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | { 4 | "id": "#twilightforest:kept_on_death", 5 | "required": false 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/claimed_grave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/claimed_grave.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/trashcan_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/trashcan_icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/unlocked_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/unlocked_btn.png -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/enchantment/death_sight.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "yigd:death_sight", 6 | "required": false 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/destroyed_grave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/destroyed_grave.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/drop_icon_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/drop_icon_cross.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/unclaimed_grave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/unclaimed_grave.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar_pressed.png -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/enchantment/non_treasure.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "yigd:death_sight", 6 | "required": false 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/block/replace_grave_blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "minecraft:lava", 5 | "#minecraft:fire", 6 | "minecraft:cactus" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/claimed_grave_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/claimed_grave_cross.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/destroyed_grave_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/destroyed_grave_cross.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/enchanted_book_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/enchanted_book_cross.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/trashcan_icon_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/trashcan_icon_cross.png -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/unclaimed_grave_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1n-ry/Youre-in-grave-danger/HEAD/src/main/resources/assets/yigd/textures/gui/sprites/unclaimed_grave_cross.png -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/ExpDropBehaviour.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | public enum ExpDropBehaviour { 4 | PERCENTAGE, 5 | VANILLA, 6 | BEST_OF_BOTH, 7 | WORST_OF_BOTH 8 | } 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | runs 24 | run-data 25 | 26 | repo -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/enchantment/tradeable.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "yigd:soulbound", 6 | "required": false 7 | }, 8 | { 9 | "id": "yigd:death_sight", 10 | "required": false 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | maven { url = 'https://maven.neoforged.net/releases' } 6 | } 7 | } 8 | 9 | plugins { 10 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/blockstates/grave.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "facing=north": { "model": "yigd:block/grave", "y": 0 }, 4 | "facing=east": { "model": "yigd:block/grave", "y": 90 }, 5 | "facing=south": { "model": "yigd:block/grave", "y": 180 }, 6 | "facing=west": { "model": "yigd:block/grave", "y": 270 } 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/slot.png.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "gui": { 3 | "scaling": { 4 | "type": "nine_slice", 5 | "width": 20, 6 | "height": 20, 7 | "border": { 8 | "left": 1, 9 | "right": 1, 10 | "top": 1, 11 | "bottom": 1 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/window_bg.png.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "gui": { 3 | "scaling": { 4 | "type": "nine_slice", 5 | "width": 20, 6 | "height": 20, 7 | "border": { 8 | "left": 4, 9 | "right": 4, 10 | "top": 4, 11 | "bottom": 4 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar.png.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "gui": { 3 | "scaling": { 4 | "type": "nine_slice", 5 | "width": 20, 6 | "height": 20, 7 | "border": { 8 | "left": 1, 9 | "right": 1, 10 | "top": 1, 11 | "bottom": 1 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/textures/gui/sprites/scroll_bar_pressed.png.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "gui": { 3 | "scaling": { 4 | "type": "nine_slice", 5 | "width": 20, 6 | "height": 20, 7 | "border": { 8 | "left": 1, 9 | "right": 1, 10 | "top": 1, 11 | "bottom": 1 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/GraveItemModificationConsumer.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | import com.b1n_ry.yigd.data.GraveItem; 4 | import net.minecraft.util.Tuple; 5 | import net.minecraft.world.item.ItemStack; 6 | 7 | @FunctionalInterface 8 | public interface GraveItemModificationConsumer { 9 | void accept(ItemStack stack, int slot, GraveItem graveItem); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/enchantment/soulbound.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "yigd:soulbound", 6 | "required": false 7 | }, 8 | { 9 | "id": "soulbound:soulbound", 10 | "required": false 11 | }, 12 | { 13 | "id": "ars_elemental:soulbound", 14 | "required": false 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/custom/grave_shape.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [ 3 | { 4 | "from": [0, 0, 0], 5 | "to": [16, 1, 16] 6 | }, 7 | { 8 | "from": [2, 1, 10], 9 | "to": [14, 3, 15] 10 | }, 11 | { 12 | "from": [3, 3, 11], 13 | "to": [13, 15, 14] 14 | }, 15 | { 16 | "from": [4, 15, 11], 17 | "to": [12, 16, 14] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/GraveStatus.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | public enum GraveStatus { 4 | UNCLAIMED, 5 | CLAIMED, 6 | DESTROYED; 7 | 8 | public int getTransparentColor() { 9 | return switch (this) { 10 | case CLAIMED -> 0x2600FF00; 11 | case DESTROYED -> 0x26FF0000; 12 | case UNCLAIMED -> 0x26FFFF00; 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/DropRule.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | public enum DropRule { 4 | DROP, // Drop items on ground, even if a grave is generated 5 | KEEP, // Keep items on person even when you die (soulbinding) 6 | DESTROY, // Destroy the items, and they can never be reclaimed ever again 7 | PUT_IN_GRAVE // Try to put items in grave. If graves are not active, or grave failed to generate, items will drop on ground 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/recipes/grave.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shaped", 3 | "pattern": [ 4 | " # ", 5 | "#B#", 6 | "GGG" 7 | ], 8 | "key": { 9 | "#": { 10 | "item": "minecraft:stone" 11 | }, 12 | "B": { 13 | "item": "minecraft:bone" 14 | }, 15 | "G": { 16 | "item": "minecraft:gravel" 17 | } 18 | }, 19 | "result": { 20 | "id": "yigd:grave", 21 | "count": 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/DeathContext.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.world.damagesource.DamageSource; 6 | import net.minecraft.world.phys.Vec3; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public record DeathContext(ServerPlayer player, @NotNull ServerLevel world, Vec3 deathPos, 10 | DamageSource deathSource) { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/enchantment/soulbound.json: -------------------------------------------------------------------------------- 1 | { 2 | "anvil_cost": 6, 3 | "exclusive_set": "minecraft:vanishing_curse", 4 | "supported_items": "#yigd:soulbindable", 5 | "description": { 6 | "translate": "enchantment.yigd.soulbound" 7 | }, 8 | "max_cost": { 9 | "base": 75, 10 | "per_level_above_first": 25 11 | }, 12 | "max_level": 1, 13 | "min_cost": { 14 | "base": 25, 15 | "per_level_above_first": 25 16 | }, 17 | "weight": 5, 18 | "slots": [ 19 | "any" 20 | ] 21 | } -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/block/keep_strict_blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [ 3 | "minecraft:bedrock", 4 | "minecraft:nether_portal", 5 | "minecraft:end_portal", 6 | "minecraft:end_portal_frame", 7 | "minecraft:end_gateway", 8 | "minecraft:dragon_egg", 9 | "minecraft:barrier", 10 | "minecraft:command_block", 11 | "minecraft:chain_command_block", 12 | "minecraft:repeating_command_block", 13 | "minecraft:structure_block", 14 | "minecraft:structure_void" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/DirectionalPos.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.Direction; 6 | import net.minecraft.core.Vec3i; 7 | 8 | public record DirectionalPos(BlockPos pos, Direction dir) { 9 | public DirectionalPos(int x, int y, int z, Direction dir) { 10 | this(new BlockPos(x, y, z), dir); 11 | } 12 | 13 | public double getSquaredDistance(Vec3i pos) { 14 | return this.pos.distSqr(pos); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/GraveRenderingConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 4 | 5 | public class GraveRenderingConfig { 6 | public boolean useCustomFeatureRenderer = true; 7 | public boolean useSkullRenderer = true; 8 | public boolean useTextRenderer = true; 9 | @ConfigEntry.Gui.RequiresRestart 10 | public boolean adaptRenderer = false; 11 | public boolean useGlowingEffect = true; 12 | public int glowingDistance = 15; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/GraveItem.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | import com.b1n_ry.yigd.util.DropRule; 4 | import net.minecraft.world.item.ItemStack; 5 | 6 | public class GraveItem { 7 | public ItemStack stack; 8 | public DropRule dropRule; 9 | 10 | public GraveItem(ItemStack stack, DropRule dropRule) { 11 | this.stack = stack; 12 | this.dropRule = dropRule; 13 | } 14 | 15 | public GraveItem copy() { 16 | return new GraveItem(this.stack.copy(), this.dropRule); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/enchantment/death_sight.json: -------------------------------------------------------------------------------- 1 | { 2 | "anvil_cost": 4, 3 | "description": { 4 | "translate": "enchantment.yigd.death_sight" 5 | }, 6 | "exclusive_set": "#minecraft:exclusive_set/helmet", 7 | "max_cost": { 8 | "base": 25, 9 | "per_level_above_first": 10 10 | }, 11 | "max_level": 1, 12 | "min_cost": { 13 | "base": 10, 14 | "per_level_above_first": 10 15 | }, 16 | "slots": [ 17 | "head" 18 | ], 19 | "supported_items": "#minecraft:enchantable/head_armor", 20 | "weight": 2 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/yigd.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.b1n_ry.yigd.mixin", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": [ 7 | "CompassItemMixin", 8 | "EndPlatformFeatureMixin", 9 | "LevelChunkMixin", 10 | "RegistryDataLoaderMixin" 11 | ], 12 | "client": [ 13 | "AbstractConfigScreenMixin", 14 | "CompassModelMixin" 15 | ], 16 | "server": [ 17 | "DedicatedServerMixin" 18 | ], 19 | "injectors": { 20 | "defaultRequire": 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 0 13 | fetch-tags: true 14 | 15 | - name: Setup JDK 17 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | 21 | - name: Build with Gradle 22 | uses: gradle/actions/setup-gradle@v3 23 | with: 24 | arguments: build 25 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/CommandConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | public class CommandConfig { 4 | public String mainCommand = "yigd"; 5 | public int basePermissionLevel = 0; 6 | public int viewLatestPermissionLevel = 0; 7 | public int viewSelfPermissionLevel = 0; 8 | public int viewUserPermissionLevel = 2; 9 | public int viewAllPermissionLevel = 2; 10 | public int restorePermissionLevel = 2; 11 | public int robPermissionLevel = 2; 12 | public int whitelistPermissionLevel = 3; 13 | public int deletePermissionLevel = 3; 14 | public int unlockPermissionLevel = 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/ExpConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 4 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 5 | 6 | public class ExpConfig { 7 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 8 | public ExpDropBehaviour dropBehaviour = ExpDropBehaviour.BEST_OF_BOTH; 9 | 10 | @Comment("Ignored if dropBehaviour is set to VANILLA") 11 | @ConfigEntry.BoundedDiscrete(max = 100) 12 | public int dropPercentage = 0; 13 | 14 | @ConfigEntry.BoundedDiscrete(max = 100) 15 | public int keepPercentage = 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/EndPlatformFeatureMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.world.level.block.Block; 5 | import net.minecraft.world.level.block.state.BlockState; 6 | import net.minecraft.world.level.levelgen.feature.EndPlatformFeature; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(EndPlatformFeature.class) 12 | public class EndPlatformFeatureMixin { 13 | @Redirect(method = "createEndPlatform", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;is(Lnet/minecraft/world/level/block/Block;)Z")) 14 | private static boolean generateEndPlatform(BlockState instance, Block block) { 15 | return instance.is(Yigd.GRAVE.get()) || instance.is(block); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/LevelChunkMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.block.entity.GraveBlockEntity; 4 | import com.llamalad7.mixinextras.sugar.Local; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.world.level.block.entity.BlockEntity; 7 | import net.minecraft.world.level.chunk.LevelChunk; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(LevelChunk.class) 15 | public class LevelChunkMixin { 16 | @Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/entity/BlockEntity;setRemoved()V")) 17 | private void blockEntityBroken(BlockPos pos, CallbackInfo ci, @Local @Nullable BlockEntity removed) { 18 | if (removed instanceof GraveBlockEntity grave) 19 | grave.onBroken(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/RequestKeyC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.UUID; 12 | 13 | public record RequestKeyC2SPacket(UUID graveId) implements CustomPacketPayload { 14 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "request_grave_key")); 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite(UUIDUtil.STREAM_CODEC, RequestKeyC2SPacket::graveId, RequestKeyC2SPacket::new); 16 | @Override 17 | public @NotNull Type type() { 18 | return TYPE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/DeleteGraveC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.UUID; 12 | 13 | public record DeleteGraveC2SPacket(UUID graveId) implements CustomPacketPayload { 14 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "delete_grave_request")); 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite(UUIDUtil.STREAM_CODEC, DeleteGraveC2SPacket::graveId, DeleteGraveC2SPacket::new); 16 | 17 | @Override 18 | public @NotNull Type type() { 19 | return TYPE; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/CompassModelMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.client.multiplayer.ClientLevel; 5 | import net.minecraft.client.renderer.item.ItemProperties; 6 | import net.minecraft.core.GlobalPos; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.item.ItemStack; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(ItemProperties.class) 15 | public class CompassModelMixin { 16 | @Inject(method = "lambda$static$11", at = @At("HEAD"), cancellable = true) 17 | private static void changeCompassDirection(ClientLevel world, ItemStack stack, Entity entity, CallbackInfoReturnable cir) { 18 | GlobalPos gravePos = stack.get(Yigd.GRAVE_LOCATION); 19 | 20 | if (gravePos != null) { 21 | cir.setReturnValue(gravePos); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/RequestCompassC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.UUID; 12 | 13 | public record RequestCompassC2SPacket(UUID graveId) implements CustomPacketPayload { 14 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "request_grave_compass")); 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite(UUIDUtil.STREAM_CODEC, RequestCompassC2SPacket::graveId, RequestCompassC2SPacket::new); 16 | @Override 17 | public @NotNull Type type() { 18 | return TYPE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 B1n_ry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/GraveOverviewRequestC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.UUID; 12 | 13 | public record GraveOverviewRequestC2SPacket(UUID graveId) implements CustomPacketPayload { 14 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "grave_overview_request")); 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite(UUIDUtil.STREAM_CODEC, GraveOverviewRequestC2SPacket::graveId, GraveOverviewRequestC2SPacket::new); 16 | @Override 17 | public @NotNull Type type() { 18 | return TYPE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/GraveSelectionRequestC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.network.RegistryFriendlyByteBuf; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.item.component.ResolvableProfile; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public record GraveSelectionRequestC2SPacket(ResolvableProfile profile) implements CustomPacketPayload { 12 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "grave_selection_request")); 13 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ResolvableProfile.STREAM_CODEC, GraveSelectionRequestC2SPacket::profile, GraveSelectionRequestC2SPacket::new); 14 | @Override 15 | public @NotNull Type type() { 16 | return TYPE; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/LockGraveC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.ByteBufCodecs; 7 | import net.minecraft.network.codec.StreamCodec; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | import net.minecraft.resources.ResourceLocation; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.UUID; 13 | 14 | public record LockGraveC2SPacket(UUID graveId, boolean locked) implements CustomPacketPayload { 15 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "lock_grave_request")); 16 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 17 | UUIDUtil.STREAM_CODEC, LockGraveC2SPacket::graveId, ByteBufCodecs.BOOL, LockGraveC2SPacket::locked, LockGraveC2SPacket::new); 18 | 19 | @Override 20 | public @NotNull Type type() { 21 | return TYPE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/DedicatedServerMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.block.entity.GraveBlockEntity; 4 | import com.b1n_ry.yigd.config.YigdConfig; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.server.dedicated.DedicatedServer; 7 | import net.minecraft.server.level.ServerLevel; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.level.block.entity.BlockEntity; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | @Mixin(DedicatedServer.class) 16 | public class DedicatedServerMixin { 17 | @Inject(method = "isUnderSpawnProtection", at = @At(value = "RETURN"), cancellable = true) 18 | private void isSpawnProtected(ServerLevel world, BlockPos pos, Player player, CallbackInfoReturnable cir) { 19 | if (!cir.getReturnValue() || !YigdConfig.getConfig().graveConfig.overrideSpawnProtection) return; 20 | 21 | BlockEntity be = world.getBlockEntity(pos); 22 | if (be instanceof GraveBlockEntity) cir.setReturnValue(false); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/UpdateConfigC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.ClaimPriority; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.ByteBufCodecs; 7 | import net.minecraft.network.codec.StreamCodec; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | import net.minecraft.resources.ResourceLocation; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public record UpdateConfigC2SPacket(ClaimPriority claiming, ClaimPriority robbing) implements CustomPacketPayload { 13 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "update_config")); 14 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 15 | ByteBufCodecs.STRING_UTF8, o -> o.claiming.toString(), 16 | ByteBufCodecs.STRING_UTF8, o -> o.robbing.toString(), 17 | (s1, s2) -> new UpdateConfigC2SPacket(ClaimPriority.valueOf(s1), ClaimPriority.valueOf(s2))); 18 | 19 | @Override 20 | public @NotNull Type type() { 21 | return TYPE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/AbstractConfigScreenMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.GraveConfig; 5 | import com.b1n_ry.yigd.config.YigdConfig; 6 | import com.b1n_ry.yigd.networking.packets.UpdateConfigC2SPacket; 7 | import me.shedaniel.clothconfig2.gui.AbstractConfigScreen; 8 | import net.neoforged.neoforge.network.PacketDistributor; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(AbstractConfigScreen.class) 15 | public class AbstractConfigScreenMixin { 16 | @Inject(method = "save", at = @At("RETURN")) 17 | private void updateServerConfigs(CallbackInfo ci) { 18 | GraveConfig config = YigdConfig.getConfig().graveConfig; 19 | try { 20 | PacketDistributor.sendToServer(new UpdateConfigC2SPacket(config.claimPriority, config.graveRobbing.robPriority)); 21 | Yigd.LOGGER.info("Synced client priority configs to server"); 22 | } catch (NullPointerException e) { 23 | Yigd.LOGGER.warn("Tried to sync client config, but didn't find a server to sync to"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/MapEntryConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public abstract class MapEntryConfig { 7 | public String key; 8 | public T value; 9 | 10 | @SuppressWarnings("unused") 11 | public MapEntryConfig() { // Required for cloth config GUI to work 12 | this.key = ""; 13 | } 14 | 15 | public MapEntryConfig(String key) { 16 | this.key = key; 17 | } 18 | 19 | public static class StringType extends MapEntryConfig { 20 | @SuppressWarnings("unused") 21 | public StringType() { // Required for cloth config GUI to work 22 | super(); 23 | this.value = ""; 24 | } 25 | 26 | public StringType(String key, String value) { 27 | super(key); 28 | this.value = value; 29 | } 30 | } 31 | 32 | public static class IntType extends MapEntryConfig { 33 | @SuppressWarnings("unused") 34 | public IntType() { // Required for cloth config GUI to work 35 | super(); 36 | this.value = 0; 37 | } 38 | 39 | public IntType(String key, int value) { 40 | super(key); 41 | this.value = value; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/loot_table/blocks/grave.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:block", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "bonus_rolls": 0, 7 | "entries": [ 8 | { 9 | "type": "minecraft:item", 10 | "name": "yigd:grave", 11 | "functions": [ 12 | { 13 | "function": "minecraft:copy_components", 14 | "include": [ 15 | "minecraft:profile", 16 | "minecraft:custom_name", 17 | "yigd:grave_id" 18 | ], 19 | "source": "block_entity", 20 | "conditions": [ 21 | { 22 | "condition": "minecraft:match_tool", 23 | "predicate": { 24 | "predicates": { 25 | "minecraft:enchantments": [ 26 | { 27 | "enchantments": "minecraft:silk_touch", 28 | "levels": { 29 | "min": 1 30 | } 31 | } 32 | ] 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | ], 43 | "random_sequence": "yigd:blocks/grave" 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/data/yigd/tags/item/soulbindable.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "#curios:curio", 6 | "required": false 7 | }, 8 | { 9 | "id": "#curios:back", 10 | "required": false 11 | }, 12 | { 13 | "id": "#curios:belt", 14 | "required": false 15 | }, 16 | { 17 | "id": "#curios:body", 18 | "required": false 19 | }, 20 | { 21 | "id": "#curios:bracelet", 22 | "required": false 23 | }, 24 | { 25 | "id": "#curios:charm", 26 | "required": false 27 | }, 28 | { 29 | "id": "#curios:feet", 30 | "required": false 31 | }, 32 | { 33 | "id": "#curios:head", 34 | "required": false 35 | }, 36 | { 37 | "id": "#curios:hands", 38 | "required": false 39 | }, 40 | { 41 | "id": "#curios:necklace", 42 | "required": false 43 | }, 44 | { 45 | "id": "#curios:ring", 46 | "required": false 47 | }, 48 | "#minecraft:enchantable/armor", 49 | "#minecraft:enchantable/equippable", 50 | "#minecraft:enchantable/weapon", 51 | "#minecraft:enchantable/bow", 52 | "#minecraft:enchantable/crossbow", 53 | "#minecraft:enchantable/durability", 54 | "#c:tools" 55 | ] 56 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/SyncConfigS2CPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.network.RegistryFriendlyByteBuf; 5 | import net.minecraft.network.codec.ByteBufCodecs; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public record SyncConfigS2CPacket(boolean gravesBreakable, boolean glowingGraves, int maxGraveGlowingDistance, double deathSightDistance) implements CustomPacketPayload { 12 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "sync_config")); 13 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 14 | ByteBufCodecs.BOOL, SyncConfigS2CPacket::gravesBreakable, 15 | ByteBufCodecs.BOOL, SyncConfigS2CPacket::glowingGraves, 16 | ByteBufCodecs.INT, SyncConfigS2CPacket::maxGraveGlowingDistance, 17 | ByteBufCodecs.DOUBLE, SyncConfigS2CPacket::deathSightDistance, 18 | SyncConfigS2CPacket::new); 19 | 20 | @Override 21 | public @NotNull Type type() { 22 | return TYPE; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/PlayerSelectionS2CPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.networking.LightPlayerData; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.List; 12 | 13 | public record PlayerSelectionS2CPacket(List data) implements CustomPacketPayload { 14 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "player_selection")); 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.ofMember(PlayerSelectionS2CPacket::write, PlayerSelectionS2CPacket::new); 16 | 17 | @Override 18 | public @NotNull Type type() { 19 | return TYPE; 20 | } 21 | 22 | public PlayerSelectionS2CPacket(RegistryFriendlyByteBuf buf) { 23 | this(buf.readList(friendlyByteBuf -> LightPlayerData.fromNbt(friendlyByteBuf.readNbt(), buf.registryAccess()))); 24 | } 25 | public void write(RegistryFriendlyByteBuf buf) { 26 | buf.writeCollection(this.data, (buf1, value) -> buf1.writeNbt(value.toNbt(buf.registryAccess()))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/RobGraveC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.ByteBufCodecs; 7 | import net.minecraft.network.codec.StreamCodec; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | import net.minecraft.resources.ResourceLocation; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.UUID; 13 | 14 | public record RobGraveC2SPacket(UUID graveId, boolean itemsInGrave, boolean itemsDeleted, boolean itemsKept, 15 | boolean itemsDropped) implements CustomPacketPayload { 16 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "rob_grave_request")); 17 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 18 | UUIDUtil.STREAM_CODEC, RobGraveC2SPacket::graveId, 19 | ByteBufCodecs.BOOL, RobGraveC2SPacket::itemsInGrave, 20 | ByteBufCodecs.BOOL, RobGraveC2SPacket::itemsDeleted, 21 | ByteBufCodecs.BOOL, RobGraveC2SPacket::itemsKept, 22 | ByteBufCodecs.BOOL, RobGraveC2SPacket::itemsDropped, 23 | RobGraveC2SPacket::new); 24 | 25 | @Override 26 | public @NotNull Type type() { 27 | return TYPE; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/LightPlayerData.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking; 2 | 3 | import net.minecraft.core.HolderLookup; 4 | import net.minecraft.nbt.CompoundTag; 5 | import net.minecraft.nbt.NbtOps; 6 | import net.minecraft.world.item.component.ResolvableProfile; 7 | 8 | public record LightPlayerData(int graveCount, int unclaimedCount, int destroyedCount, ResolvableProfile playerProfile) { 9 | public static LightPlayerData fromNbt(CompoundTag nbt, HolderLookup.Provider lookupRegistry) { 10 | if (nbt == null) 11 | return new LightPlayerData(0, 0, 0, null); 12 | 13 | int graveCount = nbt.getInt("graveCount"); 14 | int unclaimedCount = nbt.getInt("unclaimedCount"); 15 | int destroyedCount = nbt.getInt("destroyedCount"); 16 | ResolvableProfile profile = ResolvableProfile.CODEC.parse(NbtOps.INSTANCE, nbt.get("playerProfile")).result().orElseThrow(); 17 | 18 | return new LightPlayerData(graveCount, unclaimedCount, destroyedCount, profile); 19 | } 20 | 21 | public CompoundTag toNbt(HolderLookup.Provider lookupRegistry) { 22 | CompoundTag nbt = new CompoundTag(); 23 | nbt.putInt("graveCount", this.graveCount); 24 | nbt.putInt("unclaimedCount", this.unclaimedCount); 25 | nbt.putInt("destroyedCount", this.destroyedCount); 26 | ResolvableProfile.CODEC.encodeStart(NbtOps.INSTANCE, this.playerProfile).result() 27 | .ifPresent(nbtElement -> nbt.put("playerProfile", nbtElement)); 28 | 29 | return nbt; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/RestoreGraveC2SPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.UUIDUtil; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.ByteBufCodecs; 7 | import net.minecraft.network.codec.StreamCodec; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | import net.minecraft.resources.ResourceLocation; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.UUID; 13 | 14 | public record RestoreGraveC2SPacket(UUID graveId, boolean itemsInGrave, boolean itemsDeleted, boolean itemsKept, 15 | boolean itemsDropped) implements CustomPacketPayload { 16 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "restore_grave_request")); 17 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 18 | UUIDUtil.STREAM_CODEC, RestoreGraveC2SPacket::graveId, 19 | ByteBufCodecs.BOOL, RestoreGraveC2SPacket::itemsInGrave, 20 | ByteBufCodecs.BOOL, RestoreGraveC2SPacket::itemsDeleted, 21 | ByteBufCodecs.BOOL, RestoreGraveC2SPacket::itemsKept, 22 | ByteBufCodecs.BOOL, RestoreGraveC2SPacket::itemsDropped, 23 | RestoreGraveC2SPacket::new); 24 | 25 | @Override 26 | public @NotNull Type type() { 27 | return TYPE; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/ClientPacketHandler.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking; 2 | 3 | import com.b1n_ry.yigd.client.gui.GraveOverviewScreen; 4 | import com.b1n_ry.yigd.client.gui.GraveSelectionScreen; 5 | import com.b1n_ry.yigd.client.gui.PlayerSelectionScreen; 6 | import com.b1n_ry.yigd.client.render.GraveBlockEntityRenderer; 7 | import com.b1n_ry.yigd.config.YigdConfig; 8 | import com.b1n_ry.yigd.networking.packets.GraveOverviewS2CPacket; 9 | import com.b1n_ry.yigd.networking.packets.GraveSelectionS2CPacket; 10 | import com.b1n_ry.yigd.networking.packets.PlayerSelectionS2CPacket; 11 | import com.b1n_ry.yigd.networking.packets.SyncConfigS2CPacket; 12 | import net.neoforged.neoforge.network.handling.IPayloadContext; 13 | 14 | public class ClientPacketHandler { 15 | public static void graveOverview(GraveOverviewS2CPacket payload, IPayloadContext context) { 16 | GraveOverviewScreen.openScreen(payload); 17 | } 18 | public static void graveSelection(GraveSelectionS2CPacket payload, IPayloadContext context) { 19 | GraveSelectionScreen.openScreen(payload); 20 | } 21 | public static void playerSelection(PlayerSelectionS2CPacket payload, IPayloadContext context) { 22 | PlayerSelectionScreen.openScreen(payload); 23 | } 24 | public static void syncConfig(SyncConfigS2CPacket payload, IPayloadContext context) { 25 | YigdConfig.getConfig().graveConfig.retrieveMethods.onBreak = payload.gravesBreakable(); 26 | GraveBlockEntityRenderer.syncedGlowing = payload.glowingGraves(); 27 | GraveBlockEntityRenderer.syncedGlowingMaxDistance = payload.maxGraveGlowingDistance(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/GraveSelectionS2CPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.networking.LightGraveData; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.world.item.component.ResolvableProfile; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.List; 13 | 14 | public record GraveSelectionS2CPacket(List data, ResolvableProfile profile) implements CustomPacketPayload { 15 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "grave_selection")); 16 | public static final StreamCodec STREAM_CODEC = StreamCodec.ofMember(GraveSelectionS2CPacket::write, GraveSelectionS2CPacket::new); 17 | 18 | @Override 19 | public @NotNull Type type() { 20 | return TYPE; 21 | } 22 | 23 | public GraveSelectionS2CPacket(RegistryFriendlyByteBuf buf) { 24 | this(buf.readList(friendlyByteBuf -> LightGraveData.fromNbt(friendlyByteBuf.readNbt(), buf.registryAccess())), ResolvableProfile.STREAM_CODEC.decode(buf)); 25 | } 26 | 27 | public void write(RegistryFriendlyByteBuf buf) { 28 | buf.writeCollection(this.data, (buf1, value) -> buf1.writeNbt(value.toNbt(buf.registryAccess()))); 29 | ResolvableProfile.STREAM_CODEC.encode(buf, this.profile); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/InventoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import com.b1n_ry.yigd.util.DropRule; 4 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 5 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class InventoryConfig { 11 | public boolean dropPlayerHead = false; 12 | @ConfigEntry.Gui.CollapsibleObject 13 | public ItemLossConfig itemLoss = new ItemLossConfig(); 14 | // loose soulbound level 15 | public boolean loseSoulboundLevelOnDeath = false; 16 | // void slots 17 | public List vanishingSlots = new ArrayList<>(); 18 | // keep slots 19 | public List soulboundSlots = new ArrayList<>(); 20 | public List dropOnGroundSlots = new ArrayList<>(); 21 | 22 | public static class ItemLossConfig { 23 | public boolean enabled = false; 24 | public boolean affectStacks = false; 25 | public boolean usePercentRange = true; 26 | public int lossRangeFrom = 0; 27 | public int lossRangeTo = 100; 28 | public boolean weightedSelection = true; 29 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 30 | public DropRule lossDropRule = DropRule.DESTROY; 31 | 32 | @Comment("Chance of losing an item (iterated over every item picked up by lossRange)") 33 | public int percentChanceOfLoss = 50; 34 | @Comment("If true, you can lose soulbound items from the item loss feature") 35 | public boolean canLoseSoulbound = false; 36 | public boolean includeModdedInventories = true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/YigdConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import me.shedaniel.autoconfig.AutoConfig; 5 | import me.shedaniel.autoconfig.ConfigData; 6 | import me.shedaniel.autoconfig.annotation.Config; 7 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 8 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 9 | 10 | @Config(name = Yigd.MOD_ID) 11 | public class YigdConfig implements ConfigData { 12 | public static YigdConfig getConfig() { 13 | return AutoConfig.getConfigHolder(YigdConfig.class).getConfig(); 14 | } 15 | 16 | @ConfigEntry.Gui.CollapsibleObject 17 | public InventoryConfig inventoryConfig = new InventoryConfig(); 18 | 19 | @ConfigEntry.Gui.CollapsibleObject 20 | public ExpConfig expConfig = new ExpConfig(); 21 | 22 | @ConfigEntry.Gui.CollapsibleObject 23 | public GraveConfig graveConfig = new GraveConfig(); 24 | 25 | @ConfigEntry.Gui.CollapsibleObject 26 | public RespawnConfig respawnConfig = new RespawnConfig(); 27 | 28 | @ConfigEntry.Gui.CollapsibleObject 29 | public CompatConfig compatConfig = new CompatConfig(); 30 | 31 | @ConfigEntry.Gui.CollapsibleObject 32 | public CommandConfig commandConfig = new CommandConfig(); 33 | 34 | @Comment("Client only config") 35 | @ConfigEntry.Gui.CollapsibleObject 36 | public GraveRenderingConfig graveRendering = new GraveRenderingConfig(); 37 | 38 | @Comment("Toggleable custom features (registries)") 39 | @ConfigEntry.Gui.CollapsibleObject 40 | public ExtraFeaturesConfig extraFeatures = new ExtraFeaturesConfig(); 41 | 42 | @Override 43 | public void validatePostLoad() throws ValidationException { 44 | ConfigData.super.validatePostLoad(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/CompassItemMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.YigdConfig; 5 | import com.b1n_ry.yigd.data.DeathInfoManager; 6 | import com.b1n_ry.yigd.data.GraveStatus; 7 | import com.b1n_ry.yigd.util.GraveCompassHelper; 8 | import net.minecraft.world.entity.Entity; 9 | import net.minecraft.world.item.CompassItem; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraft.world.item.Items; 12 | import net.minecraft.world.level.Level; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | import java.util.UUID; 19 | 20 | @Mixin(CompassItem.class) 21 | public class CompassItemMixin { 22 | @Inject(method = "inventoryTick", at = @At("TAIL")) 23 | private void onInventoryTick(ItemStack stack, Level world, Entity entity, int slot, boolean selected, CallbackInfo ci) { 24 | if (world.isClientSide) return; 25 | 26 | if (!stack.is(Items.COMPASS)) return; 27 | 28 | // This mixin is to make the grave compass disappear when the grave is broken (or redirect to the closest grave) 29 | if (!YigdConfig.getConfig().extraFeatures.graveCompass.deleteWhenUnlinked) return; 30 | UUID graveId = stack.get(Yigd.GRAVE_ID); 31 | if (graveId == null) return; 32 | 33 | if (world.getGameTime() % 200 == 0) { 34 | GraveCompassHelper.updateClosestNbt(world.dimension(), entity.blockPosition(), entity.getUUID(), stack); 35 | } 36 | 37 | DeathInfoManager.INSTANCE.getGrave(graveId).ifPresent(grave -> { 38 | if (grave.getStatus() != GraveStatus.UNCLAIMED) { 39 | stack.setCount(0); 40 | } 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/packets/GraveOverviewS2CPacket.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking.packets; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.components.GraveComponent; 5 | import net.minecraft.network.RegistryFriendlyByteBuf; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public record GraveOverviewS2CPacket(GraveComponent component, boolean canRestore, boolean canRob, boolean canDelete, 12 | boolean canUnlock, boolean obtainableKeys, boolean obtainableCompass) implements CustomPacketPayload { 13 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "grave_overview")); 14 | public static final StreamCodec STREAM_CODEC = StreamCodec.ofMember(GraveOverviewS2CPacket::write, GraveOverviewS2CPacket::new); 15 | 16 | @Override 17 | public @NotNull Type type() { 18 | return TYPE; 19 | } 20 | 21 | private GraveOverviewS2CPacket(RegistryFriendlyByteBuf buf) { 22 | this(GraveComponent.fromNbt(buf.readNbt(), buf.registryAccess(), null), buf.readBoolean(), buf.readBoolean(), buf.readBoolean(), 23 | buf.readBoolean(), buf.readBoolean(), buf.readBoolean()); 24 | } 25 | private void write(RegistryFriendlyByteBuf buf) { 26 | buf.writeNbt(this.component.toNbt(buf.registryAccess())); 27 | 28 | buf.writeBoolean(this.canRestore); 29 | buf.writeBoolean(this.canRob); 30 | buf.writeBoolean(this.canDelete); 31 | buf.writeBoolean(this.canUnlock); 32 | buf.writeBoolean(this.obtainableKeys); 33 | buf.writeBoolean(this.obtainableCompass); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/YigdTags.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import net.minecraft.core.registries.Registries; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.tags.TagKey; 7 | import net.minecraft.world.item.Item; 8 | import net.minecraft.world.item.enchantment.Enchantment; 9 | import net.minecraft.world.level.block.Block; 10 | 11 | public interface YigdTags { 12 | TagKey REPLACE_SOFT_WHITELIST = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "replace_soft_whitelist")); 13 | TagKey KEEP_STRICT_BLACKLIST = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "keep_strict_blacklist")); 14 | TagKey REPLACE_GRAVE_BLACKLIST = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "replace_grave_blacklist")); 15 | 16 | TagKey NATURAL_SOULBOUND = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "natural_soulbound")); 17 | TagKey NATURAL_VANISHING = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "natural_vanishing")); 18 | TagKey LOSS_IMMUNE = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "loss_immune")); 19 | TagKey GRAVE_INCOMPATIBLE = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "grave_incompatible")); // For items that should be dropped instead of put into graves 20 | 21 | TagKey SOULBOUND = TagKey.create(Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "soulbound")); 22 | TagKey VANISHING = TagKey.create(Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "vanishing")); 23 | TagKey DEATH_SIGHT = TagKey.create(Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "death_sight")); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/mixin/RegistryDataLoaderMixin.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.mixin; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.ExtraFeaturesConfig; 5 | import com.b1n_ry.yigd.config.YigdConfig; 6 | import com.google.gson.JsonElement; 7 | import com.mojang.serialization.Decoder; 8 | import net.minecraft.core.RegistrationInfo; 9 | import net.minecraft.core.WritableRegistry; 10 | import net.minecraft.core.registries.Registries; 11 | import net.minecraft.resources.RegistryDataLoader; 12 | import net.minecraft.resources.RegistryOps; 13 | import net.minecraft.resources.ResourceKey; 14 | import net.minecraft.resources.ResourceLocation; 15 | import net.minecraft.server.packs.resources.Resource; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | 21 | /** 22 | * This mixin is present so that the configs can still toggle if the enchantments are loaded (without a bunch of fuzz) 23 | */ 24 | @Mixin(RegistryDataLoader.class) 25 | public class RegistryDataLoaderMixin { 26 | @Inject(method = "loadElementFromResource", at = @At("HEAD"), cancellable = true) 27 | private static void maybeCancelResourceLoad(WritableRegistry registry, Decoder decoder, RegistryOps ops, ResourceKey key, Resource resource, RegistrationInfo entryInfo, CallbackInfo ci) { 28 | ExtraFeaturesConfig extraFeaturesConfig = YigdConfig.getConfig().extraFeatures; 29 | if (key.equals(ResourceKey.create(Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "soulbound"))) && !extraFeaturesConfig.enableSoulbound) { 30 | ci.cancel(); 31 | } 32 | if (key.equals(ResourceKey.create(Registries.ENCHANTMENT, ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "death_sight"))) && !extraFeaturesConfig.deathSightEnchant.enabled) { 33 | ci.cancel(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/client/YigdClient.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.client; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.client.render.GraveBlockEntityRenderer; 5 | import com.b1n_ry.yigd.config.GraveConfig; 6 | import com.b1n_ry.yigd.config.YigdConfig; 7 | import com.b1n_ry.yigd.events.YigdClientEventHandler; 8 | import com.b1n_ry.yigd.networking.packets.UpdateConfigC2SPacket; 9 | import com.b1n_ry.yigd.util.YigdResourceHandler; 10 | import me.shedaniel.autoconfig.AutoConfig; 11 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; 12 | import net.neoforged.api.distmarker.Dist; 13 | import net.neoforged.bus.api.IEventBus; 14 | import net.neoforged.fml.ModContainer; 15 | import net.neoforged.fml.common.Mod; 16 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 17 | import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; 18 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 19 | import net.neoforged.neoforge.common.NeoForge; 20 | import net.neoforged.neoforge.network.PacketDistributor; 21 | 22 | @Mod(value = Yigd.MOD_ID, dist = Dist.CLIENT) 23 | public class YigdClient { 24 | public YigdClient(IEventBus modBus, ModContainer modContainer) { 25 | modBus.addListener(this::clientModInitializer); 26 | 27 | modBus.addListener(YigdResourceHandler::clientResourceEvent); 28 | NeoForge.EVENT_BUS.register(new YigdClientEventHandler()); 29 | 30 | modContainer.registerExtensionPoint(IConfigScreenFactory.class, (container, screen) -> AutoConfig.getConfigScreen(YigdConfig.class, screen).get()); 31 | 32 | NeoForge.EVENT_BUS.addListener(ClientPlayerNetworkEvent.LoggingIn.class, event -> { 33 | GraveConfig graveConfig = YigdConfig.getConfig().graveConfig; 34 | PacketDistributor.sendToServer(new UpdateConfigC2SPacket(graveConfig.claimPriority, graveConfig.graveRobbing.robPriority)); 35 | }); 36 | } 37 | 38 | public void clientModInitializer(FMLClientSetupEvent event) { 39 | BlockEntityRenderers.register(Yigd.GRAVE_BLOCK_ENTITY.get(), GraveBlockEntityRenderer::new); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/compat/InvModCompat.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.compat; 2 | 3 | import com.b1n_ry.yigd.compat.misc_compat_mods.TwilightCompat; 4 | import com.b1n_ry.yigd.config.CompatConfig; 5 | import com.b1n_ry.yigd.config.YigdConfig; 6 | import com.b1n_ry.yigd.events.YigdEvents; 7 | import net.minecraft.core.HolderLookup; 8 | import net.minecraft.nbt.CompoundTag; 9 | import net.minecraft.server.level.ServerPlayer; 10 | import net.neoforged.fml.ModList; 11 | import net.neoforged.neoforge.common.NeoForge; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public interface InvModCompat { 17 | List> invCompatMods = new ArrayList<>(); 18 | static void reloadModCompat() { 19 | invCompatMods.clear(); 20 | ModList modList = ModList.get(); 21 | CompatConfig compatConfig = YigdConfig.getConfig().compatConfig; 22 | 23 | boolean accessoriesPresent = modList.isLoaded("accessories"); 24 | boolean curiosPresent = modList.isLoaded("curios"); 25 | 26 | boolean ccLayerLoaded = modList.isLoaded("cclayer"); 27 | 28 | if (compatConfig.enableAccessoriesCompat && accessoriesPresent) 29 | invCompatMods.add(new AccessoriesCompat()); 30 | if (compatConfig.enableCuriosCompat && curiosPresent && !ccLayerLoaded) 31 | invCompatMods.add(new CuriosCompat()); 32 | if (modList.isLoaded("travelersbackpack")) { 33 | if (compatConfig.enableTravelersBackpackCompat && !((accessoriesPresent || curiosPresent) && TravelersBackpackCompat.isIntegrationEnabled())) 34 | invCompatMods.add(new TravelersBackpackCompat()); 35 | } 36 | if (modList.isLoaded("cosmeticarmorreworked") && compatConfig.enableCosmeticArmorCompat) 37 | invCompatMods.add(new CosmeticArmorCompat()); 38 | if (modList.isLoaded("twilightforest")) 39 | TwilightCompat.init(); 40 | 41 | NeoForge.EVENT_BUS.post(new YigdEvents.LoadModCompatEvent(invCompatMods)); 42 | } 43 | 44 | String getModName(); 45 | void clear(ServerPlayer player); 46 | CompatComponent readNbt(CompoundTag nbt, HolderLookup.Provider registries); 47 | 48 | CompatComponent getNewComponent(ServerPlayer player); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/PacketInitializer.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking; 2 | 3 | import com.b1n_ry.yigd.networking.packets.*; 4 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 5 | import net.neoforged.neoforge.network.registration.PayloadRegistrar; 6 | 7 | public class PacketInitializer { 8 | public static void register(final RegisterPayloadHandlersEvent event) { 9 | final PayloadRegistrar registrar = event.registrar("1"); 10 | registrar.playToServer(DeleteGraveC2SPacket.TYPE, DeleteGraveC2SPacket.STREAM_CODEC, ServerPacketHandler::deleteGraveRequest); 11 | registrar.playToServer(GraveOverviewRequestC2SPacket.TYPE, GraveOverviewRequestC2SPacket.STREAM_CODEC, ServerPacketHandler::graveOverviewRequest); 12 | registrar.playToServer(GraveSelectionRequestC2SPacket.TYPE, GraveSelectionRequestC2SPacket.STREAM_CODEC, ServerPacketHandler::graveSelectionRequest); 13 | registrar.playToServer(LockGraveC2SPacket.TYPE, LockGraveC2SPacket.STREAM_CODEC, ServerPacketHandler::lockGrave); 14 | registrar.playToServer(RequestCompassC2SPacket.TYPE, RequestCompassC2SPacket.STREAM_CODEC, ServerPacketHandler::requestCompass); 15 | registrar.playToServer(RequestKeyC2SPacket.TYPE, RequestKeyC2SPacket.STREAM_CODEC, ServerPacketHandler::requestKey); 16 | registrar.playToServer(RestoreGraveC2SPacket.TYPE, RestoreGraveC2SPacket.STREAM_CODEC, ServerPacketHandler::restoreGrave); 17 | registrar.playToServer(RobGraveC2SPacket.TYPE, RobGraveC2SPacket.STREAM_CODEC, ServerPacketHandler::robGrave); 18 | registrar.playToServer(UpdateConfigC2SPacket.TYPE, UpdateConfigC2SPacket.STREAM_CODEC, ServerPacketHandler::updateConfig); 19 | 20 | registrar.playToClient(GraveOverviewS2CPacket.TYPE, GraveOverviewS2CPacket.STREAM_CODEC, ClientPacketHandler::graveOverview); 21 | registrar.playToClient(GraveSelectionS2CPacket.TYPE, GraveSelectionS2CPacket.STREAM_CODEC, ClientPacketHandler::graveSelection); 22 | registrar.playToClient(PlayerSelectionS2CPacket.TYPE, PlayerSelectionS2CPacket.STREAM_CODEC, ClientPacketHandler::playerSelection); 23 | registrar.playToClient(SyncConfigS2CPacket.TYPE, SyncConfigS2CPacket.STREAM_CODEC, ClientPacketHandler::syncConfig); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/GraveOverrideAreas.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.Vec3i; 6 | import net.minecraft.resources.ResourceLocation; 7 | import net.minecraft.server.level.ServerLevel; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class GraveOverrideAreas { 13 | public static GraveOverrideAreas INSTANCE = new GraveOverrideAreas(); 14 | 15 | @SerializedName("default_drop_rule") 16 | public DropRule defaultDropRule = DropRule.PUT_IN_GRAVE; 17 | @SerializedName("values") 18 | public List values = new ArrayList<>(); 19 | 20 | public DropRule getDropRuleFromArea(BlockPos pos, ServerLevel world) { 21 | ResourceLocation worldId = world.dimension().location(); 22 | for (Area area : this.values) { 23 | if (!worldId.equals(area.worldId)) 24 | continue; 25 | 26 | int minX = Math.min(area.from.getX(), area.to.getX()); 27 | int minY = Math.min(area.from.getY(), area.to.getY()); 28 | int minZ = Math.min(area.from.getZ(), area.to.getZ()); 29 | int maxX = Math.max(area.from.getX(), area.to.getX()); 30 | int maxY = Math.max(area.from.getY(), area.to.getY()); 31 | int maxZ = Math.max(area.from.getZ(), area.to.getZ()); 32 | 33 | int blockX = pos.getX(); 34 | int blockY = pos.getY(); 35 | int blockZ = pos.getZ(); 36 | if (blockX < minX || maxX < blockX) continue; 37 | if ((blockY < minY || maxY < blockY) && !area.yDependent) continue; 38 | if (blockZ < minZ || maxZ < blockZ) continue; 39 | 40 | return area.areaDropRule; 41 | } 42 | return this.defaultDropRule; 43 | } 44 | 45 | public static class Area { 46 | @SerializedName("from") 47 | public Vec3i from; 48 | @SerializedName("to") 49 | public Vec3i to; 50 | @SerializedName("area_drop_rule") 51 | public DropRule areaDropRule; 52 | @SerializedName("y_dependent") 53 | public boolean yDependent = false; 54 | @SerializedName("world_id") 55 | public ResourceLocation worldId; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/GraveyardData.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import net.minecraft.core.Direction; 5 | import net.minecraft.resources.ResourceLocation; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class GraveyardData { 12 | @SerializedName("point2point") 13 | public boolean point2point = false; 14 | @SerializedName("dimension") 15 | public ResourceLocation dimensionId = ResourceLocation.withDefaultNamespace("overworld"); 16 | @SerializedName("use_closest") 17 | public boolean useClosest = false; 18 | @SerializedName("coordinates") 19 | public List graveLocations = new ArrayList<>(); 20 | 21 | 22 | public static class GraveLocation { 23 | @SerializedName("x") 24 | public int x; 25 | @SerializedName("y") 26 | public int y; 27 | @SerializedName("z") 28 | public int z; 29 | 30 | @Nullable 31 | @SerializedName("for_player") 32 | public String forPlayer = null; 33 | @Nullable 34 | @SerializedName("direction") 35 | public Direction direction = null; 36 | 37 | public GraveLocation(int x, int y, int z) { 38 | this.x = x; 39 | this.y = y; 40 | this.z = z; 41 | } 42 | } 43 | 44 | public void handlePoint2Point() { 45 | if (!this.point2point || this.graveLocations.size() != 2) return; // Can't handle point2point. Disabled or format not followed 46 | 47 | GraveLocation first = this.graveLocations.get(0); 48 | GraveLocation second = this.graveLocations.get(1); 49 | int minX = Math.min(first.x, second.x); 50 | int minY = Math.min(first.y, second.y); 51 | int minZ = Math.min(first.z, second.z); 52 | int maxX = Math.max(first.x, second.x); 53 | int maxY = Math.max(first.y, second.y); 54 | int maxZ = Math.max(first.z, second.z); 55 | 56 | this.graveLocations.clear(); 57 | for (int x = minX; x <= maxX; x++) { 58 | for (int y = minY; y <= maxY; y++) { 59 | for (int z = minZ; z <= maxZ; z++) { 60 | this.graveLocations.add(new GraveLocation(x, y, z)); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/RespawnConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 4 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class RespawnConfig { 10 | @Comment("On respawn, all players will receive these effects") 11 | public List respawnEffects = new ArrayList<>(); 12 | @Comment("HP given to player at respawn. If 0 or negative, default health will apply") 13 | public int respawnHealth = -1; 14 | @Comment("If false, player will respawn with the same hunger level as when they died") 15 | public boolean resetHunger = true; 16 | @Comment("Hunger given to player at respawn. If negative, default hunger will apply") 17 | public int respawnHunger = -1; 18 | @Comment("If false, player will respawn with the same saturation level as when they died") 19 | public boolean resetSaturation = true; 20 | @Comment("Saturation given to player at respawn. If negative, default saturation will apply") 21 | public float respawnSaturation = -1f; 22 | @Comment("Extra items that will be given to player once respawned") 23 | public List extraItemDrops = new ArrayList<>(); 24 | @Comment("If true, items with both curse of binding and soulbound will not be forced onto the player") 25 | public boolean treatBindingCurse = true; 26 | 27 | public static class EffectConfig { 28 | public String effectName; 29 | public int effectLevel; 30 | public int effectTime; 31 | public boolean showBubbles; 32 | 33 | // Constructor required for the config gui to work 34 | @SuppressWarnings("unused") 35 | public EffectConfig() { 36 | this.effectName = ""; 37 | this.effectLevel = 0; 38 | this.effectTime = 0; 39 | this.showBubbles = false; 40 | } 41 | } 42 | 43 | public static class ExtraItemDrop { 44 | public String itemId; 45 | public int count; 46 | public String itemNbt; 47 | 48 | // Constructor required for the config gui to work 49 | @SuppressWarnings("unused") 50 | public ExtraItemDrop() { 51 | this.itemId = ""; 52 | this.count = 0; 53 | this.itemNbt = ""; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/events/YigdClientEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.events; 2 | 3 | import com.b1n_ry.yigd.block.entity.GraveBlockEntity; 4 | import com.b1n_ry.yigd.client.render.GraveBlockEntityRenderer; 5 | import com.b1n_ry.yigd.config.ExtraFeaturesConfig; 6 | import com.b1n_ry.yigd.config.YigdConfig; 7 | import com.b1n_ry.yigd.util.YigdTags; 8 | import net.minecraft.client.player.LocalPlayer; 9 | import net.minecraft.world.entity.EquipmentSlot; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraft.world.item.component.ResolvableProfile; 12 | import net.minecraft.world.item.enchantment.EnchantmentHelper; 13 | import net.neoforged.bus.api.SubscribeEvent; 14 | 15 | public class YigdClientEventHandler { 16 | @SubscribeEvent 17 | public void renderGlowingGrave(YigdEvents.RenderGlowingGraveEvent event) { 18 | GraveBlockEntity be = event.getGrave(); 19 | LocalPlayer player = event.getPlayer(); 20 | YigdConfig config = YigdConfig.getConfig(); 21 | 22 | if (!config.graveRendering.useGlowingEffect || !GraveBlockEntityRenderer.syncedGlowing) return; 23 | 24 | ResolvableProfile graveOwner = be.getGraveSkull(); 25 | 26 | double distance = Integer.min(config.graveRendering.glowingDistance, GraveBlockEntityRenderer.syncedGlowingMaxDistance); 27 | boolean isOwner = graveOwner != null && graveOwner.gameProfile().equals(player.getGameProfile()); 28 | ExtraFeaturesConfig.DeathSightConfig deathSightConfig = config.extraFeatures.deathSightEnchant; 29 | 30 | ItemStack headStack = player.getItemBySlot(EquipmentSlot.HEAD); 31 | if (!headStack.isEmpty() && EnchantmentHelper.hasTag(headStack, YigdTags.DEATH_SIGHT)) { 32 | distance = Double.min(deathSightConfig.range, GraveBlockEntityRenderer.syncedDeathSightDistance); 33 | 34 | // This doesn't actually mean that the user is the grave owner, but that the graves should light up 35 | isOwner = deathSightConfig.targets == ExtraFeaturesConfig.DeathSightConfig.GraveTargets.ALL_GRAVES 36 | || (graveOwner != null && deathSightConfig.targets == ExtraFeaturesConfig.DeathSightConfig.GraveTargets.PLAYER_GRAVES); 37 | // If targets are OWN_GRAVES, the owner is already correct 38 | } 39 | 40 | boolean inRange = be.getBlockPos().closerToCenterThan(player.position(), distance); 41 | 42 | if (isOwner && inRange) 43 | event.setRenderGlowing(true); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/assets/yigd/models/block/grave.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "minecraft:block/block", 3 | "texture_size": [64, 64], 4 | "textures": { 5 | "0": "yigd:block/grave", 6 | "particle": "minecraft:block/stone" 7 | }, 8 | "elements": [ 9 | { 10 | "name": "ground", 11 | "from": [0, 0, 0], 12 | "to": [16, 1, 16], 13 | "faces": { 14 | "north": {"uv": [4, 4, 8, 4.25], "texture": "#0"}, 15 | "east": {"uv": [0, 4, 4, 4.25], "texture": "#0"}, 16 | "south": {"uv": [12, 4, 16, 4.25], "texture": "#0"}, 17 | "west": {"uv": [8, 4, 12, 4.25], "texture": "#0"}, 18 | "up": {"uv": [8, 4, 4, 0], "texture": "#0"}, 19 | "down": {"uv": [12, 0, 8, 4], "texture": "#0"} 20 | } 21 | }, 22 | { 23 | "name": "base", 24 | "from": [2, 1, 10], 25 | "to": [14, 3, 15], 26 | "faces": { 27 | "north": {"uv": [1.25, 6.5, 4.25, 7], "texture": "#0"}, 28 | "east": {"uv": [0, 6.5, 1.25, 7], "texture": "#0"}, 29 | "south": {"uv": [5.5, 6.5, 8.5, 7], "texture": "#0"}, 30 | "west": {"uv": [4.25, 6.5, 5.5, 7], "texture": "#0"}, 31 | "up": {"uv": [4.25, 6.5, 1.25, 5.25], "texture": "#0"}, 32 | "down": {"uv": [7.25, 5.25, 4.25, 6.5], "texture": "#0"} 33 | } 34 | }, 35 | { 36 | "name": "bust", 37 | "from": [3, 3, 11], 38 | "to": [13, 15, 14], 39 | "faces": { 40 | "north": {"uv": [0.75, 7.75, 3.25, 10.75], "texture": "#0"}, 41 | "east": {"uv": [0, 7.75, 0.75, 10.75], "texture": "#0"}, 42 | "south": {"uv": [4, 7.75, 6.5, 10.75], "texture": "#0"}, 43 | "west": {"uv": [3.25, 7.75, 4, 10.75], "texture": "#0"}, 44 | "up": {"uv": [3.25, 7.75, 0.75, 7], "texture": "#0"}, 45 | "down": {"uv": [5.75, 7, 3.25, 7.75], "texture": "#0"} 46 | } 47 | }, 48 | { 49 | "name": "top", 50 | "from": [4, 15, 11], 51 | "to": [12, 16, 14], 52 | "faces": { 53 | "north": {"uv": [0.75, 5, 2.75, 5.25], "texture": "#0"}, 54 | "east": {"uv": [2.75, 5, 3.5, 5.25], "texture": "#0"}, 55 | "south": {"uv": [3.5, 5, 5.5, 5.25], "texture": "#0"}, 56 | "west": {"uv": [0, 5, 0.75, 5.25], "texture": "#0"}, 57 | "up": {"uv": [2.75, 5, 0.75, 4.25], "texture": "#0"}, 58 | "down": {"uv": [4.75, 4.25, 2.75, 5], "texture": "#0"} 59 | } 60 | } 61 | ], 62 | "features": { 63 | "text": { 64 | "depth": 11, 65 | "height": 9.6, 66 | "width": 8.8 67 | }, 68 | "skull": { 69 | "depth": 5, 70 | "height": 2, 71 | "rotation": [90, 0, 0], 72 | "scaleFace": 1, 73 | "scaleDepth": 0.25 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/CompatConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import com.b1n_ry.yigd.util.DropRule; 4 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 5 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 6 | 7 | public class CompatConfig { 8 | public boolean enableAccessoriesCompat = true; 9 | @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 10 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 11 | public DropRule defaultAccessoriesDropRule = DropRule.PUT_IN_GRAVE; 12 | // public boolean enableInventorioCompat = true; 13 | // @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 14 | // @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 15 | // public DropRule defaultInventorioDropRule = DropRule.PUT_IN_GRAVE; 16 | // public boolean enableLevelzCompat = true; 17 | // @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 18 | // @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 19 | // public DropRule defaultLevelzDropRule = DropRule.PUT_IN_GRAVE; 20 | // public boolean enableNumismaticOverhaulCompat = true; 21 | // @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 22 | // @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 23 | // public DropRule defaultNumismaticDropRule = DropRule.PUT_IN_GRAVE; 24 | // public boolean enableOriginsInventoryCompat = true; 25 | // @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 26 | // @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 27 | // public DropRule defaultOriginsDropRule = DropRule.PUT_IN_GRAVE; 28 | public boolean enableTravelersBackpackCompat = true; 29 | @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 30 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 31 | public DropRule defaultTravelersBackpackDropRule = DropRule.PUT_IN_GRAVE; 32 | public boolean enableCuriosCompat = true; 33 | @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 34 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 35 | public DropRule defaultCuriosDropRule = DropRule.PUT_IN_GRAVE; 36 | public boolean enableCosmeticArmorCompat = true; 37 | @Comment("While PUT_IN_GRAVE, other drop rules will be prioritized") 38 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 39 | public DropRule defaultCosmeticArmorDropRule = DropRule.PUT_IN_GRAVE; 40 | // public boolean enableRespawnObelisksCompat = true; 41 | } 42 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | org.gradle.jvmargs=-Xmx1G 3 | org.gradle.daemon=true 4 | org.gradle.parallel=true 5 | org.gradle.caching=true 6 | org.gradle.configuration-cache=true 7 | 8 | #read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment 9 | # you can also find the latest versions at: https://parchmentmc.org/docs/getting-started 10 | parchment_minecraft_version=1.21.1 11 | parchment_mappings_version=2024.11.17 12 | # Environment Properties 13 | # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge 14 | # The Minecraft version must agree with the Neo version to get a valid artifact 15 | minecraft_version=1.21.1 16 | # The Minecraft version range can use any release version of Minecraft as bounds. 17 | # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly 18 | # as they do not follow standard versioning conventions. 19 | minecraft_version_range=[1.21,1.21.1) 20 | # The Neo version must agree with the Minecraft version to get a valid artifact 21 | neo_version=21.1.172 22 | # The Neo version range can use any version of Neo as bounds 23 | neo_version_range=[21.1.0,) 24 | # The loader version range can only use the major version of FML as bounds 25 | loader_version_range=[4,) 26 | 27 | ## Mod Properties 28 | 29 | # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} 30 | # Must match the String constant located in the main mod class annotated with @Mod. 31 | mod_id=yigd 32 | # The name the file will have when building the mod 33 | archives_base_name=youre-in-grave-danger-neoforge 34 | # The human-readable display name for the mod. 35 | mod_name=You're in Grave Danger 36 | # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. 37 | mod_license=MIT 38 | # The mod version. See https://semver.org/ 39 | mod_version=2.0.14 40 | # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. 41 | # This should match the base package used for the mod sources. 42 | # See https://maven.apache.org/guides/mini/guide-naming-conventions.html 43 | mod_group_id=com.b1n_ry.yigd 44 | # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. 45 | mod_authors=b1n_ry 46 | # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. 47 | mod_description=Adds graves and some other stuff to the game 48 | 49 | 50 | ## Mod dependencies 51 | 52 | config_version=15.0.140 53 | 54 | accessories_version=1.1.0-beta.43+1.21.1 55 | curios_version=9.5.1+1.21.1 56 | travelers_backpack_version=9nwiWyA3 57 | cosmetic_armor_version=237307:5610814 58 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/compat/misc_compat_mods/TwilightCompat.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.compat.misc_compat_mods; 2 | 3 | import com.b1n_ry.yigd.components.InventoryComponent; 4 | import com.b1n_ry.yigd.data.DeathContext; 5 | import com.b1n_ry.yigd.events.YigdEvents; 6 | import com.b1n_ry.yigd.util.DropRule; 7 | import net.minecraft.core.registries.BuiltInRegistries; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.world.item.Item; 10 | import net.neoforged.neoforge.common.NeoForge; 11 | 12 | public class TwilightCompat { 13 | public static void init() { 14 | NeoForge.EVENT_BUS.addListener(TwilightCompat::adjustDropRules); 15 | } 16 | 17 | public static void adjustDropRules(YigdEvents.AdjustDropRuleEvent event) { 18 | DeathContext context = event.getDeathContext(); 19 | InventoryComponent inventoryComponent = event.getInventoryComponent(); 20 | 21 | int selectedSlot = context.player().getInventory().selected; 22 | Item keepingCharm3 = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath("twilightforest", "charm_of_keeping_3")); 23 | Item keepingCharm2 = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath("twilightforest", "charm_of_keeping_2")); 24 | Item keepingCharm1 = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath("twilightforest", "charm_of_keeping_1")); 25 | boolean tier3 = inventoryComponent.containsAny( 26 | stack -> stack.is(keepingCharm3), mod -> true, slot -> true); 27 | boolean tier2 = tier3 || inventoryComponent.containsAny( 28 | stack -> stack.is(keepingCharm2), mod -> true, slot -> true); 29 | boolean tier1 = tier2 || inventoryComponent.containsAny( 30 | stack -> stack.is(keepingCharm1), 31 | mod -> true, slot -> true); 32 | 33 | Item firstMatch = tier3 ? keepingCharm3 : 34 | tier2 ? keepingCharm2 : 35 | tier1 ? keepingCharm1 : 36 | null; 37 | if (firstMatch == null) return; 38 | 39 | final int hotbarSize = 9; // We don't know for certain, but we can be pretty confident 40 | int afterOffhandIndex = inventoryComponent.mainSize + inventoryComponent.armorSize + inventoryComponent.offHandSize; 41 | inventoryComponent.handleGraveItems(mod -> true, (stack, slot, graveItem) -> { 42 | if (slot >= inventoryComponent.mainSize && slot < afterOffhandIndex || slot == selectedSlot || slot < 0) { // Tier 1: Will always be true, otherwise we exit sooner 43 | graveItem.dropRule = DropRule.KEEP; 44 | } else if (tier2 && slot < hotbarSize) { 45 | graveItem.dropRule = DropRule.KEEP; 46 | } else if (tier3) { 47 | graveItem.dropRule = DropRule.KEEP; 48 | } 49 | 50 | if (!stack.is(firstMatch)) return; 51 | graveItem.dropRule = DropRule.DESTROY; 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/ExtraFeaturesConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 4 | 5 | public class ExtraFeaturesConfig { 6 | @ConfigEntry.Gui.CollapsibleObject 7 | public boolean enableSoulbound = true; 8 | @ConfigEntry.Gui.CollapsibleObject 9 | public DeathSightConfig deathSightEnchant = new DeathSightConfig(); 10 | @ConfigEntry.Gui.CollapsibleObject 11 | public GraveKeyConfig graveKeys = new GraveKeyConfig(); 12 | @ConfigEntry.Gui.CollapsibleObject 13 | public ScrollConfig deathScroll = new ScrollConfig(); 14 | @ConfigEntry.Gui.CollapsibleObject 15 | public GraveCompassConfig graveCompass = new GraveCompassConfig(); 16 | 17 | public static class DeathSightConfig { 18 | public boolean enabled = false; 19 | public double range = 64; 20 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 21 | public DeathSightConfig.GraveTargets targets = DeathSightConfig.GraveTargets.PLAYER_GRAVES; 22 | 23 | public enum GraveTargets { 24 | OWN_GRAVES, PLAYER_GRAVES, ALL_GRAVES 25 | } 26 | } 27 | 28 | public static class GraveKeyConfig { 29 | public boolean enabled = false; 30 | public boolean rebindable = true; 31 | public boolean required = true; 32 | public boolean receiveOnRespawn = true; 33 | public boolean obtainableFromGui = false; 34 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 35 | public GraveKeyConfig.KeyTargeting targeting = GraveKeyConfig.KeyTargeting.PLAYER_GRAVE; 36 | 37 | public enum KeyTargeting { 38 | ANY_GRAVE, PLAYER_GRAVE, SPECIFIC_GRAVE 39 | } 40 | } 41 | 42 | public static class ScrollConfig { 43 | public boolean enabled = false; 44 | public boolean rebindable = false; 45 | public boolean receiveOnRespawn = false; 46 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 47 | public ScrollConfig.ClickFunction clickFunction = ScrollConfig.ClickFunction.VIEW_CONTENTS; 48 | public boolean consumeOnUse = false; 49 | public int useTime = 0; 50 | public int useCooldown = 0; 51 | 52 | public enum ClickFunction { 53 | RESTORE_CONTENTS, VIEW_CONTENTS, TELEPORT_TO_LOCATION 54 | } 55 | } 56 | 57 | public static class GraveCompassConfig { 58 | public boolean receiveOnRespawn = false; 59 | public boolean consumeOnUse = true; 60 | public boolean deleteWhenUnlinked = true; 61 | public boolean cloneRecoveryCompassWithGUI = false; 62 | @ConfigEntry.Gui.RequiresRestart 63 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 64 | public GraveCompassConfig.CompassGraveTarget pointToClosest = GraveCompassConfig.CompassGraveTarget.DISABLED; 65 | 66 | public enum CompassGraveTarget { 67 | DISABLED, PLAYER, ALL 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # You're in Grave Danger 2 | 3 | Get it? 4 | 5 | Requires Cloth Config API 6 | 7 | 8 | Join my discord 9 | 10 | ## About 11 | 12 | You're in Grave Danger is the ultimate death handling mod. With this mod you can configure almost every aspect of 13 | when you die, from items you lose, to items you keep, to items you drop, to xp, to effects you get when respawning, 14 | to health and hunger values being set when respawning, and much more. 15 | 16 | A big part of this mod, although it is technically an optional feature, are the gravestones (feature enabled by default). 17 | These gravestones generate when you die, and stores your loot, until you reclaim it. 18 | The graves are also highly configurable, to where they can and can't generate, to what will end up in your grave, 19 | how you retrieve your items, as well as many more configuration options. 20 | 21 | Graves will also remember the layout of your items, and replace this layout when grave is claimed. This is however 22 | configurable, and weather the grave layout or your current layout should be prioritized can be changed through the config. 23 | 24 | The graves are also equipped with rendering features, like rendering a head of the deceased player, and their name, that 25 | will help you keep track of whose grave is who's when playing with your friends. 26 | All rendering can be configured. 27 | 28 | ## Features 29 | 30 | The mod is available for NeoForge on 1.21! 31 | 32 | You're in Grave Danger (YiGD for short) aims to be compatible with all inventory mods. 33 | If you know of one that is not supported, let me (the developer of YiGD) know. 34 | 35 | The mod is also compatible with all soulbound enchantments, as long as they're added to the soulbound enchantment tag. 36 | 37 | Graves can be waterlogged, and renders can be adapted to the block the grave is standing on. 38 | 39 | The mod also includes a large number of other features. If you're intrigued by what YiGD can do, check the configs. 40 | The mod includes components that can control various aspects of the following, when a player die: 41 | * Player Inventory 42 | * Player experience points 43 | * Player Respawn event 44 | * Graves 45 | * Additional registries such as a few items and enchantments 46 | 47 | If you found ANYTHING there is not a config for, that you'd like to be configurable, let me know through discord, github, 48 | or similar, and I'll see about making this configurable 49 | 50 | 51 | ## Misc info 52 | 53 | If you find any bug/missing config, or have a cool idea that would fit in the theme of the mod, please open an issue on the [github issue tracker](https://github.com/B1n-ry/Youre-in-grave-danger/issues) 54 | 55 | The mod is under MIT license, so feel free to use this in your modpack! 56 | 57 | This mod is required both on client and server, as it will add custom blocks and renderers. 58 | It's intended for use in modpacks. If you're looking for a server side only grave mod, I'd recommend Universal Graves -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/item/GraveKeyItem.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.item; 2 | 3 | import com.b1n_ry.yigd.components.GraveComponent; 4 | import com.b1n_ry.yigd.config.YigdConfig; 5 | import com.b1n_ry.yigd.data.DeathInfoManager; 6 | import com.b1n_ry.yigd.data.GraveStatus; 7 | import net.minecraft.core.component.DataComponents; 8 | import net.minecraft.nbt.NbtOps; 9 | import net.minecraft.nbt.NbtUtils; 10 | import net.minecraft.world.InteractionHand; 11 | import net.minecraft.world.InteractionResultHolder; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.flag.FeatureFlagSet; 14 | import net.minecraft.world.item.Item; 15 | import net.minecraft.world.item.ItemStack; 16 | import net.minecraft.world.item.component.CustomData; 17 | import net.minecraft.world.item.component.ResolvableProfile; 18 | import net.minecraft.world.level.Level; 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.UUID; 24 | 25 | public class GraveKeyItem extends Item { 26 | public GraveKeyItem(Properties properties) { 27 | super(properties); 28 | } 29 | 30 | @Override 31 | public void onCraftedBy(@NotNull ItemStack stack, Level world, @NotNull Player player) { 32 | if (!world.isClientSide) { 33 | this.bindStackToLatestGrave(player, stack); 34 | } 35 | super.onCraftedBy(stack, world, player); 36 | } 37 | 38 | @Override 39 | public boolean isEnabled(@NotNull FeatureFlagSet enabledFeatures) { 40 | return YigdConfig.getConfig().extraFeatures.graveKeys.enabled; 41 | } 42 | 43 | @Override 44 | public @NotNull InteractionResultHolder use(@NotNull Level level, @NotNull Player user, @NotNull InteractionHand hand) { 45 | if (level.isClientSide) return super.use(level, user, hand); 46 | 47 | YigdConfig config = YigdConfig.getConfig(); 48 | 49 | if (config.extraFeatures.graveKeys.rebindable && user.isShiftKeyDown()) { 50 | ItemStack key = user.getItemInHand(hand); 51 | if (this.bindStackToLatestGrave(user, key)) 52 | return InteractionResultHolder.sidedSuccess(key, true); 53 | } 54 | 55 | return super.use(level, user, hand); 56 | } 57 | 58 | public boolean bindStackToLatestGrave(Player player, ItemStack key) { 59 | ResolvableProfile playerProfile = new ResolvableProfile(player.getGameProfile()); 60 | List graves = new ArrayList<>(DeathInfoManager.INSTANCE.getBackupData(playerProfile)); 61 | graves.removeIf(component -> component.getStatus() != GraveStatus.UNCLAIMED); 62 | 63 | int size = graves.size(); 64 | if (size >= 1) { 65 | GraveComponent component = graves.get(size - 1); 66 | this.bindStackToGrave(component.getGraveId(), playerProfile, key); 67 | return true; 68 | } 69 | return false; 70 | } 71 | public void bindStackToGrave(UUID graveId, ResolvableProfile playerProfile, ItemStack key) { 72 | key.update(DataComponents.CUSTOM_DATA, CustomData.EMPTY, comp -> comp.update(nbtCompound -> { 73 | nbtCompound.put("grave", NbtUtils.createUUID(graveId)); 74 | nbtCompound.put("user", ResolvableProfile.CODEC.encodeStart(NbtOps.INSTANCE, playerProfile).getOrThrow()); 75 | })); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/networking/LightGraveData.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.networking; 2 | 3 | import com.b1n_ry.yigd.data.GraveStatus; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.core.HolderLookup; 6 | import net.minecraft.core.Registry; 7 | import net.minecraft.nbt.CompoundTag; 8 | import net.minecraft.nbt.NbtUtils; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.resources.ResourceKey; 11 | import net.minecraft.resources.ResourceLocation; 12 | import net.minecraft.world.level.Level; 13 | 14 | import java.util.UUID; 15 | 16 | /** 17 | * This class is used to carry a representation from an overhaul of what's in a grave, not a detailed description 18 | * @param itemCount Amount of items in total on the player 19 | * @param pos {@link BlockPos} where the player died 20 | * @param xpPoints How much XP the player had 21 | * @param registryKey {@link ResourceKey} of the world player died in 22 | * @param deathMessage {@link Component} of the player's death message 23 | * @param id The grave {@link UUID} 24 | * @param status The availability status of the grave 25 | */ 26 | public record LightGraveData(int itemCount, BlockPos pos, int xpPoints, ResourceKey registryKey, 27 | Component deathMessage, UUID id, GraveStatus status) { 28 | 29 | public static LightGraveData fromNbt(CompoundTag nbt, HolderLookup.Provider registryLookup) { 30 | if (nbt == null) { 31 | return new LightGraveData(0, BlockPos.ZERO, 0, Level.OVERWORLD, Component.empty(), UUID.randomUUID(), GraveStatus.CLAIMED); 32 | } 33 | int itemCount = nbt.getInt("itemCount"); 34 | BlockPos pos = NbtUtils.readBlockPos(nbt, "pos").orElse(BlockPos.ZERO); 35 | int xpPoints = nbt.getInt("xpPoints"); 36 | ResourceKey registryKey = getRegistryKeyFromNbt(nbt.getCompound("worldKey")); 37 | Component deathMessage = Component.Serializer.fromJson(nbt.getString("deathMessage"), registryLookup); 38 | UUID id = nbt.getUUID("id"); 39 | GraveStatus status = GraveStatus.valueOf(nbt.getString("status")); 40 | 41 | return new LightGraveData(itemCount, pos, xpPoints, registryKey, deathMessage, id, status); 42 | } 43 | 44 | public CompoundTag toNbt(HolderLookup.Provider registryLookup) { 45 | CompoundTag nbt = new CompoundTag(); 46 | nbt.putInt("itemCount", this.itemCount); 47 | nbt.put("pos", NbtUtils.writeBlockPos(this.pos)); 48 | nbt.putInt("xpPoints", this.xpPoints); 49 | nbt.put("worldKey", this.getWorldRegistryKeyNbt(this.registryKey)); 50 | nbt.putString("deathMessage", Component.Serializer.toJson(this.deathMessage, registryLookup)); 51 | nbt.putUUID("id", this.id); 52 | nbt.putString("status", this.status.toString()); 53 | 54 | return nbt; 55 | } 56 | 57 | private CompoundTag getWorldRegistryKeyNbt(ResourceKey key) { 58 | CompoundTag nbt = new CompoundTag(); 59 | nbt.putString("registry", key.registry().toString()); 60 | nbt.putString("value", key.location().toString()); 61 | 62 | return nbt; 63 | } 64 | 65 | private static ResourceKey getRegistryKeyFromNbt(CompoundTag nbt) { 66 | String registry = nbt.getString("registry"); 67 | String value = nbt.getString("value"); 68 | 69 | ResourceKey> r = ResourceKey.createRegistryKey(ResourceLocation.parse(registry)); 70 | return ResourceKey.create(r, ResourceLocation.parse(value)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/data/TimePoint.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.data; 2 | 3 | import net.minecraft.nbt.CompoundTag; 4 | import net.minecraft.server.level.ServerLevel; 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.Month; 8 | import java.util.EnumMap; 9 | 10 | public class TimePoint { 11 | private final long time; 12 | private final long timeOfDay; 13 | private final LocalDateTime irlTime; 14 | 15 | private static final EnumMap MONTH_NAMES; 16 | 17 | public TimePoint(ServerLevel world) { 18 | this(world.getGameTime(), world.getDayTime(), LocalDateTime.now()); 19 | } 20 | public TimePoint(long time, long timeOfDay, LocalDateTime irlTime) { 21 | this.time = time; 22 | this.timeOfDay = timeOfDay; 23 | this.irlTime = irlTime; 24 | } 25 | 26 | public long getTime() { 27 | return this.time; 28 | } 29 | public long getDay() { 30 | return this.timeOfDay / 24000; 31 | } 32 | public String getMonthName() { 33 | return MONTH_NAMES.get(this.irlTime.getMonth()); 34 | } 35 | public int getDate() { 36 | return this.irlTime.getDayOfMonth(); 37 | } 38 | public int getYear() { 39 | return this.irlTime.getYear(); 40 | } 41 | public int getHour(boolean timePostfix) { 42 | int hour = this.irlTime.getHour(); 43 | return timePostfix ? (11 + hour) % 12 + 1 : hour; 44 | } 45 | public int getMinute() { 46 | return this.irlTime.getMinute(); 47 | } 48 | public String getTimePostfix(boolean actuallyUseIt) { 49 | if (actuallyUseIt) 50 | return this.irlTime.getHour() >= 12 ? " PM" : " AM"; 51 | 52 | return ""; 53 | } 54 | 55 | public CompoundTag toNbt() { 56 | CompoundTag nbt = new CompoundTag(); 57 | nbt.putLong("time", this.time); 58 | nbt.putLong("timeOfDay", this.timeOfDay); 59 | 60 | CompoundTag irlTimeNbt = new CompoundTag(); 61 | irlTimeNbt.putInt("year", this.irlTime.getYear()); 62 | irlTimeNbt.putInt("month", this.irlTime.getMonthValue()); 63 | irlTimeNbt.putInt("date", this.irlTime.getDayOfMonth()); 64 | 65 | irlTimeNbt.putInt("hour", this.irlTime.getHour()); 66 | irlTimeNbt.putInt("minute", this.irlTime.getMinute()); 67 | 68 | nbt.put("realTime", irlTimeNbt); 69 | return nbt; 70 | } 71 | 72 | public static TimePoint fromNbt(CompoundTag nbt) { 73 | long time = nbt.getLong("time"); 74 | long timeOfDay = nbt.getLong("timeOfDay"); 75 | 76 | CompoundTag irlTimeNbt = nbt.getCompound("realTime"); 77 | int year = irlTimeNbt.getInt("year"); 78 | int month = irlTimeNbt.getInt("month"); 79 | int date = irlTimeNbt.getInt("date"); 80 | int hour = irlTimeNbt.getInt("hour"); 81 | int minute = irlTimeNbt.getInt("minute"); 82 | 83 | LocalDateTime dateTime = LocalDateTime.of(year, month, date, hour, minute); 84 | return new TimePoint(time, timeOfDay, dateTime); 85 | } 86 | 87 | static { 88 | MONTH_NAMES = new EnumMap<>(Month.class) {{ 89 | put(Month.JANUARY, "January"); 90 | put(Month.FEBRUARY, "February"); 91 | put(Month.MARCH, "March"); 92 | put(Month.APRIL, "April"); 93 | put(Month.MAY, "May"); 94 | put(Month.JUNE, "June"); 95 | put(Month.JULY, "July"); 96 | put(Month.AUGUST, "August"); 97 | put(Month.SEPTEMBER, "September"); 98 | put(Month.OCTOBER, "October"); 99 | put(Month.NOVEMBER, "November"); 100 | put(Month.DECEMBER, "December"); 101 | }}; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/components/EffectComponent.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.components; 2 | 3 | import com.b1n_ry.yigd.config.RespawnConfig; 4 | import com.b1n_ry.yigd.config.YigdConfig; 5 | import net.minecraft.core.Holder; 6 | import net.minecraft.core.registries.BuiltInRegistries; 7 | import net.minecraft.nbt.CompoundTag; 8 | import net.minecraft.nbt.ListTag; 9 | import net.minecraft.nbt.Tag; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.effect.MobEffect; 13 | import net.minecraft.world.effect.MobEffectInstance; 14 | import net.minecraft.world.food.FoodData; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class EffectComponent { 20 | private final List effects; 21 | private final int resetHp; 22 | private final int resetHunger; 23 | private final float resetSaturation; 24 | 25 | 26 | public EffectComponent(ServerPlayer player) { 27 | YigdConfig config = YigdConfig.getConfig(); 28 | RespawnConfig rConfig = config.respawnConfig; 29 | 30 | this.effects = new ArrayList<>(); 31 | this.loadEffectsFromConfig(rConfig); 32 | 33 | this.resetHp = rConfig.respawnHealth; 34 | 35 | FoodData hungerManager = player.getFoodData(); 36 | if (!rConfig.resetHunger) { 37 | this.resetHunger = hungerManager.getFoodLevel(); 38 | } else { 39 | this.resetHunger = rConfig.respawnHunger; 40 | } 41 | if (!rConfig.resetSaturation) { 42 | this.resetSaturation = hungerManager.getSaturationLevel(); 43 | } else { 44 | this.resetSaturation = rConfig.respawnSaturation; 45 | } 46 | } 47 | 48 | public EffectComponent(List effects, int resetHp, int resetHunger, float resetSaturation) { 49 | this.effects = effects; 50 | this.resetHp = resetHp; 51 | this.resetHunger = resetHunger; 52 | this.resetSaturation = resetSaturation; 53 | } 54 | 55 | public void applyToPlayer(ServerPlayer player) { 56 | if (this.resetHp > 0) 57 | player.setHealth(this.resetHp); 58 | 59 | FoodData hungerManager = player.getFoodData(); 60 | if (this.resetHunger >= 0) 61 | hungerManager.setFoodLevel(this.resetHunger); 62 | if (this.resetSaturation >= 0) 63 | hungerManager.setSaturation(this.resetSaturation); 64 | 65 | for (MobEffectInstance effect : this.effects) { 66 | player.addEffect(effect); 67 | } 68 | } 69 | 70 | public CompoundTag toNbt() { 71 | CompoundTag nbtCompound = new CompoundTag(); 72 | nbtCompound.putInt("hp", this.resetHp); 73 | nbtCompound.putInt("hunger", this.resetHunger); 74 | nbtCompound.putFloat("saturation", this.resetSaturation); 75 | 76 | ListTag nbtEffects = new ListTag(); 77 | for (MobEffectInstance instance : this.effects) { 78 | nbtEffects.add(instance.save()); 79 | } 80 | nbtCompound.put("effects", nbtEffects); 81 | 82 | return nbtCompound; 83 | } 84 | 85 | private void loadEffectsFromConfig(RespawnConfig rConfig) { 86 | for (RespawnConfig.EffectConfig effect : rConfig.respawnEffects) { 87 | MobEffect statusEffect = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.parse(effect.effectName)); 88 | if (statusEffect == null) continue; 89 | 90 | Holder effectRegistryEntry = BuiltInRegistries.MOB_EFFECT.wrapAsHolder(statusEffect); 91 | MobEffectInstance effectInstance = new MobEffectInstance(effectRegistryEntry, effect.effectTime, effect.effectLevel - 1, false, effect.showBubbles); 92 | this.effects.add(effectInstance); 93 | } 94 | } 95 | 96 | public static EffectComponent fromNbt(CompoundTag nbt) { 97 | int resetHp = nbt.getInt("hp"); 98 | int resetHunger = nbt.getInt("hunger"); 99 | float resetSaturation = nbt.getFloat("saturation"); 100 | 101 | List effects = new ArrayList<>(); 102 | ListTag effectsNbt = nbt.getList("effects", Tag.TAG_COMPOUND); 103 | for (Tag e : effectsNbt) { 104 | CompoundTag compound = (CompoundTag) e; 105 | MobEffectInstance instance = MobEffectInstance.load(compound); 106 | effects.add(instance); 107 | } 108 | 109 | return new EffectComponent(effects, resetHp, resetHunger, resetSaturation); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/DeathHandler.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd; 2 | 3 | 4 | import com.b1n_ry.yigd.components.ExpComponent; 5 | import com.b1n_ry.yigd.components.GraveComponent; 6 | import com.b1n_ry.yigd.components.InventoryComponent; 7 | import com.b1n_ry.yigd.components.RespawnComponent; 8 | import com.b1n_ry.yigd.config.YigdConfig; 9 | import com.b1n_ry.yigd.data.DeathContext; 10 | import com.b1n_ry.yigd.data.GraveItem; 11 | import com.b1n_ry.yigd.events.YigdEvents; 12 | import com.b1n_ry.yigd.util.DropRule; 13 | import com.b1n_ry.yigd.util.GraveOverrideAreas; 14 | import net.minecraft.core.Direction; 15 | import net.minecraft.server.level.ServerLevel; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.world.damagesource.DamageSource; 18 | import net.minecraft.world.item.ItemStack; 19 | import net.minecraft.world.item.component.ResolvableProfile; 20 | import net.minecraft.world.phys.Vec3; 21 | import net.neoforged.neoforge.common.NeoForge; 22 | 23 | import java.util.UUID; 24 | 25 | public class DeathHandler { 26 | private final GraveComponent graveComponent; 27 | private final Direction playerDirection; 28 | private final DeathContext context; 29 | private final RespawnComponent respawnComponent; 30 | 31 | public DeathHandler(ServerPlayer player, ServerLevel world, Vec3 pos, DamageSource deathSource) { 32 | YigdConfig config = YigdConfig.getConfig(); 33 | 34 | UUID killerId; 35 | if (deathSource.getEntity() instanceof ServerPlayer killer) { 36 | killerId = killer.getUUID(); 37 | } else { 38 | killerId = null; 39 | } 40 | 41 | DeathContext context = new DeathContext(player, world, pos, deathSource); 42 | 43 | RespawnComponent respawnComponent = new RespawnComponent(player); // Will keep track of data used on respawn 44 | 45 | InventoryComponent inventoryComponent = new InventoryComponent(player); // Will keep track of all items 46 | ExpComponent expComponent = new ExpComponent(player); // Will keep track of XP 47 | 48 | InventoryComponent.clearPlayer(player); // No use for actual inventory when inventory component is created 49 | ExpComponent.clearXp(player); // No use for actual exp when exp component is created 50 | 51 | // Here would be an if statement for keepInventory, if the mod didn't let vanilla handle keepInventory 52 | // There once was code here, but is no more since removing it was the easiest fix a duplication bug 53 | 54 | // Handle drop rules 55 | inventoryComponent.onDeath(context); 56 | 57 | // Set kept items as soulbound in respawn component 58 | InventoryComponent soulboundInventory = inventoryComponent.filteredInv(dropRule -> dropRule == DropRule.KEEP); 59 | respawnComponent.setSoulboundInventory(soulboundInventory); 60 | // Keep XP 61 | ExpComponent keepExp = expComponent.getSoulboundExp(); 62 | respawnComponent.setSoulboundExp(keepExp); 63 | 64 | ResolvableProfile profile = new ResolvableProfile(player.getGameProfile()); 65 | Vec3 graveGenerationPos = !config.graveConfig.generateOnLastGroundPos ? pos : player.getData(Yigd.LAST_GROUND_POS); 66 | GraveComponent graveComponent = new GraveComponent(profile, inventoryComponent, expComponent, 67 | world, graveGenerationPos.add(0D, .5D, 0D), deathSource.getLocalizedDeathMessage(player), killerId); // Will keep track of player grave (if enabled) 68 | 69 | if (!graveComponent.isEmpty()) { 70 | graveComponent.backUp(); 71 | } else { 72 | Yigd.LOGGER.info("Did not backup data (grave data empty)"); // There is literally no information worth saving 73 | } 74 | 75 | respawnComponent.primeForRespawn(profile); 76 | 77 | Direction playerDirection = player.getDirection(); 78 | 79 | this.graveComponent = graveComponent; 80 | this.playerDirection = playerDirection; 81 | this.context = context; 82 | this.respawnComponent = respawnComponent; 83 | } 84 | public void addItem(ItemStack stack) { 85 | InventoryComponent inventoryComponent = this.graveComponent.getInventoryComponent(); 86 | inventoryComponent.addExtraGraveItem(new GraveItem(stack, GraveOverrideAreas.INSTANCE.defaultDropRule)); 87 | } 88 | public void finalizeDeath() { 89 | YigdEvents.DelayGraveGenerationEvent event = NeoForge.EVENT_BUS.post( 90 | new YigdEvents.DelayGraveGenerationEvent(this.graveComponent, this.playerDirection, this.context, this.respawnComponent, "vanilla")); 91 | if (!event.generationIsDelayed()) { 92 | this.graveComponent.generateOrDrop(this.playerDirection, this.context, this.respawnComponent); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/templates/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | # This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. 2 | # There are several mandatory fields (#mandatory), and many more that are optional (#optional). 3 | # The overall format is standard TOML format, v0.5.0. 4 | # Note that there are a couple of TOML lists in this file. 5 | # Find more information on toml format here: https://github.com/toml-lang/toml 6 | # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml 7 | modLoader="javafml" #mandatory 8 | 9 | # A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2. 10 | loaderVersion="${loader_version_range}" #mandatory 11 | 12 | # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. 13 | # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. 14 | license="${mod_license}" 15 | 16 | # A URL to refer people to when problems occur with this mod 17 | #issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional 18 | 19 | # A list of mods - how many allowed here is determined by the individual mod loader 20 | [[mods]] #mandatory 21 | 22 | # The modid of the mod 23 | modId="${mod_id}" #mandatory 24 | 25 | # The version number of the mod 26 | version="${mod_version}" #mandatory 27 | 28 | # A display name for the mod 29 | displayName="${mod_name}" #mandatory 30 | 31 | # A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ 32 | #updateJSONURL="https://change.me.example.invalid/updates.json" #optional 33 | 34 | # A URL for the "homepage" for this mod, displayed in the mod UI 35 | #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional 36 | 37 | # A file name (in the root of the mod JAR) containing a logo for display 38 | #logoFile="examplemod.png" #optional 39 | 40 | # A text field displayed in the mod UI 41 | #credits="" #optional 42 | 43 | # A text field displayed in the mod UI 44 | authors="${mod_authors}" #optional 45 | 46 | # The description text for the mod (multi line!) (#mandatory) 47 | description='''${mod_description}''' 48 | 49 | # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. 50 | #[[mixins]] 51 | #config="${mod_id}.mixins.json" 52 | 53 | # The [[accessTransformers]] block allows you to declare where your AT file is. 54 | # If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg 55 | #[[accessTransformers]] 56 | #file="META-INF/accesstransformer.cfg" 57 | 58 | # The coremods config file path is not configurable and is always loaded from META-INF/coremods.json 59 | 60 | # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. 61 | [[dependencies.${mod_id}]] #optional 62 | # the modid of the dependency 63 | modId="neoforge" #mandatory 64 | # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). 65 | # 'required' requires the mod to exist, 'optional' does not 66 | # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning 67 | type="required" #mandatory 68 | # Optional field describing why the dependency is required or why it is incompatible 69 | # reason="..." 70 | # The version range of the dependency 71 | versionRange="${neo_version_range}" #mandatory 72 | # An ordering relationship for the dependency. 73 | # BEFORE - This mod is loaded BEFORE the dependency 74 | # AFTER - This mod is loaded AFTER the dependency 75 | ordering="NONE" 76 | # Side this dependency is applied on - BOTH, CLIENT, or SERVER 77 | side="BOTH" 78 | 79 | # Here's another dependency 80 | [[dependencies.${mod_id}]] 81 | modId="minecraft" 82 | type="required" 83 | # This version range declares a minimum of the current minecraft version up to but not including the next major version 84 | versionRange="${minecraft_version_range}" 85 | ordering="NONE" 86 | side="BOTH" 87 | 88 | # Features are specific properties of the game environment, that you may want to declare you require. This example declares 89 | # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't 90 | # stop your mod loading on the server for example. 91 | #[features.${mod_id}] 92 | #openGLVersion="[3.2,)" 93 | [[accessTransformers]] 94 | file="META-INF/accesstransformer.cfg" 95 | 96 | [[mixins]] 97 | config="yigd.mixins.json" 98 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/components/ExpComponent.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.components; 2 | 3 | import com.b1n_ry.yigd.config.YigdConfig; 4 | import net.minecraft.nbt.CompoundTag; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.entity.ExperienceOrb; 8 | import net.minecraft.world.phys.Vec3; 9 | 10 | public class ExpComponent { 11 | private final double originalXp; 12 | private int storedXp; 13 | 14 | public ExpComponent(ServerPlayer player) { 15 | this.originalXp = this.getTotalExperience(player); 16 | this.storedXp = this.getXpDropAmount(player); 17 | } 18 | private ExpComponent(int storedXp, double originalXp) { 19 | this.storedXp = storedXp; 20 | this.originalXp = originalXp; 21 | } 22 | 23 | public void setStoredXp(int storedXp) { 24 | this.storedXp = storedXp; 25 | } 26 | 27 | public int getStoredXp() { 28 | return this.storedXp; 29 | } 30 | public double getOriginalXp() { 31 | return this.originalXp; 32 | } 33 | 34 | public int getXpDropAmount(ServerPlayer player) { 35 | YigdConfig config = YigdConfig.getConfig(); 36 | 37 | double totalExperience = this.getTotalExperience(player); 38 | int percentageDrop = (int) ((config.expConfig.dropPercentage / 100f) * totalExperience); 39 | int vanillaDrop = player.getExperienceReward(player.serverLevel(), null); 40 | return switch (config.expConfig.dropBehaviour) { 41 | case BEST_OF_BOTH -> Math.max(vanillaDrop, percentageDrop); 42 | case WORST_OF_BOTH -> Math.min(vanillaDrop, percentageDrop); 43 | case PERCENTAGE -> percentageDrop; 44 | case VANILLA -> vanillaDrop; 45 | }; 46 | } 47 | 48 | private double getTotalExperience(ServerPlayer player) { 49 | // This for some reason works more reliably than to get player.totalExperience directly 50 | int currentLevel = player.experienceLevel; 51 | double totalExperience; 52 | if (currentLevel >= 32) { 53 | totalExperience = 4.5 * Math.pow(currentLevel, 2) - 162.5 * currentLevel + 2220; 54 | } else if (currentLevel >= 17) { 55 | totalExperience = 2.5 * Math.pow(currentLevel, 2) - 40.5 * currentLevel + 360; 56 | } else { 57 | totalExperience = Math.pow(currentLevel, 2) + 6 * currentLevel; 58 | } 59 | totalExperience += player.getXpNeededForNextLevel() * player.experienceProgress; 60 | return totalExperience; 61 | } 62 | 63 | public int getXpLevel() { 64 | return ExpComponent.xpToLevels(this.storedXp); 65 | } 66 | 67 | public boolean isEmpty() { 68 | return this.storedXp == 0; 69 | } 70 | 71 | public ExpComponent getSoulboundExp() { 72 | YigdConfig config = YigdConfig.getConfig(); 73 | float soulboundFactor = config.expConfig.keepPercentage / 100f; 74 | int keepXp = (int) (this.originalXp * soulboundFactor); 75 | 76 | return new ExpComponent(keepXp, this.originalXp); 77 | } 78 | 79 | public void dropAll(ServerLevel world, Vec3 pos) { 80 | ExperienceOrb.award(world, pos, this.storedXp); 81 | } 82 | 83 | public void applyToPlayer(ServerPlayer player) { 84 | player.giveExperiencePoints(this.storedXp); 85 | } 86 | 87 | public void clear() { 88 | this.storedXp = 0; 89 | } 90 | 91 | public CompoundTag toNbt() { 92 | CompoundTag nbt = new CompoundTag(); 93 | nbt.putInt("value", this.storedXp); 94 | nbt.putDouble("original", this.originalXp); 95 | return nbt; 96 | } 97 | 98 | public static ExpComponent fromNbt(CompoundTag nbt) { 99 | int xpToDrop = nbt.getInt("value"); 100 | double originalXp = nbt.contains("original") ? nbt.getDouble("original") : xpToDrop; 101 | return new ExpComponent(xpToDrop, originalXp); 102 | } 103 | 104 | public static void clearXp(ServerPlayer player) { 105 | player.totalExperience = 0; 106 | player.experienceLevel = 0; 107 | player.experienceProgress = 0; 108 | } 109 | 110 | public static int xpToLevels(int totalXp) { 111 | int i; 112 | for (i = 0; totalXp >= 0; i++) { 113 | if (i < 16) { 114 | totalXp -= (2 * i) + 7; 115 | } else if(i < 31) { 116 | totalXp -= (5 * i) - 38; 117 | } else { 118 | totalXp -= (9 * i) - 158; 119 | } 120 | } 121 | 122 | return i - 1; 123 | } 124 | 125 | public ExpComponent copy() { 126 | return new ExpComponent(this.storedXp, this.originalXp); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/compat/CompatComponent.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.compat; 2 | 3 | import com.b1n_ry.yigd.components.InventoryComponent; 4 | import com.b1n_ry.yigd.data.DeathContext; 5 | import com.b1n_ry.yigd.data.GraveItem; 6 | import com.b1n_ry.yigd.util.DropRule; 7 | import com.b1n_ry.yigd.util.GraveItemModificationConsumer; 8 | import net.minecraft.core.HolderLookup; 9 | import net.minecraft.core.NonNullList; 10 | import net.minecraft.nbt.CompoundTag; 11 | import net.minecraft.server.level.ServerLevel; 12 | import net.minecraft.server.level.ServerPlayer; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.phys.Vec3; 15 | 16 | import java.util.function.Predicate; 17 | 18 | public abstract class CompatComponent { 19 | protected T inventory; 20 | 21 | public CompatComponent(ServerPlayer player) { 22 | this.inventory = this.getInventory(player); 23 | } 24 | public CompatComponent(T inventory) { 25 | this.inventory = inventory; 26 | } 27 | 28 | public abstract T getInventory(ServerPlayer player); 29 | 30 | /** 31 | * If curse of binding has any effect in this inventory, this method should be overwritten to remove those items 32 | * @param playerRef Player reference to check items that can or can't be unequipped 33 | * @return The removed curse of binding items 34 | */ 35 | public NonNullList pullBindingCurseItems(ServerPlayer playerRef) { 36 | return NonNullList.create(); 37 | } 38 | 39 | /** 40 | * Slaps the merging component on top of the current one. If any item is occupied in current component, merging 41 | * component should add the item that would've gone in that slot to the returning list 42 | * Component should be filtered based on drop rule before calling this, so different drop rules on items does not matter in this method 43 | * @param mergingComponent Component that will merge. REQUIRED TO BE OF SAME INSTANCE AS THIS COMPONENT 44 | * @param merger The player that is merging the components. DO NOT MODIFY THE PLAYER INVENTORY IN THIS METHOD 45 | * @return A list with all items that couldn't be merged from merging component 46 | */ 47 | public abstract NonNullList merge(CompatComponent mergingComponent, ServerPlayer merger); 48 | public abstract NonNullList storeToPlayer(ServerPlayer player); 49 | 50 | /** 51 | * Handle drop rules for each item or whatever the component holds 52 | * 53 | * @param context How the player died 54 | */ 55 | public abstract void handleDropRules(DeathContext context); 56 | 57 | /** 58 | * Get all items as a {@link NonNullList} of {@link GraveItem} containing {@link ItemStack} and {@link DropRule} in the component. 59 | * The drop rule refers to what drop rule was/will be applied on death. 60 | * @return GraveItems containing all items in the component INCLUDING EMPTY ITEMS 61 | */ 62 | public abstract NonNullList getAsGraveItemList(); 63 | public abstract CompatComponent filterInv(Predicate predicate); 64 | public abstract boolean removeItem(Predicate predicate, int itemCount); 65 | 66 | /** 67 | * Drop all items in the component to the world 68 | * @param world The world to drop items in 69 | * @param pos The position to drop items at 70 | */ 71 | public void dropItems(ServerLevel world, Vec3 pos) { 72 | NonNullList items = this.getAsGraveItemList(); 73 | for (GraveItem graveItem : items) { 74 | ItemStack stack = graveItem.stack; 75 | if (stack.isEmpty()) continue; 76 | 77 | InventoryComponent.dropItemIfToBeDropped(graveItem.stack, pos.x, pos.y, pos.z, world); 78 | } 79 | } 80 | 81 | /** 82 | * Drop items in the component to the world, but only items that should be placed in a grave or dropped anyway 83 | * @param world The world to drop items in 84 | * @param pos The position to drop items at 85 | */ 86 | public void dropGraveItems(ServerLevel world, Vec3 pos) { 87 | NonNullList items = this.getAsGraveItemList(); 88 | for (GraveItem graveItem : items) { 89 | ItemStack stack = graveItem.stack; 90 | if (stack.isEmpty() || graveItem.dropRule == DropRule.KEEP || graveItem.dropRule == DropRule.DESTROY) continue; 91 | graveItem.dropRule = DropRule.DROP; // Make sure item are marked as dropped, and not in a non-existent grave 92 | 93 | InventoryComponent.dropItemIfToBeDropped(graveItem.stack, pos.x, pos.y, pos.z, world); 94 | } 95 | } 96 | public abstract void clear(); 97 | 98 | /** 99 | * Check if the component contains any items that should be placed in a grave (according to drop rules) 100 | * @return Whether the component contains any items that should be placed in a grave 101 | */ 102 | public boolean containsGraveItems() { 103 | for (GraveItem graveItem : this.getAsGraveItemList()) { 104 | if (!graveItem.stack.isEmpty() && graveItem.dropRule == DropRule.PUT_IN_GRAVE) return true; 105 | } 106 | return false; 107 | } 108 | 109 | public boolean isEmpty() { 110 | return !this.containsAny(stack -> !stack.isEmpty()); 111 | } 112 | 113 | /** 114 | * Check if the component contains at least one item that matches the predicate. Will stop at first match 115 | * @param predicate Predicate to test items against 116 | * @return Whether the component contains at least one item that matches the predicate 117 | */ 118 | public boolean containsAny(Predicate predicate) { 119 | for (GraveItem graveItem : this.getAsGraveItemList()) { 120 | if (predicate.test(graveItem.stack)) return true; 121 | } 122 | return false; 123 | } 124 | public void handleGraveItems(GraveItemModificationConsumer modification) { 125 | for (GraveItem graveItem : this.getAsGraveItemList()) { 126 | modification.accept(graveItem.stack, -1, graveItem); 127 | } 128 | } 129 | public abstract CompoundTag writeNbt(HolderLookup.Provider registries); 130 | } 131 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # You're in Grave Danger 2.0.14 2 | 3 | ### Changes 4 | * Grave select GUI has now inverted order to view the latest death at the top 5 | 6 | ### Fixes 7 | * GUI scroll bars will now work no matter how fast you scroll 8 | 9 | --- 10 | 11 | # You're in Grave Danger 2.0.13 12 | 13 | ### Fixes 14 | * Accessories can now handle stored accessories data with more slots than the player has (in case of updates) 15 | 16 | --- 17 | 18 | # You're in Grave Danger 2.0.12 19 | 20 | ### Changes 21 | * Added configurability to minimum grave spawn height per-dimension 22 | 23 | ### Fixes 24 | * Graves replacing damaging blocks will now disappear when claiming the grave 25 | (if config is set to do so) 26 | * Fix crash with weird item-data which would lead to graves being rendered 27 | useless. 28 | * Visibility on accessories stay toggled after death 29 | * Fix soulbound items being deleted when dying due to apotheosis executions 30 | 31 | --- 32 | 33 | # You're in Grave Danger 2.0.11 34 | 35 | ### Changes 36 | * Added config option for treating curse of binding items with soulbound on respawn 37 | * Changed keep percentage of XP to be a percentage of the total XP, instead of 38 | percentage of "stored in grave"-xp 39 | 40 | ### Fixes 41 | * Removed dupe of accessories when configured to not put them in the grave 42 | * When non-standard drop rules are changed so a grave becomes empty, the 43 | grave will no longer be generated if empty grave generation is disabled 44 | 45 | --- 46 | 47 | # You're in Grave Danger 2.0.10 48 | 49 | ### Changes 50 | * Mods adding external inventories, without specific compat, should now have their items 51 | handled by YiGD (meaning they might get stored in a grave instead of dropping) 52 | * Item-loss can now be weighted to prefer selection items for destruction of larger stacks 53 | * Losing individual items though item loss will now be registered as destroyed by the 54 | backup system 55 | * Item-loss can be configured to apply other drop rules than the DESTROY drop rule 56 | * Added some common soulbound enchantments to standard soulbound enchantment tag 57 | 58 | ### Fixes 59 | * Fixed random error when applying item loss to modded inventories, which would cause death 60 | handling to be ignored 61 | * Graveyards will no longer try to generate graves where graves have already been generated 62 | * Graveyards can now successfully generate graves cross-dimensionally 63 | * When graves drop items on the ground when destroyed/claimed, it will now only contain 64 | items contained in the grave (no soulbound, already dropped, or destroyed items) 65 | 66 | --- 67 | 68 | # You're in Grave Danger 2.0.9 69 | 70 | ### Changes 71 | * Config values for glowing graves, glowing grave distance, breakable graves, and 72 | death sight enchantment range are now properly synced to the server 73 | 74 | ### Fixes 75 | * Item loss is now applied after checking items required for generating a grave 76 | 77 | --- 78 | 79 | # You're in Grave Danger 2.0.8 80 | 81 | ### Changes 82 | * Item loss can now optionally be applied to modded inventories 83 | * Soulbound can now be enchanted on all curios items 84 | * Added configurable use time and cooldown for death scroll 85 | * Replaced the `onlyMurderer` rob config with `killerSkipWaitTime` 86 | allowing the killer of a player to skip the grave robbing cooldown 87 | 88 | ### Fixes 89 | * Made graves indestructible to a lot of ways they could be destroyed by previously 90 | * Item loss will now not try and remove the same item twice, and count it as 2 91 | items (more reliable how much is lost) 92 | * Running the /clear command after retrieving items from a grave no longer clears 93 | the grave backup 94 | * Soulbound now works with curios 95 | * Graves being moved (like with carry-on mod) will now be detected when they reappear 96 | * Fixed crash with travelers backpack 97 | * Now prevents "fake players" from looting graves 98 | 99 | --- 100 | 101 | # You're in Grave Danger 2.0.7 102 | 103 | ### Changes 104 | * Added compat with [Cosmetic Armor Reworked](https://www.curseforge.com/minecraft/mc-mods/cosmetic-armor-reworked) 105 | 106 | ### Fixes 107 | * Graves will no longer overwrite other graves when dying in exact same places 108 | outside the world 109 | 110 | --- 111 | 112 | # You're in Grave Danger 2.0.6 113 | 114 | ### Changes 115 | * Added new config to look downward for ground to place a grave on, when dying in 116 | the air 117 | * Changed default max grave count per player to 100 in the config (previously 50) 118 | * Improved rendering efficiency in yigd GUIs with a scroll-bar 119 | 120 | ### Fixes 121 | * If dying for the first time in a world with an empty inventory, players will 122 | no longer be disconnected. 123 | * Grave data should no longer be generated for one player in two profiles 124 | 125 | --- 126 | 127 | # You're in Grave Danger 2.0.5 128 | 129 | ### Fixes 130 | * Game no longer has a chance to crash when loading in with traveler's backpack and accessories 131 | 132 | --- 133 | 134 | # You're in Grave Danger 2.0.4 135 | 136 | ### Fixes 137 | * Graves can now generate below y=0 (if blocks can exist there) when `generateGraveInVoid` 138 | config is set to `false` 139 | * When selecting graves in the GUI, it will no longer tell you that you have your xp point 140 | total number of levels 141 | * Empty graves will now generate if they are configured to 142 | * Graves will no longer delete modded inventory contents from graves when restarting the 143 | instance. 144 | 145 | --- 146 | 147 | # You're in Grave Danger 2.0.3 148 | 149 | ### Fixes 150 | * The mod can now launch when using a dedicated server 151 | 152 | --- 153 | 154 | # You're in Grave Danger 2.0.2 155 | 156 | ### Fixes 157 | * Added a translation for enabling the soulbound enchantment in the configs 158 | * Improved accessories compat implementation (thanks @Dragon-Seeker!) 159 | 160 | --- 161 | 162 | # You're in Grave Danger 2.0.1 163 | 164 | ### Fixes 165 | * Fixed issue related to graves clearing your inventory when trying to claim it, with a 166 | specific difference of curios/accessories slots between the grave and your own inventory 167 | 168 | ### Changes 169 | * Added syncing from client claim priority configs to server 170 | 171 | --- 172 | 173 | # You're in Grave Danger 2.0.0 for NeoForge 174 | 175 | ### Changes 176 | * Ported to NeoForge -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/compat/TravelersBackpackCompat.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.compat; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.components.InventoryComponent; 5 | import com.b1n_ry.yigd.config.CompatConfig; 6 | import com.b1n_ry.yigd.config.YigdConfig; 7 | import com.b1n_ry.yigd.data.DeathContext; 8 | import com.b1n_ry.yigd.data.GraveItem; 9 | import com.b1n_ry.yigd.events.YigdEvents; 10 | import com.b1n_ry.yigd.util.DropRule; 11 | import com.tiviacz.travelersbackpack.TravelersBackpack; 12 | import com.tiviacz.travelersbackpack.capability.AttachmentUtils; 13 | import com.tiviacz.travelersbackpack.capability.ITravelersBackpack; 14 | import net.minecraft.core.HolderLookup; 15 | import net.minecraft.core.NonNullList; 16 | import net.minecraft.nbt.CompoundTag; 17 | import net.minecraft.server.level.ServerPlayer; 18 | import net.minecraft.world.item.ItemStack; 19 | import net.neoforged.neoforge.common.NeoForge; 20 | 21 | import java.util.function.Predicate; 22 | 23 | public class TravelersBackpackCompat implements InvModCompat { 24 | public static boolean isIntegrationEnabled() { 25 | try { 26 | return TravelersBackpack.enableIntegration(); 27 | } 28 | catch (Exception | Error e) { 29 | return false; 30 | } 31 | } 32 | 33 | @Override 34 | public String getModName() { 35 | return "travelers backpack"; 36 | } 37 | 38 | @Override 39 | public void clear(ServerPlayer player) { 40 | AttachmentUtils.getAttachment(player).ifPresent(ITravelersBackpack::removeWearable); 41 | } 42 | 43 | @Override 44 | public CompatComponent readNbt(CompoundTag nbt, HolderLookup.Provider registries) { 45 | ItemStack stack = ItemStack.parse(registries, nbt).orElse(ItemStack.EMPTY); 46 | 47 | DropRule dropRule; 48 | if (nbt.contains("dropRule")) { 49 | dropRule = DropRule.valueOf(nbt.getString("dropRule")); 50 | } else { 51 | dropRule = YigdConfig.getConfig().compatConfig.defaultTravelersBackpackDropRule; 52 | } 53 | return new TBCompatComponent(new GraveItem(stack, dropRule)); 54 | } 55 | 56 | @Override 57 | public CompatComponent getNewComponent(ServerPlayer player) { 58 | return new TBCompatComponent(player); 59 | } 60 | 61 | private static class TBCompatComponent extends CompatComponent { 62 | 63 | public TBCompatComponent(ServerPlayer player) { 64 | super(player); 65 | } 66 | 67 | public TBCompatComponent(GraveItem inventory) { 68 | super(inventory); 69 | } 70 | 71 | @Override 72 | public GraveItem getInventory(ServerPlayer player) { 73 | DropRule defaultDropRule = YigdConfig.getConfig().compatConfig.defaultTravelersBackpackDropRule; 74 | ItemStack stack = AttachmentUtils.getWearingBackpack(player); 75 | return stack == null ? InventoryComponent.EMPTY_GRAVE_ITEM : new GraveItem(stack, defaultDropRule); 76 | } 77 | 78 | @Override 79 | public NonNullList merge(CompatComponent mergingComponent, ServerPlayer merger) { 80 | NonNullList extraItems = NonNullList.create(); 81 | 82 | GraveItem graveItem = (GraveItem) mergingComponent.inventory; 83 | ItemStack mergingStack = graveItem.stack; 84 | ItemStack currentStack = this.inventory.stack; 85 | 86 | if (mergingStack.isEmpty()) return extraItems; 87 | 88 | if (!currentStack.isEmpty()) { 89 | extraItems.add(graveItem); 90 | return extraItems; 91 | } 92 | 93 | this.inventory = new GraveItem(mergingStack, graveItem.dropRule); 94 | return extraItems; 95 | } 96 | 97 | @Override 98 | public NonNullList storeToPlayer(ServerPlayer player) { 99 | if (this.inventory.stack.isEmpty()) return NonNullList.create(); 100 | 101 | AttachmentUtils.equipBackpack(player, this.inventory.stack.copy()); 102 | 103 | return NonNullList.create(); 104 | } 105 | 106 | @Override 107 | public void handleDropRules(DeathContext context) { 108 | CompatConfig compatConfig = YigdConfig.getConfig().compatConfig; 109 | 110 | DropRule dropRule = compatConfig.defaultTravelersBackpackDropRule; 111 | 112 | ItemStack stack = this.inventory.stack; 113 | if (stack.isEmpty()) return; 114 | 115 | if (dropRule == DropRule.PUT_IN_GRAVE) 116 | dropRule = NeoForge.EVENT_BUS.post(new YigdEvents.DropRuleEvent(stack, -1, context, true)).getDropRule(); 117 | 118 | this.inventory.dropRule = dropRule; 119 | } 120 | 121 | @Override 122 | public NonNullList getAsGraveItemList() { 123 | NonNullList stacks = NonNullList.create(); 124 | stacks.add(this.inventory); 125 | return stacks; 126 | } 127 | 128 | @Override 129 | public CompatComponent filterInv(Predicate predicate) { 130 | GraveItem graveItem; 131 | if (predicate.test(this.inventory.dropRule)) { 132 | graveItem = this.inventory; 133 | } else { 134 | graveItem = InventoryComponent.EMPTY_GRAVE_ITEM; 135 | } 136 | return new TBCompatComponent(graveItem); 137 | } 138 | 139 | @Override 140 | public boolean removeItem(Predicate predicate, int itemCount) { 141 | ItemStack stack = this.inventory.stack; 142 | if (predicate.test(stack)) { 143 | stack.shrink(itemCount); 144 | return true; 145 | } 146 | return false; 147 | } 148 | 149 | @Override 150 | public void clear() { 151 | this.inventory = InventoryComponent.EMPTY_GRAVE_ITEM; 152 | } 153 | 154 | @Override 155 | public CompoundTag writeNbt(HolderLookup.Provider registries) { 156 | try { 157 | CompoundTag nbt = !this.inventory.stack.isEmpty() 158 | ? (CompoundTag) this.inventory.stack.save(registries) 159 | : new CompoundTag(); 160 | 161 | nbt.putString("dropRule", this.inventory.dropRule.name()); 162 | return nbt; 163 | } 164 | catch (Exception e) { 165 | Yigd.LOGGER.error("Error while converting item to NBT: {}", this.inventory.stack, e); 166 | return new CompoundTag(); 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/YigdResourceHandler.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.block.GraveBlock; 5 | import com.b1n_ry.yigd.client.render.GraveBlockEntityRenderer; 6 | import com.b1n_ry.yigd.components.GraveComponent; 7 | import com.b1n_ry.yigd.data.GraveyardData; 8 | import com.google.gson.*; 9 | import net.minecraft.core.Vec3i; 10 | import net.minecraft.resources.ResourceLocation; 11 | import net.minecraft.server.packs.resources.Resource; 12 | import net.minecraft.server.packs.resources.ResourceManager; 13 | import net.minecraft.server.packs.resources.ResourceManagerReloadListener; 14 | import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; 15 | import net.neoforged.neoforge.event.AddReloadListenerEvent; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.util.List; 21 | 22 | public class YigdResourceHandler { 23 | private static final Gson GSON = new GsonBuilder() 24 | .registerTypeAdapter(ResourceLocation.class, (JsonDeserializer) (elem, type, context) -> ResourceLocation.parse(elem.getAsString())) 25 | .registerTypeAdapter(Vec3i.class, (JsonDeserializer) (elem, type, context) -> new Vec3i( 26 | elem.getAsJsonArray().get(0).getAsInt(), 27 | elem.getAsJsonArray().get(1).getAsInt(), 28 | elem.getAsJsonArray().get(2).getAsInt())) 29 | .create(); 30 | 31 | public static void serverDataEvent(AddReloadListenerEvent event) { 32 | event.addListener(new GraveServerModelLoader()); 33 | event.addListener(new GraveyardDataLoader()); 34 | event.addListener(new GraveAreaOverrideLoader()); 35 | } 36 | public static void clientResourceEvent(RegisterClientReloadListenersEvent event) { 37 | event.registerReloadListener(new GraveResourceLoader()); 38 | } 39 | 40 | public static class GraveResourceLoader implements ResourceManagerReloadListener { 41 | @Override 42 | public void onResourceManagerReload(ResourceManager manager) { 43 | ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "models/block/grave.json"); 44 | List resources = manager.getResourceStack(resourceLocation); 45 | 46 | for (Resource resource : resources) { 47 | try (InputStream is = resource.open()) { 48 | Yigd.LOGGER.info("Reloading grave model (client)"); 49 | JsonObject resourceJson = (JsonObject) JsonParser.parseReader(new InputStreamReader(is)); 50 | GraveBlockEntityRenderer.reloadModelFromJson(resourceJson); 51 | GraveBlock.reloadShapeFromJson(resourceJson); 52 | 53 | Yigd.LOGGER.info("Grave model and shape reload successful (client)"); 54 | } 55 | catch (IOException | ClassCastException | NullPointerException e) { 56 | Yigd.LOGGER.error("Could not load resource `%s` from resource pack `%s`".formatted(resourceLocation, resource.sourcePackId()), e); 57 | } 58 | } 59 | } 60 | } 61 | private static class GraveServerModelLoader implements ResourceManagerReloadListener { 62 | @Override 63 | public void onResourceManagerReload(ResourceManager manager) { 64 | ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "custom/grave_shape.json"); 65 | List resources = manager.getResourceStack(resourceLocation); 66 | 67 | for (Resource resource : resources) { 68 | try (InputStream is = resource.open()) { 69 | Yigd.LOGGER.info("Reloading grave shape (server)"); 70 | JsonObject resourceJson = (JsonObject) JsonParser.parseReader(new InputStreamReader(is)); 71 | GraveBlock.reloadShapeFromJson(resourceJson); 72 | 73 | Yigd.LOGGER.info("Grave model and shape reload successful (server)"); 74 | } 75 | catch (IOException | ClassCastException | NullPointerException e) { 76 | Yigd.LOGGER.error("Could not load resource `%s` from datapack `%s`".formatted(resourceLocation, resource.sourcePackId()), e); 77 | } 78 | } 79 | } 80 | } 81 | private static class GraveyardDataLoader implements ResourceManagerReloadListener { 82 | @Override 83 | public void onResourceManagerReload(ResourceManager manager) { 84 | ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "custom/graveyard.json"); 85 | List resources = manager.getResourceStack(resourceLocation); 86 | 87 | for (Resource resource : resources) { 88 | try (InputStream is = resource.open()) { 89 | Yigd.LOGGER.info("Reloading YIGD graveyard data (server)"); 90 | GraveComponent.graveyardData = GSON.fromJson(new InputStreamReader(is), GraveyardData.class); 91 | GraveComponent.graveyardData.handlePoint2Point(); 92 | 93 | Yigd.LOGGER.info("Graveyard data successfully reloaded (server)"); 94 | } 95 | catch (IOException | ClassCastException | NullPointerException e) { 96 | Yigd.LOGGER.error("Could not load resource `%s` from datapack `%s`".formatted(resourceLocation, resource.sourcePackId()), e); 97 | } 98 | } 99 | } 100 | } 101 | private static class GraveAreaOverrideLoader implements ResourceManagerReloadListener { 102 | @Override 103 | public void onResourceManagerReload(ResourceManager manager) { 104 | ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(Yigd.MOD_ID, "custom/grave_areas.json"); 105 | List resources = manager.getResourceStack(resourceLocation); 106 | 107 | for (Resource resource : resources) { 108 | try (InputStream is = resource.open()) { 109 | Yigd.LOGGER.info("Reloading YIGD grave area overrides (server)"); 110 | GraveOverrideAreas.INSTANCE = GSON.fromJson(new InputStreamReader(is), GraveOverrideAreas.class); 111 | 112 | Yigd.LOGGER.info("Grave area overrides successfully reloaded (server)"); 113 | } 114 | catch (IOException | ClassCastException | NullPointerException e) { 115 | Yigd.LOGGER.error("Could not load resource `%s` from datapack `%s`".formatted(resourceLocation, resource.sourcePackId()), e); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/compat/CosmeticArmorCompat.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.compat; 2 | 3 | import com.b1n_ry.yigd.components.InventoryComponent; 4 | import com.b1n_ry.yigd.config.CompatConfig; 5 | import com.b1n_ry.yigd.config.YigdConfig; 6 | import com.b1n_ry.yigd.data.DeathContext; 7 | import com.b1n_ry.yigd.data.GraveItem; 8 | import com.b1n_ry.yigd.events.YigdEvents; 9 | import com.b1n_ry.yigd.util.DropRule; 10 | import lain.mods.cos.impl.ModObjects; 11 | import lain.mods.cos.impl.inventory.InventoryCosArmor; 12 | import net.minecraft.core.HolderLookup; 13 | import net.minecraft.core.NonNullList; 14 | import net.minecraft.nbt.CompoundTag; 15 | import net.minecraft.server.level.ServerPlayer; 16 | import net.minecraft.world.item.ItemStack; 17 | import net.neoforged.neoforge.common.NeoForge; 18 | 19 | import java.util.function.Predicate; 20 | 21 | public class CosmeticArmorCompat implements InvModCompat> { 22 | @Override 23 | public String getModName() { 24 | return "cosmeticarmor"; 25 | } 26 | 27 | @Override 28 | public void clear(ServerPlayer player) { 29 | InventoryCosArmor inv = ModObjects.invMan.getCosArmorInventory(player.getUUID()); 30 | inv.clearContent(); 31 | } 32 | 33 | @Override 34 | public CompatComponent> readNbt(CompoundTag nbt, HolderLookup.Provider registries) { 35 | NonNullList items = InventoryComponent.listFromNbt(nbt, itemTag -> { 36 | DropRule dropRule = DropRule.valueOf(itemTag.getString("dropRule")); 37 | ItemStack stack = ItemStack.parse(registries, itemTag).orElse(ItemStack.EMPTY); 38 | return new GraveItem(stack, dropRule); 39 | }, InventoryComponent.EMPTY_GRAVE_ITEM); 40 | return new CosmeticArmorCompatComponent(items); 41 | } 42 | 43 | @Override 44 | public CompatComponent> getNewComponent(ServerPlayer player) { 45 | return new CosmeticArmorCompatComponent(player); 46 | } 47 | 48 | static class CosmeticArmorCompatComponent extends CompatComponent> { 49 | public CosmeticArmorCompatComponent(ServerPlayer player) { 50 | super(player); 51 | } 52 | public CosmeticArmorCompatComponent(NonNullList inventory) { 53 | super(inventory); 54 | } 55 | 56 | @Override 57 | public NonNullList getInventory(ServerPlayer player) { 58 | InventoryCosArmor inventory = ModObjects.invMan.getCosArmorInventory(player.getUUID()); 59 | NonNullList list = NonNullList.create(); 60 | for (int i = 0; i < inventory.getContainerSize(); i++) { 61 | ItemStack stack = inventory.getItem(i); 62 | list.add(new GraveItem(stack, DropRule.PUT_IN_GRAVE)); 63 | } 64 | return list; 65 | } 66 | 67 | @Override 68 | public NonNullList storeToPlayer(ServerPlayer player) { 69 | NonNullList extraItems = NonNullList.create(); 70 | InventoryCosArmor cosArmor = ModObjects.invMan.getCosArmorInventory(player.getUUID()); 71 | 72 | for (int i = 0; i < cosArmor.getContainerSize(); i++) { 73 | if (i >= this.inventory.size()) break; 74 | GraveItem graveItem = this.inventory.get(i).copy(); 75 | if (cosArmor.getItem(i).isEmpty()) { 76 | cosArmor.setItem(i, graveItem.stack); 77 | } else { 78 | extraItems.add(graveItem.stack); 79 | } 80 | } 81 | return extraItems; 82 | } 83 | 84 | @Override 85 | public void handleDropRules(DeathContext context) { 86 | CompatConfig compatConfig = YigdConfig.getConfig().compatConfig; 87 | 88 | for (GraveItem graveItem : this.inventory) { 89 | if (graveItem.stack.isEmpty()) continue; 90 | DropRule dropRule = compatConfig.defaultCosmeticArmorDropRule; 91 | 92 | if (dropRule != DropRule.PUT_IN_GRAVE) continue; 93 | dropRule = NeoForge.EVENT_BUS.post(new YigdEvents.DropRuleEvent(graveItem.stack, -1, context, true)).getDropRule(); 94 | 95 | graveItem.dropRule = dropRule; 96 | } 97 | } 98 | 99 | @Override 100 | public NonNullList getAsGraveItemList() { 101 | return NonNullList.copyOf(this.inventory); 102 | } 103 | 104 | @Override 105 | public void clear() { 106 | this.inventory.clear(); 107 | } 108 | 109 | @Override 110 | public CompoundTag writeNbt(HolderLookup.Provider registries) { 111 | return InventoryComponent.listToNbt(this.inventory, graveItem -> { 112 | CompoundTag itemTag = (CompoundTag) graveItem.stack.save(registries); 113 | itemTag.putString("dropRule", graveItem.dropRule.toString()); 114 | return itemTag; 115 | }, graveItem -> graveItem.stack.isEmpty()); 116 | } 117 | 118 | @Override 119 | public boolean removeItem(Predicate predicate, int itemCount) { 120 | for (GraveItem graveItem : this.inventory) { 121 | ItemStack stack = graveItem.stack; 122 | if (predicate.test(stack)) { 123 | stack.shrink(itemCount); 124 | return true; 125 | } 126 | } 127 | return false; 128 | } 129 | 130 | @Override 131 | public CompatComponent> filterInv(Predicate predicate) { 132 | NonNullList list = NonNullList.create(); 133 | for (GraveItem graveItem : this.inventory) { 134 | if (predicate.test(graveItem.dropRule)) { 135 | list.add(graveItem); 136 | } else { 137 | list.add(InventoryComponent.EMPTY_GRAVE_ITEM); 138 | } 139 | } 140 | return new CosmeticArmorCompatComponent(list); 141 | } 142 | 143 | @Override 144 | public NonNullList merge(CompatComponent mergingComponent, ServerPlayer merger) { 145 | NonNullList extraItems = NonNullList.create(); 146 | 147 | @SuppressWarnings("unchecked") 148 | NonNullList mergingItems = (NonNullList) mergingComponent.inventory; 149 | for (int i = 0; i < mergingItems.size(); i++) { 150 | GraveItem graveItem = mergingItems.get(i); 151 | if (i >= this.inventory.size()) { 152 | extraItems.add(graveItem); 153 | continue; 154 | } 155 | ItemStack thisStack = this.inventory.get(i).stack; 156 | if (thisStack.isEmpty()) { 157 | this.inventory.set(i, graveItem); 158 | } else { 159 | extraItems.add(graveItem); 160 | } 161 | } 162 | return extraItems; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/components/RespawnComponent.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.components; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.ExtraFeaturesConfig; 5 | import com.b1n_ry.yigd.config.RespawnConfig; 6 | import com.b1n_ry.yigd.config.YigdConfig; 7 | import com.b1n_ry.yigd.data.DeathInfoManager; 8 | import com.b1n_ry.yigd.util.GraveCompassHelper; 9 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 10 | import net.minecraft.core.HolderLookup; 11 | import net.minecraft.core.NonNullList; 12 | import net.minecraft.core.component.DataComponents; 13 | import net.minecraft.core.registries.BuiltInRegistries; 14 | import net.minecraft.nbt.CompoundTag; 15 | import net.minecraft.nbt.NbtUtils; 16 | import net.minecraft.resources.ResourceLocation; 17 | import net.minecraft.server.level.ServerLevel; 18 | import net.minecraft.server.level.ServerPlayer; 19 | import net.minecraft.world.item.Item; 20 | import net.minecraft.world.item.ItemStack; 21 | import net.minecraft.world.item.component.CustomData; 22 | import net.minecraft.world.item.component.ResolvableProfile; 23 | import org.jetbrains.annotations.NotNull; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import java.util.List; 27 | 28 | public class RespawnComponent { 29 | @Nullable 30 | private InventoryComponent soulboundInventory; 31 | @Nullable 32 | private ExpComponent soulboundExp; 33 | private final EffectComponent respawnEffects; 34 | 35 | private boolean graveGenerated = false; 36 | 37 | public RespawnComponent(ServerPlayer player) { 38 | this.respawnEffects = new EffectComponent(player); 39 | } 40 | private RespawnComponent(@Nullable InventoryComponent soulboundInventory, @Nullable ExpComponent expComponent, EffectComponent effectComponent) { 41 | this.soulboundInventory = soulboundInventory; 42 | this.respawnEffects = effectComponent; 43 | this.soulboundExp = expComponent; 44 | } 45 | 46 | public void setSoulboundInventory(@NotNull InventoryComponent component) { 47 | this.soulboundInventory = component; 48 | } 49 | public void setSoulboundExp(@NotNull ExpComponent component) { 50 | this.soulboundExp = component; 51 | } 52 | public @Nullable ExpComponent getSoulboundExp() { 53 | return this.soulboundExp; 54 | } 55 | 56 | public void setGraveGenerated(boolean graveGenerated) { 57 | this.graveGenerated = graveGenerated; 58 | } 59 | public boolean wasGraveGenerated() { 60 | return this.graveGenerated; 61 | } 62 | 63 | public void primeForRespawn(ResolvableProfile profile) { 64 | DeathInfoManager.INSTANCE.addRespawnComponent(profile, this); 65 | DeathInfoManager.INSTANCE.setDirty(); 66 | } 67 | 68 | public boolean isEmpty() { 69 | return (this.soulboundInventory == null || this.soulboundInventory.isEmpty()) 70 | && (this.soulboundExp == null || this.soulboundExp.isEmpty()); 71 | } 72 | 73 | public void apply(ServerPlayer player) { 74 | if (this.soulboundInventory != null) { 75 | NonNullList extraItems = NonNullList.create(); 76 | if (YigdConfig.getConfig().respawnConfig.treatBindingCurse) { 77 | extraItems.addAll(this.soulboundInventory.pullBindingCurseItems(player)); 78 | } 79 | extraItems.addAll(this.soulboundInventory.applyToPlayer(player)); 80 | 81 | double x = player.getX(); 82 | double y = player.getY(); 83 | double z = player.getZ(); 84 | ServerLevel world = player.serverLevel(); 85 | for (ItemStack stack : extraItems) { 86 | InventoryComponent.dropItemIfToBeDropped(stack, x, y, z, world); 87 | } 88 | } 89 | 90 | YigdConfig config = YigdConfig.getConfig(); 91 | ExtraFeaturesConfig extraFeaturesConfig = config.extraFeatures; 92 | if (extraFeaturesConfig.deathScroll.enabled && extraFeaturesConfig.deathScroll.receiveOnRespawn) { 93 | ItemStack scroll = Yigd.DEATH_SCROLL_ITEM.get().getDefaultInstance(); 94 | boolean turned = Yigd.DEATH_SCROLL_ITEM.get().bindStackToLatestDeath(player, scroll); 95 | if (turned) 96 | player.addItem(scroll); 97 | } 98 | if (extraFeaturesConfig.graveKeys.enabled && extraFeaturesConfig.graveKeys.receiveOnRespawn) { 99 | ItemStack key = Yigd.GRAVE_KEY_ITEM.get().getDefaultInstance(); 100 | boolean turned = Yigd.GRAVE_KEY_ITEM.get().bindStackToLatestGrave(player, key); 101 | if (turned) 102 | player.addItem(key); 103 | } 104 | if (extraFeaturesConfig.graveCompass.receiveOnRespawn) { 105 | List playerGraves = DeathInfoManager.INSTANCE.getBackupData(new ResolvableProfile(player.getGameProfile())); 106 | if (!playerGraves.isEmpty()) { 107 | GraveComponent latestGrave = playerGraves.getLast(); 108 | 109 | GraveCompassHelper.giveCompass(player, latestGrave.getGraveId(), latestGrave.getPos(), latestGrave.getWorldRegistryKey()); 110 | } 111 | } 112 | 113 | for (RespawnConfig.ExtraItemDrop extraItemDrop : config.respawnConfig.extraItemDrops) { 114 | Item item = BuiltInRegistries.ITEM.get(ResourceLocation.parse(extraItemDrop.itemId)); 115 | ItemStack stack = new ItemStack(item, extraItemDrop.count); 116 | try { 117 | if (!extraItemDrop.itemNbt.isEmpty()) 118 | stack.set(DataComponents.CUSTOM_DATA, CustomData.of(NbtUtils.snbtToStructure(extraItemDrop.itemNbt))); 119 | } 120 | catch (CommandSyntaxException e) { 121 | Yigd.LOGGER.error("Could not give an item with NBT to player on respawn. Invalid NBT. Falling back to item without NBT"); 122 | } 123 | player.addItem(stack); 124 | } 125 | 126 | if (this.soulboundExp != null) 127 | this.soulboundExp.applyToPlayer(player); 128 | 129 | this.respawnEffects.applyToPlayer(player); 130 | 131 | // If there is an issue, items don't get duped 132 | DeathInfoManager.INSTANCE.removeRespawnComponent(new ResolvableProfile(player.getGameProfile())); 133 | DeathInfoManager.INSTANCE.setDirty(); 134 | } 135 | 136 | public CompoundTag toNbt(HolderLookup.Provider registryLookup) { 137 | CompoundTag nbt = new CompoundTag(); 138 | 139 | if (this.soulboundInventory != null) nbt.put("inventory", this.soulboundInventory.toNbt(registryLookup)); 140 | if (this.soulboundExp != null) nbt.put("exp", this.soulboundExp.toNbt()); 141 | nbt.put("effects", this.respawnEffects.toNbt()); 142 | 143 | return nbt; 144 | } 145 | 146 | public static RespawnComponent fromNbt(CompoundTag nbt, HolderLookup.Provider registryLookup) { 147 | InventoryComponent soulboundInventory = null; 148 | if (nbt.contains("inventory")) { 149 | CompoundTag inventoryNbt = nbt.getCompound("inventory"); 150 | soulboundInventory = InventoryComponent.fromNbt(inventoryNbt, registryLookup); 151 | } 152 | 153 | ExpComponent expComponent = null; 154 | if (nbt.contains("exp")) { 155 | CompoundTag expNbt = nbt.getCompound("exp"); 156 | expComponent = ExpComponent.fromNbt(expNbt); 157 | } 158 | 159 | CompoundTag effectsNbt = nbt.getCompound("effects"); 160 | EffectComponent effectComponent = EffectComponent.fromNbt(effectsNbt); 161 | 162 | return new RespawnComponent(soulboundInventory, expComponent, effectComponent); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/config/GraveConfig.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.config; 2 | 3 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 4 | import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class GraveConfig { 11 | public boolean enabled = true; 12 | public boolean storeItems = true; 13 | public boolean storeXp = true; 14 | @Comment("Inform player where the grave generated when respawning") 15 | public boolean informGraveLocation = true; 16 | @Comment("If true, you HAVE to have `requiredItemCount` number of `requiredItem` for a grave to generate. That many of that item will then be consumed") 17 | public boolean requireItem = false; 18 | public String requiredItem = "yigd:grave"; 19 | public int requiredItemCount = 1; 20 | // require shovel to open 21 | public boolean requireShovelToLoot = false; 22 | // retrieve method (list with enums) 23 | @ConfigEntry.Gui.CollapsibleObject 24 | public RetrieveMethods retrieveMethods = new RetrieveMethods(); 25 | // merge existing with claimed stacks for player 26 | public boolean mergeStacksOnRetrieve = true; 27 | // drop in inventory or on ground 28 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 29 | public DropType dropOnRetrieve = DropType.IN_INVENTORY; 30 | // drop grave block? 31 | public boolean dropGraveBlock = false; 32 | // No head. Because that's in the inventory module 33 | // generate empty graves 34 | public boolean generateEmptyGraves = false; 35 | // spawn protection rule override? 36 | @Comment("Allows everyone to bypass spawn protection for grave blocks") 37 | public boolean overrideSpawnProtection = true; 38 | // inventory priority 39 | @Comment("Which of the layout in the grave or in your inventory should be prioritized") 40 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 41 | public ClaimPriority claimPriority = ClaimPriority.GRAVE; 42 | // robbing 43 | @ConfigEntry.Gui.CollapsibleObject 44 | public GraveRobbing graveRobbing = new GraveRobbing(); 45 | // timeout/deletion/despawn timer 46 | @ConfigEntry.Gui.CollapsibleObject 47 | public GraveTimeout graveTimeout = new GraveTimeout(); 48 | // curse of binding compat 49 | @Comment("If false, layout prioritizing doesn't care if armor is cursed with binding") 50 | public boolean treatBindingCurse = true; 51 | // generate grave in the void? (which y level then) 52 | public boolean generateGraveInVoid = true; 53 | @Comment("Minimum y-level a grave can spawn in a dimension") 54 | public List minimumGraveYLevel = new ArrayList<>() {{ 55 | add(new MapEntryConfig.IntType("minecraft:overworld", -60)); 56 | add(new MapEntryConfig.IntType("minecraft:the_nether", 3)); 57 | add(new MapEntryConfig.IntType("minecraft:the_end", 3)); 58 | add(new MapEntryConfig.IntType("misc", 3)); 59 | }}; 60 | // Weather or not the grave can generate outside the world border 61 | public boolean generateOnlyWithinBorder = true; 62 | // ignore death types 63 | public List ignoredDeathTypes = new ArrayList<>(); 64 | // unlockable 65 | @Comment("Allow players to unlock their graves through GUI") 66 | public boolean unlockable = true; 67 | // spawn something when opened? 68 | @ConfigEntry.Gui.CollapsibleObject 69 | public RandomSpawn randomSpawn = new RandomSpawn(); 70 | // use last ground position 71 | public boolean generateOnLastGroundPos = false; 72 | public boolean tryGenerateOnGround = false; 73 | // How far in X, Y, and Z the grave can generate from where you died 74 | @ConfigEntry.Gui.CollapsibleObject 75 | public Range generationMaxDistance = new Range(); 76 | // block replacement blacklist/whitelist settings 77 | public boolean useSoftBlockWhitelist = false; 78 | public boolean useStrictBlockBlacklist = true; 79 | // replace old block when claimed 80 | public boolean replaceOldWhenClaimed = true; 81 | public boolean dropItemsIfDestroyed = false; 82 | public boolean notifyOwnerIfDestroyed = true; 83 | // Keep grave after it's looted 84 | @Comment("If true, graves will persist when claiming them, and right clicking on them after that will let you know when and how they died. Can also then be mined") 85 | @ConfigEntry.Gui.CollapsibleObject 86 | public PersistentGraves persistentGraves = new PersistentGraves(); 87 | // grave generation dimension blacklist 88 | public List dimensionBlacklist = new ArrayList<>(); 89 | // block under grave? 90 | @ConfigEntry.Gui.CollapsibleObject 91 | public BlockUnderGrave blockUnderGrave = new BlockUnderGrave(); 92 | // tell people where someone's grave is when they logg off 93 | @Comment("When people leave, should the game let everyone know where they have a grave?") 94 | public boolean sellOutOfflinePeople = false; 95 | // max backups 96 | @Comment("Max amount of backed up graves") 97 | public int maxBackupsPerPerson = 100; 98 | public boolean dropFromOldestWhenDeleted = true; 99 | 100 | public static class RetrieveMethods { 101 | public boolean onClick = true; 102 | public boolean onBreak = false; 103 | public boolean onSneak = false; 104 | public boolean onStand = false; 105 | } 106 | 107 | public static class GraveRobbing { 108 | public boolean enabled = true; 109 | public boolean killerSkipWaitTime = false; 110 | public int afterTime = 1; 111 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 112 | public TimeUnit timeUnit = TimeUnit.HOURS; 113 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 114 | public ClaimPriority robPriority = ClaimPriority.INVENTORY; 115 | public boolean notifyWhenRobbed = true; 116 | public boolean tellWhoRobbed = true; 117 | } 118 | 119 | public static class GraveTimeout { 120 | public boolean enabled = false; 121 | public int afterTime = 5; 122 | @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) 123 | public TimeUnit timeUnit = TimeUnit.HOURS; 124 | public boolean dropContentsOnTimeout = true; 125 | } 126 | 127 | public static class PersistentGraves { 128 | public boolean enabled = false; 129 | public boolean showDeathDay = true; 130 | public boolean showDeathIrlTime = true; 131 | public boolean useAmPm = true; 132 | } 133 | 134 | public static class RandomSpawn { 135 | public int percentSpawnChance = 0; 136 | public String spawnEntity = "minecraft:zombie"; 137 | public String spawnNbt = "{ArmorItems:[{},{},{},{id:\"minecraft:player_head\",components:{\"minecraft:profile\":{name:\"${owner.name}\",id:${owner.uuid}}}}]}"; 138 | } 139 | 140 | public static class Range { 141 | public int x = 5; 142 | public int y = 5; 143 | public int z = 5; 144 | } 145 | 146 | public static class BlockUnderGrave { 147 | public boolean enabled = true; 148 | public List blockInDimensions = new ArrayList<>() {{ 149 | add(new MapEntryConfig.StringType("minecraft:overworld", "minecraft:cobblestone")); 150 | add(new MapEntryConfig.StringType("minecraft:the_nether", "minecraft:soul_soil")); 151 | add(new MapEntryConfig.StringType("minecraft:the_end", "minecraft:end_stone")); 152 | add(new MapEntryConfig.StringType("misc", "minecraft:dirt")); 153 | }}; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/util/GraveCompassHelper.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.util; 2 | 3 | import com.b1n_ry.yigd.Yigd; 4 | import com.b1n_ry.yigd.config.YigdConfig; 5 | import com.b1n_ry.yigd.config.ExtraFeaturesConfig.GraveCompassConfig; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.core.GlobalPos; 8 | import net.minecraft.core.component.DataComponents; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.resources.ResourceKey; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.world.item.Items; 14 | import net.minecraft.world.level.Level; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.UUID; 22 | 23 | public class GraveCompassHelper { 24 | private static final Map, KDNode> GRAVE_POSITIONS = new HashMap<>(); 25 | 26 | public static void giveCompass(ServerPlayer player, UUID graveId, BlockPos gravePos, ResourceKey worldKey) { 27 | ItemStack compass = Items.COMPASS.getDefaultInstance(); 28 | 29 | BlockPos closestPos = null; 30 | if (YigdConfig.getConfig().extraFeatures.graveCompass.pointToClosest != GraveCompassConfig.CompassGraveTarget.DISABLED) { 31 | closestPos = findClosest(player.getUUID(), worldKey, player.blockPosition()); 32 | } 33 | if (closestPos != null) { // Point to closest not disabled and a grave was found 34 | compass.set(Yigd.GRAVE_LOCATION, new GlobalPos(worldKey, closestPos)); 35 | } else { 36 | // Standard grave compass 37 | compass.set(Yigd.GRAVE_ID, graveId); // Speed up the process of identifying the grave server side 38 | 39 | // Make clients read the grave position 40 | compass.set(Yigd.GRAVE_LOCATION, new GlobalPos(worldKey, gravePos)); 41 | } 42 | 43 | compass.set(DataComponents.CUSTOM_NAME, Component.translatable("item.yigd.grave_compass").withStyle(style -> style.withItalic(false))); 44 | player.addItem(compass); 45 | } 46 | 47 | public static void updateClosestNbt(ResourceKey worldKey, BlockPos pos, UUID holderId, ItemStack compass) { 48 | if (YigdConfig.getConfig().extraFeatures.graveCompass.pointToClosest == GraveCompassConfig.CompassGraveTarget.DISABLED) return; 49 | 50 | if (!compass.has(Yigd.GRAVE_LOCATION)) return; // Not a grave compass 51 | 52 | BlockPos closestPos = findClosest(holderId, worldKey, pos); 53 | if (closestPos != null) { 54 | compass.set(Yigd.GRAVE_LOCATION, new GlobalPos(worldKey, closestPos)); 55 | } 56 | } 57 | public static void addGravePosition(ResourceKey worldKey, BlockPos gravePos, UUID ownerId) { 58 | KDNode root = GRAVE_POSITIONS.get(worldKey); 59 | int[] pos = new int[] { gravePos.getX(), gravePos.getY(), gravePos.getZ() }; 60 | if (root == null) { 61 | GRAVE_POSITIONS.put(worldKey, new KDNode(ownerId, pos, null, null)); 62 | return; 63 | } 64 | 65 | addGravePosition(root, ownerId, pos, 0); 66 | } 67 | private static void addGravePosition(KDNode parent, UUID ownerId, int[] pos, int depth) { 68 | if (depth > 100) 69 | return; 70 | 71 | int cmp = depth % 3; 72 | if (pos[cmp] < parent.pos[cmp]) { 73 | if (parent.left == null) { 74 | parent.left = new KDNode(ownerId, pos, null, null); 75 | } else { 76 | addGravePosition(parent.left, ownerId, pos, depth + 1); 77 | } 78 | } else { 79 | if (parent.right == null) { 80 | parent.right = new KDNode(ownerId, pos, null, null); 81 | } else { 82 | addGravePosition(parent.right, ownerId, pos, depth + 1); 83 | } 84 | } 85 | } 86 | 87 | public static void setClaimed(ResourceKey worldKey, BlockPos gravePos) { 88 | KDNode root = GRAVE_POSITIONS.get(worldKey); 89 | if (root == null) { 90 | return; 91 | } 92 | 93 | int[] pos = new int[] { gravePos.getX(), gravePos.getY(), gravePos.getZ() }; 94 | 95 | setClaimed(root, pos, 0); 96 | } 97 | private static void setClaimed(KDNode node, int[] pos, int depth) { 98 | if (node == null) { 99 | return; 100 | } 101 | 102 | if (Arrays.equals(node.pos, pos)) { 103 | node.isUnclaimed = false; 104 | return; 105 | } 106 | 107 | int cmp = depth % 3; 108 | if (node.pos[cmp] < pos[cmp]) { 109 | setClaimed(node.right, pos, depth + 1); 110 | } else { 111 | setClaimed(node.left, pos, depth + 1); 112 | } 113 | } 114 | 115 | public static @Nullable BlockPos findClosest(UUID ownerId, ResourceKey worldKey, BlockPos pos) { 116 | GraveCompassConfig config = YigdConfig.getConfig().extraFeatures.graveCompass; 117 | if (config.pointToClosest == GraveCompassConfig.CompassGraveTarget.DISABLED) return null; // What how are we here? 118 | 119 | KDNode root = GRAVE_POSITIONS.get(worldKey); 120 | if (root == null) { 121 | return null; 122 | } 123 | 124 | int[] searchPos = new int[] { pos.getX(), pos.getY(), pos.getZ() }; 125 | 126 | int[] closest = findClosest(root, ownerId, searchPos, 0, config); 127 | 128 | return closest == null ? null : new BlockPos(closest[0], closest[1], closest[2]); 129 | } 130 | private static int[] findClosest(KDNode node, UUID ownerId, int[] pos, int depth, GraveCompassConfig config) { 131 | if (node == null || depth > 100) { 132 | return null; 133 | } 134 | 135 | int cmp = depth % 3; 136 | int[] closest = null; 137 | // If not all, it's player specific. If not that, it's disabled, and won't reach this 138 | if ((config.pointToClosest == GraveCompassConfig.CompassGraveTarget.ALL || node.ownerId.equals(ownerId)) && node.isUnclaimed) { 139 | closest = node.pos; 140 | } 141 | 142 | KDNode nextNode, otherNode; 143 | if (pos[cmp] < node.pos[cmp]) { 144 | nextNode = node.left; 145 | otherNode = node.right; 146 | } else { 147 | nextNode = node.right; 148 | otherNode = node.left; 149 | } 150 | 151 | int[] closestChild = findClosest(nextNode, ownerId, pos, depth + 1, config); 152 | 153 | if (closestChild != null && (closest == null || distanceSquared(closestChild, pos) < distanceSquared(closest, pos))) 154 | closest = closestChild; 155 | 156 | if (closestChild == null || Math.pow(node.pos[cmp] - pos[cmp], 2) < distanceSquared(closest, pos)) { 157 | closestChild = findClosest(otherNode, ownerId, pos, depth + 1, config); 158 | if (closestChild != null && (closest == null || distanceSquared(closestChild, pos) < distanceSquared(closest, pos))) 159 | closest = closestChild; 160 | } 161 | 162 | return closest; 163 | } 164 | private static double distanceSquared(int[] pos1, int[] pos2) { 165 | return Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2); 166 | } 167 | 168 | private static class KDNode { 169 | @NotNull 170 | private final UUID ownerId; 171 | private boolean isUnclaimed = true; 172 | private final int @NotNull [] pos; 173 | @Nullable 174 | private KDNode left; 175 | @Nullable 176 | private KDNode right; 177 | 178 | public KDNode(@NotNull UUID ownerId, int @NotNull [] pos, @Nullable KDNode left, @Nullable KDNode right) { 179 | this.ownerId = ownerId; 180 | this.pos = pos; 181 | this.left = left; 182 | this.right = right; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/Yigd.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd; 2 | 3 | import com.b1n_ry.yigd.block.GraveBlock; 4 | import com.b1n_ry.yigd.block.entity.GraveBlockEntity; 5 | import com.b1n_ry.yigd.compat.InvModCompat; 6 | import com.b1n_ry.yigd.config.ClaimPriority; 7 | import com.b1n_ry.yigd.config.YigdConfig; 8 | import com.b1n_ry.yigd.events.ServerEventHandler; 9 | import com.b1n_ry.yigd.events.YigdServerEventHandler; 10 | import com.b1n_ry.yigd.item.DeathScrollItem; 11 | import com.b1n_ry.yigd.item.GraveKeyItem; 12 | import com.b1n_ry.yigd.networking.PacketInitializer; 13 | import com.b1n_ry.yigd.util.YigdCommands; 14 | import com.b1n_ry.yigd.util.YigdResourceHandler; 15 | import com.mojang.serialization.MapCodec; 16 | import me.shedaniel.autoconfig.AutoConfig; 17 | import me.shedaniel.autoconfig.serializer.GsonConfigSerializer; 18 | import net.minecraft.core.GlobalPos; 19 | import net.minecraft.core.UUIDUtil; 20 | import net.minecraft.core.component.DataComponentType; 21 | import net.minecraft.core.registries.BuiltInRegistries; 22 | import net.minecraft.core.registries.Registries; 23 | import net.minecraft.world.item.Item; 24 | import net.minecraft.world.level.block.entity.BlockEntityType; 25 | import net.minecraft.world.phys.Vec3; 26 | import net.neoforged.neoforge.attachment.AttachmentType; 27 | import net.neoforged.neoforge.common.NeoForge; 28 | import net.neoforged.neoforge.event.server.ServerStartedEvent; 29 | import net.neoforged.neoforge.registries.*; 30 | import org.slf4j.Logger; 31 | 32 | import com.mojang.logging.LogUtils; 33 | 34 | import net.minecraft.world.item.BlockItem; 35 | import net.minecraft.world.item.CreativeModeTabs; 36 | import net.minecraft.world.level.block.Block; 37 | import net.minecraft.world.level.block.state.BlockBehaviour; 38 | import net.neoforged.bus.api.IEventBus; 39 | import net.neoforged.fml.ModContainer; 40 | import net.neoforged.fml.common.Mod; 41 | import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; 42 | import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; 43 | 44 | import java.util.*; 45 | import java.util.function.Supplier; 46 | 47 | // The value here should match an entry in the META-INF/neoforge.mods.toml file 48 | @Mod(Yigd.MOD_ID) 49 | public class Yigd 50 | { 51 | // Define mod id in a common place for everything to reference 52 | public static final String MOD_ID = "yigd"; 53 | 54 | public static final Logger LOGGER = LogUtils.getLogger(); 55 | public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(Yigd.MOD_ID); 56 | public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(Yigd.MOD_ID); 57 | 58 | public static final DeferredRegister> BLOCK_REGISTER = DeferredRegister.create(BuiltInRegistries.BLOCK_TYPE, Yigd.MOD_ID); 59 | public static final DeferredHolder, MapCodec> GRAVE_BLOCK_CODEC = BLOCK_REGISTER.register( 60 | "grave", 61 | () -> BlockBehaviour.simpleCodec(GraveBlock::new)); 62 | 63 | public static final DeferredBlock GRAVE = BLOCKS.registerBlock("grave", GraveBlock::new, 64 | BlockBehaviour.Properties.of().strength(-1.0f, 3600000.0f).noOcclusion()); 65 | 66 | public static final DeferredItem GRAVE_ITEM = ITEMS.registerSimpleBlockItem("grave", GRAVE); 67 | public static final DeferredItem DEATH_SCROLL_ITEM = ITEMS.registerItem("death_scroll", DeathScrollItem::new, new Item.Properties()); 68 | public static final DeferredItem GRAVE_KEY_ITEM = ITEMS.registerItem("grave_key", GraveKeyItem::new, new Item.Properties()); 69 | 70 | public static final DeferredRegister> BE_REGISTER = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, Yigd.MOD_ID); 71 | public static final DeferredHolder, BlockEntityType> GRAVE_BLOCK_ENTITY = BE_REGISTER.register("grave_block_entity", () -> BlockEntityType.Builder.of(GraveBlockEntity::new, GRAVE.get()).build(null)); 72 | 73 | private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, Yigd.MOD_ID); 74 | public static final Supplier> LAST_GROUND_POS = ATTACHMENT_TYPES.register("last_ground_pos", () -> AttachmentType.builder(() -> Vec3.ZERO).serialize(Vec3.CODEC).build()); 75 | 76 | private static final DeferredRegister.DataComponents DATA_COMPONENTS = DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, Yigd.MOD_ID); 77 | public static final DeferredHolder, DataComponentType> GRAVE_ID = DATA_COMPONENTS.registerComponentType("grave_id", builder -> builder.persistent(UUIDUtil.CODEC).networkSynchronized(UUIDUtil.STREAM_CODEC)); 78 | public static final DeferredHolder, DataComponentType> GRAVE_LOCATION = DATA_COMPONENTS.registerComponentType("grave_location", builder -> builder.persistent(GlobalPos.CODEC).networkSynchronized(GlobalPos.STREAM_CODEC)); 79 | public static final Map UNFINISHED_DEATHS = new HashMap<>(); 80 | 81 | /** 82 | * Any runnable added to this list will be executed on the end of the current server tick. 83 | * Use if runnable is required to run before some other event that would have otherwise ran before. 84 | */ 85 | public static List END_OF_TICK = new ArrayList<>(); 86 | 87 | public static Map> NOT_NOTIFIED_ROBBERIES = new HashMap<>(); 88 | public static Map CLAIM_PRIORITIES = new HashMap<>(); 89 | public static Map ROB_PRIORITIES = new HashMap<>(); 90 | 91 | // The constructor for the mod class is the first code that is run when your mod is loaded. 92 | // FML will recognize some parameter types like IEventBus or ModContainer and pass them in automatically. 93 | public Yigd(IEventBus modEventBus, ModContainer modContainer) 94 | { 95 | AutoConfig.register(YigdConfig.class, GsonConfigSerializer::new); 96 | 97 | // Register the commonSetup method for modloading 98 | modEventBus.addListener(this::modInitializer); 99 | 100 | // Register the Deferred Register to the mod event bus so items get registered 101 | ITEMS.register(modEventBus); 102 | // Register the Deferred Register to the mod event bus so blocks get registered 103 | BLOCKS.register(modEventBus); 104 | // Register the Deferred Register to the mod event bus so block entities get registered 105 | BE_REGISTER.register(modEventBus); 106 | // Register the Deferred Register to the mod event bus so attachments get registered 107 | ATTACHMENT_TYPES.register(modEventBus); 108 | // Register the Deferred Register to the mod event bus so data component types get registered 109 | DATA_COMPONENTS.register(modEventBus); 110 | 111 | // Register ourselves for server and other game events we are interested in. 112 | // Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events. 113 | // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. 114 | NeoForge.EVENT_BUS.register(new ServerEventHandler()); 115 | NeoForge.EVENT_BUS.register(new YigdServerEventHandler()); 116 | 117 | // Register the item to a creative tab 118 | modEventBus.addListener(this::addCreative); 119 | modEventBus.addListener(PacketInitializer::register); 120 | NeoForge.EVENT_BUS.addListener(ServerStartedEvent.class, e -> InvModCompat.reloadModCompat()); 121 | NeoForge.EVENT_BUS.addListener(YigdCommands::registerCommands); 122 | NeoForge.EVENT_BUS.addListener(YigdResourceHandler::serverDataEvent); 123 | } 124 | 125 | private void modInitializer(final FMLCommonSetupEvent event) 126 | { 127 | InvModCompat.reloadModCompat(); 128 | } 129 | 130 | // Add the example block item to the building blocks tab 131 | private void addCreative(BuildCreativeModeTabContentsEvent event) 132 | { 133 | if (event.getTabKey() == CreativeModeTabs.FUNCTIONAL_BLOCKS) 134 | event.accept(GRAVE_ITEM); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /src/main/java/com/b1n_ry/yigd/events/ServerEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.b1n_ry.yigd.events; 2 | 3 | import com.b1n_ry.yigd.DeathHandler; 4 | import com.b1n_ry.yigd.Yigd; 5 | import com.b1n_ry.yigd.components.GraveComponent; 6 | import com.b1n_ry.yigd.components.RespawnComponent; 7 | import com.b1n_ry.yigd.config.GraveConfig; 8 | import com.b1n_ry.yigd.config.YigdConfig; 9 | import com.b1n_ry.yigd.data.DeathInfoManager; 10 | import com.b1n_ry.yigd.data.GraveStatus; 11 | import com.b1n_ry.yigd.networking.packets.SyncConfigS2CPacket; 12 | import net.minecraft.core.BlockPos; 13 | import net.minecraft.network.chat.Component; 14 | import net.minecraft.server.MinecraftServer; 15 | import net.minecraft.server.level.ServerLevel; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.world.damagesource.DamageSource; 18 | import net.minecraft.world.entity.LivingEntity; 19 | import net.minecraft.world.entity.item.ItemEntity; 20 | import net.minecraft.world.entity.player.Player; 21 | import net.minecraft.world.item.component.ResolvableProfile; 22 | import net.minecraft.world.level.GameRules; 23 | import net.neoforged.bus.api.EventPriority; 24 | import net.neoforged.bus.api.SubscribeEvent; 25 | import net.neoforged.neoforge.common.NeoForge; 26 | import net.neoforged.neoforge.event.entity.living.LivingDeathEvent; 27 | import net.neoforged.neoforge.event.entity.living.LivingDropsEvent; 28 | import net.neoforged.neoforge.event.entity.player.PlayerEvent; 29 | import net.neoforged.neoforge.event.server.ServerStartedEvent; 30 | import net.neoforged.neoforge.event.tick.PlayerTickEvent; 31 | import net.neoforged.neoforge.event.tick.ServerTickEvent; 32 | import net.neoforged.neoforge.network.PacketDistributor; 33 | 34 | import java.util.*; 35 | 36 | public class ServerEventHandler { 37 | @SubscribeEvent 38 | public void onEndOfTick(ServerTickEvent.Post event) { 39 | List methodsToRun = new ArrayList<>(Yigd.END_OF_TICK); 40 | Yigd.END_OF_TICK.clear(); 41 | 42 | for (Runnable runnable : methodsToRun) { 43 | runnable.run(); 44 | } 45 | } 46 | 47 | @SubscribeEvent 48 | public void endPlayerTick(PlayerTickEvent.Post event) { 49 | Player player = event.getEntity(); 50 | if (player.onGround()) 51 | player.setData(Yigd.LAST_GROUND_POS, player.position()); 52 | } 53 | 54 | @SubscribeEvent 55 | public void serverStarted(ServerStartedEvent event) { 56 | MinecraftServer server = event.getServer(); 57 | DeathInfoManager.INSTANCE.clear(); 58 | 59 | ServerLevel overworld = server.overworld(); 60 | DeathInfoManager.INSTANCE = overworld.getDataStorage().computeIfAbsent(DeathInfoManager.getPersistentStateType(server), "yigd_data"); 61 | DeathInfoManager.INSTANCE.setDirty(); 62 | } 63 | 64 | @SubscribeEvent(priority = EventPriority.LOWEST) 65 | public void onPlayerDeathDropItems(LivingDropsEvent event) { 66 | if (!(event.getEntity() instanceof ServerPlayer player)) return; 67 | UUID playerId = player.getUUID(); 68 | 69 | DeathHandler unfinished = Yigd.UNFINISHED_DEATHS.remove(playerId); 70 | if (unfinished != null) { 71 | Collection drops = event.getDrops(); 72 | for (ItemEntity itemEntity : drops) { 73 | unfinished.addItem(itemEntity.getItem()); 74 | } 75 | drops.clear(); 76 | unfinished.finalizeDeath(); 77 | } else { 78 | Yigd.LOGGER.error("Did not find cached death handler for {}. Can't generate player loot", player.getGameProfile().getName()); 79 | } 80 | } 81 | 82 | /** 83 | * This event is used to handle deaths. 84 | * It stores the death information in a map for later processing. Note that THIS WILL CLEAR THE INVENTORY so that 85 | * items are not dropped twice. 86 | * Event priority is set to LOWEST to ensure other mods can manipulate certain items first. 87 | * Mods which would handle inventory drops in this method (in my opinion) are doing it wrong, 88 | * and should use the {@link YigdEvents.DropItemEvent} instead. 89 | * I'll argue that I'm in the right though, since I have to make sure that I keep track of all slots 90 | * (can't be done in DropItemEvent), and since I run this with lowest priority, it should be fine. 91 | * Additionally, this event has to run after some events that can stop the death from happening all together. 92 | *
93 | * If you have any questions or concerns about this, feel free to open an issue on the GitHub repository. 94 | * 95 | * @param event Death event 96 | */ 97 | @SubscribeEvent(priority = EventPriority.LOWEST) 98 | public void onLivingDeath(LivingDeathEvent event) { 99 | LivingEntity entity = event.getEntity(); 100 | 101 | if (!(entity instanceof ServerPlayer player)) return; 102 | 103 | if (Yigd.UNFINISHED_DEATHS.containsKey(player.getUUID())) { 104 | Yigd.LOGGER.warn("Player '{}' already has a registered, unhandled death. Ignoring this one", player.getGameProfile().getName()); 105 | return; 106 | } 107 | if (!player.isDeadOrDying()) { 108 | Yigd.LOGGER.error("Player '{}' is not dead, but LivingDeathEvent was called. This should not happen. Ignoring this event", player.getGameProfile().getName()); 109 | return; 110 | } 111 | if (player.isSpectator()) return; 112 | 113 | ServerLevel level = player.serverLevel(); 114 | DamageSource damageSource = event.getSource(); 115 | 116 | if (level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY)) return; 117 | 118 | Yigd.UNFINISHED_DEATHS.put(player.getUUID(), new DeathHandler(player, level, player.position(), damageSource)); 119 | } 120 | 121 | @SubscribeEvent(priority = EventPriority.LOWEST) 122 | public void afterRespawn(PlayerEvent.Clone event) { 123 | if (!event.isWasDeath()) return; 124 | 125 | Player newPlayer = event.getEntity(); 126 | Player oldPlayer = event.getOriginal(); 127 | if (newPlayer.level().isClientSide || oldPlayer.level().isClientSide) return; 128 | 129 | NeoForge.EVENT_BUS.post(new YigdEvents.BeforeSoulboundEvent((ServerPlayer) oldPlayer, (ServerPlayer) newPlayer)); 130 | 131 | ResolvableProfile newProfile = new ResolvableProfile(newPlayer.getGameProfile()); 132 | Optional respawnComponent = DeathInfoManager.INSTANCE.getRespawnComponent(newProfile); 133 | respawnComponent.ifPresent(component -> component.apply((ServerPlayer) newPlayer)); 134 | 135 | if (YigdConfig.getConfig().graveConfig.informGraveLocation && respawnComponent.isPresent() && respawnComponent.get().wasGraveGenerated()) { 136 | List graves = new ArrayList<>(DeathInfoManager.INSTANCE.getBackupData(newProfile)); 137 | graves.removeIf(grave -> grave.getStatus() != GraveStatus.UNCLAIMED); 138 | if (!graves.isEmpty()) { 139 | GraveComponent latest = graves.getLast(); 140 | BlockPos gravePos = latest.getPos(); 141 | newPlayer.sendSystemMessage(Component.translatable("text.yigd.message.grave_location", 142 | gravePos.getX(), gravePos.getY(), gravePos.getZ(), 143 | latest.getWorldRegistryKey().location().toString())); 144 | } 145 | } 146 | } 147 | 148 | @SubscribeEvent 149 | public void playerJoin(PlayerEvent.PlayerLoggedInEvent event) { 150 | if (!(event.getEntity() instanceof ServerPlayer player)) return; 151 | 152 | YigdConfig config = YigdConfig.getConfig(); 153 | PacketDistributor.sendToPlayer(player, new SyncConfigS2CPacket( 154 | config.graveConfig.retrieveMethods.onBreak, 155 | config.graveRendering.useGlowingEffect, 156 | config.graveRendering.glowingDistance, 157 | config.extraFeatures.deathSightEnchant.range)); 158 | 159 | GraveConfig.GraveRobbing robConfig = config.graveConfig.graveRobbing; 160 | UUID joiningId = player.getUUID(); 161 | 162 | if (!Yigd.NOT_NOTIFIED_ROBBERIES.containsKey(joiningId)) return; 163 | 164 | // Check if notifying when robbed is not required, since it has to be set to true for players to be added to NOT_NOTIFIED_ROBBERIES 165 | if (robConfig.tellWhoRobbed) { 166 | List robbedBy = Yigd.NOT_NOTIFIED_ROBBERIES.remove(joiningId); 167 | for (String robber : robbedBy) { 168 | player.sendSystemMessage(Component.translatable("text.yigd.message.inform_robbery.with_details", robber)); 169 | } 170 | } else { 171 | Yigd.NOT_NOTIFIED_ROBBERIES.remove(joiningId); 172 | player.sendSystemMessage(Component.translatable("text.yigd.message.inform_robbery")); 173 | } 174 | } 175 | 176 | @SubscribeEvent 177 | public void playerLeave(PlayerEvent.PlayerLoggedOutEvent event) { 178 | if (!(event.getEntity() instanceof ServerPlayer player)) return; 179 | 180 | YigdConfig config = YigdConfig.getConfig(); 181 | if (!config.graveConfig.sellOutOfflinePeople) return; 182 | 183 | ResolvableProfile loggedOffProfile = new ResolvableProfile(player.getGameProfile()); 184 | List loggedOffGraves = DeathInfoManager.INSTANCE.getBackupData(loggedOffProfile); 185 | List loggedOffUnclaimed = new ArrayList<>(loggedOffGraves); 186 | loggedOffGraves.removeIf(c -> c.getStatus() == GraveStatus.UNCLAIMED); 187 | if (!loggedOffUnclaimed.isEmpty()) { 188 | GraveComponent component = loggedOffUnclaimed.getFirst(); 189 | BlockPos lastGravePos = component.getPos(); 190 | player.server.sendSystemMessage(Component.translatable("text.yigd.message.sellout_player", 191 | loggedOffProfile.name().orElse("PLAYER_NOT_FOUND"), lastGravePos.getX(), lastGravePos.getY(), lastGravePos.getZ(), 192 | component.getWorldRegistryKey().location().toString())); 193 | } 194 | } 195 | } 196 | --------------------------------------------------------------------------------