Fixed Faction population

This commit is contained in:
2026-01-25 18:11:59 -05:00
parent 8779ad3d3b
commit 1f0b974f6c
6 changed files with 102 additions and 58 deletions

Binary file not shown.

49
main.py
View File

@@ -86,15 +86,16 @@ async def sync_state_from_file(path: Path, kind: str):
@app.post("/api/populate_friendly") @app.post("/api/populate_friendly")
async def api_populate_friendly(data: FactionRequest): async def api_populate_friendly(data: FactionRequest):
await populate_friendly(data.faction_id) await populate_friendly(data.faction_id)
# sync STATE from file # Return members list for frontend (already in STATE from populate_friendly)
await sync_state_from_file(Path("data/friendly_members.json"), "friendly") members = [m.model_dump() for m in STATE.friendly.values()]
return {"status": "friendly populated", "id": data.faction_id} return {"status": "friendly populated", "id": data.faction_id, "members": members}
@app.post("/api/populate_enemy") @app.post("/api/populate_enemy")
async def api_populate_enemy(data: FactionRequest): async def api_populate_enemy(data: FactionRequest):
await populate_enemy(data.faction_id) await populate_enemy(data.faction_id)
await sync_state_from_file(Path("data/enemy_memberes.json"), "enemy") # Return members list for frontend (already in STATE from populate_enemy)
return {"status": "enemy populated", "id": data.faction_id} members = [m.model_dump() for m in STATE.enemy.values()]
return {"status": "enemy populated", "id": data.faction_id, "members": members}
# ----------------------------- # -----------------------------
# Start status refresh loops # Start status refresh loops
@@ -214,25 +215,9 @@ async def api_bot_control(req: BotControl):
@app.post("/api/reset_groups") @app.post("/api/reset_groups")
async def reset_groups(): async def reset_groups():
# Load existing data # Clear all assignments in server state
data = load_json("assigned_groups.json") await STATE.clear_all_assignments()
return {"success": True}
# 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 }
# ============================================================ # ============================================================
@@ -284,11 +269,15 @@ async def start_bot():
# ============================================================ # ============================================================
# Main Entry Point # Main Entry Point
# ============================================================ # ============================================================
if __name__ == "__main__": async def main():
loop = asyncio.get_event_loop()
# Start Discord bot in background # Start Discord bot in background
loop.create_task(start_bot()) bot_task = asyncio.create_task(start_bot())
# Run FastAPI app — keeps loop alive # Configure and run FastAPI server
uvicorn.run(app, host="127.0.0.1", port=8000) 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())

View File

@@ -53,8 +53,8 @@ class ServerState:
if member_id in coll: if member_id in coll:
coll[member_id].group = group_key coll[member_id].group = group_key
async def remove_member_assignment(self, member_id: int): def _remove_member_assignment_unlocked(self, member_id: int):
async with self.lock: """Internal helper - assumes lock is already held"""
for gk, buckets in self.groups.items(): for gk, buckets in self.groups.items():
if member_id in buckets["friendly"]: if member_id in buckets["friendly"]:
buckets["friendly"].remove(member_id) buckets["friendly"].remove(member_id)
@@ -67,6 +67,10 @@ class ServerState:
if member_id in self.enemy: if member_id in self.enemy:
self.enemy[member_id].group = None self.enemy[member_id].group = None
async def remove_member_assignment(self, member_id: int):
async with self.lock:
self._remove_member_assignment_unlocked(member_id)
async def clear_all_assignments(self): async def clear_all_assignments(self):
async with self.lock: async with self.lock:
for gk in self.groups: for gk in self.groups:
@@ -113,7 +117,7 @@ class ServerState:
coll = self.friendly if kind == "friendly" else self.enemy coll = self.friendly if kind == "friendly" else self.enemy
to_remove = [mid for mid in coll.keys() if mid not in set(received_ids)] to_remove = [mid for mid in coll.keys() if mid not in set(received_ids)]
for mid in to_remove: for mid in to_remove:
await self.remove_member_assignment(mid) self._remove_member_assignment_unlocked(mid)
del coll[mid] del coll[mid]
# Single global state # Single global state

