< Back 

Sword Masters: Abilities

2024-09-15

Each Sword Masters player equips one ability out of seven. Every ability is a server module with the same four functions, and a handful of remotes lets the long ones ask the client for input halfway through, such as where to aim or when to fire.

LuauRobloxGameplayNetworking
Go to quick view

Seven abilities, one contract.

Warning

Sword Masters was abandoned in October 2024, and most of its animations, sounds and images belong to the group that owned it, so the game can’t run as it did and this system can’t be shown on video.

Abstract. In Sword Masters each player brings one ability into the round and fires it with E or the button on screen. There are seven: Dash, a stronger God’s Dash for admins, Superjump, Stomp, Burger, Fireball and Laser beam. Some take effect instantly, one charges while you hold the key, and two last several seconds and need the player to aim in the middle. Not all of the system is mine. The base, meaning the client’s input layer, its cooldowns and the shop screens, came from another developer. I turned the server’s manager into a library that loads one module per ability, and I wrote the abilities. This article describes the contract every ability follows, the remotes that connect client and server, the three kinds of flow that come out of it, the per-player state the server keeps, and what the design leaves unprotected. Dash, Fireball and Laser beam have their own articles.

1. The problem

The seven abilities don’t look alike. Dash, Stomp and Burger are instant: you press and something happens. Superjump charges while you hold the key and jumps when you let go. Fireball and Laser beam last several seconds, play a sequence of animations, and in the middle need something only the client knows, which is where the player is aiming and when they click. All of them have to do their damage, movement and effects on the server, so other players see them and the client can’t decide how much damage it deals.

What I wanted was for adding an ability to mean writing one file, without touching the manager or the client.

2. The catalog

Ability Rarity Price Cooldown Kind
God’s Dash Admin — 0 s instant
Dash Starter free 5 s instant
Superjump Common 4,500 13 s hold and release
Stomp Uncommon 10,000 12 s instant
Burger Rare 22,500 35 s instant
Fireball Epic 35,000 22 s long, aimed
Laser beam Legendary 67,500 44 s long, aimed

The data lives in a table per ability: image, cooldown, rarity, price, the IDs of its animations and its own numbers (Dash’s distance, Fireball’s range, Laser beam’s duration). Every new player starts with Dash equipped.

3. The contract

3.1 One module per ability

On the server, Server_Abilities_Handler/Use is a folder of modules. The manager requires every child except Lib (the shared helpers) and stores them by name, so the ability called "Fireball" is whatever the Fireball module returns. Each module exports four functions:

local main = {}

function main.Unlock()          -- does releasing the key do anything?
	return unlockable
end

function main.Load(plr, playerData, Loaded, bring, cback, kill, forceSL)
	-- runs when the player presses the key
end

function main.Use(plr, playerData, Loaded, bring, cback, kill, forceSL)
	-- runs when the client confirms the use
end

function main.Cleanup(plr, playerData)
	return function() --[[ release what the ability created ]] end
end

return main

The signature is the same for every function in every module, even when one of them doesn’t use half the arguments. That’s what lets the manager dispatch by name without knowing anything about each ability, in the same spirit as the Command pattern [1]: the caller only knows the interface.

3.2 The remotes

Six channels connect the two sides:

Remote Type Direction What it means
Load RemoteFunction client → server the key was pressed
Unlock RemoteFunction client → server the key was released; returns whether that matters
Use RemoteEvent client → server the client commits the use
Loaded RemoteEvent server → client ready, you can commit
Callback RemoteFunction server → client the server asks the client for something mid-ability
Bring RemoteEvent client → server the client reports a step mid-ability

There’s also a server-side BindableEvent, kill, which abilities fire when they kill someone so the kill shows up in the stats, and a Shiftlock event that Laser beam uses to force shift lock on the client.

4. Three flows

4.1 Instant

Dash, Stomp and Burger have a one-line Load:

function main.Load(plr, playerData, Loaded, ...)
	Loaded:FireClient(plr, self);
	return true;
end

The client presses, invokes Load, and the server answers by firing Loaded with the ability’s name. When it receives it, the client checks the cooldown, records the time, updates the slot on screen and fires Use. The effect lives in the server’s Use. It’s a round trip just to say “go”, but it gives the server the chance to say no before anything happens.

4.2 Hold and release

