Revert "Encapsulate remote process related code into new project ServerProcesses for better dependability. Likewise, add project Queue.Core for queue related API methods. Queuer incorporated into new matchmaking logic for matching and pairing players for ranked matches, as well as handling server management. Adapt Core and Arcade to new ranked system for proper loading, modification, and saving of rankings."

This reverts commit 02e9649c20.
This commit is contained in:
Ty Sayers 2015-05-01 12:34:32 -04:00
parent 02e9649c20
commit ac14cc40f4
46 changed files with 704 additions and 1274 deletions

View File

@ -14,6 +14,5 @@
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/craftbukkit.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/craftbukkit.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core.Common"/> <classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core.Common"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/NoCheatPlus.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/NoCheatPlus.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Queue.Core"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -2,20 +2,13 @@ package mineplex.core.elo;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import mineplex.core.MiniDbClientPlugin; import mineplex.core.MiniDbClientPlugin;
import mineplex.core.account.CoreClientManager; import mineplex.core.account.CoreClientManager;
import mineplex.core.common.util.NautHashMap; import mineplex.core.common.util.NautHashMap;
import mineplex.queuer.QueueParty;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class EloManager extends MiniDbClientPlugin<EloClientData> public class EloManager extends MiniDbClientPlugin<EloClientData>
@ -23,66 +16,91 @@ public class EloManager extends MiniDbClientPlugin<EloClientData>
private static Object _playerEloLock = new Object(); private static Object _playerEloLock = new Object();
private EloRepository _repository; private EloRepository _repository;
private RatingSystem _ratingSystem; private EloRatingSystem _ratingSystem;
private Map<String, RankedPlayer> _rankings; private NautHashMap<String, NautHashMap<String, Integer>> _playerElos;
public EloManager(JavaPlugin plugin, CoreClientManager clientManager) public EloManager(JavaPlugin plugin, CoreClientManager clientManager)
{ {
super("Elo Rating", plugin, clientManager); super("Elo Rating", plugin, clientManager);
_repository = new EloRepository(plugin); _repository = new EloRepository(plugin);
_ratingSystem = new EloRatingSystem(); _ratingSystem = new EloRatingSystem(new KFactor(0, 1200, 25), new KFactor(1201, 1600, 20), new KFactor(1601, 2000, 15), new KFactor(2001, 2500, 10));
_rankings = new HashMap<String, RankedPlayer>(); _playerElos = new NautHashMap<String, NautHashMap<String, Integer>>();
}
public void resolveMatch(Team winner, Team loser, int gamemode)
{
Set<RankedPlayer> winnerMembers = new HashSet<RankedPlayer>();
Set<RankedPlayer> loserMembers = new HashSet<RankedPlayer>();
for (String memberName : winner.getMemberNames())
{
RankedPlayer player = getRankedPlayer(memberName);
winnerMembers.add(player);
}
for (String memberName : loser.getMemberNames())
{
RankedPlayer player = getRankedPlayer(memberName);
loserMembers.add(player);
}
RankedTeam team1 = new RankedTeam(winnerMembers, gamemode);
RankedTeam team2 = new RankedTeam(loserMembers, gamemode);
RankedGame game = new RankedGame(team1, team2);
// Allow the rating system to resolve and update player ratings from match
_ratingSystem.resolveGame(game);
} }
public EloRating getRating(String playerName, int gamemode) public int getElo(UUID uuid, String gameType)
{ {
return getRankedPlayer(playerName).getRating(gamemode); int elo = 1000;
}
synchronized (_playerEloLock)
public RankedPlayer getRankedPlayer(String playerName)
{
return null; // TODO: Implement fetching for cached player ranking
}
private void savePlayer(RankedPlayer player)
{
// TODO: Do this asynchronously!
for (EloRating rating : player.getRatings())
{ {
if (rating.isUpdated()) if (_playerElos.containsKey(uuid.toString()))
{ {
_repository.saveRating(rating); if (_playerElos.get(uuid.toString()).containsKey(gameType))
{
elo = _playerElos.get(uuid.toString()).get(gameType);
}
} }
} }
return elo;
}
public EloTeam getNewRatings(EloTeam teamA, EloTeam teamB, GameResult result)
{
EloTeam newTeam = new EloTeam();
System.out.println("Old " + result + " Team Rating:" + teamA.TotalElo);
int newTotal = _ratingSystem.getNewRating(teamA.TotalElo / teamA.getPlayers().size(), teamB.TotalElo / teamB.getPlayers().size(), result) * teamA.getPlayers().size();
System.out.println("New " + result + " Team Rating:" + newTotal);
for (EloPlayer player : teamA.getPlayers())
{
EloPlayer newPlayer = new EloPlayer();
newPlayer.UniqueId = player.UniqueId;
newPlayer.Rating = (int)(player.Rating + ((double)player.Rating / (double)teamA.TotalElo) * (newTotal - teamA.TotalElo));
System.out.println("Old:");
player.printInfo();
System.out.println("New:");
newPlayer.printInfo();
newTeam.addPlayer(newPlayer);
}
return newTeam;
}
public void saveElo(UUID uuid, String gameType, int elo)
{
saveElo(uuid.toString(), gameType, elo);
}
public void saveElo(final String uuid, final String gameType, final int elo)
{
Bukkit.getServer().getScheduler().runTaskAsynchronously(getPlugin(), new Runnable()
{
public void run()
{
_repository.saveElo(uuid, gameType, elo);
synchronized (_playerEloLock)
{
if (_playerElos.containsKey(uuid))
{
if (_playerElos.get(uuid).containsKey(gameType))
{
_playerElos.get(uuid).put(gameType, elo);
}
}
}
}
});
} }
// TODO: DEPCRECATED?
@Override @Override
protected EloClientData AddPlayer(String player) protected EloClientData AddPlayer(String player)
{ {
@ -92,15 +110,12 @@ public class EloManager extends MiniDbClientPlugin<EloClientData>
@Override @Override
public void processLoginResultSet(String playerName, ResultSet resultSet) throws SQLException public void processLoginResultSet(String playerName, ResultSet resultSet) throws SQLException
{ {
Collection<EloRating> ratings = _repository.parseRatings(resultSet);
RankedPlayer rankedPlayer = new RankedPlayer(playerName, uuid, ratings);
Set(playerName, _repository.loadClientInformation(resultSet)); Set(playerName, _repository.loadClientInformation(resultSet));
} }
@Override @Override
public String getQuery(String uuid, String name) public String getQuery(String uuid, String name)
{ {
// Query elo ratings table for all the EloRatings associated with player logging in! return "SELECT gameType, elo FROM eloRating WHERE uuid = '" + uuid + "';";
return "SELECT uuid, gamemode, elo, matchesPlayed FROM eloRating WHERE uuid = '" + uuid + "';";
} }
} }

View File

@ -0,0 +1,12 @@
package mineplex.core.elo;
public class EloPlayer
{
public String UniqueId;
public int Rating;
public void printInfo()
{
System.out.println(UniqueId + "'s elo is " + Rating);
}
}

View File

@ -1,63 +0,0 @@
package mineplex.core.elo;
public class EloRating
{
public static final int PROVISIONAL_GAMES = 10; // Number of provisional games to ensure rating accuracy
public static final int DEFAULT_RATING = 1200; // Default starting ELO for first-time players.
private int _rating;
public int getRating() { return _rating; }
private int _gamemode;
public int getGamemode() { return _gamemode; }
private String _uuid;
public String getUuid() { return _uuid; }
private int _matchesPlayed;
public int getMatchesPlayed() { return _matchesPlayed; }
public void incrementMatchesPlayed() { _matchesPlayed++; }
private boolean _updated; // Whether this EloRating has been updated/modified since caching
public boolean isUpdated() { return _updated; }
private void onUpdate() { _updated = true; }
public EloRating(String uuid, int gamemode, int rating, int matchesPlayed)
{
_updated = false;
_uuid = uuid;
_gamemode = gamemode;
_rating = rating;
_matchesPlayed = matchesPlayed;;
}
public EloRating(String uuid, int gamemode)
{
this(uuid, gamemode, DEFAULT_RATING, 0);
}
public void updateRating(int eloDelta)
{
setRating(_rating + eloDelta);
}
public double getKFactor()
{
if (_matchesPlayed < PROVISIONAL_GAMES)
{
double provisionalPercent = _matchesPlayed / (double) PROVISIONAL_GAMES;
return 50d - (25 * provisionalPercent); // Range of [50, 25] Kfactor for provisionl games period
}
else
{
double ratio = ((double) PROVISIONAL_GAMES) / _matchesPlayed;
return 10d + 15 * (ratio); // Range of [10, 25] KFactor for games after provisionals
}
}
private void setRating(int rating)
{
_rating = Math.max(rating, 0);
onUpdate();
}
}

View File

