Skip to content

Commit

Permalink
Add rescue during world join
Browse files Browse the repository at this point in the history
  • Loading branch information
Jikoo committed May 28, 2024
1 parent 9400921 commit bb6783e
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 2 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.cryptomorin</groupId>
<artifactId>kingdoms</artifactId>
<version>1.16.8.1.1</version>
<scope>provided</scope>
</dependency>
<!-- JitPack -->
<dependency>
<groupId>com.github.cjburkey01</groupId>
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/github/jikoo/regionerator/Regionerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.github.jikoo.regionerator.listeners.DebugListener;
import com.github.jikoo.regionerator.listeners.FlaggingListener;
import com.github.jikoo.regionerator.listeners.HookListener;
import com.github.jikoo.regionerator.listeners.RescueListener;
import com.github.jikoo.regionerator.listeners.WorldListener;
import com.github.jikoo.regionerator.util.yaml.Config;
import com.github.jikoo.regionerator.util.yaml.MiscData;
Expand Down Expand Up @@ -90,8 +91,6 @@ public void onEnable() {
command.setExecutor(executor);
}

getServer().getPluginManager().registerEvents(new WorldListener(this), this);

if (config.startPaused()) {
this.setPaused(true);
}
Expand Down Expand Up @@ -159,6 +158,11 @@ public void reloadFeatures() {
protectionHooks.removeIf(hook -> hook.getClass().getPackage().getName().equals("com.github.jikoo.regionerator.hooks"));

debug(DebugLevel.LOW, () -> "Loading features...");

// Enable world case correction listener.
getServer().getPluginManager().registerEvents(new WorldListener(this), this);
// Enable rescue tagging listener.
getServer().getPluginManager().registerEvents(new RescueListener(this), this);
// Always enable hook listener in case someone else adds hooks.
getServer().getPluginManager().registerEvents(new HookListener(this), this);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright (c) 2015-2024 by Jikoo.
*
* Regionerator is licensed under a Creative Commons
* Attribution-ShareAlike 4.0 International License.
*
* You should have received a copy of the license along with this
* work. If not, see <http://creativecommons.org/licenses/by-sa/4.0/>.
*/

package com.github.jikoo.regionerator.listeners;

import com.github.jikoo.regionerator.Regionerator;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.BoundingBox;
import org.jetbrains.annotations.NotNull;
import org.spigotmc.event.player.PlayerSpawnLocationEvent;

import java.util.Collection;
import java.util.UUID;

public class RescueListener implements Listener {

private final @NotNull Regionerator plugin;
private final @NotNull NamespacedKey loggedInSinceFeature;

public RescueListener(@NotNull Regionerator plugin) {
this.plugin = plugin;
this.loggedInSinceFeature = new NamespacedKey(plugin, "safe-logout");
}

@EventHandler(priority = EventPriority.MONITOR) // Run late so we get final post-plugin-modification location
private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
Chunk chunk = event.getPlayer().getLocation().getChunk();
NamespacedKey key = getLogoutKey(event.getPlayer().getUniqueId());
chunk.getPersistentDataContainer().set(key, PersistentDataType.BYTE, (byte) 1);
}

@EventHandler(priority = EventPriority.LOWEST) // Run early so everyone overrides us
private void onPlayerSpawn(@NotNull PlayerSpawnLocationEvent event) {
Player player = event.getPlayer();
// Check if the player has logged in since this feature was added.
PersistentDataContainer playerPdc = player.getPersistentDataContainer();
if (!playerPdc.has(loggedInSinceFeature, PersistentDataType.BYTE)) {
playerPdc.set(loggedInSinceFeature, PersistentDataType.BYTE, (byte) 1);
return;
}

Chunk chunk = event.getSpawnLocation().getChunk();
PersistentDataContainer chunkPdc = chunk.getPersistentDataContainer();
NamespacedKey logoutKey = getLogoutKey(player.getUniqueId());
// If the key is set, the chunk has not been deleted since the player last logged out.
if (chunkPdc.has(logoutKey, PersistentDataType.BYTE)) {
chunkPdc.remove(logoutKey);
return;
}

// If rescue is not enabled, exit early. Note that we do still want to do the tagging in case enabled later.
if (!plugin.config().rescueEnabled()) {
return;
}

// Only rescue safe players if configured to do so.
if (!plugin.config().rescueIfSafe() && !isUnsafe(player)) {
return;
}

// If rescuing up, check if top block can be stood on safely.
if (plugin.config().rescueToTopBlock()) {
Block topBlock = player.getWorld().getHighestBlockAt(player.getLocation());
if (!isUnsafe(topBlock.getType()) && !isNotStandable(topBlock)) {
event.setSpawnLocation(topBlock.getLocation().add(0.5, 1, 0.5));
return;
}
}

// If rescuing to personal respawn location, do so if available.
if (plugin.config().rescueToRespawn()) {
Location spawnLoc = player.getBedSpawnLocation();
if (spawnLoc != null) {
event.setSpawnLocation(spawnLoc);
return;
}
}

// Otherwise, use respawn location of rescue world.
World spawnWorld = plugin.config().getRescueWorld(player.getWorld());
event.setSpawnLocation(spawnWorld.getSpawnLocation());
}

private boolean isUnsafe(Player player) {
// Underground is probably unsafe, skip more expensive checks.
if (player.getLocation().getBlockY() < player.getWorld().getSeaLevel()) {
return true;
}

Block headBlock = player.getEyeLocation().getBlock();
if (isUnsafe(headBlock.getType())) {
return true;
}

// If the player's head is in a block with a full collision box they'll suffocate.
Collection<BoundingBox> boxes = headBlock.getCollisionShape().getBoundingBoxes();
if (!boxes.isEmpty() && boxes.stream().allMatch(box -> box.getVolume() == 1.0)) {
return true;
}

Block footBlock = player.getLocation().getBlock();
if (isUnsafe(footBlock.getType())) {
return true;
}

// If the player is not standing on anything, they'll fall.
// Could get a more accurate representation by checking if any block underneath the player intersects with their
// hitbox when expanding it, but that seems like overkill.
return isNotStandable(player.getLocation().subtract(0, 1, 0).getBlock());
}

private boolean isNotStandable(Block block) {
return block.getCollisionShape().getBoundingBoxes().isEmpty();
}

private boolean isUnsafe(Material material) {
if (Tag.FIRE.isTagged(material)) {
return true;
}
return switch (material) {
case WATER, LAVA, CACTUS, CAMPFIRE, SOUL_CAMPFIRE, MAGMA_BLOCK, POWDER_SNOW, BAMBOO -> true;
default -> false;
};
}

private @NotNull NamespacedKey getLogoutKey(@NotNull UUID uuid) {
return new NamespacedKey(plugin, "safe-logout-" + uuid.toString().toLowerCase());
}

}
28 changes: 28 additions & 0 deletions src/main/java/com/github/jikoo/regionerator/util/yaml/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,32 @@ public boolean startPaused() {
return getBoolean("deletion.start-paused");
}

