// ========================= Auction Board (Readable Names) =========================
// UOX3 / SpiderMonkey 1.8.5 / ES5
// --------------------------- Button IDs (top-level) ---------------------------
var BID_SEARCH_APPLY = 8001;
var BID_SEARCH_CLEAR = 8002;
var GUMP_BG = 5054;
var G_W = 500, G_H = 900;
var BTN = 0xF7;
var ROWS_PER_PAGE = 10;
var TIMER_ID = 1;
var TICK_MS = 5000; // 5s tick; UI shows minutes anyway
var BID_TOGGLE_WATCH = 8102;
var BID_SORT_ENDING = 8201;
var BID_SORT_NEWEST = 8202;
var BID_SORT_HIGHEST = 8203;
var BID_FILTER_MINE = 8301;
var BID_FILTER_WATCH = 8302;
var BID_VIEW_BUYOUT = 6003;
// Anti-snipe defaults
var ANTISNIPE_WINDOW_MIN = 5; // last 5 minutes
var ANTISNIPE_EXTEND_MIN = 2; // extend by 2 minutes
var ANTISNIPE_MAX_EXTENDS = 5; // cap number of extensions
// Target IDs
var TID_ADD_AUCTION = 0;
// Row/View buttons
var BID_VIEW_BASE = 100000; // per-auction view base id
var BID_VIEW_BLOCK = 100000; // reserve 100k ids: [100000..199999)
var BID_PAGE_PREV = 2001;
var BID_PAGE_NEXT = 2002;
var BID_ADD_AUCTION = 3001;
var BID_WITHDRAW = 4001;
var BID_ADD_OK = 5001;
var BID_ADD_CANCEL = 5009;
var BID_VIEW_BID = 6001; // for donations: acts as Claim
var BID_VIEW_CLAIM = 6002; // winner claim after ended
// Money helpers
var GOLD_ID = 0x0EED;
var LAYER_BANK = 29;
// Cancellation fee rules
var CANCEL_FEE_FLAT = 0;
var CANCEL_FEE_PCT = 0.05;
var CANCEL_FEE_MIN = 100;
// Cancel confirm buttons
var BID_VIEW_CANCEL = 6101;
var BID_CANCEL_YES = 6102;
var BID_CANCEL_NO = 6103;
// Duration bounds
var DUR_MIN_MS = 60 * 1000; // 1 minute min
var DUR_MAX_MS = 365 * 24 * 60 * 60 * 1000; // 365 days max
// Ending-soon notify marks
var END_NOTIFY_MARKS = [{ ms: 300000, tag: "5m" }, { ms: 60000, tag: "1m" }];
// =============================== Naming Helpers ===============================
function getNowMillis() { return Date.now(); }
function daysToMilliseconds(n) { return (n | 0) * 86400000; }
function toIntOrDefault(v, d) { v = parseInt(v, 10); return isNaN(v) ? (d | 0) : v; }
function toNumberOrZero(v) { return Number(v) || 0; }
function toLowerTrimmed(s) { return ("" + (s || "")).toLowerCase(); }
function toHex4(v) { v |= 0; var s = v.toString(16).toUpperCase(); return "0x" + ("0000" + s).substr(-4); }
function toSerialHex8(v) { v |= 0; var s = v.toString(16).toUpperCase(); return "0x" + ("00000000" + s).substr(-8); }
function parseDurationToMilliseconds(input)
{
if (typeof input === "number") return Math.max(DUR_MIN_MS, Math.min(DUR_MAX_MS, input | 0));
var s = ("" + (input || "")).trim().toLowerCase();
if (s === "") return 0;
if (/^\d+$/.test(s))
{ // plain int => days
return Math.max(DUR_MIN_MS, Math.min(DUR_MAX_MS, parseInt(s, 10) * 86400000));
}
var re = /(\d+)\s*([smhd])/g, m, total = 0;
while ((m = re.exec(s)))
{
var n = parseInt(m[1], 10);
switch (m[2])
{
case "s": total += n * 1000; break;
case "m": total += n * 60000; break;
case "h": total += n * 3600000; break;
case "d": total += n * 86400000; break;
}
}
return Math.max(DUR_MIN_MS, Math.min(DUR_MAX_MS, total | 0));
}
function getCanonicalItemName(it)
{
if (!ValidateObject(it)) return "-";
var s = it.name || it.sectionID;
if (!s || s === "") s = "0x" + (it.id | 0).toString(16).toUpperCase();
return s;
}
// =============================== Fees & Bids ===============================
function getLastBidAmount(a) { return (a.bids && a.bids.length ? (a.bids[a.bids.length - 1].amount | 0) : 0); }
function getCurrentAuctionValue(a) { return Math.max((a.startPrice | 0), getLastBidAmount(a)); } // used for sorting
function getCurrentValueForFee(a) { return Math.max((a.startPrice | 0), getLastBidAmount(a)); } // used for fees
function calculateCancelFee(a)
{
var fee = Math.floor(getCurrentValueForFee(a) * CANCEL_FEE_PCT) + (CANCEL_FEE_FLAT | 0);
if (fee < CANCEL_FEE_MIN) fee = CANCEL_FEE_MIN;
return fee;
}
function getUniqueBidderSerials(a)
{
var seen = {}, out = [];
if (!a.bids) return out;
for (var i = 0; i < a.bids.length; i++)
{
var s = a.bids[i].char | 0;
if (!seen[s]) { seen[s] = 1; out.push(s); }
}
return out;
}
function formatTimeLeftShort(ms)
{
if (ms <= 0) return "ended";
var s = Math.floor(ms / 1000);
var d = Math.floor(s / 86400); s -= d * 86400;
var h = Math.floor(s / 3600); s -= h * 3600;
var m = Math.floor(s / 60);
if (d > 0) return d + "d " + h + "h";
if (h > 0) return h + "h " + m + "m";
return m + "m";
}
// =============================== Fingerprints ===============================
function buildItemFingerprint(it)
{
if (!ValidateObject(it)) return "missing";
var parts = [
it.id | 0, it.hue | 0, it.amount | 0,
it.sectionID || "", it.name || "",
it.more1 | 0, it.more2 | 0, it.morex | 0, it.morey | 0, it.morez | 0
];
if (typeof it.hp !== "undefined") parts.push(it.hp | 0);
if (typeof it.maxhp !== "undefined") parts.push(it.maxhp | 0);
return parts.join("|");
}
function fingerprintMatchesEscrowItem(a, it)
{
var expected = a.itemFP || a.fingerprint || "";
if (!expected) return true; // allow pre-fingerprint listings
return buildItemFingerprint(it) === expected;
}
// =============================== Gump helpers ===============================
function addItemArtToGump(g, y, x, itemID, hue)
{
if (typeof g.AddPictureColor === "function") g.AddPictureColor(y, x, itemID | 0, hue | 0);
}
// =============================== Payment ===============================
function payFromBackpackOrBank(pUser, amount)
{
amount |= 0;
if (amount <= 0) return true;
// 1) Backpack
if (pUser.ResourceCount(GOLD_ID, 0) >= amount)
{
pUser.UseResource(amount, GOLD_ID);
pUser.SysMessage(amount + " gold has been paid from your backpack.");
return true;
}
// 2) Bank box
var bankBox = pUser.FindItemLayer(LAYER_BANK);
if (bankBox)
{
var bankItem, foundGold = false;
for (bankItem = bankBox.FirstItem(); !bankBox.FinishedItems(); bankItem = bankBox.NextItem())
{
if (ValidateObject(bankItem) && bankItem.id == GOLD_ID && bankItem.amount >= amount)
{
bankBox.UseResource(amount, GOLD_ID);
foundGold = true; break;
}
}
if (foundGold)
{
pUser.SysMessage(amount + " gold has been paid from your bank account.");
return true;
}
}
return false;
}
// =============================== Storage (Auctions) ===============================
function loadAuctionsFromBoard(board)
{
var raw = board.GetTag("auctions");
if (!raw || raw === "") return [];
try { return JSON.parse(raw); } catch (e) { return []; }
}
function saveAuctionsToBoard(board, list)
{
board.SetTag("auctions", JSON.stringify(list));
}
function allocateNextAuctionId(board)
{
var n = toIntOrDefault(board.GetTag("nextAuctionId"), 1);
board.SetTag("nextAuctionId", (n + 1) + "");
return n;
}
function findAuctionInListById(list, aid)
{
for (var i = 0; i < list.length; i++) if ((list[i].id | 0) === (aid | 0)) return { idx: i, a: list[i] };
return { idx: -1, a: null };
}
// =============================== Storage (Credit) ===============================
function creditTagKeyForChar(charSer)
{
return "credit_" + (charSer | 0);
}
function addCreditForChar(board, charSer, amt)
{
if ((amt | 0) <= 0) return;
var key = creditTagKeyForChar(charSer);
var cur = toIntOrDefault(board.GetTag(key), 0);
board.SetTag(key, (cur + (amt | 0)) + "");
}
function withdrawCreditToGoldCoins(board, pUser)
{
var key = creditTagKeyForChar(pUser.serial);
var cur = toIntOrDefault(board.GetTag(key), 0);
if (cur <= 0) return 0;
var left = cur;
while (left > 0)
{
var chunk = left > 60000 ? 60000 : left;
var g = CreateDFNItem(pUser.socket, pUser, "0x0EED", chunk, "ITEM", true);
left -= chunk;
}
board.SetTag(key, "0");
return cur;
}
function getCreditForChar(board, who) { return toIntOrDefault(board.GetTag(creditTagKeyForChar(who.serial)), 0); }
// =============================== Watchlists (per-player) ===============================
var WATCHLIST_IDS_PER_TAG = 20;
function watchlistKeyForCharacterChunk(charSer, chunk) { return "widx_" + (charSer | 0) + "_" + (chunk | 0); }
function readWatchlistIdsForCharacter(board, whoSer)
{
var out = [];
for (var c = 0; ; c++)
{
var raw = board.GetTag(watchlistKeyForCharacterChunk(whoSer, c));
if (!raw || raw === "") break;
var parts = raw.split(",");
for (var i = 0; i < parts.length; i++)
{
var v = toIntOrDefault(parts[i], 0);
if (v > 0) out.push(v);
}
}
return out;
}
function writeWatchlistIdsForCharacter(board, whoSer, arr)
{
// clear existing
for (var c = 0; ; c++)
{
var k = watchlistKeyForCharacterChunk(whoSer, c), raw = board.GetTag(k);
if (!raw || raw === "") { if (c === 0) {} else break; }
board.SetTag(k, "");
}
// write chunks
var i = 0, ch = 0;
while (i < arr.length)
{
var slice = arr.slice(i, i + WATCHLIST_IDS_PER_TAG);
board.SetTag(watchlistKeyForCharacterChunk(whoSer, ch), slice.join(","));
i += WATCHLIST_IDS_PER_TAG; ch++;
}
}
function isCharacterWatchingAuction(board, whoSer, aid)
{
var arr = readWatchlistIdsForCharacter(board, whoSer);
return arr.indexOf(aid | 0) !== -1;
}
function toggleWatchForCharacter(board, whoSer, aid)
{
var arr = readWatchlistIdsForCharacter(board, whoSer);
var v = aid | 0, i = arr.indexOf(v), on;
if (i === -1) { arr.push(v); on = true; } else { arr.splice(i, 1); on = false; }
writeWatchlistIdsForCharacter(board, whoSer, arr);
return on;
}
function buildWatchSetForCharacter(board, whoSer)
{
var arr = readWatchlistIdsForCharacter(board, whoSer), s = {};
for (var i = 0; i < arr.length; i++) s[arr[i] | 0] = true;
return s;
}
// =============================== Escrow index (per-seller) ===============================
var ESCROW_SERIALS_PER_TAG = 8;
function escrowIndexKeyForOwnerChunk(ownerSer, chunk) { return "aidx_" + (ownerSer | 0) + "_" + (chunk | 0); }
function readEscrowItemSerialsForOwner(board, ownerSer)
{
var out = [];
for (var c = 0; ; c++)
{
var raw = board.GetTag(escrowIndexKeyForOwnerChunk(ownerSer, c));
if (!raw || raw === "") break;
var parts = raw.split(",");
for (var i = 0; i < parts.length; i++)
{
var s = toIntOrDefault(parts[i], 0);
if (s > 0) out.push(s);
}
}
return out;
}
function writeEscrowItemSerialsForOwner(board, ownerSer, arr)
{
// clear
for (var c = 0; ; c++)
{
var key = escrowIndexKeyForOwnerChunk(ownerSer, c);
var raw = board.GetTag(key);
if (!raw || raw === "") { if (c === 0) {} else break; }
board.SetTag(key, "");
}
// write
var idx = 0, chunk = 0;
while (idx < arr.length)
{
var slice = arr.slice(idx, idx + ESCROW_SERIALS_PER_TAG);
board.SetTag(escrowIndexKeyForOwnerChunk(ownerSer, chunk), slice.join(","));
idx += ESCROW_SERIALS_PER_TAG; chunk++;
}
}
function addEscrowItemForOwner(board, ownerSer, itemSer)
{
var arr = readEscrowItemSerialsForOwner(board, ownerSer);
var val = (itemSer | 0);
if (arr.indexOf(val) === -1) { arr.push(val); writeEscrowItemSerialsForOwner(board, ownerSer, arr); }
}
function removeEscrowItemForOwner(board, ownerSer, itemSer)
{
var arr = readEscrowItemSerialsForOwner(board, ownerSer);
var val = (itemSer | 0);
var i = arr.indexOf(val);
if (i >= 0) { arr.splice(i, 1); writeEscrowItemSerialsForOwner(board, ownerSer, arr); }
}
// Date.getMonth() is 0-based: Jan=0 ... Oct=9 ... Dec=11
function getHolidayPictureIDs(now)
{
if (!now) now = new Date();
var month = now.getMonth(); // 0..11
if (month === 9) return { left: 0x4689, right: 0x4688 }; // October: Halloween
if (month === 10) return { left: 0x46A0, right: 0x46A1 }; // November: Horn of Plenty
if (month === 11) return { left: 0x9DC0, right: 0x9DBF }; // December: Christmas
return { left: 0x18D9, right: 0x18DA }; // All other months
}
// =============================== Watchers (per-auction index) ===============================
function watcherIndexTagKeyForAuction(aid) { return "w_a_" + (aid | 0); }
function readWatcherSerialsForAuction(board, aid)
{
var raw = board.GetTag(watcherIndexTagKeyForAuction(aid)) || "";
if (!raw) return [];
var out = [], parts = raw.split(",");
for (var i = 0; i < parts.length; i++)
{
var v = toIntOrDefault(parts[i], 0);
if (v > 0 && out.indexOf(v) === -1) out.push(v);
}
return out;
}
function writeWatcherSerialsForAuction(board, aid, arr)
{
board.SetTag(watcherIndexTagKeyForAuction(aid), (arr || []).join(","));
}
function addWatcherToAuctionIndex(board, aid, whoSer)
{
var arr = readWatcherSerialsForAuction(board, aid);
var v = whoSer | 0;
if (arr.indexOf(v) === -1) { arr.push(v); writeWatcherSerialsForAuction(board, aid, arr); }
}
function removeWatcherFromAuctionIndex(board, aid, whoSer)
{
var arr = readWatcherSerialsForAuction(board, aid);
var v = whoSer | 0, i = arr.indexOf(v);
if (i !== -1) { arr.splice(i, 1); writeWatcherSerialsForAuction(board, aid, arr); }
}
// =============================== Notifications (ending soon) ===============================
function notificationTagKeyForMarkExt(aid, ser, tag, ext)
{
return "anotif_" + (aid | 0) + "_" + (ser | 0) + "_" + tag + "_e" + ((ext | 0) + "");
}
function maybeSendEndingSoonPings(board, a, nowMs)
{
if ((a.status || "open") !== "open") return;
var end = toNumberOrZero(a.endAt);
var remain = (end - nowMs) | 0;
if (remain <= 0) return;
// recipients = owner + watchers + unique bidders
var recipients = {};
var watchers = readWatcherSerialsForAuction(board, a.id | 0);
for (var i = 0; i < watchers.length; i++) recipients[watchers[i] | 0] = 1;
var uniq = getUniqueBidderSerials(a);
for (var j = 0; j < uniq.length; j++) recipients[uniq[j] | 0] = 1;
recipients[a.owner | 0] = 1;
var ext = a.snipeExtends | 0;
for (var k = 0; k < END_NOTIFY_MARKS.length; k++)
{
var mk = END_NOTIFY_MARKS[k];
if (remain <= mk.ms)
{
for (var serStr in recipients)
{
var ser = serStr | 0;
var key = notificationTagKeyForMarkExt(a.id | 0, ser, mk.tag, ext);
if (toIntOrDefault(board.GetTag(key), 0)) continue;
board.SetTag(key, "1");
var ch = CalcCharFromSer(ser);
if (ValidateObject(ch) && ch.socket != null)
{
var mins = (mk.tag === "5m") ? "5 minutes" : "1 minute";
ch.SysMessage("Auction ending in " + mins + ": " + ((a.name || a.itemName) || ""));
}
}
}
}
}
// =============================== Sorting & Filtering ===============================
function sortAuctionsByMode(list, mode)
{
var m = (mode || "ending");
var L = list.slice(0);
if (m === "ending")
{
L.sort(function(x, y) { return toNumberOrZero(x.endAt) - toNumberOrZero(y.endAt); });
} else if (m === "newest")
{
L.sort(function(x, y) { return (toNumberOrZero(y.createdAt || y.id) - toNumberOrZero(x.createdAt || x.id)); });
} else if (m === "highest")
{
L.sort(function(x, y) { return getCurrentAuctionValue(y) - getCurrentAuctionValue(x); });
}
return L;
}
/* Query tokens:
owner:<text> item:<text> desc:<text>
min:<n> max:<n> open | ended | donation | free | reserve | noreserve | buyout | nobuyout
Bare words fuzzy match owner/item/name/desc
*/
function filterAuctionsBySearchQuery(list, q)
{
q = toLowerTrimmed(q);
if (!q) return list;
var tokens = q.split(/\s+/);
return list.filter(function(a)
{
var name = toLowerTrimmed(a.name || "");
var item = toLowerTrimmed(a.itemName || "");
var desc = toLowerTrimmed(a.desc || "");
var owner = toLowerTrimmed(a.ownerName || "");
var status = a.status || "open";
var start = a.startPrice | 0;
var last = getLastBidAmount(a);
for (var i = 0; i < tokens.length; i++)
{
var t = tokens[i]; if (!t) continue;
if (t.indexOf("owner:") === 0) { if (owner.indexOf(t.substr(6)) === -1) return false; continue; }
if (t.indexOf("item:") === 0) { var n = t.substr(5); if (name.indexOf(n) === -1 && item.indexOf(n) === -1) return false; continue; }
if (t.indexOf("desc:") === 0) { if (desc.indexOf(t.substr(5)) === -1) return false; continue; }
if (t === "open") { if (status !== "open") return false; continue; }
if (t === "ended" || t === "closed") { if (status !== "ended") return false; continue; }
if (t === "donation" || t === "free") { if ((start | 0) > 0) return false; continue; }
if (t.indexOf("min:") === 0) { var v = toIntOrDefault(t.substr(4), 0); if (Math.max(start, last) < v) return false; continue; }
if (t.indexOf("max:") === 0) { var v = toIntOrDefault(t.substr(4), 0); if (Math.max(start, last) > v) return false; continue; }
if (t === "reserve") { if ((a.reservePrice | 0) <= 0) return false; continue; }
if (t === "noreserve") { if ((a.reservePrice | 0) > 0) return false; continue; }
if (t === "buyout") { if ((a.buyoutPrice | 0) <= 0) return false; continue; }
if (t === "nobuyout") { if ((a.buyoutPrice | 0) > 0) return false; continue; }
if (name.indexOf(t) === -1 && item.indexOf(t) === -1 && desc.indexOf(t) === -1 && owner.indexOf(t) === -1) return false;
}
return true;
});
}
// =============================== Gumps ===============================
function openBoardGumpUI(pUser, board, page)
{
var pSock = pUser.socket;
var listAll = loadAuctionsFromBoard(board);
// search & filters
var q = pUser.GetTempTag("auction_q") || "";
var list = filterAuctionsBySearchQuery(listAll, q);
var mine = (toIntOrDefault(pUser.GetTempTag("auction_filter_mine"), 0) === 1);
var watchOnly = (toIntOrDefault(pUser.GetTempTag("auction_filter_watch"), 0) === 1);
if (mine) list = list.filter(function(a) { return (a.owner | 0) === (pUser.serial | 0); });
if (watchOnly)
{
var ws = buildWatchSetForCharacter(board, pUser.serial | 0);
list = list.filter(function(a) { return !!ws[a.id | 0]; });
}
var sortMode = pUser.GetTempTag("auction_sort") || "ending";
list = sortAuctionsByMode(list, sortMode);
page = toIntOrDefault(page, 1);
var pages = Math.max(1, Math.ceil(list.length / ROWS_PER_PAGE));
if (page < 1) page = 1; if (page > pages) page = pages;
var g = new Gump;
g.AddBackground(0, 0, G_H, G_W, GUMP_BG);
g.AddCheckerTrans(0, 0, G_H, G_W);
//g.AddPicture( G_H - 430, 20, 0x46A0 ); // horn of plenty
//g.AddPicture( G_H - 580, 20, 0x46A1 ); // horn of plenty
var art = getHolidayPictureIDs();
g.AddPicture( G_H - 430, 20, art.left ); // seasonal left
g.AddPicture( G_H - 580, 20, art.right ); // seasonal right
// header
g.AddHTMLGump(G_H - 525, 20, 300, 74, false, false, "<BASEFONT color=#ffffff><H3>Auction Board</H3></BASEFONT>");
g.AddHTMLGump(G_H - 200, 20, 300, 74, false, false, "<BASEFONT color=#ffffff><H3>Page " + page + " / " + pages + "</H3></BASEFONT>");
if (q && q.length)
{
g.AddHTMLGump(56, 20, 300, 74, false, false, "<BASEFONT color=#ffffff><H3>Results: " + list.length + " / " + listAll.length + "</H3></BASEFONT>");
}
g.AddHTMLGump(100, 70, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Owner</H3></BASEFONT>");
g.AddHTMLGump(300, 70, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Item</H3></BASEFONT>");
g.AddHTMLGump(430, 70, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Price/Last</H3></BASEFONT>");
g.AddHTMLGump(540, 70, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Ends</H3></BASEFONT>");
g.AddHTMLGump(820, 70, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Status</H3></BASEFONT>");
// rows
var start = (page - 1) * ROWS_PER_PAGE;
for (var i = 0; i < ROWS_PER_PAGE; i++)
{
var idx = start + i;
if (idx >= list.length) break;
var a = list[idx];
var y = 90 + i * 28;
g.AddButton(16, y, BTN, 1, 0, BID_VIEW_BASE + (a.id | 0));
g.AddHTMLGump(100, y, 500, 74, false, false, "<BASEFONT color=#ffffff>" + (a.ownerName || "") + "</BASEFONT>");
g.AddHTMLGump(300, y, 500, 74, false, false, "<BASEFONT color=#ffffff>" + (a.name || a.itemName || "") + "</BASEFONT>");
var last = (a.startPrice <= 0) ? "DONATION"
: (a.bids && a.bids.length ? (a.bids[a.bids.length - 1].amount + " by " + (a.bids[a.bids.length - 1].name || ""))
: (a.startPrice + " (start)"));
var left = formatTimeLeftShort(toNumberOrZero(a.endAt) - getNowMillis());
g.AddHTMLGump(430, y, 200, 74, false, false, "<BASEFONT color=#ffffff>" + last + "</BASEFONT>");
g.AddHTMLGump(540, y, 400, 74, false, false, "<BASEFONT color=#ffffff>" + new Date(a.endAt).toLocaleString() + " (" + left + ")</BASEFONT>");
g.AddHTMLGump(820, y, 50, 74, false, false, "<BASEFONT color=#ffffff>" + (a.status || "open") + "</BASEFONT>");
}
// paging
if (page > 1) g.AddButton(G_H - 90, 20, 0x9A2, 1, 0, BID_PAGE_PREV);
if (page < pages) g.AddButton(G_H - 90, 20, 0x9A5, 1, 0, BID_PAGE_NEXT);
// withdraw & add
g.AddButton(760, 420, BTN, 1, 0, BID_WITHDRAW);
g.AddHTMLGump(630, 420, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Withdraw Credit</H3></BASEFONT>");
var myCred = getCreditForChar(board, pUser);
g.AddHTMLGump(830, 420, 50, 74, false, false, "<BASEFONT color=#ffffff><H3>(" + myCred + ")</H3></BASEFONT>");
g.AddButton(540, 420, BTN, 1, 0, BID_ADD_AUCTION);
g.AddHTMLGump(450, 420, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Add Auction</H3></BASEFONT>");
// filters + sort
g.AddButton(20, 470, BTN, 1, 0, BID_FILTER_MINE);
g.AddHTMLGump(90, 470, 100, 74, false, false, "<BASEFONT color=#ffffff><H3>Mine: " + (mine ? "ON" : "OFF") + "</H3></BASEFONT>");
g.AddButton(200, 470, BTN, 1, 0, BID_FILTER_WATCH);
g.AddHTMLGump(270, 470, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Watching: " + (watchOnly ? "ON" : "OFF") + "</H3></BASEFONT>");
g.AddHTMLGump(400, 470, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Sort:</H3></BASEFONT>");
g.AddButton(440, 470, BTN, 1, 0, BID_SORT_ENDING);
g.AddHTMLGump(510, 470, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Ending</H3></BASEFONT>");
g.AddButton(560, 470, BTN, 1, 0, BID_SORT_NEWEST);
g.AddHTMLGump(630, 470, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Newest</H3></BASEFONT>");
g.AddButton(690, 470, BTN, 1, 0, BID_SORT_HIGHEST);
g.AddHTMLGump(760, 470, 200, 74, false, false, "<BASEFONT color=#ffffff><H3>Highest</H3></BASEFONT>");
// search
g.AddHTMLGump(20, 420, 50, 74, false, false, "<BASEFONT color=#ffffff><H3>Search:</H3></BASEFONT>");
g.AddGump(77, 425, 0x5f); g.AddGump(88, 436, 0x60); g.AddGump(270, 425, 0x61);
g.AddButton(285, 420, 0xEE, 1, 0, BID_SEARCH_APPLY);
g.AddButton(350, 420, 0xF4, 1, 0, BID_SEARCH_CLEAR);
g.AddHTMLGump(20, 450, 500, 74, false, false, "<BASEFONT color=#ffffff>Search Tips: owner:Jane item:sword min:100 max:1000 open ended donation</BASEFONT>");
g.AddTextEntry(88, 420, 170, 16, 0x0481, 1, 200, q || "search");
// context
pUser.SetTempTag("auction_board", board.serial + "");
pUser.SetTempTag("auction_page", page + "");
g.Send(pSock);
g.Free();
}
function openAddAuctionGumpUI(pUser, board, item, prev)
{
var pSock = pUser.socket;
var g = new Gump;
g.AddBackground(0, 0, 520, 240, GUMP_BG);
g.AddCheckerTrans(0, 0, 520, 240);
var lockedName = getCanonicalItemName(item);
g.AddText(50, 16, 5, "Auction Item");
g.AddText(20, 50, 3, "Item:");
g.AddHTMLGump(230, 50, 220, 18, false, false, "<BASEFONT color=#FFFFFF>" + lockedName + "</BASEFONT>");
g.AddText(20, 80, 3, "Description:");
g.AddText(20, 110, 3, "Start Price (0 = donation):");
g.AddText(20, 140, 3, "Duration (e.g., 30m, 2h, 1d):");
g.AddText(20, 170, 3, "Reserve (0 = none):");
g.AddText(20, 200, 3, "Buyout (0 = none):");
g.AddButton(140, 16, BTN, 1, 0, BID_ADD_OK);
g.AddText(208, 16, 5, "Start Auction");
g.AddButton(300, 16, 0xF1, 1, 0, BID_ADD_CANCEL);
g.AddTextEntry(230, 80, 220, 18, 0x0481, 1, 9, (prev && prev[0]) || (item.name || "")); // desc
g.AddTextEntry(230, 110, 170, 18, 0x0481, 1, 10, (prev && prev[1]) || item.buyvalue || "0"); // start
g.AddTextEntry(230, 140, 170, 18, 0x0481, 1, 11, (prev && prev[2]) || "7d"); // duration
g.AddTextEntry(230, 170, 170, 18, 0x0481, 1, 12, (prev && prev[3]) || "0"); // reserve
g.AddTextEntry(230, 200, 170, 18, 0x0481, 1, 13, (prev && prev[4]) || "0"); // buyout
pUser.SetTempTag("auction_board", board.serial + "");
pUser.SetTempTag("auction_item", item.serial + "");
g.Send(pSock);
g.Free();
}
function openViewAuctionGumpUI(pUser, board, a)
{
var pSock = pUser.socket;
var g = new Gump;
g.AddBackground(0, 0, 350, 440, GUMP_BG);
g.AddCheckerTrans(0, 0, 350, 440);
g.AddHTMLGump(100, 16, 180, 22, false, false, "<BASEFONT color=#ffffff><H3>View Auction Item</H3></BASEFONT>");
var LBLX = 18, VALX = 80, LBLW = 58, VALW = 210, ROWH = 22, DY = 25;
var y = 44;
function addRow(lbl, val, vW)
{
g.AddHTMLGump(LBLX, y, LBLW, ROWH, false, false, "<BASEFONT color=#ffffff>" + lbl + "</BASEFONT>");
g.AddHTMLGump(VALX, y, (vW || VALW), ROWH, true, false, "<BASEFONT color=#ffffff>" + val + "</BASEFONT>");
y += DY;
}
addRow("Owner:", (a.ownerName || ""));
addRow("Desc:", (a.desc || "-"));
var isDonation = ((a.startPrice | 0) <= 0);
var minPrice = (a.bids && a.bids.length) ? (a.bids[a.bids.length - 1].amount + 1) : (a.startPrice | 0);
if (isDonation) minPrice = 0;
addRow(isDonation ? "Donation:" : "Min Bid", isDonation ? "FREE" : (minPrice + ""));
if ((a.reservePrice | 0) > 0)
{
var curVal = Math.max((a.startPrice | 0), getLastBidAmount(a));
var met = curVal >= (a.reservePrice | 0) ? " (met)" : " (not met)";
addRow("Reserve:", (a.reservePrice + "") + met);
}
if ((a.buyoutPrice | 0) > 0) addRow("Buyout:", (a.buyoutPrice + ""));
addRow("Ends:", new Date(a.endAt).toLocaleString(), 260);
addRow("Status:", (a.status || "open"));
g.AddButton(40, 260, BTN, 1, 0, BID_VIEW_BID);
g.AddHTMLGump(60, 240, 150, 22, false, false, "<BASEFONT color=#ffffff>" + (isDonation ? "Claim" : "Bid") + "</BASEFONT>");
var watching = isCharacterWatchingAuction(board, pUser.serial | 0, a.id | 0);
g.AddButton(140, 260, BTN, 1, 0, BID_TOGGLE_WATCH);
g.AddHTMLGump(150, 240, 150, 22, false, false, "<BASEFONT color=#ffffff>" + (watching ? "Unwatch" : "Watch") + "</BASEFONT>");
g.AddButton(235, 260, BTN, 1, 0, BID_WITHDRAW);
g.AddHTMLGump(215, 240, 150, 22, false, false, "<BASEFONT color=#ffffff>Withdraw Credit</BASEFONT>");
if (((a.status || "open") === "open") && ((pUser.serial | 0) === (a.owner | 0)))
{
g.AddButton(140, 320, BTN, 1, 0, BID_VIEW_CANCEL);
g.AddHTMLGump(150, 300, 150, 22, false, false, "<BASEFONT color=#ffffff>Cancel</BASEFONT>");
}
// Right preview panel
var PX = 360, PY = 16, PW = 100;
g.AddHTMLGump(PY, PX, PW, 22, false, false, "<BASEFONT color=#FFFF00><B>Preview</B></BASEFONT>");
var it = CalcItemFromSer(a.itemSerial | 0);
var hasIt = ValidateObject(it);
if (hasIt)
{
addItemArtToGump(g, PY, PX + 20, (a.itemID || it.id | 0), (a.itemHue || it.hue | 0));
var yy = PY + 100, step = 100;
function kv(lbl, val)
{
g.AddHTMLGump(yy - 25, PX, 400, 18, false, false, "<BASEFONT color=#AAAAAA>" + lbl + "</BASEFONT>");
g.AddHTMLGump(yy - 20, PX + 20, 400, 18, false, false, "<BASEFONT color=#FFFFFF>" + val + "</BASEFONT>");
yy += step;
}
if (it.lodamage > 0 && it.hidamage > 0) kv("Lo/Hi Dam", (it.lodamage + "/" + it.hidamage));
// durability (support hp/health and maxhp/maxhealth variations)
var curDur = (typeof it.hp !== "undefined") ? (it.hp | 0)
: (typeof it.health !== "undefined") ? (it.health | 0) : 0;
var maxDur = (typeof it.maxhp !== "undefined") ? (it.maxhp | 0)
: (typeof it.maxhealth !== "undefined") ? (it.maxhealth | 0) : 0;
if (curDur > 0 && maxDur > 0) kv("Durability", curDur + " / " + maxDur);
if (it.speed > 0) kv("Speed", (it.speed));
} else
{
g.AddHTMLGump(PY + 30, PX, PW, 60, true, false, "<BASEFONT color=#ff8080>Item missing from board escrow.</BASEFONT>");
}
if (((a.status || "open") === "open") && ((a.buyoutPrice | 0) > 0))
{
g.AddButton(40, 320, BTN, 1, 0, BID_VIEW_BUYOUT);
g.AddHTMLGump(50, 300, 60, 22, false, false, "<BASEFONT color=#ffffff>Buyout</BASEFONT>");
}
if ((a.status || "open") === "open")
{
g.AddHTMLGump(LBLX, y, LBLW, ROWH, false, false, "<BASEFONT color=#ffffff>Your Bid:</BASEFONT>");
g.AddTextEntry(VALX, y, 120, 19, 0x0481, 1, 40, (minPrice > 0 ? (minPrice + "") : "0"));
} else if ((a.status || "open") === "ended")
{
g.AddButton(140, 320, BTN, 1, 0, BID_VIEW_CLAIM);
g.AddHTMLGump(140, 300, 220, 22, false, false, "<BASEFONT color=#ffffff>Claim (Winner)</BASEFONT>");
}
pUser.SetTempTag("auction_board", board.serial + "");
pUser.SetTempTag("auction_aid", a.id + "");
g.Send(pSock);
g.Free();
}
function openCancelConfirmGumpUI(pUser, board, a)
{
var pSock = pUser.socket;
var g = new Gump;
var fee = calculateCancelFee(a);
var bidders = getUniqueBidderSerials(a).length;
g.AddBackground(0, 0, 280, 180, GUMP_BG);
g.AddCheckerTrans(0, 0, 280, 180);
g.AddHTMLGump(16, 16, 228, 22, false, false, "<BASEFONT color=#ffffff><B>Cancel this auction?</B></BASEFONT>");
g.AddHTMLGump(16, 46, 228, 22, false, false, "<BASEFONT color=#ffffff>Item: " + ((a.name || a.itemName) || "-") + "</BASEFONT>");
g.AddHTMLGump(16, 70, 228, 22, false, false, "<BASEFONT color=#ffffff>Cancel fee: " + fee + " gold</BASEFONT>");
g.AddHTMLGump(16, 94, 228, 44, false, false, "<BASEFONT color=#ffffff>All bidders will be notified/refunded.</BASEFONT>");
g.AddButton(40, 130, 0x850, 1, 0, BID_CANCEL_YES);
g.AddButton(140, 130, 0x847, 1, 0, BID_CANCEL_NO);
pUser.SetTempTag("auction_board", board.serial + "");
pUser.SetTempTag("auction_aid", a.id + "");
g.Send(pSock);
g.Free();
}
// =============================== Events ===============================
function onCreateDFN(objMade, objType)
{
if (objType == 0)
{
objMade.StartTimer(TICK_MS, TIMER_ID, 7520);
}
}
function onTimer(iBoard, timerID)
{
if (timerID !== TIMER_ID) return;
var list = loadAuctionsFromBoard(iBoard);
var now = getNowMillis();
var changed = false;
for (var i = 0; i < list.length; i++)
{
var a = list[i];
// ending-soon pings
if ((a.status || "open") === "open") { maybeSendEndingSoonPings(iBoard, a, now); }
if ((a.status || "open") === "open" && now >= toNumberOrZero(a.endAt))
{
if (a.bids && a.bids.length)
{
var w = a.bids[a.bids.length - 1];
var topAmt = (w.amount | 0);
var reserve = (a.reservePrice | 0);
if (reserve > 0 && topAmt < reserve)
{
// reserve not met -> return to seller, remove auction
var owner = CalcCharFromSer(a.owner | 0);
var item = CalcItemFromSer(a.itemSerial | 0);
if (ValidateObject(item))
{
item.movable = 1; item.decayable = true;
if (ValidateObject(owner)) item.SetCont(owner.pack);
else item.Teleport(iBoard.x, iBoard.y, iBoard.z);
}
removeEscrowItemForOwner(iBoard, a.owner | 0, a.itemSerial | 0);
// notify top bidder & seller
var topChar = CalcCharFromSer(w.char | 0);
if (ValidateObject(topChar) && topChar.socket != null)
topChar.SysMessage("Reserve not met on '" + (a.name || a.itemName || "") + "'. No winner.");
var seller = CalcCharFromSer(a.owner | 0);
if (ValidateObject(seller) && seller.socket != null)
seller.SysMessage("Your auction '" + (a.name || a.itemName || "") + "' ended: reserve not met.");
// clear watchers on this auction, then remove
writeWatcherSerialsForAuction(iBoard, a.id | 0, []);
list.splice(i, 1); i--;
} else
{
// reserve met or none -> ended with winner
a.status = "ended";
a.winner = w.char | 0;
a.winAmount = topAmt;
var seller2 = CalcCharFromSer(a.owner | 0);
if (ValidateObject(seller2) && seller2.socket != null)
seller2.SysMessage("Your auction '" + (a.name || a.itemName || "") + "' sold for " + topAmt + " gold.");
var winner = CalcCharFromSer(a.winner | 0);
if (ValidateObject(winner) && winner.socket != null)
winner.SysMessage("You won '" + (a.name || a.itemName || "") + "' for " + topAmt + " gold. Claim it on the board.");
}
} else
{
// no bids -> return to seller, remove OR mark ended if cannot
var owner3 = CalcCharFromSer(a.owner | 0);
var item3 = CalcItemFromSer(a.itemSerial | 0);
if (ValidateObject(owner3) && ValidateObject(item3))
{
item3.movable = 1; item3.decayable = true;
item3.SetCont(owner3.pack);
removeEscrowItemForOwner(iBoard, a.owner | 0, a.itemSerial | 0);
writeWatcherSerialsForAuction(iBoard, a.id | 0, []);
list.splice(i, 1); i--;
} else
{
a.status = "ended";
a.winner = -1;
a.winAmount = 0;
}
}
changed = true;
}
}
if (changed) saveAuctionsToBoard(iBoard, list);
iBoard.StartTimer(TICK_MS, TIMER_ID, 7520);
}
function onUseChecked(pUser, iBoard)
{
openBoardGumpUI(pUser, iBoard, 1);
return false;
}
// =============================== Gump Press ===============================
function onGumpPress(pSock, pButton, gumpData)
{
var pUser = pSock.currentChar;
var bSer = toIntOrDefault(pUser.GetTempTag("auction_board"), 0);
var board = CalcItemFromSer(bSer);
if (!ValidateObject(board)) return true;
// paging
if (pButton === BID_PAGE_PREV)
{
var pg = toIntOrDefault(pUser.GetTempTag("auction_page"), 1);
openBoardGumpUI(pUser, board, pg - 1);
return true;
}
if (pButton === BID_PAGE_NEXT)
{
var pg2 = toIntOrDefault(pUser.GetTempTag("auction_page"), 1);
openBoardGumpUI(pUser, board, pg2 + 1);
return true;
}
// search
if (pButton === BID_SEARCH_APPLY)
{
var qtxt = gumpData.getEdit(0) || "";
if (qtxt.length > 120) qtxt = qtxt.substr(0, 120);
pUser.SetTempTag("auction_q", qtxt);
openBoardGumpUI(pUser, board, 1);
return true;
}
if (pButton === BID_SEARCH_CLEAR)
{
pUser.SetTempTag("auction_q", "");
openBoardGumpUI(pUser, board, 1);
return true;
}
// withdraw
if (pButton === BID_WITHDRAW)
{
var got = withdrawCreditToGoldCoins(board, pUser);
pUser.SysMessage(got > 0 ? ("Withdrew " + got + " gold.") : "No credit to withdraw.");
openBoardGumpUI(pUser, board, toIntOrDefault(pUser.GetTempTag("auction_page"), 1));
return true;
}
// add auction flow
if (pButton === BID_ADD_AUCTION)
{
var MAX_ESCROW = 50;
if (readEscrowItemSerialsForOwner(board, pUser.serial | 0).length >= MAX_ESCROW)
{
pUser.TextMessage("You have reached the maximum number of active auctions.");
return true;
}
pSock.CustomTarget(TID_ADD_AUCTION, "Select an item in your backpack to auction.");
return true;
}
if (pButton === BID_ADD_CANCEL)
{
openBoardGumpUI(pUser, board, toIntOrDefault(pUser.GetTempTag("auction_page"), 1));
return true;
}
if (pButton === BID_ADD_OK)
{
var desc = gumpData.getEdit(0);
var price = gumpData.getEdit(1);
var durStr = gumpData.getEdit(2);
var reserve = gumpData.getEdit(3);
var buyout = gumpData.getEdit(4);
if ((desc || "").length < 3) { pUser.TextMessage("Invalid description"); reopenAddAuctionGumpUI(board, pUser, [desc, price + "", durStr + "", reserve + "", buyout + ""]); return true; }
if (price < 0) { pUser.TextMessage("Invalid start price"); reopenAddAuctionGumpUI(board, pUser, [desc, price + "", durStr + "", reserve + "", buyout + ""]); return true; }
var durMs = parseDurationToMilliseconds(durStr || "7d");
if (durMs < DUR_MIN_MS || durMs > DUR_MAX_MS)
{
pUser.TextMessage("Duration must be between " + Math.round(DUR_MIN_MS / 60000) + " minutes and 365 days.");
reopenAddAuctionGumpUI(board, pUser, [desc, price + "", durStr + "", reserve + "", buyout + ""]); return true;
}
if (reserve < 0 || buyout < 0) { pUser.TextMessage("Reserve/Buyout can't be negative."); reopenAddAuctionGumpUI(board, pUser, [desc, price + "", durStr + "", reserve + "", buyout + ""]); return true; }
if (buyout > 0 && buyout < Math.max(price | 0, reserve | 0))
{
buyout = Math.max(price | 0, reserve | 0);
pUser.SysMessage("Buyout was raised to " + buyout + " to be >= start/reserve.");
}
var item = CalcItemFromSer(toIntOrDefault(pUser.GetTempTag("auction_item"), 0));
if (!ValidateObject(item) || item.container != pUser.pack)
{
pUser.TextMessage("The item must be in your pack to be auctioned.");
openBoardGumpUI(pUser, board, toIntOrDefault(pUser.GetTempTag("auction_page"), 1));
return true;
}
var list = loadAuctionsFromBoard(board);
var aid = allocateNextAuctionId(board);
var canonical = getCanonicalItemName(item);
var fp = buildItemFingerprint(item);
list.push({
id: aid,
owner: pUser.serial | 0,
ownerName: pUser.name || ("0x" + (pUser.serial | 0).toString(16)),
itemSerial: item.serial | 0,
itemName: canonical,
itemID: item.id | 0,
itemHue: item.hue | 0,
itemFP: fp,
startPrice: price | 0,
name: canonical,
desc: desc,
endAt: getNowMillis() + durMs,
status: "open",
bids: [],
createdAt: getNowMillis(),
reservePrice: reserve | 0,
buyoutPrice: buyout | 0,
snipeWinMs: (ANTISNIPE_WINDOW_MIN * 60000) | 0,
snipeExtMs: (ANTISNIPE_EXTEND_MIN * 60000) | 0,
snipeExtends: 0,
snipeMax: ANTISNIPE_MAX_EXTENDS | 0
});
saveAuctionsToBoard(board, list);
// Escrow: lock & move into board container
item.movable = 0; item.decayable = false; item.SetCont(board);
addEscrowItemForOwner(board, pUser.serial | 0, item.serial | 0);
pUser.TextMessage("Auction started.");
openBoardGumpUI(pUser, board, toIntOrDefault(pUser.GetTempTag("auction_page"), 1));
return true;
}
// filters & sort
if (pButton === BID_FILTER_MINE)
{
var cur = toIntOrDefault(pUser.GetTempTag("auction_filter_mine"), 0);
pUser.SetTempTag("auction_filter_mine", (cur ? 0 : 1) + "");
openBoardGumpUI(pUser, board, 1);
return true;
}
if (pButton === BID_FILTER_WATCH)
{
var cur2 = toIntOrDefault(pUser.GetTempTag("auction_filter_watch"), 0);
pUser.SetTempTag("auction_filter_watch", (cur2 ? 0 : 1) + "");
openBoardGumpUI(pUser, board, 1);
return true;
}
if (pButton === BID_SORT_ENDING) { pUser.SetTempTag("auction_sort", "ending"); openBoardGumpUI(pUser, board, 1); return true; }
if (pButton === BID_SORT_NEWEST) { pUser.SetTempTag("auction_sort", "newest"); openBoardGumpUI(pUser, board, 1); return true; }
if (pButton === BID_SORT_HIGHEST) { pUser.SetTempTag("auction_sort", "highest"); openBoardGumpUI(pUser, board, 1); return true; }
// watch toggle
if (pButton === BID_TOGGLE_WATCH)
{
var aidT = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listT = loadAuctionsFromBoard(board);
var fT = findAuctionInListById(listT, aidT);
if (!fT.a) { openBoardGumpUI(pUser, board, 1); return true; }
var on = toggleWatchForCharacter(board, pUser.serial | 0, aidT);
if (on) { addWatcherToAuctionIndex(board, aidT, pUser.serial | 0); }
else { removeWatcherFromAuctionIndex(board, aidT, pUser.serial | 0); }
pUser.SysMessage(on ? "Added to Watchlist." : "Removed from Watchlist.");
openViewAuctionGumpUI(pUser, board, fT.a);
return true;
}
// view row
if (pButton >= BID_VIEW_BASE && pButton < BID_VIEW_BASE + BID_VIEW_BLOCK)
{
var aidV = pButton - BID_VIEW_BASE;
var listV = loadAuctionsFromBoard(board);
var fV = findAuctionInListById(listV, aidV);
if (fV.a) openViewAuctionGumpUI(pUser, board, fV.a);
else openBoardGumpUI(pUser, board, 1);
return true;
}
// owner clicked cancel
if (pButton === BID_VIEW_CANCEL)
{
var aidC = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listC = loadAuctionsFromBoard(board);
var fC = findAuctionInListById(listC, aidC);
if (!fC.a) { openBoardGumpUI(pUser, board, 1); return true; }
var aC = fC.a;
if ((aC.status || "open") !== "open" || (pUser.serial | 0) !== (aC.owner | 0))
{
pUser.SysMessage("You can only cancel your own open auction.");
openViewAuctionGumpUI(pUser, board, aC); return true;
}
openCancelConfirmGumpUI(pUser, board, aC);
return true;
}
// confirm YES
if (pButton === BID_CANCEL_YES)
{
var aidY = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listY = loadAuctionsFromBoard(board);
var fY = findAuctionInListById(listY, aidY);
if (!fY.a) { openBoardGumpUI(pUser, board, 1); return true; }
var aY = fY.a;
if ((aY.status || "open") !== "open" || (pUser.serial | 0) !== (aY.owner | 0))
{
pUser.SysMessage("This auction can no longer be cancelled.");
openViewAuctionGumpUI(pUser, board, aY); return true;
}
var fee = calculateCancelFee(aY);
if (!payFromBackpackOrBank(pUser, fee))
{
pUser.SysMessage("You need " + fee + " gold in your backpack or bank to cancel.");
openViewAuctionGumpUI(pUser, board, aY); return true;
}
var itemY = CalcItemFromSer(aY.itemSerial | 0);
if (ValidateObject(itemY))
{
itemY.movable = 1; itemY.decayable = true;
itemY.SetCont(pUser.pack);
if (itemY.container != pUser.pack) itemY.Teleport(pUser.x, pUser.y, pUser.z);
}
// notify bidders
var uniq = getUniqueBidderSerials(aY);
for (var i = 0; i < uniq.length; i++)
{
var bc = CalcCharFromSer(uniq[i] | 0);
if (ValidateObject(bc) && bc.socket != null)
bc.SysMessage("Auction cancelled: '" + ((aY.name || aY.itemName) || "") + "'. Your bid has been voided.");
}
removeEscrowItemForOwner(board, aY.owner | 0, aY.itemSerial | 0);
writeWatcherSerialsForAuction(board, aY.id | 0, []);
listY.splice(fY.idx, 1);
saveAuctionsToBoard(board, listY);
pUser.SysMessage("Auction cancelled. Fee paid: " + fee + " gold.");
openBoardGumpUI(pUser, board, Math.max(1, toIntOrDefault(pUser.GetTempTag("auction_page"), 1)));
return true;
}
// confirm NO
if (pButton === BID_CANCEL_NO)
{
var aidN = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listN = loadAuctionsFromBoard(board);
var fN = findAuctionInListById(listN, aidN);
if (fN.a) openViewAuctionGumpUI(pUser, board, fN.a);
else openBoardGumpUI(pUser, board, 1);
return true;
}
// bid / claim
if (pButton === BID_VIEW_BID)
{
var aidB = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listB = loadAuctionsFromBoard(board);
var fB = findAuctionInListById(listB, aidB);
if (!fB.a) { openBoardGumpUI(pUser, board, 1); return true; }
var aB = fB.a;
var isDonation = ((aB.startPrice | 0) <= 0);
if (isDonation)
{
var itemD = CalcItemFromSer(aB.itemSerial | 0);
if (ValidateObject(itemD))
{
itemD.movable = 1; itemD.decayable = true;
itemD.SetCont(pUser.pack);
if (itemD.container != pUser.pack) itemD.Teleport(pUser.x, pUser.y, pUser.z);
}
removeEscrowItemForOwner(board, aB.owner | 0, aB.itemSerial | 0);
writeWatcherSerialsForAuction(board, aB.id | 0, []);
listB.splice(fB.idx, 1);
saveAuctionsToBoard(board, listB);
pUser.TextMessage("Donation claimed.");
openBoardGumpUI(pUser, board, Math.max(1, toIntOrDefault(pUser.GetTempTag("auction_page"), 1)));
return true;
}
if ((aB.status || "open") !== "open")
{
pUser.TextMessage("Auction is not open.");
openViewAuctionGumpUI(pUser, board, aB); return true;
}
if ((pUser.serial | 0) === (aB.owner | 0))
{
pUser.TextMessage("You can't bid on your own auction.");
openViewAuctionGumpUI(pUser, board, aB); return true;
}
var offer = gumpData.getEdit(0);
var min = (aB.bids && aB.bids.length) ? (aB.bids[aB.bids.length - 1].amount + 1) : (aB.startPrice | 0);
if (offer < min)
{
pUser.TextMessage("Your bid must be at least " + min + ".");
openViewAuctionGumpUI(pUser, board, aB); return true;
}
var nowB = getNowMillis();
var prevTop = (aB.bids && aB.bids.length) ? aB.bids[aB.bids.length - 1] : null;
aB.bids = aB.bids || [];
aB.bids.push({ char: pUser.serial | 0, name: pUser.name || "", amount: offer | 0 });
// Anti-snipe
if (toNumberOrZero(aB.snipeWinMs) > 0 && toNumberOrZero(aB.snipeExtMs) > 0 && ((aB.snipeExtends | 0) < (aB.snipeMax | 0)))
{
var remain = toNumberOrZero(aB.endAt) - nowB;
if (remain <= toNumberOrZero(aB.snipeWinMs))
{
aB.endAt = toNumberOrZero(aB.endAt) + toNumberOrZero(aB.snipeExtMs);
aB.snipeExtends = (aB.snipeExtends | 0) + 1;
pUser.SysMessage("Auction extended due to late bid (+ " + Math.round(toNumberOrZero(aB.snipeExtMs) / 60000) + " min).");
var seller = CalcCharFromSer(aB.owner | 0);
if (ValidateObject(seller) && seller.socket != null)
seller.SysMessage("Your auction '" + (aB.name || aB.itemName || "") + "' was extended by a late bid.");
}
}
saveAuctionsToBoard(board, listB);
if (prevTop && (prevTop.char | 0) !== (pUser.serial | 0))
{
var prevChar = CalcCharFromSer(prevTop.char | 0);
if (ValidateObject(prevChar) && prevChar.socket != null)
{
prevChar.SysMessage("You were outbid on '" + (aB.name || aB.itemName || "") + "'. New bid: " + (offer | 0) + " gold.");
}
}
pUser.TextMessage("Bid placed.");
openViewAuctionGumpUI(pUser, board, aB);
return true;
}
// buyout
if (pButton === BID_VIEW_BUYOUT)
{
var aidBO = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listBO = loadAuctionsFromBoard(board);
var fBO = findAuctionInListById(listBO, aidBO);
if (!fBO.a) { openBoardGumpUI(pUser, board, 1); return true; }
var aBO = fBO.a;
if ((aBO.status || "open") !== "open") { pUser.SysMessage("Auction is not open."); openViewAuctionGumpUI(pUser, board, aBO); return true; }
if ((aBO.buyoutPrice | 0) <= 0) { pUser.SysMessage("No buyout available."); openViewAuctionGumpUI(pUser, board, aBO); return true; }
if ((pUser.serial | 0) === (aBO.owner | 0)) { pUser.SysMessage("You can't buyout your own auction."); openViewAuctionGumpUI(pUser, board, aBO); return true; }
var priceBO = aBO.buyoutPrice | 0;
if (!payFromBackpackOrBank(pUser, priceBO))
{
pUser.SysMessage("You need " + priceBO + " gold in your backpack or bank to buyout.");
openViewAuctionGumpUI(pUser, board, aBO); return true;
}
var itemBO = CalcItemFromSer(aBO.itemSerial | 0);
if (!ValidateObject(itemBO) || !fingerprintMatchesEscrowItem(aBO, itemBO))
{
pUser.SysMessage("Listing is blocked: escrow item does not match its fingerprint. Contact staff.");
openViewAuctionGumpUI(pUser, board, aBO); return true;
}
addCreditForChar(board, aBO.owner | 0, priceBO);
if (ValidateObject(itemBO))
{
itemBO.movable = 1; itemBO.decayable = true;
itemBO.SetCont(pUser.pack);
if (itemBO.container != pUser.pack) itemBO.Teleport(pUser.x, pUser.y, pUser.z);
}
removeEscrowItemForOwner(board, aBO.owner | 0, aBO.itemSerial | 0);
writeWatcherSerialsForAuction(board, aBO.id | 0, []);
listBO.splice(fBO.idx, 1);
saveAuctionsToBoard(board, listBO);
pUser.TextMessage("Buyout successful for " + priceBO + " gold.");
openBoardGumpUI(pUser, board, Math.max(1, toIntOrDefault(pUser.GetTempTag("auction_page"), 1)));
return true;
}
// winner claim
if (pButton === BID_VIEW_CLAIM)
{
var aidCL = toIntOrDefault(pUser.GetTempTag("auction_aid"), 0);
var listCL = loadAuctionsFromBoard(board);
var fCL = findAuctionInListById(listCL, aidCL);
if (!fCL.a) { openBoardGumpUI(pUser, board, 1); return true; }
var aCL = fCL.a;
if ((aCL.status || "open") !== "ended") { pUser.TextMessage("Not ready to claim."); openViewAuctionGumpUI(pUser, board, aCL); return true; }
if ((pUser.serial | 0) !== toIntOrDefault(aCL.winner, -1)) { pUser.TextMessage("You are not the winner."); openViewAuctionGumpUI(pUser, board, aCL); return true; }
var priceCL = toIntOrDefault(aCL.winAmount, 0);
if (priceCL > 0)
{
if (!payFromBackpackOrBank(pUser, priceCL))
{
pUser.TextMessage("You do not have enough gold in your backpack or bank to pay " + priceCL + ".");
openViewAuctionGumpUI(pUser, board, aCL); return true;
}
addCreditForChar(board, aCL.owner | 0, priceCL);
}
var itemCL = CalcItemFromSer(aCL.itemSerial | 0);
if (!ValidateObject(itemCL) || !fingerprintMatchesEscrowItem(aCL, itemCL))
{
pUser.SysMessage("Listing is blocked: escrow item does not match its fingerprint. Contact staff.");
openViewAuctionGumpUI(pUser, board, aCL); return true;
}
if (ValidateObject(itemCL))
{
itemCL.movable = 1; itemCL.decayable = true;
itemCL.SetCont(pUser.pack);
}
removeEscrowItemForOwner(board, aCL.owner | 0, aCL.itemSerial | 0);
writeWatcherSerialsForAuction(board, aCL.id | 0, []);
listCL.splice(fCL.idx, 1);
saveAuctionsToBoard(board, listCL);
pUser.TextMessage("You claimed your auction item.");
openBoardGumpUI(pUser, board, 1);
return true;
}
}
// =============================== Target Callback ===============================
function onCallback0(socket, targObj)
{
var pUser = socket.currentChar;
if (!ValidateObject(targObj) || !targObj.isItem || targObj.container != pUser.pack)
{
socket.SysMessage("Select an item in your backpack.");
return;
}
var sec = ((targObj.sectionID + "") || "").toLowerCase();
if (sec === "gold" || sec === "bankcheck" || targObj.isContainer)
{
socket.SysMessage("This is not a valid auctionable item.");
return;
}
var bSer = toIntOrDefault(pUser.GetTempTag("auction_board"), 0);
var board = CalcItemFromSer(bSer);
if (!ValidateObject(board))
{
socket.SysMessage("No board context.");
return;
}
openAddAuctionGumpUI(pUser, board, targObj, null);
pUser.SetTempTag("auction_item", targObj.serial + "");
}
// =============================== Reopen Helper ===============================
function reopenAddAuctionGumpUI(board, pUser, prev)
{
var item = CalcItemFromSer(toIntOrDefault(pUser.GetTempTag("auction_item"), 0));
if (!ValidateObject(item))
{
pUser.SysMessage("Item context lost.");
openBoardGumpUI(pUser, board, toIntOrDefault(pUser.GetTempTag("auction_page"), 1));
return;
}
// prev = [desc, price, duration, reserve, buyout]
openAddAuctionGumpUI(pUser, board, item, prev);
}