diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/Bloodmoon.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/Bloodmoon.java index b7d2381..2f83254 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/Bloodmoon.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/Bloodmoon.java @@ -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.ApplianceCommand; 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.BloodmoonMonsterDeathListener; -import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonPlayerJoinListener; -import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.BloodmoonTimeListener; +import eu.mhsl.craftattack.spawn.craftattack.appliances.gameplay.bloodmoon.listener.*; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.util.Ticks; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.*; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitTask; @@ -70,7 +65,6 @@ public class Bloodmoon extends Appliance { public final double mobHealthMultiplier = 2; private final ThreadLocalRandom random = ThreadLocalRandom.current(); - private boolean isActive = false; private final BossBar bossBar = BossBar.bossBar( Component.text("Blutmond", NamedTextColor.DARK_RED), 1f, @@ -79,6 +73,7 @@ public class Bloodmoon extends Appliance { Set.of(BossBar.Flag.CREATE_WORLD_FOG, BossBar.Flag.DARKEN_SCREEN) ); private final boolean hordesEnabled = true; + public final boolean bloodmoonSkippable = true; private final int hordeSpawnRateTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeSpawnRateVariationTicks = 40 * Ticks.TICKS_PER_SECOND; private final int hordeMinPopulation = 3; @@ -90,10 +85,10 @@ public class Bloodmoon extends Appliance { EntityType.SPIDER ); private final Map hordeSpawnTasks = new WeakHashMap<>(); - private long lastBloodmoonStartTick = 0; 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; + private boolean bossbarActive = false; private final int bloodmoonFreeDaysAtStart = 3; private final int bloodmoonStartTime = this.ticksPerDay /2; private final int bloodmoonDayInterval = 30; @@ -113,35 +108,71 @@ public class Bloodmoon extends Appliance { } 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) { - this.lastBloodmoonStartTick = startTick; - this.isActive = true; + public void startBloodmoon() { + this.bossbarActive = true; Bukkit.getOnlinePlayers().forEach(this::addPlayerToBossBar); this.startHordeSpawning(this.getRandomHordeSpawnDelay()); this.sendStartMessages(); } - public void stopBloodmoon() { - this.isActive = false; + public void stopBloodmoonIfInactive() { + 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)); + this.stopHordeSpawning(); this.sendStopMessages(); } public void updateBossBar() { long tick = Bukkit.getWorlds().getFirst().getFullTime(); - long sinceStart = tick - this.lastBloodmoonStartTick; + long sinceStart = tick - this.lastBloodmoonStartTick(); float progress = 1f - ((float) sinceStart / this.bloodmoonLength); if(progress < 0) progress = 1f; 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) { long day = tick / this.ticksPerDay; - if(day % this.bloodmoonDayInterval != 0 || day - this.bloodmoonFreeDaysAtStart <= 0) return false; - long time = tick - (day * this.ticksPerDay); + if(day % this.bloodmoonDayInterval != 0 || day <= this.bloodmoonFreeDaysAtStart) return false; + long time = tick % this.ticksPerDay; return time == this.bloodmoonStartTime; } @@ -153,7 +184,14 @@ public class Bloodmoon extends Appliance { 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); if(task != null) task.cancel(); BukkitTask newTask = Bukkit.getScheduler().runTaskLater( @@ -191,6 +229,7 @@ public class Bloodmoon extends Appliance { if(!player.getGameMode().equals(GameMode.SURVIVAL)) return; 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); this.spawnHorde(player, hordeSize, hordeEntityType); } @@ -217,7 +256,7 @@ public class Bloodmoon extends Appliance { Math.cos(spawnRadiant)*this.hordeSpawnDistance ); mobSpawnLocation.setY(player.getWorld().getHighestBlockYAt(mobSpawnLocation) + 1); - player.getWorld().spawnEntity(mobSpawnLocation, type); + player.getWorld().spawnEntity(mobSpawnLocation, type, CreatureSpawnEvent.SpawnReason.NATURAL); player.getWorld().strikeLightningEffect(mobSpawnLocation); } } @@ -271,7 +310,8 @@ public class Bloodmoon extends Appliance { new BloodmoonMonsterDeathListener(), new BloodmoonPlayerJoinListener(), new BloodmoonEntityDamageListener(), - new BloodmoonTimeListener() + new BloodmoonTimeListener(), + new BloodmoonTimeSkipListener() ); } diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/commands/BloodmoonCommand.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/commands/BloodmoonCommand.java index 42a788c..ded939a 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/commands/BloodmoonCommand.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/commands/BloodmoonCommand.java @@ -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.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; +import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -23,15 +24,19 @@ public class BloodmoonCommand extends ApplianceCommand { switch(args[0]) { case "start": { - this.getAppliance().startBloodmoon(0L); + this.getAppliance().startBloodmoon(); sender.sendMessage("Started bloodmoon."); break; } case "stop": { - this.getAppliance().stopBloodmoon(); + this.getAppliance().forceStopBloodmoon(); sender.sendMessage("Stopped bloodmoon."); break; } + case "time": { + sender.sendMessage(String.valueOf(Bukkit.getWorlds().getFirst().getFullTime())); + break; + } default: throw new Error("No such option: '%s' !".formatted(args[0])); } } diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonPlayerJoinListener.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonPlayerJoinListener.java index 30c575e..a1edd10 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonPlayerJoinListener.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonPlayerJoinListener.java @@ -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.craftattack.appliances.gameplay.bloodmoon.Bloodmoon; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerJoinEvent; @@ -9,7 +10,9 @@ public class BloodmoonPlayerJoinListener extends ApplianceListener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { if(!this.getAppliance().bloodmoonIsActive()) return; - this.getAppliance().addPlayerToBossBar(event.getPlayer()); - this.getAppliance().sendWarningMessage(event.getPlayer()); + Player player = event.getPlayer(); + this.getAppliance().addPlayerToBossBar(player); + this.getAppliance().startHordeSpawning(1500, player); + this.getAppliance().sendWarningMessage(player); } } diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeListener.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeListener.java index de7d9bd..1ce5cae 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeListener.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeListener.java @@ -12,11 +12,11 @@ public class BloodmoonTimeListener extends ApplianceListener { public void onServerTick(ServerTickStartEvent event) { long currentTime = Bukkit.getWorlds().getFirst().getFullTime(); if(this.getAppliance().isStartTick(currentTime)) { - this.getAppliance().startBloodmoon(currentTime); + this.getAppliance().startBloodmoon(); return; } if(this.getAppliance().isStartTick(currentTime - this.getAppliance().bloodmoonLength)) { - this.getAppliance().stopBloodmoon(); + this.getAppliance().stopBloodmoonIfInactive(); return; } if(currentTime % Ticks.TICKS_PER_SECOND == 0 && this.getAppliance().bloodmoonIsActive()) this.getAppliance().updateBossBar(); diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeSkipListener.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeSkipListener.java new file mode 100644 index 0000000..39ceca9 --- /dev/null +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/gameplay/bloodmoon/listener/BloodmoonTimeSkipListener.java @@ -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 { + @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")); + } +}