Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.eternalcode.core.feature.chat.ChatSettings;
import com.eternalcode.core.feature.deathmessage.config.DeathMessageConfig;
import com.eternalcode.core.feature.deathmessage.config.DeathMessageSettings;
import com.eternalcode.core.feature.deathteleport.DeathTeleportConfig;
import com.eternalcode.core.feature.deathteleport.DeathTeleportSettings;
import com.eternalcode.core.feature.enchant.EnchantConfig;
import com.eternalcode.core.feature.enchant.EnchantSettings;
import com.eternalcode.core.feature.give.GiveConfig;
Expand Down Expand Up @@ -244,6 +246,12 @@ public static class Format extends OkaeriConfig {
@Comment("# Settings for the /back command functionality")
BackConfig back = new BackConfig();

@Bean(proxied = DeathTeleportSettings.class)
@Comment("")
@Comment("# Death Teleport Configuration")
@Comment("# Automatically teleports players back to their death location after respawning")
DeathTeleportConfig deathTeleport = new DeathTeleportConfig();

@Override
public File getConfigFile(File dataFolder) {
return new File(dataFolder, "config.yml");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.eternalcode.core.feature.deathteleport;

import com.eternalcode.annotations.scan.command.DescriptionDocs;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.notice.NoticeService;
import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Sender;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import java.util.UUID;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

@Command(name = "deathteleport", aliases = "deathtp")
@Permission("eternalcore.deathteleport")
class DeathTeleportCommand {

private final DeathTeleportToggleService toggleService;
private final NoticeService noticeService;

@Inject
DeathTeleportCommand(DeathTeleportToggleService toggleService, NoticeService noticeService) {
this.toggleService = toggleService;
this.noticeService = noticeService;
}

@Execute
@DescriptionDocs(description = "Toggle automatic teleportation to your last death location after respawning")
void execute(@Sender Player sender) {
UUID player = sender.getUniqueId();
this.toggleService.toggleState(player)
.thenAccept(state -> this.noticePlayer(state, player));
}

@Execute(name = "enable")
@DescriptionDocs(description = "Enable automatic teleportation to your last death location after respawning")
void executeEnable(@Sender Player sender) {
UUID player = sender.getUniqueId();
this.toggleService.setState(player, DeathTeleportState.ENABLED)
.thenAccept(ignored -> this.noticePlayer(DeathTeleportState.ENABLED, player));
}

@Execute(name = "disable")
@DescriptionDocs(description = "Disable automatic teleportation to your last death location after respawning")
void executeDisable(@Sender Player sender) {
UUID player = sender.getUniqueId();
this.toggleService.setState(player, DeathTeleportState.DISABLED)
.thenAccept(ignored -> this.noticePlayer(DeathTeleportState.DISABLED, player));
}

@Execute
@Permission("eternalcore.deathteleport.other")
@DescriptionDocs(description = "Toggle automatic death teleportation for another player", arguments = "<player>")
void executeOther(@Sender CommandSender sender, @Arg Player target) {
UUID player = target.getUniqueId();
this.toggleService.toggleState(player)
.thenAccept(state -> this.noticeOtherPlayer(sender, state, target));
}

private void noticeOtherPlayer(CommandSender sender, DeathTeleportState state, Player target) {
this.noticePlayer(state, target.getUniqueId());
if (sender.equals(target)) {
return;
}

this.noticeService.create()
.sender(sender)
.notice(translation -> state == DeathTeleportState.ENABLED
? translation.deathTeleport().deathTeleportOtherEnabled()
: translation.deathTeleport().deathTeleportOtherDisabled())
.placeholder("{PLAYER}", target.getName())
.send();
}

private void noticePlayer(DeathTeleportState state, UUID player) {
this.noticeService.create()
.player(player)
.notice(translation -> state == DeathTeleportState.ENABLED
? translation.deathTeleport().deathTeleportSelfEnabled()
: translation.deathTeleport().deathTeleportSelfDisabled())
.send();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.eternalcode.core.feature.deathteleport;

import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
import lombok.Getter;
import lombok.experimental.Accessors;

@Getter
@Accessors(fluent = true)
public class DeathTeleportConfig extends OkaeriConfig implements DeathTeleportSettings {

@Comment({
"# Automatically teleport players back to their last death location after they respawn.",
"# This helps new players who don't know the /back command to quickly return to where they died.",
"# Players can opt out for themselves with the /deathteleport command."
})
public boolean deathTeleportEnabled = true;

@Comment("# Delay after respawn before the player is teleported back to their death location.")
public Duration deathTeleportDelay = Duration.ofSeconds(3);

@Comment("# How long a death location is remembered and eligible for automatic teleportation.")
public Duration deathLocationCacheDuration = Duration.ofHours(1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.eternalcode.core.feature.deathteleport;

import com.eternalcode.commons.bukkit.position.Position;
import com.eternalcode.commons.bukkit.position.PositionAdapter;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import com.eternalcode.core.feature.teleport.TeleportService;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.injector.annotations.component.Controller;
import com.eternalcode.core.notice.NoticeService;
import com.eternalcode.core.util.DurationUtil;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;

@Controller
class DeathTeleportController implements Listener {

private final DeathTeleportService deathTeleportService;
private final DeathTeleportToggleService toggleService;
private final DeathTeleportSettings settings;
private final TeleportService teleportService;
private final NoticeService noticeService;
private final MinecraftScheduler scheduler;

@Inject
DeathTeleportController(
DeathTeleportService deathTeleportService,
DeathTeleportToggleService toggleService,
DeathTeleportSettings settings,
TeleportService teleportService,
NoticeService noticeService,
MinecraftScheduler scheduler
) {
this.deathTeleportService = deathTeleportService;
this.toggleService = toggleService;
this.settings = settings;
this.teleportService = teleportService;
this.noticeService = noticeService;
this.scheduler = scheduler;
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();

this.deathTeleportService.markDeathLocation(player.getUniqueId(), PositionAdapter.convert(player.getLocation()));
}

@EventHandler
void onPlayerRespawn(PlayerRespawnEvent event) {
if (!this.settings.deathTeleportEnabled()) {
return;
}

Player player = event.getPlayer();
UUID playerId = player.getUniqueId();

Optional<Position> deathLocation = this.deathTeleportService.getDeathLocation(playerId);
if (deathLocation.isEmpty()) {
return;
}

this.toggleService.getState(playerId).thenAccept(state -> {
if (state == DeathTeleportState.DISABLED) {
return;
}

if (!player.isOnline()) {
return;
}

this.noticeService.create()
.player(playerId)
.notice(translation -> translation.deathTeleport().deathTeleportStarted())
.placeholder("{TIME}", DurationUtil.format(this.settings.deathTeleportDelay(), true))
.send();

this.scheduler.runLater(
() -> {
if (!player.isOnline() || player.isDead()) {
return;
}

Optional<Position> currentDeathLocation = this.deathTeleportService.getDeathLocation(playerId);
if (currentDeathLocation.isEmpty() || !currentDeathLocation.get().equals(deathLocation.get())) {
return;
}

this.deathTeleportService.clearDeathLocation(playerId);
this.teleportService.teleport(player, PositionAdapter.convert(deathLocation.get()));

this.noticeService.create()
.player(playerId)
.notice(translation -> translation.deathTeleport().deathTeleportSuccess())
.send();
},
this.settings.deathTeleportDelay()
);
});
Comment thread
ChudziudgiToJa marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.eternalcode.core.feature.deathteleport;

import com.eternalcode.commons.bukkit.position.Position;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.injector.annotations.component.Service;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Optional;
import java.util.UUID;

@Service
class DeathTeleportService {

private final Cache<UUID, Position> deathLocations;

@Inject
DeathTeleportService(
DeathTeleportSettings settings
) {
this.deathLocations = Caffeine.newBuilder()
.expireAfterWrite(settings.deathLocationCacheDuration())
.build();
}

void markDeathLocation(
UUID playerId, Position position
) {
this.deathLocations.put(playerId, position);
}

Optional<Position> getDeathLocation(
UUID playerId
) {
return Optional.ofNullable(this.deathLocations.getIfPresent(playerId));
}

void clearDeathLocation(
UUID playerId
) {
this.deathLocations.invalidate(playerId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.eternalcode.core.feature.deathteleport;

import java.time.Duration;

public interface DeathTeleportSettings {

boolean deathTeleportEnabled();

Duration deathTeleportDelay();

Duration deathLocationCacheDuration();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.eternalcode.core.feature.deathteleport;

public enum DeathTeleportState {
ENABLED,
DISABLED;


DeathTeleportState invert() {
return this == ENABLED ? DISABLED : ENABLED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.eternalcode.core.feature.deathteleport;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.util.UUID;

@DatabaseTable(tableName = "eternal_core_death_teleport_state")
class DeathTeleportStateTable {

@DatabaseField(columnName = "id", id = true)
private UUID uniqueId;

@DatabaseField(columnName = "state")
private DeathTeleportState state;

DeathTeleportStateTable(UUID uniqueId, DeathTeleportState state) {
this.uniqueId = uniqueId;
this.state = state;
}

DeathTeleportStateTable() {}

DeathTeleportState state() {
return this.state;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.eternalcode.core.feature.deathteleport;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;

interface DeathTeleportToggleRepository {

CompletableFuture<DeathTeleportState> getState(UUID uuid);

CompletableFuture<Void> setState(UUID uuid, DeathTeleportState state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.eternalcode.core.feature.deathteleport;

import com.eternalcode.commons.scheduler.Scheduler;
import com.eternalcode.core.database.AbstractRepositoryOrmLite;
import com.eternalcode.core.database.DatabaseManager;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.injector.annotations.component.Repository;
import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@Repository
class DeathTeleportToggleRepositoryOrmLite extends AbstractRepositoryOrmLite implements DeathTeleportToggleRepository {

@Inject
private DeathTeleportToggleRepositoryOrmLite(DatabaseManager databaseManager, Scheduler scheduler) throws SQLException {
super(databaseManager, scheduler);
TableUtils.createTableIfNotExists(databaseManager.connectionSource(), DeathTeleportStateTable.class);
}

@Override
public CompletableFuture<DeathTeleportState> getState(UUID uuid) {
return this.selectSafe(DeathTeleportStateTable.class, uuid)
.thenApply(optional -> optional.map(DeathTeleportStateTable::state).orElse(DeathTeleportState.ENABLED))
.exceptionally(throwable -> DeathTeleportState.ENABLED);
}

@Override
public CompletableFuture<Void> setState(UUID uuid, DeathTeleportState state) {
return this.save(DeathTeleportStateTable.class, new DeathTeleportStateTable(uuid, state))
.thenApply(status -> null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.eternalcode.core.feature.deathteleport;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;

public interface DeathTeleportToggleService {

CompletableFuture<DeathTeleportState> getState(UUID playerUniqueId);

CompletableFuture<Void> setState(UUID playerUniqueId, DeathTeleportState state);

CompletableFuture<DeathTeleportState> toggleState(UUID playerUniqueId);
}
Loading
Loading