View File

@@ -146,9 +146,10 @@ async function loadMembers(kind) {
domElement: null domElement: null
}; };
map.set(m.id, newMember); map.set(m.id, newMember);
// create card but DO NOT automatically append here; // create card and append to main list if not in a group
// placement will be handled by loadAssignmentsFromServer() const card = createMemberCard(newMember, kind);
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 container = kind === "friendly" ? friendlyContainer : enemyContainer;
const parent = member.domElement?.parentElement; 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" // Detect group zone: ids like "group-1-friendly" or "group-2-enemy"
const isInGroupZone = parent && typeof parent.id === "string" && /^group-\d+-/.test(parent.id); 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 member has no parent yet, or is in a group zone, ensure it ends up in the main list
if (!parent || isInGroupZone) { 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 it's already in the right container, nothing to do
if (member.domElement && member.domElement.parentElement !== container) { if (member.domElement && member.domElement.parentElement !== container) {
// remove from previous parent (if any) and append to main list // remove from previous parent (if any) and append to main list
const prev = member.domElement.parentElement; const prev = member.domElement.parentElement;
if (prev) prev.removeChild(member.domElement); if (prev) prev.removeChild(member.domElement);
console.log(`>>> Appending member ${member.id} to main ${kind} container`);
container.appendChild(member.domElement); container.appendChild(member.domElement);
} }
} }
@@ -245,6 +250,7 @@ function ensureMainListContains(member, kind) {
function applyAssignmentsToDOM(assignments) { function applyAssignmentsToDOM(assignments) {
console.log(">>> applyAssignmentsToDOM called with assignments:", assignments);
if (!assignments) return; if (!assignments) return;
// First, move all assigned members into their group zones // First, move all assigned members into their group zones
@@ -428,28 +434,46 @@ function setupDropZones() {
// Populate & Status functions // Populate & Status functions
// --------------------------- // ---------------------------
async function populateFriendly() { async function populateFriendly() {
console.log(">>> populateFriendly called");
const id = toInt(document.getElementById("friendly-id").value); const id = toInt(document.getElementById("friendly-id").value);
console.log(">>> Friendly faction ID:", id);
if (!id) return alert("Enter Friendly Faction 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 { try {
console.log(">>> Sending populate request to /api/populate_friendly");
const res = await fetch("/api/populate_friendly", { const res = await fetch("/api/populate_friendly", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ faction_id: id, interval: 0 }) body: JSON.stringify({ faction_id: id, interval: 0 })
}); });
console.log(">>> Response status:", res.status);
const data = await res.json(); const data = await res.json();
console.log(">>> Response data:", data);
// Update in-memory map & DOM // Update in-memory map & DOM
if (data.members) { if (data.members) {
console.log(`>>> Processing ${data.members.length} friendly members`);
for (const m of data.members) { for (const m of data.members) {
console.log(`>>> Processing member ${m.id}: ${m.name}`);
let existing = friendlyMembers.get(m.id); let existing = friendlyMembers.get(m.id);
if (existing) { if (existing) {
console.log(`>>> Updating existing member ${m.id}`);
existing.name = m.name; existing.name = m.name;
existing.level = m.level; existing.level = m.level;
existing.estimate = m.estimate; existing.estimate = m.estimate;
existing.status = m.status || "Unknown"; existing.status = m.status || "Unknown";
updateMemberCard(existing); updateMemberCard(existing);
} else { } else {
console.log(`>>> Creating new member ${m.id}`);
const newMember = { const newMember = {
id: m.id, id: m.id,
name: m.name, name: m.name,
@@ -460,9 +484,13 @@ async function populateFriendly() {
}; };
friendlyMembers.set(m.id, newMember); friendlyMembers.set(m.id, newMember);
const card = createMemberCard(newMember, "friendly"); const card = createMemberCard(newMember, "friendly");
console.log(`>>> Appending card for ${m.name} to container`);
friendlyContainer.appendChild(card); friendlyContainer.appendChild(card);
} }
} }
console.log(`>>> Friendly members count in map: ${friendlyMembers.size}`);
} else {
console.log(">>> No members in response!");
} }
// Refresh assignments & status UI // Refresh assignments & status UI
@@ -474,27 +502,45 @@ async function populateFriendly() {
} }
async function populateEnemy() { async function populateEnemy() {
console.log(">>> populateEnemy called");
const id = toInt(document.getElementById("enemy-id").value); const id = toInt(document.getElementById("enemy-id").value);
console.log(">>> Enemy faction ID:", id);
if (!id) return alert("Enter Enemy Faction 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 { try {
console.log(">>> Sending populate request to /api/populate_enemy");
const res = await fetch("/api/populate_enemy", { const res = await fetch("/api/populate_enemy", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ faction_id: id, interval: 0 }) body: JSON.stringify({ faction_id: id, interval: 0 })
}); });
console.log(">>> Response status:", res.status);
const data = await res.json(); const data = await res.json();
console.log(">>> Response data:", data);
if (data.members) { if (data.members) {
console.log(`>>> Processing ${data.members.length} enemy members`);
for (const m of data.members) { for (const m of data.members) {
console.log(`>>> Processing member ${m.id}: ${m.name}`);
let existing = enemyMembers.get(m.id); let existing = enemyMembers.get(m.id);
if (existing) { if (existing) {
console.log(`>>> Updating existing member ${m.id}`);
existing.name = m.name; existing.name = m.name;
existing.level = m.level; existing.level = m.level;
existing.estimate = m.estimate; existing.estimate = m.estimate;
existing.status = m.status || "Unknown"; existing.status = m.status || "Unknown";
updateMemberCard(existing); updateMemberCard(existing);
} else { } else {
console.log(`>>> Creating new member ${m.id}`);
const newMember = { const newMember = {
id: m.id, id: m.id,
name: m.name, name: m.name,
@@ -505,9 +551,13 @@ async function populateEnemy() {
}; };
enemyMembers.set(m.id, newMember); enemyMembers.set(m.id, newMember);
const card = createMemberCard(newMember, "enemy"); const card = createMemberCard(newMember, "enemy");
console.log(`>>> Appending card for ${m.name} to container`);
enemyContainer.appendChild(card); enemyContainer.appendChild(card);
} }
} }
console.log(`>>> Enemy members count in map: ${enemyMembers.size}`);
} else {
console.log(">>> No members in response!");
} }
// Refresh assignments & status UI // Refresh assignments & status UI
@@ -602,32 +652,33 @@ async function resetGroups() {
// Wire up buttons & init // Wire up buttons & init
// --------------------------- // ---------------------------
function wireUp() { function wireUp() {
document.getElementById("friendly-populate-btn").addEventListener("click", populateFriendly); console.log(">>> wireUp called");
document.getElementById("enemy-populate-btn").addEventListener("click", populateEnemy); 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("friendly-status-btn").addEventListener("click", toggleFriendlyStatus);
document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus); document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus);
const resetBtn = document.getElementById("reset-groups-btn"); const resetBtn = document.getElementById("reset-groups-btn");
if (resetBtn) resetBtn.addEventListener("click", resetGroups); if (resetBtn) resetBtn.addEventListener("click", resetGroups);
setupDropZones(); setupDropZones();
console.log(">>> wireUp completed");
} }
// --------------------------- // ---------------------------
// Initial load // Initial load
// --------------------------- // ---------------------------
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
console.log(">>> DOMContentLoaded fired");
wireUp(); wireUp();
// load members first // DON'T load members on initial page load - wait for user to click Populate
await loadMembers("friendly"); // This prevents showing stale data from server STATE
await loadMembers("enemy");
// load assignments and apply them // Start polling for assignments (but there won't be any until members are populated)
await pollAssignments();
startAssignmentsPolling(); 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");
}); });