Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	src/main/java/noppes/npcs/controllers/AuctionController.java
This commit is contained in:
Kam committed 2026-02-24 18:24:59 -05:00
commit e7b033ecf0
19 files changed
+242 -165

No files matched your search

@@ -14,7 +14,7 @@ public class AbilityPotionEffect {
private EnumPotionType type = EnumPotionType.None;
private int manualPotionId = 0;
private int durationTicks = 60;
private int amplifier = 0; // 0-10
private int amplifier = 0; // 0-255 (Manual), 0-10 (preset)
public AbilityPotionEffect() {
}
@@ -22,7 +22,7 @@ public class AbilityPotionEffect {
public AbilityPotionEffect(EnumPotionType type, int durationTicks, int amplifier) {
this.type = type;
this.durationTicks = Math.max(1, durationTicks);
this.amplifier = Math.max(0, Math.min(10, amplifier));
this.amplifier = Math.max(0, Math.min(255, amplifier));
}
/**
@@ -120,7 +120,7 @@ public class AbilityPotionEffect {
}
public void setAmplifier(int amplifier) {
this.amplifier = Math.max(0, Math.min(10, amplifier));
this.amplifier = Math.max(0, Math.min(255, amplifier));
}
/**
@@ -112,11 +112,22 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
widgetId++;
if (effect.getType() != EnumPotionType.Fire) {
GuiNpcButton ampBtn = new GuiNpcButton(widgetId, colLLabel + 158, y, 40, 20, ampValues, effect.getAmplifier());
sw.addButton(ampBtn);
buttonFieldMap.put(widgetId, def);
effectWidgetMeta.put(widgetId, new int[]{e, 2});
widgetId++;
if (effect.getType() == EnumPotionType.Manual) {
GuiNpcTextField ampField = new GuiNpcTextField(widgetId, parent, fontRenderer,
colLLabel + 158, y, 40, 20, String.valueOf(effect.getAmplifier()));
ampField.setIntegersOnly();
ampField.setMinMaxDefault(0, 255, 0);
sw.addTextField(ampField);
textFieldMap.put(widgetId, def);
effectWidgetMeta.put(widgetId, new int[]{e, 2});
widgetId++;
} else {
GuiNpcButton ampBtn = new GuiNpcButton(widgetId, colLLabel + 158, y, 40, 20, ampValues, effect.getAmplifier());
sw.addButton(ampBtn);
buttonFieldMap.put(widgetId, def);
effectWidgetMeta.put(widgetId, new int[]{e, 2});
widgetId++;
}
} else {
widgetId++;
}
@@ -367,8 +378,14 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
if (effects == null) return false;
switch (action) {
case 0:
if (idx < effects.size())
effects.get(idx).setType(EnumPotionType.fromIndexNoNone(((GuiNpcButton) button).getValue()));
if (idx < effects.size()) {
EnumPotionType oldType = effects.get(idx).getType();
EnumPotionType newType = EnumPotionType.fromIndexNoNone(((GuiNpcButton) button).getValue());
if (oldType == EnumPotionType.Manual && newType != EnumPotionType.Manual) {
effects.get(idx).setAmplifier(0);
}
effects.get(idx).setType(newType);
}
return true;
case 2:
if (idx < effects.size())
@@ -471,6 +488,7 @@ public class AbilityFieldBuilder extends GuiFieldBuilder {
List<AbilityPotionEffect> effects = (List<AbilityPotionEffect>) def.getValue();
if (effects != null && idx < effects.size()) {
if (action == 1) effects.get(idx).setDurationTicks(field.getInteger());
else if (action == 2) effects.get(idx).setAmplifier(field.getInteger());
else if (action == 5) effects.get(idx).setManualPotionId(field.getInteger());
}
return true;
@@ -10,6 +10,7 @@ import kamkeel.npcs.network.enums.EnumDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.controllers.PlayerDataController;
import noppes.npcs.controllers.data.PlayerData;
@@ -86,7 +87,7 @@ public final class AbilityCooldownSyncPacket extends AbstractPacket {
perDurations.put(key, duration);
}
PlayerData data = PlayerData.get(player);
PlayerData data = ClientCacheHandler.playerData;
if (data != null && data.abilityData != null) {
data.abilityData.applyCooldownSync(globalEndTime, globalDuration, perEndTimes, perDurations);
}
@@ -11,6 +11,7 @@ import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.controllers.PlayerDataController;
import noppes.npcs.controllers.data.PlayerData;
@@ -53,7 +54,7 @@ public final class PlayerAbilitySyncPacket extends AbstractPacket {
NBTTagCompound nbt = ByteBufUtils.readNBT(in);
if (nbt == null) return;
PlayerData data = PlayerData.get(player);
PlayerData data = ClientCacheHandler.playerData;
if (data != null) {
data.abilityData.readFromNBT(nbt);
}
@@ -366,8 +366,9 @@ public class ManageAuctionPacket extends AbstractPacket {
String result = controller.createGlobalListing(player, sellerName, item, startingPrice, buyoutPrice, durationHours);
if (result != null) {
// Failed create, return item (or drop to avoid loss).
if (!player.inventory.addItemStackToInventory(item.copy())) {
player.entityDropItem(item.copy(), 0.5f);
// Use same reference so partial add mutates stackSize, then drop only the remainder.
if (!player.inventory.addItemStackToInventory(item)) {
player.entityDropItem(item, 0.5f);
}
}
if (result == null) {
+1 -1
View File
@@ -122,7 +122,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta8.1")
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-beta9")
public class CustomNpcs {
@SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy")
@@ -147,10 +147,7 @@ public class ScriptPlayerEventHandler {
EntityPlayerMP mp = (EntityPlayerMP) player;
SyncController.syncEffects(mp);
SyncController.syncAbilityCooldowns(mp);
if (playerData.abilityData.needsSync) {
PlayerAbilitySyncPacket.sendToPlayer(mp);
playerData.abilityData.needsSync = false;
}
PlayerAbilitySyncPacket.sendToPlayer(mp);
playerData.abilityData.ensureLockStateClear();
}
@@ -417,7 +417,8 @@ public class ClientProxy extends CommonProxy {
@Override
public PlayerData getPlayerData(EntityPlayer player) {
if (player.getUniqueID() == Minecraft.getMinecraft().thePlayer.getUniqueID()) {
EntityPlayer local = Minecraft.getMinecraft().thePlayer;
if (local != null && player.getUniqueID().equals(local.getUniqueID())) {
if (ClientCacheHandler.playerData != null) {
if (ClientCacheHandler.playerData.player != player) {
ClientCacheHandler.playerData.player = player;
@@ -58,7 +58,13 @@ public class SubGuiNpcMeleeProperties extends SubGuiInterface implements ITextfi
if (stats.potionType != EnumPotionType.Fire) {
y += 30;
addLabel(new GuiNpcLabel(7, "stats.amplify", guiLeft + 5, y + 5));
addButton(new GuiButtonBiDirectional(7, guiLeft + 85, y, 52, 20, new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, stats.potionAmp));
if (stats.potionType == EnumPotionType.Manual) {
addTextField(new GuiNpcTextField(7, this, fontRendererObj, guiLeft + 85, y, 52, 18, stats.potionAmp + ""));
getTextField(7).integersOnly = true;
getTextField(7).setMinMaxDefault(0, 255, 0);
} else {
addButton(new GuiButtonBiDirectional(7, guiLeft + 85, y, 52, 20, new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, stats.potionAmp));
}
}
}
@@ -76,6 +82,8 @@ public class SubGuiNpcMeleeProperties extends SubGuiInterface implements ITextfi
stats.knockback = textfield.getInteger();
} else if (textfield.id == 6) {
stats.potionDuration = textfield.getInteger();
} else if (textfield.id == 7) {
stats.potionAmp = textfield.getInteger();
} else if (textfield.id == 8) {
stats.potionManualId = textfield.getInteger();
}
@@ -84,7 +92,11 @@ public class SubGuiNpcMeleeProperties extends SubGuiInterface implements ITextfi
protected void actionPerformed(GuiButton guibutton) {
GuiNpcButton button = (GuiNpcButton) guibutton;
if (button.id == 5) {
stats.potionType = EnumPotionType.fromOrdinal(button.getValue());
EnumPotionType newType = EnumPotionType.fromOrdinal(button.getValue());
if (stats.potionType == EnumPotionType.Manual && newType != EnumPotionType.Manual) {
stats.potionAmp = 0;
}
stats.potionType = newType;
initGui();
}
if (button.id == 7) {
@@ -97,7 +97,13 @@ public class SubGuiNpcProjectiles extends SubGuiInterface implements ITextfieldL
} else {
internalY += 30;
addLabel(new GuiNpcLabel(70, "stats.amplify", guiLeft + 210, internalY + 5));
addButton(new GuiButtonBiDirectional(10, guiLeft + 280, internalY, 52, 20, new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, stats.pEffAmp));
if (stats.pEffect == EnumPotionType.Manual) {
addTextField(new GuiNpcTextField(10, this, fontRendererObj, guiLeft + 280, internalY, 52, 20, stats.pEffAmp + ""));
getTextField(10).integersOnly = true;
getTextField(10).setMinMaxDefault(0, 255, 0);
} else {
addButton(new GuiButtonBiDirectional(10, guiLeft + 280, internalY, 52, 20, new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, stats.pEffAmp));
}
}
}
@@ -132,6 +138,8 @@ public class SubGuiNpcProjectiles extends SubGuiInterface implements ITextfieldL
stats.pSpeed = textfield.getInteger();
} else if (textfield.id == 5) {
stats.pDur = textfield.getInteger();
} else if (textfield.id == 10) {
stats.pEffAmp = textfield.getInteger();
} else if (textfield.id == 11) {
stats.pManualId = textfield.getInteger();
}
@@ -154,7 +162,11 @@ public class SubGuiNpcProjectiles extends SubGuiInterface implements ITextfieldL
stats.pArea = button.getValue();
}
if (button.id == 4) {
stats.pEffect = EnumPotionType.fromOrdinal(button.getValue());
EnumPotionType newType = EnumPotionType.fromOrdinal(button.getValue());
if (stats.pEffect == EnumPotionType.Manual && newType != EnumPotionType.Manual) {
stats.pEffAmp = 0;
}
stats.pEffect = newType;
initGui();
}
if (button.id == 5) {
@@ -63,6 +63,9 @@ public class GuiNpcTextField extends GuiTextField {
if (integersOnly && getText().isEmpty() && c == '-')
return true;
if ((floatsOnly || doublesOnly) && getText().isEmpty() && c == '-')
return true;
for (int j : allowedSpecialChars)
if (j == i)
return true;
@@ -289,7 +289,15 @@ public class AuctionController implements IAuctionHandler {
// Global claims are not tied to a single player.
claim.playerUUID = null;
claim.playerName = "Global";
globalClaims.add(claim);
synchronized (globalClaims) {
// Prevent duplicate claim IDs
for (AuctionClaim existing : globalClaims) {
if (existing.id.equals(claim.id)) {
return;
}
}
globalClaims.add(claim);
}
markDirty();
}
@@ -305,19 +313,23 @@ public class AuctionController implements IAuctionHandler {
private AuctionClaim getGlobalClaimInternal(String claimId) {
if (claimId == null || claimId.isEmpty()) return null;
for (AuctionClaim claim : globalClaims) {
if (claim != null && !claim.claimed && claimId.equals(claim.id)) {
return claim;
synchronized (globalClaims) {
for (AuctionClaim claim : globalClaims) {
if (claim != null && !claim.claimed && claimId.equals(claim.id)) {
return claim;
}
}
}
return null;
}
private boolean claimAndRemoveGlobal(String claimId) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) return false;
claim.claimed = true;
return globalClaims.remove(claim);
synchronized (globalClaims) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) return false;
claim.claimed = true;
return globalClaims.remove(claim);
}
}
/**
@@ -358,15 +370,27 @@ public class AuctionController implements IAuctionHandler {
return "This item cannot be listed on the Auction House.";
}
// Validate minimum price
// Validate prices
if (startingPrice <= 0) {
return "Starting price must be positive.";
}
if (startingPrice < ConfigMarket.MinimumListingPrice) {
return "Starting price must be at least " + ConfigMarket.MinimumListingPrice + " " + ConfigMarket.CurrencyName + ".";
}
if (startingPrice > ConfigMarket.MaxBalance) {
return "Starting price exceeds maximum allowed value.";
}
// Validate buyout price if set
if (buyoutPrice < 0) {
return "Buyout price cannot be negative.";
}
if (buyoutPrice > 0 && buyoutPrice < startingPrice) {
return "Buyout price must be higher than starting price.";
}
if (buyoutPrice > ConfigMarket.MaxBalance) {
return "Buyout price exceeds maximum allowed value.";
}
UUID playerUUID = player.getUniqueID();
String playerName = player.getCommandSenderName();
@@ -386,7 +410,7 @@ public class AuctionController implements IAuctionHandler {
PlayerTradeData currency = playerData.tradeData;
long fee = ConfigMarket.ListingFee;
if (fee > 0 && !currency.canAfford(fee)) {
if (!currency.canAfford(fee)) {
return "You cannot afford the listing fee (" + fee + " " + ConfigMarket.CurrencyName + ").";
}
@@ -400,7 +424,7 @@ public class AuctionController implements IAuctionHandler {
}
// Deduct fee
if (fee > 0 && !currency.withdraw(fee)) {
if (!currency.withdraw(fee)) {
return "Failed to deduct listing fee.";
}
@@ -435,9 +459,16 @@ public class AuctionController implements IAuctionHandler {
return "No item provided.";
}
// Validate prices
if (startingPrice <= 0) {
return "Starting price must be positive.";
}
if (startingPrice < ConfigMarket.MinimumListingPrice) {
return "Starting price must be at least " + ConfigMarket.MinimumListingPrice + " " + ConfigMarket.CurrencyName + ".";
}
if (buyoutPrice < 0) {
return "Buyout price cannot be negative.";
}
if (buyoutPrice > 0 && buyoutPrice < startingPrice) {
return "Buyout price must be higher than starting price.";
}
@@ -510,22 +541,23 @@ public class AuctionController implements IAuctionHandler {
listing.sellerUUID, listing.sellerName, listing.id, listing.item);
addClaimToPlayer(listing.sellerUUID, itemClaim);
// Deduct cancellation penalty from seller (take what they can afford)
long actualPenalty = 0;
// Deduct cancellation penalty from seller
if (penalty > 0) {
PlayerData sellerData = PlayerDataController.Instance.getData(listing.sellerUUID);
if (sellerData != null) {
long sellerBalance = sellerData.tradeData.getBalance();
actualPenalty = Math.min(penalty, sellerBalance);
if (actualPenalty > 0) {
sellerData.tradeData.withdraw(actualPenalty);
if (!sellerData.tradeData.withdraw(penalty)) {
// Withdraw whatever they have if they can't cover the full penalty
long available = sellerData.tradeData.getBalance();
if (available > 0) {
sellerData.tradeData.withdraw(available);
}
}
sellerData.save();
}
}
logAuction(EnumAuctionLogAction.CANCELLED, listing.sellerName, listing.item.getDisplayName(), listing.currentBid,
"Penalty: " + actualPenalty + "/" + penalty + ", Bidder refunded: " + listing.highBidderName +
"Penalty: " + penalty + ", Bidder refunded: " + listing.highBidderName +
(isAdmin ? ", Cancelled by admin: " + player.getCommandSenderName() : ""));
} else {
// No bids - just return item
@@ -556,6 +588,11 @@ public class AuctionController implements IAuctionHandler {
return "Auction is disabled.";
}
// Validate bid amount
if (bidAmount <= 0) {
return "Bid amount must be positive.";
}
UUID playerUUID = player.getUniqueID();
String playerName = player.getCommandSenderName();
@@ -647,10 +684,10 @@ public class AuctionController implements IAuctionHandler {
listing.currentBid = bidAmount;
listing.bidCount++;
// Add to new bidder's active bids (inside sync to prevent trade slot count race)
// Add to new bidder's active bids
addToPlayerBids(playerUUID, listing.id);
// Snipe protection (inside sync to prevent stale endTime reads)
// Snipe protection (must be inside sync to prevent race with endAuction)
listing.extendForSnipeProtection(ConfigMarket.SnipeProtectionMinutes);
}
@@ -839,30 +876,36 @@ public class AuctionController implements IAuctionHandler {
}
public String claimGlobalItem(String claimId, EntityPlayer player) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) {
return "Global claim not found.";
}
if (!claim.type.isItem()) {
return "This global claim is not an item claim.";
}
if (claim.item == null) {
return "Item data is missing.";
}
String listingId;
String itemName;
synchronized (globalClaims) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) {
return "Global claim not found.";
}
if (!claim.type.isItem()) {
return "This global claim is not an item claim.";
}
if (claim.item == null) {
return "Item data is missing.";
}
// Pre-check inventory space to prevent partial-add dupe
if (!canInventoryHold(player, claim.item)) {
return "Your inventory is full.";
// Check inventory space before adding (addItemStackToInventory mutates even on partial add)
if (!canFitInInventory(player, claim.item)) {
return "Not enough inventory space to claim this item.";
}
// Add to inventory (safe - we verified space above)
player.inventory.addItemStackToInventory(claim.item.copy());
listingId = claim.listingId;
itemName = claim.item.getDisplayName();
claimAndRemoveGlobal(claim.id);
}
// Safe to add - inventory has enough space for the full stack
player.inventory.addItemStackToInventory(claim.item.copy());
claimAndRemoveGlobal(claim.id);
cleanupListingIfComplete(claim.listingId);
cleanupListingIfComplete(listingId);
markDirty();
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), claim.item.getDisplayName(), 0,
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), itemName, 0,
"Global item claim");
return null;
}
@@ -871,19 +914,25 @@ public class AuctionController implements IAuctionHandler {
* Global currency claims are acknowledged/cleared and do not alter player balances.
*/
public String claimGlobalCurrency(String claimId, EntityPlayer player) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) {
return "Global claim not found.";
}
if (!claim.type.isCurrency()) {
return "This global claim is not a currency claim.";
}
String listingId;
long currency;
synchronized (globalClaims) {
AuctionClaim claim = getGlobalClaimInternal(claimId);
if (claim == null) {
return "Global claim not found.";
}
if (!claim.type.isCurrency()) {
return "This global claim is not a currency claim.";
}
claimAndRemoveGlobal(claim.id);
cleanupListingIfComplete(claim.listingId);
listingId = claim.listingId;
currency = claim.currency;
claimAndRemoveGlobal(claim.id);
}
cleanupListingIfComplete(listingId);
markDirty();
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), ConfigMarket.CurrencyName, claim.currency,
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), ConfigMarket.CurrencyName, currency,
"Global currency claim acknowledged (no balance change)");
return null;
}
@@ -919,12 +968,12 @@ public class AuctionController implements IAuctionHandler {
return "Claim was prevented.";
}
// Pre-check inventory space to prevent partial-add dupe
if (!canInventoryHold(player, claim.item)) {
return "Your inventory is full.";
// Check inventory space before adding (addItemStackToInventory mutates even on partial add)
if (!canFitInInventory(player, claim.item)) {
return "Not enough inventory space to claim this item.";
}
// Safe to add - inventory has enough space for the full stack
// Add to inventory (safe - we verified space above)
player.inventory.addItemStackToInventory(claim.item.copy());
// Remove claim from player's auction data
@@ -986,26 +1035,30 @@ public class AuctionController implements IAuctionHandler {
}
/**
* Pre-check if the player's inventory can fully hold an ItemStack.
* Unlike addItemStackToInventory, this does NOT modify the inventory.
* Prevents partial-add dupe exploits.
* Check if a player's inventory can fit the entire ItemStack without partial adds.
* Unlike addItemStackToInventory, this does NOT mutate the inventory.
*/
private boolean canInventoryHold(EntityPlayer player, ItemStack item) {
if (item == null || item.stackSize <= 0) return true;
private boolean canFitInInventory(EntityPlayer player, ItemStack stack) {
int remaining = stack.stackSize;
int maxStack = stack.getMaxStackSize();
ItemStack[] mainInventory = player.inventory.mainInventory;
int remaining = item.stackSize;
int maxStack = Math.min(player.inventory.getInventoryStackLimit(), item.getMaxStackSize());
for (int i = 0; i < mainInventory.length; i++) {
if (remaining <= 0) return true;
for (int i = 0; i < player.inventory.mainInventory.length && remaining > 0; i++) {
ItemStack slot = player.inventory.mainInventory[i];
ItemStack slot = mainInventory[i];
if (slot == null) {
remaining -= maxStack;
} else if (slot.getItem() == item.getItem()
&& slot.getItemDamage() == item.getItemDamage()
&& ItemStack.areItemStackTagsEqual(slot, item)) {
remaining -= (maxStack - slot.stackSize);
remaining -= Math.min(remaining, maxStack);
} else if (slot.getItem() == stack.getItem()
&& slot.getItemDamage() == stack.getItemDamage()
&& ItemStack.areItemStackTagsEqual(slot, stack)) {
int spaceInSlot = maxStack - slot.stackSize;
if (spaceInSlot > 0) {
remaining -= Math.min(remaining, spaceInSlot);
}
}
}
return remaining <= 0;
}
@@ -1055,7 +1108,6 @@ public class AuctionController implements IAuctionHandler {
for (AuctionListing listing : listings.values()) {
if (listing.status != EnumAuctionStatus.ACTIVE) continue;
if (listing.isExpired()) continue;
// Apply search filter (searches item name AND seller name)
if (filter.hasSearchText()) {
@@ -1106,7 +1158,6 @@ public class AuctionController implements IAuctionHandler {
int count = 0;
for (AuctionListing listing : listings.values()) {
if (listing.status != EnumAuctionStatus.ACTIVE) continue;
if (listing.isExpired()) continue;
if (filter.hasSearchText()) {
String itemName = listing.item != null ? listing.item.getDisplayName() : "";
String sellerName = listing.sellerName != null ? listing.sellerName : "";
@@ -1793,12 +1844,20 @@ public class AuctionController implements IAuctionHandler {
String itemName = listing.item != null ? listing.item.getDisplayName() : "Unknown Item";
// Return active highest bid - always refund to the actual bidder
// Return active highest bid.
if (listing.hasBids() && listing.highBidderUUID != null) {
AuctionClaim refundClaim = AuctionClaim.createRefundClaim(
listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, itemName,
listing.sellerName, null);
addClaimToPlayer(listing.highBidderUUID, refundClaim);
AuctionClaim refundClaim;
if (cancelCompletely) {
refundClaim = AuctionClaim.createRefundClaim(
null, "Global", listing.id, listing.currentBid, itemName,
listing.highBidderName, null);
addGlobalClaim(refundClaim);
} else {
refundClaim = AuctionClaim.createRefundClaim(
listing.highBidderUUID, listing.highBidderName, listing.id, listing.currentBid, itemName,
listing.sellerName, null);
addClaimToPlayer(listing.highBidderUUID, refundClaim);
}
removeFromPlayerBids(listing.highBidderUUID, listing.id);
}
@@ -5,6 +5,7 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import noppes.npcs.CustomNpcs;
import noppes.npcs.LogWriter;
import noppes.npcs.api.handler.ILinkedItemHandler;
import noppes.npcs.api.handler.data.ILinkedItem;
import noppes.npcs.api.item.IItemStack;
import noppes.npcs.controllers.data.IScriptHandler;
@@ -21,7 +22,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.zip.GZIPInputStream;
public class LinkedItemController {
public class LinkedItemController implements ILinkedItemHandler {
private static LinkedItemController Instance;
private int lastUsedID = 0;
@@ -40,25 +41,28 @@ public class LinkedItemController {
return Instance;
}
public LinkedItem createItem(String name) {
@Override
public ILinkedItem createItem(String name) {
return new LinkedItem(name);
}
@Override
public IItemStack createItemStack(int id) {
LinkedItem linkedItem = this.get(id);
LinkedItem linkedItem = (LinkedItem) this.get(id);
if (linkedItem != null) {
return linkedItem.createStack();
}
return null;
}
public void add(LinkedItem linkedItem) {
@Override
public void add(ILinkedItem linkedItem) {
if (linkedItem != null) {
String name = linkedItem.getName();
if (name != null && !name.isEmpty()) {
int linkedItemId = linkedItem.getId();
int id = this.linkedItems.containsKey(linkedItemId) ? linkedItemId : this.getUnusedId();
this.linkedItems.put(id, linkedItem);
this.linkedItems.put(id, (LinkedItem) linkedItem);
linkedItem.setId(id);
this.addScript(id);
}
@@ -69,6 +73,7 @@ public class LinkedItemController {
this.linkedItemsScripts.put(id, new LinkedItemScript());
}
@Override
public LinkedItem remove(int id) {
this.removeScript(id);
return this.linkedItems.remove(id);
@@ -78,16 +83,19 @@ public class LinkedItemController {
this.linkedItemsScripts.remove(id);
}
public LinkedItem get(int id) {
return this.linkedItems.get(id);
@Override
public ILinkedItem get(int id) {
return (LinkedItem) this.linkedItems.get(id);
}
@Override
public boolean contains(int id) {
return this.linkedItems.containsKey(id);
}
public boolean contains(LinkedItem linkedItem) {
return this.linkedItems.containsValue(linkedItem);
@Override
public boolean contains(ILinkedItem linkedItem) {
return this.linkedItems.containsValue((LinkedItem) linkedItem);
}
public IScriptHandler getScriptHandler(int id) {
@@ -219,7 +227,7 @@ public class LinkedItemController {
}
public void delete(int id) {
LinkedItem linkedItem = get(id);
LinkedItem linkedItem = (LinkedItem) get(id);
if (linkedItem != null) {
LinkedItem foundItem = remove(id);
if (foundItem != null && foundItem.name != null) {
@@ -91,14 +91,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
*/
private transient byte lastSyncedFlags = 0;
/**
* Dirty flag: set when ability data changes (unlock/lock/select/toggle).
* Checked by the periodic 10-tick sync handler, which sends an ability
* data packet and clears the flag. Avoids scattering sync calls across
* every mutation method.
*/
public transient boolean needsSync = false;
/**
* World time when the last ability was activated (for double-press cancel detection).
*/
@@ -497,7 +489,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
@Override
protected void onToggleStateChanged(String key, boolean active, int state) {
needsSync = true;
}
@Override
@@ -534,20 +525,17 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
public void setSelectedIndex(int index) {
if (index == -1 || (index >= 0 && index < unlockedAbilities.size())) {
this.selectedIndex = index;
needsSync = true;
}
}
public void selectNext() {
if (unlockedAbilities.isEmpty()) return;
selectedIndex = (selectedIndex + 1) % unlockedAbilities.size();
needsSync = true;
}
public void selectPrevious() {
if (unlockedAbilities.isEmpty()) return;
selectedIndex = (selectedIndex - 1 + unlockedAbilities.size()) % unlockedAbilities.size();
needsSync = true;
}
public String getSelectedAbilityKey() {
@@ -576,13 +564,11 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
if (selectedIndex >= unlockedAbilities.size()) {
selectedIndex = Math.max(0, unlockedAbilities.size() - 1);
}
needsSync = true;
}
public void unlockAbility(String key) {
if (key != null && !key.isEmpty() && !unlockedAbilities.contains(key)) {
unlockedAbilities.add(key);
needsSync = true;
}
}
@@ -591,7 +577,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
if (selectedIndex >= unlockedAbilities.size()) {
selectedIndex = Math.max(0, unlockedAbilities.size() - 1);
}
needsSync = true;
}
public boolean hasUnlockedAbility(String key) {
@@ -837,7 +822,6 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA
// Reset sync tracking so next sync sends fresh state
lastSyncedFlags = 0;
needsSync = true;
}
/**
@@ -245,8 +245,14 @@ public class PlayerTradeData implements IPlayerTradeData {
* Add a claim to this player's pending claims.
* Thread-safe - can be called from any thread.
*/
public void addClaim(AuctionClaim claim) {
public synchronized void addClaim(AuctionClaim claim) {
if (claim != null && !claim.claimed) {
// Prevent duplicate claim IDs
for (AuctionClaim existing : claims) {
if (existing.id.equals(claim.id)) {
return;
}
}
claims.add(claim);
}
}
@@ -276,8 +282,9 @@ public class PlayerTradeData implements IPlayerTradeData {
/**
* Get a specific claim by ID (internal use).
* Synchronized to pair with claimAndRemove for thread safety.
*/
public AuctionClaim getClaimInternal(String claimId) {
public synchronized AuctionClaim getClaimInternal(String claimId) {
for (AuctionClaim claim : claims) {
if (claim.id.equals(claimId) && !claim.claimed) {
return claim;
@@ -296,8 +303,9 @@ public class PlayerTradeData implements IPlayerTradeData {
/**
* Mark a claim as claimed and remove it.
* Synchronized to prevent double-claim from concurrent threads.
*/
public boolean claimAndRemove(String claimId) {
public synchronized boolean claimAndRemove(String claimId) {
for (AuctionClaim claim : claims) {
if (claim.id.equals(claimId) && !claim.claimed) {
claim.claimed = true;
+6 -34
View File
@@ -71,24 +71,7 @@ import noppes.npcs.api.entity.ICustomNpc;
import noppes.npcs.api.entity.IEntity;
import noppes.npcs.api.entity.IPlayer;
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.IAttributeHandler;
import noppes.npcs.api.handler.IAuctionHandler;
import noppes.npcs.api.handler.ICloneHandler;
import noppes.npcs.api.handler.ICustomEffectHandler;
import noppes.npcs.api.handler.IDialogHandler;
import noppes.npcs.api.handler.IFactionHandler;
import noppes.npcs.api.handler.IMagicHandler;
import noppes.npcs.api.handler.INaturalSpawnsHandler;
import noppes.npcs.api.handler.IPartyHandler;
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.*;
import noppes.npcs.api.handler.data.IAnimation;
import noppes.npcs.api.handler.data.IFrame;
import noppes.npcs.api.handler.data.IFramePart;
@@ -99,22 +82,7 @@ import noppes.npcs.compat.PixelmonHelper;
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;
import noppes.npcs.controllers.FactionController;
import noppes.npcs.controllers.MagicController;
import noppes.npcs.controllers.PartyController;
import noppes.npcs.controllers.QuestController;
import noppes.npcs.controllers.RecipeController;
import noppes.npcs.controllers.ScriptController;
import noppes.npcs.controllers.ScriptEntityData;
import noppes.npcs.controllers.ScriptHookController;
import noppes.npcs.controllers.ServerCloneController;
import noppes.npcs.controllers.SpawnController;
import noppes.npcs.controllers.TransportController;
import noppes.npcs.controllers.*;
import noppes.npcs.controllers.data.Animation;
import noppes.npcs.controllers.data.Frame;
import noppes.npcs.controllers.data.FramePart;
@@ -383,6 +351,10 @@ public class NpcAPI extends AbstractNpcAPI {
return AnimationController.Instance;
}
public ILinkedItemHandler getLinkedItems() {
return LinkedItemController.getInstance();
}
public IScriptHookHandler getScriptHooks() {
return ScriptHookController.Instance;
}