package openmods.block;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import cpw.mods.fml.common.gameevent.TickEvent.WorldTickEvent;
import cpw.mods.fml.relauncher.Side;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.event.world.WorldEvent;
import openmods.LibConfig;
import openmods.Log;
import openmods.utils.Coord;
public class BlockDropsStore {
public static final BlockDropsStore instance = new BlockDropsStore();
private static class BlockDrop {
public final Throwable location;
public final List<ItemStack> items;
public BlockDrop(List<ItemStack> drops) {
this.location = LibConfig.dropsDebug? new Throwable() : null;
this.items = ImmutableList.copyOf(drops);
}
public void logContents(StringBuilder result) {
result.append("\tItems: ");
result.append(items.toString());
result.append('\n');
if (location != null) {
result.append("\tLocation:\n");
for (StackTraceElement e : location.getStackTrace()) {
result.append("\t\t");
result.append(e.toString());
result.append('\n');
}
}
}
}
private static class WorldDrops {
public final Multimap<Coord, BlockDrop> drops = ArrayListMultimap.create();
public final int dimensionId;
public WorldDrops(int dimensionId) {
this.dimensionId = dimensionId;
}
public synchronized void storeDrops(int x, int y, int z, List<ItemStack> items) {
drops.put(new Coord(x, y, z), new BlockDrop(items));
}
// vanilla requires that exact return type
public synchronized ArrayList<ItemStack> harvestDrops(int x, int y, int z) {
final Coord key = new Coord(x, y, z);
if (drops.containsKey(key)) {
ArrayList<ItemStack> result = Lists.newArrayList();
Iterator<BlockDrop> it = drops.get(key).iterator();
while (it.hasNext()) {
final BlockDrop drop = it.next();
result.addAll(drop.items);
it.remove();
}
return result;
}
return null;
}
public synchronized void cleanup(String location) {
if (!drops.isEmpty()) {
StringBuilder result = new StringBuilder();
result.append(String.format("Found unharvested drops in world %d after %s\n", dimensionId, location));
if (!LibConfig.dropsDebug) result.append("To enable stacktrace logging, set config option 'debug.dropsDebug' to true\n");
for (Map.Entry<Coord, Collection<BlockDrop>> e : drops.asMap().entrySet()) {
result.append("Drops from location: ");
result.append(e.getKey());
result.append('\n');
for (BlockDrop drop : e.getValue())
drop.logContents(result);
}
Log.warn("%s", result.toString());
drops.clear();
}
}
}
private final TIntObjectHashMap<WorldDrops> worldDrops = new TIntObjectHashMap<WorldDrops>();
private synchronized WorldDrops getDrops(World world) {
if (world.isRemote) return null;
final int dimensionId = world.provider.dimensionId;
WorldDrops result = worldDrops.get(dimensionId);
if (result == null) {
result = new WorldDrops(dimensionId);
worldDrops.put(dimensionId, result);
}
return result;
}
public void storeDrops(World world, int x, int y, int z, List<ItemStack> items) {
final WorldDrops drops = getDrops(world);
if (drops != null) drops.storeDrops(x, y, z, items);
}
// ArrayList, since some Minecraft internals need it
public ArrayList<ItemStack> harvestDrops(World world, int x, int y, int z) {
final WorldDrops drops = getDrops(world);
return (drops != null)? drops.harvestDrops(x, y, z) : null;
}
public class ForgeListener {
@SubscribeEvent
public void onWorldUnload(WorldEvent.Unload evt) {
final World world = evt.world;
if (!world.isRemote) cleanup(world, "unload");
}
}
public class FmlListener {
@SubscribeEvent
public void onWorldTick(WorldTickEvent evt) {
if (evt.side == Side.SERVER && evt.phase == Phase.END) cleanup(evt.world, "tick");
}
}
public Object createForgeListener() {
return new ForgeListener();
}
public Object createFmlListener() {
return new FmlListener();
}
private void cleanup(final World world, final String location) {
final int dimensionId = world.provider.dimensionId;
final WorldDrops drops = worldDrops.get(dimensionId);
if (drops != null) drops.cleanup(location);
}
}