How to apply tool pseudoclasses in practice?

Context

I can’t seem to find many resources on the topic of applying pseudoclasses beyond, well, making a simple pseudoclass that doesn’t quite extract the full benefits of streamlining behaviour for something.

Sample class, written to reflect what I know.
local Class = {}
local ClassMeta = {}

ClassMeta.__index = ClassMeta

function ClassMeta:ABTest()
    print(self)
end

function Class.new()
    local classObject = {}
    setmetatable(classObject, ClassMeta)
    return classObject
end

return Class

I find that this structure of OOP itself is pretty ugly and have generally conditioned myself not to use any kind of OOP in my code, since my pseudoclasses are easily achievable by writing a non-OOP ModuleScript or some kind of other piece of code.

Recently, I’ve wanted to get into trying out OOP once again for my code and becoming overambitious to step out of my comfort zone with scripting. I figure that I want to try to make use of a concept and learn about it more than leave it idly in the back of my mind.


Problem

Given the above context, I would like to begin by attempting to create a tool pseudoclass, specifically a weapon pseudoclass, that essentially handles all the scripting for weapons. The only problem is… I haven’t the slightest of where to begin or how to make things flow together.

I’ll post what I have for now.

Tool

Since my goal is to streamline behaviour for all weapons, I include nothing but a configuration ModuleScript in the tool. This holds data about the tool. Anything else that’s local to the tool can be added as well, but the main point is scripts - I only have a data module.

image

Scripts

And this is where I get stuck, somewhat.

So from what I’m assuming based on what I’m familiar with, this whole gun system would be running off of only two scripts. The server handler script would reside in ServerScriptService and be responsible for the gun’s traffic and server-side needs, while the LocalScript would be in StarterPlayerScripts.

I have absolutely no clue where the class would go. Assuming that the server is only handling class traffic and doesn’t have any need to access the class, my best guess would be ReplicatedStorage or StarterPlayerScripts under a class folder.

image

Class

Once I’ve set up my scripts, it’s time to write the actual class. I assume it’d be not far off from the code example in my Context section above. I’ve been reading through a couple of threads involving weapon classes but they’re above my level; they involve inheritance, superclasses and subclasses.

Naturally, I have no clue how to set up a weapon class. I’ve been reading a couple resources such as the all-popular All About Object-Oriented Programming, but I feel that isn’t sufficient to help me understand. I’m scouting resources but I’d like any links you guys might have for OOP.

Getting the Class Connected

Once somehow my class is connected, that’s where I need to begin working on the LocalScript specifically (again, this is assuming the server has no business accessing the gun class, unless this is something that needs to be done). This is also where I draw the blank.

How exactly am I supposed to get my gun class to scale perfectly with as many weapons as I want? I don’t know how to connect the ModuleScript to work with weapons or handle post-respawn setup. This is a single LocalScript in PlayerScripts that needs to work with all weapons and function as expected when a respawn, backpack rearrangement (adding/removing tools) or otherwise edit of tools happens.


I appreciate any help and pointers that can be given. Please do not spoon feed code (e.g. posting a chunk of code), this isn’t going to help me learn. I’m looking for wisdom as well as knowledge, not knowledge alone.

2 Likes

Keep the module for each type of weapon in serverscriptstorage, and module for local code inside replicatedstorage then call from the tool or connect a thread to the tool when tool is given to the player. I do this same sort of system for my tools in my games so I have one script for all also allows you to hide your code from exploiters who wish to steal your game. (Client code will need a localscript inside the tool.)

I’d recommend separating the damage function from all the scripts so you can edit it for all tools and keep weapon damage in an array with the main damage dealer so it’s all in one easy to edit place if you need to balance or adjust later on.

1 Like

Yes! I have been in that situation before and tried to create a class based system with a friend. Complete OOP structure is possible and has been done before just a lot of metatables. The next step would be create a Class Handling class that will take all the imported classes and feed them out when needed. With your tool example the way I would do it would be:

  • Have a main gun class that is universal for all guns
  • A class inside of the gun that extends that class and overrides certain properties, events, and functions
  • Then you import all modified gun classes and bang

Extending a class is just taking all of its properties and having the new classes __index check for its own properties and the extended class’s properties

Cough Cough shameless plug: https://github.com/TheFlamingBlaster/FDK
The way I did this in FDK is a BaseClass and FDK (Class Handler) system. This allowed code such as:

