fixed bloodmoon sleep / time skip issue, added endermite hordes for end, fixed server restart during bloodmoon issue

This commit is contained in:
2026-01-17 17:09:54 +01:00
parent 0a16e1e049
commit 323e316f0f
5 changed files with 106 additions and 29 deletions

View File

@@ -5,23 +5,18 @@ import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance; import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.commands.BloodmoonCommand; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.commands.BloodmoonCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonEntityDamageListener; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.*;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonMonsterDeathListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonPlayerJoinListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonTimeListener;
import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.Ticks; import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit; import org.bukkit.*;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
@@ -70,7 +65,6 @@ public class Bloodmoon extends Appliance {
public final double mobHealthMultiplier = 2; public final double mobHealthMultiplier = 2;
private final ThreadLocalRandom random = ThreadLocalRandom.current(); private final ThreadLocalRandom random = ThreadLocalRandom.current();
private boolean isActive = false;
private final BossBar bossBar = BossBar.bossBar( private final BossBar bossBar = BossBar.bossBar(
Component.text("Blutmond", NamedTextColor.DARK_RED), Component.text("Blutmond", NamedTextColor.DARK_RED),
1f, 1f,
@@ -79,6 +73,7 @@ public class Bloodmoon extends Appliance {
Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN) Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN)
); );
private final boolean hordesEnabled = true; private final boolean hordesEnabled = true;
public final boolean bloodmoonSkippable = true;
private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND;
private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND;
private final int hordeMinPopulation = 3; private final int hordeMinPopulation = 3;
@@ -90,10 +85,10 @@ public class Bloodmoon extends Appliance {
EntityType.SPIDER EntityType.SPIDER
); );
private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>(); private final Map<Player, @Nullable BukkitTask> hordeSpawnTasks = new WeakHashMap<>();
private long lastBloodmoonStartTick = 0;
public final int ticksPerDay = 24000; public final int ticksPerDay = 24000;
public final int bloodmoonLength = this.ticksPerDay /2; public final int bloodmoonLength = this.ticksPerDay / 2;
public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50; public final int preStartMessageTicks = Ticks.TICKS_PER_SECOND * 50;
private boolean bossbarActive = false;
private final int bloodmoonFreeDaysAtStart = 3; private final int bloodmoonFreeDaysAtStart = 3;
private final int bloodmoonStartTime = this.ticksPerDay /2; private final int bloodmoonStartTime = this.ticksPerDay /2;
private final int bloodmoonDayInterval = 30; private final int bloodmoonDayInterval = 30;
@@ -113,35 +108,71 @@ public class Bloodmoon extends Appliance {
} }
public boolean bloodmoonIsActive() { public boolean bloodmoonIsActive() {
return this.isActive; long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
long day = currentTick / this.ticksPerDay;
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false;
long time = currentTick % this.ticksPerDay;
return time >= this.bloodmoonStartTime && time <= this.bloodmoonStartTime + this.bloodmoonLength;
} }
public void startBloodmoon(long startTick) { public void startBloodmoon() {
this.lastBloodmoonStartTick = startTick; this.bossbarActive = true;
this.isActive = true;
Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar); Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar);
this.startHordeSpawning(this.getRandomHordeSpawnDelay()); this.startHordeSpawning(this.getRandomHordeSpawnDelay());
this.sendStartMessages(); this.sendStartMessages();
} }
public void stopBloodmoon() { public void stopBloodmoonIfInactive() {
this.isActive = false; Bukkit.getScheduler().runTaskLater(
Main.instance(),
() -> {
if(!this.bossbarActive) return;
if(this.bloodmoonIsActive()) return;
this.bossbarActive = false;
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
this.stopHordeSpawning();
this.sendStopMessages();
},
1
);
}
// not working for entity effects and debuffs...
public void forceStopBloodmoon() {
this.bossbarActive = false;
Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar)); Bukkit.getOnlinePlayers().forEach(player -> player.hideBossBar(this.bossBar));
this.stopHordeSpawning();
this.sendStopMessages(); this.sendStopMessages();
} }
public void updateBossBar() { public void updateBossBar() {
long tick = Bukkit.getWorlds().getFirst().getFullTime(); long tick = Bukkit.getWorlds().getFirst().getFullTime();
long sinceStart = tick - this.lastBloodmoonStartTick; long sinceStart = tick - this.lastBloodmoonStartTick();
float progress = 1f - ((float) sinceStart / this.bloodmoonLength); float progress = 1f - ((float) sinceStart / this.bloodmoonLength);
if(progress < 0) progress = 1f; if(progress < 0) progress = 1f;
this.bossBar.progress(progress); this.bossBar.progress(progress);
} }
private long lastBloodmoonStartTick() {
long currentTick = Bukkit.getWorlds().getFirst().getFullTime();
long day = currentTick / this.ticksPerDay;
long time = currentTick % this.ticksPerDay;
boolean todayIsBloodmoon = (day % this.bloodmoonDayInterval == 0) && (day > this.bloodmoonFreeDaysAtStart);
if (todayIsBloodmoon && time < this.bloodmoonStartTime) {
day -= this.bloodmoonDayInterval;
} else if (!todayIsBloodmoon) {
day -= (day % this.bloodmoonDayInterval);
}
if (day <= this.bloodmoonFreeDaysAtStart) return -1L;
return day * this.ticksPerDay + this.bloodmoonStartTime;
}
public boolean isStartTick(long tick) { public boolean isStartTick(long tick) {
long day = tick / this.ticksPerDay; long day = tick / this.ticksPerDay;
if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false; if(day % this.bloodmoonDayInterval != 0 || day <= this.bloodmoonFreeDaysAtStart) return false;
long time = tick - (day * this.ticksPerDay); long time = tick % this.ticksPerDay;
return time == this.bloodmoonStartTime; return time == this.bloodmoonStartTime;
} }
@@ -153,7 +184,14 @@ public class Bloodmoon extends Appliance {
Bukkit.getOnlinePlayers().forEach(player -> this.startHordeSpawning(delay, player)); Bukkit.getOnlinePlayers().forEach(player -> this.startHordeSpawning(delay, player));
} }
private void startHordeSpawning(int delay, Player player) { private void stopHordeSpawning() {
this.hordeSpawnTasks.forEach((player, bukkitTask) -> {
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
if(task != null) task.cancel();
});
}
public void startHordeSpawning(int delay, Player player) {
@Nullable BukkitTask task = this.hordeSpawnTasks.get(player); @Nullable BukkitTask task = this.hordeSpawnTasks.get(player);
if(task != null) task.cancel(); if(task != null) task.cancel();
BukkitTask newTask = Bukkit.getScheduler().runTaskLater( BukkitTask newTask = Bukkit.getScheduler().runTaskLater(
@@ -191,6 +229,7 @@ public class Bloodmoon extends Appliance {
if(!player.getGameMode().equals(GameMode.SURVIVAL)) return; if(!player.getGameMode().equals(GameMode.SURVIVAL)) return;
EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size())); EntityType hordeEntityType = this.hordeMobList.get(this.random.nextInt(this.hordeMobList.size()));
if(player.getLocation().getWorld().getEnvironment().equals(World.Environment.THE_END)) hordeEntityType = EntityType.ENDERMITE;
int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1); int hordeSize = this.random.nextInt(this.hordeMinPopulation, this.hordeMaxPopulation + 1);
this.spawnHorde(player, hordeSize, hordeEntityType); this.spawnHorde(player, hordeSize, hordeEntityType);
} }
@@ -217,7 +256,7 @@ public class Bloodmoon extends Appliance {
Math.cos(spawnRadiant)*this.hordeSpawnDistance Math.cos(spawnRadiant)*this.hordeSpawnDistance
); );
mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1); mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1);
player.getWorld().spawnEntity(mobSpawnLocation, type); player.getWorld().spawnEntity(mobSpawnLocation, type, CreatureSpawnEvent.SpawnReason.NATURAL);
player.getWorld().strikeLightningEffect(mobSpawnLocation); player.getWorld().strikeLightningEffect(mobSpawnLocation);
} }
} }
@@ -271,7 +310,8 @@ public class Bloodmoon extends Appliance {
new BloodmoonMonsterDeathListener(), new BloodmoonMonsterDeathListener(),
new BloodmoonPlayerJoinListener(), new BloodmoonPlayerJoinListener(),
new BloodmoonEntityDamageListener(), new BloodmoonEntityDamageListener(),
new BloodmoonTimeListener() new BloodmoonTimeListener(),
new BloodmoonTimeSkipListener()
); );
} }

