CCCPaste

FACTION CORE

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