Main files for Loot distribution. requires you to download the items.json from the Albion tools repository
126 lines
5.3 KiB
JavaScript
126 lines
5.3 KiB
JavaScript
|
||
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();
|
||
};
|