-- Gun Class --
return function()
    -- @class Gun
    local Gun = BaseClass:new("Gun")
    
    -- @import (whats needed)
    -- @import EventHandler
    local CFrameUtils = import("org.game.utils.CFrameUtils") -- add as many as you want
    local EventHandler = import("org.game.utils.EventHandler")

    Gun.Gun = function(self)
         self.damage = 1
         self.damageFalloff = 3
         self.range = 30
         self.bulletSpeed = 10

         self.bulletShot = EventHandler:new()
         self.scope = EventHandler:new()
         self.inspect = EventHandler:new()

         return self
    end

    -- etc
    -- Return the new class
    return Gun
end
-- M16 Class
return function()
    -- @class M16
    local M16 = import("org.game.Gun")():Extend("M16")

   -- @import (whatever)
    local CFrameUtils = import("org.game.utils.CFrameUtils")

    M16.damage = 10
    M16.damageFalloff = 15
    M16.range = 14

    M16.M16 = function(self, player)
        -- Create all the stuff for the player and the tool needed
         self.bulletShot:connect(function()
              --person shot weapon
        end)

        self.scope:connect(function()
             --person scoped
        end)

        self.inspect:connect(function()
              --person is inspecting weapon
       end)

        return self
    end

    return M16
end

And now we can do this!

local FDKM = game:GetService("ReplicatedStorage"):WaitForChild("FDK")
local FDK = require(FDKM)

FDK:wrapEnvironment()

local Gun, M16 = import(
    "org.game.Gun",
    "org.game.guns.M16"
)

local myM16 = M16(game.Players.LocalPlayer)

myM16.bulletShot:fire() -- fires the bulletShot event
myM16.scope:fire() -- fires the scope event
myM16.inspect:fire() -- fires the inspect event

-- Then you have acess to the damage and all of that lovely jazz

Some cool things i’ve done with FDK:
https://github.com/froghopperjacob/Log4Lua - Logging system
https://github.com/froghopperjacob/RobloxEvents - Event System
https://github.com/froghopperjacob/Roblox2D - Roblox 2D engine (in the works)

5 Likes

I don’t really care about exploiters, I care about my game working. This doesn’t really tell me much though.

How is the client supposed to receive gun information if I keep weapon modules on the server? What is the rationale for placing it in ServerScriptService over ReplicatedStorage or the Tool itself where it’s easier for me to modify?

Realistically, the client is going to be the one handling the gun’s information more than the server is. That’s everything from reloading time, fire rate, cycle, so on.

I don’t really understand this. I keep the ModuleScript that handles the client in ReplicatedStorage? Why not in PlayerScripts where it’s client-sided and the server doesn’t need to access it?

As for connecting a thread to tool, that is one of the very issues I have with making this work out. You can’t connect a thread to anything. I’m unsure of what you mean by this.

This is what explains the idea of a module in ReplicatedStorage, right? I’m trying to use as little scripts as possible. LocalScripts in guns aren’t scalable unless I use packages. I want to avoid all this. I set up my stuff a certain way in OP for a reason.


Worst comes to worst, I’ll try deciphering what you mean (unless you explain later as a response to this post) and use this as my go-to paradigm until I can figure out how to run things off of a single server and client entry point, then the rest in data ModuleScripts.

This actually helps a lot for me to understand! I’m not familiar with class inheritance without frameworks though and if that’ll improve my flow though (e.g. Weapon class, then maybe subclasses for weapon types for maximum scalability).

So essentially, what I have to do is set up my classes as needed, then the client requires that class and creates an object for a gun added to the backpack?

Note the way that I showed is not the best way of doing it because I would import all the guns in another class. For example a main class that imports all the classes. But to answer your question Yes! (also make sure to add remotes and make sure their secure!)

1 Like
  1. You call the module with a script, it’s server-sided anyways so might as well hide it from exploiters.

  2. Either way but it’s just local effects so doesn’t really matter.

  3. Yes, so you’d just call from the main script when the tool is used.
    Like so:
    m = require(RepStorage.LocalToolModule)
    m.M16(script.Parent)–Just normal local code and feed it the tool data, aka script.Parent and have the script do the reset, or even have it return the module based off the tool name and handle all of that inside of the module.

I set up each base tool like so:
image
All they do is call the modules then interact with the remote event.
(They are cloned out into the tool when given to the user.)

And if an exploiter steals it this is all they’ll get.
image
(Ofc they’ll get the local module but not the one stored on the server.)