Superjump uses Load to charge. Once the player is on the ground, the server loop measures the time t and stops when it reaches 4 s or when a flag says the key was released. When the player lets go, the client invokes Unlock, which for Superjump returns true, so the client fires Use, and the server’s Use sets the flag. The loop in Load sees it and jumps.

The charge is quantized. The time is rounded up to whole seconds and shifted, τ=min⁡(max⁡(⌈t⌉,1),5)+2, and the jump’s velocity is

v→=35τfˆ+20τyˆ

where fˆ is the direction the player is looking. A release before the first second gives τ=3, and each extra second adds one. The full charge has a quirk: the loop stops on the first frame where t≥4, which is always a fraction of a frame past 4, so ⌈t⌉=5 and τ=7. That makes five possible jumps, from 105 studs/s forward and 60 up with a tap to 245 and 140 with the full charge.

4.3 Long

Fireball and Laser beam do everything inside Load. The function runs from start to finish as a script: play the takeout animation, wait for it to reach a certain point, ask the client for input through Callback:InvokeClient, run the effect, clean up and return. For these two, Unlock returns false, so letting go of the key does nothing, and the client fires Use only when Load returns, after the ability is over.

That has a nice consequence: the client records the time of use when it fires Use, so the cooldown of the long abilities starts when they end and not when they start. Laser beam’s 44 seconds are counted from the moment the beam goes out, not from the moment you press E.

Writing a long ability as a single function has a clear advantage. State lives in local variables (the projectile, the connections, the animation tracks), and the order of the phases is the order of the lines. The disadvantage shows up in section 6.

5. State on the server

The manager keeps a table per player:

playerData[plr.Name] = {
	AbilityAnims = {},   -- [ability] = { AnimationTrack, ... }
	AbilityData  = {},   -- [ability] = scratch space for that ability
	AbCooldown   = false,
}

Every time the character spawns, the server loads the animations of the equipped ability into the Animator, with Action priority, in the order the data lists them. Each module then uses them by index (anims[1] is the takeout, anims[2] the idle, and so on). If an ability doesn’t define animations, it falls back to Dash’s with a warning instead of breaking. When the player leaves, Lib:MemoryBan walks the table recursively, disconnecting connections and destroying instances, so nothing is left hanging off a player who’s gone.

Abilities share state with the rest of the game through attributes on the character. Busy marks that a long ability is in progress: Fireball and Laser beam check it in Load and refuse to start if it’s set, so they can’t stack. MoreDamage is a damage multiplier that the strength power-up raises, and every ability that deals damage multiplies by it. MoreSpeed is a speed factor that Superjump and Laser beam lower while they run.

On the client, the abilities only work while the round is running and the player is in it. Two shared flags, IsRoundRunning and IsInRound, turn on 0.667 s after RoundStarted and turn off on RoundEnded or on death. The cooldown check is

tnow−tlast≥c+0.1

with c the ability’s cooldown, t measured with os.clock() and a tenth of a second of margin.

6. Limitations

The server trusts the client too much. Cooldowns and the round flags are only checked on the client, and the server’s Use and Load don’t check either, so a modified client can fire Use as often as it wants. Fireball takes the projectile’s path from the client without validating it (its article goes into detail).

InvokeClient was the wrong tool for long abilities. If the client never answers, because it disconnects or because someone modified it, the server thread stays waiting forever, and if the client’s callback throws, the error comes back up to the server. Roblox’s own documentation warns about this [2]. A pair of RemoteEvents with a timeout does the same thing without handing the server’s control to the client.

There’s duplication left over from the inherited code. The abilities’ data lives in two modules with the same values, Classes/Abilities and Client_Modules/ReplicatedSettings, and the server’s code reads the second one. The system supports one slot, because the key configuration only has Slot1 = E. And there’s an eighth module, Decompile, that loads but whose Use is empty.

7. What I took away

The contract did what I wanted from it: adding Stomp or Burger was writing one file with four functions, and the manager didn’t change. Writing the long abilities as scripts inside Load was the right choice for reading them and the wrong one for safety, because a single function that waits on the client for seconds gives the client too much power over the server. If I did it again I’d keep the module per ability and the shared signature, but I’d move every check (cooldown, round, Busy) into the manager before calling the module, and I’d make each ability a small state machine driven by events instead of a function that waits.

References

  1. E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
  2. Roblox Creator Documentation. “Remote events and callbacks”.

This text by Mastedore. All rights reserved.