Main files for Loot distribution. requires you to download the items.json from the Albion tools repository
This commit is contained in:
2026-07-28 23:33:52 -02:30
commit f5cd057e8a
3 changed files with 162 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
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();
};