package com.asteria.game;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import plugin.minigames.fightcaves.FightCavesHandler;
import com.asteria.game.character.CharacterList;
import com.asteria.game.character.CharacterNode;
import com.asteria.game.character.npc.Npc;
import com.asteria.game.character.npc.NpcUpdating;
import com.asteria.game.character.player.IOState;
import com.asteria.game.character.player.Player;
import com.asteria.game.character.player.PlayerUpdating;
import com.asteria.game.character.player.minigame.MinigameHandler;
import com.asteria.game.item.ItemNodeManager;
import com.asteria.game.location.Position;
import com.asteria.game.object.ObjectNodeManager;
import com.asteria.game.plugin.PluginHandler;
import com.asteria.game.shop.Shop;
import com.asteria.game.sync.GameSyncExecutor;
import com.asteria.game.sync.GameSyncTask;
import com.asteria.net.ConnectionHandler;
import com.asteria.net.PlayerIO;
import com.asteria.task.Task;
import com.asteria.task.TaskQueue;
import com.asteria.utility.LoggerUtils;
/**
* The static utility class that contains functions to manage and process game
* characters.
*
* @author lare96 <http://github.com/lare96>
*/
public final class World {
/**
* The logger that will print important information.
*/
private static Logger logger = LoggerUtils.getLogger(World.class);
/**
* The collection of active players.
*/
private static CharacterList<Player> players = new CharacterList<>(2000);
/**
* The collection of active NPCs.
*/
private static CharacterList<Npc> npcs = new CharacterList<>(5000);
/**
* The game service that processes this world.
*/
private static GameService service = new GameService();
/**
* The manager for the queue of game tasks.
*/
private static TaskQueue taskQueue = new TaskQueue();
/**
* The queue of {@link Player}s waiting to be logged in.
*/
private static Queue<Player> logins = new ConcurrentLinkedQueue<>();
/**
* The queue of {@link Player}s waiting to be logged out.
*/
private static Queue<Player> logouts = new ConcurrentLinkedQueue<>();
/**
* The manager for the map of game plugins.
*/
private static PluginHandler plugins = new PluginHandler();
/**
* The manger for game synchronization.
*/
private static GameSyncExecutor executor = new GameSyncExecutor();
/**
* The default constructor, will throw an
* {@link UnsupportedOperationException} if instantiated.
*
* @throws UnsupportedOperationException
* if this class is instantiated.
*/
private World() {
throw new UnsupportedOperationException("This class cannot be instantiated!");
}
/**
* The method that executes the update sequence for all in game characters
* every cycle. The update sequence may either run sequentially or
* concurrently depending on the type of engine selected by the server.
*
* @throws Exception
* if any errors occur during the update sequence.
*/
public static void sequence() throws Exception {
// Handle queued logins.
for (int amount = 0; amount < GameConstants.LOGIN_THRESHOLD; amount++) {
Player player = logins.poll();
if (player == null)
break;
if (!players.add(player))
player.dispose();
}
// Handle queued logouts.
int amount = 0;
Iterator<Player> $it = logouts.iterator();
while ($it.hasNext()) {
Player player = $it.next();
if (player == null || amount >= GameConstants.LOGOUT_THRESHOLD)
break;
if (handleLogout(player)) {
$it.remove();
amount++;
}
}
// Handle task processing.
taskQueue.sequence();
// Handle synchronization tasks.
executor.sync(new GameSyncTask(NodeType.PLAYER, false) {
@Override
public void execute(int index) {
Player player = players.get(index);
try {
player.getSession().handleQueuedMessages();
player.getMovementQueue().sequence();
player.sequence();
} catch (Exception e) {
e.printStackTrace();
World.getPlayers().remove(player);
}
}
});
executor.sync(new GameSyncTask(NodeType.NPC, false) {
@Override
public void execute(int index) {
Npc npc = npcs.get(index);
try {
npc.sequence();
npc.getMovementQueue().sequence();
} catch (Exception e) {
e.printStackTrace();
World.getNpcs().remove(npc);
}
}
});
executor.sync(new GameSyncTask(NodeType.PLAYER) {
@Override
public void execute(int index) {
Player player = players.get(index);
synchronized (player) {
try {
PlayerUpdating.update(player);
NpcUpdating.update(player);
} catch (Exception e) {
e.printStackTrace();
World.getPlayers().remove(player);
}
}
}
});
executor.sync(new GameSyncTask(NodeType.PLAYER) {
@Override
public void execute(int index) {
Player player = players.get(index);
synchronized (player) {
try {
player.reset();
player.setCachedUpdateBlock(null);
} catch (Exception e) {
e.printStackTrace();
World.getPlayers().remove(player);
}
}
}
});
executor.sync(new GameSyncTask(NodeType.NPC) {
@Override
public void execute(int index) {
Npc npc = npcs.get(index);
synchronized (npc) {
try {
npc.reset();
} catch (Exception e) {
e.printStackTrace();
World.getNpcs().remove(npc);
}
}
}
});
}
/**
* Queues {@code player} to be logged in on the next server sequence.
*
* @param player
* the player to log in.
*/
public static void queueLogin(Player player) {
PlayerIO session = player.getSession();
if (session.getState() == IOState.LOGGING_IN && !logins.contains(player))
logins.add(player);
}
/**
* Queues {@code player} to be logged out on the next server sequence.
*
* @param player
* the player to log out.
*/
public static void queueLogout(Player player) {
PlayerIO session = player.getSession();
if (session.getState() == IOState.LOGGED_IN && !logouts.contains(player)) {
if (player.getCombatBuilder().inCombat())
player.getLogoutTimer().reset();
logouts.add(player);
}
}
/**
* Submits {@code t} to the backing {@link TaskQueue}.
*
* @param t
* the task to submit to the queue.
*/
public static void submit(Task t) {
taskQueue.submit(t);
}
/**
* Returns a player within an optional whose name hash is equal to
* {@code username}.
*
* @param username
* the name hash to check the collection of players for.
* @return the player within an optional if found, or an empty optional if
* not found.
*/
public static Optional<Player> getPlayer(long username) {
return players.search(player -> player.getUsernameHash() == username);
}
/**
* Returns a player within an optional whose name is equal to
* {@code username}.
*
* @param username
* the name to check the collection of players for.
* @return the player within an optional if found, or an empty optional if
* not found.
*/
public static Optional<Player> getPlayer(String username) {
if (username == null)
return Optional.empty();
return players.search(player -> player.getUsername().equals(username));
}
/**
* Retrieves and returns the local {@link Player}s for {@code character}.
* The specific players returned is completely dependent on the character
* given in the argument.
*
* @param character
* the character that it will be returned for.
* @return the local players.
*/
public static Iterator<Player> getLocalPlayers(CharacterNode character) {
if (character.getType() == NodeType.PLAYER)
return ((Player) character).getLocalPlayers().iterator();
return players.iterator();
}
/**
* Retrieves and returns the local {@link Npc}s for {@code character}. The
* specific npcs returned is completely dependent on the character given in
* the argument.
*
* @param character
* the character that it will be returned for.
* @return the local npcs.
*/
public static Iterator<Npc> getLocalNpcs(CharacterNode character) {
if (character.getType() == NodeType.PLAYER)
return ((Player) character).getLocalNpcs().iterator();
return npcs.iterator();
}
/**
* Gets every single character in the player and npc character lists.
*
* @return a set containing every single character.
*/
public static Set<CharacterNode> getCharacters() {
Set<CharacterNode> characters = new HashSet<>();
players.forEach(characters::add);
npcs.forEach(characters::add);
return characters;
}
/**
* Gets every single node in the player, npc, object, and item lists.
*
* @return a list containing every single node.
*/
public static List<Node> getNodes() {
List<Node> nodes = new LinkedList<>();
players.forEach(nodes::add);
npcs.forEach(nodes::add);
ObjectNodeManager.OBJECTS.forEach(nodes::add);
ItemNodeManager.ITEMS.forEach(nodes::add);
return nodes;
}
/**
* Sends {@code message} to all online players.
*
* @param message
* the message to send to all online players.
*/
public static void message(String message) {
players.forEach(p -> p.getMessages().sendMessage("@red@[ANNOUNCEMENT]: " + message));
}
/**
* Performs all of the disconnection logic for {@code player} assuming they
* are in the logout queue.
*
* @param player
* the player to attempt to logout.
* @return {@code true} if the player was logged out, {@code false}
* otherwise.
*/
private static boolean handleLogout(Player player) {
try {
PlayerIO session = player.getSession();
// Close the channel no matter what happens, so it appears to the
// player that they have logged out.
session.getChannel().close();
// If the player x-logged, don't log the player out. Keep the
// player queued until they are out of combat to prevent x-logging.
if (!player.getLogoutTimer().elapsed(GameConstants.LOGOUT_SECONDS, TimeUnit.SECONDS) || player.getCombatBuilder()
.inCombat())
return false;
// Proceed to perform disconnection logic as normal, officially
// logging out the player.
session.setState(IOState.LOGGING_OUT);
if (player.getOpenShop() != null)
Shop.SHOPS.get(player.getOpenShop()).getPlayers().remove(player);
World.getTaskQueue().cancel(player);
player.setSkillAction(false);
World.getPlayers().remove(player);
MinigameHandler.execute(player, m -> m.onLogout(player));
player.getTradeSession().reset(false);
player.getPrivateMessage().updateOtherList(false);
if (FightCavesHandler.remove(player))
player.move(new Position(2399, 5177));
player.save();
ConnectionHandler.remove(session.getHost());
session.setState(IOState.LOGGED_OUT);
logger.info(session + " has logged out.");
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* Gets the collection of active players.
*
* @return the active players.
*/
public static CharacterList<Player> getPlayers() {
return players;
}
/**
* Gets the collection of active npcs.
*
* @return the active npcs.
*/
public static CharacterList<Npc> getNpcs() {
return npcs;
}
/**
* Returns the game service that processes this world.
*
* @return the game service.
*/
public static GameService getService() {
return service;
}
/**
* Gets the manager for the queue of game tasks.
*
* @return the queue of tasks.
*/
public static TaskQueue getTaskQueue() {
return taskQueue;
}
/**
* Sets the value for {@link World.java#taskQueue}.
*
* @param taskQueue
* the new value to set.
*/
public static void setTaskQueue(TaskQueue taskQueue) {
World.taskQueue = taskQueue;
}
/**
* Gets the manager for the map of game plugins.
*
* @return the manager for plugins.
*/
public static PluginHandler getPlugins() {
return plugins;
}
}