The N1 Roleplay Engine is designed around modular, drag-and-drop plugins.
Plugins must be designed so they can be installed, moved, or removed without breaking the game.
A properly built plugin should require no manual setup beyond inserting the plugin script or folder into the Plugins directory.
The following are the best practices for developing a plugin that is easy for you to update and distribute.
1. Correct Plugin Location
All plugins must exist inside:
ServerScriptService
└ Plugins
Example:
ServerScriptService
└ Plugins
└ MyPlugin
└ Engine (Script)
The main asset of a plugin should usually be a Script, although folders are also allowed.
Examples of valid plugin formats:
Script Plugin
Plugins
└ MyPlugin
└ Engine (Script)
Folder Plugin
Plugins
└ MyPlugin
├ Engine (Script)
├ Modules
└ Assets
The most important rule is:
The entire plugin must remain modular and self-contained.
2. Do NOT Manually Create Plugin Directories
Plugin folders and asset directories should NOT be manually created by the game developer.
Instead, the plugin's engine script must automatically create its own directories if they do not exist.
This ensures:
• Plugins install cleanly
• Developers don't forget setup steps
• Plugins remain portable
3. Creating Directories via Script
Plugins should generate any required folders automatically.
Example pattern:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Folder = ReplicatedStorage:FindFirstChild("MyPluginAssets")
if not Folder then
Folder = Instance.new("Folder")
Folder.Name = "MyPluginAssets"
Folder.Parent = ReplicatedStorage
end
This guarantees the plugin self-initializes correctly. Never assume folders already exist. Always check first.
4. Plugin Initialization Pattern
Every plugin should have an Engine Script that runs when the server starts.
Typical initialization flow:
Engine Script Runs
↓
Creates required directories
↓
Registers remotes/modules
↓
Moves plugin assets if required
↓
Connects PlayerAdded
↓
Injects GUIs / Character Assets
Example skeleton:
local Players = game:GetService("Players")
local function InitializePlugin()
-- create folders
-- move assets
-- setup remotes
end
local function OnPlayerAdded(Player)
-- inject GUIs
-- plugin setup
end
InitializePlugin()
Players.PlayerAdded:Connect(OnPlayerAdded)
5. Automatic GUI Injection
Plugins that include UI should not rely on StarterGui.
Instead, the plugin engine script should insert GUIs into Player.PlayerGui when players join.
Example structure inside plugin:
MyPlugin
└ Assets
└ GUIs
└ InventoryUI
Injection example:
Players.PlayerAdded:Connect(function(Player)
local Gui = script.Assets.GUIs.InventoryUI:Clone()
Gui.Parent = Player:WaitForChild("PlayerGui")
end)
6. Character Asset Injection
If a plugin needs scripts or objects inside all characters, it must insert them into:
ReplicatedStorage
└ N1_CharacterAssets
└ GlobalCharacterAssets
Everything inside GlobalCharacterAssets is usually automatically applied when characters load by the default Character Creator.
Example plugin logic:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CharacterAssets = ReplicatedStorage:WaitForChild("N1_CharacterAssets")
local GlobalAssets = CharacterAssets:WaitForChild("GlobalCharacterAssets")
local Asset = script.CharacterAssets.MyCharacterScript:Clone()
Asset.Parent = GlobalAssets
This allows the N1 character loader to **automatically apply plugin character features**.
7. Keep Plugins Self-Contained
Plugins should avoid referencing unrelated systems.
Bad example:
require(ServerStorage.SomeOtherFramework)
Good example:
require(script.Modules.MyModule)
Every plugin should carry its **own modules and dependencies** whenever possible.
8. Plugin Removal Safety
Plugins should be removable without breaking the game.
A properly designed plugin should:
• Not alter core N1 systems
• Wait for N1 to be initialized
• Not delete core folders
• Not create required dependencies outside itself
If the plugin is removed, the game should continue running normally.
Accessing The Server Framework is through the following method in any ServerScript:
local N1ServerFramework = game:GetService('ServerScriptService'):WaitForChild('N1_ServerFramework', 3)
You can check if N1 framework has been fully initialized by the core module by checking for the initialized attribute. I.e
if N1:GetAttribute("Initialized") then
print("N1 Framework Ready")
end
Then, the default binds are found in the following directory.
local DefaultBinds = N1ServerFramework:WaitForChild('DefaultBinds')
The default binds are where the default N1 core Events & Functions are found. These relate primarily to Data & Character Appearance handling. They are as follows:
'PlayerLoadedSlot', 'PlayerDataLoaded', 'PlayerDataSaved', 'CharacterDressed' - BindableEvents
'ForceSavePlayerSlot', 'ForceSavePlayerData', 'DeleteSlot', 'DressCharacter', 'GetService' - BindableFunctions
For N1, we opted to use Folders & Values in order to simplify the development of plugins & make debugging & tracking easier for devs unfamiliar with the ROBLOX platform trying to make a server. Folders & ValueInstances actually take up 100-300 bytes, which is cheap for the ROBLOX engine and should pose no real performance issues unless you are exceeding roughly 200-250 players on your server.
N1 Player Data Structure
└ Player
└ PlayerStats
└ Slot1
├ Bag (Folder)
│ └ [Tools / Item Instances]
├ CustomSettings (Folder)
│ └ [Custom Player Settings That You Wish To Create/Store for your Server Plugins i.e drivers licenses, passes etc.]
├ Phenotype (Folder)
│ ├ BodyType (StringValue)
│ ├ Face (StringValue)
│ ├ FacialHair (StringValue)
│ ├ FacialHairColor (Color3Value)
│ ├ Hair (StringValue)
│ ├ HairColor (Color3Value)
│ ├ Height (NumberValue)
│ ├ SkinColor (Color3Value)
│ └ Weight (NumberValue)
├ Traits (Folder)
│ └ [Character Traits You Wish To Create/Store for your server plugins i.e Strength, Fitness, Age, Description etc.]
├ Vehicles (Folder)
│ └ [Owned Vehicles]
├ Wardrobe (Folder)
│ ├ Glasses (StringValue)
│ ├ Jacket (StringValue)
│ ├ Pants (StringValue)
│ ├ Shirt (StringValue)
│ └ Shoes (StringValue)
├ Armor (NumberValue)
├ Bank (NumberValue)
├ Bio (StringValue)
├ Cash (NumberValue)
├ CharName (StringValue)
├ Gender (StringValue)
├ HasChar (BoolValue)
├ Health (NumberValue)
├ Hunger (NumberValue)
├ Job (StringValue)
└ Thirst (NumberValue)
Type: BindableEvent
Fires when a player's slot finishes loading from the datastore.
userId (number)
slotName (string) — example: "Slot1"
dataExisted (boolean)
dataExisted will be:
true if slot data existed
false if default slot data was generated
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
Binds.PlayerLoadedSlot.Event:Connect(function(userId, slotName, dataExisted)
print("Slot loaded for user:", userId)
print("Slot:", slotName)
print("Data existed:", dataExisted)
end)
Type: BindableEvent
Intended to fire when all player data finishes loading.
player (Player)
⚠️ Note
This event exists but is not currently fired in the framework script.
Example listener:
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
Binds.PlayerDataLoaded.Event:Connect(function(player)
print(player.Name .. " finished loading data")
end)
Type: BindableEvent
Intended to fire when player data is saved.
player (Player)
⚠️ Note
This event exists but is not currently fired in the framework script.
Example listener:
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
Binds.PlayerDataSaved.Event:Connect(function(player)
print("Player data saved for", player.Name)
end)
Type: BindableEvent
Fires when the framework finishes applying character appearance.
character (Model)
success (boolean)
errorMessage (string or nil)
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
Binds.CharacterDressed.Event:Connect(function(character, success, errorMessage)
if success then
print(character.Name .. " was successfully dressed")
else
warn("Character dressing failed:", errorMessage)
end
end)
Manually saves a specific slot to the datastore.
player (Player)
slotNumber (number or string)
Examples of slot identifiers:
1
2
"Slot1"
"Slot2"
Nothing (For Now)
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local player = game.Players:FindFirstChild("Builderman")
if player then
Binds.ForceSavePlayerSlot:Invoke(player, 1)
print("Saved Slot1 for", player.Name)
end
Forces saving all slots belonging to a player.
player (Player)
Nothing
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local player = game.Players:FindFirstChild("Builderman")
if player then
Binds.ForceSavePlayerData:Invoke(player)
print("Saved all slots for", player.Name)
end
Deletes a slot and regenerates default slot data.
player (Player)
slotNumber (number or string)
Examples
1
2
"Slot1"
Nothing
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local player = game.Players:FindFirstChild("Builderman")
if player then
Binds.DeleteSlot:Invoke(player, 2)
print("Slot2 reset for", player.Name)
end
Result:
Slot data is wiped
Default slot values are regenerated
Slot is saved to datastore
Applies saved appearance data to a character model.
character (Model)
dataFolder (Folder containing slot data)
clientCall (boolean)
clientCall should usually be false when calling from server scripts.
character (Model)
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local player = game.Players.PlayerAdded:Wait()
local character = player.Character or player.CharacterAdded:Wait()
local slotFolder = player.PlayerStats:WaitForChild("Slot1")
Binds.DressCharacter:Invoke(character, slotFolder, false)
print("Character appearance applied for", player.Name)
This function will:
Apply skin color
Apply height and body scaling
Add hair
Add facial hair
Apply wardrobe clothing
Apply face assets
Returns internal N1 services.
serviceName (string)
Supported services:
"HousingService"
service table containing functions
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local HousingService = Binds.GetService:Invoke("HousingService")
Returns internal N1 services.
serviceName (string)
Supported services:
"HousingService"
service table containing functions
local Framework = game.ServerScriptService:WaitForChild("N1_ServerFramework")
local Binds = Framework:WaitForChild("DefaultBinds")
local HousingService = Binds.GetService:Invoke("HousingService")
Returns saved datastore information for a house.
houseName (string)
House data table or nil
local HousingService = Binds.GetService:Invoke("HousingService")
local data = HousingService.GetHouseData("BeachHouse")
if data then
print("Owner:", data.Owner)
else
warn("House data not found")
end
Loads the interior model for a house.
houseName (string)
Model or nil
local HousingService = Binds.GetService:Invoke("HousingService")
local interior = HousingService.DisplayHouseInterior("BeachHouse")
if interior then
print("Interior loaded:", interior.Name)
end
Removes the interior model.
houseName (string)
boolean
local HousingService = Binds.GetService:Invoke("HousingService")
local removed = HousingService.RemoveHouseInterior("BeachHouse")
print("Interior removed:", removed)
Creates a house entry in the global housing registry.
houseName (string)
price (number)
location (Vector3 or Part)
interiorTemplate (Model)
customSettings (table or nil)
House folder
local HousingService = Binds.GetService:Invoke("HousingService")
local interiorTemplate = game.ServerStorage.HouseInteriors.SmallHouse
HousingService.DefineHouse(
"BeachHouse",
5000,
Vector3.new(120, 5, -300),
interiorTemplate,
nil
)
Saves a house to the datastore.
houseName (string)
Nothing
local HousingService = Binds.GetService:Invoke("HousingService")
HousingService.SaveHouseData("BeachHouse")
Loads datastore information into the in-game house.
houseName (string)
Nothing
local HousingService = Binds.GetService:Invoke("HousingService")
HousingService.LoadHouseData("BeachHouse")
Creates and registers a new shop with a list of items.
shopName (string)
items (table)
{
{
Name = string,
Price = number,
Stock = number (optional),
Description = string (optional)
}
}
boolean (true if successful)
nil (if failed)
ShopService:DefineShop("WeaponShop", {
{Name = "Pistol", Price = 500},
{Name = "Rifle", Price = 1500, Stock = 10}
})
Returns a specific shop and its items.
shopName (string)
table (shop data)
nil (if not found)
local Shop = ShopService:GetShop("WeaponShop")
Returns all registered shops.
Nothing
table (all shops)
nil (if none exist)
local Shops = ShopService:GetShops()
Adds stock to an existing item in a shop.
shopName (string)
itemName (string)
stock (number)
Nothing
ShopService:StockShopItem("WeaponShop", "Rifle", 5)
Returns the current stock of an item.
shopName (string)
itemName (string)
number (stock amount)
math.huge (if infinite stock)
nil (if not found)
local Stock = ShopService:GetItemStock("WeaponShop", "Rifle")
Handles purchasing an item from a shop.
player (Player)
shopName (string)
itemName (string)
amount (number)
forceBalance (string, optional)
"Cash"
"Bank"
{
Completed = boolean,
AmountPaid = number,
FailReason = string or nil
}
local Result = ShopService:CompletePurchase(Player, "WeaponShop", "Pistol", 1)
if Result.Completed then
print("Purchased!")
else
warn(Result.FailReason)
end
If forceBalance is not provided, the system uses the higher of Cash or Bank.
Items must exist in ServerStorage.Items or ServerStorage.Tools.
If stock is not set, the item is treated as infinite.
Purchased items are placed in:
Player.CurrentChar.Value.Bag automatically.
N1 UIService is a modular UI framework designed for the N1 game framework.
It provides a structured and scalable way to dynamically generate UI elements such as menus, buttons, sliders, and text boxes.
You can view UIService in action here.
It is created in such a way that players can easily create new UI themes for the entire game without needing to overhaul major systems, whilst also being able to produce UI packs & reworks that best suit various audiences and visual aesthetics for games that may be using the N1 Engine.
The system uses a builder pattern to create UI menus called OptionsLists, allowing developers to chain commands for clean UI construction.
It can be accessed using the following script:
local UIService = reqruire(game:GetService('ReplicatedStorage'):WaitForChid('CoreClientSettings'):WaitForChild('UIService')
UIService relies on the following structure inside ReplicatedStorage:
ReplicatedStorage
└ CoreClientSettings
└ N1v1_UISettings
├ Bank
│ ├ DefaultBack
│ ├ DefaultButton
│ ├ DefaultSlider
│ ├ DefaultTwinButtons
│ ├ DefaultImageDisplay
│ ├ DefaultTextBox
│ └ DefaultTopLabel
│
├ Icons
│ └ (icon ImageLabels)
│
└ MaxListContent (NumberValue)
Contains UI templates used to generate elements.
Each element must follow the naming convention:
Default<Type>
Example:
DefaultButton
DefaultSlider
DefaultTextBox
Stores icon ImageLabels used when assigning icons to UI elements.
Defines the maximum number of UI elements that can appear before the menu automatically enables scrolling.
An OptionsList is the main UI container used to display menus.
It automatically handles:
UI layout
scrolling
animations
element stacking
overflow detection
OptionsList(Parent, Size, Position, Alignment)
Parent - Instance
Parent UI container
Size - UDim2
Optional custom size
Position - UDim2
Position of the UI
Alignment - string
"Left", "Center", or "Right"
local Menu = UIService.OptionsList(
PlayerGui.ScreenGui,
nil,
UDim2.new(0.75,0,0.25,0),
"Right"
)
All UI elements are created using the Builder pattern as follows:
Notes:
Only one TopLabel can exist per OptionsList.
It is automatically placed at the top.
Name - string
Button label
Icon- string
Icon name from Icons folder
Color - Color3
Highlight color
"Deposit",
"Money",
Color3.fromRGB(0,200,0)
)
Creates a slider UI element. The sliding system for the slider UI element is already prepared.
:Slider(Name, Icon, Color)
Parameters
Name - string
Button label
Icon- string
Icon name from Icons folder
Color - Color3
Highlight color
"Transfer Amount",
"Money",
Color3.fromRGB(255,200,0)
)
Creates a Left/Right scroller UI element. The prescribed button names should be "LeftButton" and "RightButton".
:Scroller(Name, Icon, Color)
Parameters
Name - string
Button label
Icon- string
Icon name from Icons folder
Color - Color3
Highlight color
"Shirt (1/50)",
"Shirt",
Color3.fromRGB(255,200,0)
)
:TwinButtons(A, B, Color)
A- string
Button label
B- string
Button label
Color - Color3
Highlight color
"Confirm",
"Cancel",
Color3.fromRGB(255,0,0)
)
Creates a text input UI.
:TextBox(Name, Icon, Color)
Menu:TextBox(
"Recipient",
"User",
Color3.fromRGB(255,255,255)
)
Creates an image display UI element.
:ImageDisplay(Name, Icon, Color)
Menu:ImageDisplay(
"Bank Logo",
"Bank",
Color3.fromRGB(255,255,255)
)
Menu:Open()
The UI will:
fade in
slide from the alignment direction
Animation uses:
Exponential easing
Menu:Close()
The UI will:
fade out
slide away toward its alignment side.
Menu:Destroy()
Completely removes the UI from memory.
Every button created internally returns a structure containing:
{
Clicker = ButtonObject,
Body = UIInstance,
Type = UIType
}
Example:
local Btn = Menu:Add("Button","Deposit")
Btn.Clicker.MouseButton1Click:Connect(function()
print("Deposit pressed")
end)
UIService automatically manages layout using:
UIListLayout
Elements are stacked vertically with spacing.
If the number of elements exceeds:
MaxListContent
The system automatically converts the list into a ScrollingFrame.
Example:
MaxListContent = 6
If more than 6 elements exist, scrolling activates automatically.
Color is applied dynamically using special object names inside templates.
Frame named "Highlight"
Highlight.BackgroundColor3 = Color
TextButton named "HighlightButton"
TextLabel named "HighlightTextLabel"
ImageLabel named "HighlightImageLabel"
Icons are assigned using the Icons folder.
Icons
├ Money
├ Bank
└ User
When an icon is passed:
:Button("Deposit","Money")
UIService finds:
Icons.Money.Image
and applies it to all ImageLabels in the element.
local Menu = UIService.OptionsList(
PlayerGui.ScreenGui,
nil,
UDim2.new(.75,0,.25,0),
"Right"
)
:TopLabel("Bank")
:Button(
"Deposit",
"Money",
Color3.fromRGB(0,200,0)
)
:Button(
"Withdraw",
"Money",
Color3.fromRGB(200,0,0)
)
:Slider(
"Transfer Amount",
"Money"
)
:TextBox(
"Recipient",
"User"
)
:TwinButtons(
"Confirm",
"Cancel",
Color3.fromRGB(255,0,0)
)
Menu:Open()
UIService automatically provides:
Dynamic layout
Scroll detection
Animation
Highlight coloring
Icon injection
Menu builder pattern
UI scaling
UI overflow management
Good:
Menu
:TopLabel("Bank")
:Button("Deposit")
:Button("Withdraw")
Avoid creating elements separately unless necessary (i.e you need to script functions for each button inside each element that do different things).
All UI templates must follow the naming structure:
Default<Type>
Example:
DefaultButton
DefaultSlider
DefaultTextBox