diff --git a/sku.0/sys.server/compiled/game/datatables/buildout/tatooine/tatooine_6_2.tab b/sku.0/sys.server/compiled/game/datatables/buildout/tatooine/tatooine_6_2.tab index cea5b3b39..9154970d8 100755 --- a/sku.0/sys.server/compiled/game/datatables/buildout/tatooine/tatooine_6_2.tab +++ b/sku.0/sys.server/compiled/game/datatables/buildout/tatooine/tatooine_6_2.tab @@ -1326,3 +1326,4 @@ i i h i f f f f f f f s p 1187929 1187917 object/cell/cell.iff 12 0 0 0 1 0 0 0 $| -19844656 0 object/tangible/planet_map_location/city.iff 0 1570.98 5 1342.91 0.938444 0 0.345432 0 $| -90069420 0 object/tangible/theme_park/invisible_object.iff 0 1475.19 5 1371.49 1 0 0 0 fishing.controller $| +-90069421 0 object/tangible/theme_park/invisible_object.iff 0 1478.41 5 1369.71 1 0 0 0 systems.leaderboard.controller $| diff --git a/sku.0/sys.server/compiled/game/script/DiscordWebhook.java b/sku.0/sys.server/compiled/game/script/DiscordWebhook.java new file mode 100644 index 000000000..70bf8874f --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/DiscordWebhook.java @@ -0,0 +1,398 @@ +package script; + +import script.library.utils; + +import javax.net.ssl.HttpsURLConnection; +import java.awt.Color; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * DiscordWebhook Integration + * SWG Source Addition - 2021 + * Adapted from: https://gist.github.com/k3kdude/fba6f6b37594eae3d6f9475330733bdb + */ +public class DiscordWebhook { + + private final String url; + private String content; + private String username; + private String avatarUrl; + private boolean tts; + private List embeds = new ArrayList<>(); + + /** + * Constructs a new DiscordWebhook instance + * + * @param url The webhook URL obtained in Discord + */ + public DiscordWebhook(String url) { + this.url = url; + } + + public void setContent(String content) { + this.content = content; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public void setTts(boolean tts) { + this.tts = tts; + } + + public void addEmbed(EmbedObject embed) { + this.embeds.add(embed); + } + + public void execute() { + if (this.content == null && this.embeds.isEmpty()) { + throw new IllegalArgumentException("Set content or add at least one EmbedObject"); + } + + JSONObject json = new JSONObject(); + + json.put("content", this.content); + json.put("username", this.username); + json.put("avatar_url", this.avatarUrl); + json.put("tts", this.tts); + + if (!this.embeds.isEmpty()) { + List embedObjects = new ArrayList<>(); + + for (EmbedObject embed : this.embeds) { + JSONObject jsonEmbed = new JSONObject(); + + jsonEmbed.put("title", embed.getTitle()); + jsonEmbed.put("description", embed.getDescription()); + jsonEmbed.put("url", embed.getUrl()); + + if (embed.getColor() != null) { + Color color = embed.getColor(); + int rgb = color.getRed(); + rgb = (rgb << 8) + color.getGreen(); + rgb = (rgb << 8) + color.getBlue(); + + jsonEmbed.put("color", rgb); + } + + EmbedObject.Footer footer = embed.getFooter(); + EmbedObject.Image image = embed.getImage(); + EmbedObject.Thumbnail thumbnail = embed.getThumbnail(); + EmbedObject.Author author = embed.getAuthor(); + List fields = embed.getFields(); + + if (footer != null) { + JSONObject jsonFooter = new JSONObject(); + + jsonFooter.put("text", footer.getText()); + jsonFooter.put("icon_url", footer.getIconUrl()); + jsonEmbed.put("footer", jsonFooter); + } + + if (image != null) { + JSONObject jsonImage = new JSONObject(); + + jsonImage.put("url", image.getUrl()); + jsonEmbed.put("image", jsonImage); + } + + if (thumbnail != null) { + JSONObject jsonThumbnail = new JSONObject(); + + jsonThumbnail.put("url", thumbnail.getUrl()); + jsonEmbed.put("thumbnail", jsonThumbnail); + } + + if (author != null) { + JSONObject jsonAuthor = new JSONObject(); + + jsonAuthor.put("name", author.getName()); + jsonAuthor.put("url", author.getUrl()); + jsonAuthor.put("icon_url", author.getIconUrl()); + jsonEmbed.put("author", jsonAuthor); + } + + List jsonFields = new ArrayList<>(); + for (EmbedObject.Field field : fields) { + JSONObject jsonField = new JSONObject(); + + jsonField.put("name", field.getName()); + jsonField.put("value", field.getValue()); + jsonField.put("inline", field.isInline()); + + jsonFields.add(jsonField); + } + + jsonEmbed.put("fields", jsonFields.toArray()); + embedObjects.add(jsonEmbed); + } + + json.put("embeds", embedObjects.toArray()); + } + + try { + URL url = new URL(this.url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("User-Agent", "Java-DiscordWebhook-BY-Gelox_"); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + + OutputStream stream = connection.getOutputStream(); + stream.write(json.toString().getBytes()); + stream.flush(); + stream.close(); + + connection.getInputStream().close(); //I'm not sure why but it doesn't work without getting the InputStream + connection.disconnect(); + } catch (Exception e) { + utils.throwWarning("DiscordWebhook Exception: "+e.getMessage()); + } + } + + public static class EmbedObject { + private String title; + private String description; + private String url; + private Color color; + + private Footer footer; + private Thumbnail thumbnail; + private Image image; + private Author author; + private List fields = new ArrayList<>(); + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getUrl() { + return url; + } + + public Color getColor() { + return color; + } + + public Footer getFooter() { + return footer; + } + + public Thumbnail getThumbnail() { + return thumbnail; + } + + public Image getImage() { + return image; + } + + public Author getAuthor() { + return author; + } + + public List getFields() { + return fields; + } + + public EmbedObject setTitle(String title) { + this.title = title; + return this; + } + + public EmbedObject setDescription(String description) { + this.description = description; + return this; + } + + public EmbedObject setUrl(String url) { + this.url = url; + return this; + } + + public EmbedObject setColor(Color color) { + this.color = color; + return this; + } + + public EmbedObject setFooter(String text, String icon) { + this.footer = new Footer(text, icon); + return this; + } + + public EmbedObject setThumbnail(String url) { + this.thumbnail = new Thumbnail(url); + return this; + } + + public EmbedObject setImage(String url) { + this.image = new Image(url); + return this; + } + + public EmbedObject setAuthor(String name, String url, String icon) { + this.author = new Author(name, url, icon); + return this; + } + + public EmbedObject addField(String name, String value, boolean inline) { + this.fields.add(new Field(name, value, inline)); + return this; + } + + private class Footer { + private String text; + private String iconUrl; + + private Footer(String text, String iconUrl) { + this.text = text; + this.iconUrl = iconUrl; + } + + private String getText() { + return text; + } + + private String getIconUrl() { + return iconUrl; + } + } + + private class Thumbnail { + private String url; + + private Thumbnail(String url) { + this.url = url; + } + + private String getUrl() { + return url; + } + } + + private class Image { + private String url; + + private Image(String url) { + this.url = url; + } + + private String getUrl() { + return url; + } + } + + private class Author { + private String name; + private String url; + private String iconUrl; + + private Author(String name, String url, String iconUrl) { + this.name = name; + this.url = url; + this.iconUrl = iconUrl; + } + + private String getName() { + return name; + } + + private String getUrl() { + return url; + } + + private String getIconUrl() { + return iconUrl; + } + } + + private class Field { + private String name; + private String value; + private boolean inline; + + private Field(String name, String value, boolean inline) { + this.name = name; + this.value = value; + this.inline = inline; + } + + private String getName() { + return name; + } + + private String getValue() { + return value; + } + + private boolean isInline() { + return inline; + } + } + } + + private class JSONObject { + + private final HashMap map = new HashMap<>(); + + void put(String key, Object value) { + if (value != null) { + map.put(key, value); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + Set> entrySet = map.entrySet(); + builder.append("{"); + + int i = 0; + for (Map.Entry entry : entrySet) { + Object val = entry.getValue(); + builder.append(quote(entry.getKey())).append(":"); + + if (val instanceof String) { + builder.append(quote(String.valueOf(val))); + } else if (val instanceof Integer) { + builder.append(Integer.valueOf(String.valueOf(val))); + } else if (val instanceof Boolean) { + builder.append(val); + } else if (val instanceof JSONObject) { + builder.append(val.toString()); + } else if (val.getClass().isArray()) { + builder.append("["); + int len = Array.getLength(val); + for (int j = 0; j < len; j++) { + builder.append(Array.get(val, j).toString()).append(j != len - 1 ? "," : ""); + } + builder.append("]"); + } + + builder.append(++i == entrySet.size() ? "}" : ","); + } + + return builder.toString(); + } + + private String quote(String string) { + return "\"" + string + "\""; + } + } + +} \ No newline at end of file diff --git a/sku.0/sys.server/compiled/game/script/gm/cmd.java b/sku.0/sys.server/compiled/game/script/gm/cmd.java index 2167c886d..d867af555 100755 --- a/sku.0/sys.server/compiled/game/script/gm/cmd.java +++ b/sku.0/sys.server/compiled/game/script/gm/cmd.java @@ -725,7 +725,7 @@ public class cmd extends script.base_script currentRank = pvpGetCurrentGcwRank(target); } while (currentRank != goalRank); } - sendSystemMessageTestingOnly(self, "Successfully changed the rank of "+getPlayerName(target)+ "("+target+")."); + sendSystemMessageTestingOnly(self, "Successfully changed the rank of "+getPlayerName(target)+ " ("+target+")."); return SCRIPT_CONTINUE; } public int cmdGrantSkill(obj_id self, obj_id target, String params, float defaultTime) throws InterruptedException @@ -4342,7 +4342,37 @@ public class cmd extends script.base_script return SCRIPT_CONTINUE; } - if(command.equalsIgnoreCase("dumpPermsForCell")) { + if(command.equalsIgnoreCase("dumpLeaderboardInfo")) { + obj_id oid; + if(!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin dumpLeaderboardInfo "); + return SCRIPT_CONTINUE; + } else { + oid = obj_id.getObjId(Long.parseLong(st.nextToken())); + } + if (!isValidId(oid)) { + sendSystemMessageTestingOnly(self, "The object specified for dumpLeaderboardInfo was not valid."); + return SCRIPT_CONTINUE; + } + + String message = "Leaderboard Information for "+getPlayerFullName(oid)+" ("+oid+") \n\n" + + "Current Leaderboard Period to System: \t" + leaderboard.getCurrentLeaderboardPeriod()+ "\n" + + "Current Leaderboard to Player: \t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD) + "\n\n" + + "Data from Individual Player: \n" + + "Player Score (Imperial): \t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL) + "\n" + + "Player Score (Rebel): \t\t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL) + "\n" + + "Guild Score Contribution Eligible?: \t"+ gcw.canContributeToGcwLeaderboardForGuild(oid) + "\n" + + "Guild Score (Imperial): \t\t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL) + "\n" + + "Guild Score (Rebel): \t\t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL) + "\n" + + "City Score Contribution Eligible?: \t"+ gcw.canContributeToGcwLeaderboardForCity(oid) + "\n" + + "City Score (Imperial): \t\t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL) + "\n" + + "City Score (Rebel): \t\t\t\t\t"+ getIntObjVar(oid, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL) + "\n\n" + + "Factional Presence Tracking: \t" + utils.getIntScriptVar(oid, "leaderboard_factional_presence_tracking") + "\n" + + "In Current Period Participant List? \t"; + sui.msgbox(self, self, message, sui.OK_ONLY, "Leaderboard Info", "noHandler"); + } + + else if(command.equalsIgnoreCase("dumpPermsForCell")) { obj_id oid; if(!st.hasMoreTokens()) { @@ -4366,6 +4396,11 @@ public class cmd extends script.base_script return SCRIPT_CONTINUE; } + else if(command.equalsIgnoreCase("gcwLeaderboard")) { + leaderboard.promptGcwLeaderboardDataRequestCsInternalOnly(self); + return SCRIPT_CONTINUE; + } + else if(command.equalsIgnoreCase("getRotation")) { obj_id oid; @@ -4411,6 +4446,96 @@ public class cmd extends script.base_script } } + else if (command.equalsIgnoreCase("grantGcwPoints")) { + + if(!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin grantGcwPoints "); + return SCRIPT_CONTINUE; + } else { + String toParse = st.nextToken(); + obj_id player; + if(toParse.matches(".*\\d.*")) { + player = obj_id.getObjId(Long.parseLong(toParse)); + } else + { + player = getPlayerIdFromFirstName(toParse); + } + if (isIdValid(player) && isPlayer(player)) { + if(!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin grantGcwPoints "); + return SCRIPT_CONTINUE; + } else { + int points = Integer.parseInt(st.nextToken()); + if(points < 1 || points > 1000) { + sendSystemMessageTestingOnly(self, "grantGcwPoints: Error: You may only grant a minimum of 1 and a maximum of 1000 points."); + return SCRIPT_CONTINUE; + } + gcw.grantUnmodifiedGcwPoints(player, points/5); + sendSystemMessageTestingOnly(self, "Successfully granted "+points+" GCW Points to "+getPlayerName(player)+" ("+player+")"); + return SCRIPT_CONTINUE; + } + } else { + sendSystemMessageTestingOnly(self, "grantGcwPoints: Error: The name or OID you provided is not valid or is not a player."); + return SCRIPT_CONTINUE; + } + } + } + + else if (command.equalsIgnoreCase("grantGcwPointsArea")) { + + if(!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin grantGcwPointsArea "); + return SCRIPT_CONTINUE; + } else { + float range = Float.parseFloat(st.nextToken()); + if(range < 1 || range > 1024) { + sendSystemMessageTestingOnly(self, "gcwGrantPointsArea: Error: Range must be between 1 and 1024."); + return SCRIPT_CONTINUE; + } + if(!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin grantGcwPointsArea "); + return SCRIPT_CONTINUE; + } else { + int points = Integer.parseInt(st.nextToken()); + if(points < 1 || points > 1000) { + sendSystemMessageTestingOnly(self, "gcwGrantPointsArea: Error: You may only grant a minimum of 1 and a maximum of 1000 points."); + return SCRIPT_CONTINUE; + } + int count = 0; + obj_id[] players = getAllPlayers(getLocation(self), range); + if(players.length < 1) { + sendSystemMessageTestingOnly(self, "gcwGrantPointsArea: Error: No players were found in the requested range."); + return SCRIPT_CONTINUE; + } + for (obj_id player : players) { + gcw.grantUnmodifiedGcwPoints(player, points/5); + count++; + } + sendSystemMessageTestingOnly(self, "Successfully granted "+points+" GCW Points to a total of "+count+" players within range."); + return SCRIPT_CONTINUE; + } + } + } + + else if (command.equalsIgnoreCase("removeAllScriptVars")) { + + if (!st.hasMoreTokens()) { + sendSystemMessageTestingOnly(self, "Syntax: /admin removeAllScriptVars "); + return SCRIPT_CONTINUE; + } else { + String toParse = st.nextToken(); + obj_id thing = obj_id.getObjId(Long.parseLong(toParse)); + if(!isIdValid(thing)) { + sendSystemMessageTestingOnly(self, "removeAllScriptVars: Error: The OID you provided is not valid."); + return SCRIPT_CONTINUE; + } else { + thing.clearScriptVars(); + sendSystemMessageTestingOnly(self, "Successfully removed all ScriptVars from Object "+thing); + return SCRIPT_CONTINUE; + } + } + } + else if (command.equalsIgnoreCase("setWeather")) { String weather; @@ -4494,12 +4619,20 @@ public class cmd extends script.base_script private static void showAdminCmdSyntax(obj_id self) throws InterruptedException { sendSystemMessageTestingOnly(self, "Outputting nested commands and syntax of /admin to console"); sendConsoleMessage(self, "\\#ffff00 ============ Syntax: /admin commands ============ \\#."); + sendConsoleMessage(self, "\\#00ffff dumpLeaderboardInfo \\#bfff00 \\#."); + sendConsoleMessage(self, "returns the current leaderboard information for the provided player"); sendConsoleMessage(self, "\\#00ffff dumpPermsForCell \\#bfff00 \\#."); sendConsoleMessage(self, "returns the public status and permissions list for the provided cell"); + sendConsoleMessage(self, "\\#00ffff gcwLeaderboard \\#bfff00 \\#."); + sendConsoleMessage(self, "opens a SUI window with GM/Debug options for the GCW Leaderboard"); sendConsoleMessage(self, "\\#00ffff getRotation \\#bfff00 \\#."); sendConsoleMessage(self, "returns the quaternions and rotation of an object"); sendConsoleMessage(self, "\\#00ffff getUsername \\#bfff00 \\#."); sendConsoleMessage(self, "returns the account username of the specified player"); + sendConsoleMessage(self, "\\#00ffff grantGcwPoints \\#bfff00 \\#."); + sendConsoleMessage(self, "grants the number of GCW points to the provided player"); + sendConsoleMessage(self, "\\#00ffff grantGcwPointsArea \\#bfff00 \\#."); + sendConsoleMessage(self, "grants the number of GCW points to all players within range"); sendConsoleMessage(self, "\\#00ffff setWeather \\#bfff00 \\#."); sendConsoleMessage(self, "sets the weather for the current scene"); sendConsoleMessage(self, "\\#00ffff startGcwSpaceBattle \\#bfff00 \\#."); diff --git a/sku.0/sys.server/compiled/game/script/library/city.java b/sku.0/sys.server/compiled/game/script/library/city.java index 40daef4b2..966fab7ae 100755 --- a/sku.0/sys.server/compiled/game/script/library/city.java +++ b/sku.0/sys.server/compiled/game/script/library/city.java @@ -132,6 +132,8 @@ public class city extends script.base_script public static final String CITIZEN_LIST_QUERIED = "cityhall.citizen_list_queried"; public static final String CITIZEN_LIST_DATA = "cityhall.citizen_list_data"; private static final boolean STRUCTURE_FEES_DISABLED = utils.checkConfigFlag("GameServer", "disableStructureFees"); + public static final String VAR_LEADERBOARD_PERIOD_ON_JOIN = "guild.leaderboardPeriodOnJoin"; + public static int canBuildCityHere(obj_id player, location loc) throws InterruptedException { int[] all_cities = getAllCityIds(); @@ -547,6 +549,7 @@ public class city extends script.base_script String cname = cityGetCitizenName(city_id, citizen); bodypp = prose.getPackage(NEW_CITY_CITIZEN_OTHER_BODY, mayor_name, citizen_name, city_name); utils.sendMail(NEW_CITY_CITIZEN_OTHER_SUBJECT, bodypp, cname, "City Hall"); + setObjVar(citizen, VAR_LEADERBOARD_PERIOD_ON_JOIN, leaderboard.getCurrentLeaderboardPeriod()); } public static void removeCitizen(obj_id citizen, obj_id residence) throws InterruptedException { @@ -1881,4 +1884,12 @@ public class city extends script.base_script obj_id[] returnArray = utils.toStaticObjIdArray(safeHouseCitizens); return returnArray; } + + /** + * Returns the City Chat channel name for the specified City + * e.g. chatSendToRoom(city.getChatChannelForCity(cityId), "message", ""); + */ + public static String getChatChannelForCity(int cityId) throws InterruptedException { + return getGameChatCode() + "." + getGalaxyName() + "." + "city." + cityId + ".CityChat"; + } } diff --git a/sku.0/sys.server/compiled/game/script/library/factions.java b/sku.0/sys.server/compiled/game/script/library/factions.java index c098e6b8a..3fb82b499 100755 --- a/sku.0/sys.server/compiled/game/script/library/factions.java +++ b/sku.0/sys.server/compiled/game/script/library/factions.java @@ -867,7 +867,7 @@ public class factions extends script.base_script } return pvpGetType(target) == PVPTYPE_NEUTRAL; } - public static String getFactionNameByHashCode(int hashCode) throws InterruptedException + public static String getFactionNameByHashCode(int hashCode) { if (hashCode == 0) { diff --git a/sku.0/sys.server/compiled/game/script/library/gcw.java b/sku.0/sys.server/compiled/game/script/library/gcw.java index 5d1c5686c..8210c5b80 100755 --- a/sku.0/sys.server/compiled/game/script/library/gcw.java +++ b/sku.0/sys.server/compiled/game/script/library/gcw.java @@ -67,9 +67,7 @@ public class gcw extends script.base_script public static final int GCW_POINT_TYPE_GROUND_QUEST = 6; public static final int GCW_POINT_TYPE_TRADING = 7; public static final int GCW_POINT_TYPE_ENTERTAINING = 8; - public static final int GCW_POINT_TYPE_CITY_INVASION = 9; - public static final int GCW_POINT_TYPE_GCW2_SPACE_BATTLE = 10; - public static final int GCW_POINT_TYPE_MAX = 11; + public static final int GCW_POINT_TYPE_MAX = 9; public static final String[] validScenes = { "tatooine", @@ -274,6 +272,11 @@ public class gcw extends script.base_script private static final int PRESENCE_ALIGNED_CITY_BONUS = utils.getIntConfigSetting("GameServer", "gcwFactionalPresenceAlignedCityBonusPct", 100); private static final int PRESENCE_CITY_RANK_BONUS = utils.getIntConfigSetting("GameServer", "gcwFactionalPresenceAlignedCityRankBonusPct", 10); private static final int PRESENCE_CITY_AGE_BONUS = utils.getIntConfigSetting("GameServer", "gcwFactionalPresenceAlignedCityAgeBonusPct", 5); + private static final float LEADERBOARD_BONUS_GROUND_PVE = utils.getFloatConfigSetting("GameServer","leaderboardGcwGroundPvEBonus", 1.0f); + private static final float LEADERBOARD_BONUS_GROUND_PVP = utils.getFloatConfigSetting("GameServer","leaderboardGcwGroundPvPBonus", 1.0f); + private static final float LEADERBOARD_BONUS_SPACE_PVE = utils.getFloatConfigSetting("GameServer","leaderboardGcwSpacePvEBonus", 1.0f); + private static final float LEADERBOARD_BONUS_SPACE_PVP = utils.getFloatConfigSetting("GameServer","leaderboardGcwSpacePvPBonus", 1.0f); + private static final float LEADERBOARD_BONUS_BASE_BUST = utils.getFloatConfigSetting("GameServer","leaderboardGcwBaseBustingBonus", 1.0f); public static void assignScanInterests(obj_id npc) throws InterruptedException { @@ -1315,6 +1318,7 @@ public class gcw extends script.base_script doGcwPointCsLogging(attacker, pointValue, pointType, information); gcwInvasionCreditForGCW(attacker, pointValue); grantGcwPointsToRegion(attacker, pointValue, pointType); + handleGcwLeaderboardUpdates(attacker, pointValue, pointType); } public static void doGcwPointCsLogging(obj_id player, int pointValue, int pointType, String information) throws InterruptedException { @@ -3043,6 +3047,7 @@ public class gcw extends script.base_script * It was ported from the SRC to DSRC to allow ease of customization/advancement * This is called in a 60-second loop in base_player the entire time a player is * online, replicating how it was handled in the SRC (through LFG updater loop) + * See: player.live_conversions for when heartbeat/loops are called. * * Calculation: * bonus = 100 @@ -3147,6 +3152,12 @@ public class gcw extends script.base_script adjustGcwRebelScore("FactionalPresence", player, region, points); } + // Add Presence Points to Leaderboard Score as 50% of their total value + // e.g. Lvl 90 SF Imperial General without any other modifiers would contribute ~10 points per minute + final int pointsForLeaderboard = Math.max(1, (int)(points * 0.50f)); + final int existingPoints = utils.getIntScriptVar(player, "leaderboard_factional_presence_tracking"); + utils.setScriptVar(player, "leaderboard_factional_presence_tracking", (pointsForLeaderboard + existingPoints)); + // Log the Points LOG("factional_presence", "Player "+getPlayerName(player)+" ("+player+") added "+points+" of presence with bonus "+bonus+" to region "+region+" for faction "+faction); } @@ -3173,4 +3184,149 @@ public class gcw extends script.base_script return region != null && !region.equalsIgnoreCase(""); } + /** + * Validates if a player can contribute to their city's GCW Leaderboard Score + * Present requirements are: + * Must be same faction as city, and + * Must not have joined during current period + */ + public static boolean canContributeToGcwLeaderboardForCity(obj_id player) throws InterruptedException { + final int cityId = getCitizenOfCityId(player); + if(cityId > 0) { + if(cityGetFaction(cityId) != pvpGetAlignedFaction(player)) { + return false; + } + if(!hasObjVar(player, city.VAR_LEADERBOARD_PERIOD_ON_JOIN)) { + setObjVar(player, city.VAR_LEADERBOARD_PERIOD_ON_JOIN, leaderboard.getCurrentLeaderboardPeriod()); + return false; + } + return getIntObjVar(player, city.VAR_LEADERBOARD_PERIOD_ON_JOIN) != leaderboard.getCurrentLeaderboardPeriod(); + } + return false; + } + + /** + * Validates if a player can contribute to their guild's GCW Leaderboard Score + * Present requirements are: + * Must be same faction as guild, and + * Must not have joined during current period + */ + public static boolean canContributeToGcwLeaderboardForGuild(obj_id player) throws InterruptedException { + final int guildId = getGuildId(player); + if(guildId > 0) { + if(guildGetCurrentFaction(guildId) != pvpGetAlignedFaction(player)) { + return false; + } + if(!hasObjVar(player, guild.VAR_LEADERBOARD_PERIOD_ON_JOIN)) { + setObjVar(player, guild.VAR_LEADERBOARD_PERIOD_ON_JOIN, leaderboard.getCurrentLeaderboardPeriod()); + return false; + } + return getIntObjVar(player, guild.VAR_LEADERBOARD_PERIOD_ON_JOIN) != leaderboard.getCurrentLeaderboardPeriod(); + } + return false; + } + + /** + * Called when a player does some point/presence earning activity that contributes to + * their leaderboard score to handle tracking and score tally updates. + * + * The pointType can be specified so you can apply a bonus % to certain activities, e.g. + * 10% leaderboard bonus to base busting versus just ground PvE. + * + * Note: GCW contributions are stored on the player. The leaderboard object just tracks + * who participated so it knows who to grab scores from when calculating winners and is + * responsible for tracking the history. + */ + public static void handleGcwLeaderboardUpdates(obj_id player, int pointsEarned, int pointType) throws InterruptedException { + final int playerFaction = pvpGetAlignedFaction(player); + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD)) { // first time + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD, leaderboard.getCurrentLeaderboardPeriod()); + } + final int leaderboardPeriodToPlayer = getIntObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD); + final int leaderboardPeriodToSystem = leaderboard.getCurrentLeaderboardPeriod(); + leaderboard.debugMsg("handleGcwLeaderboardUpdates called to grant "+pointsEarned+" points of type "+pointType+" to "+getPlayerName(player)+" ("+player+")"); + + // Apply Bonus Multipliers + switch (pointType) { + case GCW_POINT_TYPE_GROUND_PVE: + pointsEarned *= LEADERBOARD_BONUS_GROUND_PVE; + break; + case GCW_POINT_TYPE_GROUND_PVP: + case GCW_POINT_TYPE_GROUND_PVP_REGION: + pointsEarned *= LEADERBOARD_BONUS_GROUND_PVP; + break; + case GCW_POINT_TYPE_SPACE_PVE: + pointsEarned *= LEADERBOARD_BONUS_SPACE_PVE; + break; + case GCW_POINT_TYPE_SPACE_PVP: + pointsEarned *= LEADERBOARD_BONUS_SPACE_PVP; + break; + case GCW_POINT_TYPE_BASE_BUSTING: + pointsEarned *= LEADERBOARD_BONUS_BASE_BUST; + break; + } + float pointsFinal = (float)pointsEarned; + + // If we've earned points, let's set them + if(pointsFinal > 0) { + // For Imperials + if (playerFaction == FACTION_HASH_IMPERIAL) { + // If you're missing the ObjVar or in the wrong period, we're resetting the period and your current points is your 1st value + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL, pointsFinal); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD, leaderboard.getCurrentLeaderboardPeriod()); + } else { // add to your existing value + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL, pointsFinal); + } + // Repeat the logic above for Guild/City + if(canContributeToGcwLeaderboardForCity(player)) { + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL, pointsFinal); + } else { + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL, pointsFinal); + } + } + if (canContributeToGcwLeaderboardForGuild(player)) { + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL, pointsFinal); + } else { + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL, pointsFinal); + } + } + } + // Same thing for Rebel Faction + else if (playerFaction == FACTION_HASH_REBEL) { + // If you're missing the ObjVar or in the wrong period, we're resetting the period and your current points is your 1st value + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL, pointsFinal); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD, leaderboard.getCurrentLeaderboardPeriod()); + } else { // add to your existing value + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL, pointsFinal); + } + // Repeat the logic above for Guild/City + if(canContributeToGcwLeaderboardForCity(player)) { + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL, pointsFinal); + } else { + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL, pointsFinal); + } + } + if (canContributeToGcwLeaderboardForGuild(player)) { + if(!hasObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL) || leaderboardPeriodToPlayer != leaderboardPeriodToSystem) { + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL, pointsFinal); + } else { + pointsFinal += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL); + setObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL, pointsFinal); + } + } + } + leaderboard.addLeaderboardParticipantToCurrentPeriod(player, leaderboard.LEADERBOARD_TYPE_GCW); + } + } + } diff --git a/sku.0/sys.server/compiled/game/script/library/guild.java b/sku.0/sys.server/compiled/game/script/library/guild.java index fee422ada..2aed97d69 100755 --- a/sku.0/sys.server/compiled/game/script/library/guild.java +++ b/sku.0/sys.server/compiled/game/script/library/guild.java @@ -235,6 +235,7 @@ public class guild extends script.base_script public static final int INTERFACE_GUILD_ELECTION = 5; public static final int INTERFACE_GUILD_WAR_PREFERENCES = 6; public static final String VAR_TIME_JOINED_CURRENT_GUILD = "guild.timeJoinedCurrentGuild"; + public static final String VAR_LEADERBOARD_PERIOD_ON_JOIN = "guild.leaderboardPeriodOnJoin"; public static String resolveGuildName(int guildId) throws InterruptedException { @@ -425,6 +426,7 @@ public class guild extends script.base_script { GuildLog(guildId, "disbandForNotEnoughMembers", null, null, ""); mailToGuild(guildId, GUILD_MAIL_DISBAND_SUBJECT, GUILD_MAIL_DISBAND_NOT_ENOUGH_MEMBERS_TEXT); + leaderboard.purgeEntityGcwLeaderboardHistory(guildId, false); disbandGuild(guildId); } public static void disband(int guildId, obj_id actor) throws InterruptedException @@ -437,6 +439,7 @@ public class guild extends script.base_script { GuildLog(guildId, "disband", actor, null, ""); mailToGuild(guildId, GUILD_MAIL_DISBAND_SUBJECT, GUILD_MAIL_DISBAND_TEXT, getName(actor)); + leaderboard.purgeEntityGcwLeaderboardHistory(guildId, false); disbandGuild(guildId); } } @@ -520,6 +523,7 @@ public class guild extends script.base_script mailToGuild(guildId, GUILD_MAIL_ACCEPT_SUBJECT, GUILD_MAIL_ACCEPT_TEXT, getName(actor), memberName); guildSetMemberPermissionAndAllegiance(guildId, memberId, GUILD_PERMISSION_MEMBER, guildGetLeader(guildId)); setObjVar(memberId, VAR_TIME_JOINED_CURRENT_GUILD, getGameTime()); + setObjVar(memberId, VAR_LEADERBOARD_PERIOD_ON_JOIN, leaderboard.getCurrentLeaderboardPeriod()); mailToPerson(guildId, memberName, GUILD_MAIL_ACCEPT_TARGET_SUBJECT, GUILD_MAIL_ACCEPT_TARGET_TEXT, getName(actor), guildGetName(guildId)); messageTo(memberId, "onGuildCreateTerminalDataObject", null, 0, false); } @@ -2590,4 +2594,12 @@ public class guild extends script.base_script return false; } } + + /** + * Returns the Guild Chat channel name for the specified guild + * e.g. chatSendToRoom(guild.getChatChannelForGuild(guildId), "message", ""); + */ + public static String getChatChannelForGuild(int guildId) throws InterruptedException { + return getGameChatCode() + "." + getGalaxyName() + "." + "guild." + guildId + ".GuildChat"; + } } diff --git a/sku.0/sys.server/compiled/game/script/library/leaderboard.java b/sku.0/sys.server/compiled/game/script/library/leaderboard.java new file mode 100644 index 000000000..a5a677e01 --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/library/leaderboard.java @@ -0,0 +1,1696 @@ +package script.library; + +import script.DiscordWebhook; +import script.obj_id; + +import java.awt.*; +import java.time.*; +import java.time.temporal.*; +import java.util.*; +import java.util.stream.*; + +/** + * The Leaderboard Library is used to manipulate the GCW and Fishing (or any additional) + * Leaderboard displays in the game, as well as how their respective data is tabulated + * and stored. + * + * System Nomenclature: + * + * >>> Boards are either for "players" or "entities" (which are guilds or cities) + * >>> A board "type" is what system it's for. There are 2 right now: Fishing & GCW + * which are referenced by constants LEADERBOARD_TYPE_FISHING & LEADERBOARD_TYPE_GCW + * >>> You can "rank" on a board or "win" a board. To rank means to simply be displayed when + * browsing the list. To win means you are in an upper threshold of those ranked & get prizes. + * >>> Boards that are faction-affiliated always reference faction by their base constants + * FACTION_HASH_REBEL or FACTION_HASH_IMPERIAL, the crc32 hash of the faction name (int). + * >>>>>> Default Rank Qualification: Top 25 players, Top 10 Cities, Top 10 Guilds + * >>>>>> Default Win Qualification: Top 10 players, Top 5 Cities, Top 5 Guilds + * >>> The first place winner of the board is the "champion" (or "reigning champion") + * >>> A "period" is the time from which the leaderboard starts to end (by default, + * each period is 1 week long and ends Thursdays at 19:00 GMT, same as GCW reset) + * >>> There are 3 periods of record: "Current," "Previous," and "2 Weeks Ago" + * >>>>>> The "Current" period is the active period in continuous update cycles (by + * default, the period update heartbeat is every 15 minutes). When a period + * update heartbeat happens, this is called the "Period Update." When a period + * ends and data is moved/purged, this is called the "Period Reset." + * >>>>>> The "Previous" period is the most recently ended period from which the + * "reigning champions" are determined. + * >>>>>> The "2 Weeks Ago" period is largely ornamental the falloff of which is, + * however, used to remove the "War Planner" title, which all winners of the + * Previous and 2 Weeks Ago periods get (or the leader of guilds/cities). + * >>>>>> In general, periods are referenced by constants LEADERBOARD_PERIOD_* + * >>> The "War Planner" title is a perk for past winners as explained above but when + * it is granted, it also grants access to the Rebel/Imperial War Room Chat Channels. + * >>> The "Archive" is a history for only Guilds and Cities which displays the past + * 26 weeks (6 months) of their score data, along with some other + * + * System Functionality Notes: + * + * >>> Each leaderboard has a "Master Object" in Mos Eisley which stores the data and + * is used to handle sequential/timed activities (the period updates and resets). + * **NOTE: If the game server has no authority on the master object (e.g. if the zone + * goes to sleep or isn't active because there aren't players there) any messages + * and timed sequence activities will pause until the master object can be reached. + * There is eventual plans to make the Mos Eisley zone always spin on to resolve this + * as it also impacts other controllers/master objects, but that has not been done yet. + * >>> Note that leaderboard rankings are used to award additional Travel Points under the + * Waypoint Registry system. + * >>> See the master objects for other notes at systems.leaderboard.*_master_object + * >>> Admin Commands: + * >>>>>> /admin gcwLeaderboard opens a SUI window with GM/Debug Commands + * >>>>>> /admin dumpLeaderboardInfo provides a dump of leaderboard info for the player + * >>> A Web API is implemented so you can send leaderboard data via POST or via JSON to an + * PHP script or a Discord Web Hook, respectively. + * + * Future Implementations (ideas on how this can be expanded outside of cloning the SOE version): + * + * >>> It would be fairly easy to implement tracking for Guilds/Cities to display an "MVP" + * meaning the player in the guild/city that has scored the most for the guild/city + * >>> Not only is it annoying to transpose cluster-wide scores to localized scores (see those + * methods for what this refers to), it's actually more interesting to see a % score under + * the context of the entire galaxy versus compared to just the other ranked entities, so + * either adding that to the display, or ranking by it instead, would be nice. + * >>> By SOE's design, the weighting of these calculations lean somewhat heavily on factional + * presence and PvP which doesn't seem ideal. + * >>> Space is factored into the current boards by nature of the fact that factional presence + * and GCW points are earned in space, but separate space leaderboards might be enjoyed + * more by the pilot community. + * + * Most methods have JavaDoc notes and are well-commented to explain reasoning/logic where + * applicable. But if you have any questions, or to report any bugs, please feel free to + * message Aconite in the SWG Source Discord. + * + * SWG Source Addition - 2021 + * Authors: Aconite + */ +@SuppressWarnings({"rawtypes", "unused"}) +public class leaderboard extends script.base_script { + + public leaderboard() + { + } + + public static final int LEADERBOARD_TYPE_FISHING = 1; + public static final int LEADERBOARD_TYPE_GCW = 2; + public static final int LEADERBOARD_PERIOD_CURRENT = 1; + public static final int LEADERBOARD_PERIOD_PREVIOUS = 2; + public static final int LEADERBOARD_PERIOD_2WEEKSAGO = 3; + public static final int PERIOD_LENGTH = 604800; + public static final int[] WIN_COUNTS_WITH_BADGES = {1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100}; + public static final String FISHING = "_fishing"; + public static final String GCW = "_gcw"; + public static final String GCW_LEADERBOARD_TITLE = "GCW Leaderboard"; + public static final String[] GCW_LEADERBOARD_COL_NAMES = {"Rank", "Name", "Rank Value"}; + public static final String[] GCW_LEADERBOARD_COL_TYPES = {"text", "text", "text"}; + public static final String[] GCW_LEADERBOARD_ARCHIVE_COL_NAMES = {"Period Ending", "Faction", "Rank Value", "Winner (Y/N)", "Champion (Y/N)"}; + public static final String[] GCW_LEADERBOARD_ARCHIVE_COL_TYPES = {"text", "text", "text", "text", "text"}; + public static final String OBJVAR_LEADERBOARD_PERIOD = "leaderboard.current_period"; + public static final String OBJVAR_LEADERBOARD_PERIOD_START = "leaderboard.current_period_start"; + public static final String OBJVAR_LEADERBOARD_PERIOD_END = "leaderboard.current_period_end"; + public static final String OBJVAR_LEADERBOARD_PARTICIPANT_GROUP = "leaderboard.current_participants_"; + public static final String OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS = "leaderboard.current_participants_guild_"; + public static final String OBJVAR_LEADERBOARD_CITY_PARTICIPANTS = "leaderboard.current_participants_city_"; + public static final String OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL = "leaderboard.score.player_imperial"; + public static final String OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL = "leaderboard.score.player_rebel"; + public static final String OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL = "leaderboard.score.guild_imperial"; + public static final String OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL = "leaderboard.score.guild_rebel"; + public static final String OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL = "leaderboard.score.city_imperial"; + public static final String OBJVAR_LEADERBOARD_CITY_SCORE_REBEL = "leaderboard.score.city_rebel"; + public static final String OBJVAR_LEADERBOARD_PLAYER_PERIOD = "leaderboard.current_period_to_player"; + public static final String OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT = "leaderboard.winner_count.player_"; // append faction + public static final String OBJVAR_LEADERBOARD_GUILD_WIN_COUNT = "leaderboard.winner_count.guild_"; // append faction + public static final String OBJVAR_LEADERBOARD_CITY_WIN_COUNT = "leaderboard.winner_count.city_"; // append faction + public static final String OBJVAR_CURRENT_PERIOD_LAST_UPDATED = "leaderboard.data.current.last_updated"; + public static final String OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD = "leaderboard.notify.gcw_guild_rank"; + public static final String OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY = "leaderboard.notify.gcw_city_rank"; + public static final String OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER = "leaderboard.notify.gcw_player_rank"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_ID = "leaderboard.gcw_data.current.players_imperial_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_SCORE = "leaderboard.gcw_data.current.players_imperial_score"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_ID = "leaderboard.gcw_data.current.players_rebel_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_SCORE = "leaderboard.gcw_data.current.players_rebel_score"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_ID = "leaderboard.gcw_data.current.guild_imperial_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_SCORE = "leaderboard.gcw_data.current.guild_imperial_score"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_ID = "leaderboard.gcw_data.current.guild_rebel_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_SCORE = "leaderboard.gcw_data.current.guild_rebel_score"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_ID = "leaderboard.gcw_data.current.city_imperial_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_SCORE = "leaderboard.gcw_data.current.city_imperial_score"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_ID = "leaderboard.gcw_data.current.city_rebel_ids"; + public static final String OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_SCORE = "leaderboard.gcw_data.current.city_rebel_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_ID = "leaderboard.gcw_data.previous.players_imperial_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_SCORE = "leaderboard.gcw_data.previous.players_imperial_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_ID = "leaderboard.gcw_data.previous.players_rebel_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_SCORE = "leaderboard.gcw_data.previous.players_rebel_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_ID = "leaderboard.gcw_data.previous.guild_imperial_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_SCORE = "leaderboard.gcw_data.previous.guild_imperial_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_ID = "leaderboard.gcw_data.previous.guild_rebel_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_SCORE = "leaderboard.gcw_data.previous.guild_rebel_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_ID = "leaderboard.gcw_data.previous.city_imperial_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_SCORE = "leaderboard.gcw_data.previous.city_imperial_score"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_ID = "leaderboard.gcw_data.previous.city_rebel_ids"; + public static final String OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_SCORE = "leaderboard.gcw_data.previous.city_rebel_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_ID = "leaderboard.gcw_data.2weeksago.players_imperial_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_SCORE = "leaderboard.gcw_data.2weeksago.players_imperial_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_ID = "leaderboard.gcw_data.2weeksago.players_rebel_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_SCORE = "leaderboard.gcw_data.2weeksago.players_rebel_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_ID = "leaderboard.gcw_data.2weeksago.guild_imperial_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_SCORE = "leaderboard.gcw_data.2weeksago.guild_imperial_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_ID = "leaderboard.gcw_data.2weeksago.guild_rebel_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_SCORE = "leaderboard.gcw_data.2weeksago.guild_rebel_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_ID = "leaderboard.gcw_data.2weeksago.city_imperial_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_SCORE = "leaderboard.gcw_data.2weeksago.city_imperial_score"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_ID = "leaderboard.gcw_data.2weeksago.city_rebel_ids"; + public static final String OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_SCORE = "leaderboard.gcw_data.2weeksago.city_rebel_score"; + public static final String OBJVAR_DATA_GCW_ENTITY_HISTORY = "leaderboard.gcw_data.entity_history_"; // append guild/cityId + public static final String OBJVAR_PLAYER_TRACKING_PLAYER_ALREADY = "leaderboard.already_tracking_player"; + public static final String OBJVAR_PLAYER_TRACKING_GUILD_ALREADY = "leaderboard.already_tracking_guild"; + public static final String OBJVAR_PLAYER_TRACKING_CITY_ALREADY = "leaderboard.already_tracking_city"; + public static final String OBJVAR_PLAYER_IMPERIAL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.player_imperial"; + public static final String OBJVAR_PLAYER_REBEL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.player_rebel"; + public static final String OBJVAR_GUILD_IMPERIAL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.guild_imperial"; + public static final String OBJVAR_GUILD_REBEL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.guild_rebel"; + public static final String OBJVAR_CITY_IMPERIAL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.city_imperial"; + public static final String OBJVAR_CITY_REBEL_CURRENT_TEMP_SCORE = "leaderboard.temp_score.city_rebel"; + public static final String OBJVAR_LEADERBOARD_LAST_DATA_PULL_TIME = "leaderboard.last_data_pull_time"; + public static final String SCRIPTVAR_LEADERBOARD_LOCAL_SCORE_CACHED = "leaderboard.cached_data.local_score"; + public static final String SCRIPTVAR_LEADERBOARD_SCORE_DATA_CACHED = "leaderboard.cached_data.score_data"; + private static final String ANNOUNCEMENT_WEBHOOK_URL = getConfigSetting("GameServer", "leaderboardDiscordWebhook"); + private static final boolean VERBOSE_LOGGING = utils.checkConfigFlag("GameServer", "leaderboardVerboseLogging"); + + /** + * @return obj_id of the master leaderboard object, if it exists + */ + public static obj_id getMasterLeaderboardObject() { + obj_id id = getObjIdObjVar(getPlanetByName("tatooine"), "leaderboard.master_object"); + return isIdValid(id) ? id : obj_id.NULL_ID; + } + + /** + * @return the current period number for the leaderboards (+1 for each week they run). + */ + public static int getCurrentLeaderboardPeriod() { + return getIntObjVar(getMasterLeaderboardObject(), OBJVAR_LEADERBOARD_PERIOD); + } + + /** + * @return the Unix Timestamp of when the current leaderboard period started + */ + public static int getCurrentLeaderboardPeriodStartTime() { + return getIntObjVar(getMasterLeaderboardObject(), OBJVAR_LEADERBOARD_PERIOD_START); + } + + /** + * @return the stored Unix Timestamp of when the current leaderboard period will end + */ + public static int getCurrentLeaderboardPeriodEndTime() { + return getIntObjVar(getMasterLeaderboardObject(), OBJVAR_LEADERBOARD_PERIOD_END); + } + + /** + * @return Unix Timestamp of when the next leaderboard period will end + * Works by evaluating the time until the nearest upcoming Thursday at 19:00 GMT + * and then converts it to Unix Timestamp + */ + public static int getNextLeaderboardPeriodEndTime() { + ZoneId zone = ZoneId.of("GMT"); + LocalDateTime now = LocalDateTime.now(zone).with(TemporalAdjusters.next(DayOfWeek.THURSDAY)).withHour(19).withMinute(0).withSecond(0); + return (int)now.atZone(zone).toEpochSecond(); + } + + /** + * @return the Unix Timestamp of when the current period data was last updated + */ + public static int getCurrentLeaderboardPeriodLastUpdated() { + return getIntObjVar(getMasterLeaderboardObject(), OBJVAR_CURRENT_PERIOD_LAST_UPDATED); + } + + /** + * @return true if we are in the middle of the period buffer, + * which is 15 minutes before the end of a period + */ + public static boolean isWithinPeriodBuffer() { + return getCalendarTime() >= (getCurrentLeaderboardPeriodEndTime() - 900); + } + + /** + * @return true if the server started less than 15 minutes ago + * In which case we don't want to be tracking/pushing data + * Note: This helps catch a few issues with load sync and authority + */ + public static boolean isWithinStartupBuffer() { + //todo remove before push + return false; + //return getCalendarTime() <= (getIntObjVar(getPlanetByName("tatooine"), "server_startup_time") + 900); + } + + /** + * Adds the provided player (and their guild/city) to the participant tracking list for + * the given leaderboard type so that the master object will know to include their data + * when tabulating scores and winners. + * @param player obj_id of player to add + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + */ + public static void addLeaderboardParticipantToCurrentPeriod(obj_id player, int type) throws InterruptedException { + if(isWithinPeriodBuffer() || isWithinStartupBuffer()) { + return; + } + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + final obj_id[] fishing_ids = objvar_mangle.getMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+FISHING); + if(fishing_ids == null) { + objvar_mangle.setMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+FISHING, new obj_id[] {player}); + } else { + if(!Arrays.asList(fishing_ids).contains(player)) { + obj_id[] newIds = Arrays.copyOf(fishing_ids, fishing_ids.length + 1); + newIds[newIds.length - 1] = player; + objvar_mangle.setMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP + FISHING, newIds); + } + } + break; + case LEADERBOARD_TYPE_GCW: + if(!hasObjVar(player, OBJVAR_PLAYER_TRACKING_PLAYER_ALREADY)) { + final obj_id[] gcw_ids = objvar_mangle.getMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP + GCW); + if (gcw_ids == null) { + // actually confirm that it doesn't exist manually because of sync delay + if(!hasObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW+"_mangled.count")) { + objvar_mangle.setMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP + GCW, new obj_id[]{player}); + } + } else { + if (!Arrays.asList(gcw_ids).contains(player)) { + obj_id[] newIds = Arrays.copyOf(gcw_ids, gcw_ids.length + 1); + newIds[newIds.length - 1] = player; + objvar_mangle.setMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP + GCW, newIds); + } + } + setObjVar(player, OBJVAR_PLAYER_TRACKING_PLAYER_ALREADY, true); + debugMsg("Adding new participant to GCW Leaderboard Participant Group: "+getPlayerName(player)+" ("+player+")"); + } + final int guildId = getGuildId(player); + final int cityId = getCitizenOfCityId(player); + if(guildId > 0 && gcw.canContributeToGcwLeaderboardForGuild(player)) { + if (!hasObjVar(player, OBJVAR_PLAYER_TRACKING_GUILD_ALREADY)) { + Vector guildIds = getResizeableIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS + GCW); + if (guildIds == null) { + setObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS + GCW, new int[]{guildId}); + } else { + if (!guildIds.contains(guildId)) { + utils.addElement(guildIds, guildId); + setObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS + GCW, guildIds); + } + } + setObjVar(player, OBJVAR_PLAYER_TRACKING_GUILD_ALREADY, true); + debugMsg("Adding new guild to GCW Leaderboard Participant Group: " + guildGetAbbrev(guildId) + " (" + guildId + ")"); + } + } + if(cityId > 0 && gcw.canContributeToGcwLeaderboardForCity(player)) { + if (!hasObjVar(player, OBJVAR_PLAYER_TRACKING_CITY_ALREADY)) { + Vector cityIds = getResizeableIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_CITY_PARTICIPANTS + GCW); + if (cityIds == null) { + setObjVar(masterObject, OBJVAR_LEADERBOARD_CITY_PARTICIPANTS + GCW, new int[]{cityId}); + } else { + if (!cityIds.contains(cityId)) { + utils.addElement(cityIds, cityId); + setObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS + GCW, cityIds); + } + } + setObjVar(player, OBJVAR_PLAYER_TRACKING_CITY_ALREADY, true); + debugMsg("Adding new city to GCW Leaderboard Participant Group: " + cityGetName(guildId) + " (" + cityId + ")"); + } + } + break; + } + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @return a Set of all players that have participated in the current leaderboard period + */ + public static Set getPlayerParticipantsForCurrentPeriod(int type) throws InterruptedException { + final obj_id masterObject = getMasterLeaderboardObject(); + obj_id[] temp; + Set data = new HashSet<>(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + temp = objvar_mangle.getMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+FISHING); + if(temp != null) { + data.addAll(Arrays.asList(temp)); + return data; + } + case LEADERBOARD_TYPE_GCW: + temp = objvar_mangle.getMangledObjIdArrayObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW); + if(temp != null) { + data.addAll(Arrays.asList(temp)); + return data; + } + } + return null; + } + + /** + * @param isCity true if a city, false if a guild (to avoid 2 methods) + * @return Returns a list of all entities (guild/city) that have participated in the current + * leaderboard period for the provided leaderboard type. + */ + public static Set getEntityParticipantsForCurrentPeriod(int type, boolean isCity) { + final obj_id masterObject = getMasterLeaderboardObject(); + int[] temp; + Set data = new HashSet<>(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + if(isCity) { + temp = getIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_CITY_PARTICIPANTS+FISHING); + } else { + temp = getIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS+FISHING); + } + if(temp != null) { + Arrays.stream(temp).forEach(data::add); + } + return data; + case LEADERBOARD_TYPE_GCW: + if(isCity) { + temp = getIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_CITY_PARTICIPANTS+GCW); + } else { + temp = getIntArrayObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS+GCW); + } + if(temp != null) { + Arrays.stream(temp).forEach(data::add); + } + return data; + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return obj_id[] players ranked for the given period given the type and faction + * Players are in rank order from 1st place to 25th place + */ + public static obj_id[] getRankedPlayersForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch(period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getObjIdArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_ID); + } + break; + } + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return int[] guilds ranked for the given period given the type and faction + * Entities are in rank order from 1st place to 10th place + */ + public static int[] getRankedGuildsForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch(period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_ID); + } + break; + } + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return int[] cities ranked for the given period given the type and faction + * Entities are in rank order from 1st place to 10th place + */ + public static int[] getRankedCitiesForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch (period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_ID); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_ID); + } else if (faction == FACTION_HASH_REBEL) { + return getIntArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_ID); + } + break; + } + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return float[] of scores of ranked players for the current period given the type and faction + * Scores are in rank order from 1st to 25th to match the value pair of the getRankedPlayersForPeriod() method + */ + public static float[] getRankedPlayerScoresForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch(period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_SCORE); + } + break; + } + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return float[] of scores of ranked guilds for the current period given the type and faction + * Scores are in rank order from 1st to 10th to match the value pair of the getRankedGuildsForPeriod() method + */ + public static float[] getRankedGuildScoresForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch(period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_SCORE); + } + break; + } + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @param faction = FACTION_HASH_IMPERIAL or FACTION_HASH_REBEL + * @param period = LEADERBOARD_PERIOD_CURRENT, LEADERBOARD_PERIOD_PREVIOUS, or LEADERBOARD_PERIOD_2WEEKSAGO + * @return float[] of scores of ranked cities for the current period given the type and faction + * Scores are in rank order from 1st to 10th to match the value pair of the getRankedCitiesForPeriod() method + */ + public static float[] getRankedCityScoresForPeriod(int type, int faction, int period) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case LEADERBOARD_TYPE_FISHING: + break; + case LEADERBOARD_TYPE_GCW: + switch (period) { + case LEADERBOARD_PERIOD_CURRENT: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_PREVIOUS: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_SCORE); + } + break; + case LEADERBOARD_PERIOD_2WEEKSAGO: + if (faction == FACTION_HASH_IMPERIAL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + return getFloatArrayObjVar(masterObject, OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_SCORE); + } + break; + } + } + return null; + } + + /** + * @return a HashMap of the current Player Winners and their Scores for the given faction + */ + public static HashMap getGcwLeaderboardCurrentWinnersPlayerData(int faction) { + HashMap data = new HashMap<>(); + final obj_id[] players = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS); + final float[] scores = getRankedPlayerScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS); + if (scores != null && players != null) { + for (int i = 0; i < 11; i++) { + data.put(players[i], scores[i]); + } + return data; + } + return null; + } + + /** + * @param isCity true if asking for city data, false if asking for guild Data + * @return a HashMap of the current Entity Winners and their Scores for the given faction and entity type + */ + public static HashMap getGcwLeaderboardCurrentWinnersEntityData(int faction, boolean isCity) { + HashMap data = new HashMap<>(); + final int[] entities = isCity ? getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS) : + getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS); + final float[] scores = isCity ? getRankedCityScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS) : + getRankedGuildScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, LEADERBOARD_PERIOD_PREVIOUS); + if (scores != null && entities != null) { + for (int i = 0; i < 6; i++) { + data.put(entities[i], scores[i]); + } + return data; + } + return null; + } + + /** + * @param type = LEADERBOARD_TYPE_FISHING or LEADERBOARD_TYPE_GCW + * @return true if the given player is in the current participant list for the given type + */ + public static boolean isPlayerInCurrentPeriodParticipantList(obj_id player, int type) throws InterruptedException { + switch (type) { + case LEADERBOARD_TYPE_FISHING: + obj_id[] ids = objvar_mangle.getMangledObjIdArrayObjVar(getMasterLeaderboardObject(), OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+FISHING); + if(ids != null) { + return Arrays.asList(ids).contains(player); + } else { + return false; + } + case LEADERBOARD_TYPE_GCW: + obj_id[] idsx = objvar_mangle.getMangledObjIdArrayObjVar(getMasterLeaderboardObject(), OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW); + if(idsx != null) { + return Arrays.asList(idsx).contains(player); + } else { + return false; + } + } + return false; + } + + /** + * @param player obj_id of player to check + * @return true if the given player is in the current ranked list + */ + public static boolean isPlayerInCurrentRankedList(obj_id player) { + final obj_id[] ranked = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, pvpGetAlignedFaction(player), LEADERBOARD_PERIOD_CURRENT); + if(ranked != null) { + return Arrays.stream(ranked).anyMatch(x-> x == player); + } else { + return false; + } + } + + /** + * @param entityId guild/city ID + * @param isCity true if city, false if guild + * @return true if the given entity is in the current ranked list + */ + public static boolean isEntityInCurrentRankedList(int entityId, boolean isCity) { + int[] ranked; + if(isCity) { + ranked = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, cityGetFaction(entityId), LEADERBOARD_PERIOD_CURRENT); + } else { + ranked = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, guildGetCurrentFaction(entityId), LEADERBOARD_PERIOD_CURRENT); + } + if(ranked != null) { + return Arrays.stream(ranked).anyMatch(x-> x == entityId); + } else { + return false; + } + } + + /** + * @return true if the player is currently a player/guild/city Reigning Champion + */ + public static boolean isPlayerGcwLeaderboardReigningChampion(obj_id player) { + final long impSlot = getCollectionSlotValue(player, "gcw_leaderboard_imperial_current_winner"); + final long rebSlot = getCollectionSlotValue(player, "gcw_leaderboard_rebel_current_winner"); + return (impSlot > 0 || rebSlot > 0); + } + + /** + * Handler for players who win the GCW Leaderboard Players Category + * Winners = Top 10 Players + */ + public static void handleGcwLeaderboardPlayerWinners(int faction, obj_id[] players, float[] scores) throws InterruptedException { + if(players == null || scores == null || players.length < 1 || scores.length < 1) { + return; + } + final String factionFormatted = factions.getFactionNameByHashCode(faction); + final String badgeString = toLower(factionFormatted); + // Craft Mail Message + String message = "Congratulations.\n\n" + + getPlayerFullName(players[0])+" has won the GCW "+factionFormatted+" leaderboard for the weekly period ending " + + getCalendarTimeStringLocal(getCurrentLeaderboardPeriodEndTime())+".\n\n"; + StringBuilder winners = new StringBuilder(); + for (int i = 0; i < players.length; i++) { + if(isIdValid(players[i])) { // so we don't add null entries popped from leaderboard object array copy + winners.append(i + 1).append(". ").append(getPlayerFullName(players[i])).append(", ").append(scores[i]).append("%\n"); + } + } + String finalMessage = message + winners; + + // Handle First Place + if(!badge.hasBadge(players[0], "bdg_gcw_leaderboard_" + badgeString + "_winner_rank1")) { + badge.grantBadge(players[0], "bdg_gcw_leaderboard_" + badgeString + "_winner_rank1"); + } + for (obj_id player : players) { + // Grant Badges for Record X times won + if (!hasObjVar(player, OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT + badgeString)) { + // first time win + setObjVar(player, OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT + badgeString, 1); + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_winner_1x"); + } else { + // grant a badge if they've reached a win count that has a badge + final int winCount = getIntObjVar(player, OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT + badgeString) +1; + if(Arrays.stream(WIN_COUNTS_WITH_BADGES).anyMatch(i-> i == winCount)) { + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_winner_"+winCount+"x"); + } + setObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString, winCount); + } + // Handle Winner Flags & Mail Message + // All player winners get War Planner, Reigning Champion, & Email + modifyCollectionSlotValue(player, badgeString+"_gcw_war_planner", 1); + modifyCollectionSlotValue(player, "gcw_leaderboard_"+ badgeString +"_current_winner", 1); + chatSendPersistentMessage("gcw leaderboard", player, "Winner - Player - "+factionFormatted, finalMessage, null); + LOG("gcw_leaderboard_winners", getPlayerName(player)+" ("+player+") was added to "+factionFormatted+" player winners list for period "+getCurrentLeaderboardPeriod()); + } + announceGcwLeaderboardWinnersToDiscord(1, faction, players, null, scores); + } + + /** + * Handler for guilds who win the GCW Leaderboard + * Winners = Top 5 Guilds + */ + public static void handleGcwLeaderboardGuildWinners(int faction, int[] guildIds, float[] scores) throws InterruptedException { + if(guildIds == null || scores == null || guildIds.length < 1 || scores.length < 1) { + return; + } + final String factionFormatted = factions.getFactionNameByHashCode(faction); + final String badgeString = toLower(factionFormatted); + // Craft Mail Message + String message = "Congratulations.\n\n" + + guildGetName(guildIds[0])+" ("+guildGetAbbrev(guildIds[0])+") has won the GCW "+factionFormatted+" leaderboard for the weekly period ending " + + getCalendarTimeStringLocal(getCurrentLeaderboardPeriodEndTime())+".\n\n"; + StringBuilder winners = new StringBuilder(); + for (int i = 0; i < guildIds.length; i++) { + if(isIdValid(guildGetLeader(guildIds[i]))) { // so we don't add null entries popped from leaderboard object array copy + winners.append(i + 1).append(". ").append(guildGetName(guildIds[i])).append(" (").append(guildGetAbbrev(guildIds[i])).append("), ").append(scores[i]).append("%\n"); + } + } + String finalMessage = message + winners; + + // Handle First Place + obj_id[] firstPlaceIds = guildGetMemberIds(guildIds[0]); + for (obj_id fp : firstPlaceIds) { + if (!badge.hasBadge(fp, "bdg_gcw_leaderboard_" + badgeString + "_guild_winner_rank1")) { + badge.grantBadge(fp, "bdg_gcw_leaderboard_" + badgeString + "_guild_winner_rank1"); + LOG("gcw_leaderboard_winners", getPlayerName(fp)+"("+fp+") was granted badge bdg_gcw_leaderboard_"+badgeString+"_guild_winner_rank1"); + } + } + for (int i : guildIds) { + obj_id[] players = guildGetMemberIds(i); + for (obj_id player : players) { + // Grant Badges for Record X times won + if (!hasObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString)) { + // first time win + setObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString, 1); + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_guild_winner_1x"); + } else { + // grant a badge if they've reached a win count that has a badge + final int winCount = getIntObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString) +1; + if(Arrays.stream(WIN_COUNTS_WITH_BADGES).anyMatch(x-> x == winCount)) { + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_guild_winner_"+winCount+"x"); + } + setObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString, winCount); + } + // Handle Winner Flags & Mail Message + // All members of the guild get Reigning Champion & email + modifyCollectionSlotValue(player, "gcw_leaderboard_"+ badgeString +"_current_winner", 1); + chatSendPersistentMessage("gcw leaderboard", player, "Winner - Guild - "+factionFormatted, finalMessage, null); + } + // Only guild leader gets War Planner Slot + modifyCollectionSlotValue(guildGetLeader(i), badgeString+"_gcw_war_planner", 1); + LOG("gcw_leaderboard_winners", guildGetAbbrev(i)+" ("+i+") was added to "+factionFormatted+" guild winners list for period "+getCurrentLeaderboardPeriod()); + } + announceGcwLeaderboardWinnersToDiscord(2, faction, null, guildIds, scores); + } + + /** + * Handler for cities who win the GCW Leaderboard + * Winners = Top 5 Cities + */ + public static void handleGcwLeaderboardCityWinners(int faction, int[] cityIds, float[] scores) throws InterruptedException { + if(cityIds == null || scores == null || cityIds.length < 1 || scores.length < 1) { + return; + } + final String factionFormatted = factions.getFactionNameByHashCode(faction); + final String badgeString = toLower(factionFormatted); + // Craft Mail Message + String message = "Congratulations.\n\n" + + cityGetName(cityIds[0])+" has won the GCW "+factionFormatted+" leaderboard for the weekly period ending " + + getCalendarTimeStringLocal(getCurrentLeaderboardPeriodEndTime())+".\n\n"; + StringBuilder winners = new StringBuilder(); + for (int i = 0; i < cityIds.length; i++) { + if(isIdValid(cityGetLeader(cityIds[i]))) { // so we don't add null entries popped from leaderboard object array copy + winners.append(i + 1).append(". ").append(cityGetName(cityIds[i])).append(", ").append(scores[i]).append("%\n"); + } + } + String finalMessage = message + winners; + + // Handle First Place + obj_id[] firstPlaceIds = cityGetCitizenIds(cityIds[0]); + for (obj_id fp : firstPlaceIds) { + if (!badge.hasBadge(fp, "bdg_gcw_leaderboard_" + badgeString + "_city_winner_rank1")) { + badge.grantBadge(fp, "bdg_gcw_leaderboard_" + badgeString + "_city_winner_rank1"); + LOG("gcw_leaderboard_winners", getPlayerName(fp)+"("+fp+") was granted badge bdg_gcw_leaderboard_"+badgeString+"_city_winner_rank1"); + } + } + for (int i : cityIds) { + obj_id[] players = cityGetCitizenIds(i); + for (obj_id player : players) { + // Grant Badges for Record X times won + if (!hasObjVar(player, OBJVAR_LEADERBOARD_CITY_WIN_COUNT + badgeString)) { + // first time win + setObjVar(player, OBJVAR_LEADERBOARD_CITY_WIN_COUNT + badgeString, 1); + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_city_winner_1x"); + LOG("gcw_leaderboard_winners", getPlayerName(player)+"("+player+") was granted badge bdg_gcw_leaderboard_"+badgeString+"_city_winner_1x"); + } else { + // grant a badge if they've reached a win count that has a badge + final int winCount = getIntObjVar(player, OBJVAR_LEADERBOARD_CITY_WIN_COUNT + badgeString) +1; + if(Arrays.stream(WIN_COUNTS_WITH_BADGES).anyMatch(x-> x == winCount)) { + badge.grantBadge(player, "bdg_gcw_leaderboard_"+badgeString+"_city_winner_"+winCount+"x"); + LOG("gcw_leaderboard_winners", getPlayerName(player)+"("+player+") was granted badge bdg_gcw_leaderboard_"+badgeString+"_city_winner_"+winCount+"x"); + } + setObjVar(player, OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + badgeString, winCount); + } + // Handle Winner Flags & Mail Message + // All members of the city get Reigning Champion & email + modifyCollectionSlotValue(player, "gcw_leaderboard_"+ badgeString +"_current_winner", 1); + chatSendPersistentMessage("gcw leaderboard", player, "Winner - City - "+factionFormatted, finalMessage, null); + } + // Only city mayor gets War Planner Slot + modifyCollectionSlotValue(guildGetLeader(i), badgeString+"_gcw_war_planner", 1); + LOG("gcw_leaderboard_winners", cityGetName(i)+" ("+i+") was added to "+factionFormatted+" city winners list for period "+getCurrentLeaderboardPeriod()); + } + announceGcwLeaderboardWinnersToDiscord(3, faction, null, cityIds, scores); + } + + /** + * Sets the score data for the current period after it has been rank_pair'ed (players) + */ + public static void setCurrentPeriodScoreData(int type, obj_id[] entity, float[] scores) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch (type) { + case 1: // Player Imperials + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_SCORE, scores); + break; + case 2: // Player Rebels + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_SCORE, scores); + break; + } + debugMsg("setCurrentPeriodScoreData() called for type "+type+" and entities "+Arrays.toString(entity)+" and scores "+Arrays.toString(scores)); + } + + /** + * Sets the score data for the current period after it has been rank_pair'ed (guild/city) + */ + public static void setCurrentPeriodScoreData(int type, int[] entity, float[] scores) { + final obj_id masterObject = getMasterLeaderboardObject(); + switch(type) { + case 3: // Guild Imperials + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_SCORE, scores); + break; + case 4: // Guild Rebels + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_SCORE, scores); + break; + case 5: // City Imperials + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_SCORE, scores); + break; + case 6: // City Rebels + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_ID, entity); + setObjVar(masterObject, OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_SCORE, scores); + break; + } + debugMsg("setCurrentPeriodScoreData() called for type "+type+" and entities "+Arrays.toString(entity)+" and scores "+Arrays.toString(scores)); + } + + /** + * This is the method called when a player selects the "GCW Leaderboards..." radial menu options + * to actually see a specific leaderboard on one of the objects that allow a player to request + * them (War Terminal, War IntelPad, War Planning Table) + * + * This does not include the City/Guild Extended History Data as that is handled in the separate + * method renderGcwLeaderboardEntityHistory() + * + * This method and the subsequent methods that access and/or store data within it identify the request + * from a "render type" which is just an integer based on the index of the radial menu option chosen: + * 1 - Player - Rebel - Current Week + * 2 - Player - Imperial - Current Week + * 3 - Guild - Rebel - Current Week + * 4 - Guild - Imperial - Current Week + * 5 - City - Rebel - Current Week + * 6 - City - Imperial - Current Week + * 7 - Player - Rebel - Previous Week + * 8 - Player - Imperial - Previous Week + * 9 - Guild - Rebel - Previous Week + * 10 - Guild - Imperial - Previous Week + * 11 - City - Rebel - Previous Week + * 12 - City - Imperial - Previous Week + * 13 - Player - Rebel - 2 Weeks Ago + * 14 - Player - Imperial - 2 Weeks Ago + * 15 - Guild - Rebel - 2 Weeks Ago + * 16 - Guild - Imperial - 2 Weeks Ago + * 17 - City - Rebel - 2 Weeks Ago + * 18 - City - Imperial - 2 Weeks Ago + */ + public static void renderGcwLeaderboardDataToPlayer(obj_id player, obj_id requestingObject, int renderType) throws InterruptedException { + + // Determine if a period update hasn't occurred since our last request for data, in which case use the cached data + // that has been stored on the requesting object instead of pulling it all again to reduce the total load + int lastUpdatedTime = getCurrentLeaderboardPeriodLastUpdated(); + boolean usedCachedData = false; + if(hasObjVar(requestingObject, OBJVAR_LEADERBOARD_LAST_DATA_PULL_TIME)) { + final int pullTime = getIntObjVar(requestingObject, OBJVAR_LEADERBOARD_LAST_DATA_PULL_TIME); + if(pullTime == lastUpdatedTime || + // always used cached data for the previous/2 weeks ago boards unless a reset has happened since we logged in + (renderType > 6 && getCurrentLeaderboardPeriodStartTime() < getPlayerLastLoginTime(player))) { + usedCachedData = true; + lastUpdatedTime = pullTime; + } + } + setObjVar(requestingObject, OBJVAR_LEADERBOARD_LAST_DATA_PULL_TIME, getCurrentLeaderboardPeriodLastUpdated()); + final String topMessage = "Last Updated: "+getCalendarTimeStringLocal(lastUpdatedTime)+"\n\n"; + final int factionHash = renderType % 2 == 0 ? FACTION_HASH_IMPERIAL : FACTION_HASH_REBEL; // even renderType vs odd to determine faction + final int weekType = renderType < 7 ? LEADERBOARD_PERIOD_CURRENT : (renderType < 13) ? LEADERBOARD_PERIOD_PREVIOUS : LEADERBOARD_PERIOD_2WEEKSAGO; + final String weekName = renderType < 7 ? "(Current Week)" : (renderType < 13) ? "(Previous Week)" : "(2 Weeks Ago)"; + final String factionName = factions.getFactionNameByHashCode(factionHash); + final int guildId = getGuildId(player); + final int cityId = getCitizenOfCityId(player); + // Handle logic relative to what type of entity we are fetching data for Player/Guild/City + String entityName = getPlayerFullName(player); + String entityType = "Player"; + switch (renderType) { + case 3: + case 4: + case 9: + case 10: + case 15: + case 16: + entityType = "Guild"; + if(guildId > 0) { + entityName = guildGetName(guildId) + " ("+guildGetAbbrev(guildId)+")"; + } else { + entityName = "None"; + } + break; + case 5: + case 6: + case 11: + case 12: + case 17: + case 18: + entityType = "City"; + if(cityId > 0) { + entityName = cityGetName(cityId); + } else { + entityName = "None"; + } + break; + } + + float score = -1.0f; + String[][] data; + String finalMessage; + // Get Local Score (this person/entity's score relative to the ranked players) + // unless requesting data for guild/city and you aren't in a guild/city, respectively + if(entityType.contains("Player") || (entityType.contains("Guild") && guildId > 0) || (entityType.contains("City") && cityId > 0)) { + if(usedCachedData && hasCachedLocalScore(requestingObject, renderType)) { // if we should use cached data & we have it + score = getCachedLocalScore(requestingObject, renderType); + } else { // actually pull the data and cache it + if(entityType.contains("Player")) { + score = getPlayerRelativeScoreToTransposedLeaderboardScores(player, factionHash, weekType); + } else if (entityType.contains("Guild")) { + score = getEntityRelativeScoreToTransposedLeaderboardScores(guildId, factionHash, weekType, false); + } else { + score = getEntityRelativeScoreToTransposedLeaderboardScores(cityId, factionHash, weekType, true); + } + cacheLocalScore(requestingObject, renderType, score); + } + } + + // Get Score Data (actual ranked entities on the board) + if(usedCachedData && hasCachedScoreData(requestingObject, renderType)) { // if we should use cached data & we have it + data = getCachedScoreData(requestingObject, renderType); + } else { // actually pull the data and cache it + data = getFormattedGcwLeaderboardData(renderType, false); + if(data != null) { + cacheScoreData(requestingObject, renderType, data); + } + } + if(data == null || data.length < 1) { + sendSystemMessageTestingOnly(player, "There is no score data available for the requested GCW Leaderboard. Please try again later."); + return; + } + String prompt = "GCW "+factionName +" "+ entityType+" Leaderboard "+weekName; + if(score >= 0) { + String scoreInfo = ". Rank Value for "+entityName+" = "+score+"%"; // append local score if we have one + finalMessage = topMessage + prompt + scoreInfo; + } else { + finalMessage = topMessage + prompt; + } + sui.table(player, player, sui.OK_ONLY, GCW_LEADERBOARD_TITLE, "noHandler", finalMessage, GCW_LEADERBOARD_COL_NAMES, GCW_LEADERBOARD_COL_TYPES, data, true, true); + } + + /** + * Handles pulling ranked entities and their respective scores and compiles it + * in a format suitable for rendering in a SUI Table + * @return String[][] for sui table with ranked entities and their scores + */ + public static String[][] getFormattedGcwLeaderboardData(int renderType, boolean debug) { + + final int factionHash = renderType % 2 == 0 ? FACTION_HASH_IMPERIAL : FACTION_HASH_REBEL; + final int weekType = renderType < 7 ? LEADERBOARD_PERIOD_CURRENT : (renderType < 13) ? LEADERBOARD_PERIOD_PREVIOUS : LEADERBOARD_PERIOD_2WEEKSAGO; + obj_id[] players; + int[] entities; + float[] scores; + + switch (renderType) { + // Players + case 1: + case 2: + case 7: + case 8: + case 13: + case 14: + players = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + scores = getRankedPlayerScoresForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + if (scores != null && players != null) { + final float[] transposedScores = transposeClusterScoreDataToLocalLeaderboardScoreData(scores); + String[][] data = new String[players.length][GCW_LEADERBOARD_COL_NAMES.length]; + for (int i = 0; i < players.length; i++) { + data[i][0] = String.valueOf(i+1); + data[i][1] = debug ? getPlayerFullName(players[i])+" ("+players[i]+")" : getPlayerFullName(players[i]); + data[i][2] = debug ? "Cluster: "+scores[i]+" / Transposed: "+transposedScores[i]+" %" : transposedScores[i] + "%"; + } + return data; + } + break; + // Guilds + case 3: + case 4: + case 9: + case 10: + case 15: + case 16: + entities = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + scores = getRankedGuildScoresForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + if (scores != null && entities != null) { + final float[] transposedScores = transposeClusterScoreDataToLocalLeaderboardScoreData(scores); + String[][] data = new String[entities.length][GCW_LEADERBOARD_COL_NAMES.length]; + for (int i = 0; i < entities.length; i++) { + data[i][0] = String.valueOf(i+1); + data[i][1] = debug ? guildGetName(entities[i])+" ("+entities[i]+")" : guildGetName(entities[i]); + data[i][2] = debug ? "Cluster: "+scores[i]+"% | Transposed: "+transposedScores[i]+"%" : transposedScores[i] + "%"; + } + return data; + } + break; + // Cities + case 5: + case 6: + case 11: + case 12: + case 17: + case 18: + entities = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + scores = getRankedCityScoresForPeriod(LEADERBOARD_TYPE_GCW, factionHash, weekType); + if (scores != null && entities != null) { + final float[] transposedScores = transposeClusterScoreDataToLocalLeaderboardScoreData(scores); + String[][] data = new String[entities.length][GCW_LEADERBOARD_COL_NAMES.length]; + for (int i = 0; i < entities.length; i++) { + data[i][0] = String.valueOf(i+1); + data[i][1] = debug ? cityGetName(entities[i])+" ("+entities[i]+")" : cityGetName(entities[i]); + data[i][2] = debug ? "Cluster: "+scores[i]+"% | Transposed: "+transposedScores[i]+"%" : transposedScores[i] + "%"; + } + return data; + } + break; + } + return null; + } + + /** + * Because the leaderboard does not display the % score of the ranked player/entity + * relative to the entire cluster but rather relative to the other ranked entities, + * i.e. the top score cluster-wide might only be 1.58%, but relative to the other + * top-ranked entities, it might be 25% of their collective score, so we have to + * transpose this data to display the scores relative to the other ranked scores + * as opposed to displaying cluster-wide data. + * @return float[] of transposed Cluster to Local scores + */ + public static float[] transposeClusterScoreDataToLocalLeaderboardScoreData(float[] scoreData) { + final double total = IntStream.range(0, scoreData.length).mapToDouble(i -> scoreData[i]).sum(); + float[] transposedData = new float[scoreData.length]; + for (int i = 0; i < scoreData.length; i++) { + transposedData[i] = (float)(scoreData[i] / total) * 100; + } + debugMsg("transposeClusterScoreDataToLocalLeaderboardScoreData() took in scores param of "+Arrays.toString(scoreData)); + debugMsg("transposeClusterScoreDataToLocalLeaderboardScoreData() returned score sum "+total+" and transposed data: "+Arrays.toString(transposedData)); + return transposedData; + } + + /** + * Returns the % score of a player relative to the transposed local leaderboard + * scores which shows how far the player is, in relation to the other ranked + * players, from reaching a ranked position on the leaderboard. + * @return float score relative to the transposed scores + */ + public static float getPlayerRelativeScoreToTransposedLeaderboardScores(obj_id player, int faction, int period) { + final float[] scores = getRankedPlayerScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, period); + final obj_id[] players = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, faction, period); + if(players != null && scores != null) { // don't calculate a relative score for someone already ranked because that wouldn't make sense + if (Arrays.stream(players).anyMatch(x-> x== player)) { + return scores[Arrays.asList(players).indexOf(player)]; + } + } + if(scores != null) { + double total = IntStream.range(0, scores.length).mapToDouble(i -> scores[i]).sum(); + float score = 0.0f; + if(faction == FACTION_HASH_IMPERIAL) { + score = getFloatObjVar(player, OBJVAR_PLAYER_IMPERIAL_CURRENT_TEMP_SCORE); + } else if (faction == FACTION_HASH_REBEL) { + score = getFloatObjVar(player, OBJVAR_PLAYER_REBEL_CURRENT_TEMP_SCORE); + } + if (score > 0) { + float toReturn = (float)(score / (score + total)) * 100; + debugMsg("getPlayerRelativeScoreToTransposedLeaderboardScores() searched for scores from faction "+faction+" and period "+period+" for score data: "+Arrays.toString(scores)); + debugMsg("getPlayerRelativeScoreToTransposedLeaderboardScores() returned transposed local score: "+toReturn); + return toReturn; + } + } + return 0.0f; + } + + /** + * See: getPlayerRelativeScoreToTransposedLeaderboardScores but for Guilds/Cities + */ + public static float getEntityRelativeScoreToTransposedLeaderboardScores(int entity, int faction, int period, boolean isCity) { + final float[] scores = isCity ? getRankedCityScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, period) : + getRankedGuildScoresForPeriod(LEADERBOARD_TYPE_GCW, faction, period); + final int[] entities = isCity? getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, faction, period) : + getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, faction, period); + if(entities != null && scores != null) { // don't calculate a relative score for someone already ranked because that wouldn't make sense + if (Arrays.stream(entities).anyMatch(x-> x== entity)) { + return scores[Arrays.binarySearch(entities, entity)]; + } + } + if(scores != null) { + final double total = IntStream.range(0, scores.length).mapToDouble(i -> scores[i]).sum(); + final obj_id masterObject = isCity ? getMasterCityObject() : getMasterGuildObject(); + String var; + float score = 0.0f; + if(faction == FACTION_HASH_IMPERIAL) { + var = isCity ? OBJVAR_CITY_IMPERIAL_CURRENT_TEMP_SCORE : OBJVAR_GUILD_IMPERIAL_CURRENT_TEMP_SCORE; + score = getFloatObjVar(masterObject, var+"_"+entity); + } else if (faction == FACTION_HASH_REBEL) { + var = isCity ? OBJVAR_CITY_REBEL_CURRENT_TEMP_SCORE : OBJVAR_GUILD_REBEL_CURRENT_TEMP_SCORE; + score = getFloatObjVar(masterObject, var+"_"+entity); + } + if (score > 0) { + float toReturn = (float)(score / (score + total)) * 100; + debugMsg("getEntityRelativeScoreToTransposedLeaderboardScores() searched for scores from faction "+faction+" and period "+period+" for score data: "+Arrays.toString(scores)); + debugMsg("getEntityRelativeScoreToTransposedLeaderboardScores() returned transposed local score: "+toReturn); + return toReturn; + } + } + return 0; + } + + /** + * Used to render the Guild/City GCW Leaderboard Score Archive SUI Tables + * See packGcwEntityHistoryData() for information on how this works + */ + public static void renderGcwLeaderboardEntityHistory(obj_id player, obj_id requestingObject, boolean isCity) throws InterruptedException { + final obj_id masterObject = isCity ? getMasterCityObject() : getMasterGuildObject(); + final int entityId = isCity ? getCitizenOfCityId(player) : getGuildId(player); + final int renderType = isCity ? 20 : 19; + final String type = isCity ? "City" : "Guild"; + final String ObjVar = OBJVAR_DATA_GCW_ENTITY_HISTORY + "_" + entityId; + if(entityId < 1) { + return; + } + final String[] packedData = getStringArrayObjVar(masterObject, ObjVar); + if(packedData == null || packedData.length == 0) { + sendSystemMessageTestingOnly(player, "There is no GCW Leaderboard History available for your "+type+" at this time."); + return; + } + boolean useCachedData = true; + // always used cached data for accessing archives because they're old unless a reset has happened since we've logged in + if(getCurrentLeaderboardPeriodStartTime() > getPlayerLastLoginTime(player)) { + useCachedData = false; + } + String[][] data; + if(useCachedData && hasCachedScoreData(requestingObject, renderType)) { + data = getCachedScoreData(requestingObject, renderType); + } else { + data = new String[packedData.length][GCW_LEADERBOARD_ARCHIVE_COL_NAMES.length]; + String[] temp; + for(int i = 0; i < packedData.length; i++) { + temp = packedData[i].split(","); + data[i][0] = getCalendarTimeStringLocal(Integer.parseInt(temp[0])); + data[i][1] = factions.getFactionNameByHashCode(Integer.parseInt(temp[1])); + data[i][2] = temp[2]+"%"; + data[i][3] = Integer.parseInt(temp[3]) == 0 ? "No" : "Yes"; + data[i][4] = Integer.parseInt(temp[4]) == 0 ? "No" : "Yes"; + } + cacheScoreData(requestingObject, renderType, data); + } + final String name = isCity ? cityGetName(entityId) : guildGetName(entityId) + " ("+guildGetAbbrev(entityId)+")"; + final String message = "GCW Leaderboard History for "+type+": "+name; + sui.table(player, player, sui.OK_ONLY, GCW_LEADERBOARD_TITLE, "noHandler", message, GCW_LEADERBOARD_ARCHIVE_COL_NAMES, GCW_LEADERBOARD_ARCHIVE_COL_TYPES, data, true, true); + } + + /** + * Used to pack the historical data of a guild/city. The data is stored as a String Array ObjVar + * on the Master Guild/City objects for each guild/city. Guild/City disband methods will remove + * this data automatically if a guild or city is disbanded/destroyed. + * + * Each object in the array is structured like this: + * Period End Unix Timestamp , Faction Hash , Percentile Score , Winner (0/1) , Champion (0/1) + * example: 1617508148,-615855020,4.49242,1,0 + * Which would read in the SUI something like: "April 3 2020 | Imperial | 0.49242% | Yes | No" + * + * Up to 6 months (26 periods) of historical data is stored. If you wanted to expand this, you + * could but it would probably be better to store data older than that on an external source, + * such as a website so you don't have to mangle the ObjVars. + * + * @param isCity true if it's a city, false if it's a guild (just to save having to make 2 methods) + */ + public static void packGcwEntityHistoryData(int entityId, String[] data, boolean isCity) { + final obj_id masterObject = isCity ? getMasterCityObject() : getMasterGuildObject(); + final String ObjVar = OBJVAR_DATA_GCW_ENTITY_HISTORY + "_" + entityId; + String[] packedData = new String[1]; + StringBuilder buildData = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + buildData.append(data[i]); + if(i != data.length -1) { + buildData.append(","); + } + } + packedData[0] = buildData.toString(); + if(!hasObjVar(masterObject, ObjVar)) { + setObjVar(masterObject, ObjVar, packedData); + debugMsg("packGcwEntityHistoryData added new packed history for entity "+entityId+ " as: "+ Arrays.toString(packedData)); + } else { + String[] temp = getStringArrayObjVar(masterObject, ObjVar); + if(temp != null) { + Vector vData = new Vector<>(Arrays.asList(temp)); + if(vData.size() > 25) { + vData.remove(0); + } + vData.add(packedData[0]); + String[] newData = new String[vData.size()]; + for(int i = 0; i < newData.length; i++) { + newData[i] = vData.get(i); + } + setObjVar(masterObject, ObjVar, newData); + debugMsg("packGcwEntityHistoryData added new packed history for entity "+entityId+ " as: "+Arrays.toString(newData)); + } + } + } + + /** + * Caches on the requesting object a tabulated local score + */ + public static void cacheLocalScore(obj_id requestingObject, int renderType, float score) throws InterruptedException { + debugMsg("cacheLocalScore cached score "+score+" on object "+requestingObject+" for renderType "+renderType); + utils.setScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_LOCAL_SCORE_CACHED+"_"+renderType, score); + } + + /** + * @return true if a local score has been cached for the respective renderType + */ + public static boolean hasCachedLocalScore(obj_id requestingObject, int renderType) throws InterruptedException { + return utils.hasScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_LOCAL_SCORE_CACHED+"_"+renderType); + } + + /** + * @return float cached local score + */ + public static float getCachedLocalScore(obj_id requestingObject, int renderType) throws InterruptedException { + return utils.getFloatScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_LOCAL_SCORE_CACHED+"_"+renderType); + } + + /** + * Caches on the requesting object a complete ranked score data for the respective renderType + */ + public static void cacheScoreData(obj_id requestingObject, int renderType, String[][] scoreData) throws InterruptedException { + debugMsg("cacheScoreData cached score data on object "+requestingObject+" for renderType "+renderType+" with data: "+Arrays.toString(scoreData)); + utils.setScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_SCORE_DATA_CACHED+"_"+renderType, scoreData); + } + + /** + * @return true if score data has been cached for the respective renderType + */ + public static boolean hasCachedScoreData(obj_id requestingObject, int renderType) throws InterruptedException { + return utils.hasScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_SCORE_DATA_CACHED+"_"+renderType); + } + + /** + * @return String[][] cached local score data + */ + public static String[][] getCachedScoreData(obj_id requestingObject, int renderType) throws InterruptedException { + return utils.getStringArrayArrayScriptVar(requestingObject, SCRIPTVAR_LEADERBOARD_SCORE_DATA_CACHED+"_"+renderType); + } + + /** + * Adds the temporary tracking score onto the player + */ + public static void addPlayerGcwLeaderboardTempTrackingScore(obj_id player, int faction, float score) { + if(faction == FACTION_HASH_IMPERIAL) { + setObjVar(player, leaderboard.OBJVAR_PLAYER_IMPERIAL_CURRENT_TEMP_SCORE, score); + } else if (faction == FACTION_HASH_REBEL) { + setObjVar(player, leaderboard.OBJVAR_PLAYER_REBEL_CURRENT_TEMP_SCORE, score); + } + } + + /** + * Adds the temporary tracking score for a guild/player to their + * respective master objects + */ + public static void addEntityGcwLeaderboardTempTrackingScore(int entity, int faction, float score, boolean isCity) { + final obj_id masterObject = isCity ? getMasterCityObject() : getMasterGuildObject(); + if(faction == FACTION_HASH_IMPERIAL) { + if(isCity) { + setObjVar(masterObject, leaderboard.OBJVAR_CITY_IMPERIAL_CURRENT_TEMP_SCORE+"_"+entity, score); + } else { + setObjVar(masterObject, leaderboard.OBJVAR_GUILD_IMPERIAL_CURRENT_TEMP_SCORE+"_"+entity, score); + } + } else if (faction == FACTION_HASH_REBEL) { + if(isCity) { + setObjVar(masterObject, leaderboard.OBJVAR_CITY_REBEL_CURRENT_TEMP_SCORE+"_"+entity, score); + } else { + setObjVar(masterObject, leaderboard.OBJVAR_GUILD_REBEL_CURRENT_TEMP_SCORE+"_"+entity, score); + } + } + } + + /** + * Handles announcing Leaderboard Rank Changes to Discord, if a Web Hook URL has been set in config + */ + public static void announceGcwLeaderboardRankChangeToDiscord(String message) { + if(ANNOUNCEMENT_WEBHOOK_URL == null || ANNOUNCEMENT_WEBHOOK_URL.isEmpty()) { + return; + } + DiscordWebhook hook = new DiscordWebhook(ANNOUNCEMENT_WEBHOOK_URL); + hook.setContent(message); + hook.execute(); + } + + /** + * Handles announcing Leaderboard Winners to Discord, if a Web Hook URL has been set in config + */ + public static void announceGcwLeaderboardWinnersToDiscord(int winnerType, int faction, obj_id[] players, int[] entities, float[] scores) { + if(ANNOUNCEMENT_WEBHOOK_URL == null || ANNOUNCEMENT_WEBHOOK_URL.isEmpty()) { + return; + } + DiscordWebhook hook = new DiscordWebhook(ANNOUNCEMENT_WEBHOOK_URL); + DiscordWebhook.EmbedObject embed = new DiscordWebhook.EmbedObject(); + embed.setColor(faction == FACTION_HASH_IMPERIAL ? Color.BLUE : Color.RED); + switch (winnerType) { + case 1: // Players + if(players != null && scores != null) { + embed.setTitle("GCW Leaderboard Winners - Players - "+factions.getFactionNameByHashCode(faction)); + for(int i = 0; i < players.length; i++) { + if(isIdValid(players[i])) { + embed.addField(getPlayerFullName(players[i]), "Rank " + i+1 + " ("+scores[i]+"%)", false); + } + } + } + break; + case 2: // Guilds + if(entities != null && scores != null) { + embed.setTitle("GCW Leaderboard Winners - Guilds - "+factions.getFactionNameByHashCode(faction)); + for(int i = 0; i < entities.length; i++) { + if(isIdValid(guildGetLeader(entities[i]))) { + embed.addField(guildGetName(entities[i]), "Rank " + i+1 + " ("+scores[i]+"%)", false); + } + } + } + break; + case 3: // Cities + if(entities != null && scores != null) { + embed.setTitle("GCW Leaderboard Winners - Cities - "+factions.getFactionNameByHashCode(faction)); + for(int i = 0; i < entities.length; i++) { + if (isIdValid(cityGetLeader(entities[i]))) { + embed.addField(cityGetName(entities[i]), "Rank "+ i+1 +" ("+scores[i]+"%)", false); + } + } + } + break; + } + hook.addEmbed(embed); + hook.execute(); + } + + /** + * Removes tracking vars for participant lists after a period reset. + * Note: the messageTo herein can be enabled to act as a script-trigger + * equivalent for notifying players the period has reset. It is not + * currently implemented given there was no need for it, but I left it + * here as it's a great spot to implement a notification if needed. + */ + public static void clearGcwLeaderboardTrackingVars(Set players) { + if(players != null) { + for (obj_id player : players) { + removeObjVar(player, leaderboard.OBJVAR_PLAYER_TRACKING_PLAYER_ALREADY); + removeObjVar(player, leaderboard.OBJVAR_PLAYER_TRACKING_GUILD_ALREADY); + removeObjVar(player, leaderboard.OBJVAR_PLAYER_TRACKING_CITY_ALREADY); + //messageTo(player, "OnGcwLeaderboardPeriodResetFinished", null, 30f, false); + } + } + } + + /** + * Handles removing the players who are no longer considered "Winners" (top 10) + * because they haven't won in the most recent period. + * + * Note: This should run when a period reset occurs but before any new winners + * are assigned (so you don't purge new winners after they won). + */ + public static void handlePurgeReigningChampions() { + final obj_id[] players1 = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_PREVIOUS); + final obj_id[] players2 = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_PREVIOUS); + final int[] guild1 = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_PREVIOUS); + final int[] guild2 = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_PREVIOUS); + final int[] city1 = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_PREVIOUS); + final int[] city2 = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_PREVIOUS); + if(players1 != null) { + purgeReigningChampions(players1[0]); + } + if(players2 != null) { + purgeReigningChampions(players2[0]); + } + if(guild1 != null) { + purgeReigningChampions(guildGetMemberIds(guild1[0])); + } + if(guild2 != null) { + purgeReigningChampions(guildGetMemberIds(guild2[0])); + } + if(city1 != null) { + purgeReigningChampions(cityGetCitizenIds(city1[0])); + } + if(city2 != null) { + purgeReigningChampions(cityGetCitizenIds(city2[0])); + } + } + public static void purgeReigningChampions(obj_id[] playersToRemove) { + for(obj_id player : playersToRemove) { + purgeReigningChampions(player); + } + } + public static void purgeReigningChampions(obj_id player) { + if(getCollectionSlotValue(player, "gcw_leaderboard_imperial_current_winner") > 0) { + modifyCollectionSlotValue(player, "gcw_leaderboard_imperial_current_winner", -1); + LOG("gcw_leaderboard_purge", "gcw_leaderboard_imperial_current_winner was removed from "+getPlayerName(player)+" ("+player+")"); + } + if(getCollectionSlotValue(player, "gcw_leaderboard_rebel_current_winner") > 0) { + modifyCollectionSlotValue(player, "gcw_leaderboard_rebel_current_winner", -1); + LOG("gcw_leaderboard_purge", "gcw_leaderboard_rebel_current_winner was removed from "+getPlayerName(player)+" ("+player+")"); + } + } + + /** + * Called to remove the "War Planner" titles from players (anyone who is currently in the + * "2 weeks ago" slot at a period reset and is getting pushed out). It will get re-applied + * if they are back in a new period. + * + * Note: This should run when a period reset occurs but before any new winners + * are assigned (so you don't purge new winners after they won). + */ + public static void handlePurgeWarPlanners() { + final obj_id[] players1 = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_2WEEKSAGO); + final obj_id[] players2 = getRankedPlayersForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_2WEEKSAGO); + final int[] guild1 = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_2WEEKSAGO); + final int[] guild2 = getRankedGuildsForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_2WEEKSAGO); + final int[] city1 = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_REBEL, LEADERBOARD_PERIOD_2WEEKSAGO); + final int[] city2 = getRankedCitiesForPeriod(LEADERBOARD_TYPE_GCW, FACTION_HASH_IMPERIAL, LEADERBOARD_PERIOD_2WEEKSAGO); + if(players1 != null) { + purgeWarPlanners(players1); + } + if(players2 != null) { + purgeWarPlanners(players2); + } + if(guild1 != null) { + for(int id : guild1) { + purgeWarPlanners(guildGetLeader(id)); + } + } + if(guild2 != null) { + for(int id : guild2) { + purgeWarPlanners(guildGetLeader(id)); + } + } + if(city1 != null) { + for(int id : city1) { + purgeWarPlanners(cityGetLeader(id)); + } + } + if(city2 != null) { + for(int id : city2) { + purgeWarPlanners(cityGetLeader(id)); + } + } + } + public static void purgeWarPlanners(obj_id[] playersToRemove) { + for(obj_id player : playersToRemove) { + purgeWarPlanners(player); + } + } + public static void purgeWarPlanners(obj_id player) { + if(getCollectionSlotValue(player, "rebel_gcw_war_planner") > 0) { + modifyCollectionSlotValue(player, "rebel_gcw_war_planner", -1); + LOG("gcw_leaderboard_purge", "rebel_gcw_war_planner was removed from "+getPlayerName(player)+" ("+player+")"); + } + if(getCollectionSlotValue(player, "imperial_gcw_war_planner") > 0) { + modifyCollectionSlotValue(player, "imperial_gcw_war_planner", -1); + LOG("gcw_leaderboard_purge", "imperial_gcw_war_planner was removed from "+getPlayerName(player)+" ("+player+")"); + } + } + + /** + * Drops all participant data from the current period + */ + public static void clearAllCurrentPeriodParticipants() { + final obj_id masterObject = getMasterLeaderboardObject(); + final int mangleCt = getIntObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW+"_mangled.count"); + for(int i = 0; i < mangleCt; i++) { + removeObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW+"_mangled.segment."+i); + } + removeObjVar(masterObject, OBJVAR_LEADERBOARD_PARTICIPANT_GROUP+GCW+"_mangled.count"); + removeObjVar(masterObject, OBJVAR_LEADERBOARD_GUILD_PARTICIPANTS+GCW); + removeObjVar(masterObject, OBJVAR_LEADERBOARD_CITY_PARTICIPANTS+GCW); + debugMsg("Finished running leaderboard.clearAllCurrentPeriodParticipants()"); + } + + /** + * Called on guild disband / city destruction to purge the history data from the master objects + */ + public static void purgeEntityGcwLeaderboardHistory(int entityId, boolean isCity) { + final obj_id masterObject = isCity ? getMasterCityObject() : getMasterGuildObject(); + final String ObjVar = OBJVAR_DATA_GCW_ENTITY_HISTORY + "_" + entityId; + removeObjVar(masterObject, ObjVar); + } + + /** + * Shows an SUI List Box with available Debugging/Admin Data for the GCW Leaderboard + * which is then passed to handler > renderGcwLeaderboardDataCsInternalOnly + * CS Cmd: /admin gcwLeaderboard + */ + public static void promptGcwLeaderboardDataRequestCsInternalOnly(obj_id player) throws InterruptedException { + + if(!isGod(player)) { + return; + } + String[] menuOptions = { + "Leaderboard Statistics", + "Request Period Update", + "Request Period Reset", + "View Board: Current Player Imperial", + "View Board: Current Player Rebel", + "View Board: Current Guild Imperial", + "View Board: Current Guild Rebel", + "View Board: Current City Imperial", + "View Board: Current City Rebel", + "View Board: Previous Player Imperial", + "View Board: Previous Player Rebel", + "View Board: Previous Guild Imperial", + "View Board: Previous Guild Rebel", + "View Board: Previous City Imperial", + "View Board: Previous City Rebel", + "View Board: 2WksAgo Player Imperial", + "View Board: 2WksAgo Player Rebel", + "View Board: 2WksAgo Guild Imperial", + "View Board: 2WksAgo Guild Rebel", + "View Board: 2WksAgo City Imperial", + "View Board: 2WksAgo City Rebel", + }; + sui.listbox(player, player, "GM Resources for GCW Leaderboard. Please select an option below.", sui.OK_CANCEL, "GCW Leaderboard Debug", menuOptions, "handleCsLeaderboardDataMenuSelection", true, false); + } + + /** + * Handler for the response to promptGcwLeaderboardDataRequestCsInternalOnly + * "View Board" items are identical to renderGcwLeaderboardDataToPlayer but includes debugging/CS info + * ***NOTE*** if you add new switch options in front of the view leaderboard options, make sure you + * adjust the subtraction inside getFormattedGcwLeaderboardData so it calls the correct board + */ + public static void handleGcwLeaderboardDataRequestCsInternalOnly(obj_id player, int menuOptionIndex) throws InterruptedException { + + switch(menuOptionIndex) { + + case 0: + sendSystemMessageTestingOnly(player, "Nothing is implemented for this option yet because Aconite couldn't decide what all would be useful here. If you have ideas, feel free to message him."); + break; + case 1: + messageTo(getMasterLeaderboardObject(), "handleCurrentPeriodUpdateHeartbeat", null, 0f, false); + sendSystemMessageTestingOnly(player, "Requesting a forced leaderboard period update..."); + LOG("leaderboard_gm", "GM "+getPlayerName(player)+" ("+player+") used /admin gcwLeaderboard to call handleCurrentPeriodUpdateHeartbeat"); + break; + case 2: + messageTo(getMasterLeaderboardObject(), "handleLeaderboardPeriodReset", null, 0f, false); + sendSystemMessageTestingOnly(player, "Requesting a forced leaderboard period reset..."); + LOG("leaderboard_gm", "GM "+getPlayerName(player)+" ("+player+") used /admin gcwLeaderboard to call handleLeaderboardPeriodReset"); + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + String[][] data = getFormattedGcwLeaderboardData(menuOptionIndex-3, true); + sui.table(player, player, sui.OK_ONLY, GCW_LEADERBOARD_TITLE, "noHandler", "Debug GM Data", GCW_LEADERBOARD_COL_NAMES, GCW_LEADERBOARD_COL_TYPES, data, true, true); + break; + } + } + + /** + * Conditionally prints a debug message to console if debug mode is on + */ + public static void debugMsg(String message) { + if(VERBOSE_LOGGING) { + LOG("leaderboard_debug", message); + } + } + +} // end library.leaderboard diff --git a/sku.0/sys.server/compiled/game/script/library/utils.java b/sku.0/sys.server/compiled/game/script/library/utils.java index b985e0fdb..55e73b6ec 100755 --- a/sku.0/sys.server/compiled/game/script/library/utils.java +++ b/sku.0/sys.server/compiled/game/script/library/utils.java @@ -7213,4 +7213,7 @@ public class utils extends script.base_script public static boolean inDebugMode() throws InterruptedException { return (utils.getIntConfigSetting("GameServer", "debugMode") == 1); } + public static void throwWarning(String message) { + WARNING(message); + } } diff --git a/sku.0/sys.server/compiled/game/script/library/web_api.java b/sku.0/sys.server/compiled/game/script/library/web_api.java new file mode 100644 index 000000000..9300b2b31 --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/library/web_api.java @@ -0,0 +1,70 @@ +package script.library; + +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * Web API + * Methods for sending scripted content from the game server to the web via POST + * + * SWG Source Addition - 2021 + * Authors: Aconite + * Adapted from work by: + * https://github.com/thomasdarimont + */ +public class web_api extends script.base_script { + + public web_api() + { + } + + private static final boolean API_ENABLED = utils.checkConfigFlag("GameServer", "scriptWebApiEnabled"); + + /** + * @param url the URL of the PHP script receiving the data + * @param data a map with the name of the POST field as the key and the field data as the value + * + * Simple Example: + * Map data = new HashMap<>(); + * data.put("player", getPlayerName(somePlayer)); + * web_api.sendDataAsPost("swg.com/someScript.php", data); + */ + public static void sendDataAsPost(String url, Map data) { + if(!API_ENABLED) { + return; + } + HttpClient client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .build(); + try { + HttpRequest request = HttpRequest.newBuilder() + .POST(buildFormDataFromMap(data)) + .uri(URI.create(url)) + .header("Content-Type", "application/x-www-form-urlencoded") + .build(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body); + } catch (Exception e) { + WARNING("Java Exception in web_api.sendDataAsPost(): "+e.getMessage()); + } + } + + private static HttpRequest.BodyPublisher buildFormDataFromMap(Map data) { + var builder = new StringBuilder(); + for (Map.Entry entry : data.entrySet()) { + if (builder.length() > 0) { + builder.append("&"); + } + builder.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8)); + builder.append("="); + builder.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)); + } + return HttpRequest.BodyPublishers.ofString(builder.toString()); + } + +} diff --git a/sku.0/sys.server/compiled/game/script/planet/planet_base.java b/sku.0/sys.server/compiled/game/script/planet/planet_base.java index 19747d406..60389565a 100755 --- a/sku.0/sys.server/compiled/game/script/planet/planet_base.java +++ b/sku.0/sys.server/compiled/game/script/planet/planet_base.java @@ -17,6 +17,7 @@ public class planet_base extends script.base_script } public int OnUniverseComplete(obj_id self) throws InterruptedException { + CustomerServiceLog("holidayEvent", "planet_base.OnUniverseComplete: trigger initialized."); dictionary dctParams = new dictionary(); messageTo(self, "doSpawnSetup", dctParams, 60, true); @@ -48,6 +49,7 @@ public class planet_base extends script.base_script obj_id tatooinePlanet = getPlanetByName("tatooine"); if (isIdValid(tatooinePlanet) && exists(tatooinePlanet)) { + setObjVar(tatooinePlanet, "server_start_time", getCalendarTime()); CustomerServiceLog("holidayEvent", "planet_base.doSpawnSetup: Tatooine Planet detected obj_id: " + tatooinePlanet); if (!hasScript(tatooinePlanet, "event.planet_event_handler")) { diff --git a/sku.0/sys.server/compiled/game/script/player/base/base_player.java b/sku.0/sys.server/compiled/game/script/player/base/base_player.java index c09099a7c..8e994adaf 100755 --- a/sku.0/sys.server/compiled/game/script/player/base/base_player.java +++ b/sku.0/sys.server/compiled/game/script/player/base/base_player.java @@ -348,12 +348,7 @@ public class base_player extends script.base_script { if ((toLower(skill)).startsWith("pilot")) { - int godLevel = -1; - if (isGod(self)) - { - godLevel = getGodLevel(self); - } - if (!utils.hasScriptVar(self, "revokePilotSkill") && (godLevel < 50)) + if (!utils.hasScriptVar(self, "revokePilotSkill") && !isGod(self)) { space_skill.retireWarning(self, skill); return SCRIPT_OVERRIDE; @@ -12285,4 +12280,28 @@ public class base_player extends script.base_script gcw.grantGcwFactionalPresenceScore(self); return SCRIPT_CONTINUE; } + public int playerFactionalPresenceReportingHeartbeat(obj_id self, dictionary params) throws InterruptedException { + int points = utils.getIntScriptVar(self, "leaderboard_factional_presence_tracking"); + gcw.handleGcwLeaderboardUpdates(self, points, gcw.GCW_POINT_TYPE_MAX); + utils.setScriptVar(self, "leaderboard_factional_presence_tracking", 0); + leaderboard.debugMsg("Player "+getPlayerName(self)+" ("+self+") reported to leaderboard from presence heartbeat a total of "+points+" points which have now been reset to 0."); + return SCRIPT_CONTINUE; + } + public int handleCsLeaderboardDataMenuSelection(obj_id self, dictionary params) throws InterruptedException { + if ((params == null) || (params.isEmpty()) || !isGod(self)) { + return SCRIPT_CONTINUE; + } + obj_id player = sui.getPlayerId(params); + int btn = sui.getIntButtonPressed(params); + int idx = sui.getListboxSelectedRow(params); + if (btn == sui.BP_CANCEL) { + return SCRIPT_CONTINUE; + } + if (idx == -1) { + return SCRIPT_CONTINUE; + } + leaderboard.handleGcwLeaderboardDataRequestCsInternalOnly(self, idx); + return SCRIPT_CONTINUE; + } + } diff --git a/sku.0/sys.server/compiled/game/script/player/live_conversions.java b/sku.0/sys.server/compiled/game/script/player/live_conversions.java index 8b1389d9c..898011418 100755 --- a/sku.0/sys.server/compiled/game/script/player/live_conversions.java +++ b/sku.0/sys.server/compiled/game/script/player/live_conversions.java @@ -63,6 +63,11 @@ public class live_conversions extends script.base_script runOncePerTravelConversions(self); return SCRIPT_CONTINUE; } + public int OnLogout(obj_id self) throws InterruptedException + { + endFactionalPresenceReportingLoops(self); + return SCRIPT_CONTINUE; + } public int OnNewbieTutorialResponse(obj_id self, String action) throws InterruptedException { if (action.equals("clientReady")) @@ -105,6 +110,7 @@ public class live_conversions extends script.base_script handleMailOptInRewards(player); handleCombatUpgradePlaqueReward(player); startFactionalPresenceTrackingLoop(player); + startFactionalPresenceReportingLoop(player); } public void runOncePerTravelConversions(obj_id player) throws InterruptedException { @@ -1014,4 +1020,18 @@ public class live_conversions extends script.base_script recurringMessageTo(player, "playerFactionalPresenceHeartbeat", null, 60.0f); } + /** + * Handles looper for reporting factional presence to leaderboard + * Pulls ScriptVar store of factional presence every 12 minutes to reduce + * the amount of times we're running leaderboard queries + */ + public void startFactionalPresenceReportingLoop(obj_id player) throws InterruptedException { + recurringMessageTo(player, "playerFactionalPresenceReportingHeartbeat", null, 720.0f); + } + + public void endFactionalPresenceReportingLoops(obj_id player) throws InterruptedException { + cancelRecurringMessageTo(player, "playerFactionalPresenceHeartbeat"); + cancelRecurringMessageTo(player, "playerFactionalPresenceReportingHeartbeat"); + } + } diff --git a/sku.0/sys.server/compiled/game/script/player/player_faction.java b/sku.0/sys.server/compiled/game/script/player/player_faction.java index 6f0701601..78e2c6cf9 100755 --- a/sku.0/sys.server/compiled/game/script/player/player_faction.java +++ b/sku.0/sys.server/compiled/game/script/player/player_faction.java @@ -66,14 +66,24 @@ public class player_faction extends script.base_script { pp = prose.setStringId(pp, SID_OVERT_TO_COVERT); commPlayer(self, self, pp, recruiter); - factions.goCovertWithDelay(self, 300.0f); + if(isGod(self)) { + sendSystemMessageTestingOnly(self, "GOD MODE: Overriding your /pvp effect delay to immediate because you are in God Mode."); + factions.goCovertWithDelay(self, 1.0f); + } else { + factions.goCovertWithDelay(self, 300.0f); + } return SCRIPT_CONTINUE; } if (pvpGetType(self) == PVPTYPE_COVERT) { pp = prose.setStringId(pp, SID_COVERT_TO_OVERT); commPlayer(self, self, pp, recruiter); - factions.goOvertWithDelay(self, 30.0f); + if(isGod(self)) { + sendSystemMessageTestingOnly(self, "GOD MODE: Overriding your /pvp effect delay to immediate because you are in God Mode."); + factions.goOvertWithDelay(self, 1.0f); + } else { + factions.goOvertWithDelay(self, 30.0f); + } return SCRIPT_CONTINUE; } return SCRIPT_OVERRIDE; diff --git a/sku.0/sys.server/compiled/game/script/rank_pair.java b/sku.0/sys.server/compiled/game/script/rank_pair.java index f366f9ef3..417ab0f6f 100644 --- a/sku.0/sys.server/compiled/game/script/rank_pair.java +++ b/sku.0/sys.server/compiled/game/script/rank_pair.java @@ -4,24 +4,30 @@ import java.util.*; /** * Rank Pair Wrapper - * Provides a rank_pair for a leader board given a dictionary of data + * Provides a rank_pair for a leader board given a HashMap of data * - * The dictionary should contain a set of entity IDs (integer identifier + * The HashMap should contain a set of entity IDs (integer identifier * for e.g. guilds and cities) or a set of obj_id (identifier for players) - * as the keys and a set of scores (double) to be sorted as the values. + * as the keys and a set of scores (float) to be sorted as the values. + * *******NOTE****** sortForPlayers() must get passed a HashMap with + * obj_id's parsed as Long(s), do NOT pass obj_ids directly or you'll get + * ClassCastException errors. It will give you back obj_id's though. * * Example implementation: * - * rank_pair data = new rank_pair.sortForPlayers(dictionary); + * rank_pair data = rank_pair.sortForPlayers(map, 25); * obj_id[] players = data.getPlayerEntrants; * double[] scores = data.getScores; * * From the above, you'd have the sorted arrays containing the player * winners and their respective scores, both stored in rank order from - * 1st place to X place (based on total number of entries in dictionary). + * 1st place to X place (based on total number of entries in HashMap). * This design is intended to support direct compatibility with the * existing implementation and logic of SUI tables. * + * Use the limit parameter to filter the size of the + * resulting data (i.e. you only want the top 10 results) + * * SWG Source Addition - 2021 * Authors: Aconite */ @@ -29,39 +35,46 @@ public class rank_pair { public int[] getEntityEntrants; public obj_id[] getPlayerEntrants; - public double[] getScores; + public float[] getScores; - private rank_pair(int[] entities, double[] scores) { + private rank_pair(int[] entities, float[] scores) { this.getEntityEntrants = entities; this.getPlayerEntrants = null; this.getScores = scores; } - private rank_pair(obj_id[] players, double[] scores) { + private rank_pair(obj_id[] players, float[] scores) { this.getEntityEntrants = null; this.getPlayerEntrants = players; this.getScores = scores; } - public static rank_pair sortForEntity(dictionary d) { - List> list = new ArrayList<>(d.entrySet()); + public static rank_pair sortForEntity(HashMap d, int sizeLimit) { + List> list = new ArrayList<>(d.entrySet()); list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + double[] temp = list.stream().mapToDouble(Map.Entry::getValue).toArray(); + float[] data = new float[temp.length]; + for (int i = 0; i < temp.length; i++) { + data[i] = (float)temp[i]; + } return new rank_pair( - list.stream().mapToInt(Map.Entry::getKey).toArray(), - list.stream().mapToDouble(Map.Entry::getValue).toArray() + list.stream().mapToInt(Map.Entry::getKey).limit(sizeLimit).toArray(), + data ); } - public static rank_pair sortForPlayers(dictionary d) { - List> list = new ArrayList<>(d.entrySet()); + public static rank_pair sortForPlayers(HashMap d, int sizeLimit) { + List> list = new ArrayList<>(d.entrySet()); list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); - long[] players_raw = list.stream().mapToLong(Map.Entry::getKey).toArray(); + long[] players_raw = list.stream().mapToLong(Map.Entry::getKey).limit(sizeLimit).toArray(); obj_id[] players = new obj_id[players_raw.length]; for (int i = 0; i < players_raw.length; i++) { players[i] = obj_id.getObjId(players_raw[i]); } - return new rank_pair(players, - list.stream().mapToDouble(Map.Entry::getValue).toArray() - ); + double[] temp = list.stream().mapToDouble(Map.Entry::getValue).toArray(); + float[] data = new float[temp.length]; + for (int i = 0; i < temp.length; i++) { + data[i] = (float)temp[i]; + } + return new rank_pair(players, data); } - } \ No newline at end of file diff --git a/sku.0/sys.server/compiled/game/script/script_class_loader.java b/sku.0/sys.server/compiled/game/script/script_class_loader.java index 7cc5fd34a..9d9a0e35a 100755 --- a/sku.0/sys.server/compiled/game/script/script_class_loader.java +++ b/sku.0/sys.server/compiled/game/script/script_class_loader.java @@ -359,6 +359,14 @@ public class script_class_loader extends ClassLoader defaultLoad.add("script.custom_var"); defaultLoad.add("script.deltadictionary"); defaultLoad.add("script.dictionary"); + defaultLoad.add("script.DiscordWebhook"); + defaultLoad.add("script.DiscordWebhook$EmbedObject"); + defaultLoad.add("script.DiscordWebhook$EmbedObject$Field"); + defaultLoad.add("script.DiscordWebhook$EmbedObject$Thumbnail"); + defaultLoad.add("script.DiscordWebhook$EmbedObject$Image"); + defaultLoad.add("script.DiscordWebhook$EmbedObject$Author"); + defaultLoad.add("script.DiscordWebhook$EmbedObject$Footer"); + defaultLoad.add("script.DiscordWebhook$JSONObject"); defaultLoad.add("script.draft_schematic"); defaultLoad.add("script.draft_schematic$simple_ingredient"); defaultLoad.add("script.draft_schematic$slot"); @@ -387,6 +395,7 @@ public class script_class_loader extends ClassLoader defaultLoad.add("script.prose_package$participant_info"); defaultLoad.add("script.random"); defaultLoad.add("script.ranged_int_custom_var"); + defaultLoad.add("script.rank_pair"); defaultLoad.add("script.region"); defaultLoad.add("script.resource_attribute"); defaultLoad.add("script.resource_density"); diff --git a/sku.0/sys.server/compiled/game/script/systems/city/city_hall.java b/sku.0/sys.server/compiled/game/script/systems/city/city_hall.java index 40a6b6491..06efa6d86 100755 --- a/sku.0/sys.server/compiled/game/script/systems/city/city_hall.java +++ b/sku.0/sys.server/compiled/game/script/systems/city/city_hall.java @@ -127,6 +127,7 @@ public class city_hall extends script.base_script outparams.put("city_hall", self); messageTo(cityMaster, "confirmCityRemoved", outparams, 600, true); int city_id = findCityByCityHall(self); + leaderboard.purgeEntityGcwLeaderboardHistory(city_id, true); obj_id[] structures = cityGetStructureIds(city_id); if (structures != null) { diff --git a/sku.0/sys.server/compiled/game/script/systems/leaderboard/controller.java b/sku.0/sys.server/compiled/game/script/systems/leaderboard/controller.java new file mode 100644 index 000000000..4ac207f32 --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/systems/leaderboard/controller.java @@ -0,0 +1,56 @@ +package script.systems.leaderboard; + +import script.dictionary; +import script.location; +import script.obj_id; + +public class controller extends script.base_script { + + public controller() + { + } + + /** + * Controller which handles spawning the master object if it does not exist. + * Should only run the first time the server is started after a fresh installation. + */ + public static final String CONTROLLER = "object/tangible/ground_spawning/patrol_waypoint.iff"; + + public int OnInitialize(obj_id self) throws InterruptedException { + setName(self, "Leaderboard Controller"); + location selfLoc = getLocation(self); + obj_id objects[] = getObjectsInRange(selfLoc, 0.1f); + boolean exists = false; + if (objects != null || objects.length > 0) + { + for (obj_id object : objects) { + if ((getTemplateName(object)).equals(CONTROLLER)) { + exists = true; + } + } + } + if (!exists) + { + createMasterObject(self); + } + return SCRIPT_CONTINUE; + } + + public int OnAboutToBeTransferred(obj_id self, obj_id destContainer, obj_id transferer) throws InterruptedException { + sendSystemMessageTestingOnly(transferer, "You cannot move this item!"); + return SCRIPT_OVERRIDE; + } + + public int createMasterObject(obj_id self, dictionary params) throws InterruptedException { + createMasterObject(self); + return SCRIPT_CONTINUE; + } + + public void createMasterObject(obj_id self) throws InterruptedException { + obj_id object = createObject(CONTROLLER, getLocation(self)); + attachScript(object, "systems.leaderboard.gcw_master_object"); + persistObject(object); + WARNING("Creating Master Leaderboard Object ("+object+")..."); + } + +} diff --git a/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_display_object.java b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_display_object.java new file mode 100644 index 000000000..8dfb4caa9 --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_display_object.java @@ -0,0 +1,222 @@ +package script.systems.leaderboard; + +import script.library.leaderboard; +import script.menu_info; +import script.menu_info_types; +import script.obj_id; +import script.string_id; + +/** + * Attach this script to any object which you want to have the radial options + * for accessing the GCW Leaderboard Menus. By default, this is the War Terminal, + * War IntelPad, and War/Invasion Planning Tables. + */ +public class gcw_display_object extends script.terminal.base.base_terminal { + + public gcw_display_object() + { + } + + public static final string_id SID_MENU_LEADERBOARD = new string_id("gcw", "gcw_war_terminal_leaderboard_menu"); + public static final string_id SID_LEADERBOARD_CITY_CURRENT_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_imp_current_menu"); + public static final string_id SID_LEADERBOARD_CITY_CURRENT_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_reb_current_menu"); + public static final string_id SID_LEADERBOARD_CITY_PREVIOUS_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_imp_prev1_menu"); + public static final string_id SID_LEADERBOARD_CITY_PREVIOUS_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_reb_prev1_menu"); + public static final string_id SID_LEADERBOARD_CITY_2WEEKSAGO_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_imp_prev2_menu"); + public static final string_id SID_LEADERBOARD_CITY_2WEEKSAGO_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_reb_prev2_menu"); + public static final string_id SID_LEADERBOARD_CITY_HISTORICAL = new string_id("gcw", "gcw_war_terminal_leaderboard_city_historical_menu"); + public static final string_id SID_LEADERBOARD_GUILD_CURRENT_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_imp_current_menu"); + public static final string_id SID_LEADERBOARD_GUILD_CURRENT_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_reb_current_menu"); + public static final string_id SID_LEADERBOARD_GUILD_PREVIOUS_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_imp_prev1_menu"); + public static final string_id SID_LEADERBOARD_GUILD_PREVIOUS_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_reb_prev1_menu"); + public static final string_id SID_LEADERBOARD_GUILD_2WEEKSAGO_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_imp_prev2_menu"); + public static final string_id SID_LEADERBOARD_GUILD_2WEEKSAGO_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_reb_prev2_menu"); + public static final string_id SID_LEADERBOARD_GUILD_HISTORICAL = new string_id("gcw", "gcw_war_terminal_leaderboard_guild_historical_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_CURRENT_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_imp_current_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_CURRENT_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_reb_current_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_PREVIOUS_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_imp_prev1_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_PREVIOUS_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_reb_prev1_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_2WEEKSAGO_IMPERIAL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_imp_prev2_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_2WEEKSAGO_REBEL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_reb_prev2_menu"); + public static final string_id SID_LEADERBOARD_PLAYER_HISTORICAL = new string_id("gcw", "gcw_war_terminal_leaderboard_player_historical_menu"); + + public int OnObjectMenuRequest(obj_id self, obj_id player, menu_info mi) throws InterruptedException { + + int leaderboardMenu = mi.addRootMenu(menu_info_types.SERVER_MENU20, SID_MENU_LEADERBOARD); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU21, SID_LEADERBOARD_PLAYER_CURRENT_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU22, SID_LEADERBOARD_PLAYER_CURRENT_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU23, SID_LEADERBOARD_GUILD_CURRENT_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU24, SID_LEADERBOARD_GUILD_CURRENT_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU25, SID_LEADERBOARD_CITY_CURRENT_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU26, SID_LEADERBOARD_CITY_CURRENT_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU27, SID_LEADERBOARD_PLAYER_PREVIOUS_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU28, SID_LEADERBOARD_PLAYER_PREVIOUS_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU29, SID_LEADERBOARD_GUILD_PREVIOUS_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU30, SID_LEADERBOARD_GUILD_PREVIOUS_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU31, SID_LEADERBOARD_CITY_PREVIOUS_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU32, SID_LEADERBOARD_CITY_PREVIOUS_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU33, SID_LEADERBOARD_PLAYER_2WEEKSAGO_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU34, SID_LEADERBOARD_PLAYER_2WEEKSAGO_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU35, SID_LEADERBOARD_GUILD_2WEEKSAGO_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU36, SID_LEADERBOARD_GUILD_2WEEKSAGO_IMPERIAL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU37, SID_LEADERBOARD_CITY_2WEEKSAGO_REBEL); + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU38, SID_LEADERBOARD_CITY_2WEEKSAGO_IMPERIAL); + if(getGuildId(player) > 0) { + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU39, SID_LEADERBOARD_GUILD_HISTORICAL); + } + if(getCitizenOfCityId(player) > 0) { + mi.addSubMenu(leaderboardMenu, menu_info_types.SERVER_MENU40, SID_LEADERBOARD_CITY_HISTORICAL); + } + + return super.OnObjectMenuRequest(self, player, mi); + } + + public int OnObjectMenuSelect(obj_id self, obj_id player, int item) throws InterruptedException { + + if(item == menu_info_types.SERVER_MENU21) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 1); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU22) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 2); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU23) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 3); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU24) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 4); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU25) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 5); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU26) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 6); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU27) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 7); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU28) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 8); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU29) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 9); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU30) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 10); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU31) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 11); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU32) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 12); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU33) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 13); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU34) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 14); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU35) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 15); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU36) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 16); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU37) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 17); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU38) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardDataToPlayer(player, self, 18); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU39 && getGuildId(player) > 0) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardEntityHistory(player, self, false); + } else { + notifyPlayerBoardUnavailable(player); + } + } + if(item == menu_info_types.SERVER_MENU40 && getCitizenOfCityId(player) > 0) { + if(!leaderboard.isWithinPeriodBuffer()) { + leaderboard.renderGcwLeaderboardEntityHistory(player, self, true); + } else { + notifyPlayerBoardUnavailable(player); + } + } + return SCRIPT_CONTINUE; + } + + public static void notifyPlayerBoardUnavailable(obj_id player) { + sendSystemMessageTestingOnly(player, "The GCW Leaderboard is not available right now because an update process is ongoing. Please try again later."); + } +} diff --git a/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_master_object.java b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_master_object.java new file mode 100644 index 000000000..86d79206f --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_master_object.java @@ -0,0 +1,634 @@ +package script.systems.leaderboard; + +import script.*; +import script.library.*; +import java.util.*; + +@SuppressWarnings("unused") +public class gcw_master_object extends script.base_script { + + public gcw_master_object() + { + } + + public static final int TYPE = leaderboard.LEADERBOARD_TYPE_GCW; + private static final float CURRENT_PERIOD_HEARTBEAT = utils.getFloatConfigSetting("GameServer", "leaderboardPeriodHeartbeat", 900f); + private static final boolean VERBOSE_LOGGING = utils.checkConfigFlag("GameServer", "leaderboardVerboseLogging"); + + public int OnAttach(obj_id self) throws InterruptedException { + messageTo(self, "handleLeaderboardInitialization", null, 60f, true); + return SCRIPT_CONTINUE; + } + + public int OnInitialize(obj_id self) throws InterruptedException { + messageTo(self, "handleLeaderboardInitialization", null, 60f, true); + return SCRIPT_CONTINUE; + } + + public int OnAboutToBeTransferred(obj_id self, obj_id destContainer, obj_id transferer) throws InterruptedException { + sendSystemMessageTestingOnly(transferer, "You cannot move this item!"); + return SCRIPT_OVERRIDE; + } + + public int OnHearSpeech(obj_id self, obj_id objSpeaker, String strText) throws InterruptedException { + if(!isGod(objSpeaker)) { + return SCRIPT_CONTINUE; + } + if(strText.equalsIgnoreCase("gcwLeaderboardUpdate")) { + messageTo(self, "handleCurrentPeriodUpdateHeartbeat", null, 0f, false); + sendSystemMessageTestingOnly(objSpeaker, "Requesting a forced leaderboard period update..."); + + } + if(strText.equalsIgnoreCase("gcwLeaderboardReset")) { + messageTo(self, "handleLeaderboardPeriodReset", null, 0f, false); + sendSystemMessageTestingOnly(objSpeaker, "Requesting a forced leaderboard period reset..."); + } + return SCRIPT_CONTINUE; + } + + public int handleLeaderboardInitialization(obj_id self, dictionary params) { + setName(self, "Master GCW Leaderboard Object"); + final obj_id tatooine = getPlanetByName("tatooine"); + + // First Time Setup + if(!hasObjVar(tatooine, "leaderboard.master_object")) { + setObjVar(getPlanetByName("tatooine"), "leaderboard.master_object", self); + messageTo(self, "handleLeaderboardFirstTimeSetup", null, 0f, true); + leaderboard.debugMsg("Handling board initialization as first time setup..."); + } else { + messageTo(self, "handleLeaderboardStartupChecks", null, 0f, true); + leaderboard.debugMsg("Handling board initialization as startup check..."); + } + return SCRIPT_CONTINUE; + } + + public int handleLeaderboardFirstTimeSetup(obj_id self, dictionary params) { + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD, 1); + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD_START, getCalendarTime()); + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD_END, leaderboard.getNextLeaderboardPeriodEndTime()); + messageTo(self, "handleLeaderboardStartupChecks", null, 0f, true); + return SCRIPT_CONTINUE; + } + + public int handleLeaderboardStartupChecks(obj_id self, dictionary params) { + // If we've been offline too long and missed an alarm, force a period reset (+15 min for startup buffer) + if(getCalendarTime() > leaderboard.getCurrentLeaderboardPeriodEndTime()) { + messageTo(self, "handleLeaderboardPeriodReset", null, 900f, true); + LOG("leaderboard_object", "Running a forced handleLeaderboardPeriodReset() because calendar time > period time + period length."); + return SCRIPT_CONTINUE; + } else { + // Set reset time clock (Thursdays at 19:00 GMT) + createWeeklyAlarmClock(self, "handleLeaderboardPeriodReset", null, DAY_OF_WEEK_THU, 19, 0, 0); + // Start current period update heartbeat + recurringMessageTo(self, "handleCurrentPeriodUpdateHeartbeat", null, CURRENT_PERIOD_HEARTBEAT); + leaderboard.debugMsg("Starting alarm clock and period update heartbeat."); + } + return SCRIPT_CONTINUE; + } + + /** + * Called on heartbeat to update the data for the "current" period. + * This is a stupidly expensive operation so it should be limited. + * By default, it only runs every 15 minutes. Depending on the size + * and resources of your server, you may want to increase/decrease. + * Change CURRENT_PERIOD_HEARTBEAT config value to adjust the time + * (in seconds). But if you change that, be sure to look at the + * Period Buffer inside the leaderboard lib to ensure the heartbeat + * does not run while a period reset is in progress. + * + * Note: This calculates the cluster-wide entity scores. Meaning it + * is calculating the % of points earned by a single player/guild/city + * compared to every other player/guild/city on the cluster cumulatively. + * But this isn't what the leaderboard actually determines rank value by. + * The leaderboard displays the top 25 players and top 10 cities/guilds + * and their % score compared to the other top ranked entities. So a % + * display in the leaderboard table is the % of scores compared to other + * ranked entities, not the % compared to the cluster-wide data. However, + * the top window of the SUI table will display for the entity *both* + * their cluster-wide % score and their % score compared to the top of + * the ranks so that entity can see their relative contributions. + */ + public int handleCurrentPeriodUpdateHeartbeat(obj_id self, dictionary params) throws InterruptedException { + + // don't run if we're already running + if(utils.hasScriptVar(self, "heartbeat_is_in_progress") || utils.hasScriptVar(self, "reset_is_in_progress")) { + leaderboard.debugMsg("Skipping period heartbeat because we're already in the middle of running one or in a reset..."); + return SCRIPT_CONTINUE; + } + // don't run in a buffer, grab all participants + if(leaderboard.isWithinPeriodBuffer() || leaderboard.isWithinStartupBuffer()) { + leaderboard.debugMsg("Skipping period heartbeat because we're within a period or startup buffer..."); + return SCRIPT_CONTINUE; + } + final Set participants = leaderboard.getPlayerParticipantsForCurrentPeriod(TYPE); + if(participants == null) { + return SCRIPT_CONTINUE; + } + leaderboard.debugMsg("Starting current period heartbeat..."); + utils.setScriptVar(self, "heartbeat_is_in_progress", true); + + // get total points earned by everyone for everything + float totalImperialPlayerPoints = 0.0f; + float totalRebelPlayerPoints = 0.0f; + float totalImperialGuildPoints = 0.0f; + float totalRebelGuildPoints = 0.0f; + float totalImperialCityPoints = 0.0f; + float totalRebelCityPoints = 0.0f; + for(obj_id player : participants) { + totalImperialPlayerPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL); + totalRebelPlayerPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL); + totalImperialGuildPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL); + totalRebelGuildPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL); + totalImperialCityPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL); + totalRebelCityPoints += getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL); + leaderboard.debugMsg("Grabbing earned points from player " + getPlayerName(player) + " (" + player + ") *******************"); + if(VERBOSE_LOGGING) { + leaderboard.debugMsg("Imperial Player Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL)); + leaderboard.debugMsg("Rebel Player Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL)); + leaderboard.debugMsg("Imperial Guild Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL)); + leaderboard.debugMsg("Rebel Guild Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL)); + leaderboard.debugMsg("Imperial City Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL)); + leaderboard.debugMsg("Rebel City Points: " + getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL)); + leaderboard.debugMsg("*******************"); + } + } + leaderboard.debugMsg("Finished Score Calculations..."); + leaderboard.debugMsg("Point Groups: IP: "+totalImperialPlayerPoints+" RP: "+totalRebelPlayerPoints+" IG: "+totalImperialGuildPoints+" RG: "+totalRebelGuildPoints+" IC: "+totalImperialCityPoints+" RC: "+totalRebelCityPoints); + + // maps to hold our score data + HashMap scoresImperialPlayers = new HashMap<>(); + HashMap scoresRebelPlayers = new HashMap<>(); + HashMap scoresImperialGuild = new HashMap<>(); + HashMap scoresRebelGuild = new HashMap<>(); + HashMap scoresImperialCity = new HashMap<>(); + HashMap scoresRebelCity = new HashMap<>(); + + // calculate and store player scores + float tempScore; + for (obj_id player : participants) { + if(getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL) > 0) { + tempScore = (getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL) / totalImperialPlayerPoints) * 100; + leaderboard.addPlayerGcwLeaderboardTempTrackingScore(player, FACTION_HASH_IMPERIAL, tempScore); + scoresImperialPlayers.put(player.getValue(), tempScore); + leaderboard.debugMsg("Adding Cluster Score "+tempScore+" for Imperial Player "+player+" to dictionary."); + } + if(getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL) > 0) { + tempScore = (getFloatObjVar(player, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL) / totalRebelPlayerPoints) * 100; + leaderboard.addPlayerGcwLeaderboardTempTrackingScore(player, FACTION_HASH_REBEL, tempScore); + scoresRebelPlayers.put(player.getValue(), tempScore); + leaderboard.debugMsg("Adding Cluster Score " + tempScore + " for Rebel Player " + player + " to dictionary."); + } + } + + // calculate guild and city scores + int faction; + float tempEntityPoints = 0.0f; + obj_id[] citizens; + final Set cityParticipants = leaderboard.getEntityParticipantsForCurrentPeriod(TYPE, true); + final Set guildParticipants = leaderboard.getEntityParticipantsForCurrentPeriod(TYPE, false); + if(cityParticipants != null) { + for(int city : cityParticipants) { + faction = cityGetFaction(city); + citizens = cityGetCitizenIds(city); + for(obj_id citizen : citizens) { + if(participants.contains(citizen)) { + if(faction == FACTION_HASH_IMPERIAL) { + if(getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL) > 0) { + tempEntityPoints += getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL); + } + } else if (faction == FACTION_HASH_REBEL) { + if(getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL) > 0) { + tempEntityPoints += getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL); + } + } + } + } + if(tempEntityPoints > 0) { + if(faction == FACTION_HASH_IMPERIAL) { + tempScore = tempEntityPoints / totalImperialCityPoints * 100; + scoresImperialCity.put(city, tempScore); + leaderboard.addEntityGcwLeaderboardTempTrackingScore(city, FACTION_HASH_IMPERIAL, tempScore, true); + leaderboard.debugMsg("Adding Cluster Score " + tempScore + " for Imperial City " + city + " to dictionary."); + } else if (faction == FACTION_HASH_REBEL) { + tempScore = tempEntityPoints / totalRebelCityPoints * 100; + scoresRebelCity.put(city, tempScore); + leaderboard.addEntityGcwLeaderboardTempTrackingScore(city, FACTION_HASH_REBEL, tempScore, true); + leaderboard.debugMsg("Adding Cluster Score " + tempScore + " for Rebel City " + city + " to dictionary."); + } + } + tempEntityPoints = 0; + } + } + if(guildParticipants != null) { + for(int guild : guildParticipants) { + faction = guildGetCurrentFaction(guild); + citizens = guildGetMemberIds(guild); + for(obj_id citizen : citizens) { + if(participants.contains(citizen)) { + if(faction == FACTION_HASH_IMPERIAL) { + if(getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL) > 0) { + tempEntityPoints += getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL); + } + } else if (faction == FACTION_HASH_REBEL) { + if(getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL) > 0) { + tempEntityPoints += getFloatObjVar(citizen, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL); + } + } + } + } + if(tempEntityPoints > 0) { + if(faction == FACTION_HASH_IMPERIAL) { + tempScore = tempEntityPoints / totalImperialGuildPoints * 100; + scoresImperialGuild.put(guild, tempScore); + leaderboard.addEntityGcwLeaderboardTempTrackingScore(guild, FACTION_HASH_IMPERIAL, tempScore, false); + leaderboard.debugMsg("Adding Cluster Score " + tempScore + " for Imperial Guild " + guild + " to dictionary."); + + } else if (faction == FACTION_HASH_REBEL) { + tempScore = tempEntityPoints / totalRebelGuildPoints * 100; + scoresRebelGuild.put(guild, tempScore); + leaderboard.addEntityGcwLeaderboardTempTrackingScore(guild, FACTION_HASH_REBEL, tempScore, false); + leaderboard.debugMsg("Adding Cluster Score " + tempScore + " for Rebel Guild " + guild + " to dictionary."); + } + } + } + } + + // sort, pair, and store data + if(scoresImperialPlayers.size() > 0) { + rank_pair winnersPlayerImperials = rank_pair.sortForPlayers(scoresImperialPlayers, 25); + leaderboard.setCurrentPeriodScoreData(1, winnersPlayerImperials.getPlayerEntrants, winnersPlayerImperials.getScores); + leaderboard.debugMsg("rank_pair'ing scoresImperialPlayers because scoresImperialPlayers.size() > 0"); + } + if(scoresRebelPlayers.size() > 0) { + rank_pair winnersPlayerRebels = rank_pair.sortForPlayers(scoresRebelPlayers, 25); + leaderboard.setCurrentPeriodScoreData(2, winnersPlayerRebels.getPlayerEntrants, winnersPlayerRebels.getScores); + leaderboard.debugMsg("rank_pair'ing scoresRebelPlayers because scoresRebelPlayers.size() > 0"); + } + if(scoresImperialGuild.size() > 0) { + rank_pair winnersGuildImperials = rank_pair.sortForEntity(scoresImperialGuild, 10); + leaderboard.setCurrentPeriodScoreData(3, winnersGuildImperials.getEntityEntrants, winnersGuildImperials.getScores); + leaderboard.debugMsg("rank_pair'ing scoresImperialGuild because scoresImperialGuild.size() > 0"); + } + if(scoresRebelGuild.size() > 0) { + rank_pair winnersGuildRebels = rank_pair.sortForEntity(scoresRebelGuild, 10); + leaderboard.setCurrentPeriodScoreData(4, winnersGuildRebels.getEntityEntrants, winnersGuildRebels.getScores); + leaderboard.debugMsg("rank_pair'ing scoresRebelGuild because scoresRebelGuild.size() > 0"); + } + if(scoresImperialCity.size() > 0) { + rank_pair winnersCityImperials = rank_pair.sortForEntity(scoresImperialCity, 10); + leaderboard.setCurrentPeriodScoreData(5, winnersCityImperials.getEntityEntrants, winnersCityImperials.getScores); + leaderboard.debugMsg("rank_pair'ing scoresImperialCity because scoresImperialCity.size() > 0"); + } + if(scoresRebelCity.size() > 0) { + rank_pair winnersCityRebels = rank_pair.sortForEntity(scoresRebelCity, 10); + leaderboard.setCurrentPeriodScoreData(6, winnersCityRebels.getEntityEntrants, winnersCityRebels.getScores); + leaderboard.debugMsg("rank_pair'ing scoresRebelCity because scoresRebelCity.size() > 0"); + } + + setObjVar(self, leaderboard.OBJVAR_CURRENT_PERIOD_LAST_UPDATED, getCalendarTime()); + messageTo(self, "handleFinishedCurrentPeriodUpdate", null, 30f, false); + LOG("gcw_leaderboard_object", "Finished running handleCurrentPeriodUpdateHeartbeat() at "+getCalendarTime()); + return SCRIPT_CONTINUE; + } + + /** + * Called when a period update finishes for any necessary handlers. + * Mainly this handles notifying the Guild/City if they have + * "ranked" (top 25 players, top 10 guild/cities) or if their rank + * position has changed (but only if it has changed to a value that + * isn't what they were most recently notified about, e.g. to prevent + * spamming players every 15 minutes with position updates that they + * already know about. + * + * todo the patch notes for these chat messages say the player is + * notified in the "GCW Channel" but that doesn't exist and sending to + * faction channels would mean notifying potentially 100s of players + * about a single player's rank position change, so this has been + * implemented as a system message for now. + */ + public int handleFinishedCurrentPeriodUpdate(obj_id self, dictionary params) throws InterruptedException { + + final int period = leaderboard.LEADERBOARD_PERIOD_CURRENT; + final obj_id[] imperialPlayers = leaderboard.getRankedPlayersForPeriod(TYPE, FACTION_HASH_IMPERIAL, period); + final obj_id[] rebelPlayers = leaderboard.getRankedPlayersForPeriod(TYPE, FACTION_HASH_REBEL, period); + final int[] imperialCities = leaderboard.getRankedCitiesForPeriod(TYPE, FACTION_HASH_IMPERIAL, period); + final int[] rebelCities = leaderboard.getRankedCitiesForPeriod(TYPE, FACTION_HASH_REBEL, period); + final int[] imperialGuilds = leaderboard.getRankedGuildsForPeriod(TYPE, FACTION_HASH_IMPERIAL, period); + final int[] rebelGuilds = leaderboard.getRankedGuildsForPeriod(TYPE, FACTION_HASH_REBEL, period); + + int rank = 1; + String channel; + obj_id leader; + if(imperialPlayers != null) { + for (obj_id i : imperialPlayers) { + if(getIntObjVar(i, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER) != rank) { + sendSystemMessageTestingOnly(i, "[GCW Leaderboard] Congratulations! You are now ranked in position " + rank + " on the Current Imperial Players GCW Leaderboard."); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(getPlayerFullName(i)+" is now ranked in position "+rank+" on the Current Imperial Players GCW Leaderboard."); + setObjVar(i, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER, rank); + } + ++rank; + } + } + if(rebelPlayers != null) { + rank = 1; + for (obj_id i : rebelPlayers) { + if(getIntObjVar(i, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER) != rank) { + sendSystemMessageTestingOnly(i, "[GCW Leaderboard] Congratulations! You are now ranked in position " + rank + " on the Current Rebel Players GCW Leaderboard."); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(getPlayerFullName(i)+" is now ranked in position "+rank+" on the Current Rebel Players GCW Leaderboard."); + setObjVar(i, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER, rank); + } + ++rank; + } + } + if(imperialCities != null) { + rank = 1; + for (int i : imperialCities) { + leader = cityGetLeader(i); + if(getIntObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY) != rank) { + channel = city.getChatChannelForCity(i); + chatSendToRoom(channel, "[GCW Leaderboard] Congratulations! Your city is now ranked in position " + rank + " on the Current Imperial Cities GCW Leaderboard.", ""); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(cityGetName(i)+" is now ranked in position "+rank+" on the Current Imperial Cities GCW Leaderboard."); + setObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY, rank); + } + ++rank; + } + } + if(rebelCities != null) { + rank = 1; + for (int i : rebelCities) { + leader = cityGetLeader(i); + if(getIntObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY) != rank) { + channel = city.getChatChannelForCity(i); + chatSendToRoom(channel, "[GCW Leaderboard] Congratulations! Your city is now ranked in position " + rank + " on the Current Rebel Cities GCW Leaderboard.", ""); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(cityGetName(i)+" is now ranked in position "+rank+" on the Current Rebel Cities GCW Leaderboard."); + setObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY, rank); + } + ++rank; + } + } + if(imperialGuilds != null) { + rank = 1; + for (int i : imperialGuilds) { + leader = guildGetLeader(i); + if(getIntObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD) != rank) { + channel = guild.getChatChannelForGuild(i); + chatSendToRoom(channel, "[GCW Leaderboard] Congratulations! Your guild is now ranked in position " + rank + " on the Current Imperial Guild GCW Leaderboard.", ""); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(guildGetName(i)+" ("+guildGetAbbrev(i)+") is now ranked in position "+rank+" on the Current Imperial Guild GCW Leaderboard."); + setObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD, rank); + } + ++rank; + } + } + if(rebelGuilds != null) { + rank = 1; + for (int i : rebelGuilds) { + leader = guildGetLeader(i); + if(getIntObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD) != rank) { + channel = guild.getChatChannelForGuild(i); + chatSendToRoom(channel, "[GCW Leaderboard] Congratulations! Your guild is now ranked in position " + rank + " on the Current Rebel Guild GCW Leaderboard.", ""); + leaderboard.announceGcwLeaderboardRankChangeToDiscord(guildGetName(i)+" ("+guildGetAbbrev(i)+") is now ranked in position "+rank+" on the Current Rebel Guild GCW Leaderboard."); + setObjVar(leader, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD, rank); + } + ++rank; + } + } + + utils.removeScriptVar(self, "heartbeat_is_in_progress"); + LOG("gcw_leaderboard_object", "Finished running handleFinishedCurrentPeriodUpdate() at "+getCalendarTime()); + return SCRIPT_CONTINUE; + } + + /** + * Called when the weekly "reset" period happens to handle purging all of + * the oldest data and revoking slots from players as applicable + */ + public int handleLeaderboardPeriodReset(obj_id self, dictionary params) throws InterruptedException { + + // don't reset in the middle of a reset + if(utils.hasScriptVar(self, "reset_is_in_progress")) { + leaderboard.debugMsg("Skipping period reset because we're already in the middle of running one..."); + return SCRIPT_CONTINUE; + } + utils.setScriptVar(self, "reset_is_in_progress", true); + + // Stop heartbeat while we move through this process + cancelRecurringMessageTo(self, "handleCurrentPeriodUpdateHeartbeat"); + leaderboard.debugMsg("Period Reset Started... Cancelling heartbeat message."); + + // Purge "War Planner" slots from players in the "2 Weeks Ago" board who are falling off + leaderboard.handlePurgeWarPlanners(); + + // Purge Reigning Champion slots from players in the "Previous Week" board who are getting pushed back + leaderboard.handlePurgeReigningChampions(); + + // Add a time buffer for the above to process before we start pushing data around + messageTo(self, "handleLeaderboardDataPushBack", null, 30f, true); + LOG("gcw_leaderboard_object", "Finished running handleLeaderboardPeriodReset() at "+getCalendarTime()); + + return SCRIPT_CONTINUE; + } + + + public int handleLeaderboardDataPushBack(obj_id self, dictionary params) throws InterruptedException { + + // Purge all 2 Weeks Ago Data by moving Previous Week Data into it + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_ID, getObjIdArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_IMPERIAL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_SCORE)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_ID, getObjIdArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_PLAYERS_REBEL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_SCORE)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_ID, getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_IMPERIAL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_SCORE)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_ID, getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_GUILD_REBEL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_SCORE)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_ID, getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_IMPERIAL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_SCORE)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_ID, getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_ID)); + setObjVar(self, leaderboard.OBJVAR_DATA_2WEEKSAGO_PERIOD_CITY_REBEL_SCORE, getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_SCORE)); + + // Grab new top ranked list + final obj_id[] rankedImperialPlayerIds = getObjIdArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_ID); + final float[] rankedImperialPlayerScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_SCORE); + final obj_id[] rankedRebelPlayerIds = getObjIdArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_ID); + final float[] rankedRebelPlayerScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_SCORE); + final int[] rankedImperialGuildIds = getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_ID); + final float[] rankedImperialGuildScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_SCORE); + final int[] rankedRebelGuildIds = getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_ID); + final float[] rankedRebelGuildScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_SCORE); + final int[] rankedImperialCityIds = getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_ID); + final float[] rankedImperialCityScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_SCORE); + final int[] rankedRebelCityIds = getIntArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_ID); + final float[] rankedRebelCityScores = getFloatArrayObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_SCORE); + int[] winnerImperialCityIds = {}; + int[] winnerRebelCityIds = {}; + int[] winnerImperialGuildIds = {}; + int[] winnerRebelGuildIds = {}; + if(rankedImperialCityIds != null) { // use Math.min to protect from copying in null values when grabbing only winners + winnerImperialCityIds = Arrays.copyOfRange(rankedImperialCityIds, 0, Math.min(rankedImperialCityIds.length, 5)); + } + if(rankedRebelCityIds != null) { + winnerRebelCityIds = Arrays.copyOfRange(rankedRebelCityIds, 0, Math.min(rankedRebelCityIds.length, 5)); + } + if(rankedImperialGuildIds != null) { + winnerImperialGuildIds = Arrays.copyOfRange(rankedImperialGuildIds, 0, Math.min(rankedImperialGuildIds.length, 5)); + } + if(rankedRebelGuildIds != null) { + winnerRebelGuildIds = Arrays.copyOfRange(rankedRebelGuildIds, 0, Math.min(rankedRebelGuildIds.length, 5)); + } + + // Store the history of guilds and cities + final Set guildParticipants = leaderboard.getEntityParticipantsForCurrentPeriod(TYPE, false); + final Set cityParticipants = leaderboard.getEntityParticipantsForCurrentPeriod(TYPE, true); + int entityFaction; + int winner = 0; + int champion = 0; + if(guildParticipants != null) { + for (int guildId : guildParticipants) { + entityFaction = guildGetCurrentFaction(guildId); + if(entityFaction == FACTION_HASH_IMPERIAL) { + if (Arrays.stream(winnerImperialGuildIds).anyMatch(x -> x == guildId)) { + winner = 1; + } + if (winnerImperialGuildIds[0] == guildId) { + champion = 1; + } + } else if (entityFaction == FACTION_HASH_REBEL) { + if (Arrays.stream(winnerRebelGuildIds).anyMatch(x -> x == guildId)) { + winner = 1; + } + if (winnerRebelGuildIds[0] == guildId) { + champion = 1; + } + } + String[] data = { + Integer.toString(leaderboard.getCurrentLeaderboardPeriodEndTime()), + Integer.toString(entityFaction), + Float.toString(leaderboard.getEntityRelativeScoreToTransposedLeaderboardScores(guildId, entityFaction, leaderboard.LEADERBOARD_PERIOD_CURRENT, false)), + Integer.toString(winner), + Integer.toString(champion) + }; + leaderboard.packGcwEntityHistoryData(guildId, data, false); + winner = 0; + champion = 0; + } + } + if(cityParticipants != null) { + for (int cityId : cityParticipants) { + entityFaction = cityGetFaction(cityId); + if(entityFaction == FACTION_HASH_IMPERIAL) { + if (Arrays.stream(winnerImperialGuildIds).anyMatch(x -> x == cityId)) { + winner = 1; + } + if (winnerImperialGuildIds[0] == cityId) { + champion = 1; + } + } else if (entityFaction == FACTION_HASH_REBEL) { + if (Arrays.stream(winnerRebelGuildIds).anyMatch(x -> x == cityId)) { + winner = 1; + } + if (winnerRebelGuildIds[0] == cityId) { + champion = 1; + } + } + String[] data = { + Integer.toString(leaderboard.getCurrentLeaderboardPeriodEndTime()), + Integer.toString(entityFaction), + Float.toString(leaderboard.getEntityRelativeScoreToTransposedLeaderboardScores(cityId, entityFaction, leaderboard.LEADERBOARD_PERIOD_CURRENT, true)), + Integer.toString(winner), + Integer.toString(champion) + }; + leaderboard.packGcwEntityHistoryData(cityId, data, true); + winner = 0; + champion = 0; + } + } + leaderboard.debugMsg("Finished packing guild/city entity history..."); + + // Purge all Previous Week Data by moving Current into it + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_ID, rankedImperialPlayerIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_IMPERIAL_SCORE, rankedImperialPlayerScores); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_ID, rankedRebelPlayerIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_PLAYERS_REBEL_SCORE, rankedRebelPlayerScores); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_ID, rankedImperialGuildIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_IMPERIAL_SCORE, rankedImperialGuildScores); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_ID, rankedRebelGuildIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_GUILD_REBEL_SCORE, rankedRebelGuildScores); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_ID, rankedImperialCityIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_IMPERIAL_SCORE, rankedImperialCityScores); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_ID, rankedRebelCityIds); + setObjVar(self, leaderboard.OBJVAR_DATA_PREVIOUS_PERIOD_CITY_REBEL_SCORE, rankedRebelCityScores); + + // Grant winner rewards + if(rankedRebelPlayerIds != null && rankedRebelPlayerScores != null) { + leaderboard.handleGcwLeaderboardPlayerWinners(FACTION_HASH_REBEL, + Arrays.copyOfRange(rankedRebelPlayerIds, 0, Math.min(rankedRebelPlayerIds.length, 10)), + Arrays.copyOfRange(rankedRebelPlayerScores, 0, Math.min(rankedRebelPlayerScores.length, 10))); + } + if(rankedImperialPlayerIds != null && rankedImperialPlayerScores != null) { + leaderboard.handleGcwLeaderboardPlayerWinners(FACTION_HASH_IMPERIAL, + Arrays.copyOfRange(rankedImperialPlayerIds, 0, Math.min(rankedImperialPlayerIds.length, 10)), + Arrays.copyOfRange(rankedImperialPlayerScores, 0, Math.min(rankedImperialPlayerScores.length, 10))); + } + if(rankedRebelCityScores != null) { + leaderboard.handleGcwLeaderboardCityWinners(FACTION_HASH_REBEL, + winnerRebelCityIds, + Arrays.copyOfRange(rankedRebelCityScores, 0, Math.min(rankedRebelCityScores.length, 5))); + } + if(rankedImperialCityScores != null) { + leaderboard.handleGcwLeaderboardCityWinners(FACTION_HASH_IMPERIAL, + winnerImperialCityIds, + Arrays.copyOfRange(rankedImperialCityScores, 0, Math.min(rankedImperialCityScores.length, 5))); + } + if(rankedRebelGuildScores != null) { + leaderboard.handleGcwLeaderboardGuildWinners(FACTION_HASH_REBEL, + winnerRebelGuildIds, + Arrays.copyOfRange(rankedRebelGuildScores, 0, Math.min(rankedRebelGuildScores.length, 5))); + } + if(rankedImperialGuildScores != null) { + leaderboard.handleGcwLeaderboardGuildWinners(FACTION_HASH_IMPERIAL, + winnerImperialGuildIds, + Arrays.copyOfRange(rankedImperialGuildScores, 0, Math.min(rankedImperialGuildScores.length, 5))); + } + + // Add a time buffer for the above to process before we are done + messageTo(self, "handleLeaderboardFinishReset", null, 30f, true); + LOG("gcw_leaderboard_object", "Finished running handleLeaderboardDataPushBack() at "+getCalendarTime()); + + return SCRIPT_CONTINUE; + } + + public int handleLeaderboardFinishReset(obj_id self, dictionary params) throws InterruptedException { + + // Clear Current Week Data + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_IMPERIAL_SCORE); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_PLAYERS_REBEL_SCORE); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_IMPERIAL_SCORE); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_GUILD_REBEL_SCORE); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_IMPERIAL_SCORE); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_ID); + removeObjVar(self, leaderboard.OBJVAR_DATA_CURRENT_PERIOD_CITY_REBEL_SCORE); + + // Clear tracking vars and message existing participants we've finished + leaderboard.clearGcwLeaderboardTrackingVars(leaderboard.getPlayerParticipantsForCurrentPeriod(TYPE)); + + // Clear participant data + leaderboard.clearAllCurrentPeriodParticipants(); + + // +1 the Period Data + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD, getIntObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD+1)); + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD_START, getCalendarTime()); + setObjVar(self, leaderboard.OBJVAR_CURRENT_PERIOD_LAST_UPDATED, getCalendarTime()); + setObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PERIOD_END, leaderboard.getNextLeaderboardPeriodEndTime()); + + // Resume Heartbeat + recurringMessageTo(self, "handleCurrentPeriodUpdateHeartbeat", null, CURRENT_PERIOD_HEARTBEAT); + + utils.removeScriptVar(self, "reset_is_in_progress"); + LOG("gcw_leaderboard_object", "Finished running handleLeaderboardFinishReset() at "+getCalendarTime()); + return SCRIPT_CONTINUE; + } +} diff --git a/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_player_fix_it.java b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_player_fix_it.java new file mode 100644 index 000000000..97b226138 --- /dev/null +++ b/sku.0/sys.server/compiled/game/script/systems/leaderboard/gcw_player_fix_it.java @@ -0,0 +1,206 @@ +package script.systems.leaderboard; + +import script.dictionary; +import script.library.*; +import script.obj_id; + +/** + * This script is used as a tool to drop all possible leaderboard records from a player + * in the event their leaderboard tracking is somehow bugged. + * + * Usage: /script attach (player) systems.leaderboard.gcw_player_fix_it + * The script will do the rest. + */ +public class gcw_player_fix_it extends script.base_script { + + public gcw_player_fix_it() + { + } + + public static final String[] BADGE_LIST = { + "bdg_gcw_leaderboard_rebel_winner_rank1", + "bdg_gcw_leaderboard_rebel_winner_1x", + "bdg_gcw_leaderboard_rebel_winner_5x", + "bdg_gcw_leaderboard_rebel_winner_10x", + "bdg_gcw_leaderboard_rebel_winner_15x", + "bdg_gcw_leaderboard_rebel_winner_20x", + "bdg_gcw_leaderboard_rebel_winner_25x", + "bdg_gcw_leaderboard_rebel_winner_30x", + "bdg_gcw_leaderboard_rebel_winner_35x", + "bdg_gcw_leaderboard_rebel_winner_40x", + "bdg_gcw_leaderboard_rebel_winner_45x", + "bdg_gcw_leaderboard_rebel_winner_50x", + "bdg_gcw_leaderboard_rebel_winner_55x", + "bdg_gcw_leaderboard_rebel_winner_60x", + "bdg_gcw_leaderboard_rebel_winner_65x", + "bdg_gcw_leaderboard_rebel_winner_70x", + "bdg_gcw_leaderboard_rebel_winner_75x", + "bdg_gcw_leaderboard_rebel_winner_80x", + "bdg_gcw_leaderboard_rebel_winner_85x", + "bdg_gcw_leaderboard_rebel_winner_90x", + "bdg_gcw_leaderboard_rebel_winner_95x", + "bdg_gcw_leaderboard_rebel_winner_100x", + "bdg_gcw_leaderboard_imperial_winner_rank1", + "bdg_gcw_leaderboard_imperial_winner_1x", + "bdg_gcw_leaderboard_imperial_winner_5x", + "bdg_gcw_leaderboard_imperial_winner_10x", + "bdg_gcw_leaderboard_imperial_winner_15x", + "bdg_gcw_leaderboard_imperial_winner_20x", + "bdg_gcw_leaderboard_imperial_winner_25x", + "bdg_gcw_leaderboard_imperial_winner_30x", + "bdg_gcw_leaderboard_imperial_winner_35x", + "bdg_gcw_leaderboard_imperial_winner_40x", + "bdg_gcw_leaderboard_imperial_winner_45x", + "bdg_gcw_leaderboard_imperial_winner_50x", + "bdg_gcw_leaderboard_imperial_winner_55x", + "bdg_gcw_leaderboard_imperial_winner_60x", + "bdg_gcw_leaderboard_imperial_winner_65x", + "bdg_gcw_leaderboard_imperial_winner_70x", + "bdg_gcw_leaderboard_imperial_winner_75x", + "bdg_gcw_leaderboard_imperial_winner_80x", + "bdg_gcw_leaderboard_imperial_winner_85x", + "bdg_gcw_leaderboard_imperial_winner_90x", + "bdg_gcw_leaderboard_imperial_winner_95x", + "bdg_gcw_leaderboard_imperial_winner_100x", + "bdg_gcw_leaderboard_rebel_guild_winner_rank1", + "bdg_gcw_leaderboard_rebel_guild_winner_1x", + "bdg_gcw_leaderboard_rebel_guild_winner_5x", + "bdg_gcw_leaderboard_rebel_guild_winner_10x", + "bdg_gcw_leaderboard_rebel_guild_winner_15x", + "bdg_gcw_leaderboard_rebel_guild_winner_20x", + "bdg_gcw_leaderboard_rebel_guild_winner_25x", + "bdg_gcw_leaderboard_rebel_guild_winner_30x", + "bdg_gcw_leaderboard_rebel_guild_winner_35x", + "bdg_gcw_leaderboard_rebel_guild_winner_40x", + "bdg_gcw_leaderboard_rebel_guild_winner_45x", + "bdg_gcw_leaderboard_rebel_guild_winner_50x", + "bdg_gcw_leaderboard_rebel_guild_winner_55x", + "bdg_gcw_leaderboard_rebel_guild_winner_60x", + "bdg_gcw_leaderboard_rebel_guild_winner_65x", + "bdg_gcw_leaderboard_rebel_guild_winner_70x", + "bdg_gcw_leaderboard_rebel_guild_winner_75x", + "bdg_gcw_leaderboard_rebel_guild_winner_80x", + "bdg_gcw_leaderboard_rebel_guild_winner_85x", + "bdg_gcw_leaderboard_rebel_guild_winner_90x", + "bdg_gcw_leaderboard_rebel_guild_winner_95x", + "bdg_gcw_leaderboard_rebel_guild_winner_100x", + "bdg_gcw_leaderboard_imperial_guild_winner_rank1", + "bdg_gcw_leaderboard_imperial_guild_winner_1x", + "bdg_gcw_leaderboard_imperial_guild_winner_5x", + "bdg_gcw_leaderboard_imperial_guild_winner_10x", + "bdg_gcw_leaderboard_imperial_guild_winner_15x", + "bdg_gcw_leaderboard_imperial_guild_winner_20x", + "bdg_gcw_leaderboard_imperial_guild_winner_25x", + "bdg_gcw_leaderboard_imperial_guild_winner_30x", + "bdg_gcw_leaderboard_imperial_guild_winner_35x", + "bdg_gcw_leaderboard_imperial_guild_winner_40x", + "bdg_gcw_leaderboard_imperial_guild_winner_45x", + "bdg_gcw_leaderboard_imperial_guild_winner_50x", + "bdg_gcw_leaderboard_imperial_guild_winner_55x", + "bdg_gcw_leaderboard_imperial_guild_winner_60x", + "bdg_gcw_leaderboard_imperial_guild_winner_65x", + "bdg_gcw_leaderboard_imperial_guild_winner_70x", + "bdg_gcw_leaderboard_imperial_guild_winner_75x", + "bdg_gcw_leaderboard_imperial_guild_winner_80x", + "bdg_gcw_leaderboard_imperial_guild_winner_85x", + "bdg_gcw_leaderboard_imperial_guild_winner_90x", + "bdg_gcw_leaderboard_imperial_guild_winner_95x", + "bdg_gcw_leaderboard_imperial_guild_winner_100x", + "bdg_gcw_leaderboard_rebel_city_winner_rank1", + "bdg_gcw_leaderboard_rebel_city_winner_1x", + "bdg_gcw_leaderboard_rebel_city_winner_5x", + "bdg_gcw_leaderboard_rebel_city_winner_10x", + "bdg_gcw_leaderboard_rebel_city_winner_15x", + "bdg_gcw_leaderboard_rebel_city_winner_20x", + "bdg_gcw_leaderboard_rebel_city_winner_25x", + "bdg_gcw_leaderboard_rebel_city_winner_30x", + "bdg_gcw_leaderboard_rebel_city_winner_35x", + "bdg_gcw_leaderboard_rebel_city_winner_40x", + "bdg_gcw_leaderboard_rebel_city_winner_45x", + "bdg_gcw_leaderboard_rebel_city_winner_50x", + "bdg_gcw_leaderboard_rebel_city_winner_55x", + "bdg_gcw_leaderboard_rebel_city_winner_60x", + "bdg_gcw_leaderboard_rebel_city_winner_65x", + "bdg_gcw_leaderboard_rebel_city_winner_70x", + "bdg_gcw_leaderboard_rebel_city_winner_75x", + "bdg_gcw_leaderboard_rebel_city_winner_80x", + "bdg_gcw_leaderboard_rebel_city_winner_85x", + "bdg_gcw_leaderboard_rebel_city_winner_90x", + "bdg_gcw_leaderboard_rebel_city_winner_95x", + "bdg_gcw_leaderboard_rebel_city_winner_100x", + "bdg_gcw_leaderboard_imperial_city_winner_rank1", + "bdg_gcw_leaderboard_imperial_city_winner_1x", + "bdg_gcw_leaderboard_imperial_city_winner_5x", + "bdg_gcw_leaderboard_imperial_city_winner_10x", + "bdg_gcw_leaderboard_imperial_city_winner_15x", + "bdg_gcw_leaderboard_imperial_city_winner_20x", + "bdg_gcw_leaderboard_imperial_city_winner_25x", + "bdg_gcw_leaderboard_imperial_city_winner_30x", + "bdg_gcw_leaderboard_imperial_city_winner_35x", + "bdg_gcw_leaderboard_imperial_city_winner_40x", + "bdg_gcw_leaderboard_imperial_city_winner_45x", + "bdg_gcw_leaderboard_imperial_city_winner_50x", + "bdg_gcw_leaderboard_imperial_city_winner_55x", + "bdg_gcw_leaderboard_imperial_city_winner_60x", + "bdg_gcw_leaderboard_imperial_city_winner_65x", + "bdg_gcw_leaderboard_imperial_city_winner_70x", + "bdg_gcw_leaderboard_imperial_city_winner_75x", + "bdg_gcw_leaderboard_imperial_city_winner_80x", + "bdg_gcw_leaderboard_imperial_city_winner_85x", + "bdg_gcw_leaderboard_imperial_city_winner_90x", + "bdg_gcw_leaderboard_imperial_city_winner_95x", + "bdg_gcw_leaderboard_imperial_city_winner_100x" + }; + + public int OnAttach(obj_id self) throws InterruptedException { + messageTo(self, "runFixIt", null, 3f, true); + return SCRIPT_CONTINUE; + } + + public int OnInitialize(obj_id self) throws InterruptedException { + messageTo(self, "runFixIt", null, 3f, true); + return SCRIPT_CONTINUE; + } + + public int runFixIt(obj_id self, dictionary params) throws InterruptedException { + + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PLAYER_PERIOD); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_TRACKING_PLAYER_ALREADY); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_TRACKING_GUILD_ALREADY); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_TRACKING_CITY_ALREADY); + removeObjVar(self, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_PLAYER); + removeObjVar(self, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_GUILD); + removeObjVar(self, leaderboard.OBJVAR_LAST_NOTIFIED_RANK_GCW_CITY); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_IMPERIAL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_REBEL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_PLAYER_REBEL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_GUILD_IMPERIAL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_GUILD_REBEL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_CITY_IMPERIAL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_CITY_REBEL_CURRENT_TEMP_SCORE); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_IMPERIAL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PLAYER_SCORE_REBEL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_IMPERIAL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_CITY_SCORE_REBEL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_IMPERIAL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_GUILD_SCORE_REBEL); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT + "imperial"); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_PLAYER_WIN_COUNT + "rebel"); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + "imperial"); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_GUILD_WIN_COUNT + "rebel"); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_CITY_WIN_COUNT + "imperial"); + removeObjVar(self, leaderboard.OBJVAR_LEADERBOARD_CITY_WIN_COUNT + "rebel"); + removeObjVar(self, city.VAR_LEADERBOARD_PERIOD_ON_JOIN); + removeObjVar(self, guild.VAR_LEADERBOARD_PERIOD_ON_JOIN); + + utils.removeScriptVar(self, "leaderboard_factional_presence_tracking"); + leaderboard.purgeReigningChampions(self); + leaderboard.purgeWarPlanners(self); + for (String b : BADGE_LIST) { + badge.revokeBadge(self, b, false); + } + + detachScript(self, "systems.leaderboard.gcw_player_fix_it"); + return SCRIPT_CONTINUE; + } +} diff --git a/sku.0/sys.server/compiled/game/script/terminal/terminal_gcw_publish_gift.java b/sku.0/sys.server/compiled/game/script/terminal/terminal_gcw_publish_gift.java index ddd03cdfb..86bdbd637 100755 --- a/sku.0/sys.server/compiled/game/script/terminal/terminal_gcw_publish_gift.java +++ b/sku.0/sys.server/compiled/game/script/terminal/terminal_gcw_publish_gift.java @@ -58,6 +58,18 @@ public class terminal_gcw_publish_gift extends script.terminal.base.base_termina "TATOOINE" } }; + public int OnInitialize(obj_id self) throws InterruptedException { + if(!hasScript(self, "systems.leaderboard.gcw_display_object")) { + attachScript(self, "systems.leaderboard.gcw_display_object"); + } + return SCRIPT_CONTINUE; + } + public int OnAttach(obj_id self) throws InterruptedException { + if(!hasScript(self, "systems.leaderboard.gcw_display_object")) { + attachScript(self, "systems.leaderboard.gcw_display_object"); + } + return SCRIPT_CONTINUE; + } public int OnObjectMenuRequest(obj_id self, obj_id player, menu_info mi) throws InterruptedException { menu_info_data data = mi.getMenuItemByType(menu_info_types.ITEM_USE);