diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index b78ec21f..e70f2b3a 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -7,10 +7,10 @@ import carpet.commands.CameraModeCommand; import carpet.commands.DrawCommand; //import carpet.commands.ScriptCommand; //import carpet.commands.InfoCommand; -//import carpet.commands.LogCommand; //import carpet.commands.PerimeterInfoCommand; //import carpet.commands.PlayerCommand; //import carpet.commands.SpawnCommand; +import carpet.commands.LogCommand; import carpet.commands.TestCommand; //import carpet.commands.TickCommand; //import carpet.logging.LoggerRegistry; @@ -53,7 +53,7 @@ public class CarpetServer // static for now - easier to handle all around the co //CarpetCommand.register(dispatcher); //TickCommand.register(dispatcher); //CounterCommand.register(dispatcher); - //LogCommand.register(dispatcher); + LogCommand.register(dispatcher); //SpawnCommand.register(dispatcher); //PlayerCommand.register(dispatcher); CameraModeCommand.register(dispatcher); diff --git a/src/main/java/carpet/commands/LogCommand.java b/src/main/java/carpet/commands/LogCommand.java new file mode 100644 index 00000000..4e7cc4e1 --- /dev/null +++ b/src/main/java/carpet/commands/LogCommand.java @@ -0,0 +1,214 @@ +package carpet.commands; + +import carpet.CarpetSettings; +import carpet.logging.Logger; +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static net.minecraft.server.command.CommandSource.suggestMatching; + +public class LogCommand +{ + public static void register(CommandDispatcher dispatcher) + { + LiteralArgumentBuilder literalargumentbuilder = CommandManager.literal("log"). + requires((player) -> CarpetSettings.getBool("commandLog")). + executes((context) -> listLogs(context.getSource())). + then(CommandManager.literal("clear"). + executes( (c) -> unsubFromAll(c.getSource(), c.getSource().getName())). + then(CommandManager.argument("player", StringArgumentType.word()). + suggests( (c, b)-> suggestMatching(c.getSource().getPlayerNames(),b)). + executes( (c) -> unsubFromAll(c.getSource(), getString(c, "player"))))); + + literalargumentbuilder.then(CommandManager.argument("log name",StringArgumentType.word()). + suggests( (c, b)-> suggestMatching(LoggerRegistry.getLoggerNames(),b)). + executes( (c)-> toggleSubscription(c.getSource(), c.getSource().getName(), getString(c, "log name"))). + then(CommandManager.literal("clear"). + executes( (c) -> unsubFromLogger( + c.getSource(), + c.getSource().getName(), + getString(c, "log name")))). + then(CommandManager.argument("option", StringArgumentType.word()). + suggests( (c, b) -> suggestMatching( + (LoggerRegistry.getLogger(getString(c, "log name"))==null + ?new String[]{} + :LoggerRegistry.getLogger(getString(c, "log name")).getOptions()), + b)). + executes( (c) -> subscribePlayer( + c.getSource(), + c.getSource().getName(), + getString(c, "log name"), + getString(c, "option"))). + then(CommandManager.argument("player", StringArgumentType.word()). + suggests( (c, b) -> suggestMatching(c.getSource().getPlayerNames(),b)). + executes( (c) -> subscribePlayer( + c.getSource(), + getString(c, "player"), + getString(c, "log name"), + getString(c, "option")))))); + + dispatcher.register(literalargumentbuilder); + } + private static int listLogs(ServerCommandSource source) + { + PlayerEntity player; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "For players only"); + return 0; + } + Map subs = LoggerRegistry.getPlayerSubscriptions(source.getName()); + if (subs == null) + { + subs = new HashMap<>(); + } + List all_logs = new ArrayList<>(LoggerRegistry.getLoggerNames()); + Collections.sort(all_logs); + Messenger.m(player, "w _____________________"); + Messenger.m(player, "w Available logging options:"); + for (String lname: all_logs) + { + List comp = new ArrayList<>(); + String color = subs.containsKey(lname)?"w":"g"; + comp.add("w - "+lname+": "); + Logger logger = LoggerRegistry.getLogger(lname); + String [] options = logger.getOptions(); + if (options.length == 0) + { + if (subs.containsKey(lname)) + { + comp.add("l Subscribed "); + } + else + { + comp.add(color + " [Subscribe] "); + comp.add("^w subscribe to " + lname); + comp.add("!/log " + lname); + } + } + else + { + for (String option : logger.getOptions()) + { + if (subs.containsKey(lname) && subs.get(lname).equalsIgnoreCase(option)) + { + comp.add("l [" + option + "] "); + } else + { + comp.add(color + " [" + option + "] "); + comp.add("^w subscribe to " + lname + " " + option); + comp.add("!/log " + lname + " " + option); + } + + } + } + if (subs.containsKey(lname)) + { + comp.add("nb [X]"); + comp.add("^w Click to unsubscribe"); + comp.add("!/log "+lname); + } + Messenger.m(player,comp.toArray(new Object[0])); + } + return 1; + } + private static int unsubFromAll(ServerCommandSource source, String player_name) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + for (String logname : LoggerRegistry.getLoggerNames()) + { + LoggerRegistry.unsubscribePlayer(player_name, logname); + } + Messenger.m(source, "gi Unsubscribed from all logs"); + return 1; + } + private static int unsubFromLogger(ServerCommandSource source, String player_name, String logname) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logname) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logname); + return 0; + } + LoggerRegistry.unsubscribePlayer(player_name, logname); + Messenger.m(source, "gi Unsubscribed from "+logname); + return 1; + } + + private static int toggleSubscription(ServerCommandSource source, String player_name, String logName) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logName) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logName); + return 0; + } + boolean subscribed = LoggerRegistry.togglePlayerSubscription(player_name, logName); + if (subscribed) + { + Messenger.m(source, "gi "+player_name+" subscribed to " + logName + "."); + } + else + { + Messenger.m(source, "gi "+player_name+" unsubscribed from " + logName + "."); + } + return 1; + } + private static int subscribePlayer(ServerCommandSource source, String player_name, String logname, String option) + { + PlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(player_name); + if (player == null) + { + Messenger.m(source, "r No player specified"); + return 0; + } + if (LoggerRegistry.getLogger(logname) == null) + { + Messenger.m(source, "r Unknown logger: ","rb "+logname); + return 0; + } + LoggerRegistry.subscribePlayer(player_name, logname, option); + if (option!=null) + { + Messenger.m(source, "gi Subscribed to " + logname + "(" + option + ")"); + } + else + { + Messenger.m(source, "gi Subscribed to " + logname); + } + return 1; + } +} diff --git a/src/main/java/carpet/helpers/HopperCounter.java b/src/main/java/carpet/helpers/HopperCounter.java new file mode 100644 index 00000000..02cbf7f9 --- /dev/null +++ b/src/main/java/carpet/helpers/HopperCounter.java @@ -0,0 +1,156 @@ +package carpet.helpers; + +import carpet.utils.Messenger; +import com.google.common.collect.Maps; +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.util.DyeColor; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.MinecraftServer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +public class HopperCounter +{ + public static final Map COUNTERS; + + static + { + EnumMap counterMap = new EnumMap<>(DyeColor.class); + for (DyeColor color : DyeColor.values()) + { + counterMap.put(color, new HopperCounter(color)); + } + COUNTERS = Maps.immutableEnumMap(counterMap); + } + + public final DyeColor color; + private final Object2LongMap counter = new Object2LongLinkedOpenHashMap<>(); + private long startTick; + private long startMillis; + // private PubSubInfoProvider pubSubProvider; + + private HopperCounter(DyeColor color) + { + this.color = color; + // pubSubProvider = new PubSubInfoProvider<>(QuickCarpet.PUBSUB, "carpet.counter." + color.getName(), 0, this::getTotalItems); + } + + public void add(MinecraftServer server, ItemStack stack) + { + if (startTick == 0) + { + startTick = server.getTicks(); + startMillis = System.currentTimeMillis(); + } + Item item = stack.getItem(); + counter.put(item, counter.getLong(item) + stack.getAmount()); + // pubSubProvider.publish(); + } + + public void reset(MinecraftServer server) + { + counter.clear(); + startTick = server.getTicks(); + startMillis = System.currentTimeMillis(); + // pubSubProvider.publish(); + } + + public static void resetAll(MinecraftServer server) + { + for (HopperCounter counter : COUNTERS.values()) + { + counter.reset(server); + } + } + + public static List formatAll(MinecraftServer server, boolean realtime) + { + List text = new ArrayList<>(); + + for (HopperCounter counter : COUNTERS.values()) + { + List temp = counter.format(server, realtime, false); + if (temp.size() > 1) + { + if (!text.isEmpty()) text.add(Messenger.s("")); + text.addAll(temp); + } + } + if (text.isEmpty()) + { + text.add(Messenger.s("No items have been counted yet.")); + } + return text; + } + + public List format(MinecraftServer server, boolean realTime, boolean brief) + { + if (counter.isEmpty()) + { + if (brief) + { + return Collections.singletonList(Messenger.c("g "+color+": -, -/h, - min ")); + } + return Collections.singletonList(Messenger.s(String.format("No items for %s yet", color.getName()))); + } + long total = getTotalItems(); + long ticks = Math.max(realTime ? (System.currentTimeMillis() - startMillis) / 50 : server.getTicks() - startTick, 1); + if (total == 0) + { + if (brief) + { + return Collections.singletonList(Messenger.c(String.format("c %s: 0, 0/h, %.1f min ", color, ticks / (20.0 * 60.0)))); + } + return Collections.singletonList(Messenger.c(String.format("w No items for %s yet (%.2f min.%s)", + color.getName(), ticks / (20.0 * 60.0), (realTime ? " - real time" : "")), + "nb [X]", "^g reset", "!/counter " + color.getName() +" reset")); + } + if (brief) + { + return Collections.singletonList(Messenger.c(String.format("c %s: %d, %d/h, %.1f min ", + color.getName(), total, total * (20 * 60 * 60) / ticks, ticks / (20.0 * 60.0)))); + } + List items = new ArrayList<>(); + items.add(Messenger.c(String.format("w Items for %s (%.2f min.%s), total: %d, (%.1f/h):", + color, ticks*1.0/(20*60), (realTime?" - real time":""), total, total*1.0*(20*60*60)/ticks), + "nb [X]", "^g reset", "!/counter "+color+" reset" + )); + items.addAll(counter.object2LongEntrySet().stream().map(e -> + { + BaseComponent itemName = new TranslatableComponent(e.getKey().getTranslationKey()); + long count = e.getLongValue(); + return Messenger.c("w - ", itemName, String.format("w : %d, %.1f/h", + count, + count * (20.0 * 60.0 * 60.0) / ticks)); + }).collect(Collectors.toList())); + return items; + } + + public static HopperCounter getCounter(String color) + { + try + { + DyeColor colorEnum = DyeColor.valueOf(color.toUpperCase(Locale.ROOT)); + return COUNTERS.get(colorEnum); + } + catch (IllegalArgumentException e) + { + return null; + } + } + + public long getTotalItems() + { + return counter.values().stream().mapToLong(Long::longValue).sum(); + } +} diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java new file mode 100644 index 00000000..e594dc5f --- /dev/null +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -0,0 +1,169 @@ +package carpet.helpers; + +import carpet.CarpetServer; +import carpet.utils.Messenger; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.CommandManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.network.chat.BaseComponent; + +public class TickSpeed +{ + public static final int PLAYER_GRACE = 2; + public static float tickrate = 20.0f; + public static long mspt = 50l; + public static long time_bias = 0; + public static long time_warp_start_time = 0; + public static long time_warp_scheduled_ticks = 0; + public static PlayerEntity time_advancerer = null; + public static String tick_warp_callback = null; + public static ServerCommandSource tick_warp_sender = null; + public static int player_active_timeout = 0; + public static boolean process_entities = true; + public static boolean is_paused = false; + public static boolean is_superHot = false; + + public static void reset_player_active_timeout() + { + if (player_active_timeout < PLAYER_GRACE) + { + player_active_timeout = PLAYER_GRACE; + } + } + + public static void add_ticks_to_run_in_pause(int ticks) + { + player_active_timeout = PLAYER_GRACE+ticks; + } + + public static void tickrate(float rate) + { + tickrate = rate; + mspt = (long)(1000.0/tickrate); + if (mspt <=0) + { + mspt = 1l; + tickrate = 1000.0f; + } + } + + public static BaseComponent tickrate_advance(PlayerEntity player, int advance, String callback, ServerCommandSource source) + { + if (0 == advance) + { + tick_warp_callback = null; + tick_warp_sender = null; + finish_time_warp(); + return Messenger.c("gi Warp interrupted"); + } + if (time_bias > 0) + { + return Messenger.c("l Another player is already advancing time at the moment. Try later or talk to them"); + } + time_advancerer = player; + time_warp_start_time = System.nanoTime(); + time_warp_scheduled_ticks = advance; + time_bias = advance; + tick_warp_callback = callback; + tick_warp_sender = source; + return Messenger.c("gi Warp speed ...."); + } + + public static void finish_time_warp() + { + + long completed_ticks = time_warp_scheduled_ticks - time_bias; + double milis_to_complete = System.nanoTime()-time_warp_start_time; + if (milis_to_complete == 0.0) + { + milis_to_complete = 1.0; + } + milis_to_complete /= 1000000.0; + int tps = (int) (1000.0D*completed_ticks/milis_to_complete); + double mspt = (1.0*milis_to_complete)/completed_ticks; + time_warp_scheduled_ticks = 0; + time_warp_start_time = 0; + if (tick_warp_callback != null) + { + CommandManager icommandmanager = tick_warp_sender.getMinecraftServer().getCommandManager(); + try + { + int j = icommandmanager.execute(tick_warp_sender, tick_warp_callback); + + if (j < 1) + { + if (time_advancerer != null) + { + Messenger.m(time_advancerer, "r Command Callback failed: ", "rb /"+tick_warp_callback,"/"+tick_warp_callback); + } + } + } + catch (Throwable var23) + { + if (time_advancerer != null) + { + Messenger.m(time_advancerer, "r Command Callback failed - unknown error: ", "rb /"+tick_warp_callback,"/"+tick_warp_callback); + } + } + tick_warp_callback = null; + tick_warp_sender = null; + } + if (time_advancerer != null) + { + Messenger.m(time_advancerer, String.format("gi ... Time warp completed with %d tps, or %.2f mspt",tps, mspt )); + time_advancerer = null; + } + else + { + Messenger.print_server_message(CarpetServer.minecraft_server, String.format("... Time warp completed with %d tps, or %.2f mspt",tps, mspt )); + } + time_bias = 0; + + } + + public static boolean continueWarp() + { + if (time_bias > 0) + { + if (time_bias == time_warp_scheduled_ticks) //first call after previous tick, adjust start time + { + time_warp_start_time = System.nanoTime(); + } + time_bias -= 1; + return true; + } + else + { + finish_time_warp(); + return false; + } + } + + public static void tick(MinecraftServer server) + { + process_entities = true; + if (player_active_timeout > 0) + { + player_active_timeout--; + } + if (is_paused) + { + if (player_active_timeout < PLAYER_GRACE) + { + process_entities = false; + } + } + else if (is_superHot) + { + if (player_active_timeout <= 0) + { + process_entities = false; + + } + } + + + } +} + diff --git a/src/main/java/carpet/logging/HUDLogger.java b/src/main/java/carpet/logging/HUDLogger.java new file mode 100644 index 00000000..a526a0c6 --- /dev/null +++ b/src/main/java/carpet/logging/HUDLogger.java @@ -0,0 +1,29 @@ +package carpet.logging; + +import carpet.utils.HUDController; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; + +public class HUDLogger extends Logger +{ + public HUDLogger(String logName, String def, String[] options) + { + super(logName, def, options); + } + + @Override + public void removePlayer(String playerName) + { + PlayerEntity player = playerFromName(playerName); + if (player != null) HUDController.clear_player(player); + super.removePlayer(playerName); + } + + @Override + public void sendPlayerMessage(PlayerEntity player, BaseComponent... messages) + { + for (BaseComponent m:messages) HUDController.addMessage(player, m); + } + + +} diff --git a/src/main/java/carpet/logging/Logger.java b/src/main/java/carpet/logging/Logger.java new file mode 100644 index 00000000..963abb23 --- /dev/null +++ b/src/main/java/carpet/logging/Logger.java @@ -0,0 +1,193 @@ +package carpet.logging; + +import carpet.CarpetServer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.chat.BaseComponent; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class Logger +{ + // The set of subscribed and online players. + private Map subscribedOnlinePlayers; + + // The set of subscribed and offline players. + private Map subscribedOfflinePlayers; + + // The logName of this log. Gets prepended to logged messages. + private String logName; + + private String default_option; + + private String[] options; + + public Logger(String logName, String def, String [] options) + { + subscribedOnlinePlayers = new HashMap<>(); + subscribedOfflinePlayers = new HashMap<>(); + this.logName = logName; + this.default_option = def; + this.options = options; + } + + public String getDefault() + { + return default_option; + } + public String [] getOptions() + { + if (options == null) + { + return new String[0]; + } + return options; + } + public String getLogName() + { + return logName; + } + + /** + * Subscribes the player with the given logName to the logger. + */ + public void addPlayer(String playerName, String option) + { + if (playerFromName(playerName) != null) + { + subscribedOnlinePlayers.put(playerName, option); + } + else + { + subscribedOfflinePlayers.put(playerName, option); + } + LoggerRegistry.setAccess(this); + } + + /** + * Unsubscribes the player with the given logName from the logger. + */ + public void removePlayer(String playerName) + { + subscribedOnlinePlayers.remove(playerName); + subscribedOfflinePlayers.remove(playerName); + LoggerRegistry.setAccess(this); + } + + /** + * Returns true if there are any online subscribers for this log. + */ + public boolean hasOnlineSubscribers() + { + return subscribedOnlinePlayers.size() > 0; + } + + /** + * serves messages to players fetching them from the promise + * will repeat invocation for players that share the same option + */ + @FunctionalInterface + public interface lMessage { BaseComponent [] get(String playerOption, PlayerEntity player);} + public void log(lMessage messagePromise) + { + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + BaseComponent [] messages = messagePromise.get(en.getValue(),player); + if (messages != null) + sendPlayerMessage(player, messages); + } + } + } + + /** + * guarantees that each message for each option will be evaluated once from the promise + * and served the same way to all other players subscribed to the same option + */ + @FunctionalInterface + public interface lMessageIgnorePlayer { BaseComponent [] get(String playerOption);} + public void log(lMessageIgnorePlayer messagePromise) + { + Map cannedMessages = new HashMap<>(); + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + String option = en.getValue(); + if (!cannedMessages.containsKey(option)) + { + cannedMessages.put(option,messagePromise.get(option)); + } + BaseComponent [] messages = cannedMessages.get(option); + if (messages != null) + sendPlayerMessage(player, messages); + } + } + } + /** + * guarantees that message is evaluated once, so independent from the player and chosen option + */ + public void log(Supplier messagePromise) + { + BaseComponent [] cannedMessages = null; + for (Map.Entry en : subscribedOnlinePlayers.entrySet()) + { + PlayerEntity player = playerFromName(en.getKey()); + if (player != null) + { + if (cannedMessages == null) cannedMessages = messagePromise.get(); + sendPlayerMessage(player, cannedMessages); + } + } + } + + public void sendPlayerMessage(PlayerEntity player, BaseComponent ... messages) + { + Arrays.stream(messages).forEach(player::sendMessage); + } + + /** + * Gets the {@code PlayerEntity} instance for a player given their UUID. Returns null if they are offline. + */ + protected PlayerEntity playerFromName(String name) + { + return CarpetServer.minecraft_server.getPlayerManager().getPlayer(name); + } + + // ----- Event Handlers ----- // + + public void onPlayerConnect(PlayerEntity player) + { + // If the player was subscribed to the log and offline, move them to the set of online subscribers. + String playerName = player.getName().getString(); + if (subscribedOfflinePlayers.containsKey(playerName)) + { + subscribedOnlinePlayers.put(playerName, subscribedOfflinePlayers.get(playerName)); + subscribedOfflinePlayers.remove(playerName); + } + LoggerRegistry.setAccess(this); + } + + public void onPlayerDisconnect(PlayerEntity player) + { + // If the player was subscribed to the log, move them to the set of offline subscribers. + String playerName = player.getName().getString(); + if (subscribedOnlinePlayers.containsKey(playerName)) + { + subscribedOfflinePlayers.put(playerName, subscribedOnlinePlayers.get(playerName)); + subscribedOnlinePlayers.remove(playerName); + } + LoggerRegistry.setAccess(this); + } + + public String getAcceptedOption(String arg) + { + if (options != null && Arrays.asList(options).contains(arg)) return arg; + return null; + } +} diff --git a/src/main/java/carpet/logging/LoggerRegistry.java b/src/main/java/carpet/logging/LoggerRegistry.java new file mode 100644 index 00000000..9d8a40d6 --- /dev/null +++ b/src/main/java/carpet/logging/LoggerRegistry.java @@ -0,0 +1,157 @@ +package carpet.logging; + +import carpet.CarpetSettings; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumDyeColor; +import net.minecraft.server.MinecraftServer; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class LoggerRegistry +{ + // Map from logger names to loggers. + private static Map loggerRegistry = new HashMap<>(); + // Map from player names to the set of names of the logs that player is subscribed to. + private static Map> playerSubscriptions = new HashMap<>(); + //statics to quickly asses if its worth even to call each one + public static boolean __tnt; + public static boolean __projectiles; + public static boolean __fallingBlocks; + public static boolean __kills; + public static boolean __tps; + public static boolean __counter; + public static boolean __mobcaps; + public static boolean __damage; + public static boolean __packets; + public static boolean __weather; + + public static void initLoggers() + { + registerLogger("tnt", new Logger("tnt", "brief", new String[]{"brief", "full"})); + registerLogger("projectiles", new Logger("projectiles", "brief", new String[]{"brief", "full"})); + registerLogger("fallingBlocks",new Logger("fallingBlocks", "brief", new String[]{"brief", "full"})); + registerLogger("kills", new Logger("kills", null, null)); + registerLogger("damage", new Logger("damage", "all", new String[]{"all","players","me"})); + registerLogger("weather", new Logger("weather", null, null)); + + registerLogger("tps", new HUDLogger("tps", null, null)); + registerLogger("packets", new HUDLogger("packets", null, null)); + registerLogger("counter",new HUDLogger("counter","white", Arrays.stream(EnumDyeColor.values()).map(Object::toString).toArray(String[]::new))); + registerLogger("mobcaps", new HUDLogger("mobcaps", "dynamic",new String[]{"dynamic", "overworld", "nether","end"})); + } + + /** + * Gets the logger with the given name. Returns null if no such logger exists. + */ + public static Logger getLogger(String name) { return loggerRegistry.get(name); } + + /** + * Gets the set of logger names. + */ + public static Set getLoggerNames() { return loggerRegistry.keySet(); } + + /** + * Subscribes the player with name playerName to the log with name logName. + */ + public static void subscribePlayer(String playerName, String logName, String option) + { + if (!playerSubscriptions.containsKey(playerName)) playerSubscriptions.put(playerName, new HashMap<>()); + Logger log = loggerRegistry.get(logName); + if (option == null) option = log.getDefault(); + playerSubscriptions.get(playerName).put(logName,option); + log.addPlayer(playerName, option); + } + + /** + * Unsubscribes the player with name playerName from the log with name logName. + */ + public static void unsubscribePlayer(String playerName, String logName) + { + if (playerSubscriptions.containsKey(playerName)) + { + Map subscriptions = playerSubscriptions.get(playerName); + subscriptions.remove(logName); + loggerRegistry.get(logName).removePlayer(playerName); + if (subscriptions.size() == 0) playerSubscriptions.remove(playerName); + } + } + + /** + * If the player is not subscribed to the log, then subscribe them. Otherwise, unsubscribe them. + */ + public static boolean togglePlayerSubscription(String playerName, String logName) + { + if (playerSubscriptions.containsKey(playerName) && playerSubscriptions.get(playerName).containsKey(logName)) + { + unsubscribePlayer(playerName, logName); + return false; + } + else + { + subscribePlayer(playerName, logName, null); + return true; + } + } + + /** + * Get the set of logs the current player is subscribed to. + */ + public static Map getPlayerSubscriptions(String playerName) + { + if (playerSubscriptions.containsKey(playerName)) + { + return playerSubscriptions.get(playerName); + } + return null; + } + + protected static void setAccess(Logger logger) + { + String name = logger.getLogName(); + boolean value = logger.hasOnlineSubscribers(); + try + { + Field f = LoggerRegistry.class.getDeclaredField("__"+name); + f.setBoolean(null, value); + } + catch (IllegalAccessException e) + { + CarpetSettings.LOG.error("Cannot change logger quick access field"); + } + catch (NoSuchFieldException e) + { + CarpetSettings.LOG.error("Wrong logger name"); + } + } + /** + * Called when the server starts. Creates the logs used by Carpet mod. + */ + private static void registerLogger(String name, Logger logger) + { + loggerRegistry.put(name, logger); + setAccess(logger); + } + + public static void playerConnected(EntityPlayer player) + { + for(Logger log: loggerRegistry.values() ) + { + log.onPlayerConnect(player); + } + + } + public static void playerDisconnected(EntityPlayer player) + { + for(Logger log: loggerRegistry.values() ) + { + log.onPlayerDisconnect(player); + } + } + + + +} diff --git a/src/main/java/carpet/logging/logHelpers/DamageReporter.java b/src/main/java/carpet/logging/logHelpers/DamageReporter.java new file mode 100644 index 00000000..3027ae30 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/DamageReporter.java @@ -0,0 +1,102 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.MobEffects; +import net.minecraft.util.DamageSource; +import net.minecraft.util.text.ITextComponent; + +import java.util.function.Supplier; + +public class DamageReporter +{ + private static ITextComponent[] verifyAndProduceMessage(String option, EntityPlayer player, Entity from, Entity to, Supplier messageFuture) + { + if ("all".equalsIgnoreCase(option) + || ("players".equalsIgnoreCase(option) && (from instanceof EntityPlayer || to instanceof EntityPlayer)) + || ("me".equalsIgnoreCase(option) && ( (from == player) || ( to == player) ) )) + { + return new ITextComponent[]{messageFuture.get()}; + } + return null; + } + + public static boolean register_damage_attacker(Entity target, EntityLivingBase source, float amount) + { + if (!LoggerRegistry.__damage) return true; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source, target, () -> + Messenger.c(source.getDisplayName(), + "g attacking ", target.getDisplayName(),"g for ", + String.format("r %.2f",amount), "g points of damage") + ) + ); + return true; + } + + public static void register_damage(EntityLivingBase target, DamageSource source, float amount) + { + if (!LoggerRegistry.__damage) return; + if (source.isFireDamage() && (target.isImmuneToFire() || + target.isPotionActive(MobEffects.FIRE_RESISTANCE))) + return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getTrueSource(), target, () -> + Messenger.c(target.getDisplayName(), + "g receiving ", + String.format("r %.2f", amount), + String.format("g points of damage from %s", source.getDamageType())) + ) + ); + } + + public static void register_final_damage(EntityLivingBase target, DamageSource source, float amount) + { + if (!LoggerRegistry.__damage) return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getTrueSource(), target, () -> + Messenger.c("g - total received ", + String.format("r %.2f", amount), + "g points of damage") + ) + ); + } + + public static void modify_damage(EntityLivingBase target, DamageSource source, float previous_amount, float final_amount, String component) + { + if (!LoggerRegistry.__damage) + return; + if (previous_amount == final_amount) + return; + if (source.isFireDamage() && (target.isImmuneToFire() || + target.isPotionActive(MobEffects.FIRE_RESISTANCE))) + return; + LoggerRegistry.getLogger("damage").log( (option, player)-> + verifyAndProduceMessage(option, player, source.getTrueSource(), target, () -> + { + if (final_amount == 0.0f) + { + return Messenger.c("g - reduced to ","r 0.0","g due to: "+component); + } + else if (previous_amount > final_amount) + { + float reduction = previous_amount-final_amount; + return Messenger.c("g - reduced to ", + String.format("l %.2f",final_amount), + String.format("g by %.2f (%.1f%% less) due to: %s",reduction,100.0*reduction/previous_amount, component)); + } + else + { + float increase = final_amount-previous_amount; + return Messenger.c("g - increased to ", + String.format("r %.2f",final_amount), + String.format("g by %.2f (%.1f%% more) due to: %s",increase,100.0*increase/previous_amount, component)); + } + } + ) + ); + } +} diff --git a/src/main/java/carpet/logging/logHelpers/KillLogHelper.java b/src/main/java/carpet/logging/logHelpers/KillLogHelper.java new file mode 100644 index 00000000..7fc1f09d --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/KillLogHelper.java @@ -0,0 +1,26 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.util.text.ITextComponent; + +public class KillLogHelper { + + public static void onSweep(String name, int count) + { + LoggerRegistry.getLogger("kills").log( () -> new ITextComponent[]{ + Messenger.c("g "+name+" smacked ","r "+count,"g entities with sweeping")}); + } + + public static void onNonSweepAttack(String name) + { + LoggerRegistry.getLogger("kills").log( () -> new ITextComponent[]{ + Messenger.c("g "+name+" smacked ","r 1","g (no sweeping)")}); + } + + public static void onDudHit(String name) + { + LoggerRegistry.getLogger("kills").log( () -> new ITextComponent[]{ + Messenger.c("g "+name+" dud hot = no one affected")}); + } +} diff --git a/src/main/java/carpet/logging/logHelpers/PacketCounter.java b/src/main/java/carpet/logging/logHelpers/PacketCounter.java new file mode 100644 index 00000000..1e5bf00f --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/PacketCounter.java @@ -0,0 +1,8 @@ +package carpet.logging.logHelpers; + +public class PacketCounter +{ + public static long totalOut=0; + public static long totalIn=0; + public static void reset() {totalIn = 0l; totalOut = 0L; } +} diff --git a/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java new file mode 100644 index 00000000..e218bf65 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/TNTLogHelper.java @@ -0,0 +1,41 @@ +package carpet.logging.logHelpers; + +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.util.text.ITextComponent; + +public class TNTLogHelper +{ + private double primedX, primedY, primedZ, primedAngle; + /** + * Runs when the TNT is primed. Expects the position and motion angle of the TNT. + */ + public void onPrimed(double x, double y, double z, float angle) + { + primedX = x; + primedY = y; + primedZ = z; + primedAngle = angle; + } + /** + * Runs when the TNT explodes. Expects the position of the TNT. + */ + public void onExploded(double x, double y, double z) + { + LoggerRegistry.getLogger("tnt").log( (option) -> { + switch (option) + { + case "brief": + return new ITextComponent[]{Messenger.c( + "l P ",Messenger.dblt("l",primedX,primedY,primedZ,primedAngle), + "r E ",Messenger.dblt("r",x, y, z))}; + case "full": + return new ITextComponent[]{Messenger.c( + "l P ",Messenger.dblf("l",primedX,primedY,primedZ,primedAngle), + "r E ",Messenger.dblf("r",x, y, z))}; + } + return null; + }); + } + +} diff --git a/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java b/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java new file mode 100644 index 00000000..d4a01e10 --- /dev/null +++ b/src/main/java/carpet/logging/logHelpers/TrajectoryLogHelper.java @@ -0,0 +1,80 @@ +package carpet.logging.logHelpers; + +import carpet.logging.Logger; +import carpet.logging.LoggerRegistry; +import carpet.utils.Messenger; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.ITextComponent; + +import java.util.ArrayList; +import java.util.List; + +/** + * A generic log helper for logging the trajectory of things like blocks and throwables. + */ +public class TrajectoryLogHelper +{ + private static final int MAX_TICKS_PER_LINE = 20; + + private boolean doLog; + private Logger logger; + + private ArrayList positions = new ArrayList<>(); + private ArrayList motions = new ArrayList<>(); + + public TrajectoryLogHelper(String logName) + { + this.logger = LoggerRegistry.getLogger(logName); + this.doLog = this.logger.hasOnlineSubscribers(); + } + + public void onTick(double x, double y, double z, double motionX, double motionY, double motionZ) + { + if (!doLog) return; + positions.add(new Vec3d(x, y, z)); + motions.add(new Vec3d(motionX, motionY, motionZ)); + } + + public void onFinish() + { + if (!doLog) return; + logger.log( (option) -> { + List comp = new ArrayList<>(); + switch (option) + { + case "brief": + comp.add(Messenger.s("")); + List line = new ArrayList<>(); + + for (int i = 0; i < positions.size(); i++) + { + Vec3d pos = positions.get(i); + Vec3d mot = motions.get(i); + line.add("w x"); + line.add(String.format("^w Tick: %d\nx: %f\ny: %f\nz: %f\n------------\nmx: %f\nmy: %f\nmz: %f", + i, pos.x, pos.y, pos.z, mot.x, mot.y, mot.z)); + if ((((i+1) % MAX_TICKS_PER_LINE)==0) || i == positions.size()-1) + { + comp.add(Messenger.c(line.toArray(new Object[0]))); + line.clear(); + } + } + break; + case "full": + comp.add(Messenger.c("w ---------")); + for (int i = 0; i < positions.size(); i++) + { + Vec3d pos = positions.get(i); + Vec3d mot = motions.get(i); + comp.add(Messenger.c( + String.format("w tick: %3d pos",i),Messenger.dblt("w",pos.x, pos.y, pos.z), + "w mot",Messenger.dblt("w",mot.x, mot.y, mot.z))); + } + break; + } + return comp.toArray(new ITextComponent[0]); + }); + doLog = false; + } +} + diff --git a/src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java b/src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java new file mode 100644 index 00000000..30555d36 --- /dev/null +++ b/src/main/java/carpet/mixins/logging/PlayerListHeaderS2CPacketMixin.java @@ -0,0 +1,15 @@ +package carpet.mixins.logging; + +import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerListHeaderS2CPacket.class) +public interface PlayerListHeaderS2CPacketMixin { + @Accessor("header") + void setHeader(Component header); + + @Accessor("footer") + void setFooter(Component footer); +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/BlockInfo.java b/src/main/java/carpet/utils/BlockInfo.java new file mode 100644 index 00000000..2b503c82 --- /dev/null +++ b/src/main/java/carpet/utils/BlockInfo.java @@ -0,0 +1,227 @@ +package carpet.utils; + +import net.minecraft.block.Block; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.block.Material; +import net.minecraft.block.MaterialColor; +import net.minecraft.block.BlockState; +import net.minecraft.entity.mob.MobEntityWithAi; +import net.minecraft.entity.ai.goal.WanderAroundGoal; +import net.minecraft.entity.ai.PathfindingUtil; +import net.minecraft.entity.mob.ZombiePigmanEntity; +import net.minecraft.block.BlockPlacementEnvironment; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BlockInfo +{ + public static Map soundName = new HashMap() {{ + put(BlockSoundGroup.WOOD, "wood" ); + put(BlockSoundGroup.GROUND, "gravel"); + put(BlockSoundGroup.PLANT, "grass" ); + put(BlockSoundGroup.STONE, "stone" ); + put(BlockSoundGroup.METAL, "metal" ); + put(BlockSoundGroup.GLASS , "glass" ); + put(BlockSoundGroup.CLOTH , "wool" ); + put(BlockSoundGroup.SAND , "sand" ); + put(BlockSoundGroup.SNOW , "snow" ); + put(BlockSoundGroup.LADDER, "ladder"); + put(BlockSoundGroup.ANVIL , "anvil" ); + put(BlockSoundGroup.SLIME , "slime" ); + put(BlockSoundGroup.WET_GRASS , "sea_grass" ); + put(BlockSoundGroup.CORAL , "coral" ); + }}; + + public static Map mapColourName = new HashMap() {{ + put(MaterialColor.AIR , "air" ); + put(MaterialColor.GRASS , "grass" ); + put(MaterialColor.SAND , "sand" ); + put(MaterialColor.WOOL , "wool" ); + put(MaterialColor.TNT , "tnt" ); + put(MaterialColor.ICE , "ice" ); + put(MaterialColor.IRON , "iron" ); + put(MaterialColor.FOLIAGE , "foliage" ); + put(MaterialColor.SNOW , "snow" ); + put(MaterialColor.CLAY , "clay" ); + put(MaterialColor.DIRT , "dirt" ); + put(MaterialColor.STONE , "stone" ); + put(MaterialColor.WATER , "water" ); + put(MaterialColor.WOOD , "wood" ); + put(MaterialColor.QUARTZ , "quartz" ); + put(MaterialColor.ADOBE , "adobe" ); + put(MaterialColor.MAGENTA , "magenta" ); + put(MaterialColor.LIGHT_BLUE, "light_blue"); + put(MaterialColor.YELLOW , "yellow" ); + put(MaterialColor.LIME , "lime" ); + put(MaterialColor.PINK , "pink" ); + put(MaterialColor.GRAY , "gray" ); + put(MaterialColor.LIGHT_GRAY, "light_gray"); + put(MaterialColor.CYAN , "cyan" ); + put(MaterialColor.PURPLE , "purple" ); + put(MaterialColor.BLUE , "blue" ); + put(MaterialColor.BROWN , "brown" ); + put(MaterialColor.GREEN , "green" ); + put(MaterialColor.RED , "red" ); + put(MaterialColor.BLACK , "black" ); + put(MaterialColor.GOLD , "gold" ); + put(MaterialColor.DIAMOND , "diamond" ); + put(MaterialColor.LAPIS , "lapis" ); + put(MaterialColor.EMERALD , "emerald" ); + put(MaterialColor.OBSIDIAN , "obsidian" ); + put(MaterialColor.NETHERRACK, "netherrack"); + put(MaterialColor.WHITE_TERRACOTTA , "white_terracotta" ); + put(MaterialColor.ORANGE_TERRACOTTA , "orange_terracotta" ); + put(MaterialColor.MAGENTA_TERRACOTTA , "magenta_terracotta" ); + put(MaterialColor.LIGHT_BLUE_TERRACOTTA, "light_blue_terracotta" ); + put(MaterialColor.YELLOW_TERRACOTTA , "yellow_terracotta" ); + put(MaterialColor.LIME_TERRACOTTA , "lime_terracotta" ); + put(MaterialColor.PINK_TERRACOTTA , "pink_terracotta" ); + put(MaterialColor.GRAY_TERRACOTTA , "gray_terracotta" ); + put(MaterialColor.LIGHT_GRAY_TERRACOTTA, "light_gray_terracotta" ); + put(MaterialColor.CYAN_TERRACOTTA , "cyan_terracotta" ); + put(MaterialColor.PURPLE_TERRACOTTA , "purple_terracotta" ); + put(MaterialColor.BLUE_TERRACOTTA , "blue_terracotta" ); + put(MaterialColor.BROWN_TERRACOTTA , "brown_terracotta" ); + put(MaterialColor.GREEN_TERRACOTTA , "green_terracotta" ); + put(MaterialColor.RED_TERRACOTTA , "red_terracotta" ); + put(MaterialColor.BLACK_TERRACOTTA , "black_terracotta" ); + }}; + + public static Map materialName = new HashMap() {{ + put(Material.AIR , "air" ); + put(Material.STRUCTURE_VOID , "void" ); + put(Material.PORTAL , "portal" ); + put(Material.CARPET , "carpet" ); + put(Material.PLANTS , "plant" ); + put(Material.OCEAN_PLANT , "water_plant" ); + put(Material.VINE , "vine" ); + put(Material.SEA_GRASS , "sea_grass" ); + put(Material.WATER , "water" ); + put(Material.BUBBLE_COLUMN , "bubble_column"); + put(Material.LAVA , "lava" ); + put(Material.SNOW , "snow_layer" ); + put(Material.FIRE , "fire" ); + put(Material.CIRCUITS , "redstone_bits"); + put(Material.WEB , "cobweb" ); + put(Material.REDSTONE_LIGHT , "redstone_lamp"); + put(Material.CLAY , "clay" ); + put(Material.GROUND , "dirt" ); + put(Material.GRASS , "grass" ); + put(Material.PACKED_ICE , "packed_ice" ); + put(Material.SAND , "sand" ); + put(Material.SPONGE , "sponge" ); + put(Material.WOOD , "wood" ); + put(Material.CLOTH , "wool" ); + put(Material.TNT , "tnt" ); + put(Material.LEAVES , "leaves" ); + put(Material.GLASS , "glass" ); + put(Material.ICE , "ice" ); + put(Material.CACTUS , "cactus" ); + put(Material.ROCK , "stone" ); + put(Material.IRON , "iron" ); + put(Material.CRAFTED_SNOW , "snow" ); + put(Material.ANVIL , "anvil" ); + put(Material.BARRIER , "barrier" ); + put(Material.PISTON , "piston" ); + put(Material.CORAL , "coral" ); + put(Material.GOURD , "gourd" ); + put(Material.DRAGON_EGG , "dragon_egg" ); + put(Material.CAKE , "cake" ); + }}; + + public static List blockInfo(BlockPos pos, World world) + { + BlockState state = world.getBlockState(pos); + Material material = state.getMaterial(); + Block block = state.getBlock(); + String metastring = ""; + for (net.minecraft.state.IProperty iproperty : state.getProperties()) + { + metastring += ", "+iproperty.getName() + '='+state.get(iproperty); + } + List lst = new ArrayList<>(); + lst.add(Messenger.s("")); + lst.add(Messenger.s("=====================================")); + lst.add(Messenger.s(String.format("Block info for %s%s (id %d%s):",Registry.BLOCK.getKey(block),metastring, Registry.BLOCK.getId(block), metastring ))); + lst.add(Messenger.s(String.format(" - Material: %s", materialName.get(material)))); + lst.add(Messenger.s(String.format(" - Map colour: %s", mapColourName.get(state.getMaterialColor(world, pos))))); + lst.add(Messenger.s(String.format(" - Sound type: %s", soundName.get(block.getBlockSoundGroup())))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Full block: %s", state.isFullCube() ))); + lst.add(Messenger.s(String.format(" - Normal cube: %s", state.isNormalCube()))); + lst.add(Messenger.s(String.format(" - Is liquid: %s", material.isLiquid()))); + lst.add(Messenger.s(String.format(" - Is solid: %s", material.isSolid()))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Light in: %d, above: %d", world.getLight(pos), world.getLight(pos.up())))); + lst.add(Messenger.s(String.format(" - Brightness in: %.2f, above: %.2f", world.getBrightness(pos), world.getBrightness(pos.up())))); + lst.add(Messenger.s(String.format(" - Is opaque: %s", material.isOpaque() ))); + lst.add(Messenger.s(String.format(" - Light opacity: %d", state.getOpacity(world,pos)))); + lst.add(Messenger.s(String.format(" - Blocks light: %s", state.propagatesSkylightDown(world, pos)))); + lst.add(Messenger.s(String.format(" - Emitted light: %d", state.getLightValue()))); + lst.add(Messenger.s(String.format(" - Picks neighbour light value: %s", state.useNeighborBrightness(world, pos)))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Causes suffocation: %s", state.causesSuffocation()))); + lst.add(Messenger.s(String.format(" - Blocks movement on land: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.LAND)))); + lst.add(Messenger.s(String.format(" - Blocks movement in air: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.AIR)))); + lst.add(Messenger.s(String.format(" - Blocks movement in liquids: %s", !state.canPlaceAtSide(world,pos, BlockPlacementEnvironment.WATER)))); + lst.add(Messenger.s(String.format(" - Can burn: %s", material.isFlammable()))); + lst.add(Messenger.s(String.format(" - Requires a tool: %s", !material.isToolNotRequired()))); + lst.add(Messenger.s(String.format(" - Hardness: %.2f", state.getBlockHardness(world, pos)))); + lst.add(Messenger.s(String.format(" - Blast resistance: %.2f", block.getExplosionResistance()))); + lst.add(Messenger.s(String.format(" - Ticks randomly: %s", block.ticksRandomly(state)))); + lst.add(Messenger.s("")); + lst.add(Messenger.s(String.format(" - Can provide power: %s", state.canProvidePower()))); + lst.add(Messenger.s(String.format(" - Strong power level: %d", world.getStrongPower(pos)))); + lst.add(Messenger.s(String.format(" - Redstone power level: %d", world.getRedstonePowerFromNeighbors(pos)))); + lst.add(Messenger.s("")); + lst.add(wander_chances(pos.up(), world)); + + return lst; + } + + private static BaseComponent wander_chances(BlockPos pos, World worldIn) + { + MobEntityWithAi creature = new ZombiePigmanEntity(worldIn); + creature.onInitialSpawn(worldIn.getDifficultyForLocation(pos), null, null); + creature.setLocationAndAngles(pos.getX()+0.5F, pos.getY(), pos.getZ()+0.5F, 0.0F, 0.0F); + WanderAroundGoal wander = new WanderAroundGoal(creature, 0.8D); + int success = 0; + for (int i=0; i<1000; i++) + { + + Vec3d vec = PathfindingUtil.findRandomTarget(creature, 10, 7); + if (vec == null) + { + continue; + } + success++; + } + long total_ticks = 0; + for (int trie=0; trie<1000; trie++) + { + int i; + for (i=1;i<30*20*60; i++) //*60 used to be 5 hours, limited to 30 mins + { + if (wander.shouldExecute()) + { + break; + } + } + total_ticks += 3*i; + } + creature.remove(); + long total_time = (total_ticks)/1000/20; + return Messenger.s(String.format(" - Wander chance above: %.1f%%%%\n - Average standby above: %s", + (100.0F*success)/1000, + ((total_time>5000)?"INFINITY":(Long.toString(total_time)+" s")) + )); + } +} diff --git a/src/main/java/carpet/utils/DistanceCalculator.java b/src/main/java/carpet/utils/DistanceCalculator.java new file mode 100644 index 00000000..6045e9e9 --- /dev/null +++ b/src/main/java/carpet/utils/DistanceCalculator.java @@ -0,0 +1,64 @@ +package carpet.utils; + +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.chat.BaseComponent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class DistanceCalculator +{ + public static final HashMap START_POINT_STORAGE = new HashMap<>(); + + public static boolean hasStartingPoint(ServerCommandSource source) + { + return START_POINT_STORAGE.containsKey(source.getName()); + } + + public static List findDistanceBetweenTwoPoints(Vec3d pos1, Vec3d pos2) + { + double dx = MathHelper.abs((float)pos1.x-(float)pos2.x); + double dy = MathHelper.abs((float)pos1.y-(float)pos2.y); + double dz = MathHelper.abs((float)pos1.z-(float)pos2.z); + double manhattan = dx+dy+dz; + double spherical = MathHelper.sqrt(dx*dx + dy*dy + dz*dz); + double cylindrical = MathHelper.sqrt(dx*dx + dz*dz); + List res = new ArrayList<>(); + res.add(Messenger.c("w Distance between ", + Messenger.tp("c",pos1),"w and ", + Messenger.tp("c",pos2),"w :")); + res.add(Messenger.c("w - Manhattan: ", String.format("wb %.1f", manhattan))); + res.add(Messenger.c("w - Spherical: ", String.format("wb %.2f", spherical))); + res.add(Messenger.c("w - Cylindrical: ", String.format("wb %.2f", cylindrical))); + return res; + } + + public static int distance(ServerCommandSource source, Vec3d pos1, Vec3d pos2) + { + Messenger.send(source, findDistanceBetweenTwoPoints(pos1, pos2)); + return 1; + } + + public static int setStart(ServerCommandSource source, Vec3d pos) + { + START_POINT_STORAGE.put(source.getName(), pos); + Messenger.m(source,"gi Initial point set to: ", Messenger.tp("g",pos)); + return 1; + } + + public static int setEnd(ServerCommandSource source, Vec3d pos) + { + if ( !hasStartingPoint(source) ) + { + START_POINT_STORAGE.put(source.getName(), pos); + Messenger.m(source,"gi There was no initial point for "+source.getName()); + Messenger.m(source,"gi Initial point set to: ", Messenger.tp("g",pos)); + return 0; + } + Messenger.send(source, findDistanceBetweenTwoPoints( START_POINT_STORAGE.get(source.getName()), pos)); + return 1; + } +} diff --git a/src/main/java/carpet/utils/EvictingQueue.java b/src/main/java/carpet/utils/EvictingQueue.java new file mode 100644 index 00000000..253d51c5 --- /dev/null +++ b/src/main/java/carpet/utils/EvictingQueue.java @@ -0,0 +1,13 @@ +package carpet.utils; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class EvictingQueue extends LinkedHashMap +{ + @Override + protected boolean removeEldestEntry(Map.Entry eldest) + { + return this.size() > 10; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/HUDController.java b/src/main/java/carpet/utils/HUDController.java new file mode 100644 index 00000000..859d0681 --- /dev/null +++ b/src/main/java/carpet/utils/HUDController.java @@ -0,0 +1,133 @@ +package carpet.utils; + +import carpet.helpers.HopperCounter; +import carpet.helpers.TickSpeed; +import carpet.logging.LoggerRegistry; +import carpet.logging.logHelpers.PacketCounter; +import carpet.mixins.logging.PlayerListHeaderS2CPacketMixin; +import net.minecraft.entity.EntityCategory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Pair; +import net.minecraft.util.math.MathHelper; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.network.chat.TextComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class HUDController +{ + public static Map> player_huds = new HashMap<>(); + + public static void addMessage(PlayerEntity player, BaseComponent hudMessage) + { + if (!player_huds.containsKey(player)) + { + player_huds.put(player, new ArrayList<>()); + } + else + { + player_huds.get(player).add(new TextComponent("\n")); + } + player_huds.get(player).add(hudMessage); + } + public static void clear_player(PlayerEntity player) + { + PlayerListHeaderS2CPacket packet = new PlayerListHeaderS2CPacket(); + ((PlayerListHeaderS2CPacketMixin)packet).setHeader(new TextComponent("")); + ((PlayerListHeaderS2CPacketMixin)packet).setFooter(new TextComponent("")); + ((ServerPlayerEntity)player).networkHandler.sendPacket(packet); + } + + + public static void update_hud(MinecraftServer server) + { + if(server.getTicks() % 20 != 0) + return; + + player_huds.clear(); + + if (LoggerRegistry.__tps) + LoggerRegistry.getLogger("tps").log(()-> send_tps_display(server)); + + if (LoggerRegistry.__mobcaps) + LoggerRegistry.getLogger("mobcaps").log((option, player) -> { + int dim = player.dimension.getRawId(); + switch (option) + { + case "overworld": + dim = 0; + break; + case "nether": + dim = -1; + break; + case "end": + dim = 1; + break; + } + return send_mobcap_display(dim); + }); + + if(LoggerRegistry.__counter) + LoggerRegistry.getLogger("counter").log((option)->send_counter_info(server, option)); + + if (LoggerRegistry.__packets) + LoggerRegistry.getLogger("packets").log(()-> packetCounter()); + + for (PlayerEntity player: player_huds.keySet()) + { + PlayerListHeaderS2CPacket packet = new PlayerListHeaderS2CPacket(); + ((PlayerListHeaderS2CPacketMixin)packet).setHeader(new TextComponent("")); + ((PlayerListHeaderS2CPacketMixin)packet).setFooter(Messenger.c(player_huds.get(player).toArray(new Object[0]))); + ((ServerPlayerEntity)player).networkHandler.sendPacket(packet); + } + } + private static BaseComponent [] send_tps_display(MinecraftServer server) + { + double MSPT = MathHelper.average(server.lastTickLengths) * 1.0E-6D; + double TPS = 1000.0D / Math.max((TickSpeed.time_warp_start_time != 0)?0.0:TickSpeed.mspt, MSPT); + String color = Messenger.heatmap_color(MSPT,TickSpeed.mspt); + return new BaseComponent[]{Messenger.c( + "g TPS: ", String.format(Locale.US, "%s %.1f",color, TPS), + "g MSPT: ", String.format(Locale.US,"%s %.1f", color, MSPT))}; + } + + private static BaseComponent [] send_mobcap_display(int dim) + { + List components = new ArrayList<>(); + for (EntityCategory type:EntityCategory.values()) + { + Pair counts = SpawnReporter.mobcaps.get(dim).getOrDefault(type, new Pair<>(0, 0)); + int actual = counts.getLeft(); int limit = counts.getRight(); + components.add(Messenger.c( + (actual+limit == 0)?"g -":Messenger.heatmap_color(actual,limit)+" "+actual, + Messenger.creatureTypeColor(type)+" /"+((actual+limit == 0)?"-":limit) + )); + components.add(Messenger.c("w ")); + } + components.remove(components.size()-1); + return new BaseComponent[]{Messenger.c(components.toArray(new Object[0]))}; + } + + private static BaseComponent [] send_counter_info(MinecraftServer server, String color) + { + HopperCounter counter = HopperCounter.getCounter(color); + List res = counter == null ? Collections.emptyList() : counter.format(server, false, true); + return new BaseComponent[]{ Messenger.c(res.toArray(new Object[0]))}; + } + private static BaseComponent [] packetCounter() + { + BaseComponent [] ret = new BaseComponent[]{ + Messenger.c("w I/" + PacketCounter.totalIn + " O/" + PacketCounter.totalOut), + }; + PacketCounter.reset(); + return ret; + } +} diff --git a/src/main/java/carpet/utils/SpawnReporter.java b/src/main/java/carpet/utils/SpawnReporter.java new file mode 100644 index 00000000..883495f5 --- /dev/null +++ b/src/main/java/carpet/utils/SpawnReporter.java @@ -0,0 +1,510 @@ + +package carpet.utils; + +import java.util.List; + +import java.util.ArrayList; +import java.util.HashMap; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.util.Pair; +import net.minecraft.util.registry.Registry; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; +import net.minecraft.util.math.BlockPos; + +import net.minecraft.util.DyeColor; + +import net.minecraft.entity.EntityCategory; +import net.minecraft.world.biome.Biome; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.SpawnHelper; +import net.minecraft.entity.SpawnRestriction; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.Entity; + +import net.minecraft.entity.passive.OcelotEntity; +import net.minecraft.world.Heightmap; +import net.minecraft.world.gen.chunk.ChunkGenerator; + +import java.lang.Math; + +public class SpawnReporter +{ + public static String [] mob_groups = {"hostile","passive","water","ambient"}; + public static boolean mock_spawns = false; + + public static Long track_spawns = 0L; + public static final HashMap>> mobcaps = new HashMap<>(); + public static final HashMap> spawn_stats = new HashMap>(); + public static double mobcap_exponent = 0.0D; + + public static final HashMap spawn_attempts = new HashMap(); + public static final HashMap overall_spawn_ticks = new HashMap(); + public static final HashMap spawn_ticks_full = new HashMap(); + public static final HashMap spawn_ticks_fail = new HashMap(); + public static final HashMap spawn_ticks_succ = new HashMap(); + public static final HashMap spawn_ticks_spawns = new HashMap(); + public static final HashMap spawn_cap_count = new HashMap(); + public static class SpawnPos + { + public String mob; + public BlockPos pos; + public SpawnPos(String mob, BlockPos pos) + { + this.mob = mob; + this.pos = pos; + } + } + public static final HashMap> spawned_mobs = new HashMap<>(); + public static final HashMap spawn_tries = new HashMap<>(); + public static BlockPos lower_spawning_limit = null; + public static BlockPos upper_spawning_limit = null; + + static { + reset_spawn_stats(true); + } + + public static void registerSpawn(LivingEntity el, String type, String mob, BlockPos pos) { registerSpawn(el, type, mob, pos, 1L);} + public static void registerSpawn(LivingEntity el, String type, String mob, BlockPos pos, long value) + { + if (lower_spawning_limit != null) + { + if (!( (lower_spawning_limit.getX() <= pos.getX() && pos.getX() <= upper_spawning_limit.getX()) && + (lower_spawning_limit.getY() <= pos.getY() && pos.getY() <= upper_spawning_limit.getY()) && + (lower_spawning_limit.getZ() <= pos.getZ() && pos.getZ() <= upper_spawning_limit.getZ()) + )) + { + return; + } + } + + long count = spawn_stats.get(type).getOrDefault(mob, 0L); + spawn_stats.get(type).put(mob, count + value); + spawned_mobs.get(type).put(new SpawnPos(mob, new BlockPos(el)), 1); + } + + + public static List printMobcapsForDimension(int dim) + { + String name = DimensionType.byRawId(dim).toString(); + List lst = new ArrayList<>(); + lst.add(Messenger.s(String.format("Mobcaps for %s:",name))); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + Pair stat = mobcaps.get(dim).getOrDefault(enumcreaturetype, new Pair<>(0,0)); + int cur = stat.getLeft(); + int max = stat.getRight(); + int rounds = spawn_tries.get(type_code); + lst.add( Messenger.c(String.format("w %s: ",type_code), + (cur+max==0)?"g -/-":String.format("%s %d/%d", (cur >= max)?"r":((cur >= 8*max/10)?"y":"l") ,cur, max), + (rounds == 1)?"w ":String.format("fi (%d rounds/tick)",spawn_tries.get(type_code)) + )); + } + return lst; + } + + public static List recent_spawns(World world, String creature_type_code) + { + + EntityCategory creature_type = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if ((track_spawns == 0L)) + { + lst.add(Messenger.s("Spawn tracking not started")); + return lst; + } + if (creature_type == null) + { + lst.add(Messenger.s(String.format("Incorrect creature type: %s",creature_type_code))); + return lst; + } + + String type_code = get_type_string(creature_type); + + lst.add(Messenger.s(String.format("Recent %s spawns:",type_code))); + for (SpawnPos entry: spawned_mobs.get(type_code).keySet()) + { + lst.add( Messenger.c(String.format("w - %s ",entry.mob), Messenger.tp("wb",entry.pos))); + } + + if (lst.size()==1) + { + lst.add(Messenger.s(" - Nothing spawned yet, sorry.")); + } + return lst; + + } + + public static List show_mobcaps(BlockPos pos, World worldIn) + { + DyeColor under = WoolTool.getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) + { + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + //return print_general_mobcaps(worldIn); + } + } + String creature_type = get_type_code_from_wool_code(under); + if (creature_type != null) + { + if (track_spawns > 0L) + { + return recent_spawns(worldIn, creature_type); + } + else + { + return printEntitiesByType(creature_type, worldIn); + + } + + } + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + return tracking_report(worldIn); + //// TODO + //return print_general_mobcaps(worldIn); + } + + } + + public static String get_type_code_from_wool_code(DyeColor color) + { + switch (color) + { + case RED: + return "hostile"; + case GREEN: + return "passive"; + case BLUE: + return "water"; + case BROWN: + return "ambient"; + } + return null; + } + + public static EntityCategory get_creature_type_from_code(String type_code) + { + if ("hostile".equalsIgnoreCase(type_code)) + { + return EntityCategory.MONSTER; + } + else if ("passive".equalsIgnoreCase(type_code)) + { + return EntityCategory.CREATURE; + } + else if ("water".equalsIgnoreCase(type_code)) + { + return EntityCategory.WATER_CREATURE; + } + else if ("ambient".equalsIgnoreCase(type_code)) + { + return EntityCategory.AMBIENT; + } + return null; + } + + + public static String get_type_string(EntityCategory type) + { + return String.format("%s", type); + } + + public static String get_creature_code_from_string(String str) + { + return get_type_string(get_creature_type_from_code(str)); + } + + public static List printEntitiesByType(String creature_type_code, World worldIn) //Class entityType) + { + EntityCategory typ = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if (typ == null) + { + lst.add(Messenger.c(String.format("r Incorrect creature type: %s",creature_type_code))); + return lst; + } + Class cls = typ.getBaseClass();// getCreatureClass(); + lst.add( Messenger.s(String.format("Loaded entities for %s class:", get_type_string(typ)))); + for (Entity entity : worldIn.loadedEntityList) + { + if ((!(entity instanceof LivingEntity) || !((LivingEntity)entity).isNoDespawnRequired()) && cls.isAssignableFrom(entity.getClass())) + { + lst.add(Messenger.c("w - ", + Messenger.tp("w", entity.posX, entity.posY, entity.posZ),"w : "+EntityType.getId(entity.getType()))); + } + } + if (lst.size()==1) + { + lst.add(Messenger.s(" - Empty.")); + } + return lst; + } + + public static void initialize_mocking() + { + reset_spawn_stats(false); + mock_spawns = true; + + } + public static void stop_mocking() + { + reset_spawn_stats(false); + mock_spawns = false; + + } + public static void reset_spawn_stats(boolean full) + { + spawn_stats.clear(); + spawned_mobs.clear(); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + if (full) + { + spawn_tries.put(type_code, 1); + } + for (String suffix: new String[] {""," (N)"," (E)"}) + { + String code = type_code+suffix; + overall_spawn_ticks.put(code, 0L); + spawn_attempts.put(code, 0L); + spawn_ticks_full.put(code, 0L); + spawn_ticks_fail.put(code, 0L); + spawn_ticks_succ.put(code, 0L); + spawn_ticks_spawns.put(code, 0L); + spawn_cap_count.put(code, 0L); + } + + spawn_stats.put(type_code, new HashMap<>()); + spawned_mobs.put(type_code, new EvictingQueue<>()); + } + mobcaps.put(-1,new HashMap<>()); + mobcaps.put(0,new HashMap<>()); + mobcaps.put(1,new HashMap<>()); + track_spawns = 0L; + } + + public static List tracking_report(World worldIn) + { + List report = new ArrayList<>(); + if (track_spawns == 0L) + { + report.add(Messenger.c( + "w Spawn tracking disabled, type '", + "wi /spawn tracking start","/spawn tracking start", + "w ' to enable")); + return report; + } + Long duration = (long) worldIn.getServer().getTickCounter() - track_spawns; + report.add(Messenger.c("bw --------------------")); + String simulated = mock_spawns?"[SIMULATED] ":""; + String location = (lower_spawning_limit != null)?String.format("[in (%d, %d, %d)x(%d, %d, %d)]", + lower_spawning_limit.getX(),lower_spawning_limit.getY(),lower_spawning_limit.getZ(), + upper_spawning_limit.getX(),upper_spawning_limit.getY(),upper_spawning_limit.getZ() ):""; + report.add(Messenger.s(String.format("%sSpawn statistics %s: for %.1f min", simulated, location, (duration/72000.0)*60))); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + boolean there_are_mobs_to_list = false; + for (String world_code: new String[] {"", " (N)", " (E)"}) + { + String code = type_code+world_code; + if (spawn_ticks_spawns.get(code) > 0L) + { + there_are_mobs_to_list = true; + double hours = overall_spawn_ticks.get(code)/72000.0; + report.add(Messenger.s(String.format(" > %s (%.1f min), %.1f m/t, {%.1f%%F / %.1f%%- / %.1f%%+}; %.2f s/att", + code, + 60*hours, + (1.0D*spawn_cap_count.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_full.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_fail.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_succ.get(code))/ spawn_attempts.get(code), + (1.0D*spawn_ticks_spawns.get(code))/(spawn_ticks_fail.get(code)+spawn_ticks_succ.get(code)) + ))); + } + } + if (there_are_mobs_to_list) + { + for (String creature_name : spawn_stats.get(type_code).keySet()) + { + report.add(Messenger.s(String.format(" - %s: %d spawns, %d per hour", + creature_name, + spawn_stats.get(type_code).get(creature_name), + (72000 * spawn_stats.get(type_code).get(creature_name)/duration )))); + } + } + } + return report; + } + + + + public static void killEntity(LivingEntity entity) + { + if (entity.isPassenger()) + { + entity.getRidingEntity().remove(); + } + if (entity.isBeingRidden()) + { + for (Entity e: entity.getPassengers()) + { + e.remove(); + } + } + if (entity instanceof OcelotEntity) + { + for (Entity e: entity.getEntityWorld().getEntitiesWithinAABB(OcelotEntity.class, entity.getBoundingBox())) + { + e.remove(); + } + } + entity.remove(); + } + + public static List report(BlockPos pos, World worldIn) + { + List rep = new ArrayList<>(); + int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); + Chunk chunk = worldIn.getChunk(pos); + int lc = chunk.getTopBlockY(Heightmap.Type.LIGHT_BLOCKING, x, z) + 1; + String where = String.format((y >= lc) ? "%d blocks above it." : "%d blocks below it.", MathHelper.abs(y-lc)); + if (y == lc) where = "right at it."; + rep.add(Messenger.s(String.format("Maximum spawn Y value for (%+d, %+d) is %d. You are "+where, x, z, lc ))); + rep.add(Messenger.s("Spawns:")); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype).substring(0, 3); + List lst = ((ChunkGenerator)worldIn.getChunkManager().getChunkGenerator()).getEntitySpawnList(enumcreaturetype, pos); + if (lst != null && !lst.isEmpty()) + { + for (Biome.SpawnEntry SpawnListEntry animal : lst) + { + boolean canspawn = SpawnHelper.canCreatureTypeSpawnAtLocation(SpawnRestriction.getPlacementType(animal.entityType), worldIn, pos, animal.entityType); + int will_spawn = -1; + boolean fits = false; + boolean fits1 = false; + + LivingEntity entityliving; + try + { + entityliving = animal.entityType.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + + boolean fits_true = false; + boolean fits_false = false; + + if (canspawn) + { + will_spawn = 0; + for (int attempt = 0; attempt < 50; ++attempt) + { + float f = (float)x + 0.5F; + float f1 = (float)z + 0.5F; + entityliving.setLocationAndAngles((double)f, (double)y, (double)f1, worldIn.rand.nextFloat() * 360.0F, 0.0F); + fits1 = entityliving.isNotColliding(); + + for (int i = 0; i < 20; ++i) + { + if (entityliving.canSpawn(worldIn, false)) + { + will_spawn += 1; + } + } + entityliving.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityliving)), null, null); + // the code invokes onInitialSpawn after getCanSpawHere + fits = fits1 && entityliving.isNotColliding(); + if (fits) + { + fits_true = true; + } + else + { + fits_false = true; + } + + killEntity(entityliving); + + try + { + entityliving = animal.entityType.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + } + } + + String creature_name = Registry.ENTITY_TYPE.getKey(entityliving.getType()).toString().replaceFirst("minecraft:",""); + String pack_size = String.format("%d", entityliving.getMaxSpawnedInChunk());//String.format("%d-%d", animal.minGroupCount, animal.maxGroupCount); + int weight = animal.getItemWeight(); + if (canspawn) + { + String c = (fits_true && will_spawn>0)?"e":"gi"; + rep.add(Messenger.c( + String.format("%s %s: %s (%d:%d-%d/%d), can: ",c,type_code,creature_name,weight,animal.minGroupCount, animal.maxGroupCount, entityliving.getMaxSpawnedInChunk()), + "l YES", + c+" , fit: ", + ((fits_true && fits_false)?"y YES and NO":(fits_true?"l YES":"r NO")), + c+" , will: ", + ((will_spawn>0)?"l ":"r ")+Math.round((double)will_spawn)/10+"%" + )); + } + else + { + rep.add(Messenger.c(String.format("gi %s: %s (%d:%d-%d/%d), can: ",type_code,creature_name,weight,animal.minGroupCount, animal.maxGroupCount, entityliving.getMaxSpawnedInChunk()), "n NO")); + } + killEntity(entityliving); + } + } + } + return rep; + } + + public static boolean check_player_within_spawning_range(World worldServerIn, float x, float y, float z) + { + + for (int i = 0; i < worldServerIn.playerEntities.size(); ++i) + { + PlayerEntity entityplayer = worldServerIn.playerEntities.get(i); + + if (EntityPredicates.NOT_SPECTATING.test(entityplayer)) + { + double d0 = entityplayer.getDistanceSq(x, y, z); + + if ( d0 > 24.0*24.0 && d0 < 128.0*128.0) + { + return true; + } + } + } + + return false; + } + + +} \ No newline at end of file diff --git a/src/main/java/carpet/utils/WoolTool.java b/src/main/java/carpet/utils/WoolTool.java new file mode 100644 index 00000000..41fd2aee --- /dev/null +++ b/src/main/java/carpet/utils/WoolTool.java @@ -0,0 +1,101 @@ +package carpet.utils; + +import carpet.CarpetSettings; +import carpet.helpers.HopperCounter; +import net.minecraft.block.Material; +import net.minecraft.block.MaterialColor; +import net.minecraft.block.BlockState; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class WoolTool +{ + private static final HashMap Material2Dye = new HashMap<>(); + static + { + for (DyeColor color: DyeColor.values()) + { + Material2Dye.put(color.getMaterialColor(),color); + } + } + + public static void carpetPlacedAction(DyeColor color, PlayerEntity placer, BlockPos pos, World worldIn) + { + if (!CarpetSettings.getBool("carpets")) + { + return; + } + switch (color) + { + case PINK: + if (CarpetSettings.getBool("commandSpawn")) + Messenger.send(placer, SpawnReporter.report(pos, worldIn)); + + break; + case BLACK: + if (CarpetSettings.getBool("commandSpawn")) + Messenger.send(placer, SpawnReporter.show_mobcaps(pos, worldIn)); + break; + case BROWN: + if (CarpetSettings.getBool("commandDistance")) + { + ServerCommandSource source = placer.getCommandSource(); + if (!DistanceCalculator.hasStartingPoint(source) || placer.isSneaking()) { + DistanceCalculator.setStart(source, new Vec3d(pos)); + } + else { + DistanceCalculator.setEnd(source, new Vec3d(pos)); + } + } + break; + case GRAY: + if (CarpetSettings.getBool("commandInfo")) + Messenger.send(placer, BlockInfo.blockInfo(pos.down(), worldIn)); + break; + case YELLOW: + if (CarpetSettings.getBool("commandInfo")) + EntityInfo.issue_entity_info(placer); + break; + case GREEN: + if (CarpetSettings.getBool("hopperCounters")) + { + DyeColor under = getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) return; + HopperCounter counter = HopperCounter.getCounter(under.toString()); + if (counter != null) + Messenger.send(placer, counter.format(worldIn.getServer(), false, false)); + } + break; + case RED: + if (CarpetSettings.getBool("hopperCounters")) + { + DyeColor under = getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) return; + HopperCounter counter = HopperCounter.getCounter(under.toString()); + if (counter == null) return; + counter.reset(placer.getServer()); + List res = new ArrayList<>(); + res.add(Messenger.s(String.format("%s counter reset",under.toString()))); + Messenger.send(placer, res); + } + break; + } + } + + public static DyeColor getWoolColorAtPosition(World worldIn, BlockPos pos) + { + BlockState state = worldIn.getBlockState(pos); + if (state.getMaterial() != Material.CLOTH || !state.isFullCube()) + return null; + return Material2Dye.get(state.getMaterialColor(worldIn, pos)); + } +} diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index ab776bde..b386cb9c 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -4,7 +4,8 @@ "compatibilityLevel": "JAVA_8", "mixins": [ "carpetServer/CommandManagerMixin", - "carpetServer/MinecraftServerMixin" + "carpetServer/MinecraftServerMixin", + "logging/PlayerListHeaderS2CPacketMixin" ], "client": [ "carpetServer/MinecraftClientMixin",