#HELP! Issue with ModuleScripts and Server vs. Client

16 messages · Page 1 of 1 (latest)

soft shuttle
#

Hello! I made a pretty dumb mistake. I've been working on a big project for about two weeks now and I've been storing values that are supposed to be server-wide on two different moduleScripts within the ReplicatedStorage workspace. I didn't realize it at first until after I did a ton of work, but the ReplicatedStorage only updates things on the client side. I tried modifying and using Remote Events to change the server's moduleScript but I'm completely out of ideas. I've tried to get it working but I'm not sure what to do.

How can I make my ModuleScript be something that's server-wide and updateable as the game continues on?

viral pelican
#

Hey, I made a similar mistake in one of my first games.

It looks like you've already realized the mistake: ModuleScripts are independent on different clients & the server. See 3rd paragraph of Roblox docs: https://create.roblox.com/docs/reference/engine/classes/ModuleScript

Basically each client has its own copy of the ModuleScript and any variables you modify on client only exist in that copy of the ModuleScript. The server doesn't see the changes.

An idea I used was to make IntValues in the workspace that act as server-wide values.

If the client does something that should update the server-wide values you can fire a RemoteEvent from the client. The server listens to this RemoteEvent and updates the workspace.IntValue.Value. Then Roblox will tell all the clients about the changed IntValue for you.

If the clients need to do something when it changes (like update a GUI with a server-wide score) you can listen to the intValue changed in a LocalScript: https://create.roblox.com/docs/reference/engine/classes/IntValue#Changed

Just always keep in mind the server is the owner of the IntValues and clients can only ask the server to change it via RemoteEvents.

#

You could also have your server maintain your server-wide state in its ModuleScript. But each time it changes you broadcast the state to all clients and they update their own copy of ModuleScript with the new data.
https://create.roblox.com/docs/scripting/events/remote#server-all-clients

Something like:
Client LocalScript -> server: scoreChangeEvent.FireServer(value)

ServerScript:

scoreChangeEvent.OnServerEvent:Connect(function(_, value)
    yourModule.gameValues.score = value -- update value on server
    serverStateRemoteEvent:FireAllClients(yourModule.gameValues) -- broadcast to all clients the new server state
end)

(This code example makes it easy for a hacker to change the score because it changes server value to whatever the client says)

soft shuttle
# viral pelican Hey, I made a similar mistake in one of my first games. It looks like you've a...

Sorry for the long wait time between replies, and thank you so much for replying! If you don't mind me asking a few more questions about where to go from here -- the only issue about using IntValues is that the game I'm currently working on (which is a global stratedy game) requires storage on a ridiculously big scale. I can send some examples of the kinds of things I'm storing:

["American Samoa"] = {
        CountryName = "American Samoa",
        Quid = 600000000,
        Cores = {001, 002},
        Land = {1, 2},
        Government = "Democracy",
        Population = 0,
        Color = BrickColor.new(0.502, 0.734, 0.859),
        ClassPercentages = {upper = 0.02, middle = 0.50, lower = 0.48},
        Subjects = "",
        Overlord = "",
        Resources = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    },

would making an array and keeping it within a serverScript be a viable option for storing this vast amount of data? specifically, there are going to be around 100-200 countries which are copied and pasted from the code above, and I feel like updating that every time there's a change could result in alot of efficiency issues down the road

What I'm thinking about combatting this is that the remote events could specifically only the country that the remote event has as one of the parameters when it fires.

viral pelican
#

Hmm that's an interesting problem.

You could almost do it with int and string values. Since you have 200 of them you would certaintly want to make a "template" folder, and then have the server loop over a list of countries and clone the template.

A benefit here is your data over the wire will be incredibly small. Only specific value changes will be replicated to the clients.

#

The other approach of custom remote events to move the data:

would making an array and keeping it within a serverScript be a viable option for storing this vast amount of data
This is perfectly fine, that object you've shown is only like 500 bytes of memory. Your server will use 100kb of memory to keep track of 200 of them.

However 100kb is too much to be sending over the wire constantly.

You could have an event countryUpdatedRE:FireAllClients(countryData) that sends a single countries data only when the server changes that countries values

viral pelican
#

Here's an idea that might work to get started:

-- define Country types
type Government = "Democracy" | "Monarchy" | "Dictatorship"
type Resources = {
    stone: number,
    wood: number,
}
type Country = {
    name: string,
    quid: number,
    cores: {number},
    land: {number},
    government: Government,
    resources: Resources
}

-- create a map of country name to country object
local countries: {[string]:Country} = {}

