engine.ecal 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import "./const.ecal" as const
  2. import "./helper.ecal" as hlp
  3. /*
  4. Constant rate for moving. The higher the less movement we do in a given time.
  5. */
  6. moveRate := 30
  7. /*
  8. Time the move loop was executed last (used for time correction)
  9. */
  10. lastMoveCycleTime := 0
  11. /*
  12. Game engine object which moves objects in a game world.
  13. */
  14. GameEngine := {
  15. /*
  16. Partition to manage
  17. */
  18. "part" : null,
  19. /*
  20. Game world
  21. */
  22. "world" : null,
  23. /*
  24. Game state
  25. */
  26. gameState : null,
  27. /*
  28. Active websocket connections
  29. */
  30. websocket : null,
  31. /*
  32. Constructor
  33. */
  34. "init" : func (part, world, gameState, websocket) {
  35. this.part := part
  36. this.world := world
  37. this.gameState := gameState
  38. this.websocket := websocket
  39. },
  40. /*
  41. updateStat updates a statistic value.
  42. */
  43. "updateStat" : func (key, value) {
  44. mutex GameStateMutex {
  45. this.gameState[this.part][key] := value
  46. }
  47. },
  48. /*
  49. moveLoop handles object movement in the game world.
  50. */
  51. "moveLoop" : func () {
  52. let moveLoopTime := now()
  53. let timeDelta := moveLoopTime - lastMoveCycleTime # Do the move
  54. mutex GameStateMutex {
  55. if this.gameState == null or this.gameState[this.part] == null {
  56. return null
  57. }
  58. }
  59. /*
  60. Do a single move step with compensation for the time delta
  61. */
  62. time := now()
  63. this.move(timeDelta)
  64. this.updateStat("time_total_move", now() - time)
  65. lastMoveCycleTime := moveLoopTime
  66. },
  67. /*
  68. move calculates one move step
  69. */
  70. "move" : func (timeDelta) {
  71. /*
  72. Calculate a correction multiplier for the time lag
  73. */
  74. let timeCorrection := timeDelta / moveRate
  75. if math.isNaN(timeCorrection) or math.isInf(timeCorrection, 0) or timeCorrection > 10000 {
  76. timeCorrection := 1
  77. }
  78. mutex GameStateMutex {
  79. this.updateStat("time_move_correction", timeCorrection)
  80. entitiesToRemove := []
  81. /* First move things a step */
  82. for [playername, obj] in this.gameState[this.part].players {
  83. if not this.moveObject(timeCorrection, obj) {
  84. entitiesToRemove := add(entitiesToRemove, obj)
  85. }
  86. this.executeAction(obj)
  87. }
  88. for obj in this.gameState[this.part].sprites {
  89. if not this.moveObject(timeCorrection, obj) {
  90. entitiesToRemove := add(entitiesToRemove, obj)
  91. }
  92. this.executeAction(obj)
  93. }
  94. /* Detect collisions */
  95. for [playername, obj] in this.gameState[this.part].players {
  96. entitiesToRemove := concat(entitiesToRemove, this.collisionDetection(obj))
  97. }
  98. for obj in this.gameState[this.part].sprites {
  99. entitiesToRemove := concat(entitiesToRemove, this.collisionDetection(obj))
  100. }
  101. /* Remove things from the world */
  102. if len(entitiesToRemove) > 0 {
  103. let toRemoveSpriteIds := []
  104. let toRemovePlayerIds := []
  105. for e in entitiesToRemove {
  106. if e.kind == const.ObjectKinds.Player {
  107. log("Removing player: {{e.id}}")
  108. toRemovePlayerIds := add(toRemovePlayerIds, e.id)
  109. this.gameState[this.part].players := del(this.gameState[this.part].players, e.id)
  110. } else {
  111. toRemoveSpriteIds := add(toRemoveSpriteIds, e.id)
  112. }
  113. }
  114. for [commID, data] in this.websocket {
  115. if data.gamename == this.part {
  116. addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"toRemovePlayerIds" : toRemovePlayerIds}})
  117. }
  118. }
  119. this.gameState[this.part].sprites := hlp.filter(this.gameState[this.part].sprites, func (i) {
  120. return not i.id in toRemoveSpriteIds
  121. })
  122. for [commID, data] in this.websocket {
  123. if data.gamename == this.part {
  124. addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"toRemoveSpriteIds" : toRemoveSpriteIds}})
  125. }
  126. }
  127. }
  128. }
  129. },
  130. /*
  131. Move a specific object in the game world. Return false if the object
  132. should be removed from the world.
  133. */
  134. "moveObject" : func (timeCorrection, obj) {
  135. let keepObj := true
  136. /*
  137. Calculate new entity coordinates
  138. */
  139. let moveStep := timeCorrection * obj.speed * obj.moveSpeed
  140. let strafeStep := timeCorrection * obj.strafe * obj.moveSpeed * 0.02
  141. /*
  142. Forward / backward movement
  143. */
  144. let newX := obj.x + math.cos(obj.rot) * moveStep
  145. let newY := obj.y + math.sin(obj.rot) * moveStep
  146. /*
  147. Left / right strafe movement
  148. */
  149. newX := newX - math.sin(obj.rot) * strafeStep
  150. newY := newY + math.cos(obj.rot) * strafeStep
  151. /*
  152. Rotate the entity
  153. */
  154. obj.rot := obj.rot + timeCorrection * obj.dir * obj.rotSpeed
  155. obj.x := math.floor(newX)
  156. obj.y := math.floor(newY)
  157. /*
  158. Ensure the entity does not move outside the boundaries
  159. */
  160. if obj.displayLoop {
  161. hmin := 0 - obj.dim - 20
  162. hmax := this.world.screenWidth + obj.dim + 20
  163. if obj.x > hmax {
  164. obj.x := 0 - obj.dim - 10
  165. } elif obj.x < hmin {
  166. obj.x := this.world.screenWidth + obj.dim + 10
  167. }
  168. vmin := 0 - obj.dim - 20
  169. vmax := this.world.screenHeight + obj.dim + 20
  170. if obj.y > vmax {
  171. obj.y := 0 - obj.dim - 10
  172. } elif obj.y < vmin {
  173. obj.y := this.world.screenHeight + obj.dim + 10
  174. }
  175. } elif obj.x > this.world.screenWidth or obj.x < 0 or obj.y > this.world.screenHeight or obj.y < 0 {
  176. keepObj := false
  177. }
  178. mutex WebsocketMutex {
  179. for [commID, data] in this.websocket {
  180. if data.gamename == this.part {
  181. res := addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : commID, "payload" : {"state" : obj}})
  182. if len(res) > 0 {
  183. log("Removing unknown websocket", commID)
  184. del(this.websocket, commID)
  185. }
  186. }
  187. }
  188. }
  189. return keepObj
  190. },
  191. /*
  192. Detect collisions with other objects. Return false if the object
  193. should be removed from the world.
  194. */
  195. "collisionDetection" : func (entity) {
  196. let entitiesToRemove := []
  197. checkCollision := func (e1, e2) {
  198. let e1dh := e1.dim / 2
  199. let e2dh := e2.dim / 2
  200. 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
  201. }
  202. for [playername, obj] in this.gameState[this.part].players {
  203. if entity.id == obj.id {
  204. break
  205. }
  206. if checkCollision(entity, obj) {
  207. entitiesToRemove := concat(entitiesToRemove, entity.collision(entity, obj, this), obj.collision(obj, entity, this))
  208. }
  209. }
  210. for obj in this.gameState[this.part].sprites {
  211. if entity.id == obj.id {
  212. break
  213. }
  214. if checkCollision(entity, obj) {
  215. entitiesToRemove := concat(entitiesToRemove, entity.collision(entity, obj, this), obj.collision(obj, entity, this))
  216. }
  217. }
  218. return entitiesToRemove
  219. },
  220. /*
  221. Execute an action for a given object.
  222. */
  223. "executeAction" : func (entity) {
  224. if entity.action != null {
  225. entity.doAction(entity, entity.action, this)
  226. entity.action := null
  227. }
  228. }
  229. }