Development
Getting Started
If you have never done any development for Dysruption and would like a step-by-step guide on how to launch development and production builds of the game, click here.
Known Issues
Dependencies
Docker – used for local development and for packaging releases
Node – used for the game server, the game client, and AWS dependencies
AWS CLI – used mainly by the AWS CDK, but also for AWS authentication
AWS CDK – used to manage the AWS resources used by the system
Git – access to project source code
Angular – used by the Front End (client)
Angular CLI – used to generate new front end components and run the development Angular server
QGIS – used to modify the geospatial database features
Docker login for command line access to stored Dysruption Docker images:
docker login registry.gitlab.nps.edu/moves/dysruption:latest
Create and Publish a New Release
Edit Version Strings
From the develop
branch, modify the following files with the new version string:
shared/app-version.ts
package.json
client/package.json
shared/package.json
Edit cdk/config.txt
Add a unique identifier tag in this file. To be used with the naming of all the CDK generated resources. (e.g. “DEMO”)
Install Packages
Root Folder:
npm run init
CDK Folder:
npm install
Deploy CDK
cd cdk
cdk deploy --outputs-file ../server/cdk-outputs.json ProdStack
Create Git Release
Commit and push all above version file changes (not the
config.txt
)From GitLab
Deployments
->New Release
Create new Tag (e.g. 0.10.0)
Add release title (e.g. Version 0.10.0)
Select a Milestone if applicable
Click
Create Release
Data Development
This is the list of properties that are being used either on the client or server of the game that must be in the GIS data or Flow Solver models. The actual property names used in the data can have any name and be mapped to specific accessors using the Infrastructure Configuration objects.
By Data Model
Node
(GIS Point Feature)
id
: The unique id number for this Node so connectedArcs
can find itname
: String to display to user about whichNode
this isgridX
: The x coordinate to move this Node in schematic viewgridY
: The y coordinate to move this Node in schematic view
Arc
(GIS Line Feature)
from
: The fid of theNode
thisArc
is coming fromto
: The fid of theNode
thisArc
is going toactive
: Whether theArc
is enabled or disabled
By Use Case
Creating a Game
model instance
Node
id
: Read by Arcs to find the Arc’sfrom
and `toname
: Read by Arcs to name themselves if not given a name in GISgridx
: Read by Arcs connected to this Node to determine their grid coordinatesgridy
: Read by Arcs connected to this Node to determine their grid coordinates
Arc
from
: Used to find theNode
thisArc
starts at to determine grid coordinatesto
: Used to find theNode
thisArc
ends at to determine grid coordinatesactive
: Initial state set based on game mode - this requirement will be removed here when scenario editors can set initial property overrides for different games/modes
In order to allow customizing the infrastructures used in Dysruption scenarios without requiring a new deployment of Dysruption, configuration objects can be added to the Dysruption DynamoDB Table to define each one. A configuration object must have the following properties defined:
Configuration File
Many of the display properties for infrastructure components can be modified in the Configuration file:
dysruption/cdk/lib/configuration-construct.ts
Infrastructure Configuration
objectKey
: Always set toINFRASTRUCTURE_TYPE
id
: The unique identifier for which type of infrastructure this represents (e.g.fuel
)displayName
: The name of the infrastructure shown to users in the Scenario EditormapObjectConfiguration
: A Map Object Configuration (as defined below) for every single type of map object (Nodes and Arcs) with the following required accesors:gisId
: referencing a GIS property for the GIS ID of the map objectname
: referencing a GIS property for the human readable name of the map objectvisibility
: referencing a GIS property for which views this object should show up in0
: This object is invisible1
: This object shows in all views2
: This object only shows in the Schematic View3
: This object only shows in the Map View
vulnerable
: Whether this object is vulnerable to being disabled
nodeConfiguration
: A Map Object Configuration for Node map objects (points) with the following required accesors:gridX
: referencing a GIS property for what x value this node will have in the schematic viewgridY
: referencing a GIS property for what y value this node will have in the schematic view
arcConfiguration
: A Map Object Configuration for Arc map objects (lines) with the following required accesors:from
: referencing a GIS property for the Node’s GIS id of where this arc starts fromto
: referencing a GIS property for the Node’s GIS id of where this arc goes toisActive
: referencing a GIS property for determining if this Arc is active/enabled or not
flowSolverAddress
: the AWS ARN address for where the AWS state machine this infrastructure uses during a simulation step
In the Dysruption system, Nodes on the map will merge the mapObjectConfiguration
and nodeConfiguration
objects to define what gets displayed and what actions a user can take on Nodes. Likewise, Arcs on the map will merge the mapObjectConfiguration
and arcConfiguration
objects.
Map Object Configuration
This object is a map between GIS, the Dysruption system, and the flow solver this infrastructure uses. It has the following properties:
accessors
: The list of Accessors (see below) that define default values and how the Dysruption system can access GIS/Flow Solver propertiesactions
: The list of Actions (see below) a user can perform in each game mode‘0’ is Break It Bad, ‘1’ is Fix It Fast, and ‘2’ is Save Our System
tooltipDisplay
: What Display Properties (see below) will be displayed as tooltips when a user mouses over a map objectmapDisplay
: What Display Properties will be shown as labels on top of a map object at all timesdetailDisplay
: What Display Properties will be displayed in the inspector panel when a map object is selectedstyleModifiers
: What Style Modifiers (see below) will be applied to objects of this type
Accessors
This is a map of Properties that either have to be defined for the Dysruption system use (see above configuration objects) or to add default values for properties the flow solver uses that aren’t defined in GIS. If the accessor is one Dysruption requires, the key in the map is listed above (e.g. name
for mapObjectConfiguration), otherwise the key must be unique, but has no other requirement.
Example default accessor definition for mapObjectConfiguration:
accessors: {
gisId: { key: 'fid', customizable: false },
name: { key: 'name', customizable: false },
failureProbabilityOffset: { key: 'failure_probability_offset', defaultValue: 0 },
temperature: { key: 'temperature_property', defaultValue: 72 }
}
Actions
This is the list of actions you can perform in each game mode. The actions object is a map with three keys: 0 (Break It Bad), 1 (Fix It Fast), and 2 (Save Our System). If more game modes get added, more keys will be needed in this map. Each key’s value is a list of actions for that mode.
Each action object in this list has the following properties:
type
: A unique identifier for what type of action this is, must be unique within a game mode’s list of actionsdisplayName
: The name of the action users will see in gameclassName
: Space separate attributes to determine the icon and other styling of this action in gameExamples include
dys-icon target
,dys-icon wrench
,dys-icon swap
conditions
: The list of Conditions (see below) that must all be true for this action to be available for the user to perform on an object (defaults to no conditions)modifications
: The list of Property (see below) changes that will be applied when this action is performedproperty
: The Property whose value will be setvalue
: The value the Property will be set to
cost
: How much of a user’s budget will be used when this action is performed (defaults to 500)
Example default actions for arcObjectConfiguration:
actions: {
"0": [
{ type: 'disable', displayName: 'Attack', className: 'dys-icon target',
conditions: [
{ __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 }
],
modifications: [
{ property: { key: 'xhat' }, value: 1 }
],
cost: 0
}
],
"1": [
{ type: 'enable', displayName: 'Enable', className: 'dys-icon wrench',
conditions: [
{ __type: 'not-equal-condition', property: { key: 'xhat' }, value: 0 }
],
modifications: [
{ property: { key: 'xhat' }, value: 0 }
],
cost: 0
}
],
"2": [
{ type: 'repair', displayName: 'Repair', className: 'dys-icon wrench',
conditions: [
{ __type: 'not-equal-condition', property: { key: 'xhat' }, value: 0 },
{ __type: 'not-equal-condition', property: { key: 'fci' }, value: 100 }
],
modifications: [
{ property: { key: 'xhat' }, value: 0 },
{ property: { key: 'fci' }, value: 97.0 },
{ property: { key: "skip_fci_turns" }, value: 1 }
],
cost: 250 },
{ type: 'replace', displayName: 'Replace', className: 'dys-icon swap',
modifications: [
{ property: { key: 'xhat' }, value: 0 },
{ property: { key: 'fci' }, value: 100 },
{ property: { key: "skip_fci_turns" }, value: 1 }
],
cost: 500 },
{ type: 'maintain', displayName: "Maintain", className: 'dys-icon checklist',
conditions: [
{ __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 },
],
modifications: [
{ property: { key: "skip_fci_turns" }, value: 1 }
],
cost: 50
}
],
}
Display Properties
Display properties all have the following properties to fill out:
displayName
: The name that will be displayed in front of the value (defaults to undefined)conditions
: The list of Conditions necessary that must all be true to show this property (defaults to no conditions)property
: The actual Property being displayedgraphable
: Whether this property is graphable or not (defaults to true)
Examples:
{ displayName: 'Supply', conditions: [{ __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 }], property: { key: 'supply' } },
{ displayName: 'Demand', conditions: [{ __type: 'less-than-equal-condition', property: { key: 'supply' }, value: 0 }], property: { __type: 'absolute-property', key: 'supply' } }
{ property: { key: 'name' } }
Style Modifiers
These objects allow setting various flags to modify the visuals of the map object in game. These include setting the color theme for the infrastructure, the icons to use, how animations are determined, among other things.
Style modifiers have the following properties:
className
: What flag to add to the element if all conditions are trueconditions
: The list of Conditions (see below) that must all be true for this flag to be set (defaults to no conditions)
Examples:
// Styles for infrastructure type
{ className: type },
// Styles for on/off
{
className: 'on',
conditions: [
{ __type: 'equal-condition', property: { key: 'shortfall' }, value: 0 },
]
},
{
className: 'off',
conditions: [
{ __type: 'greater-than-condition', property: { key: 'shortfall' }, value: 0 },
]
},
// Styles for supply/demand
{
className: 'supply',
conditions: [
{ __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 },
]
},
{
className: 'demand',
conditions: [
{ __type: 'less-than-equal-condition', property: { key: 'supply' }, value: 0 },
]
}
Property
This object is a map between the Dysruption system and the GIS/Flow Solver data. Based on the type of Property, the GIS value may be modified. All Property types have the following fields:
key
: The GIS/Flow Solver property we are getting a value from or setting a value todefaultValue
: The default value we want to set in case the property does not exist in GIS on an objectcustomizable
: Whether this property can be customized in the scenario editor or not
AbsoluteProperty
When the value is read, its absolute value will be given used instead.
Add the following fields to your configuration Property object:
__type
:absolute-property
DifferenceProperty
When the value is read, it shows a value that’s the difference between two GIS/Flow Solver properties.
Add the following fields to your configuration Property object:
__type
:difference-property
otherKey
: The key of the GIS/Flow Solver property to subtract from thekey
property
InvertedProperty
When the value is read, it shows the opposite value (really only useful for values of 0 or 1).
Add the following fields to your configuration Property object:
__type
:inverted-property
Conditions
These classes use Properties (see above) to check if they meet certain conditions with the current value.
GreaterThanCondition
This condition is true if the Property’s current value is higher than the Condition’s value.
This condition has the following properties:
__type
:greater-than-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 }
GreaterThanEqualToCondition
This condition is true if the Property’s current value is higher than or equal to the Condition’s value.
This condition has the following properties:
__type
:greater-than-equal-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'greater-than-equal-condition', property: { key: 'fci' }, value: 50 }
LessThanCondition
This condition is true if the Property’s current value is lower than the Condition’s value.
This condition has the following properties:
__type
:less-than-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'less-than-condition', property: { key: 'supply' }, value: 1 }
LessThanEqualToCondition
This condition is true if the Property’s current value is lower than or equal to the Condition’s value.
This condition has the following properties:
__type
:less-than-equal-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'less-than-equal-condition', property: { key: 'fci' }, value: 100 }
EqualCondition
This condition is true if the Property’s current value is equal to the Condition’s value.
This condition has the following properties:
__type
:equal-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'equal-condition', property: { key: 'supply' }, value: 0 }
NotEqualCondition
This condition is true if the Property’s current value is not equal to the Condition’s value.
This condition has the following properties:
__type
:not-equal-condition
property
: The Property to get the current value fromvalue
: The value to compare the Property value against
Example:
{ __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 }
Full Example
Here is what one of the default infrastructure type configuration objects look like:
{
"objectType": "INFRASTRUCTURE_TYPE",
"id": "fuel",
"displayName": "Fuel",
"mapObjectConfiguration": {
"accessors": {
"gisId": {
"key": "fid",
"customizable": false
},
"name": {
"key": "name",
"customizable": false
},
"failureProbabilityOffset": {
"key": "failure_probability_offset",
"defaultValue": 0
},
"temperature": {
"key": "temperature_property",
"defaultValue": 72
}
},
"actions": {
"0": [],
"1": [],
"2": []
},
"detailDisplay": [
{
"displayName": "Name",
"property": {
"key": "name"
},
"graphable": false
},
{
"displayName": "Temperature",
"property": {
"key": "temperature_property"
}
}
],
"mapDisplay": [],
"styleModifiers": [],
"tooltipDisplay": []
},
"nodeConfiguration": {
"accessors": {
"gridX": {
"key": "gridx",
"customizable": false
},
"gridY": {
"key": "gridy",
"customizable": false
},
"penalty": {
"key": "penalty",
"defaultValue": 10
}
},
"actions": {
"0": [],
"1": [],
"2": []
},
"detailDisplay": [
{
"displayName": "Supply",
"property": {
"key": "supply"
},
"conditions": [
{
"__type": "greater-than-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
},
{
"displayName": "Demand",
"property": {
"__type": "absolute-property",
"key": "supply"
},
"conditions": [
{
"__type": "less-than-equal-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
},
{
"displayName": "Shortfall",
"property": {
"key": "shortfall"
}
},
{
"displayName": "Penalty",
"property": {
"key": "penalty"
},
"graphable": false
}
],
"mapDisplay": [
{
"property": {
"key": "name"
}
}
],
"styleModifiers": [
{
"className": "fuel"
},
{
"className": "on",
"conditions": [
{
"__type": "equal-condition",
"property": {
"key": "shortfall"
},
"value": 0
}
]
},
{
"className": "off",
"conditions": [
{
"__type": "greater-than-condition",
"property": {
"key": "shortfall"
},
"value": 0
}
]
},
{
"className": "supply",
"conditions": [
{
"__type": "greater-than-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
},
{
"className": "demand",
"conditions": [
{
"__type": "less-than-equal-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
}
],
"tooltipDisplay": [
{
"displayName": "Supply",
"property": {
"key": "supply"
},
"conditions": [
{
"__type": "greater-than-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
},
{
"displayName": "Demand",
"property": {
"__type": "absolute-property",
"key": "supply"
},
"conditions": [
{
"__type": "less-than-equal-condition",
"property": {
"key": "supply"
},
"value": 0
}
]
}
]
},
"arcConfiguration": {
"accessors": {
"from": {
"key": "from",
"customizable": false
},
"to": {
"key": "to",
"customizable": false
},
"isActive": {
"__type": "inverted-property",
"key": "xhat"
}
},
"actions": {
"0": [
{
"type": "disable",
"displayName": "Attack",
"className": "dys-icon target",
"conditions": [
{
"__type": "not-equal-condition",
"property": {
"key": "xhat"
},
"value": 1
}
],
"modifications": [
{
"property": {
"key": "xhat"
},
"value": 1
}
],
"cost": 0
}
],
"1": [
{
"type": "enable",
"displayName": "Enable",
"className": "dys-icon wrench",
"conditions": [
{
"__type": "not-equal-condition",
"property": {
"key": "xhat"
},
"value": 0
}
],
"modifications": [
{
"property": {
"key": "xhat"
},
"value": 0
}
],
"cost": 0
}
],
"2": [
{
"type": "repair",
"displayName": "Repair",
"className": "dys-icon wrench",
"conditions": [
{
"__type": "not-equal-condition",
"property": {
"key": "xhat"
},
"value": 0
},
{
"__type": "not-equal-condition",
"property": {
"key": "fci"
},
"value": 100
}
],
"modifications": [
{
"property": {
"key": "xhat"
},
"value": 0
},
{
"property": {
"key": "fci"
},
"value": 97
},
{
"property": {
"key": "skip_fci_turns"
},
"value": 1
}
],
"cost": 250
},
{
"type": "replace",
"displayName": "Replace",
"className": "dys-icon swap",
"modifications": [
{
"property": {
"key": "xhat"
},
"value": 0
},
{
"property": {
"key": "fci"
},
"value": 100
},
{
"property": {
"key": "skip_fci_turns"
},
"value": 1
}
],
"cost": 500
},
{
"type": "maintain",
"displayName": "Maintain",
"className": "dys-icon checklist",
"conditions": [
{
"__type": "not-equal-condition",
"property": {
"key": "xhat"
},
"value": 1
}
],
"modifications": [
{
"property": {
"key": "skip_fci_turns"
},
"value": 1
}
],
"cost": 50
}
]
},
"detailDisplay": [
{
"displayName": "Flow",
"property": {
"__type": "absolute-property",
"key": "flow"
}
},
{
"displayName": "FCI",
"property": {
"key": "fci"
}
}
],
"mapDisplay": [],
"styleModifiers": [
{
"className": "fuel"
},
{
"className": "on",
"conditions": [
{
"__type": "equal-condition",
"property": {
"key": "xhat"
},
"value": 0
}
]
},
{
"className": "off",
"conditions": [
{
"__type": "equal-condition",
"property": {
"key": "xhat"
},
"value": 1
}
]
},
{
"className": "forward",
"conditions": [
{
"__type": "less-than-condition",
"property": {
"key": "flow"
},
"value": 0
}
]
},
{
"className": "reverse",
"conditions": [
{
"__type": "greater-than-condition",
"property": {
"key": "flow"
},
"value": 0
}
]
},
{
"className": "stopped",
"conditions": [
{
"__type": "equal-condition",
"property": {
"key": "flow"
},
"value": 0
}
]
},
{
"className": "darkness-fade-low",
"conditions": [
{
"__type": "greater-than-equal-condition",
"property": {
"key": "fci"
},
"value": 0
},
{
"__type": "less-than-condition",
"property": {
"key": "fci"
},
"value": 25
}
]
},
{
"className": "darkness-fade-mid",
"conditions": [
{
"__type": "greater-than-equal-condition",
"property": {
"key": "fci"
},
"value": 25
},
{
"__type": "less-than-condition",
"property": {
"key": "fci"
},
"value": 50
}
]
},
{
"className": "darkness-fade-high",
"conditions": [
{
"__type": "greater-than-equal-condition",
"property": {
"key": "fci"
},
"value": 50
},
{
"__type": "less-than-condition",
"property": {
"key": "fci"
},
"value": 75
}
]
},
{
"className": "darkness-fade-max",
"conditions": [
{
"__type": "greater-than-equal-condition",
"property": {
"key": "fci"
},
"value": 75
}
]
}
],
"tooltipDisplay": [
{
"displayName": "Flow",
"property": {
"__type": "absolute-property",
"key": "flow"
}
},
{
"displayName": "FCI",
"property": {
"key": "fci"
}
}
]
},
"flowSolverAddress": "arn:aws:states:us-west-1:252182408753:stateMachine:APIStateMachineauthor1F103352-9YHjOS78OMcW"
}
Building Images for GitLab
To build an image that will be pushed to the Dysruption Gitlab Registry
docker build registry.gitlab.nps.edu/moves/dysruption:<version> .
Or to re-tag an existing image
docker tag <existing image> registry.gitlab.nps.edu/moves/dysruption:<version>
For example, docker tag dysruption:latest registry.gitlab.nps.edu/moves/dysruption:1.0.0
To build an image with multi-platform support
First, create and use a custom Docker Builder called “mybuilder”. This only needs to happen once.
docker buildx create --name mybuilder --driver docker-container --bootstrap --use
Then, create, tag, and push to the Gitlab Container Repository in one swell foop:
docker buildx build --platform linux/amd64,linux/arm64 -t <image>:<tab> --push .
For example, to build an image for the QGIS Server for both AMD64 and ARM64 and push to the Gitlab repository:
docker buildx build --platform linux/amd64,linux/arm64 -t registry.gitlab.nps.edu/moves/dysruption/qgis_server:latest --push .
Note: The Gitlab Container Registry web page will not display this image correctly in the UI, even though it is stored and accessed correctly.
Login to Docker before pulling or pushing images to the Gitlab repository
docker login registry.gitlab.nps.edu/moves/dysruption:<version>
Push the tagged image to Gitlab (requires Docker Login)
docker push registry.gitlab.nps.edu/moves/dysruption:<version>
For example, docker push registry.gitlab.nps.edu/moves/dysruption:1.0.0
Pull an image from Gitlab (requires Docker Login)
docker pull registry.gitlab.nps.edu/moves/dysruption:<version>