You can write your topic however you want, but you need to answer these questions:
-
What do you want to achieve? Keep it simple and clear!
So I’m making a Weapon system with OOP for the first time using components instead of inheritance(keep in mind im a noob at oop) -
What is the issue? Include screenshots / videos if possible!
So, basically what happens is that, when i pass the Weapon Object to the client, it only replicates the main object methods, and not the component methods.
Here’s my code:
Weapon Class(the main one):
local Weapon = {}
Weapon.__index = Weapon
local Get = shared.Get
local GunHandler = Get("GunHandler")
local AbilityHandler = Get("AbilityHandler")
local MeleeHandler = Get("MeleeHandler")
local TowerHandler = Get("TowerHandler")
local viewModelModule = Get("ViewModelModule")
local cachedModules = {}
local secretNum = 1
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Events = ReplicatedStorage:WaitForChild("Events")
local rfn_newWeapon = Events.Remote.newWeapon
local RunService = game:GetService("RunService")
local ServerStorage = nil
local AssetsStorage = nil
local isClient = RunService:IsClient()
if not isClient then
ServerStorage = game:GetService("ServerStorage")
AssetsStorage = ServerStorage.Assets
end
local Tools = ReplicatedStorage.Assets.TOOLS
function Weapon.Start()
for i,v in pairs(script:GetDescendants()) do
if v.ClassName ~= "ModuleScript" then continue end
local mod = require(v)
cachedModules[v.Name] = mod
end
end
function Weapon.Get(mod : string)
return cachedModules[mod]
end
local cachedWeapons = {}
function Weapon.GetWeaponObject(id : number)
return cachedWeapons[id]
end
function Weapon:Search(objectClass,nextTable)
for i,str in pairs(nextTable) do
if type(str) == "string" then
print(self.WeaponModel)
print(self.UniqueID)
print(self.Owner)
local newObjectClass = Weapon.Get(str).new()
newObjectClass:Initialize(self.WeaponModel,self.Owner,self.UniqueID)
objectClass:AddComponent(newObjectClass)
else
if i == "DATA" then
continue
else
print(self.WeaponModel)
print(self.UniqueID)
print(self.Owner)
local newObjectClass = Weapon.Get(i).new(str.DATA)
newObjectClass:Initialize(self.WeaponModel,self.Owner,self.UniqueID)
objectClass:AddComponent(newObjectClass)
Weapon:Search(newObjectClass,str)
end
end
end
end
function Weapon.new(template,plr)
secretNum += 1
local self = setmetatable({}, Weapon)
self.Name = template.Name or "Unnamed Weapon"
self.ViewModel = nil
self.Components = {} -- Holds behavior components
self.Owner = plr
self.WeaponModel = Tools.DefaultGun:Clone()
self.UniqueID = secretNum
self.MainArm = template.MainArm
return self
end
function Weapon:Serialize()
return {ViewModel = self.ViewModel,Name = self.Name,Components = self.Components,UniqueID = self.UniqueID}
end
function Weapon.Deserialize(data)
local self = setmetatable(data, Weapon)
self.Name = data.Name
self.Components = data.Components
self.ViewModel = data.ViewModel
self.UniqueID = data.UniqueID
return self
end
function Weapon:Initialize(template)
Weapon:Search(self,template.Components)
cachedWeapons[self.UniqueID] = self
print(self.UniqueID)
self.WeaponModel:SetAttribute("UniqueID",self.UniqueID)
print(rfn_newWeapon:InvokeClient(self.Owner,self:Serialize()))
self.WeaponModel.Parent = self.Owner.Backpack
end
local function searchClient(object,viewModel,id)
for i,v in pairs(object) do
v:InitializeClient(viewModel,id)
if v.Components then
searchClient(v.Components,viewModel,id)
end
end
end
function Weapon:InitializeClient(viewModel,id)
viewModel:Load()
searchClient(self.Components,viewModel,id)
end
function Weapon:GetContents()
print(self.Components)
print(self.ViewModel)
end
-- Add a component to the weapon
function Weapon:AddComponent(component)
table.insert(self.Components, component)
component:Initialize(component.UniqueID) -- Pass the weapon to the component
end
-- General Equip method
function Weapon:Equip(whichOne)
if isClient then
self.ViewModel:Equip()
end
print(self.Name .. " is equipped.")
for _, component in ipairs(self.Components) do
if component.OnEquip then
component:OnEquip()
end
end
end
-- General Unequip method
function Weapon:UnEquip()
if isClient then
self.ViewModel:UnEquip()
end
print(self.Name .. " is unequipped.")
for _, component in ipairs(self.Components) do
if component.OnUnequip then
component:OnUnequip()
end
end
end
-- Delegate Attack to components
return Weapon
This is an example of one of my components so far(W.I.P):
local RangedAttack = {}
RangedAttack.__index = RangedAttack
local rep = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")
local events = rep.Events
local assets = rep.Assets
local models = assets.MODELS
local rev_reloadGun = events.Client:WaitForChild("reloadGun")
local rev_noAmmo = events.Client:WaitForChild("noAmmo")
local rev_deployWeapon = events.Client:WaitForChild("deployWeapon")
local rev_showAmmo = events.Client:WaitForChild("showAmmo")
local rfn_shoot = events.Remote:WaitForChild("Shoot")
local placeHolderBullet = models.PLACEHOLDER_BULLET
function RangedAttack.new(template)
local self = setmetatable({}, RangedAttack)
self.Ammo = 1
self.ClipAmmo = template.ClipAmmo or 90
self.MaxAmmo = template.MaxAmmo or 30
self.Projectile = template.Projectile or placeHolderBullet
self.Interval = template.Interval or 0.1
self.ReloadTime = template.ReloadTime or 1
self.DeployTime = template.DeployTime or 0
self.Deploying = true
self.Components = {}
self.Name = script.Name
self.WeaponID = nil
spawn(function()
while wait() do
print(self.WeaponID)
end
end)
return self
end
function RangedAttack:Initialize(weapon,owner,ID)
print("initialized")
print(weapon)
print(owner)
print(ID)
self.Weapon = weapon
self.Owner = owner
self.WeaponID = ID
end
function RangedAttack:InitializeClient(viewModel,ID)
self.ViewModel = viewModel
self.WeaponID = ID
end
function RangedAttack:AddComponent(component)
table.insert(self.Components, component)
component:Initialize() -- Pass the weapon to the component
end
function RangedAttack:RefreshAmmo()
local gui = self.Owner.PlayerGui.ScreenGui
gui.TextLabel.Visible = true
gui.TextLabel.Text = self.Ammo .. "/" .. self.ClipAmmo
end
function RangedAttack:OnEquip()
if runService:IsClient() then
self:RefreshAmmo()
wait(self.DeployTime)
self.Deploying = false
else
wait(self.DeployTime)
self.Deploying = false
end
end
function RangedAttack:Activate()
print(self.WeaponID)
if RangedAttack:HasEnoughAmmo() then
end
end
function RangedAttack:UnEquip()
end
-- Triggered when the weapon attacks
function RangedAttack:Shoot()
if runService:IsClient() then
else
if self:HasEnoughAmmo() then
for i,v in pairs(self.Components) do
end
else
end
end
end
function RangedAttack:HasEnoughAmmo()
return self.Ammo > 0
end
return RangedAttack
And this is my WeaponHandler from the client:
repeat task.wait() until shared.LoadedPlayer == true
local get = shared.Get
local rep = game:GetService("ReplicatedStorage")
local contextActionService = game:GetService("ContextActionService")
local events = rep:WaitForChild("Events")
local rev_reloadGun = events.Client:WaitForChild("reloadGun")
local rev_noAmmo = events.Client:WaitForChild("noAmmo")
local rev_deployWeapon = events.Client:WaitForChild("deployWeapon")
local rev_showAmmo = events.Client:WaitForChild("showAmmo")
local rfn_shoot = events.Remote:WaitForChild("Shoot")
local rfn_newWeapon = events.Remote:WaitForChild("newWeapon")
local rfn_requestInteraction = events.Remote.doAction
local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local mouse = player:GetMouse()
local weaponClass = get("WeaponClass")
local viewModelClass = get("ViewModelModule")
local weapons = {}
local function getWeapon(child)
if not child then
return nil
end
local uniqueID = child:GetAttribute("UniqueID")
if uniqueID and child.ClassName == "Tool" then
local currentWeapon = weapons[uniqueID]
if currentWeapon then
return currentWeapon
end
end
end
local globalCurrentWeapon = nil
local canPass = false
local function InteractRequest(ActionName,inputState)
if inputState == Enum.UserInputState.Begin then
for i,v in pairs(globalCurrentWeapon.Components) do
if v.Name == ActionName then
if v.Cooldown == 0 and not globalCurrentWeapon.WeaponModel:GetAttribute("Deploying") or true then
rfn_requestInteraction:InvokeServer(globalCurrentWeapon.UniqueID,ActionName)
end
end
end
end
end
char.ChildAdded:Connect(function(child)
local currentWeapon = getWeapon(child)
if currentWeapon then
globalCurrentWeapon = currentWeapon
currentWeapon:Equip()
print("equipped " .. currentWeapon.Name .. " weapon!")
for i,v in pairs(currentWeapon.Components) do
if v.Name == "ClickComponent" then
print("click :D")
currentWeapon.Connection = child.Activated:Connect(function()
InteractRequest("ClickComponent",Enum.UserInputState.Begin)
end)
canPass = true
end
if v.Name == "QComponent" then
contextActionService:BindAction("QComponent",InteractRequest,false,Enum.KeyCode.Q)
end
if v.Name == "EComponent" then
contextActionService:BindAction("EComponent",InteractRequest,false,Enum.KeyCode.E)
end
end
end
end)
char.ChildRemoved:Connect(function(child)
local couldBeCurrentWeapon = char:FindFirstChildOfClass("Tool")
local result = getWeapon(couldBeCurrentWeapon)
if result then
globalCurrentWeapon = result
else
globalCurrentWeapon = nil
end
local currentWeapon = getWeapon(child)
if currentWeapon then
currentWeapon:UnEquip()
print("unequipped " .. currentWeapon.Name .. " weapon!")
for i,v in pairs(currentWeapon.Components) do
if v.Name == "ClickComponent" then
if currentWeapon.Connection then
currentWeapon.Connection:Disconnect()
currentWeapon.Connection = nil
end end
if v.Name == "QComponent" then
contextActionService:UnbindAction("QComponent",InteractRequest,false,Enum.KeyCode.Q)
end
if v.Name == "EComponent" then
contextActionService:UnbindAction("EComponent",InteractRequest,false,Enum.KeyCode.E)
end
end
end
end)
rfn_newWeapon.OnClientInvoke = function(SerializedMetaTable)
weapons[SerializedMetaTable.UniqueID] = weaponClass.Deserialize(SerializedMetaTable)
local currentWeapon = weapons[SerializedMetaTable.UniqueID]
print(currentWeapon.Components)
currentWeapon:InitializeClient(viewModelClass.new(currentWeapon.Name),SerializedMetaTable.UniqueID)
spawn(function()
print(rfn_newWeapon:InvokeServer(SerializedMetaTable.UniqueID))
end)
return "success"
end
rfn_shoot.OnClientInvoke = function()
return mouse.Hit
end
rev_reloadGun.OnClientEvent:Connect(function()
end)
rev_noAmmo.OnClientEvent:Connect(function()
end)
rev_showAmmo.OnClientEvent:Connect(function(ammo,clipAmmo,hide)
end)
rev_deployWeapon.OnClientEvent:Connect(function()
end)
And from the server:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local get = shared.Get
local events = ReplicatedStorage:WaitForChild("Events")
local moduleLoader = require(ReplicatedStorage.moduleLoader)
moduleLoader.Init(script:GetChildren())
moduleLoader.Init(ReplicatedStorage.Data:GetChildren())
moduleLoader.Init(ReplicatedStorage.Utils:GetChildren())
moduleLoader.Init(ReplicatedStorage.WeaponModules:GetChildren())
moduleLoader.Start()
local gunHandler = moduleLoader.Get("GunHandler")
local weaponClass = moduleLoader.Get("WeaponClass")
local playerModule = moduleLoader.Get("PlayerHandler")
local rev_reloadGun = events.Client.reloadGun
local rev_noAmmo = events.Client.noAmmo
local rev_deployWeapon = events.Client.deployWeapon
local rev_showAmmo = events.Client.showAmmo
local rfn_shoot = events.Remote.Shoot
local rfn_newWeapon = events.Remote.newWeapon
local rfn_requestInteraction = events.Remote.doAction
local weapons = {}
--tras crear un objeto con el WeaponClass, lo mando al cliente, para despues mandarlo a esta script
rfn_newWeapon.OnServerInvoke = function(UniqueID)
weapons[UniqueID] = weaponClass.GetWeaponObject(UniqueID)
return "success"
end
--Checkea que las acciones sean legales y las ejecuta con un delay(argumento quarto)
rfn_requestInteraction.OnServerInvoke = function(plr,UniqueID,actionName,delayTime)
local currentWeapon = weaponClass.GetWeaponObject(UniqueID)
if plr == currentWeapon.Owner then
for i,v in pairs(currentWeapon.Components) do
if v.Name == actionName then
if v.Cooldown == 0 and not v.WeaponModel:GetAttribute("Deploying") or true then
v.AlreadyUsing = true
spawn(function()
wait(delayTime or 0)
if actionName == "ClickComponent" then
if v.Name == "ClickComponent" then
v:Clicked()
end
end
if actionName == "EComponent" then
if v.Name == "EComponent" then
v:E()
end end
if actionName == "QComponent" then
if v.Name == "QComponent" then
v:Q()
end
end
end)
return true
end
end
end
end
end
local playerJoinFunc = function(plr)
plr:SetAttribute("Loaded","loading")
local char = plr.Character or plr.CharacterAdded:Wait()
playerModule.PlayerJoin(plr)
wait(3)
plr:SetAttribute("Loaded","loaded")
local tbl = gunHandler.Get("DefaultGun")
local newWeapon = weaponClass.new(tbl,plr)
newWeapon:Initialize(tbl)
end
game.Players.PlayerAdded:Connect(playerJoinFunc)
for i,v in pairs(game.Players:GetPlayers()) do
if v:GetAttribute("Loaded") == nil then
playerJoinFunc(v)
end
end
I tried everything, from serializing the data, passing it to the client, and then deserializing it, and more D: