More UHC Fixes

Fixes:
PC-137
PC-138
PC-497
PC-665
PC-710
PC-719
PC-754
Also fixes chunk synchronization issue, plugs potential memory leaks

Fancy spectating

Use enum priorities
This commit is contained in:
samczsun 2016-07-04 21:07:02 -04:00
parent 8cf470cfd0
commit 63b6158fe4
20 changed files with 1553 additions and 1097 deletions

View File

@ -6,6 +6,7 @@ import mineplex.core.account.CoreClientManager;
import mineplex.core.boosters.command.BoosterCommand;
import mineplex.core.boosters.event.BoosterActivateEvent;
import mineplex.core.boosters.event.BoosterExpireEvent;
import mineplex.core.boosters.event.BoosterItemGiveEvent;
import mineplex.core.boosters.event.BoosterUpdateEvent;
import mineplex.core.boosters.gui.BoosterShop;
import mineplex.core.boosters.redis.BoosterUpdateRepository;
@ -14,6 +15,7 @@ import mineplex.core.common.util.C;
import mineplex.core.common.util.Callback;
import mineplex.core.common.util.UtilGear;
import mineplex.core.common.util.UtilInv;
import mineplex.core.common.util.UtilServer;
import mineplex.core.donation.DonationManager;
import mineplex.core.inventory.InventoryManager;
import mineplex.core.itemstack.ItemStackFactory;
@ -361,6 +363,11 @@ public class BoosterManager extends MiniPlugin
{
if (_giveInterfaceItem && !UtilGear.isMat(player.getInventory().getItem(INTERFACE_SLOT), Material.EMERALD))
{
BoosterItemGiveEvent event = new BoosterItemGiveEvent(player);
UtilServer.CallEvent(event);
if (event.isCancelled())
return;
player.getInventory().setItem(INTERFACE_SLOT, INTERFACE_ITEM);
UtilInv.Update(player);

View File

@ -0,0 +1,48 @@
package mineplex.core.boosters.event;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Called when a player is about to receive a booster gem. If cancelled the player will not receive said gem
*/
public class BoosterItemGiveEvent extends Event implements Cancellable
{
private Player _player;
private boolean _cancelled;
public BoosterItemGiveEvent(Player player)
{
this._player = player;
}
public Player getPlayer()
{
return this._player;
}
public boolean isCancelled()
{
return this._cancelled;
}
public void setCancelled(boolean cancelled)
{
this._cancelled = cancelled;
}
private static final HandlerList _handlers = new HandlerList();
private static HandlerList getHandlerList()
{
return _handlers;
}
@Override
public HandlerList getHandlers()
{
return getHandlerList();
}
}

View File

@ -1,12 +1,8 @@
package mineplex.core.packethandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import com.mineplex.spigot.PacketProcessor;
import mineplex.core.MiniPlugin;
import net.minecraft.server.v1_8_R3.Packet;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -15,16 +11,22 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import com.mineplex.spigot.PacketProcessor;
import mineplex.core.MiniPlugin;
import mineplex.core.common.util.NautHashMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
public class PacketHandler extends MiniPlugin
{
private NautHashMap<Player, PacketVerifier> _playerVerifierMap = new NautHashMap<Player, PacketVerifier>();
private HashMap<Class, ArrayList<IPacketHandler>> _forceMainThread = new HashMap<Class, ArrayList<IPacketHandler>>();
private HashMap<Class, ArrayList<IPacketHandler>> _packetHandlers = new HashMap<Class, ArrayList<IPacketHandler>>();
private Map<Player, PacketVerifier> _playerVerifierMap = new HashMap<>();
private Map<Class<? extends Packet>, Set<IPacketHandler>> _forceMainThread = new HashMap<>();
private Map<Class<? extends Packet>, Map<ListenerPriority, List<IPacketHandler>>> _packetHandlers = new HashMap<>();
public PacketHandler(JavaPlugin plugin)
{
@ -40,18 +42,6 @@ public class PacketHandler extends MiniPlugin
.get(event.getPlayer()));
}
public boolean handlePacket(Player player, Packet packet)
{
if (!_playerVerifierMap.containsKey(player))
{
return true;
}
PacketVerifier verifier = _playerVerifierMap.get(player);
return handlePacket(new PacketInfo(player, packet, verifier));
}
public boolean handlePacket(PacketInfo packetInfo)
{
if (!_packetHandlers.containsKey(packetInfo.getPacket().getClass()))
@ -60,15 +50,18 @@ public class PacketHandler extends MiniPlugin
return true;
}
for (IPacketHandler handler : _packetHandlers.get(packetInfo.getPacket().getClass()))
for (Entry<ListenerPriority, List<IPacketHandler>> entry : _packetHandlers.get(packetInfo.getPacket().getClass()).entrySet())
{
try
for (IPacketHandler handler : entry.getValue())
{
handler.handle(packetInfo);
}
catch (Exception ex)
{
ex.printStackTrace();
try
{
handler.handle(packetInfo);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
@ -90,72 +83,106 @@ public class PacketHandler extends MiniPlugin
@SafeVarargs
public final void addPacketHandler(IPacketHandler packetHandler, Class<? extends Packet>... packetsToListen)
{
if (packetsToListen.length == 0)
{
throw new IllegalArgumentException("When registering a new packet listener, add the packets its going to listen to");
}
addPacketHandler(packetHandler, false, packetsToListen);
addPacketHandler(packetHandler, ListenerPriority.NORMAL, false, packetsToListen);
}
/**
* This should only be used for incoming packets
*/
public void addPacketHandler(IPacketHandler packetHandler, boolean forceMainThread,
Class<? extends Packet>... packetsToListen)
@SafeVarargs
public final void addPacketHandler(IPacketHandler packetHandler, boolean forceMainThread, Class<? extends Packet>... packetsToListen)
{
if (packetsToListen.length == 0)
{
throw new IllegalArgumentException("When registering a new packet listener, add the packets its going to listen to");
}
for (Class c : packetsToListen)
addPacketHandler(packetHandler, ListenerPriority.NORMAL, forceMainThread, packetsToListen);
}
@SafeVarargs
public final void addPacketHandler(IPacketHandler packetHandler, ListenerPriority priority, Class<? extends Packet>... packetsToListen)
{
if (packetsToListen.length == 0)
{
throw new IllegalArgumentException("When registering a new packet listener, add the packets its going to listen to");
}
addPacketHandler(packetHandler, priority, false, packetsToListen);
}
/**
* This should only be used for incoming packets
*/
@SafeVarargs
public final void addPacketHandler(IPacketHandler packetHandler, ListenerPriority priority, boolean forceMainThread,
Class<? extends Packet>... packetsToListen)
{
if (packetsToListen.length == 0)
{
throw new IllegalArgumentException("When registering a new packet listener, add the packets its going to listen to");
}
for (Class<? extends Packet> c : packetsToListen)
{
if (forceMainThread)
{
if (!_forceMainThread.containsKey(c))
{
_forceMainThread.put(c, new ArrayList());
}
_forceMainThread.get(c).add(packetHandler);
_forceMainThread
.computeIfAbsent(c, key -> new HashSet<>())
.add(packetHandler);
}
if (!_packetHandlers.containsKey(c))
{
_packetHandlers.put(c, new ArrayList());
}
_packetHandlers
.computeIfAbsent(c, key -> new TreeMap<>())
.computeIfAbsent(priority, key -> new ArrayList<>())
.add(packetHandler);
_packetHandlers.get(c).add(packetHandler);
PacketProcessor.addPacket(c, forceMainThread || _forceMainThread.containsKey(c));
}
}
public void removePacketHandler(IPacketHandler packetHandler)
{
Iterator<Entry<Class, ArrayList<IPacketHandler>>> itel = _packetHandlers.entrySet().iterator();
Iterator<Entry<Class<? extends Packet>, Map<ListenerPriority, List<IPacketHandler>>>> itel = _packetHandlers.entrySet().iterator();
while (itel.hasNext())
{
Entry<Class, ArrayList<IPacketHandler>> entry = itel.next();
Entry<Class<? extends Packet>, Map<ListenerPriority, List<IPacketHandler>>> entry = itel.next();
if (entry.getValue().remove(packetHandler))
Set<ListenerPriority> removedFrom = new HashSet<>();
for (Entry<ListenerPriority, List<IPacketHandler>> ent : entry.getValue().entrySet())
{
if (_forceMainThread.containsKey(entry.getKey()) && _forceMainThread.get(entry.getKey()).remove(packetHandler))
if (ent.getValue().remove(packetHandler))
{
if (_forceMainThread.get(entry.getKey()).isEmpty())
{
_forceMainThread.remove(entry.getKey());
PacketProcessor.addPacket(entry.getKey(), false);
}
removedFrom.add(ent.getKey());
}
}
if (entry.getValue().isEmpty())
for (ListenerPriority priority : removedFrom)
{
if (entry.getValue().get(priority).isEmpty())
{
PacketProcessor.removePacket(entry.getKey());
itel.remove();
entry.getValue().remove(priority);
}
}
if (_forceMainThread.containsKey(entry.getKey()) && _forceMainThread.get(entry.getKey()).remove(packetHandler))
{
if (_forceMainThread.get(entry.getKey()).isEmpty())
{
_forceMainThread.remove(entry.getKey());
PacketProcessor.addPacket(entry.getKey(), false);
}
}
if (entry.getValue().isEmpty())
{
PacketProcessor.removePacket(entry.getKey());
itel.remove();
}
}
}
public enum ListenerPriority
{
HIGH, NORMAL, LOW
}
}

View File

@ -231,6 +231,9 @@ public class DamageManager extends MiniPlugin
@EventHandler
public void onEntityCombust(EntityCombustByEntityEvent event)
{
if (!_enabled)
return;
if (!(event.getCombuster() instanceof Player || event.getCombuster() instanceof Arrow))
return;

View File

@ -172,6 +172,7 @@ public class ArcadeManager extends MiniPlugin implements IRelation
private KitProgressionManager _kitProgressionManager;
private ProgressingKitManager _progressionKitManager;
private BoosterManager _boosterManager;
private GameSpectatorManager _spectatorManager;
private IncognitoManager _incognitoManager;
@ -290,7 +291,7 @@ public class ArcadeManager extends MiniPlugin implements IRelation
YoutubeManager youtubeManager = new YoutubeManager(plugin, clientManager, donationManager);
_bonusManager = new BonusManager(plugin, clientManager, serverStatusManager, donationManager, pollManager , npcManager, hologramManager, statsManager, _inventoryManager, petManager, facebookManager, youtubeManager, _cosmeticManager.getGadgetManager(), boosterManager);
new GameLootManager(this, petManager, _bonusManager.getRewardManager());
new GameSpectatorManager(this);
_spectatorManager = new GameSpectatorManager(this);
_gameWorldManager = new GameWorldManager(this);
new MiscManager(this);
_hologramManager = hologramManager;
@ -1705,4 +1706,9 @@ public class ArcadeManager extends MiniPlugin implements IRelation
{
return _kitProgressionManager;
}
public GameSpectatorManager getGameSpectatorManager()
{
return _spectatorManager;
}
}

View File

@ -22,6 +22,7 @@ import nautilus.game.arcade.events.GameStateChangeEvent;
import nautilus.game.arcade.events.PlayerGameRespawnEvent;
import nautilus.game.arcade.events.PlayerStateChangeEvent;
import nautilus.game.arcade.game.GameTeam.PlayerState;
import nautilus.game.arcade.game.modules.Module;
import nautilus.game.arcade.kit.*;
import nautilus.game.arcade.managers.GameLobbyManager;
import nautilus.game.arcade.managers.chat.ChatStatData;
@ -311,8 +312,6 @@ public abstract class Game implements Listener
public boolean AllowEntitySpectate = true;
public boolean PlayerTeamSelection = false;
public boolean TeamMode = false;
public boolean TeamPerSpawn = false;
@ -332,6 +331,8 @@ public abstract class Game implements Listener
private NautHashMap<Player, Player> _teamReqs = new NautHashMap<Player, Player>();
public WinEffectManager WinEffectManager = new WinEffectManager();
private Map<Class<? extends Module>, Module> _modules = new HashMap<>();
public Game(ArcadeManager manager, GameType gameType, Kit[] kits, String[] gameDesc)
{
Manager = manager;
@ -418,6 +419,20 @@ public abstract class Game implements Listener
System.out.println("Loading " + GetName() + "...");
}
public void registerModule(Module module)
{
if (!_modules.containsKey(module.getClass()))
{
module.initialize(this);
_modules.put(module.getClass(), module);
UtilServer.RegisterEvents(module);
}
else
{
throw new IllegalStateException("Module " + module.getClass() + " is already registered");
}
}
public void setKits(Kit[] kits)
{
_kits = kits;
@ -1897,157 +1912,6 @@ public abstract class Game implements Listener
}
}
@EventHandler(priority = EventPriority.HIGH)
public void teamSelectInteract(PlayerInteractEntityEvent event)
{
if (!PlayerTeamSelection)
return;
if (GetState() != GameState.Recruit)
return;
if (event.getRightClicked() == null)
return;
if (!(event.getRightClicked() instanceof Player))
return;
Player player = event.getPlayer();
//Observer
if (Manager.IsObserver(player))
{
UtilPlayer.message(player, F.main("Game", "Spectators cannot partake in games."));
return;
}
selectTeamMate(player, (Player) event.getRightClicked());
}
@EventHandler
public void teamSelectCommand(PlayerCommandPreprocessEvent event)
{
if (!PlayerTeamSelection)
return;
if (GetState() != GameState.Recruit)
return;
if (!event.getMessage().toLowerCase().startsWith("/team "))
return;
event.setCancelled(true);
Player target = UtilPlayer.searchOnline(event.getPlayer(), event.getMessage().split(" ")[1],
true);
if (target == null)
return;
//Observer
if (Manager.IsObserver(event.getPlayer()))
{
UtilPlayer.message(event.getPlayer(),
F.main("Game", "Spectators cannot partake in games."));
return;
}
if (event.getPlayer().equals(target))
return;
selectTeamMate(event.getPlayer(), target);
}
public void selectTeamMate(Player player, Player ally)
{
//Accept Invite
if (_teamReqs.containsKey(ally) && _teamReqs.get(ally).equals(player))
{
//Remove Prefs
_teamReqs.remove(player);
_teamReqs.remove(ally);
//Inform
UtilPlayer.message(player,
F.main("Game", "You accepted " + ally.getName() + "'s Team Request!"));
UtilPlayer.message(ally,
F.main("Game", player.getName() + " accepted your Team Request!"));
//Leave Old Teams
if (GetTeam(player) != null)
GetTeam(player).DisbandTeam();
if (GetTeam(ally) != null)
GetTeam(ally).DisbandTeam();
//Get Team
GameTeam team = getEmptyTeam();
if (team == null)
return;
//Join Team
SetPlayerTeam(player, team, true);
SetPlayerTeam(ally, team, true);
}
//Send Invite
else
{
//Already on Team with Target
if (GetTeam(player) != null)
if (GetTeam(player).HasPlayer(ally))
return;
//Inform Player
UtilPlayer.message(player,
F.main("Game", "You sent a Team Request to " + ally.getName() + "!"));
//Inform Target
if (Recharge.Instance.use(player, "Team Req " + ally.getName(), 2000, false, false))
{
UtilPlayer.message(ally,
F.main("Game", player.getName() + " sent you a Team Request!"));
UtilPlayer.message(ally, F.main("Game",
"Type " + F.elem("/team " + player.getName()) + " to accept!"));
}
//Add Pref
_teamReqs.put(player, ally);
}
}
@EventHandler
public void teamQuit(PlayerQuitEvent event)
{
if (!PlayerTeamSelection)
return;
if (GetState() != GameState.Recruit)
return;
Player player = event.getPlayer();
if (GetTeam(player) != null)
GetTeam(player).DisbandTeam();
Iterator<Player> teamIter = _teamReqs.keySet().iterator();
while (teamIter.hasNext())
{
Player sender = teamIter.next();
if (sender.equals(player) || _teamReqs.get(sender).equals(player))
teamIter.remove();
}
}
public GameTeam getEmptyTeam()
{
for (GameTeam team : GetTeamList())
{
if (team.GetPlayers(false).isEmpty())
return team;
}
return null;
}
@EventHandler
public void CustomTeamGeneration(GameStateChangeEvent event)
{
@ -2271,4 +2135,17 @@ public abstract class Game implements Listener
{
}
public void cleanupModules()
{
for (Module module : this._modules.values())
{
module.cleanup();
HandlerList.unregisterAll(module);
}
}
public <T extends Module> T getModule(Class<T> clazz)
{
return clazz.cast(_modules.get(clazz));
}
}

View File

@ -24,6 +24,7 @@ import org.bukkit.Location;
import org.bukkit.entity.Creature;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Team;
public class GameTeam
{
@ -192,7 +193,11 @@ public class GameTeam
{
for(Player other : UtilServer.getPlayers())
{
other.getScoreboard().getTeam(Host.Manager.GetClients().Get(player).GetRank().Name + _name.toUpperCase()).removePlayer(player);
Team team = other.getScoreboard().getTeam(Host.Manager.GetClients().Get(player).GetRank().Name + _name.toUpperCase());
if (team != null)
{
team.removePlayer(player);
}
other.getScoreboard().getTeam(Host.Manager.GetClients().Get(player).GetRank().Name).addPlayer(player);
}
UtilPlayer.message(player, F.main("Team", _color + C.Bold + getDisplayName() + " Team was disbanded."));

View File

@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import nautilus.game.arcade.game.modules.TeamModule;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Sound;
@ -40,7 +41,9 @@ public class TeamBuild extends Build
super(manager, GameType.Brawl);
TeamMode = true;
PlayerTeamSelection = true;
registerModule(new TeamModule());
TeamPerSpawn = true;
FillTeamsInOrderToCount = 2;

View File

@ -19,6 +19,7 @@ import nautilus.game.arcade.GameType;
import nautilus.game.arcade.events.GameStateChangeEvent;
import nautilus.game.arcade.game.GameTeam;
import nautilus.game.arcade.game.Game.GameState;
import nautilus.game.arcade.game.modules.TeamModule;
import nautilus.game.arcade.stats.DeathBomberStatTracker;
import nautilus.game.arcade.stats.SkywarsKillZombieStatTracker;
import nautilus.game.arcade.stats.SkywarsTNTStatTracker;
@ -78,10 +79,11 @@ public class TeamSkywars extends Skywars
DamageTeamSelf = false;
DontAllowOverfill = true;
PlayerTeamSelection = true;
TeamMode = true;
TeamPerSpawn = true;
registerModule(new TeamModule());
}
@Override

View File

@ -15,6 +15,7 @@ import nautilus.game.arcade.ArcadeManager;
import nautilus.game.arcade.GameType;
import nautilus.game.arcade.events.GameStateChangeEvent;
import nautilus.game.arcade.game.GameTeam;
import nautilus.game.arcade.game.modules.TeamModule;
import nautilus.game.arcade.managers.chat.ChatStatData;
import nautilus.game.arcade.stats.FreeKitWinStatTracker;
import nautilus.game.arcade.stats.KillFastStatTracker;
@ -55,7 +56,8 @@ public class TeamSuperSmash extends SuperSmash
DontAllowOverfill = true;
TeamMode = true;
PlayerTeamSelection = true;
registerModule(new TeamModule());
registerStatTrackers(
new WinWithoutDyingStatTracker(this, "MLGPro"),

View File

@ -17,6 +17,7 @@ import nautilus.game.arcade.ArcadeManager;
import nautilus.game.arcade.GameType;
import nautilus.game.arcade.events.GameStateChangeEvent;
import nautilus.game.arcade.game.GameTeam;
import nautilus.game.arcade.game.modules.TeamModule;
import nautilus.game.arcade.managers.chat.ChatStatData;
import nautilus.game.arcade.stats.FirstSupplyDropOpenStatTracker;
import nautilus.game.arcade.stats.KillsWithinTimeLimitStatTracker;
@ -59,7 +60,8 @@ public class TeamSurvivalGames extends SurvivalGames
DontAllowOverfill = true;
TeamMode = true;
PlayerTeamSelection = true;
registerModule(new TeamModule());
registerStatTrackers(new WinWithoutWearingArmorStatTracker(this),
new KillsWithinTimeLimitStatTracker(this, 3, 60, "Bloodlust"),

View File

@ -0,0 +1,385 @@
package nautilus.game.arcade.game.games.uhc.helpers;
import mineplex.core.common.util.UtilMath;
import mineplex.core.common.util.UtilServer;
import mineplex.core.timing.TimingManager;
import nautilus.game.arcade.game.Game;
import nautilus.game.arcade.game.GameTeam;
import nautilus.game.arcade.game.games.uhc.UHC;
import net.minecraft.server.v1_8_R3.Chunk;
import net.minecraft.server.v1_8_R3.ChunkProviderServer;
import net.minecraft.server.v1_8_R3.ChunkRegionLoader;
import net.minecraft.server.v1_8_R3.NBTTagCompound;
import net.minecraft.server.v1_8_R3.WorldServer;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_8_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_8_R3.util.LongHash;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.spigotmc.AsyncCatcher;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static nautilus.game.arcade.game.games.uhc.UHC.VIEW_DISTANCE;
public class ChunkLoadingThread extends Thread implements Listener
{
private Game _game;
private volatile boolean _isDecorating = false;
private AtomicInteger _actual = new AtomicInteger();
private AtomicInteger _expected = new AtomicInteger(23000); // Most likely it'll be around 23000
private Set<net.minecraft.server.v1_8_R3.Entity> _entities = new HashSet<>();
public ChunkLoadingThread(Game game)
{
super("Chunk Loader");
this._game = game;
UtilServer.RegisterEvents(this);
}
public void run()
{
WorldServer worldServer = ((CraftWorld) _game.WorldData.World).getHandle();
Location spawn = _game.WorldData.World.getSpawnLocation();
Map<Long, Chunk> loaded = new ConcurrentHashMap<>();
Map<Long, NBTTagCompound> compounds = new ConcurrentHashMap<>();
try
{
TimingManager.start("UHC Chunk Loading");
ChunkProviderServer chunkProviderServer = worldServer.chunkProviderServer;
Field chunkLoaderField = chunkProviderServer.getClass().getDeclaredField("chunkLoader");
chunkLoaderField.setAccessible(true);
ChunkRegionLoader loader = (ChunkRegionLoader) chunkLoaderField.get(chunkProviderServer);
// Step 1: Read all the required chunks from the disk
// We're going to read all the required chunks from disk async
{
Set<Long> coordPairs = new HashSet<>();
// Special case for 0, 0
{
int x = spawn.getBlockX() >> 4;
int z = spawn.getBlockZ() >> 4;
for (int dx = -VIEW_DISTANCE; dx <= VIEW_DISTANCE; dx++)
{
for (int dz = -VIEW_DISTANCE; dz <= VIEW_DISTANCE; dz++)
{
coordPairs.add(LongHash.toLong(x + dx, z + dz));
}
}
}
// All the team spawns
{
for (int i = 0; i < _game.GetTeamList().size(); i++)
{
GameTeam team = _game.GetTeamList().get(i);
for (Location l : team.GetSpawns())
{
int x = l.getChunk().getX();
int z = l.getChunk().getZ();
for (int dx = -VIEW_DISTANCE; dx <= VIEW_DISTANCE; dx++)
{
for (int dz = -VIEW_DISTANCE; dz <= VIEW_DISTANCE; dz++)
{
coordPairs.add(LongHash.toLong(x + dx, z + dz));
}
}
}
}
}
AtomicBoolean lockCompleted = new AtomicBoolean(false);
Object lock = new Object();
// Hop back onto the main thread
_game.getArcadeManager().runSync(() ->
{
for (Chunk chunk : new ArrayList<>(chunkProviderServer.chunks.values()))
{
chunk.bukkitChunk.unload(true, false);
}
lockCompleted.set(true);
synchronized(lock)
{
lock.notifyAll();
}
});
if (!lockCompleted.get())
{
synchronized (lock)
{
lock.wait();
}
}
if (!lockCompleted.get())
{
throw new IllegalStateException("Lock was not completed");
}
// Sigh... I don't want this to be here but it needs to be set somewhere...
// Multiply by 3 because there are 3 stages
_expected.set(coordPairs.size() * 3);
// Load them now
ExecutorService chunkLoaders = Executors.newFixedThreadPool(UHC.THREADS_FOR_CHUNK_LOADING);
for (long coord : coordPairs)
{
chunkLoaders.submit(() ->
{
int x = LongHash.msw(coord);
int z = LongHash.lsw(coord);
try
{
Object[] data = loader.loadChunk(worldServer, x, z);
if (data != null)
{
NBTTagCompound compound = (NBTTagCompound) data[1];
net.minecraft.server.v1_8_R3.Chunk chunk = (net.minecraft.server.v1_8_R3.Chunk) data[0];
loaded.put(coord, chunk);
compounds.put(coord, compound);
}
else
{
System.out.println("Failed to load chunk " + x + "," + z);
}
}
catch (Throwable t)
{
t.printStackTrace();
}
finally
{
_actual.getAndIncrement();
}
});
}
chunkLoaders.shutdown();
// We've got plenty of time to wait
System.out.println("Finished submitting tasks to executor, waiting...");
chunkLoaders.awaitTermination(1, TimeUnit.DAYS);
System.out.println("Loaded: " + loaded.size() + " and coords: " + coordPairs.size());
coordPairs.clear();
}
// Step 2: Recreate structures, update neighbors, load entities
// This step should be super quick so there's no point in scheduling it elsewhere
// Code is plain copypasted from ChunkIOProvider
{
for (net.minecraft.server.v1_8_R3.Chunk chunk : loaded.values())
{
NBTTagCompound compound = compounds.get(LongHash.toLong(chunk.locX, chunk.locZ));
loader.loadEntities(chunk, compound.getCompound("Level"), worldServer);
chunk.setLastSaved(chunkProviderServer.world.getTime());
if (chunkProviderServer.chunkProvider != null)
{
chunkProviderServer.chunkProvider.recreateStructures(chunk, chunk.locX, chunk.locZ);
}
for (int x = -2; x < 3; ++x)
{
for (int z = -2; z < 3; ++z)
{
if (x != 0 || z != 0)
{
net.minecraft.server.v1_8_R3.Chunk neighbor = loaded.get(LongHash.toLong(chunk.locX + x, chunk.locZ + z));
if (neighbor != null)
{
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
}
}
}
}
_actual.getAndIncrement();
}
}
AtomicBoolean lockCompleted = new AtomicBoolean(false);
Object lock = new Object();
// Hop back onto the main thread
_game.getArcadeManager().runSync(() ->
{
// We want to add all the chunks to the chunkmap so that the server is not out of sync
for (Map.Entry<Long, net.minecraft.server.v1_8_R3.Chunk> ent : loaded.entrySet())
{
ent.getValue().addEntities();
chunkProviderServer.chunks.put(ent.getKey(), ent.getValue());
ChunkLoadEvent event = new ChunkLoadEvent(ent.getValue().bukkitChunk, true);
UtilServer.CallEvent(event);
}
lockCompleted.set(true);
synchronized (lock)
{
lock.notifyAll();
}
});
if (!lockCompleted.get())
{
synchronized (lock)
{
lock.wait();
}
}
if (!lockCompleted.get())
{
throw new IllegalStateException("Lock was not completed");
}
// Step 3: Decorate the chunks. This step must be performed async as otherwise the server lags way too hard
// Notes: Do not allow the server to tick the world. If this is allowed EntityTracker will raise CME
// NextTickList will also raise errors
// And worst case the server will crash
{
// Live life on the edge
AsyncCatcher.enabled = false;
_isDecorating = true;
int ct = 0;
for (net.minecraft.server.v1_8_R3.Chunk chunk : loaded.values())
{
chunk.loadNearby(chunkProviderServer, chunkProviderServer, chunk.locX, chunk.locZ);
ct++;
if (ct % 100 == 0)
{
System.out.println(ct);
}
_actual.getAndIncrement();
}
TimingManager.stop("UHC Chunk Loading");
_isDecorating = false;
AsyncCatcher.enabled = true;
System.out.println("Expected: " + _expected.get() + ", actual: " + _actual.get());
_game.getArcadeManager().runSync(() ->
{
for (Chunk chunk : chunkProviderServer.chunks.values())
{
// Clear
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
chunk.setNeighborUnloaded(x, z);
}
}
}
for (Chunk chunk : chunkProviderServer.chunks.values())
{
// Refresh
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
Chunk neighbor = chunkProviderServer.getChunkIfLoaded(chunk.locX + x, chunk.locZ + z);
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
chunk.setNeighborLoaded(x, z);
}
}
}
}
for (net.minecraft.server.v1_8_R3.Entity entity : _entities)
{
entity.dead = false;
worldServer.addEntity(entity, CreatureSpawnEvent.SpawnReason.CHUNK_GEN);
}
_entities.clear();
// You may tick again
worldServer.getMinecraftServer().worlds.add(worldServer);
// Well, if they're not equal, not much we can do. We've hit the end
_actual.set(_expected.get());
});
}
loaded.clear();
compounds.clear();
UtilServer.Unregister(this);
}
catch (Throwable t)
{
t.printStackTrace();
}
}
@EventHandler
public void on(EntitySpawnEvent event)
{
// Don't allow entity spawns while decorating, period
if (_isDecorating)
{
if (event.getLocation().getWorld().getUID() == _game.WorldData.World.getUID())
{
_entities.add(((CraftEntity) event.getEntity()).getHandle());
event.setCancelled(true);
}
}
}
public void flagDone()
{
_actual.set(_expected.get());
}
public boolean isDone()
{
return _actual.get() == _expected.get();
}
public int getPercentageComplete()
{
return UtilMath.clamp((int) ((_actual.get() * 1.0 / _expected.get()) * 100), 0, 100);
}
public String getProgress()
{
return getPercentageComplete() + "%";
}
public void flagStop()
{
this.interrupt();
}
}

View File

@ -0,0 +1,239 @@
package nautilus.game.arcade.game.games.uhc.helpers;
import mineplex.core.common.util.C;
import mineplex.core.common.util.UtilMath;
import mineplex.core.common.util.UtilTime;
import mineplex.core.timing.TimingManager;
import nautilus.game.arcade.game.Game;
import nautilus.game.arcade.game.games.uhc.UHC;
import net.minecraft.server.v1_8_R3.BiomeCache;
import net.minecraft.server.v1_8_R3.ChunkProviderServer;
import net.minecraft.server.v1_8_R3.FileIOThread;
import net.minecraft.server.v1_8_R3.IChunkProvider;
import net.minecraft.server.v1_8_R3.MinecraftServer;
import net.minecraft.server.v1_8_R3.WorldChunkManager;
import org.bukkit.craftbukkit.v1_8_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_8_R3.util.LongHash;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class WorldGenThread extends Thread
{
private UHC _game;
private volatile boolean _mapLoaded = false;
private volatile int _chunksPerTick = 1;
private volatile boolean _stopGen = false;
private int _chunkTotal;
private int _chunkX = 0;
private int _chunkZ = 0;
private int _chunksLoaded = 0;
private int _currentBorder = 1000;
public WorldGenThread(UHC game)
{
super("WorldGen Thread");
this._game = game;
_chunkX = (int) -(_currentBorder / 16);
_chunkZ = (int) -(_currentBorder / 16);
_chunkTotal = (int) ((_currentBorder * 2 / 16) * (_currentBorder * 2 / 16));
}
public void run()
{
try
{
Field fileIOThreadB = FileIOThread.class.getDeclaredField("b");
fileIOThreadB.setAccessible(true);
// This list is the list of chunks to be saved on the File IO Thread
List list = (List) fileIOThreadB.get(FileIOThread.a());
net.minecraft.server.v1_8_R3.WorldServer worldServer = ((CraftWorld) _game.WorldData.World).getHandle();
WorldChunkManager manager = worldServer.getWorldChunkManager();
Field biomeCacheField = manager.getClass().getDeclaredField("d");
biomeCacheField.setAccessible(true);
// A thread safe BiomeCache
// The implementation is literally a copy/paste from the original BiomeCache, but with some synchronization
// Reason being while the server is ticking the world (for some reason, if you want to dig through the entire Arcade codebase go for it)
// it stores stuff in the BiomeCache, and chunk gen needs that BiomeCache info too
// Causing desynchronization in the cache
biomeCacheField.set(manager, new BiomeCache(manager)
{
private final Object _lock = new Object();
private long _lastCleanTime; // b -> _lastCleanTime
private Map<Long, BiomeCacheBlock> _blockByCoord = new HashMap<>(); // LongHashMap -> HashMap, c -> _blockByCoord
private List<BiomeCache.BiomeCacheBlock> _blocks = new ArrayList<>(); // d -> _blocks
@Override
public BiomeCache.BiomeCacheBlock a(int x, int z)
{
x >>= 4;
z >>= 4;
long var3 = hash(x, z);
BiomeCache.BiomeCacheBlock var5 = this._blockByCoord.get(var3);
if (var5 == null)
{
var5 = new BiomeCache.BiomeCacheBlock(x, z);
synchronized (_lock)
{
this._blockByCoord.put(var3, var5);
this._blocks.add(var5);
}
}
var5.e = MinecraftServer.az();
return var5;
}
@Override
public void a()
{
long currentTime = MinecraftServer.az();
long deltaTime = currentTime - this._lastCleanTime;
if (deltaTime > 7500L || deltaTime < 0L)
{
this._lastCleanTime = currentTime;
synchronized (_lock)
{
for (int i = 0; i < this._blocks.size(); ++i)
{
BiomeCache.BiomeCacheBlock biomeCacheBlock = (BiomeCache.BiomeCacheBlock) this._blocks.get(i);
long var7 = currentTime - biomeCacheBlock.e;
if (var7 > 30000L || var7 < 0L)
{
this._blocks.remove(i--);
this._blockByCoord.remove(hash(biomeCacheBlock.c, biomeCacheBlock.d));
}
}
}
}
}
private long hash(int x, int z)
{
return (long) x & 4294967295L | ((long) z & 4294967295L) << 32;
}
});
ChunkProviderServer cps = worldServer.chunkProviderServer;
IChunkProvider icp = cps.chunkProvider;
System.out.println("Using chunk provider " + icp.getClass());
TimingManager.start("Map Generation");
long start = System.currentTimeMillis();
long last = start;
while (!_stopGen)
{
long now = System.currentTimeMillis();
if ((now - last) >= 10 * 1000)
{
_game.Announce(C.cGreen + C.Bold + "Generating Map: " + C.cWhite + getMapLoadETA() + " Remaining...", false);
last = now;
}
long hash = LongHash.toLong(_chunkX, _chunkZ);
// This is just a shortcut to how the Minecraft server would have generated a chunk if it doesn't exist.
// This should always create a chunk because we're not loading any chunks beforehand...
// /me looks at new maintainer
net.minecraft.server.v1_8_R3.Chunk chunk = icp.getOrCreateChunk(_chunkX, _chunkZ);
// Run the copypasted code for chunk saving.
cps.saveChunk(chunk);
cps.saveChunkNOP(chunk);
cps.unloadQueue.remove(_chunkX, _chunkZ);
cps.chunks.remove(hash);
if (_chunkX < _currentBorder / 16)
{
_chunkX++;
}
else if (_chunkZ < _currentBorder / 16)
{
_chunkX = (int) -(_currentBorder / 16);
_chunkZ++;
}
else
{
_mapLoaded = true;
System.out.println("Map Loading Finished! Took " + TimeUnit.MILLISECONDS.toSeconds(now - start) + " seconds");
break;
}
_chunksLoaded++;
_chunksPerTick = (int) (_chunksLoaded / ((now - start) / 50.0));
}
TimingManager.stop("Map Generation");
if (_stopGen)
{
return;
}
TimingManager.start("Map Saving");
// Wait for all the chunks to save (but do we need this?)
while (!list.isEmpty())
{
Thread.sleep(100);
}
TimingManager.stop("Map Saving");
_game.getArcadeManager().runSync(_game::generateSpawns);
}
catch (Throwable t)
{
// todo proper exception handling
// maybe force shutdown?
t.printStackTrace();
}
}
public void flagStop()
{
this._stopGen = true;
}
public String getMapLoadETA()
{
int chunksToGo = _chunkTotal - _chunksLoaded;
return UtilTime.MakeStr((long) ((double) chunksToGo / (double) (_chunksPerTick * 20) * 1000d), 1);
}
public boolean isMapLoaded()
{
return this._mapLoaded;
}
public int getPercentageComplete()
{
return UtilMath.clamp((int) ((_chunksLoaded * 1.0 / _chunkTotal) * 100), 0, 100);
}
public String getProgress()
{
return getPercentageComplete() + "%";
}
}

View File

@ -0,0 +1,29 @@
package nautilus.game.arcade.game.modules;
import nautilus.game.arcade.game.Game;
import org.bukkit.event.Listener;
public abstract class Module implements Listener
{
// The game this module belongs to
private Game _game;
public void initialize(Game game)
{
if (_game != null)
{
throw new IllegalArgumentException("Attempting to initialize module which has already been initialized for " + _game);
}
this._game = game;
}
public void cleanup()
{
}
public Game getGame()
{
return this._game;
}
}

View File

@ -0,0 +1,164 @@
package nautilus.game.arcade.game.modules;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.recharge.Recharge;
import nautilus.game.arcade.game.Game;
import nautilus.game.arcade.game.GameTeam;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.UUID;
public class TeamModule extends Module
{
private BiMap<UUID, UUID> _teamReqs = HashBiMap.create();
@EventHandler(priority = EventPriority.HIGH)
public void teamSelectInteract(PlayerInteractEntityEvent event)
{
if (getGame().GetState() != Game.GameState.Recruit)
return;
if (event.getRightClicked() == null)
return;
if (!(event.getRightClicked() instanceof Player))
return;
Player player = event.getPlayer();
//Observer
if (getGame().getArcadeManager().IsObserver(player))
{
UtilPlayer.message(player, F.main("Game", "Spectators cannot partake in games."));
return;
}
selectTeamMate(player, (Player) event.getRightClicked());
}
@EventHandler
public void teamSelectCommand(PlayerCommandPreprocessEvent event)
{
if (!event.getMessage().toLowerCase().startsWith("/team "))
return;
event.setCancelled(true);
if (getGame().GetState() != Game.GameState.Recruit)
{
UtilPlayer.message(event.getPlayer(), F.main("Game", "You cannot send team requests while the game is live"));
return;
}
//Observer
if (getGame().getArcadeManager().IsObserver(event.getPlayer()))
{
UtilPlayer.message(event.getPlayer(), F.main("Game", "Spectators cannot partake in games."));
return;
}
Player target = UtilPlayer.searchOnline(event.getPlayer(), event.getMessage().split(" ")[1], true);
if (target == null)
{
UtilPlayer.message(event.getPlayer(), F.main("Game", "That player is not online"));
return;
}
if (event.getPlayer().equals(target))
{
UtilPlayer.message(event.getPlayer(), F.main("Game", "You can't team with yourself!"));
return;
}
selectTeamMate(event.getPlayer(), target);
}
private void selectTeamMate(Player player, Player ally)
{
//Accept Invite
if (_teamReqs.containsKey(ally.getUniqueId()) && _teamReqs.get(ally.getUniqueId()).equals(player.getUniqueId()))
{
//Remove Prefs
_teamReqs.remove(player.getUniqueId());
_teamReqs.remove(ally.getUniqueId());
//Inform
UtilPlayer.message(player, F.main("Game", "You accepted " + ally.getName() + "'s Team Request!"));
UtilPlayer.message(ally, F.main("Game", player.getName() + " accepted your Team Request!"));
//Leave Old Teams
if (getGame().GetTeam(player) != null)
getGame().GetTeam(player).DisbandTeam();
if (getGame().GetTeam(ally) != null)
getGame().GetTeam(ally).DisbandTeam();
//Get Team
GameTeam team = getEmptyTeam();
if (team == null)
return;
team.setDisplayName(player.getName() + " & " + ally.getName());
//Join Team
getGame().SetPlayerTeam(player, team, true);
getGame().SetPlayerTeam(ally, team, true);
}
//Send Invite
else
{
//Already on Team with Target
if (getGame().GetTeam(player) != null)
if (getGame().GetTeam(player).HasPlayer(ally))
return;
//Inform Player
UtilPlayer.message(player, F.main("Game", "You sent a Team Request to " + ally.getName() + "!"));
//Inform Target
if (Recharge.Instance.use(player, "Team Req " + ally.getName(), 2000, false, false))
{
UtilPlayer.message(ally, F.main("Game", player.getName() + " sent you a Team Request!"));
UtilPlayer.message(ally, F.main("Game", "Type " + F.elem("/team " + player.getName()) + " to accept!"));
}
//Add Pref
_teamReqs.put(player.getUniqueId(), ally.getUniqueId());
}
}
@EventHandler
public void teamQuit(PlayerQuitEvent event)
{
if (getGame().GetState() != Game.GameState.Recruit)
return;
Player player = event.getPlayer();
if (getGame().GetTeam(player) != null)
getGame().GetTeam(player).DisbandTeam();
_teamReqs.remove(player.getUniqueId());
_teamReqs.inverse().remove(player.getUniqueId());
}
private GameTeam getEmptyTeam()
{
for (GameTeam team : getGame().GetTeamList())
{
if (team.GetPlayers(false).isEmpty())
return team;
}
return null;
}
}

View File

@ -41,18 +41,7 @@ public class SpectatorButton implements IButton
if(clickType == ClickType.RIGHT)
{
_player.closeInventory();
_player.teleport(_target.getLocation().add(0, 1, 0));
_arcadeManager.runSyncLater(new Runnable()
{
@Override
public void run()
{
_player.setGameMode(GameMode.SPECTATOR);
_player.setSpectatorTarget(_target);
UtilTextBottom.display(C.cYellow + "You are spectating " + F.game(_target.getName()), player);
UtilPlayer.message(_player, F.main("Game", "Sneak to stop spectating"));
}
}, 3);
_arcadeManager.getGameSpectatorManager().setSpectating(_player, _target);
}
else
{

View File

@ -105,6 +105,7 @@ public class GameCreationManager implements Listener
{
Game game = gameIterator.next();
game.cleanupModules();
game.disable();
HandlerList.unregisterAll(game);

View File

@ -948,7 +948,8 @@ public class GameLobbyManager implements Listener
public void Combust(EntityCombustEvent event)
{
for (LobbyEnt ent : _kits.values())
if (event.getEntity().equals(ent.GetEnt()))
if (event.getEntity().getWorld().getUID().equals(ent.GetEnt().getWorld().getUID()) &&
event.getEntity().equals(ent.GetEnt()))
{
event.setCancelled(true);
return;

View File

@ -1,8 +1,22 @@
package nautilus.game.arcade.managers;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mineplex.core.packethandler.IPacketHandler;
import mineplex.core.packethandler.PacketHandler;
import mineplex.core.packethandler.PacketInfo;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.NetworkManager;
import net.minecraft.server.v1_8_R3.PacketPlayOutCamera;
import net.minecraft.server.v1_8_R3.PacketPlayOutGameStateChange;
import net.minecraft.server.v1_8_R3.PacketPlayOutNamedEntitySpawn;
import net.minecraft.server.v1_8_R3.WorldSettings;
import org.bukkit.GameMode;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -10,6 +24,7 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
@ -26,8 +41,10 @@ import mineplex.core.updater.event.UpdateEvent;
import nautilus.game.arcade.ArcadeManager;
import nautilus.game.arcade.game.Game.GameState;
public class GameSpectatorManager implements Listener
public class GameSpectatorManager implements Listener, IPacketHandler
{
private Set<UUID> _pendingSpectate = Collections.synchronizedSet(new HashSet<>());
ArcadeManager Manager;
public GameSpectatorManager(ArcadeManager manager)
@ -35,11 +52,13 @@ public class GameSpectatorManager implements Listener
Manager = manager;
Manager.getPluginManager().registerEvents(this, Manager.getPlugin());
Manager.getPacketHandler().addPacketHandler(this, PacketHandler.ListenerPriority.HIGH, PacketPlayOutNamedEntitySpawn.class);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
public void interactCancel(PlayerInteractEvent event)
{
{
if (Manager.GetGame() == null)
return;
@ -47,30 +66,30 @@ public class GameSpectatorManager implements Listener
if (!Manager.GetGame().IsAlive(player))
event.setCancelled(true);
processClick(player, event.getAction());
}
public void processClick(Player player, Action action)
{
if (Manager.GetGame() == null)
return;
if(!Manager.GetGame().AllowEntitySpectate)
return;
if(!Manager.GetGame().IsLive())
return;
if(player.getGameMode() != GameMode.SPECTATOR)
return;
if(player.getSpectatorTarget() == null)
return;
if(!(player.getSpectatorTarget() instanceof Player))
return;
List<Player> players = Manager.GetGame().GetPlayers(true);
int currentPlayer = 0;
for(Player otherPlayer : players)
@ -79,32 +98,32 @@ public class GameSpectatorManager implements Listener
if(((Player) player.getSpectatorTarget()) == otherPlayer)
break;
}
if(action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK)
currentPlayer = currentPlayer - 2;
else
return;
if(currentPlayer < 0)
currentPlayer = players.size() - 1;
if(currentPlayer >= players.size())
currentPlayer = 0;
if(players.get(currentPlayer) == null)
return;
Player specPlayer = players.get(currentPlayer);
setSpectating(player, specPlayer);
}
@EventHandler(priority = EventPriority.LOW)
public void interactEntityCancel(PlayerInteractEntityEvent event)
{
if (Manager.GetGame() == null)
return;
Player player = event.getPlayer();
if (Manager.GetGame().GetState() == GameState.Recruit)
@ -114,7 +133,7 @@ public class GameSpectatorManager implements Listener
return;
}
}
if (!Manager.GetGame().IsAlive(player))
{
event.setCancelled(true);
@ -127,7 +146,7 @@ public class GameSpectatorManager implements Listener
}
}
}
@EventHandler
public void updateSpecEntitys(UpdateEvent event)
{
@ -136,7 +155,7 @@ public class GameSpectatorManager implements Listener
if(Manager.GetGame() == null)
return;
if(Manager.GetGame().IsLive() || Manager.GetGame().GetState() == GameState.End)
{
if(Manager.GetGame().AllowEntitySpectate)
@ -158,13 +177,13 @@ public class GameSpectatorManager implements Listener
}
}
}
@EventHandler(priority = EventPriority.LOW)
public void spectatedEntityDeath(PlayerDeathEvent event)
{
if(Manager.GetGame() == null)
return;
if(Manager.GetGame().IsLive() || Manager.GetGame().GetState() == GameState.End)
{
if(Manager.GetGame().AllowEntitySpectate)
@ -191,13 +210,13 @@ public class GameSpectatorManager implements Listener
}
}
}
@EventHandler(priority = EventPriority.LOW)
public void dismountEntity(PlayerToggleSneakEvent event)
{
if(Manager.GetGame() == null)
return;
if(Manager.GetGame().IsLive() || Manager.GetGame().GetState() == GameState.End)
{
if(Manager.GetGame().AllowEntitySpectate)
@ -213,41 +232,53 @@ public class GameSpectatorManager implements Listener
}
}
}
private void setSpectating(Player player, Entity target)
{
player.setGameMode(GameMode.SURVIVAL);
player.teleport(target.getLocation().add(0, 1, 0));
player.getInventory().setHeldItemSlot(5);
Manager.runSyncLater(new Runnable()
{
@Override
public void run()
{
if (Manager.GetGame().IsAlive(player))
return;
Player ptarget = null;
if (target instanceof Player)
ptarget = (Player) target;
if (ptarget != null)
{
if (!Manager.GetGame().IsAlive(ptarget))
return;
}
player.setGameMode(GameMode.SPECTATOR);
player.setSpectatorTarget(target);
if (ptarget != null)
UtilTextBottom.display(C.cGray + "You are spectating " + F.elem(Manager.GetGame().GetTeam(ptarget).GetColor() + ptarget.getName()) + ".", player);
UtilPlayer.message(player, F.main("Game", "Sneak to stop spectating."));
public void setSpectating(Player player, Entity target)
{
if (Manager.GetGame().IsAlive(player))
{
return;
}
Player playerTarget = null;
if (target instanceof Player)
{
playerTarget = (Player) target;
if (!Manager.GetGame().IsAlive(playerTarget))
{
return;
}
}, 1);
}
_pendingSpectate.add(target.getUniqueId());
player.teleport(target.getLocation().add(0, 1, 0));
// If the player already has the entity loaded, we have to set it now
// todo
// In the future, we could do some really cool stuff where we listen to all incoming and outgoing
// spawn/destroy packets and track whether the client has loaded the entity in memory
// However, that approach has the risk of desynchronization at which point we're screwed
player.setGameMode(GameMode.SPECTATOR);
player.setSpectatorTarget(target);
if (playerTarget != null)
UtilTextBottom.display(C.cGray + "You are spectating " + F.elem(Manager.GetGame().GetTeam(playerTarget).GetColor() + playerTarget.getName()) + ".", player);
UtilPlayer.message(player, F.main("Game", "Sneak to stop spectating."));
// And if the player did have the entity loaded, we also need to clean up after ourselves
// 20 ticks should be more than enough time considering we're just waiting for the server to attempt to
// send the packet
// and even if the server was lagging, the scheduler should be lagging too
Manager.runSyncLater(() ->
{
_pendingSpectate.remove(target.getUniqueId());
}, 20L);
}
@EventHandler(priority = EventPriority.LOW)
public void vehicleDamage(VehicleDamageEvent event)
{
@ -256,10 +287,57 @@ public class GameSpectatorManager implements Listener
if (!(event.getAttacker() instanceof Player))
return;
Player player = (Player)event.getAttacker();
if (!Manager.GetGame().IsAlive(player))
event.setCancelled(true);
}
@Override
public void handle(PacketInfo packetInfo)
{
if (packetInfo.getPacket() instanceof PacketPlayOutNamedEntitySpawn)
{
PacketPlayOutNamedEntitySpawn packet = (PacketPlayOutNamedEntitySpawn) packetInfo.getPacket();
if (_pendingSpectate.remove(packet.b))
{
// Handle Minestrike spam race condition
if (Manager.GetGame().IsAlive(packetInfo.getPlayer()))
{
return;
}
packetInfo.setCancelled(true);
EntityPlayer ep = ((CraftPlayer) packetInfo.getPlayer()).getHandle();
NetworkManager manager = ep.playerConnection.networkManager;
manager.a(packet, future ->
{
Manager.runSync(() ->
{
PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(packetInfo.getPlayer(), GameMode.SPECTATOR);
UtilServer.CallEvent(event);
if(event.isCancelled()) {
return;
}
ep.playerInteractManager.setGameMode(WorldSettings.EnumGamemode.getById(GameMode.SPECTATOR.getValue()));
ep.fallDistance = 0.0F;
manager.a(new PacketPlayOutCamera(ep), future1 ->
{
manager.a(new PacketPlayOutGameStateChange(3, (float)GameMode.SPECTATOR.getValue()), future2 ->
{
PacketPlayOutCamera p1 = new PacketPlayOutCamera();
p1.a = packet.a;
manager.handle(p1);
});
});
});
});
}
}
}
}