Question about putting logic functions in shared file

I’m working on a party system and I’m making the logic functions right now.
This is what I have so far:



sv_party.lua

local plyMeta = FindMetaTable( "Player" )

--[GetParty]
function plyMeta:GetParty()

	return --Get players party name here, this is where I'm stuck. I don't know how to actually create the party framework.

end

--[isInParty]
function plyMeta:IsInParty()

	if self:GetParty() then
		return true --if GetParty returns NIL then we conclude they aren't in a party and IsInParty returns false.
	end

end


As shown above, I kind of have an idea of what I’m doing, I just don’t know where to even start on making a framework for this. I have a Create Party button on my Derma menu".
I think I should use tables to generate the party, and then for each party it has specific values like who the owner is, and who is in the party.
However, I don’t have any idea how to construct the table at all, let alonst so that there can be more than 1 party. If i remember correctly you have to assign the table to the player so that there can be more than one and i won’t just be grabbing info from the same first party that’s created.

On top of that, how would I go about using these functions on the clientside part so it doesn’t return an “Attempt to call method IsInParty” error when I use it on the client?

Thanks for reading.

When the person creates a party client side, send it and any relevant information (e.g. type, etc).

Once you get that information server side, create a party table that contains the party information, e.g. it would probably have a name index, creator index, players index (would contain all of the players within the party).

Put that table into a party table that would contain all parties, this way for example you can send all of the currently created parties to new clients.

Whenever a person requests to join a party set a variable on them that would contain that party id or name, after that add them to the table of players within that parties table.

Hopefully that’s understandable, that’s about the most basic structure I could give for something like this.

Define these functions in a client side file, or define it in a shared file so it’s created for both the server and client.

From a design standpoint, this would be a good start. It’d also be a nice introduction to metatables, if you haven’t used those before.


GROUPRANK =
{
	CREATOR = bit.lshift(1, 10),
	LEADER = bit.lshift(1, 5),
	MEMBER = 0;
}

Groups =
{
--[[
	[groupID] =  -- Set a metatable on this table that share common actions
	{
		[userID] =
		{
			rank = GROUPRANK.CREATOR
			somethingelse = "party!"
		}
	}
]]
}

local mt

-- You can use ipairs instead of pairs to get all groups
-- Alternatively, create a sub-table Groups.groups. I like the first option.
function Groups:Create(ply)
	local group = setmetatable({}, mt)
	group:AddPlayer(ply, GROUPRANK.CREATOR)

	table.insert(self, group)
	return group
end

-- Metatable for common group operations
mt =
{
	__index =
	{
		AddPlayer = function(this, ply, rank)
			rank = rank or GROUPRANK.MEMBER
			this[ply:UserID()] =
			{
				rank = rank
				-- Other stuff needed goes here
			}
			ply.Group = this
		end,

		GetPlayers = function(this, sanitize)
			sanitize = sanitize or true

			local ret = {}
			for plyID, data in pairs(this) do
				if sanitize then
					if not IsValid(plyID) then
						this[plyID] = nil
						continue
					end
				end
				table.insert(ret, Player(plyID))
			end

			return ret
		end
		-- Other common logic such as Set/GetPlayerRank, Disband and anything else that may be needed
	}
}



Note that this is all quick pseudocode / prototyping, you’ll have to do some work to actually make it usable.

Yes, using tables would be an efficient way to carry out this task. The general concept is that you don’t allow the client to do any of the logic behind creating a party, no matter what you do. If you want a secure addon or gamemode, that is.

Usually, your “getter” functions will reside inside of a shared file, so that both the client and the server can utilize the functions you have made. Although, it does not always have to be like that. It depends on the scope of what you are implementing. In your case, I’d put all of your “getter” functions inside of a shared file. So far, you have GetParty and IsInParty. That is why you got the “attempt to call method IsInParty” error. Any functions declared in a server-side file will not be accessible by the client.

Any functions regarding the creation, deletion, and joination (yes, I made a word up) of a party should be created in a server-side file. This way, the client should have no possible way to manipulate what logic the server performs, such as checking if the wanna-be party owner already has a party, or such things like that.

The only thing that will need to happen client-side is the receiving and sending of net messages. Basically, the client tells the server that they want a party and it gives the server the name of the party and who wants to create the party, and then the server receives that message and checks if that action is going to be allowed.

An example of how I would set up the table for a party system:

In sv_party.lua…



GM.Parties = {}

function meta:CreateParty(name)
    //Check if player is in party already, yadayada...
   //Don't forget to check if player owns a party already, too
   
    local t = {}
    t.ID = 1231 //I will leave the ID system up to you
    t.Owner = self
    t.Members = {}

    //Usually the indexes used to access a specific party would be the ID of the party.
    GAMEMODE.Parties[t.ID] = t 
end


If you are unfamiliar with the net system, please familiarize yourself with it here. Implementations like this can take a lot of time and effort. Please note that you will need a copy of the parties table on both the server and the client, therefore net messages would be in order.

I barely touched the surface of implementing a system like this. There are many ways to go about a system like this. Mine may not be the most efficient or understandable, but it does work if you implement the steps right. If you need extra help or explanation, please message me and we can talk.

If I defined them in the shared file, would I use “if SERVER then” before the funcs? If I don’t wont the client be able to mess with it?

You can if that function has no reason to be client side, although that would stop the client from messing around with any data in their state (as that’s impossible).

In your scenario, no. Since you’d need a copy of the parties table on both the client and the server, your functions would look something like this:



function meta:GetParty()
    if !self:IsInParty() then return nil; end

    local scope = nil
    if SERVER then
        scope = GAMEMODE.Parties
    else
        scope = GAMEMODE.Parties
    end

    for k, v in pairs(scope) do
        for _, p in pairs(v.Members) do
            if p == self then
                return v
            end
     end
end


Then somewhere else you could use the code like so (assuming ply is a valid player variable):



if ply:GetParty() != nil then
    print(ply:GetParty().Name)

    //or to get the members, assuming they're stored as player entites...
    for k, v in pairs(ply:GetParty().Members) do
        print(v:Nick())
    end
end


So I can’t do “if SERVER then” for IsInParty() because I need to run that clientside as well as serverside.
But if I do put it under shared so that both server and client handle it, then the client will be able to mess with the vars/data in the function. So what should I do?

And if i do that, the client can’t mess with any of that? What determines if the client will or will not be able to?

If you play your cards right, it won’t matter if the client modifies anything on their side. Shared “getter” functions will have no effect on what happens logically. Even if a malicious player were to modify the parties table on the client-side, you (ideally) will have written net functions that will not allow the direct modification of server-side variables.

The client can mess with anything you give it access to, although only in it’s state, it can’t mess with server side variables.

Take this code for example,



local money = {}
local meta = FindMetaTable "Player"

function meta:HasMoney(amount)
  return money[self] >= amount
end

function meta:SetMoney(amount)
  money[self] = amount
end


If I run this in a shared file, it will run on both the server and client states. This means it’s as if I had two files with the exact same contents, just one’s a server side file, and the other’s a client side file.

If I call



Entity(1):SetMoney(1000)


on the server, then



print(Entity(1):HasMoney(1000)


on the client, it will print false. The reason it will print false is because that call to SetMoney only changed the value on the server, likewise if we did that client side and the HasMoney check server side, it would print false.