View File

@@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.comm
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -23,15 +24,19 @@ public class BloodmoonCommand extends ApplianceCommand<Bloodmoon> {
switch(args[0]) { switch(args[0]) {
case "start": { case "start": {
this.getAppliance().startBloodmoon(0L); this.getAppliance().startBloodmoon();
sender.sendMessage("Started bloodmoon."); sender.sendMessage("Started bloodmoon.");
break; break;
} }
case "stop": { case "stop": {
this.getAppliance().stopBloodmoon(); this.getAppliance().forceStopBloodmoon();
sender.sendMessage("Stopped bloodmoon."); sender.sendMessage("Stopped bloodmoon.");
break; break;
} }
case "time": {
sender.sendMessage(String.valueOf(Bukkit.getWorlds().getFirst().getFullTime()));
break;
}
default: throw new Error("No such option: '%s' !".formatted(args[0])); default: throw new Error("No such option: '%s' !".formatted(args[0]));
} }
} }

View File

@@ -2,6 +2,7 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.list
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
@@ -9,7 +10,9 @@ public class BloodmoonPlayerJoinListener extends ApplianceListener<Bloodmoon> {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
if(!this.getAppliance().bloodmoonIsActive()) return; if(!this.getAppliance().bloodmoonIsActive()) return;
this.getAppliance().addPlayerToBossBar(event.getPlayer()); Player player = event.getPlayer();
this.getAppliance().sendWarningMessage(event.getPlayer()); this.getAppliance().addPlayerToBossBar(player);
this.getAppliance().startHordeSpawning(1500, player);
this.getAppliance().sendWarningMessage(player);
} }
} }

