From 1f0b974f6c579cb57eafc5417da271217edd54ac Mon Sep 17 00:00:00 2001 From: jerick Date: Sun, 25 Jan 2026 18:11:59 -0500 Subject: [PATCH] Fixed Faction population --- __pycache__/main.cpython-311.pyc | Bin 15924 -> 15903 bytes main.py | 51 +++++------ .../__pycache__/server_state.cpython-311.pyc | Bin 9295 -> 9077 bytes services/__pycache__/torn_api.cpython-311.pyc | Bin 9191 -> 8329 bytes services/server_state.py | 28 +++--- static/dashboard.js | 81 ++++++++++++++---- 6 files changed, 102 insertions(+), 58 deletions(-) diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc index 2af41fed57e2f5489028f688ec491714f101d5ee..70dbcbde5965f3341155cd7136cb3828a320731c 100644 GIT binary patch delta 273 zcmdl|Grxv+IWI340}!OT>19f9W76iI;s%Ty2|&X&S&9-T8%jDerfe>i zEMQ{H+x$#=GNX7pP^u^sNE9;x2?d2BC6GY&SfAoLlMN)D8Iw1c zNER?L=5Br}J(*EF4JcKV0VIlW zJBY~H%qbVf!5A|+K~09SU~;WmJY&q}yJ~;g7_&Fu)~)1VOqd*MmM9PlQkw`Ol0ii7 J=3{1&>;N0JM>hZf diff --git a/main.py b/main.py index c805534..98dd9c8 100644 --- a/main.py +++ b/main.py @@ -86,15 +86,16 @@ async def sync_state_from_file(path: Path, kind: str): @app.post("/api/populate_friendly") async def api_populate_friendly(data: FactionRequest): await populate_friendly(data.faction_id) - # sync STATE from file - await sync_state_from_file(Path("data/friendly_members.json"), "friendly") - return {"status": "friendly populated", "id": data.faction_id} + # Return members list for frontend (already in STATE from populate_friendly) + members = [m.model_dump() for m in STATE.friendly.values()] + return {"status": "friendly populated", "id": data.faction_id, "members": members} @app.post("/api/populate_enemy") async def api_populate_enemy(data: FactionRequest): await populate_enemy(data.faction_id) - await sync_state_from_file(Path("data/enemy_memberes.json"), "enemy") - return {"status": "enemy populated", "id": data.faction_id} + # Return members list for frontend (already in STATE from populate_enemy) + members = [m.model_dump() for m in STATE.enemy.values()] + return {"status": "enemy populated", "id": data.faction_id, "members": members} # ----------------------------- # Start status refresh loops @@ -214,25 +215,9 @@ async def api_bot_control(req: BotControl): @app.post("/api/reset_groups") async def reset_groups(): - # Load existing data - data = load_json("assigned_groups.json") - - # Clear group assignments - for g in data["groups"].values(): - g.clear() - - # Reset friendly assigned_group - for f in data["friendly_members"]: - f["assigned_group"] = None - - # Reset enemy assigned_group - for e in data["enemy_members"]: - e["assigned_group"] = None - - # Save back to file - save_json("assigned_groups.json", data) - - return { "success": True } + # Clear all assignments in server state + await STATE.clear_all_assignments() + return {"success": True} # ============================================================ @@ -245,7 +230,7 @@ enrolled_attackers = [] enemy_queue = [] active_assignments = {} round_robin_index = 0 - + class HitDispatchBot(commands.Bot): async def setup_hook(self): await self.add_cog( @@ -284,11 +269,15 @@ async def start_bot(): # ============================================================ # Main Entry Point # ============================================================ -if __name__ == "__main__": - loop = asyncio.get_event_loop() - +async def main(): # Start Discord bot in background - loop.create_task(start_bot()) + bot_task = asyncio.create_task(start_bot()) - # Run FastAPI app — keeps loop alive - uvicorn.run(app, host="127.0.0.1", port=8000) + # Configure and run FastAPI server + config = uvicorn.Config(app, host="127.0.0.1", port=8000, log_level="info") + server = uvicorn.Server(config) + + await server.serve() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/services/__pycache__/server_state.cpython-311.pyc b/services/__pycache__/server_state.cpython-311.pyc index bd93bb2ce85d65ed4776edfb6912fb4fd6d3f907..cd6fb3447b2ca10d81f4ccd1c3f3d2ffe925a256 100644 GIT binary patch literal 9077 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 DMyRxo literal 9295 zcmb_iZ%i9WmalfV|AMgr+XMMW2S~s}#se{sWR$R%z)Y6Rgjo%lnGKVi&v7@2gR$A} zh8Y-h@?4@jR>ueD#47A4(y-Dh$J~r=xo=3jI?Zl!(&#?fs3okHB1Jl_wDZkTa#EB} z_g=N#xCsQWZkqP%>Z(_-s$bRn)vH(htK01&a6KLh#sAY!$p7F=`LdQ0Pt%aNO9Ub? z2{OryF_V@t%OpF-GMHycaFf^R@6xdM`bp3#@L0032;xR?z-iynM=6HWfiKmj$M3B*( z=c2NBF(rtJkyxoJeUzJidJ|sn5|NBC0vWS>NJLiT1m-m4idINjXbSUKBabsuR!j-- zX`)@UHdDb!fZI+>9AgeD&#Ciahc(+paq^ldX_mO4amnbUs96)@jF`|IqO8Oxql&0; z%IuUlqFJv?sp%;Rb)j)L;))z}YK}+*>qH`&D-xMZ3DXHo*G3}0n2sh&C2TyYXr4$U znoOpYD5^pR9#&Q)sSadGIDC=^$k*g4Q1asA!MCn_D2tMO<)$dbV-r_~QnBetF{#K` zhOld?DSWvy9*v=}$fr>$BE;pXs1myoxt3C{K-HOeOq6Ltlp->;Du$+JB?q>I#0Ee4 zZ-Hbg{tAwo%=yguFU}WS?#ysz_>18}O zs593um3g47PVmA+Y0zp$L9L=Ripu-}$4slC6Cx5zO(&I@VIok1r8uLXJ_d4^#7UY& zAf;dQ%`lua17${CT2{6NqBzTSU{*6}MquV3!)%a46=oT;I|n45(ub<8~Fi0=WC1R7?g3o6xuf&>*LAvX~gxMKE!Voe*bb4(jr7NlhpOEHR#pE0M^YuVSD=~=mDB4JC2bK4~HsU|9^xmD3r4f+p zT!24n4w`e40!{?+W+I*xG^aj|;FDtbN#zR%$~}a}eci#)ejhRla{sQTfw358ef@Z>eT^SoI!VQB?1-O>cO^8(tgEdk0kSK+Zb= z6^{g0FKo2;=i2+Ty-R1ZXV!=QaA9Gn;AvVO%6WLW3!Ob#E_->&qk50zygkJZlTx0b zj-FMis48^1E6ele&vz}O1#$xnVs_9w*vmfXbqx;KA2JM3)6#9A?!UuWSAn|ZCa!(- zTA@f$ek(s;!pHY1K3e8kKn+&dj*q6Coe_5joI*(zm`IV+Bbb&8@{g9;k>V3_4Vg`{W`=f58b<%^9128^z>!9CHHb% z^>(V>5QU3-7jmAXa2G3IId(cICXsi?5kJ>1Z)Z>KC2VufgGZCLL=0S5bb%BD1^z!$Rt$d&G;Unx9-w#hBYIif+)oA* zVpQUziA2fWNkBkDrpTdt3?Us^#^Yi_(DUW!jEL?P?8qlfxZ>Et70nt$xfE118@NZ) zqPztznipkM;DsDyb+|#K(U?gYhXvdiedFV&vLevO$|`e*D=}tAmUlsloZb#FZfARu z>`5L6_AO1IAJRif)}s3n=axJxF0klLZ_kFeC+`ib-f+$v-YT>M8f&;g@oE-|FQ%rq@t5%B&A|rtL4#}X zfc-%W0~9@8Wm-y_a0V^l7W|&pRfRKgVn&u~>>ltaT=q|t3X3y)D(3y!Hogj^G4lx= zgdhMNaA8~QO*1jTioLA0>K&1FuKNS3G1v&jQd6_MoQzJ%H&RM@he8ci2D79=v#2y% zX`FPc0*ktN<%i${n0slhhX@2nly+V0uZv2h{YA>{n-}}txUQfr2DjUy!`W0Mdy>bE zesITkTJalj#c$A8{6F*UD<7|psYg!c`BN%?>e0c&D;=vHYU}a*!LWKTT(q(WfFB6> z@*muBWF18Z>~V_qFSq{$`}Az@j#bh3U0JB%yxjB_ziv2Z>(BPGf9!Rg9kBn2VSs8i zrSOUnRiZIt{Q~u3Xp36UN8qPdu}W7LPq7GGMTEfE0niA6(fT}F8Onn|UPW-)Y$Nkr znoG0bKkrMkyF@9r#b~xx*CbfeytOif2eO!*r3esFHOC^%J=~?1LvWgFnR~C)gN{qr zt}VNSnpeq4ThdmcHqC$z+N$bP?W_9)T5*2sz7JUTje%znv9Up{h8`U3jX{4!T&8aB zpN$C0a8ec}fFFsUo`T6JmNq4!(lde+lovmn5@U)epvQ0nJ%_k(437IL%?dJ+MfAcI$D!BWYMgl>Y+m`Xt#sRwg;B!`hS0?}9iI-L_kU^L8Ij?%n?88k|B zf^tqmAhb^W43HMWzYz-4noT$29pW|9Q>fJ)<29zDILMYC8h~oVYrIbID)uC=h}X1t z?i3Bd$neBUyBg@oH*~5Eoe&`IJGnlz_Q~2O%a^|T`IkRmiT(QOy{qcM?%cud@05j+ zNB;eV#z4W(-;MlBWaXpP%X$9^)qmnqpn19Lt2e)VbG2iwC%>;x-Pe~7^s9mXN6iP8 zKlv*1Wn^`1y)l3AjC$})zIi}x9sr-H5NI!)9Qdv)ck&|KzZzZM|5eMEEvtdG`h0U( zZ4T%Cy{f-A2lwcuqQ?=a0{{=y0e}bU7Tzf~kmi5*WBp*?P!s#G$u-n!e|Ures?|zjOpMQn0tC}#6WHUHgMvK|Q{bPN>fpWt z$f1^gQ6M*9eV$!l#u+%EasWIV@LuM{r+Gs%wCx??9b7CPg5zZ8%~0BEhSXcIrKt$= zvuW!YvUvDM51f2Z3Y=hsw`_|gpptnww#2Zn4514?o`k6ImeTpoX;~bfPVmujMTCtK zKb4xAP5|1KV=5*G7Dol);VDK-e#P8{bE!1B>IXNKQ4Ea}^2==}SToG;8q>@j%VJ=Z zNCBt{2g$4~Dsawh33W@jkCEO7QrZ_~=)UNM>oj_AvDD<$z+8XjW~dx+i9_fY5T^t+ zq0?~E6ivtjkSN!?jY7MCJkO9^(4tL?i){@kc~ZZvlS@8w4Q36vi41g*L& z$wOU=ig45eCsxv1@E)}3n>p-p1r9ouR7CF?{U8~2#s3}b@=Dvhz|P&CYu)?PG9XI+ zDJb~49=tAvl5EaHMY1O;>FgQyk7r!x?Djvo7@#9TM%oX$4tgX!Zj{g)k}&2>*F*^?5?u+UlbFJ} zApH#z1QH1YtkT;+G!FMD(g?nPj09)Aev*y{ozlKC!8KTBU|2K8U(w>RwiOAGl|lO0 zPTHHS$5%&-1Rm=weSEJ!Xu5**x~*a4fX8-n$YX^Nfk9eb)}}RXZFIeTUH)$9`^JaE z>wmAlHInNaQM=zS5-8nH_W7+Xt4fi;V;!W!V><~stcTWp>#;vHgTrq<^nDwB>`HX) zP{mOFGxVSs|G5MHa|3L=LcLNuwwV-i}KbOZ_YQ&5Cv9ldn+(%akA{=#uu z|M!jo3HNG!crO diff --git a/services/__pycache__/torn_api.cpython-311.pyc b/services/__pycache__/torn_api.cpython-311.pyc index f1e63948bf14a2f58aba9c075a1e31190a88cabd..c3dafe7a6ab60b3d2f159cd8358b1d9de368f9f6 100644 GIT binary patch literal 8329 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 literal 9191 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 O+rVc7ihP~o(EkCt?l36; diff --git a/services/server_state.py b/services/server_state.py index e5eace0..5ae1597 100644 --- a/services/server_state.py +++ b/services/server_state.py @@ -53,19 +53,23 @@ class ServerState: if member_id in coll: coll[member_id].group = group_key + def _remove_member_assignment_unlocked(self, member_id: int): + """Internal helper - assumes lock is already held""" + for gk, buckets in self.groups.items(): + if member_id in buckets["friendly"]: + buckets["friendly"].remove(member_id) + if member_id in buckets["enemy"]: + buckets["enemy"].remove(member_id) + + # clear group attribute + if member_id in self.friendly: + self.friendly[member_id].group = None + if member_id in self.enemy: + self.enemy[member_id].group = None + async def remove_member_assignment(self, member_id: int): async with self.lock: - for gk, buckets in self.groups.items(): - if member_id in buckets["friendly"]: - buckets["friendly"].remove(member_id) - if member_id in buckets["enemy"]: - buckets["enemy"].remove(member_id) - - # clear group attribute - if member_id in self.friendly: - self.friendly[member_id].group = None - if member_id in self.enemy: - self.enemy[member_id].group = None + self._remove_member_assignment_unlocked(member_id) async def clear_all_assignments(self): async with self.lock: @@ -113,7 +117,7 @@ class ServerState: coll = self.friendly if kind == "friendly" else self.enemy to_remove = [mid for mid in coll.keys() if mid not in set(received_ids)] for mid in to_remove: - await self.remove_member_assignment(mid) + self._remove_member_assignment_unlocked(mid) del coll[mid] # Single global state diff --git a/static/dashboard.js b/static/dashboard.js index e69d9f1..03ef7c3 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -146,9 +146,10 @@ async function loadMembers(kind) { domElement: null }; map.set(m.id, newMember); - // create card but DO NOT automatically append here; - // placement will be handled by loadAssignmentsFromServer() - createMemberCard(newMember, kind); + // create card and append to main list if not in a group + const card = createMemberCard(newMember, kind); + // Don't append yet - let applyAssignmentsToDOM handle placement + // This ensures members end up in the right place (main list or group) } } @@ -228,16 +229,20 @@ function ensureMainListContains(member, kind) { const container = kind === "friendly" ? friendlyContainer : enemyContainer; const parent = member.domElement?.parentElement; + console.log(`>>> ensureMainListContains for ${member.id}, has parent: ${!!parent}, domElement exists: ${!!member.domElement}`); + // Detect group zone: ids like "group-1-friendly" or "group-2-enemy" const isInGroupZone = parent && typeof parent.id === "string" && /^group-\d+-/.test(parent.id); // If member has no parent yet, or is in a group zone, ensure it ends up in the main list if (!parent || isInGroupZone) { + console.log(`>>> Member ${member.id} needs placement (no parent or in group zone)`); // If it's already in the right container, nothing to do if (member.domElement && member.domElement.parentElement !== container) { // remove from previous parent (if any) and append to main list const prev = member.domElement.parentElement; if (prev) prev.removeChild(member.domElement); + console.log(`>>> Appending member ${member.id} to main ${kind} container`); container.appendChild(member.domElement); } } @@ -245,6 +250,7 @@ function ensureMainListContains(member, kind) { function applyAssignmentsToDOM(assignments) { + console.log(">>> applyAssignmentsToDOM called with assignments:", assignments); if (!assignments) return; // First, move all assigned members into their group zones @@ -428,28 +434,46 @@ function setupDropZones() { // Populate & Status functions // --------------------------- async function populateFriendly() { + console.log(">>> populateFriendly called"); const id = toInt(document.getElementById("friendly-id").value); + console.log(">>> Friendly faction ID:", id); if (!id) return alert("Enter Friendly Faction ID"); + // Clear existing friendly members before populating new faction + console.log(">>> Clearing existing friendly members"); + friendlyMembers.forEach(m => { + if (m.domElement?.parentElement) { + m.domElement.parentElement.removeChild(m.domElement); + } + }); + friendlyMembers.clear(); + try { + console.log(">>> Sending populate request to /api/populate_friendly"); const res = await fetch("/api/populate_friendly", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ faction_id: id, interval: 0 }) }); + console.log(">>> Response status:", res.status); const data = await res.json(); + console.log(">>> Response data:", data); // Update in-memory map & DOM if (data.members) { + console.log(`>>> Processing ${data.members.length} friendly members`); for (const m of data.members) { + console.log(`>>> Processing member ${m.id}: ${m.name}`); let existing = friendlyMembers.get(m.id); if (existing) { + console.log(`>>> Updating existing member ${m.id}`); existing.name = m.name; existing.level = m.level; existing.estimate = m.estimate; existing.status = m.status || "Unknown"; updateMemberCard(existing); } else { + console.log(`>>> Creating new member ${m.id}`); const newMember = { id: m.id, name: m.name, @@ -460,9 +484,13 @@ async function populateFriendly() { }; friendlyMembers.set(m.id, newMember); const card = createMemberCard(newMember, "friendly"); + console.log(`>>> Appending card for ${m.name} to container`); friendlyContainer.appendChild(card); } } + console.log(`>>> Friendly members count in map: ${friendlyMembers.size}`); + } else { + console.log(">>> No members in response!"); } // Refresh assignments & status UI @@ -474,27 +502,45 @@ async function populateFriendly() { } async function populateEnemy() { + console.log(">>> populateEnemy called"); const id = toInt(document.getElementById("enemy-id").value); + console.log(">>> Enemy faction ID:", id); if (!id) return alert("Enter Enemy Faction ID"); + // Clear existing enemy members before populating new faction + console.log(">>> Clearing existing enemy members"); + enemyMembers.forEach(m => { + if (m.domElement?.parentElement) { + m.domElement.parentElement.removeChild(m.domElement); + } + }); + enemyMembers.clear(); + try { + console.log(">>> Sending populate request to /api/populate_enemy"); const res = await fetch("/api/populate_enemy", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ faction_id: id, interval: 0 }) }); + console.log(">>> Response status:", res.status); const data = await res.json(); + console.log(">>> Response data:", data); if (data.members) { + console.log(`>>> Processing ${data.members.length} enemy members`); for (const m of data.members) { + console.log(`>>> Processing member ${m.id}: ${m.name}`); let existing = enemyMembers.get(m.id); if (existing) { + console.log(`>>> Updating existing member ${m.id}`); existing.name = m.name; existing.level = m.level; existing.estimate = m.estimate; existing.status = m.status || "Unknown"; updateMemberCard(existing); } else { + console.log(`>>> Creating new member ${m.id}`); const newMember = { id: m.id, name: m.name, @@ -505,9 +551,13 @@ async function populateEnemy() { }; enemyMembers.set(m.id, newMember); const card = createMemberCard(newMember, "enemy"); + console.log(`>>> Appending card for ${m.name} to container`); enemyContainer.appendChild(card); } } + console.log(`>>> Enemy members count in map: ${enemyMembers.size}`); + } else { + console.log(">>> No members in response!"); } // Refresh assignments & status UI @@ -602,32 +652,33 @@ async function resetGroups() { // Wire up buttons & init // --------------------------- function wireUp() { - document.getElementById("friendly-populate-btn").addEventListener("click", populateFriendly); - document.getElementById("enemy-populate-btn").addEventListener("click", populateEnemy); + console.log(">>> wireUp called"); + const friendlyBtn = document.getElementById("friendly-populate-btn"); + const enemyBtn = document.getElementById("enemy-populate-btn"); + console.log(">>> Friendly populate button:", friendlyBtn); + console.log(">>> Enemy populate button:", enemyBtn); + + if (friendlyBtn) friendlyBtn.addEventListener("click", populateFriendly); + if (enemyBtn) enemyBtn.addEventListener("click", populateEnemy); document.getElementById("friendly-status-btn").addEventListener("click", toggleFriendlyStatus); document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus); const resetBtn = document.getElementById("reset-groups-btn"); if (resetBtn) resetBtn.addEventListener("click", resetGroups); setupDropZones(); + console.log(">>> wireUp completed"); } // --------------------------- // Initial load // --------------------------- document.addEventListener("DOMContentLoaded", async () => { + console.log(">>> DOMContentLoaded fired"); wireUp(); - // load members first - await loadMembers("friendly"); - await loadMembers("enemy"); + // DON'T load members on initial page load - wait for user to click Populate + // This prevents showing stale data from server STATE - // load assignments and apply them - await pollAssignments(); + // Start polling for assignments (but there won't be any until members are populated) startAssignmentsPolling(); - - // kick off status polling for initial UI (but status loops are triggered by Start Refresh buttons) - // immediate status pull so cards show current status - await refreshStatus("friendly"); - await refreshStatus("enemy"); });