Ive been racking brains all day tryna fix this and i am new with lua and cant come up with a solution to this problem even though im rather close.
SO
I have a drift leader board in my server. The problem is the script doesnt check for the players [id] in the table. So if player A has a score of 10 and Player B has a score of 5. Player A can take the 2nd position on the leaderboard (table) by getting a score of any int between. so lets say 7.
LEADERBOARD_CLEAR_TIME = 15 -- THIS IS IN MINUTES
local Players = {}
local Leaderboards = {}
RegisterCommand('cleardriftscores', function(source)
if IsPlayerAceAllowed(source, 'drift') then --Ace permissions
clearLeaderboard()
end
end)
RegisterNetEvent("SaveScore")
AddEventHandler("SaveScore", function(client, data)
local identifier = (GetPlayerIdentifier(source, 0))
local playerName = GetPlayerName(source)
if Players[identifier] ~= nil then
if Players[identifier].pb < data.curScore then
-- Personal Best Beat
local oldScore = Players[identifier].pb
Players[identifier] = { pb = data.curScore }
chatMessage(source, string.format("Congrats! You have just beaten your personal best drift chain score of ^2%s^0 with ^2%s^0!", oldScore, data.curScore))
end
else
Players[identifier] = { pb = data.curScore }
end
if #Leaderboards == 0 then
table.insert(Leaderboards, {score = data.curScore, name = playerName, id = identifier})
chatMessage(-1, string.format("^2%s^0 has started off the leaderboard with a score of ^2%s^0!", playerName, data.curScore))
end
for k, v in ipairs(Leaderboards) do
if v.score < data.curScore and checkLeaderboard(identifier) then
-- Outpassed Someone
table.insert(Leaderboards, { score = data.curScore, name = playerName, id = identifier } )
chatMessage(-1, string.format("^2%s^0 has beaten ^2%s's^0 score of ^2%s^0 with ^2%s^0! They are in ^2%s^0 place", playerName, v.name, v.score, data.curScore, GetPlacement(k)))
break
end
end
table.sort(Leaderboards, compare) --Currently bugged, when player 1 is in first and player 2 is in second. Player 1 can take second by getting a score inbetween table entry 1 & 2
end)
function chatMessage(target, msg)
TriggerClientEvent('chat:addMessage', target or -1, {
color = { 255, 0, 0},
multiline = true,
args = {"[Drift] ", msg}
})
end
function checkLeaderboard(identifier)
for k, v in ipairs(Leaderboards) do
if v.id == identifier then
Leaderboards[k] = nil
end
end
return true
end
function compare(a, b) --actual compare funtion. Need to check for player id's somehow...
if a ~= nil and b ~= nil then
return a.score > b.score --I tried adding another compare here but that didnt work.
end
end
function GetPlacement(number) --This doesnt work very well. (12nd place??)
lastDigit = number % 10
local placement = 'th'
if lastDigit == 1 then
placement = 'st'
elseif lastDigit == 2 then
placement = 'nd'
elseif lastDigit == 3 then
placement = 'rd'
end
return number .. placement
end
function clearLeaderboard()
Leaderboards = {}
chatMessage(-1, "The Drift leaderboard has been cleared!")
end
Citizen.CreateThread(function() --Code for timer to reset leaderboard
while true do
while #Leaderboards == 0 do
Citizen.Wait(0)
end
Citizen.Wait((LEADERBOARD_CLEAR_TIME * 1000 * 60) - (60 * 1000 * 5))
chatMessage(-1, "The Drift Leaderboard is clearing in 5 minutes!")
Citizen.Wait(1000 * 60 * 3)
chatMessage(-1, "The Drift Leaderboard is clearing in 2 minutes!")
Citizen.Wait(1000 * 60 * 1)
chatMessage(-1, "The Drift Leaderboard is clearing in 1 minute!")
Citizen.Wait(1000 * 60 * 1)
clearLeaderboard()
end
end)
The problem here is the code that adds to the leaderboards.
If a player is only allowed to be on a leaderboard once, you should check for that!
Here's how I'd do it:
If the player has beaten his/her personal best then attempt to add his new score to the leaderboards.
Start from the lowest score on the leaderboard. For each score:
If the score owner is equal to the player, delete this score (it's outdated).
If the personal best is smaller than the currently inspected score, insert the personal best one below the current inspected score.
Doing so there won't be any duplicates.
Btw, compare function is just fine ;)
But, just a friendly reminder:
a ~= nil and b ~= nil
--This is simpler and faster!
a and b
Remember that nil in Lua evaluates to false. Therefore, if you need to check if something holds value just:
if a then --This checks if a isn't false or nil
--your code goes here
end
And also that line is not really useful in your comparing function.
Hope that helps!
Related
I was implementing score and high score
then after I implemented it the spikes were not moving.
also if you didn't know, I'm working with a roblox game
i do not know anything about this so yeah
don't ask me anything about it.
here is the code from the script that moves the spikes: (also handles the literal game)
local score = 0
local highscore = 0
local function ResetGame()
score = 0
for _, spike in workspace.Spikes:GetChildren() do
spike.Position = spike:GetAttribute("SpawnPos")
end
end
function BoxCollision3D(p1,p2)
local w1,w2,h1,h2,d1,d2=p1.Size.x/2,p2.Size.x/2,p1.Size.y/2,p2.Size.y/2,p1.Size.z/2,p2.Size.z/2
local axis={
(p1.CFrame*CFrame.new(1,0,0)).p-p1.CFrame.p, --Ax
(p1.CFrame*CFrame.new(0,1,0)).p-p1.CFrame.p, --Ay
(p1.CFrame.lookVector), --Az
(p2.CFrame*CFrame.new(1,0,0)).p-p2.CFrame.p, --Bx
(p2.CFrame*CFrame.new(0,1,0)).p-p2.CFrame.p, --By
(p2.CFrame.lookVector) --Bz
}
local L
local T=p2.CFrame.p-p1.CFrame.p
local a_count,b_count=1,4
for i=1,15 do
if i<=6 then
L=axis[i]
else
L=axis[a_count]:Cross(axis[b_count])
b_count=b_count+1
if b_count>=6 then
b_count=4
a_count=a_count+1
end
end
--print("T=("..T.x..","..T.y.."), L=("..L.x..","..L.y.."), TdotL="..math.abs(T:Dot(L)))
if (math.abs(T:Dot(L)))>(math.abs((w1*axis[1]):Dot(L))+math.abs((h1*axis[2]):Dot(L))+math.abs((d1*axis[3]):Dot(L))+math.abs((w2*axis[4]):Dot(L))+math.abs((h2*axis[5]):Dot(L)))+math.abs((d2*axis[6]):Dot(L)) then
return false;
end
end
return true;
end
for _, spike in workspace.Spikes:GetChildren() do
spike:SetAttribute("SpawnPos", spike.Position)
end
while true do
for _, spike in workspace.Spikes:GetChildren() do
spike.Position += Vector3.new(0, 0, -4)
score += 1
if score > highscore then
highscore = score
end
script.Parent.ScoreGui.Score.Text = tostring(score)
script.Parent.ScoreGui.HiScore.Text = "HI: " .. tostring(highscore)
if BoxCollision3D(workspace.Player, spike) then
ResetGame()
break
end
end
wait(1)
end
I'm working on a FFA gamemode in Garry's mod where players have to survive each other and a nextbot NPC hunting them. My override of the GM:PlayerDeath adequately indicates when the current round should be over, however after SpawnGhoulInitial() is called, any deaths cause the game to crash. Im doing all this round stuff in my roundsystem_2.lua file:
-- GLOBAL VALUES
roundActive = false
roundCountdownStartTime = 0
roundStartTime = 0
phase = 1
countDownLength = 5
initialGhoulDelay = 10
spawnWepDelay = 15
ghoulMadnessDelay = 20
roundLength = 30
DEFINE_BASECLASS( "gamemode_base" )
-- vector list dictionary of nexbot spawn locations per map, starting with initial spawn location
nextbot_spawns = {gm_backrooms_classic = {Vector(-7413, 318, 93), Vector(-4270, 4079, 93), Vector(-3279, -4620, 93), Vector(-11092, -4632, 93), Vector(-11086, 4316, 93)}}
function GM:UpdateTimer(time)
net.Start("round_timer")
net.WriteInt(time, 10)
net.Broadcast()
end
-- function used to spawn the first ghoul of the round
function SpawnGhoulInitial()
local name = RandomizeNextbot()
local Ent = ents.Create(name)
if( !IsValid(Ent)) then
print("entity is not valid.")
return
end
Ent:SetPos(nextbot_spawns.gm_backrooms_classic[1])
Ent:Spawn()
print("Spawned ghoul ".. name)
PrintMessage(4, "The Ghoul has spawned. SURVIVE!")
end
-- function called to spawn the next four ghouls to sweep the map and kill off straggling players
function SpawnGhoulMadness()
local name = RandomizeNextbot()
local Ent = ents.Create(name)
if( !IsValid(Ent)) then
print("entity is not valid.")
return
end
for i=2, 5 do
Ent:SetPos(nextbot_spawns.gm_backrooms_classic[i])
Ent:Spawn()
print("Spawned ghoul madness ghoul number ".. tostring(i))
end
PrintMessage(4, "The Ghoul has summoned some friends. RUN!")
end
-- function called to spawn in weapons for the players
function SpawnWeps()
for k, v in pairs(player.GetAll()) do
if (v:Alive()) then
v:Give("weapon_crowbar", false)
v:Give("weapon_pistol", false)
end
end
PrintMessage(4, "You've found supplies. Kill other survivors!")
end
function CountAlivePlayers()
local players = player.GetAll()
local alive = 0
for i = 1, #players do
local player = players[i]
if (player:Alive()) then
alive = alive + 1
end
end
return alive
end
-- function called to give us a random nextbot to spawn in
function RandomizeNextbot()
local names = {"table of nextbot names here, not posting them for stackoverflow because its embarrassing"}
local random_nextbot = math.random(#names)
return names[random_nextbot]
end
-- function called under the conditions that the round has ended, will start the next round.
function GM:EndRound(winner)
-- clear the map, clear sounds, print winner on screen, allow the respawning
if (winner == nil) then
PrintMessage(4, "Nobody Wins!")
else
PrintMessage(4, winner:Nick().." Wins!")
end
game.CleanUpMap(false, {})
RunConsoleCommand("stopsound")
timer.Remove("RoundTimer")
timer.Create("roundEndTimer", 5, 1, function()
for k, v in pairs(player.GetAll()) do
if (v:Alive()) then
v:StripWeapons()
v:KillSilent()
end
end
net.Start("round_active")
net.WriteBool(false)
net.Broadcast()
roundActive = false
end)
end
function GM:StartCountDownTimer(repetitions)
self:UpdateTimer(repetitions)
timer.Create("CountdownTimer", 1, repetitions, function()
repetitions = repetitions - 1
self:UpdateTimer(repetitions)
end)
end
function GM:StartRoundTimer(repetitions)
self:UpdateTimer(repetitions)
timer.Create("RoundTimer", 1, repetitions, function()
repetitions = repetitions - 1
self:UpdateTimer(repetitions)
if (repetitions <= 0) then
self:EndRound(nil)
end
end)
end
--overriding playerdeath function for best death results
function GM:PlayerDeath(victim, inflictor, attacker)
BaseClass.PlayerDeath(self, victim, inflictor, attacker)
local players = player.GetAll()
local lastAlive = NULL
for i = 1, #players do
local p = players[i]
if (p:Alive()) then
lastAlive = p
end
end
if (CountAlivePlayers() <= 1 && roundActive) then
self:EndRound(lastAlive)
end
end
-- hook for starting the round on player spawn if there are enough players.
-- countdown for round start will restart if a new player spawns in during the countdown.
function GM:DoRoundSpawn()
--every time player joins reset the countdown
if (CountAlivePlayers() > 1) then
roundCountdownStartTime = CurTime()
self:StartCountDownTimer(countDownLength)
end
end
-- The main timing of the round. Checks against curtime to find when
-- specific events in the round need to happen.
function GM:Think()
-- if there is more than one player, and the time elapsed is a full 30 sec, start round
if (CountAlivePlayers() > 1 && roundActive == false) then
if ((roundCountdownStartTime + countDownLength) < CurTime()) then
roundStartTime = CurTime()
roundActive = true
phase = 1
net.Start("round_active")
net.WriteBool(true)
net.Broadcast()
timer.Remove("CountdownTimer")
self:StartRoundTimer(roundLength)
PrintMessage(4, "The Round has begun. Last player to survive wins!")
end
end
-- if the round is active, do this stuff
if (roundActive == true) then
-- if its been 10 sec since round start, spawn the ghoul
if ((roundStartTime + initialGhoulDelay) < CurTime() && phase == 1) then
SpawnGhoulInitial()
phase = 2
end
-- if its been 30 sec since round start, spawn in weapons for players
if((roundStartTime + spawnWepDelay) < CurTime() && phase == 2) then
SpawnWeps()
phase = 3
end
-- if its been 120 sec (2 minutes) since round start, begin the ghoul madness
if((roundStartTime + ghoulMadnessDelay) < CurTime() && phase == 3) then
SpawnGhoulMadness()
phase = 4
end
end
end
However, I do call my GM:DoRoundSpawn function in my init.lua file:
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
include("roundsystem_2.lua")
util.AddNetworkString("round_timer")
util.AddNetworkString("round_active")
roundActive = false
playerSpawns = {--table of spawnpoints, too much to paste for stackoverflow}
function GM:PlayerSpawn(ply)
ply:SetGravity(.85)
ply:SetMaxHealth(100)
ply:SetupHands()
ply:SetWalkSpeed(250)
ply:SetRunSpeed(450)
ply:SetModel("models/player/Kleiner.mdl")
local random_spawn = math.random(#playerSpawns)
ply:SetPos(playerSpawns[random_spawn])
--this section basically will make it so if someone spawns
--in while the round is active, they will stay dead until
--the round is no longer active
print("Player: " .. ply:GetName() .. " has spawned!")
if (roundActive == true) then
ply:KillSilent()
return
end
self:DoRoundSpawn()
end
function GM:PlayerDeathThink(ply)
if (roundActive == false) then
ply:Spawn()
return true
else
return false
end
end
I've combed through both of these files a couple times over and I cant for the life of me figure out why deaths don't break the game until after the nextbot spawn happens. Is there something here I'm missing?
I am developing a remastered game, and want to figure how the round will stop if all players were killed or reset, and display a message for all players, "Game over", and returns back to the lobby intermission.
This is my main script for the game I am making. In the past, I've tried If statements in this script, however it didn't work and it ends up not displaying my another scripts as well.
local s = script.Stat
t = 0
while true do
local plrs = game.Players:GetPlayers()
t = 15
repeat
t = t - 1
s.Value = t.." seconds left"
wait(1)
if plrs == 0 then
s.Value = "Alive: "..plrs
end
until t == 0
s.Value = "Game over!"
wait(5)
end
I expect the script to find all the players in a round that is killed, but the output seems to work only for the seconds remaining.
If you add a listener to every player's Humanoid.Died signal, you can keep track of which players die in a round.
In a Script, write something like this :
-- use a map of player names to keep track of who is still alive
local playersAlive = {}
local listenerTokens = {}
local SPAWN_LOCATION = CFrame.new()
-- make some helper functions
local function getCountPlayersAlive()
local numPlayersAlive = 0
for playerName, isAlive in pairs(playersAlive) do
numPlayersAlive = numPlayersAlive + 1
end
return numPlayersAlive
end
local function beginRound()
-- get the list of all of the players in the round
local allPlayers = game.Players:GetChildren()
for _, player in ipairs(allPlayers) do
-- find the associated Player in the workspace
local character = game.Workspace:FindFirstChild(player.Name)
if character then
-- add an event listener for when they die
local humanoid = character:FindFirstChildOfClass("Humanoid")
local deathSignalToken = humanoid.Died:Connect(function()
-- when this player dies, signal that there is one less player
playersAlive[player.Name] = nil
end)
-- add the player to the list of players for this round
playersAlive[player.Name] = true
-- move the players into the game arena
player.Character:SetPrimaryPartCFrame(SPAWN_LOCATION)
-- disconnect this signal at the end of the round
table.insert(listenerTokens, deathSignalToken)
else
-- could not find the player, make them sit out this round
end
end
end
local function endRound()
-- clean up the game board, reload the players, do whatever you have to
-- clean up any death signals from last round
for _, token in ipairs(listenerTokens) do
token:Disconnect()
end
end
-- set up the game loop
local GAME_LENGTH = 30 -- seconds
local INTERMISSION_LENGTH = 15 -- seconds
spawn(function()
while true do
-- phase 1 - set up the round
beginRound()
-- phase 2 - play the round
local gameTimer = GAME_LENGTH
while gameTimer > 0 and getCountPlayersAlive() > 0 do
local count = getCountPlayersAlive()
print(string.format("Time Left : %d - Players Left : %d", gameTimer, count))
wait(1)
gameTimer = gameTimer - 1
end
-- phase 3 - celebrate winners and clean up the previous round
endRound()
-- phase 4 - intermission before the next round
wait(INTERMISSION_LENGTH)
end
end)
Sorry for the lengthy example, but I hope this helps.
How i can add to table only max 10 users ?
Because i saving scoretbl in txt, and this file have more 100 lines :/ So i want save only 10 users.
I don't know how can i check there is username in the table, and don't add this user or not this username in the table and add it?
Example:
local scoretbl = {}
local num = 0
for i=1, 10 do
table.insert(scoretbl,{'Name '..i, 100 + num})
num = num + 100
end
local function AddToTable(name, score)
if table.HasValue(scoretbl,name) then return end // hmm its not work ?
table.insert(scoretbl,{name, score})
end
AddToTable('User 55', 5454)// 11 user
AddToTable('User 55', 5454)// check: only one username in table
AddToTable('User 32', 5454)// 12 user
local function ShowOnly10()
table.sort( scoretbl, function( a, b ) return a[2] > b[2] end )
//table.remove(scoretbl,#scoretbl) remove last index in table, if i need only 10 value, i need delete in cycle ?
for k, v in pairs(scoretbl) do
print(k ,v[1], v[2])
end
end
ShowOnly10()
// upd: maybe its fix username ?
local function AddToTable(name, score)
for k, v in pairs(scoretbl) do
if v[1] == name then return false end
end
table.insert(scoretbl,{name, score})
end
I recommend that you make use of Lua's hashtable, v.name and v.score are easier to read than v[1] and v[2].
The function table.HasValue does not exist. You have to write your own.
When you want to print only the first ten elements, you should only iterate over the first ten (or up to the length of the table if it is less than ten elements long).
Line comments in Lua start with --, not //.
local scoretbl = {}
for i = 1,10 do
table.insert(scoretbl, { name = 'Name '..i, score = 100*i })
end
local function AddToTable(name, score)
-- Walk the whole table to find whether a name exists
for i,v in ipairs(scoretbl) do
if v.name == name then
-- if the record is present, update it
scoretbl[i].score = score
return
end
end
-- Insert new record
table.insert(scoretbl, { name = name, score = score })
end
AddToTable('User 55', 5454) -- 11 users
AddToTable('User 55', 5454) -- check: only one username in table
AddToTable('User 32', 5454) -- 12 users
local function ShowOnly10()
table.sort(scoretbl,function(a,b) return a.score > b.score end)
for i = 1,math.min(#scoretbl,10) do
print(i, scoretbl[i].name, scoretbl[i].score)
end
end
ShowOnly10()
I'm trying to implement a speed-based turn system for a roguelike. I've set up a Mob class using metamethods, so that assigning the following to a variable will spawn a mob into the map at certain grid coordinates:
function Mob:spawn(x,y,m)
local mob = {}
setmetatable(mob, Mob)
mob.x = x
mob.y = y
mob.is_monster = m
return mob
end
Once that's done, I call the following:
function Mob:roll_call()
who_is_here[self.y][self.x] = self.is_monster
self.turn_counter = self.turn_counter * math.random(0.9, 1.1)
table.insert(allTurnCounters, self.turn_counter)
end
This puts the mob's self.turn_counter into a table. Meanwhile, in another module, I've defined these two functions, the heart of the problem:
function turn.decrement_counters(dt) -- runs in Dungeon.update(dt) and subtracts from allTurnCounters
for i = 1,#allMobsSpawned do
if allTurnCounters[i] <= 0 then
allTurnCounters[i] = 0
turn_active = true
whose_turn = i
return
elseif allTurnCounters[i] > 0 then
allTurnCounters[i] = allTurnCounters[i] - (10 * dt)
end
end
end
function turn.whose_is_it() -- called when an entry in allTurnCounters goes zero
if whose_turn == 1 then -- spots 1 and 2 in the spawn list are only ever for players
player1.my_turn = true -- turns on player 1's keys
elseif whose_turn == 2 then
player2.my_turn = true -- turns on player 2's keys
elseif whose_turn >= 3 then -- above 3 we're in monster territory
end
end
I've decided that the first two instances of Mob to be initialized will always be players 1 and 2, assigned to the variables player1 and player2, respectively. And, as it is, it works fine for passing control back and forth between players! But obviously, that's not enough for a fully-featured game. I need monsters, too.
The allTurnCounters table gets new entries, in order, from every mob that spawns (a class which includes both the players and the monsters, so they can share stats). Here's my question: How can I get Lua to dynamically retrieve the name of the table associated with a given turn_counter/value within that table, and use it to take turn priority, even in the event that I don't know what's been procedurally spawned ahead of time or what place it will occupy in the spawn order?
I have 3 ideas, none of which I'm solid on how to implement. One method would be something like sending the entire instance table to another table, rather than just their turn_counters, and then somehow grabbing a pair of values (the table itself and my_turn within the table), updating my_turn's value directly.
Another method might be to use the environment _G... somehow. I'm still poring over Chapter 14 of PiL trying to adapt it to my purposes, but value = _G[varname] seems to be a powerful bit of code I might be able to use for this. Not sure how, just yet.
My last idea was to maybe write some kind of string-sensing find-replace that can grab some other value in each mob's table and then pop it on the front of my_turn. Like, assigning some value with a known pattern for each mob type, that I can use in a string.find and then a string.gsub, to like... manually make the code line read as intended. Seems inelegant, though.
I had good luck with asking my previous Lua/Love2D question here, so I figured let's toss it out there while I'm thinking!
Here is my suggestion for how you should implement this:
Instead of allTurnCounters[i], give mobs a turn_counter property and use allMobsSpawned[i].turn_counter. Then, delete allTurnCounters.
Instead of storing the mob number in whose_turn, store the mob itself. (Note: when I say "the mob itself", it's short for "a reference to the mob itself")
So instead of
whose_turn = i
you would have:
whose_turn = allMobsSpawned[i]
Now whose_turn holds the mob whose turn it is. You can easily check whose_turn == player1, whose_turn == player2, etc. As a bonus, it doesn't rely on the players being the first mobs any more.
You can access the mob's properties through whose_turn - if whose_turn == player1 is true for example then whose_turn.x accesses the same field as player1.x
Here's a somewhat janky solution that can be made more elegant by incorporating the method from the other answer. This is what I came up with on my own while waiting for an answer.
-- in "Mob.lua" module
function Mob:roll_call()
who_is_here[self.y][self.x] = self.is_monster
self.turn_counter = self.turn_counter * math.random(0.9, 1.1)
table.insert(allMobs, {self.name, self.turn_counter})
if self.is_monster == true then
table.insert(allMonsters, {self.name, self.turn_counter})
end
end
function Mob:move(dx, dy)
who_is_here[self.y][self.x] = nil
self.x, self.y = self.x + dx, self.y + dy
who_is_here[self.y][self.x] = self.is_monster
self.turn_counter = 1 * self.speed
for k,v in ipairs(allMobs) do
if v[1] == self.name then
v[2] = self.turn_counter
end
end
self.my_turn = false -- flags turn is over
turn_active = false -- flags no active turn
end
-- in "turn.lua" module
function turn.decrement_counters(dt)
if turn_active == false then
for k,v in ipairs(allMobs) do
v[2] = v[2] - (10 * dt)
if v[2] < 0 then
v[2] = 0
turn_active = true
whose_turn = v[1]
return
end
end
else turn.whose_is_it()
end
end
function turn.whose_is_it()
if whose_turn == player1.name then
player1.my_turn = true
elseif whose_turn == player2.name then
player2.my_turn = true
elseif whose_turn == ant1.name then
ant1.my_turn = true
end
end
turn.whose_is_it() is the part that will need refining. If I use immibis' method of assigning allMobs[i].turn_counter, that will simplify things considerably and allow for future expansion. This answer only works for player1, player2, and an ant called ant1 in particular.