Add system for dimensions to be marked for deletion (#6515)
This commit is contained in:
parent
9114bec81d
commit
6345f2670c
4 changed files with 290 additions and 6 deletions
|
@ -19,6 +19,7 @@
|
|||
|
||||
package net.minecraftforge.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -27,6 +28,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -44,7 +47,9 @@ import com.google.common.collect.Multiset;
|
|||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
import net.minecraft.nbt.StringNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.management.PlayerList;
|
||||
|
@ -58,6 +63,8 @@ import net.minecraft.world.chunk.listener.IChunkStatusListener;
|
|||
import net.minecraft.world.server.ServerMultiWorld;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
import net.minecraft.world.storage.SaveHandler;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import net.minecraftforge.event.world.RegisterDimensionsEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.fml.StartupQuery;
|
||||
|
@ -65,6 +72,7 @@ import net.minecraftforge.fml.server.ServerModLoader;
|
|||
import net.minecraftforge.registries.ClearableRegistry;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
@ -85,6 +93,7 @@ public class DimensionManager
|
|||
private static final ConcurrentMap<World, World> weakWorldMap = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
private static final Multiset<Integer> leakedWorlds = HashMultiset.create();
|
||||
private static final Map<ResourceLocation, SavedEntry> savedEntries = new HashMap<>();
|
||||
private static final List<String> foldersScheduledForDeletion = new ArrayList<>();
|
||||
private static volatile Set<World> playerWorlds = new HashSet<>();
|
||||
|
||||
/**
|
||||
|
@ -100,7 +109,8 @@ public class DimensionManager
|
|||
* @param magnifier The biome generation processor
|
||||
* @return the DimensionType for the dimension.
|
||||
*/
|
||||
public static DimensionType registerOrGetDimension(ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight) {
|
||||
public static DimensionType registerOrGetDimension(ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight)
|
||||
{
|
||||
return REGISTRY.getValue(name).orElseGet(()->registerDimension(name, type, data, hasSkyLight));
|
||||
}
|
||||
/**
|
||||
|
@ -199,14 +209,49 @@ public class DimensionManager
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the given dimension for deletion upon next reload of the save.
|
||||
* The corresponding data directory will be deleted as well.
|
||||
*
|
||||
* Note that the dimension need not be currently unloaded. The dimension will stay functioning as normal
|
||||
* until the save is reloaded.
|
||||
*
|
||||
* Vanilla dimensions are not supported and will throw an exception.
|
||||
*
|
||||
* @param dim the dimension to delete
|
||||
*/
|
||||
public static void markForDeletion(DimensionType dim)
|
||||
{
|
||||
if (dim.isVanilla())
|
||||
{
|
||||
throw new IllegalArgumentException("Cannot delete vanilla dimensions.");
|
||||
}
|
||||
getData(dim).markedForDeletion = true;
|
||||
|
||||
Path base = Paths.get(".").toAbsolutePath().normalize();
|
||||
Path directory = dim.getDirectory(base.toFile()).toPath().toAbsolutePath().normalize();
|
||||
if (!directory.startsWith(base))
|
||||
{
|
||||
LOGGER.warn("DimensionType {} returned save directory outside of base directory. This is bad. The directory will not be deleted.", dim);
|
||||
}
|
||||
else
|
||||
{
|
||||
String relative = base.relativize(directory).toString();
|
||||
foldersScheduledForDeletion.add(relative);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// FORGE INTERNAL
|
||||
//==========================================================================================================
|
||||
|
||||
/**
|
||||
* Deprecated, unregistering dimensions at runtime is not supported.
|
||||
* @see DimensionManager#markForDeletion(DimensionType)
|
||||
*/
|
||||
@Deprecated
|
||||
public static void unregisterDimension(int id)
|
||||
{
|
||||
Validate.isTrue(dimensions.containsKey(id), String.format("Failed to unregister dimension for id %d; No provider registered", id));
|
||||
dimensions.remove(id);
|
||||
}
|
||||
|
||||
public static DimensionType registerDimensionInternal(int id, ResourceLocation name, ModDimension type, PacketBuffer data, boolean hasSkyLight)
|
||||
|
@ -347,7 +392,7 @@ public class DimensionManager
|
|||
data.putInt("version", 1);
|
||||
List<SavedEntry> list = new ArrayList<>();
|
||||
for (DimensionType type : REGISTRY)
|
||||
list.add(new SavedEntry(type));
|
||||
list.add(new SavedEntry(type, getData(type).markedForDeletion));
|
||||
savedEntries.values().forEach(list::add);
|
||||
|
||||
Collections.sort(list, (a, b) -> a.id - b.id);
|
||||
|
@ -355,6 +400,10 @@ public class DimensionManager
|
|||
list.forEach(e -> lst.add(e.write()));
|
||||
|
||||
data.put("entries", lst);
|
||||
|
||||
ListNBT deleteLst = new ListNBT();
|
||||
foldersScheduledForDeletion.forEach(folder -> deleteLst.add(StringNBT.valueOf(folder)));
|
||||
data.put("delete_dirs", deleteLst);
|
||||
}
|
||||
|
||||
public static void readRegistry(CompoundNBT data)
|
||||
|
@ -396,7 +445,7 @@ public class DimensionManager
|
|||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!entry.markedForDeletion)
|
||||
{
|
||||
ModDimension mod = ForgeRegistries.MOD_DIMENSIONS.getValue(entry.type);
|
||||
if (mod == null)
|
||||
|
@ -408,6 +457,46 @@ public class DimensionManager
|
|||
registerDimensionInternal(entry.id, entry.name, mod, entry.data == null ? null : new PacketBuffer(Unpooled.wrappedBuffer(entry.data)), entry.skyLight());
|
||||
}
|
||||
}
|
||||
|
||||
foldersScheduledForDeletion.clear();
|
||||
|
||||
ListNBT folderList = data.getList("delete_dirs", Constants.NBT.TAG_STRING);
|
||||
folderList.stream()
|
||||
.map(INBT::getString)
|
||||
.forEach(foldersScheduledForDeletion::add);
|
||||
}
|
||||
|
||||
public static void processScheduledDeletions(SaveHandler saveHandler)
|
||||
{
|
||||
List<String> toDelete = new ArrayList<>(foldersScheduledForDeletion);
|
||||
foldersScheduledForDeletion.clear();
|
||||
if (!toDelete.isEmpty())
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append("The following dimensions are marked for deletion by their owning mod. Proceed?\n\n");
|
||||
for (String folder : toDelete)
|
||||
{
|
||||
text.append(folder).append('\n');
|
||||
}
|
||||
if (!StartupQuery.confirm(text.toString()))
|
||||
{
|
||||
StartupQuery.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (String folderName : toDelete)
|
||||
{
|
||||
File folder = new File(saveHandler.getWorldDirectory(), folderName);
|
||||
try
|
||||
{
|
||||
FileUtils.deleteDirectory(folder);
|
||||
LOGGER.info(DIMMGR, "Modded dimension directory {} scheduled for deletion was deleted.", folderName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error(DIMMGR, "Failed to delete modded dimension directory {} that was scheduled for deletion.", folderName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void fireRegister()
|
||||
|
@ -436,6 +525,7 @@ public class DimensionManager
|
|||
{
|
||||
int ticksWaited = 0;
|
||||
boolean keepLoaded = false;
|
||||
boolean markedForDeletion = false;
|
||||
}
|
||||
|
||||
public static class SavedEntry
|
||||
|
@ -445,6 +535,7 @@ public class DimensionManager
|
|||
ResourceLocation type;
|
||||
byte[] data;
|
||||
boolean skyLight;
|
||||
boolean markedForDeletion;
|
||||
|
||||
public int getId()
|
||||
{
|
||||
|
@ -480,9 +571,10 @@ public class DimensionManager
|
|||
this.type = data.contains("type", 8) ? new ResourceLocation(data.getString("type")) : null;
|
||||
this.data = data.contains("data", 7) ? data.getByteArray("data") : null;
|
||||
this.skyLight = data.contains("sky_light", 99) ? data.getBoolean("sky_light") : true;
|
||||
this.markedForDeletion = data.getBoolean("marked_for_deletion");
|
||||
}
|
||||
|
||||
private SavedEntry(DimensionType data)
|
||||
private SavedEntry(DimensionType data, boolean markedForDeletion)
|
||||
{
|
||||
this.id = REGISTRY.getId(data);
|
||||
this.name = REGISTRY.getKey(data);
|
||||
|
@ -491,6 +583,7 @@ public class DimensionManager
|
|||
if (data.getData() != null)
|
||||
this.data = data.getData().array();
|
||||
this.skyLight = data.hasSkyLight();
|
||||
this.markedForDeletion = markedForDeletion;
|
||||
}
|
||||
|
||||
private CompoundNBT write()
|
||||
|
@ -503,6 +596,7 @@ public class DimensionManager
|
|||
if (data != null)
|
||||
ret.putByteArray("data", data);
|
||||
ret.putBoolean("sky_light", skyLight);
|
||||
ret.putBoolean("marked_for_deletion", markedForDeletion);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,6 +158,7 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook
|
|||
{
|
||||
if (tag.contains("dims", 10))
|
||||
DimensionManager.readRegistry(tag.getCompound("dims"));
|
||||
DimensionManager.processScheduledDeletions(handler);
|
||||
}
|
||||
|
||||
public void mappingChanged(FMLModIdMappingEvent evt)
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
package net.minecraftforge.debug.world;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
import net.minecraft.world.dimension.OverworldDimension;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
import net.minecraftforge.common.ModDimension;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import net.minecraftforge.common.util.ITeleporter;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mod(MarkDimensionForDeletionTest.MODID)
|
||||
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = MarkDimensionForDeletionTest.MODID)
|
||||
public class MarkDimensionForDeletionTest
|
||||
{
|
||||
|
||||
public static final String MODID = "mark_dimension_for_deletion_test";
|
||||
|
||||
private static final DeferredRegister<ModDimension> DIMENSIONS = new DeferredRegister<>(ForgeRegistries.MOD_DIMENSIONS, MODID);
|
||||
private static final DeferredRegister<Item> ITEMS = new DeferredRegister<>(ForgeRegistries.ITEMS, MODID);
|
||||
|
||||
private static final RegistryObject<ModDimension> DYNAMIC_DIMENSION_TYPE = DIMENSIONS.register(
|
||||
"dynamic_dimension",
|
||||
() -> ModDimension.withFactory(OverworldDimension::new)
|
||||
);
|
||||
private static final RegistryObject<Item> DIM_ITEM = ITEMS.register("dim_item", () -> new Item(new Item.Properties().group(ItemGroup.MISC))
|
||||
{
|
||||
@Override
|
||||
public ActionResult<ItemStack> onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn)
|
||||
{
|
||||
if (!worldIn.isRemote)
|
||||
{
|
||||
if (playerIn.isShiftKeyDown())
|
||||
{
|
||||
playerIn.sendMessage(new StringTextComponent("You are in dimension " + worldIn.dimension.getType().getRegistryName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
DynamicDimensionCap cap = playerIn.getCapability(CAP).orElseThrow(IllegalStateException::new);
|
||||
ITeleporter teleporter = new ITeleporter()
|
||||
{
|
||||
@Override
|
||||
public Entity placeEntity(Entity entity, ServerWorld currentWorld, ServerWorld destWorld, float yaw, Function<Boolean, Entity> repositionEntity)
|
||||
{
|
||||
return repositionEntity.apply(false);
|
||||
}
|
||||
};
|
||||
if (cap.dimension == null)
|
||||
{
|
||||
ResourceLocation dimName = new ResourceLocation(MODID, "dynamic_" + playerIn.getUniqueID().toString() + "_" + UUID.randomUUID().toString());
|
||||
cap.dimension = DimensionManager.registerDimension(dimName, DYNAMIC_DIMENSION_TYPE.get(), null, true);
|
||||
DimensionManager.initWorld(worldIn.getServer(), cap.dimension);
|
||||
playerIn.changeDimension(cap.dimension, teleporter);
|
||||
}
|
||||
else if (playerIn.dimension == cap.dimension)
|
||||
{
|
||||
playerIn.changeDimension(DimensionType.OVERWORLD, teleporter);
|
||||
DimensionManager.markForDeletion(cap.dimension);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerIn.changeDimension(cap.dimension, teleporter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ActionResult.resultSuccess(playerIn.getHeldItem(handIn));
|
||||
}
|
||||
});
|
||||
|
||||
@CapabilityInject(DynamicDimensionCap.class)
|
||||
public static Capability<DynamicDimensionCap> CAP;
|
||||
|
||||
public MarkDimensionForDeletionTest()
|
||||
{
|
||||
DIMENSIONS.register(FMLJavaModLoadingContext.get().getModEventBus());
|
||||
ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void commonSetup(FMLCommonSetupEvent event)
|
||||
{
|
||||
CapabilityManager.INSTANCE.register(DynamicDimensionCap.class, new DynamicDimensionCap.Storage(), DynamicDimensionCap::new);
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE, modid = MODID)
|
||||
public static class ForgeEventSubscriber
|
||||
{
|
||||
|
||||
@SubscribeEvent
|
||||
public static void attachCaps(AttachCapabilitiesEvent<Entity> event)
|
||||
{
|
||||
if (event.getObject() instanceof ServerPlayerEntity)
|
||||
{
|
||||
event.addCapability(new ResourceLocation(MODID, "dynamic_dimension"), new DynamicDimensionCap());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DynamicDimensionCap implements ICapabilitySerializable<CompoundNBT>
|
||||
{
|
||||
|
||||
static class Storage implements Capability.IStorage<DynamicDimensionCap>
|
||||
{
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public INBT writeNBT(Capability<DynamicDimensionCap> capability, DynamicDimensionCap instance, Direction side)
|
||||
{
|
||||
return instance.serializeNBT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readNBT(Capability<DynamicDimensionCap> capability, DynamicDimensionCap instance, Direction side, INBT nbt)
|
||||
{
|
||||
instance.deserializeNBT((CompoundNBT) nbt);
|
||||
}
|
||||
}
|
||||
|
||||
public DimensionType dimension = null;
|
||||
|
||||
private final LazyOptional<DynamicDimensionCap> instance = LazyOptional.of(() -> this);
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side)
|
||||
{
|
||||
return CAP.orEmpty(cap, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT serializeNBT()
|
||||
{
|
||||
CompoundNBT result = new CompoundNBT();
|
||||
if (dimension != null && dimension.getRegistryName() != null)
|
||||
{
|
||||
result.putString("dimension", dimension.getRegistryName().toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundNBT nbt)
|
||||
{
|
||||
if (nbt.contains("dimension", Constants.NBT.TAG_STRING))
|
||||
{
|
||||
dimension = DimensionType.byName(new ResourceLocation(nbt.getString("dimension")));
|
||||
}
|
||||
else
|
||||
{
|
||||
dimension = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
modLoader="javafml"
|
||||
loaderVersion="[28,)"
|
||||
|
||||
[[mods]]
|
||||
modId="mark_dimension_for_deletion_test"
|
||||
[[mods]]
|
||||
modId="containertypetest"
|
||||
[[mods]]
|
||||
|
@ -10,6 +12,7 @@ loaderVersion="[28,)"
|
|||
[[mods]]
|
||||
modId="farmland_trample_test"
|
||||
[[mods]]
|
||||
|
||||
modId="neighbor_notify_event_test"
|
||||
[[mods]]
|
||||
modId="block_place_event_test"
|
||||
|
|
Loading…
Reference in a new issue