mmbvn

🧩 Syntax:
--[[
    GameServer (Server)
    Integrates with Matchmaker module, handles player connections and basic events.
    Board/base creation is now handled by Matchmaker.StartMatch.

    MODIFIED: Added print statements BEFORE and AFTER require calls to debug which one is failing.
    MODIFIED: Added print statements to confirm RemoteEvent creation.
]]

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local CollectionService = game:GetService("CollectionService")

-- Load Modules needed on the server
print("GameServer: Attempting to require BoardManager...") -- Debug print BEFORE require
local BoardManager = require(ReplicatedStorage:WaitForChild("BoardManager"))
print("GameServer: Successfully required BoardManager.") -- Debug print AFTER require

print("GameServer: Attempting to require TroopManager...") -- Debug print BEFORE require
local TroopManager = require(ReplicatedStorage:WaitForChild("TroopManager"))
print("GameServer: Successfully required TroopManager.") -- Debug print AFTER require

print("GameServer: Attempting to require Matchmaker...") -- Debug print BEFORE require
local Matchmaker = require(ServerScriptService:WaitForChild("Matchmaker")) -- Require the new module
print("GameServer: Successfully required Matchmaker.") -- Debug print AFTER require


print("GameServer: Starting initialization...") -- This print should now appear if all requires succeed

-- Create RemoteEvents if they don't exist
local RequestTroopMoveEvent = ReplicatedStorage:FindFirstChild("RequestTroopMove") or Instance.new("RemoteEvent", ReplicatedStorage)
RequestTroopMoveEvent.Name = "RequestTroopMove"
print("GameServer: Created/Verified RemoteEvent: RequestTroopMove") -- Debug print

local BuyTroopEvent = ReplicatedStorage:FindFirstChild("BuyTroop") or Instance.new("RemoteEvent", ReplicatedStorage)
BuyTroopEvent.Name = "BuyTroop"
print("GameServer: Created/Verified RemoteEvent: BuyTroop") -- Debug print

local UpdateMoneyEvent = ReplicatedStorage:FindFirstChild("UpdateMoney") or Instance.new("RemoteEvent", ReplicatedStorage)
UpdateMoneyEvent.Name = "UpdateMoney"
print("GameServer: Created/Verified RemoteEvent: UpdateMoney") -- Debug print

local SendFeedbackMessage = ReplicatedStorage:FindFirstChild("SendFeedbackMessage") or Instance.new("RemoteEvent", ReplicatedStorage)
SendFeedbackMessage.Name = "SendFeedbackMessage"
print("GameServer: Created/Verified RemoteEvent: SendFeedbackMessage") -- Debug print

-- **NEW Matchmaking RemoteEvents**
local RequestJoinQueueEvent = ReplicatedStorage:FindFirstChild("RequestJoinQueue") or Instance.new("RemoteEvent", ReplicatedStorage)
RequestJoinQueueEvent.Name = "RequestJoinQueue"
print("GameServer: Created/Verified RemoteEvent: RequestJoinQueue") -- Debug print

local RequestLeaveQueueEvent = ReplicatedStorage:FindFirstChild("RequestLeaveQueue") or Instance.new("RemoteEvent", ReplicatedStorage)
RequestLeaveQueueEvent.Name = "RequestLeaveQueue"
print("GameServer: Created/Verified RemoteEvent: RequestLeaveQueue") -- Debug print

local UpdateClientStateEvent = ReplicatedStorage:FindFirstChild("UpdateClientState") or Instance.new("RemoteEvent", ReplicatedStorage)
UpdateClientStateEvent.Name = "UpdateClientState"
print("GameServer: Created/Verified RemoteEvent: UpdateClientState") -- Debug print


print("GameServer: All RemoteEvents creation/verification steps completed.") -- Confirmation print

-- == REMOVED Board and Base creation from server start ==
-- BoardManager:CreateBoard() -- Now called by Matchmaker
-- Base creation/tagging -- Now called by Matchmaker

-- == Connect Matchmaking Event Handlers ==
RequestJoinQueueEvent.OnServerEvent:Connect(function(player)
	print("GameServer: Received RequestJoinQueue from", player.Name)
	Matchmaker.AddPlayerToQueue(player)
end)

RequestLeaveQueueEvent.OnServerEvent:Connect(function(player)
	print("GameServer: Received RequestLeaveQueue from", player.Name)
	Matchmaker.RemovePlayerFromQueue(player)
end)

-- == Player Connection Handlers ==
Players.PlayerAdded:Connect(function(player)
	print("GameServer:", player.Name, "joined.")
	-- Initialize player state via Matchmaker (sends "Lobby" state to client)
	Matchmaker.InitializePlayer(player)

	-- Set initial money attribute (consider DataStore loading here later)
	if not player:GetAttribute("Money") then
		player:SetAttribute("Money", 500) -- Starting money
		print("GameServer: Set initial money for", player.Name, "to", player:GetAttribute("Money"))
		-- No need to fire UpdateMoneyEvent here, client will get it when needed or GUI loads
	end
end)

