[yahtzee_dice]
{
get=base_item
NAME=Yahtzee dice
ID=0x15F8
COLOR=1150
SCRIPT=7500
WEIGHT=1
}
{
get=base_item
NAME=Yahtzee dice
ID=0x15F8
COLOR=1150
SCRIPT=7500
WEIGHT=1
}
// ============================================================================
// Yahtzee (UOX3 / SpiderMonkey 1.8.5 / ES5)
// ScriptID: 7500 (your convention)
// Author: ported from RunUO C# version you uploaded by ChatGPT
// Notes:
// - Command: [yahtzee -> open a solo Yahtzee game gump
// - Optional consumable item: "yahtzee_dice" (DFN snippet below) opens game then deletes itself
// - Uses per-player TempTags to persist state between gump refreshes
// - Uses AddGump for dice art (base 11279 + 1..6) to mirror the C# UI
// - "Hold" uses toggle buttons (we draw a checkmark gump for state) so we don't rely on gumpData iteration
// ============================================================================
/* --------------------------- Config --------------------------- */
var Y_GUMP_W = 265, Y_GUMP_H = 500;
var Y_GUMP_X = 275, Y_GUMP_Y = 50;
var Y_DICE_BASE_GUMP = 11279; // base + [1..6] as per original C# gump art
var Y_BTN_GUMP_UP = 5601, Y_BTN_GUMP_DN = 5605; // RunUO used these; in UOX3 they’re valid gumps too
var Y_TEXT_LEFT = Y_GUMP_X + 20;
var Y_BTN_X = Y_GUMP_X + 86; // was +80
var Y_SCORE_RIGHT = Y_GUMP_X + 185;
var Y_BTN_Y_ADJ = -2; // tiny vertical nudge for nicer alignment
// Minimal skin bits
var Y_STRETCH_BAR1 = 3607;
var Y_STRETCH_BAR2 = 3004;
var Y_BG = 9270;
// Add near your button IDs
var BTN_NEWGAME = 99;
var GOLD_TILE_ID = 0x0EED; // gold coins
/* --------------------------- Button IDs --------------------------- */
// Keep order similar to C# enum for familiarity
var BTN_NONE = 0;
var BTN_HOLD1 = 1, BTN_HOLD2 = 2, BTN_HOLD3 = 3, BTN_HOLD4 = 4, BTN_HOLD5 = 5;
var BTN_ROLL = 6;
var BTN_ONES = 7, BTN_TWOS = 8, BTN_THREES = 9, BTN_FOURS = 10, BTN_FIVES = 11, BTN_SIXES = 12;
var BTN_3KIND = 13, BTN_4KIND = 14, BTN_FULLHOUSE = 15, BTN_SSTRAIGHT = 16, BTN_LSTRAIGHT = 17, BTN_YAHTZEE = 18, BTN_CHANCE = 19;
// Close is implicit (button 0)
/* --------------------------- State helpers --------------------------- */
function _tagGetIntArray(pChar, key, len, defVal)
{
var raw = pChar.GetTempTag(key) || "";
var out = [];
if (!raw)
{
for (var i = 0; i < len; i++) out.push(defVal);
return out;
}
var parts = raw.split(",");
for (var j = 0; j < len; j++)
{
var v = parseInt(parts[j], 10);
if (isNaN(v)) v = defVal;
out.push(v);
}
return out;
}
function _tagGetBoolArray(pChar, key, len, defVal)
{
var raw = pChar.GetTempTag(key) || "";
var out = [];
if (!raw)
{
for (var i = 0; i < len; i++) out.push(!!defVal);
return out;
}
var parts = raw.split(",");
for (var j = 0; j < len; j++)
{
var v = parts[j] == "1" || parts[j] === "true";
if (parts[j] == "" || parts[j] == null || parts[j] == undefined) v = !!defVal;
out.push(v);
}
return out;
}
function _tagSetIntArray(pChar, key, arr) { pChar.SetTempTag(key, arr.join(",")); }
function _tagSetBoolArray(pChar, key, arr) { pChar.SetTempTag(key, arr.map(function(b) { return b ? 1 : 0; }).join(",")); }
function _getRoll(pChar) { return parseInt(pChar.GetTempTag("yaht_roll") || "0", 10) || 0; }
function _setRoll(pChar, v) { pChar.SetTempTag("yaht_roll", "" + (v | 0)); }
function _getUsedRoll(pChar) { return (pChar.GetTempTag("yaht_usedRoll") | 0) === 1; }
function _setUsedRoll(pChar, b) { pChar.SetTempTag("yaht_usedRoll", b ? "1" : "0"); }
function _getDice(pChar) { return _tagGetIntArray(pChar, "yaht_dice", 5, 1); }
function _setDice(pChar, a) { _tagSetIntArray(pChar, "yaht_dice", a); }
function _getHolds(pChar) { return _tagGetBoolArray(pChar, "yaht_hold", 5, false); }
function _setHolds(pChar, a) { _tagSetBoolArray(pChar, "yaht_hold", a); }
function _getScores(pChar) { return _tagGetIntArray(pChar, "yaht_scores", 13, -1); }
function _setScores(pChar, a) { _tagSetIntArray(pChar, "yaht_scores", a); }
function _getTotals(pChar) { return _tagGetIntArray(pChar, "yaht_totals", 4, 0); }
function _setTotals(pChar, a) { _tagSetIntArray(pChar, "yaht_totals", a); }
function _resetHolds(pChar)
{
var h = [false, false, false, false, false];
_setHolds(pChar, h);
_setUsedRoll(pChar, true); // mimic original: selecting a category consumes the roll window
}
function _initIfNeeded(pChar)
{
if (!pChar.GetTempTag("yaht_inited"))
{
_setRoll(pChar, 0);
_setDice(pChar, [1, 1, 1, 1, 1]);
_setHolds(pChar, [false, false, false, false, false]);
_setScores(pChar, [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
_setTotals(pChar, [0, 0, 0, 0]);
_setUsedRoll(pChar, true);
pChar.SetTempTag("yaht_inited", "1");
}
}
function Y_CanPick(scores, catIdx, dice)
{
// normal categories: only if unscored
if (catIdx !== 11) return (scores[catIdx] | 0) === -1;
// Yahtzee: unscored -> can pick; scored 50+ -> allow only if another Yahtzee is rolled
var cur = scores[11] | 0;
if (cur === -1) return true;
if (cur >= 50) return Y_CalcYahtzee(dice) === 50;
return false;
}
/* --------------------------- Scoring Core (ported 1:1) --------------------------- */
// AddDice (upper section)
function Y_AddDice(dice, face)
{
var s = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === (face | 0)) s += face; return s;
}
// Totals
function Y_CalcTotals(scores, totals)
{
var score = 0, i = 0;
for (i = 0; i < 6; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
totals[0] = score;
totals[1] = (score >= 63) ? 35 : 0; // <-- ensure it zeroes out when <63
score = 0;
for (; i < 13; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
totals[2] = score;
totals[3] = (totals[0] | 0) + (totals[1] | 0) + (totals[2] | 0);
return totals;
}
// Three/Four of a kind -> sum of all dice if condition
function Y_CalcMultiples(dice, amount)
{
for (var face = 1; face <= 6; face++)
{
var count = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === face)
{
count++; if (count >= amount)
{
var sum = 0; for (var j = 0; j < 5; j++) sum += dice[j] | 0; return sum;
}
}
}
return 0;
}
function Y_CalcChance(dice) { var s = 0; for (var i = 0; i < 5; i++) s += dice[i] | 0; return s; }
function _clone(a) { var b = []; for (var i = 0; i < a.length; i++) b[i] = a[i] | 0; return b; }
function _sort(a) { a.sort(function(x, y) { return (x | 0) - (y | 0); }); return a; }
function Y_CalcFullHouse(dice)
{
var d = _sort(_clone(dice));
if (((d[0] == d[1] && d[1] == d[2]) && (d[3] == d[4]) && (d[2] != d[3])) ||
((d[0] == d[1]) && (d[2] == d[3] && d[3] == d[4]) && (d[1] != d[2]))) return 25;
return 0;
}
function Y_CalcShortStraight(dice)
{
var d = _sort(_clone(dice));
// move duplicates to the end (as in C#)
for (var i = 0; i < 4; i++)
{
if (d[i] == d[i + 1])
{
var tmp = d[i];
for (var k = i; k < 4; k++) d[k] = d[k + 1];
d[4] = tmp;
}
}
if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4) ||
(d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5) ||
(d[0] == 3 && d[1] == 4 && d[2] == 5 && d[3] == 6) ||
(d[1] == 1 && d[2] == 2 && d[3] == 3 && d[4] == 4) ||
(d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
(d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 30;
return 0;
}
function Y_CalcLongStraight(dice)
{
var d = _sort(_clone(dice));
if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
(d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 40;
return 0;
}
function Y_CalcYahtzee(dice)
{
return (dice[0] == dice[1] && dice[1] == dice[2] && dice[2] == dice[3] && dice[3] == dice[4]) ? 50 : 0;
}
function Y_AllScored(scores)
{
for (var i = 0; i < 13; i++) if ((scores[i] | 0) === -1) return false;
return true;
}
/* --------------------------- Reward --------------------------- */
function Y_GiveReward(pChar, total)
{
// Mimic original reward: (total - 200)*10, min 0
var toGive = (total - 200) * 10; if (toGive < 0) toGive = 0;
if (toGive > 0)
{
// Create gold in backpack
// CreateBlankItem(socket, character, amount, itemName, itemID, colour, objectType, inPack)
CreateBlankItem(pChar.socket, pChar, toGive, "gold coins", GOLD_TILE_ID, 0x0, "ITEM", true); // in pack
}
pChar.SysMessage("Congratulations! You scored " + total + " points and received " + toGive + " gold.");
pChar.EmoteMessage("*Yahtzee! " + total + " points*");
}
/* --------------------------- UI Build --------------------------- */
function Y_SendGump(pSock)
{
var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;
_initIfNeeded(pUser);
var roll = _getRoll(pUser);
var dice = _getDice(pUser);
var holds = _getHolds(pUser);
var scores = _getScores(pUser);
var totals = _getTotals(pUser);
var usedRoll = _getUsedRoll(pUser);
var g = new Gump();
g.AddPage(0);
// background & title
g.AddBackground(Y_GUMP_X, Y_GUMP_Y, Y_GUMP_W, Y_GUMP_H, Y_BG);
g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 20, Y_GUMP_W - 20, 16, Y_STRETCH_BAR1);
g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 10, Y_GUMP_W - 20, 20, Y_STRETCH_BAR2);
g.AddText(Y_GUMP_X + 110, Y_GUMP_Y + 10, 31, "Yahtzee");
// dice row
var xBase = Y_GUMP_X + 75;
var xOff = 25;
for (var d = 0; d < 5; d++)
{
var gID = Y_DICE_BASE_GUMP + (dice[d] | 0);
g.AddGump(xBase + d * xOff, Y_GUMP_Y + 60, gID);
}
// Roll block
g.AddText(Y_GUMP_X + 20, Y_GUMP_Y + 60, 1149, "Roll");
g.AddText(Y_GUMP_X + 30, Y_GUMP_Y + 80, 1149, "" + roll);
if (roll < 3)
{
// AddButton(x,y, up, ???, page, buttonID)
g.AddButton(Y_GUMP_X + 50, Y_GUMP_Y + 62, Y_BTN_GUMP_UP, 1, 0, BTN_ROLL);
}
// Hold toggles (visual check + button hotspot)
if (roll < 3)
{
g.AddText(Y_GUMP_X + 205, Y_GUMP_Y + 85, 1149, "Hold");
for (d = 0; d < 5; d++)
{
// draw check look (2715 = checked, 2714 = unchecked – same art used in your C#)
var checkGump = holds[d] ? 2715 : 2714;
g.AddGump(xBase + d * xOff + 1, Y_GUMP_Y + 85, checkGump);
// clickable tiny button overlapping the check image to toggle
var btnId = BTN_HOLD1 + d;
g.AddButton(xBase + d * xOff, Y_GUMP_Y + 85, Y_BTN_GUMP_UP, 1, 0, btnId);
}
}
// Section labels (left column)
var yBaseUpper = Y_GUMP_Y + 115;
var yOff = 20;
var labels = ["Ones", "Twos", "Threes", "Fours", "Fives", "Sixes"];
for (var i = 0; i < labels.length; i++)
{
g.AddText(Y_GUMP_X + 20, yBaseUpper + i * yOff, 1149, labels[i]);
}
g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 235, 1152, "Upper Total:");
g.AddText(Y_GUMP_X + 100, Y_GUMP_Y + 255, 1152, "Bonus:");
// Lower labels
var yBaseLower = Y_GUMP_Y + 295;
var lowers = ["Three of a kind", "Four of a kind", "Full House", "Short Straight", "Long Straight", "Yahtzee", "Chance"];
for (i = 0; i < lowers.length; i++)
{
g.AddText(Y_GUMP_X + 20, yBaseLower + i * yOff, 1149, lowers[i]);
}
g.AddText(Y_GUMP_X + 63, Y_GUMP_Y + 435, 1152, "Lower Total:");
g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 455, 1152, "Grand Total:");
if (!usedRoll)
{
for (i = 0; i < 6; i++)
{
if (Y_CanPick(scores, i, dice))
g.AddButton(Y_BTN_X, yBaseUpper + i * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, BTN_ONES + i);
}
}
if (!usedRoll)
{
var y = yBaseLower;
var map = [
[6, BTN_3KIND, 0],
[7, BTN_4KIND, 1],
[8, BTN_FULLHOUSE, 2],
[9, BTN_SSTRAIGHT, 3],
[10, BTN_LSTRAIGHT, 4],
[11, BTN_YAHTZEE, 5],
[12, BTN_CHANCE, 6]
];
for (var m = 0; m < map.length; m++)
{
var idx = map[m][0];
var btnID = map[m][1];
var row = map[m][2];
if (Y_CanPick(scores, idx, dice))
g.AddButton(Y_BTN_X, y + row * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, btnID);
}
}
// Score columns (right side)
// Upper (6 lines)
for (i = 0; i < 6; i++)
{
var s = scores[i] | 0;
g.AddText(Y_GUMP_X + 185, yBaseUpper + i * yOff, 1149, (s > -1 ? ("" + s) : "-"));
}
// Lower (7 lines)
for (; i < 13; i++)
{
s = scores[i] | 0;
g.AddText(Y_GUMP_X + 185, yBaseLower + (i - 6) * yOff, 1149, (s > -1 ? ("" + s) : "-"));
}
// Totals
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 255, 1152, "" + (totals[0] | 0)); // upper total
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 275, 1152, "" + (totals[1] | 0)); // bonus
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 435, 1152, "" + (totals[2] | 0)); // lower total
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 455, 1152, "" + (totals[3] | 0)); // grand total
// In Y_SendGump, after you draw totals, show when all scored:
if (Y_AllScored(scores))
{
// draw a New Game button/footer
g.AddButton(Y_GUMP_X + 20, Y_GUMP_Y + 470, Y_BTN_GUMP_UP, 1, 0, BTN_NEWGAME);
g.AddText(Y_GUMP_X + 55, Y_GUMP_Y + 470, 1152, "New Game");
}
g.Send(pSock);
g.Free();
}
/* --------------------------- Game Actions --------------------------- */
function Y_ToggleHold(pChar, idx)
{
var h = _getHolds(pChar);
h[idx] = !h[idx];
_setHolds(pChar, h);
}
function Y_Roll(pChar)
{
var roll = _getRoll(pChar);
if (roll >= 3) return;
var dice = _getDice(pChar);
var holds = _getHolds(pChar);
for (var i = 0; i < 5; i++)
{
if (!holds[i] || roll === 0)
{
dice[i] = RandomNumber(1, 6); // inclusive
}
}
_setDice(pChar, dice);
_setRoll(pChar, roll + 1);
_setUsedRoll(pChar, false);
}
function Y_ScoreCategory(pChar, catBtn)
{
var dice = _getDice(pChar);
var scores = _getScores(pChar);
function setScore(idx, val)
{
if ((scores[idx] | 0) === -1)
{
scores[idx] = val | 0;
_setScores(pChar, scores);
_setRoll(pChar, 0);
_resetHolds(pChar);
}
}
switch (catBtn)
{
case BTN_ONES: setScore(0, Y_AddDice(dice, 1)); break;
case BTN_TWOS: setScore(1, Y_AddDice(dice, 2)); break;
case BTN_THREES: setScore(2, Y_AddDice(dice, 3)); break;
case BTN_FOURS: setScore(3, Y_AddDice(dice, 4)); break;
case BTN_FIVES: setScore(4, Y_AddDice(dice, 5)); break;
case BTN_SIXES: setScore(5, Y_AddDice(dice, 6)); break;
case BTN_3KIND: setScore(6, Y_CalcMultiples(dice, 3)); break;
case BTN_4KIND: setScore(7, Y_CalcMultiples(dice, 4)); break;
case BTN_FULLHOUSE: setScore(8, Y_CalcFullHouse(dice)); break;
case BTN_SSTRAIGHT: setScore(9, Y_CalcShortStraight(dice)); break;
case BTN_LSTRAIGHT: setScore(10, Y_CalcLongStraight(dice)); break;
case BTN_YAHTZEE:
// First Yahtzee fills slot; subsequent Yahtzees add +50 if you roll another 50 (mirrors C#)
var cur = scores[11] | 0;
var rolled = Y_CalcYahtzee(dice);
if (cur === -1)
{
setScore(11, rolled);
} else if (cur >= 50 && rolled === 50)
{
scores[11] = cur + 50;
_setScores(pChar, scores);
_setRoll(pChar, 0);
_resetHolds(pChar);
}
break;
case BTN_CHANCE: setScore(12, Y_CalcChance(dice)); break;
}
// Update totals
var totals = Y_CalcTotals(_getScores(pChar), _getTotals(pChar));
_setTotals(pChar, totals);
}
/* --------------------------- Events --------------------------- */
function onGumpPress(pSock, pButton, gumpData)
{
var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;
if (pButton === 0) { return; } // closed
_initIfNeeded(pUser);
if (pButton >= BTN_HOLD1 && pButton <= BTN_HOLD5)
{
Y_ToggleHold(pUser, pButton - BTN_HOLD1);
Y_SendGump(pSock);
return;
}
if (pButton === BTN_ROLL)
{
Y_Roll(pUser);
Y_SendGump(pSock);
return;
}
if (pButton === BTN_NEWGAME)
{
Y_ResetGame(pUser);
_initIfNeeded(pUser);
Y_SendGump(pSock);
return;
}
// Category pick
Y_ScoreCategory(pUser, pButton);
// Totals + end check
var totals = _getTotals(pUser);
var scores = _getScores(pUser);
if (Y_AllScored(scores))
{
Y_GiveReward(pUser, totals[3] | 0);
// Lock UI at end (shows roll=3 and disables picks)
_setRoll(pUser, 3);
_setUsedRoll(pUser, true);
}
Y_SendGump(pSock);
}
// Reset all state so a brand-new game starts on next open
function Y_ResetGame(pChar)
{
// Prefer DelTempTag when available; fall back to blanking
function _del(k)
{
if (typeof pChar.DelTempTag === "function") pChar.DelTempTag(k);
else pChar.SetTempTag(k, "");
}
_del("yaht_inited");
_del("yaht_roll");
_del("yaht_usedRoll");
_del("yaht_dice");
_del("yaht_hold");
_del("yaht_scores");
_del("yaht_totals");
_del("yaht_itemSer"); // optional binding (see #3)
}
// Test command: [yahtzee
/** @param {Socket} socket */
function command_YAHTZEE(socket, cmdString)
{
if (!socket || !ValidateObject(socket.currentChar)) return;
var p = (cmdString || "").trim().toLowerCase();
if (p === "reset")
{
Y_ResetGame(socket.currentChar);
}
_initIfNeeded(socket.currentChar);
Y_SendGump(socket);
}
function onUseChecked(pUser, iUsed)
{
if (ValidateObject(iUsed) && iUsed.isItem)
{
var ser = iUsed.serial | 0;
var last = parseInt(pUser.GetTempTag("yaht_itemSer") || "0", 10) | 0;
if (last !== ser)
{
Y_ResetGame(pUser);
pUser.SetTempTag("yaht_itemSer", "" + ser);
}
}
_initIfNeeded(pUser);
Y_SendGump(pUser.socket);
return false;
}
// Yahtzee (UOX3 / SpiderMonkey 1.8.5 / ES5)
// ScriptID: 7500 (your convention)
// Author: ported from RunUO C# version you uploaded by ChatGPT
// Notes:
// - Command: [yahtzee -> open a solo Yahtzee game gump
// - Optional consumable item: "yahtzee_dice" (DFN snippet below) opens game then deletes itself
// - Uses per-player TempTags to persist state between gump refreshes
// - Uses AddGump for dice art (base 11279 + 1..6) to mirror the C# UI
// - "Hold" uses toggle buttons (we draw a checkmark gump for state) so we don't rely on gumpData iteration
// ============================================================================
/* --------------------------- Config --------------------------- */
var Y_GUMP_W = 265, Y_GUMP_H = 500;
var Y_GUMP_X = 275, Y_GUMP_Y = 50;
var Y_DICE_BASE_GUMP = 11279; // base + [1..6] as per original C# gump art
var Y_BTN_GUMP_UP = 5601, Y_BTN_GUMP_DN = 5605; // RunUO used these; in UOX3 they’re valid gumps too
var Y_TEXT_LEFT = Y_GUMP_X + 20;
var Y_BTN_X = Y_GUMP_X + 86; // was +80
var Y_SCORE_RIGHT = Y_GUMP_X + 185;
var Y_BTN_Y_ADJ = -2; // tiny vertical nudge for nicer alignment
// Minimal skin bits
var Y_STRETCH_BAR1 = 3607;
var Y_STRETCH_BAR2 = 3004;
var Y_BG = 9270;
// Add near your button IDs
var BTN_NEWGAME = 99;
var GOLD_TILE_ID = 0x0EED; // gold coins
/* --------------------------- Button IDs --------------------------- */
// Keep order similar to C# enum for familiarity
var BTN_NONE = 0;
var BTN_HOLD1 = 1, BTN_HOLD2 = 2, BTN_HOLD3 = 3, BTN_HOLD4 = 4, BTN_HOLD5 = 5;
var BTN_ROLL = 6;
var BTN_ONES = 7, BTN_TWOS = 8, BTN_THREES = 9, BTN_FOURS = 10, BTN_FIVES = 11, BTN_SIXES = 12;
var BTN_3KIND = 13, BTN_4KIND = 14, BTN_FULLHOUSE = 15, BTN_SSTRAIGHT = 16, BTN_LSTRAIGHT = 17, BTN_YAHTZEE = 18, BTN_CHANCE = 19;
// Close is implicit (button 0)
/* --------------------------- State helpers --------------------------- */
function _tagGetIntArray(pChar, key, len, defVal)
{
var raw = pChar.GetTempTag(key) || "";
var out = [];
if (!raw)
{
for (var i = 0; i < len; i++) out.push(defVal);
return out;
}
var parts = raw.split(",");
for (var j = 0; j < len; j++)
{
var v = parseInt(parts[j], 10);
if (isNaN(v)) v = defVal;
out.push(v);
}
return out;
}
function _tagGetBoolArray(pChar, key, len, defVal)
{
var raw = pChar.GetTempTag(key) || "";
var out = [];
if (!raw)
{
for (var i = 0; i < len; i++) out.push(!!defVal);
return out;
}
var parts = raw.split(",");
for (var j = 0; j < len; j++)
{
var v = parts[j] == "1" || parts[j] === "true";
if (parts[j] == "" || parts[j] == null || parts[j] == undefined) v = !!defVal;
out.push(v);
}
return out;
}
function _tagSetIntArray(pChar, key, arr) { pChar.SetTempTag(key, arr.join(",")); }
function _tagSetBoolArray(pChar, key, arr) { pChar.SetTempTag(key, arr.map(function(b) { return b ? 1 : 0; }).join(",")); }
function _getRoll(pChar) { return parseInt(pChar.GetTempTag("yaht_roll") || "0", 10) || 0; }
function _setRoll(pChar, v) { pChar.SetTempTag("yaht_roll", "" + (v | 0)); }
function _getUsedRoll(pChar) { return (pChar.GetTempTag("yaht_usedRoll") | 0) === 1; }
function _setUsedRoll(pChar, b) { pChar.SetTempTag("yaht_usedRoll", b ? "1" : "0"); }
function _getDice(pChar) { return _tagGetIntArray(pChar, "yaht_dice", 5, 1); }
function _setDice(pChar, a) { _tagSetIntArray(pChar, "yaht_dice", a); }
function _getHolds(pChar) { return _tagGetBoolArray(pChar, "yaht_hold", 5, false); }
function _setHolds(pChar, a) { _tagSetBoolArray(pChar, "yaht_hold", a); }
function _getScores(pChar) { return _tagGetIntArray(pChar, "yaht_scores", 13, -1); }
function _setScores(pChar, a) { _tagSetIntArray(pChar, "yaht_scores", a); }
function _getTotals(pChar) { return _tagGetIntArray(pChar, "yaht_totals", 4, 0); }
function _setTotals(pChar, a) { _tagSetIntArray(pChar, "yaht_totals", a); }
function _resetHolds(pChar)
{
var h = [false, false, false, false, false];
_setHolds(pChar, h);
_setUsedRoll(pChar, true); // mimic original: selecting a category consumes the roll window
}
function _initIfNeeded(pChar)
{
if (!pChar.GetTempTag("yaht_inited"))
{
_setRoll(pChar, 0);
_setDice(pChar, [1, 1, 1, 1, 1]);
_setHolds(pChar, [false, false, false, false, false]);
_setScores(pChar, [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
_setTotals(pChar, [0, 0, 0, 0]);
_setUsedRoll(pChar, true);
pChar.SetTempTag("yaht_inited", "1");
}
}
function Y_CanPick(scores, catIdx, dice)
{
// normal categories: only if unscored
if (catIdx !== 11) return (scores[catIdx] | 0) === -1;
// Yahtzee: unscored -> can pick; scored 50+ -> allow only if another Yahtzee is rolled
var cur = scores[11] | 0;
if (cur === -1) return true;
if (cur >= 50) return Y_CalcYahtzee(dice) === 50;
return false;
}
/* --------------------------- Scoring Core (ported 1:1) --------------------------- */
// AddDice (upper section)
function Y_AddDice(dice, face)
{
var s = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === (face | 0)) s += face; return s;
}
// Totals
function Y_CalcTotals(scores, totals)
{
var score = 0, i = 0;
for (i = 0; i < 6; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
totals[0] = score;
totals[1] = (score >= 63) ? 35 : 0; // <-- ensure it zeroes out when <63
score = 0;
for (; i < 13; i++) { if ((scores[i] | 0) !== -1) score += scores[i] | 0; }
totals[2] = score;
totals[3] = (totals[0] | 0) + (totals[1] | 0) + (totals[2] | 0);
return totals;
}
// Three/Four of a kind -> sum of all dice if condition
function Y_CalcMultiples(dice, amount)
{
for (var face = 1; face <= 6; face++)
{
var count = 0; for (var i = 0; i < 5; i++) if ((dice[i] | 0) === face)
{
count++; if (count >= amount)
{
var sum = 0; for (var j = 0; j < 5; j++) sum += dice[j] | 0; return sum;
}
}
}
return 0;
}
function Y_CalcChance(dice) { var s = 0; for (var i = 0; i < 5; i++) s += dice[i] | 0; return s; }
function _clone(a) { var b = []; for (var i = 0; i < a.length; i++) b[i] = a[i] | 0; return b; }
function _sort(a) { a.sort(function(x, y) { return (x | 0) - (y | 0); }); return a; }
function Y_CalcFullHouse(dice)
{
var d = _sort(_clone(dice));
if (((d[0] == d[1] && d[1] == d[2]) && (d[3] == d[4]) && (d[2] != d[3])) ||
((d[0] == d[1]) && (d[2] == d[3] && d[3] == d[4]) && (d[1] != d[2]))) return 25;
return 0;
}
function Y_CalcShortStraight(dice)
{
var d = _sort(_clone(dice));
// move duplicates to the end (as in C#)
for (var i = 0; i < 4; i++)
{
if (d[i] == d[i + 1])
{
var tmp = d[i];
for (var k = i; k < 4; k++) d[k] = d[k + 1];
d[4] = tmp;
}
}
if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4) ||
(d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5) ||
(d[0] == 3 && d[1] == 4 && d[2] == 5 && d[3] == 6) ||
(d[1] == 1 && d[2] == 2 && d[3] == 3 && d[4] == 4) ||
(d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
(d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 30;
return 0;
}
function Y_CalcLongStraight(dice)
{
var d = _sort(_clone(dice));
if ((d[0] == 1 && d[1] == 2 && d[2] == 3 && d[3] == 4 && d[4] == 5) ||
(d[0] == 2 && d[1] == 3 && d[2] == 4 && d[3] == 5 && d[4] == 6)) return 40;
return 0;
}
function Y_CalcYahtzee(dice)
{
return (dice[0] == dice[1] && dice[1] == dice[2] && dice[2] == dice[3] && dice[3] == dice[4]) ? 50 : 0;
}
function Y_AllScored(scores)
{
for (var i = 0; i < 13; i++) if ((scores[i] | 0) === -1) return false;
return true;
}
/* --------------------------- Reward --------------------------- */
function Y_GiveReward(pChar, total)
{
// Mimic original reward: (total - 200)*10, min 0
var toGive = (total - 200) * 10; if (toGive < 0) toGive = 0;
if (toGive > 0)
{
// Create gold in backpack
// CreateBlankItem(socket, character, amount, itemName, itemID, colour, objectType, inPack)
CreateBlankItem(pChar.socket, pChar, toGive, "gold coins", GOLD_TILE_ID, 0x0, "ITEM", true); // in pack
}
pChar.SysMessage("Congratulations! You scored " + total + " points and received " + toGive + " gold.");
pChar.EmoteMessage("*Yahtzee! " + total + " points*");
}
/* --------------------------- UI Build --------------------------- */
function Y_SendGump(pSock)
{
var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;
_initIfNeeded(pUser);
var roll = _getRoll(pUser);
var dice = _getDice(pUser);
var holds = _getHolds(pUser);
var scores = _getScores(pUser);
var totals = _getTotals(pUser);
var usedRoll = _getUsedRoll(pUser);
var g = new Gump();
g.AddPage(0);
// background & title
g.AddBackground(Y_GUMP_X, Y_GUMP_Y, Y_GUMP_W, Y_GUMP_H, Y_BG);
g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 20, Y_GUMP_W - 20, 16, Y_STRETCH_BAR1);
g.AddTiledGump(Y_GUMP_X + 10, Y_GUMP_Y + 10, Y_GUMP_W - 20, 20, Y_STRETCH_BAR2);
g.AddText(Y_GUMP_X + 110, Y_GUMP_Y + 10, 31, "Yahtzee");
// dice row
var xBase = Y_GUMP_X + 75;
var xOff = 25;
for (var d = 0; d < 5; d++)
{
var gID = Y_DICE_BASE_GUMP + (dice[d] | 0);
g.AddGump(xBase + d * xOff, Y_GUMP_Y + 60, gID);
}
// Roll block
g.AddText(Y_GUMP_X + 20, Y_GUMP_Y + 60, 1149, "Roll");
g.AddText(Y_GUMP_X + 30, Y_GUMP_Y + 80, 1149, "" + roll);
if (roll < 3)
{
// AddButton(x,y, up, ???, page, buttonID)
g.AddButton(Y_GUMP_X + 50, Y_GUMP_Y + 62, Y_BTN_GUMP_UP, 1, 0, BTN_ROLL);
}
// Hold toggles (visual check + button hotspot)
if (roll < 3)
{
g.AddText(Y_GUMP_X + 205, Y_GUMP_Y + 85, 1149, "Hold");
for (d = 0; d < 5; d++)
{
// draw check look (2715 = checked, 2714 = unchecked – same art used in your C#)
var checkGump = holds[d] ? 2715 : 2714;
g.AddGump(xBase + d * xOff + 1, Y_GUMP_Y + 85, checkGump);
// clickable tiny button overlapping the check image to toggle
var btnId = BTN_HOLD1 + d;
g.AddButton(xBase + d * xOff, Y_GUMP_Y + 85, Y_BTN_GUMP_UP, 1, 0, btnId);
}
}
// Section labels (left column)
var yBaseUpper = Y_GUMP_Y + 115;
var yOff = 20;
var labels = ["Ones", "Twos", "Threes", "Fours", "Fives", "Sixes"];
for (var i = 0; i < labels.length; i++)
{
g.AddText(Y_GUMP_X + 20, yBaseUpper + i * yOff, 1149, labels[i]);
}
g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 235, 1152, "Upper Total:");
g.AddText(Y_GUMP_X + 100, Y_GUMP_Y + 255, 1152, "Bonus:");
// Lower labels
var yBaseLower = Y_GUMP_Y + 295;
var lowers = ["Three of a kind", "Four of a kind", "Full House", "Short Straight", "Long Straight", "Yahtzee", "Chance"];
for (i = 0; i < lowers.length; i++)
{
g.AddText(Y_GUMP_X + 20, yBaseLower + i * yOff, 1149, lowers[i]);
}
g.AddText(Y_GUMP_X + 63, Y_GUMP_Y + 435, 1152, "Lower Total:");
g.AddText(Y_GUMP_X + 65, Y_GUMP_Y + 455, 1152, "Grand Total:");
if (!usedRoll)
{
for (i = 0; i < 6; i++)
{
if (Y_CanPick(scores, i, dice))
g.AddButton(Y_BTN_X, yBaseUpper + i * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, BTN_ONES + i);
}
}
if (!usedRoll)
{
var y = yBaseLower;
var map = [
[6, BTN_3KIND, 0],
[7, BTN_4KIND, 1],
[8, BTN_FULLHOUSE, 2],
[9, BTN_SSTRAIGHT, 3],
[10, BTN_LSTRAIGHT, 4],
[11, BTN_YAHTZEE, 5],
[12, BTN_CHANCE, 6]
];
for (var m = 0; m < map.length; m++)
{
var idx = map[m][0];
var btnID = map[m][1];
var row = map[m][2];
if (Y_CanPick(scores, idx, dice))
g.AddButton(Y_BTN_X, y + row * yOff + Y_BTN_Y_ADJ, Y_BTN_GUMP_UP, 1, 0, btnID);
}
}
// Score columns (right side)
// Upper (6 lines)
for (i = 0; i < 6; i++)
{
var s = scores[i] | 0;
g.AddText(Y_GUMP_X + 185, yBaseUpper + i * yOff, 1149, (s > -1 ? ("" + s) : "-"));
}
// Lower (7 lines)
for (; i < 13; i++)
{
s = scores[i] | 0;
g.AddText(Y_GUMP_X + 185, yBaseLower + (i - 6) * yOff, 1149, (s > -1 ? ("" + s) : "-"));
}
// Totals
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 255, 1152, "" + (totals[0] | 0)); // upper total
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 275, 1152, "" + (totals[1] | 0)); // bonus
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 435, 1152, "" + (totals[2] | 0)); // lower total
g.AddText(Y_GUMP_X + 185, Y_GUMP_Y + 455, 1152, "" + (totals[3] | 0)); // grand total
// In Y_SendGump, after you draw totals, show when all scored:
if (Y_AllScored(scores))
{
// draw a New Game button/footer
g.AddButton(Y_GUMP_X + 20, Y_GUMP_Y + 470, Y_BTN_GUMP_UP, 1, 0, BTN_NEWGAME);
g.AddText(Y_GUMP_X + 55, Y_GUMP_Y + 470, 1152, "New Game");
}
g.Send(pSock);
g.Free();
}
/* --------------------------- Game Actions --------------------------- */
function Y_ToggleHold(pChar, idx)
{
var h = _getHolds(pChar);
h[idx] = !h[idx];
_setHolds(pChar, h);
}
function Y_Roll(pChar)
{
var roll = _getRoll(pChar);
if (roll >= 3) return;
var dice = _getDice(pChar);
var holds = _getHolds(pChar);
for (var i = 0; i < 5; i++)
{
if (!holds[i] || roll === 0)
{
dice[i] = RandomNumber(1, 6); // inclusive
}
}
_setDice(pChar, dice);
_setRoll(pChar, roll + 1);
_setUsedRoll(pChar, false);
}
function Y_ScoreCategory(pChar, catBtn)
{
var dice = _getDice(pChar);
var scores = _getScores(pChar);
function setScore(idx, val)
{
if ((scores[idx] | 0) === -1)
{
scores[idx] = val | 0;
_setScores(pChar, scores);
_setRoll(pChar, 0);
_resetHolds(pChar);
}
}
switch (catBtn)
{
case BTN_ONES: setScore(0, Y_AddDice(dice, 1)); break;
case BTN_TWOS: setScore(1, Y_AddDice(dice, 2)); break;
case BTN_THREES: setScore(2, Y_AddDice(dice, 3)); break;
case BTN_FOURS: setScore(3, Y_AddDice(dice, 4)); break;
case BTN_FIVES: setScore(4, Y_AddDice(dice, 5)); break;
case BTN_SIXES: setScore(5, Y_AddDice(dice, 6)); break;
case BTN_3KIND: setScore(6, Y_CalcMultiples(dice, 3)); break;
case BTN_4KIND: setScore(7, Y_CalcMultiples(dice, 4)); break;
case BTN_FULLHOUSE: setScore(8, Y_CalcFullHouse(dice)); break;
case BTN_SSTRAIGHT: setScore(9, Y_CalcShortStraight(dice)); break;
case BTN_LSTRAIGHT: setScore(10, Y_CalcLongStraight(dice)); break;
case BTN_YAHTZEE:
// First Yahtzee fills slot; subsequent Yahtzees add +50 if you roll another 50 (mirrors C#)
var cur = scores[11] | 0;
var rolled = Y_CalcYahtzee(dice);
if (cur === -1)
{
setScore(11, rolled);
} else if (cur >= 50 && rolled === 50)
{
scores[11] = cur + 50;
_setScores(pChar, scores);
_setRoll(pChar, 0);
_resetHolds(pChar);
}
break;
case BTN_CHANCE: setScore(12, Y_CalcChance(dice)); break;
}
// Update totals
var totals = Y_CalcTotals(_getScores(pChar), _getTotals(pChar));
_setTotals(pChar, totals);
}
/* --------------------------- Events --------------------------- */
function onGumpPress(pSock, pButton, gumpData)
{
var pUser = pSock.currentChar; if (!ValidateObject(pUser)) return;
if (pButton === 0) { return; } // closed
_initIfNeeded(pUser);
if (pButton >= BTN_HOLD1 && pButton <= BTN_HOLD5)
{
Y_ToggleHold(pUser, pButton - BTN_HOLD1);
Y_SendGump(pSock);
return;
}
if (pButton === BTN_ROLL)
{
Y_Roll(pUser);
Y_SendGump(pSock);
return;
}
if (pButton === BTN_NEWGAME)
{
Y_ResetGame(pUser);
_initIfNeeded(pUser);
Y_SendGump(pSock);
return;
}
// Category pick
Y_ScoreCategory(pUser, pButton);
// Totals + end check
var totals = _getTotals(pUser);
var scores = _getScores(pUser);
if (Y_AllScored(scores))
{
Y_GiveReward(pUser, totals[3] | 0);
// Lock UI at end (shows roll=3 and disables picks)
_setRoll(pUser, 3);
_setUsedRoll(pUser, true);
}
Y_SendGump(pSock);
}
// Reset all state so a brand-new game starts on next open
function Y_ResetGame(pChar)
{
// Prefer DelTempTag when available; fall back to blanking
function _del(k)
{
if (typeof pChar.DelTempTag === "function") pChar.DelTempTag(k);
else pChar.SetTempTag(k, "");
}
_del("yaht_inited");
_del("yaht_roll");
_del("yaht_usedRoll");
_del("yaht_dice");
_del("yaht_hold");
_del("yaht_scores");
_del("yaht_totals");
_del("yaht_itemSer"); // optional binding (see #3)
}
// Test command: [yahtzee
/** @param {Socket} socket */
function command_YAHTZEE(socket, cmdString)
{
if (!socket || !ValidateObject(socket.currentChar)) return;
var p = (cmdString || "").trim().toLowerCase();
if (p === "reset")
{
Y_ResetGame(socket.currentChar);
}
_initIfNeeded(socket.currentChar);
Y_SendGump(socket);
}
function onUseChecked(pUser, iUsed)
{
if (ValidateObject(iUsed) && iUsed.isItem)
{
var ser = iUsed.serial | 0;
var last = parseInt(pUser.GetTempTag("yaht_itemSer") || "0", 10) | 0;
if (last !== ser)
{
Y_ResetGame(pUser);
pUser.SetTempTag("yaht_itemSer", "" + ser);
}
}
_initIfNeeded(pUser);
Y_SendGump(pUser.socket);
return false;
}