I’ve been using this method for a very long time. I want to migrate it to be Luau compatible. My methods, CharValid and CharAlive, should guarantee the existence of certain properties similar to assert() or if statements.
--!strict
local module = {}
function module:CharValid(char: Instance?)
return char and char.Parent and char:IsA("Model") and char.PrimaryPart
end
function module:CharAlive(char: Instance?)
return module:CharValid(char) and char:FindFirstChild("Humanoid") and char.Humanoid.Health > 0
end
-- use case: kill brick
local function onTouch(hit: BasePart)
if module:CharAlive(hit.Parent) then -- CharAlive implies hit.Parent, humanoid, and PrimaryPart
hit.Parent.Humanoid:TakeDamage(100)
end
end
You can see it’s not very happy with me directly accessing hit.Parent.Humanoid even though the :CharAlive function guarantees it won’t error.
-- use case: kill brick w/o :CharAlive
local function onTouch(hit: BasePart)
if hit.Parent and hit.Parent:IsA("Model") and hit.Parent.PrimaryPart then -- CharAlive implies hit.Parent, humanoid, and PrimaryPart then
local humanoid = hit.Parent:FindFirstChild("Humanoid") :: Humanoid
if humanoid and humanoid.Health > 0 then
humanoid:TakeDamage(100)
end
end
end
Here’s how I did it to make the Luau linter happy. It defeats the purpose, since there’s now 2 separate if statements with a :FindFirstChild cast. I want to turn all of that mess into a single function check similar to :CharAlive that’ll keep the Luau linter happy. How can I do so?
As far as I know, luau doesn’t have an in-built type for player characters. The only way that comes into my mind is to create your custom type and use it instead like this:
export type Character = {
PrimaryPart:Instance?,
Humanoid:Humanoid,
}
export type Character = {
PrimaryPart:Instance,
Humanoid:Humanoid,
}
function module:CharValid(char: Instance?)
return char and char.Parent and char:IsA("Model") and char.PrimaryPart
end
function module:CharAlive(char: Instance?)
return module:CharValid(char) and char:FindFirstChild("Humanoid") and char.Humanoid.Health > 0
end
-- use case: kill brick
local function onTouch(hit: BasePart)
if module:CharAlive(hit.Parent) then -- CharAlive implies hit.Parent, humanoid, and PrimaryPart
local char = hit.Parent :: Character
hit.Parent.Humanoid:TakeDamage(100)
end
end
I usually just get around this by validating a local variable using :FindFirstChildOfClass()
function charAlive(char: Model): boolean
local humanoid = char:FindFirstChildOfClass("Humanoid")
if not humanoid then return false end
--if statement works and the type check will assume it exists
--autocomplete works
return humanoid.Health > 0 and humanoid:GetState() ~= Enum.HumanoidStateType.Dead
end
the type checker doesnt assume children exist for any given instance, only for its own properties (given that the instance exists)
it also doesn’t like like single line assertions that much.
The only real way to access it with the dot is by extending the type
type Character = Model & { --extends the type
Humanoid: Humanoid,
PrimaryPart: Part
}