Players.PlayerRemoving:Connect(function(player)
	print("GameServer:", player.Name, "leaving.")
	-- Let Matchmaker handle cleanup (remove from queue, end match etc.)
	Matchmaker.HandlePlayerLeaving(player)
end)


-- == Troop Movement Handling (Remains the same) ==
RequestTroopMoveEvent.OnServerEvent:Connect(function(player, troopToMove, targetTile)
	-- Check if player is actually in a game state before processing move
	local playerState = Matchmaker.GetPlayerState(player)
	if playerState ~= "InGame" then
		warn("GameServer: Player", player.Name, "attempted move while not InGame (State:", playerState, ")")
		-- Optionally send feedback here if needed, but usually moves are only possible in InGame
		return -- Ignore move if not in game
	end

	print("GameServer: Received RequestTroopMove from", player.Name)

	-- 1. --- VALIDATION ---
	local boardModel = nil
	local matchId = Matchmaker.playerMatchMap[player.UserId] -- Get matchId from map
	if matchId then
		local matchInfo = Matchmaker.activeMatches[matchId]
		if matchInfo then
			boardModel = matchInfo.boardModel -- Get the correct board for this match
		end
	end

	if not boardModel then
		warn("GameServer: Could not find board model for player", player.Name, "'s match.")
		SendFeedbackMessage:FireClient(player, "Match board not found.") -- Feedback
		return
	end


	-- Basic checks: Ensure instances exist and are the correct type
	if not (troopToMove and troopToMove:IsA("BasePart") and troopToMove.Parent == Workspace) then
		warn("GameServer: Invalid troop instance received from", player.Name)
		SendFeedbackMessage:FireClient(player, "Invalid troop selected.") -- **Feedback**
		return -- Ignore invalid request
	end
	-- Check if targetTile is a valid board tile part *within the correct match board*
	if not (targetTile and targetTile:IsA("BasePart") and targetTile.Parent and targetTile.Parent == boardModel) then
		warn("GameServer: Invalid target tile instance received from", player.Name, "or not part of correct board.")
		SendFeedbackMessage:FireClient(player, "Invalid target tile.") -- **Feedback**
		return -- Ignore invalid request
	end


	-- Check Ownership
	if not TroopManager:DoesPlayerOwnTroop(troopToMove, player) then
		warn("GameServer: Player", player.Name, "attempted to move troop they don't own:", troopToMove.Name)
		SendFeedbackMessage:FireClient(player, "You don't own this troop.") -- **Feedback**
		return -- Ignore request
	end

	-- Check if troop is anchored
	if not troopToMove.Anchored then
		warn("GameServer: Troop", troopToMove.Name, "is not anchored, cannot move via script.")
		SendFeedbackMessage:FireClient(player, "Troop is not ready to move.") -- **Feedback**
		return
	end

	-- Check Distance (Server-side calculation)
	-- Use BoardManager:GetTileAtWorldPos, assumes it works with boards not at origin
	local currentTile = BoardManager:GetTileAtWorldPos(troopToMove.Position)
	-- Also check if the tile found is actually part of the correct match board
	if not currentTile or not currentTile.Parent or currentTile.Parent ~= boardModel then
		warn("GameServer: Could not determine current tile for troop:", troopToMove.Name, "on correct board.")
		SendFeedbackMessage:FireClient(player, "Could not determine troop's current location.") -- **Feedback**
		return -- Cannot validate distance
	end

	local currentTileX = currentTile:GetAttribute("TileX")
	local currentTileZ = currentTile:GetAttribute("TileZ")
	local targetTileX = targetTile:GetAttribute("TileX")
	local targetTileZ = targetTile:GetAttribute("TileZ")

	if currentTileX == nil or currentTileZ == nil or targetTileX == nil or targetTileZ == nil then
		warn("GameServer: Tile coordinates missing attributes for distance check.")
		SendFeedbackMessage:FireClient(player, "Error checking move distance.") -- **Feedback**
		return -- Cannot validate distance
	end

	local distance = math.abs(currentTileX - targetTileX) + math.abs(currentTileZ - targetTileZ)
	print("GameServer: Calculated move distance:", distance)

	if distance <= 0 or distance > MAX_MOVE_DISTANCE then
		warn("GameServer: Invalid move distance for", troopToMove.Name, "- Distance:", distance)
		SendFeedbackMessage:FireClient(player, "Target tile is too far away.") -- **Feedback**
		return -- Ignore request
	end

	-- Check if Target Tile is Occupied by ANOTHER TROOP or a BUILDING (within the match context)
	local overlapParams = OverlapParams.new()
	overlapParams.FilterDescendantsInstances = {troopToMove, boardModel} -- Ignore self and the board model
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude
	-- Expand the check slightly vertically to catch things resting on the tile
	local checkRegion = Region3.new(targetTile.Position - Vector3.new(targetTile.Size.X/2, 0.5, targetTile.Size.Z/2), targetTile.Position + Vector3.new(targetTile.Size.X/2, troopToMove.Size.Y + 0.5, targetTile.Size.Z/2))
	local occupants = Workspace:FindPartsInRegion3WithIgnoreList(checkRegion, overlapParams.FilterDescendantsInstances, 100)

	local isOccupied = false
	for _, part in ipairs(occupants) do
		-- Check if the overlapping part is another troop OR a building IN THIS MATCH
		-- Check for troop owner attribute OR base owner attribute
		local ownerAttribute = part:GetAttribute("OwnerUserId") -- Check for base owner attribute (set by Matchmaker)
		local troopOwnerAttribute = part:GetAttribute("Owner") -- Check for troop owner attribute (set by TroopManager)

		if (part:IsA("BasePart") and troopOwnerAttribute) or (ownerAttribute and (ownerAttribute == matchInfo.player1.UserId or ownerAttribute == matchInfo.player2.UserId)) then
			print("GameServer: Target tile", targetTile.Name, "is occupied by", part.Name)
			isOccupied = true
			break
		end
	end

	if isOccupied then
		warn("GameServer: Target tile", targetTile.Name, "is occupied. Move denied.")
		SendFeedbackMessage:FireClient(player, "Target tile is occupied.") -- **Feedback**
		return -- Ignore request
	end

	-- 2. --- EXECUTION ---
	print("GameServer: Move validated for", troopToMove.Name, "to", targetTile.Name)
	local targetPosition = targetTile.Position + Vector3.new(0, troopToMove.Size.Y / 2, 0)
	troopToMove.Position = targetPosition
	print("GameServer:", troopToMove.Name, "moved successfully.")
end)


