From 316a73533a7e7b84078095a27b14e7644fcc9756 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Mon, 19 Feb 2018 23:57:19 +0100 Subject: [PATCH 1/9] Added Galactic Civil War point system --- .../holocore/intents/CivilWarPointIntent.java | 28 ++ .../commands/callbacks/QaToolCmdCallback.java | 18 + .../services/faction/CivilWarService.java | 322 ++++++++++++++++++ ...actionService.java => FactionManager.java} | 16 +- .../holocore/services/galaxy/GameManager.java | 4 +- .../holocore/services/TestServices.java | 2 + .../services/faction/TestCivilWarService.java | 106 ++++++ 7 files changed, 490 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java create mode 100644 src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java rename src/main/java/com/projectswg/holocore/services/faction/{FactionService.java => FactionManager.java} (96%) create mode 100644 src/test/java/com/projectswg/holocore/services/faction/TestCivilWarService.java diff --git a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java new file mode 100644 index 000000000..1cad7840f --- /dev/null +++ b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java @@ -0,0 +1,28 @@ +package com.projectswg.holocore.intents; + +import com.projectswg.common.control.Intent; +import com.projectswg.holocore.resources.objects.player.PlayerObject; + +public class CivilWarPointIntent extends Intent { + + public static void broadcast(PlayerObject receiver, int points) { + new CivilWarPointIntent(receiver, points).broadcast(); + } + + private final PlayerObject receiver; + private final int points; + + private CivilWarPointIntent(PlayerObject receiver, int points) { + this.receiver = receiver; + this.points = points; + } + + public PlayerObject getReceiver() { + return receiver; + } + + public int getPoints() { + return points; + } + +} diff --git a/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java b/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java index 76a38ba2d..20293bca1 100644 --- a/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java +++ b/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java @@ -32,6 +32,8 @@ import com.projectswg.common.data.location.Terrain; import com.projectswg.common.data.sui.SuiEvent; import com.projectswg.common.debug.Log; +import com.projectswg.holocore.intents.CivilWarPointIntent; +import com.projectswg.holocore.resources.objects.player.PlayerObject; import com.projectswg.holocore.scripts.commands.admin.qatool.QaToolDetails; import com.projectswg.holocore.intents.chat.SystemMessageIntent; import com.projectswg.holocore.intents.experience.ExperienceIntent; @@ -102,6 +104,9 @@ public class QaToolCmdCallback implements ICmdCallback { else SystemMessageIntent.broadcastPersonal(player, "QATool XP: Expected format: /qatool xp "); break; + case "gcw": + grantGcw(player, command[1]); + break; default: displayMainWindow(player); break; @@ -226,6 +231,19 @@ public class QaToolCmdCallback implements ICmdCallback { } } + private void grantGcw(Player player, String pointsArg) { + PlayerObject receiver = player.getCreatureObject().getPlayerObject(); + + try { + int points = Integer.parseInt(pointsArg); + CivilWarPointIntent.broadcast(receiver, points); + Log.i("GCW command: %s gave themselves %d GCW points", player.getUsername(), points); + } catch (NumberFormatException e) { + SystemMessageIntent.broadcastPersonal(player, String.format("XP command: %s is not a number", pointsArg)); + } + + } + /* Utility Methods */ private void createMessageBox(Player player, String title, String prompt) { diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java new file mode 100644 index 000000000..de3cadebd --- /dev/null +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -0,0 +1,322 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ + +package com.projectswg.holocore.services.faction; + +import com.projectswg.common.control.Service; +import com.projectswg.common.data.encodables.oob.ProsePackage; +import com.projectswg.common.data.encodables.oob.StringId; +import com.projectswg.common.data.encodables.tangible.PvpFaction; +import com.projectswg.common.data.encodables.tangible.PvpStatus; +import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; +import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage; +import com.projectswg.common.utilities.ThreadUtilities; +import com.projectswg.holocore.intents.CivilWarPointIntent; +import com.projectswg.holocore.intents.chat.SystemMessageIntent; +import com.projectswg.holocore.intents.combat.CreatureKilledIntent; +import com.projectswg.holocore.intents.object.DestroyObjectIntent; +import com.projectswg.holocore.intents.object.ObjectCreatedIntent; +import com.projectswg.holocore.resources.objects.SWGObject; +import com.projectswg.holocore.resources.objects.creature.CreatureDifficulty; +import com.projectswg.holocore.resources.objects.creature.CreatureObject; +import com.projectswg.holocore.resources.objects.player.PlayerObject; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +final class CivilWarService extends Service { + + private final Set playerObjects; + private final ScheduledExecutorService executor; + private final DayOfWeek updateWeekDay; + private final LocalTime updateTime; + private final ZoneOffset updateOffset; + private final TimeUnit updateUnit; + private int rankEpoch; + + CivilWarService() { + playerObjects = new HashSet<>(); + executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("civil-war-service")); + // Rank update time is the night between Thursday and Friday at 00:00 UTC + updateWeekDay = DayOfWeek.FRIDAY; + updateTime = LocalTime.MIDNIGHT; + updateOffset = ZoneOffset.UTC; + updateUnit = TimeUnit.SECONDS; + + registerForIntent(CivilWarPointIntent.class, this::handleCivilWarPointIntent); + registerForIntent(CreatureKilledIntent.class, this::handleCreatureKilledIntent); + registerForIntent(DestroyObjectIntent.class, this::handleDestroyObjectIntent); + registerForIntent(ObjectCreatedIntent.class, this::handleObjectCreatedIntent); + } + + @Override + public boolean initialize() { + scheduleRankUpdate(); + + return super.initialize(); + } + + private void scheduleRankUpdate() { + LocalDate now = LocalDate.now(); + int nowEpoch = (int) now.toEpochSecond(updateTime, updateOffset); + rankEpoch = nextUpdateTime(now); // Is in the future + int delay = rankEpoch - nowEpoch; + + executor.schedule(this::updateRanks, delay, updateUnit); + playerObjects.stream().forEach(this::updateTimer); + } + + private void updateTimer(PlayerObject playerObject) { + playerObject.setGcwNextUpdate(rankEpoch); + } + + private void updateRank(PlayerObject playerObject) { + int currentRank = playerObject.getCurrentRank(); + float oldProgress = playerObject.getRankProgress(); + int points = playerObject.getGcwPoints(); + float newProgress = rankProgress(oldProgress, 0, currentRank, points); + + if (newProgress >= 100) { + // Rank them up! + int newRank = currentRank + 1; + + playerObject.setCurrentRank(newRank); + + // Leftover points carry over + int leftoverPoints = leftoverPoints(newProgress, points); + + // Calculate progress within the new rank using leftover points + float nextRankProgress = rankProgress(newProgress, 0, newRank, leftoverPoints); + + playerObject.setRankProgress(nextRankProgress); + } else if (newProgress > 0) { + // Push their progress forwards + playerObject.setRankProgress(newProgress); + } else if (newProgress < 0) { + if (isRankDown(oldProgress, newProgress)) { + int newRank = currentRank - 1; + + playerObject.setCurrentRank(newRank); + + // Push their progress backwards in the new rank + int leftoverPoints = leftoverPoints(newProgress, points); + + // Calculate progress within the new rank using leftover points + float nextRankProgress = 100 - rankProgress(newProgress, 0, newRank, leftoverPoints); + + playerObject.setRankProgress(nextRankProgress); + } else { + // Push their progress backwards + playerObject.setRankProgress(newProgress); + } + + } + + int kills = playerObject.getPvpKills(); + + // Reset current kills and points + playerObject.setGcwPoints(0); + playerObject.setPvpKills(0); + + // Add current stats to lifetime stats + playerObject.setLifetimeGcwPoints(playerObject.getLifetimeGcwPoints() + points); + playerObject.setLifetimePvpKills(playerObject.getLifetimePvpKills() + kills); + } + + private void updateRanks() { + playerObjects.stream() + .filter(playerObject -> playerObject.getCurrentRank() > 1) + .forEach(this::updateRank); + + scheduleRankUpdate(); // Schedule next rank update + } + + int leftoverPoints(float progress, int points) { + return (int) ((progress - 100) / 100 * (float) points); + } + + boolean isRankDown(float oldProgress, float newProgress) { + return oldProgress + newProgress < 0; + } + + int nextUpdateTime(LocalDate now) { + LocalDate nextUpdateDate = now.with(updateWeekDay); + + return (int) nextUpdateDate.toEpochSecond(updateTime, updateOffset); + } + + boolean isDecayRank(int rank) { + return rank >= 7; // Lieutenant and above has decay + } + + float rankProgress(float currentProgress, float decay, int currentRank, int points) { + float progress = currentProgress - decay; + + progress += (float) points / (currentRank * 100.0f); + + return progress; + } + + boolean isFactionEligible(PvpFaction killerFaction, PvpFaction corpseFaction, boolean specialForces) { + return killerFaction != PvpFaction.NEUTRAL && corpseFaction != PvpFaction.NEUTRAL && killerFaction != corpseFaction && specialForces; + } + + byte makeMultiplier(boolean specialForces, boolean player) { + byte multiplier = 1; + + if (specialForces) { + multiplier += 1; + } + + if (player) { + multiplier += 18; + } + + return multiplier; + } + + int baseForDifficulty(CreatureDifficulty difficulty) { + switch (difficulty) { + case NORMAL: + return 5; + case ELITE: + return 10; + case BOSS: + return 15; + default: + throw new IllegalArgumentException("Unhandled CreatureDifficulty: " + difficulty); + } + } + + int pointsGranted(int base, byte multiplier) { + return base * multiplier; + } + + private void grantPoints(ProsePackage prose, PlayerObject receiver, int points) { + receiver.setGcwPoints(receiver.getGcwPoints() + points); + SystemMessageIntent.broadcastPersonal(receiver.getOwner(), prose); + } + + private void handleCivilWarPointIntent(CivilWarPointIntent cwpi) { + int points = cwpi.getPoints(); + PlayerObject receiver = cwpi.getReceiver(); + ProsePackage prose = new ProsePackage(new StringId("gcw", "gcw_rank_generic_point_grant"), "DI", points); + + grantPoints(prose, receiver, points); + } + + private void handleCreatureKilledIntent(CreatureKilledIntent cki) { + CreatureObject corpseCreature = cki.getCorpse(); + CreatureObject killerCreature = cki.getKiller(); + PvpFaction killerFaction = killerCreature.getPvpFaction(); + + if (!killerCreature.isPlayer()) { + return; + } + + boolean specialForces = corpseCreature.getPvpStatus() == PvpStatus.SPECIALFORCES; + + if (!isFactionEligible(killerFaction, corpseCreature.getPvpFaction(), specialForces)) { + return; + } + + PlayerObject killerPlayer = killerCreature.getPlayerObject(); + + byte multiplier = makeMultiplier(specialForces, corpseCreature.isPlayer()); + int base = baseForDifficulty(corpseCreature.getDifficulty()); + int granted = pointsGranted(base, multiplier); + ProsePackage prose; + + if (specialForces) { + // Increment kill counter + killerPlayer.setPvpKills(killerPlayer.getPvpKills() + 1); + + // Determine which effect and sound to play + String effectFile; + String soundFile; + + if (killerFaction == PvpFaction.REBEL) { + effectFile = "clienteffect/holoemote_rebel.cef"; + soundFile = "sound/music_themequest_victory_rebel.snd"; + } else { + effectFile = "clienteffect/holoemote_imperial.cef"; + soundFile = "sound/music_themequest_victory_imperial.snd"; + } + + // PvP GCW point system message + prose = new ProsePackage("StringId", new StringId("gcw", "gcw_rank_pvp_kill_point_grant"), "DI", granted, "TT", corpseCreature.getObjectName()); + + // Send visual effect to killer and everyone around + killerCreature.sendObserversAndSelf(new PlayClientEffectObjectMessage(effectFile, "head", killerCreature.getObjectId(), "")); + + // Send sound to just to the killer + killerCreature.sendSelf(new PlayMusicMessage(0, soundFile, 0, false)); + + // TODO planetary control points? + } else { + // NPC GCW point system message + prose = new ProsePackage(new StringId("gcw", "gcw_rank_generic_point_grant"), "DI", granted); + } + + // Increment GCW point counter + grantPoints(prose, killerPlayer, granted); + } + + private void handleDestroyObjectIntent(DestroyObjectIntent doi) { + SWGObject object = doi.getObject(); + + if (!(object instanceof PlayerObject)) { + return; + } + + playerObjects.remove(object); + } + + private void handleObjectCreatedIntent(ObjectCreatedIntent oci) { + SWGObject object = oci.getObject(); + + if (!(object instanceof PlayerObject)) { + return; + } + + PlayerObject playerObject = (PlayerObject) object; + + // Let's make sure the timer's displayed for them + updateTimer(playerObject); + + playerObjects.add(playerObject); + } + +} diff --git a/src/main/java/com/projectswg/holocore/services/faction/FactionService.java b/src/main/java/com/projectswg/holocore/services/faction/FactionManager.java similarity index 96% rename from src/main/java/com/projectswg/holocore/services/faction/FactionService.java rename to src/main/java/com/projectswg/holocore/services/faction/FactionManager.java index 8e6337722..e7529c180 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/FactionService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/FactionManager.java @@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import com.projectswg.common.concurrency.PswgScheduledThreadPool; -import com.projectswg.common.control.Service; +import com.projectswg.common.control.Manager; import com.projectswg.common.data.encodables.tangible.PvpFaction; import com.projectswg.common.data.encodables.tangible.PvpFlag; import com.projectswg.common.data.encodables.tangible.PvpStatus; @@ -45,15 +45,17 @@ import com.projectswg.holocore.resources.objects.creature.CreatureObject; import com.projectswg.holocore.resources.objects.tangible.TangibleObject; import com.projectswg.holocore.resources.player.Player; -public final class FactionService extends Service { +public final class FactionManager extends Manager { private final Map> statusChangers; private final PswgScheduledThreadPool executor; - public FactionService() { + public FactionManager() { statusChangers = new ConcurrentHashMap<>(); executor = new PswgScheduledThreadPool(1, "faction-service"); + addChildService(new CivilWarService()); + registerForIntent(FactionIntent.class, this::handleFactionIntent); } @@ -97,7 +99,13 @@ public final class FactionService extends Service { if(target.getBaselineType() == BaselineType.CREO && target.getPvpFaction() != PvpFaction.NEUTRAL) { // We're given rank 1 upon joining a non-neutral faction - ((CreatureObject) target).setFactionRank((byte) 1); + CreatureObject creatureTarget = (CreatureObject) target; + + if (!creatureTarget.isPlayer()) { + return; + } + + creatureTarget.getPlayerObject().setCurrentRank(1); } } diff --git a/src/main/java/com/projectswg/holocore/services/galaxy/GameManager.java b/src/main/java/com/projectswg/holocore/services/galaxy/GameManager.java index 72222d059..24df2f667 100644 --- a/src/main/java/com/projectswg/holocore/services/galaxy/GameManager.java +++ b/src/main/java/com/projectswg/holocore/services/galaxy/GameManager.java @@ -36,7 +36,7 @@ import com.projectswg.holocore.services.commands.CommandService; import com.projectswg.holocore.services.commands.EntertainmentService; import com.projectswg.holocore.services.crafting.CraftingManager; import com.projectswg.holocore.services.experience.ExperienceManager; -import com.projectswg.holocore.services.faction.FactionService; +import com.projectswg.holocore.services.faction.FactionManager; import com.projectswg.holocore.services.group.GroupService; import com.projectswg.holocore.services.sui.SuiService; @@ -49,7 +49,7 @@ public class GameManager extends Manager { addChildService(new CollectionService()); addChildService(new CollectionBadgeManager()); addChildService(new EnvironmentService()); - addChildService(new FactionService()); + addChildService(new FactionManager()); addChildService(new GroupService()); addChildService(new SkillModService()); addChildService(new EntertainmentService()); diff --git a/src/test/java/com/projectswg/holocore/services/TestServices.java b/src/test/java/com/projectswg/holocore/services/TestServices.java index 99c6412bb..346fd3be4 100644 --- a/src/test/java/com/projectswg/holocore/services/TestServices.java +++ b/src/test/java/com/projectswg/holocore/services/TestServices.java @@ -27,6 +27,7 @@ ***********************************************************************************/ package com.projectswg.holocore.services; +import com.projectswg.holocore.services.faction.TestCivilWarService; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @@ -38,6 +39,7 @@ import com.projectswg.holocore.services.player.TestPlayer; @RunWith(Suite.class) @SuiteClasses({ + TestCivilWarService.class, TestRareLootService.class, TestCrafting.class, TestPlayer.class, diff --git a/src/test/java/com/projectswg/holocore/services/faction/TestCivilWarService.java b/src/test/java/com/projectswg/holocore/services/faction/TestCivilWarService.java new file mode 100644 index 000000000..d91f5c3fe --- /dev/null +++ b/src/test/java/com/projectswg/holocore/services/faction/TestCivilWarService.java @@ -0,0 +1,106 @@ +/************************************************************************************ + * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * -------------------------------------------------------------------------------- * + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + * * + ***********************************************************************************/ + +package com.projectswg.holocore.services.faction; + +import com.projectswg.common.data.encodables.tangible.PvpFaction; +import com.projectswg.holocore.resources.objects.creature.CreatureDifficulty; +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDate; + +public class TestCivilWarService { + + private final CivilWarService service; + + public TestCivilWarService() { + service = new CivilWarService(); + } + + @Test + public void testIsFactionEligible() { + Assert.assertTrue(service.isFactionEligible(PvpFaction.REBEL, PvpFaction.IMPERIAL, true)); + Assert.assertFalse(service.isFactionEligible(PvpFaction.NEUTRAL, PvpFaction.NEUTRAL, false)); + Assert.assertFalse(service.isFactionEligible(PvpFaction.NEUTRAL, PvpFaction.REBEL, false)); + Assert.assertFalse(service.isFactionEligible(PvpFaction.NEUTRAL, PvpFaction.IMPERIAL, false)); + Assert.assertFalse(service.isFactionEligible(PvpFaction.REBEL, PvpFaction.REBEL, true)); + } + + @Test + public void testMakeMultiplier() { + Assert.assertEquals(1, service.makeMultiplier(false, false)); + Assert.assertEquals(2, service.makeMultiplier(true, false)); + Assert.assertEquals(19, service.makeMultiplier(false, true)); + Assert.assertEquals(20, service.makeMultiplier(true, true)); + } + + @Test + public void testBaseForDifficulty() { + Assert.assertEquals(5, service.baseForDifficulty(CreatureDifficulty.NORMAL)); + Assert.assertEquals(10, service.baseForDifficulty(CreatureDifficulty.ELITE)); + Assert.assertEquals(15, service.baseForDifficulty(CreatureDifficulty.BOSS)); + } + + @Test + public void testPointsGranted() { + Assert.assertEquals(200, service.pointsGranted(10, (byte) 20)); + } + + @Test + public void testNextUpdateTime() { + LocalDate now = LocalDate.of(2018, 2, 19); // It's a Monday + LocalDate rankDay = LocalDate.of(2018, 2, 23); // It's a Friday + int expectedRankTime = 1519344000; // The Friday that week, at 00:00 UTC (night between thursday and friday) + + Assert.assertEquals(expectedRankTime, service.nextUpdateTime(now)); + Assert.assertEquals(expectedRankTime, service.nextUpdateTime(rankDay)); + } + + @Test + public void testIsDecayRank() { + Assert.assertFalse(service.isDecayRank(6)); + Assert.assertTrue(service.isDecayRank(7)); + } + + @Test + public void testRankProgress() { + Assert.assertEquals(2.86f, service.rankProgress(10.0f, 20.0f, 7, 9000), 1); + } + + @Test + public void testIsRankDown() { + Assert.assertFalse(service.isRankDown(40, 50)); + Assert.assertTrue(service.isRankDown(3, -10)); + } + + @Test + public void testLeftoverPoints() { + Assert.assertEquals(6000, service.leftoverPoints(130, 20000)); + } +} From fe0639c1999ae5eeadedfab06260b1affeb53b42 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 00:01:14 +0100 Subject: [PATCH 2/9] Added GCW rank decay --- .../holocore/services/faction/CivilWarService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index de3cadebd..aa132f848 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -106,7 +106,13 @@ final class CivilWarService extends Service { int currentRank = playerObject.getCurrentRank(); float oldProgress = playerObject.getRankProgress(); int points = playerObject.getGcwPoints(); - float newProgress = rankProgress(oldProgress, 0, currentRank, points); + float decay = 0; + + if (isDecayRank(currentRank)) { + decay = currentRank; + } + + float newProgress = rankProgress(oldProgress, decay, currentRank, points); if (newProgress >= 100) { // Rank them up! @@ -153,6 +159,8 @@ final class CivilWarService extends Service { // Add current stats to lifetime stats playerObject.setLifetimeGcwPoints(playerObject.getLifetimeGcwPoints() + points); playerObject.setLifetimePvpKills(playerObject.getLifetimePvpKills() + kills); + + // TODO award GCW abilities } private void updateRanks() { From cea870db604aba87266936b2399e75f6f19c76b6 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 00:06:19 +0100 Subject: [PATCH 3/9] Added license header to CivilWarPointIntent --- .../holocore/intents/CivilWarPointIntent.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java index 1cad7840f..7c91ae5b6 100644 --- a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java +++ b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java @@ -1,3 +1,29 @@ +/*********************************************************************************** + * Copyright (c) 2018 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + ***********************************************************************************/ package com.projectswg.holocore.intents; import com.projectswg.common.control.Intent; From 434e9d495f1de5719ec41202c6c8099d55c7b982 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 00:11:41 +0100 Subject: [PATCH 4/9] Fixed a GCW rank progression bug --- .../services/faction/CivilWarService.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index aa132f848..ef0e235d1 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -128,26 +128,20 @@ final class CivilWarService extends Service { playerObject.setRankProgress(nextRankProgress); } else if (newProgress > 0) { - // Push their progress forwards + // Set their new progress playerObject.setRankProgress(newProgress); - } else if (newProgress < 0) { - if (isRankDown(oldProgress, newProgress)) { - int newRank = currentRank - 1; + } else if (newProgress < 0 && isRankDown(oldProgress, newProgress)) { + int newRank = currentRank - 1; - playerObject.setCurrentRank(newRank); + playerObject.setCurrentRank(newRank); - // Push their progress backwards in the new rank - int leftoverPoints = leftoverPoints(newProgress, points); + // Push their progress backwards in the new rank + int leftoverPoints = leftoverPoints(newProgress, points); - // Calculate progress within the new rank using leftover points - float nextRankProgress = 100 - rankProgress(newProgress, 0, newRank, leftoverPoints); + // Calculate progress within the new rank using leftover points + float nextRankProgress = 100 - rankProgress(newProgress, 0, newRank, leftoverPoints); - playerObject.setRankProgress(nextRankProgress); - } else { - // Push their progress backwards - playerObject.setRankProgress(newProgress); - } - + playerObject.setRankProgress(nextRankProgress); } int kills = playerObject.getPvpKills(); From 47b3f8fd7dfe6768ae3a78c005dfa46abcb61007 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 01:50:40 +0100 Subject: [PATCH 5/9] Added GCW rank abilities --- .../services/faction/CivilWarService.java | 105 ++++++++++++++++-- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index ef0e235d1..d4912d656 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -33,6 +33,7 @@ import com.projectswg.common.data.encodables.oob.ProsePackage; import com.projectswg.common.data.encodables.oob.StringId; import com.projectswg.common.data.encodables.tangible.PvpFaction; import com.projectswg.common.data.encodables.tangible.PvpStatus; +import com.projectswg.common.debug.Log; import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage; import com.projectswg.common.utilities.ThreadUtilities; @@ -45,12 +46,18 @@ import com.projectswg.holocore.resources.objects.SWGObject; import com.projectswg.holocore.resources.objects.creature.CreatureDifficulty; import com.projectswg.holocore.resources.objects.creature.CreatureObject; import com.projectswg.holocore.resources.objects.player.PlayerObject; +import com.projectswg.holocore.resources.server_info.SdbLoader; +import com.projectswg.holocore.resources.server_info.StandardLog; +import java.io.File; +import java.io.IOException; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneOffset; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -58,6 +65,10 @@ import java.util.concurrent.TimeUnit; final class CivilWarService extends Service { + private static final int IMPERIAL_INDEX = 0; + private static final int REBEL_INDEX = 1; + + private final Map rankAbilities; private final Set playerObjects; private final ScheduledExecutorService executor; private final DayOfWeek updateWeekDay; @@ -67,6 +78,7 @@ final class CivilWarService extends Service { private int rankEpoch; CivilWarService() { + rankAbilities = new HashMap<>(); playerObjects = new HashSet<>(); executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("civil-war-service")); // Rank update time is the night between Thursday and Friday at 00:00 UTC @@ -75,6 +87,8 @@ final class CivilWarService extends Service { updateOffset = ZoneOffset.UTC; updateUnit = TimeUnit.SECONDS; + loadRankAbilities(); + registerForIntent(CivilWarPointIntent.class, this::handleCivilWarPointIntent); registerForIntent(CreatureKilledIntent.class, this::handleCreatureKilledIntent); registerForIntent(DestroyObjectIntent.class, this::handleDestroyObjectIntent); @@ -88,6 +102,29 @@ final class CivilWarService extends Service { return super.initialize(); } + private void loadRankAbilities() { + String what = "rank abilities"; + long startTime = StandardLog.onStartLoad(what); + + try (SdbLoader.SdbResultSet set = SdbLoader.load(new File("serverdata/gcw/abilities.sdb"))) { + while (set.next()) { + int rank = (int) set.getInt(0); + String imperial = set.getText(1); + String rebel = set.getText(2); + String[] abilities = new String[2]; + + abilities[IMPERIAL_INDEX] = imperial; + abilities[REBEL_INDEX] = rebel; + + rankAbilities.put(rank, abilities); + } + } catch (IOException e) { + Log.e(e); + } + + StandardLog.onEndLoad(rankAbilities.size(), what, startTime); + } + private void scheduleRankUpdate() { LocalDate now = LocalDate.now(); int nowEpoch = (int) now.toEpochSecond(updateTime, updateOffset); @@ -102,6 +139,51 @@ final class CivilWarService extends Service { playerObject.setGcwNextUpdate(rankEpoch); } + private void changeRank(PlayerObject playerObject, int newRank) { + if (newRank < 1 || newRank > 12) { + throw new IllegalArgumentException("Rank must be at least 1 and max 12"); + } + + int oldRank = playerObject.getCurrentRank(); + + if (oldRank < 1) { + throw new IllegalArgumentException("Unranked players cannot receive rank changes"); + } + + int highestRank = Math.max(oldRank, newRank); + int lowestRank = Math.min(oldRank, newRank); + CreatureObject creature = (CreatureObject) playerObject.getParent(); + PvpFaction faction = creature.getPvpFaction(); + int abilityIndex = faction == PvpFaction.IMPERIAL ? IMPERIAL_INDEX : REBEL_INDEX; + + playerObject.setCurrentRank(newRank); + + if (oldRank > newRank) { + // They've been demoted + for (int rank = highestRank; rank >= lowestRank; rank--) { + if (!isDecayRank(rank)) { + break; + } + + String ability = rankAbilities.get(rank)[abilityIndex]; + + creature.removeAbility(ability); + } + } else if (oldRank < newRank) { + // They've been promoted + for (int rank = lowestRank; rank <= highestRank; rank++) { + if (!isDecayRank(rank)) { + continue; + } + + Log.d("Rank: " + rank); + String ability = rankAbilities.get(rank)[abilityIndex]; + + creature.addAbility(ability); + } + } + } + private void updateRank(PlayerObject playerObject) { int currentRank = playerObject.getCurrentRank(); float oldProgress = playerObject.getRankProgress(); @@ -115,31 +197,30 @@ final class CivilWarService extends Service { float newProgress = rankProgress(oldProgress, decay, currentRank, points); if (newProgress >= 100) { - // Rank them up! - int newRank = currentRank + 1; + int promotion = playerObject.getCurrentRank() + 1; - playerObject.setCurrentRank(newRank); + changeRank(playerObject, promotion); // Leftover points carry over int leftoverPoints = leftoverPoints(newProgress, points); // Calculate progress within the new rank using leftover points - float nextRankProgress = rankProgress(newProgress, 0, newRank, leftoverPoints); + float nextRankProgress = rankProgress(newProgress, 0, promotion, leftoverPoints); playerObject.setRankProgress(nextRankProgress); } else if (newProgress > 0) { // Set their new progress playerObject.setRankProgress(newProgress); } else if (newProgress < 0 && isRankDown(oldProgress, newProgress)) { - int newRank = currentRank - 1; - - playerObject.setCurrentRank(newRank); + int demotion = playerObject.getCurrentRank() - 1; + + changeRank(playerObject, demotion); // Push their progress backwards in the new rank int leftoverPoints = leftoverPoints(newProgress, points); // Calculate progress within the new rank using leftover points - float nextRankProgress = 100 - rankProgress(newProgress, 0, newRank, leftoverPoints); + float nextRankProgress = 100 - rankProgress(newProgress, 0, demotion, leftoverPoints); playerObject.setRankProgress(nextRankProgress); } @@ -159,7 +240,7 @@ final class CivilWarService extends Service { private void updateRanks() { playerObjects.stream() - .filter(playerObject -> playerObject.getCurrentRank() > 1) + .filter(playerObject -> playerObject.getCurrentRank() > 0) .forEach(this::updateRank); scheduleRankUpdate(); // Schedule next rank update @@ -318,6 +399,12 @@ final class CivilWarService extends Service { // Let's make sure the timer's displayed for them updateTimer(playerObject); + // TODO debugging - remove three lines below! + CreatureObject creature = (CreatureObject) playerObject.getParent(); + creature.setPvpFaction(PvpFaction.REBEL); + playerObject.setCurrentRank(1); + changeRank(playerObject, 12); + playerObjects.add(playerObject); } From c7d5426a79658f2f8f7f1951618397b5774a389e Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 02:13:25 +0100 Subject: [PATCH 6/9] Removed debug code from CivilWarService --- .../holocore/services/faction/CivilWarService.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index d4912d656..3ca97b07b 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -132,7 +132,7 @@ final class CivilWarService extends Service { int delay = rankEpoch - nowEpoch; executor.schedule(this::updateRanks, delay, updateUnit); - playerObjects.stream().forEach(this::updateTimer); + playerObjects.forEach(this::updateTimer); } private void updateTimer(PlayerObject playerObject) { @@ -176,7 +176,6 @@ final class CivilWarService extends Service { continue; } - Log.d("Rank: " + rank); String ability = rankAbilities.get(rank)[abilityIndex]; creature.addAbility(ability); @@ -399,12 +398,6 @@ final class CivilWarService extends Service { // Let's make sure the timer's displayed for them updateTimer(playerObject); - // TODO debugging - remove three lines below! - CreatureObject creature = (CreatureObject) playerObject.getParent(); - creature.setPvpFaction(PvpFaction.REBEL); - playerObject.setCurrentRank(1); - changeRank(playerObject, 12); - playerObjects.add(playerObject); } From 3cb6ca96bed370bce5a43f2834c31c7b405ba223 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 03:05:47 +0100 Subject: [PATCH 7/9] Moved CivilWarPointIntent.broadcast() to bottom --- .../projectswg/holocore/intents/CivilWarPointIntent.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java index 7c91ae5b6..92a3ced9a 100644 --- a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java +++ b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java @@ -31,10 +31,6 @@ import com.projectswg.holocore.resources.objects.player.PlayerObject; public class CivilWarPointIntent extends Intent { - public static void broadcast(PlayerObject receiver, int points) { - new CivilWarPointIntent(receiver, points).broadcast(); - } - private final PlayerObject receiver; private final int points; @@ -51,4 +47,8 @@ public class CivilWarPointIntent extends Intent { return points; } + public static void broadcast(PlayerObject receiver, int points) { + new CivilWarPointIntent(receiver, points).broadcast(); + } + } From 17ff9bab3060c0add36c906bd0855f9914b3808b Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 03:20:26 +0100 Subject: [PATCH 8/9] Fixed a couple of GCW issues --- .../commands/callbacks/QaToolCmdCallback.java | 2 +- .../services/faction/CivilWarService.java | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java b/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java index 20293bca1..509b8d6ae 100644 --- a/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java +++ b/src/main/java/com/projectswg/holocore/resources/commands/callbacks/QaToolCmdCallback.java @@ -239,7 +239,7 @@ public class QaToolCmdCallback implements ICmdCallback { CivilWarPointIntent.broadcast(receiver, points); Log.i("GCW command: %s gave themselves %d GCW points", player.getUsername(), points); } catch (NumberFormatException e) { - SystemMessageIntent.broadcastPersonal(player, String.format("XP command: %s is not a number", pointsArg)); + SystemMessageIntent.broadcastPersonal(player, String.format("GCW command: %s is not a number", pointsArg)); } } diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index 3ca97b07b..af5aba104 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -28,6 +28,7 @@ package com.projectswg.holocore.services.faction; +import com.projectswg.common.concurrency.PswgScheduledThreadPool; import com.projectswg.common.control.Service; import com.projectswg.common.data.encodables.oob.ProsePackage; import com.projectswg.common.data.encodables.oob.StringId; @@ -36,7 +37,6 @@ import com.projectswg.common.data.encodables.tangible.PvpStatus; import com.projectswg.common.debug.Log; import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage; import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage; -import com.projectswg.common.utilities.ThreadUtilities; import com.projectswg.holocore.intents.CivilWarPointIntent; import com.projectswg.holocore.intents.chat.SystemMessageIntent; import com.projectswg.holocore.intents.combat.CreatureKilledIntent; @@ -56,11 +56,9 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneOffset; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; final class CivilWarService extends Service { @@ -70,7 +68,7 @@ final class CivilWarService extends Service { private final Map rankAbilities; private final Set playerObjects; - private final ScheduledExecutorService executor; + private final PswgScheduledThreadPool threadPool; private final DayOfWeek updateWeekDay; private final LocalTime updateTime; private final ZoneOffset updateOffset; @@ -79,8 +77,8 @@ final class CivilWarService extends Service { CivilWarService() { rankAbilities = new HashMap<>(); - playerObjects = new HashSet<>(); - executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("civil-war-service")); + playerObjects = ConcurrentHashMap.newKeySet(); + threadPool = new PswgScheduledThreadPool(1, "civil-war-service"); // Rank update time is the night between Thursday and Friday at 00:00 UTC updateWeekDay = DayOfWeek.FRIDAY; updateTime = LocalTime.MIDNIGHT; @@ -102,15 +100,22 @@ final class CivilWarService extends Service { return super.initialize(); } + @Override + public boolean terminate() { + threadPool.stop(); + + return super.terminate(); + } + private void loadRankAbilities() { String what = "rank abilities"; long startTime = StandardLog.onStartLoad(what); try (SdbLoader.SdbResultSet set = SdbLoader.load(new File("serverdata/gcw/abilities.sdb"))) { while (set.next()) { - int rank = (int) set.getInt(0); - String imperial = set.getText(1); - String rebel = set.getText(2); + int rank = (int) set.getInt("rank"); + String imperial = set.getText("imperial"); + String rebel = set.getText("rebel"); String[] abilities = new String[2]; abilities[IMPERIAL_INDEX] = imperial; @@ -131,7 +136,8 @@ final class CivilWarService extends Service { rankEpoch = nextUpdateTime(now); // Is in the future int delay = rankEpoch - nowEpoch; - executor.schedule(this::updateRanks, delay, updateUnit); + + threadPool.execute(delay, this::updateRanks); playerObjects.forEach(this::updateTimer); } From 86ed70a4da60c48a284d14976943458d519b6e95 Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Tue, 20 Feb 2018 05:08:16 +0100 Subject: [PATCH 9/9] Fixed GCW rank update timing issue --- .../holocore/intents/CivilWarPointIntent.java | 5 + .../services/faction/CivilWarService.java | 160 +++++++++--------- 2 files changed, 83 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java index 92a3ced9a..e64314f1c 100644 --- a/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java +++ b/src/main/java/com/projectswg/holocore/intents/CivilWarPointIntent.java @@ -29,16 +29,20 @@ package com.projectswg.holocore.intents; import com.projectswg.common.control.Intent; import com.projectswg.holocore.resources.objects.player.PlayerObject; +import javax.annotation.Nonnull; + public class CivilWarPointIntent extends Intent { private final PlayerObject receiver; private final int points; + @Nonnull private CivilWarPointIntent(PlayerObject receiver, int points) { this.receiver = receiver; this.points = points; } + @Nonnull public PlayerObject getReceiver() { return receiver; } @@ -47,6 +51,7 @@ public class CivilWarPointIntent extends Intent { return points; } + @Nonnull public static void broadcast(PlayerObject receiver, int points) { new CivilWarPointIntent(receiver, points).broadcast(); } diff --git a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java index af5aba104..b6492a863 100644 --- a/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java +++ b/src/main/java/com/projectswg/holocore/services/faction/CivilWarService.java @@ -59,7 +59,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; final class CivilWarService extends Service { @@ -72,7 +71,6 @@ final class CivilWarService extends Service { private final DayOfWeek updateWeekDay; private final LocalTime updateTime; private final ZoneOffset updateOffset; - private final TimeUnit updateUnit; private int rankEpoch; CivilWarService() { @@ -83,7 +81,6 @@ final class CivilWarService extends Service { updateWeekDay = DayOfWeek.FRIDAY; updateTime = LocalTime.MIDNIGHT; updateOffset = ZoneOffset.UTC; - updateUnit = TimeUnit.SECONDS; loadRankAbilities(); @@ -95,6 +92,7 @@ final class CivilWarService extends Service { @Override public boolean initialize() { + threadPool.start(); scheduleRankUpdate(); return super.initialize(); @@ -102,11 +100,73 @@ final class CivilWarService extends Service { @Override public boolean terminate() { - threadPool.stop(); + threadPool.stop(); // To stop more threads from running + threadPool.awaitTermination(1000); // Waits for them to complete executing return super.terminate(); } + int leftoverPoints(float progress, int points) { + return (int) ((progress - 100) / 100 * (float) points); + } + + boolean isRankDown(float oldProgress, float newProgress) { + return oldProgress + newProgress < 0; + } + + int nextUpdateTime(LocalDate now) { + LocalDate nextUpdateDate = now.with(updateWeekDay); + + return (int) nextUpdateDate.toEpochSecond(updateTime, updateOffset); + } + + boolean isDecayRank(int rank) { + return rank >= 7; // Lieutenant and above has decay + } + + float rankProgress(float currentProgress, float decay, int currentRank, int points) { + float progress = currentProgress - decay; + + progress += (float) points / (currentRank * 100.0f); + + return progress; + } + + boolean isFactionEligible(PvpFaction killerFaction, PvpFaction corpseFaction, boolean specialForces) { + return killerFaction != PvpFaction.NEUTRAL && corpseFaction != PvpFaction.NEUTRAL && killerFaction != corpseFaction && specialForces; + } + + byte makeMultiplier(boolean specialForces, boolean player) { + byte multiplier = 1; + + if (specialForces) { + multiplier += 1; + } + + if (player) { + multiplier += 18; + } + + return multiplier; + } + + int baseForDifficulty(CreatureDifficulty difficulty) { + switch (difficulty) { + case NORMAL: + return 5; + case ELITE: + return 10; + case BOSS: + return 15; + default: + throw new IllegalArgumentException("Unhandled CreatureDifficulty: " + difficulty); + } + } + + int pointsGranted(int base, byte multiplier) { + return base * multiplier; + } + private void loadRankAbilities() { String what = "rank abilities"; long startTime = StandardLog.onStartLoad(what); @@ -133,9 +193,8 @@ final class CivilWarService extends Service { private void scheduleRankUpdate() { LocalDate now = LocalDate.now(); int nowEpoch = (int) now.toEpochSecond(updateTime, updateOffset); - rankEpoch = nextUpdateTime(now); // Is in the future - int delay = rankEpoch - nowEpoch; - + rankEpoch = nextUpdateTime(now); // Is in the future + int delay = (rankEpoch - nowEpoch) * 1000; // Must be milliseconds threadPool.execute(delay, this::updateRanks); playerObjects.forEach(this::updateTimer); @@ -146,15 +205,11 @@ final class CivilWarService extends Service { } private void changeRank(PlayerObject playerObject, int newRank) { - if (newRank < 1 || newRank > 12) { - throw new IllegalArgumentException("Rank must be at least 1 and max 12"); - } + assert (newRank < 1 || newRank > 12); int oldRank = playerObject.getCurrentRank(); - if (oldRank < 1) { - throw new IllegalArgumentException("Unranked players cannot receive rank changes"); - } + assert (oldRank > 1); int highestRank = Math.max(oldRank, newRank); int lowestRank = Math.min(oldRank, newRank); @@ -220,13 +275,13 @@ final class CivilWarService extends Service { int demotion = playerObject.getCurrentRank() - 1; changeRank(playerObject, demotion); - + // Push their progress backwards in the new rank int leftoverPoints = leftoverPoints(newProgress, points); - + // Calculate progress within the new rank using leftover points float nextRankProgress = 100 - rankProgress(newProgress, 0, demotion, leftoverPoints); - + playerObject.setRankProgress(nextRankProgress); } @@ -244,72 +299,12 @@ final class CivilWarService extends Service { } private void updateRanks() { - playerObjects.stream() - .filter(playerObject -> playerObject.getCurrentRank() > 0) - .forEach(this::updateRank); + playerObjects.forEach(playerObject -> { + if (playerObject.getCurrentRank() > 0) + updateRank(playerObject); + }); - scheduleRankUpdate(); // Schedule next rank update - } - - int leftoverPoints(float progress, int points) { - return (int) ((progress - 100) / 100 * (float) points); - } - - boolean isRankDown(float oldProgress, float newProgress) { - return oldProgress + newProgress < 0; - } - - int nextUpdateTime(LocalDate now) { - LocalDate nextUpdateDate = now.with(updateWeekDay); - - return (int) nextUpdateDate.toEpochSecond(updateTime, updateOffset); - } - - boolean isDecayRank(int rank) { - return rank >= 7; // Lieutenant and above has decay - } - - float rankProgress(float currentProgress, float decay, int currentRank, int points) { - float progress = currentProgress - decay; - - progress += (float) points / (currentRank * 100.0f); - - return progress; - } - - boolean isFactionEligible(PvpFaction killerFaction, PvpFaction corpseFaction, boolean specialForces) { - return killerFaction != PvpFaction.NEUTRAL && corpseFaction != PvpFaction.NEUTRAL && killerFaction != corpseFaction && specialForces; - } - - byte makeMultiplier(boolean specialForces, boolean player) { - byte multiplier = 1; - - if (specialForces) { - multiplier += 1; - } - - if (player) { - multiplier += 18; - } - - return multiplier; - } - - int baseForDifficulty(CreatureDifficulty difficulty) { - switch (difficulty) { - case NORMAL: - return 5; - case ELITE: - return 10; - case BOSS: - return 15; - default: - throw new IllegalArgumentException("Unhandled CreatureDifficulty: " + difficulty); - } - } - - int pointsGranted(int base, byte multiplier) { - return base * multiplier; + scheduleRankUpdate(); // Schedule next rank update } private void grantPoints(ProsePackage prose, PlayerObject receiver, int points) { @@ -364,7 +359,8 @@ final class CivilWarService extends Service { } // PvP GCW point system message - prose = new ProsePackage("StringId", new StringId("gcw", "gcw_rank_pvp_kill_point_grant"), "DI", granted, "TT", corpseCreature.getObjectName()); + prose = new ProsePackage("StringId", new StringId("gcw", "gcw_rank_pvp_kill_point_grant"), "DI", granted, "TT", corpseCreature + .getObjectName()); // Send visual effect to killer and everyone around killerCreature.sendObserversAndSelf(new PlayClientEffectObjectMessage(effectFile, "head", killerCreature.getObjectId(), ""));