game-controller.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { BackendClient } from '../backend/api-helper';
  2. import { AssetDefinition } from '../backend/asset-loader';
  3. import { EliasDBGraphQLClient } from '../backend/eliasdb-graphql';
  4. import { MainDisplayController } from '../display/engine';
  5. import { PlayerState, SpriteState } from '../display/types';
  6. import { stringToNumber } from '../helper';
  7. import { AnimationController, AnimationStyle, playOneSound } from './lib';
  8. import { Player, Sprite } from './objects';
  9. /**
  10. * Main game controller.
  11. */
  12. export class MainGameController {
  13. protected gameName: string; // Name of the game
  14. protected playerState: PlayerState;
  15. protected spriteMap: Record<string, SpriteState>;
  16. private display: MainDisplayController;
  17. protected backedClient: BackendClient;
  18. protected graphQLClient: EliasDBGraphQLClient;
  19. private assets: Record<string, AssetDefinition>;
  20. constructor(
  21. gameName: string,
  22. playerName: string,
  23. assets: Record<string, AssetDefinition>,
  24. display: MainDisplayController,
  25. backedClient: BackendClient,
  26. graphQLClient: EliasDBGraphQLClient
  27. ) {
  28. this.gameName = gameName;
  29. this.assets = assets;
  30. this.display = display;
  31. this.backedClient = backedClient;
  32. this.graphQLClient = graphQLClient;
  33. this.playerState = new Player(gameName, backedClient);
  34. this.playerState.id = playerName;
  35. this.spriteMap = {};
  36. // Retrieve score update
  37. this.graphQLClient.subscribe(
  38. `
  39. subscription {
  40. score(ascending:score) {
  41. key,
  42. score,
  43. }
  44. }`,
  45. (data) => {
  46. let text = [];
  47. let scores: any[] = data.data.score.reverse().slice(0, 10);
  48. let score = 0;
  49. text.push(``);
  50. text.push(`Highscores:`);
  51. text.push(``);
  52. for (let item of scores) {
  53. text.push(` ${item.key}`);
  54. text.push(` ${item.score}`);
  55. if (item.key == this.playerState.id) {
  56. score = item.score;
  57. }
  58. }
  59. text.unshift(`Score: ${score}`);
  60. this.display.setOverlayData(text);
  61. }
  62. );
  63. }
  64. public setPlayerWebsocket(ws: WebSocket) {
  65. (this.playerState as Player).setWebsocket(ws);
  66. }
  67. /**
  68. * Handler which is called every time the backend pushes an update via the
  69. * websocket for game state updates.
  70. *
  71. * @param data Data object from the server.
  72. */
  73. public updatePushHandler(data: any): void {
  74. if (data.payload.audioEvent) {
  75. let event = data.payload.audioEvent;
  76. if (event === 'explosion') {
  77. let explosionType = Math.floor(Math.random() * 5) + 1;
  78. playOneSound(
  79. this.assets[`explosion_00${explosionType}`].audio!
  80. );
  81. } else if (event === 'vanish') {
  82. playOneSound(
  83. this.assets[`vanish_001`].audio!
  84. );
  85. } else if (event === 'shot') {
  86. let player = data.payload.player;
  87. let shotType = stringToNumber(player, 1, 9);
  88. playOneSound(this.assets[`shotfired_00${shotType}`].audio!);
  89. }
  90. return;
  91. }
  92. if (data.payload.toRemovePlayerIds) {
  93. for (let i of data.payload.toRemovePlayerIds) {
  94. let entity: SpriteState = this.playerState;
  95. let callback: () => void;
  96. if (i === this.playerState.id) {
  97. console.log(
  98. 'Sorry',
  99. this.playerState.id,
  100. 'but you are gone ...'
  101. );
  102. callback = () => {
  103. this.display.spectatorMode();
  104. };
  105. } else {
  106. entity = this.spriteMap[i];
  107. if (!entity) {
  108. continue; // Some removal messages can be send multiple times
  109. }
  110. callback = () => {
  111. delete this.spriteMap[i];
  112. this.display.removeSprite(entity);
  113. };
  114. }
  115. entity.animation = new AnimationController(
  116. this.assets['ship_explosion_ani_001'].image!,
  117. 24,
  118. 24,
  119. AnimationStyle.ForwardAndBackward,
  120. 3,
  121. 100,
  122. callback
  123. );
  124. playOneSound(this.assets['ship_explosion_snd_001'].audio!);
  125. }
  126. return;
  127. }
  128. if (data.payload.toRemoveSpriteIds) {
  129. for (let i of data.payload.toRemoveSpriteIds) {
  130. let entity = this.spriteMap[i];
  131. if (!entity) {
  132. continue; // Some removal messages can be send multiple times
  133. }
  134. delete this.spriteMap[i];
  135. this.display.removeSprite(entity);
  136. }
  137. return;
  138. }
  139. let obj = data.payload.state as Record<string, any>;
  140. if (obj.id === this.playerState.id) {
  141. this.playerState.setState(obj);
  142. } else {
  143. let sprite = this.spriteMap[obj.id];
  144. if (!sprite) {
  145. sprite = new Sprite();
  146. sprite.setState(obj);
  147. this.spriteMap[sprite.id] = sprite;
  148. this.display.addSprite(sprite);
  149. }
  150. sprite.setState(obj);
  151. }
  152. // Start the game once we had the first update of all object coordinates
  153. if (!this.display.running) {
  154. this.display.start(this.playerState);
  155. }
  156. }
  157. }