-- MineCommander Faction Manager Server -- File: faction_manager.lua -- Purpose: Handles faction creation, player management, and joins -- Computer: Command Computer #1 -- Configuration local MODEM_SIDE = "back" local DATA_FILE = "faction_data.txt" local HEARTBEAT_SERVER_ID = 1 -- ID of the heartbeat server -- Initialize modem rednet.open(MODEM_SIDE) -- Global data structure local gameData = { factions = {}, players = {}, pendingJoins = {}, nextFactionId = 1, nextCoreId = 1 } -- Utility Functions function log(message) print("[FACTION-MGR " .. os.date("%H:%M:%S") .. "] " .. message) end function saveData() local file = fs.open(DATA_FILE, "w") if file then file.write(textutils.serialize(gameData)) file.close() log("Faction data saved") return true else log("ERROR: Failed to save faction data") return false end end function loadData() if fs.exists(DATA_FILE) then local file = fs.open(DATA_FILE, "r") if file then local data = file.readAll() file.close() local loaded = textutils.unserialize(data) if loaded then gameData = loaded log("Faction data loaded - " .. #gameData.factions .. " factions") return true end end end log("No existing faction data found, starting fresh") return false end function broadcastAnnouncement(message) log("ANNOUNCEMENT: " .. message) -- Broadcast to all players rednet.broadcast({ type = "announcement", message = message, timestamp = os.epoch("utc") }, "announcement") -- Send to Minecraft chat commands.say(message) end function getFactionById(id) for _, faction in ipairs(gameData.factions) do if faction.id == id then return faction end end return nil end function getPlayerFaction(playerName) return gameData.players[playerName] end function createPendingFaction(ownerName, factionName) -- Check if player is already in a faction if getPlayerFaction(ownerName) then return nil, "Player already in a faction" end -- Check if faction name already exists for _, faction in ipairs(gameData.factions) do if faction.name == factionName and not faction.defeated then return nil, "Faction name already exists" end end -- Create pending faction (not active until core is placed) local faction = { id = gameData.nextFactionId, name = factionName, owner = ownerName, members = {ownerName}, created = os.epoch("utc"), defeated = false, coreId = gameData.nextCoreId, pending = true, -- Faction is pending until core is activated active = false -- Not active until heartbeat server confirms } table.insert(gameData.factions, faction) gameData.players[ownerName] = gameData.nextFactionId local coreId = gameData.nextCoreId gameData.nextFactionId = gameData.nextFactionId + 1 gameData.nextCoreId = gameData.nextCoreId + 1 -- Notify heartbeat server about new faction rednet.send(HEARTBEAT_SERVER_ID, { type = "new_faction", faction = faction, coreId = coreId }, "faction_sync") log("Created pending faction '" .. factionName .. "' for " .. ownerName .. " (Core ID: " .. coreId .. ")") saveData() return faction, coreId end function activateFaction(factionId) local faction = getFactionById(factionId) if not faction then return false end if not faction.pending then return false end -- Already activated -- Activate the faction faction.pending = false faction.active = true log("Activated faction '" .. faction.name .. "' - ID: " .. factionId) broadcastAnnouncement("Faction '" .. faction.name .. "' has been established and is now active!") saveData() return true end function joinFaction(playerName, factionId) -- Check if player is already in a faction if getPlayerFaction(playerName) then return false, "Player already in a faction" end local faction = getFactionById(factionId) if not faction then return false, "Faction not found" end if faction.defeated then return false, "Faction has been defeated" end if faction.pending then return false, "Faction is not yet active" end -- Add player to faction table.insert(faction.members, playerName) gameData.players[playerName] = factionId log(playerName .. " joined faction '" .. faction.name .. "'") broadcastAnnouncement(playerName .. " has joined faction '" .. faction.name .. "'!") -- Notify heartbeat server of member update rednet.send(HEARTBEAT_SERVER_ID, { type = "member_update", factionId = factionId, members = faction.members }, "faction_sync") saveData() return true, "Successfully joined faction" end function defeatFaction(factionId) local faction = getFactionById(factionId) if not faction or faction.defeated then return false end faction.defeated = true faction.active = false -- Clear players from faction (heartbeat server handles spectator mode) for _, memberName in ipairs(faction.members) do gameData.players[memberName] = nil log("Removed " .. memberName .. " from faction data") end log("Faction '" .. faction.name .. "' marked as defeated") saveData() return true end function handleDiscoveryMessage(senderId, message) if message.type == "server_discovery" then -- Respond to discovery request rednet.send(senderId, { type = "server_response", serverId = os.getComputerID(), serverName = "MineCommander Faction Manager", serverType = "faction_manager", timestamp = os.epoch("utc") }, "discovery") log("Responded to discovery request from computer " .. senderId) end end function handlePlayerMessage(senderId, message) if message.type == "create_faction" then local playerName = message.playerName local factionName = message.factionName local faction, coreId = createPendingFaction(playerName, factionName) if not faction then rednet.send(senderId, { type = "error", message = coreId -- Error message is in coreId when faction is nil }, "player") return end -- Give command computer and ender modem commands.clear(playerName, "computercraft:pocket_computer_advanced") commands.give(playerName, "computercraft:computer_command", 1) commands.give(playerName, "computercraft:ender_modem", 1) rednet.send(senderId, { type = "faction_created", faction = faction, coreId = coreId, message = "Faction pending activation. Place your command computer with ender modem and run the core software." }, "player") log("Faction creation request processed for " .. playerName) elseif message.type == "join_faction" then local playerName = message.playerName local factionId = message.factionId local faction = getFactionById(factionId) if not faction then rednet.send(senderId, { type = "error", message = "Faction not found" }, "player") return end if faction.defeated then rednet.send(senderId, { type = "error", message = "Faction has been defeated" }, "player") return end if faction.pending then rednet.send(senderId, { type = "error", message = "Faction is not yet active" }, "player") return end -- Store pending join request gameData.pendingJoins[playerName] = { factionId = factionId, computerId = senderId, timestamp = os.epoch("utc") } -- Ask faction owner for approval commands.tellraw(faction.owner, { text = playerName .. " wants to join your faction '" .. faction.name .. "'. Type /say accept_" .. playerName .. " or /say deny_" .. playerName, color = "yellow" }) rednet.send(senderId, { type = "join_pending", message = "Join request sent to faction owner" }, "player") log("Join request from " .. playerName .. " for faction " .. faction.name) elseif message.type == "list_factions" then local activeFactions = {} for _, faction in ipairs(gameData.factions) do if not faction.defeated and not faction.pending and faction.active then table.insert(activeFactions, { id = faction.id, name = faction.name, owner = faction.owner, memberCount = #faction.members }) end end rednet.send(senderId, { type = "faction_list", factions = activeFactions }, "player") log("Sent faction list to computer " .. senderId) end end function handleHeartbeatMessage(senderId, message) if senderId ~= HEARTBEAT_SERVER_ID then return -- Only accept messages from heartbeat server end if message.type == "faction_activated" then local factionId = message.factionId if activateFaction(factionId) then log("Faction " .. factionId .. " activated by heartbeat server") end elseif message.type == "faction_defeated" then local factionId = message.factionId if defeatFaction(factionId) then log("Faction " .. factionId .. " defeated by heartbeat server") end elseif message.type == "sync_request" then -- Send all faction data to heartbeat server rednet.send(HEARTBEAT_SERVER_ID, { type = "faction_sync", factions = gameData.factions }, "faction_sync") log("Sent faction sync to heartbeat server") end end function handleChatMessage(message) -- Handle faction join approvals from chat if message:match("^accept_(.+)") then local playerName = message:match("^accept_(.+)") local pending = gameData.pendingJoins[playerName] if pending then local success, errorMsg = joinFaction(playerName, pending.factionId) if success then rednet.send(pending.computerId, { type = "join_approved", message = "Welcome to the faction!" }, "player") -- Remove pocket computer from player commands.clear(playerName, "computercraft:pocket_computer_advanced") else rednet.send(pending.computerId, { type = "error", message = errorMsg }, "player") end gameData.pendingJoins[playerName] = nil log("Join approval processed for " .. playerName) end elseif message:match("^deny_(.+)") then local playerName = message:match("^deny_(.+)") local pending = gameData.pendingJoins[playerName] if pending then rednet.send(pending.computerId, { type = "join_denied", message = "Join request was denied" }, "player") gameData.pendingJoins[playerName] = nil log("Join denial processed for " .. playerName) end end end function chatListener() while true do local event, username, message = os.pullEvent("chat") handleChatMessage(message) end end function networkListener() while true do local senderId, message, protocol = rednet.receive() if protocol == "player" and type(message) == "table" then handlePlayerMessage(senderId, message) elseif protocol == "discovery" and type(message) == "table" then handleDiscoveryMessage(senderId, message) elseif protocol == "faction_sync" and type(message) == "table" then handleHeartbeatMessage(senderId, message) end end end function statusDisplay() while true do term.clear() term.setCursorPos(1, 1) print("=== FACTION MANAGER SERVER ===") print("Time: " .. os.date("%H:%M:%S")) print("Server ID: " .. os.getComputerID()) print("Heartbeat Server: " .. HEARTBEAT_SERVER_ID) print("") print("Total Factions: " .. #gameData.factions) local activeFactions = 0 local pendingFactions = 0 local defeatedFactions = 0 for _, faction in ipairs(gameData.factions) do if faction.defeated then defeatedFactions = defeatedFactions + 1 elseif faction.pending then pendingFactions = pendingFactions + 1 else activeFactions = activeFactions + 1 end end print(" Active: " .. activeFactions) print(" Pending: " .. pendingFactions) print(" Defeated: " .. defeatedFactions) print("") local totalPlayers = 0 for _ in pairs(gameData.players) do totalPlayers = totalPlayers + 1 end print("Active Players: " .. totalPlayers) local pendingJoins = 0 for _ in pairs(gameData.pendingJoins) do pendingJoins = pendingJoins + 1 end print("Pending Joins: " .. pendingJoins) print("") print("=== RECENT FACTIONS ===") local recentCount = 0 for i = #gameData.factions, math.max(1, #gameData.factions - 4), -1 do local faction = gameData.factions[i] if faction then local status if faction.defeated then status = "[DEFEATED]" elseif faction.pending then status = "[PENDING]" else status = "[ACTIVE]" end print(faction.name .. " " .. status) print(" Owner: " .. faction.owner .. " | Members: " .. #faction.members) recentCount = recentCount + 1 if recentCount >= 5 then break end end end if #gameData.factions == 0 then print("No factions created yet") end sleep(3) end end function autoSaver() while true do sleep(30) -- Save every 30 seconds saveData() end end -- Main function function main() term.clear() term.setCursorPos(1, 1) print("=== FACTION MANAGER SERVER ===") print("Starting up...") -- Load existing data loadData() log("Faction Manager Server ready!") log("Server ID: " .. os.getComputerID()) log("Heartbeat Server ID: " .. HEARTBEAT_SERVER_ID) log("Loaded " .. #gameData.factions .. " factions") -- Request sync from heartbeat server rednet.send(HEARTBEAT_SERVER_ID, { type = "sync_request" }, "faction_sync") -- Run all systems in parallel parallel.waitForAny( networkListener, chatListener, autoSaver, statusDisplay ) end -- Start the server main()