diff --git a/.github/workflows/discord-issues.yml b/.github/workflows/discord-issues.yml index 83ee205..bac3231 100644 --- a/.github/workflows/discord-issues.yml +++ b/.github/workflows/discord-issues.yml @@ -9,6 +9,7 @@ on: jobs: notify: runs-on: ubuntu-latest + if: "!(github.event_name == 'issue_comment' && github.event.comment.user.login == 'codecov-commenter')" steps: - name: Send issue or comment to Discord diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ecc20..7d8ba68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.5.6] - 2026-05-22 + +Through time we've made the stock market more stable, by doing this the documentation got outdated and configuration options that existed before got phased out. This version there was focus on updating the documentation and adding back options that got phased out in a more stable way. + +### Added +- **Configurable stock price-engine parameters** - `volatility-min`, `volatility-max`, `demand-multiplier`, `min-price`, and `update-interval` are now real `config.yml` options under the `stock:` section. The plugin reads them on startup and applies them to the price engine. All defaults match the previously hardcoded values so existing behaviour is preserved. + +### Fixed +- **Incorrect stock-market configuration documented** - `docs/configuration/main-settings.md` and `docs/shops/pricing/stock-market.md` previously documented a non-existent `stock-market:` config block. Both pages now document the real `stock:` section (`enabled`, `cooldown-millis`, `blocked`, `overrides`, `categories`). Price-engine parameters (`volatility-min`, `volatility-max`, `demand-multiplier`, `min-price`, `update-interval`) are now implemented as real config options (see Unreleased → Added). +- **`/sell` command missing from documentation** - the Quick Sell GUI command (`/sell`) is now documented in the Commands reference with its behavior and permission node. +- **`/shopadmin` command missing from documentation** - `/shopadmin [browse|market]` is now documented under Admin Commands including both the player-shops and team-market views. +- **`/teamshop market` subcommand missing from documentation** - the team P2P market subcommand is now documented in Commands, the TeamsAPI integration page, and the tab-completion list for `/teamshop`. +- **Missing permission nodes in documentation** - the following nodes were present in `plugin.yml` but absent from the Permissions reference; they are now documented: + - `ezshops.shop.admin` (open `/shopadmin` GUI) + - `ezshops.teamshop.market` (access team P2P market) + - `ezshops.pricing.admin.set`, `ezshops.pricing.admin.disable`, `ezshops.pricing.admin.list` (granular pricing-admin nodes) + ## [2.5.5] - 2026-05-18 ### Fixed diff --git a/docs/commands.md b/docs/commands.md index 6c79580..b5bd4ad 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -66,6 +66,20 @@ You can also use the category display name (case-insensitive, color codes ignore ### Trading Commands +#### `/sell` +Opens the Quick Sell GUI, allowing you to drag items in and sell them all at once. + +**Usage:** `/sell` +**Permission:** `ezshops.shop.sell` +**Aliases:** None + +**Behavior:** +- Opens a chest-style GUI where you can place items to sell +- Confirm to sell all items at once and receive combined earnings +- Can be disabled per-server via the configuration + +--- + #### `/sellhand` Sells the item currently held in your hand to the shop. @@ -200,6 +214,36 @@ Quick overview of cached stock quotes with pagination. ## Admin Commands +### Shop Administration + +#### `/shopadmin` +Opens the admin moderation GUI for inspecting and removing any active player shop or team market listing. + +**Usage:** +``` +/shopadmin +/shopadmin browse +/shopadmin market +``` +**Permission:** `ezshops.shop.admin` +**Aliases:** None + +**Subcommands:** + +##### `/shopadmin` / `/shopadmin browse` +Opens the paginated **Player Shops** view listing all active player-owned chest shops. Admins can remove any listing directly from the GUI. + +**Permission:** `ezshops.shop.admin` +**Example:** `/shopadmin browse` + +##### `/shopadmin market` +Switches the browse GUI to the **Team Market** view, showing all active team market listings across every team. + +**Permission:** `ezshops.shop.admin` +**Example:** `/shopadmin market` + +--- + ### Sign Shop Setup #### `/signshop` @@ -344,7 +388,7 @@ These commands require [TeamsAPI ≥ 1.4.1](integrations/teams-api) to be instal Opens the **Team Shop Dashboard** showing your team's name, current role bonuses and quick links to the treasury and stock browser. **Permission:** `ezshops.teamshop` (default: true) -**Tab completion:** `treasury`, `stocks` +**Tab completion:** `treasury`, `stocks`, `market` ##### `/teamshop treasury` Opens the **Team Treasury GUI**. View the shared balance, deposit, and (with the appropriate permission) withdraw funds. @@ -358,6 +402,12 @@ Opens the **Team Stock GUI**, listing all items currently in the team's shared s **Permission:** `ezshops.teamshop` (default: true) **Example:** `/teamshop stocks` +##### `/teamshop market` +Opens the **Team P2P Market GUI** where players can list items for sale and purchase listings from teammates. + +**Permission:** `ezshops.teamshop.market` (default: true) +**Example:** `/teamshop market` + --- ## Command Notes diff --git a/docs/configuration/main-settings.md b/docs/configuration/main-settings.md index 2bee791..ef1bc49 100644 --- a/docs/configuration/main-settings.md +++ b/docs/configuration/main-settings.md @@ -78,39 +78,74 @@ player-shops: --- -## 📉 Stock Market & Price Calculation +## 📉 Stock Market *(For a full deep-dive into the Stock Market system, check out the [Stock Market Documentation](../shops/pricing/stock-market.md))* -**Version 2.0.0+ Security Improvements:** -- All stock sales now require confirmation through a GUI -- Fixed infinite money glitch vulnerability -- Enhanced transaction validation +**All stock sales require confirmation through a GUI before the transaction is finalised.** + +### Configuration (`config.yml`) + +The stock section controls which items are tradeable and how the GUI presents them. Price-engine parameters (volatility, demand factor, minimum price, and save interval) are configurable via `config.yml`. -### Available Settings Overview ```yaml -stock-market: - # Enable stock market system +stock: + # Master switch – set to false to disable all /stock commands and GUIs. enabled: true - # Price volatility range (-10% to +10% by default) + # Per-player cooldown between trades in milliseconds. 0 = no cooldown. + cooldown-millis: 10000 + + # Price volatility range: random noise applied per trade (fraction of price). volatility-min: -0.10 volatility-max: 0.10 - # Demand multiplier for price changes + # Demand multiplier: fraction of price change per unit bought (+) or sold (-). demand-multiplier: 0.02 - # Minimum price floor (prevents prices from going too low) + # Minimum price floor: prices cannot drop below this value. min-price: 1.0 - # Auto-update interval in minutes - update-interval: 15 + # How often (in minutes) market prices are saved to disk. + update-interval: 5 + + # Materials that cannot be traded on the stock market. + blocked: + - BEDROCK + - COMMAND_BLOCK + + # Custom items to expose in the stock market. + # 'display' sets the GUI label; 'base-price' is the reference price shown + # in the all-stocks listing (the live trading price evolves independently). + overrides: + - id: "DIAMOND" + display: "&bDiamond" + base-price: 100.0 + + # Optional: group items under named category tabs in the GUI. + categories: + gems: + - DIAMOND + - EMERALD ``` +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `enabled` | boolean | `true` | Enable/disable all stock features | +| `cooldown-millis` | integer | `0` | Milliseconds between player trades | +| `volatility-min` | decimal | `-0.10` | Lower bound of random volatility per trade (-10 %) | +| `volatility-max` | decimal | `0.10` | Upper bound of random volatility per trade (+10 %) | +| `demand-multiplier` | decimal | `0.02` | Price change per unit traded (2 % per unit) | +| `min-price` | decimal | `1.0` | Absolute price floor | +| `update-interval` | integer | `5` | Minutes between automatic price saves to disk | +| `blocked` | list | `[]` | Materials blocked from trading | +| `overrides` | list | `[]` | Custom items with display names and reference prices | +| `categories` | map | `{}` | Named category → list of item groupings | + ### Price Calculation Formula -The stock market calculates price fluctuations using the following formula: ```text -New price = max(min-price, current price × (1 + (demand × demand-multiplier) + random volatility)) +Per-unit change = (±demand-multiplier) + random(volatility-min, volatility-max) +New price after N units = current × (1 + per-unit-change)^N, floor at min-price ``` --- diff --git a/docs/integrations/teams-api.md b/docs/integrations/teams-api.md index cd9fb7e..053e12a 100644 --- a/docs/integrations/teams-api.md +++ b/docs/integrations/teams-api.md @@ -135,11 +135,21 @@ Opens the **Team Stock GUI**, listing all items your team currently has in the s --- +### `/teamshop market` + +Opens the **Team P2P Market GUI** where players can list items for sale and purchase listings from teammates. + +**Permission:** `ezshops.teamshop.market` +**Default:** true + +--- + ## Permissions | Node | Default | Description | |------|---------|-------------| | `ezshops.teamshop` | true | Access `/teamshop` and sub-GUIs | +| `ezshops.teamshop.market` | true | Access the team P2P market via `/teamshop market` | | `ezshops.teamshop.treasury.withdraw` | true | Withdraw funds from the team treasury | | `ezshops.teamshop.admin` | op | Administrative access to team stock data | diff --git a/docs/permissions.md b/docs/permissions.md index a54b2b8..c52a622 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -84,10 +84,12 @@ EzShops uses a hierarchical permission system. Permissions are organized into ca | Permission Node | Default | Description | |-----------------------------|---------|--------------------------------------------------| | `ezshops.reload` | op | Reload shop configuration with `/shop reload` | +| `ezshops.shop.admin` | op | Open `/shopadmin` moderation GUI | | `ezshops.shop.admin.minionhead` | op | Purchase minion heads directly (bypass restrictions) | **Details:** - `ezshops.reload` - Allows reloading all shop configurations, menus, categories, and pricing without server restart +- `ezshops.shop.admin` - Opens the admin browse GUI to inspect and remove any player shop or team market listing - `ezshops.shop.admin.minionhead` - Bypasses normal restrictions on minion heads (usually crate-only items) ### Sign Shop Management @@ -233,6 +235,7 @@ Permissions for the [TeamsAPI integration](integrations/teams-api.md). These nod | Permission | Default | Description | |------------|---------|-------------| | `ezshops.teamshop` | true | Access `/teamshop`, `/teamshop stocks`, and view the treasury GUI | +| `ezshops.teamshop.market` | true | Access the team P2P market via `/teamshop market` | | `ezshops.teamshop.treasury.withdraw` | true | Withdraw funds from the team treasury | | `ezshops.teamshop.admin` | op | Administrative access to team stock data (view/clear any team's stock) | diff --git a/docs/shops/pricing/stock-market.md b/docs/shops/pricing/stock-market.md index 69f1240..33bb021 100644 --- a/docs/shops/pricing/stock-market.md +++ b/docs/shops/pricing/stock-market.md @@ -12,30 +12,80 @@ The Stock Market system introduces a global economic layer where item prices flu --- -## ⚙️ Available Settings Overview +## ⚙️ Configuration -Configure the stock market behavior in your `config.yml`. +Configure the stock market in the `stock:` section of `config.yml`. All price-engine parameters are fully configurable. ```yaml -stock-market: +stock: + # Master switch. Set to false to disable all /stock commands and GUIs. enabled: true - # Price volatility range (-10% to +10% by default) + + # Per-player cooldown between trades in milliseconds. 0 = no cooldown. + cooldown-millis: 10000 + + # Price volatility range: random noise applied per trade (fraction of price). volatility-min: -0.10 volatility-max: 0.10 - # Demand multiplier for price changes + + # Demand multiplier: fraction of price change per unit bought (+) or sold (-). demand-multiplier: 0.02 - # Minimum price floor (prevents prices from going too low) + + # Minimum price floor: prices cannot drop below this value. min-price: 1.0 - # Auto-update interval in minutes - update-interval: 15 + + # How often (in minutes) market prices are saved to disk. + update-interval: 5 + + # Materials that cannot be traded on the stock market. + blocked: + - BEDROCK + - COMMAND_BLOCK + + # Custom items to expose in the stock market. + # 'display' – label shown in the all-stocks GUI listing. + # 'base-price' – reference price shown in the all-stocks listing; + # the live trading price evolves independently. + overrides: + - id: "DIAMOND" + display: "&bDiamond" + base-price: 100.0 + + # Optional: group items under named category tabs in the GUI. + categories: + gems: + - DIAMOND + - EMERALD ``` +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `enabled` | boolean | `true` | Enable/disable all stock features | +| `cooldown-millis` | integer | `0` | Milliseconds between player trades | +| `volatility-min` | decimal | `-0.10` | Lower bound of random volatility per trade (-10 %) | +| `volatility-max` | decimal | `0.10` | Upper bound of random volatility per trade (+10 %) | +| `demand-multiplier` | decimal | `0.02` | Price change per unit traded (2 % per unit) | +| `min-price` | decimal | `1.0` | Absolute price floor | +| `update-interval` | integer | `5` | Minutes between automatic price saves to disk | +| `blocked` | list | `[]` | Materials blocked from trading | +| `overrides` | list | `[]` | Custom display names and reference prices | +| `categories` | map | `{}` | Named category → list of item groupings | + --- ## 🧮 Price Calculation Formula -The engine calculates the next price point using this exact logic: +The engine applies a **per-unit multiplicative update** each time an item is bought or sold. + +| Value | Default | Description | +|-------|---------|-------------| +| Starting price | 100.0 | Default price for any item not yet traded | +| Demand factor | ±2 % per unit | `demand-multiplier` — +2 % per unit bought, −2 % per unit sold | +| Random volatility | ±10 % per trade | Drawn from `[volatility-min, volatility-max]` | +| Price floor | 1.0 | `min-price` — prices cannot drop below this value | +| Auto-save interval | 5 minutes | `update-interval` — how often prices are written to disk | ```text -New price = max(min-price, current price × (1 + (demand × demand-multiplier) + random volatility)) +Per-unit change = (±demand-multiplier) + random(volatility-min, volatility-max) +New price after N units = current × (1 + per-unit-change)^N, floor at min-price ``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index d04aa0b..2c587f9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.skyblockexp ezshops - 2.5.5 + 2.5.6 EzShops Plugin Standalone plugin providing the Skyblock shop command and sign shops. jar diff --git a/src/main/java/com/skyblockexp/ezshops/bootstrap/StockComponent.java b/src/main/java/com/skyblockexp/ezshops/bootstrap/StockComponent.java index 59b022a..3a7d2d6 100644 --- a/src/main/java/com/skyblockexp/ezshops/bootstrap/StockComponent.java +++ b/src/main/java/com/skyblockexp/ezshops/bootstrap/StockComponent.java @@ -45,9 +45,16 @@ public void enable(EzShopsPlugin plugin) { StockMarketRepository repository = new YmlStockMarketRepository(plugin.getDataFolder()); this.frozenStore = new StockMarketFrozenStore(repository); this.stockMarketManager = new StockMarketManager(); + this.stockMarketManager.configure( + stockMarketConfig.getVolatilityMin(), + stockMarketConfig.getVolatilityMax(), + stockMarketConfig.getDemandFactor(), + stockMarketConfig.getMinPrice() + ); this.stockMarketManager.setStockMarketRepository(repository); - // Enable async periodic persistence (every 5 minutes = 6000 ticks) - this.stockMarketManager.enablePersistence(plugin, 6000L); + // Enable async periodic persistence using the configured interval. + long saveIntervalTicks = stockMarketConfig.getSaveIntervalMinutes() * 60L * 20L; + this.stockMarketManager.enablePersistence(plugin, saveIntervalTicks); this.cooldownMillis = config.getConfigurationSection("stock") != null ? config.getLong("stock.cooldown-millis", 0L) : 0L; registerCommand("stock", new StockCommand(plugin, stockMarketManager, cooldownMillis, stockMarketConfig, frozenStore)); registerCommand("stockadmin", new StockAdminCommand(stockMarketManager, frozenStore, stockMarketConfig)); diff --git a/src/main/java/com/skyblockexp/ezshops/config/StockMarketConfig.java b/src/main/java/com/skyblockexp/ezshops/config/StockMarketConfig.java index 91eb05e..13f3ea5 100644 --- a/src/main/java/com/skyblockexp/ezshops/config/StockMarketConfig.java +++ b/src/main/java/com/skyblockexp/ezshops/config/StockMarketConfig.java @@ -9,6 +9,13 @@ public class StockMarketConfig { private final Set blocked; private final Map overrides; + // Price-engine parameters (configurable, defaults match original hardcoded values) + private final double volatilityMin; + private final double volatilityMax; + private final double demandFactor; + private final double minPrice; + private final int saveIntervalMinutes; + public StockMarketConfig(FileConfiguration config) { this.blocked = new HashSet<>(); this.overrides = new HashMap<>(); @@ -41,9 +48,26 @@ public StockMarketConfig(FileConfiguration config) { } } } + this.volatilityMin = stockSection.getDouble("volatility-min", -0.10); + this.volatilityMax = stockSection.getDouble("volatility-max", 0.10); + this.demandFactor = stockSection.getDouble("demand-multiplier", 0.02); + this.minPrice = stockSection.getDouble("min-price", 1.0); + this.saveIntervalMinutes = stockSection.getInt("update-interval", 5); + } else { + this.volatilityMin = -0.10; + this.volatilityMax = 0.10; + this.demandFactor = 0.02; + this.minPrice = 1.0; + this.saveIntervalMinutes = 5; } } + public double getVolatilityMin() { return volatilityMin; } + public double getVolatilityMax() { return volatilityMax; } + public double getDemandFactor() { return demandFactor; } + public double getMinPrice() { return minPrice; } + public int getSaveIntervalMinutes(){ return saveIntervalMinutes; } + public boolean isBlocked(String id) { return blocked.contains(id.toUpperCase(Locale.ROOT)); } diff --git a/src/main/java/com/skyblockexp/ezshops/stock/StockMarketManager.java b/src/main/java/com/skyblockexp/ezshops/stock/StockMarketManager.java index b6c73af..74f5049 100644 --- a/src/main/java/com/skyblockexp/ezshops/stock/StockMarketManager.java +++ b/src/main/java/com/skyblockexp/ezshops/stock/StockMarketManager.java @@ -25,12 +25,27 @@ public class StockMarketManager { private final Map prices = new HashMap<>(); private final Random random = new Random(); private static final double BASE_PRICE = 100.0; - private static final double MAX_CHANGE = 0.10; - // per-unit deterministic demand factor (matches previous aggregated 0.02 per unit) - private static final double PER_UNIT_DEMAND_FACTOR = 0.02; + // Engine parameters – defaults match original hardcoded values; override via configure(). + private double volatilityMin = -0.10; + private double volatilityMax = 0.10; + private double demandFactor = 0.02; + private double minPrice = 1.0; private StockMarketRepository stockMarketRepository; private final StockHistoryManager historyManager = new StockHistoryManager(); + /** + * Apply configurable price-engine parameters. + * Call this before {@link #enablePersistence} so the loaded prices are + * immediately governed by the configured floor. + */ + public void configure(double volatilityMin, double volatilityMax, + double demandFactor, double minPrice) { + this.volatilityMin = volatilityMin; + this.volatilityMax = volatilityMax; + this.demandFactor = Math.max(0.0, demandFactor); + this.minPrice = Math.max(0.0, minPrice); + } + // Persistence private TaskHandle saveTask; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @@ -116,12 +131,12 @@ public void updatePrice(String productId, int demand) { return; } // Compute a single random component for the entire bulk operation (preserves similar randomness scale) - double randomComponent = (random.nextDouble() * 2 - 1) * MAX_CHANGE; + double randomComponent = volatilityMin + random.nextDouble() * (volatilityMax - volatilityMin); // per-unit change (positive for buys, negative for sells) plus shared random - double perUnitChange = (demand > 0 ? PER_UNIT_DEMAND_FACTOR : -PER_UNIT_DEMAND_FACTOR) + randomComponent; + double perUnitChange = (demand > 0 ? demandFactor : -demandFactor) + randomComponent; int steps = Math.abs(demand); for (int i = 0; i < steps; i++) { - current = Math.max(1.0, current * (1.0 + perUnitChange)); + current = Math.max(minPrice, current * (1.0 + perUnitChange)); } prices.put(productId, current); historyManager.recordPrice(productId, current); @@ -156,15 +171,15 @@ public double estimateBulkTotal(String productId, int amount, ShopTransactionTyp boolean isBuy = type == ShopTransactionType.BUY; for (int i = 0; i < amount; i++) { total += sim; - double change = isBuy ? PER_UNIT_DEMAND_FACTOR : -PER_UNIT_DEMAND_FACTOR; - sim = Math.max(1.0, sim * (1.0 + change)); + double change = isBuy ? demandFactor : -demandFactor; + sim = Math.max(minPrice, sim * (1.0 + change)); } return total; } public void setPrice(String productId, double price) { - double p = Math.max(1.0, price); + double p = Math.max(minPrice, price); lock.writeLock().lock(); try { prices.put(productId, p); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6dbeb15..a67e837 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -137,6 +137,15 @@ stock: # Use /stock overview to see all available stocks and their prices. enabled: true cooldown-millis: 10000 + # Price volatility range: random noise added per trade (as a fraction of price). + volatility-min: -0.10 + volatility-max: 0.10 + # Demand multiplier: fraction of price applied per unit bought (+) or sold (-). + demand-multiplier: 0.02 + # Minimum price floor: prices cannot drop below this value. + min-price: 1.0 + # How often (in minutes) market prices are saved to disk. + update-interval: 5 # List of item names (Material) to block from stock trading blocked: - BEDROCK