Start typing to search packages!
roblox-core
By @hurreman
Roblox
Mirrored from Wallyroblox-core
Small shared building blocks for hurreman's Roblox games. Each module is taken from the game whose copy was best, and generalized so it needs no game modules.
# wally.toml
[dependencies]
Core = "hurreman/roblox-core@0.1.0"
local Core = require(ReplicatedStorage.Packages.Core)
local Format = Core.Format -- modules load on first access
Modules load lazily: requiring the package doesn't run the client-only SoundManager on a server, and doesn't connect Buffs' server listeners on a client.
Modules
| Module | Where it runs | From | What it does |
|---|---|---|---|
Bootstrap | server, client | Magic Neighborhood init.server / init.client | Explicit ordered service init; lifecycle hooks in their own ordered lists, checked both ways; client start list with slow-start warnings |
Maid | any | Slime Nebula Maid.lua | Teardown of connections, instances, threads, functions, disposables and promises; labels; debug dump |
Signal | any | Slime Nebula's GoodSignal (stravant, MIT), plus Once | Yield-safe signal with RBXScriptSignal semantics |
Format | any | Magic Neighborhood Format | Number (truncating suffixes up to Vg, then scientific), Rate, Money, Robux, Multiplier, Clock, Stopwatch |
Numbers | any | Slime Nebula NumberUtils | NaN-safe IsFinite, ToFiniteInteger, SanitizeCount, RoundToNearest |
ResetClock | any | Slime Nebula StockholmTime, generalized | Daily-reset calendar math for a UTC offset plus an EU or US daylight-saving rule; ResetClock.Stockholm, ResetClock.UTC |
Heartbeat | any | Slime Nebula RunServiceHandler | One Heartbeat connection for all per-frame callbacks, with disposers and slow-callback warnings |
Profile | any | Magic Neighborhood Profile | Profile.Wrap(label, fn): microprofiler scopes that survive yielding handlers; Profile.prefix |
Buffs | server | Slime Nebula BuffManager, groups configurable | Stacking timed buffs published as <prefix><Stat> and <prefix><Stat>_Duration attributes |
CollisionGroups | server | Magic Neighborhood CollisionGroups | Setup({ groups, noCollide }) |
SoundManager | client | Slime Nebula + Magic Neighborhood | Registry-driven playback: categories, mute, cooldowns, sequences, random pools, random pitch, 3D roll-off, looped music that follows volume, Studio keepalive, preload |
Bootstrap
-- src/server/init.server.luau
local Bootstrap = require(ReplicatedStorage.Packages.Core).Bootstrap
local app = Bootstrap.Server({
folder = script.Services,
init = { "DataService", "PlotService", "SyncService", "ShopService" },
hooks = {
onPlayerReady = { "PlotService", "ShopService" }, -- (player, profile)
onPlayerRemoving = { "PlotService" }, -- (player)
},
})
app:Init() -- each service's init(app.Services), in init order
Players.PlayerAdded:Connect(function(player)
local profile = app.Services.DataService.load(player)
app:Run("onPlayerReady", player, profile)
end)
-- src/client/init.client.luau
Bootstrap.Start({ script.Store, script.Controllers.SoundController, function() ... end })
Server() fails at boot in these cases:
- a service has a hook method but isn't in that hook's list;
- a listed service lacks the hook;
- a name is listed twice;
- a module is missing.
The player lifecycle itself (data load, plot assignment, spawning) stays in the game's entry script.
Buffs
local buffs = Core.Buffs.new({ prefixes = { Food = "Food_", GamePass = "GamePass_" }, default = "Food" })
buffs:Add(player, "Speed", 4, 30) -- Food_Speed = 4, Food_Speed_Duration = 30
buffs:Add(player, "Speed", 2, 60, { group = "GamePass", onExpired = fn })
buffs:Split(player, "Speed") -- { Food = 4, GamePass = 2, total = 6 }
SoundManager
local SoundManager = Core.SoundManager
SoundManager.Configure({
registry = require(ReplicatedStorage.Shared.Audio.SoundRegistry),
startMuted = false, -- true: muted until saved settings unmute it
volumes = { Music = 0.5 }, -- over Master 1, Music 0.6, Ambient 0.7, SFX 1
defaultParent = SoundService, -- where 2D sounds go
})
SoundManager.Play("Coin")
SoundManager.Play("Hit", nil, { Parent = part }) -- 3D, with the entry's rollOffMin/Max
These are the registry entry fields:
| Field | Meaning |
|---|---|
id, or ids with random / sequence (plus sequenceTimeout, reverse) | Which asset plays |
volume, category | Base volume and its volume category |
looped | Loops, following later volume and mute changes |
cooldown | Minimum seconds between plays of this entry |
playbackSpeed / pitch | Fixed playback speed |
randomPitch with minPitch / maxPitch (or pitchRange) | Random pitch within a range |
rollOffMin / rollOffMax | 3D distance falloff |
Moving the games over
- Slime Nebula: most of these become one-line re-exports of the package:
Maid.luaandClientModules/UI/Signal.luare-exportCore.MaidandCore.Signal. Leave ZonePlus's ownZone/Signalalone.StockholmTime.luabecomesreturn Core.ResetClock.Stockholm. This was checked hour by hour across 2025–2027 bytests/specs/ResetClock.spec.luau.RunServiceHandler.luabecomesreturn Core.Heartbeat.NumberUtils.luare-exportsCore.Numbersand keeps its ownFormatNumber. Note thatCore.Format.Numbertruncates whileFormatNumberrounds (1999 shows "1.99K", not "2K"), so switching it is a separate decision.BuffManager.luakeeps its API as a thin shim over aCore.Buffsset with SN's four prefixes.AddTempBuffmaps toAdd(opts.prefixbecomesopts.group),RemoveBufftoRemove,GetCurrentBuffstoCurrent,GetStatSplittoSplit, and_ticktoTick.ClientModules/Audio/SoundManager.luabecomesCore.SoundManager, plusConfigure({ registry = SoundRegistry, startMuted = true, defaultParent = workspace })before the loading screen applies settings.- The bootstrap doesn't change: SN keeps ModuleRegistry.
- Magic Neighborhood:
init.server.luauandinit.client.luaubuild their lists withBootstrap.Server/Bootstrap.Start.- In
Format, renamenumber→Number,rate→Rate,cash→Money,mult→Multiplier,clock→Clock,raceTime→Stopwatch,robux→Robux.candystays as a local one-liner. ProfilebecomesCore.ProfilewithProfile.prefix = "TOTD.".CollisionGroups.setup()becomesCore.CollisionGroups.Setup({...}).shared/Audio/SoundManager.luaubecomesCore.SoundManagerwithConfigure({ registry = SoundRegistry }).
- Steal an Egg: the same moves as Magic Neighborhood for
init.server/init.clientandFormat. Itsclient/Sounds.luauhas a different API (play,at,on,attach,loop); move its cue table into a SoundManager registry when that file is next touched.
Developing
lune run tests/run # specs under tests/specs, on hurreman/luau-spec (DevPackages, else ../luau-spec)
The ResetClock spec also checks against ../SlimeNebula's StockholmTime when that checkout exists. SoundManager needs real Sound instances, so it was verified in Studio rather than under Lune. That run covered: start-muted, cooldown, looped music following SetVolume and mute immediately (even under deferred SignalBehavior), sequences and ping-pong, random pools, the random pitch range, PlayAssetId, 3D roll-off and defaultParent.
Package Details
Install command (Click to copy)
Version
0.1.0
License
MIT
Safe for commercial use
Automated license review — not legal advice.
