-- 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()