├── changelog.md ├── src └── main │ ├── resources │ ├── data │ │ ├── gravestone │ │ │ ├── loot_table │ │ │ │ ├── entities │ │ │ │ │ └── player_ghost.json │ │ │ │ └── blocks │ │ │ │ │ └── gravestone.json │ │ │ ├── tags │ │ │ │ ├── entity_type │ │ │ │ │ └── undead.json │ │ │ │ └── block │ │ │ │ │ └── grave_replaceable.json │ │ │ └── recipe │ │ │ │ └── gravestone.json │ │ └── minecraft │ │ │ └── tags │ │ │ └── block │ │ │ └── dragon_immune.json │ ├── icon.png │ ├── assets │ │ └── gravestone │ │ │ ├── items │ │ │ ├── obituary.json │ │ │ └── gravestone.json │ │ │ ├── textures │ │ │ ├── gui │ │ │ │ ├── info.png │ │ │ │ └── death_items.png │ │ │ └── item │ │ │ │ ├── obituary.png │ │ │ │ └── gravestone.png │ │ │ ├── blockstates │ │ │ └── gravestone.json │ │ │ ├── lang │ │ │ ├── es_es.json │ │ │ ├── zh_cn.json │ │ │ ├── zh_tw.json │ │ │ ├── ko_kr.json │ │ │ ├── ja_jp.json │ │ │ ├── sv_se.json │ │ │ ├── tr_tr.json │ │ │ ├── en_us.json │ │ │ ├── de_de.json │ │ │ ├── pt_br.json │ │ │ ├── uk_ua.json │ │ │ ├── cs_cz.json │ │ │ ├── ru_ru.json │ │ │ ├── es_mx.json │ │ │ ├── hu_hu.json │ │ │ ├── pl_pl.json │ │ │ └── es_cl.json │ │ │ └── models │ │ │ ├── item │ │ │ ├── obituary.json │ │ │ └── gravestone.json │ │ │ └── block │ │ │ └── gravestone.json │ ├── pack.mcmeta │ └── META-INF │ │ └── neoforge.mods.toml │ └── java │ └── de │ └── maxhenkel │ └── gravestone │ ├── entity │ ├── GhostPlayerRenderState.java │ ├── DummyPlayer.java │ ├── PlayerGhostRenderer.java │ └── GhostPlayerEntity.java │ ├── ClientUtils.java │ ├── net │ ├── ClientNetworking.java │ └── MessageOpenObituary.java │ ├── tileentity │ ├── render │ │ ├── GravestoneRenderState.java │ │ └── GravestoneRenderer.java │ └── GraveStoneTileEntity.java │ ├── events │ ├── CreativeTabEvents.java │ └── DeathEvents.java │ ├── integration │ └── waila │ │ ├── PluginGraveStone.java │ │ ├── GravestoneDataProvider.java │ │ └── HUDHandlerGraveStone.java │ ├── GravestoneClientMod.java │ ├── ClientConfig.java │ ├── gui │ ├── PageList.java │ ├── Page.java │ └── ObituaryScreen.java │ ├── DeathInfo.java │ ├── GraveUtils.java │ ├── commands │ └── RestoreCommand.java │ ├── ServerConfig.java │ ├── items │ └── ObituaryItem.java │ ├── GravestoneMod.java │ └── blocks │ └── GraveStoneBlock.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ ├── translation.yml │ └── bug_report.yml ├── settings.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── readme.md └── gradlew /changelog.md: -------------------------------------------------------------------------------- 1 | - Updated to 1.21.11 2 | -------------------------------------------------------------------------------- /src/main/resources/data/gravestone/loot_table/entities/player_ghost.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/src/main/resources/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/block/dragon_immune.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "gravestone:gravestone" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/data/gravestone/tags/entity_type/undead.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "gravestone:player_ghost" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/items/obituary.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": { 3 | "type": "minecraft:model", 4 | "model": "gravestone:item/obituary" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/entity/GhostPlayerRenderState.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.entity; 2 | 3 | public class GhostPlayerRenderState { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/items/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": { 3 | "type": "minecraft:model", 4 | "model": "gravestone:item/gravestone" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/textures/gui/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/src/main/resources/assets/gravestone/textures/gui/info.png -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/textures/item/obituary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/src/main/resources/assets/gravestone/textures/item/obituary.png -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/textures/gui/death_items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/src/main/resources/assets/gravestone/textures/gui/death_items.png -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/textures/item/gravestone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/gravestone/HEAD/src/main/resources/assets/gravestone/textures/item/gravestone.png -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "gravestone resources", 4 | "min_format": [65, 0], 5 | "max_format": [999, 0] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: FAQ 4 | about: Frequently asked questions 5 | url: https://modrepo.de/minecraft/gravestone/faq 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { url = 'https://maven.neoforged.net/releases' } 5 | maven { url = 'https://maven.maxhenkel.de/repository/public' } 6 | } 7 | } 8 | 9 | plugins { 10 | id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' 11 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/ClientUtils.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.network.chat.Component; 5 | 6 | public class ClientUtils { 7 | 8 | public static void sendMessage(Component message) { 9 | Minecraft.getInstance().gui.getChat().addMessage(message); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/data/gravestone/recipe/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shaped", 3 | "pattern": [ 4 | "C ", 5 | "C ", 6 | "DDD" 7 | ], 8 | "key": { 9 | "C": "minecraft:cobblestone", 10 | "D": "minecraft:dirt" 11 | }, 12 | "result": { 13 | "id": "gravestone:gravestone", 14 | "count": 1 15 | }, 16 | "mirrored": true 17 | } -------------------------------------------------------------------------------- /.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 | logs 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | 28 | curseforge_api_key.txt 29 | mod_update_api_key.txt 30 | modrinth_token.txt 31 | runs -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/blockstates/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "facing=north": { "model": "gravestone:block/gravestone" }, 4 | "facing=south": { "model": "gravestone:block/gravestone", "y": 180 }, 5 | "facing=west": { "model": "gravestone:block/gravestone", "y": 270 }, 6 | "facing=east": { "model": "gravestone:block/gravestone", "y": 90 } 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/net/ClientNetworking.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.net; 2 | 3 | import de.maxhenkel.corelib.death.Death; 4 | import de.maxhenkel.gravestone.gui.ObituaryScreen; 5 | import net.minecraft.client.Minecraft; 6 | 7 | public class ClientNetworking { 8 | 9 | public static void openObituary(Death death) { 10 | Minecraft.getInstance().setScreen(new ObituaryScreen(death)); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/tileentity/render/GravestoneRenderState.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.tileentity.render; 2 | 3 | import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; 4 | import net.minecraft.core.Direction; 5 | import net.minecraft.util.FormattedCharSequence; 6 | 7 | import java.util.UUID; 8 | 9 | public class GravestoneRenderState extends BlockEntityRenderState { 10 | 11 | public FormattedCharSequence name; 12 | public Direction direction; 13 | public boolean renderHead; 14 | public UUID playerId; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/events/CreativeTabEvents.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.events; 2 | 3 | import de.maxhenkel.gravestone.GravestoneMod; 4 | import net.minecraft.world.item.CreativeModeTabs; 5 | import net.minecraft.world.item.ItemStack; 6 | import net.neoforged.bus.api.SubscribeEvent; 7 | import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; 8 | 9 | public class CreativeTabEvents { 10 | 11 | @SubscribeEvent 12 | public static void onCreativeModeTabBuildContents(BuildCreativeModeTabContentsEvent event) { 13 | if (event.getTabKey().equals(CreativeModeTabs.FUNCTIONAL_BLOCKS)) { 14 | event.accept(new ItemStack(GravestoneMod.GRAVESTONE.get())); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/data/gravestone/loot_table/blocks/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:block", 3 | "pools": [ 4 | { 5 | "name": "gravestone", 6 | "rolls": 1, 7 | "entries": [ 8 | { 9 | "type": "minecraft:item", 10 | "name": "gravestone:gravestone" 11 | } 12 | ], 13 | "conditions": [ 14 | { 15 | "condition": "minecraft:match_tool", 16 | "predicate": { 17 | "predicates": { 18 | "minecraft:enchantments": [ 19 | { 20 | "enchantments": "minecraft:silk_touch", 21 | "levels": { 22 | "min": 1 23 | } 24 | } 25 | ] 26 | } 27 | } 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Tumba", 3 | "item.gravestone.obituary": "Obituario", 4 | "entity.gravestone.player_ghost": "Fantasma del jugador", 5 | "message.gravestone.died": "%s murió el %s", 6 | "message.gravestone.deathlocation": "Lugar de fallecimiento", 7 | "message.gravestone.no_deathlocation": "No está disponible el lugar de fallecimiento", 8 | "message.gravestone.create_grave_failed": "La tumba no se pudo situar", 9 | "gui.obituary.name": "Nombre", 10 | "gui.obituary.title": "Información de la muerte del jugador", 11 | "gui.obituary.location": "Lugar", 12 | "gui.obituary.dimension": "Dimensión", 13 | "gui.obituary.title.items": "Objetos portados", 14 | "gui.obituary.page": "Página %s", 15 | "gui.obituary.time": "Fecha", 16 | "button.gravestone.next": "Siguiente", 17 | "button.gravestone.prev": "Atrás" 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/integration/waila/PluginGraveStone.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.integration.waila; 2 | 3 | import de.maxhenkel.gravestone.blocks.GraveStoneBlock; 4 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 5 | import snownee.jade.api.IWailaClientRegistration; 6 | import snownee.jade.api.IWailaCommonRegistration; 7 | import snownee.jade.api.IWailaPlugin; 8 | import snownee.jade.api.WailaPlugin; 9 | 10 | @WailaPlugin 11 | public class PluginGraveStone implements IWailaPlugin { 12 | 13 | @Override 14 | public void registerClient(IWailaClientRegistration registration) { 15 | registration.registerBlockComponent(HUDHandlerGraveStone.INSTANCE, GraveStoneBlock.class); 16 | } 17 | 18 | @Override 19 | public void register(IWailaCommonRegistration registration) { 20 | registration.registerBlockDataProvider(GravestoneDataProvider.INSTANCE, GraveStoneTileEntity.class); 21 | } 22 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/translation.yml: -------------------------------------------------------------------------------- 1 | name: Translation 2 | description: Submit a translation for this project 3 | labels: [translation] 4 | assignees: henkelmax 5 | body: 6 | - type: textarea 7 | id: notes 8 | attributes: 9 | label: Additional notes 10 | description: Additional information. 11 | validations: 12 | required: false 13 | - type: input 14 | id: locale_code 15 | attributes: 16 | label: Locale code 17 | description: The Minecraft locale code (See [this](https://minecraft.wiki/w/Language#Languages) for more information). 18 | placeholder: en_us 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: translation 23 | attributes: 24 | label: Translation json 25 | description: The contents of your translation file. 26 | render: json 27 | placeholder: | 28 | { 29 | "translation.key": "Translated value" 30 | } 31 | validations: 32 | required: true 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | org.gradle.daemon=false 3 | 4 | java_version=21 5 | java_toolchain_version=21 6 | 7 | neogradle.subsystems.parchment.minecraftVersion=1.21.10 8 | neogradle.subsystems.parchment.mappingsVersion=2025.10.12 9 | 10 | mod_loader=neoforge 11 | minecraft_version=1.21.11 12 | neoforge_version=21.11.0-beta 13 | neoforge_dependency=[21.11.0-beta,) 14 | corelib_version=2.1.12 15 | 16 | jade_version=19.0.3+neoforge 17 | 18 | # Mod information 19 | mod_version=1.21.11-1.0.35 20 | mod_id=gravestone 21 | mod_display_name=Gravestone Mod 22 | 23 | # Project upload 24 | curseforge_upload_id=238551 25 | modrinth_upload_id=RYtXKJPr 26 | upload_release_type=release 27 | upload_recommended=true 28 | 29 | curseforge_upload_optional_dependencies=jade 30 | modrinth_upload_optional_dependencies=jade 31 | 32 | # Gradle plugins 33 | mod_gradle_script_version=1.0.47 34 | neogradle_version=[7.1.11,7.2) 35 | mod_update_version=2.0.0 36 | cursegradle_version=1.5.0 37 | shadow_version=9.2.2 38 | minotaur_version=2.+ 39 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | license = "All rights reserved" 4 | issueTrackerURL = "https://github.com/henkelmax/gravestone/issues" 5 | [[mods]] 6 | modId = "gravestone" 7 | version = "${mod_version}" 8 | displayName = "Gravestone Mod" 9 | updateJSONURL = "https://update.maxhenkel.de/neoforge/gravestone" 10 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/gravestone-mod" 11 | logoFile = "icon.png" 12 | authors = "Max Henkel" 13 | description = '''Places a gravestone with your inventory items inside when you die''' 14 | [[dependencies.gravestone]] 15 | modId = "neoforge" 16 | type = "required" 17 | versionRange = "${neoforge_dependency}" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | [[dependencies.gravestone]] 21 | modId = "minecraft" 22 | type = "required" 23 | versionRange = "[${minecraft_version}]" 24 | ordering = "NONE" 25 | side = "BOTH" 26 | [[dependencies.gravestone]] 27 | modId = "jade" 28 | type = "optional" 29 | versionRange = "*" 30 | ordering = "NONE" 31 | side = "BOTH" -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/models/item/obituary.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "builtin/generated", 3 | "textures": { 4 | "layer0": "gravestone:item/obituary" 5 | }, 6 | "display": { 7 | "thirdperson_righthand": { 8 | "rotation": [ 0, 0, 0 ], 9 | "translation": [ 0, 2, 0 ], 10 | "scale": [ 0.55, 0.55, 0.55 ] }, 11 | "thirdperson_lefthand": { 12 | "rotation": [ 0, 0, 0 ], 13 | "translation": [ 0, 2, 0 ], 14 | "scale": [ 0.55, 0.55, 0.55 ] }, 15 | "firstperson_lefthand": { 16 | "rotation": [ 0, 0, 0 ], 17 | "translation": [ 0, 2, 0 ], 18 | "scale": [ 0.55, 0.55, 0.55 ] }, 19 | "firstperson_righthand": { 20 | "rotation": [ 0, 0, 0 ], 21 | "translation": [ 0, 2, 0 ], 22 | "scale": [ 0.55, 0.55, 0.55 ] }, 23 | "ground": { 24 | "rotation": [ 0, 0, 0 ], 25 | "translation": [ 0, 0, 0], 26 | "scale":[ 0.55, 0.55, 0.55] 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/models/item/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "builtin/generated", 3 | "textures": { 4 | "layer0": "gravestone:item/gravestone" 5 | }, 6 | "display": { 7 | "thirdperson_righthand": { 8 | "rotation": [ 0, 0, 0 ], 9 | "translation": [ 0, 2, 0 ], 10 | "scale": [ 0.55, 0.55, 0.55 ] }, 11 | "thirdperson_lefthand": { 12 | "rotation": [ 0, 0, 0 ], 13 | "translation": [ 0, 2, 0 ], 14 | "scale": [ 0.55, 0.55, 0.55 ] }, 15 | "firstperson_lefthand": { 16 | "rotation": [ 0, 0, 0 ], 17 | "translation": [ 0, 2, 0 ], 18 | "scale": [ 0.55, 0.55, 0.55 ] }, 19 | "firstperson_righthand": { 20 | "rotation": [ 0, 0, 0 ], 21 | "translation": [ 0, 2, 0 ], 22 | "scale": [ 0.55, 0.55, 0.55 ] }, 23 | "ground": { 24 | "rotation": [ 0, 0, 0 ], 25 | "translation": [ 0, 0, 0], 26 | "scale":[ 0.55, 0.55, 0.55] 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/GravestoneClientMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import de.maxhenkel.gravestone.entity.PlayerGhostRenderer; 4 | import de.maxhenkel.gravestone.tileentity.render.GravestoneRenderer; 5 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; 6 | import net.minecraft.client.renderer.entity.EntityRenderers; 7 | import net.neoforged.api.distmarker.Dist; 8 | import net.neoforged.bus.api.SubscribeEvent; 9 | import net.neoforged.fml.common.EventBusSubscriber; 10 | import net.neoforged.fml.common.Mod; 11 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 12 | 13 | @Mod(value = GravestoneMod.MODID, dist = Dist.CLIENT) 14 | @EventBusSubscriber(modid = GravestoneMod.MODID, value = Dist.CLIENT) 15 | public class GravestoneClientMod { 16 | 17 | @SubscribeEvent 18 | static void clientSetup(FMLClientSetupEvent event) { 19 | BlockEntityRenderers.register(GravestoneMod.GRAVESTONE_TILEENTITY.get(), GravestoneRenderer::new); 20 | EntityRenderers.register(GravestoneMod.GHOST.get(), PlayerGhostRenderer::new); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/gravestone/tags/block/grave_replaceable.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "minecraft:tall_grass", 5 | "minecraft:short_grass", 6 | "minecraft:water", 7 | "minecraft:lava", 8 | "minecraft:dandelion", 9 | "minecraft:lilac", 10 | "minecraft:rose_bush", 11 | "minecraft:peony", 12 | "minecraft:sunflower", 13 | "minecraft:poppy", 14 | "minecraft:blue_orchid", 15 | "minecraft:azure_bluet", 16 | "minecraft:oxeye_daisy", 17 | "minecraft:orange_tulip", 18 | "minecraft:pink_tulip", 19 | "minecraft:red_tulip", 20 | "minecraft:white_tulip", 21 | "minecraft:allium", 22 | "minecraft:fern", 23 | "minecraft:large_fern", 24 | "minecraft:spruce_sapling", 25 | "minecraft:acacia_sapling", 26 | "minecraft:birch_sapling", 27 | "minecraft:dark_oak_sapling", 28 | "minecraft:jungle_sapling", 29 | "minecraft:oak_sapling", 30 | "minecraft:brown_mushroom", 31 | "minecraft:red_mushroom", 32 | "minecraft:snow", 33 | "minecraft:vine", 34 | "minecraft:dead_bush", 35 | "minecraft:fire" 36 | ] 37 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/integration/waila/GravestoneDataProvider.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.integration.waila; 2 | 3 | import de.maxhenkel.gravestone.GravestoneMod; 4 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 5 | import net.minecraft.nbt.CompoundTag; 6 | import net.minecraft.resources.Identifier; 7 | import snownee.jade.api.*; 8 | 9 | public class GravestoneDataProvider implements IServerDataProvider { 10 | 11 | public static final GravestoneDataProvider INSTANCE = new GravestoneDataProvider(); 12 | 13 | private static final Identifier UID = Identifier.fromNamespaceAndPath(GravestoneMod.MODID, "grave_data"); 14 | 15 | @Override 16 | public void appendServerData(CompoundTag compoundTag, BlockAccessor blockAccessor) { 17 | if (blockAccessor.getBlockEntity() instanceof GraveStoneTileEntity grave) { 18 | compoundTag.putInt("ItemCount", (int) grave.getDeath().getAllItems().stream().filter(itemStack -> !itemStack.isEmpty()).count()); 19 | } 20 | } 21 | 22 | @Override 23 | public Identifier getUid() { 24 | return UID; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/entity/DummyPlayer.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.entity; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.client.multiplayer.ClientLevel; 5 | import net.minecraft.client.player.RemotePlayer; 6 | import net.minecraft.world.entity.EquipmentSlot; 7 | import net.minecraft.world.entity.ai.attributes.AttributeInstance; 8 | import net.minecraft.world.entity.player.PlayerModelPart; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.neoforged.neoforge.common.NeoForgeMod; 11 | 12 | import java.util.EnumMap; 13 | 14 | public class DummyPlayer extends RemotePlayer { 15 | 16 | private final byte model; 17 | 18 | public DummyPlayer(ClientLevel world, GameProfile gameProfile, EnumMap equipment, byte model) { 19 | super(world, gameProfile); 20 | this.model = model; 21 | for (EnumMap.Entry entry : equipment.entrySet()) { 22 | setItemSlot(entry.getKey(), entry.getValue()); 23 | } 24 | AttributeInstance attribute = getAttributes().getInstance(NeoForgeMod.NAMETAG_DISTANCE); 25 | if (attribute != null) { 26 | attribute.setBaseValue(0D); 27 | } 28 | } 29 | 30 | @Override 31 | public boolean isSpectator() { 32 | return false; 33 | } 34 | 35 | @Override 36 | public boolean isModelPartShown(PlayerModelPart part) { 37 | return (model & part.getMask()) == part.getMask(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "墓碑", 3 | "item.gravestone.obituary": "死亡笔记", 4 | "entity.gravestone.player_ghost": "幽灵玩家", 5 | "message.gravestone.grave_of": "%s的墓碑", 6 | "message.gravestone.date_of_death": "死于 %s", 7 | "message.gravestone.item_count": "包含 %s 组", 8 | "message.gravestone.died": "%s 死在了 %s", 9 | "message.gravestone.deathlocation": "死亡地点", 10 | "message.gravestone.no_deathlocation": "无死亡地点记录", 11 | "message.gravestone.create_grave_failed": "无法放置坟墓", 12 | "message.gravestone.copied": "复制 %s 到剪贴板", 13 | "message.gravestone.restore": "恢复物品", 14 | "message.gravestone.restore.replace": "替换", 15 | "message.gravestone.restore.replace.description": "从你的物品栏中移除所有物品,并恢复你死前的物品", 16 | "message.gravestone.restore.add": "添加", 17 | "message.gravestone.restore.add.description": "将你死前拥有的物品添加到你的物品栏中", 18 | "message.gravestone.death_id": "死亡ID", 19 | "message.gravestone.death_id_not_found": "未找到死亡ID %s", 20 | "message.gravestone.restore.success": "成功恢复 %s 的物品", 21 | "message.gravestone.death_not_found": "死亡未找到", 22 | "gui.obituary.name": "名字", 23 | "gui.obituary.title": "死亡笔记", 24 | "gui.obituary.location": "位置", 25 | "gui.obituary.dimension": "维度", 26 | "gui.obituary.title.items": "死前物品", 27 | "gui.obituary.page": "%s/%s", 28 | "gui.obituary.time": "时间", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "复制ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "下一页", 33 | "button.gravestone.prev": "上一页" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/ClientConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import de.maxhenkel.corelib.config.ConfigBase; 4 | import net.neoforged.fml.event.config.ModConfigEvent; 5 | import net.neoforged.neoforge.common.ModConfigSpec; 6 | 7 | public class ClientConfig extends ConfigBase { 8 | 9 | public final ModConfigSpec.BooleanValue renderSkull; 10 | private final ModConfigSpec.ConfigValue graveTextColorSpec; 11 | 12 | public int graveTextColor = 0xFFFFFF; 13 | 14 | public ClientConfig(ModConfigSpec.Builder builder) { 15 | super(builder); 16 | renderSkull = builder 17 | .comment("If this is set to true the players head will be rendered on the gravestone when there is a full block under it") 18 | .translation("render_skull") 19 | .define("render_skull", true); 20 | graveTextColorSpec = builder 21 | .comment("The color of the text at the gravestone (Hex RGB)") 22 | .translation("grave_text_color") 23 | .define("grave_text_color", "FFFFFF"); 24 | } 25 | 26 | @Override 27 | public void onReload(ModConfigEvent.Reloading event) { 28 | super.onReload(event); 29 | onConfigChange(); 30 | } 31 | 32 | @Override 33 | public void onLoad(ModConfigEvent.Loading event) { 34 | super.onLoad(event); 35 | onConfigChange(); 36 | } 37 | 38 | private void onConfigChange() { 39 | graveTextColor = Integer.parseInt(graveTextColorSpec.get(), 16); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "墓碑", 3 | "item.gravestone.obituary": "死亡資訊", 4 | "entity.gravestone.player_ghost": "玩家鬼魂", 5 | "message.gravestone.grave_of": "%s 的墓碑", 6 | "message.gravestone.date_of_death": "死亡於 %s", 7 | "message.gravestone.item_count": "包含 %s 組物品", 8 | "message.gravestone.died": "%s 死亡於 %s", 9 | "message.gravestone.deathlocation": "死亡地點", 10 | "message.gravestone.no_deathlocation": "沒有可用的死亡地點", 11 | "message.gravestone.create_grave_failed": "無法放置墓碑", 12 | "message.gravestone.copied": "已複製 %s 到剪貼簿", 13 | "message.gravestone.restore": "復原物品", 14 | "message.gravestone.restore.replace": "取代", 15 | "message.gravestone.restore.replace.description": "移除你物品欄中的所有物品,並復原你在死亡前擁有的物品", 16 | "message.gravestone.restore.add": "新增", 17 | "message.gravestone.restore.add.description": "將你在死亡前擁有的物品新增到你的物品欄中", 18 | "message.gravestone.death_id": "死亡 ID", 19 | "message.gravestone.death_id_not_found": "找不到 ID 為 %s 的死亡記錄", 20 | "message.gravestone.restore.success": "已成功復原 %s 的物品", 21 | "message.gravestone.death_not_found": "找不到死亡記錄", 22 | "gui.obituary.name": "名稱", 23 | "gui.obituary.title": "死亡資訊", 24 | "gui.obituary.location": "位置", 25 | "gui.obituary.dimension": "維度", 26 | "gui.obituary.title.items": "死亡物品", 27 | "gui.obituary.page": "第 %s 頁,共 %s 頁", 28 | "gui.obituary.time": "日期", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "複製 ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "下一頁", 33 | "button.gravestone.prev": "上一頁", 34 | "config.jade.plugin_gravestone.grave": "墓碑" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "무덤", 3 | "item.gravestone.obituary": "사망 기사", 4 | "entity.gravestone.player_ghost": "플레이어의 유령", 5 | "message.gravestone.grave_of": "%s의 무덤", 6 | "message.gravestone.date_of_death": "%s에 죽음", 7 | "message.gravestone.item_count": "%s개를 보관함", 8 | "message.gravestone.died": "%s이(가) %s에 죽음", 9 | "message.gravestone.deathlocation": "죽은 위치", 10 | "message.gravestone.no_deathlocation": "죽은 위치를 찾을 수 없습니다", 11 | "message.gravestone.create_grave_failed": "무덤이 설치될 수 없습니다", 12 | "message.gravestone.copied": "클립보드에 %s 복사함", 13 | "message.gravestone.restore": "아이템 복구", 14 | "message.gravestone.restore.replace": "Replace", 15 | "message.gravestone.restore.replace.description": "현재 인벤토리의 아이템을 제거하고 죽기 직전의 상태로 되돌립니다", 16 | "message.gravestone.restore.add": "Add", 17 | "message.gravestone.restore.add.description": "현재 인벤토리에 죽기 직전에 가졌던 아이템을 추가합니다", 18 | "message.gravestone.death_id": "사망자 ID", 19 | "message.gravestone.death_id_not_found": "ID %s의 죽음을 찾을 수 없습니다", 20 | "message.gravestone.restore.success": "%s의 아이템이 성공적으로 복구됨", 21 | "message.gravestone.death_not_found": "죽음을 찾을 수 없습니다", 22 | "gui.obituary.name": "이름", 23 | "gui.obituary.title": "사망 기사", 24 | "gui.obituary.location": "위치", 25 | "gui.obituary.dimension": "차원", 26 | "gui.obituary.title.items": "유품", 27 | "gui.obituary.page": "페이지 %s (총 %s 페이지)", 28 | "gui.obituary.time": "날짜", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "클립보드에 ID 복사하기", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "다음", 33 | "button.gravestone.prev": "이전" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/ja_jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "墓", 3 | "item.gravestone.obituary": "死亡記録", 4 | "entity.gravestone.player_ghost": "プレイヤーの幽霊", 5 | "message.gravestone.grave_of": "%sの墓", 6 | "message.gravestone.date_of_death": "%sに死亡した", 7 | "message.gravestone.item_count": "%sスタックを含む", 8 | "message.gravestone.died": "%sは%sに死亡した", 9 | "message.gravestone.deathlocation": "死亡場所", 10 | "message.gravestone.no_deathlocation": "有効な死亡場所がありません", 11 | "message.gravestone.create_grave_failed": "墓が設置できませんでした", 12 | "message.gravestone.copied": "%sをクリップボードにコピーしました", 13 | "message.gravestone.restore": "アイテムを戻す", 14 | "message.gravestone.restore.replace": "置き換える", 15 | "message.gravestone.restore.replace.description": "インベントリからすべてのアイテムを消去し、死亡前に持っていたアイテムに戻します", 16 | "message.gravestone.restore.add": "追加する", 17 | "message.gravestone.restore.add.description": "インベントリに死亡前に持っていたアイテムを追加します", 18 | "message.gravestone.death_id": "Death ID", 19 | "message.gravestone.death_id_not_found": "Death ID [%s] は見つかりませんでした", 20 | "message.gravestone.restore.success": "%sのアイテムを戻すことに成功しました", 21 | "message.gravestone.death_not_found": "死亡は確認されませんでした", 22 | "gui.obituary.name": "名前", 23 | "gui.obituary.title": "死亡記録", 24 | "gui.obituary.location": "場所", 25 | "gui.obituary.dimension": "ディメンション", 26 | "gui.obituary.title.items": "死亡時のアイテム", 27 | "gui.obituary.page": "ページ: %s / %s", 28 | "gui.obituary.time": "日付", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "IDをコピーする", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "次へ", 33 | "button.gravestone.prev": "前へ" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/gui/PageList.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.gui; 2 | 3 | import net.minecraft.client.gui.GuiGraphics; 4 | import net.minecraft.core.NonNullList; 5 | import net.minecraft.world.item.ItemStack; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.stream.Stream; 12 | 13 | public class PageList { 14 | 15 | private final List list; 16 | 17 | public PageList(NonNullList items, ObituaryScreen gui) { 18 | this.list = new ArrayList<>(); 19 | 20 | ItemStack[] temp = new ItemStack[10]; 21 | int i = 0; 22 | for (ItemStack s : items) { 23 | temp[i] = s; 24 | 25 | i++; 26 | if (i > 9) { 27 | list.add(new Page(temp, gui)); 28 | temp = new ItemStack[10]; 29 | i = 0; 30 | } 31 | } 32 | 33 | if (Stream.of(temp).anyMatch(Objects::nonNull)) { 34 | list.add(new Page(temp, gui)); 35 | } 36 | 37 | } 38 | 39 | public int getPages() { 40 | return list.size(); 41 | } 42 | 43 | public void drawPage(GuiGraphics guiGraphics, int p, int mouseX, int mouseY) { 44 | if (p >= list.size()) { 45 | p = list.size() - 1; 46 | } 47 | 48 | Page page = list.get(p); 49 | page.drawPage(guiGraphics, p + 1, list.size(), mouseX, mouseY); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return Arrays.deepToString(list.toArray(new Page[0])); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/net/MessageOpenObituary.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.net; 2 | 3 | import de.maxhenkel.corelib.death.Death; 4 | import de.maxhenkel.corelib.net.Message; 5 | import de.maxhenkel.gravestone.GravestoneMod; 6 | import net.minecraft.network.RegistryFriendlyByteBuf; 7 | import net.minecraft.network.protocol.PacketFlow; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | import net.minecraft.resources.Identifier; 10 | import net.neoforged.neoforge.network.handling.IPayloadContext; 11 | 12 | public class MessageOpenObituary implements Message { 13 | 14 | public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath(GravestoneMod.MODID, "open_obituary")); 15 | 16 | private Death death; 17 | 18 | public MessageOpenObituary() { 19 | 20 | } 21 | 22 | public MessageOpenObituary(Death death) { 23 | this.death = death; 24 | } 25 | 26 | @Override 27 | public PacketFlow getExecutingSide() { 28 | return PacketFlow.CLIENTBOUND; 29 | } 30 | 31 | @Override 32 | public void executeClientSide(IPayloadContext context) { 33 | ClientNetworking.openObituary(death); 34 | } 35 | 36 | @Override 37 | public MessageOpenObituary fromBytes(RegistryFriendlyByteBuf buf) { 38 | death = Death.read(buf.registryAccess(), buf.readNbt()); 39 | return this; 40 | } 41 | 42 | @Override 43 | public void toBytes(RegistryFriendlyByteBuf buf) { 44 | buf.writeNbt(death.write(buf.registryAccess())); 45 | } 46 | 47 | @Override 48 | public Type type() { 49 | return TYPE; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/sv_se.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Gravsten", 3 | "item.gravestone.obituary": "Dödsruna", 4 | "entity.gravestone.player_ghost": "Spökspelare", 5 | "message.gravestone.grave_of": "%ss grav", 6 | "message.gravestone.date_of_death": "Dog den %s", 7 | "message.gravestone.item_count": "Innehåller %s föremålsgrupp(er)", 8 | "message.gravestone.died": "%s dog den %s", 9 | "message.gravestone.deathlocation": "Plats vid dödsfall", 10 | "message.gravestone.no_deathlocation": "Ingen plats vid dödsfall är tillgänglig", 11 | "message.gravestone.create_grave_failed": "Gravstenen kunde inte placeras", 12 | "message.gravestone.copied": "Kopierade %s till urklipp", 13 | "message.gravestone.restore": "Återställ föremål", 14 | "message.gravestone.restore.replace": "Ersätt", 15 | "message.gravestone.restore.replace.description": "Tar bort alla föremål från ditt förråd och ersätter dem med föremålen du hade på dig när du dog", 16 | "message.gravestone.restore.add": "Lägg till", 17 | "message.gravestone.restore.add.description": "Lägger till föremålen du hade i ditt förråd när du dog", 18 | "message.gravestone.death_id": "ID för dödsfall", 19 | "message.gravestone.death_id_not_found": "Dödsfall med ID %s hittades inte", 20 | "message.gravestone.restore.success": "Återställde föremålen för %s", 21 | "gui.obituary.name": "Namn", 22 | "gui.obituary.title": "Dödsruna", 23 | "gui.obituary.location": "Plats", 24 | "gui.obituary.dimension": "Dimension", 25 | "gui.obituary.title.items": "Föremål vid dödsfall", 26 | "gui.obituary.page": "Sida %s av %s", 27 | "gui.obituary.time": "Datum", 28 | "gui.obituary.id": "ID", 29 | "gui.obituary.copy_id": "Kopiera ID", 30 | "gui.gravestone.date_format": "d MMM yyyy HH:mm:ss", 31 | "button.gravestone.next": "Nästa", 32 | "button.gravestone.prev": "Tillbaka" 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Mezar", 3 | "item.gravestone.obituary": "Ölüm Belgesi", 4 | "entity.gravestone.player_ghost": "Oyuncunun Hayaleti", 5 | "message.gravestone.grave_of": "%s Mezarı", 6 | "message.gravestone.date_of_death": "%s tarihinde öldü", 7 | "message.gravestone.item_count": "%s yığın bulundurur", 8 | "message.gravestone.died": "%s, %s tarihinde öldü", 9 | "message.gravestone.deathlocation": "Ölüm konumu", 10 | "message.gravestone.no_deathlocation": "Ölüm konumu mevcut değil", 11 | "message.gravestone.create_grave_failed": "Mezar yerleştirilemedi", 12 | "message.gravestone.copied": "%s panoya kopyalandı", 13 | "message.gravestone.restore": "Eşyaları geri verildi", 14 | "message.gravestone.restore.replace": "Değiştir", 15 | "message.gravestone.restore.replace.description": "Envanterinden tüm eşyaları ve ölmeden öncekilerle değiştirir", 16 | "message.gravestone.restore.add": "Ekle", 17 | "message.gravestone.restore.add.description": "Envanterine ölmeden önce sahip olduğun eşyaları ekler", 18 | "message.gravestone.death_id": "Ölüm ID", 19 | "message.gravestone.death_id_not_found": "%s IDli ölüm bulunamadı", 20 | "message.gravestone.restore.success": "%s oyuncusunun eşyaları başarıyla kurtarıldı", 21 | "message.gravestone.death_not_found": "Ölüm bulunamadı", 22 | "gui.obituary.name": "İsim", 23 | "gui.obituary.title": "Ölüm Belgesi", 24 | "gui.obituary.location": "Konum", 25 | "gui.obituary.dimension": "Boyut", 26 | "gui.obituary.title.items": "Ölüm Eşyaları", 27 | "gui.obituary.page": "Sayfa %s/%s", 28 | "gui.obituary.time": "Tarih", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "IDyi kopyala", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "İleri", 33 | "button.gravestone.prev": "Geri", 34 | "config.jade.plugin_gravestone.grave": "Mezar" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Grave", 3 | "item.gravestone.obituary": "Obituary", 4 | "entity.gravestone.player_ghost": "Ghost of Player", 5 | "message.gravestone.grave_of": "Grave of %s", 6 | "message.gravestone.date_of_death": "Died on %s", 7 | "message.gravestone.item_count": "Contains %s stack(s)", 8 | "message.gravestone.died": "%s died on %s", 9 | "message.gravestone.deathlocation": "Death location", 10 | "message.gravestone.no_deathlocation": "No death location available", 11 | "message.gravestone.create_grave_failed": "Grave could not be placed", 12 | "message.gravestone.copied": "Copied %s to clipboard", 13 | "message.gravestone.restore": "Restore items", 14 | "message.gravestone.restore.replace": "Replace", 15 | "message.gravestone.restore.replace.description": "Removes all items from your inventory and restores the ones that you had just before you died", 16 | "message.gravestone.restore.add": "Add", 17 | "message.gravestone.restore.add.description": "Adds to your inventory the items that you had just before you died", 18 | "message.gravestone.death_id": "Death ID", 19 | "message.gravestone.death_id_not_found": "Death with ID %s not found", 20 | "message.gravestone.restore.success": "Successfully restored %s's items", 21 | "message.gravestone.death_not_found": "Death not found", 22 | "gui.obituary.name": "Name", 23 | "gui.obituary.title": "Obituary", 24 | "gui.obituary.location": "Location", 25 | "gui.obituary.dimension": "Dimension", 26 | "gui.obituary.title.items": "Death Items", 27 | "gui.obituary.page": "Page %s of %s", 28 | "gui.obituary.time": "Date", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Copy ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "Next", 33 | "button.gravestone.prev": "Back", 34 | "config.jade.plugin_gravestone.grave": "Grave" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Grabstein", 3 | "item.gravestone.obituary": "Nachruf", 4 | "entity.gravestone.player_ghost": "Geisterspieler", 5 | "message.gravestone.grave_of": "Grab von %s", 6 | "message.gravestone.date_of_death": "Starb am %s", 7 | "message.gravestone.item_count": "Enthält %s stack(s)", 8 | "message.gravestone.died": "%s starb am %s", 9 | "message.gravestone.deathlocation": "Todesposition", 10 | "message.gravestone.no_deathlocation": "Keine Todesposition vorhanden", 11 | "message.gravestone.create_grave_failed": "Grabstein konnte nicht platziert werden", 12 | "message.gravestone.copied": "%s wurde in die Zwischenablage gelegt", 13 | "message.gravestone.restore": "Gegenstände wiederherstellen", 14 | "message.gravestone.restore.replace": "Ersetzen", 15 | "message.gravestone.restore.replace.description": "Löscht alle Gegenstände vom Inventar und stellt die Todesgegenstände wieder her", 16 | "message.gravestone.restore.add": "Hinzufügen", 17 | "message.gravestone.restore.add.description": "Fügt die Todesgegenstände dem Inventar zu", 18 | "message.gravestone.death_id": "Todes ID", 19 | "message.gravestone.death_id_not_found": "Todes ID %s wurde nicht gefunden", 20 | "message.gravestone.restore.success": "Die Gegenstände von %s wurden erfolgreich wiederhergestellt", 21 | "message.gravestone.death_not_found": "Tod nicht gefunden", 22 | "gui.obituary.name": "Name", 23 | "gui.obituary.title": "Nachruf", 24 | "gui.obituary.location": "Position", 25 | "gui.obituary.dimension": "Dimension", 26 | "gui.obituary.title.items": "Todesgegenstände", 27 | "gui.obituary.page": "Seite %s von %s", 28 | "gui.obituary.time": "Zeit", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "ID kopieren", 31 | "gui.gravestone.date_format": "yyyy.MM.dd HH:mm:ss", 32 | "button.gravestone.next": "Weiter", 33 | "button.gravestone.prev": "Zurück" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/pt_br.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Lápide", 3 | "item.gravestone.obituary": "Obituário", 4 | "entity.gravestone.player_ghost": "Fantasma do jogador", 5 | "message.gravestone.grave_of": "Lápide de %s", 6 | "message.gravestone.date_of_death": "Morreu em %s", 7 | "message.gravestone.item_count": "Contém %s stack(s)", 8 | "message.gravestone.died": "%s morreu em %s", 9 | "message.gravestone.deathlocation": "Localização da morte", 10 | "message.gravestone.no_deathlocation": "Sem localizações de morte disponíveis", 11 | "message.gravestone.create_grave_failed": "A lápide não pôde ser colocada", 12 | "message.gravestone.copied": "Copiar %s para a área de transferência", 13 | "message.gravestone.restore": "Recuperar itens", 14 | "message.gravestone.restore.replace": "Substituir", 15 | "message.gravestone.restore.replace.description": "Remove todos os itens do seu inventário e recupera os que você tinha antes de morrer", 16 | "message.gravestone.restore.add": "Adicionar", 17 | "message.gravestone.restore.add.description": "Adiciona ao seu inventário os itens que você tinha antes de morrer", 18 | "message.gravestone.death_id": "ID da morte", 19 | "message.gravestone.death_id_not_found": "Morte com o ID %s não encontrada", 20 | "message.gravestone.restore.success": "Recuperou os itens de %s com sucesso", 21 | "message.gravestone.death_not_found": "Morte não encontrada", 22 | "gui.obituary.name": "Nome", 23 | "gui.obituary.title": "Obituário", 24 | "gui.obituary.location": "Localização", 25 | "gui.obituary.dimension": "Dimensão", 26 | "gui.obituary.title.items": "Itens", 27 | "gui.obituary.page": "Página %s de %s", 28 | "gui.obituary.time": "Data", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Copiar ID", 31 | "gui.gravestone.date_format": "dd/MM/yyyy HH:mm:ss", 32 | "button.gravestone.next": "Próximo", 33 | "button.gravestone.prev": "Volta" 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/uk_ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Могила", 3 | "item.gravestone.obituary": "Інформація про смерть", 4 | "entity.gravestone.player_ghost": "Гравець-привид", 5 | "message.gravestone.grave_of": "Могила %s", 6 | "message.gravestone.date_of_death": "Помер о %s", 7 | "message.gravestone.item_count": "Зберігав %s стак(-ів)", 8 | "message.gravestone.died": "%s помер о %s", 9 | "message.gravestone.deathlocation": "Місце смерті", 10 | "message.gravestone.no_deathlocation": "Немає доступних місць смертей", 11 | "message.gravestone.create_grave_failed": "Не вдалось встановити могилу", 12 | "message.gravestone.copied": "Скопійовано %s у буфер обміну", 13 | "message.gravestone.restore": "Відновити предмети", 14 | "message.gravestone.restore.replace": "Замінити", 15 | "message.gravestone.restore.replace.description": "Видаляє всі предмети з вашого інвентаря та відновлює ті, що були у вас незадовго до смерті", 16 | "message.gravestone.restore.add": "Додати", 17 | "message.gravestone.restore.add.description": "Додає до вашого інвентарю предмети, які були у вас незадовго до смерті", 18 | "message.gravestone.death_id": "ID смерті", 19 | "message.gravestone.death_id_not_found": "Смерть з ID %s не знайдена", 20 | "message.gravestone.restore.success": "Успішно відновлено предмети гравця %s", 21 | "message.gravestone.death_not_found": "Смерть не знайдена", 22 | "gui.obituary.name": "Ім'я", 23 | "gui.obituary.title": "Інформація про смерть", 24 | "gui.obituary.location": "Місцезнаходження", 25 | "gui.obituary.dimension": "Вимір", 26 | "gui.obituary.title.items": "Предмети при смерті", 27 | "gui.obituary.page": "Стор. %s із %s", 28 | "gui.obituary.time": "Час", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Скопіювати ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "Наст.", 33 | "button.gravestone.prev": "Попер." 34 | } -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/cs_cz.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Hrob", 3 | "item.gravestone.obituary": "Parte", 4 | "entity.gravestone.player_ghost": "Duch Hráče", 5 | "message.gravestone.grave_of": "Hrob %s", 6 | "message.gravestone.date_of_death": "Zemřel dne %s", 7 | "message.gravestone.item_count": "Obsahuje %s předmětů", 8 | "message.gravestone.died": "%s zemřel dne %s", 9 | "message.gravestone.deathlocation": "Místo úmrtí", 10 | "message.gravestone.no_deathlocation": "Není dostupné místo úmrtí", 11 | "message.gravestone.create_grave_failed": "Hrob nemohl být vytvořen", 12 | "message.gravestone.copied": "Zkopírováno %s do schránky", 13 | "message.gravestone.restore": "Obnovení předmětů:", 14 | "message.gravestone.restore.replace": "Obnovení inventáře", 15 | "message.gravestone.restore.replace.description": "Odstraní všechny předměty z inventáře a obnoví ty, které byli v inventáři těsně před smrtí", 16 | "message.gravestone.restore.add": "Přidání do inventáře", 17 | "message.gravestone.restore.add.description": "Přidá do inventáře předměty, které byli v inventáři těsně před smrtí", 18 | "message.gravestone.death_id": "ID úmrtí", 19 | "message.gravestone.death_id_not_found": "Úmrtí s ID %s nebylo nalezeno", 20 | "message.gravestone.restore.success": "Úspěšně obnoveny předměty hráče %s", 21 | "message.gravestone.death_not_found": "Úmrtí nenalezeno", 22 | "gui.obituary.name": "Jméno", 23 | "gui.obituary.title": "Parte", 24 | "gui.obituary.location": "Lokace", 25 | "gui.obituary.dimension": "Dimenze", 26 | "gui.obituary.title.items": "Předměty před smrtí", 27 | "gui.obituary.page": "Strana %s z %s", 28 | "gui.obituary.time": "Datum", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Kopírovat ID", 31 | "gui.gravestone.date_format": "dd.MM.yyyy HH:mm:ss", 32 | "button.gravestone.next": "Další", 33 | "button.gravestone.prev": "Zpět", 34 | "config.jade.plugin_gravestone.grave": "Hrob" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Могила", 3 | "item.gravestone.obituary": "Некролог", 4 | "entity.gravestone.player_ghost": "Призрак игрока", 5 | "message.gravestone.grave_of": "Могила %s", 6 | "message.gravestone.date_of_death": "Умер %s", 7 | "message.gravestone.item_count": "Содержит %s стак(ов)", 8 | "message.gravestone.died": "%s умер %s", 9 | "message.gravestone.deathlocation": "Место смерти", 10 | "message.gravestone.no_deathlocation": "Нет доступных мест смертей", 11 | "message.gravestone.create_grave_failed": "Невозможно разместить могилу", 12 | "message.gravestone.copied": "Скопировано %s в буфер обмена", 13 | "message.gravestone.restore": "Предметы восстановлены", 14 | "message.gravestone.restore.replace": "Заменить", 15 | "message.gravestone.restore.replace.description": "Удаляет все предметы из инвентаря и восстанавливает те, которые были у вас перед смертью", 16 | "message.gravestone.restore.add": "Добавить", 17 | "message.gravestone.restore.add.description": "Добавляет в инвентарь предметы, которые были у вас перед смертью", 18 | "message.gravestone.death_id": "ID смерти", 19 | "message.gravestone.death_id_not_found": "Смерть с идентификатором %s не найдена", 20 | "message.gravestone.restore.success": "Успешно восстановлены элементы для %s", 21 | "message.gravestone.death_not_found": "Смерть не найдена", 22 | "gui.obituary.name": "Имя", 23 | "gui.obituary.title": "Информация о смерти", 24 | "gui.obituary.location": "Координаты", 25 | "gui.obituary.dimension": "Измерение", 26 | "gui.obituary.title.items": "Список предметов", 27 | "gui.obituary.page": "Страница %s из %s", 28 | "gui.obituary.time": "Дата", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Копировать ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "Вперёд", 33 | "button.gravestone.prev": "Назад", 34 | "config.jade.plugin_gravestone.grave": "Могила" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/es_mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Tumba", 3 | "item.gravestone.obituary": "Obituario", 4 | "entity.gravestone.player_ghost": "Fantasma del jugador", 5 | "message.gravestone.grave_of": "Tumba de %s", 6 | "message.gravestone.date_of_death": "Murió en %s", 7 | "message.gravestone.item_count": "Contiene %s stack(s)", 8 | "message.gravestone.died": "%s murió el %s", 9 | "message.gravestone.deathlocation": "Lugar de muerte", 10 | "message.gravestone.no_deathlocation": "Lugar de merte no disponible", 11 | "message.gravestone.create_grave_failed": "La tumba no se pudo colocar", 12 | "message.gravestone.copied": "Copiado %s al portapapeles", 13 | "message.gravestone.restore": "Restourar objetos", 14 | "message.gravestone.restore.replace": "Reemplazar", 15 | "message.gravestone.restore.replace.description": "Elimina todos los objetos de tu inventario y reemplaza los que tenías justo antes de morir.", 16 | "message.gravestone.restore.add": "Añadir", 17 | "message.gravestone.restore.add.description": "Añade a tu inventario los objetos que tenías justo antes de morir.", 18 | "message.gravestone.death_id": "ID de muerte", 19 | "message.gravestone.death_id_not_found": "Muerte con ID %s no encontrada", 20 | "message.gravestone.restore.success": "¡Los objetos de %s se restauraron exitosamente!", 21 | "message.gravestone.death_not_found": "Muerte no encontrada", 22 | "gui.obituary.name": "Nombre", 23 | "gui.obituary.title": "Obituario", 24 | "gui.obituary.location": "Coordenadas", 25 | "gui.obituary.dimension": "Dimensión", 26 | "gui.obituary.title.items": "Objetos", 27 | "gui.obituary.page": "Página %s de %s", 28 | "gui.obituary.time": "Fecha", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "copiar ID", 31 | "gui.gravestone.date_format": "dd/MM/yyyy HH:mm:ss", 32 | "button.gravestone.next": "Siguiente", 33 | "button.gravestone.prev": "Atrás", 34 | "config.jade.plugin_gravestone.grave": "Tumba" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/hu_hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Sír", 3 | "item.gravestone.obituary": "Halotti értesítő", 4 | "entity.gravestone.player_ghost": "Játékos szelleme", 5 | "message.gravestone.grave_of": "%s sírja", 6 | "message.gravestone.date_of_death": "Meghalt %s", 7 | "message.gravestone.item_count": "Tartalmaz %s halm(ok)at", 8 | "message.gravestone.died": "%s meghalt %s", 9 | "message.gravestone.deathlocation": "Halálozási hely", 10 | "message.gravestone.no_deathlocation": "Nincs elérhető halálozási hely", 11 | "message.gravestone.create_grave_failed": "Nem sikerült létrehozni a sírt", 12 | "message.gravestone.copied": "%s másolva a vágólapra", 13 | "message.gravestone.restore": "Tárgyak visszaállítása", 14 | "message.gravestone.restore.replace": "Csere", 15 | "message.gravestone.restore.replace.description": "Az összes tárgyat eltávolítja a táskádból, és visszaállítja azokat, amelyek a halálod előtt nálad voltak", 16 | "message.gravestone.restore.add": "hozzáad", 17 | "message.gravestone.restore.add.description": "Hozzáadja a táskádhoz azokat a tárgyakat, amelyek a halálod előtt nálad voltak", 18 | "message.gravestone.death_id": "Halál ID", 19 | "message.gravestone.death_id_not_found": "Nem található %s azonosítójú halál", 20 | "message.gravestone.restore.success": "Sikeresen visszaállítva %s tárgy", 21 | "message.gravestone.death_not_found": "Nem található halál", 22 | "gui.obituary.name": "Név", 23 | "gui.obituary.title": "Halotti értesítő", 24 | "gui.obituary.location": "Hely", 25 | "gui.obituary.dimension": "Dimenzió", 26 | "gui.obituary.title.items": "Tárgyak", 27 | "gui.obituary.page": "Oldal %s / %s", 28 | "gui.obituary.time": "Dátum", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "ID másolása", 31 | "gui.gravestone.date_format": "yyyy-MM-dd HH:mm:ss", 32 | "button.gravestone.next": "Tovább", 33 | "button.gravestone.prev": "Vissza", 34 | "config.jade.plugin_gravestone.grave": "Sír" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/pl_pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Grób", 3 | "item.gravestone.obituary": "Nekrolog", 4 | "entity.gravestone.player_ghost": "Duch gracza", 5 | "message.gravestone.grave_of": "Grób gracza %s", 6 | "message.gravestone.date_of_death": "Gracz zmarł %s", 7 | "message.gravestone.item_count": "Zawiera stacków: %s", 8 | "message.gravestone.died": "Gracz %s zmarł %s", 9 | "message.gravestone.deathlocation": "Miejsce śmierci", 10 | "message.gravestone.no_deathlocation": "Brak dostępnego miejsca śmierci", 11 | "message.gravestone.create_grave_failed": "Grób nie mógł zostać postawiony", 12 | "message.gravestone.copied": "Skopiowano %s do schowka", 13 | "message.gravestone.restore": "Odzyskaj rzeczy", 14 | "message.gravestone.restore.replace": "Zastąp", 15 | "message.gravestone.restore.replace.description": "Usuwa wszystkie przedmioty z Twojego ekwipunku i odzyskuje te, które miałeś(-aś) tuż przed swoją śmiercią", 16 | "message.gravestone.restore.add": "Dodaj", 17 | "message.gravestone.restore.add.description": "Dodaje do Twojego ekwipunku przedmioty, które miałeś(-aś) tuż przed swoją śmiercią", 18 | "message.gravestone.death_id": "ID śmierci", 19 | "message.gravestone.death_id_not_found": "Nie znaleziono śmierci o ID %s", 20 | "message.gravestone.restore.success": "Pomyślnie odzyskano rzeczy gracza %s", 21 | "message.gravestone.death_not_found": "Nie znaleziono śmierci", 22 | "gui.obituary.name": "Nazwa", 23 | "gui.obituary.title": "Nekrolog", 24 | "gui.obituary.location": "Lokalizacja", 25 | "gui.obituary.dimension": "Wymiar", 26 | "gui.obituary.title.items": "Rzeczy zmarłej osoby", 27 | "gui.obituary.page": "Strona %s z %s", 28 | "gui.obituary.time": "Data", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Kopiuj ID", 31 | "gui.gravestone.date_format": "dd/MM/yyyy HH:mm:ss", 32 | "button.gravestone.next": "Dalej", 33 | "button.gravestone.prev": "Wstecz", 34 | "config.jade.plugin_gravestone.grave": "Grób" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/integration/waila/HUDHandlerGraveStone.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.integration.waila; 2 | 3 | import de.maxhenkel.gravestone.GraveUtils; 4 | import de.maxhenkel.gravestone.GravestoneMod; 5 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 6 | import net.minecraft.ChatFormatting; 7 | import net.minecraft.nbt.CompoundTag; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraft.resources.Identifier; 10 | import snownee.jade.api.BlockAccessor; 11 | import snownee.jade.api.IBlockComponentProvider; 12 | import snownee.jade.api.ITooltip; 13 | import snownee.jade.api.config.IPluginConfig; 14 | 15 | public class HUDHandlerGraveStone implements IBlockComponentProvider { 16 | 17 | public static final HUDHandlerGraveStone INSTANCE = new HUDHandlerGraveStone(); 18 | 19 | private static final Identifier OBJECT_NAME_TAG = Identifier.fromNamespaceAndPath("jade", "object_name"); 20 | 21 | private static final Identifier UID = Identifier.fromNamespaceAndPath(GravestoneMod.MODID, "grave"); 22 | 23 | @Override 24 | public void appendTooltip(ITooltip iTooltip, BlockAccessor blockAccessor, IPluginConfig iPluginConfig) { 25 | if (blockAccessor.getBlockEntity() instanceof GraveStoneTileEntity grave) { 26 | iTooltip.remove(OBJECT_NAME_TAG); 27 | iTooltip.add(grave.getName().copy().withStyle(ChatFormatting.WHITE)); 28 | Component time = GraveUtils.getDate(grave.getDeath().getTimestamp()); 29 | if (time != null) { 30 | iTooltip.add(Component.translatable("message.gravestone.date_of_death", time)); 31 | } 32 | 33 | CompoundTag data = blockAccessor.getServerData(); 34 | if (data.contains("ItemCount")) { 35 | iTooltip.add(Component.translatable("message.gravestone.item_count", data.getIntOr("ItemCount", 0))); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public Identifier getUid() { 42 | return UID; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/lang/es_cl.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.gravestone.gravestone": "Lápida", 3 | "item.gravestone.obituary": "Obituario", 4 | "entity.gravestone.player_ghost": "Fantasma del jugador", 5 | "message.gravestone.grave_of": "Tumba de %s", 6 | "message.gravestone.date_of_death": "Murió en %s", 7 | "message.gravestone.item_count": "Contiene pila(s) de %s", 8 | "message.gravestone.died": "%s murió el %s", 9 | "message.gravestone.deathlocation": "Lugar de fallecimiento", 10 | "message.gravestone.no_deathlocation": "No está disponible el lugar de fallecimiento", 11 | "message.gravestone.create_grave_failed": "La lápida no se pudo situar", 12 | "message.gravestone.copied": "Copiado %s al portapapeles", 13 | "message.gravestone.restore": "Restaurar items", 14 | "message.gravestone.restore.replace": "Reemplazar", 15 | "message.gravestone.restore.replace.description": "Elimina todos los items de tu inventario y restaura los que tenías justo antes de morir.", 16 | "message.gravestone.restore.add": "Agregar", 17 | "message.gravestone.restore.add.description": "Agrega a tu inventario los items que tenías justo antes de morir", 18 | "message.gravestone.death_id": "ID de muerte", 19 | "message.gravestone.death_id_not_found": "Muerte con ID %s no encontrada", 20 | "message.gravestone.restore.success": "Items de %s se restauraron exitosamente", 21 | "message.gravestone.death_not_found": "Muerte no encontrada", 22 | "gui.obituary.name": "Nombre", 23 | "gui.obituary.title": "Información de la muerte del jugador", 24 | "gui.obituary.location": "Lugar", 25 | "gui.obituary.dimension": "Dimensión", 26 | "gui.obituary.title.items": "Objetos portados", 27 | "gui.obituary.page": "Página %s", 28 | "gui.obituary.time": "Fecha", 29 | "gui.obituary.id": "ID", 30 | "gui.obituary.copy_id": "Copiar ID", 31 | "gui.gravestone.date_format": "yyyy/MM/dd HH:mm:ss", 32 | "button.gravestone.next": "Siguiente", 33 | "button.gravestone.prev": "Atrás", 34 | "config.jade.plugin_gravestone.grave": "Tumba" 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/DeathInfo.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | import net.minecraft.core.UUIDUtil; 6 | import net.minecraft.network.RegistryFriendlyByteBuf; 7 | import net.minecraft.network.codec.StreamCodec; 8 | 9 | import java.util.Objects; 10 | import java.util.UUID; 11 | 12 | public class DeathInfo { 13 | 14 | public static final Codec CODEC = RecordCodecBuilder.create(i -> { 15 | return i.group( 16 | UUIDUtil.CODEC.fieldOf("PlayerUUID").forGetter(DeathInfo::getPlayerId), 17 | UUIDUtil.CODEC.fieldOf("DeathID").forGetter(DeathInfo::getDeathId) 18 | ).apply(i, DeathInfo::new); 19 | }); 20 | 21 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 22 | UUIDUtil.STREAM_CODEC, 23 | DeathInfo::getPlayerId, 24 | UUIDUtil.STREAM_CODEC, 25 | DeathInfo::getDeathId, 26 | DeathInfo::new 27 | ); 28 | 29 | private final UUID playerId; 30 | private final UUID deathId; 31 | 32 | public DeathInfo(UUID playerId, UUID deathId) { 33 | this.playerId = playerId; 34 | this.deathId = deathId; 35 | } 36 | 37 | public UUID getPlayerId() { 38 | return playerId; 39 | } 40 | 41 | public UUID getDeathId() { 42 | return deathId; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) { 48 | return true; 49 | } 50 | if (o == null || getClass() != o.getClass()) { 51 | return false; 52 | } 53 | 54 | DeathInfo deathInfo = (DeathInfo) o; 55 | return Objects.equals(playerId, deathInfo.playerId) && Objects.equals(deathId, deathInfo.deathId); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | int result = Objects.hashCode(playerId); 61 | result = 31 * result + Objects.hashCode(deathId); 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/gui/Page.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.gui; 2 | 3 | import de.maxhenkel.corelib.FontColorUtils; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.client.gui.GuiGraphics; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.world.item.ItemStack; 8 | 9 | import java.util.Arrays; 10 | 11 | public class Page { 12 | 13 | private static final int ITEM_START_Y = 60; 14 | 15 | private ItemStack[] items; 16 | private ObituaryScreen gui; 17 | 18 | public Page(ItemStack[] it, ObituaryScreen gui) { 19 | this.gui = gui; 20 | int arraySize = 10; 21 | items = new ItemStack[10]; 22 | if (it.length < 10) { 23 | arraySize = it.length; 24 | } 25 | for (int i = 0; i < items.length && i < arraySize; i++) { 26 | items[i] = it[i]; 27 | } 28 | } 29 | 30 | public void drawPage(GuiGraphics guiGraphics, int page, int pageCount, int mouseX, int mouseY) { 31 | gui.drawCentered(guiGraphics, gui.getFontRenderer(), Component.translatable("gui.obituary.title.items").withStyle(ChatFormatting.UNDERLINE), gui.width / 2, 30, FontColorUtils.getFontColor(ChatFormatting.BLACK)); 32 | gui.drawCentered(guiGraphics, gui.getFontRenderer(), Component.translatable("gui.obituary.page", page, pageCount), gui.width / 2, 43, FontColorUtils.getFontColor(ChatFormatting.BLACK)); 33 | 34 | int y = ITEM_START_Y; 35 | final int space = 12; 36 | 37 | for (ItemStack s : items) { 38 | if (s == null || s.isEmpty()) { 39 | continue; 40 | } 41 | gui.drawItem(guiGraphics, s.getItemName().copy().withStyle(ChatFormatting.ITALIC), y); 42 | gui.drawItemSize(guiGraphics, Component.literal(String.valueOf(s.getCount())), y); 43 | y = y + space; 44 | } 45 | 46 | if (mouseX >= gui.getGuiLeft() + ObituaryScreen.ITEM_SIZE_OFFSET_LEFT && mouseX <= gui.getGuiLeft() + ObituaryScreen.TEXTURE_X - ObituaryScreen.OFFSET_RIGHT) { 47 | if (mouseY >= ITEM_START_Y && mouseY <= ITEM_START_Y + 10 * space) { 48 | int index = (mouseY + 3 - ITEM_START_Y) / 12; 49 | ItemStack stack = items[Math.max(0, Math.min(items.length - 1, index))]; 50 | if (stack != null && !stack.isEmpty()) { 51 | guiGraphics.setTooltipForNextFrame(gui.getFontRenderer(), stack, mouseX, mouseY); 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return Arrays.toString(items); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: [triage] 4 | assignees: henkelmax 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | > [!WARNING] 10 | > This form is **only for bug reports**! 11 | > Please don't abuse this for feature requests or questions. 12 | > Forms that are not filled out properly will be closed without response! 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Bug description 17 | description: A clear and concise description of what the bug is. 18 | validations: 19 | required: true 20 | - type: input 21 | id: mc_version 22 | attributes: 23 | label: Minecraft version 24 | description: The Minecraft version you are using. 25 | placeholder: 1.20.4 26 | validations: 27 | required: true 28 | - type: input 29 | id: mod_version 30 | attributes: 31 | label: Mod version 32 | description: The version of the mod. 33 | placeholder: 1.20.4-1.2.3 34 | validations: 35 | required: true 36 | - type: input 37 | id: mod_loader_version 38 | attributes: 39 | label: Mod loader and version 40 | description: The mod loader and mod loader version you are using. 41 | placeholder: Fabric Loader 0.15.6 / NeoForge 20.4.1 / Forge 48.1.0 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: steps 46 | attributes: 47 | label: Steps to reproduce 48 | description: | 49 | Steps to reproduce the issue. 50 | Please **don't** report issues that are not reproducible. 51 | placeholder: | 52 | 1. Go to '...' 53 | 2. Click on '...' 54 | 3. Scroll down to '...' 55 | 4. See error 56 | validations: 57 | required: true 58 | - type: textarea 59 | id: expected 60 | attributes: 61 | label: Expected behavior 62 | description: A clear and concise description of what you expected to happen. 63 | validations: 64 | required: false 65 | - type: input 66 | id: logs 67 | attributes: 68 | label: Log files 69 | description: | 70 | Please provide log files of the game session in which the problem occurred. 71 | Don't paste the complete logs into the issue. 72 | You can use [https://gist.github.com/](https://gist.github.com/). 73 | placeholder: https://gist.github.com/exampleuser/example 74 | validations: 75 | required: true 76 | - type: textarea 77 | id: screenshots 78 | attributes: 79 | label: Screenshots 80 | description: Screenshots of the issue. 81 | validations: 82 | required: false 83 | -------------------------------------------------------------------------------- /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 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/GraveUtils.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.network.chat.MutableComponent; 7 | import net.minecraft.server.level.ServerPlayer; 8 | import net.minecraft.server.permissions.Permissions; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraft.world.level.block.Block; 12 | import net.minecraft.world.level.block.entity.BlockEntity; 13 | 14 | import javax.annotation.Nullable; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | import java.util.UUID; 18 | 19 | public class GraveUtils { 20 | 21 | public static final UUID EMPTY_UUID = new UUID(0L, 0L); 22 | 23 | @Nullable 24 | public static BlockPos getGraveStoneLocation(Level world, BlockPos pos) { 25 | BlockPos.MutableBlockPos location = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()); 26 | 27 | location.set(world.getWorldBorder().clampToBounds(location)); 28 | 29 | if (world.isOutsideBuildHeight(location) && location.getY() <= world.getMinY()) { 30 | location.set(location.getX(), world.getMinY() + 1, location.getZ()); 31 | } 32 | 33 | while (!world.isOutsideBuildHeight(location)) { 34 | if (isReplaceable(world, location)) { 35 | return location; 36 | } 37 | 38 | location.move(0, 1, 0); 39 | } 40 | 41 | if (GravestoneMod.SERVER_CONFIG.strictPlacement.get()) { 42 | location.set(location.getX(), world.getMaxY(), location.getZ()); 43 | return location; 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public static boolean isReplaceable(Level world, BlockPos pos) { 50 | Block b = world.getBlockState(pos).getBlock(); 51 | 52 | if (world.isEmptyBlock(pos)) { 53 | return true; 54 | } 55 | 56 | return GravestoneMod.SERVER_CONFIG.replaceableBlocks.stream().anyMatch(blockTag -> blockTag.contains(b)); 57 | } 58 | 59 | @Nullable 60 | public static MutableComponent getDate(long timestamp) { 61 | if (timestamp <= 0L) { 62 | return null; 63 | } 64 | SimpleDateFormat dateFormat = new SimpleDateFormat(Component.translatable("gui.gravestone.date_format").getString()); 65 | return Component.literal(dateFormat.format(new Date(timestamp))); 66 | } 67 | 68 | public static boolean canBreakGrave(Level world, Player player, BlockPos pos) { 69 | if (player.isDeadOrDying()) { 70 | return false; 71 | } 72 | if (!GravestoneMod.SERVER_CONFIG.onlyOwnersCanBreak.get()) { 73 | return true; 74 | } 75 | 76 | BlockEntity te = world.getBlockEntity(pos); 77 | 78 | if (!(te instanceof GraveStoneTileEntity grave)) { 79 | return true; 80 | } 81 | 82 | if (player instanceof ServerPlayer p) { 83 | if (p.permissions().hasPermission(Permissions.COMMANDS_MODERATOR)) { 84 | return true; 85 | } 86 | } 87 | UUID uuid = grave.getDeath().getPlayerUUID(); 88 | if (uuid.equals(GraveUtils.EMPTY_UUID)) { 89 | return true; 90 | } 91 | 92 | return player.getUUID().equals(uuid); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # GraveStone Mod 4 | 5 | ## Links 6 | - [Modrinth](https://modrinth.com/mod/gravestone-mod) 7 | - [CurseForge](https://www.curseforge.com/minecraft/mc-mods/gravestone-mod) 8 | - [Credits](https://modrepo.de/minecraft/gravestone/credits) 9 | 10 | --- 11 | 12 | 13 | 14 | ## Basic Functionality 15 | Every time you die, a grave is placed at your position. 16 | To retrieve your items just break the grave. 17 | There is also a config option to get your items back by sneaking on the grave (1.16.3+ only). 18 | 19 | ![](https://i.imgur.com/7CGWKim.png) 20 | 21 | When you right-click the grave it will show information regarding the death of the player. 22 | 23 | ![](https://i.imgur.com/2CtZE7H.png) 24 | 25 | ## Sorting (1.16.3+) 26 | Since version 1.16.3-2.0.0 the grave is now able to store your items back into their original slots. 27 | 28 | ![](https://media1.giphy.com/media/em8yzTjuJOxrMQJqBG/giphy.gif) 29 | 30 | ## The Obituary 31 | Everytime you die you get an obituary. If you don't want that you can disable it in the config. 32 | 33 | It contains: 34 | 35 | - The name of the player 36 | - The dimension the player died in 37 | - The date the player died 38 | - The coordinates where the player died 39 | - An image of the player with its equipment at the time of death 40 | - A list of every item the player had when he died 41 | 42 | If you don't want to keep the obituary after you recovered your items, you can enable the automatic removal in the config. 43 | 44 | ![](https://i.imgur.com/mc2CMaK.png) 45 | 46 | ![](https://i.imgur.com/vOVetsl.png) 47 | 48 | ## Naming the Grave 49 | If you break the grave with silk touch, you get it dropped as an item. 50 | You can also craft graves. 51 | 52 | Naming the grave in an anvil, allows you to have custom text displayed on your grave. 53 | 54 | ![](https://i.imgur.com/fB6gl6Y.png) 55 | 56 | ![](https://i.imgur.com/auVMOS1.png) 57 | 58 | ## Customization 59 | There are several things you can change in the configs: 60 | 61 | - The color of the text on the grave 62 | - If the skull on the grave should be rendered 63 | - Whether you want to get the obituary on death 64 | - The blocks that can get replaced by a grave 65 | - If the death note should get removed form the players inventory when breaking a grave 66 | - If only the owners of the grave should be able to break it 67 | - If a ghost of the player should spawn when breaking the grave 68 | - If the ghost should be friendly and defend the player or if it should attack the player 69 | - If the grave should get broken when sneaking on the grave 70 | - If the items should get sorted back into their original slots when breaking the grave 71 | 72 | ## Recovering Lost Items (1.16.3+) 73 | If you somehow lost your items you can recover them with the recover command. 74 | 75 | The syntax is `/restore `. 76 | 77 | The parameter `player` is the player whose inventory should get restored. 78 | 79 | The parameter `death_id` is the ID of the death. You can find it out by enabling advanced tooltips (F3 + H) and opening the obituary. The death ID also gets written into the logs if a grave couldn't get placed. 80 | 81 | The last parameter defines if you want to get the players inventory replaced (Overwritten) or added (Just adds the items to the players existing inventory). 82 | 83 | 84 | You can also get a pre made restore command by sneaking and right-clicking the obituary as an admin. 85 | 86 | 87 | ## Edge Cases 88 | If you die inside a block, the grave is placed at the next empty space above your location. If there is no free block above your position, or you are above the build limit, your items drop as usual. 89 | 90 | If you fall into the void, the grave is placed at the lowest point in the world where blocks can be placed. If there is no free space it will get placed at the next free spot above. 91 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/commands/RestoreCommand.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.commands; 2 | 3 | import com.mojang.brigadier.Command; 4 | import com.mojang.brigadier.CommandDispatcher; 5 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 6 | import com.mojang.brigadier.tree.LiteralCommandNode; 7 | import de.maxhenkel.corelib.death.Death; 8 | import de.maxhenkel.corelib.death.DeathManager; 9 | import de.maxhenkel.gravestone.GravestoneMod; 10 | import net.minecraft.commands.CommandSourceStack; 11 | import net.minecraft.commands.Commands; 12 | import net.minecraft.commands.arguments.EntityArgument; 13 | import net.minecraft.commands.arguments.UuidArgument; 14 | import net.minecraft.core.NonNullList; 15 | import net.minecraft.network.chat.Component; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.server.permissions.Permissions; 18 | import net.minecraft.world.item.ItemStack; 19 | 20 | import java.util.UUID; 21 | 22 | public class RestoreCommand { 23 | 24 | public static void register(CommandDispatcher dispatcher) { 25 | LiteralArgumentBuilder literalBuilder = Commands.literal("restore").requires((commandSource) -> commandSource.permissions().hasPermission(Permissions.COMMANDS_MODERATOR)); 26 | 27 | Command add = (commandSource) -> { 28 | UUID deathID = UuidArgument.getUuid(commandSource, "death_id"); 29 | ServerPlayer player = EntityArgument.getPlayer(commandSource, "target"); 30 | Death death = DeathManager.getDeath(player.level(), deathID); 31 | if (death == null) { 32 | commandSource.getSource().sendFailure(Component.translatable("message.gravestone.death_id_not_found", deathID.toString())); 33 | return 0; 34 | } 35 | for (ItemStack stack : death.getAllItems()) { 36 | if (!player.getInventory().add(stack)) { 37 | player.drop(stack, false); 38 | } 39 | } 40 | commandSource.getSource().sendSuccess(() -> Component.translatable("message.gravestone.restore.success", player.getDisplayName()), true); 41 | return 1; 42 | }; 43 | 44 | Command replace = (commandSource) -> { 45 | UUID deathID = UuidArgument.getUuid(commandSource, "death_id"); 46 | ServerPlayer player = EntityArgument.getPlayer(commandSource, "target"); 47 | Death death = DeathManager.getDeath(player.level(), deathID); 48 | if (death == null) { 49 | commandSource.getSource().sendFailure(Component.translatable("message.gravestone.death_id_not_found", deathID.toString())); 50 | return 0; 51 | } 52 | player.getInventory().clearContent(); 53 | NonNullList itemStacks = GravestoneMod.GRAVESTONE.get().fillPlayerInventory(player, death); 54 | for (ItemStack stack : itemStacks) { 55 | player.drop(stack, false); 56 | } 57 | commandSource.getSource().sendSuccess(() -> Component.translatable("message.gravestone.restore.success", player.getDisplayName()), true); 58 | return 1; 59 | }; 60 | 61 | literalBuilder 62 | .then(Commands.argument("target", EntityArgument.player()) 63 | .then(Commands.argument("death_id", UuidArgument.uuid()) 64 | .then(Commands.literal("replace").executes(replace)) 65 | .then(Commands.literal("add").executes(add)) 66 | )); 67 | 68 | LiteralCommandNode register = dispatcher.register(literalBuilder); 69 | LiteralArgumentBuilder alias = Commands.literal("restoreinventory").requires((commandSource) -> commandSource.permissions().hasPermission(Permissions.COMMANDS_MODERATOR)).redirect(register); 70 | dispatcher.register(alias); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import de.maxhenkel.corelib.config.ConfigBase; 4 | import de.maxhenkel.corelib.tag.Tag; 5 | import de.maxhenkel.corelib.tag.TagUtils; 6 | import net.minecraft.world.level.block.Block; 7 | import net.neoforged.fml.event.config.ModConfigEvent; 8 | import net.neoforged.neoforge.common.ModConfigSpec; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.stream.Collectors; 15 | 16 | public class ServerConfig extends ConfigBase { 17 | 18 | public final ModConfigSpec.BooleanValue giveObituaries; 19 | public final ModConfigSpec.ConfigValue> replaceableBlocksSpec; 20 | public final ModConfigSpec.BooleanValue removeObituary; 21 | public final ModConfigSpec.BooleanValue onlyOwnersCanBreak; 22 | public final ModConfigSpec.BooleanValue spawnGhost; 23 | public final ModConfigSpec.BooleanValue friendlyGhost; 24 | public final ModConfigSpec.BooleanValue sneakPickup; 25 | public final ModConfigSpec.BooleanValue breakPickup; 26 | public final ModConfigSpec.BooleanValue strictPlacement; 27 | 28 | public List> replaceableBlocks = new ArrayList<>(); 29 | 30 | public ServerConfig(ModConfigSpec.Builder builder) { 31 | super(builder); 32 | giveObituaries = builder 33 | .comment("If this is set to true you get an obituary after you died") 34 | .define("enable_obituary", true); 35 | replaceableBlocksSpec = builder 36 | .comment("The blocks that can be replaced with a grave", "If it starts with '#' it is a tag") 37 | .defineList("replaceable_blocks", Collections.singletonList("#gravestone:grave_replaceable"), Objects::nonNull); 38 | removeObituary = builder 39 | .comment("If this is set to true the obituary will be taken out of your inventory when you break the grave") 40 | .define("remove_obituary", false); 41 | onlyOwnersCanBreak = builder 42 | .comment("If this is set to true only the player that owns the grave and admins can break the grave") 43 | .define("only_owners_can_break", false); 44 | spawnGhost = builder 45 | .comment("If this is set to true the ghost of the dead player will be spawned when the grave is broken") 46 | .define("spawn_ghost", false); 47 | friendlyGhost = builder 48 | .comment("If this is set to true the ghost player will defend the player") 49 | .define("friendly_ghost", true); 50 | sneakPickup = builder 51 | .comment("If this is set to true you get your items back into your inventory by sneaking on the grave") 52 | .define("sneak_pickup", false); 53 | breakPickup = builder 54 | .comment("If this is set to true you get your items sorted back into your inventory by breaking the grave") 55 | .define("break_pickup", true); 56 | strictPlacement = builder 57 | .comment( 58 | "If this is set to true the grave will replace other blocks if there is no free space above", 59 | "Note that this might cause issues with other mods or multiblock structures - This option is not recommended and subject to change" 60 | ) 61 | .define("strict_placement", false); 62 | } 63 | 64 | @Override 65 | public void onReload(ModConfigEvent.Reloading event) { 66 | super.onReload(event); 67 | onConfigChange(); 68 | } 69 | 70 | @Override 71 | public void onLoad(ModConfigEvent.Loading event) { 72 | super.onLoad(event); 73 | onConfigChange(); 74 | } 75 | 76 | private void onConfigChange() { 77 | replaceableBlocks = replaceableBlocksSpec.get().stream().map(TagUtils::getBlock).filter(Objects::nonNull).collect(Collectors.toList()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/entity/PlayerGhostRenderer.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.entity; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import de.maxhenkel.corelib.client.PlayerSkins; 5 | import net.minecraft.client.model.geom.ModelLayers; 6 | import net.minecraft.client.model.player.PlayerModel; 7 | import net.minecraft.client.renderer.SubmitNodeCollector; 8 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 9 | import net.minecraft.client.renderer.entity.HumanoidMobRenderer; 10 | import net.minecraft.client.renderer.entity.LivingEntityRenderer; 11 | import net.minecraft.client.renderer.entity.layers.*; 12 | import net.minecraft.client.renderer.entity.state.AvatarRenderState; 13 | import net.minecraft.client.renderer.state.CameraRenderState; 14 | import net.minecraft.resources.Identifier; 15 | import net.minecraft.world.entity.player.PlayerModelPart; 16 | import net.minecraft.world.entity.player.PlayerModelType; 17 | 18 | public class PlayerGhostRenderer extends LivingEntityRenderer { 19 | 20 | private final PlayerModel playerModel; 21 | private final PlayerModel playerModelSlim; 22 | 23 | public PlayerGhostRenderer(EntityRendererProvider.Context context) { 24 | super(context, null, 0.5F); 25 | playerModel = new PlayerModel(context.bakeLayer(ModelLayers.PLAYER), false); 26 | playerModelSlim = new PlayerModel(context.bakeLayer(ModelLayers.PLAYER_SLIM), true); 27 | model = playerModel; 28 | 29 | addLayer(new PlayerItemInHandLayer<>(this)); 30 | addLayer(new ArrowLayer<>(this, context)); 31 | addLayer(new Deadmau5EarsLayer(this, context.getModelSet())); 32 | addLayer(new CapeLayer(this, context.getModelSet(), context.getEquipmentAssets())); 33 | addLayer(new CustomHeadLayer<>(this, context.getModelSet(), context.getPlayerSkinRenderCache())); 34 | addLayer(new WingsLayer<>(this, context.getModelSet(), context.getEquipmentRenderer())); 35 | addLayer(new ParrotOnShoulderLayer(this, context.getModelSet())); 36 | addLayer(new SpinAttackEffectLayer(this, context.getModelSet())); 37 | addLayer(new BeeStingerLayer<>(this, context)); 38 | } 39 | 40 | @Override 41 | public void submit(AvatarRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) { 42 | super.submit(state, stack, collector, cameraRenderState); 43 | 44 | if (state.skin.model().equals(PlayerModelType.SLIM)) { 45 | model = playerModelSlim; 46 | } else { 47 | model = playerModel; 48 | } 49 | } 50 | 51 | @Override 52 | public Identifier getTextureLocation(AvatarRenderState renderState) { 53 | return renderState.skin.body().texturePath(); 54 | } 55 | 56 | @Override 57 | protected boolean shouldShowName(GhostPlayerEntity entity, double d) { 58 | return false; 59 | } 60 | 61 | @Override 62 | public AvatarRenderState createRenderState() { 63 | return new AvatarRenderState(); 64 | } 65 | 66 | @Override 67 | public void extractRenderState(GhostPlayerEntity entity, AvatarRenderState state, float partialTicks) { 68 | super.extractRenderState(entity, state, partialTicks); 69 | HumanoidMobRenderer.extractHumanoidRenderState(entity, state, partialTicks, itemModelResolver); 70 | state.skin = PlayerSkins.getSkin(entity.getPlayerUUID()); 71 | state.showHat = entity.isWearing(PlayerModelPart.HAT); 72 | state.showJacket = entity.isWearing(PlayerModelPart.JACKET); 73 | state.showLeftPants = entity.isWearing(PlayerModelPart.LEFT_PANTS_LEG); 74 | state.showRightPants = entity.isWearing(PlayerModelPart.RIGHT_PANTS_LEG); 75 | state.showLeftSleeve = entity.isWearing(PlayerModelPart.LEFT_SLEEVE); 76 | state.showRightSleeve = entity.isWearing(PlayerModelPart.RIGHT_SLEEVE); 77 | state.showCape = entity.isWearing(PlayerModelPart.CAPE); 78 | state.id = entity.getId(); 79 | } 80 | 81 | @Override 82 | protected void scale(AvatarRenderState state, PoseStack stack) { 83 | float scale = 0.9375F; 84 | stack.scale(scale, scale, scale); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/resources/assets/gravestone/models/block/gravestone.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": { 3 | "particle": "block/dirt", 4 | "0": "block/dirt", 5 | "1": "block/cobblestone" 6 | }, 7 | "elements": [ 8 | { 9 | "name": "base_top", 10 | "from": [ 1.0, 1.0, 2.0 ], 11 | "to": [ 15.0, 2.0, 12.0 ], 12 | "faces": { 13 | "east": { "texture": "#0", "uv": [ 4.0, 14.0, 14.0, 15.0 ] }, 14 | "south": { "texture": "#0", "uv": [ 1.0, 14.0, 15.0, 15.0 ] }, 15 | "west": { "texture": "#0", "uv": [ 2.0, 14.0, 12.0, 15.0 ] }, 16 | "up": { "texture": "#0", "uv": [ 1.0, 2.0, 15.0, 12.0 ] } 17 | } 18 | }, 19 | { 20 | "name": "stone_base", 21 | "from": [ 1.0, 1.0, 1.0 ], 22 | "to": [ 15.0, 12.0, 2.0 ], 23 | "faces": { 24 | "north": { "texture": "#1", "uv": [ 1.0, 4.0, 15.0, 15.0 ] }, 25 | "east": { "texture": "#1", "uv": [ 14.0, 4.0, 15.0, 15.0 ] }, 26 | "south": { "texture": "#1", "uv": [ 1.0, 4.0, 15.0, 15.0 ] }, 27 | "west": { "texture": "#1", "uv": [ 1.0, 4.0, 2.0, 15.0 ] }, 28 | "up": { "texture": "#1", "uv": [ 1.0, 1.0, 15.0, 2.0 ] } 29 | } 30 | }, 31 | { 32 | "name": "stone_middle", 33 | "from": [ 2.0, 12.0, 1.0 ], 34 | "to": [ 14.0, 14.0, 2.0 ], 35 | "faces": { 36 | "north": { "texture": "#1", "uv": [ 2.0, 2.0, 14.0, 4.0 ] }, 37 | "east": { "texture": "#1", "uv": [ 14.0, 2.0, 15.0, 4.0 ] }, 38 | "south": { "texture": "#1", "uv": [ 2.0, 2.0, 14.0, 4.0 ] }, 39 | "west": { "texture": "#1", "uv": [ 1.0, 2.0, 2.0, 4.0 ] }, 40 | "up": { "texture": "#1", "uv": [ 2.0, 1.0, 14.0, 2.0 ] } 41 | } 42 | }, 43 | { 44 | "name": "stone_top", 45 | "from": [ 3.0, 14.0, 1.0 ], 46 | "to": [ 13.0, 15.0, 2.0 ], 47 | "faces": { 48 | "north": { "texture": "#1", "uv": [ 3.0, 1.0, 13.0, 2.0 ] }, 49 | "east": { "texture": "#1", "uv": [ 14.0, 1.0, 15.0, 2.0 ] }, 50 | "south": { "texture": "#1", "uv": [ 3.0, 1.0, 13.0, 2.0 ] }, 51 | "west": { "texture": "#1", "uv": [ 1.0, 1.0, 2.0, 2.0 ] }, 52 | "up": { "texture": "#1", "uv": [ 3.0, 1.0, 13.0, 2.0 ] } 53 | } 54 | }, 55 | { 56 | "name": "base", 57 | "from": [ 0.0, 0.0, 0.0 ], 58 | "to": [ 16.0, 1.0, 16.0 ], 59 | "faces": { 60 | "north": { "texture": "#0", "uv": [ 0.0, 15.0, 16.0, 16.0 ] }, 61 | "east": { "texture": "#0", "uv": [ 0.0, 15.0, 16.0, 16.0 ] }, 62 | "south": { "texture": "#0", "uv": [ 0.0, 15.0, 16.0, 16.0 ] }, 63 | "west": { "texture": "#0", "uv": [ 0.0, 15.0, 16.0, 16.0 ] }, 64 | "up": { "texture": "#0", "uv": [ 0.0, 0.0, 16.0, 16.0 ] }, 65 | "down": { "texture": "#0", "uv": [ 0.0, 0.0, 16.0, 16.0 ] } 66 | } 67 | }, 68 | { 69 | "name": "base_middle", 70 | "from": [ 4.0, 1.0, 12.0 ], 71 | "to": [ 14.0, 2.0, 14.0 ], 72 | "faces": { 73 | "east": { "texture": "#0", "uv": [ 2.0, 14.0, 4.0, 15.0 ] }, 74 | "south": { "texture": "#0", "uv": [ 4.0, 14.0, 14.0, 15.0 ] }, 75 | "west": { "texture": "#0", "uv": [ 12.0, 14.0, 14.0, 15.0 ] }, 76 | "up": { "texture": "#0", "uv": [ 4.0, 12.0, 14.0, 14.0 ] } 77 | } 78 | }, 79 | { 80 | "name": "base_front", 81 | "from": [ 6.0, 1.0, 14.0 ], 82 | "to": [ 11.0, 2.0, 15.0 ], 83 | "faces": { 84 | "east": { "texture": "#0", "uv": [ 1.0, 14.0, 2.0, 15.0 ] }, 85 | "south": { "texture": "#0", "uv": [ 6.0, 14.0, 11.0, 15.0 ] }, 86 | "west": { "texture": "#0", "uv": [ 14.0, 14.0, 15.0, 15.0 ] }, 87 | "up": { "texture": "#0", "uv": [ 6.0, 14.0, 11.0, 15.0 ] } 88 | } 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/tileentity/GraveStoneTileEntity.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.tileentity; 2 | 3 | import de.maxhenkel.corelib.codec.CodecUtils; 4 | import de.maxhenkel.corelib.codec.ValueInputOutputUtils; 5 | import de.maxhenkel.corelib.death.Death; 6 | import de.maxhenkel.gravestone.GraveUtils; 7 | import de.maxhenkel.gravestone.GravestoneMod; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.core.HolderLookup; 10 | import net.minecraft.nbt.CompoundTag; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.network.chat.ComponentSerialization; 13 | import net.minecraft.network.protocol.Packet; 14 | import net.minecraft.network.protocol.game.ClientGamePacketListener; 15 | import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; 16 | import net.minecraft.world.Nameable; 17 | import net.minecraft.world.level.block.entity.BlockEntity; 18 | import net.minecraft.world.level.block.state.BlockState; 19 | import net.minecraft.world.level.storage.TagValueOutput; 20 | import net.minecraft.world.level.storage.ValueInput; 21 | import net.minecraft.world.level.storage.ValueOutput; 22 | 23 | import javax.annotation.Nullable; 24 | import java.util.Optional; 25 | 26 | public class GraveStoneTileEntity extends BlockEntity implements Nameable { 27 | 28 | protected Death death; 29 | 30 | @Nullable 31 | protected Component customName; 32 | 33 | public GraveStoneTileEntity(BlockPos pos, BlockState state) { 34 | super(GravestoneMod.GRAVESTONE_TILEENTITY.get(), pos, state); 35 | death = new Death.Builder(GraveUtils.EMPTY_UUID, GraveUtils.EMPTY_UUID).build(); 36 | } 37 | 38 | @Override 39 | protected void saveAdditional(ValueOutput valueOutput) { 40 | super.saveAdditional(valueOutput); 41 | death.write(valueOutput, "Death"); 42 | if (customName != null) { 43 | CompoundTag tag = new CompoundTag(); 44 | CodecUtils.toJsonString(ComponentSerialization.CODEC, customName).ifPresent(s -> tag.putString("CustomName", s)); 45 | valueOutput.store(tag); 46 | } 47 | } 48 | 49 | @Override 50 | protected void loadAdditional(ValueInput valueInput) { 51 | super.loadAdditional(valueInput); 52 | death = Death.read(valueInput, "Death"); 53 | Optional optionalName = valueInput.getString("CustomName"); 54 | optionalName.ifPresent(s -> customName = CodecUtils.fromJson(ComponentSerialization.CODEC, s).orElse(null)); 55 | } 56 | 57 | @Override 58 | public Packet getUpdatePacket() { 59 | return ClientboundBlockEntityDataPacket.create(this); 60 | } 61 | 62 | @Override 63 | public CompoundTag getUpdateTag(HolderLookup.Provider provider) { 64 | TagValueOutput valueOutput = ValueInputOutputUtils.createValueOutput(this, provider); 65 | saveAdditional(valueOutput); 66 | return ValueInputOutputUtils.toTag(valueOutput); 67 | } 68 | 69 | public Death getDeath() { 70 | return death; 71 | } 72 | 73 | public void setDeath(Death death) { 74 | this.death = death; 75 | setChanged(); 76 | } 77 | 78 | public void setCustomName(Component name) { 79 | this.customName = name; 80 | setChanged(); 81 | } 82 | 83 | @Override 84 | public Component getName() { 85 | return customName != null ? customName : getDefaultName(); 86 | } 87 | 88 | @Override 89 | public Component getDisplayName() { 90 | return getName(); 91 | } 92 | 93 | @Nullable 94 | @Override 95 | public Component getCustomName() { 96 | return customName; 97 | } 98 | 99 | protected Component getDefaultName() { 100 | String name = death.getPlayerName(); 101 | if (name == null || name.isEmpty()) { 102 | return Component.translatable(GravestoneMod.GRAVESTONE.get().getDescriptionId()); 103 | } 104 | return Component.translatable("message.gravestone.grave_of", name); 105 | } 106 | 107 | @Nullable 108 | public Component getGraveName() { 109 | if (!death.getPlayerName().isEmpty()) { 110 | return Component.literal(death.getPlayerName()); 111 | } else if (customName != null) { 112 | return customName; 113 | } else { 114 | return null; 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/events/DeathEvents.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.events; 2 | 3 | import de.maxhenkel.corelib.death.Death; 4 | import de.maxhenkel.corelib.death.PlayerDeathEvent; 5 | import de.maxhenkel.gravestone.GraveUtils; 6 | import de.maxhenkel.gravestone.GravestoneMod; 7 | import de.maxhenkel.gravestone.blocks.GraveStoneBlock; 8 | import de.maxhenkel.gravestone.items.ObituaryItem; 9 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 10 | import net.minecraft.core.BlockPos; 11 | import net.minecraft.server.level.ServerLevel; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.level.Level; 15 | import net.minecraft.world.level.block.Blocks; 16 | import net.minecraft.world.level.block.entity.BlockEntity; 17 | import net.minecraft.world.level.gamerules.GameRules; 18 | import net.neoforged.bus.api.EventPriority; 19 | import net.neoforged.bus.api.SubscribeEvent; 20 | import net.neoforged.neoforge.event.entity.player.PlayerEvent; 21 | 22 | public class DeathEvents { 23 | 24 | public DeathEvents() { 25 | de.maxhenkel.corelib.death.DeathEvents.register(); 26 | } 27 | 28 | @SubscribeEvent 29 | public void playerDeath(PlayerDeathEvent event) { 30 | event.storeDeath(); 31 | 32 | Death death = event.getDeath(); 33 | Player player = event.getPlayer(); 34 | Level world = player.level(); 35 | 36 | if (keepInventory(player)) { 37 | return; 38 | } 39 | 40 | GravestoneMod.LOGGER.info("The death ID of player {} is {}", death.getPlayerName(), death.getId()); 41 | 42 | BlockPos graveStoneLocation = GraveUtils.getGraveStoneLocation(world, death.getBlockPos()); 43 | 44 | if (GravestoneMod.SERVER_CONFIG.giveObituaries.get()) { 45 | player.getInventory().add(GravestoneMod.OBITUARY.get().toStack(death)); 46 | } 47 | 48 | if (graveStoneLocation == null) { 49 | GravestoneMod.LOGGER.info("Grave of '{}' can't be placed (No space)", death.getPlayerName()); 50 | GravestoneMod.LOGGER.info("The death ID of '{}' is {}", death.getPlayerName(), death.getId().toString()); 51 | return; 52 | } 53 | 54 | world.setBlockAndUpdate(graveStoneLocation, GravestoneMod.GRAVESTONE.get().defaultBlockState().setValue(GraveStoneBlock.FACING, player.getDirection().getOpposite())); 55 | 56 | if (GraveUtils.isReplaceable(world, graveStoneLocation.below())) { 57 | world.setBlockAndUpdate(graveStoneLocation.below(), Blocks.DIRT.defaultBlockState()); 58 | } 59 | 60 | BlockEntity tileentity = world.getBlockEntity(graveStoneLocation); 61 | 62 | if (!(tileentity instanceof GraveStoneTileEntity gravestone)) { 63 | GravestoneMod.LOGGER.info("Grave of '{}' can't be filled with loot (No tileentity found)", death.getPlayerName()); 64 | GravestoneMod.LOGGER.info("The death ID of '{}' is {}", death.getPlayerName(), death.getId().toString()); 65 | return; 66 | } 67 | 68 | gravestone.setDeath(death); 69 | event.removeDrops(); 70 | } 71 | 72 | @SubscribeEvent(priority = EventPriority.LOWEST) 73 | public void onPlayerCloneLast(PlayerEvent.Clone event) { 74 | if (!GravestoneMod.SERVER_CONFIG.giveObituaries.get()) { 75 | return; 76 | } 77 | 78 | if (!event.isWasDeath()) { 79 | return; 80 | } 81 | 82 | if (keepInventory(event.getEntity())) { 83 | return; 84 | } 85 | 86 | for (int i = 0; i < event.getOriginal().getInventory().getContainerSize(); i++) { 87 | ItemStack stack = event.getOriginal().getInventory().getItem(i); 88 | if (stack.getItem() instanceof ObituaryItem) { 89 | event.getEntity().getInventory().add(stack); 90 | } 91 | } 92 | } 93 | 94 | public static boolean keepInventory(Player player) { 95 | try { 96 | if (player.level() instanceof ServerLevel serverLevel) { 97 | return serverLevel.getGameRules().get(GameRules.KEEP_INVENTORY); 98 | } else { 99 | return false; 100 | } 101 | } catch (Exception e) { 102 | return false; 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/tileentity/render/GravestoneRenderer.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.tileentity.render; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.math.Axis; 5 | import de.maxhenkel.corelib.client.PlayerSkins; 6 | import de.maxhenkel.gravestone.GraveUtils; 7 | import de.maxhenkel.gravestone.GravestoneMod; 8 | import de.maxhenkel.gravestone.blocks.GraveStoneBlock; 9 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 10 | import net.minecraft.client.gui.Font; 11 | import net.minecraft.client.model.geom.ModelLayers; 12 | import net.minecraft.client.model.object.skull.SkullModel; 13 | import net.minecraft.client.renderer.SubmitNodeCollector; 14 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; 15 | import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; 16 | import net.minecraft.client.renderer.feature.ModelFeatureRenderer; 17 | import net.minecraft.client.renderer.state.CameraRenderState; 18 | import net.minecraft.client.renderer.texture.OverlayTexture; 19 | import net.minecraft.network.chat.Component; 20 | import net.minecraft.resources.Identifier; 21 | import net.minecraft.world.level.block.state.BlockState; 22 | import net.minecraft.world.phys.Vec3; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | public class GravestoneRenderer implements BlockEntityRenderer { 26 | 27 | private final BlockEntityRendererProvider.Context renderer; 28 | 29 | public GravestoneRenderer(BlockEntityRendererProvider.Context renderer) { 30 | this.renderer = renderer; 31 | } 32 | 33 | @Override 34 | public GravestoneRenderState createRenderState() { 35 | return new GravestoneRenderState(); 36 | } 37 | 38 | @Override 39 | public void extractRenderState(GraveStoneTileEntity grave, GravestoneRenderState state, float partialTicks, Vec3 pos, @Nullable ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) { 40 | BlockEntityRenderer.super.extractRenderState(grave, state, partialTicks, pos, crumblingOverlay); 41 | Component graveName = grave.getGraveName(); 42 | state.name = graveName == null ? null : graveName.getVisualOrderText(); 43 | state.direction = grave.getBlockState().getValue(GraveStoneBlock.FACING); 44 | 45 | BlockState blockState = grave.getLevel().getBlockState(grave.getBlockPos().below()); 46 | //TODO fix with slime block 47 | state.renderHead = blockState.isRedstoneConductor(grave.getLevel(), grave.getBlockPos()); 48 | 49 | state.playerId = grave.getDeath().getPlayerUUID(); 50 | } 51 | 52 | @Override 53 | public void submit(GravestoneRenderState state, PoseStack stack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) { 54 | if (state.name == null) { 55 | return; 56 | } 57 | 58 | stack.pushPose(); 59 | stack.translate(0.5D, 1D, 0.5D); 60 | stack.mulPose(Axis.XP.rotationDegrees(180F)); 61 | stack.mulPose(Axis.YP.rotationDegrees(180F + state.direction.toYRot())); 62 | 63 | Font font = renderer.font(); 64 | 65 | int textWidth = font.width(state.name); 66 | double textScale = Math.min(0.8D / textWidth, 0.02D); 67 | 68 | stack.translate(0D, 0.3D, 0.37D); 69 | stack.scale((float) textScale, (float) textScale, (float) textScale); 70 | float left = (float) (-textWidth / 2); 71 | collector.submitText(stack, left, 0F, state.name, false, Font.DisplayMode.NORMAL, state.lightCoords, 0xFFFFFFFF, 0, 0); 72 | stack.popPose(); 73 | 74 | if (state.renderHead && state.playerId != null && !state.playerId.equals(GraveUtils.EMPTY_UUID) && GravestoneMod.CLIENT_CONFIG.renderSkull.get()) { 75 | renderSkull(collector, state, stack); 76 | } 77 | } 78 | 79 | public void renderSkull(SubmitNodeCollector collector, GravestoneRenderState state, PoseStack stack) { 80 | SkullModel model = new SkullModel(renderer.bakeLayer(ModelLayers.PLAYER_HEAD)); 81 | Identifier resourcelocation = PlayerSkins.getSkin(state.playerId).body().texturePath(); 82 | 83 | stack.pushPose(); 84 | 85 | stack.translate(0.5D, 0D, 0.5D); 86 | 87 | stack.mulPose(Axis.YP.rotationDegrees(180F - state.direction.toYRot())); 88 | stack.mulPose(Axis.YP.rotationDegrees(-26F)); 89 | stack.translate(0D, -0.14D, 0.18D); 90 | stack.mulPose(Axis.XP.rotationDegrees(180F)); 91 | stack.mulPose(Axis.XP.rotationDegrees(-61F)); 92 | 93 | int light = state.lightCoords; 94 | collector.submitCustomGeometry(stack, model.renderType(resourcelocation), (pose, vertexConsumer) -> { 95 | PoseStack s = new PoseStack(); 96 | s.mulPose(pose.pose()); 97 | model.renderToBuffer(s, vertexConsumer, light, OverlayTexture.NO_OVERLAY); 98 | }); 99 | 100 | stack.popPose(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/items/ObituaryItem.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.items; 2 | 3 | import de.maxhenkel.corelib.death.Death; 4 | import de.maxhenkel.corelib.death.DeathManager; 5 | import de.maxhenkel.gravestone.DeathInfo; 6 | import de.maxhenkel.gravestone.GravestoneMod; 7 | import de.maxhenkel.gravestone.net.MessageOpenObituary; 8 | import net.minecraft.ChatFormatting; 9 | import net.minecraft.core.UUIDUtil; 10 | import net.minecraft.core.component.DataComponents; 11 | import net.minecraft.nbt.CompoundTag; 12 | import net.minecraft.network.chat.ClickEvent; 13 | import net.minecraft.network.chat.Component; 14 | import net.minecraft.network.chat.ComponentUtils; 15 | import net.minecraft.network.chat.HoverEvent; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.server.permissions.Permissions; 18 | import net.minecraft.util.Util; 19 | import net.minecraft.world.InteractionHand; 20 | import net.minecraft.world.InteractionResult; 21 | import net.minecraft.world.entity.player.Player; 22 | import net.minecraft.world.item.Item; 23 | import net.minecraft.world.item.ItemStack; 24 | import net.minecraft.world.item.component.CustomData; 25 | import net.minecraft.world.level.Level; 26 | import net.neoforged.neoforge.network.PacketDistributor; 27 | 28 | import javax.annotation.Nullable; 29 | import java.util.Optional; 30 | 31 | public class ObituaryItem extends Item { 32 | 33 | public ObituaryItem(Item.Properties properties) { 34 | super(properties.stacksTo(1)); 35 | } 36 | 37 | @Override 38 | public InteractionResult use(Level world, Player p, InteractionHand hand) { 39 | if (!(p instanceof ServerPlayer player)) { 40 | return InteractionResult.SUCCESS; 41 | } 42 | 43 | ItemStack itemInHand = player.getItemInHand(hand); 44 | convert(itemInHand); 45 | Death death = fromStack(player, itemInHand); 46 | 47 | if (death == null) { 48 | player.displayClientMessage(Component.translatable("message.gravestone.death_not_found"), true); 49 | } else if (player.isShiftKeyDown()) { 50 | if (player.permissions().hasPermission(Permissions.COMMANDS_MODERATOR)) { 51 | Component replace = ComponentUtils.wrapInSquareBrackets(Component.translatable("message.gravestone.restore.replace")) 52 | .withStyle((style) -> style 53 | .applyFormat(ChatFormatting.GREEN) 54 | .withClickEvent(new ClickEvent.SuggestCommand("/restore @s " + death.getId().toString() + " replace")) 55 | .withHoverEvent(new HoverEvent.ShowText(Component.translatable("message.gravestone.restore.replace.description"))) 56 | ); 57 | Component add = ComponentUtils.wrapInSquareBrackets(Component.translatable("message.gravestone.restore.add")) 58 | .withStyle((style) -> style 59 | .applyFormat(ChatFormatting.GREEN) 60 | .withClickEvent(new ClickEvent.SuggestCommand("/restore @s " + death.getId().toString() + " add")) 61 | .withHoverEvent(new HoverEvent.ShowText(Component.translatable("message.gravestone.restore.add.description"))) 62 | ); 63 | player.sendSystemMessage(Component.translatable("message.gravestone.restore").append(" ").append(replace).append(" ").append(add)); 64 | } 65 | } else { 66 | PacketDistributor.sendToPlayer(player, new MessageOpenObituary(death)); 67 | } 68 | return InteractionResult.SUCCESS; 69 | } 70 | 71 | @Nullable 72 | public Death fromStack(ServerPlayer player, ItemStack stack) { 73 | DeathInfo deathInfo = stack.get(GravestoneMod.DEATH_DATA_COMPONENT); 74 | if (deathInfo == null) { 75 | return null; 76 | } 77 | return DeathManager.getDeath(player.level(), deathInfo.getPlayerId(), deathInfo.getDeathId()); 78 | } 79 | 80 | public ItemStack toStack(Death death) { 81 | ItemStack stack = new ItemStack(this); 82 | stack.set(GravestoneMod.DEATH_DATA_COMPONENT, new DeathInfo(death.getPlayerUUID(), death.getId())); 83 | return stack; 84 | } 85 | 86 | public static void convert(ItemStack stack) { 87 | if (!(stack.getItem() instanceof ObituaryItem)) { 88 | return; 89 | } 90 | if (stack.has(GravestoneMod.DEATH_DATA_COMPONENT)) { 91 | return; 92 | } 93 | CustomData customData = stack.get(DataComponents.CUSTOM_DATA); 94 | if (customData == null) { 95 | return; 96 | } 97 | CompoundTag compoundTag = customData.copyTag(); 98 | Optional deathOptional = compoundTag.getCompound("Death"); 99 | if (deathOptional.isEmpty()) { 100 | return; 101 | } 102 | CompoundTag death = deathOptional.get(); 103 | DeathInfo info = new DeathInfo(death.read("PlayerUUID", UUIDUtil.CODEC).orElse(Util.NIL_UUID), death.read("DeathID", UUIDUtil.CODEC).orElse(Util.NIL_UUID)); 104 | compoundTag.remove("Death"); 105 | if (compoundTag.isEmpty()) { 106 | stack.remove(DataComponents.CUSTOM_DATA); 107 | } else { 108 | stack.set(DataComponents.CUSTOM_DATA, CustomData.of(compoundTag)); 109 | } 110 | stack.set(GravestoneMod.DEATH_DATA_COMPONENT, info); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/GravestoneMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone; 2 | 3 | import de.maxhenkel.corelib.CommonRegistry; 4 | import de.maxhenkel.gravestone.blocks.GraveStoneBlock; 5 | import de.maxhenkel.gravestone.commands.RestoreCommand; 6 | import de.maxhenkel.gravestone.entity.GhostPlayerEntity; 7 | import de.maxhenkel.gravestone.events.CreativeTabEvents; 8 | import de.maxhenkel.gravestone.events.DeathEvents; 9 | import de.maxhenkel.gravestone.items.ObituaryItem; 10 | import de.maxhenkel.gravestone.net.MessageOpenObituary; 11 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 12 | import net.minecraft.core.component.DataComponentType; 13 | import net.minecraft.core.registries.BuiltInRegistries; 14 | import net.minecraft.core.registries.Registries; 15 | import net.minecraft.world.entity.EntityType; 16 | import net.minecraft.world.entity.MobCategory; 17 | import net.minecraft.world.item.BlockItem; 18 | import net.minecraft.world.item.Item; 19 | import net.minecraft.world.level.block.Block; 20 | import net.minecraft.world.level.block.entity.BlockEntityType; 21 | import net.minecraft.world.level.block.state.BlockBehaviour; 22 | import net.neoforged.bus.api.IEventBus; 23 | import net.neoforged.bus.api.SubscribeEvent; 24 | import net.neoforged.fml.common.EventBusSubscriber; 25 | import net.neoforged.fml.common.Mod; 26 | import net.neoforged.fml.config.ModConfig; 27 | import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; 28 | import net.neoforged.neoforge.common.NeoForge; 29 | import net.neoforged.neoforge.event.RegisterCommandsEvent; 30 | import net.neoforged.neoforge.event.entity.EntityAttributeCreationEvent; 31 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 32 | import net.neoforged.neoforge.network.registration.PayloadRegistrar; 33 | import net.neoforged.neoforge.registries.DeferredHolder; 34 | import net.neoforged.neoforge.registries.DeferredRegister; 35 | import org.apache.logging.log4j.LogManager; 36 | import org.apache.logging.log4j.Logger; 37 | 38 | @Mod(GravestoneMod.MODID) 39 | @EventBusSubscriber(modid = GravestoneMod.MODID) 40 | public class GravestoneMod { 41 | 42 | public static final String MODID = "gravestone"; 43 | 44 | public static final Logger LOGGER = LogManager.getLogger(GravestoneMod.MODID); 45 | 46 | private static final DeferredRegister.Blocks BLOCK_REGISTER = DeferredRegister.createBlocks(GravestoneMod.MODID); 47 | public static final DeferredHolder GRAVESTONE = BLOCK_REGISTER.registerBlock("gravestone", GraveStoneBlock::new, BlockBehaviour.Properties::of); 48 | 49 | private static final DeferredRegister.Items ITEM_REGISTER = DeferredRegister.createItems(GravestoneMod.MODID); 50 | public static final DeferredHolder GRAVESTONE_ITEM = ITEM_REGISTER.registerSimpleBlockItem(GRAVESTONE); 51 | public static final DeferredHolder OBITUARY = ITEM_REGISTER.registerItem("obituary", ObituaryItem::new); 52 | 53 | private static final DeferredRegister> BLOCK_ENTITY_REGISTER = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, GravestoneMod.MODID); 54 | public static final DeferredHolder, BlockEntityType> GRAVESTONE_TILEENTITY = BLOCK_ENTITY_REGISTER.register("gravestone", () -> new BlockEntityType<>(GraveStoneTileEntity::new, GRAVESTONE.get())); 55 | 56 | private static final DeferredRegister> ENTITY_REGISTER = DeferredRegister.create(BuiltInRegistries.ENTITY_TYPE, GravestoneMod.MODID); 57 | public static final DeferredHolder, EntityType> GHOST = ENTITY_REGISTER.register("player_ghost", () -> 58 | CommonRegistry.registerEntity(GravestoneMod.MODID, "player_ghost", MobCategory.MONSTER, GhostPlayerEntity.class, builder -> builder.sized(0.6F, 1.95F)) 59 | ); 60 | 61 | private static final DeferredRegister.DataComponents DATA_COMPONENT_TYPE_REGISTER = DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, GravestoneMod.MODID); 62 | public static final DeferredHolder, DataComponentType> DEATH_DATA_COMPONENT = DATA_COMPONENT_TYPE_REGISTER.registerComponentType("death", (b) -> b.persistent(DeathInfo.CODEC).networkSynchronized(DeathInfo.STREAM_CODEC)); 63 | 64 | public static ServerConfig SERVER_CONFIG; 65 | public static ClientConfig CLIENT_CONFIG; 66 | 67 | public GravestoneMod(IEventBus eventBus) { 68 | eventBus.addListener(CreativeTabEvents::onCreativeModeTabBuildContents); 69 | 70 | SERVER_CONFIG = CommonRegistry.registerConfig(MODID, ModConfig.Type.SERVER, ServerConfig.class, true); 71 | CLIENT_CONFIG = CommonRegistry.registerConfig(MODID, ModConfig.Type.CLIENT, ClientConfig.class, true); 72 | 73 | BLOCK_REGISTER.register(eventBus); 74 | ITEM_REGISTER.register(eventBus); 75 | BLOCK_ENTITY_REGISTER.register(eventBus); 76 | ENTITY_REGISTER.register(eventBus); 77 | DATA_COMPONENT_TYPE_REGISTER.register(eventBus); 78 | } 79 | 80 | @SubscribeEvent 81 | static void commonSetup(FMLCommonSetupEvent event) { 82 | NeoForge.EVENT_BUS.register(new DeathEvents()); 83 | } 84 | 85 | 86 | @SubscribeEvent 87 | static void onRegisterCommands(RegisterCommandsEvent event) { 88 | RestoreCommand.register(event.getDispatcher()); 89 | } 90 | 91 | @SubscribeEvent 92 | static void onRegisterPayloadHandler(RegisterPayloadHandlersEvent event) { 93 | PayloadRegistrar registrar = event.registrar(MODID).versioned("0"); 94 | CommonRegistry.registerMessage(registrar, MessageOpenObituary.class); 95 | } 96 | 97 | @SubscribeEvent 98 | static void registerAttributes(EntityAttributeCreationEvent event) { 99 | event.put(GravestoneMod.GHOST.get(), GhostPlayerEntity.getGhostAttributes()); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/entity/GhostPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.entity; 2 | 3 | import de.maxhenkel.gravestone.GraveUtils; 4 | import de.maxhenkel.gravestone.GravestoneMod; 5 | import net.minecraft.core.UUIDUtil; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.network.syncher.EntityDataAccessor; 8 | import net.minecraft.network.syncher.EntityDataSerializers; 9 | import net.minecraft.network.syncher.SynchedEntityData; 10 | import net.minecraft.server.level.ServerLevel; 11 | import net.minecraft.world.entity.*; 12 | import net.minecraft.world.entity.ai.attributes.AttributeSupplier; 13 | import net.minecraft.world.entity.ai.attributes.Attributes; 14 | import net.minecraft.world.entity.ai.goal.*; 15 | import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; 16 | import net.minecraft.world.entity.monster.Creeper; 17 | import net.minecraft.world.entity.monster.Monster; 18 | import net.minecraft.world.entity.monster.Slime; 19 | import net.minecraft.world.entity.player.Player; 20 | import net.minecraft.world.entity.player.PlayerModelPart; 21 | import net.minecraft.world.item.ItemStack; 22 | import net.minecraft.world.level.Level; 23 | import net.minecraft.world.level.storage.ValueInput; 24 | import net.minecraft.world.level.storage.ValueOutput; 25 | 26 | import javax.annotation.Nullable; 27 | import java.util.EnumMap; 28 | import java.util.Optional; 29 | import java.util.UUID; 30 | 31 | public class GhostPlayerEntity extends Monster { 32 | 33 | private static final EntityDataAccessor>> PLAYER_UUID = SynchedEntityData.defineId(GhostPlayerEntity.class, EntityDataSerializers.OPTIONAL_LIVING_ENTITY_REFERENCE); 34 | private static final EntityDataAccessor PLAYER_MODEL = SynchedEntityData.defineId(GhostPlayerEntity.class, EntityDataSerializers.BYTE); 35 | 36 | public GhostPlayerEntity(EntityType type, Level world) { 37 | super(type, world); 38 | } 39 | 40 | public GhostPlayerEntity(Level world, UUID playerUUID, Component name, EnumMap equipment, byte model) { 41 | this(GravestoneMod.GHOST.get(), world); 42 | setPlayerUUID(playerUUID); 43 | setCustomName(name); 44 | setModel(model); 45 | //TODO Check 46 | //Arrays.fill(armorDropChances, 0F); 47 | //Arrays.fill(handDropChances, 0F); 48 | 49 | for (EnumMap.Entry entry : equipment.entrySet()) { 50 | setItemSlot(entry.getKey(), entry.getValue()); 51 | } 52 | } 53 | 54 | @Override 55 | protected void defineSynchedData(SynchedEntityData.Builder builder) { 56 | super.defineSynchedData(builder); 57 | builder.define(PLAYER_UUID, Optional.empty()); 58 | builder.define(PLAYER_MODEL, (byte) 0); 59 | } 60 | 61 | public static AttributeSupplier getGhostAttributes() { 62 | return Mob.createMobAttributes() 63 | .add(Attributes.MAX_HEALTH, 20D) 64 | .add(Attributes.ATTACK_DAMAGE, 3D) 65 | .add(Attributes.ARMOR, 2D) 66 | .add(Attributes.MOVEMENT_SPEED, 0.23000000417232513D) 67 | .add(Attributes.FOLLOW_RANGE, 35D).build(); 68 | } 69 | 70 | @Override 71 | public boolean shouldShowName() { 72 | return false; 73 | } 74 | 75 | @Override 76 | protected void registerGoals() { 77 | this.goalSelector.addGoal(0, new FloatGoal(this)); 78 | this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, false)); 79 | this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); 80 | this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); 81 | this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); 82 | this.goalSelector.addGoal(9, new RandomLookAroundGoal(this)); 83 | 84 | if (GravestoneMod.SERVER_CONFIG.friendlyGhost.get()) { 85 | targetSelector.addGoal(10, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, false, true, (entity, level) -> 86 | entity != null 87 | && !entity.isInvisible() 88 | && (entity instanceof Monster || entity instanceof Slime) 89 | && !(entity instanceof Creeper) 90 | && !(entity instanceof GhostPlayerEntity) 91 | )); 92 | } else { 93 | targetSelector.addGoal(10, new NearestAttackableTargetGoal<>(this, Player.class, true)); 94 | } 95 | } 96 | 97 | @Override 98 | public boolean isInvertedHealAndHarm() { 99 | return true; 100 | } 101 | 102 | public void setPlayerUUID(UUID uuid) { 103 | this.getEntityData().set(PLAYER_UUID, Optional.of(EntityReference.of(uuid))); 104 | if (uuid.toString().equals("af3bd5f4-8634-4700-8281-e4cc851be180")) { 105 | setOverpowered(); 106 | } 107 | } 108 | 109 | private void setOverpowered() { 110 | getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(35.0D); 111 | getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.4D); 112 | getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(20.0D); 113 | } 114 | 115 | @Override 116 | public void setCustomName(@Nullable Component name) { 117 | super.setCustomName(name); 118 | if (name != null && name.getString().equals("henkelmax")) { 119 | setOverpowered(); 120 | } 121 | } 122 | 123 | public UUID getPlayerUUID() { 124 | return getEntityData().get(PLAYER_UUID).map(EntityReference::getUUID).orElse(GraveUtils.EMPTY_UUID); 125 | } 126 | 127 | public void setModel(byte model) { 128 | entityData.set(PLAYER_MODEL, model); 129 | } 130 | 131 | public byte getModel() { 132 | return entityData.get(PLAYER_MODEL); 133 | } 134 | 135 | public boolean isWearing(PlayerModelPart part) { 136 | return (getModel() & part.getMask()) == part.getMask(); 137 | } 138 | 139 | @Override 140 | protected void addAdditionalSaveData(ValueOutput valueOutput) { 141 | super.addAdditionalSaveData(valueOutput); 142 | getEntityData().get(PLAYER_UUID).ifPresent(uuid -> { 143 | valueOutput.store("PlayerUUID", UUIDUtil.CODEC, uuid.getUUID()); 144 | }); 145 | valueOutput.putByte("Model", getModel()); 146 | } 147 | 148 | @Override 149 | protected void readAdditionalSaveData(ValueInput valueInput) { 150 | super.readAdditionalSaveData(valueInput); 151 | valueInput.read("PlayerUUID", UUIDUtil.CODEC).ifPresent(this::setPlayerUUID); 152 | setModel(valueInput.getByteOr("Model", (byte) 0)); 153 | } 154 | 155 | @Override 156 | public boolean doHurtTarget(ServerLevel level, Entity entity) { 157 | if (entity.getName().getString().equals("henkelmax") || entity.getUUID().toString().equals("af3bd5f4-8634-4700-8281-e4cc851be180")) { 158 | return true; 159 | } else { 160 | return super.doHurtTarget(level, entity); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 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\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/gui/ObituaryScreen.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.gui; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import de.maxhenkel.corelib.FontColorUtils; 5 | import de.maxhenkel.corelib.death.Death; 6 | import de.maxhenkel.gravestone.GraveUtils; 7 | import de.maxhenkel.gravestone.GravestoneMod; 8 | import de.maxhenkel.gravestone.entity.DummyPlayer; 9 | import net.minecraft.ChatFormatting; 10 | import net.minecraft.client.gui.Font; 11 | import net.minecraft.client.gui.GuiGraphics; 12 | import net.minecraft.client.gui.components.Button; 13 | import net.minecraft.client.gui.screens.Screen; 14 | import net.minecraft.client.input.MouseButtonEvent; 15 | import net.minecraft.client.renderer.RenderPipelines; 16 | import net.minecraft.client.resources.sounds.SimpleSoundInstance; 17 | import net.minecraft.core.BlockPos; 18 | import net.minecraft.network.chat.*; 19 | import net.minecraft.resources.Identifier; 20 | import net.minecraft.sounds.SoundEvents; 21 | 22 | import java.util.Collections; 23 | 24 | public class ObituaryScreen extends Screen { 25 | 26 | private static final Identifier GUI_TEXTURE = Identifier.fromNamespaceAndPath(GravestoneMod.MODID, "textures/gui/info.png"); 27 | protected static final int TEXTURE_X = 163; 28 | protected static final int TEXTURE_Y = 165; 29 | protected static final int ITEM_OFFSET_LEFT = 40; 30 | protected static final int OFFSET_LEFT = 7; 31 | protected static final int OFFSET_RIGHT = 14; 32 | protected static final int ITEM_SIZE_OFFSET_LEFT = 15; 33 | 34 | private DummyPlayer player; 35 | private Death death; 36 | 37 | private Button buttonPrev; 38 | private Button buttonNext; 39 | 40 | private int page; 41 | 42 | private PageList pageList; 43 | 44 | private int guiLeft; 45 | private int guiTop; 46 | 47 | public ObituaryScreen(Death death) { 48 | super(Component.translatable("gui.obituary.title")); 49 | this.death = death; 50 | this.page = 0; 51 | this.pageList = new PageList(death.getAllItems(), this); 52 | } 53 | 54 | @Override 55 | protected void init() { 56 | super.init(); 57 | 58 | guiLeft = (width - TEXTURE_X) / 2; 59 | guiTop = (height - TEXTURE_Y) / 2; 60 | 61 | int left = (width - TEXTURE_X) / 2; 62 | buttonPrev = addRenderableWidget(Button.builder(Component.translatable("button.gravestone.prev"), button -> { 63 | page--; 64 | if (page < 0) { 65 | page = 0; 66 | } 67 | checkButtons(); 68 | }).bounds(left, 190, 75, 20).build()); 69 | 70 | buttonNext = addRenderableWidget(Button.builder(Component.translatable("button.gravestone.next"), button -> { 71 | page++; 72 | if (page > pageList.getPages()) { 73 | page = pageList.getPages(); 74 | } 75 | checkButtons(); 76 | }).bounds(left + TEXTURE_X - 75, 190, 75, 20).build()); 77 | buttonPrev.active = false; 78 | if (pageList.getPages() <= 0) { 79 | buttonNext.active = false; 80 | } 81 | } 82 | 83 | protected void checkButtons() { 84 | if (page <= 0) { 85 | buttonPrev.active = false; 86 | } else { 87 | buttonPrev.active = true; 88 | } 89 | 90 | if (page >= pageList.getPages()) { 91 | buttonNext.active = false; 92 | } else { 93 | buttonNext.active = true; 94 | } 95 | } 96 | 97 | @Override 98 | public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { 99 | super.render(guiGraphics, mouseX, mouseY, partialTicks); 100 | int left = (width - TEXTURE_X) / 2; 101 | 102 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, GUI_TEXTURE, left, 20, 0, 0, TEXTURE_X, TEXTURE_Y, 256, 256); 103 | 104 | if (page == 0) { 105 | drawFirstPage(guiGraphics, mouseX, mouseY); 106 | } else if (page > 0) { 107 | if (pageList.getPages() < page - 1) { 108 | 109 | } else { 110 | pageList.drawPage(guiGraphics, page - 1, mouseX, mouseY); 111 | } 112 | } 113 | } 114 | 115 | public void drawFirstPage(GuiGraphics guiGraphics, int mouseX, int mouseY) { 116 | drawCentered(guiGraphics, font, Component.translatable("gui.obituary.title").withStyle(ChatFormatting.UNDERLINE), width / 2, 30, FontColorUtils.getFontColor(ChatFormatting.BLACK)); 117 | 118 | int height = 50; 119 | 120 | if (minecraft.options.advancedItemTooltips) { 121 | drawLeft(guiGraphics, Component.translatable("gui.obituary.id").append(":").withStyle(ChatFormatting.BLACK), height); 122 | drawRight(guiGraphics, Component.literal(death.getId().toString()).withStyle(ChatFormatting.DARK_GRAY), height, 0.5F); 123 | height += 13; 124 | } 125 | 126 | drawLeft(guiGraphics, Component.translatable("gui.obituary.name").append(":").withStyle(ChatFormatting.BLACK), height); 127 | drawRight(guiGraphics, Component.literal(death.getPlayerName()).withStyle(ChatFormatting.DARK_GRAY), height); 128 | height += 13; 129 | drawLeft(guiGraphics, Component.translatable("gui.obituary.dimension").append(":").withStyle(ChatFormatting.BLACK), height); 130 | drawRight(guiGraphics, Component.literal(death.getDimension().split(":")[1]).withStyle(ChatFormatting.DARK_GRAY), height); 131 | height += 13; 132 | drawLeft(guiGraphics, Component.translatable("gui.obituary.time").append(":").withStyle(ChatFormatting.BLACK), height); 133 | MutableComponent date = GraveUtils.getDate(death.getTimestamp()); 134 | if (date != null) { 135 | drawRight(guiGraphics, date.withStyle(ChatFormatting.DARK_GRAY), height); 136 | } else { 137 | drawRight(guiGraphics, Component.literal("N/A").withStyle(ChatFormatting.DARK_GRAY), height); 138 | } 139 | height += 13; 140 | drawLeft(guiGraphics, Component.translatable("gui.obituary.location").append(":").withStyle(ChatFormatting.BLACK), height); 141 | BlockPos pos = death.getBlockPos(); 142 | drawRight(guiGraphics, Component.literal("X: " + pos.getX()).withStyle(ChatFormatting.DARK_GRAY), height); 143 | height += 13; 144 | drawRight(guiGraphics, Component.literal("Y: " + pos.getY()).withStyle(ChatFormatting.DARK_GRAY), height); 145 | height += 13; 146 | drawRight(guiGraphics, Component.literal("Z: " + pos.getZ()).withStyle(ChatFormatting.DARK_GRAY), height); 147 | 148 | if (player == null) { 149 | player = new DummyPlayer(minecraft.level, new GameProfile(death.getPlayerUUID(), death.getPlayerName()), death.getEquipment(), death.getModel()); 150 | } 151 | 152 | //TODO Re-add and fix name tag and position 153 | //InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, guiLeft + TEXTURE_X / 2 - 25, guiTop + 70, guiLeft + TEXTURE_X / 2 + 25, guiTop + 140, 30, 0.0625F, mouseX, mouseY, player); 154 | 155 | if (minecraft.options.advancedItemTooltips) { 156 | if (mouseX >= guiLeft + 7 && mouseX <= guiLeft + TEXTURE_X - 7 && mouseY >= 50 && mouseY <= 50 + font.lineHeight) { 157 | guiGraphics.setTooltipForNextFrame(font, Collections.singletonList(Component.translatable("gui.obituary.copy_id").getVisualOrderText()), mouseX, mouseY); 158 | } 159 | } 160 | } 161 | 162 | @Override 163 | public boolean mouseClicked(MouseButtonEvent event, boolean b) { 164 | if (minecraft.options.advancedItemTooltips && page == 0) { 165 | if (event.x() >= guiLeft + 7 && event.x() <= guiLeft + TEXTURE_X - 7 && event.y() >= 50 && event.y() <= 50 + font.lineHeight) { 166 | minecraft.keyboardHandler.setClipboard(death.getId().toString()); 167 | Component deathID = ComponentUtils.wrapInSquareBrackets(Component.translatable("message.gravestone.death_id")) 168 | .withStyle((style) -> style 169 | .applyFormat(ChatFormatting.GREEN) 170 | .withClickEvent(new ClickEvent.SuggestCommand("/restore @s " + death.getId().toString() + " replace")) 171 | .withHoverEvent(new HoverEvent.ShowText(Component.literal(death.getId().toString()))) 172 | ); 173 | minecraft.gui.getChat().addMessage(Component.translatable("message.gravestone.copied", deathID)); 174 | minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1F)); 175 | minecraft.setScreen(null); 176 | } 177 | } 178 | return super.mouseClicked(event, b); 179 | } 180 | 181 | public void drawCentered(GuiGraphics guiGraphics, Font fontRenderer, MutableComponent text, int x, int y, int color) { 182 | guiGraphics.drawString(fontRenderer, text.getVisualOrderText(), x - fontRenderer.width(text) / 2, y, color, false); 183 | } 184 | 185 | public void drawItem(GuiGraphics guiGraphics, MutableComponent string, int height) { 186 | guiGraphics.drawString(font, string.getVisualOrderText(), guiLeft + ITEM_OFFSET_LEFT, height, FontColorUtils.getFontColor(ChatFormatting.BLACK), false); 187 | } 188 | 189 | public void drawItemSize(GuiGraphics guiGraphics, MutableComponent string, int height) { 190 | guiGraphics.drawString(font, string.getVisualOrderText(), guiLeft + ITEM_SIZE_OFFSET_LEFT, height, FontColorUtils.getFontColor(ChatFormatting.BLACK), false); 191 | } 192 | 193 | public void drawLeft(GuiGraphics guiGraphics, MutableComponent string, int height) { 194 | guiGraphics.drawString(font, string.getVisualOrderText(), guiLeft + OFFSET_LEFT, height, FontColorUtils.getFontColor(ChatFormatting.BLACK), false); 195 | } 196 | 197 | public void drawRight(GuiGraphics guiGraphics, MutableComponent string, int height) { 198 | drawRight(guiGraphics, string, height, 1F); 199 | } 200 | 201 | public void drawRight(GuiGraphics guiGraphics, MutableComponent string, int height, float scale) { 202 | guiGraphics.pose().pushMatrix(); 203 | guiGraphics.pose().scale(scale, scale); 204 | float f = 1F / scale; 205 | int strWidth = font.width(string); 206 | float spacing = (font.lineHeight * f - font.lineHeight) / 2F; 207 | guiGraphics.drawString(font, string.getVisualOrderText(), (int) ((guiLeft + TEXTURE_X - strWidth * scale - OFFSET_RIGHT) * f), (int) (height * f + spacing), FontColorUtils.getFontColor(ChatFormatting.BLACK), false); 208 | guiGraphics.pose().popMatrix(); 209 | } 210 | 211 | public Font getFontRenderer() { 212 | return font; 213 | } 214 | 215 | public int getGuiLeft() { 216 | return guiLeft; 217 | } 218 | 219 | public int getGuiTop() { 220 | return guiTop; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/de/maxhenkel/gravestone/blocks/GraveStoneBlock.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.gravestone.blocks; 2 | 3 | import de.maxhenkel.corelib.block.DirectionalVoxelShape; 4 | import de.maxhenkel.corelib.death.Death; 5 | import de.maxhenkel.gravestone.ClientUtils; 6 | import de.maxhenkel.gravestone.GraveUtils; 7 | import de.maxhenkel.gravestone.GravestoneMod; 8 | import de.maxhenkel.gravestone.entity.GhostPlayerEntity; 9 | import de.maxhenkel.gravestone.tileentity.GraveStoneTileEntity; 10 | import net.minecraft.core.BlockPos; 11 | import net.minecraft.core.Direction; 12 | import net.minecraft.core.NonNullList; 13 | import net.minecraft.core.component.DataComponents; 14 | import net.minecraft.network.chat.Component; 15 | import net.minecraft.server.level.ServerLevel; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.sounds.SoundEvents; 18 | import net.minecraft.sounds.SoundSource; 19 | import net.minecraft.util.RandomSource; 20 | import net.minecraft.world.Containers; 21 | import net.minecraft.world.InteractionResult; 22 | import net.minecraft.world.entity.Entity; 23 | import net.minecraft.world.entity.EquipmentSlot; 24 | import net.minecraft.world.entity.InsideBlockEffectApplier; 25 | import net.minecraft.world.entity.LivingEntity; 26 | import net.minecraft.world.entity.player.Inventory; 27 | import net.minecraft.world.entity.player.Player; 28 | import net.minecraft.world.item.*; 29 | import net.minecraft.world.item.context.BlockPlaceContext; 30 | import net.minecraft.world.level.*; 31 | import net.minecraft.world.level.block.Block; 32 | import net.minecraft.world.level.block.EntityBlock; 33 | import net.minecraft.world.level.block.RenderShape; 34 | import net.minecraft.world.level.block.SimpleWaterloggedBlock; 35 | import net.minecraft.world.level.block.entity.BlockEntity; 36 | import net.minecraft.world.level.block.state.BlockState; 37 | import net.minecraft.world.level.block.state.StateDefinition; 38 | import net.minecraft.world.level.block.state.properties.BlockStateProperties; 39 | import net.minecraft.world.level.block.state.properties.BooleanProperty; 40 | import net.minecraft.world.level.block.state.properties.EnumProperty; 41 | import net.minecraft.world.level.material.*; 42 | import net.minecraft.world.phys.BlockHitResult; 43 | import net.minecraft.world.phys.shapes.CollisionContext; 44 | import net.minecraft.world.phys.shapes.VoxelShape; 45 | 46 | import javax.annotation.Nullable; 47 | import java.util.List; 48 | import java.util.UUID; 49 | 50 | public class GraveStoneBlock extends Block implements EntityBlock, SimpleWaterloggedBlock { 51 | 52 | public static final EnumProperty FACING = BlockStateProperties.HORIZONTAL_FACING; 53 | public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; 54 | 55 | private static final VoxelShape BASE1 = Block.box(0D, 0D, 0D, 16D, 1D, 16D); 56 | private static final VoxelShape BASE2 = Block.box(1D, 1D, 1D, 15D, 2D, 15D); 57 | 58 | private static final DirectionalVoxelShape SHAPE = new DirectionalVoxelShape.Builder() 59 | .direction(Direction.NORTH, 60 | BASE1, 61 | BASE2, 62 | Block.box(1D, 2D, 1D, 15D, 12D, 2D), 63 | Block.box(2D, 12D, 1D, 14D, 14D, 2D), 64 | Block.box(3D, 14D, 1D, 13D, 15D, 2D)) 65 | .direction(Direction.SOUTH, 66 | BASE1, 67 | BASE2, 68 | Block.box(1D, 2D, 14D, 15D, 12D, 15D), 69 | Block.box(2D, 12D, 14D, 14D, 14D, 15D), 70 | Block.box(3D, 14D, 14D, 13D, 15D, 15D) 71 | ).direction(Direction.EAST, 72 | BASE1, 73 | BASE2, 74 | Block.box(14D, 2D, 1D, 15D, 12D, 15D), 75 | Block.box(14D, 12D, 2D, 15D, 14D, 14D), 76 | Block.box(14D, 14D, 3D, 15D, 15D, 13D) 77 | ).direction(Direction.WEST, 78 | BASE1, 79 | BASE2, 80 | Block.box(1D, 2D, 1D, 2D, 12D, 15D), 81 | Block.box(1D, 12D, 2D, 2D, 14D, 14D), 82 | Block.box(1D, 14D, 3D, 2D, 15D, 13D) 83 | ).build(); 84 | 85 | public GraveStoneBlock(Properties properties) { 86 | super(properties.mapColor(MapColor.DIRT).strength(0.3F, Float.MAX_VALUE)); 87 | registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(WATERLOGGED, false)); 88 | } 89 | 90 | @Override 91 | protected void createBlockStateDefinition(StateDefinition.Builder builder) { 92 | builder.add(FACING); 93 | builder.add(WATERLOGGED); 94 | } 95 | 96 | @Override 97 | public FluidState getFluidState(BlockState state) { 98 | return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); 99 | } 100 | 101 | @Override 102 | protected BlockState updateShape(BlockState state, LevelReader level, ScheduledTickAccess tickAccess, BlockPos pos, Direction direction, BlockPos facingPos, BlockState facingState, RandomSource randomSource) { 103 | if (state.getValue(WATERLOGGED)) { 104 | tickAccess.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); 105 | } 106 | return super.updateShape(state, level, tickAccess, pos, direction, facingPos, facingState, randomSource); 107 | } 108 | 109 | @Override 110 | public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { 111 | if (stack.has(DataComponents.CUSTOM_NAME)) { 112 | BlockEntity tileentity = world.getBlockEntity(pos); 113 | if (tileentity instanceof GraveStoneTileEntity) { 114 | GraveStoneTileEntity grave = (GraveStoneTileEntity) tileentity; 115 | grave.setCustomName(stack.getHoverName()); 116 | } 117 | } 118 | super.setPlacedBy(world, pos, state, placer, stack); 119 | } 120 | 121 | @Override 122 | public void wasExploded(ServerLevel level, BlockPos pos, Explosion explosion) { 123 | 124 | } 125 | 126 | @Override 127 | public void onBlockExploded(BlockState state, ServerLevel level, BlockPos pos, Explosion explosion) { 128 | 129 | } 130 | 131 | @Nullable 132 | @Override 133 | public BlockState getStateForPlacement(BlockPlaceContext context) { 134 | return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection()).setValue(WATERLOGGED, context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER); 135 | } 136 | 137 | @Override 138 | public boolean canEntityDestroy(BlockState state, BlockGetter world, BlockPos pos, Entity entity) { 139 | return false; 140 | } 141 | 142 | @Override 143 | public RenderShape getRenderShape(BlockState state) { 144 | return RenderShape.MODEL; 145 | } 146 | 147 | @Override 148 | protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) { 149 | BlockEntity tileentity = level.getBlockEntity(pos); 150 | 151 | if (!(tileentity instanceof GraveStoneTileEntity)) { 152 | return InteractionResult.FAIL; 153 | } 154 | 155 | GraveStoneTileEntity grave = (GraveStoneTileEntity) tileentity; 156 | 157 | Component name = grave.getGraveName(); 158 | 159 | if (name == null) { 160 | return InteractionResult.FAIL; 161 | } 162 | 163 | if (level.isClientSide()) { 164 | Component time = GraveUtils.getDate(grave.getDeath().getTimestamp()); 165 | if (time == null) { 166 | ClientUtils.sendMessage(name); 167 | } else { 168 | ClientUtils.sendMessage(Component.translatable("message.gravestone.died", name, time)); 169 | } 170 | } 171 | 172 | return InteractionResult.SUCCESS; 173 | } 174 | 175 | 176 | @Override 177 | protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean moving) { 178 | if (level.getBlockEntity(pos) instanceof GraveStoneTileEntity grave) { 179 | dropItems(level, pos, grave.getDeath().getAllItems()); 180 | } 181 | super.affectNeighborsAfterRemoval(state, level, pos, moving); 182 | } 183 | 184 | public void dropItems(Level world, BlockPos pos, NonNullList items) { 185 | for (ItemStack item : items) { 186 | Containers.dropItemStack(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, item); 187 | } 188 | } 189 | 190 | @Override 191 | public boolean onDestroyedByPlayer(BlockState state, Level world, BlockPos pos, Player player, ItemStack toolStack, boolean willHarvest, FluidState fluid) { 192 | if (!GraveUtils.canBreakGrave(world, player, pos)) { 193 | return false; 194 | } 195 | BlockEntity te = world.getBlockEntity(pos); 196 | if (!world.isClientSide() && te instanceof GraveStoneTileEntity grave) { 197 | removeObituary(player, grave); 198 | spawnGhost(world, pos, grave); 199 | 200 | if (!grave.getDeath().getId().equals(GraveUtils.EMPTY_UUID) && GravestoneMod.SERVER_CONFIG.breakPickup.get()) { 201 | sortItems(world, pos, player, grave); 202 | } 203 | } 204 | return super.onDestroyedByPlayer(state, world, pos, player, toolStack, willHarvest, fluid); 205 | } 206 | 207 | protected void spawnGhost(Level world, BlockPos pos, GraveStoneTileEntity grave) { 208 | if (!GravestoneMod.SERVER_CONFIG.spawnGhost.get()) { 209 | return; 210 | } 211 | if (!world.isEmptyBlock(pos.above())) { 212 | return; 213 | } 214 | 215 | UUID uuid = grave.getDeath().getPlayerUUID(); 216 | 217 | if (uuid.equals(GraveUtils.EMPTY_UUID)) { 218 | return; 219 | } 220 | 221 | GhostPlayerEntity ghost = new GhostPlayerEntity(world, uuid, Component.literal(grave.getDeath().getPlayerName()), grave.getDeath().getEquipment(), grave.getDeath().getModel()); 222 | ghost.setPos(pos.getX() + 0.5D, pos.getY() + 0.1D, pos.getZ() + 0.5D); 223 | world.addFreshEntity(ghost); 224 | } 225 | 226 | protected void removeObituary(Player p, GraveStoneTileEntity grave) { 227 | if (!GravestoneMod.SERVER_CONFIG.removeObituary.get()) { 228 | return; 229 | } 230 | if (!(p instanceof ServerPlayer)) { 231 | return; 232 | } 233 | ServerPlayer player = (ServerPlayer) p; 234 | 235 | Inventory inv = player.getInventory(); 236 | 237 | for (int i = 0; i < player.getInventory().getContainerSize(); i++) { 238 | ItemStack stack = player.getInventory().getItem(i); 239 | if (stack.getItem().equals(GravestoneMod.OBITUARY.get())) { 240 | Death death = GravestoneMod.OBITUARY.get().fromStack(player, stack); 241 | if (death != null && !grave.getDeath().getId().equals(GraveUtils.EMPTY_UUID) && grave.getDeath().getId().equals(death.getId())) { 242 | inv.removeItem(stack); 243 | } 244 | } 245 | } 246 | } 247 | 248 | @Override 249 | protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier effectApplier, boolean b) { 250 | super.entityInside(state, level, pos, entity, effectApplier, b); 251 | if (!(entity instanceof ServerPlayer) || !entity.isAlive() || !GravestoneMod.SERVER_CONFIG.sneakPickup.get()) { 252 | return; 253 | } 254 | ServerPlayer player = (ServerPlayer) entity; 255 | if (!player.isShiftKeyDown() || player.getAbilities().instabuild || !GraveUtils.canBreakGrave(level, player, pos)) { 256 | return; 257 | } 258 | BlockEntity te = level.getBlockEntity(pos); 259 | if (!(te instanceof GraveStoneTileEntity)) { 260 | return; 261 | } 262 | GraveStoneTileEntity grave = (GraveStoneTileEntity) te; 263 | if (grave.getDeath().getId().equals(GraveUtils.EMPTY_UUID)) { 264 | return; 265 | } 266 | 267 | removeObituary(player, grave); 268 | spawnGhost(level, pos, grave); 269 | 270 | sortItems(level, pos, player, grave); 271 | level.destroyBlock(pos, true); 272 | } 273 | 274 | protected void sortItems(Level world, BlockPos pos, Player player, GraveStoneTileEntity grave) { 275 | Death death = grave.getDeath(); 276 | dropItems(world, pos, fillPlayerInventory(player, death)); 277 | world.playSound(null, pos, SoundEvents.ITEM_FRAME_REMOVE_ITEM, SoundSource.BLOCKS, 1F, 1F); 278 | grave.setChanged(); 279 | } 280 | 281 | public NonNullList fillPlayerInventory(Player player, Death death) { 282 | NonNullList additionalItems = NonNullList.create(); 283 | fillInventory(additionalItems, death.getMainInventory(), player.getInventory().getNonEquipmentItems()); 284 | 285 | fillInventoryEquipment(player, additionalItems, death.getArmorInventory().get(EquipmentSlot.FEET.getIndex()), EquipmentSlot.FEET); 286 | fillInventoryEquipment(player, additionalItems, death.getArmorInventory().get(EquipmentSlot.LEGS.getIndex()), EquipmentSlot.LEGS); 287 | fillInventoryEquipment(player, additionalItems, death.getArmorInventory().get(EquipmentSlot.CHEST.getIndex()), EquipmentSlot.CHEST); 288 | fillInventoryEquipment(player, additionalItems, death.getArmorInventory().get(EquipmentSlot.HEAD.getIndex()), EquipmentSlot.HEAD); 289 | 290 | fillInventoryEquipment(player, additionalItems, death.getOffHandInventory().getFirst(), EquipmentSlot.OFFHAND); 291 | 292 | additionalItems.addAll(death.getAdditionalItems()); 293 | NonNullList restItems = NonNullList.create(); 294 | for (ItemStack stack : additionalItems) { 295 | if (!player.getInventory().add(stack)) { 296 | restItems.add(stack); 297 | } 298 | } 299 | 300 | death.getAdditionalItems().clear(); 301 | return restItems; 302 | } 303 | 304 | public void fillInventoryEquipment(Player player, List additionalItems, ItemStack item, EquipmentSlot slot) { 305 | if (item.isEmpty()) { 306 | return; 307 | } 308 | ItemStack oldPlayerItem = player.getItemBySlot(slot); 309 | if (!oldPlayerItem.isEmpty()) { 310 | additionalItems.add(oldPlayerItem); 311 | } 312 | player.setItemSlot(slot, item); 313 | } 314 | 315 | public void fillInventory(List additionalItems, NonNullList inventory, NonNullList playerInv) { 316 | for (int i = 0; i < inventory.size(); i++) { 317 | ItemStack stack = inventory.get(i); 318 | if (stack.isEmpty()) { 319 | continue; 320 | } 321 | ItemStack playerStack = playerInv.get(i); 322 | if (!playerStack.isEmpty()) { 323 | additionalItems.add(playerStack); 324 | } 325 | inventory.set(i, ItemStack.EMPTY); 326 | playerInv.set(i, stack); 327 | } 328 | } 329 | 330 | @Override 331 | public VoxelShape getShape(BlockState state, BlockGetter reader, BlockPos pos, CollisionContext context) { 332 | return SHAPE.get(state.getValue(FACING)); 333 | } 334 | 335 | @Nullable 336 | @Override 337 | public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { 338 | return new GraveStoneTileEntity(pos, state); 339 | } 340 | 341 | } 342 | --------------------------------------------------------------------------------