Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import net.tfminecraft.simplefactions.guild.upgrade.Upgrade;
import net.tfminecraft.simplefactions.guild.upgrade.UpgradeExpansion;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.guild.income.LedgerHistory;
import net.tfminecraft.simplefactions.loaders.BranchLoader;
import net.tfminecraft.simplefactions.loaders.RankLoader;
import net.tfminecraft.simplefactions.loaders.TitleLoader;
Expand Down Expand Up @@ -408,6 +409,13 @@ public void saveFaction(Faction f) {
gd.vehicleFeeIncome = g.getLedger().getVehicleFeeIncome();
Map<String, Double> citizenTaxes = g.getLedger().getCitizenTaxesCopy();
gd.citizenTaxes = citizenTaxes.isEmpty() ? null : citizenTaxes;
LedgerHistory history = g.getLedger().getHistory();
Map<String, Map<String, Double>> lastDay = history.getLastDayCopy();
gd.ledgerLastDay = lastDay.isEmpty() ? null : lastDay;
Map<String, Map<String, Double>> lifetime = history.getLifetimeCopy();
gd.ledgerLifetime = lifetime.isEmpty() ? null : lifetime;
Map<String, Double> depositsToday = history.getDepositsTodayCopy();
gd.depositsToday = depositsToday.isEmpty() ? null : depositsToday;
gd.company = g.getCompany() != null ? g.getCompany().serialize() : null;

// --- Bank ---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,12 @@ public class GuildData {
@SerializedName("citizen taxes")
public Map<String, Double> citizenTaxes;

@SerializedName("ledger last day")
public Map<String, Map<String, Double>> ledgerLastDay;
@SerializedName("ledger lifetime")
public Map<String, Map<String, Double>> ledgerLifetime;
@SerializedName("deposits today")
public Map<String, Double> depositsToday;

public MercenaryCompanyData company;
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ public Guild(GuildData data, Faction host) {
this.ledger.setCasinoProfit(data.casinoProfit);
}
this.ledger.setCitizenTaxes(data.citizenTaxes);
this.ledger.getHistory().load(data.ledgerLastDay, data.ledgerLifetime, data.depositsToday);
this.loanHandler = new LoanHandler(this, data.creditScore == null ? 50 : data.creditScore);
if(data.favoured != null) this.favoured = data.favoured;
if(data.repressed != null) this.repressed = data.repressed;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.tfminecraft.simplefactions.guild.income;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -53,6 +54,8 @@ public class Ledger {
// Vehicle tax and fees collected today, already in the bank. Saved like casinoProfit.
private double vehicleFeeIncome;

private final LedgerHistory history = new LedgerHistory();

// Dowsing registers this as the daily upkeep of the guild's active nodes; unset means no
// nodes plugin, so the line is 0. Settlement charges exactly what this reports.
private static ToDoubleFunction<Guild> nodeUpkeepLookup = guild -> 0.0;
Expand Down Expand Up @@ -169,6 +172,10 @@ public void setVehicleFeeIncome(double amount) {
vehicleFeeIncome = amount;
}

public LedgerHistory getHistory() {
return history;
}

public double getIncome(Cashflow cashflow) {
double amount = 0;
if(skipsMoneyMovement()) return 0.0; //bankrupt guilds dont pay or receive money, they need to get our of bankrupcy first
Expand Down Expand Up @@ -732,7 +739,7 @@ public double getWarReparationsReceived() {
* war reparations, vassal guild rollups). Used as the base for tribute and
* reparations so ledger queries cannot recurse between factions.
*/
double getInternalTaxableIncome() {
public double getInternalTaxableIncome() {
if (skipsMoneyMovement()) {
return 0.0;
}
Expand Down Expand Up @@ -777,13 +784,70 @@ public void clearDailyIncome() {
* Bankruptcy freezes a guild. A missing bank is not bankruptcy, but it also
* cannot pay or receive, so settlement must skip it instead of throwing.
*/
private boolean skipsMoneyMovement() {
boolean skipsMoneyMovement() {
if (guild.isBankrupt()) {
return true;
}
return guild.getBank() == null;
}

/**
* What each capital is about to receive per ledger view source, walked from the payer side
* as {@link #applySettlementFor} moves it. Read before settlement clears the day.
*/
public static Map<Guild, Map<LedgerHistory.Source, Map<String, Double>>> collectHistoryDay(Iterable<Guild> guilds) {
Map<Guild, Map<LedgerHistory.Source, Map<String, Double>>> out = new HashMap<>();
for (Guild payer : guilds) {
if (payer == null || payer.getLedger() == null) continue;
Ledger ledger = payer.getLedger();
Faction f = payer.getFaction();
if (f == null || ledger.skipsMoneyMovement()) continue;

if (payer.isBase()) {
ledger.citizenTaxes.forEach((name, tax) ->
recordHistory(out, payer, LedgerHistory.Source.CITIZENS, name, tax));

Faction overlord = f.getOverlord();
if (overlord != null) {
recordHistory(out, overlord.getOrCreateMainGuild(), LedgerHistory.Source.VASSALS,
f.getName(), Math.abs(ledger.getIncome(Cashflow.OVERLORD_TAX)));
}

if (f.getModifiers() != null) {
double base = ledger.getInternalTaxableIncome();
for (FactionModifier mod : f.getModifiers()) {
if (mod.getFrom() == null) continue;
if (!mod.getType().equals(FactionModifiers.TRIBUTE)) continue;
recordHistory(out, mod.getFrom().getOrCreateMainGuild(), LedgerHistory.Source.TRIBUTES,
f.getName(), base * (mod.getAmount() / 100.0));
}
}
} else {
recordHistory(out, f.getOrCreateMainGuild(), LedgerHistory.Source.GUILD_TAXES,
payer.getName(), Math.abs(ledger.getIncome(Cashflow.GUILD_PAYMENTS)));
}

TradeBreakdown trade = payer.getTradeBreakdown();
if (trade != null && trade.getTariffsByFactionMap() != null) {
for (Map.Entry<Faction, Double> entry : trade.getTariffsByFactionMap().entrySet()) {
if (entry.getKey() == null || entry.getValue() == null) continue;
recordHistory(out, entry.getKey().getOrCreateMainGuild(), LedgerHistory.Source.TARIFFS,
f.getName(), entry.getValue());
}
}
}
return out;
}

private static void recordHistory(Map<Guild, Map<LedgerHistory.Source, Map<String, Double>>> out,
Guild receiver, LedgerHistory.Source source, String name, Double amount) {
if (receiver == null || receiver.getBank() == null) return;
if (name == null || amount == null || amount <= 0) return;
out.computeIfAbsent(receiver, g -> new EnumMap<>(LedgerHistory.Source.class))
.computeIfAbsent(source, s -> new HashMap<>())
.merge(name, amount, Double::sum);
}

public void populateDailyTransfers(DailyGuildTransfers buffer) {
if(guild.isBankrupt() && guild.getLoanHandler() != null && guild.getLoanHandler().getLoansTaken() != null) {
for(Loan loan : guild.getLoanHandler().getLoansTaken()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package net.tfminecraft.simplefactions.guild.income;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.tfminecraft.simplefactions.utils.Formatter;

/**
* Last-day and lifetime totals behind the ledger view icons, keyed by payer name. This is a
* record of what already moved; settlement never reads it, so it cannot change a bank.
*/
public final class LedgerHistory {

public enum Source {
CITIZENS,
GUILD_TAXES,
VASSALS,
TRIBUTES,
TARIFFS,
DEPOSITS
}

private final Map<Source, Map<String, Double>> lastDay = new EnumMap<>(Source.class);
private final Map<Source, Map<String, Double>> lifetime = new EnumMap<>(Source.class);
// Deposits land in the bank straight away, so they are gathered here until the day ends.
private final Map<String, Double> depositsToday = new HashMap<>();

public void addDeposit(String name, double amount) {
if (name == null || name.isBlank() || amount <= 0) return;
depositsToday.merge(name, amount, Double::sum);
}

public Map<String, Double> getDepositsToday() {
return depositsToday;
}

/** Ends the day: what settled becomes the last day and is added to the lifetime totals. */
public void closeDay(Map<Source, Map<String, Double>> settled) {
lastDay.clear();
Map<Source, Map<String, Double>> day = new EnumMap<>(Source.class);
if (settled != null) day.putAll(settled);
day.put(Source.DEPOSITS, new HashMap<>(depositsToday));
depositsToday.clear();

for (Map.Entry<Source, Map<String, Double>> entry : day.entrySet()) {
if (entry.getKey() == null || entry.getValue() == null) continue;
Map<String, Double> kept = new HashMap<>();
for (Map.Entry<String, Double> amount : entry.getValue().entrySet()) {
String name = amount.getKey();
Double value = amount.getValue();
if (name == null || name.isBlank() || value == null || value <= 0) continue;
kept.merge(name, value, Double::sum);
lifetime.computeIfAbsent(entry.getKey(), s -> new HashMap<>())
.merge(name, value, (a, b) -> Formatter.formatDouble(a + b));
}
if (!kept.isEmpty()) lastDay.put(entry.getKey(), kept);
}
}

public Map<String, Double> getLastDay(Source source) {
return lastDay.getOrDefault(source, Map.of());
}

public Map<String, Double> getLifetime(Source source) {
return lifetime.getOrDefault(source, Map.of());
}

public static double total(Map<String, Double> amounts) {
double total = 0;
for (double value : amounts.values()) total += value;
return total;
}

public static List<Map.Entry<String, Double>> descending(Map<String, Double> amounts) {
return amounts.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.toList();
}

// --- Save/load ---

public Map<String, Map<String, Double>> getLastDayCopy() {
return copyOut(lastDay);
}

public Map<String, Map<String, Double>> getLifetimeCopy() {
return copyOut(lifetime);
}

public Map<String, Double> getDepositsTodayCopy() {
return new HashMap<>(depositsToday);
}

public void load(Map<String, Map<String, Double>> lastDayData,
Map<String, Map<String, Double>> lifetimeData,
Map<String, Double> depositsTodayData) {
copyIn(lastDayData, lastDay);
copyIn(lifetimeData, lifetime);
depositsToday.clear();
if (depositsTodayData != null) {
depositsTodayData.forEach((name, value) -> {
if (value != null) addDeposit(name, value);
});
}
}

private static Map<String, Map<String, Double>> copyOut(Map<Source, Map<String, Double>> from) {
Map<String, Map<String, Double>> out = new HashMap<>();
from.forEach((source, amounts) -> {
if (!amounts.isEmpty()) out.put(source.name(), new HashMap<>(amounts));
});
return out;
}

private static void copyIn(Map<String, Map<String, Double>> from, Map<Source, Map<String, Double>> into) {
into.clear();
if (from == null) return;
for (Map.Entry<String, Map<String, Double>> entry : from.entrySet()) {
Source source;
try {
source = Source.valueOf(entry.getKey());
} catch (IllegalArgumentException | NullPointerException e) {
continue;
}
if (entry.getValue() == null) continue;
Map<String, Double> amounts = new HashMap<>();
entry.getValue().forEach((name, value) -> {
if (name != null && !name.isBlank() && value != null && value > 0) amounts.put(name, value);
});
if (!amounts.isEmpty()) into.put(source, amounts);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
pouch.change(amount*-1);
g.getBank().deposit(amount);
g.getLedger().getHistory().addDeposit(p.getName(), amount);
p.sendMessage("搂e============搂6[Bank Report]搂e==============");
p.sendMessage(StringFormatter.formatHex("#6ab05aDeposited: #b39122"+amount+"#dbaf1dd"));
p.sendMessage(StringFormatter.formatHex("#3ce8c9New Guild Balance: #b39122"+b.getWealth()+"#dbaf1dd"));
Expand Down Expand Up @@ -1129,6 +1130,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
pouch.change(amount*-1);
f.getBank().deposit(amount);
f.getOrCreateMainGuild().getLedger().getHistory().addDeposit(p.getName(), amount);
p.sendMessage("搂e============搂6[Bank Report]搂e==============");
p.sendMessage(StringFormatter.formatHex("#6ab05aDeposited: #b39122"+amount+"#dbaf1dd"));
p.sendMessage(StringFormatter.formatHex("#3ce8c9New Faction Balance: #b39122"+b.getWealth()+"#dbaf1dd"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import net.tfminecraft.simplefactions.diplomacy.Relation;
import net.tfminecraft.simplefactions.diplomacy.RelationType;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.guild.income.Ledger;
import net.tfminecraft.simplefactions.guild.income.LedgerHistory;
import net.tfminecraft.simplefactions.guild.loans.Loan;
import net.tfminecraft.simplefactions.loaders.RankLoader;
import net.tfminecraft.simplefactions.loaders.RelationLoader;
Expand Down Expand Up @@ -506,6 +508,10 @@ public void settleIncome() {
runDailyStep("mercenary accrual", () ->
net.tfminecraft.simplefactions.mercenary.contract.ContractAccrualService.accrueDailyAndPush());

// Read the ledger view history before Phase 1 clears the day's citizen taxes.
Map<Guild, Map<LedgerHistory.Source, Map<String, Double>>> history = new HashMap<>();
runDailyStep("ledger history", () -> history.putAll(Ledger.collectHistoryDay(getAllGuilds())));

// Phase 1: collect transfers & external deltas
for (Guild g : getAllGuilds()) {
if (g == null || g.getLedger() == null) continue;
Expand Down Expand Up @@ -552,6 +558,11 @@ public void settleIncome() {
});
}

for (Guild g : getAllGuilds()) {
if (g == null || g.getLedger() == null) continue;
runDailyStep("ledger history " + g.getId(), () -> g.getLedger().getHistory().closeDay(history.get(g)));
}

PostSettlementPayouts.apply(
buffer,
DenarEconomyPlayerBank.INSTANCE,
Expand Down
Loading