diff --git a/build.gradle b/build.gradle index 447d919d..4f42e9a9 100644 --- a/build.gradle +++ b/build.gradle @@ -129,7 +129,7 @@ dependencies { implementation fg.deobf("mezz.jei:jei-1.19.2-forge:11.5.0.297") - implementation 'ca.bradj:RoomRecipes:1.19.2-0.0.4-alpha.2' + implementation 'ca.bradj:RoomRecipes:1.19.2-0.0.5-alpha.1' compileOnly fg.deobf("vazkii.patchouli:Patchouli:1.19.2-77:api") runtimeOnly fg.deobf("vazkii.patchouli:Patchouli:1.19.2-77") diff --git a/src/main/java/ca/bradj/questown/commands/CommandEvents.java b/src/main/java/ca/bradj/questown/commands/CommandEvents.java index 24a4824e..bd89c746 100644 --- a/src/main/java/ca/bradj/questown/commands/CommandEvents.java +++ b/src/main/java/ca/bradj/questown/commands/CommandEvents.java @@ -16,5 +16,7 @@ public static void on(RegisterCommandsEvent event) { FreezeCommand.register(event.getDispatcher()); ConfigCommand.register(event.getDispatcher()); FlagCommand.register(event.getDispatcher()); + DebugCommand.register(event.getDispatcher()); + DebugDoorsCommand.register(event.getDispatcher()); } } diff --git a/src/main/java/ca/bradj/questown/commands/DebugCommand.java b/src/main/java/ca/bradj/questown/commands/DebugCommand.java new file mode 100644 index 00000000..bc0c55f4 --- /dev/null +++ b/src/main/java/ca/bradj/questown/commands/DebugCommand.java @@ -0,0 +1,36 @@ +package ca.bradj.questown.commands; + +import ca.bradj.questown.town.TownFlagBlockEntity; +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; + +public class DebugCommand { + public static void register(CommandDispatcher p_137808_) { + p_137808_.register(Commands.literal("qtdebug") + .requires((p_137812_) -> { + return p_137812_.hasPermission(2); + }) + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(css -> { + return startDebug(css.getSource(), BlockPosArgument.getLoadedBlockPos(css, "pos")); + }))); + } + + private static int startDebug( + CommandSourceStack source, + BlockPos target + ) { + BlockEntity e = source.getLevel().getBlockEntity(target); + if (!(e instanceof TownFlagBlockEntity tfbe)) { + // TODO: Better error handling? + return -1; + } + + tfbe.toggleDebugMode(); + return 0; + } +} diff --git a/src/main/java/ca/bradj/questown/commands/DebugDoorsCommand.java b/src/main/java/ca/bradj/questown/commands/DebugDoorsCommand.java new file mode 100644 index 00000000..ab6ef133 --- /dev/null +++ b/src/main/java/ca/bradj/questown/commands/DebugDoorsCommand.java @@ -0,0 +1,50 @@ +package ca.bradj.questown.commands; + +import ca.bradj.questown.blocks.TownFlagBlock; +import ca.bradj.questown.core.init.items.ItemsInit; +import ca.bradj.questown.town.TownFlagBlockEntity; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; + +public class DebugDoorsCommand { + public static void register(CommandDispatcher p_137808_) { + p_137808_.register(Commands.literal("qtdebugdoors") + .requires((p_137812_) -> { + return p_137812_.hasPermission(2); + }) + .then(Commands.argument("pos", BlockPosArgument.blockPos()) + .executes(css -> { + return giveDebug(css.getSource(), BlockPosArgument.getLoadedBlockPos(css, "pos")); + }))); + } + + private static int giveDebug( + CommandSourceStack source, + BlockPos target + ) { + BlockEntity e = source.getLevel().getBlockEntity(target); + if (!(e instanceof TownFlagBlockEntity tfbe)) { + // TODO: Better error handling? + return -1; + } + + ItemStack debugItem = ItemsInit.TOWN_DOOR_TESTER.get() + .getDefaultInstance(); + TownFlagBlock.StoreParentOnNBT( + debugItem, + target + ); + try { + source.getPlayerOrException().getInventory().add(debugItem); + } catch (CommandSyntaxException ex) { + throw new RuntimeException(ex); + } + return 0; + } +} diff --git a/src/main/java/ca/bradj/questown/core/Config.java b/src/main/java/ca/bradj/questown/core/Config.java index 3fd1b9e9..09708c41 100644 --- a/src/main/java/ca/bradj/questown/core/Config.java +++ b/src/main/java/ca/bradj/questown/core/Config.java @@ -55,6 +55,7 @@ public class Config { public static final ForgeConfigSpec.ConfigValue JOB_BOARD_ENABLED; public static final ForgeConfigSpec.ConfigValue CRASH_ON_FAILED_WARP; public static final ForgeConfigSpec.ConfigValue TIME_WARP_MAX_TICKS; + public static final ForgeConfigSpec.ConfigValue LOG_WARP_RESULT; public static final ForgeConfigSpec.ConfigValue BASE_MAX_LOOP; public static final ForgeConfigSpec.ConfigValue MAX_TICKS_WITHOUT_SUPPLIES; public static final ForgeConfigSpec.ConfigValue BASE_FULLNESS; @@ -65,7 +66,8 @@ public class Config { public static final ForgeConfigSpec.ConfigValue NEUTRAL_MOOD; public static final ForgeConfigSpec.ConfigValue MOOD_EFFECT_DURATION_ATE_UNCOMFORTABLY; public static final ForgeConfigSpec.ConfigValue MOOD_EFFECT_DURATION_ATE_COMFORTABLY; - + public static final ForgeConfigSpec.ConfigValue MAX_ROOM_DIMENSION; + public static final ForgeConfigSpec.ConfigValue MAX_ROOM_SCAN_ITERATIONS; static { // Scanning Config @@ -86,6 +88,12 @@ public class Config { BIOME_SCAN_RADIUS = BUILDER.comment( "The radius of chunks that will be scanned outward (in a plus shape) from the flag for the purpose of populating gatherer loot" ).defineInRange("BiomeScanRadius", 20, 0, 100); + MAX_ROOM_DIMENSION = BUILDER.comment( + "The maximum length or width of a room that can be detected" + ).defineInRange("MaxRoomDimension", 20, 2, 100); + MAX_ROOM_SCAN_ITERATIONS = BUILDER.comment( + "The maximum number of ticks that will be spent searching for town rooms" + ).defineInRange("MaxRoomScanIterations", 1000, 1, 24000); BUILDER.pop(); // Quests Config @@ -212,6 +220,9 @@ public class Config { "The radius of \"Meta-Rooms\" that exist around points of interest in the town. E.g." + "There is a meta room around the town flag itself, and one around each welcome mat" ).defineInRange("MetaRoomDiameter", 2, 1, 100); + BLOCK_CLAIMS_TICK_LIMIT = BUILDER.comment( + "If a job claims a block. It will hold that claim for this many ticks. (Or until they finish their work, whatever happens first)" + ).defineInRange("BlockClaimsTickLimit", 1000L, 1, 24000); // Time Warp BUILDER.push("TimeWarp").comment( @@ -223,9 +234,9 @@ public class Config { TIME_WARP_MAX_TICKS = BUILDER.comment( "Since the player can be gone for a very long time, we enforce a maximum warp to prevent the warp taking too long to compute." ).defineInRange("MaxTicks", 200000, 1, Integer.MAX_VALUE); - BLOCK_CLAIMS_TICK_LIMIT = BUILDER.comment( - "If a job claims a block. It will hold that claim for this many ticks. (Or until they finish their work, whatever happens first)" - ).defineInRange("BlockClaimsTickLimit", 1000L, 1, 24000); + LOG_WARP_RESULT = BUILDER.comment( + "Set true to enable logging of the town state after a time warp. This produces a large log message and may cause lag." + ).define("LogWarpResult", false); BUILDER.pop(); // Yep, really thrice. Getting out of nested config BUILDER.pop(); BUILDER.pop(); diff --git a/src/main/java/ca/bradj/questown/core/init/items/ItemsInit.java b/src/main/java/ca/bradj/questown/core/init/items/ItemsInit.java index 2ac90617..4c211fc2 100644 --- a/src/main/java/ca/bradj/questown/core/init/items/ItemsInit.java +++ b/src/main/java/ca/bradj/questown/core/init/items/ItemsInit.java @@ -79,6 +79,11 @@ public class ItemsInit { TownDoorItem::new ); + public static final RegistryObject TOWN_DOOR_TESTER = ITEMS.register( + TownDoorTestItem.ITEM_ID, + TownDoorTestItem::new + ); + public static final RegistryObject FALSE_DOOR = ITEMS.register( FalseDoorItem.ITEM_ID, FalseDoorItem::new diff --git a/src/main/java/ca/bradj/questown/integration/minecraft/MCContainer.java b/src/main/java/ca/bradj/questown/integration/minecraft/MCContainer.java index 5d514731..aec0af5a 100644 --- a/src/main/java/ca/bradj/questown/integration/minecraft/MCContainer.java +++ b/src/main/java/ca/bradj/questown/integration/minecraft/MCContainer.java @@ -85,9 +85,9 @@ public String toShortString() { @Override public String toString() { - Collection items = new ArrayList<>(); + Collection items = new ArrayList<>(); for (int i = 0; i < container.getContainerSize(); i++) { - items.add(getItem(i)); + items.add(getItem(i).getShortName()); } return "MCContainer{" + "container.items=" + items + diff --git a/src/main/java/ca/bradj/questown/integration/minecraft/MCTownItem.java b/src/main/java/ca/bradj/questown/integration/minecraft/MCTownItem.java index 739d1cd7..f8381e7d 100644 --- a/src/main/java/ca/bradj/questown/integration/minecraft/MCTownItem.java +++ b/src/main/java/ca/bradj/questown/integration/minecraft/MCTownItem.java @@ -114,6 +114,9 @@ public String getShortName() { if (registryName != null) { name = registryName.toString(); } + if (quantity > 1) { + name = String.format("%sx%s", quantity, name); + } return name; } diff --git a/src/main/java/ca/bradj/questown/items/TownDoorTestItem.java b/src/main/java/ca/bradj/questown/items/TownDoorTestItem.java new file mode 100644 index 00000000..d53e0b8c --- /dev/null +++ b/src/main/java/ca/bradj/questown/items/TownDoorTestItem.java @@ -0,0 +1,53 @@ +package ca.bradj.questown.items; + +import ca.bradj.questown.Questown; +import ca.bradj.questown.blocks.TownFlagBlock; +import ca.bradj.questown.town.TownFlagBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import org.jetbrains.annotations.NotNull; + +public class TownDoorTestItem extends Item { + public static final String ITEM_ID = "town_door_tester"; + + public TownDoorTestItem() { + super(Questown.DEFAULT_ITEM_PROPS); + } + + @Override + public InteractionResult useOn(net.minecraft.world.item.context.UseOnContext ctx) { + return startDebug(ctx); + } + + @NotNull + private static InteractionResult startDebug(net.minecraft.world.item.context.UseOnContext ctx) { + if (ctx.getLevel() + .isClientSide()) { + return InteractionResult.PASS; + } + TownFlagBlockEntity parent = TownFlagBlock.GetParentFromNBT((ServerLevel) ctx.getLevel(), ctx.getItemInHand()); + BlockPos clickedPos = ctx.getClickedPos(); + BlockState bs = parent.getServerLevel() + .getBlockState(clickedPos); + if (bs.getBlock() instanceof DoorBlock) { + if (DoubleBlockHalf.UPPER.equals(bs.getValue(DoorBlock.HALF))) { + clickedPos = clickedPos.below(); + } + } else { + bs = parent.getServerLevel() + .getBlockState(clickedPos.above()); + if (bs.getBlock() instanceof DoorBlock) { + clickedPos = clickedPos.above(); + } + } + parent.startDebugTask(parent.getRoomHandle() + .getDebugTaskForDoor(clickedPos)); + return InteractionResult.CONSUME; + } + +} diff --git a/src/main/java/ca/bradj/questown/jobs/declarative/DinerWork.java b/src/main/java/ca/bradj/questown/jobs/declarative/DinerWork.java index d142cd62..a989e190 100644 --- a/src/main/java/ca/bradj/questown/jobs/declarative/DinerWork.java +++ b/src/main/java/ca/bradj/questown/jobs/declarative/DinerWork.java @@ -90,7 +90,7 @@ public static Work asWork( new ExpirationRules( () -> Long.MAX_VALUE, jobId -> jobId, - Config.MAX_TICKS_WITHOUT_DINING_TABLE, + Util.configGet(Config.MAX_TICKS_WITHOUT_DINING_TABLE), jobId -> DinerNoTableWork.getIdForRoot(jobId.rootId()) ) ); diff --git a/src/main/java/ca/bradj/questown/mc/Util.java b/src/main/java/ca/bradj/questown/mc/Util.java index 89a84080..e08d07cc 100644 --- a/src/main/java/ca/bradj/questown/mc/Util.java +++ b/src/main/java/ca/bradj/questown/mc/Util.java @@ -16,6 +16,7 @@ import net.minecraft.world.MenuProvider; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkHooks; @@ -122,4 +123,8 @@ public static ImmutableMap realize(ImmutableMap b.put(k, v.get())); return b.build(); } + + public static Supplier configGet(ForgeConfigSpec.ConfigValue cfg) { + return cfg::get; + } } diff --git a/src/main/java/ca/bradj/questown/town/TownFlagBlockEntity.java b/src/main/java/ca/bradj/questown/town/TownFlagBlockEntity.java index e7f53547..80ec9f11 100644 --- a/src/main/java/ca/bradj/questown/town/TownFlagBlockEntity.java +++ b/src/main/java/ca/bradj/questown/town/TownFlagBlockEntity.java @@ -45,7 +45,6 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; -import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; @@ -78,12 +77,15 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import static ca.bradj.questown.town.TownFlagState.NBT_TIME_WARP_REFERENCE_TICK; import static ca.bradj.questown.town.TownFlagState.NBT_TOWN_STATE; -public class TownFlagBlockEntity extends BlockEntity implements TownInterface, ActiveRecipes.ChangeListener>, QuestBatch.ChangeListener, TownPois.Listener { +public class TownFlagBlockEntity extends BlockEntity implements TownInterface, + ActiveRecipes.ChangeListener>, QuestBatch.ChangeListener, + TownPois.Listener { public static final String ID = "flag_base_block_entity"; // TODO: Extract serialization @@ -127,6 +129,8 @@ public class TownFlagBlockEntity extends BlockEntity implements TownInterface, A private final TownRoomsHandle roomsHandle = new TownRoomsHandle(); private final TownVillagerHandle villagerHandle = new TownVillagerHandle(); + private @Nullable Supplier debugTask; + private boolean debugMode; public TownFlagBlockEntity( BlockPos p_155229_, @@ -152,7 +156,8 @@ public static void tick( QT.FLAG_LOGGER.error("No players detected in world"); return; } - double distToPlayer = nearestPlayer.blockPosition().distSqr(e.worldPosition); + double distToPlayer = nearestPlayer.blockPosition() + .distSqr(e.worldPosition); if (distToPlayer > Config.TOWN_TICK_RADIUS.get()) { if (!stopped) { QT.FLAG_LOGGER.info( @@ -177,8 +182,20 @@ public static void tick( return; } + // Must tick sub-blocks even with debug mode enabled, + // because non-ticked sub-blocks will self-destruct. e.subBlocks.parentTick(sl); + if (e.debugMode) { + if (e.debugTask != null) { + boolean done = e.debugTask.get(); + if (done) { + e.debugTask = null; + } + } + return; + } + if (!e.mornings.empty()) { e.morningTick(e.mornings.pop()); } @@ -196,18 +213,18 @@ public static void tick( e.workHandle.tick(sl); e.quests.tick(e); - long gameTime = level.getGameTime(); - long l = gameTime % 10; - if (l != 0) { - return; - } - if (e.nearbyBiomes.isEmpty()) { computeNearbyBiomes(level, blockPos, e); } e.roomsHandle.tick(sl, blockPos); + long gameTime = level.getGameTime(); + long l = gameTime % 10; + if (l != 0) { + return; + } + Collection allRooms = e.roomsHandle.getAllRoomsIncludingMeta(); e.jobHandle.tick(sl, allRooms); e.jobHandles.forEach((k, v) -> v.tick(sl, allRooms)); @@ -229,12 +246,14 @@ private static void computeNearbyBiomes( TownFlagBlockEntity e ) { ChunkPos here = new ChunkPos(blockPos); - Biome value = level.getBiome(blockPos).value(); + Biome value = level.getBiome(blockPos) + .value(); e.nearbyBiomes.add(value); for (Direction d : Direction.Plane.HORIZONTAL) { for (int i = 0; i < Config.BIOME_SCAN_RADIUS.get(); i++) { ChunkPos there = new ChunkPos(here.x + d.getStepX() * i, here.z + d.getStepZ() * i); - Biome biome = level.getBiome(there.getMiddleBlockPosition(blockPos.getY())).value(); + Biome biome = level.getBiome(there.getMiddleBlockPosition(blockPos.getY())) + .value(); e.nearbyBiomes.add(biome); } } @@ -248,7 +267,8 @@ private void morningTick(Long newTime) { this.setChanged(); villagerHandle.forEach(LivingEntity::stopSleeping); villagerHandle.makeAllTotallyHungry(); - Util.getBlockStoredTagData(this).putLong(NBT_TIME_WARP_REFERENCE_TICK, newTime); + Util.getBlockStoredTagData(this) + .putLong(NBT_TIME_WARP_REFERENCE_TICK, newTime); } private static void profileTick( @@ -262,7 +282,10 @@ private static void profileTick( if (e.times.size() > Config.TICK_SAMPLING_RATE.get()) { QT.PROFILE_LOGGER.debug( "Average tick length: {}", - e.times.stream().mapToInt(Integer::intValue).average() + e.times.stream() + .mapToInt(Integer::intValue) + .average() + .getAsDouble() ); e.times.clear(); } @@ -322,7 +345,8 @@ private static void loadNextTick(Queue> i initializers.add(t -> { CompoundTag tag = Util.getBlockStoredTagData(t); if (tag.contains(NBT_ROOMS)) { - TownRoomsMapSerializer.INSTANCE.deserialize(tag.getCompound(NBT_ROOMS), t, t.roomsHandle.getRegisteredRooms()); + TownRoomsMapSerializer.INSTANCE.deserialize( + tag.getCompound(NBT_ROOMS), t, t.roomsHandle.getRegisteredRooms()); } return true; }); @@ -387,13 +411,17 @@ private static void loadNextTick(Queue> i t.villagerHandle.associate(t); } t.villagerHandle.addHungryListener(e -> { - if (t.getVillagerHandle().isDining(e.getUUID())) { + if (t.getVillagerHandle() + .isDining(e.getUUID())) { return; } - if (!t.getVillagerHandle().canDine(e.getUUID())) { + if (!t.getVillagerHandle() + .canDine(e.getUUID())) { return; } - t.changeJobForVisitor(e.getUUID(), DinerWork.getIdForRoot(e.getJobId().rootId())); + t.changeJobForVisitor( + e.getUUID(), DinerWork.getIdForRoot(e.getJobId() + .rootId())); }); t.villagerHandle.addStatsListener(s -> t.setChanged()); return true; @@ -419,7 +447,10 @@ public void writeTownData(CompoundTag tag) { tag.put(NBT_ROOMS, TownRoomsMapSerializer.INSTANCE.serializeNBT(roomsHandle.getRegisteredRooms())); tag.put(NBT_JOBS, TownWorkHandleSerializer.INSTANCE.serializeNBT(workHandle)); QTNBT.put(tag, NBT_KNOWLEDGE, TownKnowledgeStoreSerializer.INSTANCE.serializeNBT(knowledgeHandle)); - QTNBT.put(tag, NBT_VILLAGERS, TownVillagerHandle.SERIALIZER.serialize(villagerHandle, Util.getTick(getServerLevel()))); + QTNBT.put( + tag, NBT_VILLAGERS, + TownVillagerHandle.SERIALIZER.serialize(villagerHandle, Util.getTick(getServerLevel())) + ); // TODO: Serialization for ASAPss } @@ -479,19 +510,20 @@ private void updateWorkersAfterRequestChange() { prefix )); villagerHandle.stream() - .filter(v -> v instanceof VisitorMobEntity) - .map(v -> (VisitorMobEntity) v) - .filter(e -> { - for (WorkRequest r : workHandle.getRequestedResults()) { - if (JobsRegistry.canSatisfy(td, e.getJobId(), r.asIngredient())) { - if (e.getStatusForServer().isBusy()) { - return false; - } - } - } - return true; - }) - .forEach(e -> changeJobForVisitor(e.getUUID(), WorkSeekerJob.getIDForRoot(e.getJobId()))); + .filter(v -> v instanceof VisitorMobEntity) + .map(v -> (VisitorMobEntity) v) + .filter(e -> { + for (WorkRequest r : workHandle.getRequestedResults()) { + if (JobsRegistry.canSatisfy(td, e.getJobId(), r.asIngredient())) { + if (e.getStatusForServer() + .isBusy()) { + return false; + } + } + } + return true; + }) + .forEach(e -> changeJobForVisitor(e.getUUID(), WorkSeekerJob.getIDForRoot(e.getJobId()))); } @Override @@ -522,11 +554,12 @@ public void addImmediateReward(MCReward r) { private void grantAdvancementOnApproach() { MinecraftForge.EVENT_BUS.addListener((EntityEvent.EnteringSection event) -> { if (event.getEntity() instanceof ServerPlayer sp) { - double v = event.getEntity().distanceToSqr( - this.worldPosition.getX() + 0.5D, - this.worldPosition.getY() + 0.5D, - this.worldPosition.getZ() + 0.5D - ); + double v = event.getEntity() + .distanceToSqr( + this.worldPosition.getX() + 0.5D, + this.worldPosition.getY() + 0.5D, + this.worldPosition.getZ() + 0.5D + ); if (v < 100) { AdvancementsInit.APPROACH_TOWN_TRIGGER.trigger( sp, ApproachTownTrigger.Triggers.FirstVisit @@ -562,7 +595,9 @@ void broadcastMessage( Object... args ) { QT.FLAG_LOGGER.info("Broadcasting message: {} {}", key, args); - for (ServerPlayer p : level.getServer().getPlayerList().getPlayers()) { + for (ServerPlayer p : level.getServer() + .getPlayerList() + .getPlayers()) { p.displayClientMessage(Util.translatable(key, args), false); } } @@ -580,7 +615,8 @@ public void roomRecipeCreated( broadcastMessage( "messages.building.recipe_created", RoomRecipes.getName(match.getRecipeID()), - roomDoorPos.getDoorPos().getUIString() + roomDoorPos.getDoorPos() + .getUIString() ); // TODO: get room for rendering effect // handleRoomChange(room, ParticleTypes.HAPPY_VILLAGER); @@ -604,16 +640,22 @@ private Void swapJobBoardSign( ServerLevel level, RoomRecipeMatch room ) { - BlockPredicate predicate = BlockPredicate.Builder.block().of(BlockTags.SIGNS).build(); - for (Map.Entry e : room.getContainedBlocks().entrySet()) { + BlockPredicate predicate = BlockPredicate.Builder.block() + .of(BlockTags.SIGNS) + .build(); + for (Map.Entry e : room.getContainedBlocks() + .entrySet()) { if (!predicate.matches(level, e.getKey())) { continue; } // TODO: Get direction // Direction value = level.getBlockState(e.getKey()).getValue(HorizontalDirectionalBlock.FACING); Direction value = Direction.Plane.HORIZONTAL.getRandomDirection(level.getRandom()); - level.setBlockAndUpdate(e.getKey(), BlocksInit.JOB_BOARD_BLOCK.get().defaultBlockState() - .setValue(HorizontalDirectionalBlock.FACING, value) + level.setBlockAndUpdate( + e.getKey(), BlocksInit.JOB_BOARD_BLOCK.get() + .defaultBlockState() + .setValue( + HorizontalDirectionalBlock.FACING, value) ); registerJobsBoard(e.getKey()); jobHandle.setJobBlockState(e.getKey(), AbstractWorkStatusStore.State.freshAtState(WorkSeekerJob.MAX_STATE)); @@ -634,7 +676,8 @@ public void roomRecipeChanged( "messages.building.room_changed", Util.translatable("room." + oldMatchID.getPath()), Util.translatable("room." + newMatchID.getPath()), - newRoom.getDoorPos().getUIString() + newRoom.getDoorPos() + .getUIString() ); TownRooms.addParticles(getServerLevel(), newRoom, ParticleTypes.HAPPY_VILLAGER); if (oldMatch == null && newMatch != null) { @@ -664,8 +707,10 @@ public void roomRecipeDestroyed( ) { broadcastMessage( "messages.building.room_destroyed", - Util.translatable("room." + oldRecipeId.getRecipeID().getPath()), - roomDoorPos.getDoorPos().getUIString() + Util.translatable("room." + oldRecipeId.getRecipeID() + .getPath()), + roomDoorPos.getDoorPos() + .getUIString() ); TownRooms.addParticles(getServerLevel(), roomDoorPos, ParticleTypes.SMOKE); quests.markQuestAsLost(roomDoorPos, oldRecipeId.getRecipeID()); @@ -683,7 +728,8 @@ public void questCompleted(MCQuest quest) { getBlockPos().getX(), getBlockPos().getY() + 10, getBlockPos().getZ(), - new ItemStack(Items.FIREWORK_ROCKET.getDefaultInstance().getItem(), 3) + new ItemStack(Items.FIREWORK_ROCKET.getDefaultInstance() + .getItem(), 3) ); level.addFreshEntity(firework); } @@ -750,7 +796,9 @@ private JobID getVillagerPreferredWork( UUID uuid, Collection requestedResults ) { - Optional f = villagerHandle.stream().filter(v -> uuid.equals(v.getUUID())).findFirst(); + Optional f = villagerHandle.stream() + .filter(v -> uuid.equals(v.getUUID())) + .findFirst(); if (f.isEmpty()) { QT.BLOCK_LOGGER.error("No entities found for UUID: {}", uuid); return null; @@ -784,7 +832,9 @@ private JobID getVillagerPreferredWork( // job option. for (JobID p : preference) { - List i = requestedResults.stream().map(WorkRequest::asIngredient).toList(); + List i = requestedResults.stream() + .map(WorkRequest::asIngredient) + .toList(); for (Ingredient requestedResult : i) { // TODO: Think about how work chains work. // E.g. If a blacksmith needs iron ingots to do a requested job, @@ -814,10 +864,11 @@ public void changeJobForVisitor( boolean announce ) { Optional f = villagerHandle.stream() - .filter(v -> v instanceof VisitorMobEntity) - .map(v -> (VisitorMobEntity) v) - .filter(v -> v.getUUID().equals(visitorUUID)) - .findFirst(); + .filter(v -> v instanceof VisitorMobEntity) + .map(v -> (VisitorMobEntity) v) + .filter(v -> v.getUUID() + .equals(visitorUUID)) + .findFirst(); if (f.isEmpty()) { QT.FLAG_LOGGER.error("Could not find entity {} to apply job change: {}", visitorUUID, jobID); } else { @@ -835,7 +886,8 @@ private void doSetJob( JobID jobName, VisitorMobEntity f ) { - f.setJob(JobsRegistry.getInitializedJob(this, jobName, f.getJobJournalSnapshot().items(), visitorUUID)); + f.setJob(JobsRegistry.getInitializedJob(this, jobName, f.getJobJournalSnapshot() + .items(), visitorUUID)); } @Override @@ -844,7 +896,8 @@ private void doSetJob( return null; } List villagers = ImmutableList.copyOf(getVillagers()); - return villagers.get(getServerLevel().getRandom().nextInt(villagers.size())); + return villagers.get(getServerLevel().getRandom() + .nextInt(villagers.size())); } @Override @@ -855,11 +908,11 @@ public boolean isVillagerMissing(UUID uuid) { @Override public Collection getUnemployedVillagers() { return villagerHandle.stream() - .filter(v -> v instanceof VisitorMobEntity) - .map(v -> (VisitorMobEntity) v) - .filter(VisitorMobEntity::canAcceptJob) - .map(Entity::getUUID) - .toList(); + .filter(v -> v instanceof VisitorMobEntity) + .map(v -> (VisitorMobEntity) v) + .filter(VisitorMobEntity::canAcceptJob) + .map(Entity::getUUID) + .toList(); } @Override @@ -903,7 +956,9 @@ public void addBatchOfQuests( @Override public ImmutableSet getVillagers() { - return ImmutableSet.copyOf(villagerHandle.stream().map(Entity::getUUID).collect(Collectors.toSet())); + return ImmutableSet.copyOf(villagerHandle.stream() + .map(Entity::getUUID) + .collect(Collectors.toSet())); } @Override @@ -1001,30 +1056,33 @@ public Optional assignToFarm(UUID ownerUUID) { @Override public Optional getBiggestFarm() { - return roomsHandle.getFarms().stream().max(Comparator.comparingInt( - v -> v.getSpaces() - .stream() - .map(InclusiveSpaces::calculateArea) - .mapToInt(Double::intValue) - .sum() - )); + return roomsHandle.getFarms() + .stream() + .max(Comparator.comparingInt( + v -> v.getSpaces() + .stream() + .map(InclusiveSpaces::calculateArea) + .mapToInt(Double::intValue) + .sum() + )); } @Override public Collection getAvailableRootJobs() { // TODO: Scan villagers to make this decision - Set allJobs = JobsRegistry.getAllJobs().stream() - .map(JobID::rootId) - .collect(Collectors.toSet()); + Set allJobs = JobsRegistry.getAllJobs() + .stream() + .map(JobID::rootId) + .collect(Collectors.toSet()); Set allFilledJobs = villagerHandle.stream() - .filter(v -> v instanceof VisitorMobEntity) - .map(v -> (VisitorMobEntity) v) - .map(VisitorMobEntity::getJobId) - .map(JobID::rootId) - .collect(Collectors.toSet()); + .filter(v -> v instanceof VisitorMobEntity) + .map(v -> (VisitorMobEntity) v) + .map(VisitorMobEntity::getJobId) + .map(JobID::rootId) + .collect(Collectors.toSet()); Set allNewJobs = allJobs.stream() - .filter(v -> !allFilledJobs.contains(v)) - .collect(Collectors.toSet()); + .filter(v -> !allFilledJobs.contains(v)) + .collect(Collectors.toSet()); if (allNewJobs.isEmpty()) { allNewJobs = allJobs; } @@ -1042,7 +1100,8 @@ public ResourceLocation getRandomNearbyBiome() { if (nearbyBiomes.isEmpty()) { computeNearbyBiomes(level, getBlockPos(), this); } - Biome biome = nearbyBiomes.get(getServerLevel().getRandom().nextInt(nearbyBiomes.size())); + Biome biome = nearbyBiomes.get(getServerLevel().getRandom() + .nextInt(nearbyBiomes.size())); return ForgeRegistries.BIOMES.getKey(biome); } @@ -1081,18 +1140,22 @@ public void assumeStateFromTown( VisitorMobEntity visitorMobEntity, ServerLevel sl ) { - if (!Util.getBlockStoredTagData(this).contains(NBT_TOWN_STATE)) { + if (!Util.getBlockStoredTagData(this) + .contains(NBT_TOWN_STATE)) { QT.FLAG_LOGGER.error( "Villager entity exists but town state is missing. This is a bug and may cause unexpected behaviour."); return; } MCTownState state = TownStateSerializer.INSTANCE.load( - Util.getBlockStoredTagData(this).getCompound(NBT_TOWN_STATE), - sl, bp -> this.pois.getWelcomeMats().contains(bp) + Util.getBlockStoredTagData(this) + .getCompound(NBT_TOWN_STATE), + sl, bp -> this.pois.getWelcomeMats() + .contains(bp) ); Optional> match = state.villagers.stream() - .filter(v -> v.uuid.equals(visitorMobEntity.getUUID())) - .findFirst(); + .filter(v -> v.uuid.equals( + visitorMobEntity.getUUID())) + .findFirst(); if (match.isEmpty()) { QT.FLAG_LOGGER.error( "Villager entity exists but is not present on town state. This is a bug and may cause unexpected behaviour."); @@ -1158,11 +1221,12 @@ public Collection getKnownBiomes() { List> cs = TownContainers.getAllContainers(this, getServerLevel()); cs.forEach(v -> { v.getItems() - .stream() - .filter(i -> ItemsInit.GATHERER_MAP.get().equals(i.get())) - .map(i -> GathererMap.getBiome(i.toItemStack())) - .filter(Objects::nonNull) - .forEach(b::add); + .stream() + .filter(i -> ItemsInit.GATHERER_MAP.get() + .equals(i.get())) + .map(i -> GathererMap.getBiome(i.toItemStack())) + .filter(Objects::nonNull) + .forEach(b::add); }); nearbyBiomes.forEach(v -> { ResourceLocation key = ForgeRegistries.BIOMES.getKey(v); @@ -1181,9 +1245,9 @@ public void warpTime(int ticks) { public void freezeVillagers(Integer ticks) { villagerHandle.stream() - .filter(VisitorMobEntity.class::isInstance) - .map(VisitorMobEntity.class::cast) - .forEach(v -> v.freeze(ticks)); + .filter(VisitorMobEntity.class::isInstance) + .map(VisitorMobEntity.class::cast) + .forEach(v -> v.freeze(ticks)); } public VillagerHolder getVillagerHandle() { @@ -1193,4 +1257,13 @@ public VillagerHolder getVillagerHandle() { public int getY() { return getTownFlagBasePos().getY(); } + + public void startDebugTask(Supplier debugTask) { + this.debugTask = debugTask; + } + + public void toggleDebugMode() { + this.debugMode = !this.debugMode; + broadcastMessage("message.debug_mode", this.debugMode ? "enabled" : "disabled"); + } } diff --git a/src/main/java/ca/bradj/questown/town/TownFlagState.java b/src/main/java/ca/bradj/questown/town/TownFlagState.java index fc696d5f..d7ddd374 100644 --- a/src/main/java/ca/bradj/questown/town/TownFlagState.java +++ b/src/main/java/ca/bradj/questown/town/TownFlagState.java @@ -154,7 +154,9 @@ static MCTownState advanceTime( long after = System.currentTimeMillis(); - QT.FLAG_LOGGER.debug("State after warp of {}: {}", ticksPassed, liveState); + if (Config.LOG_WARP_RESULT.get()) { + QT.FLAG_LOGGER.info("State after warp of {}: {}", ticksPassed, liveState); + } QT.FLAG_LOGGER.debug("Warp took {} milliseconds", after - before); return new MCTownState( diff --git a/src/main/java/ca/bradj/questown/town/TownRoomsHandle.java b/src/main/java/ca/bradj/questown/town/TownRoomsHandle.java index b6fbadad..c80864a2 100644 --- a/src/main/java/ca/bradj/questown/town/TownRoomsHandle.java +++ b/src/main/java/ca/bradj/questown/town/TownRoomsHandle.java @@ -1,5 +1,6 @@ package ca.bradj.questown.town; +import ca.bradj.questown.QT; import ca.bradj.questown.core.Config; import ca.bradj.questown.roomrecipes.Spaces; import ca.bradj.questown.town.interfaces.RoomsHolder; @@ -8,6 +9,9 @@ import ca.bradj.questown.town.special.SpecialQuests; import ca.bradj.roomrecipes.adapter.Positions; import ca.bradj.roomrecipes.adapter.RoomRecipeMatch; +import ca.bradj.roomrecipes.core.Room; +import ca.bradj.roomrecipes.core.space.Position; +import ca.bradj.roomrecipes.logic.LevelRoomDetector; import ca.bradj.roomrecipes.recipes.ActiveRecipes; import ca.bradj.roomrecipes.recipes.RecipeDetection; import ca.bradj.roomrecipes.serialization.MCRoom; @@ -26,10 +30,13 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Function; import java.util.function.Supplier; -public class TownRoomsHandle implements RoomsHolder, ActiveRecipes.ChangeListener>, Supplier { +public class TownRoomsHandle implements RoomsHolder, ActiveRecipes.ChangeListener>, + Supplier { private final TownRoomsMap roomsMap = new TownRoomsMap(this); @Nullable @@ -83,24 +90,37 @@ private List> getWelcomeMatMetaRooms(@NotNull TownFlagBl return t.getWelcomeMats() .stream() .map(p -> Spaces.metaRoomAround(p, Config.META_ROOM_DIAMETER.get())) - .map(v -> new RoomRecipeMatch<>(v, SpecialQuests.TOWN_GATE, fn.apply(v).entrySet())) + .map(v -> new RoomRecipeMatch<>( + v, SpecialQuests.TOWN_GATE, fn.apply(v) + .entrySet())) .toList(); } @Override - public void roomRecipeCreated(MCRoom mcRoom, RoomRecipeMatch mcRoomRoomRecipeMatch) { + public void roomRecipeCreated( + MCRoom mcRoom, + RoomRecipeMatch mcRoomRoomRecipeMatch + ) { @NotNull TownFlagBlockEntity t = unsafeGetTown(); t.roomRecipeCreated(mcRoom, mcRoomRoomRecipeMatch); } @Override - public void roomRecipeChanged(MCRoom mcRoom, RoomRecipeMatch mcRoomRoomRecipeMatch, MCRoom room1, RoomRecipeMatch key1) { + public void roomRecipeChanged( + MCRoom mcRoom, + RoomRecipeMatch mcRoomRoomRecipeMatch, + MCRoom room1, + RoomRecipeMatch key1 + ) { @NotNull TownFlagBlockEntity t = unsafeGetTown(); t.roomRecipeChanged(mcRoom, mcRoomRoomRecipeMatch, room1, key1); } @Override - public void roomRecipeDestroyed(MCRoom mcRoom, RoomRecipeMatch mcRoomRoomRecipeMatch) { + public void roomRecipeDestroyed( + MCRoom mcRoom, + RoomRecipeMatch mcRoomRoomRecipeMatch + ) { @NotNull TownFlagBlockEntity t = unsafeGetTown(); t.roomRecipeDestroyed(mcRoom, mcRoomRoomRecipeMatch); } @@ -114,7 +134,10 @@ public TownRoomsMap getRegisteredRooms() { return roomsMap; } - void tick(ServerLevel sl, BlockPos blockPos) { + void tick( + ServerLevel sl, + BlockPos blockPos + ) { roomsMap.tick(sl, blockPos); } @@ -136,7 +159,8 @@ public Collection getFarms() { public Collection findMatchedRecipeBlocks(TownInterface.MatchRecipe mr) { ImmutableList.Builder b = ImmutableList.builder(); for (RoomRecipeMatch i : roomsMap.getAllMatches()) { - for (Map.Entry j : i.getContainedBlocks().entrySet()) { + for (Map.Entry j : i.getContainedBlocks() + .entrySet()) { if (mr.doesMatch(j.getValue())) { b.add(j.getKey()); } @@ -147,10 +171,14 @@ public Collection findMatchedRecipeBlocks(TownInterface.MatchRecipe mr boolean hasEnoughBeds(long numVillagers) { // TODO: This returns false positives if called before entities have been loaded from tile data - long beds = roomsMap.getAllMatches().stream() - .flatMap(v -> v.getContainedBlocks().values().stream()) - .filter(v -> Ingredient.of(ItemTags.BEDS).test(new ItemStack(v.asItem()))) - .count(); + long beds = roomsMap.getAllMatches() + .stream() + .flatMap(v -> v.getContainedBlocks() + .values() + .stream()) + .filter(v -> Ingredient.of(ItemTags.BEDS) + .test(new ItemStack(v.asItem()))) + .count(); if (beds == 0 && numVillagers == 0) { return false; } @@ -168,6 +196,57 @@ public void registerDoor(BlockPos clickedPos) { t.setChanged(); } + @Override + public Supplier getDebugTaskForDoor(BlockPos clickedPos) { + @NotNull TownFlagBlockEntity t = unsafeGetTown(); + LinkedBlockingQueue flightRecorder = new LinkedBlockingQueue<>(); + Position clickedRRPOs = Positions.FromBlockPos(clickedPos); + final LevelRoomDetector d = new LevelRoomDetector( + ImmutableList.of(clickedRRPOs), + Config.MAX_ROOM_DIMENSION.get(), + Config.MAX_ROOM_SCAN_ITERATIONS.get(), + p -> WallDetection.IsWall(t.getServerLevel(), p, clickedPos.getY()), + true, + flightRecorder + ); + return () -> { + @Nullable ImmutableMap> done = d.proceed(); + if (done == null) { + return false; + } + flightRecorder.forEach(QT.FLAG_LOGGER::debug); + d.getDebugArt(true).forEach( + (k, v) -> QT.FLAG_LOGGER.debug("Art for {}\n{}", k.getUIString(), v) + ); + Optional room = done.get(clickedRRPOs); + QT.FLAG_LOGGER.debug("Room is {}", room); + room.ifPresent(r -> { + Optional> recipe = t.getRoomHandle() + .computeRecipe(new MCRoom(r.getDoorPos(), + r.getSpaces(), clickedPos.getY() + )); + QT.FLAG_LOGGER.debug("Recipe is {}", recipe); + }); + if (!t.getRoomHandle().isDoorRegistered(clickedPos)) { + QT.FLAG_LOGGER.warn("{} is not registered as a door", clickedPos); + } + return true; + }; + } + + @Override + public boolean isDoorRegistered(BlockPos clickedPos) { + @NotNull TownFlagBlockEntity t = unsafeGetTown(); + return roomsMap.isDoorRegistered(Positions.FromBlockPos(clickedPos), clickedPos.getY() - t.getY()); + } + + public Optional> computeRecipe( + MCRoom r + ) { + @NotNull TownFlagBlockEntity t = unsafeGetTown(); + return roomsMap.computeRecipe(t.getServerLevel(), r, r.yCoord - t.getY()); + } + public void registerFenceGate(BlockPos clickedPos) { @NotNull TownFlagBlockEntity t = unsafeGetTown(); roomsMap.registerFenceGate(Positions.FromBlockPos(clickedPos), clickedPos.getY() - t.getY()); diff --git a/src/main/java/ca/bradj/questown/town/interfaces/RoomsHolder.java b/src/main/java/ca/bradj/questown/town/interfaces/RoomsHolder.java index 141e435a..4e45256e 100644 --- a/src/main/java/ca/bradj/questown/town/interfaces/RoomsHolder.java +++ b/src/main/java/ca/bradj/questown/town/interfaces/RoomsHolder.java @@ -1,11 +1,14 @@ package ca.bradj.questown.town.interfaces; import ca.bradj.roomrecipes.adapter.RoomRecipeMatch; +import ca.bradj.roomrecipes.core.Room; import ca.bradj.roomrecipes.serialization.MCRoom; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; import java.util.Collection; +import java.util.Optional; +import java.util.function.Supplier; public interface RoomsHolder { Collection> getRoomsMatching(ResourceLocation roomRecipeId); @@ -19,4 +22,10 @@ public interface RoomsHolder { void registerFenceGate(BlockPos above); void registerDoor(BlockPos above); + + Supplier getDebugTaskForDoor(BlockPos clickedPos); + + boolean isDoorRegistered(BlockPos clickedPos); + + Optional> computeRecipe(MCRoom r); } diff --git a/src/main/java/ca/bradj/questown/town/rooms/PendingTownRooms.java b/src/main/java/ca/bradj/questown/town/rooms/PendingTownRooms.java new file mode 100644 index 00000000..d4f7925d --- /dev/null +++ b/src/main/java/ca/bradj/questown/town/rooms/PendingTownRooms.java @@ -0,0 +1,114 @@ +package ca.bradj.questown.town.rooms; + +import ca.bradj.questown.core.Config; +import ca.bradj.questown.town.TownRooms; +import ca.bradj.roomrecipes.adapter.RoomRecipeMatch; +import ca.bradj.roomrecipes.core.Room; +import ca.bradj.roomrecipes.core.space.Position; +import ca.bradj.roomrecipes.logic.LevelRoomDetector; +import ca.bradj.roomrecipes.recipes.ActiveRecipes; +import ca.bradj.roomrecipes.recipes.RecipeDetection; +import ca.bradj.roomrecipes.serialization.MCRoom; +import com.google.common.collect.ImmutableMap; +import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Supplier; + +public class PendingTownRooms { + private final ServerLevel level; + private final Position scanAroundPos; + private final int y; + private final Map> foundRooms = new HashMap<>(); + private final LinkedBlockingQueue roomsToScan = new LinkedBlockingQueue<>(); + private final LevelRoomDetector roomDetector; + private final Supplier>> recipes; + private final TownRooms rooms; + private @Nullable Long trueStart; + + public PendingTownRooms( + ServerLevel level, + Position scanAroundPos, + TownRooms rooms, + Supplier>> getRecipes, + int y, + Set doorsAtLevel + ) { + this.level = level; + this.scanAroundPos = scanAroundPos; + this.recipes = getRecipes; + this.y = y; + this.rooms = rooms; + this.roomDetector = new LevelRoomDetector( + doorsAtLevel, + Config.MAX_ROOM_DIMENSION.get(), + Config.MAX_ROOM_SCAN_ITERATIONS.get(), + rooms::IsWall, + false, + null + ); + } + + public boolean proceed() { + if (this.trueStart == null) { + this.trueStart = System.currentTimeMillis(); + } + if (roomDetector.isDone() && roomsToScan.isEmpty() && foundRooms.isEmpty()) { + return true; + } + + if (!roomsToScan.isEmpty()) { + long start = System.currentTimeMillis(); + MCRoom room = roomsToScan.remove(); + Optional> recipe = RecipeDetection.getActiveRecipe( + level, + room, + rooms + ); + ActiveRecipes> rs = recipes.get(); + rs.update( + room, + room, + recipe.orElse(null) + ); + return false; + } + + @Nullable ImmutableMap> result = roomDetector.proceed(); + if (result != null) { + ImmutableMap> build = handleNewRooms(result); + rooms.update(build); + } + return false; + } + + @NotNull + private ImmutableMap> handleNewRooms( + @NotNull ImmutableMap> result + ) { + ImmutableMap.Builder> b = ImmutableMap.builder(); + result.forEach((k, v) -> { + Optional value = v.map(z -> { + MCRoom mcRoom = new MCRoom( + z.getDoorPos(), + z.getSpaces(), + y + ); + return mcRoom; + }); + b.put( + k, + value + ); + value.ifPresent(roomsToScan::add); + }); + ImmutableMap> build = b.build(); + return build; + } +} diff --git a/src/main/java/ca/bradj/questown/town/rooms/TownRoomsMap.java b/src/main/java/ca/bradj/questown/town/rooms/TownRoomsMap.java index bb189472..88869e06 100644 --- a/src/main/java/ca/bradj/questown/town/rooms/TownRoomsMap.java +++ b/src/main/java/ca/bradj/questown/town/rooms/TownRoomsMap.java @@ -1,5 +1,6 @@ package ca.bradj.questown.town.rooms; +import ca.bradj.questown.QT; import ca.bradj.questown.Questown; import ca.bradj.questown.blocks.FalseDoorBlock; import ca.bradj.questown.core.Config; @@ -30,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,6 +43,7 @@ public class TownRoomsMap implements TownRooms.RecipeRoomChangeListener { private int scanBuffer = 0; private TownRoomsHandle changeListener; private final ArrayList times = new ArrayList<>(); + private final LinkedBlockingQueue pendingRooms = new LinkedBlockingQueue<>(); Set getRegisteredDoors() { return registeredDoors; @@ -77,24 +80,28 @@ private void updateActiveRooms( } List>> array = rooms.entrySet() - .stream() - .map(v -> new AbstractMap.SimpleEntry<>( - v.getKey(), v.getValue().map(z -> new MCRoom( - z.getDoorPos(), z.getSpaces(), scanY - )))) - .toList(); + .stream() + .map(v -> new AbstractMap.SimpleEntry<>( + v.getKey(), v.getValue() + .map(z -> new MCRoom( + z.getDoorPos(), + z.getSpaces(), + scanY + )))) + .toList(); ImmutableMap> mcRooms = ImmutableMap.copyOf(array); ars.update(mcRooms); - ars.getAll().forEach(room -> { - Optional> recipe = RecipeDetection.getActiveRecipe( - level, - room, - ars - ); - ActiveRecipes> rs = activeRecipes.get(scanLevel); - rs.update(room, room, recipe.orElse(null)); - }); + ars.getAll() + .forEach(room -> { + Optional> recipe = RecipeDetection.getActiveRecipe( + level, + room, + ars + ); + ActiveRecipes> rs = activeRecipes.get(scanLevel); + rs.update(room, room, recipe.orElse(null)); + }); } private void updateActiveFarms( @@ -109,12 +116,15 @@ private void updateActiveFarms( registeredDoors, 20, (Position p) -> isFence(level, Positions.ToBlock(p, scanY)) ); List>> array = rooms.entrySet() - .stream() - .map(v -> new AbstractMap.SimpleEntry<>( - v.getKey(), v.getValue().map(z -> new MCRoom( - z.getDoorPos(), z.getSpaces(), scanY - )))) - .toList(); + .stream() + .map(v -> new AbstractMap.SimpleEntry<>( + v.getKey(), v.getValue() + .map(z -> new MCRoom( + z.getDoorPos(), + z.getSpaces(), + scanY + )))) + .toList(); ImmutableMap> mcRooms = ImmutableMap.copyOf(array); ars.update(mcRooms); } @@ -124,8 +134,12 @@ private static boolean isFence( BlockPos bPos ) { BlockState bs = level.getBlockState(bPos); - return Ingredient.of(Tags.Items.FENCES).test(new ItemStack(bs.getBlock().asItem(), 1)) || - Ingredient.of(Tags.Items.FENCE_GATES).test(new ItemStack(bs.getBlock().asItem(), 1)); + return Ingredient.of(Tags.Items.FENCES) + .test(new ItemStack(bs.getBlock() + .asItem(), 1)) || + Ingredient.of(Tags.Items.FENCE_GATES) + .test(new ItemStack(bs.getBlock() + .asItem(), 1)); } private TownRooms getOrCreateRooms(int scanLevel) { @@ -176,18 +190,30 @@ public void tick( BlockPos blockPos ) { registeredDoors.stream() - .filter(tp -> { - BlockPos bp = new BlockPos(tp.x, blockPos.getY() + tp.scanLevel, tp.z); - BlockState bs = level.getBlockState(bp); - return !(bs.getBlock() instanceof DoorBlock || bs.getBlock() instanceof FalseDoorBlock); - }) - .toList() - .forEach(tp -> { - registeredDoors.remove(tp); - Questown.LOGGER.debug("Door was de-registered due to not existing anymore"); - }); + .filter(tp -> { + BlockPos bp = new BlockPos(tp.x, blockPos.getY() + tp.scanLevel, tp.z); + BlockState bs = level.getBlockState(bp); + return !(bs.getBlock() instanceof DoorBlock || bs.getBlock() instanceof FalseDoorBlock); + }) + .toList() + .forEach(tp -> { + registeredDoors.remove(tp); + Questown.LOGGER.debug("Door was de-registered due to not existing anymore"); + }); long start = System.currentTimeMillis(); + + + if (!this.pendingRooms.isEmpty()) { + PendingTownRooms next = this.pendingRooms.remove(); + boolean finished = next.proceed(); + if (!finished) { + this.pendingRooms.add(next); + } + profileTick("PTR.proceed", start); + return; + } + // scanBuffer = (scanBuffer + 1) % 2; // if (scanBuffer == 0) { // TODO: Use a FIFO queue and only run one iteration (y level) per tick @@ -195,44 +221,73 @@ public void tick( // } Position scanAroundPos = Positions.FromBlockPos(blockPos); Set doorsAtZero = registeredDoors.stream() - .filter(v -> v.scanLevel == 0) - .map(p -> new Position(p.x, p.z)) - .collect(Collectors.toSet()); - updateActiveRooms(level, scanAroundPos, 0, blockPos.getY(), doorsAtZero); + .filter(v -> v.scanLevel == 0) + .map(p -> new Position(p.x, p.z)) + .collect(Collectors.toSet()); + + this.pendingRooms.add(new PendingTownRooms( + level, scanAroundPos, + getOrCreateRooms(0), + () -> activeRecipes.get(0), + blockPos.getY(), doorsAtZero + )); if (scanLevel != 0) { Set doorsAtLevel = registeredDoors.stream() - .filter(v -> v.scanLevel == scanLevel) - .map(p -> new Position(p.x, p.z)) - .collect(Collectors.toSet()); - int y = blockPos.offset(0, scanLevel, 0).getY(); - updateActiveRooms(level, scanAroundPos, scanLevel, y, doorsAtLevel); + .filter(v -> v.scanLevel == scanLevel) + .map(p -> new Position(p.x, p.z)) + .collect(Collectors.toSet()); + int y = blockPos.offset(0, scanLevel, 0) + .getY(); + this.pendingRooms.add(new PendingTownRooms( + level, scanAroundPos, + getOrCreateRooms(scanLevel), + () -> activeRecipes.get(scanLevel), + y, doorsAtLevel + )); } - for (int scanLev : registeredDoors.stream().map(v -> v.scanLevel).distinct().toList()) { + for (int scanLev : registeredDoors.stream() + .map(v -> v.scanLevel) + .distinct() + .toList()) { if (scanLev == scanLevel || scanLev == 0) { continue; } Set doorsAtLevel = registeredDoors.stream() - .filter(v -> v.scanLevel == scanLev) - .map(p -> new Position(p.x, p.z)) - .collect(Collectors.toSet()); - int y1 = blockPos.offset(0, scanLev, 0).getY(); - updateActiveRooms(level, null, scanLev, y1, doorsAtLevel); + .filter(v -> v.scanLevel == scanLev) + .map(p -> new Position(p.x, p.z)) + .collect(Collectors.toSet()); + int y1 = blockPos.offset(0, scanLev, 0) + .getY(); + this.pendingRooms.add(new PendingTownRooms( + level, null, + getOrCreateRooms(scanLev), + () -> activeRecipes.get(scanLev), + y1, doorsAtLevel + )); } - for (int scanLev : registeredFenceGates.stream().map(v -> v.scanLevel).distinct().toList()) { + for (int scanLev : registeredFenceGates.stream() + .map(v -> v.scanLevel) + .distinct() + .toList()) { Set doorsAtLevel = registeredFenceGates.stream() - .filter(v -> v.scanLevel == scanLev) - .map(p -> new Position(p.x, p.z)) - .collect(Collectors.toSet()); - int y1 = blockPos.offset(0, scanLev, 0).getY(); + .filter(v -> v.scanLevel == scanLev) + .map(p -> new Position(p.x, p.z)) + .collect(Collectors.toSet()); + int y1 = blockPos.offset(0, scanLev, 0) + .getY(); + // FIXME: Do the farms too updateActiveFarms(level, scanLev, y1, doorsAtLevel); } - profileTick(start); + profileTick("queue+farm", start); } - private void profileTick(long start) { + private void profileTick( + String prefix, + long start + ) { if (Config.TICK_SAMPLING_RATE.get() == 0) { return; } @@ -240,10 +295,17 @@ private void profileTick(long start) { times.add((int) (end - start)); if (times.size() > Config.TICK_SAMPLING_RATE.get()) { - Questown.LOGGER.debug( - "[TownRoomsMap] Average tick length: {}", - times.stream().mapToInt(Integer::intValue).average() - ); + String msg = "[TownRoomsMap:{}] Average tick length: {}"; + OptionalDouble val = times.stream() + .mapToInt(Integer::intValue) + .average(); + if (val.isPresent() && val.getAsDouble() > 10) { + QT.PROFILE_LOGGER.error(msg, prefix, val); + } else if (val.isPresent() && val.getAsDouble() > 1) { + QT.PROFILE_LOGGER.warn(msg, prefix, val); + } else { + QT.PROFILE_LOGGER.debug(msg, prefix, val); + } times.clear(); } } @@ -266,7 +328,11 @@ public void initialize( } public Collection getAllRooms() { - return this.activeRooms.values().stream().map(TownRooms::getAll).flatMap(Collection::stream).toList(); + return this.activeRooms.values() + .stream() + .map(TownRooms::getAll) + .flatMap(Collection::stream) + .toList(); } @Override @@ -277,7 +343,8 @@ public void updateRecipeForRoom( @Nullable RoomRecipeMatch resourceLocation ) { getOrCreateRooms(scanLevel); - activeRecipes.get(scanLevel).update(oldRoom, newRoom, resourceLocation); + activeRecipes.get(scanLevel) + .update(oldRoom, newRoom, resourceLocation); } public int numRecipes() { @@ -286,9 +353,10 @@ public int numRecipes() { public Collection> getAllMatches() { Stream> objectStream = this.activeRecipes.values() - .stream() - .map(ActiveRecipes::entrySet) - .flatMap(v -> v.stream().map(Map.Entry::getValue)); + .stream() + .map(ActiveRecipes::entrySet) + .flatMap(v -> v.stream() + .map(Map.Entry::getValue)); return objectStream.collect(Collectors.toSet()); } @@ -313,13 +381,20 @@ public Collection> getRoomsMatching(ResourceLocation rec for (TownPosition p : registeredDoors) { Position pz = new Position(p.x, p.z); TownRooms rooms = getOrCreateRooms(p.scanLevel); - Optional room = rooms.getAll().stream().filter(v -> v.getDoorPos().equals(pz)).findFirst(); + Optional room = rooms.getAll() + .stream() + .filter(v -> v.getDoorPos() + .equals(pz)) + .findFirst(); if (room.isEmpty()) { continue; } ActiveRecipes> recipes = activeRecipes.get(p.scanLevel); for (Map.Entry> m : recipes.entrySet()) { - if (m.getKey().equals(room.get()) && m.getValue().getRecipeID().equals(recipeId)) { + if (m.getKey() + .equals(room.get()) && m.getValue() + .getRecipeID() + .equals(recipeId)) { b.add(m.getValue()); } } @@ -329,9 +404,27 @@ public Collection> getRoomsMatching(ResourceLocation rec public Collection getFarms() { return activeFarms.entrySet() + .stream() + .flatMap(v -> v.getValue() + .getAll() + .stream()) + .toList(); + } + + public boolean isDoorRegistered( + Position position, + int y + ) { + return registeredDoors .stream() - .flatMap(v -> v.getValue().getAll().stream()) - .toList(); + .anyMatch(v -> v.scanLevel == y && position.x == v.x && position.z == v.z); } + public Optional> computeRecipe( + ServerLevel serverLevel, + MCRoom r, + int scanLevel + ) { + return RecipeDetection.getActiveRecipe(serverLevel, r, getOrCreateRooms(scanLevel)); + } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 5d7b7188..3c45d33b 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -60,7 +60,7 @@ Questown is a new mod that intends to add a quest-based component to village bui modId="roomrecipes" mandatory=true ordering="BEFORE" - versionRange="[1.19.2-0.0.4-alpha.2]" + versionRange="[1.19.2-0.0.5-alpha.1]" side="BOTH" [[dependencies.questown]] modId="jei" diff --git a/src/main/resources/assets/questown/lang/en_us.json b/src/main/resources/assets/questown/lang/en_us.json index 50f55e2e..61b98461 100644 --- a/src/main/resources/assets/questown/lang/en_us.json +++ b/src/main/resources/assets/questown/lang/en_us.json @@ -17,6 +17,7 @@ "message.baker.villagers_will_add_wheat": "Baker villagers can add wheat and coal to bake bread", "message.baker.villagers_will_add_coal": "Baker villagers can add coal to bake bread", "messages.jobs.changed": "Job changed to %s for villager %s", + "message.debug_mode": "Debug Mode: %s", "tooltips.quests": "Quests", "tooltips.stats": "Stats", "tooltips.villagers.job.inventory.locked": "This item was given by a player. Gatherer will not drop it.",