View File

@@ -12,11 +12,11 @@ public class BloodmoonTimeListener extends ApplianceListener<Bloodmoon> {
public void onServerTick(ServerTickStartEvent event) { public void onServerTick(ServerTickStartEvent event) {
long currentTime = Bukkit.getWorlds().getFirst().getFullTime(); long currentTime = Bukkit.getWorlds().getFirst().getFullTime();
if(this.getAppliance().isStartTick(currentTime)) { if(this.getAppliance().isStartTick(currentTime)) {
this.getAppliance().startBloodmoon(currentTime); this.getAppliance().startBloodmoon();
return; return;
} }
if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) { if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) {
this.getAppliance().stopBloodmoon(); this.getAppliance().stopBloodmoonIfInactive();
return; return;
} }
if(currentTime % Ticks.TICKS_PER_SECOND == 0 && this.getAppliance().bloodmoonIsActive()) this.getAppliance().updateBossBar(); if(currentTime % Ticks.TICKS_PER_SECOND == 0 && this.getAppliance().bloodmoonIsActive()) this.getAppliance().updateBossBar();

View File

@@ -0,0 +1,29 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.Bloodmoon;
import net.kyori.adventure.text.Component;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.event.world.TimeSkipEvent;
public class BloodmoonTimeSkipListener extends ApplianceListener<Bloodmoon> {
@EventHandler
public void onTimeSkip(TimeSkipEvent event) {
if(this.getAppliance().bloodmoonSkippable || !event.getSkipReason().equals(TimeSkipEvent.SkipReason.NIGHT_SKIP)) {
this.getAppliance().stopBloodmoonIfInactive();
return;
}
event.setCancelled(true);
}
@EventHandler
public void onPlayerSleep(PlayerBedEnterEvent event) {
if(!this.getAppliance().bloodmoonIsActive()) return;
if(this.getAppliance().bloodmoonSkippable) return;
if(!event.getBedEnterResult().equals(PlayerBedEnterEvent.BedEnterResult.OK)) return;
event.setUseBed(Event.Result.DENY);
event.getPlayer().sendActionBar(Component.text("Du kannst während dem Blutmond nicht schlafen"));
}
}