achievement System
Posted: Sun Jun 22, 2025 5:13 pm
Uox3 first of its kind achievement system.
at the moment it's just simple script to be able to start making Achievements
I will work on more update version with how to do it with crafting
killing monsters collecting items ect.
// achievementGump.js - UOX3 JavaScript
function onUseChecked(pUser, iUsed)
{
if (!pUser || !ValidateObject(pUser))
return false;
var progressMap = LoadAchievements(pUser);
var achID = 101; // ID for "Skeleton Slayer"
var current = progressMap[achID] || 0;
var gain = 5;
var capped = Math.min(current + gain, 100); // ? Prevent overfilling
progressMap[achID] = capped;
// Check for unlock at exact value (and not multiple triggers)
if (current < 100 && capped == 100)
{
pUser.SysMessage("Achievement Unlocked: Skeleton Slayer!");
// Optionally: ShowAchievementUnlockGump(pUser, achID);
}
SaveAchievements(pUser, progressMap);
ShowAchievementGump(pUser.socket, 1); // Default to category 1
}
function LoadAchievements(pChar)
{
var mFile = new UOXCFile();
var accountID = pChar.account.id;
var fileName = "Achievements_" + accountID + ".jsdata";
var progressMap = {};
mFile.Open(fileName, "r", "Achievements");
if (mFile && mFile.Length() > 0)
{
while (!mFile.EOF())
{
var line = manualTrim(mFile.ReadUntil("\n"));
if (!line || line.indexOf(":") == -1)
continue;
var parts = line.split(":");
var id = parseInt(parts[0]);
var val = parseInt(parts[1]);
if (!isNaN(id) && !isNaN(val))
progressMap[id] = val;
}
mFile.Close();
mFile.Free();
}
else if (mFile)
{
mFile.Free();
}
return progressMap;
}
function manualTrim(str)
{
return str.replace(/^\s+|\s+$/g, "");
}
function SaveAchievements(pChar, progressMap)
{
var mFile = new UOXCFile();
var accountID = pChar.account.id;
var fileName = "Achievements_" + accountID + ".jsdata";
mFile.Open(fileName, "w", "Achievements"); // Must match a valid [UOX3Directories] entry
if (mFile)
{
for (var key in progressMap)
{
if (progressMap.hasOwnProperty(key))
{
mFile.Write(key + ":" + progressMap[key] + "\n");
}
}
mFile.Close();
mFile.Free();
return true;
}
else
{
if (mFile) mFile.Free();
return false;
}
}
function ShowAchievementGump(pSock, categoryID, page)
{
if (!pSock || !ValidateObject(pSock.currentChar))
return;
var pChar = pSock.currentChar;
var gump = new Gump; // create a new gump
//var totalPoints = parseInt(pChar.GetTag("achv_totalpoints")) || 0;
var progressData = LoadAchievements(pChar);
var totalPoints = 0;
for (var key in progressData)
{
if (progressData[key] > 0)
totalPoints += progressData[key]; // Placeholder logic, adjust if points are separate
}
var page = parseInt(pChar.GetTag("achvPage")) || 1;
gump.AddBackground(11, 15, 758, 575, 2600);
gump.AddBackground(57, 92, 666, 478, 9250);
gump.AddBackground(321, 104, 386, 453, 9270);
gump.AddBackground(72, 104, 245, 453, 9270);
gump.AddBackground(72, 34, 635, 53, 9270);
gump.AddBackground(327, 0, 133, 41, 9200);
gump.AddText(330, 52, 68, "Achievement System");
gump.AddText(360, 11, 82, totalPoints + " Points");
gump.AddBackground(341, 522, 353, 26, 9200);
// Categories - sample static list for now
var categories = [
{ id: 1, name: "Combat", parent: 0 },
{ id: 2, name: "Crafting", parent: 0 },
{ id: 3, name: "Exploration", parent: 0 },
];
var currentCat = categoryID || 1;
var catCount = 0;
for (var i = 0; i < categories.length; ++i)
{
var cat = categories[i];
var x = 90;
var bgID = 9200;
if (cat.id == currentCat)
bgID = 5120;
gump.AddBackground(x, 123 + (catCount * 31), 188, 25, bgID);
if (cat.id == currentCat)
gump.AddGump(x + 12, 129 + (catCount * 31), 1210);
else
gump.AddButton(x + 12, 129 + (catCount * 31), 1209, 1210, 1, 0, 5000 + cat.id);
gump.AddText(x + 32, 125 + (catCount * 31), 0, cat.name);
catCount++;
}
var achievements = GetAchievements();
var achievementsInCategory = achievements.filter(function(ac) { return ac.categoryID == currentCat });
var totalPages = Math.ceil(achievementsInCategory.length / 4);
var displayCount = 0;
for (var j = 0; j < achievements.length; ++j)
{
var ac = achievements[j];
if (ac.categoryID != currentCat)
continue;
//var progress = parseInt(pChar.GetTag("achv_" + ac.id)) || 0;
var progress = progressData[ac.id] || 0;
var bg = (progress >= ac.total) ? 9300 : 9350;
var yOffset = 122 + ((displayCount % 4) * 100);
var pageNum = Math.floor(displayCount / 4) + 1;
if (pageNum != page)
{
displayCount++;
continue;
}
if (page > 1)
gump.AddButton(345, 524, 4014, 4015, 1, 0, 6000 + (page - 1));
if (page < totalPages)
gump.AddButton(658, 524, 4005, 4006, 1, 0, 6100 + (page + 1));
gump.AddText(484, 526, 32, "Page " + pageNum);
// ??? Fixed progress bar logic
var maxWidth = 95;
var barWidth = Math.floor(Math.min((progress / ac.total) * maxWidth, maxWidth));
gump.AddBackground(340, yOffset, 347, 97, bg);
gump.AddText(414, yOffset + 9, 49, ac.title);
if (ac.icon > 0)
gump.AddPicture(357, yOffset + 25, ac.icon);
gump.AddTiledGump(416, yOffset + 81, maxWidth, 9, 9750); // Bar background
gump.AddTiledGump(416, yOffset + 81, barWidth, 9, 9752); // Filled progress
//gump.AddTiledGump(416, yOffset + 81, 95, 9, 9750);
//gump.AddTiledGump(416, yOffset + 81, Math.min(progress / ac.total * 95, 95), 9, 9752);
gump.AddText(413, yOffset + 30, 0, ac.desc);
gump.AddText(522, yOffset + 74, 0, progress + " / " + ac.total);
gump.AddBackground(628, yOffset + 27, 48, 48, 9200);
gump.AddText(648, yOffset + 42, 32, ac.points.toString());
displayCount++;
}
gump.Send( pSock );
gump.Free();
}
function onGumpPress(pSock, pButton, gumpData)
{
var pUser = pSock.currentChar;
if (!ValidateObject(pUser))
return;
// Handle category switching
if (pButton >= 5000 && pButton < 6000)
{
pUser.SetTag("achvPage", 1); // Reset to page 1 on category switch
ShowAchievementGump(pSock, pButton - 5000);
return;
}
// Prev page
if (pButton >= 6000 && pButton < 6100)
{
var prevPage = pButton - 6000;
pUser.SetTag("achvPage", prevPage);
ShowAchievementGump(pSock);
return;
}
// Next page
if (pButton >= 6100 && pButton < 6200)
{
var nextPage = pButton - 6100;
pUser.SetTag("achvPage", nextPage);
ShowAchievementGump(pSock);
return;
}
}
function GetAchievements()
{
return [
{ id: 101, categoryID: 1, title: "Skeleton Slayer", desc: "Kill 100 skeletons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 102, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 103, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 104, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 105, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 106, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 107, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 202, categoryID: 2, title: "Master Blacksmith", desc: "Reach 100.0 Blacksmithing.", points: 15, icon: 0x13E3, total: 1000 }
];
}
function onUseChecked(pUser, iUsed)
{
if (!pUser || !ValidateObject(pUser))
return false;
var progressMap = LoadAchievements(pUser);
var achID = 101; // ID for "Skeleton Slayer"
var current = progressMap[achID] || 0;
var gain = 5;
var capped = Math.min(current + gain, 100); // ? Prevent overfilling
progressMap[achID] = capped;
// Check for unlock at exact value (and not multiple triggers)
if (current < 100 && capped == 100)
{
pUser.SysMessage("Achievement Unlocked: Skeleton Slayer!");
// Optionally: ShowAchievementUnlockGump(pUser, achID);
}
SaveAchievements(pUser, progressMap);
ShowAchievementGump(pUser.socket, 1); // Default to category 1
}
function LoadAchievements(pChar)
{
var mFile = new UOXCFile();
var accountID = pChar.account.id;
var fileName = "Achievements_" + accountID + ".jsdata";
var progressMap = {};
mFile.Open(fileName, "r", "Achievements");
if (mFile && mFile.Length() > 0)
{
while (!mFile.EOF())
{
var line = manualTrim(mFile.ReadUntil("\n"));
if (!line || line.indexOf(":") == -1)
continue;
var parts = line.split(":");
var id = parseInt(parts[0]);
var val = parseInt(parts[1]);
if (!isNaN(id) && !isNaN(val))
progressMap[id] = val;
}
mFile.Close();
mFile.Free();
}
else if (mFile)
{
mFile.Free();
}
return progressMap;
}
function manualTrim(str)
{
return str.replace(/^\s+|\s+$/g, "");
}
function SaveAchievements(pChar, progressMap)
{
var mFile = new UOXCFile();
var accountID = pChar.account.id;
var fileName = "Achievements_" + accountID + ".jsdata";
mFile.Open(fileName, "w", "Achievements"); // Must match a valid [UOX3Directories] entry
if (mFile)
{
for (var key in progressMap)
{
if (progressMap.hasOwnProperty(key))
{
mFile.Write(key + ":" + progressMap[key] + "\n");
}
}
mFile.Close();
mFile.Free();
return true;
}
else
{
if (mFile) mFile.Free();
return false;
}
}
function ShowAchievementGump(pSock, categoryID, page)
{
if (!pSock || !ValidateObject(pSock.currentChar))
return;
var pChar = pSock.currentChar;
var gump = new Gump; // create a new gump
//var totalPoints = parseInt(pChar.GetTag("achv_totalpoints")) || 0;
var progressData = LoadAchievements(pChar);
var totalPoints = 0;
for (var key in progressData)
{
if (progressData[key] > 0)
totalPoints += progressData[key]; // Placeholder logic, adjust if points are separate
}
var page = parseInt(pChar.GetTag("achvPage")) || 1;
gump.AddBackground(11, 15, 758, 575, 2600);
gump.AddBackground(57, 92, 666, 478, 9250);
gump.AddBackground(321, 104, 386, 453, 9270);
gump.AddBackground(72, 104, 245, 453, 9270);
gump.AddBackground(72, 34, 635, 53, 9270);
gump.AddBackground(327, 0, 133, 41, 9200);
gump.AddText(330, 52, 68, "Achievement System");
gump.AddText(360, 11, 82, totalPoints + " Points");
gump.AddBackground(341, 522, 353, 26, 9200);
// Categories - sample static list for now
var categories = [
{ id: 1, name: "Combat", parent: 0 },
{ id: 2, name: "Crafting", parent: 0 },
{ id: 3, name: "Exploration", parent: 0 },
];
var currentCat = categoryID || 1;
var catCount = 0;
for (var i = 0; i < categories.length; ++i)
{
var cat = categories[i];
var x = 90;
var bgID = 9200;
if (cat.id == currentCat)
bgID = 5120;
gump.AddBackground(x, 123 + (catCount * 31), 188, 25, bgID);
if (cat.id == currentCat)
gump.AddGump(x + 12, 129 + (catCount * 31), 1210);
else
gump.AddButton(x + 12, 129 + (catCount * 31), 1209, 1210, 1, 0, 5000 + cat.id);
gump.AddText(x + 32, 125 + (catCount * 31), 0, cat.name);
catCount++;
}
var achievements = GetAchievements();
var achievementsInCategory = achievements.filter(function(ac) { return ac.categoryID == currentCat });
var totalPages = Math.ceil(achievementsInCategory.length / 4);
var displayCount = 0;
for (var j = 0; j < achievements.length; ++j)
{
var ac = achievements[j];
if (ac.categoryID != currentCat)
continue;
//var progress = parseInt(pChar.GetTag("achv_" + ac.id)) || 0;
var progress = progressData[ac.id] || 0;
var bg = (progress >= ac.total) ? 9300 : 9350;
var yOffset = 122 + ((displayCount % 4) * 100);
var pageNum = Math.floor(displayCount / 4) + 1;
if (pageNum != page)
{
displayCount++;
continue;
}
if (page > 1)
gump.AddButton(345, 524, 4014, 4015, 1, 0, 6000 + (page - 1));
if (page < totalPages)
gump.AddButton(658, 524, 4005, 4006, 1, 0, 6100 + (page + 1));
gump.AddText(484, 526, 32, "Page " + pageNum);
// ??? Fixed progress bar logic
var maxWidth = 95;
var barWidth = Math.floor(Math.min((progress / ac.total) * maxWidth, maxWidth));
gump.AddBackground(340, yOffset, 347, 97, bg);
gump.AddText(414, yOffset + 9, 49, ac.title);
if (ac.icon > 0)
gump.AddPicture(357, yOffset + 25, ac.icon);
gump.AddTiledGump(416, yOffset + 81, maxWidth, 9, 9750); // Bar background
gump.AddTiledGump(416, yOffset + 81, barWidth, 9, 9752); // Filled progress
//gump.AddTiledGump(416, yOffset + 81, 95, 9, 9750);
//gump.AddTiledGump(416, yOffset + 81, Math.min(progress / ac.total * 95, 95), 9, 9752);
gump.AddText(413, yOffset + 30, 0, ac.desc);
gump.AddText(522, yOffset + 74, 0, progress + " / " + ac.total);
gump.AddBackground(628, yOffset + 27, 48, 48, 9200);
gump.AddText(648, yOffset + 42, 32, ac.points.toString());
displayCount++;
}
gump.Send( pSock );
gump.Free();
}
function onGumpPress(pSock, pButton, gumpData)
{
var pUser = pSock.currentChar;
if (!ValidateObject(pUser))
return;
// Handle category switching
if (pButton >= 5000 && pButton < 6000)
{
pUser.SetTag("achvPage", 1); // Reset to page 1 on category switch
ShowAchievementGump(pSock, pButton - 5000);
return;
}
// Prev page
if (pButton >= 6000 && pButton < 6100)
{
var prevPage = pButton - 6000;
pUser.SetTag("achvPage", prevPage);
ShowAchievementGump(pSock);
return;
}
// Next page
if (pButton >= 6100 && pButton < 6200)
{
var nextPage = pButton - 6100;
pUser.SetTag("achvPage", nextPage);
ShowAchievementGump(pSock);
return;
}
}
function GetAchievements()
{
return [
{ id: 101, categoryID: 1, title: "Skeleton Slayer", desc: "Kill 100 skeletons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 102, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 103, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 104, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 105, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 106, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 107, categoryID: 1, title: "Dragon Slayer", desc: "Kill 100 Dragons.", points: 10, icon: 0x1B7A, total: 100 },
{ id: 202, categoryID: 2, title: "Master Blacksmith", desc: "Reach 100.0 Blacksmithing.", points: 15, icon: 0x13E3, total: 1000 }
];
}
I will work on more update version with how to do it with crafting
killing monsters collecting items ect.