diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index e70f2b3a..dd4c2c33 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -1,13 +1,13 @@ 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.PerimeterInfoCommand; +import carpet.commands.PerimeterInfoCommand; //import carpet.commands.PlayerCommand; //import carpet.commands.SpawnCommand; import carpet.commands.LogCommand; @@ -20,6 +20,8 @@ import carpet.commands.TestCommand; 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,32 +36,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) { //TickSpeed.tick(server); - //HUDController.update_hud(server); + HUDController.update_hud(server); } public static void registerCarpetCommands(CommandDispatcher dispatcher) { - //CarpetCommand.register(dispatcher); + CarpetCommand.register(dispatcher); //TickCommand.register(dispatcher); - //CounterCommand.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 3cc88844..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"), 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/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.javax b/src/main/java/carpet/commands/PlayerCommand.javax new file mode 100644 index 00000000..ae5f2c33 --- /dev/null +++ b/src/main/java/carpet/commands/PlayerCommand.javax @@ -0,0 +1,474 @@ +package carpet.commands; + +import carpet.CarpetSettings; +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.suggestion.SuggestionProviders; +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.GameType; +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; + +public class PlayerCommand +{ + ServerPlayerEntity + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = literal("player"). + requires((player) -> CarpetSettings.getBool("commandPlayer")). + then(argument("player", word()). + suggests( (c, b) -> SuggestionProviders.suggest(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",RotationArgument.rotation()). + 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",RotationArgument.rotation()). + 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", Vec3Argument.vec3()). + executes(PlayerCommand::spawn). + then(literal("facing"). + then(argument("direction",RotationArgument.rotation()). + executes(PlayerCommand::spawn). + then(literal("in"). + then(argument("dimension",DimensionArgument.getDimension()). + 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 ServerPlayerEntityy getPlayer(CommandContext context) + { + String playerName = getString(context,"player"); + MinecraftServer server = context.getSource().getServer(); + return (ServerPlayerEntityy) server.getPlayerList().getPlayerByUsername(playerName); + } + + private static boolean cantSpawn(CommandContext context) + { + String playerName = getString(context,"player"); + PlayerEntity player = context.getSource().getServer().getPlayerList().getPlayerByUsername(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().asPlayer(); + } + catch (CommandSyntaxException e) + { + return false; + } + + if (!(context.getSource().getServer().getPlayerList().canSendCommands(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; + getPlayer(context).actionPack.stop(); + return 1; + } + private static int useOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.useOnce(); + return 1; + } + private static int useContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.setUseForever(); + return 1; + } + private static int useInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + getPlayer(context).actionPack.setUse(ticks, 0); + return 1; + } + private static int jumpOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.jumpOnce(); + return 1; + } + private static int jumpContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.setJumpForever(); + return 1; + } + private static int jumpInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + getPlayer(context).actionPack.setJump(ticks, 0); + return 1; + } + private static int attackOnce(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.attackOnce(); + getPlayer(context).actionPack.resetBlockRemoving(true); + + return 1; + } + private static int attackContinuous(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.setAttackForever(); + return 1; + } + private static int attackInterval(CommandContext context) + { + if (cantManipulate(context)) return 0; + int ticks = getInteger(context, "ticks"); + getPlayer(context).actionPack.setAttack(ticks, 0); + return 1; + } + private static int dropItem(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.dropItem(); + return 1; + } + private static int swapHands(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.swapHands(); + return 1; + } + private static int kill(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).onKillCommand(); + return 1; + } + private static int shadow(CommandContext context) + { + if (cantShadow(context)) return 0; + EntityPlayerMPFake.createShadow( + context.getSource().getServer(), + getPlayer(context)); + return 1; + } + private static int mount(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.mount(); + return 1; + } + private static int dismount(CommandContext context) + { + if (cantManipulate(context)) return 0; + getPlayer(context).actionPack.dismount(); + return 1; + } + private static int sneak(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.setSneaking(true); + return 1; + } + private static int sprint(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.setSprinting(true); + return 1; + } + private static int lookNorth(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("north"); + return 1; + } + private static int lookSouth(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("south"); + return 1; + } + private static int lookEast(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("east"); + return 1; + } + private static int lookWest(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("west"); + return 1; + } + private static int lookUp(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("up"); + return 1; + } + private static int lookDown(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.look("down"); + return 1; + } + private static int lookAround(CommandContext context) + { + if (cantReMove(context)) return 0; + Vec2f vec2f = RotationArgument.getRotation(context, "direction").getRotation(context.getSource()); + getPlayer(context).actionPack.look(vec2f.y,vec2f.x); + return 1; + } + private static int turnRight(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.turn("right"); + return 1; + } + private static int turnLeft(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.turn("left"); + return 1; + } + private static int turnBack(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.turn("back"); + return 1; + } + private static int turn(CommandContext context) + { + if (cantReMove(context)) return 0; + Vec2f vec2f = RotationArgument.getRotation(context, "direction").getRotation(context.getSource()); + getPlayer(context).actionPack.turn(vec2f.y,vec2f.x); + return 1; + } + private static int moveForward(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.setForward(1.0F); + return 1; + } + private static int moveBackward(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.setForward(-1.0F); + return 1; + } + private static int strafeLeft(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.setStrafing(-1.0F); + return 1; + } + private static int strafeRight(CommandContext context) + { + if (cantReMove(context)) return 0; + getPlayer(context).actionPack.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 = Vec3Argument.getVec3(context, "position"); + } + catch (IllegalArgumentException e) + { + pos = context.getSource().getPos(); + } + try + { + facing = RotationArgument.getRotation(context, "direction").getRotation(context.getSource()); + } + catch (IllegalArgumentException e) + { + facing = context.getSource().getRotation(); + } + try + { + dim = DimensionArgument.func_212592_a(context, "dimension"); + } + catch (IllegalArgumentException e) + { + dim = context.getSource().getWorld().dimension.getType(); + } + GameType mode = GameType.CREATIVE; + try + { + ServerPlayerEntityy player = context.getSource().asPlayer(); + mode = player.interactionManager.getGameType(); + } + catch (CommandSyntaxException ignored) + { + } + PlayerEntity p = EntityPlayerMPFake.createFake( + getString(context,"player"), + context.getSource().getServer(), + 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/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.javax b/src/main/java/carpet/helpers/EntityPlayerActionPack.javax new file mode 100644 index 00000000..957ab4ad --- /dev/null +++ b/src/main/java/carpet/helpers/EntityPlayerActionPack.javax @@ -0,0 +1,865 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import com.google.common.base.Predicate; +import net.minecraft.block.Block; +import net.minecraft.block.BlockCommandBlock; +import net.minecraft.block.BlockStructure; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.BlockWorldState; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.fluid.IFluidState; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemSword; +import net.minecraft.network.play.client.CPacketPlayerDigging; +import net.minecraft.util.EntitySelectors; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RayTraceFluidMode; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameType; +import net.minecraft.world.World; + +import java.util.List; + +public class EntityPlayerActionPack +{ + private EntityPlayerMP 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(EntityPlayerMP 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.rotationYaw, -90.0F); return true; + case "down": + look(player.rotationYaw, 90.0F); return true; + case "left": + case "right": + return turn(where); + } + return false; + } + public EntityPlayerActionPack look(float yaw, float pitch) + { + player.setPositionAndRotation(player.posX, player.posY, player.posZ, 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.setPositionAndRotation(player.posX, player.posY, player.posZ, player.rotationYaw+yaw,MathHelper.clamp(player.rotationPitch+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() + { + player.connection.processPlayerDigging(new CPacketPlayerDigging(null, null, CPacketPlayerDigging.Action.SWAP_HELD_ITEMS)); + } + + public void dropItem() + { + player.connection.processPlayerDigging(new CPacketPlayerDigging(null, null, CPacketPlayerDigging.Action.DROP_ITEM)); + } + public void mount() + { + List entities = player.world.getEntitiesWithinAABBExcludingEntity(player,player.getBoundingBox().expand(3.0D, 1.0D, 3.0D)); + if (entities.size()==0) + { + return; + } + Entity closest = entities.get(0); + double distance = player.getDistanceSq(closest); + for (Entity e: entities) + { + double dd = player.getDistanceSq(e); + if (dd 1) + { + resetBlockRemoving(true); + } + break; + } + } + } + + public boolean useOnce() + { + RayTraceResult 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; + } + + private RayTraceResult 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 RayTraceResult mouseOver() + { + World world = player.getEntityWorld(); + RayTraceResult 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); + AxisAlignedBB axisalignedbb = entity1.getBoundingBox().grow((double) entity1.getCollisionBorderSize()); + RayTraceResult 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 RayTraceResult(RayTraceResult.Type.MISS, hitVec, (EnumFacing) null, new BlockPos(hitVec)); + } + + if (pointedEntity != null && (d2 < extendedReach || result == null)) + { + result = new RayTraceResult(pointedEntity, hitVec); + } + + return result; + } + + public boolean clickBlock(BlockPos loc, EnumFacing face) // don't call this one + { + World world = player.getEntityWorld(); + if (player.interactionManager.getGameType()!=GameType.ADVENTURE) + { + if (player.interactionManager.getGameType() == GameType.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()==GameType.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)); + } + + IBlockState 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, EnumFacing facing) + { + if (!world.extinguishFire(player, pos, facing)) + { + onPlayerDestroyBlock(pos); + } + } + + public boolean onPlayerDamageBlock(BlockPos posBlock, EnumFacing directionFacing) //continue clicking - one to call + { + if (this.blockHitDelay > 0) + { + --this.blockHitDelay; + return true; + } + World world = player.getEntityWorld(); + if (player.interactionManager.getGameType()==GameType.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)) + { + IBlockState 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()!=GameType.ADVENTURE) + { + if (player.interactionManager.getGameType() == GameType.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()==GameType.CREATIVE && !player.getHeldItemMainhand().isEmpty() && player.getHeldItemMainhand().getItem() instanceof ItemSword) + { + return false; + } + else + { + IBlockState 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); + IFluidState 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()==GameType.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, EnumFacing.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, EnumFacing 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 != GameType.SPECTATOR) + { + IBlockState 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 != GameType.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 == GameType.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 == GameType.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, RayTraceResult 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 == GameType.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..ad8cd6da --- /dev/null +++ b/src/main/java/carpet/helpers/FeatureGenerator.javax @@ -0,0 +1,200 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import net.minecraft.init.Biomes; +import net.minecraft.init.Blocks; +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.Feature; +import net.minecraft.world.gen.feature.IFeatureConfig; +import net.minecraft.world.gen.feature.IcebergConfig; +import net.minecraft.world.gen.feature.LakesConfig; +import net.minecraft.world.gen.feature.NoFeatureConfig; +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.world.gen.feature.structure.OceanRuinStructure; +import net.minecraft.world.gen.feature.structure.ShipwreckConfig; +import net.minecraft.world.gen.feature.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, IFeatureConfig.NO_FEATURE_CONFIG); + } + private static Thing simplePlop(Feature feature, IFeatureConfig config) + { + return (w, p) -> { + CarpetSettings.skipGenerationChecks=true; + boolean res = feature.place(w, w.getChunkProvider().getChunkGenerator(), w.rand, p, config); + CarpetSettings.skipGenerationChecks=false; + return res; + }; + + } + + private static Thing spawnCustomStructure(Structure structure, IFeatureConfig config) + { + return (w, p) -> { + IChunkGenerator chunkgen = new ChunkGeneratorOverworld(w, null, new OverworldGenSettings()) + { + @Nullable + @Override + public IFeatureConfig 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(OceanRuinStructure.Type.COLD, 0.0F, 0.5F))); + put("oceanruin_warm_small", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinStructure.Type.WARM, 0.0F, 0.5F))); + put("oceanruin_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinStructure.Type.COLD, 1.0F, 0.0F))); + put("oceanruin_warm_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinStructure.Type.WARM, 1.0F, 0.0F))); + put("oceanruin", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinStructure.Type.COLD, 1.0F, 1.0F))); + put("oceanruin_warm", spawnCustomStructure(Feature.OCEAN_RUIN, + new OceanRuinConfig(OceanRuinStructure.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/PortalHelper.javax b/src/main/java/carpet/helpers/PortalHelper.javax new file mode 100644 index 00000000..f6c98d86 --- /dev/null +++ b/src/main/java/carpet/helpers/PortalHelper.javax @@ -0,0 +1,19 @@ +package carpet.helpers; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.block.Blocks; +import net.minecraft.item.ItemBlock; + +public class PortalHelper +{ + public static boolean player_holds_obsidian(EntityPlayer playerIn) + { + if ( (!playerIn.getHeldItemMainhand().isEmpty() + && playerIn.getHeldItemMainhand().getItem() instanceof ItemBlock && + ((ItemBlock)(playerIn.getHeldItemMainhand().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/XPcombine.java b/src/main/java/carpet/helpers/XPcombine.java new file mode 100644 index 00000000..7d8fe0eb --- /dev/null +++ b/src/main/java/carpet/helpers/XPcombine.java @@ -0,0 +1,134 @@ +package carpet.helpers; + +import carpet.CarpetSettings; +import carpet.fakes.ExperienceOrbInterface; +import carpet.mixins.ExperienceOrbEntityMixin; +import net.minecraft.client.network.packet.PlaySoundFromEntityS2CPacket; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ExperienceOrbEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +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/mixins/carpetServer/CommandManagerMixin.java b/src/main/java/carpet/mixins/CommandManagerMixin.java similarity index 95% rename from src/main/java/carpet/mixins/carpetServer/CommandManagerMixin.java rename to src/main/java/carpet/mixins/CommandManagerMixin.java index 4c4fa3de..7f1262e9 100644 --- a/src/main/java/carpet/mixins/carpetServer/CommandManagerMixin.java +++ b/src/main/java/carpet/mixins/CommandManagerMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.carpetServer; +package carpet.mixins; import carpet.CarpetServer; import com.mojang.brigadier.CommandDispatcher; 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/carpetServer/IntegratedServerMixin.java b/src/main/java/carpet/mixins/IntegratedServerMixin.java similarity index 95% rename from src/main/java/carpet/mixins/carpetServer/IntegratedServerMixin.java rename to src/main/java/carpet/mixins/IntegratedServerMixin.java index c2fc216d..1e7ba79c 100644 --- a/src/main/java/carpet/mixins/carpetServer/IntegratedServerMixin.java +++ b/src/main/java/carpet/mixins/IntegratedServerMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.carpetServer; +package carpet.mixins; import carpet.CarpetServer; import net.minecraft.server.integrated.IntegratedServer; diff --git a/src/main/java/carpet/mixins/carpetServer/MinecraftClientMixin.java b/src/main/java/carpet/mixins/MinecraftClientMixin.java similarity index 94% rename from src/main/java/carpet/mixins/carpetServer/MinecraftClientMixin.java rename to src/main/java/carpet/mixins/MinecraftClientMixin.java index c7e319ed..806c6d1d 100644 --- a/src/main/java/carpet/mixins/carpetServer/MinecraftClientMixin.java +++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.carpetServer; +package carpet.mixins; import carpet.CarpetServer; import net.minecraft.client.MinecraftClient; diff --git a/src/main/java/carpet/mixins/carpetServer/MinecraftDedicatedServerMixin.java b/src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java similarity index 96% rename from src/main/java/carpet/mixins/carpetServer/MinecraftDedicatedServerMixin.java rename to src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java index 2605e933..a169081b 100644 --- a/src/main/java/carpet/mixins/carpetServer/MinecraftDedicatedServerMixin.java +++ b/src/main/java/carpet/mixins/MinecraftDedicatedServerMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.carpetServer; +package carpet.mixins; import carpet.CarpetServer; import net.minecraft.server.dedicated.MinecraftDedicatedServer; diff --git a/src/main/java/carpet/mixins/carpetServer/MinecraftServerMixin.java b/src/main/java/carpet/mixins/MinecraftServerMixin.java similarity index 98% rename from src/main/java/carpet/mixins/carpetServer/MinecraftServerMixin.java rename to src/main/java/carpet/mixins/MinecraftServerMixin.java index 3f49f4ad..d2989430 100644 --- a/src/main/java/carpet/mixins/carpetServer/MinecraftServerMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServerMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.carpetServer; +package carpet.mixins; import carpet.CarpetServer; import com.mojang.authlib.GameProfileRepository; diff --git a/src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java b/src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java similarity index 93% rename from src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java rename to src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java index 30555d36..2929df3e 100644 --- a/src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java +++ b/src/main/java/carpet/mixins/PlayerListHeaderS2CPacketMixin.java @@ -1,4 +1,4 @@ -package carpet.mixins.logging; +package carpet.mixins; import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; import net.minecraft.network.chat.Component; 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/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/HUDController.java b/src/main/java/carpet/utils/HUDController.java index 859d0681..78a0225e 100644 --- a/src/main/java/carpet/utils/HUDController.java +++ b/src/main/java/carpet/utils/HUDController.java @@ -4,7 +4,7 @@ import carpet.helpers.HopperCounter; import carpet.helpers.TickSpeed; import carpet.logging.LoggerRegistry; import carpet.logging.logHelpers.PacketCounter; -import carpet.mixins.logging.PlayerListHeaderS2CPacketMixin; +import carpet.mixins.PlayerListHeaderS2CPacketMixin; import net.minecraft.entity.EntityCategory; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.network.ServerPlayerEntity; 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/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 0bfbf448..3851e1f3 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -3,17 +3,18 @@ "package": "carpet.mixins", "compatibilityLevel": "JAVA_8", "mixins": [ - "carpetServer/CommandManagerMixin", - "carpetServer/MinecraftServerMixin", - "logging/PlayerListHeaderS2CPacketMixin", - "WeightedPickerEntryMixin" + "CommandManagerMixin", + "MinecraftServerMixin", + "PlayerListHeaderS2CPacketMixin", + "WeightedPickerEntryMixin", + "ExperienceOrbEntityMixin" ], "client": [ - "carpetServer/MinecraftClientMixin", - "carpetServer/IntegratedServerMixin" + "MinecraftClientMixin", + "IntegratedServerMixin" ], "server": [ - "carpetServer/MinecraftDedicatedServerMixin" + "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": "*"