diff --git a/build.gradle b/build.gradle index f7c0c66e..68e69288 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ dependencies { modCompile "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. - modCompile "net.fabricmc:fabric:${project.fabric_version}" + // modCompile "net.fabricmc:fabric:${project.fabric_version}" // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index b0af8590..99ebe115 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -1,25 +1,25 @@ package carpet; import carpet.commands.CameraModeCommand; -//import carpet.commands.CarpetCommand; -//import carpet.commands.CounterCommand; +import carpet.commands.CarpetCommand; +import carpet.commands.CounterCommand; //import carpet.commands.DistanceCommand; import carpet.commands.DrawCommand; //import carpet.commands.ScriptCommand; //import carpet.commands.InfoCommand; -//import carpet.commands.LogCommand; -//import carpet.commands.PerimeterInfoCommand; -//import carpet.commands.PlayerCommand; -//import carpet.commands.SpawnCommand; +import carpet.commands.PerimeterInfoCommand; +import carpet.commands.PlayerCommand; +import carpet.commands.SpawnCommand; +import carpet.commands.LogCommand; import carpet.commands.TestCommand; -//import carpet.commands.TickCommand; -//import carpet.logging.LoggerRegistry; +import carpet.commands.TickCommand; //import carpet.script.ExpressionInspector; -//import carpet.utils.HUDController; import java.util.Random; //import carpet.helpers.TickSpeed; +import carpet.logging.LoggerRegistry; +import carpet.utils.HUDController; import com.mojang.brigadier.CommandDispatcher; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.MinecraftServer; @@ -34,33 +34,32 @@ public class CarpetServer // static for now - easier to handle all around the co } public static void onServerLoaded(MinecraftServer server) { - //CarpetSettings.apply_settings_from_conf(server); + CarpetSettings.apply_settings_from_conf(server); //ExpressionInspector.CarpetExpression_resetExpressionEngine(); } // Separate from onServerLoaded, because a server can be loaded multiple times in singleplayer public static void onGameStarted() { - //LoggerRegistry.initLoggers(); + LoggerRegistry.initLoggers(); } public static void tick(MinecraftServer server) { - CarpetSettings.LOG.error("ticking..."); //TickSpeed.tick(server); - //HUDController.update_hud(server); + HUDController.update_hud(server); } public static void registerCarpetCommands(CommandDispatcher dispatcher) { - //CarpetCommand.register(dispatcher); - //TickCommand.register(dispatcher); - //CounterCommand.register(dispatcher); - //LogCommand.register(dispatcher); - //SpawnCommand.register(dispatcher); - //PlayerCommand.register(dispatcher); + CarpetCommand.register(dispatcher); + TickCommand.register(dispatcher); + CounterCommand.register(dispatcher); + LogCommand.register(dispatcher); + SpawnCommand.register(dispatcher); + PlayerCommand.register(dispatcher); CameraModeCommand.register(dispatcher); //InfoCommand.register(dispatcher); //DistanceCommand.register(dispatcher); - //PerimeterInfoCommand.register(dispatcher); + PerimeterInfoCommand.register(dispatcher); DrawCommand.register(dispatcher); //ScriptCommand.register(dispatcher); diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 297a5887..7ba57e65 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -179,33 +179,12 @@ public class CarpetSettings Messenger.print_server_message(CarpetServer.minecraft_server, "view distance can only be changed on a server"); } }), - /*rule("disableSpawnChunks", "creative", "Removes the spawn chunks.") + rule("disableSpawnChunks", "creative", "Removes the spawn chunks.") .validate((s) -> { - if (!CarpetSettings.getBool("disableSpawnChunks")) { - ServerWorld overworld = CarpetServer.minecraft_server.getWorld(DimensionType.OVERWORLD); - if (overworld == null) - return; - - List chunkList = SpawnChunks.listIncludedChunks(overworld); - - // Reused from MinecraftServer initialWorldChunkLoad - CompletableFuture completablefuture = overworld.getChunkProvider().loadChunks(chunkList, (c) -> {}); - while (!completablefuture.isDone()) { - try { - completablefuture.get(1L, TimeUnit.SECONDS); - } catch (InterruptedException interruptedexception) { - throw new RuntimeException(interruptedexception); - } catch (ExecutionException executionexception) { - if (executionexception.getCause() instanceof RuntimeException) { - throw (RuntimeException) executionexception.getCause(); - } - - throw new RuntimeException(executionexception.getCause()); - } catch (TimeoutException var22) { - } - } - } - }).boolAccelerate(),*/ + if (!CarpetSettings.getBool("disableSpawnChunks")) + if (CarpetServer.minecraft_server != null) + Messenger.print_server_message(CarpetServer.minecraft_server, "Spawn chunks re-enabled. Visit spawn to load it."); + }).boolAccelerate(), rule("kelpGenerationGrowLimit", "feature", "limits growth limit of newly naturally generated kelp to this amount of blocks") .choices("25", "0 2 25").setNotStrict(), rule("renewableCoral", "feature", "Coral structures will grow with bonemeal from coral plants"), @@ -221,7 +200,7 @@ public class CarpetSettings private static void notifyPlayersCommandsChanged() { - if (CarpetServer.minecraft_server == null) + if (CarpetServer.minecraft_server == null || CarpetServer.minecraft_server.getPlayerManager() == null) { return; } diff --git a/src/main/java/carpet/commands/CarpetCommand.java b/src/main/java/carpet/commands/CarpetCommand.java new file mode 100644 index 00000000..fd212749 --- /dev/null +++ b/src/main/java/carpet/commands/CarpetCommand.java @@ -0,0 +1,202 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.CarpetSettings.CarpetSettingEntry; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class CarpetCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("carpet").requires((player) -> + player.hasPermissionLevel(2) && !CarpetSettings.locked); + + literalargumentbuilder.executes((context)->listAllSettings(context.getSource())). + then(literal("list"). + executes( (c) -> listSettings(c.getSource(), + "All CarpetMod Settings", + CarpetSettings.findAll(null))). + then(literal("defaults"). + executes( (c)-> listSettings(c.getSource(), + "Current CarpetMod Startup Settings from carpet.conf", + CarpetSettings.findStartupOverrides(c.getSource().getMinecraftServer())))). + then(argument("tag",StringArgumentType.word()). + suggests( (c, b)->suggestMatching(CarpetSettings.default_tags, b)). + executes( (c) -> listSettings(c.getSource(), + String.format("CarpetMod Settings matching \"%s\"", StringArgumentType.getString(c, "tag")), + CarpetSettings.findAll(StringArgumentType.getString(c, "tag")))))); + + for (CarpetSettingEntry rule: CarpetSettings.getAllCarpetSettings()) + { + literalargumentbuilder.then(literal(rule.getName()).executes( (context) -> + displayRuleMenu(context.getSource(),rule))); + literalargumentbuilder.then(literal("removeDefault"). + then(literal(rule.getName()).executes((context) -> + removeDefault(context.getSource(), rule)))); + literalargumentbuilder.then(literal(rule.getName()). + then(argument("value", StringArgumentType.word()). + suggests((c, b)-> suggestMatching(rule.getOptions(),b)). + executes((context) -> + setRule(context.getSource(), rule, StringArgumentType.getString(context, "value"))))); + literalargumentbuilder.then(literal("setDefault"). + then(literal(rule.getName()). + then(argument("value", StringArgumentType.word()). + suggests((c, b)-> suggestMatching(rule.getOptions(),b)). + executes((context) -> + setDefault(context.getSource(), rule, StringArgumentType.getString(context, "value")))))); + } + dispatcher.register(literalargumentbuilder); + } + + private static int displayRuleMenu(ServerCommandSource source, CarpetSettingEntry rule) + { + PlayerEntity player; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "w "+rule.getName() +" is set to: ","wb "+rule.getStringValue()); + return 1; + } + + Messenger.m(player, ""); + Messenger.m(player, "wb "+rule.getName(),"!/carpet "+rule.getName(),"^g refresh"); + Messenger.m(player, "w "+rule.getToast()); + + Arrays.stream(rule.getInfo()).forEach(s -> Messenger.m(player, "g "+s)); + + List tags = new ArrayList<>(); + tags.add(Messenger.c("w Tags: ")); + for (String t: rule.getTags()) + { + tags.add(Messenger.c("c ["+t+"]", "^g list all "+t+" settings","!/carpet list "+t)); + tags.add(Messenger.c("w , ")); + } + tags.remove(tags.size()-1); + Messenger.m(player, tags.toArray(new Object[0])); + + Messenger.m(player, "w Current value: ",String.format("%s %s (%s value)",rule.getBoolValue()?"lb":"nb", rule.getStringValue(),rule.isDefault()?"default":"modified")); + List options = new ArrayList<>(); + options.add(Messenger.c("w Options: ", "y [ ")); + for (String o: rule.getOptions()) + { + options.add(Messenger.c( + String.format("%s%s %s",(o.equals(rule.getDefault()))?"u":"", (o.equals(rule.getStringValue()))?"bl":"y", o ), + "^g switch to "+o, + String.format("?/carpet %s %s",rule.getName(),o))); + options.add(Messenger.c("w ")); + } + options.remove(options.size()-1); + options.add(Messenger.c("y ]")); + Messenger.m(player, options.toArray(new Object[0])); + + return 1; + } + + private static int setRule(ServerCommandSource source, CarpetSettingEntry rule, String newValue) + { + CarpetSettings.set(rule.getName(), newValue); + Messenger.m(source, "w "+rule.toString()+", ", "c [change permanently?]", + "^w Click to keep the settings in carpet.conf to save across restarts", + "?/carpet setDefault "+rule.getName()+" "+rule.getStringValue()); + return 1; + } + private static int setDefault(ServerCommandSource source, CarpetSettingEntry rule, String defaultValue) + { + CarpetSettings.setDefaultRule(source.getMinecraftServer(), rule.getName(), defaultValue); + Messenger.m(source ,"gi rule "+ rule.getName()+" will now default to "+ defaultValue); + return 1; + } + private static int removeDefault(ServerCommandSource source, CarpetSettingEntry rule) + { + CarpetSettings.removeDefaultRule(source.getMinecraftServer(), rule.getName()); + Messenger.m(source ,"gi rule "+ rule.getName()+" defaults to Vanilla"); + return 1; + } + + + private static BaseComponent displayInteractiveSetting(CarpetSettingEntry e) + { + List args = new ArrayList<>(); + args.add("w - "+e.getName()+" "); + args.add("!/carpet "+e.getName()); + args.add("^y "+e.getToast()); + for (String option: e.getOptions()) + { + String style = e.isDefault()?"g":(option.equalsIgnoreCase(e.getDefault())?"y":"e"); + if (option.equalsIgnoreCase(e.getDefault())) + style = style+"b"; + else if (option.equalsIgnoreCase(e.getStringValue())) + style = style+"u"; + args.add(style+" ["+option+"]"); + if (!option.equalsIgnoreCase(e.getStringValue())) + { + args.add("!/carpet " + e.getName() + " " + option); + args.add("^w switch to " + option); + } + args.add("w "); + } + args.remove(args.size()-1); + return Messenger.c(args.toArray(new Object[0])); + } + + private static int listSettings(ServerCommandSource source, String title, CarpetSettingEntry[] settings_list) + { + try + { + PlayerEntity player = source.getPlayer(); + Messenger.m(player,String.format("wb %s:",title)); + Arrays.stream(settings_list).forEach(e -> Messenger.m(player,displayInteractiveSetting(e))); + + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "w s:"+title); + Arrays.stream(settings_list).forEach(r -> Messenger.m(source, "w - "+ r.toString())); + } + return 1; + } + private static int listAllSettings(ServerCommandSource source) + { + listSettings(source, "Current CarpetMod Settings", CarpetSettings.find_nondefault(source.getMinecraftServer())); + + Messenger.m(source, "Carpet Mod version: "+CarpetSettings.carpetVersion); + try + { + PlayerEntity player = source.getPlayer(); + List tags = new ArrayList<>(); + tags.add("w Browse Categories:\n"); + for (String t : CarpetSettings.default_tags) + { + tags.add("c [" + t+"]"); + tags.add("^g list all " + t + " settings"); + tags.add("!/carpet list " + t); + tags.add("w "); + } + tags.remove(tags.size() - 1); + Messenger.m(player, tags.toArray(new Object[0])); + } + catch (CommandSyntaxException e) + { + } + return 1; + } +} diff --git a/src/main/java/carpet/commands/CounterCommand.java b/src/main/java/carpet/commands/CounterCommand.java new file mode 100644 index 00000000..f92a52f1 --- /dev/null +++ b/src/main/java/carpet/commands/CounterCommand.java @@ -0,0 +1,78 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.helpers.HopperCounter; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.command.CommandException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.util.DyeColor; +import net.minecraft.network.chat.BaseComponent; + +public class CounterCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = CommandManager.literal("counter").executes((context) + -> listAllCounters(context.getSource(), false)).requires((player) -> + CarpetSettings.getBool("hopperCounters")); + + literalargumentbuilder. + then((CommandManager.literal("reset").executes( (p_198489_1_)-> + resetCounter(p_198489_1_.getSource(), null)))); + for (DyeColor enumDyeColor: DyeColor.values()) + { + String color = enumDyeColor.toString(); + literalargumentbuilder. + then((CommandManager.literal(color).executes( (p_198489_1_)-> displayCounter(p_198489_1_.getSource(), color, false)))); + literalargumentbuilder.then(CommandManager.literal(color). + then(CommandManager.literal("reset").executes((context) -> + resetCounter(context.getSource(), color)))); + literalargumentbuilder.then(CommandManager.literal(color). + then(CommandManager.literal("realtime").executes((context) -> + displayCounter(context.getSource(), color, true)))); + } + dispatcher.register(literalargumentbuilder); + } + + private static int displayCounter(ServerCommandSource source, String color, boolean realtime) + { + HopperCounter counter = HopperCounter.getCounter(color); + if (counter == null) throw new CommandException(Messenger.s("Unknown wool color: "+color)); + + for (BaseComponent message: counter.format(source.getMinecraftServer(), realtime, false)) + { + source.sendFeedback(message, false); + } + return 1; + } + + private static int resetCounter(ServerCommandSource source, String color) + { + if (color == null) + { + HopperCounter.resetAll(source.getMinecraftServer()); + Messenger.m(source, "w Restarted all counters"); + } + else + { + HopperCounter counter = HopperCounter.getCounter(color); + if (counter == null) throw new CommandException(Messenger.s("Unknown wool color")); + counter.reset(source.getMinecraftServer()); + Messenger.m(source, "w Restarted "+color+" counter"); + } + return 1; + } + + private static int listAllCounters(ServerCommandSource source, boolean realtime) + { + for (BaseComponent message: HopperCounter.formatAll(source.getMinecraftServer(), realtime)) + { + source.sendFeedback(message, false); + } + return 1; + } + +} diff --git a/src/main/java/carpet/commands/LogCommand.java b/src/main/java/carpet/commands/LogCommand.java new file mode 100644 index 00000000..4e7cc4e1 --- /dev/null +++ b/src/main/java/carpet/commands/LogCommand.java @@ -0,0 +1,214 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.logging.Logger; +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class LogCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = CommandManager.literal("log"). + requires((player) -> CarpetSettings.getBool("commandLog")). + executes((context) -> listLogs(context.getSource())). + then(CommandManager.literal("clear"). + executes( (c) -> unsubFromAll(c.getSource(), c.getSource().getName())). + then(CommandManager.argument("player", StringArgumentType.word()). + suggests( (c, b)-> suggestMatching(c.getSource().getPlayerNames(),b)). + executes( (c) -> unsubFromAll(c.getSource(), getString(c, "player"))))); + + literalargumentbuilder.then(CommandManager.argument("log name",StringArgumentType.word()). + suggests( (c, b)-> suggestMatching(LoggerRegistry.getLoggerNames(),b)). + executes( (c)-> toggleSubscription(c.getSource(), c.getSource().getName(), getString(c, "log name"))). + then(CommandManager.literal("clear"). + executes( (c) -> unsubFromLogger( + c.getSource(), + c.getSource().getName(), + getString(c, "log name")))). + then(CommandManager.argument("option", StringArgumentType.word()). + suggests( (c, b) -> suggestMatching( + (LoggerRegistry.getLogger(getString(c, "log name"))==null + ?new String[]{} + :LoggerRegistry.getLogger(getString(c, "log name")).getOptions()), + b)). + executes( (c) -> subscribePlayer( + c.getSource(), + c.getSource().getName(), + getString(c, "log name"), + getString(c, "option"))). + then(CommandManager.argument("player", StringArgumentType.word()). + suggests( (c, b) -> suggestMatching(c.getSource().getPlayerNames(),b)). + executes( (c) -> subscribePlayer( + c.getSource(), + getString(c, "player"), + getString(c, "log name"), + getString(c, "option")))))); + + dispatcher.register(literalargumentbuilder); + } + private static int listLogs(ServerCommandSource source) + { + PlayerEntity player; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "For players only"); + return 0; + } + Map subs = LoggerRegistry.getPlayerSubscriptions(source.getName()); + if (subs == null) + { + subs = new HashMap<>(); + } + List all_logs = new ArrayList<>(LoggerRegistry.getLoggerNames()); + Collections.sort(all_logs); + Messenger.m(player, "w _____________________"); + Messenger.m(player, "w Available logging options:"); + for (String lname: all_logs) + { + List comp = new ArrayList<>(); + String color = subs.containsKey(lname)?"w":"g"; + comp.add("w - "+lname+": "); + Logger logger = LoggerRegistry.getLogger(lname); + String [] options = logger.getOptions(); + if (options.length == 0) + { + if (subs.containsKey(lname)) + { + comp.add("l Subscribed "); + } + else + { + comp.add(color + " [Subscribe] "); + comp.add("^w subscribe to " + lname); + comp.add("!/log " + lname); + } + } + else + { + for (String option : logger.getOptions()) + { + if (subs.containsKey(lname) && subs.get(lname).equalsIgnoreCase(option)) + { + comp.add("l [" + option + "] "); + } else + { + comp.add(color + " [" + option + "] "); + comp.add("^w subscribe to " + lname + " " + option); + comp.add("!/log " + lname + " " + option); + } + + } + } + if (subs.containsKey(lname)) + { + comp.add("nb [X]"); + comp.add("^w Click to unsubscribe"); + comp.add("!/log "+lname); + } + Messenger.m(player,comp.toArray(new Object[0])); + } + return 1; + } + private static int unsubFromAll(ServerCommandSource source, String player_name) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + for (String logname : LoggerRegistry.getLoggerNames()) + { + LoggerRegistry.unsubscribePlayer(player_name, logname); + } + Messenger.m(source, "gi Unsubscribed from all logs"); + return 1; + } + private static int unsubFromLogger(ServerCommandSource source, String player_name, String logname) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logname) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logname); + return 0; + } + LoggerRegistry.unsubscribePlayer(player_name, logname); + Messenger.m(source, "gi Unsubscribed from "+logname); + return 1; + } + + private static int toggleSubscription(ServerCommandSource source, String player_name, String logName) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logName) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logName); + return 0; + } + boolean subscribed = LoggerRegistry.togglePlayerSubscription(player_name, logName); + if (subscribed) + { + Messenger.m(source, "gi "+player_name+" subscribed to " + logName + "."); + } + else + { + Messenger.m(source, "gi "+player_name+" unsubscribed from " + logName + "."); + } + return 1; + } + private static int subscribePlayer(ServerCommandSource source, String player_name, String logname, String option) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logname) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logname); + return 0; + } + LoggerRegistry.subscribePlayer(player_name, logname, option); + if (option!=null) + { + Messenger.m(source, "gi Subscribed to " + logname + "(" + option + ")"); + } + else + { + Messenger.m(source, "gi Subscribed to " + logname); + } + return 1; + } +} diff --git a/src/main/java/carpet/commands/PerimeterInfoCommand.java b/src/main/java/carpet/commands/PerimeterInfoCommand.java new file mode 100644 index 00000000..7cf381a5 --- /dev/null +++ b/src/main/java/carpet/commands/PerimeterInfoCommand.java @@ -0,0 +1,75 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.utils.Messenger; +import carpet.utils.PerimeterDiagnostics; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.command.suggestion.SuggestionProviders; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.command.arguments.BlockPosArgumentType; +import net.minecraft.command.arguments.EntitySummonArgumentType; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.math.BlockPos; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class PerimeterInfoCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder command = literal("perimeterinfo"). + requires((player) -> CarpetSettings.getBool("commandPerimeterInfo")). + executes( (c) -> perimeterDiagnose( + c.getSource(), + new BlockPos(c.getSource().getPosition()), + null)). + then(argument("center position", BlockPosArgumentType.create()). + executes( (c) -> perimeterDiagnose( + c.getSource(), + BlockPosArgumentType.getBlockPos(c, "center position"), + null)). + then(argument("mob",EntitySummonArgumentType.create()). + suggests(SuggestionProviders.SUMMONABLE_ENTITIES). + executes( (c) -> perimeterDiagnose( + c.getSource(), + BlockPosArgumentType.getBlockPos(c, "center position"), + EntitySummonArgumentType.getEntitySummon(c, "mob").toString() + )))); + dispatcher.register(command); + } + + private static int perimeterDiagnose(ServerCommandSource source, BlockPos pos, String mobId) + { + CompoundTag nbttagcompound = new CompoundTag(); + MobEntity entityliving = null; + if (mobId != null) + { + nbttagcompound.putString("id", mobId); + entityliving = (MobEntity) EntityType.loadEntityWithPassengers(nbttagcompound, source.getWorld(), (entity_1x) -> { + entity_1x.setPositionAndAngles(pos.getX(), pos.getY(), pos.getZ(), entity_1x.yaw, entity_1x.pitch); + return !source.getWorld().method_18768(entity_1x) ? null : entity_1x; + }); + if (entityliving == null) + { + Messenger.m(source, "r Failed to spawn test entity"); + return 0; + } + } + PerimeterDiagnostics.Result res = PerimeterDiagnostics.countSpots(source.getWorld(), pos, entityliving); + + Messenger.m(source, "w Spawning spaces around ",Messenger.tp("c",pos), "w :"); + Messenger.m(source, "w potential in-liquid: ","wb "+res.liquid); + Messenger.m(source, "w potential on-ground: ","wb "+res.ground); + if (entityliving != null) + { + Messenger.m(source, "w ", entityliving.getDisplayName() ,"w : ","wb "+res.specific); + res.samples.forEach(bp -> Messenger.m(source, "w ", Messenger.tp("c", bp))); + entityliving.remove(); + } + return 1; + } +} diff --git a/src/main/java/carpet/commands/PlayerCommand.java b/src/main/java/carpet/commands/PlayerCommand.java new file mode 100644 index 00000000..4901d1b1 --- /dev/null +++ b/src/main/java/carpet/commands/PlayerCommand.java @@ -0,0 +1,477 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.fakes.ServerPlayerEntityInterface; +import carpet.patches.EntityPlayerMPFake; +import carpet.utils.Messenger; +import com.google.common.collect.Sets; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.command.arguments.DimensionArgumentType; +import net.minecraft.command.arguments.RotationArgumentType; +import net.minecraft.command.arguments.Vec3ArgumentType; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameMode; +import net.minecraft.world.dimension.DimensionType; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class PlayerCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("player"). + requires((player) -> CarpetSettings.getBool("commandPlayer")). + then(argument("player", word()). + suggests( (c, b) -> suggestMatching(getPlayers(c.getSource()), b)). + then(literal("stop"). + executes(PlayerCommand::stop)). + then(literal("use"). + executes(PlayerCommand::useOnce). + then(literal("once"). + executes( PlayerCommand::useOnce)). + then(literal("continuous"). + executes( PlayerCommand::useContinuous)). + then(literal("interval"). + then(argument("ticks",integer(2)). + executes( PlayerCommand::useInterval)))). + then(literal("jump"). + executes( PlayerCommand::jumpOnce). + then(literal("once"). + executes( PlayerCommand::jumpOnce)). + then(literal("continuous"). + executes( PlayerCommand::jumpContinuous)). + then(literal("interval"). + then(argument("ticks",integer(2)). + executes( PlayerCommand::jumpInterval )))). + then(literal("attack"). + executes(PlayerCommand::attackOnce). + then(literal("once"). + executes(PlayerCommand::attackOnce)). + then(literal("continuous"). + executes(PlayerCommand::attackContinuous)). + then(literal("interval"). + then(argument("ticks",integer(2)). + executes(PlayerCommand::attackInterval)))). + then(literal("drop"). + executes(PlayerCommand::dropItem)). + then(literal("swapHands"). + executes(PlayerCommand::swapHands)). + then(literal("kill"). + executes(PlayerCommand::kill)). + then(literal("shadow"). + executes(PlayerCommand::shadow)). + then(literal("mount"). + executes(PlayerCommand::mount)). + then(literal("dismount"). + executes(PlayerCommand::dismount)). + then(literal("sneak"). + executes(PlayerCommand::sneak)). + then(literal("sprint"). + executes(PlayerCommand::sprint)). + then(literal("look"). + then(literal("north"). + executes(PlayerCommand::lookNorth)). + then(literal("south"). + executes(PlayerCommand::lookSouth)). + then(literal("east"). + executes(PlayerCommand::lookEast)). + then(literal("west"). + executes(PlayerCommand::lookWest)). + then(literal("up"). + executes(PlayerCommand::lookUp)). + then(literal("down"). + executes(PlayerCommand::lookDown)). + then(argument("direction", RotationArgumentType.create()). + executes(PlayerCommand::lookAround))). + then(literal("turn"). + then(literal("left"). + executes(PlayerCommand::turnLeft)). + then(literal("right"). + executes(PlayerCommand::turnRight)). + then(literal("back"). + executes(PlayerCommand::turnBack)). + then(argument("direction",RotationArgumentType.create()). + executes(PlayerCommand::turn))). + then(literal("move"). + then(literal("forward"). + executes(PlayerCommand::moveForward)). + then(literal("backward"). + executes(PlayerCommand::moveBackward)). + then(literal("left"). + executes(PlayerCommand::strafeLeft)). + then(literal("right"). + executes(PlayerCommand::strafeRight))). + then(literal("spawn"). + executes(PlayerCommand::spawn). + then(literal("at"). + then(argument("position", Vec3ArgumentType.create()). + executes(PlayerCommand::spawn). + then(literal("facing"). + then(argument("direction",RotationArgumentType.create()). + executes(PlayerCommand::spawn). + then(literal("in"). + then(argument("dimension", DimensionArgumentType.create()). + executes(PlayerCommand::spawn))))))))); + + dispatcher.register(literalargumentbuilder); + } + private static Collection getPlayers(ServerCommandSource source) + { + Set players = Sets.newLinkedHashSet( Arrays.asList("Steve","Alex")); + players.addAll(source.getPlayerNames()); + return players; + } + + private static ServerPlayerEntity getPlayer(CommandContext context) + { + String playerName = getString(context,"player"); + MinecraftServer server = context.getSource().getMinecraftServer(); + return server.getPlayerManager().getPlayer(playerName); + } + + private static boolean cantSpawn(CommandContext context) + { + String playerName = getString(context,"player"); + PlayerEntity player = context.getSource().getMinecraftServer().getPlayerManager().getPlayer(playerName); + if (player != null) + { + Messenger.m(context.getSource(), "r Player ", "rb "+playerName, "r is already logged on"); + return true; + } + return false; + } + + private static boolean cantManipulate(CommandContext context) + { + PlayerEntity player = getPlayer(context); + if (player == null) + { + Messenger.m(context.getSource(),"r Can only manipulate existing players"); + return true; + } + PlayerEntity sendingPlayer; + try + { + sendingPlayer = context.getSource().getPlayer(); + } + catch (CommandSyntaxException e) + { + return false; + } + + if (!(context.getSource().getMinecraftServer().getPlayerManager().isOperator(sendingPlayer.getGameProfile()))) + { + if (!(sendingPlayer == player || player instanceof EntityPlayerMPFake)) + { + Messenger.m(context.getSource(),"r Non OP players can't control other real players"); + return true; + } + } + return false; + } + + private static boolean cantShadow(CommandContext context) + { + if (cantManipulate(context)) + return true; + PlayerEntity player = getPlayer(context); + if (player instanceof EntityPlayerMPFake) + { + Messenger.m(context.getSource(), "r Only real players can be shadowed"); + return true; + } + return false; + } + + private static boolean cantReMove(CommandContext context) + { + if (cantManipulate(context)) + return true; + PlayerEntity player = getPlayer(context); + if (!(player instanceof EntityPlayerMPFake)) + { + Messenger.m(context.getSource(), "r Only fake players can be moved or killed"); + return true; + } + return false; + } + + private static int stop(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().stop(); + return 1; + } + private static int useOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().useOnce(); + return 1; + } + private static int useContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setUseForever(); + return 1; + } + private static int useInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setUse(ticks, 0); + return 1; + } + private static int jumpOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().jumpOnce(); + return 1; + } + private static int jumpContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setJumpForever(); + return 1; + } + private static int jumpInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setJump(ticks, 0); + return 1; + } + private static int attackOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().attackOnce(); + //((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().resetBlockRemoving(true); + + return 1; + } + private static int attackContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setAttackForever(); + return 1; + } + private static int attackInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setAttack(ticks, 0); + return 1; + } + private static int dropItem(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().dropItem(); + return 1; + } + private static int swapHands(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().swapHands(); + return 1; + } + private static int kill(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).kill(); + return 1; + } + private static int shadow(CommandContext context) + { + if (cantShadow(context)) return 0; + EntityPlayerMPFake.createShadow( + context.getSource().getMinecraftServer(), + getPlayer(context)); + return 1; + } + private static int mount(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().mount(); + return 1; + } + private static int dismount(CommandContext context) + { + if (cantManipulate(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().dismount(); + return 1; + } + private static int sneak(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setSneaking(true); + return 1; + } + private static int sprint(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setSprinting(true); + return 1; + } + private static int lookNorth(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("north"); + return 1; + } + private static int lookSouth(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("south"); + return 1; + } + private static int lookEast(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("east"); + return 1; + } + private static int lookWest(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("west"); + return 1; + } + private static int lookUp(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("up"); + return 1; + } + private static int lookDown(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look("down"); + return 1; + } + private static int lookAround(CommandContext context) + { + if (cantReMove(context)) return 0; + Vec2f vec2f = RotationArgumentType.getRotation(context, "direction").toAbsoluteRotation(context.getSource()); + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().look(vec2f.y,vec2f.x); + return 1; + } + private static int turnRight(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().turn("right"); + return 1; + } + private static int turnLeft(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().turn("left"); + return 1; + } + private static int turnBack(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().turn("back"); + return 1; + } + private static int turn(CommandContext context) + { + if (cantReMove(context)) return 0; + Vec2f vec2f = RotationArgumentType.getRotation(context, "direction").toAbsoluteRotation(context.getSource()); + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().turn(vec2f.y,vec2f.x); + return 1; + } + private static int moveForward(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setForward(1.0F); + return 1; + } + private static int moveBackward(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setForward(-1.0F); + return 1; + } + private static int strafeLeft(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setStrafing(-1.0F); + return 1; + } + private static int strafeRight(CommandContext context) + { + if (cantReMove(context)) return 0; + ((ServerPlayerEntityInterface)getPlayer(context)).getActionPack().setStrafing(1.0F); + return 1; + } + private static int spawn(CommandContext context) throws CommandSyntaxException + { + if (cantSpawn(context)) return 0; + Vec3d pos; + Vec2f facing; + DimensionType dim; + try + { + pos = Vec3ArgumentType.getVec3(context, "position"); + } + catch (IllegalArgumentException e) + { + pos = context.getSource().getPosition(); + } + try + { + facing = RotationArgumentType.getRotation(context, "direction").toAbsoluteRotation(context.getSource()); + } + catch (IllegalArgumentException e) + { + facing = context.getSource().getRotation(); + } + try + { + dim = DimensionArgumentType.getDimensionArgument(context, "dimension"); + } + catch (IllegalArgumentException e) + { + dim = context.getSource().getWorld().dimension.getType(); + } + GameMode mode = GameMode.CREATIVE; + try + { + ServerPlayerEntity player = context.getSource().getPlayer(); + mode = player.interactionManager.getGameMode(); + } + catch (CommandSyntaxException ignored) + { + } + PlayerEntity p = EntityPlayerMPFake.createFake( + getString(context,"player"), + context.getSource().getMinecraftServer(), + pos.x, pos.y, pos.z, + facing.y, facing.x, + dim, + mode); + if (p == null) + { + Messenger.m(context.getSource(), "rb Player "+getString(context,"player")+" doesn't exist " + + "and cannot spawn in online mode. Turn the server offline to spawn non-existing players"); + } + return 1; + } +} diff --git a/src/main/java/carpet/commands/SpawnCommand.java b/src/main/java/carpet/commands/SpawnCommand.java new file mode 100644 index 00000000..3c46ae40 --- /dev/null +++ b/src/main/java/carpet/commands/SpawnCommand.java @@ -0,0 +1,261 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.helpers.HopperCounter; +import carpet.helpers.TickSpeed; +import carpet.utils.Messenger; +import carpet.utils.SpawnReporter; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.command.arguments.BlockPosArgumentType; +import net.minecraft.command.arguments.DimensionArgumentType; +import net.minecraft.entity.EntityCategory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.DyeColor; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.dimension.DimensionType; + +import java.util.Arrays; + +import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.string; +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandSource.suggestMatching; + + +public class SpawnCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("spawn"). + requires((player) -> CarpetSettings.getBool("commandSpawn")); + + literalargumentbuilder. + then(literal("list"). + then(argument("pos", BlockPosArgumentType.create()). + executes( (c) -> listSpawns(c.getSource(), BlockPosArgumentType.getBlockPos(c, "pos"))))). + then(literal("tracking"). + executes( (c) -> printTrackingReport(c.getSource())). + then(literal("start"). + executes( (c) -> startTracking(c.getSource(), null, null)). + then(argument("from", BlockPosArgumentType.create()). + then(argument("to", BlockPosArgumentType.create()). + executes( (c) -> startTracking( + c.getSource(), + BlockPosArgumentType.getBlockPos(c, "from"), + BlockPosArgumentType.getBlockPos(c, "to")))))). + then(literal("stop"). + executes( (c) -> stopTracking(c.getSource()))). + then(argument("type", word()). + suggests( (c, b) -> suggestMatching(SpawnReporter.mob_groups,b)). + executes( (c) -> recentSpawnsForType(c.getSource(), getString(c, "type"))))). + then(literal("test"). + executes( (c)-> runTest(c.getSource(), 72000, null)). + then(argument("ticks", integer(10,720000)). + executes( (c)-> runTest( + c.getSource(), + getInteger(c, "ticks"), + null)). + then(argument("counter", word()). + suggests( (c, b) -> suggestMatching(Arrays.stream(DyeColor.values()).map(DyeColor::toString),b)). + executes((c)-> runTest( + c.getSource(), + getInteger(c, "ticks"), + getString(c, "counter")))))). + then(literal("mocking"). + then(argument("to do or not to do?", BoolArgumentType.bool()). + executes( (c) -> toggleMocking(c.getSource(), BoolArgumentType.getBool(c, "to do or not to do?"))))). + then(literal("rates"). + executes( (c) -> generalMobcaps(c.getSource())). + then(literal("reset"). + executes( (c) -> resetSpawnRates(c.getSource()))). + then(argument("type", word()). + suggests( (c, b) -> suggestMatching(SpawnReporter.mob_groups,b)). + then(argument("rounds", integer(0)). + suggests( (c, b) -> suggestMatching(new String[]{"1"},b)). + executes( (c) -> setSpawnRates( + c.getSource(), + getString(c, "type"), + getInteger(c, "rounds")))))). + then(literal("mobcaps"). + executes( (c) -> generalMobcaps(c.getSource())). + then(literal("set"). + then(argument("cap (hostile)", integer(1,1400)). + executes( (c) -> setMobcaps(c.getSource(), getInteger(c, "cap (hostile)"))))). + then(argument("dimension", DimensionArgumentType.create()). + executes( (c)-> mobcapsForDimension(c.getSource(), DimensionArgumentType.getDimensionArgument(c, "dimension"))))). + then(literal("entities"). + executes( (c) -> generalMobcaps(c.getSource()) ). + then(argument("type", string()). + suggests( (c, b)->suggestMatching(SpawnReporter.mob_groups, b)). + executes( (c) -> listEntitiesOfType(c.getSource(), getString(c, "type"))))); + + dispatcher.register(literalargumentbuilder); + } + + private static int listSpawns(ServerCommandSource source, BlockPos pos) + { + Messenger.send(source, SpawnReporter.report(pos, source.getWorld())); + return 1; + } + + private static int printTrackingReport(ServerCommandSource source) + { + Messenger.send(source, SpawnReporter.tracking_report(source.getWorld())); + return 1; + } + + private static int startTracking(ServerCommandSource source, BlockPos a, BlockPos b) + { + if (SpawnReporter.track_spawns != 0L) + { + Messenger.m(source, "r You are already tracking spawning."); + return 0; + } + BlockPos lsl = null; + BlockPos usl = null; + if (a != null && b != null) + { + lsl = new BlockPos( + Math.min(a.getX(), b.getX()), + Math.min(a.getY(), b.getY()), + Math.min(a.getZ(), b.getZ()) ); + usl = new BlockPos( + Math.max(a.getX(), b.getX()), + Math.max(a.getY(), b.getY()), + Math.max(a.getZ(), b.getZ()) ); + } + SpawnReporter.reset_spawn_stats(false); + SpawnReporter.track_spawns = (long) source.getMinecraftServer().getTicks(); + SpawnReporter.lower_spawning_limit = lsl; + SpawnReporter.upper_spawning_limit = usl; + Messenger.m(source, "gi Spawning tracking started."); + return 1; + } + + private static int stopTracking(ServerCommandSource source) + { + Messenger.send(source, SpawnReporter.tracking_report(source.getWorld())); + SpawnReporter.reset_spawn_stats(false); + SpawnReporter.track_spawns = 0L; + SpawnReporter.lower_spawning_limit = null; + SpawnReporter.upper_spawning_limit = null; + Messenger.m(source, "gi Spawning tracking stopped."); + return 1; + } + + private static int recentSpawnsForType(ServerCommandSource source, String mob_type) + { + if (!Arrays.asList(SpawnReporter.mob_groups).contains(mob_type)) + { + Messenger.m(source, "r Wrong mob type: "+mob_type); + return 0; + } + Messenger.send(source, SpawnReporter.recent_spawns(source.getWorld(), mob_type)); + return 1; + } + + private static int runTest(ServerCommandSource source, int ticks, String counter) + { + //stop tracking + SpawnReporter.reset_spawn_stats(false); + //start tracking + SpawnReporter.track_spawns = (long) source.getMinecraftServer().getTicks(); + //counter reset + if (counter == null) + { + HopperCounter.resetAll(source.getMinecraftServer()); + } + else + { + HopperCounter hCounter = HopperCounter.getCounter(counter); + if (hCounter != null) + hCounter.reset(source.getMinecraftServer()); + } + + + // tick warp 0 + TickSpeed.tickrate_advance(null, 0, null, null); + // tick warp given player + ServerCommandSource csource = null; + PlayerEntity player = null; + try + { + player = source.getPlayer(); + csource = source; + } + catch (CommandSyntaxException ignored) + { + } + TickSpeed.tickrate_advance(player, ticks, null, csource); + Messenger.m(source, String.format("gi Started spawn test for %d ticks", ticks)); + return 1; + } + + private static int toggleMocking(ServerCommandSource source, boolean domock) + { + if (domock) + { + SpawnReporter.initialize_mocking(); + Messenger.m(source, "gi Mock spawns started, Spawn statistics reset"); + } + else + { + SpawnReporter.stop_mocking(); + Messenger.m(source, "gi Normal mob spawning, Spawn statistics reset"); + } + return 1; + } + + private static int generalMobcaps(ServerCommandSource source) + { + Messenger.send(source, SpawnReporter.printMobcapsForDimension(source.getWorld().getDimension().getType().getRawId())); + return 1; + } + + private static int resetSpawnRates(ServerCommandSource source) + { + for (String s: SpawnReporter.spawn_tries.keySet()) + { + SpawnReporter.spawn_tries.put(s,1); + } + Messenger.m(source, "gi Spawn rates brought to 1 round per tick for all groups."); + + return 1; + } + + private static int setSpawnRates(ServerCommandSource source, String mobtype, int rounds) + { + String code = SpawnReporter.get_creature_code_from_string(mobtype); + SpawnReporter.spawn_tries.put(code, rounds); + Messenger.m(source, "gi "+mobtype+" mobs will now spawn "+rounds+" times per tick"); + return 1; + } + + private static int setMobcaps(ServerCommandSource source, int hostile_cap) + { + double desired_ratio = (double)hostile_cap/ EntityCategory.MONSTER.getSpawnCap(); + SpawnReporter.mobcap_exponent = 4.0*Math.log(desired_ratio)/Math.log(2.0); + Messenger.m(source, String.format("gi Mobcaps for hostile mobs changed to %d, other groups will follow", hostile_cap)); + return 1; + } + + private static int mobcapsForDimension(ServerCommandSource source, DimensionType dim) + { + Messenger.send(source, SpawnReporter.printMobcapsForDimension(dim.getRawId())); + return 1; + } + + private static int listEntitiesOfType(ServerCommandSource source, String mobtype) + { + Messenger.send(source, SpawnReporter.printEntitiesByType(mobtype, source.getWorld())); + return 1; + } +} diff --git a/src/main/java/carpet/commands/TickCommand.java b/src/main/java/carpet/commands/TickCommand.java new file mode 100644 index 00000000..dbe9759d --- /dev/null +++ b/src/main/java/carpet/commands/TickCommand.java @@ -0,0 +1,144 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.helpers.TickSpeed; +import carpet.utils.CarpetProfiler; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.server.command.ServerCommandSource; + +import static com.mojang.brigadier.arguments.FloatArgumentType.floatArg; +import static com.mojang.brigadier.arguments.FloatArgumentType.getFloat; +import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class TickCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("tick"). + requires((player) -> CarpetSettings.getBool("commandTick")). + then(literal("rate"). + executes((c) -> queryTps(c.getSource())). + then(argument("rate", floatArg(0.1F, 500.0F)). + suggests( (c, b) -> suggestMatching(new String[]{"20.0"},b)). + executes((c) -> setTps(c.getSource(), getFloat(c, "rate"))))). + then(literal("warp"). + executes( (c)-> setWarp(c.getSource(), 0, null)). + then(argument("ticks", integer(0,4000000)). + suggests( (c, b) -> suggestMatching(new String[]{"3600","72000"},b)). + executes((c) -> setWarp(c.getSource(), getInteger(c,"ticks"), null)). + then(argument("tail command", greedyString()). + executes( (c) -> setWarp( + c.getSource(), + getInteger(c,"ticks"), + getString(c, "tail command")))))). + then(literal("freeze").executes( (c)-> toggleFreeze(c.getSource()))). + then(literal("step"). + executes((c) -> step(1)). + then(argument("ticks", integer(1,72000)). + suggests( (c, b) -> suggestMatching(new String[]{"20"},b)). + executes((c) -> step(getInteger(c,"ticks"))))). + then(literal("superHot").executes( (c)-> toggleSuperHot(c.getSource()))). + then(literal("health"). + executes( (c) -> healthReport(c.getSource(), 100)). + then(argument("ticks", integer(20,24000)). + executes( (c) -> healthReport(c.getSource(), getInteger(c, "ticks"))))). + then(literal("entities"). + executes((c) -> healthEntities(c.getSource(), 100)). + then(argument("ticks", integer(20,24000)). + executes((c) -> healthEntities(c.getSource(), getInteger(c, "ticks"))))); + + + dispatcher.register(literalargumentbuilder); + } + + + private static int setTps(ServerCommandSource source, float tps) + { + TickSpeed.tickrate(tps); + queryTps(source); + return (int)tps; + } + + private static int queryTps(ServerCommandSource source) + { + Messenger.m(source, "w Current tps is: ",String.format("wb %.1f", TickSpeed.tickrate)); + return (int)TickSpeed.tickrate; + } + + private static int setWarp(ServerCommandSource source, int advance, String tail_command) + { + PlayerEntity player = null; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException ignored) + { + } + BaseComponent message = TickSpeed.tickrate_advance(player, advance, tail_command, source); + if (message != null) + { + source.sendFeedback(message, false); + } + return 1; + } + + private static int toggleFreeze(ServerCommandSource source) + { + TickSpeed.is_paused = !TickSpeed.is_paused; + if (TickSpeed.is_paused) + { + Messenger.m(source, "gi Game is paused"); + } + else + { + Messenger.m(source, "gi Game runs normally"); + } + return 1; + } + + private static int step(int advance) + { + TickSpeed.add_ticks_to_run_in_pause(advance); + return 1; + } + + private static int toggleSuperHot(ServerCommandSource source) + { + TickSpeed.is_superHot = !TickSpeed.is_superHot; + if (TickSpeed.is_superHot) + { + Messenger.m(source,"gi Superhot enabled"); + } + else + { + Messenger.m(source, "gi Superhot disabled"); + } + return 1; + } + + private static int healthReport(ServerCommandSource source, int ticks) + { + CarpetProfiler.prepare_tick_report(ticks); + return 1; + } + + private static int healthEntities(ServerCommandSource source, int ticks) + { + CarpetProfiler.prepare_entity_report(ticks); + return 1; + } + +} + diff --git a/src/main/java/carpet/fakes/ExperienceOrbInterface.java b/src/main/java/carpet/fakes/ExperienceOrbInterface.java new file mode 100644 index 00000000..00179d01 --- /dev/null +++ b/src/main/java/carpet/fakes/ExperienceOrbInterface.java @@ -0,0 +1,8 @@ +package carpet.fakes; + +public interface ExperienceOrbInterface +{ + int getCombineDelay(); + void setCombineDelay(int what); + void setAmount(int what); +} diff --git a/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java b/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java new file mode 100644 index 00000000..1d7aa428 --- /dev/null +++ b/src/main/java/carpet/fakes/ServerPlayerEntityInterface.java @@ -0,0 +1,8 @@ +package carpet.fakes; + +import carpet.helpers.EntityPlayerActionPack; + +public interface ServerPlayerEntityInterface +{ + EntityPlayerActionPack getActionPack(); +} diff --git a/src/main/java/carpet/helpers/BlockRotator.javax b/src/main/java/carpet/helpers/BlockRotator.javax new file mode 100644 index 00000000..edf7bb14 --- /dev/null +++ b/src/main/java/carpet/helpers/BlockRotator.javax @@ -0,0 +1,262 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.TrapdoorBlock; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.init.Blocks; +import net.minecraft.item.BlockItemUseContext; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.state.properties.ComparatorMode; +import net.minecraft.state.properties.Half; +import net.minecraft.state.properties.SlabType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +public class BlockRotator +{ + public static boolean flipBlockWithCactus(World worldIn, BlockPos pos, BlockState state, PlayerEntity playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) + { + if (!playerIn.abilities.allowModifyWorld || !CarpetSettings.getBool("flippinCactus") || !player_holds_cactus_mainhand(playerIn)) + { + return false; + } + return flip_block(worldIn, pos, state, playerIn, hand, facing, hitX, hitY, hitZ); + } + + public static BlockState alternativeBlockPlacement(Block block, BlockItemUseContext context)//World worldIn, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer) + { + //actual alternative block placement code + // + EnumFacing facing; + float hitX = context.getHitX(); + PlayerEntity placer = context.getPlayer(); + BlockPos pos = context.getPos(); + World world = context.getWorld(); + + if (block instanceof BlockGlazedTerracotta) + { + facing = EnumFacing.byIndex((int)hitX - 2); + if(facing == EnumFacing.UP || facing == EnumFacing.DOWN) + { + facing = placer.getHorizontalFacing().getOpposite(); + } + return block.getDefaultState().with(BlockHorizontal.HORIZONTAL_FACING, facing); + } + else if (block instanceof BlockObserver) + { + return block.getDefaultState() + .with(BlockDirectional.FACING, EnumFacing.byIndex((int)hitX - 2)) + .with(BlockObserver.POWERED, true); + } + else if (block instanceof BlockRedstoneRepeater) + { + facing = EnumFacing.byIndex((((int)hitX) % 10) - 2); + if(facing == EnumFacing.UP || facing == EnumFacing.DOWN) + { + facing = placer.getHorizontalFacing().getOpposite(); + } + return block.getDefaultState() + .with(BlockHorizontal.HORIZONTAL_FACING, facing) + .with(BlockRedstoneRepeater.DELAY, MathHelper.clamp((((int) hitX) / 10) + 1, 1, 4)) + .with(BlockRedstoneRepeater.LOCKED, Boolean.FALSE); + } + else if (block instanceof BlockTrapDoor) + { + return block.getDefaultState() + .with(TrapdoorBlock.HORIZONTAL_FACING, EnumFacing.byIndex((((int)hitX) % 10) - 2)) + .with(TrapdoorBlock.OPEN, Boolean.FALSE) + .with(TrapdoorBlock.HALF, (hitX > 10) ? Half.TOP : Half.BOTTOM) + .with(TrapdoorBlock.OPEN, world.isBlockPowered(pos)); + } + else if (block instanceof BlockRedstoneComparator) + { + facing = EnumFacing.byIndex((((int)hitX) % 10) - 2); + if((facing == EnumFacing.UP) || (facing == EnumFacing.DOWN)) + { + facing = placer.getHorizontalFacing().getOpposite(); + } + ComparatorMode m = (hitX > 10)?ComparatorMode.SUBTRACT: ComparatorMode.COMPARE; + return block.getDefaultState() + .with(BlockHorizontal.HORIZONTAL_FACING, facing) + .with(BlockRedstoneComparator.POWERED, Boolean.FALSE) + .with(BlockRedstoneComparator.MODE, m); + } + else if (block instanceof BlockDispenser) + { + return block.getDefaultState() + .with(BlockDispenser.FACING, EnumFacing.byIndex((int)hitX - 2)) + .with(BlockDispenser.TRIGGERED, Boolean.FALSE); + } + else if (block instanceof BlockPistonBase) + { + return block.getDefaultState() + .with(BlockDirectional.FACING,EnumFacing.byIndex((int)hitX - 2) ) + .with(BlockPistonBase.EXTENDED, Boolean.FALSE); + } + else if (block instanceof BlockStairs) + { + return block.getStateForPlacement(context)//worldIn, pos, facing, hitX, hitY, hitZ, meta, placer) + .with(BlockStairs.FACING, EnumFacing.byIndex((((int)hitX) % 10) - 2)) + .with(BlockStairs.HALF, ( hitX > 10)?Half.TOP : Half.BOTTOM); + } + return null; + } + + public static ItemStack dispenserRotate(IBlockSource source, ItemStack stack) + { + EnumFacing sourceFace = source.getBlockState().get(BlockDispenser.FACING); + World world = source.getWorld(); + BlockPos blockpos = source.getBlockPos().offset(sourceFace); + BlockState iblockstate = world.getBlockState(blockpos); + Block block = iblockstate.getBlock(); + + // Block rotation for blocks that can be placed in all 6 rotations. + if(block instanceof BlockDirectional || block instanceof BlockDispenser) + { + EnumFacing face = iblockstate.get(BlockDirectional.FACING); + face = face.rotateAround(sourceFace.getAxis()); + if(sourceFace.getIndex() % 2 == 0) + { // Rotate twice more to make blocks always rotate clockwise relative to the dispenser + // when index is equal to zero. when index is equal to zero the dispenser is in the opposite direction. + face = face.rotateAround(sourceFace.getAxis()); + face = face.rotateAround(sourceFace.getAxis()); + } + world.setBlockState(blockpos, iblockstate.with(BlockDirectional.FACING, face), 3); + + } + else if(block instanceof BlockHorizontal) // Block rotation for blocks that can be placed in only 4 horizontal rotations. + { + EnumFacing face = iblockstate.get(BlockHorizontal.HORIZONTAL_FACING); + face = face.rotateAround(sourceFace.getAxis()); + if(sourceFace.getIndex() % 2 == 0) + { // same as above. + face = face.rotateAround(sourceFace.getAxis()); + face = face.rotateAround(sourceFace.getAxis()); + } + if(sourceFace.getIndex() <= 1) + { // Make sure to suppress rotation when index is lower then 2 as that will result in a faulty rotation for + // blocks that only can be placed horizontaly. + world.setBlockState(blockpos, iblockstate.with(BlockHorizontal.HORIZONTAL_FACING, face), 3); + } + } + // Send block update to the block that just have been rotated. + world.neighborChanged(blockpos, block, source.getBlockPos()); + + return stack; + } + + + + + public static boolean flip_block(World worldIn, BlockPos pos, BlockState state, PlayerEntity playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) + { + Block block = state.getBlock(); + if ( (block instanceof BlockGlazedTerracotta) || (block instanceof BlockRedstoneDiode) || (block instanceof BlockRailBase) || + (block instanceof BlockTrapDoor) || (block instanceof BlockLever) || (block instanceof BlockFenceGate)) + { + worldIn.setBlockState(pos, block.rotate(state, Rotation.CLOCKWISE_90), 2 | 1024); + } + else if ((block instanceof BlockObserver) || (block instanceof BlockEndRod)) + { + worldIn.setBlockState(pos, state.with(BlockDirectional.FACING, (EnumFacing)state.get(BlockDirectional.FACING).getOpposite()), 2 | 1024); + } + else if (block instanceof BlockDispenser) + { + worldIn.setBlockState(pos, state.with(BlockDispenser.FACING, state.get(BlockDispenser.FACING).getOpposite()), 2 | 1024); + } + else if (block instanceof BlockPistonBase) + { + if (!(state.get(BlockPistonBase.EXTENDED))) + worldIn.setBlockState(pos, state.with(BlockDirectional.FACING, state.get(BlockDirectional.FACING).getOpposite()), 2 | 1024); + } + else if (block instanceof BlockSlab) + { + if (!((BlockSlab) block).isFullCube(state)) + { + worldIn.setBlockState(pos, state.with(BlockSlab.TYPE, state.get(BlockSlab.TYPE) == SlabType.TOP ? SlabType.BOTTOM : SlabType.TOP), 2 | 1024); + } + } + else if (block instanceof BlockHopper) + { + if ((EnumFacing)state.get(BlockHopper.FACING) != EnumFacing.DOWN) + { + worldIn.setBlockState(pos, state.with(BlockHopper.FACING, state.get(BlockHopper.FACING).rotateY()), 2 | 1024); + } + } + else if (block instanceof BlockStairs) + { + //LOG.error(String.format("hit with facing: %s, at side %.1fX, X %.1fY, Y %.1fZ",facing, hitX, hitY, hitZ)); + if ((facing == EnumFacing.UP && hitY == 1.0f) || (facing == EnumFacing.DOWN && hitY == 0.0f)) + { + worldIn.setBlockState(pos, state.with(BlockStairs.HALF, state.get(BlockStairs.HALF) == Half.TOP ? Half.BOTTOM : Half.TOP ), 2 | 1024); + } + else + { + boolean turn_right; + if (facing == EnumFacing.NORTH) + { + turn_right = (hitX <= 0.5); + } + else if (facing == EnumFacing.SOUTH) + { + turn_right = !(hitX <= 0.5); + } + else if (facing == EnumFacing.EAST) + { + turn_right = (hitZ <= 0.5); + } + else if (facing == EnumFacing.WEST) + { + turn_right = !(hitZ <= 0.5); + } + else + { + return false; + } + if (turn_right) + { + worldIn.setBlockState(pos, block.rotate(state, Rotation.COUNTERCLOCKWISE_90), 2 | 1024); + } + else + { + worldIn.setBlockState(pos, block.rotate(state, Rotation.CLOCKWISE_90), 2 | 1024); + } + } + } + else + { + return false; + } + worldIn.markBlockRangeForRenderUpdate(pos, pos); + return true; + } + private static boolean player_holds_cactus_mainhand(PlayerEntity playerIn) + { + return (!playerIn.getHeldItemMainhand().isEmpty() + && playerIn.getHeldItemMainhand().getItem() instanceof ItemBlock && + ((ItemBlock) (playerIn.getHeldItemMainhand().getItem())).getBlock() == Blocks.CACTUS); + } + public static boolean flippinEligibility(Entity entity) + { + if (CarpetSettings.getBool("flippinCactus") + && (entity instanceof PlayerEntity)) + { + PlayerEntity player = (PlayerEntity)entity; + return (!player.getHeldItemOffhand().isEmpty() + && player.getHeldItemOffhand().getItem() instanceof ItemBlock && + ((ItemBlock) (player.getHeldItemOffhand().getItem())).getBlock() == Blocks.CACTUS); + } + return false; + } +} diff --git a/src/main/java/carpet/helpers/BlockSaplingHelper.java b/src/main/java/carpet/helpers/BlockSaplingHelper.java new file mode 100644 index 00000000..abe45c3f --- /dev/null +++ b/src/main/java/carpet/helpers/BlockSaplingHelper.java @@ -0,0 +1,22 @@ +package carpet.helpers; + +import net.minecraft.block.Material; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IWorld; + +public class BlockSaplingHelper +{ + // Added code for checking water for dead shrub rule + public static boolean hasWater(IWorld worldIn, BlockPos pos) + { + for (BlockPos blockpos$mutableblockpos : BlockPos.iterate(pos.add(-4, -4, -4), pos.add(4, 1, 4))) + { + if (worldIn.getBlockState(blockpos$mutableblockpos).getMaterial() == Material.WATER) + { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/carpet/helpers/CtrlQCrafting.javax b/src/main/java/carpet/helpers/CtrlQCrafting.javax new file mode 100644 index 00000000..d72b4bc9 --- /dev/null +++ b/src/main/java/carpet/helpers/CtrlQCrafting.javax @@ -0,0 +1,48 @@ +package carpet.helpers; + +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import java.util.List; + +public class CtrlQCrafting { + + public static ItemStack dropAllCrafting(EntityPlayer playerIn, int index, List invSlotParam) + { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot = invSlotParam.get(index); + + if (slot != null && slot.getHasStack()) + { + ItemStack itemstack1 = slot.getStack(); + itemstack = itemstack1.copy(); + EntityEquipmentSlot entityequipmentslot = EntityLiving.getSlotForItemStack(itemstack); + + if (index == 0) + { + playerIn.dropItem(itemstack, true); + + itemstack1.setCount(0); + + slot.onSlotChange(itemstack1, itemstack); + } + + if (itemstack.getCount() == itemstack1.getCount()) + { + return ItemStack.EMPTY; + } + + ItemStack itemstack2 = slot.onTake(playerIn, itemstack1); + + if (index == 0) + { + playerIn.dropItem(itemstack2, false); + } + } + + return itemstack; + } +} diff --git a/src/main/java/carpet/helpers/EntityPlayerActionPack.java b/src/main/java/carpet/helpers/EntityPlayerActionPack.java new file mode 100644 index 00000000..d52114f7 --- /dev/null +++ b/src/main/java/carpet/helpers/EntityPlayerActionPack.java @@ -0,0 +1,858 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import carpet.mixins.PlayerActionC2SPacketMixin; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.network.packet.PlayerActionC2SPacket; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; + +import java.util.List; + +public class EntityPlayerActionPack +{ + private ServerPlayerEntity player; + + private boolean doesAttack; + private int attackInterval; + private int attackCooldown; + + private boolean doesUse; + private int useInterval; + private int useCooldown; + + private boolean doesJump; + private int jumpInterval; + private int jumpCooldown; + + private BlockPos currentBlock = new BlockPos(-1,-1,-1); + private int blockHitDelay; + private boolean isHittingBlock; + private float curBlockDamageMP; + + private boolean sneaking; + private boolean sprinting; + private float forward; + private float strafing; + + public EntityPlayerActionPack(ServerPlayerEntity playerIn) + { + player = playerIn; + stop(); + } + public void copyFrom(EntityPlayerActionPack other) + { + doesAttack = other.doesAttack; + attackInterval = other.attackInterval; + attackCooldown = other.attackCooldown; + + doesUse = other.doesUse; + useInterval = other.useInterval; + useCooldown = other.useCooldown; + + doesJump = other.doesJump; + jumpInterval = other.jumpInterval; + jumpCooldown = other.jumpCooldown; + + + currentBlock = other.currentBlock; + blockHitDelay = other.blockHitDelay; + isHittingBlock = other.isHittingBlock; + curBlockDamageMP = other.curBlockDamageMP; + + sneaking = other.sneaking; + sprinting = other.sprinting; + forward = other.forward; + strafing = other.strafing; + } + + public EntityPlayerActionPack setAttack(int interval, int offset) + { + if (interval < 1) + { + CarpetSettings.LOG.error("attack interval needs to be positive"); + return this; + } + this.doesAttack = true; + this.attackInterval = interval; + this.attackCooldown = interval+offset; + return this; + } + public EntityPlayerActionPack setUse(int interval, int offset) + { + if (interval < 1) + { + CarpetSettings.LOG.error("use interval needs to be positive"); + return this; + } + this.doesUse = true; + this.useInterval = interval; + this.useCooldown = interval+offset; + return this; + } + public EntityPlayerActionPack setUseForever() + { + this.doesUse = true; + this.useInterval = 1; + this.useCooldown = 1; + return this; + } + public EntityPlayerActionPack setAttackForever() + { + this.doesAttack = true; + this.attackInterval = 1; + this.attackCooldown = 1; + return this; + } + public EntityPlayerActionPack setJump(int interval, int offset) + { + if (interval < 1) + { + CarpetSettings.LOG.error("jump interval needs to be positive"); + return this; + } + this.doesJump = true; + this.jumpInterval = interval; + this.jumpCooldown = interval+offset; + return this; + } + public EntityPlayerActionPack setJumpForever() + { + this.doesJump = true; + this.jumpInterval = 1; + this.jumpCooldown = 1; + return this; + } + public EntityPlayerActionPack setSneaking(boolean doSneak) + { + sneaking = doSneak; + player.setSneaking(doSneak); + if (sprinting && sneaking) + setSprinting(false); + return this; + } + public EntityPlayerActionPack setSprinting(boolean doSprint) + { + sprinting = doSprint; + player.setSprinting(doSprint); + if (sneaking && sprinting) + setSneaking(false); + return this; + } + + public EntityPlayerActionPack setForward(float value) + { + forward = value; + return this; + } + public EntityPlayerActionPack setStrafing(float value) + { + strafing = value; + return this; + } + public boolean look(String where) + { + switch (where) + { + case "north": + look(180.0f,0.0F); return true; + case "south": + look (0.0F, 0.0F); return true; + case "east": + look(-90.0F, 0.0F); return true; + case "west": + look(90.0F, 0.0F); return true; + case "up": + look(player.yaw, -90.0F); return true; + case "down": + look(player.yaw, 90.0F); return true; + case "left": + case "right": + return turn(where); + } + return false; + } + public EntityPlayerActionPack look(float yaw, float pitch) + { + player.setPositionAndAngles(player.x, player.y, player.z, yaw, MathHelper.clamp(pitch,-90.0F, 90.0F)); + return this; + } + public boolean turn(String where) + { + switch (where) + { + case "left": + turn(-90.0F,0.0F); return true; + case "right": + turn (90.0F, 0.0F); return true; + case "back": + turn (180.0F, 0.0F); return true; + case "up": + turn(0.0F, -5.0F); return true; + case "down": + turn(0.0F, 5.0F); return true; + } + return false; + } + public EntityPlayerActionPack turn(float yaw, float pitch) + { + player.setPositionAndAngles(player.x, player.y, player.z, player.yaw+yaw,MathHelper.clamp(player.pitch+pitch,-90.0F, 90.0F)); + return this; + } + + + + public EntityPlayerActionPack stop() + { + this.doesUse = false; + this.doesAttack = false; + this.doesJump = false; + //resetBlockRemoving(false); + setSneaking(false); + setSprinting(false); + forward = 0.0F; + strafing = 0.0F; + player.setJumping(false); + + + return this; + } + + public void swapHands() + { + PlayerActionC2SPacket packet = new PlayerActionC2SPacket(); + ((PlayerActionC2SPacketMixin)packet).setAction(PlayerActionC2SPacket.Action.SWAP_HELD_ITEMS); + player.networkHandler.onPlayerAction(packet); + } + + public void dropItem() + { + PlayerActionC2SPacket packet = new PlayerActionC2SPacket(); + ((PlayerActionC2SPacketMixin)packet).setAction(PlayerActionC2SPacket.Action.DROP_ITEM); + player.networkHandler.onPlayerAction(packet); + } + public void mount() + { + List entities = player.world.getEntities(player,player.getBoundingBox().expand(3.0D, 1.0D, 3.0D)); + if (entities.size()==0) + { + return; + } + Entity closest = entities.get(0); + double distance = player.squaredDistanceTo(closest); + for (Entity e: entities) + { + double dd = player.squaredDistanceTo(e); + if (dd 1) + { + resetBlockRemoving(true); + } + break; + } + } + */ + } + + public boolean useOnce() + { + /*needs rework due to 1.14 changes + BlockHitResult raytraceresult = mouseOver(); + for (EnumHand enumhand : EnumHand.values()) + { + ItemStack itemstack = this.player.getHeldItem(enumhand); + if (raytraceresult != null) + { + switch (raytraceresult.type) + { + case ENTITY: + Entity target = raytraceresult.entity; + Vec3d vec3d = new Vec3d(raytraceresult.hitVec.x - target.posX, raytraceresult.hitVec.y - target.posY, raytraceresult.hitVec.z - target.posZ); + + boolean flag = player.canEntityBeSeen(target); + double d0 = 36.0D; + + if (!flag) + { + d0 = 9.0D; + } + + if (player.getDistanceSq(target) < d0) + { + EnumActionResult res = player.interactOn(target,enumhand); + if (res == EnumActionResult.SUCCESS) + { + return true; + } + res = target.applyPlayerInteraction(player, vec3d, enumhand); + if (res == EnumActionResult.SUCCESS) + { + return true; + } + } + break; + case MISS: + break; + case BLOCK: + BlockPos blockpos = raytraceresult.getBlockPos(); + + if (player.getEntityWorld().getBlockState(blockpos).getMaterial() != Material.AIR) + { + float x = (float) raytraceresult.hitVec.x; + float y = (float) raytraceresult.hitVec.y; + float z = (float) raytraceresult.hitVec.z; + + EnumActionResult res = player.interactionManager.processRightClickBlock(player, player.getEntityWorld(), itemstack, enumhand, blockpos, raytraceresult.sideHit, x, y, z); + if (res == EnumActionResult.SUCCESS) + { + this.player.swingArm(enumhand); + return true; + } + } + } + } + EnumActionResult res = player.interactionManager.processRightClick(player,player.getEntityWorld(),itemstack,enumhand); + if (res == EnumActionResult.SUCCESS) + { + return true; + } + } + */ + return false; + } + + /*needs rework due to 1.14 changes + private BlockHitResult rayTraceBlocks(double blockReachDistance) + { + Vec3d eyeVec = player.getEyePosition(1.0F); + Vec3d lookVec = player.getLook(1.0F); + Vec3d pointVec = eyeVec.add(lookVec.x * blockReachDistance, lookVec.y * blockReachDistance, lookVec.z * blockReachDistance); + return player.getEntityWorld().rayTraceBlocks(eyeVec, pointVec, RayTraceFluidMode.NEVER, false, true); + } + + private BlockHitResult mouseOver() + { + World world = player.getEntityWorld(); + BlockHitResult result = null; + + Entity pointedEntity = null; + double reach = player.isCreative() ? 5.0D : 4.5D; + result = rayTraceBlocks(reach); + Vec3d eyeVec = player.getEyePosition(1.0F); + boolean flag = !player.isCreative(); + if (player.isCreative()) reach = 6.0D; + double extendedReach = reach; + + if (result != null) + { + extendedReach = result.hitVec.distanceTo(eyeVec); + if (world.getBlockState(result.getBlockPos()).getMaterial() == Material.AIR) + result = null; + } + + Vec3d lookVec = player.getLook(1.0F); + Vec3d pointVec = eyeVec.add(lookVec.x * reach, lookVec.y * reach, lookVec.z * reach); + Vec3d hitVec = null; + List list = world.getEntitiesInAABBexcluding( + player, + player.getBoundingBox().expand(lookVec.x * reach, lookVec.y * reach, lookVec.z * reach).grow(1.0D, 1.0D, 1.0D), + EntitySelectors.NOT_SPECTATING.and((Predicate) e -> e != null && e.canBeCollidedWith()) + ); + double d2 = extendedReach; + + for (int j = 0; j < list.size(); ++j) + { + Entity entity1 = list.get(j); + BoundingBox axisalignedbb = entity1.getBoundingBox().grow((double) entity1.getCollisionBorderSize()); + BlockHitResult raytraceresult = axisalignedbb.calculateIntercept(eyeVec, pointVec); + + if (axisalignedbb.contains(eyeVec)) + { + if (d2 >= 0.0D) + { + pointedEntity = entity1; + hitVec = raytraceresult == null ? eyeVec : raytraceresult.hitVec; + d2 = 0.0D; + } + } + else if (raytraceresult != null) + { + double d3 = eyeVec.distanceTo(raytraceresult.hitVec); + + if (d3 < d2 || d2 == 0.0D) + { + if (entity1.getLowestRidingEntity() == player.getLowestRidingEntity()) + { + if (d2 == 0.0D) + { + pointedEntity = entity1; + hitVec = raytraceresult.hitVec; + } + } + else + { + pointedEntity = entity1; + hitVec = raytraceresult.hitVec; + d2 = d3; + } + } + } + } + + if (pointedEntity != null && flag && eyeVec.distanceTo(hitVec) > 3.0D) + { + pointedEntity = null; + result = new BlockHitResult(BlockHitResult.Type.MISS, hitVec, (Direction) null, new BlockPos(hitVec)); + } + + if (pointedEntity != null && (d2 < extendedReach || result == null)) + { + result = new BlockHitResult(pointedEntity, hitVec); + } + + return result; + } + + public boolean clickBlock(BlockPos loc, Direction face) // don't call this one + { + World world = player.getEntityWorld(); + if (player.interactionManager.getGameType()!=GameMode.ADVENTURE) + { + if (player.interactionManager.getGameType() == GameMode.SPECTATOR) + { + return false; + } + + if (!player.abilities.allowEdit) + { + ItemStack itemstack = player.getHeldItemMainhand(); + + if (itemstack.isEmpty()) + { + return false; + } + + BlockWorldState blockworldstate = new BlockWorldState(world, loc, false); + + if (!itemstack.canDestroy(world.getTags(), blockworldstate)) + { + return false; + } + } + } + + if (!world.getWorldBorder().contains(loc)) + { + return false; + } + else + { + if (player.interactionManager.getGameType()==GameMode.CREATIVE) + { + player.connection.processPlayerDigging(new CPacketPlayerDigging(loc, face, CPacketPlayerDigging.Action.START_DESTROY_BLOCK)); + clickBlockCreative(world, loc, face); + this.blockHitDelay = 5; + } + else if (!this.isHittingBlock || !(currentBlock.equals(loc))) + { + if (this.isHittingBlock) + { + player.connection.processPlayerDigging(new CPacketPlayerDigging(this.currentBlock, face, CPacketPlayerDigging.Action.ABORT_DESTROY_BLOCK)); + } + + BlockState iblockstate = world.getBlockState(loc); + player.connection.processPlayerDigging(new CPacketPlayerDigging(loc, face, CPacketPlayerDigging.Action.START_DESTROY_BLOCK)); + boolean flag = iblockstate.getMaterial() != Material.AIR; + + if (flag && this.curBlockDamageMP == 0.0F) + { + iblockstate.getBlock().onBlockClicked(iblockstate, world, loc, player); + } + + if (flag && iblockstate.getPlayerRelativeBlockHardness(player, world, loc) >= 1.0F) + { + this.onPlayerDestroyBlock(loc); + } + else + { + this.isHittingBlock = true; + this.currentBlock = loc; + this.curBlockDamageMP = 0.0F; + world.sendBlockBreakProgress(player.getEntityId(), this.currentBlock, (int)(this.curBlockDamageMP * 10.0F) - 1); + } + } + + return true; + } + } + + private void clickBlockCreative(World world, BlockPos pos, Direction facing) + { + if (!world.extinguishFire(player, pos, facing)) + { + onPlayerDestroyBlock(pos); + } + } + + public boolean onPlayerDamageBlock(BlockPos posBlock, Direction directionFacing) //continue clicking - one to call + { + if (this.blockHitDelay > 0) + { + --this.blockHitDelay; + return true; + } + World world = player.getEntityWorld(); + if (player.interactionManager.getGameType()==GameMode.CREATIVE && world.getWorldBorder().contains(posBlock)) + { + this.blockHitDelay = 5; + player.connection.processPlayerDigging(new CPacketPlayerDigging(posBlock, directionFacing, CPacketPlayerDigging.Action.START_DESTROY_BLOCK)); + clickBlockCreative(world, posBlock, directionFacing); + return true; + } + else if (posBlock.equals(currentBlock)) + { + BlockState iblockstate = world.getBlockState(posBlock); + + if (iblockstate.getMaterial() == Material.AIR) + { + this.isHittingBlock = false; + return false; + } + else + { + this.curBlockDamageMP += iblockstate.getPlayerRelativeBlockHardness(player, world, posBlock); + + if (this.curBlockDamageMP >= 1.0F) + { + this.isHittingBlock = false; + player.connection.processPlayerDigging(new CPacketPlayerDigging(posBlock, directionFacing, CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK)); + this.onPlayerDestroyBlock(posBlock); + this.curBlockDamageMP = 0.0F; + this.blockHitDelay = 5; + } + //player.getEntityId() + //send to all, even the breaker + world.sendBlockBreakProgress(-1, this.currentBlock, (int)(this.curBlockDamageMP * 10.0F) - 1); + return true; + } + } + else + { + return this.clickBlock(posBlock, directionFacing); + } + } + + private boolean onPlayerDestroyBlock(BlockPos pos) + { + World world = player.getEntityWorld(); + if (player.interactionManager.getGameType()!=GameMode.ADVENTURE) + { + if (player.interactionManager.getGameType() == GameMode.SPECTATOR) + { + return false; + } + + if (player.abilities.allowEdit) + { + ItemStack itemstack = player.getHeldItemMainhand(); + + if (itemstack.isEmpty()) + { + return false; + } + + BlockWorldState blockworldstate = new BlockWorldState(world, pos, false); + + if (!itemstack.canDestroy(world.getTags(), blockworldstate)) + { + return false; + } + } + } + + if (player.interactionManager.getGameType()==GameMode.CREATIVE && !player.getHeldItemMainhand().isEmpty() && player.getHeldItemMainhand().getItem() instanceof ItemSword) + { + return false; + } + else + { + BlockState iblockstate = world.getBlockState(pos); + Block block = iblockstate.getBlock(); + + if ((block instanceof BlockCommandBlock || block instanceof BlockStructure) && !player.canUseCommandBlock()) + { + return false; + } + else if (iblockstate.getMaterial() == Material.AIR) + { + return false; + } + else + { + world.playEvent(2001, pos, Block.getStateId(iblockstate)); + block.onBlockHarvested(world, pos, iblockstate, player); + FluidState ifluidstate = world.getFluidState(pos); + boolean flag = world.setBlockState(pos, ifluidstate.getBlockState(), 11); + + if (flag) + { + block.onPlayerDestroy(world, pos, iblockstate); + } + + this.currentBlock = new BlockPos(this.currentBlock.getX(), -1, this.currentBlock.getZ()); + + if (!(player.interactionManager.getGameType()==GameMode.CREATIVE)) + { + ItemStack itemstack1 = player.getHeldItemMainhand(); + + if (!itemstack1.isEmpty()) + { + itemstack1.onBlockDestroyed(world, iblockstate, pos, player); + + if (itemstack1.isEmpty()) + { + player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY); + } + } + } + + return flag; + } + } + } + + public void resetBlockRemoving(boolean force) + { + if (this.isHittingBlock || force) + { + player.connection.processPlayerDigging(new CPacketPlayerDigging(this.currentBlock, Direction.DOWN, CPacketPlayerDigging.Action.ABORT_DESTROY_BLOCK)); + this.isHittingBlock = false; + this.curBlockDamageMP = 0.0F; + player.getEntityWorld().sendBlockBreakProgress(player.getEntityId(), this.currentBlock, -1); + player.resetCooldown(); + blockHitDelay = 0; + this.currentBlock = new BlockPos(-1,-1,-1); + } + } + + + /* + public EnumActionResult processRightClickBlock(EntityPlayerSP player, WorldClient worldIn, BlockPos stack, Direction pos, Vec3d facing, EnumHand vec) + { + this.syncCurrentPlayItem(); + ItemStack itemstack = player.getHeldItem(vec); + float f = (float)(facing.xCoord - (double)stack.getX()); + float f1 = (float)(facing.yCoord - (double)stack.getY()); + float f2 = (float)(facing.zCoord - (double)stack.getZ()); + boolean flag = false; + + if (!this.mc.world.getWorldBorder().contains(stack)) + { + return EnumActionResult.FAIL; + } + else + { + if (this.currentGameType != GameMode.SPECTATOR) + { + BlockState iblockstate = worldIn.getBlockState(stack); + + if ((!player.isSneaking() || player.getHeldItemMainhand().func_190926_b() && player.getHeldItemOffhand().func_190926_b()) && iblockstate.getBlock().onBlockActivated(worldIn, stack, iblockstate, player, vec, pos, f, f1, f2)) + { + flag = true; + } + + if (!flag && itemstack.getItem() instanceof ItemBlock) + { + ItemBlock itemblock = (ItemBlock)itemstack.getItem(); + + if (!itemblock.canPlaceBlockOnSide(worldIn, stack, pos, player, itemstack)) + { + return EnumActionResult.FAIL; + } + } + } + + this.connection.sendPacket(new CPacketPlayerTryUseItemOnBlock(stack, pos, vec, f, f1, f2)); + + if (!flag && this.currentGameType != GameMode.SPECTATOR) + { + if (itemstack.func_190926_b()) + { + return EnumActionResult.PASS; + } + else if (player.getCooldownTracker().hasCooldown(itemstack.getItem())) + { + return EnumActionResult.PASS; + } + else + { + if (itemstack.getItem() instanceof ItemBlock && !player.canUseCommandBlock()) + { + Block block = ((ItemBlock)itemstack.getItem()).getBlock(); + + if (block instanceof BlockCommandBlock || block instanceof BlockStructure) + { + return EnumActionResult.FAIL; + } + } + + if (this.currentGameType.isCreative()) + { + int i = itemstack.getMetadata(); + int j = itemstack.func_190916_E(); + EnumActionResult enumactionresult = itemstack.onItemUse(player, worldIn, stack, vec, pos, f, f1, f2); + itemstack.setItemDamage(i); + itemstack.func_190920_e(j); + return enumactionresult; + } + else + { + return itemstack.onItemUse(player, worldIn, stack, vec, pos, f, f1, f2); + } + } + } + else + { + return EnumActionResult.SUCCESS; + } + } + } + + public EnumActionResult processRightClick(EntityPlayer player, World worldIn, EnumHand stack) + { + if (this.currentGameType == GameMode.SPECTATOR) + { + return EnumActionResult.PASS; + } + else + { + this.syncCurrentPlayItem(); + this.connection.sendPacket(new CPacketPlayerTryUseItem(stack)); + ItemStack itemstack = player.getHeldItem(stack); + + if (player.getCooldownTracker().hasCooldown(itemstack.getItem())) + { + return EnumActionResult.PASS; + } + else + { + int i = itemstack.func_190916_E(); + ActionResult actionresult = itemstack.useItemRightClick(worldIn, player, stack); + ItemStack itemstack1 = actionresult.getResult(); + + if (itemstack1 != itemstack || itemstack1.func_190916_E() != i) + { + player.setHeldItem(stack, itemstack1); + } + + return actionresult.getType(); + } + } + } + + public EnumActionResult interactWithEntity(EntityPlayer player, Entity target, EnumHand heldItem) + { + this.syncCurrentPlayItem(); + this.connection.sendPacket(new CPacketUseEntity(target, heldItem)); + return this.currentGameType == GameMode.SPECTATOR ? EnumActionResult.PASS : player.func_190775_a(target, heldItem); + } + */ + + /* + * Handles right clicking an entity from the entities side, sends a packet to the server. + * + public EnumActionResult interactWithEntity(EntityPlayer player, Entity target, BlockHitResult raytrace, EnumHand heldItem) + { + this.syncCurrentPlayItem(); + Vec3d vec3d = new Vec3d(raytrace.hitVec.xCoord - target.posX, raytrace.hitVec.yCoord - target.posY, raytrace.hitVec.zCoord - target.posZ); + this.connection.sendPacket(new CPacketUseEntity(target, heldItem, vec3d)); + return this.currentGameType == GameMode.SPECTATOR ? EnumActionResult.PASS : target.applyPlayerInteraction(player, vec3d, heldItem); + } +*/ + +} diff --git a/src/main/java/carpet/helpers/FeatureGenerator.javax b/src/main/java/carpet/helpers/FeatureGenerator.javax new file mode 100644 index 00000000..23f6987a --- /dev/null +++ b/src/main/java/carpet/helpers/FeatureGenerator.javax @@ -0,0 +1,203 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import net.minecraft.init.Biomes; +import net.minecraft.init.Blocks; +import net.minecraft.structure.Structure; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.provider.BiomeProvider; +import net.minecraft.world.biome.provider.OverworldBiomeProvider; +import net.minecraft.world.biome.provider.OverworldBiomeProviderSettings; +import net.minecraft.world.gen.ChunkGeneratorOverworld; +import net.minecraft.world.gen.IChunkGenerator; +import net.minecraft.world.gen.OverworldGenSettings; +import net.minecraft.world.gen.feature.BlockBlobConfig; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import net.minecraft.world.gen.feature.Feature; +import net.minecraft.world.gen.feature.FeatureConfig; +import net.minecraft.world.gen.feature.IcebergConfig; +import net.minecraft.world.gen.feature.LakesConfig; +import net.minecraft.world.gen.feature.OceanRuinFeature; +import net.minecraft.world.gen.feature.SeaGrassConfig; +import net.minecraft.world.gen.feature.ShrubFeature; +import net.minecraft.world.gen.feature.TallGrassConfig; +import net.minecraft.world.gen.feature.TallGrassFeature; +import net.minecraft.world.gen.feature.structure.MineshaftConfig; +import net.minecraft.world.gen.feature.structure.MineshaftStructure; +import net.minecraft.world.gen.feature.structure.OceanRuinConfig; +import net.minecraft.structure.OceanRuinGenerator; +import net.minecraft.world.gen.feature.structure.ShipwreckConfig; + +import net.minecraft.structure.Structure; +import net.minecraft.world.gen.feature.structure.VillageConfig; +import net.minecraft.world.gen.feature.structure.VillagePieces; +import net.minecraft.world.gen.placement.CountConfig; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +public class FeatureGenerator +{ + @FunctionalInterface + private interface Thing + { + Boolean plop(World world, BlockPos pos); + } + private static Thing simplePlop(Feature feature) + { + return simplePlop(feature, FeatureConfig.DEFAULT); + } + private static Thing simplePlop(Feature feature, FeatureConfig config) + { + return (w, p) -> { + CarpetSettings.skipGenerationChecks=true; + boolean res = feature.generate(w, w.getChunkManager().getChunkGenerator(), w.random, p, config); + CarpetSettings.skipGenerationChecks=false; + return res; + }; + + } + + private static Thing spawnCustomStructure(Structure structure, FeatureConfig config) + { + return (w, p) -> { + IChunkGenerator chunkgen = new ChunkGeneratorOverworld(w, null, new OverworldGenSettings()) + { + @Nullable + @Override + public FeatureConfig getStructureConfig(Biome biomeIn, Structure structureIn) + { + return config; + } + + @Override + public BiomeProvider getBiomeProvider() + { + return new OverworldBiomeProvider(new OverworldBiomeProviderSettings().setWorldInfo(w.getWorldInfo())) + { + @Nullable + @Override + public Biome getBiome(BlockPos pos, @Nullable Biome defaultBiome) + { + return Biomes.PLAINS; + } + }; + } + }; + + + return structure.plopAnywhere(w, p, chunkgen); + }; + } + + public static Boolean spawn(String name, World world, BlockPos pos) + { + if (featureMap.containsKey(name)) + return featureMap.get(name).plop(world, pos); + return null; + } + + private static Map featureMap = new HashMap() {{ + + + put("oak", simplePlop(Feature.TREE)); + put("oak_large", simplePlop(Feature.BIG_TREE)); + put("birch", simplePlop(Feature.BIRCH_TREE)); + put("birch_large", simplePlop(Feature.TALL_BIRCH_TREE)); + put("shrub", simplePlop(Feature.SHRUB)); + put("shrub_acacia", simplePlop(new ShrubFeature(Blocks.ACACIA_WOOD.getDefaultState(), Blocks.ACACIA_LEAVES.getDefaultState()))); + put("shrub_birch", simplePlop(new ShrubFeature(Blocks.BIRCH_WOOD.getDefaultState(), Blocks.BIRCH_LEAVES.getDefaultState()))); + put("shrub_snowy", simplePlop(new ShrubFeature(Blocks.BONE_BLOCK.getDefaultState(), Blocks.COBWEB.getDefaultState()))); + put("jungle", simplePlop(Feature.JUNGLE_TREE)); + put("spruce_matchstick", simplePlop(Feature.POINTY_TAIGA_TREE)); + put("dark_oak", simplePlop(Feature.CANOPY_TREE)); + put("acacia", simplePlop(Feature.SAVANNA_TREE)); + put("spruce", simplePlop(Feature.TALL_TAIGA_TREE)); + put("oak_swamp", simplePlop(Feature.SWAMP_TREE)); + put("jungle_large", simplePlop(Feature.MESA_JUNGLE)); + put("spruce_matchstick_large", simplePlop(Feature.MEGA_PINE_TREE_1)); + put("spruce_large", simplePlop(Feature.MEGA_PINE_TREE_2)); + put("well", simplePlop(Feature.DESERT_WELLS)); + put("grass_jungle", simplePlop(Feature.JUNGLE_GRASS)); + put("fern", simplePlop(Feature.TAIGA_GRASS)); + put("grass", simplePlop(new TallGrassFeature(), new TallGrassConfig(Blocks.GRASS.getDefaultState()))); + //put("", simplePlop(Feature.)); + //put("", simplePlop(Feature.)); + //put("", simplePlop(Feature.)); + //put("", simplePlop(Feature.)); + + put("cactus", simplePlop(Feature.CACTUS)); + put("dead_bush", simplePlop(Feature.DEAD_BUSH)); + put("fossils", simplePlop(Feature.FOSSILS)); // spawn above, spawn invisible + put("mushroom_brown", simplePlop(Feature.BIG_BROWN_MUSHROOM)); + put("mushroom_red", simplePlop(Feature.BIG_RED_MUSHROOM)); + put("ice_spike", simplePlop(Feature.ICE_SPIKE)); + put("glowstone", simplePlop(Feature.GLOWSTONE)); + put("melon", simplePlop(Feature.MELON)); + put("pumpkin", simplePlop(Feature.PUMPKIN)); + put("sugarcane", simplePlop(Feature.REED)); + put("lilypad", simplePlop(Feature.WATERLILY)); + put("dungeon", simplePlop(Feature.DUNGEONS)); + put("iceberg", simplePlop(Feature.ICEBERG, new IcebergConfig(Blocks.PACKED_ICE.getDefaultState()))); + put("iceberg_blue", simplePlop(Feature.ICEBERG, new IcebergConfig(Blocks.BLUE_ICE.getDefaultState()))); + put("lake", simplePlop(Feature.LAKES, new LakesConfig(Blocks.WATER))); + put("lake_lava", simplePlop(Feature.LAKES, new LakesConfig(Blocks.LAVA))); + //put("end tower", simplePlop(Feature.END_CRYSTAL_TOWER)); // requires more complex setup + put("end_island", simplePlop(Feature.END_ISLAND)); + put("chorus", simplePlop(Feature.CHORUS_PLANT)); + put("sea_grass", simplePlop(Feature.SEA_GRASS, new SeaGrassConfig(80, 0.8D))); + put("sea_grass_river", simplePlop(Feature.SEA_GRASS, new SeaGrassConfig(48, 0.4D))); + put("kelp", simplePlop(Feature.KELP)); + put("coral_tree", simplePlop(Feature.CORAL_TREE)); + put("coral_mushroom", simplePlop(Feature.CORAL_MUSHROOM)); + put("coral_claw", simplePlop(Feature.CORAL_CLAW)); + put("coral", (w, p) -> + w.rand.nextInt(3)!=0? + (w.rand.nextInt(2)!=0? + simplePlop(Feature.CORAL_MUSHROOM).plop(w, p): + simplePlop(Feature.CORAL_TREE).plop(w, p)) + :simplePlop(Feature.CORAL_CLAW).plop(w, p)); + put("sea_pickle", simplePlop(Feature.SEA_PICKLE, new CountConfig(20))); + put("boulder", simplePlop(Feature.BLOCK_BLOB, new BlockBlobConfig(Blocks.MOSSY_COBBLESTONE, 0))); + //structures + put("monument", Feature.OCEAN_MONUMENT::plopAnywhere); + put("fortress", Feature.FORTRESS::plopAnywhere); + put("mansion", Feature.WOODLAND_MANSION::plopAnywhere); + put("jungle_temple", Feature.JUNGLE_PYRAMID::plopAnywhere); + put("desert_temple", Feature.DESERT_PYRAMID::plopAnywhere); + put("end_city", Feature.END_CITY::plopAnywhere); + put("igloo", Feature.IGLOO::plopAnywhere); + put("shipwreck", spawnCustomStructure(Feature.SHIPWRECK, new ShipwreckConfig(true))); + put("shipwreck2", spawnCustomStructure(Feature.SHIPWRECK, new ShipwreckConfig(false))); + put("witchhut", Feature.SWAMP_HUT::plopAnywhere); + put("stronghold", Feature.STRONGHOLD::plopAnywhere); + + put("oceanruin_small", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.COLD, 0.0F, 0.5F))); + put("oceanruin_warm_small", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.WARM, 0.0F, 0.5F))); + put("oceanruin_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.COLD, 1.0F, 0.0F))); + put("oceanruin_warm_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.WARM, 1.0F, 0.0F))); + put("oceanruin", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.COLD, 1.0F, 1.0F))); + put("oceanruin_warm", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinGenerator.Type.WARM, 1.0F, 1.0F))); + put("treasure", Feature.BURIED_TREASURE::plopAnywhere); + + + put("mineshaft", spawnCustomStructure(Feature.MINESHAFT, new MineshaftConfig(0.0, MineshaftStructure.Type.NORMAL))); + put("mineshaft_mesa", spawnCustomStructure(Feature.MINESHAFT, new MineshaftConfig(0.0, MineshaftStructure.Type.MESA))); + + put("village", spawnCustomStructure(Feature.VILLAGE, new VillageConfig(0,VillagePieces.Type.OAK))); + put("village_desert", spawnCustomStructure(Feature.VILLAGE, new VillageConfig(0,VillagePieces.Type.SANDSTONE))); + put("village_savanna", spawnCustomStructure(Feature.VILLAGE, new VillageConfig(0,VillagePieces.Type.ACACIA))); + put("village_taiga", spawnCustomStructure(Feature.VILLAGE, new VillageConfig(0,VillagePieces.Type.SPRUCE))); + + }}; + +} diff --git a/src/main/java/carpet/helpers/HopperCounter.java b/src/main/java/carpet/helpers/HopperCounter.java new file mode 100644 index 00000000..02cbf7f9 --- /dev/null +++ b/src/main/java/carpet/helpers/HopperCounter.java @@ -0,0 +1,156 @@ +package carpet.helpers; + +import carpet.utils.Messenger; +import com.google.common.collect.Maps; +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.util.DyeColor; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.MinecraftServer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +public class HopperCounter +{ + public static final Map COUNTERS; + + static + { + EnumMap counterMap = new EnumMap<>(DyeColor.class); + for (DyeColor color : DyeColor.values()) + { + counterMap.put(color, new HopperCounter(color)); + } + COUNTERS = Maps.immutableEnumMap(counterMap); + } + + public final DyeColor color; + private final Object2LongMap counter = new Object2LongLinkedOpenHashMap<>(); + private long startTick; + private long startMillis; + // private PubSubInfoProvider pubSubProvider; + + private HopperCounter(DyeColor color) + { + this.color = color; + // pubSubProvider = new PubSubInfoProvider<>(QuickCarpet.PUBSUB, "carpet.counter." + color.getName(), 0, this::getTotalItems); + } + + public void add(MinecraftServer server, ItemStack stack) + { + if (startTick == 0) + { + startTick = server.getTicks(); + startMillis = System.currentTimeMillis(); + } + Item item = stack.getItem(); + counter.put(item, counter.getLong(item) + stack.getAmount()); + // pubSubProvider.publish(); + } + + public void reset(MinecraftServer server) + { + counter.clear(); + startTick = server.getTicks(); + startMillis = System.currentTimeMillis(); + // pubSubProvider.publish(); + } + + public static void resetAll(MinecraftServer server) + { + for (HopperCounter counter : COUNTERS.values()) + { + counter.reset(server); + } + } + + public static List formatAll(MinecraftServer server, boolean realtime) + { + List text = new ArrayList<>(); + + for (HopperCounter counter : COUNTERS.values()) + { + List temp = counter.format(server, realtime, false); + if (temp.size() > 1) + { + if (!text.isEmpty()) text.add(Messenger.s("")); + text.addAll(temp); + } + } + if (text.isEmpty()) + { + text.add(Messenger.s("No items have been counted yet.")); + } + return text; + } + + public List format(MinecraftServer server, boolean realTime, boolean brief) + { + if (counter.isEmpty()) + { + if (brief) + { + return Collections.singletonList(Messenger.c("g "+color+": -, -/h, - min ")); + } + return Collections.singletonList(Messenger.s(String.format("No items for %s yet", color.getName()))); + } + long total = getTotalItems(); + long ticks = Math.max(realTime ? (System.currentTimeMillis() - startMillis) / 50 : server.getTicks() - startTick, 1); + if (total == 0) + { + if (brief) + { + return Collections.singletonList(Messenger.c(String.format("c %s: 0, 0/h, %.1f min ", color, ticks / (20.0 * 60.0)))); + } + return Collections.singletonList(Messenger.c(String.format("w No items for %s yet (%.2f min.%s)", + color.getName(), ticks / (20.0 * 60.0), (realTime ? " - real time" : "")), + "nb [X]", "^g reset", "!/counter " + color.getName() +" reset")); + } + if (brief) + { + return Collections.singletonList(Messenger.c(String.format("c %s: %d, %d/h, %.1f min ", + color.getName(), total, total * (20 * 60 * 60) / ticks, ticks / (20.0 * 60.0)))); + } + List items = new ArrayList<>(); + items.add(Messenger.c(String.format("w Items for %s (%.2f min.%s), total: %d, (%.1f/h):", + color, ticks*1.0/(20*60), (realTime?" - real time":""), total, total*1.0*(20*60*60)/ticks), + "nb [X]", "^g reset", "!/counter "+color+" reset" + )); + items.addAll(counter.object2LongEntrySet().stream().map(e -> + { + BaseComponent itemName = new TranslatableComponent(e.getKey().getTranslationKey()); + long count = e.getLongValue(); + return Messenger.c("w - ", itemName, String.format("w : %d, %.1f/h", + count, + count * (20.0 * 60.0 * 60.0) / ticks)); + }).collect(Collectors.toList())); + return items; + } + + public static HopperCounter getCounter(String color) + { + try + { + DyeColor colorEnum = DyeColor.valueOf(color.toUpperCase(Locale.ROOT)); + return COUNTERS.get(colorEnum); + } + catch (IllegalArgumentException e) + { + return null; + } + } + + public long getTotalItems() + { + return counter.values().stream().mapToLong(Long::longValue).sum(); + } +} diff --git a/src/main/java/carpet/helpers/PortalHelper.java b/src/main/java/carpet/helpers/PortalHelper.java new file mode 100644 index 00000000..2cba9115 --- /dev/null +++ b/src/main/java/carpet/helpers/PortalHelper.java @@ -0,0 +1,19 @@ +package carpet.helpers; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.block.Blocks; +import net.minecraft.item.BlockItem; + +public class PortalHelper +{ + public static boolean player_holds_obsidian(PlayerEntity playerIn) + { + if ( (!playerIn.getMainHandStack().isEmpty() + && playerIn.getMainHandStack().getItem() instanceof BlockItem && + ((BlockItem)(playerIn.getMainHandStack().getItem())).getBlock() == Blocks.OBSIDIAN )) + { + return true; + } + return false; + } +} diff --git a/src/main/java/carpet/helpers/RedstoneWireTurbo.javax b/src/main/java/carpet/helpers/RedstoneWireTurbo.javax new file mode 100644 index 00000000..02b39435 --- /dev/null +++ b/src/main/java/carpet/helpers/RedstoneWireTurbo.javax @@ -0,0 +1,922 @@ +package carpet.helpers; +//Author: theosib + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.block.Block; +import net.minecraft.block.RedstoneWireBlock; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class RedstoneWireTurbo +{ + /* + * This is Helper class for RedstoneWireBlock. It implements a minimially-invasive + * bolt-on accelerator that performs a breadth-first search through redstone wire blocks + * in order to more efficiently and deterministicly compute new redstone wire power levels + * and determine the order in which other blocks should be updated. + * + * Features: + * - Changes to RedstoneWireBlock are very limited, no other classes are affected, and the + * choice between old and new redstone wire update algorithms is switchable on-line. + * - The vanilla implementation relied on World.notifyNeighborsOfStateChange for redstone + * wire blocks to communicate power level changes to each other, generating 36 block + * updates per call. This improved implementation propagates power level changes directly + * between redstone wire blocks. Redstone wire power levels are therefore computed more quickly, + * and block updates are sent only to non-redstone blocks, many of which may perform an + * action when informed of a change in redstone power level. (Note: Block updates are not + * the same as state changes to redstone wire. Wire block states are updated as soon + * as they are computed.) + * - Of the 36 block updates generated by a call to World.notifyNeighborsOfStateChange, + * 12 of them are obviously redundant (e.g. the west neighbor of the east neighbor). + * These are eliminated. + * - Updates to redstone wire and other connected blocks are propagated in a breath-first + * manner, radiating out from the initial trigger (a block update to a redstone wire + * from something other than redstone wire). + * - Updates are scheduled both deterministicly and in an intuitive order, addressing bug + * MC-11193. + * - All redstone behavior that used to be locational now works the same in all locations. + * - All behaviors of redstone wire that used to be orientational now work the same in all + * orientations, as long as orientation can be determined; random otherwise. Some other + * redstone components still update directionally (e.g. switches), and this code can't + * compensate for that. + * - Information that is otherwise computed over and over again or which is expensive to + * to compute is cached for faster lookup. This includes coordinates of block position + * neighbors and block states that won't change behind our backs during the execution of + * this search algorithm. + * - Redundant block updates (both to redstone wire and to other blocks) are heavily + * consolidated. For worst-case scenarios (depowering of redstone wire) this results + * in a reduction of block updates by as much as 95% (factor of 1/21). Due to overheads, + * empirical testing shows a speedup better than 10x. This addresses bug MC-81098. + * + * Extensive testing has been performed to ensure that existing redstone contraptions still + * behave as expected. Results of early testing that identified undesirable behavior changes + * were addressed. Additionally, real-time performance testing revealed compute inefficiencies + * With earlier implementations of this accelerator. Some compatibility adjustments and + * performance optimizations resulted in harmless increases in block updates above the + * theoretical minimum. + * + * Only a single redstone machine was found to break: An instant dropper line hack that + * relies on powered rails and quasiconnectivity but doesn't work in all directions. The + * replacement is to lay redstone wire directly on top of the dropper line, which now works + * reliably in any direction. + * + * There are numerous other optimization that can be made, but those will be provided later in + * separate updates. This version is designed to be minimalistic. + * + * Many thanks to the following individuals for their help in testing this functionality: + * - pokechu22, _MethodZz_, WARBEN, NarcolepticFrog, CommandHelper (nessie), ilmango, + * OreoLamp, Xcom6000, tryashtar, RedCMD, Smokey95Dog, EDDxample, Rays Works, + * Nodnam, BlockyPlays, Grumm, NeunEinser, HelVince. + */ + + + /* Reference to RedstoneWireBlock object, which uses this accelerator */ + private final RedstoneWireBlock wire; + + + /* + * Implementation: + * + * RedstoneWire Blocks are updated in concentric rings or "layers" radiating out from the + * initial block update that came from a call to RedstoneWireBlock.neighborChanged(). + * All nodes put in Layer N are those with Manattan distance N from the trigger + * position, reachable through connected redstone wire blocks. + * + * Layer 0 represents the trigger block position that was input to neighborChanged. + * Layer 1 contains the immediate neighbors of that position. + * Layer N contains the neighbors of blocks in layer N-1, not including + * those in previous layers. + * + * Layers enforce an update order that is a function of Manhattan distance + * from the initial coordinates input to neighborChanged. The same + * coordinates may appear in multiple layers, but redundant updates are minimized. + * Block updates are sent layer-by-layer. If multiple of a block's neighbors experience + * redstone wire changes before its layer is processed, then those updates will be merged. + * If a block's update has been sent, but its neighboring redstone changes + * after that, then another update will be sent. This preserves compatibility with + * machines that rely on zero-tick behavior, except that the new functionality is non- + * locational. + * + * Within each layer, updates are ordered left-to-right relative to the direction of + * information flow. This makes the implementation non-orientational. Only when + * this direction is ambiguous is randomness applied (intentionally). + */ + //private final List> updateLayers = new ArrayList<>(); + private List updateQueue0 = new ArrayList<>(); + private List updateQueue1 = new ArrayList<>(); + private List updateQueue2 = new ArrayList<>(); + + + public RedstoneWireTurbo(RedstoneWireBlock wire) { + this.wire = wire; + } + + + /* + * Compute neighbors of a block. When a redstone wire value changes, previously it called + * World.notifyNeighborsOfStateChange. That lists immediately neighboring blocks in + * west, east, down, up, north, south order. For each of those neighbors, their own + * neighbors are updated in the same order. This generates 36 updates, but 12 of them are + * redundant; for instance the west neighbor of a block's east neighbor. + * + * Note that this ordering is only used to create the initial list of neighbors. Once + * the direction of signal flow is identified, the ordering of updates is completely + * reorganized. + */ + public static BlockPos[] computeAllNeighbors(final BlockPos pos) { + final int x = pos.getX(); + final int y = pos.getY(); + final int z = pos.getZ(); + final BlockPos[] n = new BlockPos[24]; + + // Immediate neighbors, in the same order as + // World.notifyNeighborsOfStateChange, etc.: + // west, east, down, up, north, south + n[ 0] = new BlockPos(x-1, y , z ); + n[ 1] = new BlockPos(x+1, y , z ); + n[ 2] = new BlockPos(x , y-1, z ); + n[ 3] = new BlockPos(x , y+1, z ); + n[ 4] = new BlockPos(x , y , z-1); + n[ 5] = new BlockPos(x , y , z+1); + + // Neighbors of neighbors, in the same order, + // except that duplicates are not included + n[ 6] = new BlockPos(x-2, y , z ); + n[ 7] = new BlockPos(x-1, y-1, z ); + n[ 8] = new BlockPos(x-1, y+1, z ); + n[ 9] = new BlockPos(x-1, y , z-1); + n[10] = new BlockPos(x-1, y , z+1); + n[11] = new BlockPos(x+2, y , z ); + n[12] = new BlockPos(x+1, y-1, z ); + n[13] = new BlockPos(x+1, y+1, z ); + n[14] = new BlockPos(x+1, y , z-1); + n[15] = new BlockPos(x+1, y , z+1); + n[16] = new BlockPos(x , y-2, z ); + n[17] = new BlockPos(x , y-1, z-1); + n[18] = new BlockPos(x , y-1, z+1); + n[19] = new BlockPos(x , y+2, z ); + n[20] = new BlockPos(x , y+1, z-1); + n[21] = new BlockPos(x , y+1, z+1); + n[22] = new BlockPos(x , y , z-2); + n[23] = new BlockPos(x , y , z+2); + return n; + } + + /* + * We only want redstone wires to update redstone wires that are + * immediately adjacent. Some more distant updates can result + * in cross-talk that (a) wastes time and (b) can make the update + * order unintuitive. Therefore (relative to the neighbor order + * computed by computeAllNeighbors), updates are not scheduled + * for redstone wire in those non-connecting positions. On the + * other hand, updates will always be sent to *other* types of blocks + * in any of the 24 neighboring positions. + */ + private static final boolean[] update_redstone = { + true, true, false, false, true, true, // 0 to 5 + false, true, true, false, false, false, // 6 to 11 + true, true, false, false, false, true, // 12 to 17 + true, false, true, true, false, false}; // 18 to 23 + + // Internal numbering for cardinal directions + private static final int North = 0; + private static final int East = 1; + private static final int South = 2; + private static final int West = 3; + + // Names for debug print statements + private static final char dirname[] = {'N', 'E', 'S', 'W'}; + + /* + * These lookup tables completely remap neighbor positions into a left-to-right + * ordering, based on the cardinal direction that is determined to be forward. + * See below for more explanation. + */ + private static final int[] forward_is_north = {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}; + private static final int[] forward_is_east = {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}; + private static final int[] forward_is_south = {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}; + private static final int[] forward_is_west = {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}; + + /* For any orientation, we end up with the update order defined below. This order is relative to any redstone wire block + * that is itself having an update computed, and this center position is marked with C. + * - The update position marked 0 is computed first, and the one marked 23 is last. + * - Forward is determined by the local direction of information flow into position C from prior updates. + * - The first updates are scheduled for the four positions below and above C. + * - Then updates are scheduled for the four horizontal neighbors of C, followed by the positions below and above those neighbors. + * - Finally, updates are scheduled for the remaining positions with Manhattan distance 2 from C (at the same Y coordinate). + * - For a given horizontal distance from C, updates are scheduled starting from directly left and stepping clockwise to directly + * right. The remaining positions behind C are scheduled counterclockwise so as to maintain the left-to-right ordering. + * - If C is in layer N of the update schedule, then all 24 positions may be scheduled for layer N+1. For redstone wire, no + * updates are scheduled for positions that cannot directly connect. Additionally, the four positions above and below C + * are ALSO scheduled for layer N+2. + * - This update order was selected after experimenting with a number of alternative schedules, based on its compatibility + * with existing redstone designs and behaviors that were considered to be intuitive by various testers. WARBEN in particular + * made some of the most challenging test cases, but the 3-tick clocks (made by RedCMD) were also challenging to fix, + * along with the rail-based instant dropper line built by ilmango. Numerous others made test cases as well, including + * NarcolepticFrog, nessie, and Pokechu22. + * + * - The forward direction ls determined locally. So when there are branches in the redstone wire, the left one will get updated + * before the right one. Each branch can have its own relative forward direction, resulting in the left side of a left branch + * having priority over the right branch of a left branch, which has priority over the left branch of a right branch, followed + * by the right branch of a right branch. And so forth. Since redstone power reduces to zero after a path distance of 15, + * that imposes a practical limit on the branching. Note that the branching is not tracked explicitly -- relative forward + * directions dictate relative sort order, which maintains the proper global ordering. This also makes it unnecessary to be + * concerned about branches meeting up with each other. + * + * ^ + * | + * Forward + * <-- Left Right --> + * + * 18 + * 10 17 5 19 11 + * 2 8 0 12 16 4 C 6 20 9 1 13 3 + * 14 21 7 23 15 + * Further 22 Further + * Down Down Up Up + * + * Backward + * | + * V + */ + + // This allows the above remapping tables to be looked up by cardial direction index + private static final int[][] reordering = {forward_is_north, forward_is_east, forward_is_south, forward_is_west}; + + /* + * Input: Array of UpdateNode objects in an order corresponding to the positions + * computed by computeAllNeighbors above. + * Output: Array of UpdateNode objects oriented using the above remapping tables + * corresponding to the identified heading (direction of information flow). + */ + private static void orientNeighbors(final UpdateNode[] src, final UpdateNode[] dst, final int heading) { + final int[] re = reordering[heading]; + for (int i=0; i<24; i++) { + dst[i] = src[re[i]]; + } + } + + /* + * Structure to keep track of redstone wire blocks and + * neighbors that will receive updates. + */ + private static class UpdateNode { + public static enum Type { + UNKNOWN, REDSTONE, OTHER + } + + BlockState currentState; // Keep track of redstone wire value + UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) + BlockPos self; // UpdateNode's own position + BlockPos parent; // Which block pos spawned/updated this node + Type type = Type.UNKNOWN; // unknown, redstone wire, other type of block + int layer; // Highest layer this node is scheduled in + boolean visited; // To keep track of information flow direction, visited restone wire is marked + int xbias, zbias; // Remembers directionality of ancestor nodes; helps eliminate directional ambiguities. + } + + + /* + * Keep track of all block positions discovered during search and their current states. + * We want to remember one entry for each position. + */ + private final Map nodeCache = new HashMap<>(); + + + /* + * For a newly created UpdateNode object, determine what type of block it is. + */ + private void identifyNode(final World worldIn, final UpdateNode upd1) { + final BlockPos pos = upd1.self; + final BlockState oldState = worldIn.getBlockState(pos); + upd1.currentState = oldState; + + // Some neighbors of redstone wire are other kinds of blocks. + // These need to receive block updates to inform them that + // redstone wire values have changed. + final Block block = oldState.getBlock(); + if (block != wire) { + // Mark this block as not redstone wire and therefore + // requiring updates + upd1.type = UpdateNode.Type.OTHER; + + // Non-redstone blocks may propagate updates, but those updates + // are not handled by this accelerator. Therefore, we do not + // expand this position's neighbors. + return; + } + + // One job of RedstoneWireBlock.neighborChanged is to convert + // redstone wires to items if the block beneath was removed. + // With this accelerator, RedstoneWireBlock.neighborChanged + // is only typically called for a single wire block, while + // others are processed internally by the breadth first search + // algorithm. To preserve this game behavior, this check must + // be replicated here. + if (!oldState.isValidPosition(worldIn, pos)) { + // Pop off the redstone dust + oldState.dropBlockAsItem(worldIn, pos, 0); + worldIn.removeBlock(pos); + + // Mark this position as not being redstone wire + upd1.type = UpdateNode.Type.OTHER; + + // Note: Sending updates to air blocks leads to an empty method. + // Testing shows this to be faster than explicitly avoiding updates to + // air blocks. + return; + } + + // If the above conditions fail, then this is a redstone wire block. + upd1.type = UpdateNode.Type.REDSTONE; + } + + + /* + * Given which redstone wire blocks have been visited and not visited + * around the position currently being updated, compute the cardinal + * direction that is "forward." + * + * rx is the forward direction along the West/East axis + * rz is the forward direction along the North/South axis + */ + static private int computeHeading(final int rx, final int rz) { + // rx and rz can only take on values -1, 0, and 1, so we can + // compute a code number that allows us to use a single switch + // to determine the heading. + final int code = (rx + 1) + 3*(rz + 1); + switch (code) { + case 0: { + // Both rx and rz are -1 (northwest) + // Randomly choose one to be forward. + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j==0) ? North : West; + } + case 1: { + // rx=0, rz=-1 + // Definitively North + return North; + } + case 2: { + // rx=1, rz=-1 (northeast) + // Choose randomly between north and east + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j==0) ? North : East; + } + case 3: { + // rx=-1, rz=0 + // Definitively West + return West; + } + case 4: { + // rx=0, rz=0 + // Heading is completely ambiguous. Choose + // randomly among the four cardinal directions. + return ThreadLocalRandom.current().nextInt(0, 4); + } + case 5: { + // rx=1, rz=0 + // Definitively East + return East; + } + case 6: { + // rx=-1, rz=1 (southwest) + // Choose randomly between south and west + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j==0) ? South : West; + } + case 7: { + // rx=0, rz=1 + // Definitively South + return South; + } + case 8: { + // rx=1, rz=1 (southeast) + // Choose randomly between south and east + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j==0) ? South : East; + } + } + + // We should never get here + return ThreadLocalRandom.current().nextInt(0, 4); + } + + // Select whether to use updateSurroundingRedstone from RedstoneWireBlock (old) + // or this helper class (new) + private static final boolean old_current_change = false; + + /* + * Process a node whose neighboring redstone wire has experienced value changes. + */ + private void updateNode(final World worldIn, final UpdateNode upd1, final int layer) { + final BlockPos pos = upd1.self; + + // Mark this redstone wire as having been visited so that it can be used + // to calculate direction of information flow. + upd1.visited = true; + + // Look up the last known state. + // Due to the way other redstone components are updated, we do not + // have to worry about a state changing behind our backs. The rare + // exception is handled by scheduleReentrantNeighborChanged. + final BlockState oldState = upd1.currentState; + + // Ask the wire block to compute its power level from its neighbors. + // This will also update the wire's power level and return a new + // state if it has changed. When a wire power level is changed, + // calculateCurrentChanges will immediately update the block state in the world + // and return the same value here to be cached in the corresponding + // UpdateNode object. + BlockState newState; + if (old_current_change) { + newState = wire.func_212568_b(worldIn, pos, oldState); + } else { + // Looking up block state is slow. This accelerator includes a version of + // calculateCurrentChanges that uses cahed wire values for a + // significant performance boost. + newState = this.calculateCurrentChanges(worldIn, upd1); + } + + // Only inform neighors if the state has changed + if (newState != oldState) { + // Store the new state + upd1.currentState = newState; + + // Inform neighbors of the change + propagateChanges(worldIn, upd1, layer); + } + } + + /* + * This identifies the neighboring positions of a new UpdateNode object, + * determines their types, and links those to into the graph. Then based on + * what nodes in the redstone wire graph have been visited, the neighbors + * are reordered left-to-right relative to the direction of information flow. + */ + private void findNeighbors(final World worldIn, final UpdateNode upd1) { + final BlockPos pos = upd1.self; + + // Get the list of neighbor coordinates + final BlockPos[] neighbors = computeAllNeighbors(pos); + + // Temporary array of neighbors in cardinal ordering + final UpdateNode[] neighbor_nodes = new UpdateNode[24]; + + // Target array of neighbors sorted left-to-right + upd1.neighbor_nodes = new UpdateNode[24]; + + for (int i=0; i<24; i++) { + // Look up each neighbor in the node cache + final BlockPos pos2 = neighbors[i]; + UpdateNode upd2 = nodeCache.get(pos2); + if (upd2 == null) { + // If this is a previously unreached position, create + // a new update node, add it to the cache, and identify what it is. + upd2 = new UpdateNode(); + upd2.self = pos2; + upd2.parent = pos; + nodeCache.put(pos2, upd2); + identifyNode(worldIn, upd2); + } + + // For non-redstone blocks, any of the 24 neighboring positions + // should receive a block update. However, some block coordinates + // may contain a redstone wire that does not directly connect to the + // one being expanded. To avoid redundant calculations and confusing + // cross-talk, those neighboring positions are not included. + if (update_redstone[i] || upd2.type != UpdateNode.Type.REDSTONE) { + neighbor_nodes[i] = upd2; + } + } + + // Determine the directions from which the redstone signal may have come from. This + // checks for redstone wire at the same Y level and also Y+1 and Y-1, relative to the + // block being expanded. + final boolean fromWest = (neighbor_nodes[0].visited || neighbor_nodes[7].visited || neighbor_nodes[8].visited); + final boolean fromEast = (neighbor_nodes[1].visited || neighbor_nodes[12].visited || neighbor_nodes[13].visited); + final boolean fromNorth = (neighbor_nodes[4].visited || neighbor_nodes[17].visited || neighbor_nodes[20].visited); + final boolean fromSouth = (neighbor_nodes[5].visited || neighbor_nodes[18].visited || neighbor_nodes[21].visited); + + int cx = 0, cz = 0; + if (fromWest) cx += 1; + if (fromEast) cx -= 1; + if (fromNorth) cz += 1; + if (fromSouth) cz -= 1; + + int heading; + if (cx==0 && cz==0) { + // If there is no clear direction, try to inherit the heading from ancestor nodes. + heading = computeHeading(upd1.xbias, upd1.zbias); + + // Propagate that heading to descendent nodes. + for (int i=0; i<24; i++) { + final UpdateNode nn = neighbor_nodes[i]; + if (nn != null) { + nn.xbias = upd1.xbias; + nn.zbias = upd1.zbias; + } + } + } else { + if (cx != 0 && cz != 0) { + // If the heading is somewhat ambiguous, try to disambiguate based on + // ancestor nodes. + if (upd1.xbias != 0) cz = 0; + if (upd1.zbias != 0) cx = 0; + } + heading = computeHeading(cx, cz); + + // Propagate that heading to descendent nodes. + for (int i=0; i<24; i++) { + final UpdateNode nn = neighbor_nodes[i]; + if (nn != null) { + nn.xbias = cx; + nn.zbias = cz; + } + } + } + + // Reorder neighboring UpdateNode objects according to the forward direction + // determined above. + orientNeighbors(neighbor_nodes, upd1.neighbor_nodes, heading); + } + + /* + * For any redstone wire block in layer N, inform neighbors to recompute their states + * in layers N+1 and N+2; + */ + private void propagateChanges(final World worldIn, final UpdateNode upd1, final int layer) { + if (upd1.neighbor_nodes == null) { + // If this node has not been expanded yet, find its neigbors + findNeighbors(worldIn, upd1); + } + + final BlockPos pos = upd1.self; + final int x = pos.getX(); + final int y = pos.getY(); + final int z = pos.getZ(); + + // Make sure there are enough layers in the list + //while (updateLayers.size() <= layer+2) updateLayers.add(new ArrayList()); + + // All neighbors may be scheduled for layer N+1 + final int layer1 = layer + 1; + + // If the node being updated (upd1) has already been expanded, then merely + // schedule updates to its neighbors. + for (int i=0; i<24; i++) { + final UpdateNode upd2 = upd1.neighbor_nodes[i]; + + // This test ensures that an UpdateNode is never scheduled to the same layer + // more than once. Also, skip non-connecting redstone wire blocks + if (upd2 != null && layer1 > upd2.layer) { + upd2.layer = layer1; + updateQueue1.add(upd2); + + // Keep track of which block updated this neighbor + upd2.parent = pos; + } + } + + // Nodes above and below are scheduled ALSO for layer N+2 + final int layer2 = layer + 2; + + // Repeat of the loop above, but only for the first four (above and below) neighbors + // and for layer N+2; + for (int i=0; i<4; i++) { + final UpdateNode upd2 = upd1.neighbor_nodes[i]; + if (upd2 != null && layer2 > upd2.layer) { + upd2.layer = layer2; + updateQueue2.add(upd2); + upd2.parent = pos; + } + } + } + + + // The breadth-first search below will send block updates to blocks + // that are not redstone wire. If one of those updates results in + // a distant redstone wire getting an update, then this.neighborChanged + // will get called. This would be a reentrant call, and + // it is necessary to properly integrate those updates into the + // on-going search through redstone wire. Thus, we make the layer + // currently being processed visible at the object level. + + // The current layer being processed by the breadth-first search + private int currentWalkLayer = 0; + + private void shiftQueue() { + final List t = updateQueue0; + t.clear(); + updateQueue0 = updateQueue1; + updateQueue1 = updateQueue2; + updateQueue2 = t; + } + + /* + * Perform a breadth-first (layer by layer) traversal through redstone + * wire blocks, propagating value changes to neighbors in an order + * that is a function of distance from the initial call to + * this.neighborChanged. + */ + private void breadthFirstWalk(final World worldIn) { + shiftQueue(); + currentWalkLayer = 1; + + // Loop over all layers + while (updateQueue0.size()>0 || updateQueue1.size()>0) { + // Get the set of blocks in this layer + final List thisLayer = updateQueue0; + //if (thisLayer.size() < 1) { + //shiftQueue(); + //currentWalkLayer++; + //continue; + //} + + // Loop over all blocks in the layer. Recall that + // this is a List, preserving the insertion order of + // left-to-right based on direction of information flow. + for (UpdateNode upd : thisLayer) { + if (upd.type == UpdateNode.Type.REDSTONE) { + // If the node is is redstone wire, + // schedule updates to neighbors if its value + // has changed. + updateNode(worldIn, upd, currentWalkLayer); + } else { + // If this block is not redstone wire, send a block update. + // Redstone wire blocks get state updates, but they don't + // need block updates. Only non-redstone neighbors need updates. + + // The following function is called "neighborChanged" in the latest + // deobfuscated names. World.func_190524_a is called from + // World.notifyNeighborsOfStateChange, and + // notifyNeighborsOfStateExcept. We don't use + // World.notifyNeighborsOfStateChange here, since we are + // already keeping track of all of the neighbor positions + // that need to be updated. All on its own, handling neighbors + // this way reduces block updates by 1/3 (24 instead of 36). + worldIn.neighborChanged(upd.self, wire, upd.parent); + } + } + + // Move on to the next layer + shiftQueue(); + currentWalkLayer++; + } + + currentWalkLayer = 0; + } + + + /* + * Normally, when Minecraft is computing redstone wire power changes, and a wire power level + * change sends a block update to a neighboring functional component (e.g. piston, repeater, etc.), + * those updates are queued. Only once all redstone wire updates are complete will any component + * action generate any further block updates to redstone wire. Instant repeater lines, for instance, + * will process all wire updates for one redstone line, after which the pistons will zero-tick, + * after which the next redstone line performs all of its updates. Thus, each wire is processed in its + * own discrete wave. + * + * However, there are some corner cases where this pattern breaks, with a proof of concept discovered + * by Rays Works, which works the same in vanilla. The scenario is as follows: + * (1) A redstone wire is conducting a signal. + * (2) Partway through that wave of updates, a neighbor is updated that causes an update to a completely + * separate redstone wire. + * (3) This results in a call to RedstoneWireBlock.neighborChanged for that other wire, in the middle of + * an already on-going propagation through the first wire. + * + * The vanilla code, being depth-first, would end up fully processing the second wire before going back + * to finish processing the first one. (Although technically, vanilla has no special concept of "being + * in the middle" of processing updates to a wire.) For the breadth-first algorithm, we give this + * situation special handling, where the updates for the second wire are incorporated into the schedule + * for the first wire, and then the callstack is allowed to unwind back to the on-going search loop in + * order to continue processing both the first and second wire in the order of distance from the initial + * trigger. + */ + private BlockState scheduleReentrantNeighborChanged(final World worldIn, final BlockPos pos, final BlockState newState, final BlockPos source) + { + if (source != null) { + // If the cause of the redstone wire update is known, we can use that to help determine + // direction of information flow. + UpdateNode src = nodeCache.get(source); + if (src == null) { + src = new UpdateNode(); + src.self = source; + src.parent = source; + src.visited = true; + identifyNode(worldIn, src); + nodeCache.put(source, src); + } + } + + // Find or generate a node for the redstone block position receiving the update + UpdateNode upd = nodeCache.get(pos); + if (upd == null) { + upd = new UpdateNode(); + upd.self = pos; + upd.parent = pos; + upd.visited = true; + identifyNode(worldIn, upd); + nodeCache.put(pos, upd); + } + upd.currentState = newState; + + // Receiving this block update may mean something in the world changed. + // Therefore we clear the cached block info about all neighbors of + // the position receiving the update and then re-identify what they are. + if (upd.neighbor_nodes != null) { + for (int i=0; i<24; i++) { + final UpdateNode upd2 = upd.neighbor_nodes[i]; + if (upd2 == null) continue; + upd2.type = UpdateNode.Type.UNKNOWN; + upd2.currentState = null; + identifyNode(worldIn, upd2); + } + } + + // The block at 'pos' is a redstone wire and has been updated already by calling + // wire.calculateCurrentChanges, so we don't schedule that. However, we do need + // to schedule its neighbors. By passing the current value of 'currentWalkLayer' to + // propagateChanges, the neighbors of 'pos' are schedule for layers currentWalkLayer+1 + // and currentWalkLayer+2. + propagateChanges(worldIn, upd, currentWalkLayer); + + // Return here. The call stack will unwind back to the first call to + // updateSurroundingRedstone, whereupon the new updates just scheduled will + // be propagated. This also facilitates elimination of superfluous and + // redundant block updates. + return newState; + } + + /* + * New version of pre-existing updateSurroundingRedstone, which is called from + * wire.updateSurroundingRedstone, which is called from wire.neighborChanged and a + * few other methods in RedstoneWireBlock. This sets off the breadth-first + * walk through all redstone dust connected to the initial position triggered. + */ + public BlockState updateSurroundingRedstone(final World worldIn, final BlockPos pos, final BlockState state, final BlockPos source) + { + // Check this block's neighbors and see if its power level needs to change + // Use the calculateCurrentChanges method in RedstoneWireBlock since we have no + // cached block states at this point. + final BlockState newState = wire.func_212568_b(worldIn, pos, state); + + // If no change, exit + if (newState == state) { + return state; + } + + // Check to see if this update was received during an on-going breadth first search + if (currentWalkLayer>0 || nodeCache.size()>0) { + // As breadthFirstWalk progresses, it sends block updates to neighbors. Some of those + // neighbors may affect the world so as to cause yet another redstone wire block to receive + // an update. If that happens, we need to integrate those redstone wire updates into the + // already on-going graph walk being performed by breadthFirstWalk. + return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); + } + // If there are no on-going walks through redstone wire, then start a new walk. + + // If the source of the block update to the redstone wire at 'pos' is known, we can use + // that to help determine the direction of information flow. + if (source != null) { + final UpdateNode src = new UpdateNode(); + src.self = source; + src.parent = source; + src.visited = true; + nodeCache.put(source, src); + identifyNode(worldIn, src); + } + + // Create a node representing the block at 'pos', and then propagate updates + // to its neighbors. As stated above, the call to wire.calculateCurrentChanges + // already performs the update to the block at 'pos', so it is not added to the schedule. + final UpdateNode upd = new UpdateNode(); + upd.self = pos; + upd.parent = source!=null ? source : pos; + upd.currentState = newState; + upd.type = UpdateNode.Type.REDSTONE; + upd.visited = true; + nodeCache.put(pos, upd); + propagateChanges(worldIn, upd, 0); + + // Perform the walk over all directly reachable redstone wire blocks, propagating wire value + // updates in a breadth first order out from the initial update received for the block at 'pos'. + breadthFirstWalk(worldIn); + + // With the whole search completed, clear the list of all known blocks. + // We do not want to keep around state information that may be changed by other code. + // In theory, we could cache the neighbor block positions, but that is a separate + // optimization. + nodeCache.clear(); + + return newState; + } + + + // For any array of neighbors in an UpdateNode object, these are always + // the indices of the four immediate neighbors at the same Y coordinate. + private static final int[] rs_neighbors = {4, 5, 6, 7}; + private static final int[] rs_neighbors_up = {9, 11, 13, 15}; + private static final int[] rs_neighbors_dn = {8, 10, 12, 14}; + + /* + * Updated calculateCurrentChanges that is optimized for speed and uses + * the UpdateNode's neighbor array to find the redstone states of neighbors + * that might power it. + */ + private BlockState calculateCurrentChanges(final World worldIn, final UpdateNode upd) + { + BlockState state = upd.currentState; + final int i = state.get(RedstoneWireBlock.POWER); + int j = 0; + j = this.getMaxCurrentStrength(upd, j); + int l = 0; + + wire.setCanProvidePower(false); + // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, + // and I'm not ready to try to replicate even more functionality from + // elsewhere in Minecraft into this accelerator. So sadly, we must + // suffer the performance hit of this very expensive call. If there + // is consistency to what this call returns, we may be able to cache it. + final int k = worldIn.getRedstonePowerFromNeighbors(upd.self); + wire.setCanProvidePower(true); + + // The variable 'k' holds the maximum redstone power value of any adjacent blocks. + // If 'k' has the highest level of all neighbors, then the power level of this + // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the + // following loop can affect the power level of the wire. Therefore, the loop is + // skipped if k is already 15. + if (k<15) { + if (upd.neighbor_nodes == null) { + // If this node's neighbors are not known, expand the node + findNeighbors(worldIn, upd); + } + + // These remain constant, so pull them out of the loop. + // Regardless of which direction is forward, the UpdateNode for the + // position directly above the node being calculated is always + // at index 1. + UpdateNode center_up = upd.neighbor_nodes[1]; + boolean center_up_is_cube = center_up.currentState.isNormalCube(); + + for (int m=0; m<4; m++) { + // Get the neighbor array index of each of the four cardinal + // neighbors. + int n = rs_neighbors[m]; + + // Get the max redstone power level of each of the cardinal + // neighbors + UpdateNode neighbor = upd.neighbor_nodes[n]; + l = this.getMaxCurrentStrength(neighbor, l); + + // Also check the positions above and below the cardinal + // neighbors + boolean neighbor_is_cube = neighbor.currentState.isNormalCube(); + if (!neighbor_is_cube) { + UpdateNode neighbor_down = upd.neighbor_nodes[rs_neighbors_dn[m]]; + l = this.getMaxCurrentStrength(neighbor_down, l); + } else + if (!center_up_is_cube) { + UpdateNode neighbor_up = upd.neighbor_nodes[rs_neighbors_up[m]]; + l = this.getMaxCurrentStrength(neighbor_up, l); + } + } + } + + // The new code sets this redstonewire block's power level to the highest neighbor + // minus 1. This usually results in wire power levels dropping by 2 at a time. + // This optimization alone has no impact on opdate order, only the number of updates. + j = l-1; + + // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will + // always be in the range of 0 to 15, the following if will correct that. + if (k>j) j=k; + + if (i != j) { + // If the power level has changed from its previous value, compute a new state + // and set it in the world. + // Possible optimization: Don't commit state changes to the world until they + // need to be known by some nearby non-redstone-wire block. + state = state.with(RedstoneWireBlock.POWER, j); + worldIn.setBlockState(upd.self, state, 2); + } + + return state; + } + + /* + * Optimized function to compute a redstone wire's power level based on cached + * state. + */ + private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) { + if (upd.type != UpdateNode.Type.REDSTONE) return strength; + final int i = upd.currentState.get(RedstoneWireBlock.POWER); + return i > strength ? i : strength; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java new file mode 100644 index 00000000..e594dc5f --- /dev/null +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -0,0 +1,169 @@ +package carpet.helpers; + +import carpet.CarpetServer; +import carpet.utils.Messenger; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.network.chat.BaseComponent; + +public class TickSpeed +{ + public static final int PLAYER_GRACE = 2; + public static float tickrate = 20.0f; + public static long mspt = 50l; + public static long time_bias = 0; + public static long time_warp_start_time = 0; + public static long time_warp_scheduled_ticks = 0; + public static PlayerEntity time_advancerer = null; + public static String tick_warp_callback = null; + public static ServerCommandSource tick_warp_sender = null; + public static int player_active_timeout = 0; + public static boolean process_entities = true; + public static boolean is_paused = false; + public static boolean is_superHot = false; + + public static void reset_player_active_timeout() + { + if (player_active_timeout < PLAYER_GRACE) + { + player_active_timeout = PLAYER_GRACE; + } + } + + public static void add_ticks_to_run_in_pause(int ticks) + { + player_active_timeout = PLAYER_GRACE+ticks; + } + + public static void tickrate(float rate) + { + tickrate = rate; + mspt = (long)(1000.0/tickrate); + if (mspt <=0) + { + mspt = 1l; + tickrate = 1000.0f; + } + } + + public static BaseComponent tickrate_advance(PlayerEntity player, int advance, String callback, ServerCommandSource source) + { + if (0 == advance) + { + tick_warp_callback = null; + tick_warp_sender = null; + finish_time_warp(); + return Messenger.c("gi Warp interrupted"); + } + if (time_bias > 0) + { + return Messenger.c("l Another player is already advancing time at the moment. Try later or talk to them"); + } + time_advancerer = player; + time_warp_start_time = System.nanoTime(); + time_warp_scheduled_ticks = advance; + time_bias = advance; + tick_warp_callback = callback; + tick_warp_sender = source; + return Messenger.c("gi Warp speed ...."); + } + + public static void finish_time_warp() + { + + long completed_ticks = time_warp_scheduled_ticks - time_bias; + double milis_to_complete = System.nanoTime()-time_warp_start_time; + if (milis_to_complete == 0.0) + { + milis_to_complete = 1.0; + } + milis_to_complete /= 1000000.0; + int tps = (int) (1000.0D*completed_ticks/milis_to_complete); + double mspt = (1.0*milis_to_complete)/completed_ticks; + time_warp_scheduled_ticks = 0; + time_warp_start_time = 0; + if (tick_warp_callback != null) + { + CommandManager icommandmanager = tick_warp_sender.getMinecraftServer().getCommandManager(); + try + { + int j = icommandmanager.execute(tick_warp_sender, tick_warp_callback); + + if (j < 1) + { + if (time_advancerer != null) + { + Messenger.m(time_advancerer, "r Command Callback failed: ", "rb /"+tick_warp_callback,"/"+tick_warp_callback); + } + } + } + catch (Throwable var23) + { + if (time_advancerer != null) + { + Messenger.m(time_advancerer, "r Command Callback failed - unknown error: ", "rb /"+tick_warp_callback,"/"+tick_warp_callback); + } + } + tick_warp_callback = null; + tick_warp_sender = null; + } + if (time_advancerer != null) + { + Messenger.m(time_advancerer, String.format("gi ... Time warp completed with %d tps, or %.2f mspt",tps, mspt )); + time_advancerer = null; + } + else + { + Messenger.print_server_message(CarpetServer.minecraft_server, String.format("... Time warp completed with %d tps, or %.2f mspt",tps, mspt )); + } + time_bias = 0; + + } + + public static boolean continueWarp() + { + if (time_bias > 0) + { + if (time_bias == time_warp_scheduled_ticks) //first call after previous tick, adjust start time + { + time_warp_start_time = System.nanoTime(); + } + time_bias -= 1; + return true; + } + else + { + finish_time_warp(); + return false; + } + } + + public static void tick(MinecraftServer server) + { + process_entities = true; + if (player_active_timeout > 0) + { + player_active_timeout--; + } + if (is_paused) + { + if (player_active_timeout < PLAYER_GRACE) + { + process_entities = false; + } + } + else if (is_superHot) + { + if (player_active_timeout <= 0) + { + process_entities = false; + + } + } + + + } +} + diff --git a/src/main/java/carpet/helpers/XPcombine.java b/src/main/java/carpet/helpers/XPcombine.java new file mode 100644 index 00000000..9a08affd --- /dev/null +++ b/src/main/java/carpet/helpers/XPcombine.java @@ -0,0 +1,126 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import carpet.fakes.ExperienceOrbInterface; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ExperienceOrbEntity; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Vec3d; + +public class XPcombine +{ + public static void searchForOtherXPNearbyCarpet(ExperienceOrbEntity first) + { + for (ExperienceOrbEntity entityxp : first.world.getEntities(ExperienceOrbEntity.class, first.getBoundingBox().expand(0.5D, 0.0D, 0.5D))) + { + combineItems(first, entityxp); + } + } + private static long tone = 0; + private static boolean combineItems(ExperienceOrbEntity first, ExperienceOrbEntity other) + { + if ( + first == other || first.world.isClient + || !first.isAlive() || !other.isAlive() + || first.pickupDelay == 32767 || other.pickupDelay == 32767 + || first.age == -32768 || other.age == -32768 + || ((ExperienceOrbInterface)first).getCombineDelay() != 0 || ((ExperienceOrbInterface)other).getCombineDelay() != 0 + ) + { + return false; + } + + int size = getTextureByXP(first.getExperienceAmount()); + ((ExperienceOrbInterface) first).setAmount(first.getExperienceAmount() + other.getExperienceAmount()); + ((ExperienceOrbInterface) first).setCombineDelay(Math.max(first.pickupDelay, other.pickupDelay)); + first.age = Math.min(first.age, other.age); + other.remove(); + + + ExperienceOrbEntity newOrb; + if (getTextureByXP(first.getExperienceAmount()) != size) + { + newOrb = new ExperienceOrbEntity(EntityType.EXPERIENCE_ORB, first.world); + newOrb.setPositionAndAngles(first.x, first.y, first.z, first.yaw, first.pitch); + ((ExperienceOrbInterface)newOrb).setAmount(first.getExperienceAmount()); + + first.world.spawnEntity(newOrb); + first.remove(); + } + else + { + ((ExperienceOrbInterface) first).setCombineDelay(50); + newOrb = first; + } + newOrb.setVelocity(first.getVelocity().add(new Vec3d( + (first.world.random.nextDouble() * 1.0D - 0.5D), + (first.world.random.nextDouble() * 0.5D), + (first.world.random.nextDouble() * 1.0D - 0.5D) + ))); + double pitch = 0.5+Math.log(1+(tone%12))/Math.log(12); + + tone++; + + CarpetSettings.LOG.error("Playing tone: "+pitch); + + newOrb.world.playSoundFromEntity( + null, + newOrb, + SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, + SoundCategory.BLOCKS, (float)pitch , (float)pitch); //newOrb.world.random.nextFloat()*0.5F+0.5F + + /*newOrb.world.playSound(newOrb.x, newOrb.y, newOrb.z, + SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, + SoundCategory.BLOCKS, + (float)pitch, + newOrb.world.random.nextFloat()*0.5F+0.5F, + true);*/ + return true; + } + + // COPY FROM CLIENT CODE + private static int getTextureByXP(int xpValue) + { + if (xpValue >= 2477) + { + return 10; + } + else if (xpValue >= 1237) + { + return 9; + } + else if (xpValue >= 617) + { + return 8; + } + else if (xpValue >= 307) + { + return 7; + } + else if (xpValue >= 149) + { + return 6; + } + else if (xpValue >= 73) + { + return 5; + } + else if (xpValue >= 37) + { + return 4; + } + else if (xpValue >= 17) + { + return 3; + } + else if (xpValue >= 7) + { + return 2; + } + else + { + return xpValue >= 3 ? 1 : 0; + } + } +} diff --git a/src/main/java/carpet/logging/HUDLogger.java b/src/main/java/carpet/logging/HUDLogger.java new file mode 100644 index 00000000..a526a0c6 --- /dev/null +++ b/src/main/java/carpet/logging/HUDLogger.java @@ -0,0 +1,29 @@ +package carpet.logging; + +import carpet.utils.HUDController; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; + +public class HUDLogger extends Logger +{ + public HUDLogger(String logName, String def, String[] options) + { + super(logName, def, options); + } + + @Override + public void removePlayer(String playerName) + { + PlayerEntity player = playerFromName(playerName); + if (player != null) HUDController.clear_player(player); + super.removePlayer(playerName); + } + + @Override + public void sendPlayerMessage(PlayerEntity player, BaseComponent... messages) + { + for (BaseComponent m:messages) HUDController.addMessage(player, m); + } + + +} diff --git a/src/main/java/carpet/logging/Logger.java b/src/main/java/carpet/logging/Logger.java new file mode 100644 index 00000000..963abb23 --- /dev/null +++ b/src/main/java/carpet/logging/Logger.java @@ -0,0 +1,193 @@ +package carpet.logging; + +import carpet.CarpetServer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class Logger +{ + // The set of subscribed and online players. + private Map subscribedOnlinePlayers; + + // The set of subscribed and offline players. + private Map subscribedOfflinePlayers; + + // The logName of this log. Gets prepended to logged messages. + private String logName; + + private String default_option; + + private String[] options; + + public Logger(String logName, String def, String [] options) + { + subscribedOnlinePlayers = new HashMap<>(); + subscribedOfflinePlayers = new HashMap<>(); + this.logName = logName; + this.default_option = def; + this.options = options; + } + + public String getDefault() + { + return default_option; + } + public String [] getOptions() + { + if (options == null) + { + return new String[0]; + } + return options; + } + public String getLogName() + { + return logName; + } + + /** + * Subscribes the player with the given logName to the logger. + */ + public void addPlayer(String playerName, String option) + { + if (playerFromName(playerName) != null) + { + subscribedOnlinePlayers.put(playerName, option); + } + else + { + subscribedOfflinePlayers.put(playerName, option); + } + LoggerRegistry.setAccess(this); + } + + /** + * Unsubscribes the player with the given logName from the logger. + */ + public void removePlayer(String playerName) + { + subscribedOnlinePlayers.remove(playerName); + subscribedOfflinePlayers.remove(playerName); + LoggerRegistry.setAccess(this); + } + + /** + * Returns true if there are any online subscribers for this log. + */ + public boolean hasOnlineSubscribers() + { + return subscribedOnlinePlayers.size() > 0; + } + + /** + * serves messages to players fetching them from the promise + * will repeat invocation for players that share the same option + */ + @FunctionalInterface + public interface lMessage { BaseComponent [] get(String playerOption, PlayerEntity player);} + public void log(lMessage messagePromise) + { + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + BaseComponent [] messages = messagePromise.get(en.getValue(),player); + if (messages != null) + sendPlayerMessage(player, messages); + } + } + } + + /** + * guarantees that each message for each option will be evaluated once from the promise + * and served the same way to all other players subscribed to the same option + */ + @FunctionalInterface + public interface lMessageIgnorePlayer { BaseComponent [] get(String playerOption);} + public void log(lMessageIgnorePlayer messagePromise) + { + Map cannedMessages = new HashMap<>(); + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + String option = en.getValue(); + if (!cannedMessages.containsKey(option)) + { + cannedMessages.put(option,messagePromise.get(option)); + } + BaseComponent [] messages = cannedMessages.get(option); + if (messages != null) + sendPlayerMessage(player, messages); + } + } + } + /** + * guarantees that message is evaluated once, so independent from the player and chosen option + */ + public void log(Supplier messagePromise) + { + BaseComponent [] cannedMessages = null; + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + if (cannedMessages == null) cannedMessages = messagePromise.get(); + sendPlayerMessage(player, cannedMessages); + } + } + } + + public void sendPlayerMessage(PlayerEntity player, BaseComponent ... messages) + { + Arrays.stream(messages).forEach(player::sendMessage); + } + + /** + * Gets the {@code PlayerEntity} instance for a player given their UUID. Returns null if they are offline. + */ + protected PlayerEntity playerFromName(String name) + { + return CarpetServer.minecraft_server.getPlayerManager().getPlayer(name); + } + + // ----- Event Handlers ----- // + + public void onPlayerConnect(PlayerEntity player) + { + // If the player was subscribed to the log and offline, move them to the set of online subscribers. + String playerName = player.getName().getString(); + if (subscribedOfflinePlayers.containsKey(playerName)) + { + subscribedOnlinePlayers.put(playerName, subscribedOfflinePlayers.get(playerName)); + subscribedOfflinePlayers.remove(playerName); + } + LoggerRegistry.setAccess(this); + } + + public void onPlayerDisconnect(PlayerEntity player) + { + // If the player was subscribed to the log, move them to the set of offline subscribers. + String playerName = player.getName().getString(); + if (subscribedOnlinePlayers.containsKey(playerName)) + { + subscribedOfflinePlayers.put(playerName, subscribedOnlinePlayers.get(playerName)); + subscribedOnlinePlayers.remove(playerName); + } + LoggerRegistry.setAccess(this); + } + + public String getAcceptedOption(String arg) + { + if (options != null && Arrays.asList(options).contains(arg)) return arg; + return null; + } +} diff --git a/src/main/java/carpet/logging/LoggerRegistry.java b/src/main/java/carpet/logging/LoggerRegistry.java new file mode 100644 index 00000000..8628e4a5 --- /dev/null +++ b/src/main/java/carpet/logging/LoggerRegistry.java @@ -0,0 +1,156 @@ +package carpet.logging; + +import carpet.CarpetSettings; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.DyeColor; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class LoggerRegistry +{ + // Map from logger names to loggers. + private static Map loggerRegistry = new HashMap<>(); + // Map from player names to the set of names of the logs that player is subscribed to. + private static Map> playerSubscriptions = new HashMap<>(); + //statics to quickly asses if its worth even to call each one + public static boolean __tnt; + public static boolean __projectiles; + public static boolean __fallingBlocks; + public static boolean __kills; + public static boolean __tps; + public static boolean __counter; + public static boolean __mobcaps; + public static boolean __damage; + public static boolean __packets; + public static boolean __weather; + + public static void initLoggers() + { + registerLogger("tnt", new Logger("tnt", "brief", new String[]{"brief", "full"})); + registerLogger("projectiles", new Logger("projectiles", "brief", new String[]{"brief", "full"})); + registerLogger("fallingBlocks",new Logger("fallingBlocks", "brief", new String[]{"brief", "full"})); + registerLogger("kills", new Logger("kills", null, null)); + registerLogger("damage", new Logger("damage", "all", new String[]{"all","players","me"})); + registerLogger("weather", new Logger("weather", null, null)); + + registerLogger("tps", new HUDLogger("tps", null, null)); + registerLogger("packets", new HUDLogger("packets", null, null)); + registerLogger("counter",new HUDLogger("counter","white", Arrays.stream(DyeColor.values()).map(Object::toString).toArray(String[]::new))); + registerLogger("mobcaps", new HUDLogger("mobcaps", "dynamic",new String[]{"dynamic", "overworld", "nether","end"})); + } + + /** + * Gets the logger with the given name. Returns null if no such logger exists. + */ + public static Logger getLogger(String name) { return loggerRegistry.get(name); } + + /** + * Gets the set of logger names. + */ + public static Set getLoggerNames() { return loggerRegistry.keySet(); } + + /** + * Subscribes the player with name playerName to the log with name logName. + */ + public static void subscribePlayer(String playerName, String logName, String option) + { + if (!playerSubscriptions.containsKey(playerName)) playerSubscriptions.put(playerName, new HashMap<>()); + Logger log = loggerRegistry.get(logName); + if (option == null) option = log.getDefault(); + playerSubscriptions.get(playerName).put(logName,option); + log.addPlayer(playerName, option); + } + + /** + * Unsubscribes the player with name playerName from the log with name logName. + */ + public static void unsubscribePlayer(String playerName, String logName) + { + if (playerSubscriptions.containsKey(playerName)) + { + Map subscriptions = playerSubscriptions.get(playerName); + subscriptions.remove(logName); + loggerRegistry.get(logName).removePlayer(playerName); + if (subscriptions.size() == 0) playerSubscriptions.remove(playerName); + } + } + + /** + * If the player is not subscribed to the log, then subscribe them. Otherwise, unsubscribe them. + */ + public static boolean togglePlayerSubscription(String playerName, String logName) + { + if (playerSubscriptions.containsKey(playerName) && playerSubscriptions.get(playerName).containsKey(logName)) + { + unsubscribePlayer(playerName, logName); + return false; + } + else + { + subscribePlayer(playerName, logName, null); + return true; + } + } + + /** + * Get the set of logs the current player is subscribed to. + */ + public static Map getPlayerSubscriptions(String playerName) + { + if (playerSubscriptions.containsKey(playerName)) + { + return playerSubscriptions.get(playerName); + } + return null; + } + + protected static void setAccess(Logger logger) + { + String name = logger.getLogName(); + boolean value = logger.hasOnlineSubscribers(); + try + { + Field f = LoggerRegistry.class.getDeclaredField("__"+name); + f.setBoolean(null, value); + } + catch (IllegalAccessException e) + { + CarpetSettings.LOG.error("Cannot change logger quick access field"); + } + catch (NoSuchFieldException e) + { + CarpetSettings.LOG.error("Wrong logger name"); + } + } + /** + * Called when the server starts. Creates the logs used by Carpet mod. + */ + private static void registerLogger(String name, Logger logger) + { + loggerRegistry.put(name, logger); + setAccess(logger); + } + + public static void playerConnected(PlayerEntity player) + { + for(Logger log: loggerRegistry.values() ) + { + log.onPlayerConnect(player); + } + + } + public static void playerDisconnected(PlayerEntity player) + { + for(Logger log: loggerRegistry.values() ) + { + log.onPlayerDisconnect(player); + } + } + + + +} diff --git a/src/main/java/carpet/logging/logHelpers/DamageReporter.java b/src/main/java/carpet/logging/logHelpers/DamageReporter.java new file mode 100644 index 00000000..943a8a30 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/DamageReporter.java @@ -0,0 +1,102 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.network.chat.BaseComponent; + +import java.util.function.Supplier; + +public class DamageReporter +{ + private static BaseComponent[] verifyAndProduceMessage(String option, PlayerEntity player, Entity from, Entity to, Supplier messageFuture) + { + if ("all".equalsIgnoreCase(option) + || ("players".equalsIgnoreCase(option) && (from instanceof PlayerEntity || to instanceof PlayerEntity)) + || ("me".equalsIgnoreCase(option) && ( (from == player) || ( to == player) ) )) + { + return new BaseComponent[]{messageFuture.get()}; + } + return null; + } + + public static boolean register_damage_attacker(Entity target, LivingEntity source, float amount) + { + if (!LoggerRegistry.__damage) return true; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source, target, () -> + Messenger.c(source.getDisplayName(), + "g attacking ", target.getDisplayName(),"g for ", + String.format("r %.2f",amount), "g points of damage") + ) + ); + return true; + } + + public static void register_damage(LivingEntity target, DamageSource source, float amount) + { + if (!LoggerRegistry.__damage) return; + if (source.isFire() && (target.isFireImmune() || + target.hasStatusEffect(StatusEffects.FIRE_RESISTANCE))) + return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getSource(), target, () -> + Messenger.c(target.getDisplayName(), + "g receiving ", + String.format("r %.2f", amount), + String.format("g points of damage from %s", source.getName())) + ) + ); + } + + public static void register_final_damage(LivingEntity target, DamageSource source, float amount) + { + if (!LoggerRegistry.__damage) return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getSource(), target, () -> + Messenger.c("g - total received ", + String.format("r %.2f", amount), + "g points of damage") + ) + ); + } + + public static void modify_damage(LivingEntity target, DamageSource source, float previous_amount, float final_amount, String component) + { + if (!LoggerRegistry.__damage) + return; + if (previous_amount == final_amount) + return; + if (source.isFire() && (target.isFireImmune() || + target.hasStatusEffect(StatusEffects.FIRE_RESISTANCE))) + return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getSource(), target, () -> + { + if (final_amount == 0.0f) + { + return Messenger.c("g - reduced to ","r 0.0","g due to: "+component); + } + else if (previous_amount > final_amount) + { + float reduction = previous_amount-final_amount; + return Messenger.c("g - reduced to ", + String.format("l %.2f",final_amount), + String.format("g by %.2f (%.1f%% less) due to: %s",reduction,100.0*reduction/previous_amount, component)); + } + else + { + float increase = final_amount-previous_amount; + return Messenger.c("g - increased to ", + String.format("r %.2f",final_amount), + String.format("g by %.2f (%.1f%% more) due to: %s",increase,100.0*increase/previous_amount, component)); + } + } + ) + ); + } +} diff --git a/src/main/java/carpet/logging/logHelpers/KillLogHelper.java b/src/main/java/carpet/logging/logHelpers/KillLogHelper.java new file mode 100644 index 00000000..8c4080b7 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/KillLogHelper.java @@ -0,0 +1,26 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.network.chat.BaseComponent; + +public class KillLogHelper { + + public static void onSweep(String name, int count) + { + LoggerRegistry.getLogger("kills").log( () -> new BaseComponent[]{ + Messenger.c("g "+name+" smacked ","r "+count,"g entities with sweeping")}); + } + + public static void onNonSweepAttack(String name) + { + LoggerRegistry.getLogger("kills").log( () -> new BaseComponent[]{ + Messenger.c("g "+name+" smacked ","r 1","g (no sweeping)")}); + } + + public static void onDudHit(String name) + { + LoggerRegistry.getLogger("kills").log( () -> new BaseComponent[]{ + Messenger.c("g "+name+" dud hot = no one affected")}); + } +} diff --git a/src/main/java/carpet/logging/logHelpers/PacketCounter.java b/src/main/java/carpet/logging/logHelpers/PacketCounter.java new file mode 100644 index 00000000..1e5bf00f --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/PacketCounter.java @@ -0,0 +1,8 @@ +package carpet.logging.logHelpers; + +public class PacketCounter +{ + public static long totalOut=0; + public static long totalIn=0; + public static void reset() {totalIn = 0l; totalOut = 0L; } +} diff --git a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java new file mode 100644 index 00000000..acbf8b00 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java @@ -0,0 +1,41 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.network.chat.BaseComponent; + +public class TNTLogHelper +{ + private double primedX, primedY, primedZ, primedAngle; + /** + * Runs when the TNT is primed. Expects the position and motion angle of the TNT. + */ + public void onPrimed(double x, double y, double z, float angle) + { + primedX = x; + primedY = y; + primedZ = z; + primedAngle = angle; + } + /** + * Runs when the TNT explodes. Expects the position of the TNT. + */ + public void onExploded(double x, double y, double z) + { + LoggerRegistry.getLogger("tnt").log( (option) -> { + switch (option) + { + case "brief": + return new BaseComponent[]{Messenger.c( + "l P ",Messenger.dblt("l",primedX,primedY,primedZ,primedAngle), + "r E ",Messenger.dblt("r",x, y, z))}; + case "full": + return new BaseComponent[]{Messenger.c( + "l P ",Messenger.dblf("l",primedX,primedY,primedZ,primedAngle), + "r E ",Messenger.dblf("r",x, y, z))}; + } + return null; + }); + } + +} diff --git a/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java b/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java new file mode 100644 index 00000000..24726e27 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java @@ -0,0 +1,80 @@ +package carpet.logging.logHelpers; + +import carpet.logging.Logger; +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.chat.BaseComponent; + +import java.util.ArrayList; +import java.util.List; + +/** + * A generic log helper for logging the trajectory of things like blocks and throwables. + */ +public class TrajectoryLogHelper +{ + private static final int MAX_TICKS_PER_LINE = 20; + + private boolean doLog; + private Logger logger; + + private ArrayList positions = new ArrayList<>(); + private ArrayList motions = new ArrayList<>(); + + public TrajectoryLogHelper(String logName) + { + this.logger = LoggerRegistry.getLogger(logName); + this.doLog = this.logger.hasOnlineSubscribers(); + } + + public void onTick(double x, double y, double z, double motionX, double motionY, double motionZ) + { + if (!doLog) return; + positions.add(new Vec3d(x, y, z)); + motions.add(new Vec3d(motionX, motionY, motionZ)); + } + + public void onFinish() + { + if (!doLog) return; + logger.log( (option) -> { + List comp = new ArrayList<>(); + switch (option) + { + case "brief": + comp.add(Messenger.s("")); + List line = new ArrayList<>(); + + for (int i = 0; i < positions.size(); i++) + { + Vec3d pos = positions.get(i); + Vec3d mot = motions.get(i); + line.add("w x"); + line.add(String.format("^w Tick: %d\nx: %f\ny: %f\nz: %f\n------------\nmx: %f\nmy: %f\nmz: %f", + i, pos.x, pos.y, pos.z, mot.x, mot.y, mot.z)); + if ((((i+1) % MAX_TICKS_PER_LINE)==0) || i == positions.size()-1) + { + comp.add(Messenger.c(line.toArray(new Object[0]))); + line.clear(); + } + } + break; + case "full": + comp.add(Messenger.c("w ---------")); + for (int i = 0; i < positions.size(); i++) + { + Vec3d pos = positions.get(i); + Vec3d mot = motions.get(i); + comp.add(Messenger.c( + String.format("w tick: %3d pos",i),Messenger.dblt("w",pos.x, pos.y, pos.z), + "w mot",Messenger.dblt("w",mot.x, mot.y, mot.z))); + } + break; + } + return comp.toArray(new BaseComponent[0]); + }); + doLog = false; + } +} + diff --git a/src/main/java/carpet/mixins/ExperienceOrbEntityMixin.java b/src/main/java/carpet/mixins/ExperienceOrbEntityMixin.java new file mode 100644 index 00000000..6f20fa66 --- /dev/null +++ b/src/main/java/carpet/mixins/ExperienceOrbEntityMixin.java @@ -0,0 +1,47 @@ +package carpet.mixins; + +import carpet.CarpetSettings; +import carpet.fakes.ExperienceOrbInterface; +import carpet.helpers.XPcombine; +import net.minecraft.entity.ExperienceOrbEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ExperienceOrbEntity.class) +public abstract class ExperienceOrbEntityMixin implements ExperienceOrbInterface +{ + @Shadow private int amount; + + public void setAmount(int amount) { this.amount = amount; } + + public int combineDelay = 50; + + public int getCombineDelay() { return combineDelay; } + public void setCombineDelay(int what) { combineDelay = what; } + + @Inject(method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/ExperienceOrbEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V", + shift = At.Shift.AFTER + )) + void checkCombineAtTick(CallbackInfo ci) + { + if (CarpetSettings.getBool("combineXPOrbs")) + { + if (getCombineDelay() > 0) + { + setCombineDelay(getCombineDelay()-1); + } + + if (getCombineDelay() == 0) + { + XPcombine.searchForOtherXPNearbyCarpet((ExperienceOrbEntity) (Object) this); + } + } + } + +} diff --git a/src/main/java/carpet/mixins/IntegratedServerMixin.java b/src/main/java/carpet/mixins/IntegratedServerMixin.java new file mode 100644 index 00000000..1e7ba79c --- /dev/null +++ b/src/main/java/carpet/mixins/IntegratedServerMixin.java @@ -0,0 +1,23 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import net.minecraft.server.integrated.IntegratedServer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(IntegratedServer.class) +public class IntegratedServerMixin +{ + @Inject(method = "setupServer", + at = @At( + value = "INVOKE", + shift = At.Shift.AFTER, + target = "Lnet/minecraft/server/integrated/IntegratedServer;setKeyPair(Ljava/security/KeyPair;)V" + ) + ) + private void onSetupServerIntegrated(CallbackInfoReturnable cir) { + CarpetServer.onServerLoaded((IntegratedServer) (Object) this); + } +} diff --git a/src/main/java/carpet/mixins/MinecraftClientMixin.java b/src/main/java/carpet/mixins/MinecraftClientMixin.java new file mode 100644 index 00000000..806c6d1d --- /dev/null +++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java @@ -0,0 +1,21 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin +{ + //to inject right before + // this.tickWorlds(booleanSupplier_1); + @Inject(method = "init", at = @At(value = "RETURN") + ) + private void onInit(CallbackInfo ci) { + //CM start game hook + CarpetServer.onGameStarted(); + } +} diff --git a/src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java b/src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java new file mode 100644 index 00000000..a169081b --- /dev/null +++ b/src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java @@ -0,0 +1,30 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import net.minecraft.server.dedicated.MinecraftDedicatedServer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(MinecraftDedicatedServer.class) +public class MinecraftDedicatedServerMixin +{ + //to inject right before + // this.tickWorlds(booleanSupplier_1); + @Inject( + method = "setupServer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/UserCache;setUseRemote(Z)V", + shift = At.Shift.AFTER, + ordinal = 0 + ) + ) + private void onSetupServer(CallbackInfoReturnable cir) { + //CM init - all stuff loaded from the server, just before worlds loading + CarpetServer.onServerLoaded((MinecraftDedicatedServer) (Object) this); + //CM start game hook + CarpetServer.onGameStarted(); + } +} diff --git a/src/main/java/carpet/mixins/MinecraftServerMixin.java b/src/main/java/carpet/mixins/MinecraftServerMixin.java index 8436a314..d2989430 100644 --- a/src/main/java/carpet/mixins/MinecraftServerMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServerMixin.java @@ -21,22 +21,27 @@ import java.util.function.BooleanSupplier; @Mixin(MinecraftServer.class) public abstract class MinecraftServerMixin { - // Called during game start - @Inject(method = "", at = @At(value = "HEAD")) + @Inject(method = "", at = @At(value = "RETURN")) private void onMinecraftServerCTOR(File file_1, Proxy proxy_1, DataFixer dataFixer_1, CommandManager serverCommandManager_1, YggdrasilAuthenticationService yggdrasilAuthenticationService_1, MinecraftSessionService minecraftSessionService_1, GameProfileRepository gameProfileRepository_1, UserCache userCache_1, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory_1, String string_1, CallbackInfo ci) { + // Called during game start CarpetServer.init((MinecraftServer) (Object) this); } //to inject right before // this.tickWorlds(booleanSupplier_1); @Inject( - method = "Lnet/minecraft/server/MinecraftServer;tick(Ljava/util/function/BooleanSupplier;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickWorlds(Ljava/util/function/BooleanSupplier;)V", shift = At.Shift.BEFORE, ordinal = 0) + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/MinecraftServer;tickWorlds(Ljava/util/function/BooleanSupplier;)V", + shift = At.Shift.BEFORE, + ordinal = 0 + ) ) private void onTick(BooleanSupplier booleanSupplier_1, CallbackInfo ci) { CarpetServer.tick((MinecraftServer) (Object) this); diff --git a/src/main/java/carpet/mixins/PlayerActionC2SPacketMixin.java b/src/main/java/carpet/mixins/PlayerActionC2SPacketMixin.java new file mode 100644 index 00000000..444d13df --- /dev/null +++ b/src/main/java/carpet/mixins/PlayerActionC2SPacketMixin.java @@ -0,0 +1,20 @@ +package carpet.mixins; + +import net.minecraft.server.network.packet.PlayerActionC2SPacket; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerActionC2SPacket.class) +public interface PlayerActionC2SPacketMixin +{ + @Accessor("pos") + void setPos(BlockPos pos); + + @Accessor("direction") + void setDirection(Direction direction); + + @Accessor("action") + void setAction(PlayerActionC2SPacket.Action action); +} diff --git a/src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java b/src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java new file mode 100644 index 00000000..dbfb4563 --- /dev/null +++ b/src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java @@ -0,0 +1,16 @@ +package carpet.mixins; + +import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerListHeaderS2CPacket.class) +public interface PlayerListHeaderS2CPacketMixin +{ + @Accessor("header") + void setHeader(Component header); + + @Accessor("footer") + void setFooter(Component footer); +} \ No newline at end of file diff --git a/src/main/java/carpet/mixins/ServerPlayerEntityMixin.java b/src/main/java/carpet/mixins/ServerPlayerEntityMixin.java new file mode 100644 index 00000000..88fe4345 --- /dev/null +++ b/src/main/java/carpet/mixins/ServerPlayerEntityMixin.java @@ -0,0 +1,43 @@ +package carpet.mixins; + +import carpet.fakes.ServerPlayerEntityInterface; +import carpet.helpers.EntityPlayerActionPack; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.server.world.ServerWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerPlayerEntity.class) +public abstract class ServerPlayerEntityMixin implements ServerPlayerEntityInterface +{ + public EntityPlayerActionPack actionPack; + public EntityPlayerActionPack getActionPack() + { + return actionPack; + } + + @Inject(method = "", at = @At(value = "RETURN")) + private void onServerPlayerEntityContructor( + MinecraftServer minecraftServer_1, + ServerWorld serverWorld_1, + GameProfile gameProfile_1, + ServerPlayerInteractionManager serverPlayerInteractionManager_1, + CallbackInfo ci) + { + this.actionPack = new EntityPlayerActionPack((ServerPlayerEntity) (Object) this); + } + + @Inject(method = "tick", at = @At(value = "HEAD")) + private void onTick(CallbackInfo ci) + { + actionPack.onUpdate(); + } + + + +} diff --git a/src/main/java/carpet/mixins/WeightedPickerEntryMixin.java b/src/main/java/carpet/mixins/WeightedPickerEntryMixin.java new file mode 100644 index 00000000..a3fe5e38 --- /dev/null +++ b/src/main/java/carpet/mixins/WeightedPickerEntryMixin.java @@ -0,0 +1,12 @@ +package carpet.mixins; + +import net.minecraft.util.WeightedPicker; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(WeightedPicker.Entry.class) +public interface WeightedPickerEntryMixin +{ + @Accessor("weight") + int getWeight(); +} diff --git a/src/main/java/carpet/patches/EntityPlayerMPFake.java b/src/main/java/carpet/patches/EntityPlayerMPFake.java new file mode 100644 index 00000000..e01211c7 --- /dev/null +++ b/src/main/java/carpet/patches/EntityPlayerMPFake.java @@ -0,0 +1,119 @@ +package carpet.patches; + +import com.mojang.authlib.GameProfile; +import net.minecraft.block.entity.SkullBlockEntity; +import net.minecraft.client.network.packet.EntityPositionS2CPacket; +import net.minecraft.client.network.packet.EntitySetHeadYawS2CPacket; +import net.minecraft.client.network.packet.PlayerListS2CPacket; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerTask; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.GameMode; +import net.minecraft.world.dimension.DimensionType; +import carpet.fakes.ServerPlayerEntityInterface; +import carpet.utils.Messenger; + +public class EntityPlayerMPFake extends ServerPlayerEntity +{ + public Runnable fixStartingPosition = () -> {}; + + public static EntityPlayerMPFake createFake(String username, MinecraftServer server, double d0, double d1, double d2, double yaw, double pitch, DimensionType dimension, GameMode gamemode) + { + //prolly half of that crap is not necessary, but it works + ServerWorld worldIn = server.getWorld(dimension); + ServerPlayerInteractionManager interactionManagerIn = new ServerPlayerInteractionManager(worldIn); + GameProfile gameprofile = server.getUserCache().findByName(username); + if (gameprofile == null) + { + return null; + } + if (gameprofile.getProperties().containsKey("textures")) + { + gameprofile = SkullBlockEntity.loadProperties(gameprofile); + } + EntityPlayerMPFake instance = new EntityPlayerMPFake(server, worldIn, gameprofile, interactionManagerIn); + instance.fixStartingPosition = () -> instance.setPositionAndAngles(d0, d1, d2, (float) yaw, (float) pitch); + server.getPlayerManager().onPlayerConnect(new NetworkManagerFake(NetworkSide.SERVERBOUND), instance); + if (instance.dimension != dimension) //player was logged in in a different dimension + { + ServerWorld old_world = server.getWorld(instance.dimension); + instance.dimension = dimension; + old_world.removePlayer(instance); + instance.removed = false; + worldIn.spawnEntity(instance); + instance.setWorld(worldIn); + server.getPlayerManager().sendWorldInfo(instance, old_world); + instance.networkHandler.requestTeleport(d0, d1, d2, (float) yaw, (float) pitch); + instance.interactionManager.setWorld(worldIn); + } + instance.setHealth(20.0F); + instance.removed = false; + instance.networkHandler.requestTeleport(d0, d1, d2, (float) yaw, (float) pitch); + instance.stepHeight = 0.6F; + interactionManagerIn.setGameMode(gamemode); + server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(instance, (byte) (instance.headYaw * 256 / 360)), instance.dimension); + server.getPlayerManager().sendToDimension(new EntityPositionS2CPacket(instance), instance.dimension); + instance.getServerWorld().method_14178().updateCameraPosition(instance); + return instance; + } + + public static EntityPlayerMPFake createShadow(MinecraftServer server, ServerPlayerEntity player) + { + player.getServer().getPlayerManager().remove(player); + player.networkHandler.disconnect(new TranslatableComponent("multiplayer.disconnect.duplicate_login")); + ServerWorld worldIn = server.getWorld(player.dimension); + ServerPlayerInteractionManager interactionManagerIn = new ServerPlayerInteractionManager(worldIn); + GameProfile gameprofile = player.getGameProfile(); + EntityPlayerMPFake playerShadow = new EntityPlayerMPFake(server, worldIn, gameprofile, interactionManagerIn); + server.getPlayerManager().onPlayerConnect(new NetworkManagerFake(NetworkSide.SERVERBOUND), playerShadow); + + playerShadow.setHealth(player.getHealth()); + playerShadow.networkHandler.requestTeleport(player.x, player.y, player.z, player.yaw, player.pitch); + interactionManagerIn.setGameMode(player.interactionManager.getGameMode()); + ((ServerPlayerEntityInterface) playerShadow).getActionPack().copyFrom(((ServerPlayerEntityInterface) player).getActionPack()); + playerShadow.stepHeight = 0.6F; + + server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(playerShadow, (byte) (player.headYaw * 256 / 360)), playerShadow.dimension); + server.getPlayerManager().sendToAll(new PlayerListS2CPacket(PlayerListS2CPacket.Action.ADD_PLAYER, playerShadow)); + player.getServerWorld().method_14178().updateCameraPosition(playerShadow); + return playerShadow; + } + + private EntityPlayerMPFake(MinecraftServer server, ServerWorld worldIn, GameProfile profile, ServerPlayerInteractionManager interactionManagerIn) + { + super(server, worldIn, profile, interactionManagerIn); + } + + @Override + public void kill() + { + this.server.method_18858(new ServerTask(this.server.getTicks(), () -> { + this.networkHandler.onDisconnected(Messenger.s("Killed")); + })); + } + + @Override + public void tick() + { + if (this.getServer().getTicks() % 10 == 0) + { + this.networkHandler.syncWithPlayerPosition(); + this.getServerWorld().method_14178().updateCameraPosition(this); + } + super.tick(); + this.method_14226(); + } + + @Override + public void onDeath(DamageSource cause) + { + super.onDeath(cause); + setHealth(20); + kill(); + } +} diff --git a/src/main/java/carpet/patches/NetHandlerPlayServerFake.java b/src/main/java/carpet/patches/NetHandlerPlayServerFake.java new file mode 100644 index 00000000..aed9a2ae --- /dev/null +++ b/src/main/java/carpet/patches/NetHandlerPlayServerFake.java @@ -0,0 +1,29 @@ +package carpet.patches; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.Packet; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; + +public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler +{ + public NetHandlerPlayServerFake(MinecraftServer server, ClientConnection cc, ServerPlayerEntity playerIn) + { + super(server, cc, playerIn); + } + + @Override + public void sendPacket(final Packet packetIn) + { + } + + @Override + public void disconnect(Component message) + { + } +} + + + diff --git a/src/main/java/carpet/patches/NetworkManagerFake.java b/src/main/java/carpet/patches/NetworkManagerFake.java new file mode 100644 index 00000000..d094fca1 --- /dev/null +++ b/src/main/java/carpet/patches/NetworkManagerFake.java @@ -0,0 +1,22 @@ +package carpet.patches; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.NetworkSide; + +public class NetworkManagerFake extends ClientConnection +{ + public NetworkManagerFake(NetworkSide p) + { + super(p); + } + + @Override + public void disableAutoRead() + { + } + + @Override + public void handleDisconnection() + { + } +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/BlockInfo.java b/src/main/java/carpet/utils/BlockInfo.java new file mode 100644 index 00000000..e43d334b --- /dev/null +++ b/src/main/java/carpet/utils/BlockInfo.java @@ -0,0 +1,239 @@ +package carpet.utils; + +import net.minecraft.block.Block; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnType; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.block.Material; +import net.minecraft.block.MaterialColor; +import net.minecraft.block.BlockState; +import net.minecraft.entity.mob.MobEntityWithAi; +import net.minecraft.entity.ai.goal.WanderAroundGoal; +import net.minecraft.entity.ai.PathfindingUtil; +import net.minecraft.entity.mob.ZombiePigmanEntity; +import net.minecraft.block.BlockPlacementEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BlockInfo +{ + public static Map soundName = new HashMap() {{ + put(BlockSoundGroup.WOOD, "wood" ); + put(BlockSoundGroup.GRAVEL, "gravel"); + put(BlockSoundGroup.GRASS, "grass" ); + put(BlockSoundGroup.STONE, "stone" ); + put(BlockSoundGroup.METAL, "metal" ); + put(BlockSoundGroup.GLASS , "glass" ); + put(BlockSoundGroup.WOOL , "wool" ); + put(BlockSoundGroup.SAND , "sand" ); + put(BlockSoundGroup.SNOW , "snow" ); + put(BlockSoundGroup.LADDER, "ladder"); + put(BlockSoundGroup.ANVIL , "anvil" ); + put(BlockSoundGroup.SLIME , "slime" ); + put(BlockSoundGroup.WET_GRASS , "sea_grass" ); + put(BlockSoundGroup.CORAL , "coral" ); + put(BlockSoundGroup.BAMBOO , "bamboo" ); + put(BlockSoundGroup.BAMBOO_SAPLING , "shoots" ); + put(BlockSoundGroup.SCAFFOLDING , "scaffolding" ); + put(BlockSoundGroup.SWEET_BERRY_BUSH , "berry" ); + put(BlockSoundGroup.CROP , "crop" ); + put(BlockSoundGroup.field_18852 , "gourd" ); + put(BlockSoundGroup.NETHER_WART , "wart" ); + put(BlockSoundGroup.LANTERN , "lantern" ); + }}; + + public static Map mapColourName = new HashMap() {{ + put(MaterialColor.AIR , "air" ); + put(MaterialColor.GRASS , "grass" ); + put(MaterialColor.SAND , "sand" ); + put(MaterialColor.WEB , "wool" ); + put(MaterialColor.LAVA , "tnt" ); + put(MaterialColor.ICE , "ice" ); + put(MaterialColor.IRON , "iron" ); + put(MaterialColor.FOLIAGE , "foliage" ); + put(MaterialColor.WHITE , "snow" ); + put(MaterialColor.CLAY , "clay" ); + put(MaterialColor.DIRT , "dirt" ); + put(MaterialColor.STONE , "stone" ); + put(MaterialColor.WATER , "water" ); + put(MaterialColor.WOOD , "wood" ); + put(MaterialColor.QUARTZ , "quartz" ); + put(MaterialColor.ORANGE , "adobe" ); + put(MaterialColor.MAGENTA , "magenta" ); + put(MaterialColor.LIGHT_BLUE, "light_blue"); + put(MaterialColor.YELLOW , "yellow" ); + put(MaterialColor.LIME , "lime" ); + put(MaterialColor.PINK , "pink" ); + put(MaterialColor.GRAY , "gray" ); + put(MaterialColor.LIGHT_GRAY, "light_gray"); + put(MaterialColor.CYAN , "cyan" ); + put(MaterialColor.PURPLE , "purple" ); + put(MaterialColor.BLUE , "blue" ); + put(MaterialColor.BROWN , "brown" ); + put(MaterialColor.GREEN , "green" ); + put(MaterialColor.RED , "red" ); + put(MaterialColor.BLACK , "black" ); + put(MaterialColor.GOLD , "gold" ); + put(MaterialColor.DIAMOND , "diamond" ); + put(MaterialColor.LAPIS , "lapis" ); + put(MaterialColor.EMERALD , "emerald" ); + put(MaterialColor.SPRUCE , "obsidian" ); + put(MaterialColor.NETHER, "netherrack"); //TODO fix these + put(MaterialColor.WHITE_TERRACOTTA , "white_terracotta" ); + put(MaterialColor.ORANGE_TERRACOTTA , "orange_terracotta" ); + put(MaterialColor.MAGENTA_TERRACOTTA , "magenta_terracotta" ); + put(MaterialColor.LIGHT_BLUE_TERRACOTTA, "light_blue_terracotta" ); + put(MaterialColor.YELLOW_TERRACOTTA , "yellow_terracotta" ); + put(MaterialColor.LIME_TERRACOTTA , "lime_terracotta" ); + put(MaterialColor.PINK_TERRACOTTA , "pink_terracotta" ); + put(MaterialColor.GRAY_TERRACOTTA , "gray_terracotta" ); + put(MaterialColor.LIGHT_GRAY_TERRACOTTA, "light_gray_terracotta" ); + put(MaterialColor.CYAN_TERRACOTTA , "cyan_terracotta" ); + put(MaterialColor.PURPLE_TERRACOTTA , "purple_terracotta" ); + put(MaterialColor.BLUE_TERRACOTTA , "blue_terracotta" ); + put(MaterialColor.BROWN_TERRACOTTA , "brown_terracotta" ); + put(MaterialColor.GREEN_TERRACOTTA , "green_terracotta" ); + put(MaterialColor.RED_TERRACOTTA , "red_terracotta" ); + put(MaterialColor.BLACK_TERRACOTTA , "black_terracotta" ); + }}; + + public static Map materialName = new HashMap() {{ + put(Material.AIR , "air" ); + put(Material.STRUCTURE_VOID , "void" ); + put(Material.PORTAL , "portal" ); + put(Material.CARPET , "carpet" ); + put(Material.PLANT , "plant" ); + put(Material.UNDERWATER_PLANT , "water_plant" ); + put(Material.REPLACEABLE_PLANT , "vine" ); + put(Material.SEAGRASS , "sea_grass" ); + put(Material.WATER , "water" ); + put(Material.BUBBLE_COLUMN , "bubble_column"); + put(Material.LAVA , "lava" ); + put(Material.SNOW , "snow_layer" ); + put(Material.FIRE , "fire" ); + put(Material.PART , "redstone_bits"); + put(Material.COBWEB , "cobweb" ); + put(Material.REDSTONE_LAMP , "redstone_lamp"); + put(Material.CLAY , "clay" ); + put(Material.EARTH , "dirt" ); + put(Material.ORGANIC , "grass" ); + put(Material.PACKED_ICE , "packed_ice" ); + put(Material.SAND , "sand" ); + put(Material.SPONGE , "sponge" ); + put(Material.SHULKER_BOX , "wood" ); + put(Material.WOOD , "wool" ); // fix these + put(Material.BAMBOO_SAPLING , "shoots" ); + put(Material.BAMBOO , "bamboo" ); + put(Material.TNT , "tnt" ); + put(Material.LEAVES , "leaves" ); + put(Material.GLASS , "glass" ); + put(Material.ICE , "ice" ); + put(Material.CACTUS , "cactus" ); + put(Material.STONE , "stone" ); + put(Material.METAL , "iron" ); + put(Material.SNOW_BLOCK , "snow" ); + put(Material.ANVIL , "anvil" ); + put(Material.BARRIER , "barrier" ); + put(Material.PISTON , "piston" ); + put(Material.UNUSED_PLANT , "coral" ); + put(Material.PUMPKIN , "gourd" ); + put(Material.DRAGON_EGG , "dragon_egg" ); + put(Material.CAKE , "cake" ); + }}; + + public static List blockInfo(BlockPos pos, World world) + { + BlockState state = world.getBlockState(pos); + Material material = state.getMaterial(); + Block block = state.getBlock(); + String metastring = ""; + for (net.minecraft.state.property.Property iproperty : state.getProperties()) + { + metastring += ", "+iproperty.getName() + '='+state.get(iproperty); + } + List lst = new ArrayList<>(); + lst.add(Messenger.s("")); + lst.add(Messenger.s("=====================================")); + lst.add(Messenger.s(String.format("Block info for %s%s (id %d%s):",Registry.BLOCK.getId(block),metastring, Registry.BLOCK.getRawId(block), metastring ))); + lst.add(Messenger.s(String.format(" - Material: %s", materialName.get(material)))); + lst.add(Messenger.s(String.format(" - Map colour: %s", mapColourName.get(state.getTopMaterialColor(world, pos))))); + lst.add(Messenger.s(String.format(" - Sound type: %s", soundName.get(block.getSoundGroup(state))))); + lst.add(Messenger.s("")); + //lst.add(Messenger.s(String.format(" - Full block: %s", block.isShapeFullCube()))); // isFullCube() ))); + lst.add(Messenger.s(String.format(" - Normal cube: %s", state.isSimpleFullBlock(world, pos)))); //isNormalCube()))); + lst.add(Messenger.s(String.format(" - Is liquid: %s", material.isLiquid()))); + lst.add(Messenger.s(String.format(" - Is solid: %s", material.isSolid()))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Light in: %d, above: %d", world.getLightLevel(pos), world.getLightLevel(pos.up())))); + lst.add(Messenger.s(String.format(" - Brightness in: %.2f, above: %.2f", world.getBrightness(pos), world.getBrightness(pos.up())))); + lst.add(Messenger.s(String.format(" - Is opaque: %s", material.isSolid() ))); + //lst.add(Messenger.s(String.format(" - Light opacity: %d", state.getOpacity(world,pos)))); + //lst.add(Messenger.s(String.format(" - Blocks light: %s", state.propagatesSkylightDown(world, pos)))); + //lst.add(Messenger.s(String.format(" - Emitted light: %d", state.getLightValue()))); + //lst.add(Messenger.s(String.format(" - Picks neighbour light value: %s", state.useNeighborBrightness(world, pos)))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Causes suffocation: %s", state.canSuffocate(world, pos)))); + lst.add(Messenger.s(String.format(" - Blocks movement on land: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.LAND)))); + lst.add(Messenger.s(String.format(" - Blocks movement in air: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.AIR)))); + lst.add(Messenger.s(String.format(" - Blocks movement in liquids: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.WATER)))); + lst.add(Messenger.s(String.format(" - Can burn: %s", material.isBurnable()))); + lst.add(Messenger.s(String.format(" - Requires a tool: %s", !material.isReplaceable()))); //?maybe + lst.add(Messenger.s(String.format(" - Hardness: %.2f", state.getHardness(world, pos)))); + lst.add(Messenger.s(String.format(" - Blast resistance: %.2f", block.getBlastResistance()))); + lst.add(Messenger.s(String.format(" - Ticks randomly: %s", block.hasRandomTicks(state)))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Can provide power: %s", state.emitsRedstonePower()))); + lst.add(Messenger.s(String.format(" - Strong power level: %d", world.getReceivedStrongRedstonePower(pos)))); + lst.add(Messenger.s(String.format(" - Redstone power level: %d", world.getReceivedRedstonePower(pos)))); + lst.add(Messenger.s("")); + lst.add(wander_chances(pos.up(), world)); + + return lst; + } + + private static BaseComponent wander_chances(BlockPos pos, World worldIn) + { + MobEntityWithAi creature = new ZombiePigmanEntity(EntityType.ZOMBIE_PIGMAN, worldIn); + creature.initialize(worldIn, worldIn.getLocalDifficulty(pos), SpawnType.NATURAL, null, null); + creature.setPositionAndAngles(pos.getX()+0.5F, pos.getY(), pos.getZ()+0.5F, 0.0F, 0.0F); + WanderAroundGoal wander = new WanderAroundGoal(creature, 0.8D); + int success = 0; + for (int i=0; i<1000; i++) + { + + Vec3d vec = PathfindingUtil.findTarget(creature, 10, 7); + if (vec == null) + { + continue; + } + success++; + } + long total_ticks = 0; + for (int trie=0; trie<1000; trie++) + { + int i; + for (i=1;i<30*20*60; i++) //*60 used to be 5 hours, limited to 30 mins + { + if (wander.canStart()) + { + break; + } + } + total_ticks += 3*i; + } + creature.remove(); + long total_time = (total_ticks)/1000/20; + return Messenger.s(String.format(" - Wander chance above: %.1f%%%%\n - Average standby above: %s", + (100.0F*success)/1000, + ((total_time>5000)?"INFINITY":(Long.toString(total_time)+" s")) + )); + } +} diff --git a/src/main/java/carpet/utils/CarpetProfiler.java b/src/main/java/carpet/utils/CarpetProfiler.java new file mode 100644 index 00000000..59f4aa18 --- /dev/null +++ b/src/main/java/carpet/utils/CarpetProfiler.java @@ -0,0 +1,372 @@ +package carpet.utils; + +import carpet.CarpetSettings; +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.registry.Registry; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class CarpetProfiler +{ + private static final HashMap time_repo = new HashMap<>(); + public static int tick_health_requested = 0; + private static int tick_health_elapsed = 0; + private static int test_type = 0; //1 for ticks, 2 for entities; + private static String current_section = null; + private static long current_section_start = 0; + private static long current_tick_start = 0; + private static String [] GENERAL_SECTIONS = {"Network", "Autosave"}; + private static String [] DIMENSIONS = {"Overworld","The End","The Nether"}; + private static String [] SECTIONS = {"Spawning","Blocks","Entities","Tile Entities","Entities(client)","Tile Entities(client)","Villages"}; + + public static void prepare_tick_report(int ticks) + { + //maybe add so it only spams the sending player, but honestly - all may want to see it + time_repo.clear(); + test_type = 1; + time_repo.put("tick",0L); + time_repo.put("Network",0L); + time_repo.put("Autosave",0L); + for (String section: GENERAL_SECTIONS) + { + time_repo.put(section,0L); + } + for (String level: DIMENSIONS) + { + for (String section: SECTIONS) + { + time_repo.put(level+"."+section,0L); + } + } + + tick_health_elapsed = ticks; + tick_health_requested = ticks; + current_tick_start = 0L; + current_section_start = 0L; + current_section = null; + + } + + public static void start_section(String dimension, String name) + { + if (tick_health_requested == 0L || test_type != 1) + { + return; + } + if (current_tick_start == 0L) + { + return; + } + if (current_section != null) + { + end_current_section(); + } + String key = name; + if (dimension != null) + { + key = dimension+"."+name; + } + current_section = key; + current_section_start = System.nanoTime(); + } + + + public static class ProfilerToken + { + public String section; + public long start; + public ProfilerToken(String name, long start) + { + this.section = name; + this.start = start; + } + } + + public static ProfilerToken start_section_concurrent(String dimension, String name, boolean isRemote) + { + if (tick_health_requested == 0L || test_type != 1) + { + return null; + } + if (current_tick_start == 0L) + { + return null; + } + + String key = name; + if (dimension != null) + { + key = dimension+"."+name; + } + if (isRemote) key += "(client)"; + long time = System.nanoTime(); + return new ProfilerToken(key, time); + } + + public static ProfilerToken start_entity_section(String dimension, Entity e) + { + if (tick_health_requested == 0L || test_type != 2) + { + return null; + } + if (current_tick_start == 0L) + { + return null; + } + String section = dimension+"."+ Registry.ENTITY_TYPE.getId(e.getType()).toString().replaceFirst("minecraft:",""); + if (e.getEntityWorld().isClient) + section += "(client)"; + long section_start = System.nanoTime(); + return new ProfilerToken(section, section_start); + } + + public static ProfilerToken start_tileentity_section(String dimension, BlockEntity e) + { + if (tick_health_requested == 0L || test_type != 2) + { + return null; + } + if (current_tick_start == 0L) + { + return null; + } + String section = dimension+"."+ BlockEntityType.getId(e.getType()).toString().replaceFirst("minecraft:",""); + if (e.getWorld() == null) + section += "??"; + else if (e.getWorld().isClient) + section += "(client)"; + long section_start = System.nanoTime(); + return new ProfilerToken(section, section_start); + } + + public static void end_current_section() + { + if (tick_health_requested == 0L || test_type != 1) + { + return; + } + long end_time = System.nanoTime(); + if (current_tick_start == 0L) + { + return; + } + if (current_section == null) + { + CarpetSettings.LOG.error("finishing section that hasn't started"); + return; + } + //CarpetSettings.LOG.error("finishing section "+current_section); + time_repo.put(current_section,time_repo.get(current_section)+end_time-current_section_start); + current_section = null; + current_section_start = 0; + } + + public static void end_current_section_concurrent(ProfilerToken tok) + { + if (tick_health_requested == 0L || test_type != 1) + { + return; + } + long end_time = System.nanoTime(); + if (current_tick_start == 0L) + { + return; + } + if (tok == null) + { + CarpetSettings.LOG.error("finishing section that hasn't started"); + return; + } + //CarpetSettings.LOG.error("finishing section "+current_section); + time_repo.put(tok.section,time_repo.get(tok.section)+end_time-tok.start); + } + + public static void end_current_entity_section(ProfilerToken tok) + { + if (tick_health_requested == 0L || test_type != 2) + { + return; + } + long end_time = System.nanoTime(); + if (current_tick_start == 0L) + { + return; + } + if (tok == null) + { + CarpetSettings.LOG.error("finishing entity/TE section that hasn't started"); + return; + } + String time_section = "t."+tok.section; + String count_section = "c."+tok.section; + time_repo.put(time_section,time_repo.getOrDefault(time_section,0L)+end_time-tok.start); + time_repo.put(count_section,time_repo.getOrDefault(count_section,0L)+1); + } + + public static void start_tick_profiling() + { + current_tick_start = System.nanoTime(); + } + + public static void end_tick_profiling(MinecraftServer server) + { + if (current_tick_start == 0L) + { + return; + } + time_repo.put("tick",time_repo.get("tick")+System.nanoTime()-current_tick_start); + tick_health_elapsed --; + //CarpetSettings.LOG.error("tick count current at "+time_repo.get("tick")); + if (tick_health_elapsed <= 0) + { + finalize_tick_report(server); + } + } + + public static void finalize_tick_report(MinecraftServer server) + { + if (test_type == 1) + { + finalize_tick_report_for_time(server); + } + if (test_type == 2) + { + finalize_tick_report_for_entities(server); + } + cleanup_tick_report(); + } + + public static void cleanup_tick_report() + { + time_repo.clear(); + time_repo.put("tick",0L); + test_type = 0; + tick_health_elapsed = 0; + tick_health_requested = 0; + current_tick_start = 0L; + current_section_start = 0L; + current_section = null; + + } + + public static void finalize_tick_report_for_time(MinecraftServer server) + { + //print stats + long total_tick_time = time_repo.get("tick"); + double divider = 1.0D/tick_health_requested/1000000; + Messenger.print_server_message(server, String.format("Average tick time: %.3fms",divider*total_tick_time)); + long accumulated = 0L; + + for (String section: GENERAL_SECTIONS) + { + double amount = divider*time_repo.get(section); + if (amount > 0.01) + { + accumulated += time_repo.get(section); + Messenger.print_server_message(server, String.format("%s: %.3fms", section, amount)); + } + } + + for (String dimension: DIMENSIONS) + { + boolean hasSomethin = false; + for (String section: SECTIONS) + { + double amount = divider*time_repo.get(dimension+"."+section); + if (amount > 0.01) + { + hasSomethin = true; + break; + } + } + if (!(hasSomethin)) + { + continue; + } + Messenger.print_server_message(server, dimension+":"); + for (String section: SECTIONS) + { + double amount = divider*time_repo.get(dimension+"."+section); + if (amount > 0.01) + { + if (!(section.endsWith("(client)"))) + accumulated += time_repo.get(dimension+"."+section); + Messenger.print_server_message(server, String.format(" - %s: %.3fms", section, amount)); + } + } + } + + long rest = total_tick_time-accumulated; + + Messenger.print_server_message(server, String.format("The Rest, whatever that might be: %.3fms",divider*rest)); + } + + public static void finalize_tick_report_for_entities(MinecraftServer server) + { + //print stats + long total_tick_time = time_repo.get("tick"); + double divider = 1.0D/tick_health_requested/1000000; + double divider_1 = 1.0D/(tick_health_requested-1)/1000000; + Messenger.print_server_message(server, String.format("Average tick time: %.3fms",divider*total_tick_time)); + time_repo.remove("tick"); + Messenger.print_server_message(server, "Top 10 counts:"); + int total = 0; + for ( Map.Entry entry : time_repo.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()) ) + { + if (entry.getKey().startsWith("t.")) + { + continue; + } + total++; + if (total > 10) + { + continue; + } + String[] parts = entry.getKey().split("\\."); + String dim = parts[1]; + String name = parts[2]; + int penalty = name.endsWith("(client)") ? 1 :0; + Messenger.print_server_message(server, String.format(" - %s in %s: %.3f",name, dim, 1.0D*entry.getValue()/(tick_health_requested-penalty))); + } + Messenger.print_server_message(server, "Top 10 grossing:"); + total = 0; + for ( Map.Entry entry : time_repo.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()) ) + { + if (entry.getKey().startsWith("c.")) + { + continue; + } + total++; + if (total > 10) + { + continue; + } + String[] parts = entry.getKey().split("\\."); + String dim = parts[1]; + String name = parts[2]; + double applicableDivider = name.endsWith("(client)") ? divider :divider_1; + Messenger.print_server_message(server, String.format(" - %s in %s: %.3fms",name, dim, applicableDivider*entry.getValue())); + } + + } + + public static void prepare_entity_report(int ticks) + { + //maybe add so it only spams the sending player, but honestly - all may want to see it + time_repo.clear(); + time_repo.put("tick",0L); + test_type = 2; + tick_health_elapsed = ticks; + tick_health_requested = ticks; + current_tick_start = 0L; + current_section_start = 0L; + current_section = null; + + } +} diff --git a/src/main/java/carpet/utils/DistanceCalculator.java b/src/main/java/carpet/utils/DistanceCalculator.java new file mode 100644 index 00000000..6045e9e9 --- /dev/null +++ b/src/main/java/carpet/utils/DistanceCalculator.java @@ -0,0 +1,64 @@ +package carpet.utils; + +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.chat.BaseComponent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class DistanceCalculator +{ + public static final HashMap START_POINT_STORAGE = new HashMap<>(); + + public static boolean hasStartingPoint(ServerCommandSource source) + { + return START_POINT_STORAGE.containsKey(source.getName()); + } + + public static List findDistanceBetweenTwoPoints(Vec3d pos1, Vec3d pos2) + { + double dx = MathHelper.abs((float)pos1.x-(float)pos2.x); + double dy = MathHelper.abs((float)pos1.y-(float)pos2.y); + double dz = MathHelper.abs((float)pos1.z-(float)pos2.z); + double manhattan = dx+dy+dz; + double spherical = MathHelper.sqrt(dx*dx + dy*dy + dz*dz); + double cylindrical = MathHelper.sqrt(dx*dx + dz*dz); + List res = new ArrayList<>(); + res.add(Messenger.c("w Distance between ", + Messenger.tp("c",pos1),"w and ", + Messenger.tp("c",pos2),"w :")); + res.add(Messenger.c("w - Manhattan: ", String.format("wb %.1f", manhattan))); + res.add(Messenger.c("w - Spherical: ", String.format("wb %.2f", spherical))); + res.add(Messenger.c("w - Cylindrical: ", String.format("wb %.2f", cylindrical))); + return res; + } + + public static int distance(ServerCommandSource source, Vec3d pos1, Vec3d pos2) + { + Messenger.send(source, findDistanceBetweenTwoPoints(pos1, pos2)); + return 1; + } + + public static int setStart(ServerCommandSource source, Vec3d pos) + { + START_POINT_STORAGE.put(source.getName(), pos); + Messenger.m(source,"gi Initial point set to: ", Messenger.tp("g",pos)); + return 1; + } + + public static int setEnd(ServerCommandSource source, Vec3d pos) + { + if ( !hasStartingPoint(source) ) + { + START_POINT_STORAGE.put(source.getName(), pos); + Messenger.m(source,"gi There was no initial point for "+source.getName()); + Messenger.m(source,"gi Initial point set to: ", Messenger.tp("g",pos)); + return 0; + } + Messenger.send(source, findDistanceBetweenTwoPoints( START_POINT_STORAGE.get(source.getName()), pos)); + return 1; + } +} diff --git a/src/main/java/carpet/utils/EntityInfo.java b/src/main/java/carpet/utils/EntityInfo.java new file mode 100644 index 00000000..fccc9f3b --- /dev/null +++ b/src/main/java/carpet/utils/EntityInfo.java @@ -0,0 +1,101 @@ +package carpet.utils; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.Entity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +// not really needed anymore +public class EntityInfo +{ + private static String makeTime(long ticks) + { + long secs = ticks/20; + if (secs < 60) + { + return String.format("%d\"", secs); + } + if (secs < 60*60) + { + return String.format("%d'%d\"", secs/60, secs%60); + } + + return String.format("%dh%d'%d\"", secs/60/60, (secs % (60*60))/60,(secs % (60*60))%60 ); + } + + private static String display_item(ItemStack item) + { + if (item == null) + { + return null; + } + if (item.isEmpty()) // func_190926_b() + { + return null; + } // func_190916_E() + String stackname = item.getAmount()>1?String.format("%dx%s",item.getAmount(), item.getDisplayName().getString()):item.getDisplayName().getString(); + if (item.isDamaged()) + { + stackname += String.format(" %d/%d", item.getMaxUseTime()-item.getDamage(), item.getMaxUseTime()); + } + if (item.hasEnchantments()) + { + stackname += " ( "; + Map enchants = EnchantmentHelper.getEnchantments(item); + for (Enchantment e: enchants.keySet()) + { + int level = enchants.get(e); + String enstring = e.getTextComponent(level).getString(); + stackname += enstring+" "; + } + stackname += ")"; + } + return stackname; + } + + public static String entity_short_string(Entity e) + { + if (e == null) + { + return "None"; + } + return String.format("%s at [%.1f, %.1f, %.1f]",e.getDisplayName().getString(), e.x, e.y, e.y); + } + + private static double get_speed(double internal) + { + return 43.1*internal; + } + + private static double get_horse_speed_percent(double internal) + { + double min = 0.45*0.25; + double max = (0.45+0.9)*0.25; + return 100*(internal-min)/(max-min); + } + + private static double get_horse_jump(double x) + { + return -0.1817584952 * x*x*x + 3.689713992 * x*x + 2.128599134 * x - 0.343930367; + } + + private static double get_horse_jump_percent(double internal) + { + double min = 0.4; + double max = 1.0; + return 100*(internal-min)/(max-min); + } + + public static List entityInfo(Entity e, World source_world) + { + List lst = new ArrayList<>(); + lst.add(Messenger.s("w Use /data get entity command")); + return lst; + } +} diff --git a/src/main/java/carpet/utils/EvictingQueue.java b/src/main/java/carpet/utils/EvictingQueue.java new file mode 100644 index 00000000..253d51c5 --- /dev/null +++ b/src/main/java/carpet/utils/EvictingQueue.java @@ -0,0 +1,13 @@ +package carpet.utils; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class EvictingQueue extends LinkedHashMap +{ + @Override + protected boolean removeEldestEntry(Map.Entry eldest) + { + return this.size() > 10; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/HUDController.java b/src/main/java/carpet/utils/HUDController.java new file mode 100644 index 00000000..78a0225e --- /dev/null +++ b/src/main/java/carpet/utils/HUDController.java @@ -0,0 +1,133 @@ +package carpet.utils; + +import carpet.helpers.HopperCounter; +import carpet.helpers.TickSpeed; +import carpet.logging.LoggerRegistry; +import carpet.logging.logHelpers.PacketCounter; +import carpet.mixins.PlayerListHeaderS2CPacketMixin; +import net.minecraft.entity.EntityCategory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Pair; +import net.minecraft.util.math.MathHelper; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.network.chat.TextComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class HUDController +{ + public static Map> player_huds = new HashMap<>(); + + public static void addMessage(PlayerEntity player, BaseComponent hudMessage) + { + if (!player_huds.containsKey(player)) + { + player_huds.put(player, new ArrayList<>()); + } + else + { + player_huds.get(player).add(new TextComponent("\n")); + } + player_huds.get(player).add(hudMessage); + } + public static void clear_player(PlayerEntity player) + { + PlayerListHeaderS2CPacket packet = new PlayerListHeaderS2CPacket(); + ((PlayerListHeaderS2CPacketMixin)packet).setHeader(new TextComponent("")); + ((PlayerListHeaderS2CPacketMixin)packet).setFooter(new TextComponent("")); + ((ServerPlayerEntity)player).networkHandler.sendPacket(packet); + } + + + public static void update_hud(MinecraftServer server) + { + if(server.getTicks() % 20 != 0) + return; + + player_huds.clear(); + + if (LoggerRegistry.__tps) + LoggerRegistry.getLogger("tps").log(()-> send_tps_display(server)); + + if (LoggerRegistry.__mobcaps) + LoggerRegistry.getLogger("mobcaps").log((option, player) -> { + int dim = player.dimension.getRawId(); + switch (option) + { + case "overworld": + dim = 0; + break; + case "nether": + dim = -1; + break; + case "end": + dim = 1; + break; + } + return send_mobcap_display(dim); + }); + + if(LoggerRegistry.__counter) + LoggerRegistry.getLogger("counter").log((option)->send_counter_info(server, option)); + + if (LoggerRegistry.__packets) + LoggerRegistry.getLogger("packets").log(()-> packetCounter()); + + for (PlayerEntity player: player_huds.keySet()) + { + PlayerListHeaderS2CPacket packet = new PlayerListHeaderS2CPacket(); + ((PlayerListHeaderS2CPacketMixin)packet).setHeader(new TextComponent("")); + ((PlayerListHeaderS2CPacketMixin)packet).setFooter(Messenger.c(player_huds.get(player).toArray(new Object[0]))); + ((ServerPlayerEntity)player).networkHandler.sendPacket(packet); + } + } + private static BaseComponent [] send_tps_display(MinecraftServer server) + { + double MSPT = MathHelper.average(server.lastTickLengths) * 1.0E-6D; + double TPS = 1000.0D / Math.max((TickSpeed.time_warp_start_time != 0)?0.0:TickSpeed.mspt, MSPT); + String color = Messenger.heatmap_color(MSPT,TickSpeed.mspt); + return new BaseComponent[]{Messenger.c( + "g TPS: ", String.format(Locale.US, "%s %.1f",color, TPS), + "g MSPT: ", String.format(Locale.US,"%s %.1f", color, MSPT))}; + } + + private static BaseComponent [] send_mobcap_display(int dim) + { + List components = new ArrayList<>(); + for (EntityCategory type:EntityCategory.values()) + { + Pair counts = SpawnReporter.mobcaps.get(dim).getOrDefault(type, new Pair<>(0, 0)); + int actual = counts.getLeft(); int limit = counts.getRight(); + components.add(Messenger.c( + (actual+limit == 0)?"g -":Messenger.heatmap_color(actual,limit)+" "+actual, + Messenger.creatureTypeColor(type)+" /"+((actual+limit == 0)?"-":limit) + )); + components.add(Messenger.c("w ")); + } + components.remove(components.size()-1); + return new BaseComponent[]{Messenger.c(components.toArray(new Object[0]))}; + } + + private static BaseComponent [] send_counter_info(MinecraftServer server, String color) + { + HopperCounter counter = HopperCounter.getCounter(color); + List res = counter == null ? Collections.emptyList() : counter.format(server, false, true); + return new BaseComponent[]{ Messenger.c(res.toArray(new Object[0]))}; + } + private static BaseComponent [] packetCounter() + { + BaseComponent [] ret = new BaseComponent[]{ + Messenger.c("w I/" + PacketCounter.totalIn + " O/" + PacketCounter.totalOut), + }; + PacketCounter.reset(); + return ret; + } +} diff --git a/src/main/java/carpet/utils/PerimeterDiagnostics.java b/src/main/java/carpet/utils/PerimeterDiagnostics.java new file mode 100644 index 00000000..00cb5a8e --- /dev/null +++ b/src/main/java/carpet/utils/PerimeterDiagnostics.java @@ -0,0 +1,184 @@ +package carpet.utils; + +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.Material; +import net.minecraft.block.BlockState; +import net.minecraft.entity.EntityCategory; +import net.minecraft.entity.SpawnRestriction; +import net.minecraft.entity.SpawnType; +import net.minecraft.entity.WaterCreatureEntity; +import net.minecraft.entity.mob.AmbientEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.mob.Monster; +import net.minecraft.entity.passive.PassiveEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.SpawnHelper; +import net.minecraft.world.biome.Biome; + +import java.util.ArrayList; +import java.util.List; + +public class PerimeterDiagnostics +{ + public static class Result + { + public int liquid; + public int ground; + public int specific; + public List samples; + Result() + { + samples = new ArrayList<>(); + } + } + private Biome.SpawnEntry sle; + private ServerWorld worldServer; + private EntityCategory ctype; + private MobEntity el; + private PerimeterDiagnostics(ServerWorld server, EntityCategory ctype, MobEntity el) + { + this.sle = null; + this.worldServer = server; + this.ctype = ctype; + this.el = el; + } + + public static Result countSpots(ServerWorld worldserver, BlockPos epos, MobEntity el) + { + BlockPos pos; + //List samples = new ArrayList(); + //if (el != null) CarpetSettings.LOG.error(String.format("Got %s to check",el.toString())); + int eY = epos.getY(); + int eX = epos.getX(); + int eZ = epos.getZ(); + Result result = new Result(); + + //int ground_spawns = 0; + //int liquid_spawns = 0; + //int specific_spawns = 0; + boolean add_water = false; + boolean add_ground = false; + EntityCategory ctype = null; + + if (el != null) + { + if (el instanceof WaterCreatureEntity) + { + add_water = true; + ctype = EntityCategory.WATER_CREATURE; + } + else if (el instanceof PassiveEntity) + { + add_ground = true; + ctype = EntityCategory.CREATURE; + } + else if (el instanceof Monster) + { + add_ground = true; + ctype = EntityCategory.MONSTER; + } + else if (el instanceof AmbientEntity) + { + ctype = EntityCategory.AMBIENT; + } + } + PerimeterDiagnostics diagnostic = new PerimeterDiagnostics(worldserver,ctype,el); + for (int x = -128; x <= 128; ++x) + { + for (int z = -128; z <= 128; ++z) + { + if (x*x + z*z > 128*128) // cut out a cyllinder first + { + continue; + } + for (int y= 0; y < 256; ++y) + { + if ((Math.abs(y-eY)>128) ) + { + continue; + } + int distsq = (x)*(x)+(eY-y)*(eY-y)+(z)*(z); + if (distsq > 128*128 || distsq < 24*24) + { + continue; + } + pos = new BlockPos(eX+x, y, eZ+z); + + BlockState iblockstate = worldserver.getBlockState(pos); + BlockState iblockstate_down = worldserver.getBlockState(pos.down()); + BlockState iblockstate_up = worldserver.getBlockState(pos.up()); + + if ( iblockstate.getMaterial() == Material.WATER && iblockstate_down.getMaterial() == Material.WATER && !iblockstate_up.isSimpleFullBlock(worldserver, pos)) + { + result.liquid++; + if (add_water && diagnostic.check_entity_spawn(pos)) + { + result.specific++; + if (result.samples.size() < 10) + { + result.samples.add(pos); + } + } + } + else + { + if (iblockstate_down.isSimpleFullBlock(worldserver, pos)) + { + Block block = iblockstate_down.getBlock(); + boolean flag = block != Blocks.BEDROCK && block != Blocks.BARRIER; + if( flag && SpawnHelper.isClearForSpawn(worldserver, pos, iblockstate, iblockstate.getFluidState()) && SpawnHelper.isClearForSpawn(worldserver, pos.up(), iblockstate_up, iblockstate_up.getFluidState())) + { + result.ground ++; + if (add_ground && diagnostic.check_entity_spawn(pos)) + { + result.specific++; + if (result.samples.size() < 10) + { + result.samples.add(pos); + } + } + } + } + } + } + } + } + //ashMap result= new HashMap<>(); + //result.put("Potential in-water spawning spaces", liquid_spawns); + //result.put("Potential on-ground spawning spaces", ground_spawns); + //if (el != null) result.put(String.format("%s spawning spaces",el.getDisplayName().getUnformattedText()),specific_spawns); + return result; + } + + + private boolean check_entity_spawn(BlockPos pos) + { + if (sle == null || !worldServer.getChunkManager().getChunkGenerator().getEntitySpawnList(ctype, pos).contains(sle)) + { + sle = null; + for (Biome.SpawnEntry sle: worldServer.getChunkManager().getChunkGenerator().getEntitySpawnList(ctype, pos)) + { + if (el.getType() == sle.type) + { + this.sle = sle; + break; + } + } + if (sle == null || !worldServer.getChunkManager().getChunkGenerator().getEntitySpawnList(ctype, pos).contains(sle)) + { + return false; + } + } + + SpawnRestriction.Location spt = SpawnRestriction.getLocation(sle.type); + + if (SpawnHelper.canSpawn(spt, worldServer, pos, sle.type)) + { + el.setPositionAndAngles((float)pos.getX() + 0.5F, (float)pos.getY(), (float)pos.getZ()+0.5F, 0.0F, 0.0F); + return el.canSpawn(worldServer) && el.canSpawn(worldServer, SpawnType.NATURAL) && worldServer.doesNotCollide(el); // check collision rules once they stop fiddling with them after 1.14.1 + } + return false; + } +} diff --git a/src/main/java/carpet/utils/SpawnReporter.java b/src/main/java/carpet/utils/SpawnReporter.java new file mode 100644 index 00000000..8e9797f8 --- /dev/null +++ b/src/main/java/carpet/utils/SpawnReporter.java @@ -0,0 +1,489 @@ + +package carpet.utils; + +import java.util.List; + +import java.util.ArrayList; +import java.util.HashMap; + +import carpet.mixins.WeightedPickerEntryMixin; +import net.minecraft.entity.SpawnType; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.util.Pair; +import net.minecraft.util.registry.Registry; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; +import net.minecraft.util.math.BlockPos; + +import net.minecraft.util.DyeColor; + +import net.minecraft.entity.EntityCategory; +import net.minecraft.world.biome.Biome; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.SpawnHelper; +import net.minecraft.entity.SpawnRestriction; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.Entity; + +import net.minecraft.entity.passive.OcelotEntity; +import net.minecraft.world.Heightmap; +import net.minecraft.world.gen.chunk.ChunkGenerator; + +import java.lang.Math; + +public class SpawnReporter +{ + public static String [] mob_groups = {"hostile","passive","water","ambient"}; + public static boolean mock_spawns = false; + + public static Long track_spawns = 0L; + public static final HashMap>> mobcaps = new HashMap<>(); + public static final HashMap> spawn_stats = new HashMap>(); + public static double mobcap_exponent = 0.0D; + + public static final HashMap spawn_attempts = new HashMap(); + public static final HashMap overall_spawn_ticks = new HashMap(); + public static final HashMap spawn_ticks_full = new HashMap(); + public static final HashMap spawn_ticks_fail = new HashMap(); + public static final HashMap spawn_ticks_succ = new HashMap(); + public static final HashMap spawn_ticks_spawns = new HashMap(); + public static final HashMap spawn_cap_count = new HashMap(); + public static class SpawnPos + { + public String mob; + public BlockPos pos; + public SpawnPos(String mob, BlockPos pos) + { + this.mob = mob; + this.pos = pos; + } + } + public static final HashMap> spawned_mobs = new HashMap<>(); + public static final HashMap spawn_tries = new HashMap<>(); + public static BlockPos lower_spawning_limit = null; + public static BlockPos upper_spawning_limit = null; + + static { + reset_spawn_stats(true); + } + + public static void registerSpawn(LivingEntity el, String type, String mob, BlockPos pos) { registerSpawn(el, type, mob, pos, 1L);} + public static void registerSpawn(LivingEntity el, String type, String mob, BlockPos pos, long value) + { + if (lower_spawning_limit != null) + { + if (!( (lower_spawning_limit.getX() <= pos.getX() && pos.getX() <= upper_spawning_limit.getX()) && + (lower_spawning_limit.getY() <= pos.getY() && pos.getY() <= upper_spawning_limit.getY()) && + (lower_spawning_limit.getZ() <= pos.getZ() && pos.getZ() <= upper_spawning_limit.getZ()) + )) + { + return; + } + } + + long count = spawn_stats.get(type).getOrDefault(mob, 0L); + spawn_stats.get(type).put(mob, count + value); + spawned_mobs.get(type).put(new SpawnPos(mob, new BlockPos(el)), 1); + } + + + public static List printMobcapsForDimension(int dim) + { + String name = DimensionType.byRawId(dim).toString(); + List lst = new ArrayList<>(); + lst.add(Messenger.s(String.format("Mobcaps for %s:",name))); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + Pair stat = mobcaps.get(dim).getOrDefault(enumcreaturetype, new Pair<>(0,0)); + int cur = stat.getLeft(); + int max = stat.getRight(); + int rounds = spawn_tries.get(type_code); + lst.add( Messenger.c(String.format("w %s: ",type_code), + (cur+max==0)?"g -/-":String.format("%s %d/%d", (cur >= max)?"r":((cur >= 8*max/10)?"y":"l") ,cur, max), + (rounds == 1)?"w ":String.format("fi (%d rounds/tick)",spawn_tries.get(type_code)) + )); + } + return lst; + } + + public static List recent_spawns(World world, String creature_type_code) + { + + EntityCategory creature_type = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if ((track_spawns == 0L)) + { + lst.add(Messenger.s("Spawn tracking not started")); + return lst; + } + if (creature_type == null) + { + lst.add(Messenger.s(String.format("Incorrect creature type: %s",creature_type_code))); + return lst; + } + + String type_code = get_type_string(creature_type); + + lst.add(Messenger.s(String.format("Recent %s spawns:",type_code))); + for (SpawnPos entry: spawned_mobs.get(type_code).keySet()) + { + lst.add( Messenger.c(String.format("w - %s ",entry.mob), Messenger.tp("wb",entry.pos))); + } + + if (lst.size()==1) + { + lst.add(Messenger.s(" - Nothing spawned yet, sorry.")); + } + return lst; + + } + + public static List show_mobcaps(BlockPos pos, World worldIn) + { + DyeColor under = WoolTool.getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) + { + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + //return print_general_mobcaps(worldIn); + } + } + String creature_type = get_type_code_from_wool_code(under); + if (creature_type != null) + { + if (track_spawns > 0L) + { + return recent_spawns(worldIn, creature_type); + } + else + { + return printEntitiesByType(creature_type, worldIn); + + } + + } + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + return tracking_report(worldIn); + //// TODO + //return print_general_mobcaps(worldIn); + } + + } + + public static String get_type_code_from_wool_code(DyeColor color) + { + switch (color) + { + case RED: + return "hostile"; + case GREEN: + return "passive"; + case BLUE: + return "water"; + case BROWN: + return "ambient"; + } + return null; + } + + public static EntityCategory get_creature_type_from_code(String type_code) + { + if ("hostile".equalsIgnoreCase(type_code)) + { + return EntityCategory.MONSTER; + } + else if ("passive".equalsIgnoreCase(type_code)) + { + return EntityCategory.CREATURE; + } + else if ("water".equalsIgnoreCase(type_code)) + { + return EntityCategory.WATER_CREATURE; + } + else if ("ambient".equalsIgnoreCase(type_code)) + { + return EntityCategory.AMBIENT; + } + return null; + } + + + public static String get_type_string(EntityCategory type) + { + return String.format("%s", type); + } + + public static String get_creature_code_from_string(String str) + { + return get_type_string(get_creature_type_from_code(str)); + } + + public static List printEntitiesByType(String creature_type_code, World worldIn) //Class entityType) + { + EntityCategory typ = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if (typ == null) + { + lst.add(Messenger.c(String.format("r Incorrect creature type: %s",creature_type_code))); + return lst; + } + Class cls = typ.getDeclaringClass();// getBaseClass();// getCreatureClass(); + lst.add( Messenger.s(String.format("Loaded entities for %s class:", get_type_string(typ)))); + + lst.add(Messenger.s(" ")); + //for (Entity entity : ((ServerWorld)worldIn).getEntities(...)) + //{ + // if ((!(entity instanceof LivingEntity) || !((LivingEntity)entity).isNoDespawnRequired()) && cls.isAssignableFrom(entity.getClass())) + // { + // lst.add(Messenger.c("w - ", + // Messenger.tp("w", entity.posX, entity.posY, entity.posZ),"w : "+EntityType.getId(entity.getType()))); + // } + //} + if (lst.size()==1) + { + lst.add(Messenger.s(" - Empty.")); + } + return lst; + } + + public static void initialize_mocking() + { + reset_spawn_stats(false); + mock_spawns = true; + + } + public static void stop_mocking() + { + reset_spawn_stats(false); + mock_spawns = false; + + } + public static void reset_spawn_stats(boolean full) + { + spawn_stats.clear(); + spawned_mobs.clear(); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + if (full) + { + spawn_tries.put(type_code, 1); + } + for (String suffix: new String[] {""," (N)"," (E)"}) + { + String code = type_code+suffix; + overall_spawn_ticks.put(code, 0L); + spawn_attempts.put(code, 0L); + spawn_ticks_full.put(code, 0L); + spawn_ticks_fail.put(code, 0L); + spawn_ticks_succ.put(code, 0L); + spawn_ticks_spawns.put(code, 0L); + spawn_cap_count.put(code, 0L); + } + + spawn_stats.put(type_code, new HashMap<>()); + spawned_mobs.put(type_code, new EvictingQueue<>()); + } + mobcaps.put(-1,new HashMap<>()); + mobcaps.put(0,new HashMap<>()); + mobcaps.put(1,new HashMap<>()); + track_spawns = 0L; + } + + public static List tracking_report(World worldIn) + { + List report = new ArrayList<>(); + if (track_spawns == 0L) + { + report.add(Messenger.c( + "w Spawn tracking disabled, type '", + "wi /spawn tracking start","/spawn tracking start", + "w ' to enable")); + return report; + } + Long duration = (long) worldIn.getServer().getTicks() - track_spawns; + report.add(Messenger.c("bw --------------------")); + String simulated = mock_spawns?"[SIMULATED] ":""; + String location = (lower_spawning_limit != null)?String.format("[in (%d, %d, %d)x(%d, %d, %d)]", + lower_spawning_limit.getX(),lower_spawning_limit.getY(),lower_spawning_limit.getZ(), + upper_spawning_limit.getX(),upper_spawning_limit.getY(),upper_spawning_limit.getZ() ):""; + report.add(Messenger.s(String.format("%sSpawn statistics %s: for %.1f min", simulated, location, (duration/72000.0)*60))); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + boolean there_are_mobs_to_list = false; + for (String world_code: new String[] {"", " (N)", " (E)"}) + { + String code = type_code+world_code; + if (spawn_ticks_spawns.get(code) > 0L) + { + there_are_mobs_to_list = true; + double hours = overall_spawn_ticks.get(code)/72000.0; + report.add(Messenger.s(String.format(" > %s (%.1f min), %.1f m/t, {%.1f%%F / %.1f%%- / %.1f%%+}; %.2f s/att", + code, + 60*hours, + (1.0D*spawn_cap_count.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_full.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_fail.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_succ.get(code))/ spawn_attempts.get(code), + (1.0D*spawn_ticks_spawns.get(code))/(spawn_ticks_fail.get(code)+spawn_ticks_succ.get(code)) + ))); + } + } + if (there_are_mobs_to_list) + { + for (String creature_name : spawn_stats.get(type_code).keySet()) + { + report.add(Messenger.s(String.format(" - %s: %d spawns, %d per hour", + creature_name, + spawn_stats.get(type_code).get(creature_name), + (72000 * spawn_stats.get(type_code).get(creature_name)/duration )))); + } + } + } + return report; + } + + + + public static void killEntity(LivingEntity entity) + { + if (entity.hasVehicle()) + { + entity.getVehicle().remove(); + } + if (entity.hasPassengers()) + { + for (Entity e: entity.getPassengerList()) + { + e.remove(); + } + } + if (entity instanceof OcelotEntity) + { + for (Entity e: entity.getEntityWorld().getEntities(OcelotEntity.class, entity.getBoundingBox())) + { + e.remove(); + } + } + entity.remove(); + } + + public static List report(BlockPos pos, World worldIn) + { + List rep = new ArrayList<>(); + int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); + Chunk chunk = worldIn.getChunk(pos); + int lc = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, x, z) + 1; + String where = String.format((y >= lc) ? "%d blocks above it." : "%d blocks below it.", MathHelper.abs(y-lc)); + if (y == lc) where = "right at it."; + rep.add(Messenger.s(String.format("Maximum spawn Y value for (%+d, %+d) is %d. You are "+where, x, z, lc ))); + rep.add(Messenger.s("Spawns:")); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype).substring(0, 3); + List lst = ((ChunkGenerator)worldIn.getChunkManager().getChunkGenerator()).getEntitySpawnList(enumcreaturetype, pos); + if (lst != null && !lst.isEmpty()) + { + for (Biome.SpawnEntry spawnEntry : lst) + { + boolean canspawn = SpawnHelper.canSpawn(SpawnRestriction.getLocation(spawnEntry.type), worldIn, pos, spawnEntry.type); + int will_spawn = -1; + boolean fits = false; + boolean fits1 = false; + + MobEntity mob; + try + { + mob = (MobEntity) spawnEntry.type.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + + boolean fits_true = false; + boolean fits_false = false; + + if (canspawn) + { + will_spawn = 0; + for (int attempt = 0; attempt < 50; ++attempt) + { + float f = (float)x + 0.5F; + float f1 = (float)z + 0.5F; + mob.setPositionAndAngles((double)f, (double)y, (double)f1, worldIn.random.nextFloat() * 360.0F, 0.0F); + fits1 = worldIn.doesNotCollide(mob); + + for (int i = 0; i < 20; ++i) + { + if (mob.canSpawn(worldIn, SpawnType.NATURAL)) + { + will_spawn += 1; + } + } + mob.initialize(worldIn, worldIn.getLocalDifficulty(new BlockPos(mob)), SpawnType.NATURAL, null, null); + // the code invokes onInitialSpawn after getCanSpawHere + fits = fits1 && worldIn.doesNotCollide(mob); + if (fits) + { + fits_true = true; + } + else + { + fits_false = true; + } + + killEntity(mob); + + try + { + mob = (MobEntity) spawnEntry.type.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + } + } + + String creature_name = Registry.ENTITY_TYPE.getId(mob.getType()).toString().replaceFirst("minecraft:",""); + String pack_size = String.format("%d", mob.getLimitPerChunk());//String.format("%d-%d", animal.minGroupCount, animal.maxGroupCount); + int weight = ((WeightedPickerEntryMixin) spawnEntry).getWeight(); + if (canspawn) + { + String c = (fits_true && will_spawn>0)?"e":"gi"; + rep.add(Messenger.c( + String.format("%s %s: %s (%d:%d-%d/%d), can: ",c,type_code,creature_name,weight,spawnEntry.minGroupSize, spawnEntry.maxGroupSize, mob.getLimitPerChunk()), + "l YES", + c+" , fit: ", + ((fits_true && fits_false)?"y YES and NO":(fits_true?"l YES":"r NO")), + c+" , will: ", + ((will_spawn>0)?"l ":"r ")+Math.round((double)will_spawn)/10+"%" + )); + } + else + { + rep.add(Messenger.c(String.format("gi %s: %s (%d:%d-%d/%d), can: ",type_code,creature_name,weight,spawnEntry.minGroupSize, spawnEntry.maxGroupSize, mob.getLimitPerChunk()), "n NO")); + } + killEntity(mob); + } + } + } + return rep; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/WoolTool.java b/src/main/java/carpet/utils/WoolTool.java new file mode 100644 index 00000000..d4d153d5 --- /dev/null +++ b/src/main/java/carpet/utils/WoolTool.java @@ -0,0 +1,102 @@ +package carpet.utils; + +import carpet.CarpetSettings; +import carpet.helpers.HopperCounter; +import net.minecraft.block.Material; +import net.minecraft.block.MaterialColor; +import net.minecraft.block.BlockState; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class WoolTool +{ + private static final HashMap Material2Dye = new HashMap<>(); + static + { + for (DyeColor color: DyeColor.values()) + { + Material2Dye.put(color.getMaterialColor(),color); + } + } + + public static void carpetPlacedAction(DyeColor color, PlayerEntity placer, BlockPos pos, World worldIn) + { + if (!CarpetSettings.getBool("carpets")) + { + return; + } + switch (color) + { + case PINK: + if (CarpetSettings.getBool("commandSpawn")) + Messenger.send(placer, SpawnReporter.report(pos, worldIn)); + + break; + case BLACK: + if (CarpetSettings.getBool("commandSpawn")) + Messenger.send(placer, SpawnReporter.show_mobcaps(pos, worldIn)); + break; + case BROWN: + if (CarpetSettings.getBool("commandDistance")) + { + ServerCommandSource source = placer.getCommandSource(); + if (!DistanceCalculator.hasStartingPoint(source) || placer.isSneaking()) { + DistanceCalculator.setStart(source, new Vec3d(pos)); + } + else { + DistanceCalculator.setEnd(source, new Vec3d(pos)); + } + } + break; + case GRAY: + if (CarpetSettings.getBool("commandInfo")) + Messenger.send(placer, BlockInfo.blockInfo(pos.down(), worldIn)); + break; + case YELLOW: + if (CarpetSettings.getBool("commandInfo")) + Messenger.m(placer, "r use data get entity command"); + //EntityInfo.issue_entity_info(placer); + break; + case GREEN: + if (CarpetSettings.getBool("hopperCounters")) + { + DyeColor under = getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) return; + HopperCounter counter = HopperCounter.getCounter(under.toString()); + if (counter != null) + Messenger.send(placer, counter.format(worldIn.getServer(), false, false)); + } + break; + case RED: + if (CarpetSettings.getBool("hopperCounters")) + { + DyeColor under = getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) return; + HopperCounter counter = HopperCounter.getCounter(under.toString()); + if (counter == null) return; + counter.reset(placer.getServer()); + List res = new ArrayList<>(); + res.add(Messenger.s(String.format("%s counter reset",under.toString()))); + Messenger.send(placer, res); + } + break; + } + } + + public static DyeColor getWoolColorAtPosition(World worldIn, BlockPos pos) + { + BlockState state = worldIn.getBlockState(pos); + if (state.getMaterial() != Material.WOOL || !state.isSimpleFullBlock(worldIn, pos)) + return null; + return Material2Dye.get(state.getTopMaterialColor(worldIn, pos)); + } +} diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index ee4411e6..01ade968 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -3,10 +3,20 @@ "package": "carpet.mixins", "compatibilityLevel": "JAVA_8", "mixins": [ - "CommandManagerMixin" + "CommandManagerMixin", + "MinecraftServerMixin", + "PlayerListHeaderS2CPacketMixin", + "WeightedPickerEntryMixin", + "ExperienceOrbEntityMixin", + "PlayerActionC2SPacketMixin", + "ServerPlayerEntityMixin" ], "client": [ - "CommandManagerMixin" + "MinecraftClientMixin", + "IntegratedServerMixin" + ], + "server": [ + "MinecraftDedicatedServerMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index ac1a9edb..8e65cc90 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,8 +26,7 @@ ], "requires": { - "fabricloader": ">=0.4.0", - "fabric": "*" + "fabricloader": ">=0.4.0" }, "suggests": { "flamingo": "*"