9
0
mirror of https://github.com/Xiao-MoMi/Custom-Crops.git synced 2025-12-27 19:09:09 +00:00

Added unsafe commands

This commit is contained in:
XiaoMoMi
2024-09-15 17:18:33 +08:00
parent ce4c12da11
commit 389fa9e3c6
27 changed files with 412 additions and 107 deletions

View File

@@ -92,29 +92,6 @@ public interface ActionManager<T> extends Reloadable {
*/
Action<T> parseAction(@NotNull String type, @NotNull Object args);
/**
* Generates a map of actions triggered by specific events from a configuration section.
*
* @param section The configuration section containing event-action mappings.
* @return A map where the keys are action triggers and the values are arrays of actions associated with those triggers.
*/
default Map<ActionTrigger, Action<T>[]> parseEventActions(Section section) {
HashMap<ActionTrigger, Action<T>[]> actionMap = new HashMap<>();
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
if (entry.getValue() instanceof Section innerSection) {
try {
actionMap.put(
ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)),
parseActions(innerSection)
);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
}
}
return actionMap;
}
/**
* Parses a configuration section to generate a map of timed actions.
*

View File

@@ -1,24 +0,0 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customcrops.api.action;
public enum ActionTrigger {
PLACE,
BREAK,
WORK, INTERACT
}

View File

@@ -117,6 +117,10 @@ public class SynchronizedCompoundMap {
return compoundMapToString("BlockData", compoundMap);
}
public String asString() {
return compoundMapToString("", compoundMap);
}
/**
* Recursively converts a CompoundMap to a string representation.
*
@@ -126,12 +130,15 @@ public class SynchronizedCompoundMap {
*/
@SuppressWarnings("unchecked")
private String compoundMapToString(String key, CompoundMap compoundMap) {
StringJoiner joiner = new StringJoiner(", ");
StringJoiner joiner = new StringJoiner(",");
for (Map.Entry<String, Tag<?>> entry : compoundMap.entrySet()) {
Tag<?> tag = entry.getValue();
String tagValue;
switch (tag.getType()) {
case TAG_STRING, TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY,
case TAG_STRING -> {
tagValue = "\"" + tag.getValue().toString() + "\"";
}
case TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY,
TAG_LONG, TAG_SHORT, TAG_SHORT_ARRAY, TAG_LONG_ARRAY, TAG_BYTE_ARRAY ->
tagValue = tag.getValue().toString();
case TAG_LIST -> {
@@ -151,8 +158,12 @@ public class SynchronizedCompoundMap {
continue; // skip unsupported tag types
}
}
joiner.add("\"" + entry.getKey() + "\":\"" + tagValue + "\"");
joiner.add(entry.getKey() + "=" + tagValue);
}
if (key.isEmpty()) {
return joiner.toString();
} else {
return key + "=[" + joiner + "]";
}
return key + "{" + joiner + "}";
}
}

View File

@@ -168,10 +168,21 @@ public class CropBlock extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return Registries.STAGE_TO_CROP_UNSAFE.containsKey(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
CropConfig config = config(state);
if (config == null) return;
int point = point(state);
CropStageConfig stageConfig = config.stageWithModelByPoint(point);
if (stageConfig != null) {
BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, stageConfig.existenceForm(), Objects.requireNonNull(stageConfig.stageID()), config.rotation() ? FurnitureRotation.random() : FurnitureRotation.NONE);
}
}
@Override
public void onInteract(WrappedInteractEvent event) {
final Player player = event.player();

View File

@@ -26,26 +26,76 @@ import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
import net.momirealms.customcrops.api.misc.NamedTextColor;
import net.momirealms.customcrops.common.util.Key;
import org.bukkit.Location;
public interface CustomCropsBlock {
/**
* Get the key
*
* @return key
*/
Key type();
/**
* Create a CustomCropsBlockState based on this type
*
* @return CustomCropsBlockState
*/
CustomCropsBlockState createBlockState();
/**
* Create a CustomCropsBlockState based on this type and provided data
*
* @return CustomCropsBlockState
*/
CustomCropsBlockState createBlockState(CompoundMap data);
/**
* Runs scheduled tick tasks
*/
void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location, boolean offlineTick);
/**
* Runs random tick tasks
*/
void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location, boolean offlineTick);
/**
* Handles interactions
*/
void onInteract(WrappedInteractEvent event);
/**
* Handles breaks
*/
void onBreak(WrappedBreakEvent event);
/**
* Handles placement
*/
void onPlace(WrappedPlaceEvent event);
boolean isBlockInstance(String id);
/**
* Checks if the id is an instance of this block type
*
* @param id id
* @return is instance or not
*/
boolean isInstance(String id);
/**
* Restores the bukkit block state or furniture based on the given block state
*
* @param location the location of the block
* @param state the provided state
*/
void restore(Location location, CustomCropsBlockState state);
/**
* Get the color on insight mode
*
* @return the color
*/
NamedTextColor insightColor();
}

