Game.java 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. /*
  2. * This file is part of MissileWars (https://github.com/Butzlabben/missilewars).
  3. * Copyright (c) 2018-2021 Daniel Nägele.
  4. *
  5. * MissileWars is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * MissileWars is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with MissileWars. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. package de.butzlabben.missilewars.game;
  19. import de.butzlabben.missilewars.Logger;
  20. import de.butzlabben.missilewars.MissileWars;
  21. import de.butzlabben.missilewars.configuration.Config;
  22. import de.butzlabben.missilewars.configuration.Messages;
  23. import de.butzlabben.missilewars.configuration.arena.Arena;
  24. import de.butzlabben.missilewars.configuration.lobby.Lobby;
  25. import de.butzlabben.missilewars.event.GameStartEvent;
  26. import de.butzlabben.missilewars.event.GameStopEvent;
  27. import de.butzlabben.missilewars.game.enums.GameResult;
  28. import de.butzlabben.missilewars.game.enums.GameState;
  29. import de.butzlabben.missilewars.game.enums.MapChooseProcedure;
  30. import de.butzlabben.missilewars.game.enums.TeamType;
  31. import de.butzlabben.missilewars.game.equipment.EquipmentManager;
  32. import de.butzlabben.missilewars.game.misc.MotdManager;
  33. import de.butzlabben.missilewars.game.misc.ScoreboardManager;
  34. import de.butzlabben.missilewars.game.misc.TeamSpawnProtection;
  35. import de.butzlabben.missilewars.game.schematics.SchematicFacing;
  36. import de.butzlabben.missilewars.game.schematics.objects.Missile;
  37. import de.butzlabben.missilewars.game.schematics.objects.Shield;
  38. import de.butzlabben.missilewars.game.signs.MWSign;
  39. import de.butzlabben.missilewars.game.stats.FightStats;
  40. import de.butzlabben.missilewars.game.timer.EndTimer;
  41. import de.butzlabben.missilewars.game.timer.GameTimer;
  42. import de.butzlabben.missilewars.game.timer.LobbyTimer;
  43. import de.butzlabben.missilewars.game.timer.TaskManager;
  44. import de.butzlabben.missilewars.listener.game.EndListener;
  45. import de.butzlabben.missilewars.listener.game.GameBoundListener;
  46. import de.butzlabben.missilewars.listener.game.GameListener;
  47. import de.butzlabben.missilewars.listener.game.LobbyListener;
  48. import de.butzlabben.missilewars.player.MWPlayer;
  49. import de.butzlabben.missilewars.util.PlayerUtil;
  50. import de.butzlabben.missilewars.util.geometry.GameArea;
  51. import de.butzlabben.missilewars.util.geometry.Geometry;
  52. import de.butzlabben.missilewars.util.serialization.Serializer;
  53. import lombok.Getter;
  54. import lombok.ToString;
  55. import org.bukkit.*;
  56. import org.bukkit.entity.Fireball;
  57. import org.bukkit.entity.Player;
  58. import org.bukkit.entity.Snowball;
  59. import org.bukkit.event.HandlerList;
  60. import org.bukkit.inventory.ItemStack;
  61. import org.bukkit.inventory.meta.ItemMeta;
  62. import org.bukkit.scheduler.BukkitTask;
  63. import org.bukkit.util.Vector;
  64. import java.util.*;
  65. import java.util.function.Consumer;
  66. /**
  67. * @author Butzlabben
  68. * @since 01.01.2018
  69. */
  70. @Getter
  71. @ToString(of = {"gameWorld", "players", "lobby", "arena", "state"})
  72. public class Game {
  73. private static final Map<String, Integer> cycles = new HashMap<>();
  74. private static int fights = 0;
  75. private final Map<UUID, MWPlayer> players = new HashMap<>();
  76. private final MapVoting mapVoting = new MapVoting(this);
  77. private final Lobby lobby;
  78. private final Map<UUID, BukkitTask> playerTasks = new HashMap<>();
  79. private final List<Location> portalBlocks = new ArrayList<>();
  80. private GameState state = GameState.LOBBY;
  81. private TeamManager teamManager;
  82. private boolean ready = false;
  83. private boolean restart = false;
  84. private GameWorld gameWorld;
  85. private GameArea gameArea;
  86. private GameArea innerGameArea;
  87. private long timestart;
  88. private Arena arena;
  89. private ScoreboardManager scoreboardManager;
  90. private GameJoinManager gameJoinManager;
  91. private GameLeaveManager gameLeaveManager;
  92. private GameBoundListener listener;
  93. private EquipmentManager equipmentManager;
  94. private TaskManager taskManager;
  95. private int remainingGameDuration;
  96. public Game(Lobby lobby) {
  97. Logger.BOOT.log("Loading lobby \"" + lobby.getName() + "\".");
  98. this.lobby = lobby;
  99. if (lobby.getBukkitWorld() == null) {
  100. Logger.ERROR.log("Lobby world \"" + lobby.getName() + "\" must not be null");
  101. return;
  102. }
  103. try {
  104. Serializer.setWorldAtAllLocations(lobby, lobby.getBukkitWorld());
  105. } catch (Exception exception) {
  106. Logger.ERROR.log("Could not inject world object at lobby \"" + lobby.getName() + "\".");
  107. exception.printStackTrace();
  108. return;
  109. }
  110. if (lobby.getPossibleArenas().isEmpty()) {
  111. Logger.ERROR.log("At least one valid arena must be set at lobby \"" + lobby.getName() + "\".");
  112. return;
  113. }
  114. if (lobby.getPossibleArenas().stream().noneMatch(Arenas::existsArena)) {
  115. Logger.ERROR.log("None of the specified arenas match a real arena for the lobby \"" + lobby.getName() + "\".");
  116. return;
  117. }
  118. teamManager = new TeamManager(this);
  119. Logger.DEBUG.log("Registering, teleporting, etc. all players");
  120. updateMOTD();
  121. Logger.DEBUG.log("Start timer");
  122. taskManager = new TaskManager(this);
  123. taskManager.stopTimer();
  124. updateGameListener(new LobbyListener(this));
  125. taskManager.setTimer(new LobbyTimer(this, lobby.getLobbyTime()));
  126. taskManager.runTimer(0, 20);
  127. state = GameState.LOBBY;
  128. Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> applyForAllPlayers(player -> gameJoinManager.runTeleportEventForPlayer(player)), 2);
  129. if (Config.isSetup()) {
  130. Logger.WARN.log("Did not fully initialize lobby \"" + lobby.getName() + "\" as the plugin is in setup mode");
  131. return;
  132. }
  133. scoreboardManager = new ScoreboardManager(this);
  134. gameJoinManager = new GameJoinManager(this);
  135. gameLeaveManager = new GameLeaveManager(this);
  136. // choose the game arena
  137. if (lobby.getMapChooseProcedure() == MapChooseProcedure.FIRST) {
  138. setArena(lobby.getArenas().get(0));
  139. prepareGame();
  140. } else if (lobby.getMapChooseProcedure() == MapChooseProcedure.MAPCYCLE) {
  141. final int lastMapIndex = cycles.getOrDefault(lobby.getName(), -1);
  142. List<Arena> arenas = lobby.getArenas();
  143. int index = lastMapIndex >= arenas.size() - 1 ? 0 : lastMapIndex + 1;
  144. cycles.put(lobby.getName(), index);
  145. setArena(arenas.get(index));
  146. prepareGame();
  147. } else if (lobby.getMapChooseProcedure() == MapChooseProcedure.MAPVOTING) {
  148. if (mapVoting.onlyOneArenaFound()) {
  149. setArena(lobby.getArenas().get(0));
  150. Logger.WARN.log("Only one arena was found for the lobby \"" + lobby.getName() + "\". The configured map voting was skipped.");
  151. prepareGame();
  152. } else {
  153. mapVoting.startVote();
  154. updateGameInfo();
  155. }
  156. }
  157. }
  158. /**
  159. * This method performs the final preparations for the game start.
  160. * <p>
  161. * It is necessary that the arena - even in the case of a map vote - is
  162. * now already defined.
  163. */
  164. public void prepareGame() {
  165. if (this.arena == null) {
  166. throw new IllegalStateException("The arena is not yet set");
  167. }
  168. updateGameInfo();
  169. equipmentManager = new EquipmentManager(this);
  170. equipmentManager.createGameItems();
  171. Logger.DEBUG.log("Making game ready");
  172. ++fights;
  173. checkFightRestart();
  174. FightStats.checkTables();
  175. Logger.DEBUG.log("Fights: " + fights);
  176. ready = true;
  177. }
  178. private void checkFightRestart() {
  179. if (Config.getFightRestart() <= 0) return;
  180. if (fights >= Config.getFightRestart()) restart = true;
  181. }
  182. private void updateGameListener(GameBoundListener newListener) {
  183. if (listener != null) HandlerList.unregisterAll(listener);
  184. Bukkit.getPluginManager().registerEvents(newListener, MissileWars.getInstance());
  185. this.listener = newListener;
  186. }
  187. private void updateMOTD() {
  188. if (!Config.isMultipleLobbies()) {
  189. MotdManager.getInstance().updateMOTD(this);
  190. }
  191. }
  192. public void startGame() {
  193. if (Config.isSetup()) {
  194. Logger.WARN.log("Did not start game. Setup mode is still enabled");
  195. return;
  196. }
  197. World world = gameWorld.getWorld();
  198. if (world == null) {
  199. Logger.ERROR.log("Could not start game in arena \"" + arena.getName() + "\". World is null");
  200. return;
  201. }
  202. taskManager.stopTimer();
  203. updateGameListener(new GameListener(this));
  204. taskManager.setTimer(new GameTimer(this));
  205. taskManager.runTimer(5, 20);
  206. state = GameState.INGAME;
  207. timestart = System.currentTimeMillis();
  208. applyForAllPlayers(player -> gameJoinManager.startForPlayer(player, true));
  209. updateMOTD();
  210. Bukkit.getPluginManager().callEvent(new GameStartEvent(this));
  211. }
  212. public void stopGame() {
  213. if (Config.isSetup()) return;
  214. Logger.DEBUG.log("Stopping");
  215. for (BukkitTask bt : playerTasks.values()) {
  216. bt.cancel();
  217. }
  218. Logger.DEBUG.log("Stopping for players");
  219. for (Player player : gameWorld.getWorld().getPlayers()) {
  220. Logger.DEBUG.log("Stopping for: " + player.getName());
  221. player.setGameMode(GameMode.SPECTATOR);
  222. teleportToArenaSpectatorSpawn(player);
  223. }
  224. // Save the remaining game duration.
  225. remainingGameDuration = taskManager.getTimer().getSeconds();
  226. taskManager.stopTimer();
  227. updateGameListener(new EndListener(this));
  228. taskManager.setTimer(new EndTimer(this));
  229. taskManager.runTimer(5, 20);
  230. state = GameState.END;
  231. updateMOTD();
  232. if (arena.isSaveStatistics()) {
  233. FightStats stats = new FightStats(this);
  234. stats.insert();
  235. }
  236. Logger.DEBUG.log("Stopped completely");
  237. Bukkit.getPluginManager().callEvent(new GameStopEvent(this));
  238. }
  239. public void reset() {
  240. if (Config.isSetup()) return;
  241. if (restart) {
  242. Bukkit.getServer().spigot().restart();
  243. return;
  244. }
  245. GameManager.getInstance().restartGame(lobby, false);
  246. }
  247. public void appendRestart() {
  248. restart = true;
  249. }
  250. public void disableGameOnServerStop() {
  251. for (MWPlayer mwPlayer : players.values()) {
  252. teleportToFallbackSpawn(mwPlayer.getPlayer());
  253. }
  254. if (gameWorld != null) gameWorld.unload();
  255. }
  256. public void resetGame() {
  257. // Teleporting players; the event listener will handle the teleport event
  258. applyForAllPlayers(this::teleportToAfterGameSpawn);
  259. // Deactivation of all event handlers
  260. HandlerList.unregisterAll(listener);
  261. taskManager.stopTimer();
  262. if (gameWorld != null) {
  263. gameWorld.unload();
  264. gameWorld.delete();
  265. }
  266. if (scoreboardManager != null) {
  267. scoreboardManager.removeScoreboard();
  268. }
  269. }
  270. /**
  271. * This method checks if the location is inside in the Lobby-Area.
  272. *
  273. * @param location (Location) the location to be checked
  274. *
  275. * @return true, if it's in the Lobby-Area
  276. */
  277. public boolean isInLobbyArea(Location location) {
  278. return Geometry.isInsideIn(location, lobby.getArea());
  279. }
  280. /**
  281. * This method checks if the location is inside in the Game-Area.
  282. *
  283. * @param location (Location) the location to be checked
  284. *
  285. * @return true, if it's in the Game-Area
  286. */
  287. public boolean isInGameArea(Location location) {
  288. return Geometry.isInsideIn(location, gameArea);
  289. }
  290. /**
  291. * This method checks if the location is inside in the Inner Game-Area.
  292. * It's the arena from the Team 1 spawn position to the Team 2 spawn
  293. * position ("length") with the same "width" of the (major) Game-Area.
  294. *
  295. * @param location (Location) the location to be checked
  296. *
  297. * @return true, if it's in the Inner Game-Area
  298. */
  299. public boolean isInInnerGameArea(Location location) {
  300. return Geometry.isInsideIn(location, innerGameArea);
  301. }
  302. /**
  303. * This method checks if the location is in the game world.
  304. *
  305. * @param location (Location) the location to be checked
  306. *
  307. * @return true, if it's in the game world
  308. */
  309. public boolean isInGameWorld(Location location) {
  310. // Is possible during the map voting phase:
  311. if (gameArea == null) return false;
  312. return Geometry.isInWorld(location, gameArea.getWorld());
  313. }
  314. /**
  315. * This (shortcut) method checks if the location is inside in the
  316. * Lobby-Area or inside in the game world.
  317. *
  318. * @param location (Location) the location to be checked
  319. *
  320. * @return true, if the statement is correct
  321. */
  322. public boolean isIn(Location location) {
  323. return isInLobbyArea(location) || isInGameWorld(location);
  324. }
  325. public MWPlayer getPlayer(Player player) {
  326. return players.get(player.getUniqueId());
  327. }
  328. /**
  329. * This method finally removes the player from the game player array. Besides former
  330. * team members, it also affects spectators.
  331. */
  332. public void removePlayer(MWPlayer mwPlayer) {
  333. players.remove(mwPlayer.getUuid());
  334. }
  335. public void broadcast(String message) {
  336. for (MWPlayer mwPlayer : players.values()) {
  337. Player player = mwPlayer.getPlayer();
  338. if (player != null && player.isOnline()) player.sendMessage(message);
  339. }
  340. }
  341. /**
  342. * This method sets the player attributes (game mode, level, enchantments, ...).
  343. *
  344. * @param player the target player
  345. */
  346. public void setPlayerAttributes(Player player) {
  347. player.setGameMode(GameMode.SURVIVAL);
  348. player.setLevel(0);
  349. player.setFireTicks(0);
  350. }
  351. /**
  352. * This method respawns the player after a short time.
  353. *
  354. * @param mwPlayer the target MissileWars player
  355. */
  356. public void autoRespawnPlayer(MWPlayer mwPlayer) {
  357. Bukkit.getScheduler().runTaskLater(MissileWars.getInstance(), () -> {
  358. TeamSpawnProtection.regenerateSpawn(mwPlayer.getTeam());
  359. mwPlayer.getPlayer().spigot().respawn();
  360. }, 20L);
  361. }
  362. /**
  363. * This method spawns the missile for the player.
  364. *
  365. * @param player the executing player
  366. * @param itemStack the spawn egg
  367. */
  368. public void spawnMissile(Player player, ItemStack itemStack) {
  369. // Are missiles only allowed to spawn inside the arena, between the two arena spawn points?
  370. boolean isOnlyBetweenSpawnPlaceable = this.arena.getMissileConfiguration().isOnlyBetweenSpawnPlaceable();
  371. if (isOnlyBetweenSpawnPlaceable) {
  372. if (!isInInnerGameArea(player.getLocation())) {
  373. player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.ARENA_MISSILE_PLACE_DENY));
  374. return;
  375. }
  376. }
  377. ItemMeta itemMeta = itemStack.getItemMeta();
  378. if (itemMeta == null) return;
  379. Missile missile = (Missile) this.arena.getMissileConfiguration().getSchematicFromDisplayName(itemMeta.getDisplayName());
  380. if (missile == null) {
  381. player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.COMMAND_INVALID_MISSILE)
  382. .replace("%input%", itemMeta.getDisplayName()));
  383. return;
  384. }
  385. itemStack.setAmount(itemStack.getAmount() - 1);
  386. player.setItemInHand(itemStack);
  387. missile.paste(this, player, SchematicFacing.getFacingPlayer(player, this.arena.getMissileConfiguration()));
  388. }
  389. /**
  390. * This method spawns the shield after his flight route.
  391. *
  392. * @param player the executing player
  393. * @param ball the snowball
  394. */
  395. public void spawnShield(Player player, Snowball ball) {
  396. ItemMeta itemMeta = ball.getItem().getItemMeta();
  397. if (itemMeta == null) return;
  398. Shield shield = (Shield) this.arena.getShieldConfiguration().getSchematicFromDisplayName(itemMeta.getDisplayName());
  399. if (shield == null) {
  400. player.sendMessage(Messages.getMessage(true, Messages.MessageEnum.COMMAND_INVALID_SHIELD)
  401. .replace("%input%", itemMeta.getDisplayName()));
  402. return;
  403. }
  404. shield.paste(ball);
  405. player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1, 1);
  406. }
  407. /**
  408. * This method spawns the fireball for the player.
  409. *
  410. * @param player the executing player
  411. */
  412. public void spawnFireball(Player player, ItemStack itemStack) {
  413. int amount = itemStack.getAmount();
  414. itemStack.setAmount(amount - 1);
  415. Fireball fb = player.launchProjectile(Fireball.class);
  416. fb.setDirection(player.getLocation().getDirection().multiply(2.5D));
  417. player.playSound(fb.getLocation(), Sound.BLOCK_ANVIL_LAND, 100.0F, 2.0F);
  418. player.playSound(fb.getLocation(), Sound.ITEM_FLINTANDSTEEL_USE, 100.0F, 1.0F);
  419. fb.setYield(3F);
  420. fb.setIsIncendiary(true);
  421. fb.setBounce(false);
  422. }
  423. public void setArena(Arena arena) {
  424. if (this.arena != null) {
  425. throw new IllegalStateException("Arena already set");
  426. }
  427. arena.getMissileConfiguration().check();
  428. arena.getShieldConfiguration().check();
  429. this.arena = arena.clone();
  430. gameWorld = new GameWorld(this, arena.getTemplateWorld());
  431. gameWorld.load();
  432. gameArea = new GameArea(gameWorld.getWorld(), arena.getAreaConfig());
  433. try {
  434. Serializer.setWorldAtAllLocations(this.arena, gameWorld.getWorld());
  435. teamManager.getTeam1().setSpawn(this.arena.getTeam1Spawn());
  436. teamManager.getTeam2().setSpawn(this.arena.getTeam2Spawn());
  437. teamManager.getTeamSpec().setSpawn(this.arena.getSpectatorSpawn());
  438. } catch (Exception exception) {
  439. Logger.ERROR.log("Could not inject world object at arena " + this.arena.getName());
  440. exception.printStackTrace();
  441. return;
  442. }
  443. createInnerGameArea();
  444. savePortalPositions();
  445. }
  446. /**
  447. * This method goes through all blocks within the arena and saves the portal
  448. * block positions so that they can be checked regularly during the game.
  449. */
  450. private void savePortalPositions() {
  451. long startTime = System.currentTimeMillis();
  452. int minX = gameArea.getMinX();
  453. int minY = gameArea.getMinY();
  454. int minZ = gameArea.getMinZ();
  455. int maxX = gameArea.getMaxX();
  456. int maxY = gameArea.getMaxY();
  457. int maxZ = gameArea.getMaxZ();
  458. for (int x = minX; x <= maxX; x++) {
  459. for (int y = minY; y <= maxY; y++) {
  460. for (int z = minZ; z <= maxZ; z++) {
  461. if (gameWorld.getWorld().getBlockAt(x, y, z).getType() == Material.NETHER_PORTAL)
  462. portalBlocks.add(new Location(gameWorld.getWorld(), x, y, z));
  463. }
  464. }
  465. }
  466. long endTime = System.currentTimeMillis();
  467. Logger.DEBUG.log("[Portal Position-Cache] Time reached for Portal-Counting: " + (endTime - startTime) + " ms.");
  468. Logger.DEBUG.log("[Portal Position-Cache] Founded " + portalBlocks.size() + " Portal blocks.");
  469. }
  470. private void createInnerGameArea() {
  471. // Depending on the rotation of the (major) Game-Area, the spawn points
  472. // of both teams are primarily on the X or Z axis opposite each other.
  473. // The Inner Game-Area is a copy of the (major) Game-Area, with the X or Z
  474. // axis going only to spawn. The X or Z distance is thus reduced.
  475. // So this algorithm allows the spawn points to face each other even if
  476. // they are offset.
  477. int x1, x2, z1, z2;
  478. Location position1, position2;
  479. if (gameArea.getDirection() == GameArea.Direction.NORTH_SOUTH) {
  480. x1 = gameArea.getMinX();
  481. x2 = gameArea.getMaxX();
  482. z1 = teamManager.getTeam1().getSpawn().getBlockZ();
  483. z2 = teamManager.getTeam2().getSpawn().getBlockZ();
  484. } else {
  485. z1 = gameArea.getMinZ();
  486. z2 = gameArea.getMaxZ();
  487. x1 = teamManager.getTeam1().getSpawn().getBlockX();
  488. x2 = teamManager.getTeam2().getSpawn().getBlockX();
  489. }
  490. position1 = new Location(gameArea.getWorld(), x1, gameArea.getMinY(), z1);
  491. position2 = new Location(gameArea.getWorld(), x2, gameArea.getMaxY(), z2);
  492. innerGameArea = new GameArea(position1, position2);
  493. }
  494. public void applyForAllPlayers(Consumer<Player> consumer) {
  495. for (Player player : Bukkit.getOnlinePlayers()) {
  496. if (!isIn(player.getLocation())) continue;
  497. consumer.accept(player);
  498. }
  499. }
  500. /**
  501. * This method manages the message output of the game result.
  502. * Each player who is currently in the arena world gets a
  503. * customized message.
  504. */
  505. public void sendGameResult() {
  506. GameResultManager resultManager = new GameResultManager(this);
  507. resultManager.executeResult();
  508. }
  509. /**
  510. * This method updates the MissileWars signs and the scoreboard.
  511. */
  512. public void updateGameInfo() {
  513. MissileWars.getInstance().getSignRepository().getSigns(this).forEach(MWSign::update);
  514. scoreboardManager.resetScoreboard();
  515. if (state == GameState.LOBBY) players.forEach((uuid, mwPlayer) -> mwPlayer.getGameJoinMenu().getMenu());
  516. Logger.DEBUG.log("Updated signs, scoreboard and menus.");
  517. }
  518. /**
  519. * This method checks whether there are too few players in
  520. * the lobby based of the configuration. (Spectators are not
  521. * counted here!)
  522. *
  523. * @return (boolean) 'true' if to few players are in the lobby
  524. */
  525. public boolean areToFewPlayers() {
  526. int minSize = lobby.getMinPlayers();
  527. int currentSize = teamManager.getTeam1().getMembers().size() + teamManager.getTeam2().getMembers().size();
  528. return currentSize < minSize;
  529. }
  530. /**
  531. * This method checks whether there are too many players in
  532. * the lobby based of the configuration. (Spectators are not
  533. * counted here!)
  534. *
  535. * @return (boolean) 'true' if to many players are in the lobby
  536. */
  537. public boolean areTooManyPlayers() {
  538. int maxSize = lobby.getMaxPlayers();
  539. if (maxSize == -1) return false;
  540. return getPlayerAmount() > maxSize;
  541. }
  542. /**
  543. * This method checks whether there are too many spectators in
  544. * the lobby based of the configuration.
  545. *
  546. * @return (boolean) 'true' if to many spectators are in the lobby
  547. */
  548. public boolean areTooManySpectators() {
  549. int maxSize = lobby.getMaxSpectators();
  550. if (maxSize == -1) return false;
  551. int currentSize = teamManager.getTeamSpec().getMembers().size();
  552. return currentSize > maxSize;
  553. }
  554. public int getGameDuration() {
  555. int time = 0;
  556. if (arena == null) return time;
  557. if (state == GameState.LOBBY) {
  558. // Show the planned duration of the next game:
  559. time = arena.getGameDuration();
  560. } else if (state == GameState.INGAME) {
  561. // Show the remaining duration of the running game:
  562. time = getTaskManager().getTimer().getSeconds() / 60;
  563. } else if (state == GameState.END) {
  564. // Show the remaining duration of the last game:
  565. time = getRemainingGameDuration() / 60;
  566. }
  567. return time;
  568. }
  569. public int getTotalGameUserAmount() {
  570. return players.size();
  571. }
  572. public int getPlayerAmount() {
  573. if ((teamManager.getTeam1() == null) || (teamManager.getTeam2() == null)) return 0;
  574. return teamManager.getTeam1().getMembers().size() + teamManager.getTeam2().getMembers().size();
  575. }
  576. public List<String> getPlayerList() {
  577. List<String> playerList = new ArrayList<>();
  578. players.values().forEach(mwPlayer -> playerList.add(mwPlayer.getPlayer().getName()));
  579. return playerList;
  580. }
  581. public static void knockbackEffect(Player player, Location from, Location to) {
  582. Vector addTo = from.toVector().subtract(to.toVector()).multiply(3);
  583. addTo.setY(0);
  584. player.teleport(from.add(addTo));
  585. }
  586. public void teleportToFallbackSpawn(Player player) {
  587. PlayerUtil.teleportSafely(player, Config.getFallbackSpawn());
  588. }
  589. public void teleportToLobbySpawn(Player player) {
  590. PlayerUtil.teleportSafely(player, lobby.getSpawnPoint());
  591. }
  592. public void teleportToArenaSpectatorSpawn(Player player) {
  593. PlayerUtil.teleportSafely(player, arena.getSpectatorSpawn());
  594. }
  595. public void teleportToAfterGameSpawn(Player player) {
  596. PlayerUtil.teleportSafely(player, lobby.getAfterGameSpawn());
  597. }
  598. /**
  599. * This method checks all previously saved portal positions to see whether the
  600. * portals are still intact. If not, the game-end is initiated.
  601. *
  602. * This could be a more performant version than using the high-frequency
  603. * "BlockPhysicsEvent" event-listener.
  604. */
  605. public void checkPortals() {
  606. for (Location location : portalBlocks) {
  607. if (location.getBlock().getType() == Material.NETHER_PORTAL) continue;
  608. runWinnerCheck(location);
  609. return;
  610. }
  611. }
  612. /**
  613. * This method determines the winning team based on the position of the destroyed
  614. * portal and brings the game to the game-phase "END".
  615. *
  616. * @param location (Location) the (first) detected portal-block location
  617. */
  618. private void runWinnerCheck(Location location) {
  619. Team team1 = getTeamManager().getTeam1();
  620. Team team2 = getTeamManager().getTeam2();
  621. if (Geometry.isCloser(location, team1.getSpawn(), team2.getSpawn())) {
  622. team1.setGameResult(GameResult.LOSE);
  623. team2.setGameResult(GameResult.WIN);
  624. } else {
  625. team1.setGameResult(GameResult.WIN);
  626. team2.setGameResult(GameResult.LOSE);
  627. }
  628. sendGameResult();
  629. stopGame();
  630. }
  631. }