======================================================================== This document provides an overview of the BYOO (Build Your Own Ogre) JSON API. BYOO (Build Your Own Ogre) is a set of tools and data for constructing custom Ogre units for use with Steve Jackson Games's Ogre line of games (http://ogre.sjgames.com). Ogre is a registered trademark of Steve Jackson Games (http://www.sjgames.com). The materials presented here conform with the SJ Games online policy (http://www.sjgames.com/general/online_policy.html). Author/maintainer: Stephan Beal (stephan@wanderinghorse.net) ======================================================================== The API is minimal but offers all features necessary for creating custom Ogre models: - Fetch all Ogres. - Fetch all Gear (Ogre equipment). - Fetch an individual an Ogre by its ID. - Fetch site-specific config data. - Saving an Ogre. - Download the database's SQL schema. The HTTP/JSON interface for each of those is described in detail in the following subsections, but first some notes on conventions used in this document: - Each section of this document starts with a hashtag enclosed in square brackets (e.g. [#TheTag]). The intent is that other sections of the document can reference the hashtag, then the reader can search for [#ThatTag] to quickly find the referenced section. - Each API lists its URI, which is a path relative to the top-level BYOO back-end path. e.g. if BYOO is installed as /cgi-bin/byoo then an API URI of /foo means /cgi-bin/byoo/foo. In addition, the response's Content-Type is given. - GET vs POST vs whatever: BYOO is implemented as a CGI, and CGI specifies only a subset of possible HTTP methods, thus it's impossible for a CGI application to offer the complete range of REST-conventional methods (e.g. PUT). All methods of this API use the HTTP GET requests except for /ogre/save, which uses POSTed JSON data. Apropos: all data POSTed to this app must use UTF-8 encoding. - "Official" vs "Fan-submitted": the Gear and Ogre data types have a flag, called isOfficial, which specifies whether the given Gear/Ogre originates from an official Steve Jackson Games source or not. The API refuses to modify "official" entries. - All JSON and text/plain responses from this API use UTF-8 encoding. ======================================================================== Error Reporting Conventions... [#ErrorReporting] The back-end tries its best to always respond "properly", even in the case an exception being thrown in the back-end. Responses with no error always return an HTTP 200 code with a body type specified by the response (typically JSON, but there are a few exceptions). Error responses use HTTP status codes 400 and higher and always return a JSON body unless the error happens at a level outside the control of the back-end framework (e.g. it happens before the framework can take over or after the framework has shut down). Such cases are rare, and are caused either by misconfiguration (which is solvable) or transient server-side problems (which solve themselves). When BYOO responds with an error code (HTTP status 400+) and sets the response's content type to application/json then the response body is an Exception object with this structure: { exception: { ////////////////////////////////////////////////////////////////// // Error codes come from C code, have unspecified values, and // are not terribly useful for the client, but may help the // admin resolve a problem. code: integer, ////////////////////////////////////////////////////////////////// // The "message" is usually a string describing a problem, but // it's possible for the message to be an arbitrary JSON-able // value (e.g. an object or array). BYOO itself only uses // strings in this context, but it's conceivable that code from // another level might throw a different type of value. message: normally a string } } The response includes one more level of embedded object than is strictly necessary, but that's so that client code can simply inspect the inbound JSON for an "exception" property (which the API guarantees does not appear in non-error responses, whereas "message" might potentially be found in other non-error JSON payloads). When running the backend in development mode, exceptions contain additional information: the script's name, code location, and a stack trace. Those data are scrubbed when running in non-development mode because they reveal information which is potentially security-relevant. ======================================================================== Authentication... [#Authentication] There is none. The market for this API encompasses only a handful of people and its backend is open to that entire handful of them. This app, since 2009, has yet to be maliciously assaulted by bots, so authentication has never been needed. Should it be required, the API will be extended to account for passing around an authentication token. Hopefully it won't come to that, but this is the internet and spambot operators have neither shame nor decency, so that can't be ruled out :/. ======================================================================== Site configuration... [#Config] URI: GET /config Response: { ////////////////////////////////////////////////////////////////// // The server-dependent path to Ogre counter/icon images. counterUri: string, ////////////////////////////////////////////////////////////////// // An array of the file names of available Ogre counter/icon // images. counters: array of strings, } The significance of these data are: Ogre instances have an "icon" field which references an image file using only its name (without a path). That name "should" be in the list provided via /config, and (config.counterUri+'/'+ogre.icon) "should" resolve to the server path from which the image can be downloaded. As of 20181115, the counters can either be served directly from the database (via the BYOO CGI) or via the web server's normal file-serving features. The counterUri property of the configuration specifies the path, either way. Letting the web server serve the images is much more efficient in terms of CPU cycles needed for serving each image, but letting BYOO serve them means that the images don't have to be placed separately in a web-servable path. See #Counters for more details. ======================================================================== Ogre Gear... [#Gear] "Gear" is BYOO's term for any optional equipment which can be added to an Ogre except for its treads (which are part of the Ogre construct itself). BYOO hosts a static list of gear and provides no APIs for editing it. A given BYOO installation could host different gear by adding it to the [gear] table of the database. URI: GET /gear Response Content-Type: application/json Response: [ { attack: 4, defense: 4, range: 3, name: "Main Batteries", // shortName is intended for use on record forms shortName: "MB", notes: null, // string or null // #Ciscos (VP) value ciscos: 24, // Database record ID... id: 1, // Specifies whether this is an SJG-offial entry or not: 1 or 0. isOfficial: 1, // Described below. sortOrder: -20.0, //////////////////////////////////////////////////////////// // countXXX are just hints for rendering record forms and // editor fields. They can generally be ignored. countGroupBy: 3, countHintMax: 15, countPerRow: 15 }, {... one entry per Gear item...} ] The order of the array entries is significant: they are sorted by their "intended" display order, with SJG-official entries first (with a hard-coded order derived from their conventional presentation in SJG products), followed by various fan-contributed entries with a fixed order. It is recommended that front-ends honor this order. e.g. when displaying Ogres, their gear "really should" be sorted by the order given here. The sortOrder field is a floating point value representing the relative order of the entries: sorting by that field will restore a "shuffled" Gear list back to "intended" order. ======================================================================== Fetching Ogres... [#OgreGet] URI: GET /ogre/get/{ID} Response Content-Type: application/json Response: { // Database record ID... id: 1, // The name must be unique (case-insensitive) within the database. name: "Mark I", // Optional nickname (string or null). Need not be unique. nickname: null, // Size class. sizeClass: 5, // Movement points. mp: 3, // Treads per movement point tump: 5, // Specifies whether this is an SJG-offial entry or not: 1 or 0. isOfficial: 1, // Optional notes from the submitter (string or null). notes: null, // Map of gear IDs to counts (described below). gear: { "1": 1, "7": 4 }, // #Ciscos point value (calculated by the server). ciscos: 47.45, // An optional "fudge value" to apply to the final #Ciscos value. // This can be used to account for the unspecified "cost" of // unit-specific special abilities. ciscosFudge: 0, // Icon/counter image. See #Config for the path. icon: "MarkI.png", // Last-saved time (updated by the server). lastUpdated: "2009-08-04 20:44:25", // Optional submitter's name/URL/whatever. Front-ends // may choose not to display this. submitter: null } To fetch all Ogres at once, simply remove the /{ID} part from the URI: /ogre/get. In that case, the response is an array of objects with the same structure. Unlike the #Gear entries, the order of the result array is not terribly significant: it orders by #Ciscos value (then by name), but always sorts SJG-official entries first. The ogre.gear property this is a map of #Gear IDs to the number of copies of that item the Ogre has. e.g. a map of {1: 3, 2: 4} says that the Orgre has 3 copies of Gear #1 and 4 copies of Gear #2. It is up to the client application to map this data to the set of #Gear. ======================================================================== Saving an Ogre... [#OgreSave] URI: POST /ogre/save The POSTed body must contain a JSON-encoded copy of the Ogre, structured exactly like the response described for #OgreGet. Certain fields will, if set, be ignored by the server because it manages them itself: ciscos, lastUpdated, isOfficial. The POSTed body must be UTF-8/ASCII encoded, and results are undefined if the data contains a different encoding, e.g. ISO8859-1. If the POSTed Ogre has an id, that record will be updated. If the Ogre has no id, or has an id of null or 0, then a new Ogre record is created. The response is the same Ogre, as saved by the server, in the same structure specified for #OgreGet. This will include updated values for the fields ("ciscos", "lastUpdated") and (if it is a new record) "id". Thus, after saving, the local application should replace the submitted Ogre with the copy returned by the save operation. The server throws an exception if the submitted Ogre is an SJG-official one - it refuses to modify those records. It is legal to fetch an official Ogre, remove its ID, change its name, and re-submit it, which will result in an "inofficial" Ogre with the same stats (except for its name, as names must be unique within the database). ======================================================================== Fetching the whole DB at once... [#WholeDb] As a convenience to client apps, the backend supports fetching all of the various required data at once: URI: GET /db Response Content-Type: application/json Response: { config: as for /config (see #Config), gear: as for /gear (see #Gear), ogres: as for /ogre/get (see #OgreGet) } For a BYOO database containing only SJG-official Ogres, that returns less than 3kb of data. For a "full" database, with 141 ogres, it's about 60kb of data. ======================================================================== Misc. API methods... [#MiscAPI] No API is complete without the obligatory "misc." category of methods... ============================================================ Fetching counters from the db: [#Counters] As mentioned in #Config, the Ogre icons/counters may be served by the CGI script or the web server, as determined by the path set in config.counterUri. To explicly fetch a counter from the CGI backend: URI: /counter/{COUNTER_NAME} Response Content-Type: image/SOMETHING (the precise image type is not guaranteed) Where {COUNTER_NAME} is one of the names from the config.counters counters (see #Config). Because the names _may_ refer to files within the filesystem, these names are case-sensitive. ============================================================ Fetch this API documentation document: URI: /doc/api.txt Response Content-Type: text/plain That serves this file. ============================================================ Fetch the DB schema: URI: GET /db/schema Response Content-Type: text/plain That includes the sqlite3 schema and some version of the db data set from a static SQL file (not exported dynamically from the db) ============================================================ Download the underling sqlite3 database: URI: GET /db/download Response Content-Type: application/octet-stream The Content-Disposition header gets set, which "should" tell a browser to download the file, rather than display it. Size: the db has historically never exceeded 150kb. Noting that if the db is updated while the download is happening, corruption might result. i don't currently have a way of avoiding that, but it's unlikely that this will ever appear because the time window for such an overlap is even smaller than this API's intended audience. ======================================================================== Ciscos: Victory Point values [#Ciscos] This toolset uses "Ciscos" as a victory point value, rather than SJG point values. There is no official SJG algorithm for assigning point values to Ogres - the official VP values were assigned based on hundreds of playtest sessions. The Cisco method, named after its creator, Francisco "Cisco" J. Cestero, is a very simple algorithm for determining approximate Ogre point values. It produces results close to the published VP values for official SJG Ogre models, and does so with _much_ less complexity than the Henry Cobb algorithm (http://www.hcobb.com/gev/formcalc.html). The algorithm for calculating the Cisco value of an Ogre is: ( (2 * Treads) + (Total Ciscos of all Gear) ) * 5.357 / (10 - (Ogre's MP)) The Cisco value of "gear" (weapons and accessories) is listed in the Gear table of the database, and were apparently derived from old calculations by Cisco himself. In any case, they differ from SJG VP. To convert Ciscos to Armor Unit Equivalents, simply divide the Ciscos by 10. To convert Ciscos to SJG VPs, multiply the Ciscos value by 0.6. Using this algorithm, we get values pretty close to those published by SJG. For example: Model SJG VP(*) Cisco VP(**) Mark I 25 28 Mark II 50 57 Mark III 100 98 Mark IIIb 120 123 Fencer 125(***) 132 Fencer-B 150(***) 140 Mark IV 150 162 Mark V 150 162 (*) = Source: Ogre Miniatures (first edition) Rulebook (**) = Source: the above algorithm applied to data from our DB. (***) = The "Ogre Minitatures Update" revised the Fencer from 140 to 125 VP and presents the Fencer-B with 150VP. The Cobb algorithm gets closer to the SJ-published values, but (A) it's _much_ more complicated and (B) the published values are based off of hundreds of playtest hours, not an algorithm. The Cobb calculator is available online (http://www.hcobb.com/gev/formcalc.html) and some older version of the algorithm is available in The Ogre Book (http://www.sjgames.com/ogre/products/ogrebook/), published by SJG (and available as a PDF from their online shop). The article has the advantage of explaining the algorithm, whereas the web page simply hosts the calculator app without explaining how it calculates. Despite its complexity, the Cobb forumula has one notable advantage over Ciscos: it can be applied to non-Ogre units, whereas the Cisco method cannot. ======================================================================== The End - you win! [#TheEnd] ========================================================================