Fixed Faction population
This commit is contained in:
Binary file not shown.
51
main.py
51
main.py
@@ -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 }
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -245,7 +230,7 @@ enrolled_attackers = []
|
|||||||
enemy_queue = []
|
enemy_queue = []
|
||||||
active_assignments = {}
|
active_assignments = {}
|
||||||
round_robin_index = 0
|
round_robin_index = 0
|
||||||
|
|
||||||
class HitDispatchBot(commands.Bot):
|
class HitDispatchBot(commands.Bot):
|
||||||
async def setup_hook(self):
|
async def setup_hook(self):
|
||||||
await self.add_cog(
|
await self.add_cog(
|
||||||
@@ -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())
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -53,19 +53,23 @@ class ServerState:
|
|||||||
if member_id in coll:
|
if member_id in coll:
|
||||||
coll[member_id].group = group_key
|
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 def remove_member_assignment(self, member_id: int):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
for gk, buckets in self.groups.items():
|
self._remove_member_assignment_unlocked(member_id)
|
||||||
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 clear_all_assignments(self):
|
async def clear_all_assignments(self):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user