From 5251bff2c7dee9c519423b8e73318a13eb7d3764 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 13 May 2019 01:48:00 -0300 Subject: [PATCH] moved rest of the stuff. mixins needed still --- .../java/carpet/commands/SpawnCommand.java | 261 +++++++++ .../carpet/fakes/PlayerListHudInterface.java | 6 + .../java/carpet/helpers/CtrlQCrafting.javax | 48 ++ .../java/carpet/mixins/InGameHudMixin.java | 28 + .../carpet/mixins/PlayerListHudMixin.java | 19 + .../mixins/ServerChunkManagerMixin.java | 53 ++ src/main/java/carpet/utils/HUDController.java | 117 ++++ src/main/java/carpet/utils/Messenger.java | 3 +- src/main/java/carpet/utils/SpawnReporter.java | 526 ++++++++++++++++++ src/main/resources/carpet.mixins.json | 17 +- 10 files changed, 1075 insertions(+), 3 deletions(-) create mode 100644 src/main/java/carpet/commands/SpawnCommand.java create mode 100644 src/main/java/carpet/fakes/PlayerListHudInterface.java create mode 100644 src/main/java/carpet/helpers/CtrlQCrafting.javax create mode 100644 src/main/java/carpet/mixins/InGameHudMixin.java create mode 100644 src/main/java/carpet/mixins/PlayerListHudMixin.java create mode 100644 src/main/java/carpet/mixins/ServerChunkManagerMixin.java create mode 100644 src/main/java/carpet/utils/HUDController.java create mode 100644 src/main/java/carpet/utils/SpawnReporter.java diff --git a/src/main/java/carpet/commands/SpawnCommand.java b/src/main/java/carpet/commands/SpawnCommand.java new file mode 100644 index 00000000..0e8827b5 --- /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(), true)); + 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, true)); + 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/fakes/PlayerListHudInterface.java b/src/main/java/carpet/fakes/PlayerListHudInterface.java new file mode 100644 index 00000000..058ae90d --- /dev/null +++ b/src/main/java/carpet/fakes/PlayerListHudInterface.java @@ -0,0 +1,6 @@ +package carpet.fakes; + +public interface PlayerListHudInterface +{ + boolean hasFooterOrHeader(); +} diff --git a/src/main/java/carpet/helpers/CtrlQCrafting.javax b/src/main/java/carpet/helpers/CtrlQCrafting.javax new file mode 100644 index 00000000..6b5d06bc --- /dev/null +++ b/src/main/java/carpet/helpers/CtrlQCrafting.javax @@ -0,0 +1,48 @@ +package carpet.helpers; + +import net.minecraft.entity.LivingEntity LivingEntity; +import net.minecraft.entity.player.PlayerEntity PlayerEntity; +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(PlayerEntity 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 = LivingEntity.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/mixins/InGameHudMixin.java b/src/main/java/carpet/mixins/InGameHudMixin.java new file mode 100644 index 00000000..3f2fb460 --- /dev/null +++ b/src/main/java/carpet/mixins/InGameHudMixin.java @@ -0,0 +1,28 @@ +package carpet.mixins; + +import carpet.fakes.PlayerListHudInterface; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.client.gui.hud.PlayerListHud; +import org.spongepowered.asm.mixin.Final; +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.Redirect; + +@Mixin(InGameHud.class) +public abstract class InGameHudMixin +{ + @Shadow + @Final + private MinecraftClient client; + + @Shadow @Final private PlayerListHud playerListHud; + + @Redirect(method = "draw", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;isInSingleplayer()Z")) + private boolean onDraw(MinecraftClient minecraftClient) + { + return this.client.isInSingleplayer() && !((PlayerListHudInterface) playerListHud).hasFooterOrHeader(); + } + +} diff --git a/src/main/java/carpet/mixins/PlayerListHudMixin.java b/src/main/java/carpet/mixins/PlayerListHudMixin.java new file mode 100644 index 00000000..ef6508c5 --- /dev/null +++ b/src/main/java/carpet/mixins/PlayerListHudMixin.java @@ -0,0 +1,19 @@ +package carpet.mixins; +import carpet.fakes.PlayerListHudInterface; +import net.minecraft.client.gui.hud.PlayerListHud; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(PlayerListHud.class) +public abstract class PlayerListHudMixin implements PlayerListHudInterface +{ + @Shadow private Component footer; + + @Shadow private Component header; + + public boolean hasFooterOrHeader() + { + return footer != null || header != null; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/mixins/ServerChunkManagerMixin.java b/src/main/java/carpet/mixins/ServerChunkManagerMixin.java new file mode 100644 index 00000000..11d3666f --- /dev/null +++ b/src/main/java/carpet/mixins/ServerChunkManagerMixin.java @@ -0,0 +1,53 @@ +package carpet.mixins; + +import carpet.CarpetSettings; +import carpet.utils.SpawnReporter; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.entity.EntityCategory; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.level.LevelProperties; +import org.spongepowered.asm.mixin.Final; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ServerChunkManager.class) +public abstract class ServerChunkManagerMixin +{ + @Shadow @Final private ServerWorld world; + + @Inject( + method = "tickChunks", + locals = LocalCapture.CAPTURE_FAILHARD, + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/world/ThreadedAnvilChunkStorage;entryIterator()Lit/unimi/dsi/fastutil/objects/ObjectBidirectionalIterator;", + shift = At.Shift.AFTER, + ordinal = 0 + + )) + private void grabMobcaps(CallbackInfo ci, + long long_1, + long long_2, + LevelProperties levelProperties_1, + boolean boolean_1, + boolean boolean_2, + int int_1, + BlockPos blockPos_1, + boolean boolean_3, + int int_2, + EntityCategory[] entityCategorys_1, + Object2IntMap object2IntMap_1) + { + DimensionType dim = this.world.dimension.getType(); + SpawnReporter.mobCounts.put(dim, (Object2IntMap)object2IntMap_1); + SpawnReporter.chunkCounts.put(dim, int_2); + } + +} diff --git a/src/main/java/carpet/utils/HUDController.java b/src/main/java/carpet/utils/HUDController.java new file mode 100644 index 00000000..d667252e --- /dev/null +++ b/src/main/java/carpet/utils/HUDController.java @@ -0,0 +1,117 @@ +package carpet.utils; + +import carpet.helpers.HopperCounter; +import carpet.helpers.TickSpeed; +import carpet.logging.LoggerRegistry; +import carpet.logging.logHelpers.PacketCounter; +import carpet.mixins.PlayerListHeaderS2CPacketMixin; +import net.minecraft.entity.EntityCategory; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.client.network.packet.PlayerListHeaderS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Pair; +import net.minecraft.util.math.MathHelper; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.world.dimension.DimensionType; + +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) -> { + DimensionType dim = player.dimension; + switch (option) + { + case "overworld": + dim = DimensionType.OVERWORLD; + break; + case "nether": + dim = DimensionType.THE_NETHER; + break; + case "end": + dim = DimensionType.THE_END; + break; + } + return new BaseComponent[]{SpawnReporter.printMobcapsForDimension(dim, false).get(0)}; + }); + + 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_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/Messenger.java b/src/main/java/carpet/utils/Messenger.java index f1031ca4..bbdfacab 100644 --- a/src/main/java/carpet/utils/Messenger.java +++ b/src/main/java/carpet/utils/Messenger.java @@ -80,7 +80,8 @@ public class Messenger } public static String heatmap_color(double actual, double reference) { - String color = "e"; + String color = "g"; + if (actual >= 0.0D) color = "e"; if (actual > 0.5D*reference) color = "y"; if (actual > 0.8D*reference) color = "r"; if (actual > reference) color = "m"; diff --git a/src/main/java/carpet/utils/SpawnReporter.java b/src/main/java/carpet/utils/SpawnReporter.java new file mode 100644 index 00000000..7e456c87 --- /dev/null +++ b/src/main/java/carpet/utils/SpawnReporter.java @@ -0,0 +1,526 @@ + +package carpet.utils; + +import java.util.List; + +import java.util.ArrayList; +import java.util.HashMap; + +import carpet.mixins.WeightedPickerEntryMixin; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.entity.SpawnType; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.structure.OceanMonumentGenerator; +import net.minecraft.util.Pair; +import net.minecraft.util.registry.Registry; +import net.minecraft.network.chat.BaseComponent; +import net.minecraft.world.World; +import net.minecraft.util.math.BlockPos; + +import net.minecraft.util.DyeColor; + +import net.minecraft.entity.EntityCategory; +import net.minecraft.world.biome.Biome; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.SpawnHelper; +import net.minecraft.entity.SpawnRestriction; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.Entity; + +import net.minecraft.entity.passive.OcelotEntity; +import net.minecraft.world.Heightmap; +import net.minecraft.world.gen.chunk.ChunkGenerator; + +import java.lang.Math; + +public class SpawnReporter +{ + public static String [] mob_groups = {"hostile","passive","water","ambient"}; + public static boolean mock_spawns = false; + + public static Long track_spawns = 0L; + public static final HashMap> mobCounts = new HashMap<>(); + public static final HashMap chunkCounts = 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(DimensionType dim, boolean multiline) + { + String name = dim.toString(); + List lst = new ArrayList<>(); + if (multiline) + lst.add(Messenger.s(String.format("Mobcaps for %s:",name))); + Object2IntMap dimCounts = mobCounts.get(dim); + int chunkcount = chunkCounts.getOrDefault(dim, -1); + if (dimCounts == null || chunkcount < 0) + { + lst.add(Messenger.c("g --UNAVAILABLE--")); + return lst; + } + + List shortCodes = new ArrayList<>(); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + int cur = dimCounts.getOrDefault(enumcreaturetype, -1); + int max = chunkcount * enumcreaturetype.getSpawnCap() / (int)Math.pow(17.0D, 2.0D); // from ServerChunkManager.CHUNKS_ELIGIBLE_FOR_SPAWNING + String color = Messenger.heatmap_color(cur, max); + String mobColor = Messenger.creatureTypeColor(enumcreaturetype); + if (multiline) + { + int rounds = spawn_tries.get(type_code); + lst.add(Messenger.c(String.format("w %s: ", type_code), + (cur < 0) ? "g -" : (color + " " + cur), "g / ", mobColor + " " + max, + (rounds == 1) ? "w " : String.format("fi (%d rounds/tick)", spawn_tries.get(type_code)) + )); + } + else + { + shortCodes.add(color+" "+((cur<0)?"-":cur)); + shortCodes.add("g /"); + shortCodes.add(mobColor+" "+max); + shortCodes.add("g ,"); + } + } + if (!multiline) + { + if (shortCodes.size()>0) + { + shortCodes.remove(shortCodes.size() - 1); + lst.add(Messenger.c(shortCodes.toArray(new Object[0]))); + } + else + { + lst.add(Messenger.c("g --UNAVAILABLE--")); + } + + } + return lst; + } + + public static List recent_spawns(World world, String creature_type_code) + { + + EntityCategory creature_type = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if ((track_spawns == 0L)) + { + lst.add(Messenger.s("Spawn tracking not started")); + return lst; + } + if (creature_type == null) + { + lst.add(Messenger.s(String.format("Incorrect creature type: %s",creature_type_code))); + return lst; + } + + String type_code = get_type_string(creature_type); + + lst.add(Messenger.s(String.format("Recent %s spawns:",type_code))); + for (SpawnPos entry: spawned_mobs.get(type_code).keySet()) + { + lst.add( Messenger.c(String.format("w - %s ",entry.mob), Messenger.tp("wb",entry.pos))); + } + + if (lst.size()==1) + { + lst.add(Messenger.s(" - Nothing spawned yet, sorry.")); + } + return lst; + + } + + public static List show_mobcaps(BlockPos pos, World worldIn) + { + DyeColor under = WoolTool.getWoolColorAtPosition(worldIn, pos.down()); + if (under == null) + { + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + //return print_general_mobcaps(worldIn); + } + } + String creature_type = get_type_code_from_wool_code(under); + if (creature_type != null) + { + if (track_spawns > 0L) + { + return recent_spawns(worldIn, creature_type); + } + else + { + return printEntitiesByType(creature_type, worldIn); + + } + + } + if (track_spawns > 0L) + { + return tracking_report(worldIn); + } + else + { + return tracking_report(worldIn); + //// TODO + //return print_general_mobcaps(worldIn); + } + + } + + public static String get_type_code_from_wool_code(DyeColor color) + { + switch (color) + { + case RED: + return "hostile"; + case GREEN: + return "passive"; + case BLUE: + return "water"; + case BROWN: + return "ambient"; + } + return null; + } + + public static EntityCategory get_creature_type_from_code(String type_code) + { + if ("hostile".equalsIgnoreCase(type_code)) + { + return EntityCategory.MONSTER; + } + else if ("passive".equalsIgnoreCase(type_code)) + { + return EntityCategory.CREATURE; + } + else if ("water".equalsIgnoreCase(type_code)) + { + return EntityCategory.WATER_CREATURE; + } + else if ("ambient".equalsIgnoreCase(type_code)) + { + return EntityCategory.AMBIENT; + } + return null; + } + + + public static String get_type_string(EntityCategory type) + { + return String.format("%s", type); + } + + public static String get_creature_code_from_string(String str) + { + return get_type_string(get_creature_type_from_code(str)); + } + + public static List printEntitiesByType(String creature_type_code, World worldIn) //Class entityType) + { + EntityCategory typ = get_creature_type_from_code(creature_type_code); + List lst = new ArrayList<>(); + if (typ == null) + { + lst.add(Messenger.c(String.format("r Incorrect creature type: %s",creature_type_code))); + return lst; + } + Class cls = typ.getDeclaringClass();// getBaseClass();// getCreatureClass(); + lst.add( Messenger.s(String.format("Loaded entities for %s class:", get_type_string(typ)))); + + lst.add(Messenger.s(" ")); + //for (Entity entity : ((ServerWorld)worldIn).getEntities(...)) + //{ + // if ((!(entity instanceof LivingEntity) || !((LivingEntity)entity).isNoDespawnRequired()) && cls.isAssignableFrom(entity.getClass())) + // { + // lst.add(Messenger.c("w - ", + // Messenger.tp("w", entity.posX, entity.posY, entity.posZ),"w : "+EntityType.getId(entity.getType()))); + // } + //} + if (lst.size()==1) + { + lst.add(Messenger.s(" - Empty.")); + } + return lst; + } + + public static void initialize_mocking() + { + reset_spawn_stats(false); + mock_spawns = true; + + } + public static void stop_mocking() + { + reset_spawn_stats(false); + mock_spawns = false; + + } + public static void reset_spawn_stats(boolean full) + { + spawn_stats.clear(); + spawned_mobs.clear(); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + if (full) + { + spawn_tries.put(type_code, 1); + } + for (String suffix: new String[] {""," (N)"," (E)"}) + { + String code = type_code+suffix; + overall_spawn_ticks.put(code, 0L); + spawn_attempts.put(code, 0L); + spawn_ticks_full.put(code, 0L); + spawn_ticks_fail.put(code, 0L); + spawn_ticks_succ.put(code, 0L); + spawn_ticks_spawns.put(code, 0L); + spawn_cap_count.put(code, 0L); + } + + spawn_stats.put(type_code, new HashMap<>()); + spawned_mobs.put(type_code, new EvictingQueue<>()); + } + track_spawns = 0L; + } + + public static List tracking_report(World worldIn) + { + List report = new ArrayList<>(); + if (track_spawns == 0L) + { + report.add(Messenger.c( + "w Spawn tracking disabled, type '", + "wi /spawn tracking start","/spawn tracking start", + "w ' to enable")); + return report; + } + Long duration = (long) worldIn.getServer().getTicks() - track_spawns; + report.add(Messenger.c("bw --------------------")); + String simulated = mock_spawns?"[SIMULATED] ":""; + String location = (lower_spawning_limit != null)?String.format("[in (%d, %d, %d)x(%d, %d, %d)]", + lower_spawning_limit.getX(),lower_spawning_limit.getY(),lower_spawning_limit.getZ(), + upper_spawning_limit.getX(),upper_spawning_limit.getY(),upper_spawning_limit.getZ() ):""; + report.add(Messenger.s(String.format("%sSpawn statistics %s: for %.1f min", simulated, location, (duration/72000.0)*60))); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype); + boolean there_are_mobs_to_list = false; + for (String world_code: new String[] {"", " (N)", " (E)"}) + { + String code = type_code+world_code; + if (spawn_ticks_spawns.get(code) > 0L) + { + there_are_mobs_to_list = true; + double hours = overall_spawn_ticks.get(code)/72000.0; + report.add(Messenger.s(String.format(" > %s (%.1f min), %.1f m/t, {%.1f%%F / %.1f%%- / %.1f%%+}; %.2f s/att", + code, + 60*hours, + (1.0D*spawn_cap_count.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_full.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_fail.get(code))/ spawn_attempts.get(code), + (100.0D*spawn_ticks_succ.get(code))/ spawn_attempts.get(code), + (1.0D*spawn_ticks_spawns.get(code))/(spawn_ticks_fail.get(code)+spawn_ticks_succ.get(code)) + ))); + } + } + if (there_are_mobs_to_list) + { + for (String creature_name : spawn_stats.get(type_code).keySet()) + { + report.add(Messenger.s(String.format(" - %s: %d spawns, %d per hour", + creature_name, + spawn_stats.get(type_code).get(creature_name), + (72000 * spawn_stats.get(type_code).get(creature_name)/duration )))); + } + } + } + return report; + } + + + + public static void killEntity(LivingEntity entity) + { + if (entity.hasVehicle()) + { + entity.getVehicle().remove(); + } + if (entity.hasPassengers()) + { + for (Entity e: entity.getPassengerList()) + { + e.remove(); + } + } + if (entity instanceof OcelotEntity) + { + for (Entity e: entity.getEntityWorld().getEntities(OcelotEntity.class, entity.getBoundingBox())) + { + e.remove(); + } + } + entity.remove(); + } + + public static List report(BlockPos pos, World worldIn) + { + List rep = new ArrayList<>(); + int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); + Chunk chunk = worldIn.getChunk(pos); + int lc = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, x, z) + 1; + String where = String.format((y >= lc) ? "%d blocks above it." : "%d blocks below it.", MathHelper.abs(y-lc)); + if (y == lc) where = "right at it."; + rep.add(Messenger.s(String.format("Maximum spawn Y value for (%+d, %+d) is %d. You are "+where, x, z, lc ))); + rep.add(Messenger.s("Spawns:")); + for (EntityCategory enumcreaturetype : EntityCategory.values()) + { + String type_code = String.format("%s", enumcreaturetype).substring(0, 3); + List lst = ((ChunkGenerator)worldIn.getChunkManager().getChunkGenerator()).getEntitySpawnList(enumcreaturetype, pos); + if (lst != null && !lst.isEmpty()) + { + for (Biome.SpawnEntry spawnEntry : lst) + { + boolean canspawn = SpawnHelper.canSpawn(SpawnRestriction.getLocation(spawnEntry.type), worldIn, pos, spawnEntry.type); + int will_spawn = -1; + boolean fits = false; + boolean fits1 = false; + + MobEntity mob; + try + { + mob = (MobEntity) spawnEntry.type.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + + boolean fits_true = false; + boolean fits_false = false; + + if (canspawn) + { + will_spawn = 0; + for (int attempt = 0; attempt < 50; ++attempt) + { + float f = (float)x + 0.5F; + float f1 = (float)z + 0.5F; + mob.setPositionAndAngles((double)f, (double)y, (double)f1, worldIn.random.nextFloat() * 360.0F, 0.0F); + fits1 = worldIn.doesNotCollide(mob); + + for (int i = 0; i < 20; ++i) + { + if (mob.canSpawn(worldIn, SpawnType.NATURAL)) + { + will_spawn += 1; + } + } + mob.initialize(worldIn, worldIn.getLocalDifficulty(new BlockPos(mob)), SpawnType.NATURAL, null, null); + // the code invokes onInitialSpawn after getCanSpawHere + fits = fits1 && worldIn.doesNotCollide(mob); + if (fits) + { + fits_true = true; + } + else + { + fits_false = true; + } + + killEntity(mob); + + try + { + mob = (MobEntity) spawnEntry.type.create(worldIn); + } + catch (Exception exception) + { + exception.printStackTrace(); + return rep; + } + } + } + + String creature_name = Registry.ENTITY_TYPE.getId(mob.getType()).toString().replaceFirst("minecraft:",""); + String pack_size = String.format("%d", mob.getLimitPerChunk());//String.format("%d-%d", animal.minGroupCount, animal.maxGroupCount); + int weight = ((WeightedPickerEntryMixin) spawnEntry).getWeight(); + if (canspawn) + { + String c = (fits_true && will_spawn>0)?"e":"gi"; + rep.add(Messenger.c( + String.format("%s %s: %s (%d:%d-%d/%d), can: ",c,type_code,creature_name,weight,spawnEntry.minGroupSize, spawnEntry.maxGroupSize, mob.getLimitPerChunk()), + "l YES", + c+" , fit: ", + ((fits_true && fits_false)?"y YES and NO":(fits_true?"l YES":"r NO")), + c+" , will: ", + ((will_spawn>0)?"l ":"r ")+Math.round((double)will_spawn)/10+"%" + )); + } + else + { + rep.add(Messenger.c(String.format("gi %s: %s (%d:%d-%d/%d), can: ",type_code,creature_name,weight,spawnEntry.minGroupSize, spawnEntry.maxGroupSize, mob.getLimitPerChunk()), "n NO")); + } + killEntity(mob); + } + } + } + return rep; + } +} \ No newline at end of file diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index ee4411e6..ea3e48fe 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -3,10 +3,23 @@ "package": "carpet.mixins", "compatibilityLevel": "JAVA_8", "mixins": [ - "CommandManagerMixin" + "CommandManagerMixin", + "MinecraftServerMixin", + "PlayerListHeaderS2CPacketMixin", + "WeightedPickerEntryMixin", + "ExperienceOrbEntityMixin", + "PlayerActionC2SPacketMixin", + "ServerPlayerEntityMixin", + "ServerChunkManagerMixin" ], "client": [ - "CommandManagerMixin" + "MinecraftClientMixin", + "IntegratedServerMixin", + "InGameHudMixin", + "PlayerListHudMixin" + ], + "server": [ + "MinecraftDedicatedServerMixin" ], "injectors": { "defaultRequire": 1