local allCountryNames = {"American Samoa", "Switzerland", "Canada"}
local function GameStart() 
    -- initialize each country with starter values
    for _, countryName in pairs(allCountryNames) do
        countries[countryName] =  {
            name = countryName,
            quid = 0,
            cores = {1},
            land = {1},
            government = "Democracy",
            resources = {
                stone=100,
                wood=100
            }
        }
    end
    
    -- 100kb initial payload to all clients
    game.ReplicatedStorage.Events.InitializeCountries:FireAllClients(countries)
end

-- example function for when the country earns a resource
local function GiveStone(countryName, amt)
    local country = countries[countryName]
    country.resources.stone += amt
    
    -- 500b payload only when country updates
    game.ReplicatedStorage.Events.CountryUpdatedRE:FireAllClients(country)
end
soft shuttle
# viral pelican The other approach of custom remote events to move the data: > would making an ...

Thank you so much for your help!

I should ask, with my example provided above, would it be alright if I were to send the array of each country? as in, for functions on the client side that calculate things (i.e. working out the prices of resources in the in-game economy) it returns just the player's country and it's information so it's not sending all 200 countries at once? In addition, and I'm sorry in advance if my phrasing of this is a bit rough, but if I were to use the intValues system, would those be able to hold arrays and could I use a function that fires when the intValues update? I've heard of the function that can fire when a value is updated, but I haven't been able to use it since hearing of it

I think using the file system would be extremely efficient for what I'm trying to accoplish, if the update function is able to work with the intValue and it can store an array -- that would make checking a country's provinces really easy!

viral pelican
#

would it be alright if I were to send the array of each country
Preface: I'm just going off of my gut so I would encourage experimenting and testing the performance & watching the network traffic stats

You probably want to only send the entire array very rarely. Only when you really need to like a new player joins and needs to get caught up, or the game first starts.

What you said above is a good solution for keeping specific countries synchronized throughout the entire game:

What I'm thinking about combatting this is that the remote events could specifically only the country that the remote event has as one of the parameters when it fires.

--

There is no built-in support for arrays or tables in IntValue, StringValue, ObjectValue, discussion on the forums why it is hard to implement: https://devforum.roblox.com/t/tablevalue-instance/881832

You can serialize an array and put it in a StringValue, then deserialize on the client. Not saying this is the cleanest way but it works:

Server:

local HttpService = game:GetService("HttpService")

local coresArray = countries["Canada"].cores -- {001, 002}
local coresString = HttpService:JSONEncode(coresArray)
workspace.Canada.CoresStringValue.Value = coresString

LocalScript:

local HttpService = game:GetService("HttpService")

workspace.Canada.CoresStringValue.Changed:Connect(function(coresString)
local coresArray = HttpService:JSONDecode(coresString)  -- {001, 002}
-- do something with the coresArray
end)
soft shuttle
viral pelican
#

You're welcome, let me know how it goes!

soft shuttle
# viral pelican You're welcome, let me know how it goes!

sounds good! I was working on a workaround from having to send the entirety of the array by using a for-loop to go through the leaderboard and firing each client that is active on the server.

-- the while true is just a test case for right now, once I get this working I'll have it set to the game's tick system, which is a remote event that fires every 1 second and contains the in-game date
while true do
    local players = game.Players:GetPlayers()
    for _, player in ipairs(players) do
        if player:FindFirstChild("leaderstats") then
            local countryValue = player.leaderstats.Country.Value
            remoteEvent:FireClient(player, countryData[countryValue])
        end
    end
    wait(1)
end

I don't know if this will be efficient, and it also means that the second the player leaves the game, their country's data will stop completely and freeze. I can't tell if that's a good thing or bad thing honestly, with the game's calculations of how well the economy is going, all those algorythms are client side (from what I understand, that makes exploiting the game extremely easy -- but at the same time I don't know if the server can handle around ~30 player's in game economy)

from what I understand about Big-O notation, I think this is a pretty effecient script? as it's running at an O(N) and the max it will see is around 30 at most, but I don't know if using a for loop would be quick and this is supposed to send out info as fast as it can

viral pelican
#

There's nothing wrong with that approach. Ideally you only send data if/when it changes. Without knowing your game logic, I assume you will continuously send most players the same data over and over.

That said your approach is probably fine and you can always come back later and make it more efficient later if your game gets popular and starts having issues (it may never have issues and work good enough!)

hollow whaleBOT
#

studio** You are now Level 7! **studio

viral pelican
#

but at the same time I don't know if the server can handle around ~30 player's in game economy
Again not knowing your game logic but as long as you are mindful of big-o notation the server is usually the right place to run the game economy, and the clients just follow along with what the server tells them.

#

Don't worry about making the code perfect the first time, working is better than perfect!