Files
Albion/app.js
T
TheAgain f5cd057e8a Main
Main files for Loot distribution. requires you to download the items.json from the Albion tools repository
2026-07-28 23:33:52 -02:30

126 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let items=[],players=[],loot=[],selected=null;
const fmt=n=>Number(n).toLocaleString();
async function loadDatabase(){
try{
const response=await fetch('items.json');
if(!response.ok) throw new Error('load');
const raw=await response.json();
items=raw.map(x=>{
const name=(x.LocalizedNames&&x.LocalizedNames["EN-US"])||"";
const u=x.UniqueName||"";
const ench=(u.match(/@(\d)/)||[])[1]||"0";
const tier=(u.match(/T(\d)/)||[])[1]||"";
return {name,label:`${name} (T${tier}.${ench})`};
}).filter(i=>i.name);
const s=document.getElementById('dbStatus')||document.getElementById('status');
if(s)s.textContent='';
search.disabled=false;
}catch(e){
const s=document.getElementById('dbStatus')||document.getElementById('status');
if(s)s.textContent='Unable to load items.json';
}
}
loadDatabase();
function renderPlayers(){
playersDiv=playersEl=document.getElementById("players");
playersEl.innerHTML="";
players.forEach((p,i)=>{
const d=document.createElement("span");
d.className="player";
d.innerHTML=`${p} <button>x</button>`;
d.querySelector("button").onclick=()=>{players.splice(i,1);renderPlayers();};
playersEl.appendChild(d);
});
updateSummary();
}
function addPlayerFn(){
const n=playerName.value.trim();
if(!n)return;
players.push(n);
playerName.value="";
renderPlayers();
}
addPlayer.onclick=addPlayerFn;
playerName.addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();addPlayerFn();}});
search.oninput=()=>{
const q=search.value.toLowerCase();
results.innerHTML="";
if(q.length<2)return;
const groups={};
items.filter(i=>i.name.toLowerCase().includes(q)).slice(0,80).forEach(i=>(groups[i.name]??=[]).push(i));
Object.keys(groups).sort().forEach(name=>{
const g=document.createElement("div");g.className="group";
g.innerHTML=`<div class="title">${name}</div>`;
groups[name].forEach(it=>{
const c=document.createElement("div");c.className="child";c.textContent=it.label;
c.onclick=()=>{selected=it;selectedSpan.textContent=it.label;search.value=it.name;results.innerHTML="";updatePriceInfo();qty.focus();};
g.appendChild(c);
});
results.appendChild(g);
});
updateSummary();
};
const priceHistory=JSON.parse(localStorage.getItem("priceHistory")||"{}");
function fmtAge(ts){if(!ts)return"Never";let m=Math.floor((Date.now()-ts)/60000);if(m<60)return m+"m ago";let h=Math.floor(m/60);if(h<24)return h+"h ago";let d=Math.floor(h/24);h%=24;return d+"d "+h+"h ago";}
function updatePriceInfo(){if(!selected){lastUpdated.textContent="Never";return;}const p=priceHistory[selected.label];if(p){priceEach.value=p.price;updateTotal();lastUpdated.textContent=fmtAge(p.updated);}else lastUpdated.textContent="Never";}
const selectedSpan=document.getElementById("selected");
function updateTotal(){totalValue.textContent=fmt((+qty.value||0)*(+priceEach.value||0));}
qty.oninput=updateTotal;priceEach.oninput=updateTotal;updateTotal();
addLoot.onclick=()=>{
if(!selected)return alert("Select an item.");
const q=+qty.value||1,e=+priceEach.value||0,total=q*e;
const existing=loot.find(x=>x.item===selected.label&&x.each===e);
if(existing){existing.qty+=q;existing.total=existing.qty*existing.each;}
else loot.push({item:selected.label,qty:q,each:e,total,keep:keepTogether.checked});
priceHistory[selected.label]={price:e,updated:Date.now()};localStorage.setItem("priceHistory",JSON.stringify(priceHistory));renderLoot();
selected=null;selectedSpan.textContent="None";search.value="";qty.value=1;priceEach.value=0;updateTotal();
};
function updateSummary(){let stacks=loot.length,items=0,val=0;loot.forEach(l=>{items+=l.qty;val+=l.total;});sumStacks.textContent=stacks;sumItems.textContent=items;sumValue.textContent=fmt(val);targetShare.textContent=players.length?fmt(Math.round(val/players.length)):0;}
function renderLoot(){
const tb=document.querySelector("#loot tbody");tb.innerHTML="";
loot.forEach((l,i)=>{
const r=tb.insertRow();
r.insertCell().textContent=l.item;
r.insertCell().textContent=l.qty;
r.insertCell().textContent=fmt(l.each);
r.insertCell().textContent=fmt(l.total);
r.insertCell().textContent=l.keep?'✓':'';
const b=document.createElement("button");b.textContent="❌";b.onclick=()=>{loot.splice(i,1);renderLoot();};
r.insertCell().appendChild(b);
});
updateSummary();
}
distribute.onclick=()=>{
if(players.length===0)return alert("Add players first.");
let best=null,bestSpread=Infinity;
for(let t=0;t<300;t++){
let expanded=[];loot.forEach(l=>{if(l.keep){expanded.push({...l});}else{for(let i=0;i<l.qty;i++)expanded.push({item:l.item,qty:1,each:l.each,total:l.each});}});let work=expanded.sort(()=>Math.random()-0.5).sort((a,b)=>b.total-a.total);
let p=players.map(n=>({name:n,total:0,items:[]}));
work.forEach(it=>{p.sort((a,b)=>a.total-b.total);p[0].items.push(it);p[0].total+=it.total;});
let vals=p.map(x=>x.total);let spread=Math.max(...vals)-Math.min(...vals);
if(spread<bestSpread){bestSpread=spread;best=JSON.parse(JSON.stringify(p));}
}
distribution.innerHTML="";
best.forEach(pl=>{
const merged={};pl.items.forEach(i=>{const k=i.item+'|'+i.each;if(!merged[k])merged[k]={...i};else{merged[k].qty+=i.qty;merged[k].total+=i.total;}});
const d=document.createElement("div");
d.innerHTML=`<h3>${pl.name}${fmt(pl.total)} silver</h3><ul>${Object.values(merged).map(i=>`<li>${i.item} ×${i.qty}</li>`).join("")}</ul>`;
distribution.appendChild(d);
});
updateSummary();
};