/*
* Copyright or © or Copr. Amaury Carrade (2014 - 2016)
*
* http://amaury.carrade.eu
*
* This software is governed by the CeCILL-B license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL-B
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-B license and that you accept its terms.
*/
package eu.carrade.amaury.UHCReloaded.game;
import eu.carrade.amaury.UHCReloaded.UHCReloaded;
import eu.carrade.amaury.UHCReloaded.UHConfig;
import eu.carrade.amaury.UHCReloaded.events.EpisodeChangedCause;
import eu.carrade.amaury.UHCReloaded.events.UHEpisodeChangedEvent;
import eu.carrade.amaury.UHCReloaded.events.UHGameStartsEvent;
import eu.carrade.amaury.UHCReloaded.events.UHPlayerResurrectedEvent;
import eu.carrade.amaury.UHCReloaded.misc.OfflinePlayersLoader;
import eu.carrade.amaury.UHCReloaded.protips.ProTips;
import eu.carrade.amaury.UHCReloaded.task.FireworksOnWinnersTask;
import eu.carrade.amaury.UHCReloaded.teams.TeamColor;
import eu.carrade.amaury.UHCReloaded.teams.TeamManager;
import eu.carrade.amaury.UHCReloaded.teams.UHTeam;
import eu.carrade.amaury.UHCReloaded.timers.UHTimer;
import eu.carrade.amaury.UHCReloaded.utils.ColorsUtils;
import eu.carrade.amaury.UHCReloaded.utils.UHSound;
import eu.carrade.amaury.UHCReloaded.utils.UHUtils;
import fr.zcraft.zlib.components.i18n.I;
import fr.zcraft.zlib.components.rawtext.RawText;
import fr.zcraft.zlib.tools.Callback;
import fr.zcraft.zlib.tools.runners.RunTask;
import fr.zcraft.zlib.tools.text.ActionBar;
import fr.zcraft.zlib.tools.text.RawMessage;
import fr.zcraft.zlib.tools.text.Titles;
import org.bukkit.Achievement;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
public class UHGameManager
{
public final boolean START_GIVE_BANNER;
public final boolean START_PLACE_BANNER_HEAD;
public final boolean START_PLACE_BANNER_SPAWN;
private final Boolean RANDOM_COLORS_IN_SOLO;
private final Boolean BROADCAST_SLOW_START_PROGRESS;
private final Long GRACE_PERIOD;
private final Long PEACE_PERIOD;
private final Long SURFACE_MOBS_FREE_PERIOD;
private final UHSound DEATH_SOUND;
private UHCReloaded p = null;
private TeamManager tm = null;
private Random random = null;
private Boolean damagesEnabled = false;
private Boolean mobsOnSurface = false;
private HashSet<String> players = new HashSet<>(); // Will be converted to UUID when a built-in API for name->UUID conversion will be available
private HashSet<UUID> alivePlayers = new HashSet<>();
private HashSet<UHTeam> aliveTeams = new HashSet<>();
private HashSet<UUID> spectators = new HashSet<>();
private Map<UUID, Location> deathLocations = new HashMap<>();
private HashSet<String> deadPlayersToBeResurrected = new HashSet<>(); // Same
private Integer alivePlayersCount = 0;
private Integer aliveTeamsCount = 0;
private Boolean gameWithTeams = true;
// Used for the slow start.
private Boolean slowStartInProgress = false;
private Boolean slowStartTPFinished = false;
private Boolean gameStarted = false;
private Boolean gameFinished = false;
private Integer episode = 0;
private Teleporter teleporter = null;
// Used to send a contextual error message in UHCommandManager, using only one exception,
// by checking the message. (Used in this.finishGame().)
public final static String FINISH_ERROR_NOT_STARTED = "Unable to finish the game: the game is not started";
public final static String FINISH_ERROR_NOT_FINISHED = "Unable to finish the game: the game is not finished";
public UHGameManager(UHCReloaded plugin)
{
this.p = plugin;
this.tm = p.getTeamManager();
this.random = new Random();
// Loads the config
RANDOM_COLORS_IN_SOLO = UHConfig.TEAMS_OPTIONS.RANDOM_COLORS.get();
BROADCAST_SLOW_START_PROGRESS = UHConfig.START.SLOW.BROADCAST_PROGRESS.get();
GRACE_PERIOD = (long) Math.max(UHUtils.string2Time(UHConfig.START.GRACE_PERIOD.get(), 30), 15) * 20l;
PEACE_PERIOD = (long) UHUtils.string2Time(UHConfig.START.PEACE_PERIOD.get(), 0) * 20l;
SURFACE_MOBS_FREE_PERIOD = (long) UHUtils.string2Time(UHConfig.START.SURFACE_MOBS_FREE_PERIOD.get(), 900) * 20l;
DEATH_SOUND = new UHSound(UHConfig.DEATH.ANNOUNCEMENTS.SOUND);
START_GIVE_BANNER = UHConfig.TEAMS_OPTIONS.BANNER.GIVE.GIVE_IN_HOTBAR.get();
START_PLACE_BANNER_SPAWN = UHConfig.TEAMS_OPTIONS.BANNER.GIVE.PLACE_ON_SPAWN.get();
START_PLACE_BANNER_HEAD = UHConfig.TEAMS_OPTIONS.BANNER.GIVE.GIVE_IN_HEAD.get();
}
/**
* Initializes the environment before the start of the game.
*/
public void initEnvironment()
{
p.getServer().getWorlds().get(0).setGameRuleValue("doDaylightCycle", "false");
p.getServer().getWorlds().get(0).setTime(6000L);
p.getServer().getWorlds().get(0).setStorm(false);
p.getServer().getWorlds().get(0).setDifficulty(Difficulty.HARD);
}
/**
* Initializes the given player.
*
* - Teleportation to the default world's spawn point.
* - Max food level & health.
* - Scoreboard.
* - Fixed health score.
* - Spectate mode disabled.
* - Gamemode: creative (if permission "uh.build" granted) or adventure (else).
*
* @param player
*/
public void initPlayer(final Player player)
{
if (UHConfig.TELEPORT_TO_SPAWN_IF_NOT_STARTED.get())
{
Location l = player.getWorld().getSpawnLocation().add(0.5, 0.5, 0.5);
if (!UHUtils.safeTP(player, l))
{
player.teleport(l.add(0, 1, 0));
}
}
player.setFoodLevel(20);
player.setSaturation(20f);
player.setHealth(20d);
p.getScoreboardManager().setScoreboardForPlayer(player);
// Used to update the "health" objective, to avoid a null one.
// Launched later because else, the health is constantly set to 20,
// and this prevents the health score to be updated.
Bukkit.getScheduler().runTaskLater(p, new Runnable()
{
@Override
public void run()
{
p.getScoreboardManager().updateHealthScore(player);
}
}, 20L);
// Disable the spectator mode if the game is not started.
p.getSpectatorsManager().setSpectating(player, false);
// Resets the achievements
if (UHConfig.ACHIEVEMENTS.RESET_ACHIEVEMENTS_AT_STARTUP.get())
{
player.removeAchievement(Achievement.OPEN_INVENTORY);
}
// If the user has the permission to build before the game, he will probably needs
// the creative mode.
if (!player.hasPermission("uh.build"))
{
player.setGameMode(GameMode.ADVENTURE);
}
else
{
player.setGameMode(GameMode.CREATIVE);
}
}
/**
* Starts the game.
*
* - Teleports the teams
* - Changes the gamemode, reset the life, clear inventories, etc.
* - Launches the timer
*
* @param sender The player who launched the game.
* @param slow If true, the slow mode is enabled. With the slow mode, the players are, at
* first, teleported one by one with a configurable delay, and with the
* fly. Then, the fly is removed and the game starts.
* @param ignoreTeams If true, the players will be teleported in individual teleportation spots,
* just like without teams, even with teams.
*
* @throws IllegalStateException if the game is running.
*/
public void start(final CommandSender sender, final Boolean slow, Boolean ignoreTeams) throws IllegalStateException
{
if (isGameRunning())
{
throw new IllegalStateException("The game is currently running!");
}
/** Initialization of the teams **/
alivePlayers.clear();
aliveTeams.clear();
alivePlayersCount = 0;
aliveTeamsCount = 0;
// Stores the teams created on-the-fly, to unregister them if something bad happens.
Set<UHTeam> onTheFlyTeams = new HashSet<>();
// If there isn't any team, we add all players (startup spectators excluded) to a new solo team.
if (tm.getTeams().isEmpty())
{
gameWithTeams = false;
for (Player player : Bukkit.getOnlinePlayers())
{
if (!spectators.contains(player.getUniqueId()))
{
UHTeam team = new UHTeam(player.getName(), RANDOM_COLORS_IN_SOLO ? TeamColor.RANDOM : null);
team.addPlayer(player, true);
tm.addTeam(team);
onTheFlyTeams.add(team);
}
}
}
// Else, every non-startup-spectator out of any team player is added to a solo team.
else
{
gameWithTeams = true;
for (Player player : Bukkit.getOnlinePlayers())
{
if (!spectators.contains(player.getUniqueId()) && tm.getTeamForPlayer(player) == null)
{
// We need an unique name for the team.
String teamName = player.getName();
while (tm.isTeamRegistered(teamName))
{
teamName = player.getName() + " " + random.nextInt(1000000);
}
UHTeam team = new UHTeam(teamName, RANDOM_COLORS_IN_SOLO ? TeamColor.RANDOM : null);
team.addPlayer(player, true);
tm.addTeam(team);
onTheFlyTeams.add(team);
}
}
}
/** Initialization of the players **/
for (UHTeam team : tm.getTeams())
{
for (OfflinePlayer player : team.getPlayers())
{
if (!spectators.contains(player.getUniqueId()))
{
alivePlayers.add(player.getUniqueId());
}
}
}
updateAliveCache();
/** Spawns check **/
Integer spawnsNeeded = ignoreTeams ? alivePlayersCount : aliveTeamsCount;
if (p.getSpawnsManager().getSpawnPoints().size() < spawnsNeeded)
{
if (sender instanceof Player) sender.sendMessage("");
sender.sendMessage(I.t("{ce}Unable to start the game: not enough teleportation spots."));
sender.sendMessage(I.t("{ci}You can use {cc}/uh spawns generate <random|circular|grid>{ci} to generate the missing spawns automatically."));
/// In the sentence: "Or click here to generate the spawns randomly."
RawMessage.send(sender, new RawText(I.t("Or"))
.then(" ")
/// In the sentence: "Or click here to generate the spawns randomly."
.then(I.t("click here"))
.color(ChatColor.GREEN).style(ChatColor.BOLD)
.command("/uh spawns generate random")
.hover(new RawText("/uh spawns generate random"))
.then(" ")
/// In the sentence: "Or click here to generate the spawns randomly."
.then(I.t("to generate the spawns randomly.")).color(ChatColor.WHITE)
.build()
);
// We clears the teams created on-the-fly
for (UHTeam team : onTheFlyTeams)
{
tm.removeTeam(team, true);
}
aliveTeamsCount = 0;
alivePlayersCount = 0;
return;
}
/** MOTD (now the game WILL start) **/
p.getMOTDManager().updateMOTDDuringStart();
/** Removes the teams action bar (if any) **/
if (UHConfig.BEFORE_START.TEAM_IN_ACTION_BAR.get())
{
for (UHTeam team : tm.getTeams())
{
for (OfflinePlayer player : team.getPlayers())
{
ActionBar.removeMessage(player.getUniqueId(), true);
}
}
}
/** Initialization of the spectator mode **/
for (Player player : Bukkit.getOnlinePlayers())
{
p.getSpectatorsManager().setSpectating(player, spectators.contains(player.getUniqueId()));
}
/** Teleportation **/
teleporter = new Teleporter();
List<Location> spawnPoints = new ArrayList<>(p.getSpawnsManager().getSpawnPoints());
Collections.shuffle(spawnPoints);
Queue<Location> unusedTP = new ArrayDeque<>(spawnPoints);
for (final UHTeam team : tm.getTeams())
{
if (team.isEmpty()) continue;
Material cageMaterial = null;
Byte cageData = null;
if (UHConfig.START.SLOW.CAGES.ENABLED.get())
{
switch (UHConfig.START.SLOW.CAGES.TYPE.get())
{
case TEAM_COLOR_TRANSPARENT:
cageMaterial = Material.STAINED_GLASS;
cageData = ColorsUtils.chat2Dye(team.getColorOrWhite().toChatColor()).getWoolData();
break;
case TEAM_COLOR_SOLID:
cageMaterial = Material.STAINED_CLAY;
cageData = ColorsUtils.chat2Dye(team.getColorOrWhite().toChatColor()).getWoolData();
break;
case CUSTOM:
cageMaterial = UHConfig.START.SLOW.CAGES.CUSTOM_BLOCK.get();
break;
}
}
if (!ignoreTeams && gameWithTeams)
{
final Location teamSpawn = unusedTP.poll();
Cage cage = null;
if (cageMaterial != null)
{
cage = new Cage(teamSpawn, UHConfig.START.SLOW.CAGES.BUILD_CEILING.get(), UHConfig.START.SLOW.CAGES.VISIBLE_WALLS.get());
cage.setCustomMaterial(cageMaterial, cageData != null ? cageData : 0);
cage.setInternalHeight(UHConfig.START.SLOW.CAGES.HEIGHT.get());
cage.setRadius(UHConfig.START.SLOW.CAGES.RADIUS.get());
}
p.getDynmapIntegration().showSpawnLocation(team, teamSpawn);
for (final UUID player : team.getPlayersUUID())
{
teleporter.setSpawnForPlayer(player, teamSpawn);
if (cage != null) teleporter.setCageForPlayer(player, cage);
}
}
else
{
for (final UUID player : team.getPlayersUUID())
{
final Location playerSpawn = unusedTP.poll();
Cage cage = null;
if (cageMaterial != null)
{
cage = new Cage(playerSpawn, UHConfig.START.SLOW.CAGES.BUILD_CEILING.get(), UHConfig.START.SLOW.CAGES.VISIBLE_WALLS.get());
cage.setCustomMaterial(cageMaterial, cageData != null ? cageData : 0);
cage.setInternalHeight(UHConfig.START.SLOW.CAGES.HEIGHT.get());
cage.setRadius(UHConfig.START.SLOW.CAGES.RADIUS.get());
}
teleporter.setSpawnForPlayer(player, playerSpawn);
if (cage != null) teleporter.setCageForPlayer(player, cage);
p.getDynmapIntegration().showSpawnLocation(Bukkit.getOfflinePlayer(player), playerSpawn);
}
}
}
if (slow)
{
slowStartInProgress = true;
slowStartTPFinished = false;
// The players are frozen during the start (if cages are not used).
if (!UHConfig.START.SLOW.CAGES.ENABLED.get())
p.getFreezer().setGlobalFreezeState(true, false);
// A simple information, because this start is slower (yeah, Captain Obvious here)
p.getServer().broadcastMessage(I.t("{lightpurple}Teleportation in progress... Please wait."));
teleporter.whenTeleportationOccurs(new Callback<UUID>()
{
private int teleported = 0;
private final int total = alivePlayersCount;
@Override
public void call(UUID uuid)
{
teleported++;
if (BROADCAST_SLOW_START_PROGRESS)
{
/// Displayed in the action bar while the slow teleportation occurs.
final String message = I.t("{lightpurple}Teleporting... {gray}({0}/{1})", teleported, total);
for (Player player : Bukkit.getOnlinePlayers())
{
ActionBar.sendPermanentMessage(player, message);
}
}
}
});
}
teleporter
.whenTeleportationSuccesses(new Callback<UUID>()
{
@Override
public void call(UUID uuid)
{
final Player player = Bukkit.getPlayer(uuid);
if (slow)
{
sender.sendMessage(I.t("{gray}Player {0}{gray} teleported.", player.getName()));
if (!UHConfig.START.SLOW.CAGES.ENABLED.get())
{
RunTask.nextTick(new Runnable()
{
@Override
public void run()
{
player.setAllowFlight(true);
player.setFlying(true);
}
});
}
}
if (UHConfig.START.SLOW.CAGES.ENABLED.get())
player.setGameMode(GameMode.ADVENTURE);
else
player.setGameMode(GameMode.SURVIVAL);
resetPlayer(player);
for (PotionEffect effect : player.getActivePotionEffects())
{
player.removePotionEffect(effect.getType());
}
player.setCompassTarget(player.getWorld().getSpawnLocation());
}
})
.whenTeleportationFails(new Callback<UUID>()
{
@Override
public void call(UUID uuid)
{
}
})
.whenTeleportationEnds(new Callback<Set<UUID>>()
{
@Override
public void call(Set<UUID> uuids)
{
if (slow)
{
slowStartTPFinished = true;
try
{
sender.sendMessage(I.t("{cs}All teams are teleported."));
RawMessage.send(sender, new RawText(I.t("{gray}Use {cc}/uh start{gray} or click here to start the game."))
.hover(new RawText(I.t("Click here to start the game")))
.command("/uh start")
);
}
catch (NullPointerException ignored) {}
if (BROADCAST_SLOW_START_PROGRESS)
{
/// Displayed in the action bar when the slow teleportation is finished but the game not started.
String message = I.t("{lightpurple}Teleportation complete. {gray}The game will start soon...");
for (Player player : Bukkit.getOnlinePlayers())
{
ActionBar.sendPermanentMessage(player, message);
}
}
}
else
{
startEnvironment();
startTimer();
scheduleDamages();
sendStartupProTips();
finalizeStart();
}
}
})
.startTeleportationProcess(slow);
}
/**
* Finalizes the start of the game, with the slow mode. Removes the fly and ends the start
* (environment, timer...)
*
* @param sender The sender of the {@code /uh start slow go} command
*/
public void finalizeStartSlow(CommandSender sender)
{
if (!slowStartInProgress)
{
sender.sendMessage(I.t("{ce}Please execute {cc}/uh start slow:true{ce} before."));
return;
}
if (!slowStartTPFinished)
{
sender.sendMessage(I.t("{ce}Please wait while the players are teleported."));
return;
}
// The freeze is removed.
if (!UHConfig.START.SLOW.CAGES.ENABLED.get())
{
p.getFreezer().setGlobalFreezeState(false, false);
// The fly is removed to everyone
for (Player player : p.getServer().getOnlinePlayers())
{
if (alivePlayers.contains(player.getUniqueId()))
{
player.setFlying(false);
player.setAllowFlight(false);
}
}
}
// The action bar is cleared
if (BROADCAST_SLOW_START_PROGRESS)
{
for (Player player : Bukkit.getOnlinePlayers())
{
ActionBar.removeMessage(player);
}
}
// The environment is initialized, the game is started.
startEnvironment();
startTimer();
scheduleDamages();
sendStartupProTips();
finalizeStart();
slowStartInProgress = false;
}
/**
* Initializes the environment at the beginning of the game.
*/
private void startEnvironment()
{
World overworld = UHUtils.getOverworld();
if (overworld != null)
{
overworld.setGameRuleValue("doDaylightCycle", (UHConfig.DAYLIGHT_CYCLE.DO.get()).toString());
overworld.setTime(UHConfig.DAYLIGHT_CYCLE.TIME.get());
overworld.setStorm(false);
}
for (World world : Bukkit.getWorlds())
{
world.setGameRuleValue("keepInventory", Boolean.FALSE.toString()); // Just in case...
world.setGameRuleValue("naturalRegeneration", (UHConfig.GAMEPLAY_CHANGES.NATURAL_REGENERATION.get()).toString());
world.setDifficulty(Difficulty.HARD);
}
}
/**
* Launches the timer by launching the task that updates the scoreboard every second.
*/
private void startTimer()
{
if (UHConfig.EPISODES.ENABLED.get())
{
this.episode = 1;
// An empty string is used for the name of the main timer, because
// such a name can't be used by players.
UHTimer mainTimer = new UHTimer("");
mainTimer.setDuration(this.getEpisodeLength());
p.getTimerManager().registerMainTimer(mainTimer);
mainTimer.start();
}
}
/**
* Enables the damages 30 seconds (600 ticks) later, the PvP after if needed, and the mobs spawns.
*/
private void scheduleDamages()
{
// When the grace period is over, damages are enabled.
RunTask.later(new Runnable()
{
@Override
public void run()
{
damagesEnabled = true;
if (UHConfig.START.BROADCAST_GRACE_END.get())
{
Bukkit.broadcastMessage(I.t("{red}{bold}Warning!{white} The grace period ended, you are now vulnerable."));
}
}
}, GRACE_PERIOD);
// When the peace period is over, PVP is enabled
if (PEACE_PERIOD > 0)
{
for (World world : Bukkit.getWorlds())
world.setPVP(false);
RunTask.later(new Runnable()
{
@Override
public void run()
{
for (World world : Bukkit.getWorlds())
world.setPVP(true);
Bukkit.broadcastMessage(I.t("{red}{bold}Warning!{white} PvP is now enabled."));
}
}, PEACE_PERIOD);
}
// Allows mobs to spawn on the surface after the mobs-free period
RunTask.later(new Runnable()
{
@Override
public void run()
{
mobsOnSurface = true;
}
}, SURFACE_MOBS_FREE_PERIOD);
}
/**
* Sends two ProTips:
* - about the team chat, to all players, 20 seconds after the beginning of the game;
* - about the invincibility, 5 seconds after the beginning of the game.
*/
private void sendStartupProTips()
{
// Team chat - 20 seconds after
if (this.isGameWithTeams())
{
Bukkit.getScheduler().runTaskLater(p, new Runnable()
{
@Override
public void run()
{
for (Player player : getOnlineAlivePlayers())
{
ProTips.USE_T_COMMAND.sendTo(player);
}
}
}, 400L);
}
// Invincibility - 5 seconds after
Bukkit.getScheduler().runTaskLater(p, new Runnable()
{
@Override
public void run()
{
for (Player player : getOnlineAlivePlayers())
{
ProTips.STARTUP_INVINCIBILITY.sendTo(player);
}
}
}, 100L);
}
/**
* Changes the state of the game. Also, forces the global freeze start to false, to avoid toggle
* bugs (like inverted state).
*/
private void finalizeStart()
{
p.getFreezer().setGlobalFreezeState(false);
teleporter.cleanup();
gameStarted = true;
gameFinished = false;
updateAliveCache();
// Survival gamemode for everyone
for (Player player : p.getServer().getOnlinePlayers())
{
if (alivePlayers.contains(player.getUniqueId()))
{
player.setGameMode(GameMode.SURVIVAL);
resetPlayer(player);
}
}
// Fires the event
p.getServer().getPluginManager().callEvent(new UHGameStartsEvent());
}
/**
* Reinitializes a player (health, XP, inventory...).
*
* @param player The player
*/
private void resetPlayer(Player player)
{
player.setHealth(20D);
player.setFoodLevel(20);
player.setSaturation(20);
player.getInventory().clear();
player.getInventory().setArmorContents(null);
player.setExp(0L);
player.setLevel(0);
player.closeInventory();
}
/**
* @return true if the slow start is in progress.
*/
public boolean isSlowStartInProgress()
{
return slowStartInProgress;
}
/**
* Updates the cached values of the alive players and teams.
*/
public void updateAliveCache()
{
// Alive teams
aliveTeams.clear();
for (UHTeam t : tm.getTeams())
{
for (UUID pid : t.getPlayersUUID())
{
if (!this.isPlayerDead(pid)) aliveTeams.add(t);
}
}
// Counters
this.alivePlayersCount = alivePlayers.size();
this.aliveTeamsCount = aliveTeams.size();
if (isGameRunning())
p.getMOTDManager().updateMOTDDuringGame();
}
/**
* Updates the cached values of the alive players and teams.
*
* @deprecated Use {@link #updateAliveCache()} instead.
*/
@Deprecated
public void updateAliveCounters() { updateAliveCache(); }
/**
* Shifts an episode.
*
* @param shifter The player who shifts the episode, an empty string if the episode is shifted
* because the timer is up.
*/
public void shiftEpisode(String shifter)
{
if (UHConfig.EPISODES.ENABLED.get())
{
this.episode++;
EpisodeChangedCause cause;
if (shifter.equals("")) cause = EpisodeChangedCause.FINISHED;
else cause = EpisodeChangedCause.SHIFTED;
// Restarts the timer.
// Useless for a normal start (restarted in the event), but needed if the episode was shifted.
if (cause == EpisodeChangedCause.SHIFTED)
{
p.getTimerManager().getMainTimer().start();
}
p.getServer().getPluginManager().callEvent(new UHEpisodeChangedEvent(episode, cause, shifter));
}
}
/**
* Shift an episode because the timer is up.
*/
public void shiftEpisode()
{
shiftEpisode("");
}
/**
* Resurrects an offline player. The tasks that needed to be executed when the player is online
* are delayed and executed when the player joins.
*
* @param playerName The name of the player to resurrect
*
* @return true if the player was dead, false otherwise.
*/
public boolean resurrect(String playerName)
{
Player playerOnline = Bukkit.getPlayer(playerName);
if (playerOnline != null && playerOnline.isOnline())
{
return resurrectPlayerOnlineTask(playerOnline);
}
else
{
// We checks if the player was a player
if (!this.players.contains(playerName))
{
return false;
}
}
// So, now, we are sure that the player is really offline.
// The task needed to be executed will be executed when the player join.
this.deadPlayersToBeResurrected.add(playerName);
return true;
}
/**
* The things that have to be done in order to resurrect the players and that needs the player
* to be online.
*
* @param player The player to resurrect
*
* @return true if the player was dead, false otherwise.
*/
public boolean resurrectPlayerOnlineTask(Player player)
{
if (alivePlayers.contains(player.getUniqueId()))
{
return false;
}
// Player registered as alive
alivePlayers.add(player.getUniqueId());
updateAliveCache();
// This method can be used to add a player after the game start.
if (!players.contains(player.getName()))
{
players.add(player.getName());
}
// Event
p.getServer().getPluginManager().callEvent(new UHPlayerResurrectedEvent(player));
return true;
}
/**
* Returns true if a player need to be resurrected.
*
* @param player
*
* @return
*/
public boolean isDeadPlayersToBeResurrected(Player player)
{
return deadPlayersToBeResurrected.contains(player.getName());
}
/**
* Registers a player as resurrected.
*
* @param player
*/
public void markPlayerAsResurrected(Player player)
{
deadPlayersToBeResurrected.remove(player.getName());
}
/**
* This method saves the location of the death of a player.
*
* @param player
* @param location
*/
public void addDeathLocation(Player player, Location location)
{
deathLocations.put(player.getUniqueId(), location);
}
/**
* This method removes the stored death location.
*
* @param player
*/
public void removeDeathLocation(Player player)
{
deathLocations.remove(player.getUniqueId());
}
/**
* This method returns the stored death location.
*
* @param player The player to retrieve the death location of.
*
* @return The death location, or {@code null} if not defined.
*/
public Location getDeathLocation(Player player)
{
if (deathLocations.containsKey(player.getUniqueId()))
{
return deathLocations.get(player.getUniqueId());
}
return null;
}
/**
* This method returns true if a death location is stored for the given player.
*
* @param player The player to check the death location of.
*
* @return {@code true} if a death location is set.
*/
public boolean hasDeathLocation(Player player)
{
return deathLocations.containsKey(player.getUniqueId());
}
/**
* Adds a spectator. When the game is started, spectators are ignored and the spectator mode is
* enabled if SpectatorPlus is present.
*
* @param player The player to register as a spectator.
*/
public void addStartupSpectator(OfflinePlayer player)
{
spectators.add(player.getUniqueId());
tm.removePlayerFromTeam(player);
}
/**
* Removes a spectator.
*
* @param player The spectator to remove.
*/
public void removeStartupSpectator(OfflinePlayer player)
{
spectators.remove(player.getUniqueId());
}
/**
* Returns a list of the current registered spectators.
*
* This returns only a list of the <em>initial</em> spectators. Use {@link #getAlivePlayers()}
* to get the alive players, and remove the elements of this list from the online players to get
* the spectators.
*
* @return The initial spectators.
*/
public HashSet<String> getStartupSpectators()
{
HashSet<String> spectatorNames = new HashSet<>();
for (UUID id : spectators)
{
final OfflinePlayer player = OfflinePlayersLoader.getOfflinePlayer(id);
final String playerName = player.getName();
if (playerName != null)
spectatorNames.add(playerName);
else
/// Spectators list item if the nick cannot be found
spectatorNames.add(I.t("Unknown player with UUID {0}", player.getUniqueId()));
}
return spectatorNames;
}
/**
* @return true if the game was launched and is not finished.
*/
public boolean isGameRunning()
{
return gameStarted && !gameFinished;
}
/**
* @return true if the game is started.
*/
public boolean isGameStarted()
{
return gameStarted;
}
/**
* @return true if the game is finished.
*/
public boolean isGameFinished()
{
return gameFinished;
}
/**
* Registers the state of the game.
*
* @param finished If true, the game will be marked as finished.
*/
public void setGameFinished(boolean finished)
{
gameFinished = finished;
}
/**
* @return true if the game is a game with teams, and false if the game is a solo game.
*/
public boolean isGameWithTeams()
{
return gameWithTeams;
}
/**
* @return true if damages are enabled. Damages are enabled 30 seconds after the beginning of
* the game.
*/
public boolean isTakingDamage()
{
return damagesEnabled;
}
public boolean isSurfaceSpawnEnabled()
{
return mobsOnSurface;
}
/**
* Returns true if the given player is dead.
*
* @param player The player.
*
* @return True if the player is dead.
*/
public boolean isPlayerDead(Player player)
{
return !alivePlayers.contains(player.getUniqueId());
}
/**
* Returns true if the given player is dead.
*
* @param player The UUID of the player.
*
* @return True if the player is dead.
*/
public boolean isPlayerDead(UUID player)
{
return !alivePlayers.contains(player);
}
/**
* Registers a player as dead.
*
* @param player The player to mark as dead.
*/
public void addDead(Player player)
{
alivePlayers.remove(player.getUniqueId());
updateAliveCache();
}
/**
* Registers a player as dead.
*
* @param player The UUID of the player to mark as dead.
*/
public void addDead(UUID player)
{
alivePlayers.remove(player);
updateAliveCache();
}
/**
* Broadcasts the winner(s) of the game and launches some fireworks
*
* @throws IllegalStateException if the game is not started or not finished (use the message to
* distinguish these cases, {@link #FINISH_ERROR_NOT_STARTED} or
* {@link #FINISH_ERROR_NOT_FINISHED}).
*/
public void finishGame()
{
if (!p.getGameManager().isGameStarted())
{
throw new IllegalStateException(FINISH_ERROR_NOT_STARTED);
}
if (p.getGameManager().getAliveTeamsCount() != 1)
{
throw new IllegalStateException(FINISH_ERROR_NOT_FINISHED);
}
// There's only one team.
UHTeam winnerTeam = p.getGameManager().getAliveTeams().iterator().next();
Set<OfflinePlayer> listWinners = winnerTeam.getPlayers();
if (UHConfig.FINISH.MESSAGE.get())
{
if (isGameWithTeams())
{
String winners = "";
int j = 0;
for (OfflinePlayer winner : listWinners)
{
if (j != 0)
{
if (j == listWinners.size() - 1)
{
/// The "and" in the winners players list (like "player1, player2 and player3").
winners += " " + I.tc("winners_list", "and") + " ";
}
else
{
winners += ", ";
}
}
winners += winner.getName();
j++;
}
p.getServer().broadcastMessage(I.t("{darkgreen}{obfuscated}--{green} Congratulations to {0} (team {1}{green}) for their victory! {darkgreen}{obfuscated}--", winners, winnerTeam.getDisplayName()));
}
else
{
p.getServer().broadcastMessage(I.t("{darkgreen}{obfuscated}--{green} Congratulations to {0} for his victory! {darkgreen}{obfuscated}--", winnerTeam.getName()));
}
}
if (UHConfig.FINISH.TITLE.get())
{
final String title;
final String subtitle;
if (isGameWithTeams())
{
/// The main title of the /title displayed when a team wins the game. {0} becomes the team display name (with colors).
title = I.t("{darkgreen}{0}", winnerTeam.getDisplayName());
/// The subtitle of the /title displayed when a team wins the game. {0} becomes the team display name (with colors).
subtitle = I.t("{green}This team wins the game!", winnerTeam.getDisplayName());
}
else
{
/// The main title of the /title displayed when a player wins the game (in solo). {0} becomes the player display name (with colors).
title = I.t("{darkgreen}{0}", winnerTeam.getDisplayName());
/// The subtitle of the /title displayed when a player wins the game (in solo). {0} becomes the player display name (with colors).
subtitle = I.t("{green}wins the game!", winnerTeam.getDisplayName());
}
Titles.broadcastTitle(5, 142, 21, title, subtitle);
}
if (UHConfig.FINISH.FIREWORKS.ENABLED.get())
{
new FireworksOnWinnersTask(listWinners).runTaskTimer(p, 0l, 10l);
}
}
/**
* Returns the names of the players of this game.
*
* @return The set.
*/
public HashSet<String> getPlayers()
{
return players;
}
/**
* Returns a list of the currently alive teams.
*
* @return The set.
*/
public Set<UHTeam> getAliveTeams()
{
return aliveTeams;
}
/**
* Returns a list of the currently alive players.
*
* @return The set.
*/
public Set<OfflinePlayer> getAlivePlayers()
{
HashSet<OfflinePlayer> alivePlayersList = new HashSet<>();
for (UUID id : alivePlayers)
{
alivePlayersList.add(p.getServer().getOfflinePlayer(id));
}
return alivePlayersList;
}
/**
* Returns a list of the currently alive and online players.
*
* @return The list.
*/
public HashSet<Player> getOnlineAlivePlayers()
{
HashSet<Player> alivePlayersList = new HashSet<>();
for (UUID id : alivePlayers)
{
Player player = p.getServer().getPlayer(id);
if (player != null)
{
alivePlayersList.add(player);
}
}
return alivePlayersList;
}
/**
* @return the death sound, or null if no death sound is registered.
*/
public UHSound getDeathSound()
{
return DEATH_SOUND;
}
/**
* @return the length of one episode, in seconds.
*/
public Integer getEpisodeLength()
{
return UHUtils.string2Time(UHConfig.EPISODES.LENGTH.get(), 20*60);
}
/**
* @return the (cached) number of alive players.
*/
public Integer getAlivePlayersCount()
{
return alivePlayersCount;
}
/**
* @return the (cached) number of alive teams.
*/
public Integer getAliveTeamsCount()
{
return aliveTeamsCount;
}
/**
* @return the number of the current episode.
*/
public Integer getEpisode()
{
return episode;
}
/**
* @return the teleporter instance used to start the game, containing the spawn points of each
* player.
*/
public Teleporter getTeleporter()
{
return teleporter;
}
/* *** Deprecated methods *** */
/**
* Adds a spectator. When the game is started, spectators are ignored and the spectator mode is
* enabled if SpectatorPlus is present.
*
* @param player The player to register as a spectator.
*
* @deprecated Use {@link #addStartupSpectator(Player)} instead.
*/
@Deprecated
public void addSpectator(Player player)
{
addStartupSpectator(player);
}
/**
* Removes a spectator.
*
* @param player
*
* @deprecated Use {@link #removeStartupSpectator(Player)} instead.
*/
@Deprecated
public void removeSpectator(Player player)
{
removeStartupSpectator(player);
}
/**
* Returns a list of the current registered spectators.
*
* This returns only a list of the <em>initial</em> spectators. Use {@link #getAlivePlayers()}
* to get the alive players, and remove the elements of this list from the online players to get
* the spectators.
*
* @return The initial spectators.
* @deprecated Use {@link #getStartupSpectators()} instead.
*/
@Deprecated
public HashSet<String> getSpectators()
{
return getStartupSpectators();
}
}