/*
* This file is part of Kenshins Hide and Seek
*
* Copyright (c) 2020-2021. Tyler Murphy
*
* Kenshins Hide and Seek free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* he Free Software Foundation version 3.
*
* Kenshins Hide and Seek is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
package net.tylermurphy.hideAndSeek.game;
import static net.tylermurphy.hideAndSeek.configuration.Config.*;
import com.cryptomorin.xseries.XMaterial;
import com.cryptomorin.xseries.XSound;
import com.cryptomorin.xseries.messages.Titles;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.ChatColor;
import net.tylermurphy.hideAndSeek.configuration.Items;
import net.tylermurphy.hideAndSeek.database.Database;
import net.tylermurphy.hideAndSeek.util.Status;
import net.tylermurphy.hideAndSeek.util.Version;
import net.tylermurphy.hideAndSeek.util.WinType;
import net.tylermurphy.hideAndSeek.world.WorldLoader;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Firework;
import org.bukkit.entity.Player;
import net.tylermurphy.hideAndSeek.Main;
import net.tylermurphy.hideAndSeek.util.Packet;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.FireworkMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import static net.tylermurphy.hideAndSeek.configuration.Localization.*;
import static net.tylermurphy.hideAndSeek.game.Game.broadcastMessage;
public class Game {
public static Taunt taunt;
public static Glow glow;
public static Border worldBorder;
public static WorldLoader worldLoader;
public static int tick = 0;
public static int countdownTime = -1;
public static int gameId = 0;
public static int timeLeft = 0;
public static Status status = Status.STANDBY;
static {
worldLoader = new WorldLoader(spawnWorld);
}
public static void start(){
Optional rand = Board.getPlayers().stream().skip(new Random().nextInt(Board.size())).findFirst();
if(!rand.isPresent()){
Main.plugin.getLogger().warning("Failed to select random seeker.");
return;
}
String seekerName = rand.get().getName();
Player temp = Bukkit.getPlayer(seekerName);
if(temp == null){
Main.plugin.getLogger().warning("Failed to select random seeker.");
return;
}
Player seeker = Board.getPlayer(temp.getUniqueId());
if(seeker == null){
Main.plugin.getLogger().warning("Failed to select random seeker.");
return;
}
start(seeker);
}
public static void start(Player seeker){
if(status == Status.STARTING || status == Status.PLAYING) return;
if(worldLoader.getWorld() != null) {
worldLoader.rollback();
} else {
worldLoader.loadMap();
}
Board.reload();
for(Player temp : Board.getPlayers()) {
if(temp.getName().equals(seeker.getName()))
continue;
Board.addHider(temp);
}
Board.addSeeker(seeker);
currentWorldborderSize = worldborderSize;
for(Player player : Board.getPlayers()) {
player.getInventory().clear();
player.setGameMode(GameMode.ADVENTURE);
player.teleport(new Location(Bukkit.getWorld("hideandseek_"+spawnWorld), spawnPosition.getX(),spawnPosition.getY(),spawnPosition.getZ()));
for(PotionEffect effect : player.getActivePotionEffects()){
player.removePotionEffect(effect.getType());
}
}
for(Player player : Board.getSeekers()) {
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,1000000,127,false,false));
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW,1000000,127,false,false));
player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP,1000000,128,false,false));
Titles.sendTitle(player, 10, 70, 20, ChatColor.WHITE + "" + message("SEEKER_TEAM_NAME"), ChatColor.WHITE + message("SEEKERS_SUBTITLE").toString());
}
for(Player player : Board.getHiders()) {
player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED,1000000,5,false,false));
Titles.sendTitle(player, 10, 70, 20, ChatColor.WHITE + "" + message("HIDER_TEAM_NAME"), ChatColor.WHITE + message("HIDERS_SUBTITLE").toString());
}
if(tauntEnabled)
taunt = new Taunt();
if (glowEnabled)
glow = new Glow();
worldBorder = new Border();
worldBorder.resetWorldborder("hideandseek_"+spawnWorld);
if(gameLength > 0)
timeLeft = gameLength;
for(Player player : Board.getPlayers())
Board.createGameBoard(player);
Board.reloadGameBoards();
status = Status.STARTING;
int temp = gameId;
broadcastMessage(messagePrefix + message("START_COUNTDOWN").addAmount(30));
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(20), gameId, 20 * 10);
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(10), gameId, 20 * 20);
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(5), gameId, 20 * 25);
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(3), gameId, 20 * 27);
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(2), gameId, 20 * 28);
sendDelayedMessage(messagePrefix + message("START_COUNTDOWN").addAmount(1), gameId, 20 * 29);
Bukkit.getServer().getScheduler().runTaskLater(Main.plugin, () -> {
if(temp != gameId) return;
broadcastMessage(messagePrefix + message("START"));
for(Player player : Board.getPlayers()) resetPlayer(player);
status = Status.PLAYING;
}, 20 * 30);
}
public static void end(WinType type){
if(status == Status.STANDBY || status == Status.ENDING) return;
status = Status.ENDING;
for(Player player : Board.getPlayers()) {
player.getInventory().clear();
player.addPotionEffect(new PotionEffect(PotionEffectType.HEAL, 1, 100));
for(PotionEffect effect : player.getActivePotionEffects()){
player.removePotionEffect(effect.getType());
}
if(Version.atLeast("1.9")){
for(Player temp : Board.getPlayers()) {
Packet.setGlow(player, temp, false);
}
}
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Main.plugin, () -> stop(type), 5*20);
}
public static void stop(WinType type){
if(status == Status.STANDBY) return;
tick = 0;
countdownTime = -1;
status = Status.STANDBY;
gameId++;
timeLeft = 0;
List players = Board.getPlayers().stream().map(Entity::getUniqueId).collect(Collectors.toList());
if(type == WinType.HIDER_WIN){
List winners = Board.getHiders().stream().map(Entity::getUniqueId).collect(Collectors.toList());
Database.playerInfo.addWins(players, winners, type);
} else if(type == WinType.SEEKER_WIN){
List winners = new ArrayList<>();
winners.add(Board.getFirstSeeker().getUniqueId());
Database.playerInfo.addWins(players, winners, type);
}
worldBorder.resetWorldborder("hideandseek_"+spawnWorld);
for(Player player : Board.getPlayers()) {
if(Version.atLeast("1.9")){
for(Player temp : Board.getPlayers()) {
Packet.setGlow(player, temp, false);
}
}
if(leaveOnEnd){
Board.removeBoard(player);
Board.remove(player);
player.getInventory().clear();
if(bungeeLeave) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(leaveServer);
player.sendPluginMessage(Main.plugin, "BungeeCord", out.toByteArray());
} else {
player.teleport(new Location(Bukkit.getWorld(exitWorld), exitPosition.getX(), exitPosition.getY(), exitPosition.getZ()));
}
} else {
player.teleport(new Location(Bukkit.getWorld(lobbyWorld), lobbyPosition.getX(),lobbyPosition.getY(),lobbyPosition.getZ()));
Board.createLobbyBoard(player);
player.setGameMode(GameMode.ADVENTURE);
Board.addHider(player);
player.getInventory().clear();
if(lobbyStartItem != null && (!lobbyItemStartAdmin || player.isOp()))
player.getInventory().setItem(lobbyItemStartPosition, lobbyStartItem);
if(lobbyLeaveItem != null)
player.getInventory().setItem(lobbyItemLeavePosition, lobbyLeaveItem);
for(PotionEffect effect : player.getActivePotionEffects()){
player.removePotionEffect(effect.getType());
}
player.addPotionEffect(new PotionEffect(PotionEffectType.HEAL, 1, 100));
}
}
EventListener.temp_loc.clear();
worldLoader.unloadMap();
Board.reloadLobbyBoards();
}
public static boolean isNotSetup() {
if(spawnPosition.getBlockX() == 0 && spawnPosition.getBlockY() == 0 && spawnPosition.getBlockZ() == 0) return true;
if(lobbyPosition.getBlockX() == 0 && lobbyPosition.getBlockY() == 0 && lobbyPosition.getBlockZ() == 0) return true;
if(exitPosition.getBlockX() == 0 && exitPosition.getBlockY() == 0 && exitPosition.getBlockZ() == 0) return true;
File destenation = new File(Main.root+File.separator+"hideandseek_"+spawnWorld);
if(!destenation.exists()) return true;
return saveMinX == 0 || saveMinZ == 0 || saveMaxX == 0 || saveMaxZ == 0;
}
public static void onTick() {
if(isNotSetup()) return;
if(status == Status.STANDBY) whileWaiting();
else if(status == Status.STARTING) whileStarting();
else if(status == Status.PLAYING) whilePlaying();
tick++;
}
public static void resetWorldborder(String worldName){
worldBorder = new Border();
worldBorder.resetWorldborder(worldName);
}
public static void broadcastMessage(String message) {
for(Player player : Board.getPlayers()) {
player.sendMessage(message);
}
}
public static void resetPlayer(Player player) {
player.getInventory().clear();
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
if (Board.isSeeker(player)) {
if(pvpEnabled)
for(ItemStack item : Items.SEEKER_ITEMS)
player.getInventory().addItem(item);
for(PotionEffect effect : Items.SEEKER_EFFECTS)
player.addPotionEffect(effect);
} else if (Board.isHider(player)) {
if(pvpEnabled)
for(ItemStack item : Items.HIDER_ITEMS)
player.getInventory().addItem(item);
for(PotionEffect effect : Items.HIDER_EFFECTS)
player.addPotionEffect(effect);
if(glowEnabled) {
assert XMaterial.SNOWBALL.parseMaterial() != null;
ItemStack snowball = new ItemStack(XMaterial.SNOWBALL.parseMaterial(), 1);
ItemMeta snowballMeta = snowball.getItemMeta();
assert snowballMeta != null;
snowballMeta.setDisplayName("Glow Powerup");
List snowballLore = new ArrayList<>();
snowballLore.add("Throw to make all seekers glow");
snowballLore.add("Last 30s, all hiders can see it");
snowballLore.add("Time stacks on multi use");
snowballMeta.setLore(snowballLore);
snowball.setItemMeta(snowballMeta);
player.getInventory().addItem(snowball);
}
}
}
public static void join(Player player){
if(Game.status == Status.STANDBY) {
player.teleport(new Location(Bukkit.getWorld(lobbyWorld), lobbyPosition.getX(),lobbyPosition.getY(),lobbyPosition.getZ()));
player.getInventory().clear();
if(lobbyStartItem != null && (!lobbyItemStartAdmin || player.hasPermission("hideandseek.start")))
player.getInventory().setItem(lobbyItemStartPosition, lobbyStartItem);
if(lobbyLeaveItem != null)
player.getInventory().setItem(lobbyItemLeavePosition, lobbyLeaveItem);
Board.addHider(player);
if(announceMessagesToNonPlayers) Bukkit.broadcastMessage(messagePrefix + message("GAME_JOIN").addPlayer(player));
else Game.broadcastMessage(messagePrefix + message("GAME_JOIN").addPlayer(player));
player.setGameMode(GameMode.ADVENTURE);
Board.createLobbyBoard(player);
Board.reloadLobbyBoards();
} else {
Board.addSpectator(player);
player.sendMessage(messagePrefix + message("GAME_JOIN_SPECTATOR"));
player.setGameMode(GameMode.SPECTATOR);
Board.createGameBoard(player);
player.teleport(new Location(Bukkit.getWorld("hideandseek_"+spawnWorld), spawnPosition.getX(),spawnPosition.getY(),spawnPosition.getZ()));
Titles.sendTitle(player, 10, 70, 20, ChatColor.GRAY + "" + ChatColor.BOLD + "SPECTATING", ChatColor.WHITE + message("SPECTATOR_SUBTITLE").toString());
}
player.setFoodLevel(20);
if(Version.atLeast("1.9")) {
AttributeInstance attribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
if (attribute != null) player.setHealth(attribute.getValue());
} else {
player.setHealth(player.getMaxHealth());
}
}
public static void leave(Player player){
if(announceMessagesToNonPlayers) Bukkit.broadcastMessage(messagePrefix + message("GAME_LEAVE").addPlayer(player));
else Game.broadcastMessage(messagePrefix + message("GAME_LEAVE").addPlayer(player));
Board.removeBoard(player);
Board.remove(player);
player.getInventory().clear();
if(Game.status == Status.STANDBY) {
Board.reloadLobbyBoards();
} else {
Board.reloadGameBoards();
Board.reloadBoardTeams();
}
if(bungeeLeave) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(leaveServer);
player.sendPluginMessage(Main.plugin, "BungeeCord", out.toByteArray());
} else {
player.teleport(new Location(Bukkit.getWorld(exitWorld), exitPosition.getX(), exitPosition.getY(), exitPosition.getZ()));
}
}
public static void removeItems(Player player){
for(ItemStack si : Items.SEEKER_ITEMS)
for(ItemStack i : player.getInventory().getContents())
if(si.isSimilar(i)) player.getInventory().remove(i);
for(ItemStack hi : Items.HIDER_ITEMS)
for(ItemStack i : player.getInventory().getContents())
if(hi.isSimilar(i)) player.getInventory().remove(i);
}
private static void whileWaiting() {
if(lobbyCountdownEnabled){
if(lobbyMin <= Board.size()){
if(countdownTime == -1)
countdownTime = countdown;
if(Board.size() >= changeCountdown)
countdownTime = Math.min(countdownTime, 10);
if(tick % 20 == 0) {
countdownTime--;
Board.reloadLobbyBoards();
}
if(countdownTime == 0){
start();
}
} else {
countdownTime = -1;
}
}
}
private static void whileStarting(){
checkWinConditions();
}
private static void whilePlaying() {
for(Player hider : Board.getHiders()) {
int distance = 100, temp = 100;
for(Player seeker : Board.getSeekers()) {
try {
temp = (int) hider.getLocation().distance(seeker.getLocation());
} catch (Exception e){
//Players in different worlds, NOT OK!!!
}
if(distance > temp) {
distance = temp;
}
}
if(seekerPing) switch(tick%10) {
case 0:
if(distance < seekerPingLevel1) XSound.BLOCK_NOTE_BLOCK_BASEDRUM.play(hider, .5f, 1f);
if(distance < seekerPingLevel3) XSound.BLOCK_NOTE_BLOCK_PLING.play(hider, .3f, 1f);
break;
case 3:
if(distance < seekerPingLevel1) XSound.BLOCK_NOTE_BLOCK_BASEDRUM.play(hider, .3f, 1f);
if(distance < seekerPingLevel3) XSound.BLOCK_NOTE_BLOCK_PLING.play(hider, .3f, 1f);
break;
case 6:
if(distance < seekerPingLevel3) XSound.BLOCK_NOTE_BLOCK_PLING.play(hider, .3f, 1f);
break;
case 9:
if(distance < seekerPingLevel2) XSound.BLOCK_NOTE_BLOCK_PLING.play(hider, .3f, 1f);
break;
}
}
if(tick%20 == 0) {
if(gameLength > 0) {
Board.reloadGameBoards();
timeLeft--;
}
if(worldborderEnabled) worldBorder.update();
if(tauntEnabled) taunt.update();
if (glowEnabled) glow.update();
}
checkWinConditions();
}
private static void checkWinConditions(){
if(Board.sizeHider() < 1) {
if(announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameoverPrefix + message("GAME_GAMEOVER_HIDERS_FOUND"));
else broadcastMessage(gameoverPrefix + message("GAME_GAMEOVER_HIDERS_FOUND"));
end(WinType.SEEKER_WIN);
} else if(Board.sizeSeeker() < 1) {
if(announceMessagesToNonPlayers) Bukkit.broadcastMessage(abortPrefix + message("GAME_GAMEOVER_SEEKERS_QUIT"));
else broadcastMessage(abortPrefix + message("GAME_GAMEOVER_SEEKERS_QUIT"));
end(WinType.NONE);
} else if(timeLeft < 1) {
if(announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameoverPrefix + message("GAME_GAMEOVER_TIME"));
else broadcastMessage(gameoverPrefix + message("GAME_GAMEOVER_TIME"));
end(WinType.HIDER_WIN);
}
}
private static void sendDelayedMessage(String message, int gameId, int delay) {
Bukkit.getScheduler().runTaskLaterAsynchronously(Main.plugin, () -> {
if(gameId == Game.gameId)
broadcastMessage(message);
}, delay);
}
}
class Glow {
private int glowTime;
private boolean running;
public Glow() {
this.glowTime = 0;
}
public void onProjectile() {
if(glowStackable) glowTime += glowLength;
else glowTime = glowLength;
running = true;
}
private void sendPackets(){
for(Player hider : Board.getHiders())
for(Player seeker : Board.getSeekers())
Packet.setGlow(hider, seeker, true);
}
protected void update() {
if(running) {
sendPackets();
glowTime--;
glowTime = Math.max(glowTime, 0);
if (glowTime == 0) {
stopGlow();
}
}
}
private void stopGlow() {
running = false;
for(Player hider : Board.getHiders()) {
for (Player seeker : Board.getSeekers()) {
Packet.setGlow(hider, seeker, false);
}
}
}
public boolean isRunning() {
return running;
}
}
class Taunt {
private UUID tauntPlayer;
private int delay;
private boolean running;
public Taunt() {
this.delay = tauntDelay;
}
protected void update() {
if(delay == 0) {
if(running) launchTaunt();
else if(tauntLast || Board.sizeHider() > 1) executeTaunt();
} else {
delay--;
delay = Math.max(delay, 0);
}
}
private void executeTaunt() {
Optional rand = Board.getHiders().stream().skip(new Random().nextInt(Board.size())).findFirst();
if(!rand.isPresent()){
Main.plugin.getLogger().warning("Failed to select random seeker.");
return;
}
Player taunted = rand.get();
taunted.sendMessage(message("TAUNTED").toString());
broadcastMessage(tauntPrefix + message("TAUNT"));
tauntPlayer = taunted.getUniqueId();
running = true;
delay = 30;
}
private void launchTaunt(){
Player taunted = Board.getPlayer(tauntPlayer);
if(taunted != null) {
if(!Board.isHider(taunted)){
Main.plugin.getLogger().info("Taunted played died and is now seeker. Skipping taunt.");
tauntPlayer = null;
running = false;
delay = tauntDelay;
return;
}
World world = taunted.getLocation().getWorld();
if(world == null){
Main.plugin.getLogger().severe("Game world is null while trying to launch taunt.");
tauntPlayer = null;
running = false;
delay = tauntDelay;
return;
}
Firework fw = (Firework) world.spawnEntity(taunted.getLocation(), EntityType.FIREWORK);
FireworkMeta fwm = fw.getFireworkMeta();
fwm.setPower(4);
fwm.addEffect(FireworkEffect.builder()
.withColor(Color.BLUE)
.withColor(Color.RED)
.withColor(Color.YELLOW)
.with(FireworkEffect.Type.STAR)
.with(FireworkEffect.Type.BALL)
.with(FireworkEffect.Type.BALL_LARGE)
.flicker(true)
.withTrail()
.build());
fw.setFireworkMeta(fwm);
broadcastMessage(tauntPrefix + message("TAUNT_ACTIVATE"));
}
tauntPlayer = null;
running = false;
delay = tauntDelay;
}
public int getDelay(){
return delay;
}
public boolean isRunning() {
return running;
}
}
class Border {
private int delay;
private boolean running;
public Border() {
delay = 60 * worldborderDelay;
}
void update(){
if(delay == 30 && !running){
broadcastMessage(worldborderPrefix + message("WORLDBORDER_WARN"));
} else if(delay == 0){
if(running){
delay = 60 * worldborderDelay;
running = false;
}
else decreaceWorldborder();
}
delay--;
}
private void decreaceWorldborder() {
if(currentWorldborderSize == 100) return;
int change = worldborderChange;
if(currentWorldborderSize-worldborderChange < 100){
change = currentWorldborderSize-100;
}
running = true;
broadcastMessage(worldborderPrefix + message("WORLDBORDER_DECREASING").addAmount(change));
currentWorldborderSize -= worldborderChange;
World world = Bukkit.getWorld("hideandseek_"+spawnWorld);
assert world != null;
org.bukkit.WorldBorder border = world.getWorldBorder();
border.setSize(border.getSize()-change,30);
delay = 30;
}
public void resetWorldborder(String worldName) {
World world = Bukkit.getWorld(worldName);
assert world != null;
org.bukkit.WorldBorder border = world.getWorldBorder();
if(worldborderEnabled) {
border.setSize(worldborderSize);
border.setCenter(worldborderPosition.getX(), worldborderPosition.getZ());
currentWorldborderSize = worldborderSize;
} else {
border.setSize(30000000);
border.setCenter(0, 0);
}
}
public int getDelay(){
return delay;
}
public boolean isRunning() {
return running;
}
}