vimp-engine 0.34.7 → 0.34.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vimp-engine",
3
- "version": "0.34.7",
3
+ "version": "0.34.8",
4
4
  "description": "VIMP — движок-приложение (мастер, P2P-транспорт, Worker-хост, мета, MVC-каркас клиента)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -258,6 +258,9 @@ export default class HostGame {
258
258
  this._handoffRestored = false;
259
259
  this._handoffMapTimeLeft = null;
260
260
 
261
+ // матч закрывается (destroy): снятие участников исход раунда не решает
262
+ this._isDestroying = false;
263
+
261
264
  // внедрение зависимостей (ядро отдаёт панель/фасад события через адаптер)
262
265
  this._socketManager.injectServices(this._game, this._panel, this._stat);
263
266
  this._game.injectServices({ vimp: this, panel: this._panel });
@@ -679,6 +682,7 @@ export default class HostGame {
679
682
  * @returns {Promise} Завершение финальной синхронизации профилей.
680
683
  */
681
684
  async destroy() {
685
+ this._isDestroying = true;
682
686
  this._timerManager.stopGameTimers();
683
687
  this._timerManager.stopIdleCheckTimer();
684
688
  this._timerManager.stopAllVoteTimers();
@@ -884,6 +888,7 @@ export default class HostGame {
884
888
 
885
889
  if (team) {
886
890
  this._scripted.removeOneForHuman(team);
891
+ this._roundManager.checkRoundOutcome();
887
892
  }
888
893
  }
889
894
 
@@ -960,6 +965,13 @@ export default class HostGame {
960
965
  this._participants.remove(gameId);
961
966
 
962
967
  this._chat.pushSystem('USER_LEFT', [user.name]);
968
+
969
+ // ушёл последний живой команды, пока раунд ждал одной выжившей. При
970
+ // закрытии матча исход не решается: таймеры уже сняты, и завершение
971
+ // раунда взвело бы новый и повторило бы finishAllGames/flushAll
972
+ if (!this._isDestroying) {
973
+ this._roundManager.checkRoundOutcome();
974
+ }
963
975
  }
964
976
 
965
977
  // обновляет команды (формат wire: 'seq:action:name', указатель —
@@ -75,6 +75,9 @@ class RoundManager {
75
75
  this._scaledMapData = null;
76
76
  this._startMapNumber = 0;
77
77
  this._isRoundEnding = false;
78
+ // команды, чей вайп уже записан в этом раунде: при 3+ командах раунд
79
+ // идёт дальше, и второй раз `deaths` той же команде не пишется
80
+ this._wipedTeamIds = new Set();
78
81
  this._removedPlayersList = []; // gameId игроков для удаления с полотна
79
82
 
80
83
  // эстафета Worker'ов (Этап 5.2): колбэк переноса на границе раунда
@@ -148,6 +151,7 @@ class RoundManager {
148
151
 
149
152
  this._participants.clearActive();
150
153
  this._removedPlayersList = [];
154
+ this._wipedTeamIds.clear();
151
155
 
152
156
  this._panel.reset();
153
157
  this._stat.reset();
@@ -264,6 +268,7 @@ class RoundManager {
264
268
  // запуск нового раунда
265
269
  initiateNewRound() {
266
270
  this._timerManager.stopRoundTimer();
271
+ this._wipedTeamIds.clear();
267
272
 
268
273
  // граница раунда достигнута — отдать состояние эстафете; новый раунд
269
274
  // стартует уже новый Worker после переноса
@@ -455,6 +460,14 @@ class RoundManager {
455
460
 
456
461
  // меняет команду игрока
457
462
  changeTeam(gameId, newTeam) {
463
+ this._applyTeamChange(gameId, newTeam);
464
+
465
+ // уход в наблюдатели или в другую команду может оставить живых только в
466
+ // одной команде — раунд, ждавший этого после вайпа, завершается здесь
467
+ this.checkRoundOutcome();
468
+ }
469
+
470
+ _applyTeamChange(gameId, newTeam) {
458
471
  const user = this._participants.get(gameId);
459
472
  const currentTeam = user.team;
460
473
  const respawns = this._scaledMapData.respawns;
@@ -638,7 +651,7 @@ class RoundManager {
638
651
  // убийца мог покинуть игру до попадания (например, взрыв его бомбы) —
639
652
  // фраг не начисляется, но раунд обязан корректно завершиться
640
653
  if (!killerUser) {
641
- this._checkTeamWipe(victimUser.teamId, null);
654
+ this._checkTeamWipe(victimUser.teamId);
642
655
  return;
643
656
  }
644
657
 
@@ -669,54 +682,108 @@ class RoundManager {
669
682
  this._chat.pushSystem('REPORT_KILL', [killerUser.name, victimUser.name]);
670
683
 
671
684
  // проверка на уничтожение всей команды противника
672
- this._checkTeamWipe(victimUser.teamId, killerUser.teamId);
685
+ this._checkTeamWipe(victimUser.teamId);
673
686
  }
674
687
  }
675
688
 
676
- // проверяет уничтожение всей команды
677
- _checkTeamWipe(victimTeamId, killerTeamId) {
678
- // под endlessRound раунд не заканчивается вообще: вычищенная команда —
679
- // не повод для результата и отложенного перезапуска
680
- if (this._endlessRound) {
689
+ // проверяет уничтожение всей команды. Победитель — команда, у которой
690
+ // остались живые, а не команда последнего убийцы: суицид, огонь по своим
691
+ // или ушедший убийца иначе объявляли бы GAME OVER живой команде
692
+ _checkTeamWipe(victimTeamId) {
693
+ if (!this._canResolveRound() || victimTeamId === this._spectatorId) {
681
694
  return;
682
695
  }
683
696
 
684
- // если раунд уже в процессе завершения
685
- if (this._isRoundEnding) {
697
+ const aliveTeamIds = this._getAliveTeamIds(victimTeamId);
698
+
699
+ // в команде жертвы есть живой — команда не уничтожена
700
+ if (!aliveTeamIds) {
686
701
  return;
687
702
  }
688
703
 
689
- let winnerTeam = null;
704
+ // запись поражения команде (один раз за раунд)
705
+ if (!this._wipedTeamIds.has(victimTeamId)) {
706
+ this._wipedTeamIds.add(victimTeamId);
707
+ this._stat.updateHead(victimTeamId, 'deaths', 1);
708
+ }
709
+
710
+ this._resolveRound(aliveTeamIds);
711
+ }
690
712
 
691
- // если команда наблюдателей, проверка не требуется
692
- if (victimTeamId === this._spectatorId) {
713
+ // перепроверяет исход раунда без смерти (уход игрока, смена команды):
714
+ // нужна только раунду, в котором вайп уже был, но живые оставались больше
715
+ // чем в одной команде. Без вайпа уход игрока раунд не завершает
716
+ checkRoundOutcome() {
717
+ if (!this._canResolveRound() || this._wipedTeamIds.size === 0) {
693
718
  return;
694
719
  }
695
720
 
696
- // проверка на живых участников в команде (люди и scripted)
721
+ this._resolveRound(this._getAliveTeamIds());
722
+ }
723
+
724
+ _canResolveRound() {
725
+ // под endlessRound раунд не заканчивается вообще: вычищенная команда —
726
+ // не повод для результата и отложенного перезапуска. Раунд, который уже
727
+ // завершается, второй раз не завершается
728
+ return !this._endlessRound && !this._isRoundEnding;
729
+ }
730
+
731
+ // id команд с живыми участниками (люди и scripted). null — если живой
732
+ // нашёлся в `victimTeamId`: остальных опрашивать незачем. Наблюдатели
733
+ // отсеиваются до вызова в ядро
734
+ _getAliveTeamIds(victimTeamId = null) {
735
+ const aliveTeamIds = new Set();
736
+
697
737
  for (const participant of this._participants.getAll()) {
698
- // если нашелся живой участник, команда не уничтожена
699
738
  if (
700
- participant.teamId === victimTeamId &&
701
- this._game.isAlive(participant.gameId)
739
+ participant.teamId === this._spectatorId ||
740
+ !this._game.isAlive(participant.gameId)
702
741
  ) {
703
- return;
742
+ continue;
704
743
  }
744
+
745
+ if (participant.teamId === victimTeamId) {
746
+ return null;
747
+ }
748
+
749
+ aliveTeamIds.add(participant.teamId);
750
+ }
751
+
752
+ return aliveTeamIds;
753
+ }
754
+
755
+ // завершает раунд, если живые остались не больше чем в одной команде:
756
+ // она побеждает, нет выживших — ничья
757
+ _resolveRound(aliveTeamIds) {
758
+ // при 3+ командах раунд идёт, пока живые есть больше чем в одной
759
+ if (aliveTeamIds.size > 1) {
760
+ return;
705
761
  }
706
762
 
707
763
  // активация флага завершения раунда
708
764
  this._isRoundEnding = true;
709
765
 
710
- // запись поражения команде
711
- this._stat.updateHead(victimTeamId, 'deaths', 1);
712
-
713
- // если убийца из другой команды, фраг для команды-победителя
714
- if (killerTeamId && killerTeamId !== victimTeamId) {
715
- this._stat.updateHead(killerTeamId, 'score', 1);
766
+ const [winnerTeamId] = aliveTeamIds;
767
+ const winnerTeam = Object.keys(this._teams).find(
768
+ key => this._teams[key] === winnerTeamId,
769
+ );
716
770
 
717
- winnerTeam = Object.keys(this._teams).find(
718
- key => this._teams[key] === killerTeamId,
719
- );
771
+ // счёт и объявление решает одно условие: команда вне `teams` не может
772
+ // получить очко, которого клиенты не увидят
773
+ if (winnerTeam) {
774
+ this._stat.updateHead(winnerTeamId, 'score', 1);
775
+ } else {
776
+ // ничья: последние игроки нескольких команд погибли в одном тике, а
777
+ // смерти после первой приходят уже в завершающийся раунд — поражение
778
+ // пишется всем вымершим командам сразу
779
+ for (const participant of this._participants.getAll()) {
780
+ const teamId = participant.teamId;
781
+
782
+ if (teamId !== this._spectatorId && !this._wipedTeamIds.has(teamId)) {
783
+ this._wipedTeamIds.add(teamId);
784
+ this._stat.updateHead(teamId, 'deaths', 1);
785
+ }
786
+ }
720
787
  }
721
788
 
722
789
  // синхронизация накопленных профилей на мастер по итогам раунда
@@ -725,26 +792,25 @@ class RoundManager {
725
792
  this._playerDataSync?.finishAllGames?.();
726
793
  this._playerDataSync?.flushAll();
727
794
 
728
- if (winnerTeam) {
729
- this._participants.getHumans().forEach(user => {
730
- const socketId = user.socketId;
731
-
732
- if (user.teamId === victimTeamId) {
733
- this._socketManager.sendSoundCue(socketId, 'defeat');
734
- } else {
735
- this._socketManager.sendSoundCue(socketId, 'victory');
736
- }
795
+ this._participants.getHumans().forEach(user => {
796
+ const socketId = user.socketId;
797
+ // наблюдатель не проигрывает: ему victory при любом исходе, ничья
798
+ // включительно
799
+ const isWinner =
800
+ user.teamId === this._spectatorId ||
801
+ (winnerTeam && user.teamId === winnerTeamId);
802
+
803
+ this._socketManager.sendSoundCue(
804
+ socketId,
805
+ isWinner ? 'victory' : 'defeat',
806
+ );
737
807
 
808
+ if (winnerTeam) {
738
809
  this._socketManager.sendRoundEnd(socketId, winnerTeam);
739
- });
740
- } else {
741
- this._participants.getHumans().forEach(user => {
742
- const socketId = user.socketId;
743
-
744
- this._socketManager.sendSoundCue(socketId, 'defeat');
810
+ } else {
745
811
  this._socketManager.sendRoundEnd(socketId);
746
- });
747
- }
812
+ }
813
+ });
748
814
 
749
815
  this._timerManager.stopRoundTimer();
750
816
  this._timerManager.startRoundRestartDelay(); // отложенный перезапуск раунда