mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
commit
39a43a339b
78 files changed
+4554
-1446
No files matched your search
+239
-49
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
+24
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
-1
@@ -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;
|
||||
|
||||
+17
-3
@@ -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));
|
||||
}
|
||||
}
|
||||
+11
-4
@@ -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);
|
||||
|
||||
@@ -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,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 |
Reference in new issue
Block a user