+ Add Server Features
Learn how to extend Stoway by creating new operations, adding API methods, and wiring them to the network protocol.
Overview
How the operations system works
Stoway uses a modular operation system. Each operation (Add, Remove, Swap, Equip, Drop) is a self-contained module that handles a specific inventory action. Adding a new feature involves:
- Create the operation module - Handle the core logic
- Add an API method - Expose the operation in InventoryService
- Wire to network protocol - Allow clients to trigger it
- Add replication (optional) - Notify clients of changes
Client Request → InventoryAction (RemoteEvent)
→ OperationHandlers[operation]
→ OperationModule.Execute()
→ InventoryState updates
→ Replicator.Send*() (optional)
→ Client update
Step 1: Create the Operation Module
Write the core logic for your operation
Create a new file in src/server/StowayServerV1_2/Operations/. Use the template below:
-- src/server/StowayServerV1_2/Operations/MyOperation.luau
-- Description: What your operation does
local MyOperation = {}
-- Type definitions for clarity
export type MyResult = {
success: boolean,
reason: string?,
-- Add your result fields here
}
-- Main execute function
function MyOperation.Execute(
state: InventoryStateType,
-- Add your parameters
param1: string,
param2: number
): MyResult
-- 1. Validate inputs
if not param1 then
return { success = false, reason = "INVALID_PARAM" }
end
-- 2. Perform the operation
-- Modify state.Items, state.Hotbar, state.Storage as needed
-- 3. Return result
return {
success = true,
-- Add result fields
}
end
return MyOperation
- Always validate inputs before modifying state
- Use the existing helpers (SlotManager, LimitChecker, etc.)
- Return descriptive error messages
- Keep the operation focused on a single task
Step 2: Add API Method
Expose your operation in InventoryService
Open src/server/StowayServerV1_2/init.luau and add:
-- At the top with other operation imports
local MyOperation = require(script.Operations.MyOperation)
-- Add in the PUBLIC API section
function InventoryService.MyOperation(
player: Player,
param1: string,
param2: number,
REPLICATE: boolean
)
local state = PlayerInventories[player]
if not state then return { success = false, reason = "NO_INVENTORY" } end
local result = MyOperation.Execute(state, param1, param2)
if result.success and REPLICATE then
-- Add replication if needed
-- Replicator.SendSomething(player, ...)
end
return result
end
Step 3: Wire to Network Protocol
Allow clients to trigger the operation
-- In the Operations table
local Operations = {
SWAP = "Swap",
EQUIP = "Equip",
UNEQUIP = "Unequip",
REMOVE = "Remove",
DROP = "Drop",
STACK = "Stack",
-- Add your new operation type
MY_OPERATION = "MyOperation",
}
-- In the OperationHandlers table
local OperationHandlers = {
-- ... existing handlers ...
-- Add your handler
[Operations.MY_OPERATION] = function(player, args)
return InventoryService.MyOperation(
player,
args.param1,
args.param2,
false -- Replication handled by API method
)
end,
}
Step 4: Add Replication (Optional)
Notify clients of changes
If your operation changes visible state, add a replication method. See Replicator.luau for patterns:
-- Add to Replicator module
function Replicator.SendMyOperation(player, -- your data)
local payload = {
Operation = "MyOperation",
-- Your payload data
}
InventoryActionRemote:FireClient(player, payload)
end
Example: Craft Operation
Complete working example
Here's a simplified craft operation that consumes ingredients and produces an output item:
-- src/server/StowayServerV1_2/Operations/CraftOperation.luau
local SlotManager = require(script.Parent.Parent.Core.SlotManager)
local RemoveOperation = require(script.Parent.Parent.Operations.RemoveOperation)
local AddOperation = require(script.Parent.Parent.Operations.AddOperation)
local CraftOperation = {}
export type CraftResult = {
success: boolean,
reason: string?,
outputItem: string?,
}
function CraftOperation.Execute(
state: InventoryStateType,
recipeId: string
): CraftResult
-- Define your recipes (could also be in a separate module)
local recipes = {
sword = {
ingredients = { wood = 2, iron = 1 },
output = "sword"
},
potion = {
ingredients = { herb = 3, water = 1 },
output = "potion"
}
}
local recipe = recipes[recipeId]
if not recipe then
return { success = false, reason = "UNKNOWN_RECIPE" }
end
-- Check ingredients exist
for itemId, amount in pairs(recipe.ingredients) do
local count = state.ItemsByID[itemId] and #state.ItemsByID[itemId] or 0
if count < amount then
return { success = false, reason = "MISSING_INGREDIENTS" }
end
end
-- Consume ingredients
for itemId, amount in pairs(recipe.ingredients) do
for i = 1, amount do
local uuid = state.ItemsByID[itemId][1]
RemoveOperation.Execute(state, uuid, 1)
end
end
-- Add output
local addResult = AddOperation.Execute(state, recipe.output, 1, nil)
return {
success = true,
outputItem = recipe.output
}
end
return CraftOperation
Next Steps
Related documentation