Add market feedback pricing
This commit is contained in:
@@ -39,6 +39,7 @@ public final class MarketListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.marketService().buy(player, clicked.getType(), purchaseAmount(event.getClick()));
|
plugin.marketService().buy(player, clicked.getType(), purchaseAmount(event.getClick()));
|
||||||
|
plugin.marketService().open(player);
|
||||||
} catch (SQLException exception) {
|
} catch (SQLException exception) {
|
||||||
plugin.getLogger().warning("Market click failed: " + exception.getMessage());
|
plugin.getLogger().warning("Market click failed: " + exception.getMessage());
|
||||||
player.sendMessage(plugin.messages().text("&cMarket ledger unavailable."));
|
player.sendMessage(plugin.messages().text("&cMarket ledger unavailable."));
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ public final class GovernmentService {
|
|||||||
repository.addTreasury(amount);
|
repository.addTreasury(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void collectMarketRevenue(long amount) throws SQLException {
|
||||||
|
repository.addTreasury(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean spendMarketTransaction(long amount) throws SQLException {
|
||||||
|
return spendTreasuryBudget(amount, "market.treasury.transaction_budget_percent");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean taxExempt(UUID uuid) throws SQLException {
|
public boolean taxExempt(UUID uuid) throws SQLException {
|
||||||
return repository.taxExempt(uuid);
|
return repository.taxExempt(uuid);
|
||||||
}
|
}
|
||||||
@@ -113,6 +121,18 @@ public final class GovernmentService {
|
|||||||
return PayFineResult.success(fine.amount(), "");
|
return PayFineResult.success(fine.amount(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean spendTreasuryBudget(long amount, String configPath) throws SQLException {
|
||||||
|
if (amount <= 0L) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long balance = repository.treasuryBalance();
|
||||||
|
long budget = Math.max(0L, Math.round(balance * Math.max(0.0D, config.getDouble(configPath, 0.0D)) / 100.0D));
|
||||||
|
if (amount > budget) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return repository.spendTreasury(amount);
|
||||||
|
}
|
||||||
|
|
||||||
private long transactionTax(UUID playerUuid, long gross, String configPath) throws SQLException {
|
private long transactionTax(UUID playerUuid, long gross, String configPath) throws SQLException {
|
||||||
if (gross <= 0L || repository.taxExempt(playerUuid)) {
|
if (gross <= 0L || repository.taxExempt(playerUuid)) {
|
||||||
return 0L;
|
return 0L;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.librewiki.coalgov.service;
|
|||||||
|
|
||||||
import com.librewiki.coalgov.CoalGovPlugin;
|
import com.librewiki.coalgov.CoalGovPlugin;
|
||||||
import com.librewiki.coalgov.storage.MarketRepository;
|
import com.librewiki.coalgov.storage.MarketRepository;
|
||||||
|
import com.librewiki.coalgov.storage.MarketRepository.Flow;
|
||||||
import com.librewiki.coalgov.util.CoalMoney;
|
import com.librewiki.coalgov.util.CoalMoney;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -70,7 +71,9 @@ public final class MarketService {
|
|||||||
long stock = simulatedStock.containsKey(material)
|
long stock = simulatedStock.containsKey(material)
|
||||||
? simulatedStock.get(material)
|
? simulatedStock.get(material)
|
||||||
: repository.stock(material, initialStock(material));
|
: repository.stock(material, initialStock(material));
|
||||||
grossCredit += sellCredit(material, basePrice, stock, item.getAmount());
|
Flow flow = repository.flow(material, flowHalfLifeMillis(material));
|
||||||
|
long alreadySold = sold.getOrDefault(material, 0L);
|
||||||
|
grossCredit += sellCredit(material, basePrice, stock, flow, alreadySold, item.getAmount());
|
||||||
simulatedStock.put(material, stock + item.getAmount());
|
simulatedStock.put(material, stock + item.getAmount());
|
||||||
sold.merge(material, (long) item.getAmount(), Long::sum);
|
sold.merge(material, (long) item.getAmount(), Long::sum);
|
||||||
}
|
}
|
||||||
@@ -80,11 +83,20 @@ public final class MarketService {
|
|||||||
}
|
}
|
||||||
long tax = plugin.governmentService().marketSaleTax(player.getUniqueId(), grossCredit);
|
long tax = plugin.governmentService().marketSaleTax(player.getUniqueId(), grossCredit);
|
||||||
long credit = Math.max(0L, grossCredit - tax);
|
long credit = Math.max(0L, grossCredit - tax);
|
||||||
plugin.economyService().credit(player.getUniqueId(), credit, "market_sale");
|
if (!plugin.governmentService().spendMarketTransaction(grossCredit)) {
|
||||||
|
player.sendMessage(plugin.messages().text("&cThe Ministry treasury cannot fund that market purchase right now."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!plugin.economyService().credit(player.getUniqueId(), credit, "market_sale")) {
|
||||||
|
plugin.governmentService().collectTax(grossCredit);
|
||||||
|
player.sendMessage(plugin.messages().text("&cMarket sale failed."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
plugin.governmentService().collectTax(tax);
|
plugin.governmentService().collectTax(tax);
|
||||||
removeSoldItems(player, sold);
|
removeSoldItems(player, sold);
|
||||||
for (var entry : sold.entrySet()) {
|
for (var entry : sold.entrySet()) {
|
||||||
repository.addStock(entry.getKey(), entry.getValue());
|
repository.addStock(entry.getKey(), entry.getValue());
|
||||||
|
repository.recordSell(entry.getKey(), entry.getValue(), flowHalfLifeMillis(entry.getKey()));
|
||||||
}
|
}
|
||||||
if (tax > 0L) {
|
if (tax > 0L) {
|
||||||
player.sendMessage(plugin.messages().text("&aSold resources for &f" + CoalMoney.format(credit) + " &aafter &f" + CoalMoney.format(tax) + " &atax."));
|
player.sendMessage(plugin.messages().text("&aSold resources for &f" + CoalMoney.format(credit) + " &aafter &f" + CoalMoney.format(tax) + " &atax."));
|
||||||
@@ -132,7 +144,8 @@ public final class MarketService {
|
|||||||
player.sendMessage(plugin.messages().text("&cYou need more inventory space."));
|
player.sendMessage(plugin.messages().text("&cYou need more inventory space."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.governmentService().collectTax(tax);
|
plugin.governmentService().collectMarketRevenue(total);
|
||||||
|
repository.recordBuy(material, amount, flowHalfLifeMillis(material));
|
||||||
if (tax > 0L) {
|
if (tax > 0L) {
|
||||||
player.sendMessage(plugin.messages().text("&aBought " + amount + " " + readable(material) + " for &f" + CoalMoney.format(total) + "&a, including &f" + CoalMoney.format(tax) + " &atax."));
|
player.sendMessage(plugin.messages().text("&aBought " + amount + " " + readable(material) + " for &f" + CoalMoney.format(total) + "&a, including &f" + CoalMoney.format(tax) + " &atax."));
|
||||||
} else {
|
} else {
|
||||||
@@ -165,34 +178,47 @@ public final class MarketService {
|
|||||||
private Quote quote(Material material) throws SQLException {
|
private Quote quote(Material material) throws SQLException {
|
||||||
double basePrice = basePrices().get(material);
|
double basePrice = basePrices().get(material);
|
||||||
long stock = repository.stock(material, initialStock(material));
|
long stock = repository.stock(material, initialStock(material));
|
||||||
return quote(material, basePrice, stock);
|
Flow flow = repository.flow(material, flowHalfLifeMillis(material));
|
||||||
|
return quote(material, basePrice, stock, flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Quote quote(Material material, double basePrice, long stock) {
|
private Quote quote(Material material, double basePrice, long stock, Flow flow) {
|
||||||
return new Quote(stock, sellUnitPrice(material, basePrice, stock), buyUnitPrice(material, basePrice, stock));
|
return new Quote(stock, sellUnitPrice(material, basePrice, stock, flow, 0L), buyUnitPrice(material, basePrice, stock, flow));
|
||||||
}
|
}
|
||||||
|
|
||||||
private long sellCredit(Material material, double basePrice, long startingStock, int amount) {
|
private long sellCredit(Material material, double basePrice, long startingStock, Flow flow, long alreadySold, int amount) {
|
||||||
double total = 0.0D;
|
double total = 0.0D;
|
||||||
for (int index = 0; index < amount; index++) {
|
for (int index = 0; index < amount; index++) {
|
||||||
total += sellUnitPrice(material, basePrice, startingStock + index);
|
total += sellUnitPrice(material, basePrice, startingStock + index, flow, alreadySold + index);
|
||||||
}
|
}
|
||||||
return Math.max(1L, CoalMoney.fromCoal(total));
|
return Math.max(1L, CoalMoney.fromCoal(total));
|
||||||
}
|
}
|
||||||
|
|
||||||
private double sellUnitPrice(Material material, double basePrice, long stock) {
|
private double sellUnitPrice(Material material, double basePrice, long stock, Flow flow, long batchSells) {
|
||||||
double target = Math.max(1.0D, targetStock());
|
double multiplier = stockPressure(material, stock) * flowPressure(material, flow, batchSells);
|
||||||
double multiplier = target / Math.max(1.0D, stock);
|
multiplier = Math.max(minMultiplier(material), Math.min(maxMultiplier(material), multiplier));
|
||||||
multiplier = Math.max(minMultiplier(), Math.min(maxMultiplier(), multiplier));
|
return Math.max(coalEquivalentFloor(material), basePrice * multiplier * sellMultiplier(material));
|
||||||
return Math.max(coalEquivalentFloor(material), basePrice * multiplier * sellMultiplier());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double buyUnitPrice(Material material, double basePrice, long stock) {
|
private double buyUnitPrice(Material material, double basePrice, long stock, Flow flow) {
|
||||||
double sellPrice = sellUnitPrice(material, basePrice, stock);
|
double sellPrice = sellUnitPrice(material, basePrice, stock, flow, 0L);
|
||||||
double target = Math.max(1.0D, targetStock());
|
double multiplier = stockPressure(material, stock) * flowPressure(material, flow, 0L);
|
||||||
double multiplier = target / Math.max(1.0D, stock);
|
multiplier = Math.max(minMultiplier(material), Math.min(maxMultiplier(material), multiplier));
|
||||||
multiplier = Math.max(minMultiplier(), Math.min(maxMultiplier(), multiplier));
|
return Math.max(sellPrice, basePrice * multiplier * buyMultiplier(material));
|
||||||
return Math.max(sellPrice, basePrice * multiplier * buyMultiplier());
|
}
|
||||||
|
|
||||||
|
private double stockPressure(Material material, long stock) {
|
||||||
|
double target = Math.max(1.0D, targetStock(material));
|
||||||
|
double floorStock = Math.max(1.0D, target * stockFloorRatio(material));
|
||||||
|
double pressure = target / Math.max(floorStock, stock);
|
||||||
|
return Math.pow(Math.max(0.0D, pressure), elasticity(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double flowPressure(Material material, Flow flow, long batchSells) {
|
||||||
|
double target = Math.max(1.0D, targetStock(material));
|
||||||
|
double netDemand = flow.recentBuys() - flow.recentSells() - Math.max(0L, batchSells);
|
||||||
|
double pressure = 1.0D + (netDemand / target) * flowWeight(material);
|
||||||
|
return Math.max(flowMinMultiplier(material), Math.min(flowMaxMultiplier(material), pressure));
|
||||||
}
|
}
|
||||||
|
|
||||||
private long buyCost(Quote quote, int amount) {
|
private long buyCost(Quote quote, int amount) {
|
||||||
@@ -262,24 +288,59 @@ public final class MarketService {
|
|||||||
return plugin.getConfig().getLong("market.initial_stock." + material.name(), initialStock());
|
return plugin.getConfig().getLong("market.initial_stock." + material.name(), initialStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
private long targetStock() {
|
private long targetStock(Material material) {
|
||||||
return plugin.getConfig().getLong("market.dynamic.target_stock", 1024L);
|
return materialLong(material, "target_stock", 1024L);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double minMultiplier() {
|
private double elasticity(Material material) {
|
||||||
return plugin.getConfig().getDouble("market.dynamic.min_multiplier", 0.35D);
|
return materialDouble(material, "elasticity", 1.0D);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double maxMultiplier() {
|
private double minMultiplier(Material material) {
|
||||||
return plugin.getConfig().getDouble("market.dynamic.max_multiplier", 3.0D);
|
return materialDouble(material, "min_multiplier", 0.35D);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double sellMultiplier() {
|
private double maxMultiplier(Material material) {
|
||||||
return plugin.getConfig().getDouble("market.dynamic.sell_multiplier", 0.85D);
|
return materialDouble(material, "max_multiplier", 3.0D);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double buyMultiplier() {
|
private double sellMultiplier(Material material) {
|
||||||
return plugin.getConfig().getDouble("market.dynamic.buy_multiplier", 1.15D);
|
return materialDouble(material, "sell_multiplier", 0.85D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double buyMultiplier(Material material) {
|
||||||
|
return materialDouble(material, "buy_multiplier", 1.15D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double stockFloorRatio(Material material) {
|
||||||
|
return materialDouble(material, "stock_floor_ratio", 0.05D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double flowWeight(Material material) {
|
||||||
|
return materialDouble(material, "flow_weight", 0.5D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double flowMinMultiplier(Material material) {
|
||||||
|
return materialDouble(material, "flow_min_multiplier", 0.70D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double flowMaxMultiplier(Material material) {
|
||||||
|
return materialDouble(material, "flow_max_multiplier", 1.75D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long flowHalfLifeMillis(Material material) {
|
||||||
|
long minutes = materialLong(material, "flow_half_life_minutes", 180L);
|
||||||
|
return Math.max(1L, minutes) * 60_000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long materialLong(Material material, String key, long fallback) {
|
||||||
|
return plugin.getConfig().getLong("market.dynamic.materials." + material.name() + "." + key,
|
||||||
|
plugin.getConfig().getLong("market.dynamic." + key, fallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double materialDouble(Material material, String key, double fallback) {
|
||||||
|
return plugin.getConfig().getDouble("market.dynamic.materials." + material.name() + "." + key,
|
||||||
|
plugin.getConfig().getDouble("market.dynamic." + key, fallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ItemStack button(Material material, String name, List<String> lore) {
|
private ItemStack button(Material material, String name, List<String> lore) {
|
||||||
|
|||||||
@@ -133,6 +133,14 @@ public final class Database {
|
|||||||
updated_at INTEGER NOT NULL
|
updated_at INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
""");
|
""");
|
||||||
|
statement.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS market_flow (
|
||||||
|
material TEXT PRIMARY KEY,
|
||||||
|
recent_buys REAL NOT NULL DEFAULT 0,
|
||||||
|
recent_sells REAL NOT NULL DEFAULT 0,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
""");
|
||||||
statement.execute("""
|
statement.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS treasury (
|
CREATE TABLE IF NOT EXISTS treasury (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -66,4 +66,71 @@ public final class MarketRepository {
|
|||||||
return statement.executeUpdate() == 1;
|
return statement.executeUpdate() == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Flow flow(Material material, long halfLifeMillis) throws SQLException {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||||
|
INSERT INTO market_flow(material, recent_buys, recent_sells, updated_at)
|
||||||
|
VALUES (?, 0, 0, ?)
|
||||||
|
ON CONFLICT(material) DO NOTHING
|
||||||
|
""")) {
|
||||||
|
insert.setString(1, material.name());
|
||||||
|
insert.setLong(2, now);
|
||||||
|
insert.executeUpdate();
|
||||||
|
}
|
||||||
|
try (PreparedStatement select = database.connection().prepareStatement(
|
||||||
|
"SELECT recent_buys, recent_sells, updated_at FROM market_flow WHERE material = ?")) {
|
||||||
|
select.setString(1, material.name());
|
||||||
|
try (ResultSet result = select.executeQuery()) {
|
||||||
|
if (!result.next()) {
|
||||||
|
return new Flow(0.0D, 0.0D);
|
||||||
|
}
|
||||||
|
double decay = decayFactor(now - result.getLong("updated_at"), halfLifeMillis);
|
||||||
|
return new Flow(result.getDouble("recent_buys") * decay, result.getDouble("recent_sells") * decay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordBuy(Material material, long amount, long halfLifeMillis) throws SQLException {
|
||||||
|
recordFlow(material, Math.max(0L, amount), 0L, halfLifeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordSell(Material material, long amount, long halfLifeMillis) throws SQLException {
|
||||||
|
recordFlow(material, 0L, Math.max(0L, amount), halfLifeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordFlow(Material material, long buys, long sells, long halfLifeMillis) throws SQLException {
|
||||||
|
if (buys <= 0L && sells <= 0L) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Flow flow = flow(material, halfLifeMillis);
|
||||||
|
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||||
|
INSERT INTO market_flow(material, recent_buys, recent_sells, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
ON CONFLICT(material) DO UPDATE SET
|
||||||
|
recent_buys = excluded.recent_buys,
|
||||||
|
recent_sells = excluded.recent_sells,
|
||||||
|
updated_at = excluded.updated_at
|
||||||
|
""")) {
|
||||||
|
statement.setString(1, material.name());
|
||||||
|
statement.setDouble(2, flow.recentBuys() + buys);
|
||||||
|
statement.setDouble(3, flow.recentSells() + sells);
|
||||||
|
statement.setLong(4, now);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double decayFactor(long elapsedMillis, long halfLifeMillis) {
|
||||||
|
if (elapsedMillis <= 0L) {
|
||||||
|
return 1.0D;
|
||||||
|
}
|
||||||
|
if (halfLifeMillis <= 0L) {
|
||||||
|
return 0.0D;
|
||||||
|
}
|
||||||
|
return Math.pow(0.5D, (double) elapsedMillis / halfLifeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Flow(double recentBuys, double recentSells) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,13 +60,30 @@ divining_rod:
|
|||||||
cooldown_seconds: 5
|
cooldown_seconds: 5
|
||||||
|
|
||||||
market:
|
market:
|
||||||
|
treasury:
|
||||||
|
transaction_budget_percent: 25.0
|
||||||
dynamic:
|
dynamic:
|
||||||
initial_stock: 1024
|
initial_stock: 1024
|
||||||
target_stock: 1024
|
target_stock: 1024
|
||||||
|
elasticity: 1.0
|
||||||
|
stock_floor_ratio: 0.05
|
||||||
min_multiplier: 0.35
|
min_multiplier: 0.35
|
||||||
max_multiplier: 3.0
|
max_multiplier: 3.0
|
||||||
sell_multiplier: 0.85
|
sell_multiplier: 0.85
|
||||||
buy_multiplier: 1.15
|
buy_multiplier: 1.15
|
||||||
|
flow_half_life_minutes: 180
|
||||||
|
flow_weight: 0.5
|
||||||
|
flow_min_multiplier: 0.70
|
||||||
|
flow_max_multiplier: 1.75
|
||||||
|
materials:
|
||||||
|
COBBLESTONE:
|
||||||
|
target_stock: 4096
|
||||||
|
elasticity: 1.0
|
||||||
|
min_multiplier: 0.20
|
||||||
|
max_multiplier: 10.0
|
||||||
|
flow_weight: 1.25
|
||||||
|
flow_min_multiplier: 0.50
|
||||||
|
flow_max_multiplier: 2.25
|
||||||
initial_stock:
|
initial_stock:
|
||||||
WHEAT: 0
|
WHEAT: 0
|
||||||
BREAD: 0
|
BREAD: 0
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Bypass mode is per-player and lasts until it is turned off or the plugin/server
|
|||||||
|
|
||||||
===== Market Controls =====
|
===== Market Controls =====
|
||||||
|
|
||||||
The market is configured in ''plugins/CoalGov/config.yml'' under ''market''. Admins can tune base prices, target stock, the starting stock for new materials, min/max price movement, and the buy/sell spread.
|
The market is configured in ''plugins/CoalGov/config.yml'' under ''market''. Admins can tune base prices, target stock, the starting stock for new materials, min/max price movement, the buy/sell spread, and per-material dynamic overrides under ''market.dynamic.materials''.
|
||||||
|
|
||||||
After editing market config, run:
|
After editing market config, run:
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ Every traded material has a base price in config. The live price is adjusted by
|
|||||||
|
|
||||||
* If stock is low, the multiplier rises and items become more expensive.
|
* If stock is low, the multiplier rises and items become more expensive.
|
||||||
* If stock is high, the multiplier falls and items become cheaper.
|
* If stock is high, the multiplier falls and items become cheaper.
|
||||||
|
* Recent net buying raises prices for a while.
|
||||||
|
* Recent net selling lowers prices for a while.
|
||||||
|
* Recent buy/sell pressure decays over time.
|
||||||
* The config has minimum and maximum multipliers to prevent runaway prices.
|
* The config has minimum and maximum multipliers to prevent runaway prices.
|
||||||
|
* Materials can override the global target stock and multipliers under ''market.dynamic.materials''.
|
||||||
* The buy price is higher than the sell price because the market has a spread.
|
* The buy price is higher than the sell price because the market has a spread.
|
||||||
|
|
||||||
This keeps the economy simple while still reacting to player behavior. If everyone sells cobblestone, cobblestone becomes less valuable. If everyone buys out iron, iron becomes more expensive until players sell more into the market.
|
This keeps the economy simple while still reacting to player behavior. If everyone sells cobblestone, cobblestone becomes less valuable. If everyone buys out iron, iron becomes more expensive until players sell more into the market.
|
||||||
@@ -41,10 +45,25 @@ Food items start with Ministry stock 0 by default, so players must sell food int
|
|||||||
dynamic:
|
dynamic:
|
||||||
initial_stock: 1024
|
initial_stock: 1024
|
||||||
target_stock: 1024
|
target_stock: 1024
|
||||||
|
elasticity: 1.0
|
||||||
|
stock_floor_ratio: 0.05
|
||||||
min_multiplier: 0.35
|
min_multiplier: 0.35
|
||||||
max_multiplier: 3.0
|
max_multiplier: 3.0
|
||||||
sell_multiplier: 0.85
|
sell_multiplier: 0.85
|
||||||
buy_multiplier: 1.15
|
buy_multiplier: 1.15
|
||||||
|
flow_half_life_minutes: 180
|
||||||
|
flow_weight: 0.5
|
||||||
|
flow_min_multiplier: 0.70
|
||||||
|
flow_max_multiplier: 1.75
|
||||||
|
materials:
|
||||||
|
COBBLESTONE:
|
||||||
|
target_stock: 4096
|
||||||
|
elasticity: 1.0
|
||||||
|
min_multiplier: 0.20
|
||||||
|
max_multiplier: 10.0
|
||||||
|
flow_weight: 1.25
|
||||||
|
flow_min_multiplier: 0.50
|
||||||
|
flow_max_multiplier: 2.25
|
||||||
initial_stock:
|
initial_stock:
|
||||||
BREAD: 0
|
BREAD: 0
|
||||||
COOKED_BEEF: 0
|
COOKED_BEEF: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user