View File

@@ -38,10 +38,15 @@ public class DeadCrop extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return Registries.ITEM_TO_DEAD_CROP.containsKey(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
// do not restore
}
@Override
public void onInteract(WrappedInteractEvent event) {
final Player player = event.player();

View File

@@ -20,6 +20,7 @@ package net.momirealms.customcrops.api.core.block;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.BuiltInBlockMechanics;
import net.momirealms.customcrops.api.core.ConfigManager;
import net.momirealms.customcrops.api.core.FurnitureRotation;
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.api.core.world.Pos3;
@@ -102,10 +103,17 @@ public class GreenhouseBlock extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return ConfigManager.greenhouse().contains(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
String id = ConfigManager.greenhouse().stream().findAny().orElse(null);
if (id == null) return;
BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, ConfigManager.greenhouseExistenceForm(), id, FurnitureRotation.NONE);
}
public CustomCropsBlockState getOrFixState(CustomCropsWorld<?> world, Pos3 pos3) {
Optional<CustomCropsBlockState> optional = world.getBlockState(pos3);
if (optional.isPresent() && optional.get().type() instanceof GreenhouseBlock) {

View File

@@ -228,10 +228,15 @@ public class PotBlock extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return Registries.ITEM_TO_POT.containsKey(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
updateBlockAppearance(location, state);
}
@Override
public void onInteract(WrappedInteractEvent event) {
PotConfig potConfig = Registries.ITEM_TO_POT.get(event.relatedID());
@@ -614,9 +619,13 @@ public class PotBlock extends AbstractCustomCropsBlock {
}
public void updateBlockAppearance(Location location, PotConfig config, boolean hasWater, @Nullable Fertilizer fertilizer) {
if (config.disablePluginMechanism()) return;
String appearance = config.getPotAppearance(hasWater, fertilizer == null ? null : fertilizer.type());
BukkitCustomCropsPlugin.getInstance().getItemManager().placeBlock(location, appearance);
if (config.disablePluginMechanism()) {
String appearance = config.blocks().stream().findAny().get();
BukkitCustomCropsPlugin.getInstance().getItemManager().placeBlock(location, appearance);
} else {
String appearance = config.getPotAppearance(hasWater, fertilizer == null ? null : fertilizer.type());
BukkitCustomCropsPlugin.getInstance().getItemManager().placeBlock(location, appearance);
}
}
private Fertilizer tagToFertilizer(CompoundMap tag) {

View File

@@ -20,6 +20,7 @@ package net.momirealms.customcrops.api.core.block;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.BuiltInBlockMechanics;
import net.momirealms.customcrops.api.core.ConfigManager;
import net.momirealms.customcrops.api.core.FurnitureRotation;
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.api.core.world.Pos3;
@@ -102,10 +103,17 @@ public class ScarecrowBlock extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return ConfigManager.scarecrow().contains(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
String id = ConfigManager.scarecrow().stream().findAny().orElse(null);
if (id == null) return;
BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, ConfigManager.scarecrowExistenceForm(), id, FurnitureRotation.NONE);
}
public CustomCropsBlockState getOrFixState(CustomCropsWorld<?> world, Pos3 pos3) {
Optional<CustomCropsBlockState> optional = world.getBlockState(pos3);
if (optional.isPresent() && optional.get().type() instanceof ScarecrowBlock) {

View File

@@ -148,10 +148,17 @@ public class SprinklerBlock extends AbstractCustomCropsBlock {
}
@Override
public boolean isBlockInstance(String id) {
public boolean isInstance(String id) {
return Registries.ITEM_TO_SPRINKLER.containsKey(id);
}
@Override
public void restore(Location location, CustomCropsBlockState state) {
SprinklerConfig config = config(state);
if (config == null) return;
updateBlockAppearance(location, config, water(state) != 0);
}
@Override
public void onInteract(WrappedInteractEvent event) {
SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.relatedID());

View File

@@ -55,4 +55,6 @@ public interface CustomCropsBlockState extends DataBlock {
@ApiStatus.Internal
byte[] getNBTDataAsBytes();
String asString();
}

View File

@@ -46,6 +46,13 @@ public class CustomCropsBlockStateImpl implements CustomCropsBlockState {
return TagUtils.toBytes(new CompoundTag("data", compoundMap.originalMap()));
}
@Override
public String asString() {
return owner.type().asString() +
"[" + compoundMap.asString() +
']';
}
@Override
public Tag<?> set(String key, Tag<?> tag) {
return compoundMap.put(key, tag);
@@ -68,10 +75,9 @@ public class CustomCropsBlockStateImpl implements CustomCropsBlockState {
@Override
public String toString() {
return "CustomCropsBlock{" +
"Type{" + owner.type().asString() +
"}, " + compoundMap +
'}';
return owner.type().asString() +
"[" + compoundMap +
']';
}
@Override

View File

@@ -333,6 +333,12 @@ public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
return this.lazyChunks.remove(chunkPos);
}
public void deleteChunk(ChunkPos chunkPos) {
this.lazyChunks.remove(chunkPos);
this.loadedChunks.remove(chunkPos);
getRegion(RegionPos.getByChunkPos(chunkPos)).ifPresent(region -> region.removeCachedChunk(chunkPos));
}
@Nullable
public CustomCropsChunk getLazyChunk(ChunkPos chunkPos) {
return this.lazyChunks.get(chunkPos);

View File

@@ -46,4 +46,16 @@ public interface MessageConstants {
TranslatableComponent.Builder COMMAND_FORCE_TICK_SUCCESS = Component.translatable().key("command.force_tick.success");
TranslatableComponent.Builder COMMAND_FORCE_TICK_FAILURE_TYPE = Component.translatable().key("command.force_tick.failure.type");
TranslatableComponent.Builder COMMAND_FORCE_TICK_FAILURE_DISABLE = Component.translatable().key("command.force_tick.failure.disable");
TranslatableComponent.Builder COMMAND_DEBUG_DATA_FAILURE = Component.translatable().key("command.debug.data.failure");
TranslatableComponent.Builder COMMAND_DEBUG_DATA_SUCCESS_VANILLA = Component.translatable().key("command.debug.data.success.vanilla");
TranslatableComponent.Builder COMMAND_DEBUG_DATA_SUCCESS_CUSTOM = Component.translatable().key("command.debug.data.success.custom");
TranslatableComponent.Builder COMMAND_DEBUG_WORLDS_FAILURE = Component.translatable().key("command.debug.worlds.failure");
TranslatableComponent.Builder COMMAND_DEBUG_WORLDS_SUCCESS = Component.translatable().key("command.debug.worlds.success");
TranslatableComponent.Builder COMMAND_DEBUG_INSIGHT_OFF = Component.translatable().key("command.debug.insight.off");
TranslatableComponent.Builder COMMAND_DEBUG_INSIGHT_ON = Component.translatable().key("command.debug.insight.on");
TranslatableComponent.Builder COMMAND_DEBUG_DELETE_FAILURE_WORLD = Component.translatable().key("command.unsafe.delete.failure.world");
TranslatableComponent.Builder COMMAND_DEBUG_DELETE_SUCCESS = Component.translatable().key("command.unsafe.delete.success");
TranslatableComponent.Builder COMMAND_DEBUG_RESTORE_FAILURE_WORLD = Component.translatable().key("command.unsafe.restore.failure.world");
TranslatableComponent.Builder COMMAND_DEBUG_RESTORE_FAILURE_CHUNK = Component.translatable().key("command.unsafe.restore.failure.chunk");
TranslatableComponent.Builder COMMAND_DEBUG_RESTORE_SUCCESS = Component.translatable().key("command.unsafe.restore.success");
}

View File

@@ -168,9 +168,9 @@ public class TranslationManager {
}
Map<String, String> bundle = new HashMap<>();
YamlDocument document = plugin.getConfigManager().loadConfig("translations" + "\\" + translationFile.getFileName(), '@');
YamlDocument document = plugin.getConfigManager().loadConfig("translations" + File.separator + translationFile.getFileName(), '@');
try {
document.save(new File(plugin.getDataDirectory().toFile(), "translations" + "\\" + translationFile.getFileName()));
document.save(new File(plugin.getDataDirectory().toFile(), "translations" + File.separator + translationFile.getFileName()));
} catch (IOException e) {
throw new IllegalStateException("Could not update translation file: " + translationFile.getFileName(), e);
}

View File

@@ -1,7 +1,7 @@
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=3.6.10
config_version=40
config_version=41
project_group=net.momirealms
# Dependency settings

View File

@@ -98,7 +98,7 @@ public class BukkitCustomCropsPluginImpl extends BukkitCustomCropsPlugin {
@Override
public InputStream getResourceStream(String filePath) {
return getBootstrap().getResource(filePath);
return getBootstrap().getResource(filePath.replace("\\", "/"));
}
@Override

View File

@@ -43,7 +43,9 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new SetDateCommand(this),
new ForceTickCommand(this),
new DebugWorldsCommand(this),
new DebugInsightCommand(this)
new DebugInsightCommand(this),
new UnsafeRestoreCommand(this),
new UnsafeDeleteCommand(this)
);
private final Index<String, CommandFeature<CommandSender>> INDEX = Index.create(CommandFeature::getFeatureID, FEATURES);

View File

@@ -17,12 +17,14 @@
package net.momirealms.customcrops.bukkit.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
import net.momirealms.customcrops.api.core.world.Pos3;
import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature;
import net.momirealms.customcrops.common.command.CustomCropsCommandManager;
import net.momirealms.customcrops.common.helper.AdventureHelper;
import net.momirealms.customcrops.common.locale.MessageConstants;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
@@ -52,22 +54,21 @@ public class DebugDataCommand extends BukkitCommandFeature<CommandSender> {
block = location.getBlock();
} else {
block = player.getTargetBlockExact(10);
if (block == null) return;
if (block == null) {
handleFeedback(context, MessageConstants.COMMAND_DEBUG_DATA_FAILURE);
return;
}
location = block.getLocation();
}
BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(location.getWorld()).ifPresent(world -> {
Optional<CustomCropsBlockState> state = world.getBlockState(Pos3.from(location));
if (state.isPresent()) {
BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(player)
.sendMessage(AdventureHelper.miniMessage("<gold>" + state.get()));
} else {
BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(player)
.sendMessage(AdventureHelper.miniMessage("<red>CustomCrops Data not found"));
}
state.ifPresent(customCropsBlockState ->
handleFeedback(context,
MessageConstants.COMMAND_DEBUG_DATA_SUCCESS_CUSTOM,
Component.text(customCropsBlockState.asString())));
});
String bData = block.getBlockData().getAsString();
BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(player)
.sendMessage(AdventureHelper.miniMessage("<green>Vanilla crop data: <hover:show_text:'<yellow>Copy'><click:copy_to_clipboard:'"+bData+"'>" + bData + "</click>"));
handleFeedback(context, MessageConstants.COMMAND_DEBUG_DATA_SUCCESS_VANILLA, Component.text(bData));
});
}

View File

@@ -23,6 +23,7 @@ import net.momirealms.customcrops.api.util.LocationUtils;
import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature;
import net.momirealms.customcrops.common.command.CustomCropsCommandManager;
import net.momirealms.customcrops.common.helper.AdventureHelper;
import net.momirealms.customcrops.common.locale.MessageConstants;
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask;
import net.momirealms.sparrow.heart.SparrowHeart;
import net.momirealms.sparrow.heart.feature.color.NamedTextColor;
@@ -59,14 +60,13 @@ public class DebugInsightCommand extends BukkitCommandFeature<CommandSender> imp
Player player = context.sender();
if (player.hasMetadata("customcrops:insight")) {
player.removeMetadata("customcrops:insight", plugin.getBootstrap());
plugin.getSenderFactory().wrap(player).sendMessage(AdventureHelper.miniMessage("<red>Insight mode: OFF"));
handleFeedback(context, MessageConstants.COMMAND_DEBUG_INSIGHT_OFF);
return;
}
player.setMetadata("customcrops:insight", new FixedMetadataValue(plugin.getBootstrap(), 1));
new InsightPlayer(player.getUniqueId());
plugin.getSenderFactory().wrap(player).sendMessage(AdventureHelper.miniMessage("<green>Insight mode: ON"));
plugin.getSenderFactory().wrap(player).sendMessage(AdventureHelper.miniMessage("<white>Note that this only shows a snapshot of the data."));
handleFeedback(context, MessageConstants.COMMAND_DEBUG_INSIGHT_ON);
});
}

View File

@@ -17,17 +17,20 @@
package net.momirealms.customcrops.bukkit.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature;
import net.momirealms.customcrops.common.command.CustomCropsCommandManager;
import net.momirealms.customcrops.common.helper.AdventureHelper;
import net.momirealms.customcrops.common.sender.Sender;
import net.momirealms.customcrops.common.locale.MessageConstants;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import java.util.Optional;
public class DebugWorldsCommand extends BukkitCommandFeature<CommandSender> {
public DebugWorldsCommand(CustomCropsCommandManager<CommandSender> commandManager) {
@@ -38,14 +41,19 @@ public class DebugWorldsCommand extends BukkitCommandFeature<CommandSender> {
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.handler(context -> {
Sender sender = BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(context.sender());
int worldCount = 0;
for (World world : Bukkit.getWorlds()) {
BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(world).ifPresent(w -> {
sender.sendMessage(AdventureHelper.miniMessage("<gold>World: " + world.getName() + "</gold>"));
sender.sendMessage(AdventureHelper.miniMessage(" - Loaded regions: " + w.loadedRegions().length));
sender.sendMessage(AdventureHelper.miniMessage(" - Loaded chunks: " + w.loadedChunks().length));
sender.sendMessage(AdventureHelper.miniMessage(" - Lazy chunks: " + w.lazyChunks().length));
});
Optional<CustomCropsWorld<?>> optional = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(world);
if (optional.isPresent()) {
worldCount++;
CustomCropsWorld<?> w = optional.get();
handleFeedback(context, MessageConstants.COMMAND_DEBUG_WORLDS_SUCCESS,
Component.text(world.getName()), Component.text(w.loadedRegions().length), Component.text(w.loadedChunks().length), Component.text(w.lazyChunks().length)
);
}
}
if (worldCount == 0) {
handleFeedback(context, MessageConstants.COMMAND_DEBUG_WORLDS_FAILURE);
}
});
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customcrops.bukkit.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.world.ChunkPos;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.api.core.world.CustomCropsWorldImpl;
import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature;
import net.momirealms.customcrops.common.command.CustomCropsCommandManager;
import net.momirealms.customcrops.common.locale.MessageConstants;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import java.util.Optional;
public class UnsafeDeleteCommand extends BukkitCommandFeature<CommandSender> {
public UnsafeDeleteCommand(CustomCropsCommandManager<CommandSender> commandManager) {
super(commandManager);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.flag(manager.flagBuilder("silent").build())
.handler(context -> {
Player player = context.sender();
Optional<CustomCropsWorld<?>> optional = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(player.getWorld());
if (optional.isEmpty()) {
handleFeedback(context, MessageConstants.COMMAND_DEBUG_DELETE_FAILURE_WORLD, Component.text(player.getWorld().getName()));
return;
}
CustomCropsWorld<?> world = optional.get();
CustomCropsWorldImpl<?> customCropsWorld = (CustomCropsWorldImpl<?>) world;
ChunkPos chunkPos = ChunkPos.fromBukkitChunk(player.getLocation().getChunk());
customCropsWorld.deleteChunk(chunkPos);
handleFeedback(context, MessageConstants.COMMAND_DEBUG_DELETE_SUCCESS);
});
}
@Override
public String getFeatureID() {
return "unsafe_delete";
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) <2024> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customcrops.bukkit.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.AbstractItemManager;
import net.momirealms.customcrops.api.core.world.*;
import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature;
import net.momirealms.customcrops.common.command.CustomCropsCommandManager;
import net.momirealms.customcrops.common.locale.MessageConstants;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import java.util.Map;
import java.util.Optional;
public class UnsafeRestoreCommand extends BukkitCommandFeature<CommandSender> {
public UnsafeRestoreCommand(CustomCropsCommandManager<CommandSender> commandManager) {
super(commandManager);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.flag(manager.flagBuilder("silent").build())
.handler(context -> {
Player player = context.sender();
World bukkitWorld = player.getWorld();
Optional<CustomCropsWorld<?>> optional = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(bukkitWorld);
if (optional.isEmpty()) {
handleFeedback(context, MessageConstants.COMMAND_DEBUG_RESTORE_FAILURE_WORLD, Component.text(bukkitWorld.getName()));
return;
}
ChunkPos chunkPos = ChunkPos.fromBukkitChunk(player.getLocation().getChunk());
CustomCropsWorld<?> world = optional.get();
Optional<CustomCropsChunk> chunk = world.getLoadedChunk(chunkPos);
if (chunk.isEmpty()) {
handleFeedback(context, MessageConstants.COMMAND_DEBUG_RESTORE_FAILURE_CHUNK);
return;
}
CustomCropsChunk customCropsChunk = chunk.get();
AbstractItemManager itemManager = BukkitCustomCropsPlugin.getInstance().getItemManager();
int totalBlocks = 0;
int restoredBlocks = 0;
for (CustomCropsSection section : customCropsChunk.sections()) {
for (Map.Entry<BlockPos, CustomCropsBlockState> entry : section.blockMap().entrySet()) {
totalBlocks++;
Pos3 pos3 = entry.getKey().toPos3(chunkPos);
Location location = pos3.toLocation(bukkitWorld);
String realID = itemManager.anyID(location);
if (!entry.getValue().type().isInstance(realID)) {
restoredBlocks++;
entry.getValue().type().restore(location, entry.getValue());
}
}
}
handleFeedback(context, MessageConstants.COMMAND_DEBUG_RESTORE_SUCCESS, Component.text(restoredBlocks), Component.text(totalBlocks));
});
}
@Override
public String getFeatureID() {
return "unsafe_restore";
}
}

View File

@@ -574,7 +574,7 @@ public class BukkitItemManager extends AbstractItemManager {
CustomCropsBlockState customCropsBlockState = optionalState.get();
String anyFurnitureID = furnitureID(location);
if (anyFurnitureID != null) {
if (!customCropsBlockState.type().isBlockInstance(anyFurnitureID)) {
if (!customCropsBlockState.type().isInstance(anyFurnitureID)) {
world.removeBlockState(pos3);
plugin.debug(() -> "[" + location.getWorld().getName() + "] Removed inconsistent block data at " + pos3 + " which used to be " + customCropsBlockState);
} else {
@@ -583,7 +583,7 @@ public class BukkitItemManager extends AbstractItemManager {
}
}
String anyBlockID = blockID(location);
if (!customCropsBlockState.type().isBlockInstance(anyBlockID)) {
if (!customCropsBlockState.type().isInstance(anyBlockID)) {
world.removeBlockState(pos3);
plugin.debug(() -> "[" + location.getWorld().getName() + "] Removed inconsistent block data at " + pos3 + " which used to be " + customCropsBlockState);
} else {

View File

@@ -86,4 +86,22 @@ force_tick:
permission: customcrops.command.force_tick
usage:
- /customcrops force-tick
- /ccrops force-tick
- /ccrops force-tick
# A command to restore the blocks by CustomCrops data in one chunk
# Usage: [COMMAND]
unsafe_restore:
enable: true
permission: customcrops.command.unsafe.restore
usage:
- /customcrops unsafe restore
- /ccrops unsafe restore
# A command to delete the CustomCrops data in one chunk
# Usage: [COMMAND]
unsafe_delete:
enable: true
permission: customcrops.command.unsafe.delete
usage:
- /customcrops unsafe delete
- /ccrops unsafe delete

View File

@@ -1,6 +1,11 @@
# Don"t change this
config-version: "38"
config-version: "41"
season.spring: "Spring"
season.summer: "Summer"
season.autumn: "Autumn"
season.winter: "Winter"
season.disable: "Disable"
exception.invalid_syntax: "<red>Invalid syntax. Correct syntax: <white><arg:0></white></red>"
exception.invalid_argument: "<red>Invalid argument. Reason: <white><arg:0></white></red>"
exception.invalid_sender: "<red><arg:0> is not allowed to execute that command. Must be of type <arg:1></red>"
@@ -57,8 +62,21 @@ command.date.set.failure.invalid: "<red>Invalid date [<arg:1>]</red>"
command.force_tick.success: "<white>Took <arg:0>ms ticking <arg:1> blocks</white>"
command.force_tick.failure.disable: "<red>CustomCrops is not enabled in world [<arg:0>]</red>"
command.force_tick.failure.type: "<red>Unknown type [<arg:0>]</red>"
season.spring: "Spring"
season.summer: "Summer"
season.autumn: "Autumn"
season.winter: "Winter"
season.disable: "Disable"
command.unsafe.delete.failure.world: "<red>CustomCrops is not enabled in world [<arg:0>]</red>"
command.unsafe.delete.success: "<white>Deleted block data in this chunk"
command.unsafe.restore.failure.world: "<red>CustomCrops is not enabled in world [<arg:0>]</red>"
command.unsafe.restore.failure.chunk: "<red>This chunk doesn't contain any data</red>"
command.unsafe.restore.success: "<white>Restored (<arg:0>/<arg:1>) blocks</white>"
command.debug.data.failure: "<red>No block selected</red>"
command.debug.data.success.vanilla: "<green>Vanilla block data: <hover:show_text:'<yellow>Copy'><click:copy_to_clipboard:'<arg:0>'><arg:0></click>"
command.debug.data.success.custom: "<gold>Custom block data: <hover:show_text:'<yellow>Copy'><click:copy_to_clipboard:'<arg:0>'><arg:0></click>"
command.debug.worlds.failure: "<red>There's no world loaded</red>"
command.debug.worlds.success:
- "<gold>World: <arg:0></gold>"
- " - Loaded regions: <arg:1>"
- " - Loaded chunks: <arg:2>"
- " - Lazy chunks: <arg:3>"
command.debug.insight.off: "<red>Insight mode: OFF</red>"
command.debug.insight.on:
- "<green>Insight mode: ON</green>"
- "<white>Note that this only shows a snapshot of the data."

View File

@@ -1,6 +1,11 @@
# 别动这个
config-version: "38"
config-version: "41"
season.spring: "春"
season.summer: "夏"
season.autumn: "秋"
season.winter: "冬"
season.disable: "未启用"
exception.invalid_syntax: "<red>无效语法. 正确语法:<white><arg:0></white></red>"
exception.invalid_argument: "<red>无效参数. 原因:<white><arg:0></white></red>"
exception.invalid_sender: "<red><arg:0> 不允许执行该命令. 执行者必须是 <arg:1></red>"
@@ -56,8 +61,21 @@ command.date.set.failure.invalid: "<red>无效的日期 [<arg:1>]</red>"
command.force_tick.success: "<white>花费 <arg:0>ms 更新了 <arg:1> 个方块</white>"
command.force_tick.failure.disable: "<red>CustomCrops没有在世界 [<arg:0>] 启用</red>"
command.force_tick.failure.type: "<red>未知的类型 [<arg:0>]</red>"
season.spring: "春"
season.summer: "夏"
season.autumn: "秋"
season.winter: "冬"
season.disable: "未启用"
command.unsafe.delete.failure.world: "<red>CustomCrops没有在世界 [<arg:0>] 启用</red>"
command.unsafe.delete.success: "<white>删除了这个区块的数据"
command.unsafe.restore.failure.world: "<red>CustomCrops没有在世界 [<arg:0>] 启用</red>"
command.unsafe.restore.failure.chunk: "<red>这个区块没有任何数据</red>"
command.unsafe.restore.success: "<white>恢复了 (<arg:0>/<arg:1>) 方块</white>"
command.debug.data.failure: "<red>未选中任何方块</red>"
command.debug.data.success.vanilla: "<green>原版方块数据: <hover:show_text:'<yellow>Copy'><click:copy_to_clipboard:'<arg:0>'><arg:0></click>"
command.debug.data.success.custom: "<gold>自定义方块数据: <hover:show_text:'<yellow>Copy'><click:copy_to_clipboard:'<arg:0>'><arg:0></click>"
command.debug.worlds.failure: "<red>没有农作物世界被加载</red>"
command.debug.worlds.success:
- "<gold>世界: <arg:0></gold>"
- " - 加载中的区域: <arg:1>"
- " - 加载中的区块: <arg:2>"
- " - 不活跃的区块: <arg:3>"
command.debug.insight.off: "<red>洞察模式: OFF</red>"
command.debug.insight.on:
- "<green>洞察模式: ON</green>"
- "<white>注意在这个模式下你只能看到数据的快照"