Skip to content

Commit

Permalink
Ported files from other projects and made some modifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
robere2 committed Aug 10, 2020
1 parent d344bd0 commit 0919c1c
Show file tree
Hide file tree
Showing 44 changed files with 2,027 additions and 103 deletions.
105 changes: 2 additions & 103 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,104 +1,3 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port
node_modules
.npmrc
51 changes: 51 additions & 0 deletions AliasedAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Action from './actions/Action'
import Resolver from './actions/Resolver'

/**
* AliasedActions are the lowest level of user-facing elements. Actions are executed by the user and are simply
* a wrapper to reference an Action and it's arguments.
*/
class AliasedAction {

/**
* The key of this AliasedAction. Should probably be CAPS_SNAKE_CASE.
*/
key: string
/**
* Array of server identifiers that this AliasedAction is available on.
*/
availableOn: string[] = []
/**
* The Action which this AliasedAction is a wrapper for.
*/
action: Action

/**
* Constructor
* @param key {string} The key of this item.
*/
constructor (key: string) {
this.key = key

}

/**
* Deserialize a JSON-stringified AliasedAction into an AliasedAction object.
* @param json {string} The JSON to deserialize.
* @return {Promise<AliasedAction>} The AliasedAction that was deserialized.
*/
static async deserialize(json: string) : Promise<AliasedAction> {
const obj = JSON.parse(json)
const aa = new AliasedAction(obj.key)
for(const prop in obj) {
if(!obj.hasOwnProperty(prop)) {
continue
}
aa[prop] = obj[prop]
}
aa.action = await Resolver.deserialize(JSON.stringify(obj.action))
return aa
}
}

export default AliasedAction
56 changes: 56 additions & 0 deletions Button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Buttons are the median user-facing element, between AliasedActions and Screens. Screens contain a list of
* Buttons, and Buttons contain a list of AliasedActions.
*/
class Button {

/**
* The key of this Button. Unique identifier, should probably be CAPS_SNAKE_CASE
*/
key: string
/**
* Array of server identifiers that this Button is available on.
*/
availableOn: string[] = []
/**
* Array of AliasedAction keys which this Button executes. AliasedAction objects are not used directly to reduce
* Action size, remove redundant references to AliasedActions, and due to the async nature in how AliasedActions are
* handled by the client.
*/
actions: string[] = []
/**
* The URL to the image which can be displayed as this Button, specifically on "IMAGES" type Screens.
*/
imageURL = ''
/**
* The translation key for this Button's title.
*/
translationKey = ''

/**
* Constructor
* @param key {string} The key/ID of this item.
*/
constructor (key: string) {
this.key = key
}

/**
* Deserialize a JSON-stringified Button into an Button object.
* @param json {string} The JSON to deserialize.
* @return {Promise<Button>} The Button that was deserialized.
*/
static async deserialize(json: string) : Promise<Button> {
const obj = JSON.parse(json)
const btn = new Button(obj.key)
for(const prop in obj) {
if(!obj.hasOwnProperty(prop)) {
continue
}
btn[prop] = obj[prop]
}
return btn
}
}

export default Button
79 changes: 79 additions & 0 deletions Screen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const ScreenTypes = Object.freeze({
IMAGES: 'IMAGES',
BUTTONS: 'BUTTONS'
})

