Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport playersSleepingPercentage gamerule and implement #162

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
10 changes: 10 additions & 0 deletions src/main/java/serverutils/ServerUtilitiesConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,16 @@ public boolean log(EntityPlayerMP player) {
@Config.DefaultBoolean(false)
public boolean show_playtime;

@Config.Comment("Enabled Player Sleeping Percentage to skip night. Use the gamerule playersSleepingPercentage to set the percentage.")
@Config.DefaultBoolean(true)
@Config.ModDetectedDefault(coremod = "ganymedes01.etfuturum.mixinplugin.EtFuturumEarlyMixins", value = "false")
public boolean enable_player_sleeping_percentage;

@Config.Comment("Default Player Sleeping. This is only what the gamerule is initially set to, not the active value that is used.")
@Config.DefaultInt(50)
@Config.RangeInt(min = 0, max = 100)
public int player_sleeping_percentage;

@Config.Ignore
private List<DisabledItem> disabledItems = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import net.minecraft.util.IChatComponent;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.world.WorldEvent;

import cpw.mods.fml.common.eventhandler.Event;
import cpw.mods.fml.common.eventhandler.EventPriority;
Expand Down Expand Up @@ -60,6 +61,17 @@ public void onCacheCleared(UniverseClearCacheEvent event) {
}
}

@SubscribeEvent
public void loadWorldEvent(WorldEvent.Load event) {
if (ServerUtilitiesConfig.world.enable_player_sleeping_percentage) {
if (!event.world.isRemote && !event.world.getGameRules().hasRule("playersSleepingPercentage")) {
event.world.getGameRules().addGameRule(
"playersSleepingPercentage",
Integer.toString(ServerUtilitiesConfig.world.player_sleeping_percentage));
}
}
}

