mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 16:17:57 -05:00
231 lines
22 KiB
CoffeeScript
231 lines
22 KiB
CoffeeScript
c = require './common'
|
|
ThangComponentSchema = require './thang_component'
|
|
|
|
SpecificArticleSchema = c.object()
|
|
c.extendNamedProperties SpecificArticleSchema # name first
|
|
SpecificArticleSchema.properties.body = { type: 'string', title: 'Content', description: "The body content of the article, in Markdown.", format: 'markdown' }
|
|
SpecificArticleSchema.displayProperty = 'name'
|
|
|
|
side = {title: "Side", description: "A side.", type: 'string', 'enum': ['left', 'right', 'top', 'bottom']}
|
|
thang = {title: "Thang", description: "The name of a Thang.", type: 'string', maxLength: 30, format:'thang'}
|
|
|
|
eventPrereqValueTypes = ["boolean", "integer", "number", "null", "string"] # not "object" or "array"
|
|
EventPrereqSchema = c.object {title: "Event Prerequisite", format: 'event-prereq', description: "Script requires that the value of some property on the event triggering it to meet some prerequisite.", "default": {eventProps: []}, required: ["eventProps"]},
|
|
eventProps: c.array {'default': ["thang"], format:'event-value-chain', maxItems: 10, title: "Event Property", description: 'A chain of keys in the event, like "thang.pos.x" to access event.thang.pos.x.'}, c.shortString(title: "Property", description: "A key in the event property key chain.")
|
|
equalTo: c.object {type: eventPrereqValueTypes, title: "==", description: "Script requires the event's property chain value to be equal to this value."}
|
|
notEqualTo: c.object {type: eventPrereqValueTypes, title: "!=", description: "Script requires the event's property chain value to *not* be equal to this value."}
|
|
greaterThan: {type: 'number', title: ">", description: "Script requires the event's property chain value to be greater than this value."}
|
|
greaterThanOrEqualTo: {type: 'number', title: ">=", description: "Script requires the event's property chain value to be greater or equal to this value."}
|
|
lessThan: {type: 'number', title: "<", description: "Script requires the event's property chain value to be less than this value."}
|
|
lessThanOrEqualTo: {type: 'number', title: "<=", description: "Script requires the event's property chain value to be less than or equal to this value."}
|
|
containingString: c.shortString(title: "Contains", description: "Script requires the event's property chain value to be a string containing this string.")
|
|
notContainingString: c.shortString(title: "Does not contain", description: "Script requires the event's property chain value to *not* be a string containing this string.")
|
|
|
|
GoalSchema = c.object {title: "Goal", description: "A goal that the player can accomplish.", required: ["name", "id"]},
|
|
name: c.shortString(title: "Name", description: "Name of the goal that the player will see, like \"Defeat eighteen dragons\".")
|
|
i18n: {type: "object", format: 'i18n', props: ['name'], description: "Help translate this goal"}
|
|
id: c.shortString(title: "ID", description: "Unique identifier for this goal, like \"defeat-dragons\".") # unique somehow?
|
|
worldEndsAfter: {title: 'World Ends After', description: "When included, ends the world this many seconds after this goal succeeds or fails.", type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
|
|
howMany: {title: "How Many", description: "When included, require only this many of the listed goal targets instead of all of them.", type: 'integer', minimum: 1}
|
|
hiddenGoal: {title: "Hidden", description: "Hidden goals don't show up in the goals area for the player until they're failed. (Usually they're obvious, like 'don't die'.)", 'type': 'boolean', default: false}
|
|
killThangs: c.array {title: "Kill Thangs", description: "A list of Thang IDs the player should kill, or team names.", uniqueItems: true, minItems: 1, "default": ["ogres"]}, thang
|
|
saveThangs: c.array {title: "Save Thangs", description: "A list of Thang IDs the player should save, or team names", uniqueItems: true, minItems: 1, "default": ["humans"]}, thang
|
|
getToLocations: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},
|
|
who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
|
|
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
|
|
keepFromLocations: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
|
|
who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
|
|
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
|
|
leaveOffSides: c.object {title: "Leave Off Sides", description: "Sides of the level to get some Thangs to leave across.", required: ["who", "sides"]},
|
|
who: c.array {title: "Who", description: "The Thangs which must leave off the sides of the level.", minItems: 1}, thang
|
|
sides: c.array {title: "Sides", description: "The sides off which the Thangs must leave.", minItems: 1}, side
|
|
keepFromLeavingOffSides: c.object {title: "Keep From Leaving Off Sides", description: "Sides of the level to keep some Thangs from leaving across.", required: ["who", "sides"]},
|
|
who: c.array {title: "Who", description: "The Thangs which must not leave off the sides of the level.", minItems: 1}, thang
|
|
sides: side, {title: "Sides", description: "The sides off which the Thangs must not leave.", minItems: 1}, side
|
|
collectThangs: c.object {title: "Collect", description: "Thangs that other Thangs must collect.", required: ["who", "targets"]},
|
|
who: c.array {title: "Who", description: "The Thangs which must collect the target items.", minItems: 1}, thang
|
|
targets: c.array {title: "Targets", description: "The target items which the Thangs must collect.", minItems: 1}, thang
|
|
keepFromCollectingThangs: c.object {title: "Keep From Collecting", description: "Thangs that the player must prevent other Thangs from collecting.", required: ["who", "targets"]},
|
|
who: c.array {title: "Who", description: "The Thangs which must not collect the target items.", minItems: 1}, thang
|
|
targets: c.array {title: "Targets", description: "The target items which the Thangs must not collect.", minItems: 1}, thang
|
|
|
|
ResponseSchema = c.object {title: "Dialogue Button", description: "A button to be shown to the user with the dialogue.", required: ["text"]},
|
|
text: {title: "Title", description: "The text that will be on the button", "default": "Okay", type: 'string', maxLength: 30}
|
|
channel: c.shortString(title: "Channel", format: 'event-channel', description: 'Channel that this event will be broadcast over, like "level-set-playing".')
|
|
event: {type: 'object', title: "Event", description: "Event that will be broadcast when this button is pressed, like {playing: true}."}
|
|
buttonClass: c.shortString(title: "Button Class", description: 'CSS class that will be added to the button, like "btn-primary".')
|
|
i18n: {type: "object", format: 'i18n', props: ['text'], description: "Help translate this button"}
|
|
|
|
PointSchema = c.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
|
|
x: {title: "x", description: "The x coordinate.", type: "number", "default": 15}
|
|
y: {title: "y", description: "The y coordinate.", type: "number", "default": 20}
|
|
|
|
SpriteCommandSchema = c.object {title: "Thang Command", description: "Make a target Thang move or say something, or select/deselect it.", required: ["id"], default: {id: "Captain Anya"}},
|
|
id: thang
|
|
select: {title: "Select", description: "Select or deselect this Thang.", type: 'boolean'}
|
|
say: c.object {title: "Say", description: "Make this Thang say a message.", required: ["text"]},
|
|
blurb: c.shortString(title: "Blurb", description: "A very short message to display above this Thang's head. Plain text.", maxLength: 50)
|
|
mood: c.shortString(title: "Mood", description: "The mood with which the Thang speaks.", "enum": ["explain", "debrief", "congrats", "attack", "joke", "tip", "alarm"], "default": "explain")
|
|
text: {title: "Text", description: "A short message to display in the dialogue area. Markdown okay.", type: "string", maxLength: 400}
|
|
sound: c.object {title: "Sound", description: "A dialogue sound file to accompany the message.", required: ["mp3", "ogg"]},
|
|
mp3: c.shortString(title: "MP3", format: 'sound-file')
|
|
ogg: c.shortString(title: "OGG", format: 'sound-file')
|
|
preload: {title: "Preload", description: "Whether to load this sound file before the level can begin (typically for the first dialogue of a level).", type: 'boolean', "default": false}
|
|
responses: c.array {title: "Buttons", description: "An array of buttons to include with the dialogue, with which the user can respond."}, ResponseSchema
|
|
i18n: {type: "object", format: 'i18n', props: ['blurb', 'text'], description: "Help translate this message"}
|
|
move: c.object {title: "Move", description: "Tell the Thang to move.", required: ['target'], default: {target: {x: 20, y: 20}, duration: 500}},
|
|
target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.'}
|
|
duration: {title: "Duration", description: "Number of milliseconds over which to move, or 0 for an instant move.", type: 'integer', minimum: 0, default: 500, format: 'milliseconds'}
|
|
|
|
NoteGroupSchema = c.object {title: "Note Group", description: "A group of notes that should be sent out as a result of this script triggering.", displayProperty: "name"},
|
|
name: {title: "Name", description: "Short name describing the script, like \"Anya greets the player\", for your convenience.", type: "string"}
|
|
dom: c.object {title: "DOM", description: "Manipulate things in the play area DOM, outside of the level area canvas."},
|
|
focus: c.shortString(title: "Focus", description: "Set the window focus to this DOM selector string.")
|
|
showVictory: {
|
|
title: "Show Victory",
|
|
description: "Show the done button and maybe also the victory modal.",
|
|
enum: [true, 'Done Button', 'Done Button And Modal'] # deprecate true, same as 'done_button_and_modal'
|
|
}
|
|
highlight: c.object {title: "Highlight", description: "Highlight the target DOM selector string with a big arrow."},
|
|
target: c.shortString(title: "Target", description: "Target highlight element DOM selector string.")
|
|
delay: {type: 'integer', minimum: 0, title: "Delay", description: "Show the highlight after this many milliseconds. Doesn't affect the dim shade cutout highlight method."}
|
|
offset: _.extend _.cloneDeep(PointSchema), {title: 'Offset', description: 'Pointing arrow tip offset in pixels from the default target.', format: null}
|
|
rotation: {type: 'number', minimum: 0, title: "Rotation", description: "Rotation of the pointing arrow, in radians. PI / 2 points left, PI points up, etc."}
|
|
sides: c.array {title: "Sides", description: "Which sides of the target element to point at."}, {type: 'string', 'enum': ['left', 'right', 'top', 'bottom'], title: "Side", description: "A side of the target element to point at."}
|
|
lock: {title: "Lock", description: "Whether the interface should be locked so that the player's focus is on the script, or specific areas to lock.", type: ['boolean', 'array'], items: {type: 'string', enum: ['surface', 'editor', 'palette', 'hud', 'playback', 'playback-hover', 'level', ]}}
|
|
letterbox: {type: 'boolean', title: 'Letterbox', description:'Turn letterbox mode on or off. Disables surface and playback controls.'}
|
|
|
|
goals: c.object {title: "Goals", description: "Add or remove goals for the player to complete in the level."},
|
|
add: c.array {title: "Add", description: "Add these goals."}, GoalSchema
|
|
remove: c.array {title: "Remove", description: "Remove these goals."}, GoalSchema
|
|
|
|
playback: c.object {title: "Playback", description: "Control the playback of the level."},
|
|
playing: {type: 'boolean', title: "Set Playing", description: "Set whether playback is playing or paused."}
|
|
scrub: c.object {title: "Scrub", description: "Scrub the level playback time to a certain point.", default: {offset: 2, duration: 1000, toRatio: 0.5}},
|
|
offset: {type: 'integer', title: "Offset", description: "Number of frames by which to adjust the scrub target time.", default: 2}
|
|
duration: {type: 'integer', title: "Duration", description: "Number of milliseconds over which to scrub time.", minimum: 0, format: 'milliseconds'}
|
|
toRatio: {type: 'number', title: "To Progress Ratio", description: "Set playback time to a target playback progress ratio.", minimum: 0, maximum: 1}
|
|
toTime: {type: 'number', title: "To Time", description: "Set playback time to a target playback point, in seconds.", minimum: 0}
|
|
toGoal: c.shortString(title: "To Goal", description: "Set playback time to when this goal was achieved. (TODO: not implemented.)")
|
|
|
|
script: c.object {title: "Script", description: "Extra configuration for this action group."},
|
|
duration: {type: 'integer', minimum: 0, title: "Duration", description: "How long this script should last in milliseconds. 0 for indefinite.", format: 'milliseconds'}
|
|
skippable: {type: 'boolean', title: "Skippable", description: "Whether this script shouldn't bother firing when the player skips past all current scripts."}
|
|
beforeLoad: {type: 'boolean', title: "Before Load", description: "Whether this script should fire before the level is finished loading."}
|
|
|
|
sprites: c.array {title: "Sprites", description: "Commands to issue to Sprites on the Surface."}, SpriteCommandSchema
|
|
|
|
surface: c.object {title: "Surface", description: "Commands to issue to the Surface itself."},
|
|
focus: c.object {title: "Camera", description: "Focus the camera on a specific point on the Surface.", format:'viewport'},
|
|
target: {anyOf: [PointSchema, thang, {type: 'null'}], title: "Target", description: "Where to center the camera view."}
|
|
zoom: {type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 64, title: "Zoom", description: "What zoom level to use."}
|
|
duration: {type:'number', minimum: 0, title: "Duration", description: "in ms"}
|
|
bounds: c.array {title:'Boundary', maxItems: 2, minItems: 2, default:[{x:0,y:0}, {x:46, y:39}], format: 'bounds'}, PointSchema
|
|
isNewDefault: {type:'boolean', format: 'hidden', title: "New Default", description: 'Set this as new default zoom once scripts end.'} # deprecated
|
|
highlight: c.object {title: "Highlight", description: "Highlight specific Sprites on the Surface."},
|
|
targets: c.array {title: "Targets", description: "Thang IDs of target Sprites to highlight."}, thang
|
|
delay: {type: 'integer', minimum: 0, title: "Delay", description: "Delay in milliseconds before the highlight appears."}
|
|
lockSelect: {type: 'boolean', title: "Lock Select", description: "Whether to lock Sprite selection so that the player can't select/deselect anything."}
|
|
|
|
sound: c.object {title: "Sound", description: "Commands to control sound playback."},
|
|
suppressSelectionSounds: {type: "boolean", title: "Suppress Selection Sounds", description: "Whether to suppress selection sounds made from clicking on Thangs."}
|
|
music: c.object { title: "Music", description: "Control music playing"},
|
|
play: { title: "Play", type: "boolean" }
|
|
file: c.shortString(title: "File", enum:['/music/music_level_1','/music/music_level_2','/music/music_level_3','/music/music_level_4','/music/music_level_5'])
|
|
|
|
ScriptSchema = c.object {
|
|
title: "Script"
|
|
description: 'A script fires off a chain of notes to interact with the game when a certain event triggers it.'
|
|
required: ["channel"]
|
|
'default': {channel: "world:won", noteChain: []}
|
|
},
|
|
id: c.shortString(title: "ID", description: "A unique ID that other scripts can rely on in their Happens After prereqs, for sequencing.") # uniqueness?
|
|
channel: c.shortString(title: "Event", format: 'event-channel', description: 'Event channel this script might trigger for, like "world:won".')
|
|
eventPrereqs: c.array {title: "Event Checks", description: "Logical checks on the event for this script to trigger.", format:'event-prereqs'}, EventPrereqSchema
|
|
repeats: {title: "Repeats", description: "Whether this script can trigger more than once during a level.", type: 'boolean', "default": false}
|
|
scriptPrereqs: c.array {title: "Happens After", description: "Scripts that need to fire first."},
|
|
c.shortString(title: "ID", description: "A unique ID of a script that must have triggered before the parent script can trigger.")
|
|
noteChain: c.array {title: "Actions", description: "A list of things that happen when this script triggers."}, NoteGroupSchema
|
|
|
|
LevelThangSchema = c.object {
|
|
title: "Thang",
|
|
description: "Thangs are any units, doodads, or abstract things that you use to build the level. (\"Thing\" was too confusing to say.)",
|
|
format: "thang"
|
|
required: ["id", "thangType", "components"]
|
|
'default':
|
|
id: "Boris"
|
|
thangType: "Soldier"
|
|
components: []
|
|
},
|
|
id: thang # TODO: figure out if we can make this unique and how to set dynamic defaults
|
|
# TODO: split thangType into "original" and "majorVersion" like the rest for consistency
|
|
thangType: c.objectId(links: [{rel: "db", href: "/db/thang_type/{($)}/version"}], title: "Thang Type", description: "A reference to the original Thang template being configured.", format: 'thang-type')
|
|
components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
|
|
|
|
LevelSystemSchema = c.object {
|
|
title: "System"
|
|
description: "Configuration for a System that this Level uses."
|
|
format: 'level-system'
|
|
required: ['original', 'majorVersion']
|
|
'default':
|
|
majorVersion: 0
|
|
config: {}
|
|
links: [{rel: "db", href: "/db/level.system/{(original)}/version/{(majorVersion)}"}]
|
|
},
|
|
original: c.objectId(title: "Original", description: "A reference to the original System being configured.", format: "hidden")
|
|
config: c.object {title: "Configuration", description: "System-specific configuration properties.", additionalProperties: true, format: 'level-system-configuration'}
|
|
majorVersion: {title: "Major Version", description: "Which major version of the System is being used.", type: 'integer', minimum: 0, default: 0, format: "hidden"}
|
|
|
|
GeneralArticleSchema = c.object {
|
|
title: "Article"
|
|
description: "Reference to a general documentation article."
|
|
required: ['original']
|
|
format: 'latest-version-reference'
|
|
'default':
|
|
original: null
|
|
majorVersion: 0
|
|
links: [{rel: "db", href: "/db/article/{(original)}/version/{(majorVersion)}"}]
|
|
},
|
|
original: c.objectId(title: "Original", description: "A reference to the original Article.")#, format: "hidden") # hidden?
|
|
majorVersion: {title: "Major Version", description: "Which major version of the Article is being used.", type: 'integer', minimum: 0}#, format: "hidden"} # hidden?
|
|
|
|
LevelSchema = c.object {
|
|
title: "Level"
|
|
description: "A spectacular level which will delight and educate its stalwart players with the sorcery of coding."
|
|
required: ["name", "description", "scripts", "thangs", "documentation"]
|
|
'default':
|
|
name: "Ineffable Wizardry"
|
|
description: "This level is indescribably flarmy."
|
|
documentation: {specificArticles: [], generalArticles: []}
|
|
scripts: []
|
|
thangs: []
|
|
}
|
|
c.extendNamedProperties LevelSchema # let's have the name be the first property
|
|
_.extend LevelSchema.properties,
|
|
description: {title: "Description", description: "A short explanation of what this level is about.", type: "string", maxLength: 65536, "default": "This level is indescribably flarmy!", format: 'markdown'}
|
|
documentation: c.object {title: "Documentation", description: "Documentation articles relating to this level.", required: ["specificArticles", "generalArticles"], 'default': {specificArticles: [], generalArticles: []}},
|
|
specificArticles: c.array {title: "Specific Articles", description: "Specific documentation articles that live only in this level.", uniqueItems: true, "default": []}, SpecificArticleSchema
|
|
generalArticles: c.array {title: "General Articles", description: "General documentation articles that can be linked from multiple levels.", uniqueItems: true, "default": []}, GeneralArticleSchema
|
|
background: c.objectId({format: 'hidden'})
|
|
nextLevel: c.objectId(links: [{rel: "extra", href: "/db/level/{($)}"}, {rel:'db', href: "/db/level/{(original)}/version/{(majorVersion)}"}], format: 'latest-version-reference', title: "Next Level", description: "Reference to the next level players will player after beating this one.")
|
|
scripts: c.array {title: "Scripts", description: "An array of scripts that trigger based on what the player does and affect things outside of the core level simulation.", "default": []}, ScriptSchema
|
|
thangs: c.array {title: "Thangs", description: "An array of Thangs that make up the level.", "default": []}, LevelThangSchema
|
|
systems: c.array {title: "Systems", description: "Levels are configured by changing the Systems attached to them.", uniqueItems: true, default: []}, LevelSystemSchema # TODO: uniqueness should be based on "original", not whole thing
|
|
victory: c.object {title: "Victory Screen", default: {}, properties: {'body': {type: 'string', format: 'markdown', title: 'Body Text', description: 'Inserted into the Victory Modal once this level is complete. Tell the player they did a good job and what they accomplished!'}, i18n: {type: "object", format: 'i18n', props: ['body'], description: "Help translate this victory message"}}}
|
|
i18n: {type: "object", format: 'i18n', props: ['name', 'description'], description: "Help translate this level"}
|
|
icon: { type: 'string', format: 'image-file', title: 'Icon' }
|
|
|
|
|
|
c.extendBasicProperties LevelSchema, 'level'
|
|
c.extendSearchableProperties LevelSchema
|
|
c.extendVersionedProperties LevelSchema, 'level'
|
|
c.extendPermissionsProperties LevelSchema, 'level'
|
|
|
|
module.exports = LevelSchema
|
|
|
|
# To test:
|
|
# 1: Copy the schema from http://localhost:3000/db/level/schema
|
|
# 2. Open up the Treema demo page http://localhost:9090/demo.html
|
|
# 3. tv4.addSchema(metaschema.id, metaschema)
|
|
# 4. S = <paste big schema here>
|
|
# 5. tv4.validateMultiple(S, metaschema) and look for errors
|