From 4783b80f38bcd62a8c6ce5c95647c65a130a8ec6 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 30 Apr 2023 12:34:20 +0100 Subject: [PATCH] Bat ponies can now sleep during the day to skip to the night --- .../com/minelittlepony/unicopia/Race.java | 2 +- .../com/minelittlepony/unicopia/Unicopia.java | 4 +- .../unicopia/entity/player/Pony.java | 9 ++- .../unicopia/mixin/MixinPlayerEntity.java | 13 +---- .../mixin/MixinServerPlayerEntity.java | 24 +++++--- .../unicopia/mixin/MixinServerWorld.java | 45 +++++++++++++- .../server/world/NocturnalSleepManager.java | 58 +++++++++++++++++++ .../resources/assets/unicopia/lang/en_us.json | 1 + src/main/resources/unicopia.mixin.json | 1 + 9 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/NocturnalSleepManager.java diff --git a/src/main/java/com/minelittlepony/unicopia/Race.java b/src/main/java/com/minelittlepony/unicopia/Race.java index aabca319..9bd072d7 100644 --- a/src/main/java/com/minelittlepony/unicopia/Race.java +++ b/src/main/java/com/minelittlepony/unicopia/Race.java @@ -29,7 +29,7 @@ public record Race (boolean canCast, FlightType flightType, boolean canUseEarth, private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("race.unknown", id)); public static Race register(String name, boolean magic, FlightType flight, boolean earth, boolean nocturnal) { - return register(Unicopia.id(name), magic, flight, earth, false); + return register(Unicopia.id(name), magic, flight, earth, nocturnal); } public static Race register(Identifier id, boolean magic, FlightType flight, boolean earth, boolean nocturnal) { diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 5cff6b58..cd2a438f 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -18,7 +18,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitLoader; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.block.UTreeGen; -import com.minelittlepony.unicopia.block.data.*; import com.minelittlepony.unicopia.block.state.StateMapLoader; import com.minelittlepony.unicopia.command.Commands; import com.minelittlepony.unicopia.container.SpellbookChapterLoader; @@ -31,6 +30,7 @@ import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; +import com.minelittlepony.unicopia.server.world.NocturnalSleepManager; import com.minelittlepony.unicopia.server.world.UGameRules; import com.minelittlepony.unicopia.server.world.WeatherConditions; import com.minelittlepony.unicopia.server.world.ZapAppleStageStore; @@ -71,6 +71,8 @@ public class Unicopia implements ModInitializer { SpellbookChapterLoader.INSTANCE.sendUpdate(w.getServer()); } }); + NocturnalSleepManager.bootstrap(); + ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE); ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(UEnchantments.POISONED_JOKE); ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new TraitLoader()); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 9a3c9d7f..be1a02ed 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -47,6 +47,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.math.*; import net.minecraft.world.GameMode; @@ -571,8 +572,12 @@ public class Pony extends Living implements Copyable, Update .map(p -> Text.translatable("block.unicopia.bed.not_safe")); } - public boolean isDaytime() { - return asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES) && getActualSpecies().isNocturnal() ? !asWorld().isDay() : asWorld().isDay(); + public ActionResult canSleepNow() { + if (asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES) && getActualSpecies().isNocturnal()) { + return asWorld().isDay() ? ActionResult.SUCCESS : ActionResult.FAIL; + } + + return ActionResult.PASS; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java index 20581ea8..74a4eb18 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java @@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.gen.Invoker; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.duck.PlayerEntityDuck; @@ -22,10 +21,10 @@ import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.stat.Stats; import net.minecraft.util.Unit; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; @Mixin(PlayerEntity.class) abstract class MixinPlayerEntity extends LivingEntity implements Equine.Container, PlayerEntityDuck { @@ -70,7 +69,7 @@ abstract class MixinPlayerEntity extends LivingEntity implements Equine.Containe get().trySleep(pos).ifPresent(reason -> { ((PlayerEntity)(Object)this).sendMessage(reason, true); - info.setReturnValue(Either.right(Unit.INSTANCE)); + info.setReturnValue(Either.left(ServerPlayerEntity.SleepFailureReason.OTHER_PROBLEM)); }); } } @@ -101,12 +100,4 @@ abstract class MixinPlayerEntity extends LivingEntity implements Equine.Containe private void onGetBlockBreakingSpeed(BlockState state, CallbackInfoReturnable info) { info.setReturnValue(info.getReturnValue() * get().getBlockBreakingSpeed()); } - - @Redirect(method = "tick", at = @At( - value = "INVOKE", - target = "net/minecraft/world/World.isDay()Z" - )) - private boolean redirectIsDay(World world) { - return get().isDaytime(); - } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java index 4b7b3998..c4efcbf2 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java @@ -4,16 +4,21 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.duck.ServerPlayerEntityDuck; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.server.world.UGameRules; +import com.mojang.datafixers.util.Either; + import net.minecraft.entity.player.PlayerEntity; import net.minecraft.screen.ScreenHandlerListener; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.world.World; +import net.minecraft.text.Text; +import net.minecraft.util.Unit; +import net.minecraft.util.math.BlockPos; @Mixin(ServerPlayerEntity.class) abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHandlerListener, Equine.Container, ServerPlayerEntityDuck { @@ -29,11 +34,14 @@ abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHan get().copyFrom(((Equine.Container)oldPlayer).get(), alive); } - @Redirect(method = "trySleep", at = @At( - value = "INVOKE", - target = "net/minecraft/world/World.isDay()Z" - )) - private boolean redirectIsDay(World world) { - return get().isDaytime(); + @Inject(method = "trySleep(Lnet/minecraft/util/math/BlockPos;)Lcom/mojang/datafixers/util/Either;", + at = @At(value = "FIELD", target = "net/minecraft/entity/player/PlayerEntity$SleepFailureReason.NOT_POSSIBLE_NOW"), + cancellable = true) + private void onTrySleep(BlockPos pos, CallbackInfoReturnable> info) { + if (get().getActualSpecies().isNocturnal() && get().asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES)) { + ((PlayerEntity)this).sendMessage(Text.translatable("block.minecraft.bed.no_sleep.nocturnal"), true); + + info.setReturnValue(Either.left(PlayerEntity.SleepFailureReason.OTHER_PROBLEM)); + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java index 456c8c25..3811ba34 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java @@ -1,24 +1,67 @@ package com.minelittlepony.unicopia.mixin; +import java.util.List; +import java.util.function.BooleanSupplier; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; +import com.minelittlepony.unicopia.server.world.NocturnalSleepManager; import net.minecraft.block.BlockState; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.SleepManager; import net.minecraft.util.math.BlockPos; import net.minecraft.world.StructureWorldAccess; import net.minecraft.world.World; @Mixin(ServerWorld.class) -abstract class MixinServerWorld extends World implements StructureWorldAccess { +abstract class MixinServerWorld extends World implements StructureWorldAccess, NocturnalSleepManager.Source { + + private NocturnalSleepManager nocturnalSleepManager; + MixinServerWorld() { super(null, null, null, null, false, false, 0, 0); } @Inject(method = "onBlockChanged", at = @At("HEAD")) private void onOnBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo info) { ((BlockDestructionManager.Source)this).getDestructionManager().onBlockChanged(pos, oldState, newState); } + + @ModifyConstant(method = "sendSleepingStatus()V", constant = @Constant( + stringValue = "sleep.skipping_night" + )) + private String modifySleepingMessage(String initial) { + return getNocturnalSleepManager().getTimeSkippingMessage(initial); + } + + @Override + public NocturnalSleepManager getNocturnalSleepManager() { + if (nocturnalSleepManager == null) { + nocturnalSleepManager = new NocturnalSleepManager((ServerWorld)(Object)this); + } + return nocturnalSleepManager; + } + + @Inject(method = "tick(Ljava/util/function/BooleanSupplier;)V", at = @At( + value = "INVOKE", + target = "net/minecraft/server/world/ServerWorld.wakeSleepingPlayers()V" + )) + public void beforeWakeup(BooleanSupplier shouldKeepTicking, CallbackInfo info) { + getNocturnalSleepManager().skipTime(); + } } + +@Mixin(SleepManager.class) +abstract class MixinSleepManager { + @ModifyVariable(method = "update(Ljava/util/List;)Z", at = @At("HEAD")) + public List modifyPlayers(List players) { + return players.size() <= 0 ? players : ((NocturnalSleepManager.Source)players.get(0).getWorld()).getNocturnalSleepManager().filterPlayers(players); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/NocturnalSleepManager.java b/src/main/java/com/minelittlepony/unicopia/server/world/NocturnalSleepManager.java new file mode 100644 index 00000000..034f517b --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/NocturnalSleepManager.java @@ -0,0 +1,58 @@ +package com.minelittlepony.unicopia.server.world; + +import java.util.List; + +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.SleepManager; +import net.minecraft.world.GameRules; + +public class NocturnalSleepManager extends SleepManager { + public static final long DAY_LENGTH = 24000L; + + private final ServerWorld world; + + public NocturnalSleepManager(ServerWorld world) { + this.world = world; + } + + public String getTimeSkippingMessage(String nightSkippingMessage) { + if (world.getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES) && world.isDay()) { + return "sleep.skipping_day"; + } + + return nightSkippingMessage; + } + + public void skipTime() { + if (world.getGameRules().getBoolean(GameRules.DO_DAYLIGHT_CYCLE)) { + world.setTimeOfDay(world.getLevelProperties().getTimeOfDay() - DAY_LENGTH + 13500); + } + } + + public List getApplicablePlayer() { + return filterPlayers(world.getPlayers()); + } + + public List filterPlayers(List players) { + if (!world.getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES)) { + return players; + } + + return players.stream().filter(player -> { + Pony pony = Pony.of(player); + return (pony.getActualSpecies().isNocturnal() == world.isDay()); + }).toList(); + } + + public static void bootstrap() { + EntitySleepEvents.ALLOW_SLEEP_TIME.register((player, pos, isDay) -> Pony.of(player).canSleepNow()); + } + + public interface Source { + NocturnalSleepManager getNocturnalSleepManager(); + } +} diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 6d75f9e0..d07ce48b 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -2,6 +2,7 @@ "block.unicopia.bed.not_safe": "You may not rest here, there are enemies nearby", "block.unicopia.bed.not_tired": "You do not feel tired right now", + "block.minecraft.bed.no_sleep.nocturnal": "You can only sleep in the day or during thunderstorms", "ability.unicopia.empty_hooves": "I need to find a jar", "ability.unicopia.indoors": "I can't see the sky from here", diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index 06cef538..fd4468a8 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -28,6 +28,7 @@ "MixinServerPlayerEntity", "MixinServerPlayNetworkHandler", "MixinServerWorld", + "MixinSleepManager", "MixinSheepEntity", "MixinShulkerEntity", "MixinStateManager",