@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onServerChatEvent(ServerChatEvent event) {
if (!ServerUtilitiesConfig.ranks.override_chat || !Ranks.isActive()) {
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/serverutils/mixin/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ public enum Mixins {
.addMixinClasses(
"minecraft.MixinMinecraftServer_PauseWhenEmpty",
"minecraft.MixinDedicatedServer_PauseWhenEmpty")
.setApplyIf(() -> general.enable_pause_when_empty_property)),;
.setApplyIf(() -> general.enable_pause_when_empty_property)),
PLAYERS_SLEEPING_PERCENTAGE(new Builder("Player Sleeping Percentage").addTargetedMod(VANILLA).setSide(Side.BOTH)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a scenario where this feature is useful in SP since the mixin is also applied client-side?
I'm not a big fan of it printing two additional chat messages whenever you sleep in SP

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe Open to lan

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to apply on both in order to work with open to lan? I don't particularly care about supporting that feature, but that's the only use case I can think of. Otherwise it could be changed to server only.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning off the chat notifications when there's only one player present could also work to prevent unnecessary messages in SP, but I don't really care much for lan either so changing it to server only is also fine by me.

.setPhase(Phase.EARLY).setApplyIf(() -> world.enable_player_sleeping_percentage)
.addMixinClasses("minecraft.MixinWorldServer"));

private final List<String> mixinClasses;
private final Supplier<Boolean> applyIf;
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/assets/serverutilities/lang/en_US.lang
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ serverutilities.world.forced_spawn_dimension_time=Forced Spawn Dimension Time
serverutilities.world.forced_spawn_dimension_weather=Forced Spawn Dimension Weather
serverutilities.world.disable_player_suffocation_damage=Disable Player Suffocation Damage
serverutilities.world.show_playtime=Show Playtime in Top-Left Corner
serverutilities.world.players_sleeping=§f%s§6 is now sleeping. %s/%s (%s%%)
serverutiltiies.world.skip_night=§6Wakey, wakey, rise and shine... Good Morning everyone!

serverutilities.world.logging=Logging
serverutilities.world.logging.include_creative_players=Include Creative Players
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package serverutils.mixins.early.minecraft;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.WorldServer;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.storage.ISaveHandler;

import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import serverutils.ServerUtilitiesConfig;
import serverutils.data.ServerUtilitiesPlayerData;
import serverutils.lib.data.Universe;

@Mixin(WorldServer.class)
public abstract class MixinWorldServer extends World {

@Shadow
private boolean allPlayersSleeping;

@Unique
private int percent;

@Unique
private List<EntityPlayer> sleepingPlayers;

/**
* We need to access this.playerEntities from the superclass, so we're extending World, and need this fake
* constructor to make Java happy
**/
public MixinWorldServer(ISaveHandler p_i45368_1_, String p_i45368_2_, WorldProvider p_i45368_3_,
WorldSettings p_i45368_4_, Profiler p_i45368_5_) {
super(p_i45368_1_, p_i45368_2_, p_i45368_3_, p_i45368_4_, p_i45368_5_);
throw new RuntimeException(
"Server Utilities player sleeping percentage broke in a huge way. This error should never happen");
}

@Inject(method = "<init>", at = @At("RETURN"))
public void serverutilities$playersSleepingConstructor(CallbackInfo ci) {
sleepingPlayers = new ArrayList<>();
}

@Inject(method = "updateAllPlayersSleepingFlag", at = @At("HEAD"), cancellable = true)
public void serverutilities$handlePlayersSleepingPercentage(CallbackInfo ci) {
percent = Integer.parseInt(this.getGameRules().getGameRuleStringValue("playersSleepingPercentage"));
if (percent > 100) {
this.allPlayersSleeping = false;
ci.cancel(/* /r/nosleep, vanilla behaviour */);
} else {
EntityPlayer theSleeper = null;
sleepingPlayers.clear();
int cap = (int) Math.ceil(getListWithoutAFK(this.playerEntities).size() * percent * 0.01f);
for (EntityPlayer player : this.playerEntities) {
if (player.isPlayerSleeping()) {
sleepingPlayers.add(player);
theSleeper = player;
if (sleepingPlayers.size() >= cap) {
this.allPlayersSleeping = true;
break;
}
}
}

if (!sleepingPlayers.isEmpty() && cap > 0 && theSleeper != null) {
for (EntityPlayer player : this.playerEntities) {
String percentString = String.format("%d", (sleepingPlayers.size() * 100) / cap);
player.addChatMessage(
new ChatComponentTranslation(
"serverutilities.world.players_sleeping",
theSleeper.getDisplayName(),
sleepingPlayers.size(),
cap,
percentString));
}
}
ci.cancel();
}
}

@Redirect(
method = "areAllPlayersAsleep",
at = @At(
value = "FIELD",
target = "Lnet/minecraft/world/WorldServer;playerEntities:Ljava/util/List;",
opcode = Opcodes.GETFIELD))
public List<EntityPlayer> serverutilities$speedup1(WorldServer instance) {
return sleepingPlayers.isEmpty() ? this.playerEntities : sleepingPlayers;
}

@Inject(
method = "areAllPlayersAsleep",
at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;"),
cancellable = true)
public void serverutilities$speedup2(CallbackInfoReturnable<Boolean> ctx) {
if (percent < 1) ctx.setReturnValue(true);
}

@Inject(
method = "wakeAllPlayers",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/EntityPlayer;wakeUpPlayer(ZZZ)V"),
locals = LocalCapture.CAPTURE_FAILHARD)
public void serverutilities$broadcast(CallbackInfo ctx, Iterator iterator, EntityPlayer player) {
if (percent > 0 && percent < 100) {
player.addChatMessage(new ChatComponentTranslation("serverutiltiies.world.skip_night"));
}
}

public List<EntityPlayer> getListWithoutAFK(List<EntityPlayer> list) {
long notificationTimer = ServerUtilitiesConfig.afk.getNotificationTimer();
return list.stream()
.filter(
(EntityPlayer entity) -> ServerUtilitiesPlayerData
.get(Universe.get().getPlayer((EntityPlayerMP) entity)).afkTime <= notificationTimer)
.collect(Collectors.toList());
}
}