A lot of changes. Sorry, didn't keep track.

The next things to do are implementing the tile entity and getting the player's items upon death.
master
BodgeMaster 2022-12-20 00:50:11 +01:00
parent a618f200ee
commit b1c80d7bde
9 changed files with 524 additions and 39 deletions

View File

@ -1,15 +1,18 @@
package lostcave.deathchests;
import lostcave.deathchests.block.BlockDeathChest;
import lostcave.deathchests.item.Obituary;
import lostcave.deathchests.item.ItemObituary;
import lostcave.deathchests.util.Config;
import lostcave.deathchests.util.DeathChestStorage;
import lostcave.deathchests.util.Debug;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.MinecraftForge;
import java.io.File;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
@ -17,6 +20,8 @@ import cpw.mods.fml.common.event.FMLConstructionEvent;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerAboutToStartEvent;
import cpw.mods.fml.common.event.FMLServerStoppedEvent;
import cpw.mods.fml.common.registry.GameRegistry;
@Mod(modid = DeathChests.MODID, version = DeathChests.VERSION)
@ -24,11 +29,13 @@ public class DeathChests {
public static final String MODID = "deathchests";
public static final String VERSION = "0-SNAPSHOT";
private static File worldSaveLocation;
@EventHandler
public void init(FMLConstructionEvent event) {
Debug.out("Received FMLConstructionEvent");
Config.configDir = Loader.instance().getConfigDir();
Debug.out("Config file is: " + Config.configDir.toString() + System.getProperty("file.separator") + Config.configFileName);
Debug.out("Config file is: " + Config.configDir.toString() + System.getProperty("file.separator") + Config.config_file_name);
Config.loadConfig();
Debug.out("Loaded config.");
@ -49,10 +56,11 @@ public class DeathChests {
Debug.out("Received FMLInitializationEvent");
GameRegistry.registerBlock(BlockDeathChest.getInstance(), "death_chest");
GameRegistry.registerItem(Obituary.getInstance(), "obituary");
GameRegistry.registerItem(ItemObituary.getInstance(), "obituary");
Debug.out("Registered block and item.");
MinecraftForge.EVENT_BUS.register(new EventHook());
FMLCommonHandler.instance().bus().register(new EventHook());
Debug.out("Registered event hook.");
}
@ -61,4 +69,17 @@ public class DeathChests {
Debug.out("Received FMLPostInitializationEvent");
}
@EventHandler
public void init(FMLServerAboutToStartEvent event) {
Debug.out("Received FMLServerAboutToStartEvent");
//TODO: get world save location
worldSaveLocation = DimensionManager.getCurrentSaveRootDirectory();
DeathChestStorage.load(worldSaveLocation);
}
@EventHandler
public void init(FMLServerStoppedEvent event) {
DeathChestStorage.save(worldSaveLocation);
}
}

View File

@ -1,17 +1,23 @@
package lostcave.deathchests;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent;
import lostcave.deathchests.block.BlockDeathChest;
import lostcave.deathchests.item.ItemObituary;
import lostcave.deathchests.util.Config;
import lostcave.deathchests.util.DeathChestStorage;
import lostcave.deathchests.util.Debug;
import net.minecraft.block.BlockAir;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityEvent.EntityConstructing;
import net.minecraftforge.event.entity.player.PlayerDropsEvent;
public class EventHook {
@SubscribeEvent
public void PlayerDropsEvent(net.minecraftforge.event.entity.player.PlayerDropsEvent event) {
public void death(PlayerDropsEvent event) {
// This is kinda misleadingly named.
// The event fires only when a player entity produces drops (due to death),
// not when a player drops an item.
@ -29,9 +35,9 @@ public class EventHook {
if (y < 0) y = 0;
if (y > 255) y = 255;
boolean foundSuitableBlock = false;
int[] suitableBlock = new int[3];
World world = event.entity.worldObj;
boolean foundSuitableBlock = false;
int[] suitableBlock = new int[4]; // x y z dimension
// search for air block by checking the outside walls of an expanding cube
if (world.getBlock(x, y, z) instanceof BlockAir) {
suitableBlock[0] = x;
@ -151,16 +157,46 @@ public class EventHook {
Debug.out("Suitable block X: " + Integer.toString(suitableBlock[0]));
Debug.out("Suitable block Y: " + Integer.toString(suitableBlock[1]));
Debug.out("Suitable block Z: " + Integer.toString(suitableBlock[2]));
//TODO: place death chest
Debug.out("Dimension: " + Integer.toString(event.entityPlayer.dimension));
suitableBlock[3] = event.entityPlayer.dimension;
world.setBlock(suitableBlock[0], suitableBlock[1], suitableBlock[2], BlockDeathChest.getInstance());
//TODO: Get the player's items and put them in the death chest
String uuid = event.entityPlayer.getGameProfile().getId().toString();
Debug.out(uuid);
DeathChestStorage.addNewDeathChestLocation(uuid, suitableBlock);
event.setCanceled(true);
Debug.out("Canceled PlayerDropEvent");
//TODO: Tell the player about death chest location upon respawn
} else {
Debug.out("No suitable location could be found.");
//TODO: tell player that a death chest couldnt be placed and that the items have been dropped
}
}
}
@SubscribeEvent
public void respawn(PlayerEvent.PlayerRespawnEvent event) {
Debug.out(event.player.getDisplayName() + " respawned");
String uuid = event.player.getGameProfile().getId().toString();
Debug.out("UUID: " + uuid);
//TODO: check for stored chest location
if (DeathChestStorage.hasNewDeathChestLocation(uuid)) {
int[] chestLocation = DeathChestStorage.popNewDeathChestLocation(uuid);
Debug.out("Chest location X: " + Integer.toString(chestLocation[0]));
Debug.out("Chest location Y: " + Integer.toString(chestLocation[1]));
Debug.out("Chest location Z: " + Integer.toString(chestLocation[2]));
Debug.out("Chest location dimension: " + Integer.toString(chestLocation[3]));
//TODO: NBT data
event.player.inventory.addItemStackToInventory(new ItemStack(ItemObituary.getInstance()));
} else {
Debug.out("No death chest location was stored.");
//TODO: tell player that a death chest couldnt be placed and that the items have been dropped
}
}
}

View File

@ -1,8 +1,22 @@
package lostcave.deathchests.block;
import net.minecraft.block.BlockChest;
import java.util.Random;
public class BlockDeathChest extends BlockChest {
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEnderChest;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.inventory.InventoryEnderChest;
import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityEnderChest;
import net.minecraft.world.Explosion;
import net.minecraft.world.World;
public class BlockDeathChest extends BlockEnderChest {
private static final BlockDeathChest instance = new BlockDeathChest();
@ -11,13 +25,89 @@ public class BlockDeathChest extends BlockChest {
}
private BlockDeathChest() {
super(0);
super();
this.setBlockName("death_chest");
this.disableStats();
this.setBlockUnbreakable();
// same value as bedrock
this.setResistance(6000000.0F);
}
@Override
protected boolean canSilkHarvest() {
return false;
}
@Override
public boolean canDropFromExplosion(Explosion p_149659_1_) {
return false;
}
@Override
public Item getItemDropped(int p_149650_1_, Random p_149650_2_, int p_149650_3_) {
return null;
}
/**
* Called upon block activation (right click on the block.)
*/
@Override
public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int p_149727_6_, float p_149727_7_, float p_149727_8_, float p_149727_9_) {
if (world.isRemote) return true;
TileDeathChest tiledeathchest = (TileDeathChest) world.getTileEntity(x, y, z);
if (tiledeathchest != null) {
//TODO: check if appropriate player
//TODO: drop items
return true;
} else return true;
}
/**
* Called when a player hits the block. Args: world, x, y, z, player
*/
@Override
public void onBlockClicked(World p_149699_1_, int p_149699_2_, int p_149699_3_, int p_149699_4_, EntityPlayer p_149699_5_) {
//TODO
}
/**
* Returns a new instance of a block's tile entity class. Called on placing the
* block.
*/
@Override
public TileEntity createNewTileEntity(World p_149915_1_, int p_149915_2_) {
return new TileDeathChest();
}
/**
* A randomly called display update to be able to add particles or other items
* for display
* <br />
* <br/>
* This is here to disable the particles that would otherwise be emitted
*/
@Override
@SideOnly(Side.CLIENT)
public void randomDisplayTick(World p_149734_1_, int p_149734_2_, int p_149734_3_, int p_149734_4_, Random p_149734_5_) {
}
@Override
@SideOnly(Side.CLIENT)
public void registerBlockIcons(IIconRegister p_149651_1_) {
this.blockIcon = p_149651_1_.registerIcon("cobblestone");
}
public static class TileDeathChest extends TileEntity {
//TODO
}
//TODO: Make death chest unobtanium (dont drop the chest when broken)
//TODO: Change capacity somehow?
//TODO: Custom texture? (optional)
//TODO: add chest inventory
//TODO: drop chest contents
//TODO: Remove chest when empty
//TODO: make unable to break by explosion (configurable?)
//TODO: Make it so items can only be taken out of the chest?
// Custom GUI? Maybe remove GUI altogether and have it drop the items when broken?

View File

@ -0,0 +1,21 @@
package lostcave.deathchests.item;
import net.minecraft.item.Item;
public class ItemObituary extends Item {
private static final ItemObituary instance = new ItemObituary();
public static ItemObituary getInstance() {
return instance;
}
private ItemObituary() {
this.setUnlocalizedName("obituary");
this.setTextureName("paper");
}
//TODO: NBT data
//TODO: right click action
//TODO: item description
}

View File

@ -1,17 +0,0 @@
package lostcave.deathchests.item;
import net.minecraft.item.Item;
public class Obituary extends Item {
private static final Obituary instance = new Obituary();
public static Obituary getInstance() {
return instance;
}
private Obituary() {
this.setUnlocalizedName("obituary");
this.setTextureName("paper");
}
}

View File

@ -9,28 +9,28 @@ import cpw.mods.fml.common.FMLCommonHandler;
public class Config {
public static final String configFileName = "deathchests.txt";
public static final String config_file_name = "deathchests.txt";
public static File configDir = null;
public static boolean debug = true;
public static boolean fixLog4J = true;
public static int maxDistance = 50;
private static final String configHeader = "# The format of this file is strictly `option=value` (no spaces).\n# Lines starting with # and empty lines are ignored.\n";
private static final String config_header = "# The format of this file is strictly `option=value` (no spaces).\n# Lines starting with # and empty lines are ignored.\n";
public static void writeConfig() {
String configOut = configHeader;
String configOut = config_header;
configOut += "\n\n# Enable debug output?\n";
configOut += "debug=";
configOut += debug ? "true" : "false";
configOut += "\n\n# Enable Log4Shell counter measures?\n";
configOut += "\n\n# Enable Log4Shell counter measures? (this feature is currently not implemented)\n";
configOut += "fixLog4J=";
configOut += fixLog4J ? "true" : "false";
configOut += "\n\n# The maximum search radius for finding a suitable location for placing the death chest\n";
configOut += "maxDistance=";
configOut += Integer.toString(maxDistance);
File configFile = new File(configDir, configFileName);
File configFile = new File(configDir, config_file_name);
try {
Files.write(configFile.toPath(), configOut.getBytes());
} catch (IOException e) {
@ -42,7 +42,7 @@ public class Config {
public static void loadConfig() {
List<String> configData = null;
File configFile = new File(configDir, configFileName);
File configFile = new File(configDir, config_file_name);
if (configFile.exists()) {
try {
configData = Files.readAllLines(configFile.toPath());

View File

@ -0,0 +1,100 @@
package lostcave.deathchests.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Set;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
public class DeathChestStorage {
private static LookUpTable<int[]> deathChestLocations;
public static final String death_chests_dat_file = "deathchests.dat";
/**
* When a new death chest has been placed, add it to the LUT. This does not
* place the chest itself.
*
* @param uuid
* @param location
*/
public static void addNewDeathChestLocation(String uuid, int[] location) {
deathChestLocations.add(uuid, location);
}
/**
* Get the location of a new death chest for a given player and remove it from
* the LUT of new death chests.
*
* @param uuid
* @return the location {x, y, z, dimension}
*/
public static int[] popNewDeathChestLocation(String uuid) {
int[] location = deathChestLocations.get(uuid);
deathChestLocations.remove(uuid);
return location;
}
/**
* Checks if a given player has a new death chest that no obituary item has been
* created for.
* This is used in respawn events.
*
* @param uuid
* @return
*/
public static boolean hasNewDeathChestLocation(String uuid) {
return deathChestLocations.get(uuid) != null;
}
public static void save(File worldSaveLocation) {
Debug.out("Putting death chest LUT into a compound");
NBTTagCompound compound = new NBTTagCompound();
for (int i = 0; i < deathChestLocations.storedElements(); i++) {
String uuid = deathChestLocations.getKey(i);
compound.setIntArray(uuid, deathChestLocations.get(uuid));
}
File file = new File(worldSaveLocation, death_chests_dat_file);
Debug.out("Saving to file: " + file.toPath().toAbsolutePath().toString());
try {
CompressedStreamTools.writeCompressed(compound, new FileOutputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void load(File worldSaveLocation) {
File file = new File(worldSaveLocation, death_chests_dat_file);
Debug.out("Loading death chest LUT from file: " + file.toPath().toAbsolutePath().toString());
// reset the LUT in case one still exists from quitting to main menu and loading a world
deathChestLocations = new LookUpTable<int[]>();
if (file.exists()) {
try {
NBTTagCompound compound = CompressedStreamTools.readCompressed(new FileInputStream(file));
// func_150296_c() should be named getKeySet() but didnt deobfuscate properly
// This might be a problem with Forge, or it might be a problem with my Frankenstein setup.
for (String uuid : (Set<String>) compound.func_150296_c()) {
deathChestLocations.add(uuid, compound.getIntArray(uuid));
}
} catch (IOException e) {
e.printStackTrace();
}
}
Debug.out("The following entries have been loaded:");
if (Config.debug) {
for (int i = 0; i < deathChestLocations.storedElements(); i++) {
String uuid = deathChestLocations.getKey(i);
int[] location = deathChestLocations.get(uuid);
Debug.out(uuid + " - x=" + Integer.toString(location[0]) + " y=" + Integer.toString(location[1]) + " z=" + Integer.toString(location[2]) + " dim=" + Integer.toString(location[3]));
}
}
Debug.out("--");
}
}

View File

@ -0,0 +1,193 @@
package lostcave.deathchests.util;
// The code below is licensed under the WTFPL.
// Here is a copy for your convenience:
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* This is a naive implementation of a look up table. Store and retrieve key
* value pairs.
* </p>
*
* <p>
* I had no reason to implement this other than not wanting to go down a rabbit
* hole of Java types rather than just using simple functionality.
* </p>
*
* @author BodgeMaster
*
* @param <T> The type to be stored
*/
public class LookUpTable<T> {
private List<T> values;
private String[] keys;
private boolean[] usedSlots;
private int storedElements;
public LookUpTable() {
this.values = new ArrayList<T>();
for (int i = 0; i < 10; i++) {
values.add(null);
}
this.keys = new String[10];
this.usedSlots = new boolean[] { false, false, false, false, false, false, false, false, false, false };
this.storedElements = 0;
}
/**
* Add an element
*
* @param key
* @param value
* @throws DuplicateKeyException
*/
public void add(String key, T value) {
// duplicate check
for (int i = 0; i < this.keys.length; i++) {
if (usedSlots[i]) {
if (keys[i].equals(key)) throw new DuplicateKeyException(key);
}
}
// Make bigger arrays when running out of space
if (this.storedElements == this.keys.length) {
String[] keys = this.keys;
boolean[] usedSlots = this.usedSlots;
this.keys = new String[keys.length + 10];
this.usedSlots = new boolean[usedSlots.length + 10];
for (int i = 0; i < keys.length; i++) {
this.keys[i] = keys[i];
this.usedSlots[i] = usedSlots[i];
}
for (int i = 0; i < 10; i++) {
// ensure capacity
this.values.add(null);
}
}
// Find next unused slot
int unusedSlot = 0;
for (int i = 0; i < this.usedSlots.length; i++) {
if (!this.usedSlots[i]) {
unusedSlot = i;
break;
}
}
this.keys[unusedSlot] = key;
this.values.set(unusedSlot, value);
usedSlots[unusedSlot] = true;
this.storedElements++;
}
/**
* Get a value by key
*
* @param key
* @return value (null if key not found)
*/
public T get(String key) {
for (int i = 0; i < usedSlots.length; i++) {
if (usedSlots[i]) {
if (keys[i].equals(key)) return values.get(i);
}
}
return null;
}
public void set(String key, T value) {
for (int i = 0; i < usedSlots.length; i++) {
if (usedSlots[i]) {
if (keys[i].equals(key)) values.set(i, value);
}
}
}
/**
* <p>
* Remove an element from the LUT
* </p>
*
* <p>
* This function marks the position of key as unused. If you actually want to
* ensure that the object is removed from the LUT, use
* {@link LookUpTable#fullRemove(String)} instead.
* </p>
*
* @param key
*/
public void remove(String key) {
for (int i = 0; i < usedSlots.length; i++) {
if (usedSlots[i]) {
if (keys[i].equals(key)) usedSlots[i] = false;
}
}
storedElements--;
}
/**
* Remove an element from the LUT by actually setting it to null.
*
* @param key
*/
public void fullRemove(String key) {
for (int i = 0; i < usedSlots.length; i++) {
if (usedSlots[i]) {
if (keys[i].equals(key)) {
values.set(i, null);
usedSlots[i] = false;
}
}
}
storedElements--;
}
public int storedElements() {
return this.storedElements;
}
/**
* Gets a key by its position in the LUT. Unused slots are ignored and do not
* count towards the position.
*
* @param index
* @return key
*/
public String getKey(int index) {
int i = 0;
int j = 0;
while (i < index) {
j++;
if (usedSlots[j]) i++;
}
return keys[j];
}
public static class DuplicateKeyException extends RuntimeException {
public DuplicateKeyException() {
super("Tried adding an element with a duplicte key to a LookUpTable.");
}
public DuplicateKeyException(String key) {
super("Tried adding an element with a duplicte key to a LookUpTable: " + key);
}
}
}

41
src/test/TestLUT.java Normal file
View File

@ -0,0 +1,41 @@
import lostcave.deathchests.util.LookUpTable;
import lostcave.deathchests.util.LookUpTable.DuplicateKeyException;
public class TestLUT {
public static void main(String[] args) {
System.out.println("Testing LookUpTable...");
LookUpTable<Integer> table = new LookUpTable<>();
table.add("one", 1);
table.add("two", 2);
table.add("three", 3);
if (table.get("one") != 1 || table.get("two") != 2 || table.get("three") != 3) System.err.println("Failed add test.");
else System.out.println("Passed add test.");
if (table.getKey(1).equals("two")) System.out.println("Passed getKey test (1/2).");
else System.err.println("Failed getKey test (1/2).");
table.remove("two");
if (table.get("two") == null) System.out.println("Passed remove test.");
else System.err.println("Failed remove test");
if (table.getKey(1).equals("three")) System.out.println("Passed getKey test (2/2).");
else System.err.println("Failed getKey test (2/2): " + table.getKey(1));
if (table.storedElements()==2) System.out.println("Passes storedElements test.");
else System.out.println("Failed storedElements test.");
table.set("three", 33);
if (table.get("three")==33) System.out.println("Passed set test.");
else System.out.println("Failed set test.");
try {
table.add("three", 2);
System.err.println("Failed duplicate key test.");
} catch (DuplicateKeyException e) {
System.out.println("Passed duplicate key test.");
}
}
}