@ -1,8 +1,6 @@
package mineplex.core.elo; package mineplex.core.elo;
import java.util.List; public class EloRatingSystem
public class EloRatingSystem implements RatingSystem
{ {
private final static int DEFAULT_KFACTOR = 25; private final static int DEFAULT_KFACTOR = 25;
@ -10,37 +8,26 @@ public class EloRatingSystem implements RatingSystem
public final static double DRAW = 0.5; public final static double DRAW = 0.5;
public final static double LOSS = 0.0; public final static double LOSS = 0.0;
public EloRatingSystem() private KFactor[] _kFactors = {};
public EloRatingSystem(KFactor...kFactors)
{ {
_kFactors = kFactors;
} }
@Override public int getNewRating(int rating, int opponentRating, GameResult result)
public void resolveGame(RankedGame game)
{ {
List<RankedTeam> teams = game.getSortedTeams(); switch (result)
RankedTeam winner = teams.get(0);
RankedTeam loser = teams.get(1); // TODO: Impelement non-arbitrary rating?
double winningExpected = getExpectedScore(winner.getElo(), loser.getElo());
double losingExpected = getExpectedScore(loser.getElo(), winner.getElo());
// Update the ratings of all participants
updateRatings(winner, winningExpected, 1.0d);
updateRatings(loser, losingExpected, 0.0d);
}
private void updateRatings(RankedTeam team, double expectedScore, double score)
{
double scoreDelta = score - expectedScore;
for (EloRating rating : team.getRatings())
{ {
// R' = R + K(S - E) case Win:
int eloDelta = (int) (rating.getKFactor() * scoreDelta); return getNewRating(rating, opponentRating, WIN);
rating.updateRating(eloDelta); case Loss:
rating.incrementMatchesPlayed(); return getNewRating(rating, opponentRating, LOSS);
case Draw:
System.out.println("Player with " + rating.getMatchesPlayed() + " games had ELO updated by " + eloDelta + " for new rating of " + rating.getRating()); return getNewRating(rating, opponentRating, DRAW);
} }
return -1;
} }
/** /**
@ -58,7 +45,7 @@ public class EloRatingSystem implements RatingSystem
*/ */
public int getNewRating(int rating, int opponentRating, double score) public int getNewRating(int rating, int opponentRating, double score)
{ {
double kFactor = 0;//getKFactor(rating); double kFactor = getKFactor(rating);
double expectedScore = getExpectedScore(rating, opponentRating); double expectedScore = getExpectedScore(rating, opponentRating);
int newRating = calculateNewRating(rating, score, expectedScore, kFactor); int newRating = calculateNewRating(rating, score, expectedScore, kFactor);
@ -70,7 +57,20 @@ public class EloRatingSystem implements RatingSystem
return oldRating + (int) (kFactor * (score - expectedScore)); return oldRating + (int) (kFactor * (score - expectedScore));
} }
public static double getExpectedScore(int rating, int opponentRating) private double getKFactor(int rating)
{
for (int i = 0; i < _kFactors.length; i++)
{
if (rating >= _kFactors[i].getStartIndex() && rating <= _kFactors[i].getEndIndex())
{
return _kFactors[i].value;
}
}
return DEFAULT_KFACTOR;
}
private double getExpectedScore(int rating, int opponentRating)
{ {
return 1.0 / (1.0 + Math.pow(10.0, ((double) (opponentRating - rating) / 400.0))); return 1.0 / (1.0 + Math.pow(10.0, ((double) (opponentRating - rating) / 400.0)));
} }

View File

@ -2,8 +2,6 @@ package mineplex.core.elo;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -14,59 +12,40 @@ import mineplex.core.database.column.ColumnVarChar;
public class EloRepository extends RepositoryBase public class EloRepository extends RepositoryBase
{ {
private static String INSERT_ELO = "INSERT INTO eloRating (uuid, gamemode, elo, matchesPlayed) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE elo=VALUES(elo);"; private static String CREATE_ELO_TABLE = "CREATE TABLE IF NOT EXISTS eloRating (id INT NOT NULL AUTO_INCREMENT, uuid VARCHAR(256), gameType VARCHAR(256), elo INT, PRIMARY KEY (id), UNIQUE INDEX uuid_gameType_index (uuid, gameType));";
private static String INSERT_ELO = "INSERT INTO eloRating (uuid, gameType, elo) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE elo=VALUES(elo);";
/**
* Constructor
* @param plugin
*/
public EloRepository(JavaPlugin plugin) public EloRepository(JavaPlugin plugin)
{ {
super(plugin, DBPool.ACCOUNT); super(plugin, DBPool.ACCOUNT);
initialize();
} }
/** public void initialize()
* Save & update an {@link EloRating} into repository.
* @param rating - the rating to be saved/updated in the repository.
*/
public void saveRating(EloRating rating)
{ {
String uuid = rating.getUuid(); //executeUpdate(CREATE_ELO_TABLE);
int gamemode = rating.getGamemode();
int elo = rating.getRating();
int matchesPlayed = rating.getMatchesPlayed();
// Execute update/insert
executeUpdate(INSERT_ELO, new ColumnVarChar("uuid", 100, uuid), new ColumnInt("gamemode", gamemode),
new ColumnInt("elo", elo), new ColumnInt("matchesPlayed", matchesPlayed));
} }
/** public void saveElo(String uuid, String gameType, int elo)
* Parse incoming SQL result from EloRatings table into usable {@link EloRating} objects.
* @param resultSet - the resulting set from SQL query for ELO ratings.
* @return parsed {@link EloRating} objects return from {@code resultSet}.
*/
public Set<EloRating> parseRatings(ResultSet resultSet) throws SQLException
{ {
Set<EloRating> ratings = new HashSet<EloRating>(); executeUpdate(INSERT_ELO, new ColumnVarChar("uuid", 100, uuid), new ColumnVarChar("gameType", 100, gameType), new ColumnInt("elo", elo));
}
public EloClientData loadClientInformation(ResultSet resultSet) throws SQLException
{
EloClientData clientData = new EloClientData();
while (resultSet.next()) while (resultSet.next())
{ {
String uuid = resultSet.getString(1); clientData.Elos.put(resultSet.getString(1), resultSet.getInt(2));
int gamemode = resultSet.getInt(2);
int elo = resultSet.getInt(3);
int matchesPlayed = resultSet.getInt(4);
EloRating rating = new EloRating(uuid, gamemode, elo, matchesPlayed);
ratings.add(rating);
} }
return ratings; return clientData;
} }
@Override @Override
protected void update() { } protected void update()
{
@Override }
protected void initialize() { }
} }

View File

@ -0,0 +1,23 @@
package mineplex.core.elo;
import java.util.ArrayList;
import java.util.List;
public class EloTeam
{
private List<EloPlayer> _players = new ArrayList<EloPlayer>();
public int TotalElo = 0;
public void addPlayer(EloPlayer player)
{
TotalElo += player.Rating;
_players.add(player);
}
public List<EloPlayer> getPlayers()
{
return _players;
}
}

View File

@ -0,0 +1,8 @@
package mineplex.core.elo;
public enum GameResult
{
Win,
Loss,
Draw
}

View File

@ -0,0 +1,34 @@
package mineplex.core.elo;
public class KFactor
{
public int startIndex, endIndex;
public double value;
public KFactor(int startIndex, int endIndex, double value)
{
this.startIndex = startIndex;
this.endIndex = endIndex;
this.value = value;
}
public int getStartIndex()
{
return startIndex;
}
public int getEndIndex()
{
return endIndex;
}
public double getValue()
{
return value;
}
public String toString()
{
return "kfactor: " + startIndex + " " + endIndex + " " + value;
}
}

View File

@ -1,43 +0,0 @@
package mineplex.core.elo;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class RankedGame
{
private Set<RankedTeam> _teams;
public RankedGame(RankedTeam team1, RankedTeam team2)
{
// TODO: Implement non-arbitrary ranking of multi-team game comps
_teams = new HashSet<RankedTeam>();
_teams.add(team1); // Winner team
_teams.add(team2); // Loser team
}
public List<RankedTeam> getSortedTeams()
{
List<RankedTeam> sortedTeams = new ArrayList<RankedTeam>(_teams);
Random rand = new Random();
double roll = rand.nextDouble();
int rating1 = sortedTeams.get(0).getElo();
int rating2 = sortedTeams.get(1).getElo();
double chanceOfWinning = EloRatingSystem.getExpectedScore(rating1, rating2);
if (roll > chanceOfWinning)
{
RankedTeam team1 = sortedTeams.remove(0);
sortedTeams.add(team1);
}
return sortedTeams;
}
}

View File

@ -1,47 +0,0 @@
package mineplex.core.elo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class RankedPlayer
{
private String _name;
public String getName() { return _name; }
private String _uuid;
private Map<Integer, EloRating> _ratings;
public RankedPlayer(String playerName, String uuid, Collection<EloRating> ratings)
{
_name = playerName;
_uuid = uuid;
// Initialize ratings
_ratings = new HashMap<Integer, EloRating>();
for (EloRating rating : ratings)
{
_ratings.put(rating.getGamemode(), rating);
}
}
public EloRating getRating(int gamemode)
{
if (!_ratings.containsKey(gamemode))
{
_ratings.put(gamemode, generateRating(gamemode));
}
return _ratings.get(gamemode);
}
public Collection<EloRating> getRatings()
{
return _ratings.values();
}
private EloRating generateRating(int gamemode)
{
return new EloRating(_uuid, gamemode);
}
}

View File

@ -1,58 +0,0 @@
package mineplex.core.elo;
import java.util.HashSet;
import java.util.Set;
import mineplex.queuer.Ranked;
public class RankedTeam implements Ranked, Team
{
private Set<RankedPlayer> _players;
private int _gamemode;
public RankedTeam(Set<RankedPlayer> players, int gamemode)
{
_players = players;
_gamemode = gamemode;
}
@Override
public int getElo()
{
int totalElo = 0;
for (EloRating rating : getRatings())
{
totalElo += rating.getRating();
}
return totalElo / _players.size(); /// Return average elo
}
@Override
public Set<String> getMemberNames()
{
Set<String> memberNames = new HashSet<String>();
for (RankedPlayer player : _players)
{
memberNames.add(player.getName());
}
return memberNames;
}
public Set<EloRating> getRatings()
{
Set<EloRating> ratings = new HashSet<EloRating>();
for (RankedPlayer player : _players)
{
ratings.add(player.getRating(_gamemode));
}
return ratings;
}
}

View File

@ -1,12 +0,0 @@
package mineplex.core.elo;
public interface RatingSystem
{
/**
* Resolve a {@link RankedGame} by updating the rankings of all participants dependant on
* the outcome of the game.
* @param game - the game to be resolved after completion (must be a completed RankedGame)
*/
public void resolveGame(RankedGame game);
}

View File

@ -1,9 +0,0 @@
package mineplex.core.elo;
import java.util.Set;
public interface Team
{
public Set<String> getMemberNames();
}

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/jedis-2.4.2.jar"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/gson-2.2.1.jar"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/commons-pool2-2.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Core.Common"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerProcesses"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AFTER_CLEAN_TARGETS" value="Hub,Arcade,StaffServer,ServerMonitor,"/>
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AUTO_TARGETS" value="Arcade,Hub,StaffServer,ServerMonitor,"/>
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_MANUAL_TARGETS" value="Arcade,Hub,StaffServer,ServerMonitor,"/>
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${project}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${BUILD_FILES}/common.xml"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Mineplex.Queue.Core}"/>
</launchConfiguration>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Mineplex.Queue.Core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="library" name="httpclient" level="project" />
<orderEntry type="library" name="httpcore" level="project" />
<orderEntry type="library" name="jedis" level="project" />
<orderEntry type="library" name="commons-pool2" level="project" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- WARNING: Eclipse auto-generated file.
Any modifications will be overwritten.
To include a user specific buildfile here, simply create one in the same
directory with the processing instruction <?eclipse.ant.import?>
as the first entry and export the buildfile again. --><project basedir="." default="build" name="Mineplex.Queue.Core">
<property environment="env"/>
<property name="REPO_DIR" value="../../"/>
<property name="Mineplex.Queuer.location" value="../Mineplex.Queuer"/>
<property name="Mineplex.Core.Common.location" value="../Mineplex.Core.Common"/>
<property name="Mineplex.ServerData.location" value="../Mineplex.ServerData"/>
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.8"/>
<property name="source" value="1.8"/>
<path id="Mineplex.Core.Common.classpath">
<pathelement location="${Mineplex.Core.Common.location}/bin"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/craftbukkit.jar"/>
</path>
<path id="Mineplex.ServerData.classpath">
<pathelement location="${Mineplex.ServerData.location}/bin"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/jedis-2.4.2.jar"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/gson-2.2.1.jar"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/commons-pool2-2.2.jar"/>
</path>
<path id="Mineplex.Queue.Core.classpath">
<pathelement location="bin"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/jedis-2.4.2.jar"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/gson-2.2.1.jar"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/commons-pool2-2.2.jar"/>
<path refid="Mineplex.Core.Common.classpath"/>
<path refid="Mineplex.ServerData.classpath"/>
</path>
<target name="init">
<mkdir dir="bin"/>
<copy includeemptydirs="false" todir="bin">
<fileset dir="src">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="bin"/>
</target>
<target depends="clean" name="cleanall">
<ant antfile="build.xml" dir="${Mineplex.Core.Common.location}" inheritAll="false" target="clean"/>
<ant antfile="build.xml" dir="${Mineplex.ServerData.location}" inheritAll="false" target="clean"/>
</target>
<target depends="build-subprojects,build-project" name="build"/>
<target name="build-subprojects">
<ant antfile="build.xml" dir="${Mineplex.Core.Common.location}" inheritAll="false" target="build-project">
<propertyset>
<propertyref name="build.compiler"/>
</propertyset>
</ant>
<ant antfile="build.xml" dir="${Mineplex.ServerData.location}" inheritAll="false" target="build-project">
<propertyset>
<propertyref name="build.compiler"/>
</propertyset>
</ant>
</target>
<target depends="init" name="build-project">
<echo message="${ant.project.name}: ${ant.file}"/>
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
<src path="src"/>
<classpath refid="Mineplex.Queue.Core.classpath"/>
</javac>
</target>
<target description="Build all projects which reference this project. Useful to propagate changes." name="build-refprojects">
<ant antfile="build.xml" dir="${Mineplex.Queuer.location}" inheritAll="false" target="clean"/>
<ant antfile="build.xml" dir="${Mineplex.Queuer.location}" inheritAll="false" target="build">
<propertyset>
<propertyref name="build.compiler"/>
</propertyset>
</ant>
</target>
<target description="copy Eclipse compiler jars to ant lib directory" name="init-eclipse-compiler">
<copy todir="${ant.library.dir}">
<fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar"/>
</copy>
<unzip dest="${ant.library.dir}">
<patternset includes="jdtCompilerAdapter.jar"/>
<fileset dir="${ECLIPSE_HOME}/plugins" includes="org.eclipse.jdt.core_*.jar"/>
</unzip>
</target>
<target description="compile project with Eclipse compiler" name="build-eclipse-compiler">
<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
<antcall target="build"/>
</target>
</project>

View File

@ -1,115 +0,0 @@
package mineplex.queuer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import mineplex.serverdata.Region;
import mineplex.serverdata.data.Data;
import mineplex.serverdata.data.ServerGroup;
public class QueueParty implements Data, Ranked
{
private int _id;
public int getId() { return _id; }
private String _state;
public String getState() { return _state; }
public void setState(String state) { this._state = state; }
private Set<String> _players;
public Set<String> getPlayers() { return _players; }
private int _assignedMatch;
public int getAssignedMatch () { return _assignedMatch; }
public void setAssignedMatch(int assignedMatch) { this._assignedMatch = assignedMatch; }
private ServerGroup _serverGroup;
public ServerGroup getServerGroup() { return _serverGroup; }
private int _averageElo;
public int getAverageElo() { return _averageElo; }
public int getElo() { return getAverageElo(); }
public int getPlayerCount() { return _players.size(); }
private long _queueStartTime;
public long getQueueDuration() { return System.currentTimeMillis() - _queueStartTime; }
private boolean _prompted;
public boolean isPrompted() { return _prompted; }
public void setPrompted(boolean prompted) { this._prompted = prompted; }
private Region _region;
private Set<String> _otherPartyStates;
public Set<String> getOtherPartyStates() { return _otherPartyStates; }
public void setOtherPartyStates(Set<String> otherPartyStates) { this._otherPartyStates = otherPartyStates; }
private QueueParty(int id)
{
_id = id;
unassignMatch();
_prompted = false;
_region = Region.US;
_players = new HashSet<String>();
_otherPartyStates = new HashSet<String>();
_queueStartTime = System.currentTimeMillis();
}
public QueueParty(int id, Collection<String> players, ServerGroup serverGroup, int averageElo)
{
this(id);
_players.addAll(players);
_serverGroup = serverGroup;
_averageElo = averageElo;
}
public boolean hasAssignedMatch()
{
return _assignedMatch != -1;
}
/**
* @return true, if the {@link QueueParty}s status is set to 'Ready', false otherwise.
*/
public boolean isReady()
{
return getState().equalsIgnoreCase("Ready");
}
/**
* Assign a {@link Match} to this queue party.
* @param match - the match to be assigned for this party
*/
public void assignMatch(Match match)
{
setState("Awaiting Players");
setAssignedMatch(match.getId());
}
/**
* Unassign any matches from this QueueParty.
*/
public void unassignMatch()
{
setState("Awaiting Match");
setAssignedMatch(-1);
}
/**
* Initiate the sending of gameplay invite to the party when the assigned match
* is ready to start.
*/
public void sendInvites()
{
setState("Awaiting Confirmation");
}
@Override
public String getDataId()
{
return Integer.toString(_id);
}
}

View File

@ -1,15 +0,0 @@
package mineplex.queuer;
/**
* Ranked represents any object that can be classified as possessing an ELO ranking.
* @author MrTwiggy
*
*/
public interface Ranked
{
/**
* @return the ELO point ranking associated with this {@link Ranked} object.
*/
public int getElo();
}

View File

@ -8,7 +8,5 @@
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/> <classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/jedis-2.4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/jedis-2.4.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.Queue.Core"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerProcesses"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -1,11 +1,11 @@
package mineplex.queuer; package mineplex.queuer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import mineplex.serverdata.data.ServerGroup; public class Match
public class Match implements Ranked
{ {
private int _id; private int _id;
public int getId() { return _id; } public int getId() { return _id; }
@ -16,23 +16,16 @@ public class Match implements Ranked
private boolean _waitingForInvites; private boolean _waitingForInvites;
public boolean isWaitingForInvites() { return _waitingForInvites; } public boolean isWaitingForInvites() { return _waitingForInvites; }
private boolean _ready;
public boolean isReady() { return _ready; }
public void setReady(boolean ready) { _ready = ready; }
private long _waitingStartTime; private long _waitingStartTime;
public long getWaitDuration() { return System.currentTimeMillis() - _waitingStartTime; } public long getWaitDuration() { return System.currentTimeMillis() - _waitingStartTime; }
private ServerGroup _serverGroup; // The server group associated with this Match type. private int _averageElo;
public ServerGroup getServerGroup() { return _serverGroup; } public int getAverageElo() { return _averageElo; }
public Match(int id, ServerGroup serverGroup, QueueParty... parties) public Match(int id, int averageElo, QueueParty... parties)
{ {
_ready = false; this._id = id;
_id = id; this._averageElo = averageElo;
_parties = new HashSet<QueueParty>();
_waitingForInvites = false;
_serverGroup = serverGroup;
for (QueueParty party : parties) for (QueueParty party : parties)
{ {
@ -58,32 +51,6 @@ public class Match implements Ranked
_parties.remove(queueParty); _parties.remove(queueParty);
} }
/**
* @return average ELO of all members of the match, if the match contains players, 0 otherwise.
*/
@Override
public int getElo()
{
if (_parties.size() == 0) return 0;
int totalElo = 0;
for (QueueParty party : _parties)
{
totalElo += (party.getAverageElo() * party.getPlayerCount());
}
return totalElo / getPlayerCount();
}
/**
* @return the number of open slots remaining in the match to be filled.
*/
public int getOpenSlots()
{
int openSlots = _serverGroup.getMaxPlayers() - getPlayerCount();
return Math.max(openSlots, 0);
}
public int getPlayerCount() public int getPlayerCount()
{ {
int playerCount = 0; int playerCount = 0;

View File

@ -0,0 +1,78 @@
package mineplex.queuer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import mineplex.serverdata.Region;
import mineplex.serverdata.data.Data;
public class QueueParty implements Data
{
private int _id;
public int getId() { return _id; }
private String _state;
public String getState() { return _state; }
public void setState(String state) { this._state = state; }
private Set<String> _players;
public Set<String> getPlayers() { return _players; }
private int _assignedMatch;
public int getAssignedMatch () { return _assignedMatch; }
public void setAssignedMatch(int assignedMatch) { this._assignedMatch = assignedMatch; }
private int _variance;
private String _gameType;
private int _averageElo;
public int getAverageElo() { return _averageElo; }
private int _playerCount;
public int getPlayerCount() { return _playerCount; }
private long _queueStartTime;
private boolean _prompted;
public boolean isPrompted() { return _prompted; }
public void setPrompted(boolean prompted) { this._prompted = prompted; }
private Region _region;
private Set<String> _otherPartyStates;
public Set<String> getOtherPartyStates() { return _otherPartyStates; }
public void setOtherPartyStates(Set<String> otherPartyStates) { this._otherPartyStates = otherPartyStates; }
public QueueParty()
{
this._id = -1;
this._state = "Awaiting Match";
this._assignedMatch = -1;
this._variance = 25;
this._prompted = false;
this._region = Region.US;
this._players = new HashSet<String>();
this._otherPartyStates = new HashSet<String>();
this._queueStartTime = System.currentTimeMillis();
}
public QueueParty(Collection<String> players, String gameType, int averageElo)
{
this._players.addAll(players);
this._gameType = gameType;
this._averageElo = averageElo;
}
public boolean hasAssignedMatch()
{
return _assignedMatch != -1;
}
@Override
public String getDataId()
{
return Integer.toString(_id);
}
}

View File

@ -14,42 +14,27 @@ import mineplex.serverdata.Utility;
import mineplex.serverdata.commands.ServerTransfer; import mineplex.serverdata.commands.ServerTransfer;
import mineplex.serverdata.commands.TransferCommand; import mineplex.serverdata.commands.TransferCommand;
import mineplex.serverdata.data.DataRepository; import mineplex.serverdata.data.DataRepository;
import mineplex.serverdata.data.DedicatedServer;
import mineplex.serverdata.data.MinecraftServer; import mineplex.serverdata.data.MinecraftServer;
import mineplex.serverdata.data.ServerGroup;
import mineplex.serverdata.redis.RedisDataRepository; import mineplex.serverdata.redis.RedisDataRepository;
import mineplex.serverdata.servers.ConnectionData; import mineplex.serverdata.servers.ConnectionData;
import mineplex.serverdata.servers.ServerManager; import mineplex.serverdata.servers.ServerManager;
import mineplex.serverdata.servers.ServerRepository; import mineplex.serverdata.servers.ServerRepository;
import mineplex.serverprocesses.GenericRunnable;
import mineplex.serverprocesses.ProcessManager;
import mineplex.serverprocesses.ProcessRunner;
public class QueueRepository public class QueueRepository
{ {
private ServerRepository _serverRepository;
private DataRepository<QueueParty> _partyRepository; private DataRepository<QueueParty> _partyRepository;
private long _cacheTimeout;
private long _lastUpdated;
private Map<Integer, QueueParty> _cachedParties;
private int _serverId;
/** /**
* Class constructor * Class constructor
* @param host - the host to connect the QueueRepository to * @param host - the host to connect the QueueRepository to
* @param port - the designated port of the QueueRepository database * @param port - the designated port of the QueueRepository database
* @param region - the region of server queues to manage * @param region - the region of server queues to manage
*/ */
public QueueRepository(ConnectionData connectionData, Region region, long cacheTimeout) public QueueRepository(ConnectionData connectionData, Region region)
{ {
_serverRepository = ServerManager.getServerRepository(region); this._partyRepository = new RedisDataRepository<QueueParty>(connectionData, region,
_partyRepository = new RedisDataRepository<QueueParty>(connectionData, region,
QueueParty.class, "queue-parties"); QueueParty.class, "queue-parties");
_cacheTimeout = cacheTimeout;
_lastUpdated = 0;
_serverId = 0;
} }
/** /**
@ -60,23 +45,24 @@ public class QueueRepository
*/ */
public QueueRepository(Region region) public QueueRepository(Region region)
{ {
this(ServerManager.getMasterConnection(), region, 1000); this(ServerManager.getMasterConnection(), region);
} }
public QueueParty getQueueParty(int partyId) public QueueParty getQueueParty(int partyId)
{ {
updatePartyCache(); return _partyRepository.getElement(Integer.toString(partyId));
return _cachedParties.get(partyId); }
public QueueParty createQueueParty(Collection<String> players, String gameType, int averageElo)
{
QueueParty queueParty = new QueueParty(players, gameType, averageElo);
updateQueueParty(queueParty);
return queueParty;
} }
/**
* Update/save a Queue Party object to the repository.
* @param queueParty - the queue party to be updated/saved in repo.
*/
public void updateQueueParty(QueueParty queueParty) public void updateQueueParty(QueueParty queueParty)
{ {
_partyRepository.addElement(queueParty); _partyRepository.addElement(queueParty);
_cachedParties.put(queueParty.getId(), queueParty);
} }
public void deleteQueueParty(int partyId) public void deleteQueueParty(int partyId)
@ -97,20 +83,9 @@ public class QueueRepository
} }
} }
public boolean queuePartyExists(int partyId)
{
return getQueueParty(partyId) != null;
}
/**
*
* @return a cached set of active {@link QueueParty}'s that are guaranteed
* to have been cached less than {@code cacheTimeout} milliseconds.
*/
public Collection<QueueParty> getQueueParties() public Collection<QueueParty> getQueueParties()
{ {
updatePartyCache(); return _partyRepository.getElements();
return _cachedParties.values();
} }
public Collection<QueueParty> getJoinedQueueParties(int matchId) public Collection<QueueParty> getJoinedQueueParties(int matchId)
@ -142,16 +117,46 @@ public class QueueRepository
public void assignMatch(QueueParty queueParty, Match match) public void assignMatch(QueueParty queueParty, Match match)
{ {
queueParty.assignMatch(match); queueParty.setAssignedMatch(match.getId());
queueParty.setState("Awaiting Confirmation");
updateQueueParty(queueParty); updateQueueParty(queueParty);
} }
public void sendInvite(QueueParty queueParty) public void startMatch(int matchId)
{ {
queueParty.sendInvites(); MinecraftServer emptyServer = getEmptyServer();
updateQueueParty(queueParty);
}
if (emptyServer != null)
{
for (QueueParty queueParty : getJoinedQueueParties(matchId))
{
for (String playerName : queueParty.getPlayers())
{
// Execute a transfer command
ServerTransfer serverTransfer = new ServerTransfer(playerName, emptyServer.getName());
TransferCommand transferCommand = new TransferCommand(serverTransfer);
transferCommand.publish();
}
}
}
}
protected MinecraftServer getEmptyServer()
{
ServerRepository serverRepository = ServerManager.getServerRepository(Region.US);
Collection<MinecraftServer> servers = serverRepository.getServersByGroup("DominateElo");
for (MinecraftServer server : servers)
{
if (server.getPlayerCount() == 0)
{
return server;
}
}
return null;
}
public void deleteMatch(int matchId) public void deleteMatch(int matchId)
{ {
for (QueueParty queueParty : getJoinedQueueParties(matchId)) for (QueueParty queueParty : getJoinedQueueParties(matchId))
@ -161,27 +166,4 @@ public class QueueRepository
updateQueueParty(queueParty); updateQueueParty(queueParty);
} }
} }
/**
* Update the internal cache of {@link QueueParty}s if the cache age is
* older than {@code _cacheTimeout}.
*/
private void updatePartyCache()
{
long cacheAge = (System.currentTimeMillis() - _lastUpdated);
if (cacheAge >= _cacheTimeout) // Old cache, update contents
{
_cachedParties = new HashMap<Integer, QueueParty>();
for (QueueParty queueParty : _partyRepository.getElements())
{
_cachedParties.put(queueParty.getId(), queueParty);
}
}
}
private static void log(String message)
{
System.out.println(message);
}
} }

View File

@ -9,55 +9,208 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import mineplex.serverdata.Region; import mineplex.serverdata.Region;
import mineplex.serverdata.commands.ServerTransfer;
import mineplex.serverdata.commands.TransferCommand;
import mineplex.serverdata.data.DedicatedServer;
import mineplex.serverdata.data.ServerGroup;
import mineplex.serverdata.servers.ConnectionData;
import mineplex.serverdata.servers.ServerManager;
import mineplex.serverdata.servers.ServerRepository;
import mineplex.serverprocesses.GenericRunnable;
import mineplex.serverprocesses.ProcessManager;
import mineplex.serverprocesses.ProcessRunner;
public class Queuer public class Queuer
{ {
public static final int MIN_QUEUE_WAIT = 0; // The number of seconds required in queue before creating a new match for a party.
private static QueueRepository _repo; private static QueueRepository _repo;
private static ServerRepository _serverRepository;
private static Set<Match> _pendingMatches; // Set of all matches awaiting players
private static int _matchId = 0;
private static int _matchesMade = 0;
private static int _updates = 0;
private static int _partyId = 0;
private static int _serverId = 0;
public static void main (String args[]) public static void main (String args[])
{ {
Region region = (!new File("eu.dat").exists()) ? Region.US : Region.EU; Region region = (!new File("eu.dat").exists()) ? Region.US : Region.EU;
_serverRepository = ServerManager.getServerRepository(region);
_repo = new QueueRepository(region); _repo = new QueueRepository(region);
_pendingMatches = new HashSet<Match>();
HashMap<Integer, Integer> playerVarianceMap = new HashMap<Integer, Integer>();
HashMap<Integer, Match> playerPrepMatchMap = new HashMap<Integer, Match>();
Set<Match> matches = new HashSet<Match>();
QueuePartySorter partySorter = new QueuePartySorter();
int matchId = 1;
while (true) while (true)
{ {
_updates++; int matchesMade = 0;
updateQueuer(); matchId %= 1500;
ProcessManager.getInstance().updateProcesses();
log("Total pending matches after update: " + _pendingMatches.size()); List<Integer> assignedMatchIdChecked = new ArrayList<Integer>();
log("Total queued parties after update: " + _repo.getQueueParties().size()); Map<Integer, QueueParty> queueParties = _repo.getMappedQueueParties();
int matchPlayerCount = 2;
System.out.println("Checking " + queueParties.size() + " queues...");
for (QueueParty queueParty : queueParties.values())
{
int partyId = queueParty.getId();
int variance = playerVarianceMap.containsKey(partyId) ? playerVarianceMap.get(partyId) : 0;
variance += 25;
playerVarianceMap.put(partyId, variance);
if (queueParty.hasAssignedMatch())
{
for (Match match : matches)
{
if (Math.abs(match.getAverageElo() - queueParty.getAverageElo()) <= variance)
{
if (playerPrepMatchMap.containsKey(partyId))
{
if (playerPrepMatchMap.get(partyId) == match)
break;
playerPrepMatchMap.get(partyId).quitQueueParty(queueParty);
}
match.joinQueueParty(queueParty);
playerPrepMatchMap.put(partyId, match);
log("Found prep match for '" + queueParty.getId() + "'");
break;
}
}
if (!playerPrepMatchMap.containsKey(partyId))
{
Match match = new Match(matchId++, queueParty.getAverageElo(), queueParty);
playerPrepMatchMap.put(partyId, match);
matches.add(match);
}
}
else if (!assignedMatchIdChecked.contains(queueParty.getAssignedMatch()))
{
int assignedMatchId = queueParty.getAssignedMatch();
log("Checking if match '" + assignedMatchId + "' is ready.");
//List<String> matchStatuses = _repo.getMatchStatuses(queueRecord.AssignedMatch);
Collection<QueueParty> joinedParties = _repo.getJoinedQueueParties(assignedMatchId);
boolean matchReady = true;
boolean matchDeny = false;
for (QueueParty joinedParty : joinedParties)
{
String partyState = joinedParty.getState();
if (partyState.equalsIgnoreCase("Deny"))
{
matchDeny = true;
matchReady = false;
break;
}
else if (!partyState.equalsIgnoreCase("Ready"))
{
matchReady = false;
}
}
if (matchReady)
{
_repo.startMatch(assignedMatchId);
_repo.deleteAssignedParties(assignedMatchId);
System.out.println("Starting match '" + assignedMatchId + "'");
}
else if (matchDeny)
{
_repo.deleteMatch(assignedMatchId);
}
assignedMatchIdChecked.add(assignedMatchId);
}
}
System.out.println("Checking " + matches.size() + " matches...");
// Check for and kick off invites for ready matches
for (Iterator<Match> matchIterator = matches.iterator(); matchIterator.hasNext();)
{
Match match = matchIterator.next();
// Don't give me crap about not using iterator...can't cuz of stupid thing.
Set<QueueParty> partiesToRemove = new HashSet<QueueParty>();
for (QueueParty queueParty : match.getParties())
{
if (!queueParties.containsKey(queueParty.getId()))
{
log("Removing matchStatus : " + queueParty.getId());
partiesToRemove.add(queueParty);
if (match.isWaitingForInvites())
{
_repo.deleteMatch(match.getId());
match.setWaitingForInvites(false);
}
}
}
for (QueueParty party : partiesToRemove)
{
match.quitQueueParty(party);
}
if (match.isWaitingForInvites())
{
if ((match.getWaitDuration()) > 15000)
{
for (QueueParty queueParty : match.getParties())
{
if (!queueParty.getState().equalsIgnoreCase("Ready"))
{
_repo.deleteQueueParty(queueParty.getId());
}
}
_repo.deleteMatch(match.getId());
match.setWaitingForInvites(false);
}
continue;
}
if (match.getPlayerCount() >= matchPlayerCount)
{
List<QueueParty> partyList = new ArrayList<QueueParty>(match.getParties());
Collections.sort(partyList, partySorter);
int playerCount = 0;
for (QueueParty party : partyList)
{
if (playerCount + party.getPlayerCount() > matchPlayerCount)
{
match.quitQueueParty(party);
playerPrepMatchMap.remove(party.getId());
log("Oops hit player cap, can't fit you in this match.");
continue;
}
playerCount += party.getPlayerCount();
}
if (playerCount == matchPlayerCount)
{
log("Sent match invites for '" + match.getId() + "'");
for (QueueParty party : match.getParties())
{
playerPrepMatchMap.remove(party.getId());
_repo.assignMatch(party, match);
}
match.setWaitingForInvites(true);
matchesMade++;
}
}
else if (match.getPlayerCount() == 0)
{
matchIterator.remove();
}
}
try try
{ {
if (_matchesMade > 0) if (matchesMade > 0)
System.out.println("Made " + _matchesMade + " matches."); System.out.println("Made " + matchesMade + " matches.");
Thread.sleep(1000); Thread.sleep(1000);
} }
@ -69,291 +222,6 @@ public class Queuer
} }
/**
* Tick & update the Queuer as a whole, making one whole pass through all queued players and pending matches to
* assign matches to parties and start matches.
*/
private static void updateQueuer()
{
// Update the status of each queue party, searching for best matchings and assigning matches to parties.
for (QueueParty queueParty : _repo.getQueueParties())
{
updateParty(queueParty);
}
// Update all matches, and remove pending matches if they are finished.
Iterator<Match> iterator = _pendingMatches.iterator();
while (iterator.hasNext())
{
Match match = iterator.next();
boolean matchFinished = updateMatch(match);
// Remove match if it is completed/finished
if (matchFinished) iterator.remove();
}
}
/**
* Update the status of a {@link QueueParty} by attempting to locate the best resulting
* {@code Match} available, or creating a new one if required.
* @param queueParty - the queue party to be updated for matchmaking purposes.
*/
private static void updateParty(QueueParty queueParty)
{
int queueDuration = (int) queueParty.getQueueDuration() / 1000; // Queue duration in seconds
Match bestMatch = getBestMatch(queueParty);
if (queueParty.hasAssignedMatch())
{
// TODO: If player has been waiting too long in current game and there is a better match, join that!
}
else
{
if (bestMatch != null) // Assign party into best match!
{
bestMatch.joinQueueParty(queueParty);
_repo.assignMatch(queueParty, bestMatch);
}
else if (queueDuration >= MIN_QUEUE_WAIT) // Create a new match for others to join!
{
Match match = new Match(_matchId++, queueParty.getServerGroup(), queueParty);
_pendingMatches.add(match);
_repo.assignMatch(queueParty, match);
}
}
}
/**
* Update a {@link Match} by verifying it's player statuses, sending out invites
* and managing a Match from creation to deletion.
* @param match - the match to be updated.
* @return true, if the match is no longer required (successful or otherwise) and should be removed, false otherwise.
*/
private static boolean updateMatch(Match match)
{
// Remove queued parties that have left queue/match
// Don't give me crap about not using iterator...can't cuz of stupid thing.
Set<QueueParty> partiesToRemove = new HashSet<QueueParty>();
for (QueueParty queueParty : match.getParties())
{
int partyId = queueParty.getId();
if (!_repo.queuePartyExists(partyId))
{
log("Removing matchStatus : " + queueParty.getId());
partiesToRemove.add(queueParty);
if (match.isWaitingForInvites())
{
_repo.deleteMatch(match.getId());
match.setWaitingForInvites(false);
}
}
}
for (QueueParty party : partiesToRemove)
{
match.quitQueueParty(party);
}
// If match took too long to find players, or is empty, quit match.
if (match.getPlayerCount() == 0)
{
return true; // Match is empty, remove from pending matches.
}
// If match sent invites and is waiting for too long (15 seconds), kick players who didn't
// accept and keep looking
// Otherwise if everyone accepted, start game!
if (match.isWaitingForInvites())
{
boolean matchReady = true;
for (QueueParty party : _repo.getJoinedQueueParties(match.getId()))
{
if (!party.isReady())
{
matchReady = false;
}
}
if (!matchReady && match.getWaitDuration() > 500)
{
matchReady = true;
}
if (match.isReady()) // Invites accepted, MinecraftServer started, and players transferred.
{
return true;
}
else if (matchReady) // Players accepted invites, start match!
{
startMatch(match);
return false;
}
else if (match.getWaitDuration() > 15000)
{
for (QueueParty queueParty : match.getParties())
{
if (!queueParty.isReady())
{
_repo.deleteQueueParty(queueParty.getId());
}
}
_repo.deleteMatch(match.getId());
match.setWaitingForInvites(false);
}
return false;
}
// Match has filled up, send out invites!
if (match.getOpenSlots() == 0)
{
for (QueueParty party : match.getParties())
{
_repo.sendInvite(party);
}
match.setWaitingForInvites(true);
}
return false;
}
/**
* @param queueParty - the party for whom a match is being searched.
* @return the best matching {@link Match} for the {@code queueParty}, if an acceptable {@link Match}
* could be found, null otherwise.
*/
private static Match getBestMatch(QueueParty queueParty)
{
Match best = null;
int minEloDelta = 0;
int variance = getSearchVariance(queueParty);
for (Match match : _pendingMatches)
{
if (match.getOpenSlots() >= queueParty.getPlayerCount())
{
int eloDelta = getEloDelta(queueParty, match);
if (eloDelta <= variance && (eloDelta < minEloDelta || best == null))
{
best = match;
minEloDelta = eloDelta;
}
}
}
return best;
}
/**
* @param r1
* @param r2
* @return the ELO point delta (difference) between two {@link Ranked} objects.
*/
private static int getEloDelta(Ranked r1, Ranked r2)
{
return Math.abs(r1.getElo() - r2.getElo());
}
public static boolean startMatch(Match match)
{
ServerGroup group = match.getServerGroup();
DedicatedServer bestServer = getBestDedicatedServer(group);
startServer(bestServer, group, match);
return true;
}
/**
* Transfer all players queue'd into a {@link Match} to one server.
* @param match - the match whose queue'd players are to be transferred
* @param serverName - the name of the server to transfer the players to
*/
public static void transferPlayers(Match match, String serverName)
{
for (QueueParty queueParty : _repo.getJoinedQueueParties(match.getId()))
{
for (String playerName : queueParty.getPlayers())
{
// Execute a transfer command
ServerTransfer serverTransfer = new ServerTransfer(playerName, serverName);
TransferCommand transferCommand = new TransferCommand(serverTransfer);
transferCommand.publish();
}
}
}
/**
* @return newly generated unique server id.
*/
public static int generateServerId()
{
return _serverId++;
}
private static void startServer(final DedicatedServer serverSpace, final ServerGroup serverGroup, final Match match)
{
String cmd = "/home/mineplex/easyRemoteStartServerCustom.sh";
final String groupPrefix = serverGroup.getPrefix();
final String serverName = serverSpace.getName();
final String serverAddress = serverSpace.getPublicAddress();
final int serverId = generateServerId();
ProcessRunner pr = new ProcessRunner(new String[] {"/bin/sh", cmd, serverAddress, serverSpace.getPrivateAddress(), (serverGroup.getPortSection() + serverId) + "", serverGroup.getRequiredRam() + "", serverGroup.getWorldZip(), serverGroup.getPlugin(), serverGroup.getConfigPath(), serverGroup.getName(), serverGroup.getPrefix() + "-" + serverId, serverSpace.isUsRegion() ? "true" : "false", serverGroup.getAddNoCheat() + "" });
pr.start(new GenericRunnable<Boolean>()
{
public void run(Boolean error)
{
if (!error)
{
// Successfully started server, now transfer players
transferPlayers(match, serverName);
}
else
{
// TODO: Error in starting server for ELO match, try again or disband queued match?
log("[" + serverName + ":" + serverAddress + " Free Resources; CPU " + serverSpace.getAvailableCpu() + " RAM " + serverSpace.getAvailableRam() + "MB] Errored " + serverName + "(" + groupPrefix+ "-" + serverId + ")");
}
}
});
ProcessManager.getInstance().addProcess(pr);
serverSpace.incrementServerCount(serverGroup);
}
private static DedicatedServer getBestDedicatedServer(ServerGroup serverGroup)
{
Collection<DedicatedServer> dedicatedServers = _serverRepository.getDedicatedServers();
DedicatedServer bestServer = null;
for (DedicatedServer serverData : dedicatedServers)
{
if (serverData.getAvailableRam() > serverGroup.getRequiredRam()
&& serverData.getAvailableCpu() > serverGroup.getRequiredCpu())
{
if (bestServer == null || serverData.getServerCount(serverGroup) < bestServer.getServerCount(serverGroup))
{
bestServer = serverData;
}
}
}
return bestServer;
}
/**
* @param party - the party whose ELO search variance is being fetched.
* @return the variance in ELO search parameters for {@code party}.
* I.E: Queuer searches for potential matches in [party.elo - variance, party.elo + variance] ELO range.
*/
private static int getSearchVariance(QueueParty party)
{
int seconds = (int) party.getQueueDuration() / 1000; // Duration of queue in seconds
return seconds * 10; // 5 ELO variance for every second in queue
}
private static void log(String message) private static void log(String message)
{ {

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType"> <launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AFTER_CLEAN_TARGETS" value="Queuer,"/> <stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AFTER_CLEAN_TARGETS" value="ServerMonitor,"/>
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AUTO_TARGETS" value="Queuer,"/> <stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_AUTO_TARGETS" value="ServerMonitor,"/>
<stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_MANUAL_TARGETS" value="Queuer,"/> <stringAttribute key="org.eclipse.ant.ui.ATTR_ANT_MANUAL_TARGETS" value="ServerMonitor,"/>
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/> <booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/> <booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${resource}"/> <stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${project}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/> <booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/> <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
@ -13,5 +13,5 @@
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${BUILD_FILES}/common.xml"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${BUILD_FILES}/common.xml"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/> <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Mineplex.Queuer}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Mineplex.ServerMonitor}"/>
</launchConfiguration> </launchConfiguration>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<projectDescription> <projectDescription>
<name>Mineplex.Queue.Core</name> <name>Mineplex.ServerData</name>
<comment></comment> <comment></comment>
<projects> <projects>
</projects> </projects>
@ -10,6 +10,16 @@
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/ServerData.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>

View File

@ -13,7 +13,7 @@
<property name="debuglevel" value="source,lines,vars"/> <property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.7"/> <property name="target" value="1.7"/>
<property name="source" value="1.7"/> <property name="source" value="1.7"/>
<path id="Mineplex.ServerDataclasspath"> <path id="Mineplex.ServerData.classpath">
<pathelement location="bin"/> <pathelement location="bin"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/jedis-2.4.2.jar"/> <pathelement location="${REPO_DIR}/Plugins/Libraries/jedis-2.4.2.jar"/>
<pathelement location="${REPO_DIR}/Plugins/Libraries/commons-pool2-2.2.jar"/> <pathelement location="${REPO_DIR}/Plugins/Libraries/commons-pool2-2.2.jar"/>

View File

@ -4,11 +4,10 @@ package mineplex.serverdata.servers;
* ConnectionData stores information relevant for initiating a connection to a repository. * ConnectionData stores information relevant for initiating a connection to a repository.
* @author MrTwiggy * @author MrTwiggy
* *
*
*/ */
public class ConnectionData public class ConnectionData
{ {
private String _host; // The host URL to connect to repository private String _host; // The host URL to connect to repository
public String getHost() { return _host; } public String getHost() { return _host; }

View File

@ -16,10 +16,10 @@ public class ServerManager
{ {
// Connection host to server database // Connection host to server database
private static final String DATABASE_HOST = "localhost";// TESTING, REPLACE WITH ORIGINAL: "10.33.53.16"; private static final String DATABASE_HOST = "10.33.53.16";
// Ports associated with slave redis instances // Ports associated with slave redis instances
private static final int[] SLAVE_PORTS = {6379}; // TESTING, REPLACE WITH: {6377, 6378, 6380, 6381, 6382}; private static final int[] SLAVE_PORTS = {6377, 6378, 6380, 6381, 6382};
private static Random random = new Random(); private static Random random = new Random();
// The cached repository instances // The cached repository instances

View File

@ -7,6 +7,5 @@
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpclient-4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpclient-4.2.jar"/>
<classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/> <classpathentry kind="var" path="REPO_DIR/Plugins/Libraries/httpcore-4.2.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/> <classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerData"/>
<classpathentry combineaccessrules="false" kind="src" path="/Mineplex.ServerProcesses"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -1,4 +1,4 @@
package mineplex.serverprocesses; package mineplex.servermonitor;
public interface GenericRunnable<T> public interface GenericRunnable<T>
{ {

View File

@ -1,4 +1,4 @@
package mineplex.serverprocesses; package mineplex.servermonitor;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -12,7 +12,7 @@ public class ProcessRunner extends Thread
boolean _done = false; boolean _done = false;
Boolean _error = false; Boolean _error = false;
public ProcessRunner(String[] args) ProcessRunner(String[] args)
{ {
super("ProcessRunner " + args); super("ProcessRunner " + args);
_processBuilder = new ProcessBuilder(args); _processBuilder = new ProcessBuilder(args);

View File

@ -29,15 +29,13 @@ import mineplex.serverdata.data.ServerGroup;
import mineplex.serverdata.servers.DedicatedServerSorter; import mineplex.serverdata.servers.DedicatedServerSorter;
import mineplex.serverdata.servers.ServerManager; import mineplex.serverdata.servers.ServerManager;
import mineplex.serverdata.servers.ServerRepository; import mineplex.serverdata.servers.ServerRepository;
import mineplex.serverprocesses.GenericRunnable;
import mineplex.serverprocesses.ProcessManager;
import mineplex.serverprocesses.ProcessRunner;
public class ServerMonitor public class ServerMonitor
{ {
private static ServerRepository _repository = null; private static ServerRepository _repository = null;
private static StatusHistoryRepository _historyRepository = null; private static StatusHistoryRepository _historyRepository = null;
private static int _count = 0; private static int _count = 0;
private static HashSet<ProcessRunner> _processes = new HashSet<ProcessRunner>();
private static HashMap<String, Boolean> _badServers = new HashMap<String, Boolean>(); private static HashMap<String, Boolean> _badServers = new HashMap<String, Boolean>();
private static Collection<MinecraftServer> _serverStatuses = null; private static Collection<MinecraftServer> _serverStatuses = null;
private static Collection<ServerGroup> _serverGroups = null; private static Collection<ServerGroup> _serverGroups = null;
@ -285,7 +283,55 @@ public class ServerMonitor
} }
} }
ProcessManager.getInstance().updateProcesses(); int processWaits = 0;
while (_processes.size() > 0)
{
for (Iterator<ProcessRunner> iterator = _processes.iterator(); iterator.hasNext();)
{
ProcessRunner pr = iterator.next();
try
{
pr.join(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if (pr.isDone())
iterator.remove();
}
if (_processes.size() > 0)
{
try
{
log("Sleeping while processes run...");
Thread.sleep(6000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
if (processWaits >= 10)
{
log("Killing stale processes.");
for (Iterator<ProcessRunner> iterator = _processes.iterator(); iterator.hasNext();)
{
iterator.next().abort();
iterator.remove();
}
}
processWaits++;
}
processWaits = 0;
try try
{ {
@ -519,9 +565,7 @@ public class ServerMonitor
if (!pr.isDone()) if (!pr.isDone())
{ _processes.add(pr);
ProcessManager.getInstance().addProcess(pr);
}
} }
private static boolean isServerOffline(DedicatedServer serverData) private static boolean isServerOffline(DedicatedServer serverData)
@ -622,9 +666,7 @@ public class ServerMonitor
serverSpace.incrementServerCount(serverGroup); serverSpace.incrementServerCount(serverGroup);
if (!pr.isDone()) if (!pr.isDone())
{ _processes.add(pr);
ProcessManager.getInstance().addProcess(pr);
}
} }
private static void log(String message) private static void log(String message)

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Mineplex.ServerProcesses</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -1,119 +0,0 @@
package mineplex.serverprocesses;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Handles the management and cleaning of {@link ProcessRunnner}s.
* @author MrTwiggy
*
*/
public class ProcessManager
{
private static ProcessManager _instance; // Singleton instance
private Set<ProcessRunner> _processes; // Set of running processes currently managed.
/**
* Private class constructor to prevent non-singletons.
*/
private ProcessManager()
{
_processes = new HashSet<ProcessRunner>();
}
/**
* Add a {@link ProcessRunner} to the manager to be updated and cleaned after completion.
* @param process - the process to be added to the manager.
*/
public void addProcess(ProcessRunner process)
{
_processes.add(process);
}
/**
* Update processes and clean up stale ones.
* NOTE: Blocks calling thread while polling process states.
* @return the number of {@link ProcessRunner}s that were finished and cleaned up.
*/
public int updateProcesses()
{
int cleanedProcesses = 0;
int processWaits = 0;
while (_processes.size() > 0)
{
for (Iterator<ProcessRunner> iterator = _processes.iterator(); iterator.hasNext();)
{
ProcessRunner pr = iterator.next();
try
{
pr.join(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if (pr.isDone())
{
iterator.remove();
cleanedProcesses++;
}
}
if (_processes.size() > 0)
{
try
{
log("Sleeping while processes run...");
Thread.sleep(6000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
if (processWaits >= 10)
{
log("Killing stale processes.");
for (Iterator<ProcessRunner> iterator = _processes.iterator(); iterator.hasNext();)
{
iterator.next().abort();
iterator.remove();
cleanedProcesses++;
}
}
processWaits++;
}
return cleanedProcesses;
}
/**
* @return a singleton instance of {@link ProcessManager}.
*/
public static ProcessManager getInstance()
{
if (_instance == null)
{
_instance = new ProcessManager();
}
return _instance;
}
/**
* Log a message to developer output.
* @param message - the message to be logged.
*/
private static void log(String message)
{
System.out.println(message); // Is this technically considered logging?
}
}

View File

@ -96,7 +96,7 @@ public abstract class Game implements Listener
public ArcadeManager Manager; public ArcadeManager Manager;
//Game //Game
protected GameType _gameType; private GameType _gameType;
protected String[] _gameDesc; protected String[] _gameDesc;
//Map //Map
@ -260,6 +260,9 @@ public abstract class Game implements Listener
public String Winner = "Nobody"; public String Winner = "Nobody";
public GameTeam WinnerTeam = null; public GameTeam WinnerTeam = null;
public boolean EloRanking = false;
public int EloStart = 1000;
public boolean CanAddStats = true; public boolean CanAddStats = true;
public boolean CanGiveLoot = true; public boolean CanGiveLoot = true;
@ -865,6 +868,18 @@ public abstract class Game implements Listener
Scoreboard.SetPlayerTeam(player, "SPEC"); Scoreboard.SetPlayerTeam(player, "SPEC");
} }
@EventHandler
public void eloStart(PlayerLoginEvent event)
{
if (EloRanking)
{
if (Manager.getEloManager().getElo(event.getPlayer().getUniqueId(), GetName()) == -1)
{
Manager.getEloManager().saveElo(event.getPlayer().getUniqueId(), GetName(), EloStart);
}
}
}
@EventHandler @EventHandler
public abstract void ScoreboardUpdate(UpdateEvent event); public abstract void ScoreboardUpdate(UpdateEvent event);

View File

@ -4,14 +4,12 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set;
import mineplex.core.common.util.C; import mineplex.core.common.util.C;
import mineplex.core.common.util.F; import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilMath; import mineplex.core.common.util.UtilMath;
import mineplex.core.common.util.UtilPlayer; import mineplex.core.common.util.UtilPlayer;
import mineplex.core.common.util.UtilServer; import mineplex.core.common.util.UtilServer;
import mineplex.core.elo.Team;
import nautilus.game.arcade.game.GameTeam.PlayerState; import nautilus.game.arcade.game.GameTeam.PlayerState;
import nautilus.game.arcade.kit.Kit; import nautilus.game.arcade.kit.Kit;
import nautilus.game.arcade.kit.KitAvailability; import nautilus.game.arcade.kit.KitAvailability;
@ -23,7 +21,7 @@ import org.bukkit.entity.Creature;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class GameTeam implements Team public class GameTeam
{ {
private Game Host; private Game Host;
@ -383,17 +381,4 @@ public class GameTeam implements Team
return _places; return _places;
} }
@Override
public Set<String> getMemberNames()
{
Set<String> memberNames = new HashSet<String>();
for (Player player : _players.keySet())
{
memberNames.add(player.getName());
}
return memberNames;
}
} }

View File

@ -27,9 +27,6 @@ public abstract class TeamGame extends Game
protected NautHashMap<String, Double> RejoinHealth = new NautHashMap<String, Double>(); protected NautHashMap<String, Double> RejoinHealth = new NautHashMap<String, Double>();
protected long RejoinTime = 120000; protected long RejoinTime = 120000;
// Whether or not this is a Ranked match.
protected boolean Ranked = false;
public TeamGame(ArcadeManager manager, GameType gameType, Kit[] kits, String[] gameDesc) public TeamGame(ArcadeManager manager, GameType gameType, Kit[] kits, String[] gameDesc)
{ {
@ -215,19 +212,6 @@ public abstract class TeamGame extends Game
if (player.isOnline()) if (player.isOnline())
AddGems(player, 10, "Participation", false, false); AddGems(player, 10, "Participation", false, false);
} }
// Update and resolve match for ranked games
if (Ranked)
{
if (GetTeamList().size() == 2) // Must have matches of 2 teams for now
{
List<GameTeam> teamList = GetTeamList();
GameTeam loserTeam = (teamList.get(1).equals(WinnerTeam)) ? teamList.get(0) : teamList.get(1);
int gamemode = _gameType.getGameId();
Manager.getEloManager().resolveMatch(WinnerTeam, loserTeam, gamemode); // Resolves and updates player ratings.
}
}
//End //End
SetState(GameState.End); SetState(GameState.End);

View File

@ -61,6 +61,9 @@ public class ChampionsDominate extends Domination
InventoryOpenChest = true; InventoryOpenChest = true;
EloRanking = false;
EloStart = 1000;
this.DisableKillCommand = false; this.DisableKillCommand = false;
registerStatTrackers( registerStatTrackers(

View File

@ -6,7 +6,6 @@ import java.util.HashMap;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -22,6 +21,9 @@ import org.bukkit.event.player.PlayerPickupItemEvent;
import mineplex.core.common.util.C; import mineplex.core.common.util.C;
import mineplex.core.common.util.UtilBlock; import mineplex.core.common.util.UtilBlock;
import mineplex.core.common.util.UtilPlayer; import mineplex.core.common.util.UtilPlayer;
import mineplex.core.elo.EloPlayer;
import mineplex.core.elo.EloTeam;
import mineplex.core.elo.GameResult;
import mineplex.core.updater.UpdateType; import mineplex.core.updater.UpdateType;
import mineplex.core.updater.event.UpdateEvent; import mineplex.core.updater.event.UpdateEvent;
import mineplex.minecraft.game.core.combat.CombatComponent; import mineplex.minecraft.game.core.combat.CombatComponent;
@ -277,6 +279,48 @@ public class Domination extends TeamGame
if (player.isOnline()) if (player.isOnline())
AddGems(player, 10, "Participation", false, false); AddGems(player, 10, "Participation", false, false);
} }
if (EloRanking)
{
EloTeam teamWinner = new EloTeam();
EloTeam teamLoser = new EloTeam();
for (GameTeam team : GetTeamList())
{
if (WinnerTeam != null && team.equals(WinnerTeam))
{
for (Player player : WinnerTeam.GetPlayers(false))
{
EloPlayer eloPlayer = new EloPlayer();
eloPlayer.UniqueId = player.getUniqueId().toString();
eloPlayer.Rating = Manager.getEloManager().getElo(player.getUniqueId(), GetName());
teamWinner.addPlayer(eloPlayer);
}
}
else
{
for (Player player : team.GetPlayers(false))
{
EloPlayer eloPlayer = new EloPlayer();
eloPlayer.UniqueId = player.getUniqueId().toString();
eloPlayer.Rating = Manager.getEloManager().getElo(player.getUniqueId(), GetName());
teamLoser.addPlayer(eloPlayer);
}
}
}
for (EloPlayer eloPlayer : Manager.getEloManager().getNewRatings(teamWinner, teamLoser, GameResult.Win).getPlayers())
{
Manager.getEloManager().saveElo(eloPlayer.UniqueId, GetName(), eloPlayer.Rating);
}
for (EloPlayer eloPlayer : Manager.getEloManager().getNewRatings(teamLoser, teamWinner, GameResult.Loss).getPlayers())
{
Manager.getEloManager().saveElo(eloPlayer.UniqueId, GetName(), eloPlayer.Rating);
}
}
//End //End
SetState(GameState.End); SetState(GameState.End);

View File

@ -21,7 +21,9 @@ import mineplex.core.common.util.UtilMath;
import mineplex.core.common.util.UtilPlayer; import mineplex.core.common.util.UtilPlayer;
import mineplex.core.common.util.UtilServer; import mineplex.core.common.util.UtilServer;
import mineplex.core.common.util.UtilTime; import mineplex.core.common.util.UtilTime;
import mineplex.core.elo.EloPlayer;
import mineplex.core.elo.EloTeam;
import mineplex.core.elo.GameResult;
import mineplex.core.updater.UpdateType; import mineplex.core.updater.UpdateType;
import mineplex.core.updater.event.UpdateEvent; import mineplex.core.updater.event.UpdateEvent;
import mineplex.minecraft.game.core.combat.DeathMessageType; import mineplex.minecraft.game.core.combat.DeathMessageType;
@ -321,6 +323,48 @@ public class TeamDeathmatch extends TeamGame
AddGems(player, 10, "Participation", false, false); AddGems(player, 10, "Participation", false, false);
} }
if (EloRanking)
{
EloTeam teamWinner = new EloTeam();
EloTeam teamLoser = new EloTeam();
for (GameTeam team : GetTeamList())
{
if (WinnerTeam != null && team.equals(WinnerTeam))
{
for (Player player : WinnerTeam.GetPlayers(false))
{
EloPlayer eloPlayer = new EloPlayer();
eloPlayer.UniqueId = player.getUniqueId().toString();
eloPlayer.Rating = Manager.getEloManager().getElo(player.getUniqueId(), GetName());
teamWinner.addPlayer(eloPlayer);
}
}
else
{
for (Player player : team.GetPlayers(false))
{
EloPlayer eloPlayer = new EloPlayer();
eloPlayer.UniqueId = player.getUniqueId().toString();
eloPlayer.Rating = Manager.getEloManager().getElo(player.getUniqueId(), GetName());
teamLoser.addPlayer(eloPlayer);
}
}
}
for (EloPlayer eloPlayer : Manager.getEloManager().getNewRatings(teamWinner, teamLoser, GameResult.Win).getPlayers())
{
Manager.getEloManager().saveElo(eloPlayer.UniqueId, GetName(), eloPlayer.Rating);
}
for (EloPlayer eloPlayer : Manager.getEloManager().getNewRatings(teamLoser, teamWinner, GameResult.Loss).getPlayers())
{
Manager.getEloManager().saveElo(eloPlayer.UniqueId, GetName(), eloPlayer.Rating);
}
}
//End //End
SetState(GameState.End); SetState(GameState.End);
} }

View File

@ -1022,8 +1022,7 @@ public class GameLobbyManager implements Listener, IPacketHandler
objective.getScore(_serverName).setScore(line--); objective.getScore(_serverName).setScore(line--);
//ELO //ELO
// TODO: Implement GUI based for ELO ranking/ratings? if (Manager.GetGame() != null && Manager.GetGame().EloRanking)
/*if (Manager.GetGame() != null && Manager.GetGame().EloRanking)
{ {
objective.getScore(" ").setScore(line--); objective.getScore(" ").setScore(line--);
objective.getScore(C.cPurple + C.Bold + "Elo").setScore(line--); objective.getScore(C.cPurple + C.Bold + "Elo").setScore(line--);
@ -1033,7 +1032,7 @@ public class GameLobbyManager implements Listener, IPacketHandler
// Set new // Set new
objective.getScore(Manager.getEloManager().getElo(entry.getKey().getUniqueId(), Manager.GetGame().GetName()) + " ").setScore(line--); objective.getScore(Manager.getEloManager().getElo(entry.getKey().getUniqueId(), Manager.GetGame().GetName()) + " ").setScore(line--);
}*/ }
} }
_oldPlayerCount = UtilServer.getPlayers().length; _oldPlayerCount = UtilServer.getPlayers().length;