Merge branch 'dev' into syntax-highlighter-unified

This commit is contained in:
bigguy345 committed 2026-01-25 19:37:27 +02:00
commit 39a43a339b
78 files changed
+4554 -1446

No files matched your search

+239 -49
View File
@@ -44,8 +44,8 @@ The currency system provides a unified economy for CNPC+ features.
### Key Files
| File | Purpose |
|------|---------|
| `PlayerCurrencyData.java` | Per-player currency storage, Vault delegation |
| `IPlayerCurrencyData.java` | API interface for currency operations |
| `PlayerTradeData.java` | Per-player trade data (currency + auction claims), Vault delegation |
| `IPlayerTradeData.java` | API interface for trade operations (currency + claims) |
| `ConfigMarket.java` | Currency configuration (UseVault, CurrencyName, StartingBalance, MaxBalance) |
| `VaultUtil.java` | Vault API integration via reflection |
@@ -67,6 +67,9 @@ player.withdrawCurrency(amount) // Remove currency (returns success)
player.canAffordCurrency(amount) // Check if can afford
player.isUsingVaultCurrency() // Check if Vault is active
player.getFormattedCurrencyBalance() // Get formatted string
// Via IPlayerData
player.getData().getTradeData() // Get IPlayerTradeData interface
```
---
@@ -125,7 +128,7 @@ Each trade slot can have:
#### Implementation Details
- Currency cost stored as `long[] currencyCost[18]` in RoleTrader
- Uses `data.currencyData.getBalance()` and `data.currencyData.withdraw()`
- Uses `data.tradeData.getBalance()` and `data.tradeData.withdraw()`
- Automatically uses Vault when `ConfigMarket.UseVault` is enabled
- NBT serialization for persistence ✅
@@ -165,20 +168,22 @@ Full API available via `IRoleTrader` interface.
```
┌─────────────────────────────────────────────────────────────────┐
│ AuctionController │
│ - Manages all auction data with player indices │
│ - Manages auction listings with player indices │
│ - Handles listing/bidding/buyout logic │
│ - Race-condition protected with ReentrantReadWriteLock │
│ - Async saves on CNPC+ thread │
│ - 20-second update cycle for performance │
│ - Uses ConcurrentHashMap for thread-safety (no locks) │
│ - Async saves on CNPC+ thread every 30 seconds │
│ - 30-second update cycle for processing ended auctions │
└─────────────────────────────────────────────────────────────────┘
│
Uses PlayerCurrencyData (Vault-aware)
│
┌─────────────────────────────────────────────────────────────────┐
│ AuctionNotification │
│ - Tracks pending notifications per player │
│ - Sends notifications on login │
│ - Types: Won, Outbid, Sold, Expired, Claimed │
│ PlayerTradeData │
│ - Combined currency + auction claims (shared across profiles) │
│ - Currency: Vault integration with local fallback │
│ - Claims: Thread-safe CopyOnWriteArrayList for async additions │
│ - Expired claims processed on login │
│ - Notifications sent immediately to online players │
└─────────────────────────────────────────────────────────────────┘
```
@@ -308,6 +313,8 @@ All actions are validated server-side:
| Auction expiry check | `placeBid()/buyout()` | Double-check inside write lock |
| Currency validation | All actions | Verify balance before operations |
| Claim ownership | `claimItem()/claimCurrency()` | `claim.isForPlayer(playerUUID)` check |
| Blacklist check | `createListing()` | `AuctionBlacklist.isBlacklistedForPlayer()` check |
| Bound item check | `createListing()` | Auto-blocks Soulbound/Profile-Slotbound items |
### 3.7 Snipe Protection
@@ -320,6 +327,53 @@ Example: Auction ends in 30 seconds, someone bids → Timer resets to 2:00
---
### 3.8 Item Blacklist System
Prevents specific items from being listed on the auction house.
#### Blacklist Types
| Type | Description | Example |
|------|-------------|---------|
| **Items** | Block specific items by registry name | `minecraft:diamond_sword` |
| **Wildcards** | Block items matching pattern | `minecraft:diamond*` |
| **Mods** | Block all items from a mod | `customnpcs` |
| **NBT Tags** | Block items with specific NBT | `ench` (enchanted items) |
#### Auto-Blocked Items (Always Blocked)
These items are **always** blocked regardless of blacklist settings:
- **Soulbound Items** - Items with `cnpc_soulbind` requirement
- **Profile-Slotbound Items** - Items with `cnpc_profile_slot` requirement
The system checks for these under `RPGCore/Requirements` in the item's NBT.
#### Bypass Permission
Players with OP status or the `customnpcs.auction.blacklist.bypass` permission can list blacklisted items.
#### Configuration
```
Market.Auction.Blacklist:
Enabled: true
BlacklistedItems: ["minecraft:barrier", "minecraft:command_block*"]
BlacklistedMods: []
BlacklistedNBTTags: []
```
#### Commands
| Command | Description |
|---------|-------------|
| `/cnpc auction blacklist add <item\|mod\|nbt> <value>` | Add to blacklist (runtime only) |
| `/cnpc auction blacklist remove <item\|mod\|nbt> <value>` | Remove from blacklist (runtime only) |
| `/cnpc auction blacklist list [item\|mod\|nbt]` | List blacklisted entries |
| `/cnpc auction blacklist reload` | Reload blacklist from config |
| `/cnpc auction blacklist check` | Check if held item is blacklisted |
---
## 4. AuctionController Performance & Thread Safety
### 4.1 Threading Model
@@ -328,14 +382,26 @@ Example: Auction ends in 30 seconds, someone bids → Timer resets to 2:00
┌─────────────────────────────────────────────────────────────────┐
│ AuctionController │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ReentrantRWLock │ │ AtomicBoolean │ │ CNPC+ Thread │ │
│ │ (dataLock) │ │ dirty, saving │ │ (async saves) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ ConcurrentHashMap │ │ AtomicBoolean │ │ CNPC+ Thread │ │
│ │ (listings, indices) │ │ dirty, saving │ │ (async saves) │ │
│ └─────────────────────┘ └─────────────────┘ └───────────────┘ │
│ │
│ Read operations → Read Lock (concurrent) │
│ Write operations → Write Lock (exclusive) │
│ Save operations → Async on CNPC+ thread │
│ All operations use ConcurrentHashMap - no explicit locking │
│ Minecraft is single-threaded for gameplay - inherent safety │
│ Async saves snapshot data before writing to disk │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ PlayerTradeData │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Currency: long balance (or Vault delegation) │ │
│ │ Claims: CopyOnWriteArrayList<AuctionClaim> │ │
│ │ - Thread-safe for concurrent read/write │ │
│ │ - Claims can be added from CNPC+ thread for offline players │ │
│ │ - Stored in PlayerData (persisted with player, not auctions) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
@@ -345,29 +411,33 @@ Example: Auction ends in 30 seconds, someone bids → Timer resets to 2:00
|-------|------|---------|
| `playerListingIds` | `Map<UUID, Set<String>>` | Active listings by seller |
| `playerBidIds` | `Map<UUID, Set<String>>` | Listings where player has active bid |
| `playerClaimIds` | `Map<UUID, Set<String>>` | Unclaimed claims by player |
**Claims are now stored in PlayerData:**
- `PlayerTradeData.claims` - CopyOnWriteArrayList per player
- Accessed via `PlayerData.get(player).tradeData`
- Shared across all profile slots (combined with currency)
**Performance Impact:**
- `getPlayerListingCount()`: O(n) → O(1)
- `getPlayerActiveListings()`: O(n) → O(k) where k = player's listings
- `getPlayerActiveBids()`: O(n) → O(k)
- `getPlayerClaims()`: O(n) → O(k)
- `getPlayerClaims()`: Direct access to player's claims list
### 4.3 Update Intervals
| Operation | Interval | Rationale |
|-----------|----------|-----------|
| Process ended auctions | 20 seconds (400 ticks) | Reduces server load |
| Process expired claims | 20 seconds (400 ticks) | Same cycle as auctions |
| Save dirty data | 60 seconds (1200 ticks) | Batches writes, reduces I/O |
| Process ended auctions | 30 seconds (600 ticks) | Reduces server load |
| Save auction data | 30 seconds (600 ticks) | Batches writes, reduces I/O |
| Process expired claims | On player login | Per-player, not global tick |
### 4.4 Race Condition Prevention
### 4.4 Data Safety
1. **Bid vs Auction End** - Double-check `isExpired()` inside write lock
2. **Currency Deduction** - Withdraw before updating listing
3. **Previous Bidder Refund** - Store info before modification
4. **Concurrent Saves** - `AtomicBoolean` prevents duplicates
5. **Index Consistency** - All updates within same write lock
1. **Currency Deduction** - Withdraw before updating listing
2. **Previous Bidder Refund** - Store info before modification
3. **Concurrent Saves** - `AtomicBoolean` prevents duplicates
4. **Offline Player Claims** - Loaded async on CNPC+ thread, saved to PlayerData
5. **Claim Migration** - Old claims in auction.dat migrated to PlayerData on load
### 4.5 Server Shutdown Handling
@@ -391,12 +461,14 @@ constants/
controllers/
AuctionController.java ✅ (with threading, indices, Vault support)
AuctionConfigSync.java ✅ (server-side config sync utility)
controllers/data/
AuctionListing.java ✅
AuctionClaim.java ✅
AuctionFilter.java ✅
AuctionNotification.java ✅
AuctionBlacklist.java ✅ (item blacklist checking + bypass)
PlayerTradeData.java ✅ (currency + claims storage per player)
roles/
RoleAuctioneer.java ✅
@@ -428,10 +500,18 @@ client/gui/util/
GuiAuctionNavButton.java ✅
network/packets/player/
AuctionDataPacket.java ✅
AuctionActionPacket.java ✅
```
### New Commands
```
command/
AuctionCommand.java ✅ /cnpc auction with list, view, open, blacklist
command/auction/
BlacklistSubCommand.java ✅ /cnpc auction blacklist subcommands
```
### Files Modified
```
@@ -444,6 +524,7 @@ ClientProxy.java ✅ Handle auction GUI types
DataAdvanced.java ✅ Handle Auctioneer role
ConfigMarket.java ✅ Added Auction section
LoginPacket.java ✅ Sync auction config to client
PlayerData.java ✅ Added tradeData field (currency + claims, shared across profiles)
en_US.lang ✅ Added auction lang keys
```
@@ -465,19 +546,23 @@ public static long MaxBalance = Long.MAX_VALUE; // Balance cap
// =========================================
// Auction (Market.Auction)
// =========================================
public static final int MAX_TRADE_SLOTS = 45; // 9x5 grid maximum
public static final int MIN_TRADE_SLOTS = 1; // Minimum allowed
public static boolean AuctionEnabled = true;
public static int AuctionDurationHours = 48; // Auction length
public static int AuctionDurationHours = 24; // Auction length (hours)
public static long ListingFee = 10; // Fee to create listing
public static long MinimumListingPrice = 1; // Minimum starting price
public static double SalesTaxPercent = 0.05; // 5% tax on sales
public static int DefaultMaxListings = 10; // Per player
public static int DefaultMaxTrades = 8; // Per player (listings + bids + claims)
public static int SnipeProtectionMinutes = 2; // Anti-snipe timer
public static int ClaimExpirationDays = 30; // Days before unclaimed deleted
public static int ClaimExpirationDays = 20; // Days before unclaimed deleted
public static double MinBidIncrementPercent = 0.05; // 5% minimum bid increase
// =========================================
// Auction Logging
// =========================================
public static boolean AuctionLoggingEnabled = true;
public static boolean AuctionLoggingEnabled = false;
public static boolean LogAuctionCreated = true;
public static boolean LogAuctionBid = true;
public static boolean LogAuctionBuyout = true;
@@ -485,11 +570,82 @@ public static boolean LogAuctionSold = true;
public static boolean LogAuctionExpired = true;
public static boolean LogAuctionCancelled = true;
public static boolean LogAuctionClaimed = true;
// =========================================
// Auction Blacklist (Market.Auction.Blacklist)
// =========================================
public static boolean BlacklistEnabled = true;
public static String[] BlacklistedItems = {}; // Registry names (supports wildcards)
public static String[] BlacklistedMods = {}; // Mod IDs to block entirely
public static String[] BlacklistedNBTTags = {}; // NBT tag keys to block
```
### Auction Permissions
| Permission | Description |
|------------|-------------|
| `customnpcs.auction.trades.*` | Unlimited trade slots (up to 45) |
| `customnpcs.auction.trades.X` | Exactly X trade slots (where X is 1-45) |
| `customnpcs.auction.blacklist.bypass` | Bypass item blacklist restrictions |
**Trade Slot Permissions:**
- Default is 8 trade slots
- Permissions only **increase** the limit (permissions below 8 are ignored)
- Maximum is capped at 45 (9x5 grid)
- Trade slots = Active Listings + Active Bids + Pending Claims
**Blacklist Bypass:**
- OP players or those with `customnpcs.auction.blacklist.bypass` can list blacklisted items
- Soulbound and Profile-Slotbound items are **always** blocked (no bypass)
---
## 7. Commands
### `/cnpc auction` Command
Allows interaction with the Auction House without needing an Auctioneer NPC.
| Subcommand | Usage | Permission | Description |
|------------|-------|------------|-------------|
| `list` | `/cnpc auction list [page]` | OP | View active auctions in chat |
| `view` | `/cnpc auction view <player>` | OP | View a player's auction activity |
| `open` | `/cnpc auction open` | Any | Opens the Auction House GUI |
**Examples:**
```
/cnpc auction list - Show first page of active auctions
/cnpc auction list 2 - Show page 2 of auctions
/cnpc auction view Steve - Show Steve's listings, bids, and claims
/cnpc auction open - Open the full Auction House GUI
```
### `/cnpc auction blacklist` Command
Manage the auction item blacklist (runtime changes, not persisted to config).
| Subcommand | Usage | Permission | Description |
|------------|-------|------------|-------------|
| `add` | `/cnpc auction blacklist add <item\|mod\|nbt> <value>` | OP | Add to blacklist |
| `remove` | `/cnpc auction blacklist remove <item\|mod\|nbt> <value>` | OP | Remove from blacklist |
| `list` | `/cnpc auction blacklist list [item\|mod\|nbt]` | OP | List blacklisted entries |
| `reload` | `/cnpc auction blacklist reload` | OP | Reload from config |
| `check` | `/cnpc auction blacklist check` | Any | Check if held item is blacklisted |
**Examples:**
```
/cnpc auction blacklist add item minecraft:diamond_sword
/cnpc auction blacklist add item minecraft:diamond* (wildcard)
/cnpc auction blacklist add mod industrialcraft2
/cnpc auction blacklist add nbt ench
/cnpc auction blacklist remove item minecraft:diamond_sword
/cnpc auction blacklist list item
/cnpc auction blacklist check (checks held item)
```
---
## 7. Implementation Status
## 8. Implementation Status
### Phase 1: Currency System - COMPLETE ✅
### Phase 2: Trader Enhancements - COMPLETE ✅
@@ -509,31 +665,65 @@ public static boolean LogAuctionClaimed = true;
### Remaining Work
- [ ] GuiManageAuction (Admin GUI)
- [ ] Permission system integration
- [ ] **GuiManageAuction (Admin GUI)** - Admin interface to view all auctions, cancel listings, manage claims
- [ ] Additional testing
### Implemented Since Initial Plan
- [x] **`/auction` Command** - Console/player command with subcommands:
- `/auction list [page]` - View active auctions from command line
- `/auction view <player>` - View a player's auction activity (listings, bids, claims)
- `/auction open` - Open Auction House GUI without needing an Auctioneer NPC
- [x] **Permission system integration** - Per-player trade slot limits via `customnpcs.auction.trades.X` permissions
- Default: 8 trade slots
- Maximum: 45 (9x5 grid)
- Synced to client when viewing My Trades
- Cached on login for performance
- [x] **Minimum Listing Price** - Configurable minimum starting price (default: 1)
- [x] **Buyout enabled by default** - Buyout toggle is ON by default when creating listings
- [x] **Search by seller** - Filter can now search by item name OR seller name
- [x] **Rebid prevention** - Players cannot rebid on auctions where they're already the highest bidder
- [x] **Buyout price difference** - When buying out an auction you're bidding on, only pay the difference
- [x] **Immediate claim cleanup** - Claims are removed from storage immediately when claimed
- [x] **Listing cleanup** - Ended listings are removed when all related claims are processed
- [x] **Live notifications** - Notifications sent immediately to online players instead of queued
- [x] **Login claim summary** - Shows detailed claim summary on login (X items and Y currency to claim)
- [x] **PlayerTradeData** - Merged currency + claims into single class (shared across profiles)
- [x] **No locking system** - Pure ConcurrentHashMap for better performance
- [x] **30-second intervals** - Combined process/save cycle for simplicity
- [x] **Async offline player claims** - Claims added to offline players via CNPC+ thread
- [x] **Claim migration** - Old claims in auction.dat automatically migrated to PlayerData
- [x] **Item Blacklist System** - Block items by registry name, mod, NBT tag, or wildcard pattern
- Auto-blocks Soulbound and Profile-Slotbound items (checks `RPGCore/Requirements`)
- Per-player bypass via `customnpcs.auction.blacklist.bypass` permission
- Runtime add/remove commands (not persisted to config)
- Check command to test held item
---
## 8. Design Decisions
## 9. Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Claims System | Everything to claims | Prevents offline handling issues |
| Claims Storage | In PlayerData, not auction.dat | Claims persist with player, reduces auction.dat size, allows offline claim addition |
| Thread Safety | ConcurrentHashMap (no locks) | Minecraft is single-threaded for gameplay; locks add overhead without benefit |
| Vault Integration | Via PlayerCurrencyData | Single point of delegation, reused by Trader & Auction |
| Update Interval | 20 seconds | Performance vs responsiveness tradeoff |
| Update Interval | 30 seconds | Performance vs responsiveness tradeoff |
| Player Indices | ConcurrentHashMap + Sets | O(1) lookups, thread-safe |
| Async Saves | CNPC+ Thread | Doesn't block server tick |
| Offline Claims | Load PlayerData async, add claim, save | Claims can be added to offline players without blocking |
| Config Sync | LoginPacket → AuctionClientConfig | Client never accesses server config directly |
| Item Blacklist | Static utility class | Simple, fast checks without instantiation; auto-blocks bound items |
| Bound Item Detection | Check `RPGCore/Requirements` NBT path | Uses AttributeItemUtil constants for consistency |
---
## 9. Future Considerations (Not in 1.11)
## 10. Future Considerations (Not in 1.11)
- Category-based filtering
- Advanced blacklist system (items, mods, NBT patterns)
- Auction history/statistics
- Command-based auction access
- Auction house tax sink
- Reserve prices
- Private auctions
- Category-based filtering (by item type, mod, etc.)
- Auction history/statistics tracking
- Auction house tax sink (currency removed from economy)
- Reserve prices (hidden minimum price)
- Private auctions (invite-only)
- Watchlist/favorites system
- Price history graphs
+127
View File
@@ -0,0 +1,127 @@
# 🏪 CustomNPC+ 1.11 - Market Update 🏪
---
The **Market Update** brings a complete economy system to CNPC+! Create bustling marketplaces with **Traders** that have limited stock, or let players trade with each other through the brand new **Auction House**!
---
## Currency System 💰
CNPC+ now has a **built-in currency** used across all Market features!
- **Fully Configurable** - Name it Coins, Gold, Credits, whatever fits your world
- **Profile Persistent** - Currency carries across all your character slots
- **Vault Support** - CNPC+ integrates seamlessly with Bukkit-compatible economy plugins if Vault is installed
> Full Scripting API support for currency operations on players.
---
## Auction House 🏛️
A **global player marketplace** where players can list items, bid against each other, and purchase instantly!
### Selling
List any item with a **Starting Price** for competitive bidding. Set an optional **Buyout Price** for players who want to skip the wait and buy immediately.
> A configurable **Listing Fee** is charged when creating auctions, and a **Sales Tax** is taken from successful sales.
### Bidding
Compete for items by placing bids! **Snipe Protection** extends auctions when last-second bids come in, giving everyone a fair shot. If you get outbid, your currency is **automatically refunded**.
### Buyout
See something you need right now? Hit **Buy Now** to purchase instantly at the seller's Buyout Price. If you're currently the highest bidder on an item, you only pay the **difference** between your bid and the buyout.
### My Trades
One screen to manage all your auction activity:
- **Active Listings** - Items you're currently selling
- **Active Bids** - Auctions you're competing in
- **Ready to Claim** - Won items and currency waiting for pickup
> Players can **cancel** their own listings. Items are returned as a claim.
### Notifications
Get notified immediately when your item sells, when you're outbid, or when you win an auction. Offline? You'll get a full **claim summary** when you log back in showing exactly what's waiting for you.
### Trade Slots
Each player has a limited number of **Trade Slots** (default 8). Trade Slots are shared between Active Listings, Active Bids, and Pending Claims.
> Server owners can grant players more slots via permissions up to a maximum of 45.
---
## Item Blacklist 🚫
Server owners have full control over what items can be listed on the Auction House.
**Block by:**
- **Item** - Specific registry names like `minecraft:diamond_sword`
- **Wildcard** - Patterns like `minecraft:diamond*` to block all diamond items
- **Mod** - Block all items from a specific mod
- **NBT Tag** - Block items containing certain NBT data
> **Soulbound** and **Profile-Slotbound** items are always blocked automatically. Bound items cannot be traded!
**Blacklist Commands:**
- `/kam auction blacklist add <item|mod|nbt> <value>` - Add entry
- `/kam auction blacklist remove <item|mod|nbt> <value>` - Remove entry
- `/kam auction blacklist list` - View all blacklisted entries
- `/kam auction blacklist check` - Check if your held item is blacklisted
- `/kam auction blacklist reload` - Reload from config
---
## Trader Enhancements 🛒
### Stock System
Traders now support **limited stock** with automatic restocking!
**Stock Modes:**
- **Global** - All players share the same stock pool
- **Per-Player** - Each player has their own purchase limits
**Restock Timers:**
- **MC Daily / Weekly** - Restock based on Minecraft time
- **RL Daily / Weekly** - Restock based on real-life time
- **Custom** - Set your own interval in MC ticks or real-time
**Visual Feedback:**
- Stock count displayed next to each trade
- **Out of Stock** overlay when unavailable
- Live **countdown timer** until next restock
> Admins can manually reset stock or cooldowns via the Trader GUI.
### Currency Trades
Trades can now require **currency on top of items**. Combine material costs with coin costs for balanced economy progression!
---
## Auctioneer Role 🧑‍💼
New NPC role for the Auction House! Set the role on any NPC and players can access the full Auction House by interacting with them.
> No configuration needed
### Auction Command
Players can also access the Auction House via command:
- `/kam auction open` - Opens the Auction House GUI
**Admin Commands:**
- `/kam auction list [page]` - View active auctions in chat
- `/kam auction view <player>` - View a player's auction activity
---
+1 -1
Submodule src/api updated: a6b4a131c8...2e5d86993c.
@@ -2,9 +2,16 @@ package foxz.command;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import java.util.List;
/**
* @deprecated The /noppes command is deprecated and no longer supported.
* Please use the /kam command instead for all CNPC+ operations.
*/
@Deprecated
public class CommandNoppes extends CommandBase {
public CmdNoppes noppes = new CmdNoppes(this);
@@ -21,6 +28,15 @@ public class CommandNoppes extends CommandBase {
@Override
public void processCommand(ICommandSender var1, String[] var2) {
// Send deprecation warning
var1.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[DEPRECATED] " +
EnumChatFormatting.YELLOW + "The /noppes command is deprecated and no longer supported."));
var1.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
"Please use " + EnumChatFormatting.GREEN + "/kam" + EnumChatFormatting.YELLOW + " commands instead."));
var1.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY +
"Type " + EnumChatFormatting.WHITE + "/kam help" + EnumChatFormatting.GRAY + " for available commands."));
// Still execute the command for backwards compatibility
noppes.processCommand(var1, var2);
}
@@ -1,10 +1,10 @@
package kamkeel.npcs.client.renderer;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import kamkeel.npcs.controllers.data.ability.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.player.EntityPlayer;
@@ -0,0 +1,179 @@
package kamkeel.npcs.command;
import kamkeel.npcs.command.auction.BlacklistSubCommand;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import noppes.npcs.NoppesUtilServer;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.constants.EnumGuiType;
import noppes.npcs.controllers.AuctionController;
import noppes.npcs.controllers.PlayerDataController;
import noppes.npcs.controllers.data.AuctionListing;
import noppes.npcs.controllers.data.PlayerData;
import java.util.List;
import java.util.UUID;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendMessage;
import static kamkeel.npcs.util.ColorUtil.sendResult;
public class AuctionCommand extends CommandKamkeelBase {
public AuctionCommand() {
super();
registerNestedCommand(new BlacklistSubCommand());
}
@Override
public String getCommandName() {
return "auction";
}
@Override
public String getDescription() {
return "Auction operations";
}
@SubCommand(
desc = "List active auctions",
usage = "[page]"
)
public void list(ICommandSender sender, String[] args) throws CommandException {
if (!ConfigMarket.AuctionEnabled) {
sendError(sender, "Auction system is disabled");
return;
}
AuctionController controller = AuctionController.getInstance();
if (controller == null) {
sendError(sender, "Auction system is not available");
return;
}
int page = 1;
if (args.length > 0) {
try {
page = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) {
sendError(sender, "Invalid page number: " + args[0]);
return;
}
}
List<AuctionListing> listings = controller.getActiveListings(null, page - 1, 10);
int total = controller.getTotalActiveListings(null);
int totalPages = Math.max(1, (int) Math.ceil((double) total / 10));
if (listings.isEmpty()) {
sendMessage(sender, "\u00A77No active auctions found");
return;
}
sendMessage(sender, String.format("\u00A76=== Active Auctions (Page %d/%d) ===", page, totalPages));
for (AuctionListing listing : listings) {
String itemName = listing.item != null ? listing.item.getDisplayName() : "Unknown";
String price = listing.hasBids() ?
String.format("%,d (bid)", listing.currentBid) :
String.format("%,d (start)", listing.startingPrice);
sendMessage(sender, String.format("\u00A7e%s \u00A77by \u00A7b%s\u00A77 - \u00A76%s %s",
itemName, listing.sellerName, price, ConfigMarket.CurrencyName));
}
}
@SubCommand(
desc = "View player's auction activity",
usage = "<player>"
)
public void view(ICommandSender sender, String[] args) throws CommandException {
if (!ConfigMarket.AuctionEnabled) {
sendError(sender, "Auction system is disabled");
return;
}
AuctionController controller = AuctionController.getInstance();
if (controller == null) {
sendError(sender, "Auction system is not available");
return;
}
String playername = args[0];
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
if (dataList.isEmpty()) {
sendError(sender, "Unknown player: " + playername);
return;
}
for (PlayerData playerdata : dataList) {
UUID playerUUID = UUID.fromString(playerdata.uuid);
List<AuctionListing> activeListings = controller.getPlayerActiveListings(playerUUID);
List<AuctionListing> activeBids = controller.getPlayerActiveBids(playerUUID);
int claimCount = controller.getPlayerClaims(playerUUID).size();
sendMessage(sender, String.format("\u00A76=== Auction Activity for %s ===", playerdata.playername));
sendMessage(sender, String.format("\u00A77Active Listings: \u00A7e%d", activeListings.size()));
sendMessage(sender, String.format("\u00A77Active Bids: \u00A7e%d", activeBids.size()));
sendMessage(sender, String.format("\u00A77Pending Claims: \u00A7e%d", claimCount));
if (!activeListings.isEmpty()) {
sendMessage(sender, "\u00A7a--- Selling ---");
for (AuctionListing listing : activeListings) {
String itemName = listing.item != null ? listing.item.getDisplayName() : "Unknown";
String price = listing.hasBids() ?
String.format("%,d (bid)", listing.currentBid) :
String.format("%,d (start)", listing.startingPrice);
sendMessage(sender, String.format(" \u00A7e%s \u00A77- \u00A76%s", itemName, price));
}
}
if (!activeBids.isEmpty()) {
sendMessage(sender, "\u00A7e--- Bidding ---");
for (AuctionListing bid : activeBids) {
String itemName = bid.item != null ? bid.item.getDisplayName() : "Unknown";
sendMessage(sender, String.format(" \u00A7e%s \u00A77- \u00A76%,d %s",
itemName, bid.currentBid, ConfigMarket.CurrencyName));
}
}
}
}
@SubCommand(
desc = "Open the Auction GUI",
usage = "",
permission = 0
)
public void open(ICommandSender sender, String[] args) throws CommandException {
if (!ConfigMarket.AuctionEnabled) {
sendError(sender, "Auction system is disabled");
return;
}
if (!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by players");
return;
}
EntityPlayer player = (EntityPlayer) sender;
// Open the auction GUI without requiring an NPC
NoppesUtilServer.sendOpenGui(player, EnumGuiType.PlayerAuction, null);
sendResult(sender, "Opening Auction...");
}
@Override
public List addTabCompletionOptions(ICommandSender sender, String[] args) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, getAllSubCommandNames());
}
// Delegate to nested commands for deeper tab completion
List nestedCompletions = getNestedTabCompletions(sender, args);
if (nestedCompletions != null) {
return nestedCompletions;
}
return null;
}
}
@@ -10,6 +10,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.server.MinecraftServer;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.config.ConfigMain;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.scripted.CustomNPCsException;
import java.lang.reflect.Method;
@@ -44,6 +45,9 @@ public class CommandKamkeel extends CommandBase {
registerCommand(new CommandCommand());
registerCommand(new EffectCommand());
registerCommand(new AbilityCommand());
registerCommand(new MoneyCommand());
if (ConfigMarket.AuctionEnabled)
registerCommand(new AuctionCommand());
if (ConfigMain.AttributesEnabled)
registerCommand(new AttributeCommand());
}
@@ -11,13 +11,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
public abstract class CommandKamkeelBase extends CommandBase {
public Map<String, Method> subcommands = new HashMap<String, Method>();
public Map<String, CommandKamkeelBase> nestedCommands = new HashMap<String, CommandKamkeelBase>();
public CommandKamkeelBase() {
for (Method m : this.getClass().getDeclaredMethods()) {
@@ -31,6 +29,14 @@ public abstract class CommandKamkeelBase extends CommandBase {
}
}
/**
* Register a nested command class as a subcommand.
* The nested command's getCommandName() becomes the subcommand name.
*/
public void registerNestedCommand(CommandKamkeelBase command) {
nestedCommands.put(command.getCommandName().toLowerCase(), command);
}
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
@@ -51,7 +57,7 @@ public abstract class CommandKamkeelBase extends CommandBase {
}
public boolean runSubCommands() {
return !subcommands.isEmpty();
return !subcommands.isEmpty() || !nestedCommands.isEmpty();
}
@Retention(value = RetentionPolicy.RUNTIME)
@@ -71,6 +77,22 @@ public abstract class CommandKamkeelBase extends CommandBase {
}
public void processSubCommand(ICommandSender sender, String command, String[] args) throws CommandException {
// Check nested commands first
CommandKamkeelBase nested = nestedCommands.get(command.toLowerCase());
if (nested != null) {
if (!canSendNestedCommand(sender, nested)) {
throw new CommandException("You are not allowed to use this command: " + command);
}
if (args.length == 0 || !nested.runSubCommands()) {
nested.processCommand(sender, args);
} else {
nested.processSubCommand(sender, args[0], Arrays.copyOfRange(args, 1, args.length));
}
return;
}
// Fall back to method subcommands
Method m = subcommands.get(command.toLowerCase());
if (m == null)
throw new CommandException("Unknown subcommand " + command);
@@ -132,4 +154,55 @@ public abstract class CommandKamkeelBase extends CommandBase {
return false;
}
public boolean canSendNestedCommand(ICommandSender sender, CommandKamkeelBase nested) {
int permission = nested.getRequiredPermissionLevel();
String subCommand = nested.getCommandName();
if (sender.canCommandSenderUseCommand(permission, getSubUniversalPermission())) {
return true;
}
if (sender.canCommandSenderUseCommand(permission, getSubCommandPermission(subCommand))) {
return true;
}
if (sender instanceof EntityPlayer) {
return CustomNpcsPermissions.hasCustomPermission((EntityPlayer) sender, getSubCommandPermission(subCommand));
}
return false;
}
// =========================================
// Tab Completion Helpers
// =========================================
/**
* Get all subcommand names (both method-based and nested)
*/
public String[] getAllSubCommandNames() {
Set<String> names = new HashSet<>();
names.addAll(subcommands.keySet());
names.addAll(nestedCommands.keySet());
return names.toArray(new String[0]);
}
/**
* Get tab completions for nested command at given depth
*/
public List getNestedTabCompletions(ICommandSender sender, String[] args) {
if (args.length == 0) return null;
String subCmd = args[0].toLowerCase();
CommandKamkeelBase nested = nestedCommands.get(subCmd);
if (nested != null && args.length > 1) {
// Delegate to nested command
String[] nestedArgs = Arrays.copyOfRange(args, 1, args.length);
return nested.addTabCompletionOptions(sender, nestedArgs);
}
return null;
}
}
@@ -0,0 +1,168 @@
package kamkeel.npcs.command;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.controllers.PlayerDataController;
import noppes.npcs.controllers.data.PlayerData;
import noppes.npcs.controllers.data.PlayerTradeData;
import java.util.List;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendResult;
public class MoneyCommand extends CommandKamkeelBase {
@Override
public String getCommandName() {
return "money";
}
@Override
public String getDescription() {
return "Currency operations";
}
@SubCommand(
desc = "Show player's balance",
usage = "<player>"
)
public void balance(ICommandSender sender, String[] args) throws CommandException {
String playername = args[0];
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
if (dataList.isEmpty()) {
sendError(sender, "Unknown player: " + playername);
return;
}
for (PlayerData playerdata : dataList) {
PlayerTradeData currency = playerdata.tradeData;
long balance = currency.getBalance();
sendResult(sender, String.format("Player \u00A7b%s\u00A77 has \u00A76%,d %s",
playerdata.playername, balance, ConfigMarket.CurrencyName));
}
}
@SubCommand(
desc = "Give currency to player",
usage = "<player> <amount>"
)
public void give(ICommandSender sender, String[] args) throws CommandException {
String playername = args[0];
long amount;
try {
amount = Long.parseLong(args[1]);
} catch (NumberFormatException ex) {
sendError(sender, "Invalid amount: " + args[1]);
return;
}
if (amount <= 0) {
sendError(sender, "Amount must be positive");
return;
}
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
if (dataList.isEmpty()) {
sendError(sender, "Unknown player: " + playername);
return;
}
for (PlayerData playerdata : dataList) {
PlayerTradeData currency = playerdata.tradeData;
if (currency.deposit(amount)) {
playerdata.save();
sendResult(sender, String.format("Gave \u00A76%,d %s\u00A77 to player \u00A7b%s\u00A77. New balance: \u00A76%,d",
amount, ConfigMarket.CurrencyName, playerdata.playername, currency.getBalance()));
} else {
sendError(sender, String.format("Failed to give currency to %s (would exceed max balance)",
playerdata.playername));
}
}
}
@SubCommand(
desc = "Withdraw currency from player",
usage = "<player> <amount>"
)
public void withdraw(ICommandSender sender, String[] args) throws CommandException {
String playername = args[0];
long amount;
try {
amount = Long.parseLong(args[1]);
} catch (NumberFormatException ex) {
sendError(sender, "Invalid amount: " + args[1]);
return;
}
if (amount <= 0) {
sendError(sender, "Amount must be positive");
return;
}
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
if (dataList.isEmpty()) {
sendError(sender, "Unknown player: " + playername);
return;
}
for (PlayerData playerdata : dataList) {
PlayerTradeData currency = playerdata.tradeData;
if (currency.withdraw(amount)) {
playerdata.save();
sendResult(sender, String.format("Withdrew \u00A76%,d %s\u00A77 from player \u00A7b%s\u00A77. New balance: \u00A76%,d",
amount, ConfigMarket.CurrencyName, playerdata.playername, currency.getBalance()));
} else {
sendError(sender, String.format("Failed to withdraw from %s (insufficient funds)",
playerdata.playername));
}
}
}
@SubCommand(
desc = "Set player's balance",
usage = "<player> <amount>"
)
public void set(ICommandSender sender, String[] args) throws CommandException {
String playername = args[0];
long amount;
try {
amount = Long.parseLong(args[1]);
} catch (NumberFormatException ex) {
sendError(sender, "Invalid amount: " + args[1]);
return;
}
if (amount < 0) {
sendError(sender, "Amount cannot be negative");
return;
}
List<PlayerData> dataList = PlayerDataController.Instance.getPlayersData(sender, playername);
if (dataList.isEmpty()) {
sendError(sender, "Unknown player: " + playername);
return;
}
for (PlayerData playerdata : dataList) {
PlayerTradeData currency = playerdata.tradeData;
currency.setBalance(amount);
playerdata.save();
sendResult(sender, String.format("Set balance of player \u00A7b%s\u00A77 to \u00A76%,d %s",
playerdata.playername, currency.getBalance(), ConfigMarket.CurrencyName));
}
}
@Override
public List addTabCompletionOptions(ICommandSender par1, String[] args) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, new String[]{"balance", "give", "withdraw", "set"});
}
return null;
}
}
@@ -0,0 +1,226 @@
package kamkeel.npcs.command.auction;
import kamkeel.npcs.command.CommandKamkeelBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import noppes.npcs.controllers.data.AuctionBlacklist;
import java.util.List;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendMessage;
import static kamkeel.npcs.util.ColorUtil.sendResult;
public class BlacklistSubCommand extends CommandKamkeelBase {
@Override
public String getCommandName() {
return "blacklist";
}
@Override
public String getDescription() {
return "Manage auction item blacklist";
}
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
// Show help when called with no subcommand
sendHelp(sender);
}
private void sendHelp(ICommandSender sender) {
sendMessage(sender, "\u00A76=== Auction Blacklist Commands ===");
sendMessage(sender, "\u00A7e/cnpc auction blacklist add <item|mod|nbt> <value>");
sendMessage(sender, "\u00A7e/cnpc auction blacklist remove <item|mod|nbt> <value>");
sendMessage(sender, "\u00A7e/cnpc auction blacklist list [item|mod|nbt]");
sendMessage(sender, "\u00A7e/cnpc auction blacklist reload");
sendMessage(sender, "\u00A7e/cnpc auction blacklist check \u00A77(checks held item)");
}
@SubCommand(
desc = "Add item, mod, or NBT tag to blacklist",
usage = "<item|mod|nbt> <value>"
)
public void add(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 2) {
sendError(sender, "Usage: /cnpc auction blacklist add <item|mod|nbt> <value>");
return;
}
String type = args[0].toLowerCase();
String value = args[1];
switch (type) {
case "item":
if (AuctionBlacklist.addItem(value)) {
sendResult(sender, "Added item to blacklist: " + value);
} else {
sendError(sender, "Failed to add item to blacklist");
}
break;
case "mod":
if (AuctionBlacklist.addMod(value)) {
sendResult(sender, "Added mod to blacklist: " + value);
} else {
sendError(sender, "Failed to add mod to blacklist");
}
break;
case "nbt":
if (AuctionBlacklist.addNBTTag(value)) {
sendResult(sender, "Added NBT tag to blacklist: " + value);
} else {
sendError(sender, "Failed to add NBT tag to blacklist");
}
break;
default:
sendError(sender, "Unknown type: " + type + ". Use: item, mod, or nbt");
}
}
@SubCommand(
desc = "Remove item, mod, or NBT tag from blacklist",
usage = "<item|mod|nbt> <value>"
)
public void remove(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 2) {
sendError(sender, "Usage: /cnpc auction blacklist remove <item|mod|nbt> <value>");
return;
}
String type = args[0].toLowerCase();
String value = args[1];
switch (type) {
case "item":
if (AuctionBlacklist.removeItem(value)) {
sendResult(sender, "Removed item from blacklist: " + value);
} else {
sendError(sender, "Item not found in blacklist: " + value);
}
break;
case "mod":
if (AuctionBlacklist.removeMod(value)) {
sendResult(sender, "Removed mod from blacklist: " + value);
} else {
sendError(sender, "Mod not found in blacklist: " + value);
}
break;
case "nbt":
if (AuctionBlacklist.removeNBTTag(value)) {
sendResult(sender, "Removed NBT tag from blacklist: " + value);
} else {
sendError(sender, "NBT tag not found in blacklist: " + value);
}
break;
default:
sendError(sender, "Unknown type: " + type + ". Use: item, mod, or nbt");
}
}
@SubCommand(
desc = "List blacklisted items, mods, or NBT tags",
usage = "[item|mod|nbt]"
)
public void list(ICommandSender sender, String[] args) throws CommandException {
String filter = args.length > 0 ? args[0].toLowerCase() : "all";
if (filter.equals("all") || filter.equals("item")) {
List<String> items = AuctionBlacklist.getBlacklistedItems();
sendMessage(sender, "\u00A76=== Blacklisted Items (" + items.size() + ") ===");
if (items.isEmpty()) {
sendMessage(sender, "\u00A77 (none)");
} else {
for (String item : items) {
sendMessage(sender, "\u00A7e " + item);
}
}
}
if (filter.equals("all") || filter.equals("mod")) {
List<String> mods = AuctionBlacklist.getBlacklistedMods();
sendMessage(sender, "\u00A76=== Blacklisted Mods (" + mods.size() + ") ===");
if (mods.isEmpty()) {
sendMessage(sender, "\u00A77 (none)");
} else {
for (String mod : mods) {
sendMessage(sender, "\u00A7e " + mod);
}
}
}
if (filter.equals("all") || filter.equals("nbt")) {
List<String> tags = AuctionBlacklist.getBlacklistedNBTTags();
sendMessage(sender, "\u00A76=== Blacklisted NBT Tags (" + tags.size() + ") ===");
if (tags.isEmpty()) {
sendMessage(sender, "\u00A77 (none)");
} else {
for (String tag : tags) {
sendMessage(sender, "\u00A7e " + tag);
}
}
}
sendMessage(sender, "\u00A77Note: Soulbound and Profile-Slotbound items are always blocked.");
}
@SubCommand(
desc = "Reload blacklist from config"
)
public void reload(ICommandSender sender, String[] args) throws CommandException {
AuctionBlacklist.reload();
sendResult(sender, "Blacklist reloaded from config");
}
@SubCommand(
desc = "Check if held item is blacklisted",
permission = 0
)
public void check(ICommandSender sender, String[] args) throws CommandException {
if (!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by players");
return;
}
EntityPlayer player = (EntityPlayer) sender;
ItemStack heldItem = player.getHeldItem();
if (heldItem == null) {
sendError(sender, "You are not holding an item");
return;
}
String registryName = AuctionBlacklist.getRegistryName(heldItem);
sendMessage(sender, "\u00A76=== Blacklist Check ===");
sendMessage(sender, "\u00A77Item: \u00A7e" + heldItem.getDisplayName());
sendMessage(sender, "\u00A77Registry: \u00A7e" + (registryName != null ? registryName : "unknown"));
boolean isBlacklisted = AuctionBlacklist.isBlacklisted(heldItem);
boolean canBypass = AuctionBlacklist.canBypass(player);
if (isBlacklisted) {
sendMessage(sender, "\u00A7cStatus: BLACKLISTED");
if (canBypass) {
sendMessage(sender, "\u00A7aYou have bypass permission");
}
} else {
sendMessage(sender, "\u00A7aStatus: ALLOWED");
}
}
@Override
public List addTabCompletionOptions(ICommandSender sender, String[] args) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, getAllSubCommandNames());
}
if (args.length == 2) {
String subCmd = args[0].toLowerCase();
if (subCmd.equals("add") || subCmd.equals("remove") || subCmd.equals("list")) {
return getListOfStringsMatchingLastWord(args, new String[]{"item", "mod", "nbt"});
}
}
return null;
}
}
@@ -832,6 +832,15 @@ public class ProfileController implements IProfileHandler {
else
compound = (NBTTagCompound) compound.copy();
playerData.setNBT(compound);
// Trade data is shared across all profile slots - copy from player's live data
PlayerData liveData = PlayerData.get(player);
if (liveData != null) {
NBTTagCompound tradeCompound = new NBTTagCompound();
liveData.tradeData.writeToNBT(tradeCompound);
playerData.tradeData.readFromNBT(tradeCompound);
}
return playerData;
}
@@ -0,0 +1,392 @@
package kamkeel.npcs.controllers;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.world.World;
import noppes.npcs.CustomNpcs;
import noppes.npcs.LogWriter;
import noppes.npcs.api.ITelegraph;
import noppes.npcs.api.ITelegraphInstance;
import noppes.npcs.api.IWorld;
import noppes.npcs.api.entity.IEntity;
import noppes.npcs.api.handler.ITelegraphHandler;
import noppes.npcs.scripted.ScriptTelegraph;
import noppes.npcs.scripted.ScriptTelegraphInstance;
import noppes.npcs.util.NBTJsonUtil;
import java.io.File;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* Server-side controller for managing telegraphs.
* Provides factory methods for creating telegraphs and spawn methods for sending to clients.
* Also manages saved telegraph presets.
*/
public class TelegraphController implements ITelegraphHandler {
public static TelegraphController Instance;
// Saved telegraph presets (name -> Telegraph)
private HashMap<String, Telegraph> savedTelegraphs = new HashMap<>();
// Active server-side telegraph instances (for removal tracking)
private ConcurrentHashMap<String, TelegraphInstance> activeInstances = new ConcurrentHashMap<>();
public TelegraphController() {
}
public static void init() {
Instance = new TelegraphController();
Instance.load();
}
// ═══════════════════════════════════════════════════════════════════════════
// INTERNAL SPAWN METHODS
// ═══════════════════════════════════════════════════════════════════════════
/**
* Spawn a telegraph at a fixed position.
*
* @param telegraph The telegraph configuration
* @param world The world
* @param x X position
* @param y Y position
* @param z Z position
* @param yaw Rotation yaw (for directional telegraphs like LINE/CONE)
* @return The spawned telegraph instance
*/
public TelegraphInstance spawn(Telegraph telegraph, World world, double x, double y, double z, float yaw) {
TelegraphInstance instance = new TelegraphInstance(telegraph, x, y, z, yaw);
instance.setWorld(world);
activeInstances.put(instance.getInstanceId(), instance);
TelegraphSpawnPacket.sendToDimension(instance, world.provider.dimensionId);
return instance;
}
/**
* Spawn a telegraph at a position with default yaw.
*/
public TelegraphInstance spawn(Telegraph telegraph, World world, double x, double y, double z) {
return spawn(telegraph, world, x, y, z, 0);
}
/**
* Spawn a telegraph that follows an entity.
*
* @param telegraph The telegraph configuration
* @param entity The entity to follow
* @return The spawned telegraph instance
*/
public TelegraphInstance spawn(Telegraph telegraph, Entity entity) {
return spawn(telegraph, entity, 0);
}
/**
* Spawn a telegraph that follows an entity with yaw.
*/
public TelegraphInstance spawn(Telegraph telegraph, Entity entity, float yaw) {
TelegraphInstance instance = new TelegraphInstance(telegraph, entity.posX, entity.posY, entity.posZ, yaw);
instance.setWorld(entity.worldObj);
instance.setEntityIdToFollow(entity.getEntityId());
activeInstances.put(instance.getInstanceId(), instance);
TelegraphSpawnPacket.sendToTracking(instance, entity);
return instance;
}
/**
* Spawn a telegraph to a specific player only.
*/
public TelegraphInstance spawnToPlayer(Telegraph telegraph, EntityPlayerMP player, double x, double y, double z, float yaw) {
TelegraphInstance instance = new TelegraphInstance(telegraph, x, y, z, yaw);
instance.setWorld(player.worldObj);
activeInstances.put(instance.getInstanceId(), instance);
TelegraphSpawnPacket.send(instance, player);
return instance;
}
/**
* Spawn a telegraph to all players on the server.
*/
public TelegraphInstance spawnToAll(Telegraph telegraph, World world, double x, double y, double z, float yaw) {
TelegraphInstance instance = new TelegraphInstance(telegraph, x, y, z, yaw);
instance.setWorld(world);
activeInstances.put(instance.getInstanceId(), instance);
TelegraphSpawnPacket.sendToAll(instance);
return instance;
}
/**
* Spawn a telegraph to all players tracking an entity.
*/
public TelegraphInstance spawnToTracking(Telegraph telegraph, Entity entity, double x, double y, double z, float yaw) {
TelegraphInstance instance = new TelegraphInstance(telegraph, x, y, z, yaw);
instance.setWorld(entity.worldObj);
activeInstances.put(instance.getInstanceId(), instance);
TelegraphSpawnPacket.sendToTracking(instance, entity);
return instance;
}
// ═══════════════════════════════════════════════════════════════════════════
// REMOVAL METHODS
// ═══════════════════════════════════════════════════════════════════════════
/**
* Remove a telegraph by instance ID.
*/
public void remove(String instanceId) {
TelegraphInstance instance = activeInstances.remove(instanceId);
if (instance != null && instance.getWorld() != null) {
TelegraphRemovePacket.sendToDimension(instanceId, instance.getDimensionId());
}
}
/**
* Remove a telegraph instance.
*/
public void remove(TelegraphInstance instance) {
if (instance != null) {
remove(instance.getInstanceId());
}
}
/**
* Remove a telegraph for a specific player only.
*/
public void removeFromPlayer(String instanceId, EntityPlayerMP player) {
activeInstances.remove(instanceId);
TelegraphRemovePacket.send(instanceId, player);
}
/**
* Get an active telegraph instance by ID.
*/
public TelegraphInstance getActiveInstance(String instanceId) {
return activeInstances.get(instanceId);
}
// ═══════════════════════════════════════════════════════════════════════════
// PRESET MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════════
/**
* Save a telegraph preset (internal use).
*
* @param name The name for the preset
* @param telegraph The telegraph configuration to save
*/
public void saveInternal(String name, Telegraph telegraph) {
if (name == null || name.isEmpty() || telegraph == null) return;
telegraph.setId(name);
savedTelegraphs.put(name, telegraph);
saveTelegraphFile(name, telegraph);
}
/**
* Get a saved telegraph preset by name (internal use).
*
* @param name The preset name
* @return The telegraph configuration, or null if not found
*/
public Telegraph getInternal(String name) {
return savedTelegraphs.get(name);
}
/**
* Delete a saved telegraph preset.
*
* @param name The preset name
*/
public void delete(String name) {
Telegraph removed = savedTelegraphs.remove(name);
if (removed != null) {
File file = new File(getDir(), name + ".json");
if (file.exists()) {
file.delete();
}
}
}
/**
* Check if a preset exists.
*/
public boolean has(String name) {
return savedTelegraphs.containsKey(name);
}
/**
* Get all saved preset names.
*/
public String[] getSavedNames() {
return savedTelegraphs.keySet().toArray(new String[0]);
}
// ═══════════════════════════════════════════════════════════════════════════
// PERSISTENCE
// ═══════════════════════════════════════════════════════════════════════════
private File getDir() {
return new File(CustomNpcs.getWorldSaveDirectory(), "telegraphs");
}
public void load() {
savedTelegraphs.clear();
LogWriter.info("Loading telegraph presets...");
File dir = getDir();
if (!dir.exists()) {
dir.mkdirs();
return;
}
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
try {
Telegraph telegraph = new Telegraph();
telegraph.readNBT(NBTJsonUtil.LoadFile(file));
String name = file.getName().substring(0, file.getName().length() - 5);
telegraph.setId(name);
savedTelegraphs.put(name, telegraph);
} catch (Exception e) {
LogWriter.error("Error loading telegraph preset: " + file.getName(), e);
}
}
LogWriter.info("Loaded " + savedTelegraphs.size() + " telegraph presets.");
}
private void saveTelegraphFile(String name, Telegraph telegraph) {
try {
File dir = getDir();
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, name + ".json_new");
File file2 = new File(dir, name + ".json");
NBTJsonUtil.SaveFile(file, telegraph.writeNBT());
if (file2.exists()) {
file2.delete();
}
file.renameTo(file2);
} catch (Exception e) {
LogWriter.error("Error saving telegraph preset: " + name, e);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// ITelegraphHandler INTERFACE IMPLEMENTATION
// ═══════════════════════════════════════════════════════════════════════════
@Override
public ITelegraph createCircle(float radius) {
return new ScriptTelegraph(Telegraph.circle(radius));
}
@Override
public ITelegraph createRing(float outerRadius, float innerRadius) {
return new ScriptTelegraph(Telegraph.ring(outerRadius, innerRadius));
}
@Override
public ITelegraph createLine(float length, float width) {
return new ScriptTelegraph(Telegraph.line(length, width));
}
@Override
public ITelegraph createCone(float length, float angle) {
return new ScriptTelegraph(Telegraph.cone(length, angle));
}
@Override
public ITelegraph createPoint() {
return new ScriptTelegraph(Telegraph.point());
}
@Override
public ITelegraph create(String type) {
if (type == null) return null;
try {
TelegraphType telegraphType = TelegraphType.valueOf(type.toUpperCase());
Telegraph telegraph;
switch (telegraphType) {
case CIRCLE:
telegraph = Telegraph.circle(3.0f);
break;
case RING:
telegraph = Telegraph.ring(5.0f, 2.0f);
break;
case LINE:
telegraph = Telegraph.line(5.0f, 2.0f);
break;
case CONE:
telegraph = Telegraph.cone(5.0f, 45.0f);
break;
case POINT:
telegraph = Telegraph.point();
break;
default:
telegraph = Telegraph.circle(3.0f);
}
return new ScriptTelegraph(telegraph);
} catch (IllegalArgumentException e) {
return null;
}
}
@Override
public ITelegraphInstance spawn(ITelegraph telegraph, IWorld world, double x, double y, double z) {
return spawn(telegraph, world, x, y, z, 0);
}
@Override
public ITelegraphInstance spawn(ITelegraph telegraph, IWorld world, double x, double y, double z, float yaw) {
if (telegraph == null || world == null) return null;
Telegraph internal = ((ScriptTelegraph) telegraph).getMCTelegraph();
TelegraphInstance instance = spawn(internal, (World) world.getMCWorld(), x, y, z, yaw);
return new ScriptTelegraphInstance(instance);
}
@Override
public ITelegraphInstance spawn(ITelegraph telegraph, IEntity entity) {
return spawn(telegraph, entity, 0);
}
@Override
public ITelegraphInstance spawn(ITelegraph telegraph, IEntity entity, float yaw) {
if (telegraph == null || entity == null) return null;
Telegraph internal = ((ScriptTelegraph) telegraph).getMCTelegraph();
TelegraphInstance instance = spawn(internal, (Entity) entity.getMCEntity(), yaw);
return new ScriptTelegraphInstance(instance);
}
@Override
public ITelegraph get(String name) {
Telegraph telegraph = savedTelegraphs.get(name);
return telegraph != null ? new ScriptTelegraph(new Telegraph(telegraph)) : null;
}
@Override
public void save(String name, ITelegraph telegraph) {
if (name == null || name.isEmpty() || telegraph == null) return;
Telegraph internal = ((ScriptTelegraph) telegraph).getMCTelegraph();
saveInternal(name, internal);
}
@Override
public void remove(ITelegraphInstance instance) {
if (instance != null) {
remove(instance.getInstanceId());
}
}
}
@@ -2,9 +2,9 @@ package kamkeel.npcs.controllers.data.ability;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.block.Block;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -4,9 +4,9 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -29,7 +29,7 @@ public class AbilityCharge extends Ability {
// Type-specific parameters
private float chargeSpeed = 0.8f;
private float damage = 15.0f;
private float damage = 8.0f;
private float knockback = 3.0f;
private float hitWidth = 1.5f;
@@ -296,7 +296,7 @@ public class AbilityCharge extends Ability {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
this.chargeSpeed = nbt.hasKey("chargeSpeed") ? nbt.getFloat("chargeSpeed") : 0.8f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 15.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 3.0f;
if (nbt.hasKey("hitWidth")) {
this.hitWidth = nbt.getFloat("hitWidth");
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -35,7 +35,7 @@ public class AbilityCutter extends Ability {
private float arcAngle = 90.0f;
private float range = 6.0f;
private float damage = 12.0f;
private float damage = 7.0f;
private float knockback = 1.5f;
private SweepMode sweepMode = SweepMode.SWIPE;
@@ -217,7 +217,7 @@ public class AbilityCutter extends Ability {
public void readTypeNBT(NBTTagCompound nbt) {
this.arcAngle = nbt.hasKey("arcAngle") ? nbt.getFloat("arcAngle") : 90.0f;
this.range = nbt.hasKey("range") ? nbt.getFloat("range") : 6.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 12.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 7.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.5f;
try {
this.sweepMode = SweepMode.valueOf(nbt.getString("sweepMode"));
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
@@ -28,7 +28,7 @@ public class AbilityGuard extends Ability {
private float damageReduction = 0.5f;
private boolean canCounter = false;
private CounterType counterType = CounterType.FLAT;
private float counterValue = 10.0f;
private float counterValue = 6.0f;
private float counterChance = 0.3f;
private String counterSound = "random.wood_click";
private int counterAnimationId = -1;
@@ -207,7 +207,7 @@ public class AbilityGuard extends Ability {
} else if (nbt.hasKey("counterDamage")) {
this.counterValue = nbt.getFloat("counterDamage");
} else {
this.counterValue = 10.0f;
this.counterValue = 6.0f;
}
this.counterChance = nbt.hasKey("counterChance") ? nbt.getFloat("counterChance") : 0.3f;
this.counterSound = nbt.hasKey("counterSound") ? nbt.getString("counterSound") : "random.wood_click";
@@ -4,8 +4,8 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -48,7 +48,7 @@ public class AbilityHazard extends Ability {
private HazardShape shape = HazardShape.CIRCLE;
private PlacementMode placement = PlacementMode.AT_TARGET;
private float damagePerTick = 1.0f;
private float damagePerSecond = 1.0f;
private int damageInterval = 20;
private boolean ignoreInvulnFrames = false;
@@ -290,12 +290,12 @@ public class AbilityHazard extends Ability {
if (damagedThisTick.contains(entity.getEntityId())) continue;
if (!isInZone(entity, npc)) continue;
if (damagePerTick > 0) {
if (damagePerSecond > 0) {
if (ignoreInvulnFrames) {
entity.hurtResistantTime = 0;
}
// Apply damage with scripted event support (no knockback for hazard ticks)
boolean wasHit = applyAbilityDamage(npc, entity, damagePerTick, 0);
// Apply damage with scripted event support (no knockback for hazard)
boolean wasHit = applyAbilityDamage(npc, entity, damagePerSecond, 0);
if (!wasHit) continue; // Skip debuffs if hit was cancelled
}
@@ -369,7 +369,7 @@ public class AbilityHazard extends Ability {
nbt.setFloat("coneAngle", coneAngle);
nbt.setString("shape", shape.name());
nbt.setString("placement", placement.name());
nbt.setFloat("damagePerTick", damagePerTick);
nbt.setFloat("damagePerSecond", damagePerSecond);
nbt.setInteger("damageInterval", damageInterval);
nbt.setBoolean("ignoreInvulnFrames", ignoreInvulnFrames);
nbt.setInteger("slownessLevel", slownessLevel);
@@ -401,7 +401,14 @@ public class AbilityHazard extends Ability {
} catch (Exception e) {
this.placement = PlacementMode.AT_TARGET;
}
this.damagePerTick = nbt.hasKey("damagePerTick") ? nbt.getFloat("damagePerTick") : 1.0f;
// Support legacy "damagePerTick" key for backwards compatibility
if (nbt.hasKey("damagePerSecond")) {
this.damagePerSecond = nbt.getFloat("damagePerSecond");
} else if (nbt.hasKey("damagePerTick")) {
this.damagePerSecond = nbt.getFloat("damagePerTick");
} else {
this.damagePerSecond = 1.0f;
}
this.damageInterval = nbt.hasKey("damageInterval") ? nbt.getInteger("damageInterval") : 20;
this.ignoreInvulnFrames = nbt.hasKey("ignoreInvulnFrames") && nbt.getBoolean("ignoreInvulnFrames");
this.slownessLevel = nbt.hasKey("slownessLevel") ? nbt.getInteger("slownessLevel") : -1;
@@ -459,12 +466,24 @@ public class AbilityHazard extends Ability {
this.placement = placement;
}
public float getDamagePerTick() {
return damagePerTick;
public float getDamagePerSecond() {
return damagePerSecond;
}
public void setDamagePerTick(float damagePerTick) {
this.damagePerTick = damagePerTick;
public void setDamagePerSecond(float damagePerSecond) {
this.damagePerSecond = damagePerSecond;
}
/** @deprecated Use {@link #getDamagePerSecond()} instead */
@Deprecated
public float getDamagePerTick() {
return damagePerSecond;
}
/** @deprecated Use {@link #setDamagePerSecond(float)} instead */
@Deprecated
public void setDamagePerTick(float damage) {
this.damagePerSecond = damage;
}
public int getDamageInterval() {
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -21,7 +21,7 @@ import noppes.npcs.entity.EntityNPCInterface;
*/
public class AbilityHeavyHit extends Ability {
private float damage = 15.0f;
private float damage = 8.0f;
private float knockback = 2.0f;
private int slownessLevel = 1;
private int weaknessLevel = 0;
@@ -100,7 +100,7 @@ public class AbilityHeavyHit extends Ability {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 15.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 2.0f;
this.slownessLevel = nbt.hasKey("slownessLevel") ? nbt.getInteger("slownessLevel") : 1;
this.weaknessLevel = nbt.hasKey("weaknessLevel") ? nbt.getInteger("weaknessLevel") : 0;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -27,7 +27,7 @@ public class AbilityOrb extends Ability {
private float orbSpeed = 0.5f;
private float orbSize = 1.0f;
private float damage = 15.0f;
private float damage = 7.0f;
private float knockback = 1.0f;
private float maxDistance = 30.0f;
private int maxLifetime = 200;
@@ -301,7 +301,7 @@ public class AbilityOrb extends Ability {
public void readTypeNBT(NBTTagCompound nbt) {
this.orbSpeed = nbt.hasKey("orbSpeed") ? nbt.getFloat("orbSpeed") : 0.5f;
this.orbSize = nbt.hasKey("orbSize") ? nbt.getFloat("orbSize") : 1.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 15.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 7.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.0f;
this.maxDistance = nbt.hasKey("maxDistance") ? nbt.getFloat("maxDistance") : 30.0f;
this.maxLifetime = nbt.hasKey("maxLifetime") ? nbt.getInteger("maxLifetime") : 200;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -23,7 +23,7 @@ import java.util.List;
public class AbilityProjectile extends Ability {
// Type-specific parameters
private float damage = 8.0f;
private float damage = 6.0f;
private float speed = 1.5f;
private float knockback = 0.5f;
private String projectileType = "fireball";
@@ -179,7 +179,7 @@ public class AbilityProjectile extends Ability {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 6.0f;
this.speed = nbt.hasKey("speed") ? nbt.getFloat("speed") : 1.5f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 0.5f;
this.projectileType = nbt.hasKey("projectileType") ? nbt.getString("projectileType") : "fireball";
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -27,7 +27,7 @@ public class AbilityShockwave extends Ability {
private float pushRadius = 8.0f;
private float pushStrength = 1.5f;
private float damage = 10.0f;
private float damage = 8.0f;
private int stunDuration = 0;
private int maxTargets = 10;
@@ -153,7 +153,7 @@ public class AbilityShockwave extends Ability {
public void readTypeNBT(NBTTagCompound nbt) {
this.pushRadius = nbt.hasKey("pushRadius") ? nbt.getFloat("pushRadius") : 8.0f;
this.pushStrength = nbt.hasKey("pushStrength") ? nbt.getFloat("pushStrength") : 1.5f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 10.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 8.0f;
this.stunDuration = nbt.hasKey("stunDuration") ? nbt.getInteger("stunDuration") : 0;
this.maxTargets = nbt.hasKey("maxTargets") ? nbt.getInteger("maxTargets") : 10;
}
@@ -4,9 +4,9 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
@@ -31,7 +31,7 @@ import java.util.List;
public class AbilitySlam extends Ability {
// Type-specific config
private float damage = 20.0f;
private float damage = 10.0f;
private float radius = 5.0f;
private float knockbackStrength = 1.5f;
private float leapSpeed = 1.0f;
@@ -420,7 +420,7 @@ public class AbilitySlam extends Ability {
@Override
public void readTypeNBT(NBTTagCompound nbt) {
damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 20.0f;
damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 10.0f;
radius = nbt.hasKey("radius") ? nbt.getFloat("radius") : 5.0f;
knockbackStrength = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.5f;
leapSpeed = nbt.hasKey("leapSpeed") ? nbt.getFloat("leapSpeed") : 1.0f;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
@@ -4,8 +4,8 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -42,7 +42,7 @@ public class AbilityTrap extends Ability {
private int armTime = 20;
private int maxTriggers = 1;
private int triggerCooldown = 20;
private float damage = 10.0f;
private float damage = 6.0f;
private float damageRadius = 0.0f;
private float knockback = 0.5f;
private int stunDuration = 0;
@@ -352,7 +352,7 @@ public class AbilityTrap extends Ability {
this.armTime = nbt.hasKey("armTime") ? nbt.getInteger("armTime") : 20;
this.maxTriggers = nbt.hasKey("maxTriggers") ? nbt.getInteger("maxTriggers") : 1;
this.triggerCooldown = nbt.hasKey("triggerCooldown") ? nbt.getInteger("triggerCooldown") : 20;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 10.0f;
this.damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 6.0f;
this.damageRadius = nbt.hasKey("damageRadius") ? nbt.getFloat("damageRadius") : 0.0f;
this.knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 0.5f;
this.stunDuration = nbt.hasKey("stunDuration") ? nbt.getInteger("stunDuration") : 0;
@@ -4,7 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
@@ -5,6 +5,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.controllers.data.PlayerData;
import noppes.npcs.controllers.data.PlayerTradeData;
import java.util.ArrayList;
import java.util.List;
@@ -19,7 +20,10 @@ public class CNPCData implements IProfileData {
@Override
public NBTTagCompound getCurrentNBT(EntityPlayer player) {
PlayerData customNPCData = PlayerData.get(player);
return customNPCData.getNBT();
NBTTagCompound compound = customNPCData.getNBT();
compound.removeTag(PlayerTradeData.NBT_KEY);
return compound;
}
@Override
@@ -32,6 +36,10 @@ public class CNPCData implements IProfileData {
@Override
public void setNBT(EntityPlayer player, NBTTagCompound replace) {
PlayerData customNPCData = PlayerData.get(player);
NBTTagCompound sharedData = new NBTTagCompound();
customNPCData.tradeData.writeToNBT(sharedData);
if (replace.hasNoTags()) {
PlayerData newData = new PlayerData();
newData.player = player;
@@ -39,6 +47,8 @@ public class CNPCData implements IProfileData {
} else {
customNPCData.setNBT(replace);
}
customNPCData.tradeData.readFromNBT(sharedData);
customNPCData.updateClient = true;
}
@@ -1,10 +1,10 @@
package kamkeel.npcs.controllers.data.ability.telegraph;
package kamkeel.npcs.controllers.data.telegraph;
import net.minecraft.nbt.NBTTagCompound;
/**
* Telegraph configuration - defines a visual warning shape.
* Telegraphs warn players about incoming attacks.
* Telegraphs warn players about incoming attacks or mark areas of effect.
*/
public class Telegraph {
@@ -146,6 +146,10 @@ public class Telegraph {
return t;
}
public static Telegraph point() {
return new Telegraph("", TelegraphType.POINT);
}
// ═══════════════════════════════════════════════════════════════════
// GETTERS & SETTERS
// ═══════════════════════════════════════════════════════════════════
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.telegraph;
package kamkeel.npcs.controllers.data.telegraph;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
@@ -34,6 +34,10 @@ public class TelegraphInstance {
// State
private boolean isWarning = false;
// Server-side tracking (for removal)
private transient World world;
private transient int dimensionId;
public TelegraphInstance() {
this.instanceId = UUID.randomUUID().toString().substring(0, 8);
}
@@ -303,4 +307,23 @@ public class TelegraphInstance {
public void setWarning(boolean warning) {
isWarning = warning;
}
public World getWorld() {
return world;
}
public void setWorld(World world) {
this.world = world;
if (world != null) {
this.dimensionId = world.provider.dimensionId;
}
}
public int getDimensionId() {
return dimensionId;
}
public void setDimensionId(int dimensionId) {
this.dimensionId = dimensionId;
}
}
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.telegraph;
package kamkeel.npcs.controllers.data.telegraph;
import net.minecraft.world.World;
@@ -95,7 +95,7 @@ public class TelegraphManager {
}
// ═══════════════════════════════════════════════════════════════════
// HELPER METHODS FOR ABILITIES
// HELPER METHODS FOR SPAWNING
// ═══════════════════════════════════════════════════════════════════
/**
@@ -1,4 +1,4 @@
package kamkeel.npcs.controllers.data.ability.telegraph;
package kamkeel.npcs.controllers.data.telegraph;
/**
* Types of telegraph shapes for visual attack warnings.
@@ -26,8 +26,8 @@ import kamkeel.npcs.network.packets.data.SoundManagementPacket;
import kamkeel.npcs.network.packets.data.SwingPlayerArmPacket;
import kamkeel.npcs.network.packets.data.UpdateAnimationsPacket;
import kamkeel.npcs.network.packets.data.VillagerListPacket;
import kamkeel.npcs.network.packets.data.ability.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.ability.TelegraphSpawnPacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
import kamkeel.npcs.network.packets.data.gui.GuiClosePacket;
import kamkeel.npcs.network.packets.data.gui.GuiErrorPacket;
import kamkeel.npcs.network.packets.data.gui.GuiOpenBookPacket;
@@ -1,9 +1,9 @@
package kamkeel.npcs.network.packets.data.ability;
package kamkeel.npcs.network.packets.data.telegraph;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -16,7 +16,7 @@ import java.io.IOException;
/**
* Packet to remove a telegraph on the client.
* Sent from server to client when an ability's telegraph should be removed early.
* Sent from server to client when a telegraph should be removed early.
*/
public final class TelegraphRemovePacket extends AbstractPacket {
public static final String packetName = "Data|TelegraphRemove";
@@ -67,4 +67,18 @@ public final class TelegraphRemovePacket extends AbstractPacket {
public static void sendToTracking(String instanceId, net.minecraft.entity.Entity entity) {
PacketHandler.Instance.sendTracking(new TelegraphRemovePacket(instanceId), entity);
}
/**
* Send remove to all players in a dimension.
*/
public static void sendToDimension(String instanceId, int dimensionId) {
PacketHandler.Instance.sendToDimension(new TelegraphRemovePacket(instanceId), dimensionId);
}
/**
* Send remove to all players on the server.
*/
public static void sendToAll(String instanceId) {
PacketHandler.Instance.sendToAll(new TelegraphRemovePacket(instanceId));
}
}
@@ -1,10 +1,10 @@
package kamkeel.npcs.network.packets.data.ability;
package kamkeel.npcs.network.packets.data.telegraph;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
@@ -18,7 +18,7 @@ import java.io.IOException;
/**
* Packet to spawn a telegraph on the client.
* Sent from server to client when an ability creates a telegraph.
* Sent from server to client when a telegraph is created.
*/
public final class TelegraphSpawnPacket extends AbstractPacket {
public static final String packetName = "Data|TelegraphSpawn";
@@ -78,4 +78,11 @@ public final class TelegraphSpawnPacket extends AbstractPacket {
public static void sendToDimension(TelegraphInstance instance, int dimensionId) {
PacketHandler.Instance.sendToDimension(new TelegraphSpawnPacket(instance), dimensionId);
}
/**
* Send telegraph to all players on the server.
*/
public static void sendToAll(TelegraphInstance instance) {
PacketHandler.Instance.sendToAll(new TelegraphSpawnPacket(instance));
}
}
@@ -20,6 +20,7 @@ import noppes.npcs.config.ConfigMarket;
import noppes.npcs.constants.EnumGuiType;
import noppes.npcs.constants.EnumRoleType;
import noppes.npcs.controllers.AuctionController;
import noppes.npcs.controllers.data.AuctionClaim;
import noppes.npcs.controllers.data.AuctionFilter;
import noppes.npcs.controllers.data.AuctionListing;
@@ -56,7 +57,7 @@ public class AuctionActionPacket extends AbstractPacket {
}
@Override
public boolean needsNPC() { return true; }
public boolean needsNPC() { return false; } // Allow opening via command without NPC
// =========================================
// Client-side static methods
@@ -173,11 +174,23 @@ public class AuctionActionPacket extends AbstractPacket {
if (!(player instanceof EntityPlayerMP)) return;
EntityPlayerMP playerMP = (EntityPlayerMP) player;
if (npc.advanced.role != EnumRoleType.Auctioneer) return;
// Skip role check if NPC is null (opened via command/script)
// Otherwise require Auctioneer role
if (npc != null && npc.advanced.role != EnumRoleType.Auctioneer) return;
if (!ConfigMarket.AuctionEnabled) return;
Action requestedAction = Action.values()[in.readInt()];
int actionOrdinal = in.readInt();
if (actionOrdinal < 0 || actionOrdinal >= Action.values().length) {
return; // Invalid action
}
Action requestedAction = Action.values()[actionOrdinal];
AuctionController controller = AuctionController.getInstance();
if (controller == null) {
sendError(playerMP, "Auction system is not available.");
return;
}
String result = null;
switch (requestedAction) {
@@ -292,8 +305,10 @@ public class AuctionActionPacket extends AbstractPacket {
String result = controller.createListing(player, item, startingPrice, buyoutPrice);
if (result != null) {
// Failed - return item
player.inventory.addItemStackToInventory(item);
// Failed - return item (drop if inventory full to prevent item loss)
if (!player.inventory.addItemStackToInventory(item)) {
player.entityDropItem(item, 0.5f);
}
}
return result;
}
@@ -308,6 +323,49 @@ public class AuctionActionPacket extends AbstractPacket {
default: guiType = EnumGuiType.PlayerAuction; break;
}
NoppesUtilServer.sendOpenGui(player, guiType, npc);
// Send trades data when opening the trades page
if (page == 2) {
AuctionController controller = AuctionController.getInstance();
if (controller != null) {
sendTradesData(player, controller);
}
}
}
/** Send full trades data to player (listings, bids, claims) */
private void sendTradesData(EntityPlayerMP player, AuctionController controller) {
NBTTagCompound response = new NBTTagCompound();
response.setBoolean("TradesData", true);
// Send player's max trade slots (permission-aware)
response.setInteger("MaxTradeSlots", controller.getMaxTradesForPlayer(player));
// Active listings (items player is selling)
List<AuctionListing> listings = controller.getPlayerActiveListings(player.getUniqueID());
NBTTagList listingsNBT = new NBTTagList();
for (AuctionListing listing : listings) {
listingsNBT.appendTag(listing.writeToNBT(new NBTTagCompound()));
}
response.setTag("ActiveListings", listingsNBT);
// Active bids (items player is bidding on)
List<AuctionListing> bids = controller.getPlayerActiveBids(player.getUniqueID());
NBTTagList bidsNBT = new NBTTagList();
for (AuctionListing bid : bids) {
bidsNBT.appendTag(bid.writeToNBT(new NBTTagCompound()));
}
response.setTag("ActiveBids", bidsNBT);
// Claims (items/currency to claim) - now from PlayerData
List<AuctionClaim> claims = controller.getPlayerClaims(player);
NBTTagList claimsNBT = new NBTTagList();
for (AuctionClaim claim : claims) {
claimsNBT.appendTag(claim.writeToNBT(new NBTTagCompound()));
}
response.setTag("Claims", claimsNBT);
GuiDataPacket.sendGuiData(player, response);
}
private void handleOpenBidding(ByteBuf in, EntityPlayerMP player, AuctionController controller) throws IOException {
@@ -368,26 +426,80 @@ public class AuctionActionPacket extends AbstractPacket {
// Helpers
// =========================================
private boolean removeItemFromInventory(EntityPlayerMP player, ItemStack target) {
for (int i = 0; i < player.inventory.getSizeInventory(); i++) {
ItemStack stack = player.inventory.getStackInSlot(i);
if (stack != null && ItemStack.areItemStacksEqual(stack, target)) {
player.inventory.setInventorySlotContents(i, null);
return true;
/**
* Checks if two ItemStacks are the same type (item, damage, NBT) ignoring stack size.
*/
private boolean areItemsSameType(ItemStack item1, ItemStack item2) {
if (item1 == null || item2 == null)
return item1 == item2;
if (item1.getItem() != item2.getItem())
return false;
if (item1.getItemDamage() != item2.getItemDamage())
return false;
return ItemStack.areItemStackTagsEqual(item1, item2);
}
/**
* Counts total matching items across all inventory stacks.
*/
private int countItemsInInventory(EntityPlayerMP player, ItemStack target) {
int count = 0;
for (int i = 0; i < player.inventory.mainInventory.length; i++) {
ItemStack stack = player.inventory.mainInventory[i];
if (stack != null && areItemsSameType(stack, target)) {
count += stack.stackSize;
}
}
return false;
return count;
}
/**
* Removes items from inventory, consuming from multiple stacks if needed.
* Returns true if successful, false if not enough items.
*/
private boolean removeItemFromInventory(EntityPlayerMP player, ItemStack target) {
if (target == null) return false;
int needed = target.stackSize;
// First verify player has enough total items
int available = countItemsInInventory(player, target);
if (available < needed) {
return false;
}
// Consume items from matching stacks
for (int i = 0; i < player.inventory.mainInventory.length && needed > 0; i++) {
ItemStack stack = player.inventory.mainInventory[i];
if (stack == null || !areItemsSameType(stack, target))
continue;
if (needed >= stack.stackSize) {
// Need entire stack
needed -= stack.stackSize;
player.inventory.mainInventory[i] = null;
} else {
// Need only part of stack
stack.splitStack(needed);
needed = 0;
}
}
player.inventory.markDirty();
return true;
}
private void sendError(EntityPlayerMP player, String message) {
player.addChatMessage(new net.minecraft.util.ChatComponentText(
net.minecraft.util.EnumChatFormatting.RED + "[Auction House] " + message));
net.minecraft.util.EnumChatFormatting.RED + "[Auction] " + message));
}
private void sendTradesUpdate(EntityPlayerMP player) {
NBTTagCompound compound = new NBTTagCompound();
compound.setBoolean("TradesUpdate", true);
GuiDataPacket.sendGuiData(player, compound);
// Send full trades data to refresh the client
AuctionController controller = AuctionController.getInstance();
if (controller != null) {
sendTradesData(player, controller);
}
}
private void sendListingsUpdate(EntityPlayerMP player) {
@@ -53,7 +53,7 @@ public class GetTraderData extends AbstractPacket {
// Player balance
PlayerData data = PlayerData.get(player);
compound.setLong("Balance", data.currencyData.getBalance());
compound.setLong("Balance", data.tradeData.getBalance());
// Stock info
compound.setBoolean("StockEnabled", role.stock.enableStock);
+2 -1
View File
@@ -24,6 +24,7 @@ import kamkeel.npcs.command.profile.CommandProfile;
import kamkeel.npcs.controllers.AttributeController;
import kamkeel.npcs.controllers.ProfileController;
import kamkeel.npcs.controllers.SyncController;
import kamkeel.npcs.controllers.TelegraphController;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.controllers.data.profile.CNPCData;
import kamkeel.npcs.developer.Developer;
@@ -331,7 +332,7 @@ public class CustomNpcs {
new AnimationController();
new AbilityController();
AbilityController.Instance.load();
TelegraphController.init();
LinkedItemController.getInstance().load();
@@ -110,6 +110,9 @@ public class CustomNpcsPermissions {
public static final Permission PROFILE_REGION_BYPASS = new Permission("customnpcs.profile.region-bypass");
public static final Permission PROFILE_MAX = new Permission("customnpcs.profile.max.*");
// Auction
public static final Permission AUCTION_TRADES_MAX = new Permission("customnpcs.auction.trades.*");
public static CustomNpcsPermissions Instance;
private Method hasPermission;
+3 -3
View File
@@ -3,9 +3,9 @@ package noppes.npcs;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import kamkeel.npcs.controllers.data.ability.AbilityPhase;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphInstance;
import kamkeel.npcs.network.packets.data.ability.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.ability.TelegraphSpawnPacket;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
@@ -68,6 +68,11 @@ public class ServerTickHandler {
playerData.onLogout();
}
// Clear auction permission cache on logout
if (AuctionController.Instance != null) {
AuctionController.Instance.onPlayerLogout(event.player.getUniqueID());
}
// Save and unload the player's profile data on logout
ProfileController.Instance.logout(event.player);
}
@@ -15,11 +15,11 @@ import net.minecraft.nbt.NBTTagCompound;
public class AuctionClientConfig {
// Cached values from server
private static long listingFee = 0;
private static String currencyName = "Gold";
private static int auctionDurationHours = 48;
private static String currencyName = "Coins";
private static int auctionDurationHours = 24;
private static double minBidIncrement = 0.05;
private static int maxActiveListings = 10;
private static int claimExpirationDays = 30;
private static int maxActiveListings = 8;
private static int claimExpirationDays = 20;
private static boolean auctionEnabled = false;
/**
@@ -47,11 +47,11 @@ public class AuctionClientConfig {
*/
public static void reset() {
listingFee = 0;
currencyName = "Gold";
auctionDurationHours = 48;
currencyName = "Coins";
auctionDurationHours = 24;
minBidIncrement = 0.05;
maxActiveListings = 10;
claimExpirationDays = 30;
maxActiveListings = 8;
claimExpirationDays = 20;
auctionEnabled = false;
}
@@ -7,7 +7,7 @@ import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import kamkeel.npcs.addon.client.DBCClient;
import kamkeel.npcs.client.renderer.TelegraphRenderer;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.model.ModelBiped;
@@ -5,7 +5,7 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphManager;
import kamkeel.npcs.controllers.data.telegraph.TelegraphManager;
import kamkeel.npcs.network.PacketClient;
import kamkeel.npcs.network.packets.player.CheckPlayerValue;
import kamkeel.npcs.network.packets.player.InputDevicePacket;
@@ -3,7 +3,7 @@ package noppes.npcs.client.gui.advanced;
import kamkeel.npcs.controllers.data.ability.Ability;
import kamkeel.npcs.controllers.data.ability.Condition;
import kamkeel.npcs.controllers.data.ability.TargetingMode;
import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.StatCollector;
@@ -47,9 +47,9 @@ public class SubGuiAbilityHazard extends SubGuiAbilityConfig {
y += 24;
// Row 3: Damage Per Tick + Damage Interval
addLabel(new GuiNpcLabel(104, "ability.dmgPerTick", labelX, y + 5));
addTextField(createFloatField(104, fieldX, y, 50, hazard.getDamagePerTick()));
// Row 3: Damage Per Second + Damage Interval
addLabel(new GuiNpcLabel(104, "ability.dmgPerSec", labelX, y + 5));
addTextField(createFloatField(104, fieldX, y, 50, hazard.getDamagePerSecond()));
addLabel(new GuiNpcLabel(105, "ability.dmgInterval", col2LabelX, y + 5));
addTextField(createIntField(105, col2FieldX, y, 50, hazard.getDamageInterval()));
@@ -99,7 +99,7 @@ public class SubGuiAbilityHazard extends SubGuiAbilityConfig {
hazard.setInnerRadius(parseFloat(field, hazard.getInnerRadius()));
break;
case 104:
hazard.setDamagePerTick(parseFloat(field, hazard.getDamagePerTick()));
hazard.setDamagePerSecond(parseFloat(field, hazard.getDamagePerSecond()));
break;
case 105:
hazard.setDamageInterval(field.getInteger());
@@ -50,7 +50,7 @@ public class GuiNpcAdvanced extends GuiNPCInterface2 implements IGuiData {
int y = guiTop + 10;
this.addButton(new GuiNpcButton(3, guiLeft + 85 + 160, y, 52, 20, "selectServer.edit"));
this.addButton(new GuiNpcButton(8, guiLeft + 85, y, 155, 20, new String[]{"role.none", "role.trader", "role.follower", "role.bank", "role.transporter", "role.mailman", NoppesStringUtils.translate("role.companion", "(WIP)"), "role.mount", "role.auctioneer"}, npc.advanced.role.ordinal()));
getButton(3).setEnabled(npc.advanced.role != EnumRoleType.None && npc.advanced.role != EnumRoleType.Postman);
getButton(3).setEnabled(npc.advanced.role != EnumRoleType.None && npc.advanced.role != EnumRoleType.Postman && npc.advanced.role != EnumRoleType.Auctioneer);
this.addButton(new GuiNpcButton(4, guiLeft + 85 + 160, y += 22, 52, 20, "selectServer.edit"));
this.addButton(new GuiNpcButton(5, guiLeft + 85, y, 155, 20, new String[]{"job.none", "job.bard", "job.healer", "job.guard", "job.itemgiver", "role.follower", "job.spawner", "job.conversation", "job.chunkloader"}, npc.advanced.job.ordinal()));
@@ -84,7 +84,7 @@ public class GuiNpcAdvanced extends GuiNPCInterface2 implements IGuiData {
hasChanges = true;
npc.advanced.setRole(button.getValue());
getButton(3).setEnabled(npc.advanced.role != EnumRoleType.None && npc.advanced.role != EnumRoleType.Postman);
getButton(3).setEnabled(npc.advanced.role != EnumRoleType.None && npc.advanced.role != EnumRoleType.Postman && npc.advanced.role != EnumRoleType.Auctioneer);
}
if (button.id == 4) {
save();
@@ -8,7 +8,9 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.StatCollector;
import noppes.npcs.client.AuctionClientConfig;
import noppes.npcs.constants.EnumClaimType;
import noppes.npcs.containers.ContainerAuctionListing;
import noppes.npcs.containers.ContainerAuctionSell;
import noppes.npcs.containers.ContainerAuctionTrades;
import noppes.npcs.controllers.data.AuctionClaim;
import noppes.npcs.controllers.data.AuctionListing;
@@ -41,6 +43,10 @@ public class AuctionTooltipHandler {
else if (screen instanceof GuiAuctionTrades) {
addTradesTooltip((GuiAuctionTrades) screen, stack, tooltip);
}
// Check if we're in the sell GUI
else if (screen instanceof GuiAuctionSell) {
addSellTooltip((GuiAuctionSell) screen, stack, tooltip);
}
}
/**
@@ -54,10 +60,7 @@ public class AuctionTooltipHandler {
AuctionListing listing = container.getListingForItem(stack);
if (listing == null) return;
// Add separator
tooltip.add("");
tooltip.add(EnumChatFormatting.DARK_GRAY + "--- " + EnumChatFormatting.GOLD +
StatCollector.translateToLocal("auction.title") + EnumChatFormatting.DARK_GRAY + " ---");
// Seller
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.seller")
@@ -94,22 +97,21 @@ public class AuctionTooltipHandler {
ContainerAuctionTrades container = (ContainerAuctionTrades) gui.inventorySlots;
if (container == null) return;
// Check if it's an active listing
AuctionListing listing = container.getListingForItem(stack);
if (listing != null) {
// Check if it's an item I'm selling
AuctionListing sellingListing = container.getSellingListingForItem(stack);
if (sellingListing != null) {
tooltip.add("");
tooltip.add(EnumChatFormatting.DARK_GRAY + "--- " + EnumChatFormatting.GOLD +
StatCollector.translateToLocal("auction.trades.activeListing") + EnumChatFormatting.DARK_GRAY + " ---");
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.selling"));
if (listing.hasBids()) {
if (sellingListing.hasBids()) {
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.currentBid")
.replace("%s", EnumChatFormatting.GOLD + formatCurrency(listing.currentBid)));
tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), listing.bidCount));
.replace("%s", EnumChatFormatting.GOLD + formatCurrency(sellingListing.currentBid)));
tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), sellingListing.bidCount));
} else {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.noBids"));
}
long timeRemaining = listing.getTimeRemaining();
long timeRemaining = sellingListing.getTimeRemaining();
String timeText = StatCollector.translateToLocal("auction.timeLeft")
.replace("%s", formatTimeRemaining(timeRemaining));
tooltip.add((timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText);
@@ -119,29 +121,65 @@ public class AuctionTooltipHandler {
return;
}
// Check if it's an item I'm bidding on
AuctionListing biddingListing = container.getBiddingListingForItem(stack);
if (biddingListing != null) {
tooltip.add("");
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.bidding"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.currentBid")
.replace("%s", EnumChatFormatting.GOLD + formatCurrency(biddingListing.currentBid)));
tooltip.add(EnumChatFormatting.GRAY + String.format(StatCollector.translateToLocal("auction.bids"), biddingListing.bidCount));
long timeRemaining = biddingListing.getTimeRemaining();
String timeText = StatCollector.translateToLocal("auction.timeLeft")
.replace("%s", formatTimeRemaining(timeRemaining));
tooltip.add((timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE) + timeText);
tooltip.add("");
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.clickToRebid"));
return;
}
// Check if it's a claim
AuctionClaim claim = container.getClaimForItem(stack);
if (claim != null) {
tooltip.add("");
String claimTitle;
// Title based on claim type
switch (claim.type) {
case ITEM:
claimTitle = StatCollector.translateToLocal("auction.claim.item");
if (claim.isReturned) {
// Expired - returned to seller
tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.expired"));
} else {
// Won item
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.won"));
}
break;
case CURRENCY:
claimTitle = StatCollector.translateToLocal("auction.claim.currency");
// Sold - money to claim
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.sold"));
if (claim.itemName != null && !claim.itemName.isEmpty()) {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.claim.soldItem")
.replace("%s", EnumChatFormatting.WHITE + claim.itemName));
}
if (claim.otherPlayerName != null && !claim.otherPlayerName.isEmpty()) {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.claim.buyer")
.replace("%s", EnumChatFormatting.WHITE + claim.otherPlayerName));
}
break;
case REFUND:
claimTitle = StatCollector.translateToLocal("auction.claim.refund");
// Outbid - refund available
tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.outbid"));
if (claim.otherPlayerName != null && !claim.otherPlayerName.isEmpty()) {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.claim.outbidBy")
.replace("%s", EnumChatFormatting.WHITE + claim.otherPlayerName));
}
break;
default:
claimTitle = "Claim";
}
tooltip.add(EnumChatFormatting.DARK_GRAY + "--- " + EnumChatFormatting.GOLD +
claimTitle + EnumChatFormatting.DARK_GRAY + " ---");
// Amount for currency claims (including REFUND)
if (claim.type.isCurrency()) {
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.claim.amount")
.replace("%s", EnumChatFormatting.GOLD + formatCurrency(claim.currency)));
@@ -158,10 +196,40 @@ public class AuctionTooltipHandler {
}
tooltip.add("");
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.claim.clickToClaim"));
// Show rebid option for REFUND claims with item (active auction)
if (claim.type == EnumClaimType.REFUND && claim.item != null) {
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.leftClickRebid"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.rightClickRefund"));
} else {
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.claim.clickToClaim"));
}
}
}
/**
* Add sell page tooltip info.
*/
private static void addSellTooltip(GuiAuctionSell gui, ItemStack stack, List<String> tooltip) {
ContainerAuctionSell container = (ContainerAuctionSell) gui.inventorySlots;
if (container == null) return;
// Check if this is the sell slot item
ItemStack sellItem = container.getItemToSell();
if (sellItem != null && ItemStack.areItemStacksEqual(stack, sellItem)) {
// This is the item in the sell slot
tooltip.add("");
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.sell.leftClear"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.sell.rightRemove"));
return;
}
// This is an inventory item - show add controls
tooltip.add("");
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.sell.leftAdd"));
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.sell.rightAddOne"));
}
/**
* Format currency with commas.
*/
@@ -29,20 +29,19 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis
private static final ResourceLocation ICON_BID = new ResourceLocation("customnpcs", "textures/items/npcCoinDiamond.png");
private static final ResourceLocation ICON_BUYOUT = new ResourceLocation("customnpcs", "textures/items/npcCoinEmerald.png");
// Layout - non-final for testing/positioning
protected int itemSlotX = 56;
protected int itemSlotY = 52;
protected int btnBidX = 56;
protected int btnBidY = 80;
protected int btnBuyoutX = 80;
protected int btnBuyoutY = 80;
protected int infoX = 110;
protected int infoY = 50;
protected int infoWidth = 130;
// Layout
private int itemSlotX = 70;
private int itemSlotY = 75;
private int btnBidX = 58;
private int btnBidY = 110;
private int btnBuyoutX = 82;
private int btnBuyoutY = 110;
private int infoX = 110;
private int infoY = 50;
// Button IDs
private static final int BTN_BID = 200;
private static final int BTN_BUYOUT = 201;
private int btnBidId = 200;
private int btnBuyoutId = 201;
private final ContainerAuctionBidding biddingContainer;
private AuctionListing listing;
@@ -64,12 +63,12 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis
super.initGui();
// Bid button (diamond coin)
btnBid = new GuiAuctionNavButton(BTN_BID, guiLeft + btnBidX, guiTop + btnBidY,
btnBid = new GuiAuctionNavButton(btnBidId, guiLeft + btnBidX, guiTop + btnBidY,
"auction.bid.place", ICON_BID);
addButton(btnBid);
// Buy Now button (emerald coin) - only visible if listing has buyout
btnBuyout = new GuiAuctionNavButton(BTN_BUYOUT, guiLeft + btnBuyoutX, guiTop + btnBuyoutY,
btnBuyout = new GuiAuctionNavButton(btnBuyoutId, guiLeft + btnBuyoutX, guiTop + btnBuyoutY,
"auction.bid.buyout", ICON_BUYOUT);
addButton(btnBuyout);
@@ -112,15 +111,12 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis
if (listing == null) return;
switch (button.id) {
case BTN_BID:
openBidSubGui();
break;
case BTN_BUYOUT:
if (listing.hasBuyout()) {
openBuyNowSubGui();
}
break;
if (button.id == btnBidId) {
openBidSubGui();
} else if (button.id == btnBuyoutId) {
if (listing.hasBuyout()) {
openBuyNowSubGui();
}
}
}
@@ -190,38 +186,46 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis
/** Draw auction listing information on the right side */
private void drawAuctionInfo() {
int x = guiLeft + infoX;
int x = guiLeft + 105;
int y = guiTop + infoY;
int lineHeight = 12;
String currencyName = AuctionClientConfig.getCurrencyName();
int xRight = guiLeft + 215;
int lineHeight = 9;
// Seller
String sellerLabel = StatCollector.translateToLocal("auction.seller").replace("%s", "");
fontRendererObj.drawString(EnumChatFormatting.GRAY + sellerLabel, x, y, 0xFFFFFF);
y += lineHeight;
fontRendererObj.drawString(EnumChatFormatting.WHITE + listing.sellerName, x + 10, y, 0xFFFFFF);
String label = listing.sellerName;
int length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.WHITE + listing.sellerName, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
// Current Bid or Starting Price
if (listing.hasBids()) {
String bidLabel = StatCollector.translateToLocal("auction.info.currentBid");
String bidLabel = StatCollector.translateToLocal("auction.info.currentBid") + ":";
fontRendererObj.drawString(EnumChatFormatting.YELLOW + bidLabel, x, y, 0xFFFFFF);
y += lineHeight;
fontRendererObj.drawString(EnumChatFormatting.GOLD + formatCurrency(listing.currentBid) + " " + currencyName, x + 10, y, 0xFFFFFF);
label = formatCurrency(listing.currentBid);
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
// Current Bidder
String bidderLabel = StatCollector.translateToLocal("auction.info.highBidder");
fontRendererObj.drawString(EnumChatFormatting.GRAY + bidderLabel, x, y, 0xFFFFFF);
y += lineHeight;
String bidderName = listing.highBidderName != null ? listing.highBidderName : "???";
fontRendererObj.drawString(EnumChatFormatting.WHITE + bidderName, x + 10, y, 0xFFFFFF);
label = listing.highBidderName != null ? listing.highBidderName : "???";
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.WHITE + label, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
} else {
String startLabel = StatCollector.translateToLocal("auction.info.startingPrice");
fontRendererObj.drawString(EnumChatFormatting.YELLOW + startLabel, x, y, 0xFFFFFF);
y += lineHeight;
fontRendererObj.drawString(EnumChatFormatting.GOLD + formatCurrency(listing.startingPrice) + " " + currencyName, x + 10, y, 0xFFFFFF);
label = formatCurrency(listing.startingPrice);
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.GOLD + label, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
}
@@ -229,25 +233,31 @@ public class GuiAuctionBidding extends GuiAuctionInterface implements ISubGuiLis
if (listing.hasBuyout()) {
String buyoutLabel = StatCollector.translateToLocal("auction.info.buyoutPrice");
fontRendererObj.drawString(EnumChatFormatting.GREEN + buyoutLabel, x, y, 0xFFFFFF);
y += lineHeight;
fontRendererObj.drawString(EnumChatFormatting.GREEN + formatCurrency(listing.buyoutPrice) + " " + currencyName, x + 10, y, 0xFFFFFF);
label = formatCurrency(listing.buyoutPrice);
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.GREEN + label, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
}
// Time Remaining
String timeLabel = StatCollector.translateToLocal("auction.info.timeLeft");
fontRendererObj.drawString(EnumChatFormatting.GRAY + timeLabel, x, y, 0xFFFFFF);
y += lineHeight;
long timeRemaining = listing.getTimeRemaining();
EnumChatFormatting timeColor = timeRemaining < 3600000 ? EnumChatFormatting.RED : EnumChatFormatting.WHITE;
fontRendererObj.drawString(timeColor + formatTimeRemaining(timeRemaining), x + 10, y, 0xFFFFFF);
label = timeColor + formatTimeRemaining(timeRemaining);
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(label, xRight - length, y, 0xFFFFFF);
y += lineHeight + 4;
// Bid Count
String bidCountLabel = StatCollector.translateToLocal("auction.info.bidCount");
fontRendererObj.drawString(EnumChatFormatting.GRAY + bidCountLabel, x, y, 0xFFFFFF);
y += lineHeight;
fontRendererObj.drawString(EnumChatFormatting.WHITE + String.valueOf(listing.bidCount), x + 10, y, 0xFFFFFF);
label = String.valueOf(listing.bidCount);
length = fontRendererObj.getStringWidth(label);
fontRendererObj.drawString(EnumChatFormatting.WHITE + label, xRight - length, y, 0xFFFFFF);
}
@Override
@@ -16,7 +16,6 @@ import noppes.npcs.client.gui.util.ITextfieldListener;
import noppes.npcs.client.AuctionClientConfig;
import noppes.npcs.containers.ContainerAuctionSell;
import noppes.npcs.entity.EntityNPCInterface;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
@@ -30,23 +29,23 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
private static final ResourceLocation ICON_COIN = new ResourceLocation("customnpcs", "textures/items/npcCoinBronze.png");
private static final ResourceLocation ICON_BAG = new ResourceLocation("customnpcs", "textures/items/npcBag.png");
// Layout - non-final for testing/positioning
protected int contentX = 50;
protected int contentY = 50;
protected int confirmBtnX = 180;
protected int confirmBtnY = 120;
protected int navYBuyout = 109;
// Layout
private int contentX = 58;
private int contentY = 50;
private int confirmBtnX = 200;
private int confirmBtnY = 118;
private int navYBuyout = 109;
// Component IDs
private static final int TXT_STARTING_PRICE = 1;
private static final int TXT_BUYOUT_PRICE = 2;
private static final int BTN_CONFIRM = 10;
private static final int BTN_ALLOW_BUYOUT = 11;
private int txtStartingPriceId = 1;
private int txtBuyoutPriceId = 2;
private int btnConfirmId = 10;
private int btnAllowBuyoutId = 11;
private final ContainerAuctionSell sellContainer;
private long startingPrice = 0;
private long buyoutPrice = 0;
private boolean allowBuyout = false;
private boolean allowBuyout = true;
private String errorMessage = null;
// Buttons
@@ -67,7 +66,7 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
// Starting Price label and field
addLabel(new GuiNpcLabel(1, "auction.sell.startingPrice", guiLeft + contentX, y + 5, 0xFFFFFF));
GuiNpcTextField startingField = new GuiNpcTextField(TXT_STARTING_PRICE, this, fontRendererObj,
GuiNpcTextField startingField = new GuiNpcTextField(txtStartingPriceId, this, fontRendererObj,
guiLeft + contentX + 80, y, 80, 18, startingPrice > 0 ? String.valueOf(startingPrice) : "");
startingField.setIntegersOnly();
addTextField(startingField);
@@ -78,20 +77,20 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
lblBuyout = new GuiNpcLabel(2, "auction.sell.buyoutPrice", guiLeft + contentX, y + 5, 0xFFFFFF);
addLabel(lblBuyout);
txtBuyout = new GuiNpcTextField(TXT_BUYOUT_PRICE, this, fontRendererObj,
txtBuyout = new GuiNpcTextField(txtBuyoutPriceId, this, fontRendererObj,
guiLeft + contentX + 80, y, 80, 18, buyoutPrice > 0 ? String.valueOf(buyoutPrice) : "");
txtBuyout.setIntegersOnly();
addTextField(txtBuyout);
// Allow Buyout toggle button (18x18 with bag icon)
btnAllowBuyout = new GuiAuctionNavButton(BTN_ALLOW_BUYOUT, guiLeft + navX, guiTop + navYBuyout,
btnAllowBuyout = new GuiAuctionNavButton(btnAllowBuyoutId, guiLeft + navX, guiTop + navYBuyout,
"auction.sell.allowBuyout", ICON_BAG);
btnAllowBuyout.setToggle(true); // This is a toggle, not a nav button
btnAllowBuyout.setSelected(allowBuyout);
addButton(btnAllowBuyout);
// Confirm button (custom textured button - using GuiAuctionNavButton)
GuiAuctionNavButton btnConfirm = new GuiAuctionNavButton(BTN_CONFIRM, guiLeft + confirmBtnX, guiTop + confirmBtnY,
GuiAuctionNavButton btnConfirm = new GuiAuctionNavButton(btnConfirmId, guiLeft + confirmBtnX - 20, guiTop + confirmBtnY - 10,
"auction.sell.confirm", ICON_COIN);
addButton(btnConfirm);
@@ -141,13 +140,13 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
@Override
public void unFocused(GuiNpcTextField textfield) {
if (textfield.id == TXT_STARTING_PRICE) {
if (textfield.id == txtStartingPriceId) {
try {
startingPrice = Long.parseLong(textfield.getText());
} catch (NumberFormatException e) {
startingPrice = 0;
}
} else if (textfield.id == TXT_BUYOUT_PRICE) {
} else if (textfield.id == txtBuyoutPriceId) {
try {
String text = textfield.getText().trim();
buyoutPrice = text.isEmpty() ? 0 : Long.parseLong(text);
@@ -162,9 +161,9 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
public void actionPerformed(GuiButton button) {
super.actionPerformed(button);
if (button.id == BTN_CONFIRM) {
if (button.id == btnConfirmId) {
createListing();
} else if (button.id == BTN_ALLOW_BUYOUT) {
} else if (button.id == btnAllowBuyoutId) {
allowBuyout = !allowBuyout;
btnAllowBuyout.setSelected(allowBuyout);
updateBuyoutVisibility();
@@ -175,8 +174,8 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
private void createListing() {
// Get latest values from text fields
GuiNpcTextField startingField = getTextField(TXT_STARTING_PRICE);
GuiNpcTextField buyoutField = getTextField(TXT_BUYOUT_PRICE);
GuiNpcTextField startingField = getTextField(txtStartingPriceId);
GuiNpcTextField buyoutField = getTextField(txtBuyoutPriceId);
if (startingField != null) {
try {
@@ -234,21 +233,20 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
@Override
protected void drawAuctionContent(float partialTicks, int mouseX, int mouseY) {
// Draw sell slot background (centered in content area)
int slotX = guiLeft + contentX + 30;
int slotY = guiTop + contentY + 55;
int slotX = guiLeft + contentX;
int slotY = guiTop + contentY + 50;
drawAuctionSlot(slotX, slotY);
// Draw listing fee info (uses cached config from server)
String feeText = StatCollector.translateToLocal("auction.sell.fee")
.replace("%s", formatCurrency(AuctionClientConfig.getListingFee()) + " " + AuctionClientConfig.getCurrencyName());
fontRendererObj.drawString(EnumChatFormatting.GRAY + feeText, guiLeft + contentX, guiTop + contentY + 80, 0xFFFFFF);
fontRendererObj.drawString(EnumChatFormatting.GRAY + feeText, guiLeft + contentX, guiTop + contentY + 77, 0xFFFFFF);
// Draw error message if any
if (errorMessage != null) {
fontRendererObj.drawString(EnumChatFormatting.RED + errorMessage, guiLeft + contentX, guiTop + contentY + 95, 0xFFFFFF);
}
// Draw item count indicator for sell slot
drawSellSlotIndicator(slotX, slotY);
}
@@ -261,14 +259,11 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
// Color: green if valid, red if over limit
int color = sellItem.stackSize <= inventoryCount ? 0x55FF55 : 0xFF5555;
int textWidth = fontRendererObj.getStringWidth(countText);
fontRendererObj.drawStringWithShadow(countText, slotX + 18 + 4, slotY + 5, color);
} else {
// Draw "Place item here" hint
String hint = StatCollector.translateToLocal("auction.sell.placeItem");
int hintWidth = fontRendererObj.getStringWidth(hint);
fontRendererObj.drawString(EnumChatFormatting.GRAY + hint, slotX + 9 - hintWidth / 2 + 20, slotY + 5, 0xFFFFFF);
fontRendererObj.drawString(EnumChatFormatting.GRAY + hint, slotX + 20, slotY + 5, 0xFFFFFF);
}
}
@@ -309,14 +304,8 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
super.drawGuiContainerForegroundLayer(mouseX, mouseY);
if (hasSubGui()) return;
// Draw tooltips for buttons
// Draw tooltips for buttons (icons, not items - item tooltips handled by AuctionTooltipHandler)
drawSellPageTooltips(mouseX, mouseY);
// Sell slot tooltip
drawSellSlotTooltip(mouseX, mouseY);
// Inventory item tooltip (only add extra info, item tooltip is handled by AuctionTooltipHandler)
drawInventoryTooltip(mouseX, mouseY);
}
/** Draw tooltips for sell page buttons */
@@ -324,7 +313,7 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
List<String> tooltip = null;
// Check all buttons
GuiNpcButton confirmBtn = getButton(BTN_CONFIRM);
GuiNpcButton confirmBtn = getButton(btnConfirmId);
if (confirmBtn instanceof GuiAuctionNavButton) {
GuiAuctionNavButton btn = (GuiAuctionNavButton) confirmBtn;
if (btn.isHovered()) {
@@ -337,64 +326,7 @@ public class GuiAuctionSell extends GuiAuctionInterface implements ITextfieldLis
}
if (tooltip != null && !tooltip.isEmpty()) {
drawTooltip(tooltip, mouseX - guiLeft, mouseY - guiTop);
drawHoveringText(tooltip, mouseX - guiLeft, mouseY - guiTop, fontRendererObj);
}
}
/** Draw tooltip for sell slot */
private void drawSellSlotTooltip(int mouseX, int mouseY) {
int slotX = guiLeft + contentX + 30;
int slotY = guiTop + contentY + 55;
if (mouseX >= slotX && mouseX < slotX + 18 && mouseY >= slotY && mouseY < slotY + 18) {
ItemStack sellItem = sellContainer.getItemToSell();
if (sellItem != null) {
List<String> tooltip = new ArrayList<>();
tooltip.add(EnumChatFormatting.GOLD + StatCollector.translateToLocal("auction.sell.slotTitle"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.sell.leftClear"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.sell.rightRemove"));
drawTooltip(tooltip, mouseX - guiLeft, mouseY - guiTop);
}
}
}
/** Draw tooltip for inventory items */
private void drawInventoryTooltip(int mouseX, int mouseY) {
Slot hoveredSlot = getSlotUnderMouse(mouseX, mouseY);
if (hoveredSlot != null && sellContainer.isPlayerInventorySlot(hoveredSlot.slotNumber)) {
if (hoveredSlot.getStack() != null) {
List<String> tooltip = new ArrayList<>();
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.sell.leftAdd"));
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.sell.rightAddOne"));
drawTooltip(tooltip, mouseX - guiLeft, mouseY - guiTop + 40);
}
}
}
/** Get slot under mouse position */
private Slot getSlotUnderMouse(int mouseX, int mouseY) {
for (int i = 0; i < inventorySlots.inventorySlots.size(); i++) {
Slot slot = (Slot) inventorySlots.inventorySlots.get(i);
if (isMouseOverSlot(slot, mouseX, mouseY)) {
return slot;
}
}
return null;
}
/** Check if mouse is over a slot */
private boolean isMouseOverSlot(Slot slot, int mouseX, int mouseY) {
int x = guiLeft + slot.xDisplayPosition;
int y = guiTop + slot.yDisplayPosition;
return mouseX >= x && mouseX < x + 16 && mouseY >= y && mouseY < y + 16;
}
/** Draw tooltip with proper GL state */
private void drawTooltip(List<String> tooltip, int x, int y) {
GL11.glPushMatrix();
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
drawHoveringText(tooltip, x, y, fontRendererObj);
GL11.glPopAttrib();
GL11.glPopMatrix();
}
}
@@ -28,6 +28,7 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
// Icon textures
private static final ResourceLocation ICON_X = new ResourceLocation("customnpcs", "textures/gui/auction/x_icon.png");
private static final ResourceLocation ICON_CHECK = new ResourceLocation("customnpcs", "textures/gui/auction/check_icon.png");
private static final ResourceLocation ICON_COIN = new ResourceLocation("customnpcs", "textures/items/npcCoinGold.png");
// Grid layout
private static final int GRID_X = 56;
@@ -37,9 +38,10 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
private static final int TOTAL_SLOTS = COLS * ROWS;
// Slot tint colors (ARGB)
private static final int TINT_BLUE = 0x303060FF; // Active listing/bid
private static final int TINT_GREEN = 0x3030FF60; // Claimable
private static final int TINT_RED = 0x30FF4040; // Refund
private static final int TINT_BLUE = 0x303060FF; // Active listing/bid (selling)
private static final int TINT_GREEN = 0x3030FF30; // Sold (currency claim) or Won (item claim)
private static final int TINT_YELLOW = 0x30FFFF30; // Outbid (refund claim)
private static final int TINT_RED = 0x30FF3030; // Expired/Returned (item returned to seller)
private final ContainerAuctionTrades tradesContainer;
private int maxTradeSlots;
@@ -53,7 +55,8 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
public GuiAuctionTrades(EntityNPCInterface npc, ContainerAuctionTrades container) {
super(npc, container);
this.tradesContainer = container;
this.maxTradeSlots = AuctionClientConfig.getMaxActiveListings() * 2;
// Initial value from client config, updated when server sends trades data
this.maxTradeSlots = AuctionClientConfig.getMaxActiveListings();
}
@Override
@@ -89,10 +92,28 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
// Right-click on own listing = show cancel
setPending(slot, PendingOp.CANCEL);
NoppesUtil.clickSound();
} else if (claim != null && mouseButton == 0) {
// Left-click on claim = show claim
setPending(slot, PendingOp.CLAIM);
} else if (listing != null && tradesContainer.isBiddingAt(slot) && mouseButton == 0) {
// Left-click on active bid = open bidding GUI to increase bid
AuctionActionPacket.openBidding(listing.id);
NoppesUtil.clickSound();
} else if (claim != null) {
// Handle claim clicks
if (claim.type == EnumClaimType.REFUND && claim.item != null) {
// REFUND with item = can rebid
if (mouseButton == 0) {
// Left-click = open bidding to rebid
AuctionActionPacket.openBidding(claim.listingId);
NoppesUtil.clickSound();
} else if (mouseButton == 1) {
// Right-click = show refund claim confirmation
setPending(slot, PendingOp.CLAIM);
NoppesUtil.clickSound();
}
} else if (mouseButton == 0) {
// Normal claim - left-click shows claim confirmation
setPending(slot, PendingOp.CLAIM);
NoppesUtil.clickSound();
}
}
}
@@ -106,18 +127,31 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
playConfirmSound();
}
clearPending();
} else if (pendingOp == PendingOp.CLAIM && mouseButton == 0) {
// Left-click confirms claim
} else if (pendingOp == PendingOp.CLAIM) {
AuctionClaim claim = tradesContainer.getClaimAt(pendingSlot);
if (claim != null) {
if (claim.type.isItem()) {
AuctionActionPacket.claimItem(claim.id);
// For REFUND claims with item (rebid option), right-click confirms refund
// For all others, left-click confirms claim
boolean shouldClaim = (claim.type == EnumClaimType.REFUND && claim.item != null)
? mouseButton == 1 // Right-click for rebiddable refunds
: mouseButton == 0; // Left-click for normal claims
if (shouldClaim) {
if (claim.type.isItem()) {
AuctionActionPacket.claimItem(claim.id);
} else {
AuctionActionPacket.claimCurrency(claim.id);
}
playConfirmSound();
clearPending();
} else {
AuctionActionPacket.claimCurrency(claim.id);
// Wrong button - cancel operation
clearPending();
NoppesUtil.clickSound();
}
playConfirmSound();
} else {
clearPending();
}
clearPending();
} else {
// Wrong button - cancel operation
clearPending();
@@ -156,7 +190,6 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
int col = relX / 18;
int row = relY / 18;
if (col >= COLS || row >= ROWS) return -1;
if (relX % 18 >= 18 || relY % 18 >= 18) return -1;
return col + row * COLS;
}
@@ -184,13 +217,23 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
drawDarkenedOverlay(x, y);
}
// Pending operation icon
// Pending operation icon or currency claim coin
if (slot == pendingSlot) {
if (pendingOp == PendingOp.CANCEL) {
drawIconOverlay(x, y, ICON_X);
} else if (pendingOp == PendingOp.CLAIM) {
drawIconOverlay(x, y, ICON_CHECK);
}
} else {
// Draw coin icon for currency claims or refund claims without item
AuctionClaim claim = tradesContainer.getClaimAt(slot);
if (claim != null) {
// Show coin if: CURRENCY claim OR REFUND claim without item
if (claim.type == EnumClaimType.CURRENCY ||
(claim.type == EnumClaimType.REFUND && claim.item == null)) {
drawIconOverlay(x, y, ICON_COIN);
}
}
}
}
}
@@ -202,9 +245,27 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
AuctionClaim claim = tradesContainer.getClaimAt(slot);
if (listing != null) {
return TINT_BLUE;
if (tradesContainer.isSellingAt(slot)) {
// Selling - Blue
return TINT_BLUE;
} else {
// Bidding - Yellow
return TINT_YELLOW;
}
} else if (claim != null) {
return (claim.type == EnumClaimType.REFUND) ? TINT_RED : TINT_GREEN;
switch (claim.type) {
case CURRENCY:
// Sold item - Green
return TINT_GREEN;
case REFUND:
// Outbid - Red (whether rebiddable or not)
return TINT_RED;
case ITEM:
// Won item (green) or Returned/Expired (red)
return claim.isReturned ? TINT_RED : TINT_GREEN;
default:
return TINT_GREEN;
}
}
return 0;
}
@@ -247,12 +308,41 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
} else if (tradesContainer.isBiddingAt(slot)) {
tooltip.add(EnumChatFormatting.AQUA + StatCollector.translateToLocal("auction.trades.activeBid"));
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.winningBid"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.leftClickToIncrease"));
}
} else if (claim != null) {
if (claim.type == EnumClaimType.REFUND) {
tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.refundClaim"));
} else {
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.pendingClaim"));
switch (claim.type) {
case CURRENCY:
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.soldClaim"));
if (!claim.itemName.isEmpty()) {
tooltip.add(EnumChatFormatting.WHITE + claim.itemName);
}
if (!claim.otherPlayerName.isEmpty()) {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.buyer") + ": " + EnumChatFormatting.AQUA + claim.otherPlayerName);
}
tooltip.add(EnumChatFormatting.GOLD + String.format("%,d", claim.currency) + " " + AuctionClientConfig.getCurrencyName());
break;
case REFUND:
tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.outbid"));
if (!claim.otherPlayerName.isEmpty()) {
tooltip.add(EnumChatFormatting.GRAY + StatCollector.translateToLocal("auction.trades.outbidBy") + ": " + EnumChatFormatting.AQUA + claim.otherPlayerName);
}
tooltip.add(EnumChatFormatting.GOLD + String.format("%,d", claim.currency) + " " + AuctionClientConfig.getCurrencyName());
// Show rebid option if item is present (active listing)
if (claim.item != null) {
tooltip.add("");
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.leftClickRebid"));
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.rightClickRefund"));
return; // Skip the generic claim message
}
break;
case ITEM:
if (claim.isReturned) {
tooltip.add(EnumChatFormatting.RED + StatCollector.translateToLocal("auction.trades.expiredClaim"));
} else {
tooltip.add(EnumChatFormatting.GREEN + StatCollector.translateToLocal("auction.trades.wonClaim"));
}
break;
}
tooltip.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocal("auction.trades.leftClickToClaim"));
} else if (slot >= maxTradeSlots) {
@@ -280,7 +370,14 @@ public class GuiAuctionTrades extends GuiAuctionInterface implements IGuiData {
@Override
public void setGuiData(NBTTagCompound compound) {
if (compound.hasKey("TradesUpdate")) {
if (compound.hasKey("TradesData")) {
// Full trades data received from server
tradesContainer.setTradesData(compound);
// Update max trade slots from server (permission-aware)
this.maxTradeSlots = tradesContainer.getMaxTradeSlots();
clearPending();
} else if (compound.hasKey("TradesUpdate")) {
// Legacy flag - should not happen now but keep for safety
tradesContainer.refreshData();
}
}
@@ -16,9 +16,9 @@ import noppes.npcs.client.gui.util.SubGuiInterface;
* Shows minimum bid, player balance, and input field.
*/
public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListener {
private static final int TXT_BID = 1;
private static final int BTN_BID = 10;
private static final int BTN_CANCEL = 11;
private int txtBidId = 1;
private int btnBidId = 10;
private int btnCancelId = 11;
private final String listingId;
private final long minimumBid;
@@ -47,25 +47,21 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe
int y = guiTop + 15;
String currencyName = AuctionClientConfig.getCurrencyName();
// Title
addLabel(new GuiNpcLabel(0, "auction.bid.title", centerX - 50, y, 0xFFFFFF));
y += 18;
// Minimum bid info
String minText = StatCollector.translateToLocal("auction.bid.minimum")
.replace("%s", EnumChatFormatting.GOLD + formatCurrency(minimumBid) + " " + currencyName);
addLabel(new GuiNpcLabel(1, minText, guiLeft + 15, y, 0xAAAAAA));
.replace("%s", EnumChatFormatting.DARK_RED + formatCurrency(minimumBid) + " " + currencyName);
addLabel(new GuiNpcLabel(1, minText, guiLeft + 15, y));
y += 14;
// Your balance
String balanceText = StatCollector.translateToLocal("auction.bid.yourBalance")
.replace("%s", EnumChatFormatting.WHITE + formatCurrency(playerBalance) + " " + currencyName);
addLabel(new GuiNpcLabel(2, balanceText, guiLeft + 15, y, 0xAAAAAA));
.replace("%s", EnumChatFormatting.DARK_GREEN + formatCurrency(playerBalance) + " " + currencyName);
addLabel(new GuiNpcLabel(2, balanceText, guiLeft + 15, y));
y += 18;
// Bid amount field
addLabel(new GuiNpcLabel(3, "auction.bid.yourBid", guiLeft + 15, y + 4, 0xFFFFFF));
GuiNpcTextField bidField = new GuiNpcTextField(TXT_BID, this, fontRendererObj,
addLabel(new GuiNpcLabel(3, "auction.bid.yourBid", guiLeft + 15, y + 4));
GuiNpcTextField bidField = new GuiNpcTextField(txtBidId, this, fontRendererObj,
guiLeft + 80, y, 100, 18, String.valueOf(minimumBid));
bidField.setIntegersOnly();
addTextField(bidField);
@@ -81,13 +77,13 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe
int totalBtnWidth = btnWidth * 2 + btnSpacing;
int btnX = centerX - totalBtnWidth / 2;
addButton(new GuiNpcButton(BTN_BID, btnX, y, btnWidth, 20, "auction.bid.placeBid"));
addButton(new GuiNpcButton(BTN_CANCEL, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel"));
addButton(new GuiNpcButton(btnBidId, btnX, y, btnWidth, 20, "auction.bid.placeBid"));
addButton(new GuiNpcButton(btnCancelId, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel"));
}
@Override
public void unFocused(GuiNpcTextField textfield) {
if (textfield.id == TXT_BID) {
if (textfield.id == txtBidId) {
try {
bidAmount = Long.parseLong(textfield.getText());
} catch (NumberFormatException e) {
@@ -100,19 +96,16 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe
@Override
public void buttonEvent(GuiButton button) {
switch (button.id) {
case BTN_BID:
placeBid();
break;
case BTN_CANCEL:
close();
break;
if (button.id == btnBidId) {
placeBid();
} else if (button.id == btnCancelId) {
close();
}
}
private void placeBid() {
// Get latest value from text field
GuiNpcTextField bidField = getTextField(TXT_BID);
GuiNpcTextField bidField = getTextField(txtBidId);
if (bidField != null) {
try {
bidAmount = Long.parseLong(bidField.getText());
@@ -14,8 +14,8 @@ import noppes.npcs.client.gui.util.SubGuiInterface;
* Shows buyout price, player balance, and confirmation buttons.
*/
public class SubGuiAuctionBuyNow extends SubGuiInterface {
private static final int BTN_CONFIRM = 10;
private static final int BTN_CANCEL = 11;
private int btnConfirmId = 10;
private int btnCancelId = 11;
private final String listingId;
private final long buyoutPrice;
@@ -44,25 +44,25 @@ public class SubGuiAuctionBuyNow extends SubGuiInterface {
// Title
String title = StatCollector.translateToLocal("auction.buyout.title");
int titleWidth = fontRendererObj.getStringWidth(title);
addLabel(new GuiNpcLabel(0, title, centerX - titleWidth / 2, y, 0xFFFFFF));
addLabel(new GuiNpcLabel(0, title, centerX - titleWidth / 2, y));
y += 20;
// Confirmation message
String confirmMsg = StatCollector.translateToLocal("auction.buyout.confirm");
int confirmWidth = fontRendererObj.getStringWidth(confirmMsg);
addLabel(new GuiNpcLabel(1, confirmMsg, centerX - confirmWidth / 2, y, 0xAAAAAA));
addLabel(new GuiNpcLabel(1, confirmMsg, centerX - confirmWidth / 2, y));
y += 16;
// Buyout price
String priceText = EnumChatFormatting.GREEN + formatCurrency(buyoutPrice) + " " + currencyName;
String priceText = EnumChatFormatting.DARK_RED + formatCurrency(buyoutPrice) + " " + currencyName;
int priceWidth = fontRendererObj.getStringWidth(priceText);
addLabel(new GuiNpcLabel(2, priceText, centerX - priceWidth / 2, y, 0xFFFFFF));
addLabel(new GuiNpcLabel(2, priceText, centerX - priceWidth / 2, y));
y += 20;
// Your balance
String balanceLabel = StatCollector.translateToLocal("auction.bid.yourBalance");
String balanceText = balanceLabel.replace("%s", EnumChatFormatting.WHITE + formatCurrency(playerBalance) + " " + currencyName);
addLabel(new GuiNpcLabel(3, balanceText, guiLeft + 20, y, 0xAAAAAA));
String balanceText = balanceLabel.replace("%s", EnumChatFormatting.DARK_GREEN + formatCurrency(playerBalance) + " " + currencyName);
addLabel(new GuiNpcLabel(3, balanceText, guiLeft + 20, y));
y += 14;
// Insufficient funds warning
@@ -78,23 +78,20 @@ public class SubGuiAuctionBuyNow extends SubGuiInterface {
int totalBtnWidth = btnWidth * 2 + btnSpacing;
int btnX = centerX - totalBtnWidth / 2;
GuiNpcButton confirmBtn = new GuiNpcButton(BTN_CONFIRM, btnX, y, btnWidth, 20, "gui.yes");
GuiNpcButton confirmBtn = new GuiNpcButton(btnConfirmId, btnX, y, btnWidth, 20, "gui.yes");
confirmBtn.enabled = playerBalance >= buyoutPrice;
addButton(confirmBtn);
addButton(new GuiNpcButton(BTN_CANCEL, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel"));
addButton(new GuiNpcButton(btnCancelId, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel"));
}
@Override
public void buttonEvent(GuiButton button) {
switch (button.id) {
case BTN_CONFIRM:
if (playerBalance >= buyoutPrice) {
confirmPurchase();
}
break;
case BTN_CANCEL:
close();
break;
if (button.id == btnConfirmId) {
if (playerBalance >= buyoutPrice) {
confirmPurchase();
}
} else if (button.id == btnCancelId) {
close();
}
}
@@ -11,16 +11,14 @@ import java.util.Arrays;
public class ConfigMarket {
public static Configuration config;
public final static String MARKET = "Market";
public final static String CURRENCY = "Market.Currency";
public final static String TRADER = "Market.Trader";
public final static String AUCTION = "Market.Auction";
public final static String AUCTION_BLACKLIST = "Market.Auction.Blacklist";
public final static String AUCTION_LOGGING = "Market.Auction.Logging";
// =========================================
// Market General
// =========================================
public static boolean MarketEnabled = true;
// Max trades grid = 9x5 = 45 slots
public static final int MAX_TRADE_SLOTS = 45;
public static final int MIN_TRADE_SLOTS = 1;
// =========================================
// Currency Settings
@@ -30,25 +28,28 @@ public class ConfigMarket {
public static long StartingBalance = 0;
public static long MaxBalance = Long.MAX_VALUE;
// =========================================
// Trader Settings
// =========================================
public static boolean EnableStockByDefault = false;
public static String DefaultResetType = "NONE";
// =========================================
// Auction Settings
// =========================================
public static boolean AuctionEnabled = true;
public static int AuctionDurationHours = 24;
public static long ListingFee = 10;
public static long MinimumListingPrice = 1;
public static double SalesTaxPercent = 0.05;
public static int DefaultMaxListings = 5;
public static int DefaultMaxTrades = 8;
public static int SnipeProtectionMinutes = 2;
public static int ClaimExpirationDays = 20;
public static double MinBidIncrementPercent = 0.05;
public static double CancellationPenaltyPercent = 0.10;
// =========================================
// Auction Blacklist Settings
// =========================================
public static boolean BlacklistEnabled = true;
public static String[] BlacklistedItems = new String[]{};
public static String[] BlacklistedMods = new String[]{};
public static String[] BlacklistedNBTTags = new String[]{};
// =========================================
// Auction Logging Settings
// =========================================
@@ -67,12 +68,6 @@ public class ConfigMarket {
try {
config.load();
// =========================================
// Market General
// =========================================
MarketEnabled = config.get(MARKET, "Enable Market System", true,
"Enable the market/economy system").getBoolean(true);
// =========================================
// Currency Settings
// =========================================
@@ -90,24 +85,13 @@ public class ConfigMarket {
MaxBalance = config.get(CURRENCY, "Max Balance", Integer.MAX_VALUE,
"Maximum currency balance a player can have").getInt(Integer.MAX_VALUE);
// =========================================
// Trader Settings
// =========================================
config.setCategoryComment(TRADER, "Trader stock system settings");
EnableStockByDefault = config.get(TRADER, "Enable Stock By Default", false,
"Enable stock system by default for new traders").getBoolean(false);
DefaultResetType = config.get(TRADER, "Default Reset Type", "NONE",
"Default stock reset type (NONE, MCDAILY, MCWEEKLY, RLDAILY, RLWEEKLY)").getString();
// =========================================
// Auction Settings
// =========================================
config.setCategoryComment(AUCTION, "Auction House system settings. The Auction House allows players to list items for sale with bidding and buyout options.");
config.setCategoryComment(AUCTION, "Auction system settings. The Auction allows players to list items for sale with bidding and buyout options.");
AuctionEnabled = config.get(AUCTION, "Enable Auction House", true,
"Enable the Auction House system").getBoolean(true);
AuctionEnabled = config.get(AUCTION, "Enable Auction", true,
"Enable the Auction system").getBoolean(true);
AuctionDurationHours = config.get(AUCTION, "Auction Duration Hours", 24,
"Default duration for auctions in hours").getInt(24);
@@ -115,11 +99,16 @@ public class ConfigMarket {
ListingFee = config.get(AUCTION, "Listing Fee", 10,
"Flat fee charged when creating a listing").getInt(10);
MinimumListingPrice = config.get(AUCTION, "Minimum Listing Price", 1,
"Minimum starting price for auction listings").getInt(1);
if (MinimumListingPrice < 1) MinimumListingPrice = 1;
SalesTaxPercent = config.get(AUCTION, "Sales Tax Percent", 0.05,
"Percentage of sale price taken as tax (0.05 = 5%). Tax is deleted as a currency sink.").getDouble(0.05);
DefaultMaxListings = config.get(AUCTION, "Default Max Listings", 5,
"Default maximum number of active listings per player").getInt(5);
int maxTrades = config.get(AUCTION, "Default Max Trades", 8,
"Default maximum number of trade slots per player (listings + bids + claims). Min: 1, Max: 45. Players can have more via customnpcs.auction.trades.X permissions.").getInt(8);
DefaultMaxTrades = Math.max(MIN_TRADE_SLOTS, Math.min(MAX_TRADE_SLOTS, maxTrades));
SnipeProtectionMinutes = config.get(AUCTION, "Snipe Protection Minutes", 2,
"When a bid is placed with less than this many minutes remaining, the auction is extended to this duration").getInt(2);
@@ -133,10 +122,49 @@ public class ConfigMarket {
CancellationPenaltyPercent = config.get(AUCTION, "Cancellation Penalty Percent", 0.10,
"Percentage of current bid taken as penalty when seller cancels an auction with bids (0.10 = 10%)").getDouble(0.10);
// =========================================
// Auction Blacklist Settings
// =========================================
config.setCategoryComment(AUCTION_BLACKLIST,
"Item Blacklist settings for the Auction House.\n" +
"Prevents specific items, mods, or items with certain NBT tags from being listed.\n\n" +
"ITEM FORMAT: Use 'modid:itemname' format (e.g., 'minecraft:bedrock', 'customnpcs:npcWand')\n" +
"WILDCARDS: Use * for wildcards (e.g., 'customnpcs:npc*' blocks all items starting with 'npc')\n" +
"MOD FORMAT: Use just the mod ID (e.g., 'projecte' blocks all items from that mod)\n" +
"NBT FORMAT: Use the NBT tag key name (e.g., 'AdminOnly' blocks items with that tag)");
BlacklistEnabled = config.get(AUCTION_BLACKLIST, "Enable Blacklist", true,
"Enable item blacklist checking when creating listings").getBoolean(true);
BlacklistedItems = config.get(AUCTION_BLACKLIST, "Blacklisted Items", new String[]{
"minecraft:bedrock",
"minecraft:command_block",
"customnpcs:npcWand",
"customnpcs:npcMobCloner",
"customnpcs:npcScripter",
"customnpcs:npcMovingPath",
"customnpcs:npcMounter",
"customnpcs:npcTeleporter",
"customnpcs:npcTool",
"customnpcs:npcSoulstoneFilled"
},
"Items that cannot be listed on the Auction House.\n" +
"Format: modid:itemname (supports * wildcards)\n" +
"Example: 'minecraft:diamond_sword' or 'customnpcs:npc*'").getStringList();
BlacklistedMods = config.get(AUCTION_BLACKLIST, "Blacklisted Mods", new String[]{},
"All items from these mods are blocked from the Auction House.\n" +
"Format: modid (e.g., 'projecte', 'thaumcraft')").getStringList();
BlacklistedNBTTags = config.get(AUCTION_BLACKLIST, "Blacklisted NBT Tags", new String[]{},
"Items containing any of these NBT tag keys are blocked.\n" +
"Checks the root level of the item's NBT compound.\n" +
"Example: 'AdminOnly', 'CreativeMode'").getStringList();
// =========================================
// Auction Logging Settings
// =========================================
config.setCategoryComment(AUCTION_LOGGING, "Auction House logging settings. Enable specific log types to track auction activity.");
config.setCategoryComment(AUCTION_LOGGING, "Auction logging settings. Enable specific log types to track auction activity.");
AuctionLoggingEnabled = config.get(AUCTION_LOGGING, "Enable Auction Logging", false,
"Master switch for auction logging. If false, no auction events are logged.").getBoolean(false);
@@ -163,10 +191,6 @@ public class ConfigMarket {
"Log when claims are collected").getBoolean(true);
// Set category order
config.setCategoryPropertyOrder(MARKET, new ArrayList<>(Arrays.asList(
"Enable Market System"
)));
config.setCategoryPropertyOrder(CURRENCY, new ArrayList<>(Arrays.asList(
"Use Vault",
"Currency Name",
@@ -174,23 +198,26 @@ public class ConfigMarket {
"Max Balance"
)));
config.setCategoryPropertyOrder(TRADER, new ArrayList<>(Arrays.asList(
"Enable Stock By Default",
"Default Reset Type"
)));
config.setCategoryPropertyOrder(AUCTION, new ArrayList<>(Arrays.asList(
"Enable Auction House",
"Enable Auction",
"Auction Duration Hours",
"Listing Fee",
"Minimum Listing Price",
"Sales Tax Percent",
"Default Max Listings",
"Default Max Trades",
"Snipe Protection Minutes",
"Claim Expiration Days",
"Min Bid Increment Percent",
"Cancellation Penalty Percent"
)));
config.setCategoryPropertyOrder(AUCTION_BLACKLIST, new ArrayList<>(Arrays.asList(
"Enable Blacklist",
"Blacklisted Items",
"Blacklisted Mods",
"Blacklisted NBT Tags"
)));
config.setCategoryPropertyOrder(AUCTION_LOGGING, new ArrayList<>(Arrays.asList(
"Enable Auction Logging",
"Log Created",
@@ -14,9 +14,9 @@ import noppes.npcs.entity.EntityNPCInterface;
*/
public class ContainerAuctionBidding extends ContainerAuction {
// Display slot layout
public static final int ITEM_SLOT_X = 75;
public static final int ITEM_SLOT_Y = 70;
public static final int ITEM_SLOT_INDEX = PLAYER_INV_SLOT_COUNT;
private int itemSlotX = 71;
private int itemSlotY = 76;
private int itemSlotIndex = PLAYER_INV_SLOT_COUNT;
private final IInventory displayInventory;
private AuctionListing listing;
@@ -26,7 +26,7 @@ public class ContainerAuctionBidding extends ContainerAuction {
displayInventory = new InventoryBasic("Display", false, 1);
// Display slot (read-only)
addSlotToContainer(new Slot(displayInventory, 0, ITEM_SLOT_X, ITEM_SLOT_Y) {
addSlotToContainer(new Slot(displayInventory, 0, itemSlotX, itemSlotY) {
@Override
public boolean isItemValid(ItemStack stack) { return false; }
@Override
@@ -58,6 +58,6 @@ public class ContainerAuctionBidding extends ContainerAuction {
* Check if this is the display slot.
*/
public boolean isDisplaySlot(int slotIndex) {
return slotIndex == ITEM_SLOT_INDEX;
return slotIndex == itemSlotIndex;
}
}
@@ -13,10 +13,10 @@ import noppes.npcs.entity.EntityNPCInterface;
* Items are cloned from inventory, not moved. Validation happens on submit.
*/
public class ContainerAuctionSell extends ContainerAuction {
public static final int SELL_SLOT_X = 101;
public static final int SELL_SLOT_Y = 106;
public static final int SELL_SLOT_INDEX = PLAYER_INV_SLOT_COUNT;
private static final int MAX_STACK = 64;
private int sellSlotX = 59;
private int sellSlotY = 101;
private int sellSlotIndex = PLAYER_INV_SLOT_COUNT;
private int maxStack = 64;
private final IInventory sellInventory;
@@ -25,7 +25,7 @@ public class ContainerAuctionSell extends ContainerAuction {
sellInventory = new InventoryBasic("Sell", false, 1);
// Sell slot blocks direct interaction
addSlotToContainer(new Slot(sellInventory, 0, SELL_SLOT_X, SELL_SLOT_Y) {
addSlotToContainer(new Slot(sellInventory, 0, sellSlotX, sellSlotY) {
@Override
public boolean isItemValid(ItemStack stack) { return false; }
@Override
@@ -59,11 +59,11 @@ public class ContainerAuctionSell extends ContainerAuction {
if (sellStack == null) {
// Empty slot - create new stack
ItemStack newStack = source.copy();
newStack.stackSize = Math.min(toAdd, MAX_STACK);
newStack.stackSize = Math.min(toAdd, maxStack);
sellInventory.setInventorySlotContents(0, newStack);
} else if (itemsMatch(sellStack, source)) {
// Same item - add to existing (capped at 64)
int space = MAX_STACK - sellStack.stackSize;
int space = maxStack - sellStack.stackSize;
int add = Math.min(toAdd, space);
if (add > 0) {
sellStack.stackSize += add;
@@ -71,7 +71,7 @@ public class ContainerAuctionSell extends ContainerAuction {
} else {
// Different item - replace
ItemStack newStack = source.copy();
newStack.stackSize = Math.min(toAdd, MAX_STACK);
newStack.stackSize = Math.min(toAdd, maxStack);
sellInventory.setInventorySlotContents(0, newStack);
}
}
@@ -93,7 +93,7 @@ public class ContainerAuctionSell extends ContainerAuction {
/** Check if sell slot is this container slot index */
public boolean isSellSlot(int slotIndex) {
return slotIndex == SELL_SLOT_INDEX;
return slotIndex == sellSlotIndex;
}
/** Check if two stacks match using NoppesUtilPlayer.compareItems (same item, damage, and NBT) */
@@ -2,6 +2,9 @@ package noppes.npcs.containers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.constants.EnumClaimType;
import noppes.npcs.controllers.AuctionController;
import noppes.npcs.controllers.data.AuctionClaim;
import noppes.npcs.controllers.data.AuctionListing;
@@ -13,6 +16,7 @@ import java.util.List;
/**
* Container for viewing player's auction trades.
* Shows active listings, bids, and claims in a 9x5 grid.
* Order: SOLD claims, OUTBID claims, WON claims, SELLING, BIDDING, EXPIRED claims
*/
public class ContainerAuctionTrades extends ContainerAuction {
// Grid layout
@@ -27,6 +31,11 @@ public class ContainerAuctionTrades extends ContainerAuction {
private List<AuctionListing> activeBids = new ArrayList<>();
private List<AuctionClaim> claims = new ArrayList<>();
private int hiddenSlot = -1;
private int maxTradeSlots = 8; // Player's max trade slots (permission-aware, synced from server)
// Slot type tracking for proper accessor methods
private int[] slotTypes = new int[SLOT_COUNT]; // 0=empty, 1=selling, 2=bidding, 3=claim
private int[] slotDataIndex = new int[SLOT_COUNT]; // Index into respective list
public ContainerAuctionTrades(EntityNPCInterface npc, EntityPlayer player) {
super(npc, player);
@@ -44,34 +53,163 @@ public class ContainerAuctionTrades extends ContainerAuction {
refreshData();
}
/** Refresh data from controller */
/** Refresh data from controller (server-side only) */
public void refreshData() {
// Clear existing data
clearData();
// Only works on server where AuctionController exists
if (AuctionController.Instance == null) return;
List<AuctionListing> newListings = AuctionController.Instance.getPlayerActiveListings(player.getUniqueID());
List<AuctionListing> newBids = AuctionController.Instance.getPlayerActiveBids(player.getUniqueID());
List<AuctionClaim> newClaims = AuctionController.Instance.getPlayerClaims(player.getUniqueID());
populateDisplay(newListings, newBids, newClaims);
}
/** Set data from NBT received from server (client-side) */
public void setTradesData(NBTTagCompound compound) {
// Clear existing data
clearData();
// Read player's max trade slots (permission-aware)
if (compound.hasKey("MaxTradeSlots")) {
maxTradeSlots = compound.getInteger("MaxTradeSlots");
}
List<AuctionListing> newListings = new ArrayList<>();
List<AuctionListing> newBids = new ArrayList<>();
List<AuctionClaim> newClaims = new ArrayList<>();
// Parse active listings
if (compound.hasKey("ActiveListings")) {
NBTTagList listingsNBT = compound.getTagList("ActiveListings", 10);
for (int i = 0; i < listingsNBT.tagCount(); i++) {
newListings.add(AuctionListing.fromNBT(listingsNBT.getCompoundTagAt(i)));
}
}
// Parse active bids
if (compound.hasKey("ActiveBids")) {
NBTTagList bidsNBT = compound.getTagList("ActiveBids", 10);
for (int i = 0; i < bidsNBT.tagCount(); i++) {
newBids.add(AuctionListing.fromNBT(bidsNBT.getCompoundTagAt(i)));
}
}
// Parse claims
if (compound.hasKey("Claims")) {
NBTTagList claimsNBT = compound.getTagList("Claims", 10);
for (int i = 0; i < claimsNBT.tagCount(); i++) {
newClaims.add(AuctionClaim.fromNBT(claimsNBT.getCompoundTagAt(i)));
}
}
populateDisplay(newListings, newBids, newClaims);
}
/** Clear all data and reset display */
private void clearData() {
activeListings.clear();
activeBids.clear();
claims.clear();
displayInventory.clear();
if (AuctionController.Instance == null) return;
// Reset slot tracking
for (int i = 0; i < SLOT_COUNT; i++) {
slotTypes[i] = 0;
slotDataIndex[i] = -1;
}
}
activeListings = AuctionController.Instance.getPlayerActiveListings(player.getUniqueID());
activeBids = AuctionController.Instance.getPlayerActiveBids(player.getUniqueID());
claims = AuctionController.Instance.getPlayerClaims(player.getUniqueID());
/** Populate display from provided data lists */
private void populateDisplay(List<AuctionListing> newListings, List<AuctionListing> newBids, List<AuctionClaim> allClaims) {
activeListings = new ArrayList<>(newListings);
activeBids = new ArrayList<>(newBids);
// Populate display: listings first, then bids, then claims
// Sort claims into categories for proper display order
List<AuctionClaim> soldClaims = new ArrayList<>(); // CURRENCY - sold items
List<AuctionClaim> outbidClaims = new ArrayList<>(); // REFUND - outbid refunds
List<AuctionClaim> wonClaims = new ArrayList<>(); // ITEM won (isReturned=false)
List<AuctionClaim> expiredClaims = new ArrayList<>(); // ITEM returned (isReturned=true)
for (AuctionClaim claim : allClaims) {
if (claim.type == EnumClaimType.CURRENCY) {
soldClaims.add(claim);
} else if (claim.type == EnumClaimType.REFUND) {
outbidClaims.add(claim);
} else if (claim.type == EnumClaimType.ITEM) {
if (claim.isReturned) {
expiredClaims.add(claim);
} else {
wonClaims.add(claim);
}
}
}
// Populate display in order: SOLD, OUTBID, WON, SELLING, BIDDING, EXPIRED
int slot = 0;
// 1. Sold claims (green) - you have money to claim!
for (AuctionClaim claim : soldClaims) {
if (slot >= SLOT_COUNT) break;
claims.add(claim);
displayInventory.setClaimItem(slot, claim);
slotTypes[slot] = 3; // claim
slotDataIndex[slot] = claims.size() - 1;
slot++;
}
// 2. Outbid claims (yellow) - refunds available
for (AuctionClaim claim : outbidClaims) {
if (slot >= SLOT_COUNT) break;
claims.add(claim);
displayInventory.setClaimItem(slot, claim);
slotTypes[slot] = 3; // claim
slotDataIndex[slot] = claims.size() - 1;
slot++;
}
// 3. Won claims (green) - items to claim!
for (AuctionClaim claim : wonClaims) {
if (slot >= SLOT_COUNT) break;
claims.add(claim);
displayInventory.setClaimItem(slot, claim);
slotTypes[slot] = 3; // claim
slotDataIndex[slot] = claims.size() - 1;
slot++;
}
// 4. Active listings (blue) - items you're selling
int listingIdx = 0;
for (AuctionListing listing : activeListings) {
if (slot >= SLOT_COUNT) break;
if (listing.item != null) displayInventory.setInventorySlotContents(slot, listing.item.copy());
slotTypes[slot] = 1; // selling
slotDataIndex[slot] = listingIdx;
slot++;
listingIdx++;
}
// 5. Active bids (blue) - items you're bidding on
int bidIdx = 0;
for (AuctionListing bid : activeBids) {
if (slot >= SLOT_COUNT) break;
if (bid.item != null) displayInventory.setInventorySlotContents(slot, bid.item.copy());
slotTypes[slot] = 2; // bidding
slotDataIndex[slot] = bidIdx;
slot++;
bidIdx++;
}
for (AuctionClaim claim : claims) {
// 6. Expired claims (red) - returned items (lowest priority)
for (AuctionClaim claim : expiredClaims) {
if (slot >= SLOT_COUNT) break;
claims.add(claim);
displayInventory.setClaimItem(slot, claim);
slotTypes[slot] = 3; // claim
slotDataIndex[slot] = claims.size() - 1;
slot++;
}
}
@@ -80,37 +218,42 @@ public class ContainerAuctionTrades extends ContainerAuction {
/** Get listing at slot (or null if claim/empty) */
public AuctionListing getListingAt(int slot) {
if (slot >= 0 && slot < activeListings.size()) {
return activeListings.get(slot);
}
int bidIdx = slot - activeListings.size();
if (bidIdx >= 0 && bidIdx < activeBids.size()) {
return activeBids.get(bidIdx);
if (slot < 0 || slot >= SLOT_COUNT) return null;
int type = slotTypes[slot];
int idx = slotDataIndex[slot];
if (idx < 0) return null;
if (type == 1 && idx < activeListings.size()) {
return activeListings.get(idx);
} else if (type == 2 && idx < activeBids.size()) {
return activeBids.get(idx);
}
return null;
}
/** Get claim at slot (or null if listing/empty) */
public AuctionClaim getClaimAt(int slot) {
int claimIdx = slot - activeListings.size() - activeBids.size();
if (claimIdx >= 0 && claimIdx < claims.size()) {
return claims.get(claimIdx);
if (slot < 0 || slot >= SLOT_COUNT) return null;
if (slotTypes[slot] == 3) {
int idx = slotDataIndex[slot];
if (idx >= 0 && idx < claims.size()) {
return claims.get(idx);
}
}
return null;
}
/** Check if slot is player's active listing */
public boolean isSellingAt(int slot) {
return slot >= 0 && slot < activeListings.size();
return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == 1;
}
/** Check if slot is player's active bid */
public boolean isBiddingAt(int slot) {
int bidIdx = slot - activeListings.size();
return bidIdx >= 0 && bidIdx < activeBids.size();
return slot >= 0 && slot < SLOT_COUNT && slotTypes[slot] == 2;
}
/** Find listing matching item */
/** Find listing matching item (searches both selling and bidding) */
public AuctionListing getListingForItem(ItemStack stack) {
if (stack == null) return null;
for (AuctionListing listing : activeListings) {
@@ -122,6 +265,24 @@ public class ContainerAuctionTrades extends ContainerAuction {
return null;
}
/** Find listing in selling list */
public AuctionListing getSellingListingForItem(ItemStack stack) {
if (stack == null) return null;
for (AuctionListing listing : activeListings) {
if (listing.item != null && ItemStack.areItemStacksEqual(listing.item, stack)) return listing;
}
return null;
}
/** Find listing in bidding list */
public AuctionListing getBiddingListingForItem(ItemStack stack) {
if (stack == null) return null;
for (AuctionListing bid : activeBids) {
if (bid.item != null && ItemStack.areItemStacksEqual(bid.item, stack)) return bid;
}
return null;
}
/** Find claim matching item */
public AuctionClaim getClaimForItem(ItemStack stack) {
if (stack == null) return null;
@@ -159,26 +320,34 @@ public class ContainerAuctionTrades extends ContainerAuction {
hiddenSlot = -1;
}
/** Restore item at slot from data */
/** Restore item at slot from data using slot tracking */
private void restoreSlotItem(int slot) {
if (slot < activeListings.size()) {
AuctionListing listing = activeListings.get(slot);
if (slot < 0 || slot >= SLOT_COUNT) return;
int type = slotTypes[slot];
int idx = slotDataIndex[slot];
if (idx < 0) return;
if (type == 1 && idx < activeListings.size()) {
// Selling
AuctionListing listing = activeListings.get(idx);
if (listing != null && listing.item != null) {
displayInventory.setInventorySlotContents(slot, listing.item.copy());
}
} else if (slot < activeListings.size() + activeBids.size()) {
int bidIdx = slot - activeListings.size();
AuctionListing bid = activeBids.get(bidIdx);
} else if (type == 2 && idx < activeBids.size()) {
// Bidding
AuctionListing bid = activeBids.get(idx);
if (bid != null && bid.item != null) {
displayInventory.setInventorySlotContents(slot, bid.item.copy());
}
} else {
int claimIdx = slot - activeListings.size() - activeBids.size();
if (claimIdx < claims.size()) {
displayInventory.setClaimItem(slot, claims.get(claimIdx));
}
} else if (type == 3 && idx < claims.size()) {
// Claim
displayInventory.setClaimItem(slot, claims.get(idx));
}
}
public int getHiddenSlot() { return hiddenSlot; }
/** Get player's max trade slots (synced from server based on permissions) */
public int getMaxTradeSlots() { return maxTradeSlots; }
}
@@ -105,7 +105,7 @@ public class ContainerNPCTrader extends ContainerNpcInterface {
long currencyCost = role.getCurrencyCost(i);
if (currencyCost > 0) {
PlayerData data = PlayerData.get(entityplayer);
if (data.currencyData.getBalance() < currencyCost) {
if (data.tradeData.getBalance() < currencyCost) {
return null; // Can't afford currency cost
}
}
@@ -117,7 +117,7 @@ public class ContainerNPCTrader extends ContainerNpcInterface {
// Withdraw currency cost (after item consumption to maintain order)
if (currencyCost > 0) {
PlayerData data = PlayerData.get(entityplayer);
data.currencyData.withdraw(currencyCost);
data.tradeData.withdraw(currencyCost);
}
// Consume stock
@@ -3,7 +3,6 @@ package noppes.npcs.containers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import noppes.npcs.CustomItems;
import noppes.npcs.constants.EnumClaimType;
import noppes.npcs.controllers.data.AuctionClaim;
import noppes.npcs.controllers.data.AuctionListing;
@@ -49,19 +48,26 @@ public class InventoryAuctionDisplay implements IInventory {
/**
* Set a claim item in the inventory.
* For ITEM claims, shows the item.
* For CURRENCY/REFUND claims, shows a gold coin as visual representation.
* Shows actual item for ITEM claims.
* For CURRENCY claims, shows coin icon (null item).
* For REFUND claims, shows item if present (for rebid option), otherwise coin icon.
*/
public void setClaimItem(int slot, AuctionClaim claim) {
if (slot >= 0 && slot < size) {
claims.set(slot, claim);
listings.set(slot, null);
if (claim != null) {
if (claim.type == EnumClaimType.ITEM && claim.item != null) {
items[slot] = claim.item;
if (claim.type == EnumClaimType.CURRENCY) {
// Sold - gold coin icon drawn by GUI
items[slot] = null;
} else if (claim.type == EnumClaimType.REFUND) {
// Outbid - show item if present (for rebid), otherwise coin
items[slot] = claim.item != null ? claim.item.copy() : null;
} else if (claim.item != null) {
// Show actual item for ITEM claims
items[slot] = claim.item.copy();
} else {
// For currency claims, show a gold coin
items[slot] = new ItemStack(CustomItems.coinGold);
items[slot] = null;
}
} else {
items[slot] = null;
@@ -25,7 +25,7 @@ public class AuctionConfigSync {
compound.setString("CurrencyName", getEffectiveCurrencyName());
compound.setInteger("AuctionDurationHours", ConfigMarket.AuctionDurationHours);
compound.setDouble("MinBidIncrement", ConfigMarket.MinBidIncrementPercent);
compound.setInteger("MaxActiveListings", ConfigMarket.DefaultMaxListings);
compound.setInteger("MaxActiveListings", ConfigMarket.DefaultMaxTrades);
compound.setInteger("ClaimExpirationDays", ConfigMarket.ClaimExpirationDays);
return compound;
}
File diff suppressed because it is too large. Load diff
@@ -364,6 +364,17 @@ public class PlayerDataController {
return data;
}
public PlayerData getData(UUID uuid) {
EntityPlayer player = getPlayerFromUUID(uuid);
PlayerData data;
if (player == null) {
data = new PlayerData();
data.setNBT(PlayerDataController.Instance.loadPlayerData(uuid.toString()));
} else
data = PlayerData.get(player);
return data;
}
public void addPlayerMessage(String username, PlayerMail mail) {
mail.time = System.currentTimeMillis();
@@ -0,0 +1,237 @@
package noppes.npcs.controllers.data;
import cpw.mods.fml.common.registry.GameRegistry;
import kamkeel.npcs.util.AttributeItemUtil;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcsPermissions;
import noppes.npcs.config.ConfigMarket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class AuctionBlacklist {
private static Set<String> blacklistedItems = new HashSet<>();
private static Set<Pattern> wildcardPatterns = new HashSet<>();
private static Set<String> blacklistedMods = new HashSet<>();
private static Set<String> blacklistedNBTTags = new HashSet<>();
// Built-in bound item requirement keys (always blocked)
private static final String SOULBIND_KEY = "cnpc_soulbind";
private static final String PROFILE_SLOT_KEY = "cnpc_profile_slot";
// Bypass permission
private static final String BYPASS_PERMISSION = "customnpcs.auction.blacklist.bypass";
public static void reload() {
blacklistedItems.clear();
wildcardPatterns.clear();
blacklistedMods.clear();
blacklistedNBTTags.clear();
// Load blacklisted items
for (String item : ConfigMarket.BlacklistedItems) {
if (item == null || item.isEmpty()) continue;
item = item.toLowerCase().trim();
if (item.contains("*")) {
String regex = "^" + item.replace(".", "\\.").replace("*", ".*") + "$";
wildcardPatterns.add(Pattern.compile(regex));
} else {
blacklistedItems.add(item);
}
}
// Load blacklisted mods
for (String mod : ConfigMarket.BlacklistedMods) {
if (mod == null || mod.isEmpty()) continue;
blacklistedMods.add(mod.toLowerCase().trim());
}
// Load blacklisted NBT tags
for (String tag : ConfigMarket.BlacklistedNBTTags) {
if (tag == null || tag.isEmpty()) continue;
blacklistedNBTTags.add(tag.trim());
}
}
/**
* Check if player can bypass the blacklist (OP or has permission)
*/
public static boolean canBypass(EntityPlayer player) {
if (player == null) return false;
// Check OP status
if (player.canCommandSenderUseCommand(2, BYPASS_PERMISSION)) {
return true;
}
// Check custom permission
return CustomNpcsPermissions.hasCustomPermission(player, BYPASS_PERMISSION);
}
/**
* Check if item is blacklisted (without bypass check)
*/
public static boolean isBlacklisted(ItemStack item) {
if (!ConfigMarket.BlacklistEnabled) return false;
if (item == null || item.getItem() == null) return true;
// Check bound items (always blocked, even if blacklist is "disabled")
if (hasBoundRequirement(item)) {
return true;
}
// Get registry name
GameRegistry.UniqueIdentifier uid = GameRegistry.findUniqueIdentifierFor(item.getItem());
if (uid == null) return true;
String registryName = (uid.modId + ":" + uid.name).toLowerCase();
String modId = uid.modId.toLowerCase();
// Check exact item match
if (blacklistedItems.contains(registryName)) {
return true;
}
// Check wildcard patterns
for (Pattern pattern : wildcardPatterns) {
if (pattern.matcher(registryName).matches()) {
return true;
}
}
// Check mod blacklist
if (blacklistedMods.contains(modId)) {
return true;
}
// Check NBT tags
if (item.hasTagCompound() && !blacklistedNBTTags.isEmpty()) {
NBTTagCompound tag = item.getTagCompound();
for (String nbtKey : blacklistedNBTTags) {
if (tag.hasKey(nbtKey)) {
return true;
}
}
}
return false;
}
/**
* Check if item is blacklisted for a specific player (with bypass check)
*/
public static boolean isBlacklistedForPlayer(ItemStack item, EntityPlayer player) {
if (canBypass(player)) return false;
return isBlacklisted(item);
}
/**
* Get the registry name of an item
*/
public static String getRegistryName(ItemStack item) {
if (item == null || item.getItem() == null) return null;
GameRegistry.UniqueIdentifier uid = GameRegistry.findUniqueIdentifierFor(item.getItem());
if (uid == null) return null;
return uid.modId + ":" + uid.name;
}
// =========================================
// Bound Item Checking (RPGCore/Requirements)
// =========================================
/**
* Check if item has soulbind or profile slot requirement.
* Requirements are stored under: item.stackTagCompound/RPGCore/Requirements
*/
private static boolean hasBoundRequirement(ItemStack item) {
if (item == null || !item.hasTagCompound()) return false;
NBTTagCompound root = item.getTagCompound();
if (!root.hasKey(AttributeItemUtil.TAG_RPGCORE)) return false;
NBTTagCompound rpgCore = root.getCompoundTag(AttributeItemUtil.TAG_RPGCORE);
if (!rpgCore.hasKey(AttributeItemUtil.TAG_REQUIREMENTS)) return false;
NBTTagCompound requirements = rpgCore.getCompoundTag(AttributeItemUtil.TAG_REQUIREMENTS);
return requirements.hasKey(SOULBIND_KEY) || requirements.hasKey(PROFILE_SLOT_KEY);
}
// =========================================
// Runtime Blacklist Management
// =========================================
public static boolean addItem(String registryName) {
if (registryName == null || registryName.isEmpty()) return false;
registryName = registryName.toLowerCase().trim();
if (registryName.contains("*")) {
String regex = "^" + registryName.replace(".", "\\.").replace("*", ".*") + "$";
wildcardPatterns.add(Pattern.compile(regex));
} else {
blacklistedItems.add(registryName);
}
return true;
}
public static boolean removeItem(String registryName) {
if (registryName == null || registryName.isEmpty()) return false;
registryName = registryName.toLowerCase().trim();
if (registryName.contains("*")) {
String regex = "^" + registryName.replace(".", "\\.").replace("*", ".*") + "$";
return wildcardPatterns.removeIf(p -> p.pattern().equals(regex));
} else {
return blacklistedItems.remove(registryName);
}
}
public static boolean addMod(String modId) {
if (modId == null || modId.isEmpty()) return false;
return blacklistedMods.add(modId.toLowerCase().trim());
}
public static boolean removeMod(String modId) {
if (modId == null || modId.isEmpty()) return false;
return blacklistedMods.remove(modId.toLowerCase().trim());
}
public static boolean addNBTTag(String tag) {
if (tag == null || tag.isEmpty()) return false;
return blacklistedNBTTags.add(tag.trim());
}
public static boolean removeNBTTag(String tag) {
if (tag == null || tag.isEmpty()) return false;
return blacklistedNBTTags.remove(tag.trim());
}
// =========================================
// Getters for listing
// =========================================
public static List<String> getBlacklistedItems() {
List<String> result = new ArrayList<>(blacklistedItems);
for (Pattern p : wildcardPatterns) {
// Convert regex back to wildcard format
String pattern = p.pattern();
pattern = pattern.substring(1, pattern.length() - 1); // Remove ^ and $
pattern = pattern.replace("\\.", ".").replace(".*", "*");
result.add(pattern);
}
return result;
}
public static List<String> getBlacklistedMods() {
return new ArrayList<>(blacklistedMods);
}
public static List<String> getBlacklistedNBTTags() {
return new ArrayList<>(blacklistedNBTTags);
}
}
@@ -2,28 +2,39 @@ package noppes.npcs.controllers.data;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.handler.data.IAuctionClaim;
import noppes.npcs.api.item.IItemStack;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.constants.EnumClaimType;
import noppes.npcs.scripted.NpcAPI;
import java.util.UUID;
public class AuctionClaim {
public class AuctionClaim implements IAuctionClaim {
public String id;
public UUID playerUUID;
public String playerName;
public String listingId;
public EnumClaimType type;
public ItemStack item;
public ItemStack item; // The item (for ITEM claims only)
public String itemName; // Display name of item (for CURRENCY/REFUND tooltip)
public String otherPlayerName; // Buyer name (CURRENCY) or who outbid (REFUND)
public long currency;
public long createdTime;
public boolean claimed;
public boolean isReturned; // For ITEM claims: true if returned to seller, false if won by buyer
public AuctionClaim() {
this.id = UUID.randomUUID().toString();
this.createdTime = System.currentTimeMillis();
this.claimed = false;
this.isReturned = false;
this.itemName = "";
this.otherPlayerName = "";
}
public static AuctionClaim createItemClaim(UUID playerUUID, String playerName, String listingId, ItemStack item) {
/** Create claim for winning an item (buyer won auction or buyout) */
public static AuctionClaim createItemWonClaim(UUID playerUUID, String playerName, String listingId, ItemStack item) {
AuctionClaim claim = new AuctionClaim();
claim.playerUUID = playerUUID;
claim.playerName = playerName;
@@ -31,28 +42,50 @@ public class AuctionClaim {
claim.type = EnumClaimType.ITEM;
claim.item = item != null ? item.copy() : null;
claim.currency = 0;
claim.isReturned = false;
return claim;
}
public static AuctionClaim createCurrencyClaim(UUID playerUUID, String playerName, String listingId, long currency) {
/** Create claim for returned item (seller's expired/cancelled listing) */
public static AuctionClaim createItemReturnedClaim(UUID playerUUID, String playerName, String listingId, ItemStack item) {
AuctionClaim claim = new AuctionClaim();
claim.playerUUID = playerUUID;
claim.playerName = playerName;
claim.listingId = listingId;
claim.type = EnumClaimType.ITEM;
claim.item = item != null ? item.copy() : null;
claim.currency = 0;
claim.isReturned = true;
return claim;
}
/** Create claim for seller's proceeds (stores item name for tooltip, not full item) */
public static AuctionClaim createCurrencyClaim(UUID playerUUID, String playerName, String listingId, long currency, String itemName, String buyerName) {
AuctionClaim claim = new AuctionClaim();
claim.playerUUID = playerUUID;
claim.playerName = playerName;
claim.listingId = listingId;
claim.type = EnumClaimType.CURRENCY;
claim.item = null;
claim.item = null; // No item stored for currency claims
claim.itemName = itemName != null ? itemName : "";
claim.otherPlayerName = buyerName != null ? buyerName : "";
claim.currency = currency;
claim.isReturned = false;
return claim;
}
public static AuctionClaim createRefundClaim(UUID playerUUID, String playerName, String listingId, long currency) {
/** Create claim for outbid refund (stores item for display and rebid option) */
public static AuctionClaim createRefundClaim(UUID playerUUID, String playerName, String listingId, long currency, String itemName, String outbidderName, ItemStack item) {
AuctionClaim claim = new AuctionClaim();
claim.playerUUID = playerUUID;
claim.playerName = playerName;
claim.listingId = listingId;
claim.type = EnumClaimType.REFUND;
claim.item = null;
claim.item = item != null ? item.copy() : null; // Store item for display and rebid
claim.itemName = itemName != null ? itemName : "";
claim.otherPlayerName = outbidderName != null ? outbidderName : "";
claim.currency = currency;
claim.isReturned = false;
return claim;
}
@@ -71,6 +104,95 @@ public class AuctionClaim {
return playerUUID != null && playerUUID.equals(uuid);
}
// =========================================
// IAuctionClaim Implementation
// =========================================
@Override
public String getId() {
return id;
}
@Override
public String getPlayerUUID() {
return playerUUID != null ? playerUUID.toString() : null;
}
@Override
public String getPlayerName() {
return playerName;
}
@Override
public String getListingId() {
return listingId;
}
@Override
public int getType() {
return type.ordinal();
}
@Override
public boolean isItemClaim() {
return type == EnumClaimType.ITEM;
}
@Override
public boolean isCurrencyClaim() {
return type == EnumClaimType.CURRENCY;
}
@Override
public boolean isRefundClaim() {
return type == EnumClaimType.REFUND;
}
@Override
public IItemStack getItem() {
return item != null ? NpcAPI.Instance().getIItemStack(item) : null;
}
@Override
public String getItemName() {
return itemName;
}
@Override
public long getCurrency() {
return currency;
}
@Override
public String getOtherPlayerName() {
return otherPlayerName;
}
@Override
public long getCreatedTime() {
return createdTime;
}
@Override
public boolean isClaimed() {
return claimed;
}
@Override
public boolean isReturnedItem() {
return isReturned;
}
@Override
public long getDaysUntilExpiration() {
return getDaysUntilExpiration(ConfigMarket.ClaimExpirationDays);
}
@Override
public boolean isExpired() {
return isExpired(ConfigMarket.ClaimExpirationDays);
}
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
compound.setString("ID", id);
compound.setString("PlayerUUID", playerUUID != null ? playerUUID.toString() : "");
@@ -84,9 +206,12 @@ public class AuctionClaim {
compound.setTag("Item", itemTag);
}
compound.setString("ItemName", itemName != null ? itemName : "");
compound.setString("OtherPlayerName", otherPlayerName != null ? otherPlayerName : "");
compound.setLong("Currency", currency);
compound.setLong("CreatedTime", createdTime);
compound.setBoolean("Claimed", claimed);
compound.setBoolean("IsReturned", isReturned);
return compound;
}
@@ -107,9 +232,12 @@ public class AuctionClaim {
item = null;
}
itemName = compound.getString("ItemName");
otherPlayerName = compound.getString("OtherPlayerName");
currency = compound.getLong("Currency");
createdTime = compound.getLong("CreatedTime");
claimed = compound.getBoolean("Claimed");
isReturned = compound.getBoolean("IsReturned");
}
public static AuctionClaim fromNBT(NBTTagCompound compound) {
@@ -8,7 +8,7 @@ import java.util.regex.Pattern;
/**
* Filter for auction listings.
* Supports search by item name and sorting options.
* Supports search by item name or seller name and sorting options.
*/
public class AuctionFilter {
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE);
@@ -37,26 +37,42 @@ public class AuctionFilter {
return stripped.toLowerCase().trim();
}
/** Check if item name matches search text */
public boolean matchesSearch(String itemName) {
/** Check if item name or seller name matches search text */
public boolean matchesSearch(String itemName, String sellerName) {
if (!hasSearchText()) return true;
String normalized = normalizeForSearch(itemName);
return normalized.contains(normalizedSearch);
String normalizedItem = normalizeForSearch(itemName);
String normalizedSeller = normalizeForSearch(sellerName);
// Match if search text is found in either item name or seller name
return normalizedItem.contains(normalizedSearch) || normalizedSeller.contains(normalizedSearch);
}
/** Advanced search - all words must be present */
public boolean matchesSearchAdvanced(String itemName) {
/** Legacy method - only checks item name */
public boolean matchesSearch(String itemName) {
return matchesSearch(itemName, "");
}
/** Advanced search - all words must be present in item name OR seller name */
public boolean matchesSearchAdvanced(String itemName, String sellerName) {
if (!hasSearchText()) return true;
String normalized = normalizeForSearch(itemName);
String normalizedItem = normalizeForSearch(itemName);
String normalizedSeller = normalizeForSearch(sellerName);
String[] words = normalizedSearch.split("\\s+");
for (String word : words) {
if (!word.isEmpty() && !normalized.contains(word)) {
return false;
if (!word.isEmpty()) {
// Word must be in item name OR seller name
if (!normalizedItem.contains(word) && !normalizedSeller.contains(word)) {
return false;
}
}
}
return true;
}
/** Legacy method - only checks item name */
public boolean matchesSearchAdvanced(String itemName) {
return matchesSearchAdvanced(itemName, "");
}
public void reset() {
searchText = "";
sortBy = EnumAuctionSort.NEWEST;
@@ -2,11 +2,15 @@ package noppes.npcs.controllers.data;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.handler.data.IAuctionListing;
import noppes.npcs.api.item.IItemStack;
import noppes.npcs.config.ConfigMarket;
import noppes.npcs.constants.EnumAuctionStatus;
import noppes.npcs.scripted.NpcAPI;
import java.util.UUID;
public class AuctionListing {
public class AuctionListing implements IAuctionListing {
public String id;
public UUID sellerUUID;
public String sellerName;
@@ -40,22 +44,6 @@ public class AuctionListing {
this.endTime = this.createdTime + durationMs;
}
public boolean hasBids() {
return bidCount > 0 && highBidderUUID != null;
}
public boolean hasBuyout() {
return buyoutPrice > 0;
}
public boolean isExpired() {
return System.currentTimeMillis() >= endTime;
}
public boolean isActive() {
return status == EnumAuctionStatus.ACTIVE && !isExpired();
}
public long getTimeRemaining() {
return Math.max(0, endTime - System.currentTimeMillis());
}
@@ -88,6 +76,136 @@ public class AuctionListing {
}
}
// =========================================
// IAuctionListing Implementation
// =========================================
@Override
public String getId() {
return id;
}
@Override
public String getSellerUUID() {
return sellerUUID != null ? sellerUUID.toString() : null;
}
@Override
public String getSellerName() {
return sellerName;
}
@Override
public IItemStack getItem() {
return item != null ? NpcAPI.Instance().getIItemStack(item) : null;
}
@Override
public long getStartingPrice() {
return startingPrice;
}
@Override
public long getBuyoutPrice() {
return buyoutPrice;
}
@Override
public long getCurrentBid() {
return currentBid;
}
@Override
public String getHighBidderUUID() {
return highBidderUUID != null ? highBidderUUID.toString() : null;
}
@Override
public String getHighBidderName() {
return highBidderName;
}
@Override
public int getBidCount() {
return bidCount;
}
@Override
public long getCreatedTime() {
return createdTime;
}
@Override
public long getEndTime() {
return endTime;
}
@Override
public long getRemainingTime() {
return getTimeRemaining();
}
@Override
public int getStatus() {
return status.ordinal();
}
@Override
public long getMinimumBid() {
return getMinimumBid(ConfigMarket.MinBidIncrementPercent);
}
@Override
public boolean isSeller(String playerUUID) {
if (playerUUID == null || sellerUUID == null) return false;
return sellerUUID.toString().equals(playerUUID);
}
@Override
public boolean isHighBidder(String playerUUID) {
if (playerUUID == null || highBidderUUID == null) return false;
return highBidderUUID.toString().equals(playerUUID);
}
@Override
public String getRemainingTimeFormatted() {
long remaining = getTimeRemaining();
if (remaining <= 0) return "Ended";
long hours = remaining / (60 * 60 * 1000);
long minutes = (remaining % (60 * 60 * 1000)) / (60 * 1000);
if (hours > 0) {
return hours + "h " + minutes + "m";
} else if (minutes > 0) {
return minutes + "m";
} else {
long seconds = (remaining % (60 * 1000)) / 1000;
return seconds + "s";
}
}
// Override existing methods with @Override annotation
@Override
public boolean hasBids() {
return bidCount > 0 && highBidderUUID != null;
}
@Override
public boolean hasBuyout() {
return buyoutPrice > 0;
}
@Override
public boolean isExpired() {
return System.currentTimeMillis() >= endTime;
}
@Override
public boolean isActive() {
return status == EnumAuctionStatus.ACTIVE && !isExpired();
}
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
compound.setString("ID", id);
compound.setString("SellerUUID", sellerUUID != null ? sellerUUID.toString() : "");
@@ -1,232 +0,0 @@
package noppes.npcs.controllers.data;
import kamkeel.npcs.util.VaultUtil;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.api.handler.IPlayerCurrencyData;
import noppes.npcs.config.ConfigMarket;
/**
* Stores player currency balance.
* This data is NOT profile slot bound - all profile slots share the same currency pool.
* When Vault API is available and UseVault config is enabled, operations delegate to Vault.
* The built-in CNPC+ currency data is always preserved in NBT, but unused when Vault is active.
*/
public class PlayerCurrencyData implements IPlayerCurrencyData {
private final PlayerData playerData;
// Built-in CNPC+ currency (always saved, used as fallback)
private long balance;
private long lifetimeEarned;
private long lifetimeSpent;
public PlayerCurrencyData(PlayerData playerData) {
this.playerData = playerData;
this.balance = ConfigMarket.StartingBalance;
this.lifetimeEarned = 0;
this.lifetimeSpent = 0;
}
/**
* Check if Vault should be used for currency operations.
* Requires both config enabled AND Vault actually available.
*/
private boolean shouldUseVault() {
return ConfigMarket.UseVault && VaultUtil.isEnabled();
}
/**
* Get player name for Vault operations
*/
private String getPlayerName() {
if (playerData != null && playerData.player != null) {
return playerData.player.getCommandSenderName();
}
return playerData != null ? playerData.playername : null;
}
public void readFromNBT(NBTTagCompound compound) {
if (compound.hasKey("CurrencyBalance")) {
this.balance = compound.getLong("CurrencyBalance");
} else {
this.balance = ConfigMarket.StartingBalance;
}
this.lifetimeEarned = compound.getLong("CurrencyLifetimeEarned");
this.lifetimeSpent = compound.getLong("CurrencyLifetimeSpent");
}
public void writeToNBT(NBTTagCompound compound) {
// Always save built-in currency data (preserved even when using Vault)
compound.setLong("CurrencyBalance", this.balance);
compound.setLong("CurrencyLifetimeEarned", this.lifetimeEarned);
compound.setLong("CurrencyLifetimeSpent", this.lifetimeSpent);
}
/**
* Get the current balance
*/
@Override
public long getBalance() {
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
return (long) VaultUtil.getBalance(playerName);
}
}
return balance;
}
/**
* Set the balance directly (use with caution)
*/
@Override
public void setBalance(long balance) {
if (balance < 0) {
balance = 0;
}
if (balance > ConfigMarket.MaxBalance) {
balance = ConfigMarket.MaxBalance;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
VaultUtil.setBalance(playerName, balance);
return;
}
}
this.balance = balance;
}
/**
* Add currency to balance (deposit)
*
* @param amount Amount to add
* @return true if successful, false if would exceed max balance
*/
@Override
public boolean deposit(long amount) {
if (amount <= 0) {
return false;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
boolean success = VaultUtil.addMoney(playerName, amount);
if (success) {
this.lifetimeEarned += amount;
}
return success;
}
}
// Built-in currency
long newBalance = this.balance + amount;
if (newBalance > ConfigMarket.MaxBalance) {
return false;
}
if (newBalance < this.balance) {
// Overflow protection
return false;
}
this.balance = newBalance;
this.lifetimeEarned += amount;
return true;
}
/**
* Remove currency from balance (withdraw)
*
* @param amount Amount to remove
* @return true if successful, false if insufficient funds
*/
@Override
public boolean withdraw(long amount) {
if (amount <= 0) {
return false;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
boolean success = VaultUtil.withdrawMoney(playerName, amount);
if (success) {
this.lifetimeSpent += amount;
}
return success;
}
}
// Built-in currency
if (this.balance < amount) {
return false;
}
this.balance -= amount;
this.lifetimeSpent += amount;
return true;
}
/**
* Check if player can afford an amount
*/
@Override
public boolean canAfford(long amount) {
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
return VaultUtil.has(playerName, amount);
}
}
return this.balance >= amount;
}
/**
* Get lifetime earned currency
*/
@Override
public long getLifetimeEarned() {
return lifetimeEarned;
}
/**
* Get lifetime spent currency
*/
@Override
public long getLifetimeSpent() {
return lifetimeSpent;
}
/**
* Format the balance for display
*/
@Override
public String formatBalance() {
if (shouldUseVault()) {
return VaultUtil.format(getBalance());
}
return formatAmount(this.balance);
}
/**
* Format an amount for display
*/
public static String formatAmount(long amount) {
return String.format("%,d %s", amount, ConfigMarket.CurrencyName);
}
/**
* Check if Vault is being used for currency operations.
* Returns true only if config is enabled AND Vault is actually available.
*/
@Override
public boolean isUsingVault() {
return shouldUseVault();
}
/**
* Check if Vault is configured (regardless of availability)
*/
public boolean isVaultConfigured() {
return ConfigMarket.UseVault;
}
}
@@ -17,7 +17,7 @@ import noppes.npcs.CustomNpcs;
import noppes.npcs.LogWriter;
import noppes.npcs.api.entity.ICustomNpc;
import noppes.npcs.api.handler.IPlayerBankData;
import noppes.npcs.api.handler.IPlayerCurrencyData;
import noppes.npcs.api.handler.IPlayerTradeData;
import noppes.npcs.api.handler.IPlayerData;
import noppes.npcs.api.handler.IPlayerDialogData;
import noppes.npcs.api.handler.IPlayerFactionData;
@@ -46,6 +46,8 @@ import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.UUID;
import static noppes.npcs.util.CustomNPCsThreader.customNPCThread;
public class PlayerData implements IExtendedEntityProperties, IPlayerData {
public PlayerDialogData dialogData = new PlayerDialogData(this);
public PlayerBankData bankData = new PlayerBankData(this);
@@ -60,8 +62,8 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
public DataSkinOverlays skinOverlays = new DataSkinOverlays(this);
public MagicData magicData = new MagicData();
// Currency data - NOT slot-bound, shared across all profile slots
public PlayerCurrencyData currencyData = new PlayerCurrencyData(this);
// Trade data (currency + auction claims) - shared across all profile slots
public PlayerTradeData tradeData = new PlayerTradeData(this);
public ActionManager actionManager = new ActionManager();
public PlayerDataScript scriptData;
@@ -134,6 +136,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
animationData.readFromNBT(data);
effectData.readFromNBT(data);
magicData.readToNBT(data);
tradeData.readFromNBT(data);
if (player != null) {
playername = player.getCommandSenderName();
@@ -176,6 +179,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
animationData.writeToNBT(compound);
effectData.writeToNBT(compound);
magicData.writeToNBT(compound);
tradeData.writeToNBT(compound);
compound.setString("PlayerName", playername);
compound.setString("UUID", uuid);
@@ -228,6 +232,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
transportData.saveNBTData(compound);
factionData.saveNBTData(compound);
mailData.saveNBTData(compound);
tradeData.writeToNBT(compound);
compound.setString("PlayerName", playername);
compound.setString("UUID", uuid);
DBCAddon.instance.writeToNBT(this, compound);
@@ -241,6 +246,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
transportData.loadNBTData(data);
factionData.loadNBTData(data);
mailData.loadNBTData(data);
tradeData.readFromNBT(data);
if (player != null) {
playername = player.getCommandSenderName();
uuid = player.getPersistentID().toString();
@@ -401,8 +407,8 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
return mailData;
}
public IPlayerCurrencyData getCurrencyData() {
return currencyData;
public IPlayerTradeData getTradeData() {
return tradeData;
}
public synchronized void save() {
@@ -414,9 +420,6 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
}
final NBTTagCompound compound = getNBT();
// Currency data is saved at player level, NOT per profile slot
currencyData.writeToNBT(compound);
final String filename;
if (ConfigMain.DatFormat) {
filename = uuid + ".dat";
@@ -453,10 +456,6 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData {
if (data.hasNoTags()) {
data = getNBT();
}
// Load currency BEFORE setNBT - currency is player-level, NOT per profile slot
currencyData.readFromNBT(data);
setNBT(data);
}
@@ -0,0 +1,414 @@
package noppes.npcs.controllers.data;
import kamkeel.npcs.util.VaultUtil;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.api.handler.IPlayerTradeData;
import noppes.npcs.api.handler.data.IAuctionClaim;
import noppes.npcs.config.ConfigMarket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Stores player's trade-related data: currency and auction claims.
* This data is shared across all profile slots.
* Uses CopyOnWriteArrayList for thread-safety when adding claims asynchronously.
*/
public class PlayerTradeData implements IPlayerTradeData {
// NBT key used by trade data - excluded from per-slot profile saving
public static final String NBT_KEY = "TradeData";
private final PlayerData playerData;
// =========================================
// Currency Data
// =========================================
// Built-in CNPC+ currency (always saved, used as fallback)
private long balance;
private long lifetimeEarned;
private long lifetimeSpent;
// =========================================
// Auction Claims Data
// =========================================
// Thread-safe list for claims - can be modified from CNPC+ thread
private final CopyOnWriteArrayList<AuctionClaim> claims = new CopyOnWriteArrayList<>();
public PlayerTradeData(PlayerData playerData) {
this.playerData = playerData;
this.balance = ConfigMarket.StartingBalance;
this.lifetimeEarned = 0;
this.lifetimeSpent = 0;
}
// =========================================
// Currency Methods
// =========================================
/**
* Check if Vault should be used for currency operations.
* Requires both config enabled AND Vault actually available.
*/
private boolean shouldUseVault() {
return ConfigMarket.UseVault && VaultUtil.isEnabled();
}
/**
* Get player name for Vault operations
*/
private String getPlayerName() {
if (playerData != null && playerData.player != null) {
return playerData.player.getCommandSenderName();
}
return playerData != null ? playerData.playername : null;
}
/**
* Get the current balance
*/
@Override
public long getBalance() {
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
return (long) VaultUtil.getBalance(playerName);
}
}
return balance;
}
/**
* Set the balance directly (use with caution)
*/
@Override
public void setBalance(long balance) {
if (balance < 0) {
balance = 0;
}
if (balance > ConfigMarket.MaxBalance) {
balance = ConfigMarket.MaxBalance;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
VaultUtil.setBalance(playerName, balance);
return;
}
}
this.balance = balance;
}
/**
* Add currency to balance (deposit)
*
* @param amount Amount to add
* @return true if successful, false if would exceed max balance
*/
@Override
public boolean deposit(long amount) {
if (amount <= 0) {
return false;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
boolean success = VaultUtil.addMoney(playerName, amount);
if (success) {
this.lifetimeEarned += amount;
}
return success;
}
}
// Built-in currency
long newBalance = this.balance + amount;
if (newBalance > ConfigMarket.MaxBalance) {
return false;
}
if (newBalance < this.balance) {
// Overflow protection
return false;
}
this.balance = newBalance;
this.lifetimeEarned += amount;
return true;
}
/**
* Remove currency from balance (withdraw)
*
* @param amount Amount to remove
* @return true if successful, false if insufficient funds
*/
@Override
public boolean withdraw(long amount) {
if (amount <= 0) {
return false;
}
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
boolean success = VaultUtil.withdrawMoney(playerName, amount);
if (success) {
this.lifetimeSpent += amount;
}
return success;
}
}
// Built-in currency
if (this.balance < amount) {
return false;
}
this.balance -= amount;
this.lifetimeSpent += amount;
return true;
}
/**
* Check if player can afford an amount
*/
@Override
public boolean canAfford(long amount) {
if (shouldUseVault()) {
String playerName = getPlayerName();
if (playerName != null) {
return VaultUtil.has(playerName, amount);
}
}
return this.balance >= amount;
}
/**
* Get lifetime earned currency
*/
@Override
public long getLifetimeEarned() {
return lifetimeEarned;
}
/**
* Get lifetime spent currency
*/
@Override
public long getLifetimeSpent() {
return lifetimeSpent;
}
/**
* Format the balance for display
*/
@Override
public String formatBalance() {
if (shouldUseVault()) {
return VaultUtil.format(getBalance());
}
return formatAmount(this.balance);
}
/**
* Format an amount for display
*/
public static String formatAmount(long amount) {
return String.format("%,d %s", amount, ConfigMarket.CurrencyName);
}
/**
* Check if Vault is being used for currency operations.
* Returns true only if config is enabled AND Vault is actually available.
*/
@Override
public boolean isUsingVault() {
return shouldUseVault();
}
/**
* Check if Vault is configured (regardless of availability)
*/
public boolean isVaultConfigured() {
return ConfigMarket.UseVault;
}
// =========================================
// Auction Claims Methods
// =========================================
/**
* Add a claim to this player's pending claims.
* Thread-safe - can be called from any thread.
*/
public void addClaim(AuctionClaim claim) {
if (claim != null && !claim.claimed) {
claims.add(claim);
}
}
/**
* Get all unclaimed claims for this player (internal use).
* Returns a copy of the list for safe iteration.
*/
public List<AuctionClaim> getClaimsList() {
List<AuctionClaim> result = new ArrayList<>();
for (AuctionClaim claim : claims) {
if (!claim.claimed) {
result.add(claim);
}
}
return result;
}
/**
* Get all unclaimed claims for this player (API).
*/
@Override
public IAuctionClaim[] getClaims() {
List<AuctionClaim> result = getClaimsList();
return result.toArray(new IAuctionClaim[0]);
}
/**
* Get a specific claim by ID (internal use).
*/
public AuctionClaim getClaimInternal(String claimId) {
for (AuctionClaim claim : claims) {
if (claim.id.equals(claimId) && !claim.claimed) {
return claim;
}
}
return null;
}
/**
* Get a specific claim by ID (API).
*/
@Override
public IAuctionClaim getClaim(String claimId) {
return getClaimInternal(claimId);
}
/**
* Mark a claim as claimed and remove it.
*/
public boolean claimAndRemove(String claimId) {
for (AuctionClaim claim : claims) {
if (claim.id.equals(claimId) && !claim.claimed) {
claim.claimed = true;
claims.remove(claim);
return true;
}
}
return false;
}
/**
* Get count of unclaimed claims.
*/
@Override
public int getClaimCount() {
int count = 0;
for (AuctionClaim claim : claims) {
if (!claim.claimed) {
count++;
}
}
return count;
}
/**
* Check if player has any unclaimed claims.
*/
@Override
public boolean hasClaims() {
for (AuctionClaim claim : claims) {
if (!claim.claimed) {
return true;
}
}
return false;
}
/**
* Process expired claims - removes claims older than expiration days.
* Returns number of claims removed.
*/
public int processExpiredClaims() {
int expirationDays = ConfigMarket.ClaimExpirationDays;
int removed = 0;
Iterator<AuctionClaim> iterator = claims.iterator();
while (iterator.hasNext()) {
AuctionClaim claim = iterator.next();
if (!claim.claimed && claim.isExpired(expirationDays)) {
claims.remove(claim);
removed++;
}
}
return removed;
}
// =========================================
// NBT Serialization
// =========================================
public void readFromNBT(NBTTagCompound compound) {
NBTTagCompound tradeData = compound.getCompoundTag(NBT_KEY);
if (tradeData == null || tradeData.hasNoTags()) {
// No trade data found, use defaults
this.balance = ConfigMarket.StartingBalance;
this.lifetimeEarned = 0;
this.lifetimeSpent = 0;
claims.clear();
return;
}
// Currency
if (tradeData.hasKey("CurrencyBalance")) {
this.balance = tradeData.getLong("CurrencyBalance");
} else {
this.balance = ConfigMarket.StartingBalance;
}
this.lifetimeEarned = tradeData.getLong("CurrencyLifetimeEarned");
this.lifetimeSpent = tradeData.getLong("CurrencyLifetimeSpent");
// Claims
claims.clear();
if (tradeData.hasKey("AuctionClaims")) {
NBTTagList claimsList = tradeData.getTagList("AuctionClaims", 10);
for (int i = 0; i < claimsList.tagCount(); i++) {
AuctionClaim claim = AuctionClaim.fromNBT(claimsList.getCompoundTagAt(i));
if (!claim.claimed) {
claims.add(claim);
}
}
}
}
public void writeToNBT(NBTTagCompound compound) {
NBTTagCompound tradeData = new NBTTagCompound();
// Currency - always save built-in currency data (preserved even when using Vault)
tradeData.setLong("CurrencyBalance", this.balance);
tradeData.setLong("CurrencyLifetimeEarned", this.lifetimeEarned);
tradeData.setLong("CurrencyLifetimeSpent", this.lifetimeSpent);
// Claims
NBTTagList claimsList = new NBTTagList();
for (AuctionClaim claim : claims) {
if (!claim.claimed) {
claimsList.appendTag(claim.writeToNBT(new NBTTagCompound()));
}
}
tradeData.setTag("AuctionClaims", claimsList);
compound.setTag(NBT_KEY, tradeData);
}
}
@@ -426,7 +426,7 @@ public class RoleTrader extends RoleInterface {
// Player balance
PlayerData data = PlayerData.get(player);
compound.setLong("Balance", data.currencyData.getBalance());
compound.setLong("Balance", data.tradeData.getBalance());
// Stock info
compound.setBoolean("StockEnabled", stock.enableStock);
@@ -5,6 +5,7 @@ import cpw.mods.fml.common.eventhandler.EventBus;
import foxz.command.ScriptedCommand;
import kamkeel.npcs.controllers.AttributeController;
import kamkeel.npcs.controllers.ProfileController;
import kamkeel.npcs.controllers.TelegraphController;
import kamkeel.npcs.controllers.data.ability.AbilityController;
import net.minecraft.block.Block;
import net.minecraft.command.CommandHandler;
@@ -52,6 +53,7 @@ import noppes.npcs.api.IContainer;
import noppes.npcs.api.IDamageSource;
import noppes.npcs.api.INbt;
import noppes.npcs.api.IParticle;
import noppes.npcs.api.ITelegraph;
import noppes.npcs.api.IPos;
import noppes.npcs.api.ISkinOverlay;
import noppes.npcs.api.ITileEntity;
@@ -63,6 +65,7 @@ import noppes.npcs.api.gui.ICustomGui;
import noppes.npcs.api.handler.IAbilityHandler;
import noppes.npcs.api.handler.IActionManager;
import noppes.npcs.api.handler.IAnimationHandler;
import noppes.npcs.api.handler.IAuctionHandler;
import noppes.npcs.api.handler.IAttributeHandler;
import noppes.npcs.api.handler.ICloneHandler;
import noppes.npcs.api.handler.ICustomEffectHandler;
@@ -75,6 +78,7 @@ import noppes.npcs.api.handler.IProfileHandler;
import noppes.npcs.api.handler.IQuestHandler;
import noppes.npcs.api.handler.IRecipeHandler;
import noppes.npcs.api.handler.IScriptHookHandler;
import noppes.npcs.api.handler.ITelegraphHandler;
import noppes.npcs.api.handler.ITransportHandler;
import noppes.npcs.api.handler.data.IAnimation;
import noppes.npcs.api.handler.data.IFrame;
@@ -87,6 +91,7 @@ import noppes.npcs.config.ConfigScript;
import noppes.npcs.constants.EnumAnimationPart;
import noppes.npcs.containers.ContainerNpcInterface;
import noppes.npcs.controllers.AnimationController;
import noppes.npcs.controllers.AuctionController;
import noppes.npcs.controllers.ChunkController;
import noppes.npcs.controllers.CustomEffectController;
import noppes.npcs.controllers.DialogController;
@@ -370,6 +375,27 @@ public class NpcAPI extends AbstractNpcAPI {
return AbilityController.Instance;
}
@Override
public ITelegraphHandler getTelegraphs() {
this.checkWorld();
return TelegraphController.Instance;
}
@Override
public ITelegraph createTelegraph(String type) {
this.checkWorld();
return TelegraphController.Instance.create(type);
}
@Override
public IAuctionHandler getAuctions() {
this.checkWorld();
if (!noppes.npcs.config.ConfigMarket.AuctionEnabled) {
return null;
}
return AuctionController.getInstance();
}
@Override
public String[] getAllBiomeNames() {
List<String> biomes = new ArrayList<>();
@@ -0,0 +1,210 @@
package noppes.npcs.scripted;
import kamkeel.npcs.controllers.TelegraphController;
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import noppes.npcs.api.ITelegraph;
import noppes.npcs.api.ITelegraphInstance;
import noppes.npcs.api.IWorld;
import noppes.npcs.api.entity.IEntity;
/**
* Script wrapper for Telegraph configuration.
* Implements ITelegraph API interface.
*/
public class ScriptTelegraph implements ITelegraph {
private final Telegraph telegraph;
public ScriptTelegraph(Telegraph telegraph) {
this.telegraph = telegraph;
}
/**
* Get the underlying Telegraph object.
*/
public Telegraph getMCTelegraph() {
return telegraph;
}
// ═══════════════════════════════════════════════════════════════════════════
// TYPE
// ═══════════════════════════════════════════════════════════════════════════
@Override
public String getType() {
return telegraph.getType().name();
}
@Override
public void setType(String type) {
if (type == null) return;
try {
telegraph.setType(TelegraphType.valueOf(type.toUpperCase()));
} catch (IllegalArgumentException ignored) {
}
}
// ═══════════════════════════════════════════════════════════════════════════
// SHAPE PARAMETERS
// ═══════════════════════════════════════════════════════════════════════════
@Override
public float getRadius() {
return telegraph.getRadius();
}
@Override
public void setRadius(float radius) {
telegraph.setRadius(radius);
}
@Override
public float getInnerRadius() {
return telegraph.getInnerRadius();
}
@Override
public void setInnerRadius(float innerRadius) {
telegraph.setInnerRadius(innerRadius);
}
@Override
public float getLength() {
return telegraph.getLength();
}
@Override
public void setLength(float length) {
telegraph.setLength(length);
}
@Override
public float getWidth() {
return telegraph.getWidth();
}
@Override
public void setWidth(float width) {
telegraph.setWidth(width);
}
@Override
public float getAngle() {
return telegraph.getAngle();
}
@Override
public void setAngle(float angle) {
telegraph.setAngle(angle);
}
// ═══════════════════════════════════════════════════════════════════════════
// TIMING
// ═══════════════════════════════════════════════════════════════════════════
@Override
public int getDuration() {
return telegraph.getDurationTicks();
}
@Override
public void setDuration(int ticks) {
telegraph.setDurationTicks(ticks);
}
// ═══════════════════════════════════════════════════════════════════════════
// COLORS
// ═══════════════════════════════════════════════════════════════════════════
@Override
public int getColor() {
return telegraph.getColor();
}
@Override
public void setColor(int argb) {
telegraph.setColor(argb);
}
@Override
public int getWarningColor() {
return telegraph.getWarningColor();
}
@Override
public void setWarningColor(int argb) {
telegraph.setWarningColor(argb);
}
@Override
public int getWarningStartTick() {
return telegraph.getWarningStartTick();
}
@Override
public void setWarningStartTick(int tick) {
telegraph.setWarningStartTick(tick);
}
// ═══════════════════════════════════════════════════════════════════════════
// ANIMATION
// ═══════════════════════════════════════════════════════════════════════════
@Override
public boolean isAnimated() {
return telegraph.isAnimated();
}
@Override
public void setAnimated(boolean animated) {
telegraph.setAnimated(animated);
}
// ═══════════════════════════════════════════════════════════════════════════
// POSITIONING
// ═══════════════════════════════════════════════════════════════════════════
@Override
public float getHeightOffset() {
return telegraph.getHeightOffset();
}
@Override
public void setHeightOffset(float offset) {
telegraph.setHeightOffset(offset);
}
// ═══════════════════════════════════════════════════════════════════════════
// SPAWN METHODS
// ═══════════════════════════════════════════════════════════════════════════
@Override
public ITelegraphInstance spawn(IWorld world, double x, double y, double z) {
return spawn(world, x, y, z, 0);
}
@Override
public ITelegraphInstance spawn(IWorld world, double x, double y, double z, float yaw) {
if (world == null || world.getMCWorld() == null) return null;
World mcWorld = (World) world.getMCWorld();
TelegraphInstance instance = TelegraphController.Instance.spawn(telegraph, mcWorld, x, y, z, yaw);
return new ScriptTelegraphInstance(instance);
}
@Override
public ITelegraphInstance spawn(IEntity entity) {
return spawn(entity, 0);
}
@Override
public ITelegraphInstance spawn(IEntity entity, float yaw) {
if (entity == null || entity.getMCEntity() == null) return null;
Entity mcEntity = (Entity) entity.getMCEntity();
TelegraphInstance instance = TelegraphController.Instance.spawn(telegraph, mcEntity, yaw);
return new ScriptTelegraphInstance(instance);
}
}
@@ -0,0 +1,122 @@
package noppes.npcs.scripted;
import kamkeel.npcs.controllers.TelegraphController;
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
import net.minecraft.entity.Entity;
import noppes.npcs.api.ITelegraphInstance;
import noppes.npcs.api.entity.IEntity;
/**
* Script wrapper for TelegraphInstance.
* Implements ITelegraphInstance API interface.
*/
public class ScriptTelegraphInstance implements ITelegraphInstance {
private final TelegraphInstance instance;
public ScriptTelegraphInstance(TelegraphInstance instance) {
this.instance = instance;
}
/**
* Get the underlying TelegraphInstance object.
*/
public TelegraphInstance getMCInstance() {
return instance;
}
@Override
public String getInstanceId() {
return instance.getInstanceId();
}
// ═══════════════════════════════════════════════════════════════════════════
// POSITION
// ═══════════════════════════════════════════════════════════════════════════
@Override
public double getX() {
return instance.getX();
}
@Override
public double getY() {
return instance.getY();
}
@Override
public double getZ() {
return instance.getZ();
}
@Override
public float getYaw() {
return instance.getYaw();
}
@Override
public void setPosition(double x, double y, double z) {
instance.setX(x);
instance.setY(y);
instance.setZ(z);
}
// ═══════════════════════════════════════════════════════════════════════════
// ENTITY FOLLOWING
// ═══════════════════════════════════════════════════════════════════════════
@Override
public void followEntity(IEntity entity) {
if (entity == null || entity.getMCEntity() == null) return;
Entity mcEntity = (Entity) entity.getMCEntity();
instance.setEntityIdToFollow(mcEntity.getEntityId());
}
@Override
public void stopFollowing() {
instance.lockPosition();
}
@Override
public boolean isFollowing() {
return instance.getEntityIdToFollow() >= 0;
}
// ═══════════════════════════════════════════════════════════════════════════
// TIMING
// ═══════════════════════════════════════════════════════════════════════════
@Override
public int getRemainingTicks() {
return instance.getRemainingTicks();
}
@Override
public int getTotalTicks() {
return instance.getTotalTicks();
}
@Override
public float getProgress() {
return instance.getProgress();
}
@Override
public boolean isWarning() {
return instance.isWarning();
}
// ═══════════════════════════════════════════════════════════════════════════
// CONTROL
// ═══════════════════════════════════════════════════════════════════════════
@Override
public void remove() {
TelegraphController.Instance.remove(instance);
}
@Override
public void lockPosition() {
instance.lockPosition();
}
}
@@ -901,36 +901,36 @@ public class ScriptPlayer<T extends EntityPlayerMP> extends ScriptLivingBase<T>
@Override
public long getCurrencyBalance() {
return getData().currencyData.getBalance();
return getData().tradeData.getBalance();
}
@Override
public void setCurrencyBalance(long amount) {
getData().currencyData.setBalance(amount);
getData().tradeData.setBalance(amount);
}
@Override
public boolean depositCurrency(long amount) {
return getData().currencyData.deposit(amount);
return getData().tradeData.deposit(amount);
}
@Override
public boolean withdrawCurrency(long amount) {
return getData().currencyData.withdraw(amount);
return getData().tradeData.withdraw(amount);
}
@Override
public boolean canAffordCurrency(long amount) {
return getData().currencyData.canAfford(amount);
return getData().tradeData.canAfford(amount);
}
@Override
public boolean isUsingVaultCurrency() {
return getData().currencyData.isUsingVault();
return getData().tradeData.isUsingVault();
}
@Override
public String getFormattedCurrencyBalance() {
return getData().currencyData.formatBalance();
return getData().tradeData.formatBalance();
}
}
@@ -157,6 +157,9 @@ gui.create=Create
gui.rename=Rename
gui.playerMap=Regen PlayerMap
gui.unused=Unused
gui.apply=Apply
gui.cancel=Cancel
gui.yes=Yes
gui.lineColor1=Upper line color
gui.lineColor2=Middle line color
@@ -1647,7 +1650,7 @@ ability.healSelf=Heal Self
ability.healAllies=Heal Allies
# Hazard Settings
ability.dmgPerTick=Dmg/Tick
ability.dmgPerSec=Dmg/Sec
ability.debuffDur=Debuff Dur
ability.shape=Shape
ability.placement=Placement
@@ -1737,17 +1740,6 @@ auction.notify.claim=You have items to claim.
# Main GUI
auction.title=Auction House
auction.browse=Browse
auction.sell=Sell
auction.trades=My Trades
auction.filter=Filter
# Browse
auction.nextPage=Next >
auction.prevPage=< Prev
auction.page=Page %d of %d
auction.noResults=No auctions found
auction.refresh=Refresh
# Listing Display
auction.seller=Seller: %s
@@ -1758,50 +1750,27 @@ auction.bids=%d bids
auction.noBids=No bids
auction.startingPrice=Starting: %s
# Create Listing
auction.sell.title=Create Listing
# Sell Page
auction.sell.startingPrice=Starting Price
auction.sell.buyoutPrice=Buyout Price
auction.sell.noBuyout=(0 = no buyout)
auction.sell.fee=Listing Fee: %s
auction.sell.create=Create Listing
auction.sell.noItem=Place an item to sell
# Trades
auction.trades.title=My Trades
auction.trades.active=Active Listings
auction.trades.activeBids=Active Bids
auction.trades.pending=Pending Claims
auction.trades.claimAll=Claim All
auction.trades.cancel=Cancel Listing
auction.trades.empty=No active trades
# Filter
auction.filter.title=Filter Auctions
auction.filter.search=Search
auction.filter.minPrice=Min Price
auction.filter.maxPrice=Max Price
auction.filter.myOnly=My Listings Only
auction.filter.apply=Apply
auction.filter.reset=Reset
# Bid Dialog
auction.bid.title=Place Bid
auction.bid.current=Current Bid: %s
auction.bid.minimum=Minimum Bid: %s
auction.bid.yourBid=Your Bid:
auction.bid.place=Place Bid
auction.bid.placeBid=Place Bid
auction.bid.buyout=Buyout Now
# Errors
auction.error.notEnoughCurrency=Not enough currency!
auction.error.auctionEnded=This auction has ended.
auction.error.bidTooLow=Bid must be at least %s.
auction.error.maxListings=You have reached your maximum listings.
auction.error.blacklisted=This item cannot be auctioned.
auction.error.noAuctioneer=You must visit an Auctioneer NPC.
auction.error.inventoryFull=Your inventory is full.
auction.error.cannotAfford=You cannot afford this.
auction.error.blacklisted=This item cannot be listed on the Auction House.
auction.error.boundItem=Bound items cannot be listed on the Auction House.
# Navigation
auction.nav.listings=Browse Auctions
@@ -1815,9 +1784,6 @@ auction.claim.expires=Expires in %s days
auction.claim.expiresSoon=Expires soon!
auction.claim.clickToClaim=Click to claim
# Create Listing
auction.createListing=Create Listing
# Filter
auction.filter.sort=Sort By
auction.filter.none=(none)
@@ -1827,7 +1793,6 @@ auction.filter.rightClick=Right-click: Cycle Sort
# Pagination
auction.page.prev=Previous Page
auction.page.next=Next Page
auction.listings.count=%d Listings
# Sell Page
auction.sell.confirm=Confirm Listing
@@ -1846,14 +1811,32 @@ auction.trades.activeBid=Active Bid
auction.trades.unavailable=Slot Unavailable
auction.trades.rightClickToCancel=Right-click to cancel
auction.trades.leftClickToClaim=Left-click to claim
auction.trades.leftClickToIncrease=Left-click to increase bid
auction.trades.winningBid=You are the highest bidder
auction.trades.pendingClaim=Pending Claim
auction.trades.refundClaim=Bid Refund
auction.trades.soldClaim=Item Sold!
auction.trades.wonClaim=Auction Won!
auction.trades.expiredClaim=Auction Expired
auction.trades.refundClaim=Outbid - Refund Available
auction.trades.confirmCancel=Cancel Listing?
auction.trades.confirmClaim=Claim Item?
auction.trades.rightClickConfirm=Right-click again to confirm
auction.trades.leftClickConfirm=Left-click again to confirm
auction.trades.otherClickCancel=Any other click to cancel
auction.trades.buyer=Buyer
auction.trades.outbidBy=Outbid by
auction.trades.selling=Selling
auction.trades.bidding=Bidding
auction.trades.sold=Sold
auction.trades.won=Won
auction.trades.expired=Expired
auction.trades.outbid=Outbid
auction.trades.clickToRebid=Click to increase bid
auction.trades.leftClickRebid=Left-click to rebid
auction.trades.rightClickRefund=Right-click to claim refund
auction.claim.soldItem=Item: %s
auction.claim.buyer=Buyer: %s
auction.claim.outbidItem=Item: %s
auction.claim.outbidBy=Outbid by: %s
# Sell Page - Buyout Toggle
auction.sell.allowBuyout=Allow Buy Now
Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 389 B