Documentation
Docs
Two audiences, two very different questions. Pick the one that's you.
What is NPS
NPS is a private server and launcher for My Singing Monsters. It runs the game's server logic itself, so you can play offline or against a server you control, keep multiple save slots, and install community-made mods - all from one app on your phone.
Install the launcher
Grab the latest build from the NPS GitHub and install the APK like any sideloaded app - you'll need to allow installs from unknown sources for your browser or file manager once.
Connect to a server
Open the launcher's menu and look for Server Config. You have three choices:
- Private - the launcher's own built-in server, running on your device. Nothing leaves your phone.
- BBB - the official My Singing Monsters servers.
- Custom - point at someone else's NPS server by address, e.g. one a friend is hosting.
Separately, if you want an NPS account (for friends, cloud saves, and mods you've published) you set a server address under Online Friends in the same menu - that's the website/account server, not the game server above.
Your account
An NPS account is optional and separate from your in-game save. Create one from the launcher or from this website - either works, since they're the same account. You can log in with either your email or your username.
- Add as many accounts as you like and switch between them from the Accounts screen.
- Your friend code is what other players add to send you a friend request - share it, don't type your email.
- Set a profile picture and bio - the same ones shown on your account page here on the site.
Saves
The Save Manager keeps multiple local save slots on your device - create, rename, load, back up, import and export a save as a file.
If you're logged into an NPS account, Cloud Saves (under Online in the menu) lets you upload a save to your account and pull it down on another device - up to 5MB total across everything you store there.
Mods
Open the Mod Marketplace from the menu to browse what other players have published. Installing a mod is one tap; the launcher restarts what it needs to and tells you when the game needs a restart to pick it up. Uninstalling removes everything it added.
A mod can add buttons and menus, change how the client looks, and react to things you do in-game (breeding, buying, hatching, and so on) - it can't do anything the launcher itself doesn't allow.
Getting help
Something broken? Use Report a Bug in the launcher's menu, or reach the community through the links in the footer of this site.
Overview
An NPS mod is a folder with a mod.json file at its root. Package the folder
contents as .npsmod or .zip for installation.
Mods can use server Python to react to events and work with player data. They can also ship client assets and Lua to replace or extend supported game files and interface behavior.
Quick start
Start with the template, then use the resources bundle when you need game or server references.
The starter template contains a minimal mod.json and one Python file. The
resources bundle contains mod_api.py and its dependencies, client Lua, client
XML, sample structure and monster .bin files, and the
db_structure and db_monster data files.
Layout
| Path | Purpose |
|---|---|
mod.json | Mod manifest. It must be at the archive root. |
Server/Python/main.py | Python loaded by the embedded game server. |
Client/Assets/... | Optional client files added or replaced by the mod. |
Client/Assets/scripts/... | Optional Lua used by client menu or interface overrides. |
mod.json must appear
at the archive root. If it is stored as your-folder/mod.json, the loader will not
recognize the archive as a mod. Check the package with unzip -l yourmod.npsmod.
mod.json
{
"id": "my-first-mod",
"name": "my-first-mod",
"version": "0.1.0",
"author": "you",
"description": "A new NPS mod.",
"gameVersion": "5.7.0",
"client": {
"assets": [],
"ui": { "buttons": [], "menus": [] }
},
"server": {
"plugins": [],
"assets": [],
"python": ["Server/Python/main.py"]
},
"permissions": ["server.python"],
"dependencies": [],
"enabled": true
}
Keep id unique and stable. Change version when publishing an update
so the launcher can offer the new version to players.
Fields
| Field | Type | Meaning |
|---|---|---|
id | string | Unique marketplace identifier. Use a name that is unlikely to collide with another mod. |
name | string | Display name shown in the marketplace and installed mod list. |
version | string | Version string such as 0.1.0. Players are offered an update when this value changes. |
author | string | Author shown on the marketplace page. |
description | string | Short description shown in listings. |
gameVersion | string | MSM client version used to build or test the mod. This is informational and does not block installation. |
client.assets | array<path> or array<{source,target}> | Client files such as art, XML, and Lua. See Client capabilities. |
client.assetPatches | array<{source,target,mode}> | Exact file replacements. These take priority over client.assets for the same target. |
client.ui.buttons | array | Standalone launcher button definitions. Use ContextBar buttons for buttons attached to structures or monsters. |
client.ui.menus | array | Custom menu definitions opened by declared UI buttons. |
server.python | array<path> | Python files loaded by the embedded server. See Server API. |
server.assets | array | Static data files used by the server. Python helpers are preferred for structures and monsters. |
server.plugins | array | Compiled server plugins for cases that cannot be handled by Python. |
permissions | array<string> | Capabilities required by the mod. See Permissions. |
dependencies | array<string> | Other mod IDs that must already be installed. |
enabled | bool | Default enabled state after installation. Players can change it later. |
Server API
Python files listed in server.python are loaded by the embedded server. Use
event handlers to react to requests and game activity.
import mod_api
from msm_playerdata import load_player, save_player
@mod_api.on_event("command_received")
def on_command(username="", command="", params=None, **_):
... # before the server handles a request
@mod_api.on_event("command_handled")
def on_handled(username="", command="", **_):
... # after the server handles a request
Available event names:
player_join,player_loadedcommand_received,command_handled,mod_commandbreeding_started,breeding_finishedfuzing_started,fuzing_finishedegg_bought,egg_hatched,player_hatchmonster_collectedisland_bought,structure_boughtupdate_properties
Event arguments are passed by keyword. Add **_ or **kwargs to your
handler so extra arguments do not break it.
Player data
root, player = load_player(username) # root is the full save
player["bio"] = "Hello from a mod"
save_player(username, root) # save the full root object
Currency values have matching _actual fields. The client HUD reads those
values first, so use the currency helpers instead of writing a currency field directly.
import mod_api
@mod_api.on_event("mod_command")
def on_mod_command(username="", command="", params=None, player_object=None, **_):
if (params or {}).get("action") == "give_100k_coins" and player_object is not None:
mod_api.add_currency(player_object, "coins", 100000)
mod_api.request_property_update(username)
add_currency and set_currency keep the normal and
_actual values in sync. request_property_update updates the HUD
immediately. Without it, the saved value is still available after a reload.
Java mods get the property update automatically when using
api.commands().addCurrency(...) or setCurrency(...).
Currency keys: coins, diamonds, food,
ethereal_currency, keys, relics,
egg_wildcards, clubbox_tokens, starpower, and
medals.
The launcher setting for functioning currencies controls built in gameplay spending and rewards. It does not block currency changes made directly by a mod.
Player data reference
load_player returns the saved data used by the server. The fields below are
documented from the current save format. Saves may contain additional fields.
json.dumps(player, indent=2)[:4000] with tab.text(...).
root and player_object
| Path | Shape | Notes |
|---|---|---|
root["player_object"] | object | Base object for the fields below unless a field is described as top level. |
username, bio | string | |
date_created | int, ms epoch | Account creation time. It also affects whether the client displays the Newsflash popup for newer accounts. |
coins, diamonds, food, ... | number | Base currency value. |
coins_actual, diamonds_actual, food_actual, ... | number | Currency value read first by the HUD. Use the currency helpers described in Server API. |
total_starpower_collected | number | Tribal Starpower total. |
costumes | {"items":[{"k":costumeId,"v":count}]} | Keyed count list. Do not treat it as a plain array of IDs. |
quest_state | object keyed by quest id | Entries use {"delivered":bool,"status":..,"completed":bool,"collected":bool}. status is a boolean or string, not an array. |
islands | array of island objects | See below. |
Island object
| Path | Shape | Notes |
|---|---|---|
user_island_id | int | Unique ID for each owned island, including duplicate or mirror islands. Allocate a new ID for each island. |
island_type | int | Island type such as Plant, Water, or Ethereal. |
monsters | array of monster objects | See below. |
structures | array of structure objects | See below. |
eggs | array | Eggs currently placed on the island. |
Monster object
| Path | Shape | Notes |
|---|---|---|
id | int | Unique ID for an owned monster. Moving or teleporting a monster should keep the same object and ID. |
pos_x, pos_y | int | Island placement coordinates. Reset them when moving a monster to another island. |
titansoul | object | Contains values such as {"awakened_state": int, ...} when a Titan Soul is socketed. |
Structure object
| Path | Shape | Notes |
|---|---|---|
user_structure_id | int | Unique ID for each structure instance. |
ext | object | Structure specific state. For an Awakener, ext.awakened_state mirrors titansoul.awakened_state on the socketed monster. |
Common event payloads
Events created with mod_api.event(name) include values that are available in
the handler that fired them.
| Key | Present when |
|---|---|
username | The event has an acting player. |
params | The request handler received client parameters. |
player_object | The handler loaded the player object. |
result | The handler produced response frames. |
root | The handler loaded the full save. |
Not every event contains every key. Use .get(...) where appropriate and keep
a **kwargs catch all in event handlers.
Custom structures & monsters
Adding a structure
structure_id = mod_api.add_new_structure(
"monument",
name="NPS Monument",
description="Tap it, then Collect, for a coin bonus.",
graphic_file="decoration_birdbath01.bin",
)
The first argument is a key chosen by your mod. NPS maps that key to a stable numeric ID.
name is required. Other db_structure fields can be supplied as
keyword arguments, for example cost_coins=5000 or view_in_market=1.
Use structure_type="mine" when you want the standard fill timer and ready
indicator used by mine type structures.
is_complete or building_completed as a replacement for
the mine timer. A structure with is_complete=0 uses a different tap context,
which can bypass ContextBar buttons configured by the mod.
Adding a monster
monster_id = mod_api.add_new_monster(
"glimmerbeast",
common_name="Glimmerbeast",
genes="DE",
graphic_file="monster_de.bin",
view_in_market=1,
cost_coins=500,
add_to_island=True,
)
Island Books use a fixed number of slots. Setting add_to_island=True assigns
the monster to an existing slot on Plant Island. Pass an island_type integer
instead of True to target another island.
graphic_file, genes,
and size_x/size_y from the same source monster. The portrait,
spore graphic, select sound, and Book instrument are tied to the gene value. Invalid
combinations can reference missing assets and crash the client.
Keeping structures in sync
The client keeps a cached copy of structure fields used by the fill bar and ready indicator. After changing one of those fields directly, sync the structure so the client receives the new value.
structure["last_collection"] = 0
mod_api.sync_structure(username, structure, player_object)
sync_structure syncs last_collection by default. Pass additional
field names as positional arguments when needed.
allocate_id
my_id = mod_api.allocate_id(namespace, key)
Returns a stable custom numeric ID at or above 9000 for a namespace and key. Structure and monster helpers use this internally. Use it when another custom object also needs a stable ID.
Client to server commands
Lua can send a mod action to the server with the NPS HUD carrier format. Use this for actions such as rewards, cooldown checks, or currency charges.
Client, Lua:
game.setPlayerDisplayName("NPSHUD:<your-mod-id>:<action-name>:<payload>")
The request is intercepted by the server and the original player display name is restored.
Server, Python:
import mod_api
@mod_api.on_hud_prompt("your-mod-id", "action-name")
def _on_action(username="", value="", player_object=None, root=None, **_):
mod_api.add_currency(player_object, "coins", 500)
return "Bonus collected: +500 coins."
on_hud_prompt(mod_id, action_id) matches the corresponding NPS HUD prefix.
The callback receives username, value, player_object,
and root. A returned string is shown as a native popup.
NPS.request - a more direct way to send commands
Every mod script also gets a global NPS table, injected automatically -
nothing to require or set up. NPS.request(command, params) sends a real
command straight to the server, without the display-name carrier trick:
NPS.request("gs_finish_structure", { user_structure_id = userStructureId })
Three shortcuts for common actions build on top of it:
NPS.official.breed(structureId, leftUserMonsterId, rightUserMonsterId)
NPS.official.finishStructure(userStructureId)
NPS.official.finishObstacle(userStructureId)
NPS.request queues the command - it's picked up and actually run the moment
the next command from the client finishes handling (whatever that happens to be:
keep_alive, a menu open, anything), and its response frames are piggybacked
onto that response. It does not return the command's result synchronously - it returns
true/false for whether it was queued at all. If your mod needs
to react to what the command actually did, hook the command's own name as an event
server-side (@mod_api.on_event("gs_finish_structure")) rather than expecting
a Lua return value.
Other real globals the same injection provides: NPS.log(message) (writes to
the mod's own log), NPS.command(name) and NPS.button(manager, id,
image, label, command) (wraps a ContextBar button insert), and a same-process
event bus, NPS.on(event, fn) / NPS.emit(event, ...), for one
mod's script to react to another's without either knowing about the other's globals.
ContextBar buttons
The ContextBar is the row of buttons shown when a structure or monster is selected. Mods can reuse an unused slot in that row for a custom action.
Button API
ContextBar behavior is defined in assets/scripts/ContextBar.lua. The resources
bundle includes the stock scripts and a workflow for decompiling Lua bytecode.
java -jar unluac.jar scripts/ContextBar.lua > ContextBar_decompiled.lua
Structure types use functions such as mine_oninit and
sellable_decoration_oninit to configure their button set with methods including
setButtonImg, setButtonLabel, setButtonFunction, and
changeButton.
Override setup
Point menus/contextbar.xml at your script:
<script src="scripts/nps_context_bar.lua" />
Load the stock module, wrap the function you need, then return the same module.
local ContextBar = include("ContextBar")
local origMineOninit = ContextBar.mine_oninit
ContextBar.mine_oninit = function(element)
origMineOninit(element)
local context = game.worldContext()
local selectedObject = context and context:selectedObject()
if selectedObject == nil or selectedObject:entityId() ~= YOUR_ENTITY_ID then
return
end
manager:changeButton("btn_sell", "collect_diamond_bag_alone", "MY_LABEL", "my_button_function")
end
function ContextBar.my_button_function(element)
-- custom action
end
return ContextBar
Available slots for mine type structures
btn_sellis available whensellable=0.btn_upgradeis available when the structure does not use a normal upgrade.
btn_upgrade only when upgrades_to is nonzero.
add_new_structure(structure_type="mine", ...) sets a self reference by default
so the slot exists for a mod to reuse.
changeButton
manager:changeButton(buttonId, icon, labelKey, functionName)
To use a sprite from another sheet:
manager:setButtonImgWithSheet(buttonId, spriteName, sheetPath)
Sprite names can be found in xml_resources/*.xml by searching for
<sprite n="...">.
Confirmation dialog
game.displayConfirmation("MY_CONFIRM_ID", "Are you sure?")
element:SetupGenericListener(game.engineReceiver(), "game::msg::MsgConfirmationSubmission", "gotMyConfirmation")
function ContextBar.gotMyConfirmation(element, msg)
if msg.choice ~= true then return end
if msg.messageID == "MY_CONFIRM_ID" then
-- confirmed
end
end
SetupGenericListener does not remove duplicate registrations. If setup can run
more than once, guard it with a global flag so one event does not call the handler multiple times.
Example: if not NPS_LISTENER_SETUP then NPS_LISTENER_SETUP = true; element:SetupGenericListener(...) end.
Launcher UI
A mod can add a tab under Mods in the launcher drawer. Tabs can contain
text, buttons, text fields, file upload controls, images, and dividers. Launcher tabs are
defined in Python and require server.python permission.
import mod_api
tab = mod_api.launcher_tab("Better Editor", icon="\U0001F6E0")
def on_click(username, value):
tab.text("Clicked by " + username + ", form=" + str(value))
def on_upload(username, value):
tab.text("Got " + value["filename"] + " (" + str(value["size"]) + " bytes)")
@tab.on_open
def build(username):
tab.text("Welcome, " + username)
tab.text_field("Note", value="type here")
tab.button("Click me", on_click=on_click)
tab.upload_file("Upload a file", on_upload=on_upload)
tab.divider()
tab.image("data:image/png;base64,....")
UI calls append elements to the current tab. @tab.on_open clears and rebuilds
the tab each time it opens. Use mod_api.cache_get and
mod_api.cache_set for data that should persist between visits.
Elements
| Call | Behavior |
|---|---|
tab.text(value) | Adds read only text. |
tab.text_field(label, on_change=None, value="") | Adds a text field. Current field values are included when a button on the same screen is pressed. |
tab.button(label, on_click=None) | Adds a button. The callback receives username and the current form value dictionary. |
tab.upload_file(label, on_upload=None, accept=None) | Opens the device file picker. The callback receives filename, size, and data_base64. Files over 4 MB are rejected by the client. |
tab.image(source) | Displays a base64 data URL image. |
tab.divider() | Adds a horizontal divider. |
tab.clear() | Removes all current elements. |
tab_id, for example
mod_api.launcher_tab("Second Tab", tab_id="second").
Client capabilities
client.assets
Client assets replace or add complete files through the game download manifest. Changes are applied when the client starts and downloads the asset.
"client": {
"assets": [
{ "source": "Client/Assets/menu_templates/some_menu.xml", "target": "assets/menu_templates/some_menu.xml" }
]
}
Shipping Lua
Lua is shipped through client.assets. A menu XML selects the Lua file with its
<script src="..."/> entry, so a mod can replace that XML and point it at a
custom script.
"client": {
"assets": [
"Client/Assets/menus/contextbar.xml",
"Client/Assets/scripts/nps_context_bar.lua"
]
}
Custom scripts can load the stock module with include(...), wrap only the
functions they need, and return the same module. See
ContextBar buttons for an example.
filesDir/BBB_android1/downloads/<same path> and restart the client.
game table
Client Lua can access the same global game table used by stock menu scripts.
This includes functions such as game.GetMonster(id) and
game.activateBoxMonster(...).
game.* is an internal client interface and its function contracts are not
documented here. Incorrect arguments can crash the game. Use the documented command and
ContextBar APIs when they cover the task.
Show the full game.* function list
| Function |
|---|
| Loading... |
Client events
Stock menus listen for engine events through SetupGenericListener.
self:SetupGenericListener(game.engineReceiver(), "game::msg::MsgPlayerUpdated", "gotMsgPlayerUpdated")
function gotMsgPlayerUpdated(self, msg)
-- handle the event
end
SetupGenericListener is called on a menu or element instance. It is available
when your script wraps an existing menu module and receives that menu instance as
self or element.
Permissions
List the capabilities used by the mod in mod.json. Missing required permissions
can block installation or prevent the related feature from loading.
| Permission | Used for | Required when | If missing |
|---|---|---|---|
server.python |
Loading Python into the embedded server | The mod uses events, save access, or launcher tabs | Installation is refused when server.python is not empty |
server.assets |
Shipping static server data files | Python reads a data file included with the mod | Installation is refused when server.assets is not empty |
server.plugins |
Loading compiled server plugins | The mod uses a compiled server plugin | Installation is refused when server.plugins is not empty |
client.assets |
Adding or replacing client files | The mod ships client XML, Lua, textures, or other client assets | Installation is refused when client.assets is not empty |
client.assetPatches |
Exact client file replacement | The mod declares an asset patch | Installation is refused when client.assetPatches is not empty |
client.ui |
Manifest UI buttons and menus | The mod declares client.ui.buttons or client.ui.menus |
The declared UI is not rendered |
Declare the permissions the mod uses and no others.
Reference downloads
The resources bundle contains server and client files useful while building mods.
| Folder | Contents |
|---|---|
src/ | Server Python source including mod_api.py and related modules such as msm_structures.py, msm_playerdata.py, msm_protocol.py, and msm_handlers.py. |
lua/ | Stock client Lua, including a decompiled ContextBar.lua, compiled scripts, and the included decompilation workflow. |
xml/ | Stock client XML, including menu XML and sprite sheet resources. |
bin/ | Sample structure and monster .bin graphic descriptors. |
data_json/ | db_structure and db_monster data loaded by the server. |
Cookbook
Small examples for common mod tasks.
Give a reward on login
import mod_api
@mod_api.on_event("player_join")
def on_join(username="", player_object=None, **_):
if player_object is None:
return
mod_api.add_currency(player_object, "coins", 500)
mod_api.request_property_update(username)
React to a game command
import mod_api
@mod_api.on_event("gs_collect_monster")
def on_collect(username="", params=None, result=None, **_):
print("collect_monster result:", result)
Show save data in a launcher tab
import json
import mod_api
from msm_playerdata import load_player
tab = mod_api.launcher_tab("My Save", icon="\U0001F4C4")
def on_click(username, value):
_, player = load_player(username)
tab.clear()
tab.text(json.dumps(player, indent=2)[:4000])
@tab.on_open
def build(username):
tab.button("Dump my save", on_click=on_click)
Structure with a custom ContextBar action
Register the structure in Python:
import mod_api
STRUCTURE_ID = mod_api.add_new_structure(
"monument",
name="NPS Monument",
structure_type="mine",
graphic_file="decoration_birdbath01.bin",
cost_coins=5000,
view_in_market=1,
extra={"time": 5, "diamonds": 1},
)
@mod_api.on_hud_prompt("your-mod-id", "collect")
def on_collect(username="", value="", player_object=None, root=None, **_):
mod_api.add_currency(player_object, "coins", 500)
return "Monument bonus collected: +500 coins."
Trigger the action from the ContextBar Lua:
function ContextBar.nps_collect_monument(element)
game.setPlayerDisplayName("NPSHUD:your-mod-id:collect:1")
end
Common mistakes
| Mistake | Result | Fix |
|---|---|---|
Writing player["coins"] = X directly |
The HUD can continue reading coins_actual. |
Use add_currency or set_currency. Call request_property_update for an immediate HUD refresh. |
| Guessing a save field format | Data can be written in the wrong shape and lost or ignored. | Check Player data reference or inspect your own save before writing. |
Leaving out **_ or **kwargs in an event handler |
An additional event argument can raise an error in the handler. | Keep a catch all keyword argument in event handlers. |
| Missing a required permission | The mod may fail installation or its declared UI may not load. | Add the permission that matches the manifest feature. See Permissions. |
Using an internal game.* function when a documented API already covers the task |
Incorrect arguments can crash the client. | Use Client to server commands or ContextBar buttons when possible. |
| Generating object IDs with a simple formula | Two objects can receive the same ID and cause save conflicts. | Use mod_api.allocate_id. |
| Reusing a monster graphic without matching its genes and size | The monster can use the wrong sound, footprint, portrait, or other linked assets. | Keep graphic_file, genes, and size_x/size_y together. |
| Using a new genes value without supplying the matching linked assets | The client can reference assets that do not exist and crash. | Provide the related portrait, spore graphic, and select sound, or reuse a complete existing combination. |
| Zipping the containing folder instead of its contents | mod.json is not at the archive root, so the loader does not recognize the mod. |
Check the archive with unzip -l yourmod.npsmod. |
Deploying a mod
Packaging and local installation currently use a zip and copy workflow.
- Zip the mod contents so
mod.jsonis at the archive root. Name the file<your-mod-id>.npsmodor use a normal.zipextension. - Install it through the launcher, or copy it with adb:
adb push yourmod.npsmod /sdcard/yourmod.npsmod adb shell run-as com.nextstars.nps cp /sdcard/yourmod.npsmod files/nps_server/mods/yourmod.npsmod - Restart the launcher server after Python changes. Restart the game client after client asset or Lua changes.
filesDir/BBB_android1/downloads/<same path> before
restarting the client when necessary.
Game command reference
This table lists the commands handled by the embedded server, the handler used for each
command, and the request parameter names read by that handler. An (inline)
entry means the command logic is defined directly in the dispatch table.
Show all commands
| Command | Handler | Parameters |
|---|---|---|
| Loading... | ||