Specs: https://github.com/Sonolus/wiki-engine-specs/tree/develop/docs
For skin, new standard #GRID_
sprites are available and skins should implement them. Engines can use these sprites in preview mode to draw lines (although engines are discouraged to use them for everything, eg don't use them for lanes but use actual lane sprites)
Sprites: https://github.com/Sonolus/sonolus-core/blob/develop/src/common/core/skin/skin-sprite-name.ts#L101-L107
Simple overview of preview mode:
- Preview mode simply draws everything onto a canvas like a static image, and player can scroll around.
- Typically you draw the level as a 2D image like various chart viewers.
- Implement archetypes of interest. Eg if you want to draw notes, implement the note archetype that has the same name as in level data.
preprocess
callback should have very light workload, most of the work should be done inrender
callback.- Typically in
preprocess
callback you would setup UI, and canvas (scrolling direction and size). - For engines that don't have length available in level so canvas size can't be calculated directly, you would have variables storing the latest beat/time of notes, and each note entity updates those variables in
preprocess
callback. render
callback simply does the drawing.
For Sonolus.js users:
- There shouldn't be any breaking changes and your existing code should still work.
- You can use Sekai engine as a reference for preview mode implementation: https://github.com/NonSpicyBurrito/sonolus-pjsekai-engine/tree/develop/preview
- You can now omit the file names in CLI arguments and configs, and it will look for
sonolus-cli.config.*
andindex.*
automatically. You can clean up your paths: https://github.com/NonSpicyBurrito/sonolus-pjsekai-engine/commit/b3bc24b0970c24997fd1459a69eef5a757cfab8e
Sonolus.js guide: https://github.com/Sonolus/wiki-sonolus.js-guide/tree/develop/docs
Sonolus.js project templates are updated, you can bootstrap using the 0.7.3
branches.
- New progress UI configuration, used in preview and watch mode.
- Remove autoplay, replaced by watch mode.
- Add progress UI.
Allows player to watch a level with advanced features like pause/resume/time skip.
Specs: https://github.com/Sonolus/wiki-engine-specs/tree/develop/docs Runtime definitions: https://discord.com/channels/696599620259807242/949074528884129824/1175665405361147924 Sonolus.js Guide: https://github.com/Sonolus/wiki-sonolus.js-guide/tree/develop/docs Sonolus.js Guide Code: https://github.com/Sonolus/wiki-sonolus.js-guide-code
Overview:
- Watch mode can be thought of as a scripted video.
- When player uses progress bar to time skip, the
skip
value in Runtime Update block will be set to1
. - Entity spawning/despwaning is entirely managed by Sonolus runtime.
- Watch mode has a global
updateSpawn
callback, where you need to return a value indicating the current "time" (it can be any timeline) - Archetype has
spawnTime
anddespawnTime
callback where you need to return values indicating when said entity is alive. - Any entity where the current time is within spawn/despawn time range, will be automatically spawned; and automatically despawned when outside of range.
Spawn
function can only be called duringpreprocess
. Entities spawned bySpawn
will also be managed the same way as regular entities.- Entities with input need to set
time
in Entity Input block. - SFX must be scheduled during
preprocess
using the scheduled variants. Non scheduled functions are not supported. - Particles will be cleared when time skips.
Patterns and gotchas in watch mode:
- Progress UI is recommended to be full width at bottom of the screen.
updateSpawn
typically returns the current scaled time (or time if the engine does not use time scale)- Note's
spawnTime
typically returns the minimum visual time, anddespawnTime
typically returns the maximum visual time. - For entities that should always be alive, you can simply return large numbers (eg
spawnTime
returns-999999
,despawnTime
returns999999
) - For entities that have despawn time smaller than spawn time, they will never be spawned.
- An entity can be spawned and despawned multiple times due to time skip.
- Because of multiple spawns,
initialize
callback may be called multiple times. If you have logic that is only supposed to execute once, do something like this:
initialize() {
if (hasInitialized) return
hasInitialized = true
// One time initialization logic here
}
- An entity can be despawned because it leaves the time range, or it could be because player used time skip. Logics in your
terminate
callback that are only meant to be executed when leaving the time range (such as note playing particle effects), should check theskip
value in Runtime Update block:
terminate() {
if (skip) return
// Spawn particle effect
}
- For entities that deal with persistent particle effects (such as hold connectors), keep in mind that particle effects are cleared on every time skip, so you must spawn them again.
- For Sonolus.js users, packages are available under
beta
tag. - For Sonolus.js users, you can use Sekai engine as reference implementation: https://github.com/NonSpicyBurrito/sonolus-pjsekai-engine/tree/develop
No specs are available yet, you can type type definition in: https://github.com/Sonolus/sonolus-core/tree/sonolus-0.8.0
- Version updates to
12
.
- Entity
ref
is now renamed toname
(however entity dataref
is stillref
, no change)
- Archetype
data
is now renamed toimports
.
- Archetype now has
exports
, for exporting data to be saved in a replay and used in watch mode.
- Now has
buckets
, for result screen when watching replay.
- New
ExportValue(index, value)
function to export value for the specific index ofexports
.
- Now has
inputOffset
andreplay
value in Runtime Environment block. - New Level Bucket block (similar to play mode)
- Two special archetype import name
#JUDGMENT
and#ACCURACY
.
- At play mode result screen, player can now create a replay. Sonolus will store all necessary information in replay configuration and replay data resources.
- A replay can be watched using watch mode of the engine.
- In play mode, engine can use
exports
to export values needed in order for watch mode to watch the replay. - In watch mode, engine can use
imports
to import not only values from Level Data, but also values exported by play mode, as well as the special#JUDGMENT
and#ACCURACY
values. - Using these imported values, watch mode needs to reconstruct what the replay gameplay was like, so players can watch and study it.
- Determine what data is necessary for watch mode to reconstruct a replay's gameplay. For most engines, judgment and accuracy are enough and they are automatically available for importing. For some engines you might need more data, such as activation/deactivation times of sliders in Sekai.
- In play mode code, implement exporting those data.
- In watch mode code, implement importing those data and changing gameplay based on them.
- Note despawn time in watch mode: because accuracy value is supposed to be
hitTime - targetTime
, so despawn time can be simply shifted by adding its accuracy. - Note hit effects in watch mode: change it depending on its judgment. This includes particle effects, SFX (Perfect/Great/Good sounds), lane effects, etc.
- Sim line despawn time in watch mode: change it depending on connecting notes despawn times.
- Buckets in watch mode: implementation should be the same as play mode.
- Use the
replay
value in Runtime Environment block to determine if watch mode is currently watching replay or not, and skip unnecessary code. - When exporting values in play mode, exporting
0
will be optimized away by Sonolus in the final replay data file to not take up any extra space, so encode your data in a way that can best make use of this to reduce replay data file size.
- You can use Sekai engine for reference: https://github.com/NonSpicyBurrito/sonolus-pjsekai-engine/tree/sonolus-0.8.0
defineData
is now renamed todefineImport
.defineExport
available for play mode, and returns a function you can call to export:
class Note extends Archetype {
export = defineExport({
foo: { name: 'ExportNameForFoo', type: Number },
bar: { name: 'ExportNameForBar', type: Boolean },
baz: { name: 'ExportNameForBaz', type: DataType<MyEnum> },
})
updateParallel() {
this.export('foo', 42)
this.export('bar', true)
this.export('baz', MyEnum.MagicValue)
}
}
- You can use
replay.isReplay
in watch mode to check if it's currently watching replay.
Specs are not yet available, use sonolus-0.8.0
branch in sonolus-core
repo as reference for type definitions.
- New
hasMultiplayer
indicates if server has multiplayer.
type ServerInfo = {
+ hasMultiplayer: boolean
}
Identical to other item types.
Identical to other item types.
- When client attempts to create a room, a request with body of
CreateRoomRequest
will be sent. - If server decides room creation is allowed, should respond with a body of
CreateRoomResponse
. - Once player has entered the room, exiting the app to share room link for other players to join might cause disconnect, so
name
contains the name of room to be created and allows room creator to share the room link before the room has finished being created. - Server should either create the room immediately and allow everyone to join but reserving a spot for the room creator, or reserve the room until room creator has finished creating.
- When client has finished creating, it joins the room with create options as query with header
Sonolus-Room-Key
containing the value ofkey
. - Server can use
Sonolus-Room-Key
to verify the client joining is the room creator, and create/reconfigure the room based on query.
type CreateRoomRequest = {}
type CreateRoomResponse = {
name: string
key: string
creates: ServerOptionsSection[]
}
- When client attempts to join a room, a request with body of
JoinRoomRequest
will be sent with headerSonolus-Signature
containing signature of the body signed by Sonolus. - Server should verify that: the signature matches body;
type
is of valueauthenticateMultiplayer
;address
is server's address;room
is room's name;time
is recent. - If server decides joining is successful, should respond with a body of
JoinRoomResponse
. url
contains the URL to WebSocket multiplayer server (wss://...
orws://...
),type
contains the room type (currently onlyround
).- Connect request to the multiplayer server will have
Sonolus-Room-Session
header with value ofsession
, server can use it to relay information to the multiplayer server.
type JoinRoomRequest = {
type: 'authenticateMultiplayer'
address: string
room: string
time: number
userProfile: UserProfile
}
type JoinRoomResponse = {
url: string
type: 'round'
session: string
}
type RoomItem = {
name: string
title: string
subtitle: string
master: string
tags: Tag[]
cover?: SRL
bgm?: SRL
preview?: SRL
}
Server search is now renamed to ServerOptionsSection
and is used in many places, eg room creation options.
- type Search = {
+ type ServerOptionsSection = {
type: string
title: Text | (string & {})
icon?: Icon
options: ServerOption[]
}
Type is no longer necessary.
- type SRL<T extends ResourceType> = {
- type: T
+ type SRL = {
hash: string
url: string
}
- Ease is now
camelCase
. - This affects UI in Engine Configuration and Particle Data.
- Particle version bumped to
3
.
- A new
multiplayer
value in Runtime Environment block.
- Multiplayer is a WebSocket server.
- Messages from server to clients are called events, messages from clients to server are called commands. Type definitions can be found in
sonolus-core
repo. - Server owns the room state (room status, current selected level, players in the room, etc)
- Client upon joining the room, first receives an
UpdateEvent
and makes a local copy of the room state. - When server changes the room state, server should send the corresponding event to clients.
- Clients upon receiving events, mutate their local copies of the room state accordingly to keep in sync with server's room state.
- Clients when performing actions, send the corresponding commands to server.
- If an command is allowed, server mutates its room state and send event to clients.
- To improve UX in high latency situations, client has optimistic update.
- This means that clients may send invalid commands to server, and server should be tolerant to invalid commands.
- Clients however are not tolerant to invalid events, so server should always make sure events are valid.
- When client first connects to the server, it's in an uninitialized state, and will ignore all received messages until an
UpdateEvent
. - When client starts gameplay, it enters a suspended state, and will ignore all received messages.
- When client exits gameplay, it sends a
FinishGameplayCommand
and reverts back to uninitialized state, awaiting anUpdateEvent
.
Events are designed in a way to accommodate common state mutations such that multiplayer server can scale with large amount of players. For example, when a player has finished gameplay and server needs to update the score of said player, server sending the entire scoreboard (n players' scores) to all clients (n players) would result in an O(n^2) update. Instead, server can send a specialized event achieving O(n).
- Room has three phases: selecting, preparing, and playing.
- During selecting: room lead can select which level to play, change standard level options and auto exit, and manage suggestions; other players can add levels to suggestions. Once room lead clicks confirm, room moves to preparing phase.
- During preparing: every player can change configuration, and ready/skip. Once room master clicks start, room moves to playing phase.
- During playing: readied players will start playing the level; skipped players remain in the room waiting for results and can participate in other activities such as chatting and suggesting for next round's level. Once room master clicks finish, room moves back to selecting phase.
- Room master can: give room master to another player; change who is room lead; kick players; starts and finishes rounds; edit room options.
- Server can set room master to
room
so no player is room master.
- Room lead can: select level; change standard level options; change auto exit; manage suggestions.
- Server can set room lead to
room
so no player is room lead.
- Suggestions is a collaborative editing level list that can be used for multiple purposes.
- The most basic usage is for other players to suggest levels to room lead.
- However server can use it as a playlist, as a way for everyone to pick a level and randomize which one to be played, as a way for a competitive match to do pick and bans, etc.
- Self explanatory.
- Server can use chat to implement more complex features such as bot commands.
- Scoreboard is completely customizable by the server, and can be used to implement any type of scoring.
- Different scoreboard sections can be used to group players together, and can be used to represent teams.
- Player scores are strings, so you can use not just numbers but also texts like "winner" etc.
- Start with a representation of room state.
- When a client connects, implement sending connected client
UpdateEvent
; implement sending other clientsAddUserEvent
. - When a client disconnects, implement sending other clients
RemoveUserEvent
. - When receiving a command from client, implement handling the command by mutating room state and sending events to all clients.
RoomUser
:authentication
is the body ofJoinRoomRequest
base64 encoded,signature
is theSonolus-Signature
header ofJoinRoomRequest
.AddChatMessageCommand
:nonce
should be echoed back to the sender'sAddChatMessageEvent
, but omitted from other clients.state
inStartRoundEvent
: will be echoed back by clients inStartGameplayCommand
andFinishGameplayCommand
, server should use it to check if the round has already ended.seed
inStartRoundEvent
: should be a number between0
and1
, will be used to populate PRNG so every client will randomize the same.UpdateLevelEvent
: upon receiving, client will reset level options to empty.UpdateStatusEvent
: upon receiving, if changing status to selecting, client will set all user statuses to waiting; if changing status to playing, client will reset results to empty, and set all skipped user's statuses to waiting.UpdateStatusEvent
: upon receiving, if changing status to playing, server should sendStartRoundEvent
to readied clients.FinishGameplayCommand
: upon receiving, client will go back to uninitialized state and wait for anUpdateEvent
.AddUserEvent
: upon receiving, client will set added user's status to waiting.RemoveUserEvent
: upon receiving, client will set master to room if removed user was master; will set lead to room if removed user was lead; will remove removed user's result; will remove removed user's status; will remove removed user from scoreboard sections.
Specs and types are not yet available.
- New
hasCommunity
indicates if item has community section.
type ItemDetails<T> = {
item: T
description: string
+ hasCommunity: boolean
sections: ItemSection<T>[]
}
- Actions are presented as buttons, clicking will bring up a form for player to fill out and submit.
- If there are top comments, more button is available to expand into fully paginated comments list; otherwise "no comments" is display and cannot expand.
type ItemCommunity = {
actions: ServerForm[]
topComments: ItemCommunityComment[]
}
- Paginated comment list.
type ItemCommunityCommentList = {
pageCount: number
comments: ItemCommunityComment[]
}
- Requested when player submits an action.
- Server can use response to instruct Sonolus to update community section.
type SubmitItemCommunityActionRequest = {
values: string
}
type SubmitItemCommunityActionResponse = {
shouldUpdateCommunity?: boolean
shouldNavigateCommentsToPage?: number
}
type ServerTextOption = {
query: string
name: Text | (string & {})
type: 'text'
placeholder: Text | (string & {})
+ limit?: number
}
type ServerTextAreaOption = {
query: string
name: Text | (string & {})
type: 'textArea'
placeholder: Text | (string & {})
limit?: number
}
- type ServerOptionsSection = {
+ type ServerForm = {
type: string
title: Text | (string & {})
icon?: Icon
options: ServerOption[]
}
type ItemCommunityComment = {
author: string
time: number
content: string
actions: ServerForm[]
}
Specs and types are not yet available. This update includes breaking changes.
- Now server specifies a list of buttons to be displayed on home page.
type ServerInfo = {
title: string
description?: string
- hasAuthentication: boolean
- hasMultiplayer: boolean
+ buttons: ServerInfoButton[]
banner?: SRL
}
type ServerInfoButton = {
type:
| 'authentication'
| 'multiplayer'
| 'post'
| 'playlist'
| 'level'
| 'replay'
| 'skin'
| 'background'
| 'effect'
| 'particle'
| 'engine'
}
- Now has optional
creates
property. - If present, a create button will be visible, and player can click it to bring up the forms to fill out to create an item.
- Creating process will lead to
/create
and/upload
endpoints below.
type ItemInfo<T> = {
+ creates?: ServerForm[]
searches?: ServerForm[]
sections: ItemSection<T>[]
banner?: SRL
}
- After filling out the create form, a request with form values will be posted.
- If success, server should respond with
name
of the created item. - If additionally files need to be uploaded, the response should also include an upload
key
, and a list ofhashes
of files to be uploaded. - Hashes should be obtained from the form values.
type CreateItemRequest = {
values: string
}
type CreateItemResponse = {
name: string
key: string
hashes: string[]
}
- Client will post to this endpoint if
/create
needs files to be uploaded. Sonolus-Upload-Key
will contain the value ofkey
.- Body will be a multipart form with files specified by
hashes
.
type UploadItemResponse = {}
- New optional leaderboard feature.
- A list of leaderboards can be specified with
leaderboards
property. - Player can select to view each leaderboard.
- All item types can have leaderboards, not limited to levels. Server can for example have a post for a seasonal competition, and use post's leaderboard for competition's player rankings.
type ItemDetails<T> = {
item: T
description: string
hasCommunity: boolean
+ leaderboards: ItemLeaderboard[]
sections: ItemSection<T>[]
}
type ItemLeaderboard = {
name: string
title: Text | (string & {})
}
- Details of a leaderboard.
- It should contain the top records; if signed in, should also show player's placement.
- Player can further expand to see all records of the leaderboard.
type ItemLeaderboardDetails = {
topRecords: ItemLeaderboardRecord[]
}
type ItemLeaderboardRecord = {
name: string
rank: Text | (string & {})
player: string
value: Text | (string & {})
}
- Paginated list of the records of a leaderboard.
type ItemLeaderboardRecordList = {
pageCount: number
records: ItemLeaderboardRecord[]
}
- Details of a record, contains replays.
type ItemLeaderboardRecordDetails = {
replays: ReplayItem[]
}
- Removed
creates
.
type CreateRoomResponse = {
name: string
key: string
- creates: ServerForm[]
}
- Moved endpoint.
- GET /sonolus/{type}/{name}/community
+ GET /sonolus/{type}/{name}/community/info
- Moved endpoint.
- Now only community actions are submitted here.
- POST /sonolus/{type}/{name}/community
+ POST /sonolus/{type}/{name}/community/submit
- Now has
name
property. - Will be used for submitting a comment's action.
type ItemCommunityComment = {
+ name: string
author: string
time: number
content: string
actions: ServerForm[]
}
- Now comments' actions are submitted here.
- Same as
POST /sonolus/{type}/{name}/community/submit
.
- Now has optional
description
property. - Now has optional
required
property. Iftrue
, will require player to modify the value before submitting.
+ description?: string
+ required?: boolean
- New option type which allows player to select an item from the current server.
- Value will be
name
of the selected item.
type ServerServerItemOption = {
query: string
name: Text | (string & {})
description?: string
required?: boolean
type: 'serverItem'
itemType: ItemType
}
- New option type which allows player to select an item from collections.
- Value will be JSON of the selected item.
type ServerCollectionItemOption = {
query: string
name: Text | (string & {})
description?: string
required?: boolean
type: 'collectionItem'
itemType: ItemType
}
- New option type which allows player to select a file.
- Value will be hash of the selected file.
type ServerFileOption = {
query: string
name: Text | (string & {})
description?: string
required?: boolean
type: 'file'
}
Specs and types are not yet available. This update includes breaking changes.
- Stricter JSON validation and detailed validation errors.
- New server configuration.
- To offer configuration, server should include a button of type
configuration
. - Configuration has general options (localization and auto login), as well as server specified options.
- Server specified option values will be included in every request's query just like localization.
- For servers that don't have options, configuration button is optional, but it might still be offered for localization and auto login.
type ServerInfo = {
title: string
description?: string
buttons: ServerInfoButton[]
+ configuration: ServerConfiguration
banner?: Srl
}
type ServerInfoButton = {
type:
| 'authentication'
// ...
+ | 'configuration'
}
type ServerConfiguration = {
options: ServerOption[]
}
- Server can add actions to item details, which will appear as buttons alongside existing buttons like Save/Share/Update/etc.
- This can be used to implement likes/dislikes, rating an item, editing, etc.
- Players can click the buttons to fill out forms and submit actions to
POST /sonolus/{type}/{itemName}/submit
.
type ServerItemDetails<T> = {
item: T
description?: string
+ actions: ServerForm[]
hasCommunity: boolean
leaderboards: ServerItemLeaderboard[]
sections: ServerItemSection<T>[]
}
- Endpoint for submitting item details actions and uploading.
type ServerSubmitItemActionRequest = {
values: string
}
type ServerSubmitItemActionResponse = {
shouldUpdate?: boolean
shouldRemove?: boolean
key: string
hashes: string[]
}
- Now can upload.
type ServerSubmitItemCommunityActionResponse = {
shouldUpdateCommunity?: boolean
shouldNavigateCommentsToPage?: number
+ key: string
+ hashes: string[]
}
- Now can have description.
type ServerItemLeaderboard = {
name: string
title: Text | (string & {})
+ description?: string
}
- Now can display items of any type, specified by
itemType
(eg'level'
). - Now has
searchValues
, if present, a more button will be available and clicking will bring player to lists with the search values as queries. - Now has
search
, if present, a search button will be available and clicking will bring up search form with search values pre filled.
type ServerItemSection<T> = {
title: Text | (string & {})
icon?: Icon | (string & {})
+ itemType: ItemType
items: T[]
+ search?: ServerForm
+ searchValues?: string
}
- Now can have description.
- Now can required player to confirm before submitting.
type ServerForm = {
type: string
title: Text | (string & {})
icon?: Icon | (string & {})
+ description?: string
+ requireConfirmation: boolean
options: ServerOption[]
}
- Now
hash
andurl
are optional.
type Srl = {
- hash: string
+ hash?: string | null
- url: string
+ url?: string | null
}
- Sonolus item locator, used in various places such as forms and multiplayer.
type Sil = {
address: string
name: string
}
required
now required.
type ServerOption = {
query: string
name: Text | (string & {})
description?: string
- required?: boolean
+ required: boolean
}
- New
def
for default value. limit
now required, use0
for unlimited.- New
shortcuts
for a list of shortcut buttons player can use to quickly insert texts.
type ServerTextOption = {
// ...
type: 'text'
+ def: string
placeholder: Text | (string & {})
- limit?: number
+ limit: number
+ shortcuts: string[]
}
- New
def
for default value. limit
now required, use0
for unlimited.- New
shortcuts
for a list of shortcut buttons player can use to quickly insert texts.
type ServerTextAreaOption = {
// ...
type: 'textArea'
+ def: string
placeholder: Text | (string & {})
- limit?: number
+ limit: number
+ shortcuts: string[]
}
def
is nowboolean
.
type ServerToggleOption = {
// ...
type: 'toggle'
- def: 0 | 1
+ def: boolean
}
defs
now renamed todef
.
type ServerMultiOption = {
// ...
type: 'multi'
- defs: boolean[]
+ def: boolean[]
values: (Text | (string & {}))[]
}
- New
def
for default value. - New
allowOtherServers
for controlling if selecting from another server is allowed. - Serialized value now uses SIL.
type ServerServerItemOption = {
// ...
type: 'serverItem'
itemType: ItemType
+ def: Sil | null
+ allowOtherServers: boolean
}
- New option for selecting multiple server items.
type ServerServerItemsOption = {
// ...
type: 'serverItems'
itemType: ItemType
def: Sil[]
allowOtherServers: boolean
limit: number
}
- Now uses SIL instead of level locator.
type AddSuggestionCommand = {
type: 'addSuggestion'
- level: LevelLocator
+ level: Sil
}
type UpdateLevelCommand = {
type: 'updateLevel'
- level: LevelLocator
+ level: Sil
}
type UpdateEvent = {
type: 'update'
// ...
- level?: LevelLocator
+ level: Sil | null
// ...
}
type UpdateLevelEvent = {
type: 'updateLevel'
- level?: LevelLocator
+ level: Sil | null
}
type Suggestion = {
userId: ServiceUserId
- level: LevelLocator
+ level: Sil
}
- Now uses
null
for room as master/lead rather than'room'
.
type UpdateEvent = {
type: 'update'
// ...
- master: 'room' | ServiceUserId
+ master: ServiceUserId | null
- lead: 'room' | ServiceUserId
+ lead: ServiceUserId | null
// ...
}
type UpdateLeadEvent = {
type: 'updateLead'
- lead: 'room' | ServiceUserId
+ lead: ServiceUserId | null
}
type UpdateMasterEvent = {
type: 'updateMaster'
- master: 'room' | ServiceUserId
+ master: ServiceUserId | null
}
type TextChatMessage = {
- userId: 'room' | ServiceUserId
+ userId: ServiceUserId | null
type: 'text'
value: string
}
Specs and types are not yet available. This update includes breaking changes.
- In all endpoints and SRL resources, when returning an error status code, server can optionally return
ServerMessage
, which will be displayed to user.
- Now required to respond with
ServerMessage
.
- When user creates a replay at result screen, Sonolus will ask user if they want to upload the created replay to
source
server. - If confirmed, this endpoint will be requested.
- Respond with a list of submit forms for user to fill out, or respond with empty array/no
submits
to indicate uploading replay is not supported. - Server must implement this endpoint even if uploading replay is not supported.
type ServerLevelResultInfo = {
submits?: ServerForm[]
}
- Similar to other submit/upload endpoints.
type ServerSubmitLevelResultRequest = {
replay: ReplayItem
values: string
}
type ServerSubmitLevelResultResponse = {
key: string
hashes: string
}
- Now has actions server can instruct client to take.
type ServerCreateItemResponse = {
- name: string
key: string
hashes: string[]
+ shouldUpdateInfo?: boolean
+ shouldNavigateToItem?: string
}
- Now has actions server can instruct client to take.
type ServerSubmitItemActionResponse = {
- shouldUpdate?: boolean
- shouldRemove?: boolean
key: string
hashes: string[]
+ shouldUpdateItem?: boolean
+ shouldRemoveItem?: boolean
+ shouldNavigateToItem?: string
}
type ServerMessage = {
message?: string
}
- New optional
description
property. - New optional
help
property.
- New optional
help
property.