import "./const.ecal" as const import "./helper.ecal" as hlp /* Constant rate for moving. The higher the less movement we do in a given time. */ moveRate := 30 /* Time the move loop was executed last (used for time correction) */ lastMoveCycleTime := 0 /* Game engine object which moves objects in a game world. */ GameEngine := { /* Partition to manage */ "part" : null, /* Game world */ "world" : null, /* Game state */ gameState : null, /* Active websocket connections */ websocket : null, /* Constructor */ "init" : func (part, world, gameState, websocket) { this.part := part this.world := world this.gameState := gameState this.websocket := websocket }, /* updateStat updates a statistic value. */ "updateStat" : func (key, value) { mutex GameStateMutex { this.gameState[this.part][key] := value } }, /* moveLoop handles object movement in the game world. */ "moveLoop" : func () { let moveLoopTime := now() let timeDelta := moveLoopTime - lastMoveCycleTime # Do the move mutex GameStateMutex { if this.gameState == null or this.gameState[this.part] == null { return null } } /* Do a single move step with compensation for the time delta */ time := now() this.move(timeDelta) this.updateStat("time_total_move", now() - time) lastMoveCycleTime := moveLoopTime }, /* move calculates one move step */ "move" : func (timeDelta) { /* Calculate a correction multiplier for the time lag */ let timeCorrection := timeDelta / moveRate if math.isNaN(timeCorrection) or math.isInf(timeCorrection, 0) or timeCorrection > 10000 { timeCorrection := 1 } mutex GameStateMutex { this.updateStat("time_move_correction", timeCorrection) entitiesToRemove := [] /* First move things a step */ for [playername, obj] in this.gameState[this.part].players { if not this.moveObject(timeCorrection, obj) { entitiesToRemove := add(entitiesToRemove, obj) } this.executeAction(obj) } for obj in this.gameState[this.part].sprites { if not this.moveObject(timeCorrection, obj) { entitiesToRemove := add(entitiesToRemove, obj) } this.executeAction(obj) } /* Detect collisions */ for [playername, obj] in this.gameState[this.part].players { entitiesToRemove := concat(entitiesToRemove, this.collisionDetection(obj)) } for obj in this.gameState[this.part].sprites { entitiesToRemove := concat(entitiesToRemove, this.collisionDetection(obj)) } /* Remove things from the world */ if len(entitiesToRemove) > 0 { let toRemoveSpriteIds := [] let toRemovePlayerIds := [] for e in entitiesToRemove { if e.kind == const.ObjectKinds.Player { log("Removing player: {{e.id}}") toRemovePlayerIds := add(toRemovePlayerIds, e.id) this.gameState[this.part].players := del(this.gameState[this.part].players, e.id) } else { toRemoveSpriteIds := add(toRemoveSpriteIds, e.id) } } for [commID, data] in this.websocket { if data.gamename == this.part { addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"toRemovePlayerIds" : toRemovePlayerIds}}) } } this.gameState[this.part].sprites := hlp.filter(this.gameState[this.part].sprites, func (i) { return not i.id in toRemoveSpriteIds }) for [commID, data] in this.websocket { if data.gamename == this.part { addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"toRemoveSpriteIds" : toRemoveSpriteIds}}) } } } } }, /* Move a specific object in the game world. Return false if the object should be removed from the world. */ "moveObject" : func (timeCorrection, obj) { let keepObj := true /* Calculate new entity coordinates */ let moveStep := timeCorrection * obj.speed * obj.moveSpeed let strafeStep := timeCorrection * obj.strafe * obj.moveSpeed * 0.02 /* Forward / backward movement */ let newX := obj.x + math.cos(obj.rot) * moveStep let newY := obj.y + math.sin(obj.rot) * moveStep /* Left / right strafe movement */ newX := newX - math.sin(obj.rot) * strafeStep newY := newY + math.cos(obj.rot) * strafeStep /* Rotate the entity */ obj.rot := obj.rot + timeCorrection * obj.dir * obj.rotSpeed obj.x := math.floor(newX) obj.y := math.floor(newY) /* Ensure the entity does not move outside the boundaries */ if obj.displayLoop { hmin := 0 - obj.dim - 20 hmax := this.world.screenWidth + obj.dim + 20 if obj.x > hmax { obj.x := 0 - obj.dim - 10 } elif obj.x < hmin { obj.x := this.world.screenWidth + obj.dim + 10 } vmin := 0 - obj.dim - 20 vmax := this.world.screenHeight + obj.dim + 20 if obj.y > vmax { obj.y := 0 - obj.dim - 10 } elif obj.y < vmin { obj.y := this.world.screenHeight + obj.dim + 10 } } elif obj.x > this.world.screenWidth or obj.x < 0 or obj.y > this.world.screenHeight or obj.y < 0 { keepObj := false } mutex WebsocketMutex { for [commID, data] in this.websocket { if data.gamename == this.part { res := addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"state" : obj}}) if len(res) > 0 { log("Removing unknown websocket", commID) del(this.websocket, commID) } } } } return keepObj }, /* Detect collisions with other objects. Return false if the object should be removed from the world. */ "collisionDetection" : func (entity) { let entitiesToRemove := [] checkCollision := func (e1, e2) { let e1dh := e1.dim / 2 let e2dh := e2.dim / 2 return e1.x + e1dh > e2.x - e2dh and e1.x - e1dh < e2.x + e2dh and e1.y + e1dh > e2.y - e2dh and e1.y - e1dh < e2.y + e2dh } for [playername, obj] in this.gameState[this.part].players { if entity.id == obj.id { break } if checkCollision(entity, obj) { entitiesToRemove := concat(entitiesToRemove, entity.collision(entity, obj, this), obj.collision(obj, entity, this)) } } for obj in this.gameState[this.part].sprites { if entity.id == obj.id { break } if checkCollision(entity, obj) { entitiesToRemove := concat(entitiesToRemove, entity.collision(entity, obj, this), obj.collision(obj, entity, this)) } } return entitiesToRemove }, /* Execute an action for a given object. */ "executeAction" : func (entity) { if entity.action != null { entity.doAction(entity, entity.action, this) entity.action := null } } }