|
@@ -3,6 +3,16 @@ import "./const.ecal" as const
|
|
import "./helper.ecal" as hlp
|
|
import "./helper.ecal" as hlp
|
|
import "./engine.ecal" as engine
|
|
import "./engine.ecal" as engine
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ GameState holds the runtime state of all active games.
|
|
|
|
+*/
|
|
|
|
+GameState := {}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ Websocket holds all active websocket connections.
|
|
|
|
+*/
|
|
|
|
+Websocket := {}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
Get details of a game world.
|
|
Get details of a game world.
|
|
|
|
|
|
@@ -16,7 +26,7 @@ sink GetGameWorld
|
|
let gameWorld
|
|
let gameWorld
|
|
|
|
|
|
try {
|
|
try {
|
|
- gameName := event.state.query.gameName[0]
|
|
+ let gameName := event.state.query.gameName[0]
|
|
|
|
|
|
if gameName != "main" {
|
|
if gameName != "main" {
|
|
raise(const.Errors.EntityNotFound, "Game world {{gameName}} not found")
|
|
raise(const.Errors.EntityNotFound, "Game world {{gameName}} not found")
|
|
@@ -24,18 +34,60 @@ sink GetGameWorld
|
|
|
|
|
|
gameWorld := tmpl.newGameWorld(gameName)
|
|
gameWorld := tmpl.newGameWorld(gameName)
|
|
|
|
|
|
- if db.fetchNode(gameName, gameName, const.NodeKinds.ConfigurationObject) == null {
|
|
+ mutex GameStateMutex {
|
|
- db.storeNode(gameName, gameWorld)
|
|
+
|
|
|
|
+ if GameState[gameName] == null {
|
|
|
|
+ db.storeNode(gameName, gameWorld)
|
|
|
|
+ sprites := []
|
|
|
|
+
|
|
|
|
+ for i in range(1, 8) {
|
|
|
|
+
|
|
|
|
+ posX := math.floor(rand() * gameWorld.screenWidth - 100) + 100
|
|
|
|
+ posY := math.floor(rand() * gameWorld.screenHeight - 100) + 100
|
|
|
|
+ size := math.floor(rand() * 30) + 20
|
|
|
|
+ rot := rand() * math.Pi * 2
|
|
|
|
+
|
|
|
|
+ sprites := add(sprites, tmpl.newAsteroid("asteroid-{{i}}", posX, posY, size, rot, 0.005))
|
|
|
|
+ }
|
|
|
|
|
|
- sprite := tmpl.newSpriteNode("asteroid", 300, 300, 40, 20, 0.01)
|
|
+ GameState[gameName] := {
|
|
- db.storeNode(gameName, sprite)
|
|
+ "players" : {},
|
|
|
|
+ "sprites" : sprites,
|
|
|
|
+ "stats" : {},
|
|
|
|
+ "world" : gameWorld
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+
|
|
} except e {
|
|
} except e {
|
|
error(e)
|
|
error(e)
|
|
- db.raiseWebEventHandled({"code" : const.ErrorCodes[e.type], "body" : {"error" : e.type}})
|
|
+ db.raiseWebEventHandled({"status" : const.ErrorCodes[e.type], "body" : {"error" : e.type}})
|
|
} otherwise {
|
|
} otherwise {
|
|
- db.raiseWebEventHandled({"code" : 200, "body" : {"result" : "success", "gameworld" : gameWorld}})
|
|
+ db.raiseWebEventHandled({"cstatusode" : 200, "body" : {"result" : "success", "gameworld" : gameWorld}})
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ Register a new websocket connection
|
|
|
|
+
|
|
|
|
+ Endpoint: wss://<host>/db/sock/gamestate
|
|
|
|
+*/
|
|
|
|
+sink WebSocketRegister
|
|
|
|
+ kindmatch ["db.web.sock"]
|
|
|
|
+ statematch {"path" : "gamestate", "method" : "GET"}
|
|
|
|
+ priority 0
|
|
|
|
+{
|
|
|
|
+ let gameName := event.state.query.gameName[0]
|
|
|
|
+ let playerName := event.state.query.playerName[0]
|
|
|
|
+ let commID := event.state.commID
|
|
|
|
+
|
|
|
|
+ mutex WebsocketMutex {
|
|
|
|
+ Websocket[commID] := {"gamename" : gameName}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ log("Register websocket for player: ", playerName, " in game: ", gameName, " commID: ", commID)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -55,20 +107,35 @@ sink RegisterNewPlayer
|
|
try {
|
|
try {
|
|
let playerName := event.state.bodyJSON.player
|
|
let playerName := event.state.bodyJSON.player
|
|
let gameName := event.state.bodyJSON.gameName
|
|
let gameName := event.state.bodyJSON.gameName
|
|
|
|
+ let gameWorld := GameState[gameName].world
|
|
|
|
+
|
|
|
|
+ mutex GameStateMutex {
|
|
|
|
+ if GameState[gameName].players[playerName] == null {
|
|
|
|
+
|
|
|
|
+ posY := math.floor(rand() * gameWorld.screenHeight - 100) + 100
|
|
|
|
|
|
- gameWorld := tmpl.DefaultGameWorld
|
|
+ GameState[gameName].players[playerName] := tmpl.newPlayer(event.state.bodyJSON.player, 20, posY)
|
|
|
|
|
|
- if db.fetchNode(gameName, playerName, const.NodeKinds.GameWorldObject) == null {
|
|
+ /*
|
|
- sprite := tmpl.newSpriteNode(event.state.bodyJSON.player, 21, 21)
|
|
+ Reset any player score
|
|
- db.storeNode(gameName, sprite)
|
|
+ */
|
|
|
|
+ addEvent("changescore", "main.gamescore", {
|
|
|
|
+ "id" : playerName,
|
|
|
|
+ "part" : gameName,
|
|
|
|
+ "changeFunc" : func (s) {
|
|
|
|
+ s.score := 0
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ log("Registered player: ", playerName, " for game:", gameName)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- log("Registered player: ", playerName, " for game:", gameName)
|
|
|
|
} except e {
|
|
} except e {
|
|
error(e)
|
|
error(e)
|
|
- db.raiseWebEventHandled({"code" : const.ErrorCodes["InternalError"], "body" : {"error" : const.Errors.InternalError}})
|
|
+ db.raiseWebEventHandled({"status" : const.ErrorCodes["InternalError"], "body" : {"error" : const.Errors.InternalError}})
|
|
} otherwise {
|
|
} otherwise {
|
|
- db.raiseWebEventHandled({"code" : 200, "body" : {
|
|
+ db.raiseWebEventHandled({"status" : 200, "body" : {
|
|
"result" : "success",
|
|
"result" : "success",
|
|
"sprite" : sprite,
|
|
"sprite" : sprite,
|
|
"gameworld" : gameWorld
|
|
"gameworld" : gameWorld
|
|
@@ -78,38 +145,62 @@ sink RegisterNewPlayer
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
- Process player input.
|
|
+ Handle player input - send over an established websocket connection.
|
|
-
|
|
|
|
- Endpoint: /db/ecal/input
|
|
|
|
*/
|
|
*/
|
|
-sink PlayerInput
|
|
+sink WebSocketHandler
|
|
- kindmatch ["db.web.ecal"]
|
|
+ kindmatch ["db.web.sock.data"]
|
|
- statematch {"path" : "input", "method" : "POST"}
|
|
+ statematch {"path" : "gamestate", "method" : "GET"}
|
|
- priority 10
|
|
+ priority 0
|
|
{
|
|
{
|
|
- let sprite
|
|
|
|
- let gameWorld
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
- let playerName := event.state.bodyJSON.player
|
|
+ let playerName := event.state.data.player
|
|
- let gameName := event.state.bodyJSON.gameName
|
|
+ let gameName := event.state.data.gameName
|
|
- let state := event.state.bodyJSON.state
|
|
+ let action := event.state.data.action
|
|
|
|
+ let state := event.state.data.state
|
|
|
|
|
|
- state["key"] := playerName
|
|
+ mutex GameStateMutex {
|
|
- state["kind"] := const.NodeKinds.GameWorldObject
|
|
+ if GameState[gameName].players[playerName] != null {
|
|
-
|
|
+ for [k, v] in state {
|
|
- db.updateNode(gameName, state)
|
|
+ GameState[gameName].players[playerName][k] := v
|
|
|
|
+ }
|
|
|
|
+ if not action in ["move", "stop move"] and GameState[gameName].players[playerName].action == null {
|
|
|
|
+ GameState[gameName].players[playerName].action := action
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ log("Someone didn't know they were gone: ", playerName)
|
|
|
|
+ addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : event.state.commID, "payload" : {"toRemovePlayerIds" : [playerName]}})
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- log("Updated player: ", playerName, " for game:", gameName, " with state:", state)
|
|
|
|
} except e {
|
|
} except e {
|
|
error(e)
|
|
error(e)
|
|
- db.raiseWebEventHandled({"code" : const.ErrorCodes["InternalError"], "body" : {"error" : const.Errors.InternalError}})
|
|
+ }
|
|
- } otherwise {
|
|
+}
|
|
- db.raiseWebEventHandled({"code" : 200, "body" : {
|
|
+
|
|
- "result" : "success",
|
|
+
|
|
- "sprite" : sprite,
|
|
+/*
|
|
- "gameworld" : gameWorld
|
|
+ GameScore sink.
|
|
- }})
|
|
+*/
|
|
|
|
+sink MainGameScore
|
|
|
|
+ kindmatch ["main.gamescore"]
|
|
|
|
+ priority 100
|
|
|
|
+{
|
|
|
|
+ try {
|
|
|
|
+ let scoreObj := db.fetchNode(event.state.part, event.state.id, const.ObjectKinds.ScoreObject)
|
|
|
|
+
|
|
|
|
+ if scoreObj == null {
|
|
|
|
+ scoreObj := {
|
|
|
|
+ "key" : event.state.id,
|
|
|
|
+ "kind" : const.ObjectKinds.ScoreObject,
|
|
|
|
+ "score" : 0
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ event.state.changeFunc(scoreObj)
|
|
|
|
+ db.storeNode(event.state.part, scoreObj)
|
|
|
|
+ } except e {
|
|
|
|
+ error("GameScore:", e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -117,7 +208,7 @@ sink PlayerInput
|
|
/*
|
|
/*
|
|
Object for main game engine.
|
|
Object for main game engine.
|
|
*/
|
|
*/
|
|
-MainGameEngine := new(engine.GameEngine, "main", tmpl.DefaultGameWorld)
|
|
+MainGameEngine := new(engine.GameEngine, "main", tmpl.DefaultGameWorld, GameState, Websocket)
|
|
|
|
|
|
/*
|
|
/*
|
|
GameLoop sink.
|
|
GameLoop sink.
|
|
@@ -129,7 +220,45 @@ sink MainGameLoop
|
|
try {
|
|
try {
|
|
MainGameEngine.moveLoop()
|
|
MainGameEngine.moveLoop()
|
|
} except e {
|
|
} except e {
|
|
- error("Gameloop:", e)
|
|
+ error("Game loop:", e)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ Period game events loop.
|
|
|
|
+*/
|
|
|
|
+sink PeriodicGameEvents
|
|
|
|
+ kindmatch ["main.periodicgameevents"]
|
|
|
|
+ priority 100
|
|
|
|
+{
|
|
|
|
+ try {
|
|
|
|
+ mutex GameStateMutex {
|
|
|
|
+
|
|
|
|
+ for [gameName, state] in GameState {
|
|
|
|
+ let gameWorld := state.world
|
|
|
|
+
|
|
|
|
+ if len(GameState[gameName].sprites) < 10 {
|
|
|
|
+ log("Adding more asteroids", gameWorld.screenWidth)
|
|
|
|
+ sprites := GameState[gameName].sprites
|
|
|
|
+
|
|
|
|
+ for i in range(1, 4) {
|
|
|
|
+
|
|
|
|
+ posX := math.floor(rand() * gameWorld.screenWidth - 100) + 100
|
|
|
|
+ posY := gameWorld.screenHeight - 100
|
|
|
|
+ size := math.floor(rand() * 40) + 20
|
|
|
|
+ rot := rand() * math.Pi * 2
|
|
|
|
+
|
|
|
|
+ sprites := add(sprites, tmpl.newAsteroid("asteroid-{{now()}}-{{i}}", posX, posY, size, rot, 0.005))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ GameState[gameName].sprites := sprites
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } except e {
|
|
|
|
+ error("Periodic events loop:", e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -139,7 +268,15 @@ sink MainGameLoop
|
|
must always be greater than the total time of the move loop (see the time_total_move
|
|
must always be greater than the total time of the move loop (see the time_total_move
|
|
stat recorded in engine.ecal).
|
|
stat recorded in engine.ecal).
|
|
|
|
|
|
- 55000 - 55 milli seconds - smooth animation calculated in the backend, frontend only needs to display
|
|
+ 35000 - 35 milli seconds - smooth animation calculated in the backend, frontend only needs to display
|
|
|
|
+
|
|
|
|
+*/
|
|
|
|
+setPulseTrigger(35000, "Main Game Loop", "main.gameloop")
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ Trigger periodic events in the game
|
|
|
|
+
|
|
|
|
+ 1000000 - 1 second
|
|
|
|
|
|
*/
|
|
*/
|
|
-setPulseTrigger(55000, "Main Game Loop", "main.gameloop")
|
|
+setPulseTrigger(1000000, "Periodic Game Events", "main.periodicgameevents")
|