-- == Buy Troop Handling (Remains mostly the same, but check player state) ==
BuyTroopEvent.OnServerEvent:Connect(function(player, troopType, placementPosition, clickedBuilding)
	-- Check if player is actually in a game state before processing purchase
	local playerState = Matchmaker.GetPlayerState(player)
	if playerState ~= "InGame" then
		warn("GameServer: Player", player.Name, "attempted purchase while not InGame (State:", playerState, ")")
		-- Optionally send feedback here if needed, but usually purchases are only possible in InGame via shop GUI
		return -- Ignore purchase if not in game
	end

	print("GameServer: Received BuyTroop request from", player.Name, "for", troopType, "at", placementPosition, "via building", clickedBuilding and clickedBuilding.Name or "nil")

	-- 1. --- VALIDATION ---
	local boardModel = nil
	local matchId = Matchmaker.playerMatchMap[player.UserId] -- Get matchId from map
	if matchId then
		local matchInfo = Matchmaker.activeMatches[matchId]
		if matchInfo then
			boardModel = matchInfo.boardModel -- Get the correct board for this match
		end
	end

	if not boardModel then
		warn("GameServer: Could not find board model for player", player.Name, "'s match (BuyTroop).")
		SendFeedbackMessage:FireClient(player, "Match board not found.") -- Feedback
		return
	end

	-- Validate the clicked building instance received from the client
	if not clickedBuilding or not clickedBuilding:IsA("BasePart") or not clickedBuilding.Parent or not CollectionService:HasTag(clickedBuilding, PRODUCTION_BUILDING_TAG) then
		warn("GameServer: Invalid or untagged building instance received from", player.Name)
		SendFeedbackMessage:FireClient(player, "Invalid building selected for placement.") -- **Feedback**
		return -- Ignore invalid request
	end

	-- **NEW: Check if the clicked building belongs to the player requesting the purchase**
	local buildingOwnerId = clickedBuilding:GetAttribute("OwnerUserId")
	if buildingOwnerId ~= player.UserId then
		warn("GameServer: Player", player.Name, "tried to buy from building owned by", buildingOwnerId)
		SendFeedbackMessage:FireClient(player, "You can only buy from your own buildings.") -- **Feedback**
		return
	end

	-- Check if the troop type is valid
	local troopStats = TroopManager.TROOP_TYPES[troopType]
	if not troopStats then
		warn("GameServer: Player", player.Name, "requested invalid troop type:", troopType)
		SendFeedbackMessage:FireClient(player, "Invalid troop type selected.") -- **Feedback**
		return -- Ignore invalid request
	end

	-- Check if player has enough money
	local playerMoney = player:GetAttribute("Money") or 0
	local troopCost = troopStats.Cost or math.huge
	if playerMoney < troopCost then
		warn("GameServer: Player", player.Name, "does not have enough money to buy", troopType)
		SendFeedbackMessage:FireClient(player, "Not enough money to buy this troop.") -- **Feedback**
		return -- Ignore request
	end

	-- Check if the placement position is on a valid board tile *on the correct board*
	local targetTile = BoardManager:GetTileAtWorldPos(placementPosition)
	if not targetTile or not targetTile.Parent or targetTile.Parent ~= boardModel then
		warn("GameServer: Invalid placement position: Not on a tile or wrong board.")
		SendFeedbackMessage:FireClient(player, "Placement position is not on the board.") -- **Feedback**
		return -- Ignore invalid request
	end

	-- Check if the target tile is within the spawn radius of the clicked building
	local buildingTile = BoardManager:GetTileAtWorldPos(clickedBuilding.Position)
	-- Also check if the tile found for the building is actually part of the correct match board
	if not buildingTile or not buildingTile.Parent or buildingTile.Parent ~= boardModel then
		warn("GameServer: Could not determine tile for clicked building:", clickedBuilding.Name, "on correct board.")
		SendFeedbackMessage:FireClient(player, "Error determining building location.") -- **Feedback**
		return -- Cannot validate proximity
	end

	local buildingTileX = buildingTile:GetAttribute("TileX")
	local buildingTileZ = buildingTile:GetAttribute("TileZ")
	local targetTileX = targetTile:GetAttribute("TileX")
	local targetTileZ = targetTile:GetAttribute("TileZ")

	if buildingTileX == nil or buildingTileZ == nil or targetTileX == nil or targetTileZ == nil then
		warn("GameServer: Tile coordinates missing attributes for proximity check.")
		SendFeedbackMessage:FireClient(player, "Error checking placement proximity.") -- **Feedback**
		return -- Cannot validate proximity
	end

	local distance = math.abs(buildingTileX - targetTileX) + math.abs(buildingTileZ - targetTileZ)
	print("GameServer: Calculated placement distance from building:", distance)

	if distance <= 0 or distance > SPAWN_RADIUS then
		warn("GameServer: Invalid placement distance from building for", troopType, "- Distance:", distance)
		SendFeedbackMessage:FireClient(player, "Placement must be near the building.") -- **Feedback**
		return -- Ignore request
	end


	-- Check if Target Tile is Occupied by ANOTHER TROOP (within the match context)
	local overlapParams = OverlapParams.new()
	overlapParams.FilterDescendantsInstances = {boardModel, clickedBuilding} -- Ignore board and clicked building
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude
	-- Expand the check slightly vertically to catch things resting on the tile
	local checkRegion = Region3.new(targetTile.Position - Vector3.new(targetTile.Size.X/2, 0.5, targetTile.Size.Z/2), targetTile.Position + Vector3.new(targetTile.Size.X/2, troopStats.Size.Y + 0.5, targetTile.Size.Z/2))
	local occupants = Workspace:FindPartsInRegion3WithIgnoreList(checkRegion, overlapParams.FilterDescendantsInstances, 100)

	local isOccupiedByTroop = false
	for _, part in ipairs(occupants) do
		if part:IsA("BasePart") and part:GetAttribute("Owner") then -- Check for troop owner attribute
			print("GameServer: Target tile", targetTile.Name, "is occupied by another troop:", part.Name)
			isOccupiedByTroop = true
			break
		end
	end

	if isOccupiedByTroop then
		warn("GameServer: Target tile", targetTile.Name, "is occupied by a troop. Placement denied.")
		SendFeedbackMessage:FireClient(player, "Target tile is already occupied by a troop.") -- **Feedback**
		return -- Ignore request
	end

	-- 2. --- EXECUTION ---
	print("GameServer: Buy and placement validated for", troopType, "at", placementPosition, "near", clickedBuilding.Name)

	-- Deduct Money
	player:SetAttribute("Money", playerMoney - troopCost)
	print("GameServer: Deducted $", troopCost, "from", player.Name, ". New balance:", player:GetAttribute("Money"))

	-- Fire event to update client GUI
	UpdateMoneyEvent:FireClient(player, player:GetAttribute("Money"))

	-- Create the troop instance
	local newTroop = TroopManager:CreateTroop(troopType, placementPosition, player)

	if newTroop then
		print("GameServer:", troopType, "created successfully for", player.Name, "at", placementPosition)
		-- TroopManager should parent the troop to Workspace. Consider parenting to matchModel?
		-- newTroop.Parent = matchModel -- Optional: Parent troop to match model for easier cleanup
	else
		warn("GameServer: Failed to create troop instance for", troopType)
		SendFeedbackMessage:FireClient(player, "Failed to create troop.") -- **Feedback**
		-- Optional: Refund money if creation failed?
	end
end)

print("GameServer: Initialization complete. Matchmaking system integrated.")