From 35c38ce4b895aa861db43114444260e157833338 Mon Sep 17 00:00:00 2001 From: Magnus Ulf Date: Mon, 15 Jul 2019 07:55:12 +0200 Subject: [PATCH] First try at a tax system This hasn't been tested. This is a tax system where Factions can tax their players. Later it should be extended do that servers can tax factions based on how much land they have claimed. We start of with this simpler system to make sure that can be tested in real-life environments before we go ahead with the more full fledged system. --- src/com/massivecraft/factions/Factions.java | 4 +- .../factions/cmd/CmdFactions.java | 1 + .../factions/cmd/CmdFactionsMoneyconvert.java | 2 +- .../factions/cmd/CmdFactionsTax.java | 11 + .../factions/cmd/CmdFactionsTaxInfo.java | 49 ++++ .../massivecraft/factions/entity/Faction.java | 66 ++++- .../factions/entity/FactionColl.java | 42 +--- .../massivecraft/factions/entity/MConf.java | 37 ++- .../massivecraft/factions/entity/MFlag.java | 4 + .../massivecraft/factions/entity/MPerm.java | 6 +- .../factions/integration/Econ.java | 14 +- .../factions/task/TaskEconLandReward.java | 39 --- .../massivecraft/factions/task/TaskTax.java | 234 ++++++++++++++++++ 13 files changed, 416 insertions(+), 93 deletions(-) create mode 100644 src/com/massivecraft/factions/cmd/CmdFactionsTax.java create mode 100644 src/com/massivecraft/factions/cmd/CmdFactionsTaxInfo.java delete mode 100644 src/com/massivecraft/factions/task/TaskEconLandReward.java create mode 100644 src/com/massivecraft/factions/task/TaskTax.java diff --git a/src/com/massivecraft/factions/Factions.java b/src/com/massivecraft/factions/Factions.java index 9990ac71..9cee196d 100644 --- a/src/com/massivecraft/factions/Factions.java +++ b/src/com/massivecraft/factions/Factions.java @@ -71,7 +71,7 @@ import com.massivecraft.factions.integration.placeholderapi.IntegrationPlacehold import com.massivecraft.factions.integration.venturechat.IntegrationVentureChat; import com.massivecraft.factions.integration.worldguard.IntegrationWorldGuard; import com.massivecraft.factions.mixin.PowerMixin; -import com.massivecraft.factions.task.TaskEconLandReward; +import com.massivecraft.factions.task.TaskTax; import com.massivecraft.factions.task.TaskFlagPermCreate; import com.massivecraft.factions.task.TaskPlayerPowerUpdate; import com.massivecraft.massivecore.MassivePlugin; @@ -203,7 +203,7 @@ public class Factions extends MassivePlugin public List> getClassesActiveTasks() { return MUtil.list( - TaskEconLandReward.class, + TaskTax.class, TaskFlagPermCreate.class, TaskPlayerPowerUpdate.class ); diff --git a/src/com/massivecraft/factions/cmd/CmdFactions.java b/src/com/massivecraft/factions/cmd/CmdFactions.java index eff73a8d..94eb4bbb 100644 --- a/src/com/massivecraft/factions/cmd/CmdFactions.java +++ b/src/com/massivecraft/factions/cmd/CmdFactions.java @@ -51,6 +51,7 @@ public class CmdFactions extends FactionsCommand public CmdFactionsRelationOld cmdFactionsRelationOldTruce = new CmdFactionsRelationOld("truce"); public CmdFactionsRelationOld cmdFactionsRelationOldNeutral = new CmdFactionsRelationOld("neutral"); public CmdFactionsRelationOld cmdFactionsRelationOldEnemy = new CmdFactionsRelationOld("enemy"); + public CmdFactionsTax cmdFactionsTax = new CmdFactionsTax(); public CmdFactionsPerm cmdFactionsPerm = new CmdFactionsPerm(); public CmdFactionsFlag cmdFactionsFlag = new CmdFactionsFlag(); public CmdFactionsFly cmdFactionsFly = new CmdFactionsFly(); diff --git a/src/com/massivecraft/factions/cmd/CmdFactionsMoneyconvert.java b/src/com/massivecraft/factions/cmd/CmdFactionsMoneyconvert.java index dfb7f050..0abdc5fd 100644 --- a/src/com/massivecraft/factions/cmd/CmdFactionsMoneyconvert.java +++ b/src/com/massivecraft/factions/cmd/CmdFactionsMoneyconvert.java @@ -51,7 +51,7 @@ public class CmdFactionsMoneyconvert extends FactionsCommand " This command allows to convert to the new system where the money of a Faction" + " is stored within the Factions plugin. Then all economy plugins can be used with Factions."); } - + ConfirmationUtil.tryConfirm(this); MConf.get().useNewMoneySystem = true; diff --git a/src/com/massivecraft/factions/cmd/CmdFactionsTax.java b/src/com/massivecraft/factions/cmd/CmdFactionsTax.java new file mode 100644 index 00000000..78dfd1ae --- /dev/null +++ b/src/com/massivecraft/factions/cmd/CmdFactionsTax.java @@ -0,0 +1,11 @@ +package com.massivecraft.factions.cmd; + +public class CmdFactionsTax extends FactionsCommand +{ + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + + +} diff --git a/src/com/massivecraft/factions/cmd/CmdFactionsTaxInfo.java b/src/com/massivecraft/factions/cmd/CmdFactionsTaxInfo.java new file mode 100644 index 00000000..84d5718c --- /dev/null +++ b/src/com/massivecraft/factions/cmd/CmdFactionsTaxInfo.java @@ -0,0 +1,49 @@ +package com.massivecraft.factions.cmd; + +import com.massivecraft.factions.entity.MConf; +import com.massivecraft.factions.task.TaskTax; +import com.massivecraft.massivecore.MassiveException; +import com.massivecraft.massivecore.money.Money; +import com.massivecraft.massivecore.util.TimeDiffUtil; +import com.massivecraft.massivecore.util.TimeUnit; +import com.massivecraft.massivecore.util.Txt; + +import java.util.LinkedHashMap; + +public class CmdFactionsTaxInfo extends FactionsCommand +{ + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + public CmdFactionsTaxInfo() + { + } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public void perform() throws MassiveException + { + if (!TaskTax.get().areTaxesEnabled()) + { + throw new MassiveException().addMsg("Tax is not enabled on this server."); + } + + LinkedHashMap timeUnitcounts = TimeDiffUtil.limit(TimeDiffUtil.unitcounts(MConf.get().taxTaskPeriodMillis, TimeUnit.getAll()), 3); + String periodString = TimeDiffUtil.formatedVerboose(timeUnitcounts); + + msg("Taxation Period: every %s.", periodString); + + long nextTaxationTime = MConf.get().taxTaskPeriodMillis + MConf.get().taxTaskPeriodMillis; + + msg("Next Taxation: %s", Txt.getTimeDeltaDescriptionRelNow(nextTaxationTime)); + + String minTax = Money.format(MConf.get().taxPlayerMinimum); + String maxTax = Money.format(MConf.get().taxPlayerMaximum); + msg("Taxes for players can be set between %s and %s.", minTax, maxTax); + } + +} diff --git a/src/com/massivecraft/factions/entity/Faction.java b/src/com/massivecraft/factions/entity/Faction.java index a2910d5e..dd1bf76a 100644 --- a/src/com/massivecraft/factions/entity/Faction.java +++ b/src/com/massivecraft/factions/entity/Faction.java @@ -11,6 +11,8 @@ import com.massivecraft.factions.predicate.PredicateCommandSenderFaction; import com.massivecraft.factions.predicate.PredicateMPlayerRank; import com.massivecraft.factions.util.MiscUtil; import com.massivecraft.factions.util.RelationUtil; +import com.massivecraft.massivecore.Couple; +import com.massivecraft.massivecore.Identified; import com.massivecraft.massivecore.collections.MassiveList; import com.massivecraft.massivecore.collections.MassiveMap; import com.massivecraft.massivecore.collections.MassiveMapDef; @@ -38,6 +40,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -158,8 +161,13 @@ public class Faction extends Entity implements FactionsParticipator, MP // Null means default. private MassiveMapDef flags = new MassiveMapDef<>(); - private Map> perms = this.createNewPermMap(); + + // What is the base tax on members of the faction? + // Specific taxes on ranks or players. + public static String IDENTIFIER_TAX_BASE = "base"; + + private Map tax = new MassiveMap<>(); // -------------------------------------------- // // FIELD: id @@ -944,6 +952,62 @@ public class Faction extends Entity implements FactionsParticipator, MP return MPermColl.get().getFixed(permId) != null; } + // -------------------------------------------- // + // FIELD: tax + // -------------------------------------------- // + + // RAW + public Map getTax() { return this.tax; } + + // FINER GET + public Double getTaxFor(String id) + { + if (id == null) throw new NullPointerException("id"); + return this.tax.get(id); + } + + public Double getTaxFor(Identified identified) + { + if (identified == null) throw new NullPointerException("identified"); + return this.getTaxFor(identified.getId()); + } + + public double getTaxForPlayer(MPlayer mplayer) + { + return getTaxAndReasonForPlayer(mplayer).map(Entry::getValue).orElse(0D); + } + + public Optional> getTaxAndReasonForPlayer(MPlayer mplayer) + { + if (mplayer == null) throw new NullPointerException("mplayer"); + + if (mplayer.getFaction() != this) throw new IllegalArgumentException("Player " + mplayer.getId() + " not in " + this.getId()); + + Double ret = null; + + ret = this.getTaxFor(mplayer); + if (ret != null) return Optional.of(new Couple<>(mplayer.getId(), ret)); + + ret = this.getTaxFor(mplayer.getRank()); + if (ret != null) return Optional.of(new Couple<>(mplayer.getRank().getId(), ret)); + + ret = this.getTaxFor(IDENTIFIER_TAX_BASE); + if (ret != null) return Optional.of(new Couple<>(IDENTIFIER_TAX_BASE, ret)); + + return Optional.empty(); + } + + // FINER SET + public Double setTaxFor(String id, Double value) + { + return this.tax.put(id, value); + } + + public Double setTaxFor(Identified identified, Double value) + { + return this.setTaxFor(identified.getId(), value); + } + // -------------------------------------------- // // OVERRIDE: RelationParticipator // -------------------------------------------- // diff --git a/src/com/massivecraft/factions/entity/FactionColl.java b/src/com/massivecraft/factions/entity/FactionColl.java index 840b69b1..cced62aa 100644 --- a/src/com/massivecraft/factions/entity/FactionColl.java +++ b/src/com/massivecraft/factions/entity/FactionColl.java @@ -2,7 +2,6 @@ package com.massivecraft.factions.entity; import com.massivecraft.factions.Factions; import com.massivecraft.factions.Rel; -import com.massivecraft.factions.integration.Econ; import com.massivecraft.factions.util.MiscUtil; import com.massivecraft.massivecore.collections.MassiveMap; import com.massivecraft.massivecore.store.Coll; @@ -165,48 +164,9 @@ public class FactionColl extends Coll } // -------------------------------------------- // - // LAND REWARD + // NAME // -------------------------------------------- // - public void econLandRewardRoutine() - { - // If econ is enabled ... - if (!Econ.isEnabled()) return; - - // ... and the land reward is non zero ... - double econLandReward = MConf.get().econLandReward; - if (econLandReward == 0.0) return; - - // ... log initiation ... - Factions.get().log("Running econLandRewardRoutine..."); - MFlag flagPeaceful = MFlag.getFlagPeaceful(); - - // ... and for each faction ... - for (Faction faction : this.getAll()) - { - // ... get the land count ... - int landCount = faction.getLandCount(); - - // ... and if the faction isn't peaceful and has land ... - if (landCount == 0 || faction.getFlag(flagPeaceful)) continue; - - // ... get the faction's members ... - List players = faction.getMPlayers(); - - // ... calculate the reward ... - int playerCount = players.size(); - double reward = econLandReward * landCount / playerCount; - - // ... and grant the reward. - String description = String.format("own %s faction land divided among %s members", landCount, playerCount); - for (MPlayer player : players) - { - Econ.modifyMoney(player, reward, description); - } - - } - } - @Override public Faction getByName(String name) { diff --git a/src/com/massivecraft/factions/entity/MConf.java b/src/com/massivecraft/factions/entity/MConf.java index d93d3cc3..b96eea5d 100644 --- a/src/com/massivecraft/factions/entity/MConf.java +++ b/src/com/massivecraft/factions/entity/MConf.java @@ -30,7 +30,7 @@ public class MConf extends Entity // -------------------------------------------- // // META // -------------------------------------------- // - + protected static transient MConf i; public static MConf get() { return i; } @@ -487,7 +487,36 @@ public class MConf extends Entity public boolean logFactionLeave = true; public boolean logLandClaims = true; public boolean logMoneyTransactions = true; - + + // -------------------------------------------- // + // TAX + // -------------------------------------------- // + + // Should the tax system be enabled? + public boolean taxEnabled = false; + + // How much can you tax a player? + public double taxPlayerMinimum = -10; + public double taxPlayerMaximum = 10; + + // How much should Factions be taxed? + //public double taxUpkeepBase = 0; + //public double taxUpkeepPerChunk = 1; + + // When is a player inactive? + public int taxInactiveDays = 3; + + // When the last run time was (in unix time) + // 0 means never + public long taxTaskLastMillis = 0; + + // Tax run when? + // 0 means at midnight UTC it can be offset by a certain millis + public long taxTaskInvocationOffsetMillis = 0; + + // How often should the task be run? + public long taxTaskPeriodMillis = TimeUnit.MILLIS_PER_DAY; + // -------------------------------------------- // // ENUMERATIONS // -------------------------------------------- // @@ -574,10 +603,6 @@ public class MConf extends Entity // This requires that you have the external plugin called "Vault" installed. public boolean econEnabled = true; - // A money reward per chunk. This reward is divided among the players in the faction. - // You set the time inbetween each reward almost at the top of this config file. (taskEconLandRewardMinutes) - public double econLandReward = 0.00; - // When paying a cost you may specify an account that should receive the money here. // Per default "" the money is just destroyed. public String econUniverseAccount = ""; diff --git a/src/com/massivecraft/factions/entity/MFlag.java b/src/com/massivecraft/factions/entity/MFlag.java index 78b4ead2..4e631c88 100644 --- a/src/com/massivecraft/factions/entity/MFlag.java +++ b/src/com/massivecraft/factions/entity/MFlag.java @@ -36,6 +36,7 @@ public class MFlag extends Entity implements Prioritized, Registerable, N public final static transient String ID_PEACEFUL = "peaceful"; public final static transient String ID_INFPOWER = "infpower"; public final static transient String ID_FLY = "fly"; + public final static transient String ID_TAXKICK = "taxkick"; public final static transient int PRIORITY_OPEN = 1_000; public final static transient int PRIORITY_MONSTERS = 2_000; @@ -53,6 +54,7 @@ public class MFlag extends Entity implements Prioritized, Registerable, N public final static transient int PRIORITY_PEACEFUL = 14_000; public final static transient int PRIORITY_INFPOWER = 15_000; public final static transient int PRIORITY_FLY = 16_000; + public final static transient int PRIORITY_TAXKICK = 17_000; // -------------------------------------------- // // META: CORE @@ -93,6 +95,7 @@ public class MFlag extends Entity implements Prioritized, Registerable, N getFlagPeaceful(); getFlagInfpower(); getFlagFly(); + getFlagTaxKick(); } public static MFlag getFlagOpen() { return getCreative(PRIORITY_OPEN, ID_OPEN, ID_OPEN, "Can the faction be joined without an invite?", "Anyone can join. No invite required.", "An invite is required to join.", false, true, true); } @@ -111,6 +114,7 @@ public class MFlag extends Entity implements Prioritized, Registerable, N public static MFlag getFlagPeaceful() { return getCreative(PRIORITY_PEACEFUL, ID_PEACEFUL, ID_PEACEFUL, "Is the faction in truce with everyone?", "The faction is in truce with everyone.", "The faction relations work as usual.", false, false, true); } public static MFlag getFlagInfpower() { return getCreative(PRIORITY_INFPOWER, ID_INFPOWER, ID_INFPOWER, "Does the faction have infinite power?", "The faction has infinite power.", "The faction power works as usual.", false, false, true); } public static MFlag getFlagFly() { return getCreative(PRIORITY_FLY, ID_FLY, ID_FLY, "Is flying allowed for members in faction territory?", "Members can fly in faction territory.", "Members can not fly in faction territory.", false, false, true); } + public static MFlag getFlagTaxKick() { return getCreative(PRIORITY_TAXKICK, ID_TAXKICK, ID_TAXKICK, "Are players kicked for not paying taxes?", "Members are kicked for not paying taxes.", "Members are not kicked for not paying taxes.", false, true, true); } public static MFlag getCreative(int priority, String id, String name, String desc, String descYes, String descNo, boolean standard, boolean editable, boolean visible) { diff --git a/src/com/massivecraft/factions/entity/MPerm.java b/src/com/massivecraft/factions/entity/MPerm.java index d8193e74..b8f361cb 100644 --- a/src/com/massivecraft/factions/entity/MPerm.java +++ b/src/com/massivecraft/factions/entity/MPerm.java @@ -56,9 +56,10 @@ public class MPerm extends Entity implements Prioritized, Registerable, N public final static transient String ID_WITHDRAW = "withdraw"; public final static transient String ID_TERRITORY = "territory"; public final static transient String ID_ACCESS = "access"; - public final static transient String ID_VOTE = "VOTE"; + public final static transient String ID_VOTE = "VOTE"; // Why is this capitalised? Can that be easily changed? public final static transient String ID_CREATEVOTE = "createvote"; public final static transient String ID_CLAIMNEAR = "claimnear"; + public final static transient String ID_TAX = "tax"; public final static transient String ID_REL = "rel"; public final static transient String ID_DISBAND = "disband"; public final static transient String ID_FLAGS = "flags"; @@ -87,6 +88,7 @@ public class MPerm extends Entity implements Prioritized, Registerable, N public final static transient int PRIORITY_VOTE = 18200; public final static transient int PRIORITY_CREATEVOTE = 18600; public final static transient int PRIORITY_CLAIMNEAR = 19000; + public final static transient int PRIORITY_TAX = 19500; public final static transient int PRIORITY_REL = 20000; public final static transient int PRIORITY_DISBAND = 21000; public final static transient int PRIORITY_FLAGS = 22000; @@ -139,6 +141,7 @@ public class MPerm extends Entity implements Prioritized, Registerable, N getPermVote(); getPermCreateVote(); getPermClaimnear(); + getPermTax(); getPermRel(); getPermDisband(); getPermFlags(); @@ -168,6 +171,7 @@ public class MPerm extends Entity implements Prioritized, Registerable, N public static MPerm getPermVote() { return getCreative(PRIORITY_VOTE, ID_VOTE, ID_VOTE, "vote", MUtil.set("LEADER", "OFFICER", "MEMBER", "RECRUIT"), false, true, true); } public static MPerm getPermCreateVote() { return getCreative(PRIORITY_CREATEVOTE, ID_CREATEVOTE, ID_CREATEVOTE, "manage votes", MUtil.set("LEADER", "OFFICER"), false, true, true); } public static MPerm getPermClaimnear() { return getCreative(PRIORITY_CLAIMNEAR, ID_CLAIMNEAR, ID_CLAIMNEAR, "claim nearby", MUtil.set("LEADER", "OFFICER", "MEMBER", "RECRUIT", "ALLY"), false, false, false); } // non editable, non visible. + public static MPerm getPermTax() { return getCreative(PRIORITY_TAX, ID_TAX, ID_TAX, "set taxes", MUtil.set("LEADER"), false, true, true); } public static MPerm getPermRel() { return getCreative(PRIORITY_REL, ID_REL, ID_REL, "change relations", MUtil.set("LEADER", "OFFICER"), false, true, true); } public static MPerm getPermDisband() { return getCreative(PRIORITY_DISBAND, ID_DISBAND, ID_DISBAND, "disband the faction", MUtil.set("LEADER"), false, true, true); } public static MPerm getPermFlags() { return getCreative(PRIORITY_FLAGS, ID_FLAGS, ID_FLAGS, "manage flags", MUtil.set("LEADER"), false, true, true); } diff --git a/src/com/massivecraft/factions/integration/Econ.java b/src/com/massivecraft/factions/integration/Econ.java index 1b9206e4..2001a4bd 100644 --- a/src/com/massivecraft/factions/integration/Econ.java +++ b/src/com/massivecraft/factions/integration/Econ.java @@ -215,6 +215,11 @@ public class Econ } } + public static boolean hasAtLeast(EconomyParticipator ep, double delta) + { + return hasAtLeast(ep, delta, null); + } + public static boolean hasAtLeast(EconomyParticipator ep, double delta, String toDoThis) { if ( ! isEnabled()) return true; @@ -287,6 +292,11 @@ public class Econ } public static boolean moveMoney(EconomyParticipator from, EconomyParticipator to, EconomyParticipator by, double amount) + { + return moveMoney(from, to, by, amount, "Factions"); + } + + public static boolean moveMoney(EconomyParticipator from, EconomyParticipator to, EconomyParticipator by, double amount, String category) { final boolean fromFaction = from instanceof Faction; final boolean toFaction = to instanceof Faction; @@ -294,13 +304,13 @@ public class Econ // If the old money system is used just do that if (!MConf.get().useNewMoneySystem) { - return Money.move(from, to, by, amount, "Factions"); + return Money.move(from, to, by, amount, category); } // Or if neither to or from is a faction if (!fromFaction && !toFaction) { - return Money.move(from, to, by, amount, "Factions"); + return Money.move(from, to, by, amount, category); } // Handle from diff --git a/src/com/massivecraft/factions/task/TaskEconLandReward.java b/src/com/massivecraft/factions/task/TaskEconLandReward.java deleted file mode 100644 index eafcca1f..00000000 --- a/src/com/massivecraft/factions/task/TaskEconLandReward.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.massivecraft.factions.task; - -import com.massivecraft.factions.entity.FactionColl; -import com.massivecraft.factions.entity.MConf; -import com.massivecraft.massivecore.MassiveCore; -import com.massivecraft.massivecore.ModuloRepeatTask; -import com.massivecraft.massivecore.util.TimeUnit; - -public class TaskEconLandReward extends ModuloRepeatTask -{ - // -------------------------------------------- // - // INSTANCE - // -------------------------------------------- // - - private static TaskEconLandReward i = new TaskEconLandReward(); - public static TaskEconLandReward get() { return i; } - - // -------------------------------------------- // - // OVERRIDE - // -------------------------------------------- // - - @Override - public long getDelayMillis() - { - // The interval is determined by the MConf rather than being set with setDelayMillis. - return (long) (MConf.get().taskEconLandRewardMinutes * TimeUnit.MILLIS_PER_MINUTE); - } - - @Override - public void invoke(long now) - { - // If this is the task server ... - if (!MassiveCore.isTaskServer()) return; - - // ... process the econ land rewards. - FactionColl.get().econLandRewardRoutine(); - } - -} diff --git a/src/com/massivecraft/factions/task/TaskTax.java b/src/com/massivecraft/factions/task/TaskTax.java new file mode 100644 index 00000000..af0f7755 --- /dev/null +++ b/src/com/massivecraft/factions/task/TaskTax.java @@ -0,0 +1,234 @@ +package com.massivecraft.factions.task; + +import com.massivecraft.factions.Factions; +import com.massivecraft.factions.entity.Faction; +import com.massivecraft.factions.entity.FactionColl; +import com.massivecraft.factions.entity.MConf; +import com.massivecraft.factions.entity.MFlag; +import com.massivecraft.factions.entity.MPlayer; +import com.massivecraft.factions.entity.MPlayerColl; +import com.massivecraft.factions.event.EventFactionsMembershipChange; +import com.massivecraft.factions.event.EventFactionsMembershipChange.MembershipChangeReason; +import com.massivecraft.factions.integration.Econ; +import com.massivecraft.massivecore.Couple; +import com.massivecraft.massivecore.Engine; +import com.massivecraft.massivecore.MassiveCore; +import com.massivecraft.massivecore.collections.MassiveMap; +import com.massivecraft.massivecore.mixin.MixinMessage; +import com.massivecraft.massivecore.money.Money; +import com.massivecraft.massivecore.util.IdUtil; +import com.massivecraft.massivecore.util.TimeUnit; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class TaskTax extends Engine +{ + // -------------------------------------------- // + // INSTANCE + // -------------------------------------------- // + + private static TaskTax i = new TaskTax(); + public static TaskTax get() { return i; } + public TaskTax() + { + // Just check once a minute + this.setPeriod(60L * 20L); + } + + // -------------------------------------------- // + // OVERRIDE: RUNNABLE + // -------------------------------------------- // + + @Override + public void run() + { + final long now = System.currentTimeMillis(); + MixinMessage.get().msgAll("Running TaskTax"); + // If this is the right server ... + if ( ! MassiveCore.isTaskServer()) return; + MixinMessage.get().msgAll("Task server"); + // ... and taxing is enabled ... + if ( ! this.areTaxesEnabled()) return; + MixinMessage.get().msgAll("Taxes enabled"); + // ... and the current invocation ... + final long currentInvocation = getInvocationFromMillis(now); + MixinMessage.get().msgAll("currentInvocation: %d", currentInvocation); + // ... and the last invocation ... + final long lastInvocation = getInvocationFromMillis(MConf.get().taxTaskLastMillis); + MixinMessage.get().msgAll("lastInvocation: %d", lastInvocation); + // ... are different ... + if (currentInvocation == lastInvocation) return; + + // ... then it's time to invoke. + invoke(now); + } + + public boolean areTaxesEnabled() + { + return Econ.isEnabled() && MConf.get().taxEnabled; + } + + public void invoke(long now) + { + taxPlayers(now); + taxFactions(now); + } + + public void taxPlayers(long now) + { + MixinMessage.get().msgAll("Taxation of players starting."); + long start = System.nanoTime(); + + // Tax players and track how many players are taxed and how much + Map> faction2tax = new MassiveMap<>(); + + List> taxes = MPlayerColl.get().getAll().stream() + .filter(mp -> shouldBeTaxed(now, mp)) + .map(mp -> new Couple<>(mp, getTax(mp))) + .filter(e -> e.getValue() != 0D) + .collect(Collectors.toList()); + + String debug = taxes.stream() + .map(c -> c.getFirst().getName() + ": " + c.getSecond()) + .reduce((s1, s2) -> s1 + "\n" + s2).orElse(""); + MixinMessage.get().messageAll(debug); + + // Pay the highest taxes first. + // That way taxes are collected before wages are given. + Comparator> comparator = Comparator.comparingDouble(Couple::getSecond); + comparator = comparator.reversed(); + taxes.sort(comparator); + + for (Couple couple : taxes) + { + double tax = doTaxPlayer(couple); + if (tax == 0D) continue; + + // Log data + Faction faction = couple.getFirst().getFaction(); + Couple newCouple = new Couple<>(1, tax); + faction2tax.merge(faction, newCouple, + (c1, c2) -> new Couple<>(c1.getFirst() + c2.getFirst(), c1.getSecond() + c2.getSecond())); + } + + // Inform factions + faction2tax.forEach(this::informFactionOfPlayerTax); + + // Infrom of taxation complete + int count = faction2tax.values().stream().mapToInt(Couple::getFirst).sum(); + MixinMessage.get().msgAll("Taxation of players complete. %d players were taxed.", count); + + long end = System.nanoTime(); + double elapsedSeconds = (end - start) / 1000_000_000D; + MixinMessage.get().msgAll("Took %.2f seconds.", elapsedSeconds); + } + + private double getTax(MPlayer mplayer) + { + return mplayer.getFaction().getTaxForPlayer(mplayer); + } + + private double doTaxPlayer(Couple couple) + { + return doTaxPlayer(couple.getFirst(), couple.getSecond()); + } + + private double doTaxPlayer(MPlayer mplayer, double tax) + { + Faction faction = mplayer.getFaction(); + boolean success = Econ.moveMoney(mplayer, faction, null, tax, "Factions Tax"); + if (success) + { + // Inform player + if (mplayer.isOnline()) + { + if (tax > 0) mplayer.msg("You were just taxed %s by your faction.", Money.format(tax)); // Tax + else mplayer.msg("You were just paid %s by your faction.", Money.format(-tax)); // Salary + } + + return tax; + } + else if (tax > 0) // If a tax + { + faction.msg("%s couldn't afford tax!", mplayer.describeTo(faction)); + boolean kicked = tryKickPlayer(mplayer); + if (!kicked) faction.msg("%s could not afford tax.", mplayer.describeTo(faction)); + return 0D; + } + else // If a salary + { + faction.msg("Your faction couldn't afford to pay %s to %s.", Money.format(-tax), mplayer.describeTo(faction)); + return 0D; + } + } + + private boolean shouldBeTaxed(long now, MPlayer mplayer) + { + // Must have faction + if ( ! mplayer.hasFaction()) return false; + + // Must have been online recently + long offlinePeriod; + if (mplayer.isOnline()) offlinePeriod = 0; + else offlinePeriod = now - mplayer.getLastActivityMillis(); + + int inactiveDays = MConf.get().taxInactiveDays; + if (inactiveDays > 0 && offlinePeriod > inactiveDays * TimeUnit.MILLIS_PER_DAY) return false; + + return true; + } + + private boolean tryKickPlayer(MPlayer mplayer) + { + Faction faction = mplayer.getFaction(); + if (mplayer.getRank().isLeader()) return false; + if ( ! faction.getFlag(MFlag.getFlagTaxKick())) return false; + + EventFactionsMembershipChange event = new EventFactionsMembershipChange(null, mplayer, FactionColl.get().getNone(), MembershipChangeReason.KICK); + event.run(); + if (event.isCancelled()) return false; + + faction.msg("%s could not afford tax and was kicked from your faction.", mplayer.describeTo(faction)); + + if (MConf.get().logFactionKick) + { + MPlayer console = MPlayer.get(IdUtil.CONSOLE_ID); + Factions.get().log("%s could not afford tax and was kicked from %s.", mplayer.describeTo(console), faction.describeTo(console)); + } + + // Apply + faction.uninvite(mplayer); + mplayer.resetFactionData(); + + return true; + } + + private void informFactionOfPlayerTax(Faction faction, Couple couple) + { + faction.msg("A total of %d players in your faction were taxed for a total of %s.", couple.getFirst(), Money.format(couple.getSecond())); + } + + public void taxFactions(long now) + { + // TODO + String msg = "For the time being factions themselves cannot be taxed. This feature will be added at a later date."; + MixinMessage.get().msgOne(IdUtil.CONSOLE_ID, msg); + } + + // -------------------------------------------- // + // TASK MILLIS AND INVOCATION + // -------------------------------------------- // + // The invocation is the amount of periods from UNIX time to now. + // It will increment by one when a period has passed. + + // Remember to check isDisabled first! + // Here we accept millis from inside the period by rounding down. + private static long getInvocationFromMillis(long millis) + { + return (millis - MConf.get().taxTaskInvocationOffsetMillis) / MConf.get().taxTaskPeriodMillis; + } + +}