Interaction Module - Nobonet/sm64-roblox GitHub Wiki
The interaction module (located at SM64.Client.Game.Interaction) provides some quick ways to affect Mario in certain ways, such as Knockback & damage.
For the sake of keeping things simple, I'll be avoiding the SM64 Objects shenanigans to an extent. Feel free to implement it yourself if you wish to (goodluckhavefun).
This module is based on interaction.c
Like in SM64, this module needs objects. But to keep things simple, I've made it so you can use these methods WITHOUT the confusing and complicated object system from SM64 by allowing you to input simple dictionary tables. However, you're free to use your own object solutions (with metatables, ported code from decomp, etc) without issues. It attempts to detect them, but do note that the way it detects objects is by checking if it's a table and if it has a metatable (pseudo-objects don't have metatables).
We'll call these simple table objects Pseudo-objects here. How it works:
- The interaction module attempts to automatically detect pseudo-objects and transform them (or, emulating some object values that SM64 uses, atleast in decomp).
- It uses a key named RbxPart to translate
Position
,HitboxRadius
,HitboxHeight
,FaceAnglePitch
,FaceAngleYaw
,FaceAngleRoll
, etc. These are in SM64 units automatically. - If there's no RbxPart, some of these values will be set to a default unless defined.
- Do note that the position is the bottom of the Part's extents, or the very bottom of the BasePart (
part.Position - partExtentsSize * 0.5
).
What it should look like, and what values it can accept
local PseudoObject = {
-- [BasePart] For auto-translating values.
RbxPart = Part,
-- [number] For damage, or coins. Defaults to 0 if nil.
DamageOrCoinValue = 1,
-- [number?] For InteractCap. Pseudo-objects only, this is not a real object value in decomp.
CapFlag = MarioFlags.METAL_CAP,
-- [boolean?] For InteractBounceTop. Makes Mario go on the twirling action and go upwards when successfully stomped.
TwirlBounce = false,
-- If you didn't set RbxPart, you can do this OR let the function set it to a default (all zero).
Position = Vector3.new(0, 64, 0),
}
If necessary, you can use Interaction.PreparePseudoObject(o: Object): Object
to quickly apply emulated values to the pseudo-object.
Ways to call Interaction
- When using Pseudo-objects, it would otherwise look like this:
-- Create our pseudo-object
local pseudoObj = {
RbxPart = part, -- Part to use for object value emulation
DamageOrCoinValue = 4, -- Some special arg
}
-- Call the interaction function we wish to use
Interaction.InteractNAME(Mario, pseudoObj)
-- Calling without variable
Interaction.InteractNAME(Mario, {
RbxPart = part,
DamageOrCoinValue = 4,
})
- If you have your own Object class, or something along these lines, you can pass that object class instead. However, it may depend on how you did it. I specifically used metatables, and different variable names/types (Numbers PosX, PosY and PosZ have been merged to Vector3 Position, and none of the variables are prefixed with o, like oPosX). If you're not using metatables, you may need to change the code of the module so that it accepts any table, in the function
isPseudoObject
.
-- Custom SM64 object class
local object = Object.new()
-- Call the interaction function we wish to use
Interaction.InteractNAME(Mario, object)
why is it even called a "pseudo-object"?
In order to understand how to use this module, you'll need:
- At-most basic understanding of programming languages, most importantly Luau (and maybe C? Find out next time!)
- If you're just getting started, see the scripting documentation, or browse the internet for guides. Don't dive right into this stuff, you'll end up getting confused.
- Basic understanding of the SM64 decomp's code and how it works
- Good self-esteem and confidence about your IQ
Scripts that use this module have to follow these conditions, sometimes not explicit:
- These can only be ran on the Client-side (via LocalScript or a Script with the RunContext set to Client).
- You need to provide Mario's data table. You can quickly access it via
shared.LocalMario
. - On some occasions you may need to use other utilities like Util.ToSM64 for converting units, the Enums dictionary, etc. for these methods. Careful!
All of these methods take these arguments:
- Mario m: Mario's data table (You can quickly access it from shared.LocalMario)
- Object o: The object, or pseudo-object table
- number? interactType: Not really used. Seems like InteractGrabbable only uses it
You'll need to require the Interaction module, to access its contents.
local Interaction = require(workspace.SM64.Game.Interaction)
Relevant Values: number DamageOrCoinValue
Applies damage to Mario and sets him in a knockback state, depending on the amount of damage. Returns a boolean indicating whether or not it succeeded.
-- InteractDamage example
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractDamage(shared.LocalMario, {
RbxPart = part,
DamageOrCoinValue = 4,
})
if interactionSuccess then
print("Inflicted damage upon Mario")
else
print("Mario is invulnerable, damage failed")
end
Relevant Values: <none>
Sets Mario on the Burning-while-running-and-stuff action. Returns a boolean indicating whether or not it succeeded.
You may skip the Object o
argument.
-- InteractFlame example
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractFlame(shared.LocalMario, {
RbxPart = part,
})
if interactionSuccess then
print("mario is BURNING!!!!!")
else
print("Mario is invulnerable, failed to set him on fire. Would've been Funny and cool and awesome if it did set him on fire ... Try again some other time .")
end
Relevant Values: <none>
Sets Mario on the Riding shell action. Returns a boolean indicating whether or not it succeeded.
-- InteractKoopaShell example
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractKoopaShell(shared.LocalMario, {
RbxPart = part,
})
if interactionSuccess then
print("YOU GO MARIO!!")
else
print("Not yet fit for skating sorry")
end
Relevant Values: number CapFlag
Makes Mario pick up an ability cap. Returns a boolean indicating whether or not it succeeded.
-
o can be either an Object table, or a number flag if you don't wish to pass any. For the number flag, it accepts these values below:
-
0x01
MarioFlags.NORMAL_CAP -
0x02
MarioFlags.VANISH_CAP -
0x04
MarioFlags.METAL_CAP -
0x08
MarioFlags.WING_CAP
-
-- InteractCap example
local Mario = shared.LocalMario
local MarioFlags = Mario.Enums.MarioFlags
local part: BasePart = script.Parent
-- You can pass an Object table, or a number flag.
-- When passing a number, interactionSuccess = Interaction.InteractCap(Mario, MarioFlags.WING_CAP)
local interactionSuccess = Interaction.InteractCap(Mario, {
RbxPart = part,
CapFlag = MarioFlags.WING_CAP,
})
if interactionSuccess then
print("https://www.youtube.com/watch?v=I3SzwS2Coas")
else
print("Failed to pick up wing cap")
end
Relevant Values: <none>
Sets Mario on the Star-Or-Key pickup action. Returns a boolean indicating whether or not it succeeded.
NOTE: Does nothing for saving which stars were collected, you have to do this on your own!
-- InteractStarOrKey example
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractStarOrKey(shared.LocalMario, {
RbxPart = part,
})
if interactionSuccess then
print("congraudlations you got new stare")
-- Like said before, you have to do it yourself if you wish to save stars
-- method 1: firing
-- it's up to you to care about cheaters
-- (you could probably just add a sanity check for reasonable touch distance i guess?)
Server.SetStarCollected:FireServer(StarID, true)
-- method 2: some global client-side table
playerData.StarsCollected[StarID] = true
else
print("Can't do that right now buddy.")
end
Relevant Values: <none>
Makes Mario climb on an object that should act as a vertical pole, like trees or Dire Dire Docks moving poles. Returns a boolean indicating whether or not it succeeded.
-- InteractPole example
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractPole(shared.LocalMario, {
RbxPart = part,
})
if interactionSuccess then
print("I ran out of ideas for print messages")
end
-
Usage notes
- When using pseudo-objects, poles use extents size, so don't worry about strict part rotation (If the part's size is
16, 1, 1
and the part's rotation is0, 0, 90
it will still work just fine) - Poles cannot be slanted, can only apply vertical movement
- Supports horizontally moving objects, but not vertically moving
- When using pseudo-objects, it's recommended for the pole part to have CanCollide off, and the hitbox to be bigger (use an invisible touch part OR a mesh along with scale)
- When using pseudo-objects, poles use extents size, so don't worry about strict part rotation (If the part's size is
Relevant Values: number DamageOrCoinValue
Makes Mario either bounce on the object, attack the object, or get hurt by the object. Returns true indicating if Mario (maybe) got damaged by the object or not. Note: If Mario bounced on the object, he will be teleported to the very top of it.
local part: BasePart = script.Parent
local interactionSuccess = Interaction.InteractBounceTop(shared.LocalMario, {
RbxPart = part,
DamageOrCoinValue = 1,
})
if interactionSuccess then
print("mario got pwned")
else
print("YEOUCH")
end
Relevant Values: number DamageOrCoinValue
Adds to Mario's NumCoins value and increments Mario's HealCounter depending on the DamageOrCoinValue. Always returns false.
local part: BasePart = script.Parent
Interaction.InteractCoin(shared.LocalMario, {
RbxPart = part,
DamageOrCoinValue = 1,
})
Let's make a part that damages Mario when touched, using a RunContext.Client Script.
Careful with the Update hz difference between Roblox and SM64, incorrect things may happen!
- Let's define our LocalPlayer (us). If you haven't remembered in the slightest somehow, this must be ran in a Client script. Then, declare the part that we want to listen to the Touched signal to.
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
local Part: BasePart = script.Parent
-- [!!!] VERY IMPORTANT STEP
-- This is a pseudo object. It's an emulated SM64 object that tries to
-- resemble a real object, but since it's a simple table with RbxPart,
-- it will work like it should in SM64 because the Interaction module
-- will take care of it automatically by "faking" values.
local PseudoObject = {
RbxPart = Part,
DamageOrCoinValue = 1,
}
- Next, let's define the stuff from the SM64 folder. For this, we just need Interaction and Util.
local SM64 = workspace.SM64 -- Change path if you put it somewhere else
local Interaction = require(SM64.Client.Game.Interaction)
local Util = require(SM64.Client.Util)
- Now we just get Mario from the
shared
table.
-- You can do this if you worry about this script loading before Mario does.
local Mario; do
repeat task.wait() until shared.LocalMario
Mario = shared.LocalMario
end
-- If you just program good, you don't.
local Mario = shared.LocalMario
- We've prepared every variable we need so far. Let's go on and make a function that runs once the Touched event has been fired.
local function onTouched(hit: BasePart)
local LocalCharacter = LocalPlayer.Character -- Our client character
local Humanoid = hit.Parent:FindFirstChildWhichIsA("Humanoid") -- The humanoid that touched this part
-- We want to check if the Character that touched this part was us, the LocalPlayer.
if Mario and (LocalCharacter and Humanoid and Humanoid:IsDescendantOf(LocalCharacter)) then
-- Apply damage on Mario, if possible. We make sure the Position is in SM64 units.
-- Let's say we're doing just 1 slice of damage.
-- Mario and this function already handles i-frames on their own, so we don't have to worry.
Interaction.InteractDamage(Mario, PseudoObject)
end
end
- That's it, let's hook it up, then watch the magic happen. Unless there was a mistake on your end...
Part.Touched:Connect(onTouched)
Need the intellisense autocompletion for Mario? You can fetch the type here. (Not sure what this is? Check out the Type checking documentation.)
-- Fetch SM64 and the Mario module.
local SM64 = workspace.SM64
local Mario = require(SM64.Client.Mario)
-- Get our type from here.
type Mario = Mario.Mario
-- Done! The auto-complete should detect it automatically.
local Mario: Mario = shared.LocalMario
Go ahead and make something cool with these!
You can always come back here if you forgot something, or if there's something new to see.