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; + } + +}