Compare commits
33
Commits
c09c55ed7a
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4953cec215 | ||
|
|
92f9e732a1 | ||
|
|
95b54d1a03 | ||
|
|
05a63b9a52 | ||
|
|
137e5a5125 | ||
|
|
3c5842a92c | ||
|
|
f5bb1564ae | ||
|
|
fa8d320da9 | ||
|
|
816c429346 | ||
|
|
afbd1a9f09 | ||
|
|
5430af2149 | ||
|
|
ce7297afb8 | ||
|
|
a10607092b | ||
|
|
fe39cdf5a1 | ||
|
|
6008378258 | ||
|
|
18b211f681 | ||
|
|
b5c59cf173 | ||
|
|
436b462ffe | ||
|
|
d11cf25fc8 | ||
|
|
e8c015790f | ||
|
|
ba8fbe83e0 | ||
|
|
d56e8eaea5 | ||
|
|
3550d75634 | ||
|
|
14158f73b8 | ||
|
|
c738f02d17 | ||
|
|
e62d0df9df | ||
|
|
eeed5b4c54 | ||
|
|
8386e773ce | ||
|
|
e5656f23ce | ||
|
|
409a1aa596 | ||
|
|
57f2898451 | ||
|
|
2242fea737 | ||
|
|
e04892156c |
@@ -4,18 +4,17 @@ import com.alttd.ctf.commands.CommandManager;
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.events.InventoryItemInteractionEvent;
|
||||
import com.alttd.ctf.events.OnPlayerDeath;
|
||||
import com.alttd.ctf.events.OnPlayerOnlineStatus;
|
||||
import com.alttd.ctf.events.SnowballEvent;
|
||||
import com.alttd.ctf.events.*;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.flag.FlagTryCaptureEvent;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.phases.EndedPhase;
|
||||
import com.alttd.ctf.gui.ClassSelectionGUI;
|
||||
import com.alttd.ctf.gui.GUIInventory;
|
||||
import com.alttd.ctf.gui.GUIListener;
|
||||
import com.alttd.ctf.json_config.JacksonConfig;
|
||||
import com.alttd.ctf.json_config.JsonConfigManager;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -29,16 +28,18 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
private GameManager gameManager = null;
|
||||
private Flag flag;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
PlayerStat.main = this;
|
||||
GUIInventory.setMain(this); // sorry
|
||||
ClassSelectionGUI.setMain(this); // sorry
|
||||
Package pkg = Main.class.getPackage();
|
||||
@@ -47,18 +48,13 @@ public class Main extends JavaPlugin {
|
||||
|
||||
reloadConfigs();
|
||||
WorldBorderApi worldBorderApi = worldBorder();
|
||||
this.gameManager = new GameManager(worldBorderApi);
|
||||
this.gameManager = new GameManager(this, worldBorderApi);
|
||||
registerTeams(); //Skipped in reloadConfig if gameManager is not created yet
|
||||
flag = new Flag(this, gameManager);
|
||||
loadPlayerStats(); //Skipped in reloadConfig if gameManager is not created yet
|
||||
Flag flag = new Flag(this, gameManager);
|
||||
new CommandManager(this, gameManager, flag, worldBorderApi);
|
||||
//Ensuring immediate respawn is on in all worlds
|
||||
log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world);
|
||||
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||
if (world != null) {
|
||||
world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true);
|
||||
} else {
|
||||
log.error("No valid flag world defined, unable to modify game rules");
|
||||
}
|
||||
enableImmediateRespawn();
|
||||
registerEvents(flag, worldBorderApi);
|
||||
}
|
||||
|
||||
@@ -68,6 +64,7 @@ public class Main extends JavaPlugin {
|
||||
GameConfig.reload(this);
|
||||
if (gameManager != null) {
|
||||
registerTeams();
|
||||
loadPlayerStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +80,24 @@ public class Main extends JavaPlugin {
|
||||
return worldBorderApiRegisteredServiceProvider.getProvider();
|
||||
}
|
||||
|
||||
private void enableImmediateRespawn() {
|
||||
log.info("Enabling immediate respawn for {}.", GameConfig.FLAG.world);
|
||||
World world = Bukkit.getWorld(GameConfig.FLAG.world);
|
||||
if (world != null) {
|
||||
world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true);
|
||||
} else {
|
||||
log.error("No valid flag world defined, unable to modify game rules");
|
||||
}
|
||||
}
|
||||
|
||||
private void registerEvents(Flag flag, WorldBorderApi worldBorderApi) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new SnowballEvent(gameManager), this);
|
||||
pluginManager.registerEvents(new OtherGameEvents(gameManager), this);
|
||||
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
|
||||
pluginManager.registerEvents(new OnPlayerDeath(gameManager, worldBorderApi, this, flag), this);
|
||||
pluginManager.registerEvents(new InventoryItemInteractionEvent(), this);
|
||||
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag), this);
|
||||
pluginManager.registerEvents(new OnPlayerOnlineStatus(gameManager, flag, worldBorderApi), this);
|
||||
pluginManager.registerEvents(new GUIListener(), this);
|
||||
}
|
||||
|
||||
@@ -119,4 +127,39 @@ public class Main extends JavaPlugin {
|
||||
teams.forEach(gameManager::registerTeam);
|
||||
}
|
||||
|
||||
}
|
||||
private void loadPlayerStats() {
|
||||
JsonConfigManager<PlayerStat> config = new JsonConfigManager<>(JacksonConfig.configureMapper());
|
||||
List<PlayerStat> playerStats;
|
||||
|
||||
try {
|
||||
File playerStatsDirectory = new File(getDataFolder(), "player_stats");
|
||||
if (!playerStatsDirectory.exists() && !playerStatsDirectory.mkdirs()) {
|
||||
log.error("Unable to make playerStats directory at {} shutting down plugin", playerStatsDirectory.getAbsolutePath());
|
||||
}
|
||||
playerStats = config.loadConfigs(PlayerStat.class, playerStatsDirectory);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to load teams, shutting down plugin", e);
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
gameManager.setPlayerStats(playerStats);
|
||||
final JsonConfigManager<PlayerStat> jsonConfigManager = new JsonConfigManager<>(JacksonConfig.configureMapper());
|
||||
Runnable runnable = () -> {
|
||||
log.info("Saving player stats");
|
||||
File playerStatsDirectory = new File(getDataFolder(), "player_stats");
|
||||
gameManager.getPlayerStats().stream().filter(PlayerStat::isTouched).forEach(playerStat -> {
|
||||
try {
|
||||
jsonConfigManager.saveConfig(playerStat, playerStatsDirectory, playerStat.getUuid().toString());
|
||||
playerStat.setUnTouched();
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to save player stats for [{}].", playerStat.getUuid(), e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, java.util.concurrent.TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
new SkipPhase(gameManager),
|
||||
new Start(gameManager, flag),
|
||||
new CreateTeam(main, gameManager),
|
||||
new HighestStat(gameManager),
|
||||
new SelectClass(gameManager, worldBorderApi),
|
||||
new Reload(main)
|
||||
);
|
||||
@@ -120,4 +121,4 @@ public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.alttd.ctf.commands.subcommands;
|
||||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class HighestStat extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
public HighestStat(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
private record HighestStatValue(Stat stat, UUID uuid, double value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
ArrayList<HighestStatValue> highestStatValues = new ArrayList<>();
|
||||
Collection<PlayerStat> playerStats = gameManager.getPlayerStats();
|
||||
for (Stat stat : Stat.values()) {
|
||||
Optional<PlayerStat> max = playerStats.stream().max(Comparator.comparingDouble(playerStat -> playerStat.getStat(stat)));
|
||||
if (max.isEmpty()) {
|
||||
log.warn("No max stat found for {}", stat.toString());
|
||||
continue;
|
||||
}
|
||||
PlayerStat playerStat = max.get();
|
||||
highestStatValues.add(new HighestStatValue(stat, playerStat.getUuid(), playerStat.getStat(stat)));
|
||||
}
|
||||
Component message = highestStatValues.stream().map(highestStatValue -> {
|
||||
TextComponent messageBuilder = Component.empty();
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(highestStatValue.uuid);
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (!offlinePlayer.isOnline() || player == null) {
|
||||
messageBuilder = messageBuilder.append(miniMessage.deserialize(offlinePlayer.getName() == null ? highestStatValue.uuid.toString() : offlinePlayer.getName()));
|
||||
} else {
|
||||
messageBuilder = messageBuilder.append(player.name());
|
||||
}
|
||||
messageBuilder = messageBuilder.append(Component.text(": "));
|
||||
messageBuilder = messageBuilder.append(Component.text(highestStatValue.stat.toString()));
|
||||
messageBuilder = messageBuilder.append(Component.text(": "));
|
||||
messageBuilder = messageBuilder.append(Component.text(highestStatValue.value()));
|
||||
return messageBuilder;
|
||||
}).reduce(Component.empty(), (a, b) -> a.append(Component.newline()).append(b));
|
||||
commandSender.sendMessage(message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "higheststat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.HIGHEST_STAT;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,9 @@ public class SelectClass extends SubCommand {
|
||||
return 0;
|
||||
}
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
if (!gamePhase.equals(GamePhase.CLASS_SELECTION) && teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 5) {
|
||||
if (!teamPlayer.isDead()
|
||||
&& !gamePhase.equals(GamePhase.CLASS_SELECTION)
|
||||
&& teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 10) {
|
||||
commandSender.sendRichMessage("<red>You have to be near your spawn to change classes.</red>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,10 @@ package com.alttd.ctf.commands.subcommands;
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class SkipPhase extends SubCommand {
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -91,7 +90,7 @@ public class GameConfig extends AbstractConfig {
|
||||
public static double CAPTURE_RADIUS = 5;
|
||||
public static int CAPTURE_SCORE = 50;
|
||||
public static double TURN_IN_RADIUS = 3;
|
||||
public static @NotNull Material MATERIAL = Material.RED_BANNER;
|
||||
public static @NotNull Material MATERIAL = Material.BLACK_BANNER;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
|
||||
@@ -28,6 +28,7 @@ public class Messages extends AbstractConfig {
|
||||
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
|
||||
public static String SELECT_CLASS = "<green>Open class selection: <gold>/ctf selectclass</gold></green>";
|
||||
public static String SKIP_PHASE = "<green>Skip the current phase: <gold>/ctf skipphase</gold></green>";
|
||||
public static String HIGHEST_STAT = "<green>Display the highest stat and the player: <gold>/ctf higheststat</gold></green>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
@@ -39,6 +40,7 @@ public class Messages extends AbstractConfig {
|
||||
START = config.getString(prefix, "start", START);
|
||||
SELECT_CLASS = config.getString(prefix, "select-class", SELECT_CLASS);
|
||||
SKIP_PHASE = config.getString(prefix, "skip-phase", SKIP_PHASE);
|
||||
HIGHEST_STAT = config.getString(prefix, "highest-stat", HIGHEST_STAT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.damage.DamageEffect;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -44,7 +46,18 @@ public class OnPlayerDeath implements Listener {
|
||||
Player player = event.getPlayer();
|
||||
player.getInventory().clear();
|
||||
player.updateInventory();
|
||||
gameManager.getTeamPlayer(player)
|
||||
.ifPresent(TeamPlayer::setDead);
|
||||
flag.handleCarrierDeathOrDisconnect(player);
|
||||
|
||||
try {
|
||||
if (event.getDamageSource().getDamageType().getDamageEffect().equals(DamageEffect.FREEZING)) {
|
||||
gameManager.getTeamPlayer(player)
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.DEATHS_IN_POWDERED_SNOW));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to check for death cause due to exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
||||
@@ -5,8 +5,10 @@ import com.alttd.ctf.database.DiscordUserMapper;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.exceptions.PersistenceException;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
@@ -26,18 +28,24 @@ public class OnPlayerOnlineStatus implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final Flag flag;
|
||||
private final WorldBorderApi worldBorderApi;
|
||||
|
||||
public OnPlayerOnlineStatus(GameManager gameManager, Flag flag) {
|
||||
public OnPlayerOnlineStatus(GameManager gameManager, Flag flag, WorldBorderApi worldBorderApi) {
|
||||
this.gameManager = gameManager;
|
||||
this.flag = flag;
|
||||
this.worldBorderApi = worldBorderApi;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
resetPlayer(player);
|
||||
handleRunningGame(player);
|
||||
if (!player.hasPermission("ctf.bypass")) {
|
||||
handleRunningGame(player);
|
||||
}
|
||||
handleDiscordLink(player);
|
||||
gameManager.getTeamPlayer(player).ifPresent(teamPlayer ->
|
||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation()));
|
||||
}
|
||||
|
||||
private void handleRunningGame(Player player) {
|
||||
@@ -57,11 +65,13 @@ public class OnPlayerOnlineStatus implements Listener {
|
||||
log.error("No team found when attempting to add freshly joined player to a team");
|
||||
return;
|
||||
}
|
||||
teamPlayer = min.get().addPlayer(player);
|
||||
teamPlayer = gameManager.registerPlayer(min.get(), player);
|
||||
} else {
|
||||
teamPlayer = optionalTeamPlayer.get();
|
||||
teamPlayer.getTeam().addToScoreboard(player);
|
||||
}
|
||||
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
|
||||
FighterCreator.createFighter(teamPlayer.getTeam().getColor())
|
||||
.apply(teamPlayer, worldBorderApi, gamePhase, true);
|
||||
}
|
||||
|
||||
private void resetPlayer(Player player) {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.ThrownPotion;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.PotionSplashEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class OtherGameEvents implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public OtherGameEvents(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||
if (gamePhase.isEmpty() || gamePhase.get() == GamePhase.ENDED) {
|
||||
return;
|
||||
}
|
||||
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
gameManager.getTeamPlayer(event.getPlayer())
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.SNOW_MINED));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
Optional<GamePhase> gamePhase = gameManager.getGamePhase();
|
||||
if (gamePhase.isEmpty() || gamePhase.get() == GamePhase.ENDED) {
|
||||
return;
|
||||
}
|
||||
if (!Tag.SNOW.isTagged(event.getBlock().getType())) {
|
||||
event.setCancelled(true);
|
||||
log.warn("Player {} placed a block that wasn't snow: {}",
|
||||
event.getPlayer().getName(), event.getBlock().getType());
|
||||
return;
|
||||
}
|
||||
gameManager.getTeamPlayer(event.getPlayer())
|
||||
.ifPresent(teamPlayer -> teamPlayer.increaseStat(Stat.BLOCKS_PLACED));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPotionSplash(PotionSplashEvent event) {
|
||||
ThrownPotion thrownPotion = event.getPotion();
|
||||
if (!(thrownPotion.getShooter() instanceof Player player))
|
||||
return;
|
||||
|
||||
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player);
|
||||
if (optionalTeamPlayer.isEmpty())
|
||||
return;
|
||||
|
||||
TeamPlayer teamPlayer = optionalTeamPlayer.get();
|
||||
|
||||
event.getAffectedEntities().stream()
|
||||
.filter(livingEntity -> livingEntity instanceof Player)
|
||||
.map(livingEntity -> (Player) livingEntity)
|
||||
.forEach(target -> {
|
||||
if (shouldHeal(teamPlayer, target)) {
|
||||
return;
|
||||
}
|
||||
event.setIntensity(target, 0);
|
||||
});
|
||||
|
||||
double totalHealing = thrownPotion.getEffects().stream()
|
||||
.filter(effect -> effect.getType() == PotionEffectType.INSTANT_HEALTH)
|
||||
.flatMapToDouble(effect -> event.getAffectedEntities().stream()
|
||||
.filter(livingEntity -> livingEntity instanceof Player)
|
||||
.map(livingEntity -> (Player) livingEntity)
|
||||
.mapToDouble(target -> calculateActualHealing(target, effect, event.getIntensity(target))))
|
||||
.sum();
|
||||
|
||||
teamPlayer.increaseStat(Stat.DAMAGE_HEALED, totalHealing);
|
||||
}
|
||||
|
||||
private boolean shouldHeal(TeamPlayer healer, Player target) {
|
||||
Optional<TeamPlayer> optionalTeamTarget = gameManager.getTeamPlayer(target);
|
||||
return optionalTeamTarget.isPresent() && healer.getTeam() == optionalTeamTarget.get().getTeam();
|
||||
}
|
||||
|
||||
private double calculateActualHealing(Player target, PotionEffect effect, double intensity) {
|
||||
AttributeInstance playerMaxHealth = target.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (playerMaxHealth == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double missingHealth = playerMaxHealth.getValue() - target.getHealth();
|
||||
//Only counts healing on teammates since intensity was set to 0 for non teammates
|
||||
double potentialHealing = (effect.getAmplifier() + 1) * intensity;
|
||||
return Math.min(potentialHealing, missingHealth);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,10 +3,14 @@ package com.alttd.ctf.events;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Mage;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -16,7 +20,11 @@ import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class SnowballEvent implements Listener {
|
||||
@@ -34,37 +42,81 @@ public class SnowballEvent implements Listener {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SnowballThrownConsumer {
|
||||
void apply(Player shooter, TeamPlayer shooterTeamPlayer);
|
||||
void apply(Player shooter, TeamPlayer shooterTeamPlayer, Snowball snowball);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
||||
handleSnowballHit(event, (hitPlayer, shooter, shooterTeamPlayer, snowball) -> {
|
||||
if (blockedAttack(hitPlayer, snowball)) { //Disable damage when it is blocked
|
||||
if (blockedAttack(hitPlayer, shooter, snowball)) {
|
||||
playBlockSounds(hitPlayer, shooter);
|
||||
return;
|
||||
}
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
|
||||
double newHealth = hitPlayer.getHealth() - shooterClass.getDamage();
|
||||
hitPlayer.setHealth(Math.max(newHealth, 0));
|
||||
double newHealth = Math.max(hitPlayer.getHealth() - shooterClass.getDamage(), 0);
|
||||
hitPlayer.setHealth(newHealth);
|
||||
|
||||
shooterTeamPlayer.increaseStat(Stat.DAMAGE_DONE, shooterClass.getDamage());
|
||||
if (newHealth <= 0) {
|
||||
shooterTeamPlayer.increaseStat(Stat.KILLS);
|
||||
}
|
||||
log.debug("{} health was set to {} because of a snowball thrown by {}",
|
||||
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
|
||||
applyDamageEffects(hitPlayer, shooter);
|
||||
});
|
||||
}
|
||||
|
||||
private void playBlockSounds(Player hitPlayer, Player shooter) {
|
||||
hitPlayer.playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
|
||||
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
private void applyDamageEffects(Player hitPlayer, Player shooter) {
|
||||
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 1.0f);
|
||||
|
||||
hitPlayer.getWorld().spawnParticle(
|
||||
Particle.DAMAGE_INDICATOR,
|
||||
hitPlayer.getLocation().add(0, 1, 0), 10, 0.5, 0.5, 0.5, 0.1
|
||||
);
|
||||
|
||||
shooter.playSound(shooter.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballThrown(ProjectileLaunchEvent event) {
|
||||
handleSnowballThrown(event, (shooter, shooterTeamPlayer) -> {
|
||||
handleSnowballThrown(event, (shooter, shooterTeamPlayer, snowball) -> {
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
if (shooterClass == null) {
|
||||
shooter.sendRichMessage("<red>You appear to not have selected a class, please do so by going to spawn and using /ctf selectclass</red>");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
shooterTeamPlayer.increaseStat(Stat.SNOWBALLS_THROWN);
|
||||
if (shooterClass instanceof Mage mage) {
|
||||
mage.duplicateSnowBalls(shooter, snowball);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Snowball snowball) {
|
||||
private final HashMap<UUID, LinkedList<Instant>> lastTankHits = new HashMap<>();
|
||||
|
||||
private boolean blockedAttack(@NotNull Player hitPlayer, @NotNull Player shooter, @NotNull Snowball snowball) {
|
||||
if (!hitPlayer.isBlocking()) {
|
||||
return false;
|
||||
}
|
||||
if (reachedMaxHits(hitPlayer)) {
|
||||
hitPlayer.setCooldown(Material.SHIELD, 40);
|
||||
if (hitPlayer.getInventory().getItemInMainHand().getType().equals(Material.SHIELD)) {
|
||||
hitPlayer.swingMainHand();
|
||||
} else if (hitPlayer.getInventory().getItemInOffHand().getType().equals(Material.SHIELD)) {
|
||||
hitPlayer.swingOffHand();
|
||||
}
|
||||
applyBlockBrokenEffects(hitPlayer, shooter);
|
||||
return false;
|
||||
}
|
||||
Location playerLocation = hitPlayer.getLocation();
|
||||
Vector playerFacing = playerLocation.getDirection().normalize();
|
||||
Location snowballLocation = snowball.getLocation();
|
||||
@@ -75,6 +127,37 @@ public class SnowballEvent implements Listener {
|
||||
return !(Math.toDegrees(angle) > 80); //Blocked if the angle was <= 80
|
||||
}
|
||||
|
||||
private void applyBlockBrokenEffects(@NotNull Player hitPlayer, @NotNull Player shooter) {
|
||||
hitPlayer.getWorld().playSound(hitPlayer.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.0f);
|
||||
|
||||
Location shieldLocation = hitPlayer.getLocation()
|
||||
.add(hitPlayer.getLocation().getDirection().normalize().multiply(0.5))
|
||||
.add(0, 0.5, 0);
|
||||
hitPlayer.getWorld().spawnParticle(
|
||||
Particle.CRIT,
|
||||
shieldLocation, 10, 0.5, 0.5, 0.5, 0.1
|
||||
);
|
||||
|
||||
shooter.playSound(shooter.getLocation(), Sound.ITEM_SHIELD_BREAK, 1.0f, 1.5f);
|
||||
}
|
||||
|
||||
private boolean reachedMaxHits(@NotNull Player hitPlayer) {
|
||||
boolean reachedMaxHits = false;
|
||||
Instant now = Instant.now();
|
||||
LinkedList<Instant> lastHits = lastTankHits.getOrDefault(hitPlayer.getUniqueId(), new LinkedList<>());
|
||||
Instant cutoffTime = now.minusMillis(300);
|
||||
while (!lastHits.isEmpty() && lastHits.peek().isBefore(cutoffTime)) {
|
||||
lastHits.poll();
|
||||
}
|
||||
lastHits.addLast(now);
|
||||
if (lastHits.size() >= 3) {
|
||||
lastHits.clear();
|
||||
reachedMaxHits = true;
|
||||
}
|
||||
lastTankHits.put(hitPlayer.getUniqueId(), lastHits);
|
||||
return reachedMaxHits;
|
||||
}
|
||||
|
||||
private void handleSnowballThrown(ProjectileLaunchEvent event, SnowballThrownConsumer consumer) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
@@ -94,7 +177,7 @@ public class SnowballEvent implements Listener {
|
||||
log.debug("The shooter that threw a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
consumer.apply(shooter, teamPlayer.get());
|
||||
consumer.apply(shooter, teamPlayer.get(), snowball);
|
||||
}
|
||||
|
||||
private void handleSnowballHit(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
||||
@@ -139,10 +222,10 @@ public class SnowballEvent implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (teamPlayerHit.get().getTeam().getId() == teamPlayerShooter.get().getTeam().getId()) {
|
||||
if (teamPlayerHit.get().getTeam().getId().equals(teamPlayerShooter.get().getTeam().getId())) {
|
||||
log.debug("The shooter hit a member of their own team");
|
||||
return;
|
||||
}
|
||||
consumer.apply(hitPlayer, shooter, teamPlayerHit.get(), snowball);
|
||||
consumer.apply(hitPlayer, shooter, teamPlayerShooter.get(), snowball);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.alttd.ctf.flag;
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
@@ -23,6 +24,7 @@ import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -79,12 +81,27 @@ public class Flag implements Runnable {
|
||||
if (flagCarrier != null) {
|
||||
return;
|
||||
}
|
||||
//TODO knockback enemies from flag location to create space for person who captured mayb short speed boost and heal?
|
||||
//TODO add de-buffs and enable buffs for others?
|
||||
Team team = teamPlayer.getTeam();
|
||||
flagLocation.getNearbyPlayers(5).forEach(nearbyPlayer -> {
|
||||
if (nearbyPlayer.getUniqueId().equals(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
Optional<TeamPlayer> nearByTeamPlayer = gameManager.getTeamPlayer(nearbyPlayer);
|
||||
if (nearByTeamPlayer.isEmpty()) {
|
||||
return;
|
||||
} else if (nearByTeamPlayer.get().getTeam().equals(team)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector direction = nearbyPlayer.getLocation().toVector().subtract(flagLocation.toVector()).normalize();
|
||||
direction.setY(0.2);
|
||||
nearbyPlayer.setVelocity(direction.multiply(5));
|
||||
});
|
||||
player.getInventory().setItem(EquipmentSlot.HEAD, new ItemStack(teamPlayer.getTeam().getFlagMaterial()));
|
||||
Bukkit.getScheduler().runTask(main, () -> flagLocation.getBlock().setType(Material.AIR));
|
||||
flagCarrier = player;
|
||||
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, PotionEffect.INFINITE_DURATION, 0, false, false));
|
||||
player.addPotionEffect(new PotionEffect(PotionEffectType.GLOWING, PotionEffect.INFINITE_DURATION, 0, false, false));
|
||||
notifyAboutCapture();
|
||||
resetFlag();
|
||||
}
|
||||
@@ -165,7 +182,8 @@ public class Flag implements Runnable {
|
||||
Placeholder.component("player", flagCarrier.displayName())));
|
||||
Bukkit.getOnlinePlayers().forEach(player ->
|
||||
gameManager.getTeam(player).ifPresent(team ->
|
||||
player.showTitle(team.getId() == winningTeam.getId() ? capturingTeamTitle : huntingTeamTitle)));
|
||||
player.showTitle(team.getId().intValue() == winningTeam.getId().intValue()
|
||||
? capturingTeamTitle : huntingTeamTitle)));
|
||||
}
|
||||
|
||||
private void spawnParticlesOnSquareBorder(Location center, double size) {
|
||||
@@ -185,7 +203,7 @@ public class Flag implements Runnable {
|
||||
});
|
||||
}
|
||||
|
||||
LinkedList<Location> particleTrail = new LinkedList<>();
|
||||
private final LinkedList<Location> particleTrail = new LinkedList<>();
|
||||
|
||||
private void spawnTrail() {
|
||||
TeamColor color = winningTeam.getColor();
|
||||
@@ -221,7 +239,10 @@ public class Flag implements Runnable {
|
||||
|
||||
flagCarrier.getInventory().setItem(EquipmentSlot.HEAD, null);
|
||||
gameManager.getTeamPlayer(flagCarrier)
|
||||
.ifPresent(teamPlayer -> teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer));
|
||||
.ifPresent(teamPlayer -> {
|
||||
teamPlayer.getGameClass().setArmor(flagCarrier, teamPlayer);
|
||||
teamPlayer.increaseStat(Stat.FLAGS_CAPTURED);
|
||||
});
|
||||
|
||||
resetFlagCarrier();
|
||||
}
|
||||
@@ -311,13 +332,15 @@ public class Flag implements Runnable {
|
||||
}
|
||||
Team winningTeam = teamLongEntry.getKey();
|
||||
|
||||
teamCounts.forEach((team, count) -> {
|
||||
teamFlagPointCount.merge(team.getId(), team.equals(winningTeam) ? 1 : -1, (oldValue, delta) -> {
|
||||
int updatedValue = oldValue + delta;
|
||||
log.debug("Set count to {} for team {}", updatedValue, team.getId());
|
||||
return Math.max(updatedValue, 0);
|
||||
});
|
||||
teamFlagPointCount.putIfAbsent(winningTeam.getId(), 0);
|
||||
teamFlagPointCount.entrySet().forEach(entry -> {
|
||||
if (entry.getKey().equals(winningTeam.getId())) {
|
||||
entry.setValue(entry.getValue() + 1);
|
||||
} else {
|
||||
entry.setValue(Math.max(0, entry.getValue() - 1));
|
||||
}
|
||||
});
|
||||
nearbyPlayers.forEach(teamPlayer -> teamPlayer.increaseStat(Stat.TIME_SPEND_CAPTURING_FLAG));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -376,6 +399,7 @@ public class Flag implements Runnable {
|
||||
resetFlagCarrier();
|
||||
resetFlag();
|
||||
wins.clear();
|
||||
gameManager.getTeams().forEach(team -> team.setScore(0));
|
||||
}
|
||||
|
||||
public void handleCarrierDeathOrDisconnect(Player player) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||
import com.alttd.ctf.game.phases.CombatPhase;
|
||||
import com.alttd.ctf.game.phases.EndedPhase;
|
||||
import com.alttd.ctf.game.phases.GatheringPhase;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
@@ -24,22 +26,26 @@ public class GameManager {
|
||||
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
||||
private RunningGame runningGame;
|
||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||
private final HashMap<UUID, PlayerStat> playerStats = new HashMap<>();
|
||||
|
||||
public GameManager(WorldBorderApi worldBorderApi) {
|
||||
public GameManager(Main main, WorldBorderApi worldBorderApi) {
|
||||
phases = new HashMap<>();
|
||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, FighterCreator::createFighter, worldBorderApi));
|
||||
phases.put(GamePhase.GATHERING, new GatheringPhase(this, worldBorderApi));
|
||||
phases.put(GamePhase.COMBAT, new CombatPhase());
|
||||
phases.put(GamePhase.ENDED, new EndedPhase());
|
||||
phases.put(GamePhase.ENDED, new EndedPhase(main, this));
|
||||
}
|
||||
|
||||
public Optional<GamePhase> getGamePhase() {
|
||||
return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase());
|
||||
}
|
||||
|
||||
public void registerPlayer(Team team, Player player) {
|
||||
public TeamPlayer registerPlayer(Team team, Player player) {
|
||||
unregisterPlayer(player);
|
||||
teams.get(team.getId()).addPlayer(player);
|
||||
UUID uuid = player.getUniqueId();
|
||||
PlayerStat playerStat = playerStats
|
||||
.computeIfAbsent(uuid, (ignored) -> new PlayerStat(uuid, player.getName()));
|
||||
return teams.get(team.getId()).addPlayer(player, playerStat);
|
||||
}
|
||||
|
||||
public void unregisterPlayer(Player player) {
|
||||
@@ -107,4 +113,20 @@ public class GameManager {
|
||||
}
|
||||
return runningGame.skipCurrentPhase();
|
||||
}
|
||||
|
||||
public void setPlayerStats(List<PlayerStat> playerStats) {
|
||||
this.playerStats.clear();
|
||||
playerStats.forEach(playerStat -> this.playerStats.put(playerStat.getUuid(), playerStat));
|
||||
}
|
||||
|
||||
public Collection<PlayerStat> getPlayerStats() {
|
||||
return this.playerStats.values();
|
||||
}
|
||||
|
||||
public Optional<Duration> getRemainingTime() {
|
||||
if (runningGame == null || runningGame.getCurrentPhase().equals(GamePhase.ENDED)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(runningGame.getRemainingTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.team.TeamScoreboard;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@@ -51,6 +52,7 @@ public class RunningGame implements Runnable {
|
||||
} else {
|
||||
executorService.shutdown();
|
||||
}
|
||||
TeamScoreboard.refreshScoreboard(gameManager);
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error in running game", e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -73,7 +75,6 @@ public class RunningGame implements Runnable {
|
||||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {
|
||||
//Remaining time for this phase
|
||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
log.debug(duration.toString());//TODO remove debug
|
||||
if ((duration.toMinutes() > 1 && (duration.toMinutes() % 15 == 0 || duration.toMinutes() <= 5)) && duration.toSecondsPart() < 2) {
|
||||
if (lastMinuteBroadcast == duration.toMinutes()) {
|
||||
return;
|
||||
@@ -115,4 +116,9 @@ public class RunningGame implements Runnable {
|
||||
//TODO say the phase ended early?
|
||||
nextPhaseActions(currentPhase, GamePhase.ENDED);
|
||||
}
|
||||
|
||||
public Duration getRemainingTime() {
|
||||
Duration duration = phaseDurations.get(currentPhase);
|
||||
return duration.minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@@ -62,7 +61,7 @@ public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
.filter(player -> gameManager.getTeamPlayer(player).isEmpty())
|
||||
.forEach(player -> {
|
||||
Team team = teamCircularIterator.next();
|
||||
team.addPlayer(player);
|
||||
gameManager.registerPlayer(team, player);
|
||||
player.sendRichMessage("You joined <team>!", Placeholder.component("team", team.getName()));
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,8 @@ public class CombatPhase implements GamePhaseExecutor {
|
||||
|
||||
@Override
|
||||
public void end(GamePhase ignored) {
|
||||
executorService.shutdown();
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.team.Team;
|
||||
@@ -12,9 +14,11 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -25,6 +29,13 @@ import java.util.Map;
|
||||
public class EndedPhase implements GamePhaseExecutor {
|
||||
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
private final Main main;
|
||||
private final GameManager gameManager;
|
||||
|
||||
public EndedPhase(Main main, GameManager gameManager) {
|
||||
this.main = main;
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Flag flag) {
|
||||
@@ -51,8 +62,8 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
player.setHealth(20);
|
||||
});
|
||||
flag.resetAll();
|
||||
gameManager.getTeams().forEach(team -> Bukkit.getOnlinePlayers().forEach(team::removePlayer));
|
||||
}).start();
|
||||
// TODO reset world (coreprotect) to prep for next round
|
||||
}
|
||||
|
||||
private List<Component> getWinnerMessages(HashMap<Team, Integer> wins) {
|
||||
@@ -71,18 +82,29 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
return messages;
|
||||
} else if (topTeams.size() > 1) { // Draw scenario, multiple teams have the same top score
|
||||
messages.add(miniMessage.deserialize("<yellow>It's a draw! Top teams:</yellow>"));
|
||||
topTeams.forEach(team -> {
|
||||
messages.add(miniMessage.deserialize("<team> had <score> captures.",
|
||||
Placeholder.component("team", team.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
});
|
||||
topTeams.forEach(team -> messages.add(
|
||||
miniMessage.deserialize("<team> had <score> captures.",
|
||||
Placeholder.component("team", team.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore)))));
|
||||
addOtherTeamsScore(wins, highestScore, messages);
|
||||
return messages;
|
||||
} else { // Single winner
|
||||
Team winner = topTeams.getFirst();
|
||||
messages.add(miniMessage.deserialize("<green><team> has won with <score> captures!</green>",
|
||||
Placeholder.component("team", winner.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
Placeholder.component("team", winner.getName()),
|
||||
Placeholder.parsed("score", String.valueOf(highestScore))));
|
||||
ConsoleCommandSender consoleSender = Bukkit.getConsoleSender();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
winner.getPlayers().forEach(teamPlayer -> {
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(teamPlayer.getUuid());
|
||||
String name = offlinePlayer.getName();
|
||||
if (name == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.dispatchCommand(consoleSender, String.format("lp user %s permission settemp ctf.victory.%s true 14d", name, winner.getId()));
|
||||
});
|
||||
});
|
||||
|
||||
if (wins.size() <= 1) {
|
||||
return messages;
|
||||
}
|
||||
@@ -97,8 +119,8 @@ public class EndedPhase implements GamePhaseExecutor {
|
||||
wins.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() < winningScore)
|
||||
.forEach(entry -> messages.add(miniMessage.deserialize("<yellow><team> had <score> captures.</yellow>",
|
||||
Placeholder.component("team", entry.getKey().getName()),
|
||||
Placeholder.parsed("score", String.valueOf(entry.getValue())))));
|
||||
Placeholder.component("team", entry.getKey().getName()),
|
||||
Placeholder.parsed("score", String.valueOf(entry.getValue())))));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,11 +4,11 @@ import com.alttd.ctf.flag.Flag;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@Slf4j
|
||||
public class GatheringPhase implements GamePhaseExecutor {
|
||||
@@ -36,10 +36,13 @@ public class GatheringPhase implements GamePhaseExecutor {
|
||||
log.error("Unable to update world border due to missing Flag");
|
||||
return;
|
||||
}
|
||||
gameManager.getTeams().forEach(team -> {
|
||||
team.getPlayers().forEach(player -> {
|
||||
player.resetWorldBorder(Bukkit.getPlayer(player.getUuid()), worldBorderApi, nextPhase, flag.getFlagLocation());
|
||||
});
|
||||
});
|
||||
gameManager.getTeams()
|
||||
.forEach(team -> team.getPlayers().forEach(teamPlayer -> {
|
||||
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
teamPlayer.resetWorldBorder(player, worldBorderApi, nextPhase, flag.getFlagLocation());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.alttd.ctf.game_class;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game_class.creation.EngineerCreator;
|
||||
import com.alttd.ctf.game_class.creation.FighterCreator;
|
||||
import com.alttd.ctf.game_class.creation.TankCreator;
|
||||
import com.alttd.ctf.game_class.creation.*;
|
||||
import com.alttd.ctf.team.Team;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,9 +12,7 @@ public class GameClassRetrieval {
|
||||
|
||||
public static HashMap<Integer, List<GameClass>> getGameClassesForAllTeams(GameManager gameManager) {
|
||||
final HashMap<Integer, List<GameClass>> gameClasses = new HashMap<>();
|
||||
gameManager.getTeams().forEach(team -> {
|
||||
gameClasses.put(team.getId(), getGameClassesForTeam(team));
|
||||
});
|
||||
gameManager.getTeams().forEach(team -> gameClasses.put(team.getId(), getGameClassesForTeam(team)));
|
||||
return gameClasses;
|
||||
}
|
||||
|
||||
@@ -24,7 +20,9 @@ public class GameClassRetrieval {
|
||||
final List<GameClass> gameClasses = new ArrayList<>();
|
||||
gameClasses.add(FighterCreator.createFighter(team.getColor()));
|
||||
gameClasses.add(TankCreator.createTank(team.getColor()));
|
||||
gameClasses.add(TrapperCreator.createTrapper(team.getColor()));
|
||||
gameClasses.add(EngineerCreator.createEngineer(team.getColor()));
|
||||
gameClasses.add(MageCreator.createMage(team.getColor()));
|
||||
return gameClasses;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Engineer;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Mage;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class MageCreator {
|
||||
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createMage(@NotNull TeamColor teamColor) {
|
||||
return new Mage(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
20, 100, 1);
|
||||
}
|
||||
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private static @NotNull @Unmodifiable List<Material> getArmor() {
|
||||
return (List.of(Material.CHAINMAIL_BOOTS, Material.AIR, Material.AIR, Material.LEATHER_HELMET));
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
private static @NotNull @Unmodifiable List<ItemStack> getTools(@NotNull TeamColor teamColor) {
|
||||
return (List.of(getShovel(teamColor)));
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getShovel(@NotNull TeamColor teamColor) {
|
||||
ItemStack shovel = new ItemStack(Material.WOODEN_SHOVEL);
|
||||
ItemMeta meta = shovel.getItemMeta();
|
||||
meta.itemName(miniMessage.deserialize(String.format("<color:%s>Snow shovel</color>", teamColor.hex())));
|
||||
meta.setUnbreakable(true);
|
||||
meta.addEnchant(Enchantment.EFFICIENCY, 1, false);
|
||||
shovel.setItemMeta(meta);
|
||||
return shovel;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||
ItemStack itemStack = new ItemStack(Material.SNOWBALL);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Mage</color>", teamColor.hex())));
|
||||
itemMeta.lore(List.of(
|
||||
miniMessage.deserialize("<gold>The Mage can throw many snowballs at once</gold>"),
|
||||
miniMessage.deserialize("<gold>But it has a long cooldown.</gold>")
|
||||
));
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.game_class.implementations.Tank;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -22,7 +21,7 @@ public class TankCreator {
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createTank(@NotNull TeamColor teamColor) {//TODO add ability to become temp invulnerable (with some particle effects mayb?)
|
||||
public static @NotNull GameClass createTank(@NotNull TeamColor teamColor) {
|
||||
return new Tank(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
30, 7, 4);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.alttd.ctf.game_class.creation;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.implementations.Trapper;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class TrapperCreator {
|
||||
|
||||
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull GameClass createTrapper(@NotNull TeamColor teamColor) {
|
||||
return new Trapper(getArmor(), getTools(teamColor), getDisplayItem(teamColor),
|
||||
20, 5, 4);
|
||||
}
|
||||
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private static @Unmodifiable List<Material> getArmor() {
|
||||
return (List.of(Material.LEATHER_BOOTS, Material.LEATHER_LEGGINGS, Material.LEATHER_CHESTPLATE, Material.AIR));
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
private static @NotNull @Unmodifiable List<ItemStack> getTools(TeamColor teamColor) {
|
||||
List<ItemStack> itemStacks = new ArrayList<>(List.of(getShovel(teamColor)));
|
||||
itemStacks.addAll(getPowderedSnow(teamColor));
|
||||
return itemStacks;
|
||||
}
|
||||
|
||||
private static @NotNull List<ItemStack> getPowderedSnow(@NotNull TeamColor teamColor) {
|
||||
ItemStack powderedSnow = new ItemStack(Material.POWDER_SNOW_BUCKET);
|
||||
ItemMeta itemMeta = powderedSnow.getItemMeta();
|
||||
itemMeta.itemName(MiniMessage.miniMessage().deserialize(
|
||||
String.format("<color:%s>Snow Trap</color>", teamColor.hex())));
|
||||
powderedSnow.setItemMeta(itemMeta);
|
||||
List<ItemStack> snowBuckets = new ArrayList<>();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
snowBuckets.add(powderedSnow.clone());
|
||||
}
|
||||
return snowBuckets;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getShovel(@NotNull TeamColor teamColor) {
|
||||
ItemStack shovel = new ItemStack(Material.WOODEN_SHOVEL);
|
||||
ItemMeta meta = shovel.getItemMeta();
|
||||
meta.setUnbreakable(true);
|
||||
meta.itemName(miniMessage.deserialize(String.format("<color:%s>Snow shovel</color>", teamColor.hex())));
|
||||
shovel.setItemMeta(meta);
|
||||
return shovel;
|
||||
}
|
||||
|
||||
private static @NotNull ItemStack getDisplayItem(@NotNull TeamColor teamColor) {
|
||||
ItemStack itemStack = new ItemStack(Material.POWDER_SNOW_BUCKET);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
itemMeta.displayName(miniMessage.deserialize(String.format("<color:%s>Trapper</color>", teamColor.hex())));
|
||||
itemMeta.lore(List.of(
|
||||
miniMessage.deserialize("<gold>The Trapper is a normal class, but it has a trick up it's sleeve</gold>"),
|
||||
miniMessage.deserialize("<gold>They can place their powdered snow to trap the enemy team.</gold>")
|
||||
));
|
||||
itemStack.setItemMeta(itemMeta);
|
||||
return itemStack;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class Mage extends GameClass {
|
||||
public Mage(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||
double health, int throwTickSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||
}
|
||||
|
||||
public void duplicateSnowBalls(Player shooter, Snowball snowball) {
|
||||
Location location = snowball.getLocation();
|
||||
PlayerInventory inventory = shooter.getInventory();
|
||||
ItemStack itemStack = inventory.getItemInMainHand();
|
||||
if (itemStack.getType() != Material.SNOWBALL) {
|
||||
ItemStack itemInOffHand = inventory.getItemInOffHand();
|
||||
if (itemInOffHand.getType() != Material.SNOWBALL) {
|
||||
log.warn("Unable to find snowballs in main/off hand");
|
||||
return;
|
||||
}
|
||||
itemStack = itemInOffHand;
|
||||
}
|
||||
if (itemStack.getAmount() <= 1) {
|
||||
return;
|
||||
}
|
||||
duplicate(shooter, location, snowball.getVelocity(), itemStack.getAmount() - 1);
|
||||
itemStack.setAmount(0);
|
||||
}
|
||||
|
||||
private void duplicate(@NotNull Player shooter, @NotNull Location location, @NotNull Vector velocity, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
double offsetX = (Math.random() - 0.5) * 2;
|
||||
double offsetY = (Math.random() - 0.5) * 2;
|
||||
double offsetZ = (Math.random() - 0.5) * 2;
|
||||
|
||||
Location newLocation = location.clone().add(offsetX, offsetY, offsetZ);
|
||||
Snowball snowball = location.getWorld().spawn(newLocation, Snowball.class);
|
||||
|
||||
snowball.setShooter(shooter);
|
||||
|
||||
double velocityChangeX = (Math.random() - 0.5) * 0.5;
|
||||
double velocityChangeY = (Math.random() - 0.5) * 0.5;
|
||||
double velocityChangeZ = (Math.random() - 0.5) * 0.5;
|
||||
|
||||
Vector newVelocity = velocity.clone().add(new Vector(velocityChangeX, velocityChangeY, velocityChangeZ));
|
||||
|
||||
snowball.setVelocity(newVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Trapper extends GameClass {
|
||||
public Trapper(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem,
|
||||
double health, int throwTickSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, throwTickSpeed, damage);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package com.alttd.ctf.gui;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.Merchant;
|
||||
import org.bukkit.inventory.MerchantInventory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.alttd.ctf.stats;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@NoArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public final class PlayerStat {
|
||||
@JsonIgnore
|
||||
public static Main main;
|
||||
|
||||
@JsonIgnore
|
||||
private boolean touched;
|
||||
@JsonIgnore
|
||||
private static final CommandSender commandSender = Bukkit.getConsoleSender();
|
||||
@JsonIgnore
|
||||
private static boolean someoneDiedInPowderedSnow = false;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private UUID uuid;
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private String inGameName;
|
||||
|
||||
private boolean completedGame = false;
|
||||
private int flagsCaptured = 0;
|
||||
private int kills = 0;
|
||||
private double damageDone = 0;
|
||||
private double damageHealed = 0;
|
||||
private int snowMined = 0;
|
||||
private int blocksPlaced = 0;
|
||||
private int snowballsThrown = 0;
|
||||
private long timeSpendCapturingFlag = 0;
|
||||
private int deathsInPowderedSnow = 0;
|
||||
|
||||
public void increaseStat(Stat stat) throws IllegalArgumentException {
|
||||
switch (stat) {
|
||||
case COMPLETED_GAME -> {
|
||||
if (!completedGame) {
|
||||
Bukkit.getScheduler().runTask(main, () -> {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.completed true 14d", inGameName));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
completedGame = true;
|
||||
}
|
||||
case FLAGS_CAPTURED -> flagsCaptured++;
|
||||
case KILLS -> kills++;
|
||||
case SNOW_MINED -> snowMined++;
|
||||
case BLOCKS_PLACED -> blocksPlaced++;
|
||||
case SNOWBALLS_THROWN -> snowballsThrown++;
|
||||
case TIME_SPEND_CAPTURING_FLAG -> timeSpendCapturingFlag++;
|
||||
case DEATHS_IN_POWDERED_SNOW -> {
|
||||
if (someoneDiedInPowderedSnow) {
|
||||
deathsInPowderedSnow++;
|
||||
return;
|
||||
}
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||
if (!offlinePlayer.isOnline()) {
|
||||
return;
|
||||
}
|
||||
Player player = offlinePlayer.getPlayer();
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<red><player> will receive a consolation prize for being the first to die in powdered snow.</red>",
|
||||
Placeholder.component("player",player.displayName())));
|
||||
Bukkit.getScheduler().runTaskAsynchronously(main, () -> {
|
||||
Bukkit.dispatchCommand(commandSender, String.format("lp user %s permission settemp ctf.game.first_powdered_snow_death true 14d", inGameName));
|
||||
});
|
||||
|
||||
someoneDiedInPowderedSnow = true;
|
||||
}
|
||||
case DAMAGE_DONE, DAMAGE_HEALED -> throw new IllegalArgumentException(String.format("%s requires a number", stat.name()));
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat, double value) throws IllegalArgumentException {
|
||||
switch (stat) {
|
||||
case DAMAGE_DONE -> damageDone += value;
|
||||
case DAMAGE_HEALED -> damageHealed += value;
|
||||
default -> throw new IllegalArgumentException(String.format("%s cannot be passed with a number", stat.name()));
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
||||
public void setUnTouched() {
|
||||
touched = false;
|
||||
}
|
||||
|
||||
public double getStat(Stat stat) {
|
||||
return switch (stat) {
|
||||
case COMPLETED_GAME -> completedGame ? 1 : 0;
|
||||
case FLAGS_CAPTURED -> flagsCaptured;
|
||||
case KILLS -> kills;
|
||||
case DAMAGE_DONE -> damageDone;
|
||||
case DAMAGE_HEALED -> damageHealed;
|
||||
case SNOW_MINED -> snowMined;
|
||||
case BLOCKS_PLACED -> blocksPlaced;
|
||||
case SNOWBALLS_THROWN -> snowballsThrown;
|
||||
case TIME_SPEND_CAPTURING_FLAG -> timeSpendCapturingFlag;
|
||||
case DEATHS_IN_POWDERED_SNOW -> deathsInPowderedSnow;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.alttd.ctf.stats;
|
||||
|
||||
public enum Stat {
|
||||
COMPLETED_GAME,
|
||||
FLAGS_CAPTURED,
|
||||
KILLS,
|
||||
DAMAGE_DONE,
|
||||
DAMAGE_HEALED,
|
||||
SNOW_MINED,
|
||||
BLOCKS_PLACED,
|
||||
SNOWBALLS_THROWN,
|
||||
TIME_SPEND_CAPTURING_FLAG,
|
||||
DEATHS_IN_POWDERED_SNOW
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.galaxy.discord.Bot;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -56,9 +55,7 @@ public class DiscordTeam {
|
||||
}
|
||||
Member nullableMember = guild.getMemberById(player.getDiscordID());
|
||||
if (nullableMember == null) {
|
||||
guild.retrieveMemberById(player.getDiscordID()).queue(member -> {
|
||||
consumer.apply(role, member);
|
||||
});
|
||||
guild.retrieveMemberById(player.getDiscordID()).queue(member -> consumer.apply(role, member));
|
||||
} else {
|
||||
consumer.apply(role, nullableMember);
|
||||
}
|
||||
@@ -70,7 +67,7 @@ public class DiscordTeam {
|
||||
log.info("Unable to remove team role from server owner");
|
||||
return;
|
||||
}
|
||||
member.getGuild().removeRoleFromMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
|
||||
member.getGuild().removeRoleFromMember(member, role).queue();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,24 +80,8 @@ public class DiscordTeam {
|
||||
gameManager.getTeams().forEach(otherTeam ->
|
||||
member.getRoles().stream()
|
||||
.filter(otherRole -> otherRole.getIdLong() == otherTeam.getDiscordRole())
|
||||
.forEach(otherRole -> {
|
||||
member.getGuild().removeRoleFromMember(member, otherRole).queue();
|
||||
}));
|
||||
member.getGuild().addRoleToMember(member, role).queue(ignored -> kickFromVoiceIfNeeded(member));
|
||||
.forEach(otherRole -> member.getGuild().removeRoleFromMember(member, otherRole).queue()));
|
||||
member.getGuild().addRoleToMember(member, role).queue();
|
||||
});
|
||||
}
|
||||
|
||||
private void kickFromVoiceIfNeeded(@NotNull Member member) {
|
||||
GuildVoiceState voiceState = member.getVoiceState();
|
||||
if (voiceState == null || !voiceState.inAudioChannel()) {
|
||||
return;
|
||||
}
|
||||
AudioChannel channel = voiceState.getChannel();
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
if (!member.hasPermission(channel, Permission.VOICE_CONNECT)) {
|
||||
member.getGuild().kickVoiceMember(member).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
@@ -8,15 +9,9 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
@@ -27,7 +22,7 @@ import java.util.*;
|
||||
public class Team {
|
||||
|
||||
@JsonIgnore
|
||||
private final static Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
private final TeamScoreboard scoreboard = new TeamScoreboard(this);
|
||||
@JsonIgnore
|
||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||
@JsonProperty("name")
|
||||
@@ -73,10 +68,10 @@ public class Team {
|
||||
discordTeam = new DiscordTeam(gameManager);
|
||||
}
|
||||
|
||||
public TeamPlayer addPlayer(Player player) {
|
||||
removeFromScoreBoard(player);
|
||||
public TeamPlayer addPlayer(Player player, PlayerStat playerStat) {
|
||||
removeFromScoreboard(player);
|
||||
UUID uuid = player.getUniqueId();
|
||||
TeamPlayer teamPlayer = new TeamPlayer(uuid, this);
|
||||
TeamPlayer teamPlayer = new TeamPlayer(player, this, playerStat);
|
||||
players.put(uuid, teamPlayer);
|
||||
addToScoreboard(player);
|
||||
if (discordTeam != null) {
|
||||
@@ -100,7 +95,7 @@ public class Team {
|
||||
}
|
||||
|
||||
public void removePlayer(@NotNull Player player) {
|
||||
removeFromScoreBoard(player);
|
||||
removeFromScoreboard(player);
|
||||
TeamPlayer remove = players.remove(player.getUniqueId());
|
||||
if (remove == null) {
|
||||
return;
|
||||
@@ -113,39 +108,16 @@ public class Team {
|
||||
log.debug("Removed player {} from team with id {}", player.getName(), id);
|
||||
}
|
||||
|
||||
private void addToScoreboard(Player player) {
|
||||
org.bukkit.scoreboard.Team team = scoreboard.getTeam("ctf_" + id);
|
||||
if (team == null) {
|
||||
team = scoreboard.registerNewTeam("ctf_" + id);
|
||||
team.displayName(name);
|
||||
NamedTextColor namedTextColor = NamedTextColor.nearestTo(TextColor.color(color.r(), color.g(), color.b()));
|
||||
team.color(namedTextColor);
|
||||
}
|
||||
team.addPlayer(player);
|
||||
player.setScoreboard(scoreboard);
|
||||
public void addToScoreboard(Player player) {
|
||||
scoreboard.addToScoreboard(player);
|
||||
}
|
||||
|
||||
private void removeFromScoreBoard(Player player) {
|
||||
scoreboard.getTeams().stream()
|
||||
.filter(team -> team.getName().startsWith("ctf_"))
|
||||
.filter(team -> team.hasPlayer(player))
|
||||
.forEach(team -> team.removePlayer(player));
|
||||
private void removeFromScoreboard(Player player) {
|
||||
scoreboard.removeFromScoreboard(player);
|
||||
}
|
||||
|
||||
public void setScore(int newScore) {
|
||||
Objective objective = getOrCreateObjective();
|
||||
Score score = objective.getScore(legacyTeamColor + PlainTextComponentSerializer.plainText().serialize(name));
|
||||
score.setScore(newScore);
|
||||
}
|
||||
|
||||
private Objective getOrCreateObjective() {
|
||||
Objective objective = scoreboard.getObjective("teamScores");
|
||||
if (objective == null) {
|
||||
objective = scoreboard.registerNewObjective("teamScores", Criteria.DUMMY,
|
||||
MiniMessage.miniMessage().deserialize("<gold>CTF score</gold>"));
|
||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
}
|
||||
return objective;
|
||||
scoreboard.setScore(newScore);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.game_class.GameClassRetrieval;
|
||||
import com.alttd.ctf.gui.ClassSelectionGUI;
|
||||
import com.alttd.ctf.stats.PlayerStat;
|
||||
import com.alttd.ctf.stats.Stat;
|
||||
import com.github.yannicklamprecht.worldborder.api.WorldBorderApi;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -21,14 +23,17 @@ import java.util.*;
|
||||
@Getter
|
||||
public class TeamPlayer {
|
||||
|
||||
private final PlayerStat playerStat;
|
||||
private final UUID uuid;
|
||||
private final Team team;
|
||||
@Setter
|
||||
private GameClass gameClass;
|
||||
private boolean isDead = false;
|
||||
|
||||
protected TeamPlayer(UUID uuid, Team team) {
|
||||
this.uuid = uuid;
|
||||
protected TeamPlayer(Player player, Team team, PlayerStat playerStat) {
|
||||
this.uuid = player.getUniqueId();
|
||||
this.team = team;
|
||||
this.playerStat = playerStat;
|
||||
}
|
||||
|
||||
public void respawn(@NotNull Player player, @NotNull WorldBorderApi worldBorderApi, @NotNull GamePhase gamePhase) {
|
||||
@@ -48,9 +53,17 @@ public class TeamPlayer {
|
||||
}
|
||||
player.teleportAsync(spawnLocation).thenAcceptAsync(unused ->
|
||||
resetWorldBorder(player, worldBorderApi, gamePhase, worldBorderCenter));
|
||||
isDead = false;
|
||||
}
|
||||
|
||||
public void setDead() {
|
||||
isDead = true;
|
||||
}
|
||||
|
||||
public void resetWorldBorder(Player player, WorldBorderApi worldBorderApi, GamePhase gamePhase, Location worldBorderCenter) {
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
WorldBorderSettings worldBorderSettings = GameConfig.WORLD_BORDER.getGAME_PHASE_WORLD_BORDER().get(gamePhase);
|
||||
if (worldBorderSettings == null) {
|
||||
throw new IllegalStateException("All phases need to have world border settings");
|
||||
@@ -78,4 +91,12 @@ public class TeamPlayer {
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat) {
|
||||
playerStat.increaseStat(stat);
|
||||
}
|
||||
|
||||
public void increaseStat(Stat stat, double amount) {
|
||||
playerStat.increaseStat(stat, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import net.kyori.adventure.text.format.*;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.*;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class TeamScoreboard {
|
||||
|
||||
private final static Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
private final Team team;
|
||||
|
||||
public TeamScoreboard(Team team) {
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
public void addToScoreboard(Player player) {
|
||||
org.bukkit.scoreboard.Team scoreboardTeam = scoreboard.getTeam("ctf_" + team.getId());
|
||||
if (scoreboardTeam == null) {
|
||||
scoreboardTeam = scoreboard.registerNewTeam("ctf_" + team.getId());
|
||||
scoreboardTeam.displayName(team.getName());
|
||||
TeamColor color = team.getColor();
|
||||
NamedTextColor namedTextColor = NamedTextColor.nearestTo(TextColor.color(color.r(), color.g(), color.b()));
|
||||
scoreboardTeam.color(namedTextColor);
|
||||
}
|
||||
scoreboardTeam.addPlayer(player);
|
||||
player.setScoreboard(scoreboard);
|
||||
}
|
||||
|
||||
protected void removeFromScoreboard(Player player) {
|
||||
scoreboard.getTeams().stream()
|
||||
.filter(team -> team.getName().startsWith("ctf_"))
|
||||
.filter(team -> team.hasPlayer(player))
|
||||
.forEach(team -> team.removePlayer(player));
|
||||
}
|
||||
|
||||
protected void setScore(int newScore) {
|
||||
Objective objective = getOrCreateObjective();
|
||||
Score score = objective.getScore(team.getLegacyTeamColor() +
|
||||
PlainTextComponentSerializer.plainText().serialize(team.getName()));
|
||||
score.setScore(newScore);
|
||||
}
|
||||
|
||||
private static Objective getOrCreateObjective() {
|
||||
Objective objective = scoreboard.getObjective("teamScores");
|
||||
if (objective == null) {
|
||||
objective = scoreboard.registerNewObjective("teamScores", Criteria.DUMMY,
|
||||
MiniMessage.miniMessage().deserialize("<gold>CTF score</gold>"));
|
||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
}
|
||||
return objective;
|
||||
}
|
||||
|
||||
private record PhaseScore(GamePhase gamePhase, Score score) {}
|
||||
private static PhaseScore phaseScore = null;
|
||||
private static void updateTime(GamePhase gamePhase, Duration duration) {
|
||||
if (phaseScore == null) {
|
||||
phaseScore = new PhaseScore(gamePhase, getOrCreateObjective()
|
||||
.getScore(ChatColor.GREEN + PlainTextComponentSerializer.plainText().serialize(gamePhase.getDisplayName())));
|
||||
} else if (phaseScore.gamePhase() != gamePhase) {
|
||||
phaseScore.score.resetScore();
|
||||
phaseScore = new PhaseScore(gamePhase, getOrCreateObjective()
|
||||
.getScore(ChatColor.GREEN + PlainTextComponentSerializer.plainText().serialize(gamePhase.getDisplayName())));
|
||||
}
|
||||
phaseScore.score.setScore(duration.toMinutesPart() == 0 ? duration.toSecondsPart() : duration.toMinutesPart());
|
||||
}
|
||||
|
||||
public static void refreshScoreboard(GameManager gameManager) {
|
||||
gameManager.getGamePhase().ifPresent(gamePhase ->
|
||||
gameManager.getRemainingTime().ifPresent(duration ->
|
||||
updateTime(gamePhase, duration)));
|
||||
// TODO if game is active show time + game phase
|
||||
// if someone has flag show that
|
||||
// show how many of each team in circle?
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
#Sat Feb 15 22:27:11 CET 2025
|
||||
buildNumber=66
|
||||
#Sun Mar 02 00:42:30 CET 2025
|
||||
buildNumber=112
|
||||
version=0.1
|
||||
|
||||
Reference in New Issue
Block a user