the script is enabled
edit:
Ive resolved this by changing the code to
local hitbox = workspace:GetPartBoundsInBox(workspace.map1.Zone.CFrame . . .
i expected you to do the line of code that’d go zone.Cframe, zone.Size
, sorry
the script is enabled
edit:
Ive resolved this by changing the code to
local hitbox = workspace:GetPartBoundsInBox(workspace.map1.Zone.CFrame . . .
i expected you to do the line of code that’d go zone.Cframe, zone.Size
, sorry
:Touched() is generally not reliable. The only real use it has is when you have hundreds of parts that need to do something when touched, like an obby.
@Amritss, how could i put the code into checking if the player is in team blue or team red or team lobby now? i tried it by checking the localplayers team and then printing it out but it seemed not to work
for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
if part.Parent:FindFirstChildOfClass("Humanoid") then
if part.Parent:FindFirstChild(localplayer == game.Teams.Blue) then
print("blue")
else print("not blue")
end
end
end
i’ve tried this aswell and it doesn’t work
for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
if part.Parent:FindFirstChildOfClass("Humanoid") then
local char = part.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if player.Team = game.Teams.Blue then
print("blue")
else
print("not blue")
end
end
end
I realised my mistake earlier and saw you wanted it on the zone, I guess you didn’t see the edit.
prints out “Not blue” no matter the team
I tried using the Touched
event for this and it wasn’t consistent. That’s why I started to use the spatial queries.
Basically, the KotH sensor is a model (or Actor if using Parallel LUA). Within that model you put the actual sensor plate and the script. The script runs the spatial query every 0.1 seconds or so. The methods to use are Workspace:GetPartsBoundInBox()
and Workspace:GetPartBoundsInRadius
. You don’t need to query 50 studs high, just a height of about 10 studs max for performance reasons. It returns a table of parts in the region. Just check if the parts belong to one or more players. If they do, then check the teams of the players. If the teams are the same, then color the sensor plate the TeamColor
. If not, then color the sensor plate the neutral color.
Another script will run with a while true do
loop timed every few seconds to check the plate color. If the color is not the neutral color, then increment the score of the color who’s team it is and display it.
EDIT:
The zone library that someone posted is good for what it does, but it doesn’t really work for a King of the Hill sensor. It’s just for announcing to the player what zone they are in. With that being said, you will have to do a spatial query to see who is on the plate.
local zone = workspace.map1.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
local hitbox = workspace:GetPartBoundsInBox(zone.CFrame, zone.Size) -- look up the syntax on documentation
for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
if part.Parent:FindFirstChildOfClass("Humanoid") then
local char = part.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if player.Team == game.Teams.Blue then
print("blue")
else
print("not blue")
end
end
end
Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end
this is the current code i have, however it doesnt print out the correct terms when the team is blue it just always prints out not blue
how else do you think I should approach this?
On the face of it, I do not see anything really wrong with your code, but there are a couple of issues. Is the player actually a member of the Blue team?
The way that you are running the query is not entirely correct. You are running the query within the part itself and not on the surface of it. However, if the part has collisions off, then this would be valid.
You are running the routine on the heartbeat. That’s going to pull quite a bit of the server’s CPU bandwidth and may lag out the server if you have other things going on. I recommend using a 0.1 second delay for this. You could get away with a 0.25 second delay.
Instead of checking for a specific team, check if the player is on a team and then print that team name. Then you can see what’s going on.
Try this code:
-- ******** Requirements
-- Required Game Services and Facilities
local playerService = game:GetService("Players")
-- ******** Local Data
local zone = script.Parent
local teamValue = zone:FindFirstChild("Team")
local defaultColor = BrickColor.new("Medium Stone Grey")
local spatialQuery = game.Workspace.GetPartBoundsInBox
local playerQuery = playerService:GetPlayerFromCharacter
-- ******** Functions/Methods
-- Returns the player, character, humanoid, and humanoid root part
-- (if any) that is associated with the given part.
local function getPlayerFromHit(hitPart: Instance): (Player, Model, Humanoid, BasePart)
-- Setup
local player = nil
local char = nil
local human = nil
local root = nil
-- Process
char = hitPart:FindFirstAncestorOfClass("Model")
if char ~= nil then
human = char:FindFirstChild("Humanoid")
root = char:FindFirstChild("HumanoidRootPart")
if human ~= nil and root ~= nil then
player = playerQuery(char)
end
end
-- Return Results
return player, char, human, root
end
-- ******** Initialize
-- If the team object value does not exist, then create it.
if teamValue == nil then
teamValue = Instance.new("ObjectValue")
teamValue.Name = "Team"
teamValue.Parent = zone
end
-- ******** Run
-- Forks off a separate thread that runs forever which
-- performs a spatial query to get all players that are
-- on the sensor plate and aggregate the teams. The
-- number of teams are then counted and the plate
-- color is adjusted accordingly.
task.spawn(function()
-- Setup
local zoneCFrame = zone:GetPivot()
local zoneSize = Vector3.new(zone.Size.X, 10, zone.Size.Z)
-- Operational
local teamList
local teamVal
local count
local color
local player
-- Process
while true do
-- First we get all players that are on the KotH sensor plate. There is no
-- need to check for duplicates because we are always setting the team
-- entry in the table to true and we are not counting the number of players
-- on each team.
teamList = {}
local hitbox = spatialQuery(zoneCFrame, zoneSize)
for _, item in ipairs(hitbox) do
player = getPlayerFromHit(item)
if player ~= nil then
if player.Team ~= nil then
teamList[player.Team] = true
end
end
end
-- Count the number of teams encountered. If it's != 1 then we set
-- the default color, otherwise we set it to the team color. If we do
-- set a team color, we also set the TeamValue object value to that
-- teams so the monitoring script can score correctly.
count = 0
for team, _ in pairs(teamList) do
count += 1
color = team.TeamColor
teamVal = team
end
if count == 1 then
zone.BrickColor = color
teamValue.Value = teamVal
else
zone.BrickColor = defaultColor
teamValue.Value = nil
end
-- Added for debugging purposes.
print(teamValue.Value, zone.BrickColor)
task.wait(0.1)
end
end)
The above code was made from memory and off the top of my head. If the player is not on a team, then they are ignored. If they are on a team, the a table entry is set to true. I didn’t check for duplicates for performance reasons because more steps are required to check, and it doesn’t sure anything repeatedly setting the same table entry to true.
The second part checks for the number of teams in the table. If there’s no team or more than one team, then the default color is set. If only one team is present, then that team color is used. You were specifically checking for a team which is not good practice. This is more generic and will work with any number of teams without modification.
An alternative would be to search the team list array and then add the team using table.insert()
if it’s not there. Then you can use #teamList
to get the number of entries in the table, but I’m not sure of the performance impact of that. You want this to run as fast as possible which is why I deployed a number of tricks to get the most performance out of it.
The scoring/round script looks something like this:
-- ******** Requirements
local playerService = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")
local teamService = game:GetService("Teams")
-- ******** Local Data
-- Remote Events
local eventFolder = replicatedStorage:FindFirstChild("Events")
local eventScore = eventFolder:FindFirstChild("Score")
local eventTimer = eventFolder:FindFirstChild("Timer")
local eventWinner = eventFolder:FindFirstChild("Winner")
-- Setup
local zone = game.Workspace.map1.Zone
local teamValue = zone:FindFirstChild("Team")
-- Settings
local defaultColor = BrickColor.new("Medium Stone Grey")
local scoreGoal = 100
local checkInterval = 5
local roundTime = 600
-- Operational
local endRound = false
local score = {}
-- ******** Functions/Methods
-- Sends a command to one or more clients.
local function sendCommand(event: RemoteEvent, player: Player,
command: number, d1: any, d2: any, d3: any, d4: any, d5: any)
if player == nil then
event:FireAllClients(command, d1, d2, d3, d4, d5)
else
event:FireClient(player, command, d1, d2, d3, d4, d5)
end
end
-- Updates the score display for all players.
local function updateScoreDisplay(score: table)
-- Do whatever that needs to be done here, which usually
-- involves a RemoteEvent:FireAllClients() to send the data
-- to the client for processing.
sendCommand(eventScore, nil, 0, score)
end
-- Updates the timer display for all players.
local function updateTimerDisplay(counter: number)
-- Do whatever that needs to be done here, which usually
-- involves a RemoteEvent:FireAllClients() to send the data
-- to the client for processing.
sendCommand(eventTimer, nil, 0, counter)
end
-- Displays the winning player, or tie on the player's screen.
local function displayWinner(data: table)
-- Do whatever that needs to be done here, which usually
-- involves a RemoteEvent:FireAllClients() to send the data
-- to the client for processing.
if data.tie == true then
-- We have a tie.
sendCommand(eventWinner, nil, 2, data)
else
-- We have a winner.
local playerList = playerService:GetPlayers()
for _, player in ipairs(playerList) do
if player.Team == data.team then
-- Winning Player
sendCommand(eventWinner, player, 1, data)
else
-- Losing Player
sendCommand(eventWinner, player, 0, data)
end
end
end
end
-- Determines if there was a winner or a tie.
local function determineWinner(scoreTable: table)
-- Setup
local data = {
team = nil;
tie = false;
}
-- Find the team with the highest score.
local maxTeam = nil
local maxScore = -math.huge
for team, score in pairs(scoreTable) do
if score > maxScore then
maxTeam = team
maxScore = score
end
end
-- Now that we know what the highest score is, we check if
-- another team has the same score. If they do, then we have
-- a tie.
local count = 0
for _, score in pairs(scoreTable) do
if score == maxScore then
count += 1
end
end
if count == 1 then
-- There can be only *1*, and that's the winning team.
data.team = maxTeam
else
-- If the count is not one, then we have a tie.
data.tie = true
end
-- Return Result
return data
end
-- ******** Initialize
-- Create a score entry for each team that exists.
do
local teamList = teamService:GetTeams()
for _, team in ipairs(teamList) do
score[team] = 0
end
end
-- ******** Run
-- Periodically checks the Team object value of the KotH sensor plate
-- for a valid team. If one is set, then that team's score in increased.
task.spawn(function()
-- Setup
local color
local team
local start
-- Process
while endRound == false do
-- Waits for the specified interval before checking if there is
-- a team on the plate. This is a precision wait that takes the
-- loop execution time into account.
task.wait(checkInterval - (os.clock() - start))
-- Check if the round ended while we were waiting. If it did,
-- then terminate thread execution **NOW**.
if endRound == true then
return
end
-- Process
start = os.clock()
team = teamValue.Value
if team ~= nil then
if type(score[team]) == "number" then
score[team] += 1
end
end
-- Updates the score display for all players.
updateScoreDisplay(score)
-- Check if any of the team scores have reached the goal
-- score.
for _, value in pairs(score) do
if value >= scoreGoal then
endRound = true
end
end
end
end)
-- This is the round timer loop. This executes on its own thread
-- and runs every second. This loop employs a precision wait
-- that is based on the loop execution time.
task.spawn(function()
local counter = roundTime
local start
-- This is the round timer loop. This loop will only exit on two conditions:
-- 1. The endRound becomes true for whatever reason.
-- 2. The counter becomes 0.
while endRound == false and counter > 0 do
start = os.clock()
counter -= 1
updateTimerDisplay(counter)
task.wait(1 - (os.clock() - start))
end
-- Set the endRound to true so all running loops will exit.
endRound = true
-- Determine if there was a tie or winner. If a winner is declared,
-- then reward them.
local winData = determineWinner(score)
if winData.tie == false then
-- Reward players on the winning team.
local playerList = winData.team:GetPlayers()
for _, player in ipairs(playerList) do
-- Reward Players
end
end
displayWinner(winData)
-- From here, do whatever needs to be done which can include teleporting
-- players to a lobby, time delay to start a new round, etc....
end)
This is pretty much an entire King of the Hill system. You’ll have to adapt the code for your uses, but this is kind of what I use in my game. The big thing about this is what to do at the end of the round. I know that some games have the lobby and the map in the same place. Mine doesn’t, so I don’t bother with resetting everything to defaults after the round ends.
How you handle your client displays is entirely up to you, but mine implements a command system where the server sends a command to the client. On the client, the command numbers index into a table of function references so it gets called when the server sends that command and the data is passed though.
As I said before, all of this came from memory and from the top of my head, so there’s probably some typos and syntax errors in it.
So basically, the way this works is that after initialization, there’s two loops that run on their own threads which do different things. The first loop (scoring) runs every few seconds to check the sensor plate for players and updates the score accordingly. The second loop (timer) runs every second and handles the round clock. The timer is primary and when it exits, it performs some end of round functions like determining who the winner is, if any, rewarding the players on the winning team, displaying the winner/tie, etc…
EDIT: After proofreading it, I found a couple of errors, so you’ll have to copy the code again.
thank you for providing me with all the codes and the information
There is an error that I’ve resolved, instead of playerService:GetPlayerFromCharacter
It has to be playerService:GetPlayerFromCharacter
however the code still doesnt change the color of anything at all
the part has collisions off and this is how its shaped:
should this code work on default? I have run it and It doesn’t do anything for me with no errors (in the dev console) aswell
(i suppose this is how we fix the tie error?
)
probably because i forgot to put the two equal signs for the if statement.
I’ve seen that, upon fixing that it still didn’t work properly
How is that possible… Your studio is cursed my friend, do you have canquery off or something.
nope it is on
local zone = workspace.map1.Zone -- This is just a part from the workspace you can use to visualise the hitbox
local Runservice = game:GetService("RunService")
while true do
local hitbox = workspace:GetPartBoundsInBox(workspace.map1.Zone.CFrame, zone.Size) -- look up the syntax on documentation
for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
if part.Parent:FindFirstChildOfClass("Humanoid") then
for i, part in hitbox do -- :GetPartBoundsInBox returns an array of baseparts detected so we can loop through them
if part.Parent:FindFirstChildOfClass("Humanoid") then
local char = part.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if player.Team == game.Teams.Blue then
print("blue")
else
print("not blue")
end
end
end
end
end
Runservice.Heartbeat:Wait() -- Just so that we don't crash.
end
Is the part anchored? Cuz it would fall into the void otherwise
yes the part is anchored
I fixed the errors that you pointed out.
The one thing that I can think of is your transparency setting. If it’s 1, then nothing will show because it’s fully transparent.
EDIT:
Wait, is that a part or a NEGATIVE part? The coloring looks like it’s a negative part. You cannot set the color on a negative part because it will not show.
I’ll test this from my side and update you on what to do.
@Maelstorm_1973
after changing the code to the after edit code, it still doesnt do anything…
I’m not sure what a negative part is, it is the default part that’d come up upon clicking the part button in the studio interface just colored to a different color to see the zone easier for now,
the transparency is 0.65
edit: upon checking what a negative part is, the zone is not a negative part, it doesnt have any option on the solid modeling selected
I’m lost here, as I don’t know why is nothing printing out
edit: also, is this a error or not?
after changing the code to this:
local replicatedStorage = game:GetService("ReplicatedStorage")
the output doesnt show anymore, instead it shows this
local eventScore = eventFolder:FindFirstChild("Score")
Just tested it right now, it literally works…