diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc index 2af41fe..70dbcbd 100644 Binary files a/__pycache__/main.cpython-311.pyc and b/__pycache__/main.cpython-311.pyc differ 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 bd93bb2..cd6fb34 100644 Binary files a/services/__pycache__/server_state.cpython-311.pyc and b/services/__pycache__/server_state.cpython-311.pyc differ diff --git a/services/__pycache__/torn_api.cpython-311.pyc b/services/__pycache__/torn_api.cpython-311.pyc index f1e6394..c3dafe7 100644 Binary files a/services/__pycache__/torn_api.cpython-311.pyc and b/services/__pycache__/torn_api.cpython-311.pyc differ 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"); });