Face Hair growth
Posted: Mon Nov 03, 2025 2:08 am
in your global.js script anywhere in the login function add this
7526=custom/facial_hair_growth.js
TriggerEvent( 7526, "hairlogin", socket );
7526=custom/facial_hair_growth.js
TriggerEvent( 7526, "hairlogin", socket );
// ============================================================================
// Facial Hair Growth (Layer 0x10 items) — UOX3 / SpiderMonkey 1.8.5 (ES5)
// - Set growth cadence in MILLISECONDS via GROWTH_INTERVAL_MS
// - Starts a repeating per-character timer once; timer keeps running even if offline
// - Females remain clean-shaven
// - NEW: Beard hue auto-matches head-hair hue (layer 0x0B); falls back to old beard hue
// ============================================================================
/* --------------------------- Config --------------------------- */
// Change this for your cadence (milliseconds):
//var GROWTH_INTERVAL_MS = 9000; // testing
var GROWTH_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000; // production example
var HAIR_LAYER = 0x0B; // head hair layer (11)
var FACIAL_LAYER = 0x10; // facial hair layer (16)
var TIMER_ID_BEARD = 7526; // unique timer id for this script/feature
var TAG_TIMER_FLAG = "BeardTimerActive"; // "1" when we've started the loop for this char
// Growth ladder (None -> ... -> Terminal). IDs are beard itemIDs for layer 0x10.
var BEARD_SEQ = [
0x0000, // None (no item)
0x2040, // Goatee
0x2041, // Mustache
0x204D, // Vandyke
0x203F, // Short Beard
0x204B, // Medium Short Beard
0x203E, // Long Beard
0x204C // Medium Long Beard (terminal)
];
// Quick lookup: itemID -> index
var BEARD_TO_IDX = (function () {
var m = {};
for (var i = 0; i < BEARD_SEQ.length; i++) m[BEARD_SEQ[i]] = i;
return m;
})();
/* --------------------------- Helpers --------------------------- */
function getFacialItem(p) { return p.FindItemLayer(FACIAL_LAYER); }
function getHairItem(p) { return p.FindItemLayer(HAIR_LAYER); }
function readHue(obj)
{
if (!obj) return 0;
if (obj.hue !== undefined) return obj.hue | 0;
if (obj.colour !== undefined) return obj.colour | 0;
return 0;
}
function setHue(obj, hue)
{
if (!obj || !hue) return;
if (obj.hue !== undefined) obj.hue = hue;
else if (obj.colour !== undefined) obj.colour = hue;
}
function getCurrentIndex(p)
{
var it = getFacialItem(p);
if (!it) return 0; // none
var id = it.id | 0;
return (BEARD_TO_IDX[id] != null) ? BEARD_TO_IDX[id] : 0; // unknown -> treat as none
}
function nextIndex(idx) { return (idx + 1 < BEARD_SEQ.length) ? (idx + 1) : -1; }
/**
* Equip beard at ladder index, coloring with:
* 1) current head-hair hue (layer 0x0B) if present,
* 2) else previous beard hue (if keepPrevBeardHue && beard existed),
* 3) else default (no hue).
*/
function equipBeardIndex(p, idx, keepPrevBeardHue)
{
var prevBeard = getFacialItem(p);
var prevBeardHue = keepPrevBeardHue ? readHue(prevBeard) : 0;
// Prefer matching head-hair hue, if any
var hairHue = readHue(getHairItem(p));
var finalHue = hairHue || prevBeardHue;
// Remove existing beard
if (prevBeard) prevBeard.Delete();
// Index 0 = clean-shaven
if (idx <= 0) return;
var beardID = "0x" + (BEARD_SEQ[idx] >>> 0).toString(16);
var made = CreateDFNItem(p.socket, p, beardID, 1, "ITEM", true);
if (made)
{
made.container = p;
made.layer = FACIAL_LAYER;
if (finalHue) setHue(made, finalHue);
}
}
function applyOneStep(p)
{
// Females remain clean-shaven
if (p.gender === 1) {
var cur = getFacialItem(p);
if (cur) cur.Delete();
return false;
}
var idx = getCurrentIndex(p);
var nxt = nextIndex(idx);
if (nxt < 0) return false; // already at terminal
// Keep old beard hue as fallback; function will override with hair hue if present
var keepPrev = (idx > 0);
equipBeardIndex(p, nxt, keepPrev);
return (nxt + 1 < BEARD_SEQ.length); // true if more growth later
}
function scheduleNext(p)
{
if (!ValidateObject(p) || !p.isChar) return;
// Re-arm fixed delay; UOX3 timers accept milliseconds
p.StartTimer(Math.max(1, GROWTH_INTERVAL_MS), TIMER_ID_BEARD, true);
}
/* --------------------------- Events --------------------------- */
// Start the loop once per character. Timers continue even if they log out.
function hairlogin(socket)
{
var p = socket.currentChar;
if (!ValidateObject(p) || !p.isChar) return;
// Keep females clean-shaven on login too
if (p.gender === 1) {
var cur = getFacialItem(p);
if (cur) cur.Delete();
}
// Only start the repeating timer once
if (p.GetTag(TAG_TIMER_FLAG) !== "1") {
p.SetTag(TAG_TIMER_FLAG, "1");
scheduleNext(p);
}
}
// Called each interval; apply one growth step, then re-schedule
function onTimer(timerObj, timerID)
{
if (timerID !== TIMER_ID_BEARD) return false;
applyOneStep(timerObj);
scheduleNext(timerObj);
return true;
}
// Facial Hair Growth (Layer 0x10 items) — UOX3 / SpiderMonkey 1.8.5 (ES5)
// - Set growth cadence in MILLISECONDS via GROWTH_INTERVAL_MS
// - Starts a repeating per-character timer once; timer keeps running even if offline
// - Females remain clean-shaven
// - NEW: Beard hue auto-matches head-hair hue (layer 0x0B); falls back to old beard hue
// ============================================================================
/* --------------------------- Config --------------------------- */
// Change this for your cadence (milliseconds):
//var GROWTH_INTERVAL_MS = 9000; // testing
var GROWTH_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000; // production example
var HAIR_LAYER = 0x0B; // head hair layer (11)
var FACIAL_LAYER = 0x10; // facial hair layer (16)
var TIMER_ID_BEARD = 7526; // unique timer id for this script/feature
var TAG_TIMER_FLAG = "BeardTimerActive"; // "1" when we've started the loop for this char
// Growth ladder (None -> ... -> Terminal). IDs are beard itemIDs for layer 0x10.
var BEARD_SEQ = [
0x0000, // None (no item)
0x2040, // Goatee
0x2041, // Mustache
0x204D, // Vandyke
0x203F, // Short Beard
0x204B, // Medium Short Beard
0x203E, // Long Beard
0x204C // Medium Long Beard (terminal)
];
// Quick lookup: itemID -> index
var BEARD_TO_IDX = (function () {
var m = {};
for (var i = 0; i < BEARD_SEQ.length; i++) m[BEARD_SEQ[i]] = i;
return m;
})();
/* --------------------------- Helpers --------------------------- */
function getFacialItem(p) { return p.FindItemLayer(FACIAL_LAYER); }
function getHairItem(p) { return p.FindItemLayer(HAIR_LAYER); }
function readHue(obj)
{
if (!obj) return 0;
if (obj.hue !== undefined) return obj.hue | 0;
if (obj.colour !== undefined) return obj.colour | 0;
return 0;
}
function setHue(obj, hue)
{
if (!obj || !hue) return;
if (obj.hue !== undefined) obj.hue = hue;
else if (obj.colour !== undefined) obj.colour = hue;
}
function getCurrentIndex(p)
{
var it = getFacialItem(p);
if (!it) return 0; // none
var id = it.id | 0;
return (BEARD_TO_IDX[id] != null) ? BEARD_TO_IDX[id] : 0; // unknown -> treat as none
}
function nextIndex(idx) { return (idx + 1 < BEARD_SEQ.length) ? (idx + 1) : -1; }
/**
* Equip beard at ladder index, coloring with:
* 1) current head-hair hue (layer 0x0B) if present,
* 2) else previous beard hue (if keepPrevBeardHue && beard existed),
* 3) else default (no hue).
*/
function equipBeardIndex(p, idx, keepPrevBeardHue)
{
var prevBeard = getFacialItem(p);
var prevBeardHue = keepPrevBeardHue ? readHue(prevBeard) : 0;
// Prefer matching head-hair hue, if any
var hairHue = readHue(getHairItem(p));
var finalHue = hairHue || prevBeardHue;
// Remove existing beard
if (prevBeard) prevBeard.Delete();
// Index 0 = clean-shaven
if (idx <= 0) return;
var beardID = "0x" + (BEARD_SEQ[idx] >>> 0).toString(16);
var made = CreateDFNItem(p.socket, p, beardID, 1, "ITEM", true);
if (made)
{
made.container = p;
made.layer = FACIAL_LAYER;
if (finalHue) setHue(made, finalHue);
}
}
function applyOneStep(p)
{
// Females remain clean-shaven
if (p.gender === 1) {
var cur = getFacialItem(p);
if (cur) cur.Delete();
return false;
}
var idx = getCurrentIndex(p);
var nxt = nextIndex(idx);
if (nxt < 0) return false; // already at terminal
// Keep old beard hue as fallback; function will override with hair hue if present
var keepPrev = (idx > 0);
equipBeardIndex(p, nxt, keepPrev);
return (nxt + 1 < BEARD_SEQ.length); // true if more growth later
}
function scheduleNext(p)
{
if (!ValidateObject(p) || !p.isChar) return;
// Re-arm fixed delay; UOX3 timers accept milliseconds
p.StartTimer(Math.max(1, GROWTH_INTERVAL_MS), TIMER_ID_BEARD, true);
}
/* --------------------------- Events --------------------------- */
// Start the loop once per character. Timers continue even if they log out.
function hairlogin(socket)
{
var p = socket.currentChar;
if (!ValidateObject(p) || !p.isChar) return;
// Keep females clean-shaven on login too
if (p.gender === 1) {
var cur = getFacialItem(p);
if (cur) cur.Delete();
}
// Only start the repeating timer once
if (p.GetTag(TAG_TIMER_FLAG) !== "1") {
p.SetTag(TAG_TIMER_FLAG, "1");
scheduleNext(p);
}
}
// Called each interval; apply one growth step, then re-schedule
function onTimer(timerObj, timerID)
{
if (timerID !== TIMER_ID_BEARD) return false;
applyOneStep(timerObj);
scheduleNext(timerObj);
return true;
}