Specification of client-server communication for FEFU MMORPG training project.
This document is a product of collaborative design by students of group B8303A of Far Eastern Federal University (FEFU) of Russian Federation. The purpose of this document is to provide students of group B8303A with unified standard of client-server communication for particular computer science course of second semester of 2013-2014 academic year by https://github.com/klenin/
- FEFU MMORPG Protocol — FEMP/0.3
- Abstract
- Status of This Memo
- Table of Contents
- Requirements
- Introduction
- Terminology
- Authorization
- Game Interaction
- Testing
- Data Invariants
The key words MUST
, MUST NOT
, REQUIRED
, SHALL
, SHALL NOT
, SHOULD
,
SHOULD NOT
, RECOMMENDED
, MAY
, and OPTIONAL
in this document are to be
interpreted as described in RFC 2119.
An implementation is not compliant if it fails to satisfy one or more of the
MUST or REQUIRED level requirements for the protocols it implements. An
implementation that satisfies all the MUST or REQUIRED level and all the SHOULD
level requirements for its protocols is said to be unconditionally compliant
;
one that satisfies all the MUST level requirements but not all the SHOULD level
requirements for its protocols is said to be conditionally compliant.
Client and server communicate with request and response messages. Each message MUST be represented by a single JSON object. Messages are sent via either HTTP/1.1 or WebSocket protocol.
Request message MUST contain a key action
with a corresponding string value
determining required action.
Each request message MUST be answered with a corresponding response message.
Response message MUST contain a key result
with a corresponding value
describing result.
Each response MUST contain key action
with a value of the same key action
of
corresponding request. This one serves for describing response type.
If server does not handle the request regardless of underlying transport and
with regard to value of 'action' key then server MUST respond with a key
result
of value badAction
.
Both request and response messages MAY contain any other key/value pairs specific for particular request/response.
TBD — http://en.wiktionary.org/wiki/TBD
Key in context of JSON message stands for name in name/value pair which is the
same as key/value pair or attribute/value pair. JSON RFC uses name
in such
context.
An authorization stage of communication MUST use HTTP/1.1 as an underlying
transport. A method of HTTP request MUST be POST. Message MUST be HTTP
message-body. Content-Type header of HTTP response MUST be application/json
.
The requirements for user credentials are as follows:
login: [a-zA-Z0-9]{2,36}
i.e. minimal length is 2 symbols and maximum
length is 36 symbols. Allowed charset is latin symbols and numbers.
password: .{6, 36}
i.e. minimal length is 6 symbols and maximum length is
36 symbols. Any character is allowed except characters indexed from 0 to 31
in ASCII.
class: see Player Classes
action: register
login: <new client's login>
password: <new client's password>
class: <player class name>
result: one of: ok, badPassword, badLogin, badClass, loginExists
action: login
login: <client's login>
password: <client's password>
result: one of: ok, invalidCredentials
sid: <string representation of session identifier>
webSocket: <WebSocket server URI>
id: <player ID for use with Game Interaction requests>
fistId: <fists' id for use it when no weapon equipped>
action: logout
sid: <client's sid>
result: one of: ok, badSid
All communication other than authorization stage (with an exception of logout) is done via WebSocket protocol as underlying transport.
Those rules apply to any client-server communication of Game Interaction section. Explicit inclusion of these rules may be ommited. If there is an exception from the rules, the corresponding section shall state it explicitly.
Each request message sent after client has been logged in MUST have a key sid
with a string value of client sid provided by server. In case of invalid sid the
result
key of response MUST have a value of badSid
.
In case of successful response result
key of respone MUST have a value ok
.
Destroys an item. It is possible if distance between item and player centers
doesn't exceed pickUpRadius or item is already in player's inventory. Otherwise
result MUST be badId
. If object with this id is not an item result MUST be
badId
.
If item has consumable
class or expendable
type (see Items)
request body MUST contain amount
field that specify amount of destroyed items.
When amount
field contain negative value or value that is greater than count of
items in stack or destroyed item is not stackable, result MUST be badAmount
.
action: destroyItem
id: <item's id>
amount: <items amount>
Drop item from inventory to the ground. Item's x and y now MUST be the same as client's player ones.
If dropped item has consumable
class or expendable
type (see Items)
request body MUST contain amount
field that specify amount of dropped items.
When amount
field contain negative value or value that is greater than count of
items in stack or dropped item is not stackable, result MUST be badAmount
.
action: drop
id: <id>
amount: <dropped items amount>
result: one of: ok, badSid, badId, badAmount
Equips item of given id onto specified slot. If it is impossible to equip this item onto that slot badSlot is returned. If slot is already occupied then item which is already there gets automatically unequipped and takes of the item being equipped.
If item is on the ground and it is possible to Pick Up it with Pick Up request
then it is also possible to try to equip this item from the ground. If the
result of such equipping is ok
than item is automatically gets picked up.
When equipping a weapon
of subtype two-handed
or bow
while wielding a
some item in other hand — last one MUST be unequipped automatically.
If equipping has modified contents of other slots, then response MUST
contain slots
field, that is mapping from modified slot to id of item, that now
is equipped in this slot.
action: equip
id: <item's id>
slot: <slot to equip onto>
slots: { <slot> : <item id>, ... }
result: one of: ok, badId, badSid, badSlot
There are three versions of Examine. One is for single id, other is for map cell and last is OPTIONAL for multiple id's.
action: examine
id: <actor's identifier>
id: <actor's id>
type: <actor's type. One of `player`, `monster`, `item`, `projectile`>
x: <x coordinate>
y: <y coordinate>
result: one of: ok, badSid, badId
If type is item
response MUST contain the following:
item: {<Item Description*>}
See Item Description
If type is either player
or monster
response MAY contain the following:
health: <actor's current number of health points>
maxHealth: <actor's maximum number of health points>
mana: <actor's current number of mana points>
maxMana: <actor's maximum number of mana points>
inventory: [<item description>, ...]
stats: {<stat name> : <stat value>, ...}
Field stats
MUST contain all stats, described in Stats with corresponding
values.
Under various circumstances inventory
field MAY be present or not. However if
given id is an id of a player corresponding to client issued an Examine
request and this player's inventory is not empty the field inventory
MUST
always be present.
If present, inventory
is an array of item ids.
If type
is monster
response MAY contain these fields:
name: <name of a monster>
mobType: <string describing the type of a monster>
If type
is player
response MAY contain these fields:
slots: {<slot name>: <item description>, ...}
login: <player's login>
Rules for presence of field slots
are the same as for field inventory
.
If slot is empty it MAY be omitted.
For possible slot names see Slots
(x, y) pair belongs to a cell. This cell is about to be examined.
action: examine
x: <x coordinate on map>
y: <y coordinate on map>
Response JSON object MUST contain a single field data
which is an array of
objects each representing a result from single id version of Examine. data MUST
contain information for all objects belonging to given cell.
data: [{<single-id-examine's result}]
The following is OPTIONAL. Examine request could be issued to examine multiple
ids. In such case id
field of request is an array of item's ids. Then
response is an array of objects each containing fields from Examine response for
single id. e.g.
```json
"data":
[
{
"id": 42,
"type": "player",
"login": "John",
"x": 0,
"y": 100,
"result": "ok"
},
{
"id": 43,
"type": "player",
"login": "Snow",
"x": 1,
"y": 99,
"result": "ok"
},
{
"result": "badId"
}
]
```
Dictionary is a JSON object describing mapping from game map cell (recieved via look action) to string value of cell type e.g.
"dictionary":
{
".": "grass",
"#": "wall"
}
action: getDictionary
dictionary: {...}
See Logout
A request for server to provide information for a map area around the client's
player. Such information MUST be provided via keys map
and actors
.
TBD: If size of such area must be standardized
map
key MUST have a value of an array of array of strings. Such strings are
decoded via dictionary got using getDictionary
request. map
2d array has
row-major order. E.g. for a 4x6 (4 rows, 6 columns) map area:
"map":
[
["#", "#", ".", "#", "#", "#"],
[".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", "."],
["#", "#", "#", ".", "#", "#"]
]
actors
key MUST have a value of an array of objects type. Each element of the
array MUST describe a single actor present at the provided area in the following
form:
type: <actor's type. One of `player`, `monster`, `item`, `projectile`>
id: <actor's id>
x: <global map space x coordinate of actor>
y: <global map space y coordinate of actor>
If type
is not item
actor description MUST contain:
health: <actor's current number of health points>
maxHealth: <actor's maximum number of health points>
mana: <actor's current number of mana points>
maxMana: <actor's maximum number of mana points>
If type
is monster
actor description MUST contain these fields:
race: <string describing the race of a monster>
If type
is player
actor description MUST contain theese fields:
class: <string describing the class of a player>
for more information about class field see Player Classes
action: look
map: [[...]]
actors: [{...}, ...]
x: <global map space x coordinate of player's center>
y: <global map space y coordinate of player's center>
See that actors don't contain current player itself
If total weight of items in Player's inventory exceeds this Player's carrying
capacity then result is tooHeavy
.
action: move
direction: one of: west, north, east, south
tick: <tick number for move action>
Request for client player to pick up an item with given ID. Object with this ID
MUST have type item
and distance between player's center and item's center
MUST be less or equal than constant pickUpRadius. Otherwise nothing is picked up
and result is badId
.
If item is contained in someone's inventory than result is badId
.
If total weight of Player's inventory exceeds Player's carrying capacity after
picking up an item and it is possible to pick up item then result MUST be
tooHeavy
.
action: pickUp
id: <item-to-pick-up's id>
For each simulation tick server MUST broadcast current tick to all the clients.
Tick message is neither request nor response message therefore implicit rules
for keys sid
, action
, result
don't apply to it.
Tick message is a JSON object. It MUST contain key tick
with a value of server
current tick number.
Tick numbers are required to grow monotonously by 1
for each tick.
Tick message MAY also contain an array events
of JSON objects describing
events occured at a given tick.
Server MAY decide to send some events only for a subset of clients. e.g. attack action only to ones for which it is visible.
If there are multiple targets for a single attack then server MUST produce
multiple attack
events for each target.
Field killed
may be omitted if it is false
.
event: "attack"
attacker: <attacker's id>
target: <target's id>
blowType: <string describing attack type e.g. "CLAW", "BITE", etc>
dealtDamage: <damage dealt>
killed: <whether target was killed or not>
x: <global-space x coordinate of effect center>
y: <global-space y coordinate of effect center>
radius: <visual effect radius>
type: <type of effect>
Unequips item with given id. badId
is returned if there is no such item
equipped on the client.
action: unequip
slot: <slot to be unequipped>
result: one of: ok, badSlot
badSlot
MUST be returned if slot
field contain invalid slot specificator
or doesn't exist in request.
Use an object by given id. In general object can be used if it is contained in the player's inventory or equipped onto player. Objects can also be used if laying on the ground and can be picked up with Pick Up request.
Some item require certain additional data to be specified. e.g. if using
equipped weapon client MUST specify x
and y
.
badSlot
MUST be returned if using item required to be equipped e.g. using
weapon present in inventory but unequipped.
If no item equipped as a weapon and player want to attack, fistId
(see Login)
MUST be used in request.
action: use
id: <item's id>
Optional data for various items:
x: <global map space x coordinate of target point>
y: <global map space y coordinate of target point>
This section is to be completed.
message: <text describing what's happened as a result of usage>
result: one of: ok, badId, badSlot, badPos
There are a number of request messages available only when server is in the
testing stage. Such messages are marked with "Testing stage only." If such
message to be sent while testing stage is not active, server MUST respond with
"result": "badAction"
.
All testing API provided via web-socket. All testing requests need SID field in
request body and can return badSid
if SID is invalid.
Get a set of current constant values.
action: getConst
result: ok
playerVelocity: <value>
slideThreshold: <value>
ticksPerSecond: <value>
screenRowCount: <value>
screenColumnCount: <value>
pickUpRadius: <value>
Testing stage only.
action: "putItem"
x: <x coordinate of item's center>
y: <y coordinate of item's center>
item: {<Item Description*>}
See also:
result: ok, badPlacing, badItem
id: <item's id>
Testing stage only.
Put specified mob onto the level map.
action: "putMob"
x: <mob's x coordinate>
y: <mob's y coordinate>
stats: {<Stats*>}
inventory : [{<Item Description*>}, ...]
flags: [<Flags*>, ...]
race: <Race*>
dealtDamage: <mob's dealt damage by single attack>
Field dealtDamage
MUST
exist in request. This field is specified by
string of the form NdM
, where N is count of verges dice and M is count
of dice pops.
See also:
id: <mob's id>
result: one of: ok, badPlacing, badFlag, badRace, badInventory, badStats, badDamage
Testing stage only.
Put player instance onto the level map, specifying inventory, slots and stats.
Player has CAN_MOVE
and CAN_BLOW
flags by default.
action: "putPlayer"
x: <player's x coordinate>
y: <player's y coordinate>
inventory: [{<Item Description*>}, ...]
slots: {<Slot name. Slots*> : {<Item Description*>}, ...}
stats: {<Stats*>}
See also:
sid: <player's session id>
id: <player's id>
fistId: <player's fist id>
inventory: [<items' id generated in inventory>]
slots: {<Slot name. Slots*> : <items' id generated for slot>, ...}
result: one of: ok, badPlacing, badInventory, badSlot, badStats
If there was not items in inventory
/slots
field in putPlayer
request
then corresponding fields in response MAY be omitted.
Testing stage only.
This request tell server perform some player's action.
action: "enforce"
enforcedAction: <object that represent any action of player>
actionResult: <object that represent result of perfoming requested action>
result: one of: ok, badSid, badEnforcedAction
Testing stage only.
Set x, y coordinates of player.
action: "setLocation"
x: <desired player's x coordinate>
y: <desired player's y coordinate>
result: on of: ok, badPlacing, badSid
Testing stage only.
Upload a set of constants for a server to immediately set up. There is a reference set of constants for the purposes of cross server testing:
{
"action": "setUpConst",
"playerVelocity": 1.0,
"slideThreshold": 0.1,
"ticksPerSecond": 60,
"screenRowCount": 7,
"screenColumnCount": 9,
"pickUpRadius": 1.5
}
action: setUpConst
playerVelocity: <portion of tile player travels through the world per second>
slideThreshold: <portion of tile which gets ignored when moving towards it>
ticksPerSecond: <number of simulation cycles per second>
screenRowCount: <number of tile rows in a rectangle get via `look`>
screenColumnCount: <number of tile columns in a rectangle get via `look`>
pickUpRadius: <maximum distance between player's center and item's one for latter to be picked up>
result: one of: ok, badAction
Testing stage only.
Upload map to server and set it up as currently active. If server assumes
uploaded map to be invalid (e.g. malformed map array or wrong cell value) then
the request MUST be responded with badMap
. Innermost values of map
key are
those found in Dictionary.
Minimal size of map is 1x1. Empty map is badMap
.
{
"action": "setUpMap",
"map": [
[<column count number of values>],
[<column count number of values>],
[<column count number of values>],
... <total of row count arrays with row values>
[<column count number of values>]
]
}
result: one of: ok, badMap, badAction
This request MUST be sent each time at the beginning of testing stage.
Once this message is responded with "result": "ok"
, it is valid to state
that testing stage is now active.
It is invalid to request Start Testing when testing stage is already active. In
such case request MUST be answered with "result": "badAction"
.
action: startTesting
result: one of: ok, badAction
Testing stage only.
Each testing stage MUST be closed with this request. Once responded with
"result": "ok"
it is valid to state that server is no more in the testing
stage.
action: stopTesting
result: one of: ok, badAction
This section describes involved data.
All units in game are measured in tiles, assuming one tile width and height to be 1.0. Upscaling this for rendering is up to client.
Game Objects are the contents of actors
array of look
response. Items in
inventory of creatures are also game objects. Hovewer those don't appear in
look
's actors
.
The type
of game object may be one of:
player
monster
item
projectile
All of those MUST have square shape. Player's and monster's size MUST be 1. Item's and projectile's size MUST be 0.
warrior
rogue
mage
left-hand
- one-handed weapon or shield slotright-hand
- one-handed weapon or shield slotammo
- slot for weapon expendables. e.g. arrows for bow.left-finger
- ringright-finger
- ringneck
- amuletbody
- armorhead
- helmetforearm
- glovesfeet
- boots
name : <monster's name>
x: <mob's x coordinate>
y: <mob's y coordinate>
stats: {<Stats*>}
race: <Race*>
Monsters have a simple logic realized by flags (see Flags). Describe main concepts of monsters' behavior logic.
- If monster found target, it has to go to it.
- If monster gets attacked by player, it MUST get player as new target.
- If monster gets attacked by another monster, first one has to options:
- if it was fighting with another monster, one MAY switch its target to attacker
- if it wasn't, one MUST gets attacker as new target
Every item MUST have a weight. Number of items in inventory and slots of any Creature (Monster or Player) MUST be limited by maximum weight Creature can handle.
If weight of item to be picked up exceeds maximum Player's weight then pickUp
request MUST result in tooHeavy
.
If total weight of items in inventory of any Creature somehow exceeds its
carrying capacity then Creature becomes immobilized. If Player is to be
immobilized then move
request MUST result in tooHeavy
.
Some requests from Testing section MAY induce a need to describe an item. Examine returns itemData in the same form.
Item description is a set of JSON fields:
name: <item's name>
weight: <item's weight>
class: <Item Class*>
type: <Item Type*>
subtype: <Item Subtype*>
bonuses : [{<Bonus Description*>}, ...]
effects : [{<Effect Description*>}, ...]
Based on the context those fields may be wrapped in JSON object.
Bonus application order is determined via order of bonuses elements. Same applies to effects.
Field subtype
MUST be omitted if there is no subtypes specified for item type.
Otherwise it MUST be present.
No shield
can be equipped with bow
or two-handed
.
arrows
can only be equipped with bow
.
garment
consumable
types for garment
class:
amulet
ring
armor
shield
helm
gloves
boots
weapon
expendable
subtypes for weapon
type of garment
class:
one-handed
two-handed
bow
subtypes for expendable
type of garment
class:
arrows
Only equipment may have bonuses.
TBD: REFORMULATE section
Json-object represent bonus description. There is a one kind of bonuses in Angband: constant bonus to some stat. Our team added something like it: bonus to any stat of active object (mob, player), but it can be calculated as percent bonus. So, we have two kinds of bonuses: constant bonus and percent bonus. Calculation for constant bonus: += <bonus_val> Calculation for percent bonus: += * <bonus_val> Value of bonus can be a negative.
stat: <stat modified by bonus>
effectCalculation: <const|percent>
value: <value of bonus>
TBD: REFORMULATE section
Json-object respresent effect description. Effects in Angband are something hazy, so I invented my own. Effect is something, that modified some only stat during some time. There is two kinds of effects: OnGoingEffect (modify stat continuously with some fixed value) and BonusEffect (add some bonus).
stat : <stat modified by effect>
duration : <effect duration in seconds>
type: "ongoing"
value: <value of effect>
duration: <effect duration in seconds>
effectType: "bonus"
bonus: <bonus description>
name: <projectile's name>
type: 'projectile'
x : <projectile's x coord>
y : <projectile's y coord>
Both players and monsters have race. Player always have PLAYER
race.
Possible races are:
ORC
EVIL
TROLL
GIANT
DEMON
METAL
DRAGON
UNDEAD
ANIMAL
PLAYER
Races aree taken from Angband (except for the PLAYER
race).
The following flags are available:
CAN_MOVE
CAN_BLOW
HATE_<race>
Angband has many flags. Of our flags CAN_MOVE
and CAN_BLOW
have relation to
Angband's NEVER_MOVE
and NEVER_BLOW
ones.
TBD: REFORMULATE the following: If mob hasn't flag NEVER_MOVE (NEVER_BLOW), it has CAN_MOVE (CAN_BLOW) flag by default. Otherwise, if mob has never-flag in angband, it hasn't corresponding flag in our MMO.
Angband have no HATE_<race>
flag. E.g. HATE_ORC
, HATE_PLAYER
etc.
TBD: describe thoroughly what does it mean to HATE_<race>
etc
Both players and monsters have stats. Stat describes to what extent a character possesses a natural, in-born characteristic.
Stats in Angband:
- STR
- INT
- WIS
- DEX
- CON
- SPEED
- STEALTH
- SEARCH
- LIGHT
- TUNNEL
Our game does not implement all of those. Instead we are using the following stats:
STRENGTH
(STR)INTELLIGENCE
(INT)DEXTERITY
(DEX)SPEED
(SPEED)DEFENSE
MAGIC_RESISTANCE
CAPACITY
HP
MAX_HP
MP
MAX_MP
Some requests from Testing section require to specify stats field. Stats field MUST be JSON object containing zero or more of the aforementioned stats.
If any particular stat as well as the whole stats field of request is not required for testing it MAY be omitted.
TBD: rework stats and describe which stats affect which.