I apologize for the trivial question. I cannot reference the same singleton data from another script. Is my setup correct?
Here is my setup:
- I have a module MobStatSingleton located in ServerScriptService.MobSystem
- I have 2 server scripts located in ServerScriptService which reference the module script in bullet 1
- server script namedMobSpawner
in ServerScriptService.MobSystem
- server script namedWeaponSystemListener
in ServerScriptService.WeaponSystem
The issue:
The mobspawner initializes the singleton with data through :RegisterMobs
. But when that same singleton is reference throught another script :TakeDamage()
rather than referencing the same self.mobStats table
it is empty as if though it initialized a new instance.
--MobSpawner.lua
local MobStatManager = require(ServerScriptService.MobSystem.MobStatManager )
--[[ this script spanws mobs, configures then and adds then to workspace.
this is the simplified version ]]
local mob = mobTemplate:Clone()
MobStatManager:registerMob(mob, {attack = 10, health = 200, maxHealth = 200})
mob.Parent = workspace
--WeaopnListener.llua
--[[ Event listener that invoked when player hits non-humanoid mobs which is why this exists and not using humanoid. Note: this is the simplified version ]]
local MobStatManager = require(ServerScriptService.MobSystem.MobStatManager )
local function processMeleeAttack(player:Player, humanoid: Humanoid | ModuleScript)
-- some code block here
MobStatManager:takeDamage(victim, damage)
end
---- Events
MeleeAttackRequest = Instance.new("RemoteEvent")
MeleeAttackRequest.Name = "MeleeAttackRequest"
MeleeAttackRequest.Parent = ReplicatedStorage.WeaponsSystem
MeleeAttackRequest.OnServerEvent:Connect(processMeleeAttack)
Here is the Single module MobStatManager
. I created 2 variations in my attempt. Is the singleton setup correctly? In either case I run into the same problem where I cannot reference data within the singleton instance.
MobStatManager
(Attempt 1 with typing):
--!nonstrict
--ServerScriptService.MobSystem.MobStatManager.lua
--[[
Stores all mob instances and stats
]]
local MobStatManager = {}
MobStatManager.__index = MobStatManager
-- Types -----------------------------------------
-- Define the type for the params table
type MobParams = {
maxHealth: number?,
health: number?,
attack: number?
}
-- Define the type for the mobStats table
type MobStats = {
[Instance]: {
maxHealth: number,
health: number,
attack: number
}
}
export type TypeMobStatManager = {
_mobStats: MobStats,
registerMob: (self: TypeMobStatManager, mob: Instance, params: MobParams?) -> (),
takeDamage: (self: TypeMobStatManager, mob: Instance, damage: number) -> boolean
}
-- Default mob stats
local DEFAULT_MOB_STATS = {
maxHealth = 100,
health = 100,
attack = 10
} :: MobParams
-- Attributes for syncing with the client
local ATTRIBUTES = {
HEALTH = "Health",
MAX_HEALTH = "MaxHealth"
}
-- Singleton instance
local instance: TypeMobStatManager? = nil
-- Private constructor
local function new(): TypeMobStatManager
local self = setmetatable({}, MobStatManager)
self._mobStats = {}
return self
end
-- Public method to get the singleton instance
function MobStatManager.getInstance(): TypeMobStatManager
if not instance then
instance = new()
end
return instance
end
--[[
Registers a new mob with default or provided attributes.
@param mob: Instance - The mob instance to register.
@param params: table (optional) - Contains mob attributes:
- maxHealth: number (default = 100)
- health: number (default = 100)
- attack: number (default = 10)
]]
function MobStatManager:registerMob(mob: Instance, params: MobParams?) : ()
if not mob then return end
-- Ensure params is always a valid table
params = params or {} :: MobParams
-- Populate _mobStats with provided or default values
self._mobStats[mob] = {
maxHealth = params.maxHealth or DEFAULT_MOB_STATS.maxHealth,
health = params.health or DEFAULT_MOB_STATS.health,
attack = params.attack or DEFAULT_MOB_STATS.attack
}
-- Sync data to client using attributes
mob:SetAttribute(ATTRIBUTES.MAX_HEALTH, self._mobStats[mob].maxHealth)
mob:SetAttribute(ATTRIBUTES.HEALTH, self._mobStats[mob].health)
end
--[[
Applies damage to a mob and updates its health.
@param mob: Instance - The mob instance to damage.
@param damage: number - The amount of damage to apply.
@return boolean - True if damage was applied, false otherwise.
]]
function MobStatManager:takeDamage(mob: Instance, damage: number) : boolean
if not mob or not damage then return false end
if not self._mobStats[mob] then
error("Error: E-012 Mob not registered on hit")
return false
end
-- Apply damage
local health = self._mobStats[mob].health
local newHealthValue = math.max(health - damage, 0)
self._mobStats[mob].health = newHealthValue
-- Update health attribute (for client UI)
mob:SetAttribute(ATTRIBUTES.HEALTH, newHealthValue)
-- Handle mob death
if newHealthValue <= 0 then
self._mobStats[mob] = nil -- Remove from `_mobStats`
-- TODO: Implement mob despawning logic
-- mob:Destroy()
end
return true
end
return MobStatManager
MobStatManager
(Attempt 2 without typing):
local module = {
_mobStats = {}
}
local DEFAULT_MOB_STATS = {
maxHealth = 100,
health = 100,
attack = 10
}
local STAT_ATTRIBUTES = {
HEALTH = "Health",
MAX_HEALTH = "MaxHealth"
}
function module:registerMob(mob: Instance, params: MobParams?) : ()
if not mob then return end
-- Ensure params is always a valid table
params = params or {} :: MobParams
-- Populate _mobStats with provided or default values
self._mobStats[mob] = {
maxHealth = params.maxHealth or DEFAULT_MOB_STATS.maxHealth,
health = params.health or DEFAULT_MOB_STATS.health,
attack = params.attack or DEFAULT_MOB_STATS.attack
}
-- Sync data to client using attributes
mob:SetAttribute(STAT_ATTRIBUTES.MAX_HEALTH, self._mobStats[mob].maxHealth)
mob:SetAttribute(STAT_ATTRIBUTES.HEALTH, self._mobStats[mob].health)
end
function module:takeDamage(mob: Instance, damage: number) : boolean
if not mob or not damage then return false end
if not self._mobStats[mob] then
error("Error: E-012 Mob not registered on hit")
return false
end
-- Apply damage
local health = self._mobStats[mob].health
local newHealthValue = math.max(health - damage, 0)
self._mobStats[mob].health = newHealthValue
-- Update health attribute (for client UI)
mob:SetAttribute(STAT_ATTRIBUTES.HEALTH, newHealthValue)
-- Handle mob death
if newHealthValue <= 0 then
self._mobStats[mob] = nil -- Remove from `_mobStats`
-- TODO: Implement mob despawning logic
-- mob:Destroy()
end
return true
end
return module