public boolean rescueEnabled() {
return getBoolean("safe-login.enabled");
}

public boolean rescueIfSafe() {
return getBoolean("safe-login.rescue-if-safe");
}

public boolean rescueToTopBlock() {
return getBoolean("safe-login.try-top-block");
}

public boolean rescueToRespawn() {
return getBoolean("safe-login.try-respawn");
}

public @NotNull World getRescueWorld(@NotNull World current) {
String worldName = getString("safe-login.world-override");
if (worldName == null) {
return current;
}
World world = Bukkit.getWorld(worldName);
if (world == null) {
return current;
}
return world;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ static void doUpdates(@NotNull Config config) {

private static void updateConfig1To2(Config config) {
config.set("deletion.start-paused", false);

config.set("safe-login.enabled", true);
config.set("safe-login.rescue-if-safe", false);
config.set("safe-login.try-top-block", true);
config.set("safe-login.try-respawn", true);
config.set("safe-login.world-override", "");

config.set("config-version", 2);
}

Expand Down
12 changes: 12 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ worlds:
# days-till-flag-expires must be greater than 0 to be used with delete-new-unvisited-chunks
days-till-flag-expires: -1

safe-login:
# Whether to modify login location if in a deleted chunk.
enabled: true
# Whether to always rescue players, even if considered safe.
rescue-if-safe: false
# Prefer the top block at the player's current location?
try-top-block: true
# After top block, prefer the player's bed/respawn anchor location?
try-respawn: true
# After respawn location, override player's current world spawn with another.
world-override: ""

# Hooks to attempt to check for protections from.
# If your protection plugin of choice isn't here, ask!
hooks:
Expand Down

0 comments on commit bb6783e

Please sign in to comment.