/**
* A Screen in Quickplay is the highest level of user-facing objects. Screens are a set of Buttons which can be laid
* out in a variety of ways. The client can display these Screens.
*/
class Screen {

/**
* The key of this Screen. Unique identifier, should probably be CAPS_SNAKE_CASE.
*/
key: string
/**
* Array of server identifiers that this Screen is available on.
*/
availableOn: string[] = []
/**
* Array of Button keys which this Screen renders. Button objects are not used directly to reduce Action
* size, remove redundant references to Buttons, and due to the async nature in how Buttons are
* handled by the client.
*/
buttons: string[] = []
/**
* Type of Screen that this Screen is. Should be "IMAGES" or "BUTTONS", in most cases.
* @see ScreenTypes
*/
screenType = ''
/**
* The translation key for this Screen's title.
*/
translationKey = ''
/**
* List of AliasedActions which should be executed when the client clicks this Screen's back button.
*/
backButtonActions: string[] = []
/**
* The URL to the image which can be displayed at the top of this Screen.
*/
imageURL = ''

/**
* Constructor
* @param key {string} The key/ID of this item.
* @param screenType {string} Type of screen that this screen is.
*/
constructor (key: string, screenType: string) {
this.key = key
this.buttons = []
this.screenType = screenType
if(!ScreenTypes[this.screenType]) {
throw new Error('Invalid screen type: Screen type must be IMAGES or BUTTONS.')
}
this.backButtonActions = []
}

/**
* Deserialize a JSON-stringified Screen into an Screen object.
* @param json {string} The JSON to deserialize.
* @return {Promise<Screen>} The Screen that was deserialized.
*/
static async deserialize(json: string) : Promise<Screen> {
const obj = JSON.parse(json)
const screen = new Screen(obj.key, obj.screenType)
for(const prop in obj) {
if(!obj.hasOwnProperty(prop)) {
continue
}
screen[prop] = obj[prop]
}
return screen
}

}

export default Screen
export {ScreenTypes}
83 changes: 83 additions & 0 deletions actions/Action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {Buffer} from 'buffer'

/**
* Actions are the core mechanism behind how Quickplay operates. Whenever the client/user
* clicks a button, presses a keybind, receives instructions from the web server, etc.,
* Besides some of the current commands system, the client is not able to do any I/O other than what
* is available through Actions (eventually, ideally all the Quickplay commands would also run Actions).
*
* Actions are serializable in a similar format to Minecraft packets, and can be sent over the wire.
* The structure is as follows:
* When serialized, all Actions must contain at least 2 bytes. These are the first two bytes, which
* are the Action's ID. All subsequent bytes are considered the payload. They can be considered arguments
* to the Action, and are split up into partitions, each of which is one argument. An argument begins
* with the first 4 bytes being the length x of the argument. After those bytes, the next x bytes are
* the actual argument. This signature repeats, until there are no more bytes.
*
* If there are too few bytes in the Action, a RangeError will be thrown. It is possible
* for a serialized Action to be valid, but the subsequent execution of the Action to fail if there were
* not enough arguments provided in the payload.
*
* Actions can also be sent to the web server, providing context to actions/events occurring on the client,
* such as exceptions, connection status, button presses, etc.
*/
class Action {

static id = 0
id = 0
payloadObjs:Buffer[] = []

/**
* Build an action into a Buffer from its ID and payload list.
* @return {Buffer} Built buffer which can be sent over the wire.
*/
build () : Buffer {
let body = Buffer.alloc(2)
body.writeUInt16BE(this.id, 0)

for(let i = 0; i < this.payloadObjs.length; i++) {
const payloadSize = Buffer.alloc(4)

payloadSize.writeInt32BE(this.payloadObjs[i].byteLength, 0)
body = Buffer.concat([body, payloadSize, this.payloadObjs[i]])
}

return body
}

/**
* Add an item to the payload.
* @param obj {Buffer} Item to add.
*/
addPayload (obj: Buffer) : void {
this.payloadObjs.push(obj)
}

/**
* Get an object from the payload at the specified index
* @param index {number} Index of the item to get. Should be >= 0 and < payloadObjs.length
* @return {Buffer} The payload item, or null if it does not exist.
*/
getPayloadObject(index: number) : Buffer {
if(this.payloadObjs.length <= index) {
return null
}
return this.payloadObjs[index]
}

/**
* Get an item from the Payload and convert it to a String in UTF-8.
* @param index {number} Index of the item to get. Must be >= 0 and < payloadObjs.length
* @return {string} Decoded String
*/
getPayloadObjectAsString(index: number) : string {
const obj = this.getPayloadObject(index)
if(obj == null) {
return null
}
return obj.toString()
}

}

export default Action
Loading

0 comments on commit 0919c1c

Please sign in to comment.