mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 18:57:32 +00:00
Merge branch 'dev' into syntax-highlighter-unified
This commit is contained in:
commit
3e1b4fd692
31 files changed
+381
-148
No files matched your search
+15
@@ -10,6 +10,7 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyHomingData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData;
|
||||
import kamkeel.npcs.controllers.data.ability.data.ProjectileData;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.controllers.data.telegraph.Telegraph;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance;
|
||||
import kamkeel.npcs.controllers.data.telegraph.TelegraphType;
|
||||
@@ -287,6 +288,15 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
chargeVisualIds[index] = id;
|
||||
chargeVisualCaster = caster;
|
||||
EnergyChargeSpawnPacket.sendToTracking(id, previewEntity, caster);
|
||||
|
||||
// Register with server-side tracker for late-join support
|
||||
EnergyChargeTracker.Instance.add(new EnergyChargeTracker.ChargeEntry(
|
||||
id,
|
||||
previewEntity.getClass().getName(),
|
||||
previewEntity.exportSpawnNBT(),
|
||||
caster.getEntityId(),
|
||||
(int) caster.worldObj.getTotalWorldTime()
|
||||
));
|
||||
}
|
||||
|
||||
protected void removeChargeVisual(EntityLivingBase caster, int index) {
|
||||
@@ -296,6 +306,7 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
String id = chargeVisualIds[index];
|
||||
if (id != null && !id.isEmpty()) {
|
||||
EnergyChargeRemovePacket.sendToTracking(id, caster);
|
||||
EnergyChargeTracker.Instance.remove(id, caster.getEntityId());
|
||||
chargeVisualIds[index] = null;
|
||||
}
|
||||
}
|
||||
@@ -305,6 +316,10 @@ public abstract class AbilityEnergyProjectile<E extends EntityEnergyProjectile>
|
||||
for (int i = 0; i < chargeVisualIds.length; i++) {
|
||||
removeChargeVisual(caster, i);
|
||||
}
|
||||
// Safety net: ensure tracker is fully cleaned for this caster
|
||||
if (!isPreview() && caster.worldObj != null && !caster.worldObj.isRemote) {
|
||||
EnergyChargeTracker.Instance.removeAllForCaster(caster.getEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LIFECYCLE ====================
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package kamkeel.npcs.controllers.data.energycharge;
|
||||
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.packets.data.energycharge.EnergyChargeSpawnPacket;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Server-side tracker for active charging preview visuals.
|
||||
* Stores enough data to resend {@link EnergyChargeSpawnPacket} to late-joining players
|
||||
* who enter tracking range after the initial broadcast.
|
||||
* <p>
|
||||
* Keyed by caster entity ID so {@code PlayerEvent.StartTracking} can look up
|
||||
* active charges for a newly tracked entity.
|
||||
*/
|
||||
public class EnergyChargeTracker {
|
||||
public static final EnergyChargeTracker Instance = new EnergyChargeTracker();
|
||||
|
||||
public static class ChargeEntry {
|
||||
public final String instanceId;
|
||||
public final String entityClassName;
|
||||
public final NBTTagCompound spawnNbt;
|
||||
public final int casterEntityId;
|
||||
public final int startTick;
|
||||
|
||||
public ChargeEntry(String instanceId, String entityClassName,
|
||||
NBTTagCompound spawnNbt, int casterEntityId, int startTick) {
|
||||
this.instanceId = instanceId;
|
||||
this.entityClassName = entityClassName;
|
||||
this.spawnNbt = spawnNbt;
|
||||
this.casterEntityId = casterEntityId;
|
||||
this.startTick = startTick;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Integer, List<ChargeEntry>> activeCharges = new HashMap<>();
|
||||
|
||||
public void add(ChargeEntry entry) {
|
||||
List<ChargeEntry> list = activeCharges.get(entry.casterEntityId);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
activeCharges.put(entry.casterEntityId, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
|
||||
public void remove(String instanceId, int casterEntityId) {
|
||||
List<ChargeEntry> list = activeCharges.get(casterEntityId);
|
||||
if (list == null) return;
|
||||
Iterator<ChargeEntry> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().instanceId.equals(instanceId)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
activeCharges.remove(casterEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllForCaster(int casterEntityId) {
|
||||
activeCharges.remove(casterEntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all active charges for a caster to a specific player.
|
||||
* Adjusts ChargeTick in the NBT to reflect elapsed time so the
|
||||
* late-joining client starts at the correct visual progress.
|
||||
*/
|
||||
public void sendToPlayer(int casterEntityId, EntityPlayerMP player, int currentWorldTick) {
|
||||
List<ChargeEntry> entries = activeCharges.get(casterEntityId);
|
||||
if (entries == null || entries.isEmpty()) return;
|
||||
|
||||
for (ChargeEntry entry : entries) {
|
||||
int elapsed = currentWorldTick - entry.startTick;
|
||||
int chargeDuration = entry.spawnNbt.hasKey("ChargeDuration")
|
||||
? entry.spawnNbt.getInteger("ChargeDuration") : 0;
|
||||
|
||||
// Charge already complete — server remove packet should follow shortly
|
||||
if (chargeDuration > 0 && elapsed >= chargeDuration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NBTTagCompound adjustedNbt = (NBTTagCompound) entry.spawnNbt.copy();
|
||||
if (elapsed > 0) {
|
||||
adjustedNbt.setInteger("ChargeTick", elapsed);
|
||||
}
|
||||
|
||||
PacketHandler.Instance.sendToPlayer(
|
||||
new EnergyChargeSpawnPacket(entry.instanceId, entry.entityClassName, adjustedNbt),
|
||||
player
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasCharges(int casterEntityId) {
|
||||
List<ChargeEntry> list = activeCharges.get(casterEntityId);
|
||||
return list != null && !list.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
activeCharges.clear();
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,9 @@ public class CNPCData implements IProfileData {
|
||||
if (playerData.partyUUID != null)
|
||||
return ProfileOperation.error("Cannot switch while in Party");
|
||||
|
||||
if (playerData.abilityData.isExecutingAbility())
|
||||
return ProfileOperation.error("Cannot switch while performing an Ability");
|
||||
|
||||
return ProfileOperation.success("");
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,10 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
if (ticksExisted == 1 && !worldObj.isRemote) {
|
||||
trackProjectile(this);
|
||||
}
|
||||
} else {
|
||||
// Preview entities skip super.onUpdate() but still need ticksExisted
|
||||
// for renderer pulsing effects (e.g., sin(ticksExisted + partialTicks)).
|
||||
this.ticksExisted++;
|
||||
}
|
||||
|
||||
if (!previewMode) {
|
||||
@@ -230,8 +234,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
// Update rotation
|
||||
updateRotation();
|
||||
|
||||
// Lerp render size toward actual size
|
||||
this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f;
|
||||
// Update render size: during charging, track directly (size already grows smoothly).
|
||||
// Post-charging, lerp for smooth visual transitions.
|
||||
if (isCharging()) {
|
||||
this.renderCurrentSize = this.size;
|
||||
} else {
|
||||
this.renderCurrentSize = this.renderCurrentSize + (this.size - this.renderCurrentSize) * 0.15f;
|
||||
}
|
||||
|
||||
// Server-synced visual timer used for brief barrier impact lightning.
|
||||
if (!previewMode && !worldObj.isRemote) {
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
@@ -311,6 +311,10 @@ public class AuctionActionPacket extends AbstractPacket {
|
||||
ItemStack item = ItemStack.loadItemStackFromNBT(itemNBT);
|
||||
if (item == null) return "Invalid item data.";
|
||||
|
||||
// Validate stack size
|
||||
if (item.stackSize <= 0) return "Invalid item amount.";
|
||||
if (item.stackSize > item.getMaxStackSize()) return "Stack size exceeds item maximum.";
|
||||
|
||||
// Remove item from inventory first
|
||||
if (!removeItemFromInventory(player, item)) {
|
||||
return "Item not found in inventory.";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,10 +13,13 @@ import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
|
||||
import noppes.npcs.CustomNpcsPermissions;
|
||||
import noppes.npcs.NoppesUtilServer;
|
||||
import noppes.npcs.controllers.DialogController;
|
||||
import noppes.npcs.controllers.QuestController;
|
||||
import noppes.npcs.controllers.data.Dialog;
|
||||
import noppes.npcs.controllers.data.Quest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -80,6 +83,13 @@ public final class DialogSavePacket extends AbstractPacket {
|
||||
} else {
|
||||
NoppesUtilServer.sendDialogData((EntityPlayerMP) player, dialog.category);
|
||||
}
|
||||
|
||||
// Send full dialog data back so the client doesn't need a second round-trip
|
||||
NBTTagCompound dialogData = dialog.writeToNBT(new NBTTagCompound());
|
||||
Quest quest = QuestController.Instance.quests.get(dialog.quest);
|
||||
if (quest != null)
|
||||
dialogData.setString("DialogQuestName", quest.title);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, dialogData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import kamkeel.npcs.util.ByteBufUtils;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import kamkeel.npcs.network.packets.data.large.GuiDataPacket;
|
||||
import noppes.npcs.CustomNpcsPermissions;
|
||||
import noppes.npcs.NoppesUtilServer;
|
||||
import noppes.npcs.controllers.QuestController;
|
||||
@@ -79,6 +80,12 @@ public final class QuestSavePacket extends AbstractPacket {
|
||||
} else {
|
||||
NoppesUtilServer.sendQuestData((EntityPlayerMP) player, quest.category);
|
||||
}
|
||||
|
||||
// Send full quest data back so the client doesn't need a second round-trip
|
||||
NBTTagCompound questData = new NBTTagCompound();
|
||||
if (quest.hasNewQuest())
|
||||
questData.setString("NextQuestTitle", quest.getNextQuest().title);
|
||||
GuiDataPacket.sendGuiData((EntityPlayerMP) player, quest.writeToNBT(questData));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ 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.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.controllers.data.profile.CNPCData;
|
||||
import kamkeel.npcs.developer.Developer;
|
||||
import kamkeel.npcs.entity.EntityAbilityBarrier;
|
||||
@@ -37,6 +38,7 @@ import kamkeel.npcs.entity.EntityAbilityPanel;
|
||||
import kamkeel.npcs.entity.EntityAbilitySweeper;
|
||||
import kamkeel.npcs.entity.EntityAbilityZone;
|
||||
import kamkeel.npcs.entity.EntityAbilityDome;
|
||||
import kamkeel.npcs.entity.EntityEnergyProjectile;
|
||||
import kamkeel.npcs.entity.EntityEnergySlicer;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.util.BukkitUtil;
|
||||
@@ -430,8 +432,9 @@ public class CustomNpcs {
|
||||
AuctionController.Instance.save();
|
||||
}
|
||||
|
||||
kamkeel.npcs.entity.EntityEnergyProjectile.clearAllProjectiles();
|
||||
EntityEnergyProjectile.clearAllProjectiles();
|
||||
EntityAbilityBarrier.clearAllBarriers();
|
||||
EnergyChargeTracker.Instance.clear();
|
||||
|
||||
if (FMLCommonHandler.instance().getSide().isClient())
|
||||
clientJaninoCompiler = null;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.data.energycharge.EnergyChargeTracker;
|
||||
import kamkeel.npcs.entity.EntityAbilityBarrier;
|
||||
import kamkeel.npcs.entity.EntityAbilityDome;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
@@ -300,6 +301,7 @@ public class ServerEventsHandler {
|
||||
public void invoke(LivingDeathEvent event) {
|
||||
if (event.entityLiving.worldObj.isRemote)
|
||||
return;
|
||||
EnergyChargeTracker.Instance.removeAllForCaster(event.entityLiving.getEntityId());
|
||||
if (event.source.getEntity() != null) {
|
||||
if (event.source.getEntity() instanceof EntityPlayer) {
|
||||
doExcalibur((EntityPlayer) event.source.getEntity(), event.entityLiving);
|
||||
@@ -535,7 +537,19 @@ public class ServerEventsHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void playerTracking(PlayerEvent.StartTracking event) {
|
||||
if (!(event.target instanceof EntityPlayerMP || event.target instanceof EntityNPCInterface) || event.target.worldObj.isRemote)
|
||||
if (event.target.worldObj.isRemote) return;
|
||||
|
||||
// Energy charge visuals: send active charges for newly tracked entity
|
||||
if (event.target instanceof EntityLivingBase
|
||||
&& event.entityPlayer instanceof EntityPlayerMP) {
|
||||
EnergyChargeTracker.Instance.sendToPlayer(
|
||||
event.target.getEntityId(),
|
||||
(EntityPlayerMP) event.entityPlayer,
|
||||
(int) event.target.worldObj.getTotalWorldTime()
|
||||
);
|
||||
}
|
||||
|
||||
if (!(event.target instanceof EntityPlayerMP || event.target instanceof EntityNPCInterface))
|
||||
return;
|
||||
|
||||
AnimationData animationData = AnimationData.getData(event.target);
|
||||
|
||||
@@ -419,7 +419,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;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class GuiNpcPather extends GuiNPCInterface implements IGuiData {
|
||||
drawDefaultBackground = false;
|
||||
xSize = 176;
|
||||
title = "Npc Pather";
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
ai = npc.ais;
|
||||
PacketClient.sendClient(new MovingPathGetPacket());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SubGuiAnimationFrame extends SubGuiInterface implements ITextfieldL
|
||||
|
||||
public SubGuiAnimationFrame(Frame editingFrame) {
|
||||
this.editingFrame = editingFrame;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class SubGuiAnimationOptions extends SubGuiInterface {
|
||||
|
||||
public SubGuiAnimationOptions(Animation animation) {
|
||||
this.animation = animation;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class SubGuiEditText extends SubGuiInterface {
|
||||
|
||||
public SubGuiEditText(String text) {
|
||||
this.text = text;
|
||||
setBackground("extrasmallbg.png");
|
||||
setBackground("menubg.png");
|
||||
closeOnEsc = true;
|
||||
xSize = 176;
|
||||
ySize = 71;
|
||||
|
||||
@@ -220,7 +220,7 @@ public class GuiFieldBuilder {
|
||||
float fVal = def.getValue() instanceof Number ? ((Number) def.getValue()).floatValue() : 0f;
|
||||
GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, String.valueOf(fVal));
|
||||
tf.setFloatsOnly();
|
||||
if (def.hasRange()) tf.setMinMaxDefaultFloat(def.getMin(), def.getMax(), fVal);
|
||||
tf.setMinMaxDefaultFloat(def.getMin(), def.getMax(), fVal);
|
||||
if (!def.isEnabled()) tf.setEnabled(false);
|
||||
if (hover != null) tf.setHoverText(hover);
|
||||
sw.addTextField(tf);
|
||||
@@ -233,7 +233,7 @@ public class GuiFieldBuilder {
|
||||
int iVal = def.getValue() instanceof Number ? ((Number) def.getValue()).intValue() : 0;
|
||||
GuiNpcTextField tf = new GuiNpcTextField(widgetId, parent, fontRenderer, fieldX, y, fieldW, 20, String.valueOf(iVal));
|
||||
tf.setIntegersOnly();
|
||||
if (def.hasRange()) tf.setMinMaxDefault((int) def.getMin(), (int) def.getMax(), iVal);
|
||||
tf.setMinMaxDefault((int) def.getMin(), (int) def.getMax(), iVal);
|
||||
if (!def.isEnabled()) tf.setEnabled(false);
|
||||
if (hover != null) tf.setHoverText(hover);
|
||||
sw.addTextField(tf);
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.lwjgl.opengl.GL11;
|
||||
public class GuiQuestCompletion extends GuiNPCInterface implements ITopButtonListener {
|
||||
|
||||
private Quest quest;
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
|
||||
public GuiQuestCompletion(Quest quest) {
|
||||
super();
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.Vector;
|
||||
|
||||
public class GuiTransportSelection extends GuiNPCInterface implements ITopButtonListener, IScrollData {
|
||||
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
protected int xSize;
|
||||
protected int guiLeft;
|
||||
protected int guiTop;
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class GuiScriptGlobal extends GuiNPCInterface implements IGuiData {
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/smallbg.png");
|
||||
private final ResourceLocation resource = new ResourceLocation("customnpcs", "textures/gui/menubg.png");
|
||||
private HashMap<Integer, Class<?>> scriptGuiClasses = new HashMap<>();
|
||||
|
||||
public GuiScriptGlobal() {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SubGuiNpcCooldownPicker extends SubGuiInterface implements ITextfie
|
||||
public SubGuiNpcCooldownPicker(boolean isMCCustom, long currentCooldown) {
|
||||
this.isMCCustom = isMCCustom;
|
||||
this.cooldownValue = currentCooldown;
|
||||
setBackground("smallbg.png");
|
||||
setBackground("menubg.png");
|
||||
xSize = 220;
|
||||
ySize = 222;
|
||||
closeOnEsc = 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 (!currency.canAfford(fee)) {
|
||||
if (fee > 0 && !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 (!currency.withdraw(fee)) {
|
||||
if (fee > 0 && !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.";
|
||||
}
|
||||
@@ -514,7 +545,13 @@ public class AuctionController implements IAuctionHandler {
|
||||
if (penalty > 0) {
|
||||
PlayerData sellerData = PlayerDataController.Instance.getData(listing.sellerUUID);
|
||||
if (sellerData != null) {
|
||||
sellerData.tradeData.withdraw(penalty);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -551,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();
|
||||
|
||||
@@ -641,14 +683,14 @@ public class AuctionController implements IAuctionHandler {
|
||||
listing.highBidderName = playerName;
|
||||
listing.currentBid = bidAmount;
|
||||
listing.bidCount++;
|
||||
|
||||
// Add to new bidder's active bids
|
||||
addToPlayerBids(playerUUID, listing.id);
|
||||
|
||||
// Snipe protection (must be inside sync to prevent race with endAuction)
|
||||
listing.extendForSnipeProtection(ConfigMarket.SnipeProtectionMinutes);
|
||||
}
|
||||
|
||||
// Add to new bidder's active bids
|
||||
addToPlayerBids(playerUUID, listing.id);
|
||||
|
||||
// Snipe protection
|
||||
listing.extendForSnipeProtection(ConfigMarket.SnipeProtectionMinutes);
|
||||
|
||||
markDirty();
|
||||
|
||||
logAuction(EnumAuctionLogAction.BID, playerName, listing.item.getDisplayName(), bidAmount,
|
||||
@@ -834,26 +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.";
|
||||
}
|
||||
|
||||
if (!player.inventory.addItemStackToInventory(claim.item.copy())) {
|
||||
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.";
|
||||
}
|
||||
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
cleanupListingIfComplete(claim.listingId);
|
||||
// Add to inventory (safe - we verified space above)
|
||||
player.inventory.addItemStackToInventory(claim.item.copy());
|
||||
|
||||
listingId = claim.listingId;
|
||||
itemName = claim.item.getDisplayName();
|
||||
claimAndRemoveGlobal(claim.id);
|
||||
}
|
||||
cleanupListingIfComplete(listingId);
|
||||
markDirty();
|
||||
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), claim.item.getDisplayName(), 0,
|
||||
logAuction(EnumAuctionLogAction.CLAIMED, player.getCommandSenderName(), itemName, 0,
|
||||
"Global item claim");
|
||||
return null;
|
||||
}
|
||||
@@ -862,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;
|
||||
}
|
||||
@@ -910,11 +968,14 @@ public class AuctionController implements IAuctionHandler {
|
||||
return "Claim was prevented.";
|
||||
}
|
||||
|
||||
// Check inventory space
|
||||
if (!player.inventory.addItemStackToInventory(claim.item.copy())) {
|
||||
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());
|
||||
|
||||
// Remove claim from player's auction data
|
||||
playerData.tradeData.claimAndRemove(claimId);
|
||||
playerData.updateClient = true;
|
||||
@@ -973,6 +1034,34 @@ public class AuctionController implements IAuctionHandler {
|
||||
return null; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player's inventory can fit the entire ItemStack without partial adds.
|
||||
* Unlike addItemStackToInventory, this does NOT mutate the inventory.
|
||||
*/
|
||||
private boolean canFitInInventory(EntityPlayer player, ItemStack stack) {
|
||||
int remaining = stack.stackSize;
|
||||
int maxStack = stack.getMaxStackSize();
|
||||
ItemStack[] mainInventory = player.inventory.mainInventory;
|
||||
|
||||
for (int i = 0; i < mainInventory.length; i++) {
|
||||
if (remaining <= 0) return true;
|
||||
|
||||
ItemStack slot = mainInventory[i];
|
||||
if (slot == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up a listing if it's ended and no longer needed.
|
||||
* Now just removes ended listings after some delay since claims are in PlayerData.
|
||||
@@ -1019,6 +1108,7 @@ 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()) {
|
||||
@@ -1069,6 +1159,7 @@ 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 : "";
|
||||
@@ -1755,20 +1846,12 @@ public class AuctionController implements IAuctionHandler {
|
||||
|
||||
String itemName = listing.item != null ? listing.item.getDisplayName() : "Unknown Item";
|
||||
|
||||
// Return active highest bid.
|
||||
// Return active highest bid - always refund to the actual bidder
|
||||
if (listing.hasBids() && listing.highBidderUUID != null) {
|
||||
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);
|
||||
}
|
||||
AuctionClaim 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedItem get(int id) {
|
||||
return this.linkedItems.get(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;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ItemLinked extends ItemCustomizable {
|
||||
IItemStack itemStack = NpcAPI.Instance().getIItemStack(stack);
|
||||
if (itemStack instanceof ScriptLinkedItem) {
|
||||
ScriptLinkedItem scriptLinkedItem = (ScriptLinkedItem) itemStack;
|
||||
LinkedItem linkedItem = LinkedItemController.getInstance().get(scriptLinkedItem.linkedItem.getId());
|
||||
LinkedItem linkedItem = (LinkedItem) LinkedItemController.getInstance().get(scriptLinkedItem.linkedItem.getId());
|
||||
int prevVersion = scriptLinkedItem.linkedVersion;
|
||||
if (linkedItem != null && scriptLinkedItem.linkedVersion != linkedItem.version) {
|
||||
scriptLinkedItem.linkedItem = linkedItem.clone();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user