vimp-engine 0.3.0 → 0.4.0
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/bin/vimp-sim.js +137 -0
- package/package.json +10 -3
- package/src/config/gameCodes.js +8 -0
- package/src/config/lobby.js +23 -1
- package/src/config/master.js +8 -0
- package/src/devtools/RecordingSocketManager.js +118 -0
- package/src/devtools/ScenarioRunner.js +529 -0
- package/src/devtools/VirtualClient.js +363 -0
- package/src/devtools/VirtualClock.js +169 -0
- package/src/devtools/inspectHost.js +31 -0
- package/src/devtools/invariants.js +672 -0
- package/src/devtools/pluginLoader.js +101 -0
- package/src/devtools/report.js +202 -0
- package/src/devtools/resetHostSingletons.js +23 -0
- package/src/host/DebugRecorder.js +224 -0
- package/src/host/GameCoreAdapter.js +17 -0
- package/src/host/HostGame.js +129 -7
- package/src/host/host.worker.js +46 -47
- package/src/host/meta/SocketManager.js +15 -7
- package/src/host/meta/modules/Panel.js +7 -0
- package/src/host/meta/modules/PlayerDataSync.js +29 -5
- package/src/host/meta/modules/RTTManager.js +4 -2
- package/src/host/meta/modules/Stat.js +7 -0
- package/src/host/meta/modules/TimerManager.js +15 -7
- package/src/host/meta/modules/Vote.js +10 -1
- package/src/host/meta/modules/chat/Chat.js +7 -0
- package/src/host/meta/modules/chat/index.js +1 -1
- package/src/host/meta/player/HumanParticipant.js +2 -1
- package/src/lib/AbstractTimer.js +6 -4
- package/src/lib/applyRoomOverrides.js +6 -0
- package/src/lib/clientCoreConfig.js +4 -0
- package/src/lib/clock.js +53 -0
- package/src/lib/createHostRuntime.js +108 -0
- package/src/lib/reconstructHot.js +78 -0
- package/src/lib/validators.js +10 -0
- package/tests/fixtures/miniGame/client/fakeClientCore.js +304 -0
- package/tests/fixtures/miniGame/client/index.js +37 -0
- package/tests/fixtures/miniGame/client/parts/Actor.js +18 -0
- package/tests/fixtures/miniGame/client/parts/ActorRadar.js +7 -0
- package/tests/fixtures/miniGame/config/auth.js +41 -0
- package/tests/fixtures/miniGame/config/client.js +139 -0
- package/tests/fixtures/miniGame/config/game.js +148 -0
- package/tests/fixtures/miniGame/host/ScriptedManager.js +129 -0
- package/tests/fixtures/miniGame/host/createModules.js +6 -0
- package/tests/fixtures/miniGame/host/fakeCore.js +233 -0
- package/tests/fixtures/miniGame/host/index.js +29 -0
- package/tests/fixtures/miniGame/host/spawnCommand.js +14 -0
- package/tests/fixtures/miniGame/host/systemMessages.js +6 -0
- package/tests/fixtures/miniGame.contract.test.js +76 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Заглушечный part фикстуры (ClientPlugin.parts, PLAN.md §3.3) — минимальная
|
|
2
|
+
// форма рендер-сущности: конструктор + update(state)/destroy(), без PixiJS
|
|
3
|
+
// (тесты не поднимают живой канвас). Доказывает, что Factory/CanvasManager
|
|
4
|
+
// не требуют конкретно PixiJS-Container-класса — только этот интерфейс.
|
|
5
|
+
export default class Actor {
|
|
6
|
+
constructor(id) {
|
|
7
|
+
this.id = id;
|
|
8
|
+
this.destroyed = false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
update(state) {
|
|
12
|
+
this.state = state;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
destroy() {
|
|
16
|
+
this.destroyed = true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import Actor from './Actor.js';
|
|
2
|
+
|
|
3
|
+
// Второй заглушечный part фикстуры — радар-проекция того же актёра,
|
|
4
|
+
// зеркало пары Tank/TankRadar танков (доказывает, что gameSets/
|
|
5
|
+
// entitiesOnCanvas умеют распределять одну сущность по нескольким
|
|
6
|
+
// полотнам произвольным числом part-классов).
|
|
7
|
+
export default class ActorRadar extends Actor {}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import gameConfig from './game.js';
|
|
2
|
+
|
|
3
|
+
// Схема авторизации фикстуры (HostPlugin.authSchema, PLAN.md §3.2) —
|
|
4
|
+
// зеркало vimp-tanks/src/config/auth.js с единственной моделью.
|
|
5
|
+
export default {
|
|
6
|
+
elems: {
|
|
7
|
+
authId: 'auth',
|
|
8
|
+
formId: 'auth-form',
|
|
9
|
+
errorId: 'auth-error',
|
|
10
|
+
enterId: 'auth-enter',
|
|
11
|
+
titleId: 'auth-title',
|
|
12
|
+
informsId: 'auth-informs',
|
|
13
|
+
},
|
|
14
|
+
// собственные тексты фикстуры (Д2): каркас auth.pug нейтрален,
|
|
15
|
+
// заголовок/подсказки — данные игры
|
|
16
|
+
texts: {
|
|
17
|
+
title: 'Mini Game Fixture',
|
|
18
|
+
sections: [
|
|
19
|
+
{
|
|
20
|
+
heading: 'Controls',
|
|
21
|
+
lines: [
|
|
22
|
+
{ keys: 'W, S', text: 'move' },
|
|
23
|
+
{ keys: 'J', text: 'fire' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
params: [
|
|
29
|
+
{
|
|
30
|
+
name: 'model',
|
|
31
|
+
value: 'm1',
|
|
32
|
+
options: {
|
|
33
|
+
validator: 'isValidModel',
|
|
34
|
+
storage: 'model',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
validators: {
|
|
39
|
+
isValidModel: model => model in gameConfig.parts.models,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Игровая половина клиентского CONFIG_DATA (HostPlugin.buildClientGameConfig,
|
|
2
|
+
// PLAN.md §3.5) — зеркало vimp-tanks/src/config/client.js, минимальная
|
|
3
|
+
// схема: одно полотно, одна колонка статистики сверх имени, один keySet.
|
|
4
|
+
export default {
|
|
5
|
+
parts: {
|
|
6
|
+
gameSets: {
|
|
7
|
+
// ключ снапшот-схемы (a1) — по нему клиент разводит записи кадра по
|
|
8
|
+
// parts; setId карты (m1) — по нему создаётся статика полотна
|
|
9
|
+
a1: ['Actor'],
|
|
10
|
+
m1: ['Actor'],
|
|
11
|
+
},
|
|
12
|
+
entitiesOnCanvas: {
|
|
13
|
+
Actor: 'vimp',
|
|
14
|
+
},
|
|
15
|
+
bakedAssets: {},
|
|
16
|
+
componentDependencies: {},
|
|
17
|
+
sounds: {},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
initIdList: ['vimp', 'panel', 'chat'],
|
|
21
|
+
|
|
22
|
+
modules: {
|
|
23
|
+
canvasManager: {
|
|
24
|
+
canvases: {
|
|
25
|
+
vimp: {
|
|
26
|
+
width: 640,
|
|
27
|
+
height: 400,
|
|
28
|
+
aspectRatio: '16:10',
|
|
29
|
+
baseScale: '5:1',
|
|
30
|
+
dynamicCamera: false,
|
|
31
|
+
shakeCamera: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
controls: {
|
|
37
|
+
keySetList: [
|
|
38
|
+
// spectator keyset (пусто — у фикстуры нет наблюдательных команд)
|
|
39
|
+
{},
|
|
40
|
+
// player keyset
|
|
41
|
+
{
|
|
42
|
+
87: 'forward', // forward (w)
|
|
43
|
+
83: 'back', // back (s)
|
|
44
|
+
74: 'fire', // fire (j)
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
chat: {
|
|
50
|
+
params: {
|
|
51
|
+
messages: {
|
|
52
|
+
s: [
|
|
53
|
+
'Team {0} is full. Your current team: {1}',
|
|
54
|
+
'Your team: {0}',
|
|
55
|
+
'Your new team: {0}',
|
|
56
|
+
'Your new status: spectator',
|
|
57
|
+
'{0} killed {1}',
|
|
58
|
+
'{0} joined the game',
|
|
59
|
+
'{0} left the game',
|
|
60
|
+
],
|
|
61
|
+
v: [
|
|
62
|
+
'A vote has been created',
|
|
63
|
+
'Voting has started',
|
|
64
|
+
'Your vote has been accepted',
|
|
65
|
+
'Voting is temporarily unavailable',
|
|
66
|
+
'Vote passed',
|
|
67
|
+
'Vote failed',
|
|
68
|
+
],
|
|
69
|
+
m: ['Current map: {0}', 'Next map: {0}'],
|
|
70
|
+
c: ['Command not found'],
|
|
71
|
+
n: ['Invalid name', '{0} changed name to {1}'],
|
|
72
|
+
g: ['{0} scripted participant(s) spawned'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
panel: {
|
|
78
|
+
// поле нарочно называется не health: бар обязан работать по
|
|
79
|
+
// type: 'bar', а не по имени поля (Д2)
|
|
80
|
+
// 't' — движковый ключ времени раунда: его шлёт Panel хоста, поэтому
|
|
81
|
+
// без маппинга клиент получал бы значение с именем undefined
|
|
82
|
+
keys: {
|
|
83
|
+
h: 'energy',
|
|
84
|
+
t: 'time',
|
|
85
|
+
},
|
|
86
|
+
fields: [
|
|
87
|
+
{
|
|
88
|
+
name: 'energy',
|
|
89
|
+
elem: 'panel-energy',
|
|
90
|
+
type: 'bar',
|
|
91
|
+
max: 100,
|
|
92
|
+
blocks: 10,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'time',
|
|
96
|
+
elem: 'panel-time',
|
|
97
|
+
type: 'time',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
stat: {
|
|
103
|
+
params: {
|
|
104
|
+
columns: ['names', 'status', 'score', 'deaths', 'latency'],
|
|
105
|
+
heads: {
|
|
106
|
+
1: 'team1',
|
|
107
|
+
},
|
|
108
|
+
bodies: {
|
|
109
|
+
1: 'team1',
|
|
110
|
+
2: 'spectators',
|
|
111
|
+
},
|
|
112
|
+
sortList: {
|
|
113
|
+
team1: [
|
|
114
|
+
[2, true],
|
|
115
|
+
[3, false],
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
vote: {
|
|
122
|
+
params: {
|
|
123
|
+
templates: {
|
|
124
|
+
teamChange: ['Choose a team', 'teams', true],
|
|
125
|
+
mapChangeBySystem: ['Choose the next map'],
|
|
126
|
+
mapChangeByUser: ['{0} suggested the map: {1}', ['Yes', 'No']],
|
|
127
|
+
},
|
|
128
|
+
menu: [
|
|
129
|
+
['teamChange', ['Switch team', 'teams']],
|
|
130
|
+
['mapChange', ['Suggest map', 'maps']],
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
gameInform: {
|
|
137
|
+
list: ['{0} WINS!', 'ROUND START!', 'GAME OVER!'],
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Игровая конфигурация фикстуры «миниигра» (Этап 7 плана отделения
|
|
2
|
+
// движка — HostPlugin.gameConfig): нарочно минимальна и структурно
|
|
3
|
+
// отличается от vimp-tanks/src/config/game.js — доказывает, что
|
|
4
|
+
// движковая мета (Panel/Stat/RoundManager/ParticipantManager/
|
|
5
|
+
// CommandProcessor) настраивается произвольной схемой, а не только
|
|
6
|
+
// схемой танков. Одна играющая команда (team1) вместо двух у танков —
|
|
7
|
+
// проверка нижней границы настраиваемости Stat/RoundManager.
|
|
8
|
+
export default {
|
|
9
|
+
parts: {
|
|
10
|
+
models: { m1: { radius: 10 } },
|
|
11
|
+
weapons: {},
|
|
12
|
+
friendlyFire: false,
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// собственная снапшот-схема (нарочно не танковая: один hot-ключ актора
|
|
16
|
+
// с 4 полями и один event-ключ) — движок обязан работать с любой
|
|
17
|
+
// раскладкой из gameConfig.snapshot, а не только m1/w1/…/c2
|
|
18
|
+
snapshot: {
|
|
19
|
+
a1: {
|
|
20
|
+
id: 1,
|
|
21
|
+
kind: 'indexed8',
|
|
22
|
+
class: 'hot',
|
|
23
|
+
fields: [
|
|
24
|
+
{ name: 'x', ty: 'f32', interp: 'lerp' },
|
|
25
|
+
{ name: 'y', ty: 'f32', interp: 'lerp' },
|
|
26
|
+
{ name: 'angle', ty: 'f32', interp: 'lerpAngle' },
|
|
27
|
+
{ name: 'team', ty: 'u8' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
e1: {
|
|
31
|
+
id: 2,
|
|
32
|
+
kind: 'list16',
|
|
33
|
+
class: 'event',
|
|
34
|
+
fields: [
|
|
35
|
+
{ name: 'x', ty: 'f32' },
|
|
36
|
+
{ name: 'y', ty: 'f32' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
soundCues: {},
|
|
42
|
+
initialVote: 'teamChange',
|
|
43
|
+
|
|
44
|
+
maps: {
|
|
45
|
+
arena: {
|
|
46
|
+
setId: 'm1',
|
|
47
|
+
scale: 1,
|
|
48
|
+
spriteSheet: { img: 'tiles.png', frames: [[0, 0, 32, 32]] },
|
|
49
|
+
layers: { 1: [0] },
|
|
50
|
+
physicsStatic: [1],
|
|
51
|
+
physicsDynamic: [],
|
|
52
|
+
step: 32,
|
|
53
|
+
respawns: {
|
|
54
|
+
team1: [
|
|
55
|
+
[100, 100, 0],
|
|
56
|
+
[200, 100, 0],
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
map: [
|
|
60
|
+
[1, 1, 1, 1],
|
|
61
|
+
[1, 0, 0, 1],
|
|
62
|
+
[1, 0, 0, 1],
|
|
63
|
+
[1, 1, 1, 1],
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
mapScale: 1,
|
|
68
|
+
currentMap: 'arena',
|
|
69
|
+
mapsInVote: 1,
|
|
70
|
+
mapSetId: 'm1',
|
|
71
|
+
|
|
72
|
+
roomDefaults: {
|
|
73
|
+
maxPlayers: 4,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// RoundManager/RTTManager (движковые) обновляют status/score/deaths/latency
|
|
77
|
+
// безусловно (не через схему) — поэтому набор колонок такой же, как у
|
|
78
|
+
// танков; отличие фикстуры — ОДНА играющая команда (см. teams ниже)
|
|
79
|
+
stat: {
|
|
80
|
+
name: {
|
|
81
|
+
key: 0,
|
|
82
|
+
bodyMethod: '=',
|
|
83
|
+
headSync: true,
|
|
84
|
+
headMethod: '#',
|
|
85
|
+
},
|
|
86
|
+
status: {
|
|
87
|
+
key: 1,
|
|
88
|
+
bodyMethod: '=',
|
|
89
|
+
bodyValue: '',
|
|
90
|
+
headValue: '',
|
|
91
|
+
},
|
|
92
|
+
score: {
|
|
93
|
+
key: 2,
|
|
94
|
+
bodyMethod: '+',
|
|
95
|
+
bodyValue: 0,
|
|
96
|
+
headMethod: '+',
|
|
97
|
+
headValue: 0,
|
|
98
|
+
},
|
|
99
|
+
deaths: {
|
|
100
|
+
key: 3,
|
|
101
|
+
bodyMethod: '+',
|
|
102
|
+
bodyValue: 0,
|
|
103
|
+
headMethod: '+',
|
|
104
|
+
headValue: 0,
|
|
105
|
+
},
|
|
106
|
+
latency: {
|
|
107
|
+
key: 4,
|
|
108
|
+
bodyMethod: '=',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
panel: {
|
|
113
|
+
// поле нарочно не health (Д2): семантику ячейки клиенту задаёт
|
|
114
|
+
// type в схеме client.js, а не имя поля
|
|
115
|
+
fields: {
|
|
116
|
+
energy: {
|
|
117
|
+
key: 'h',
|
|
118
|
+
value: 100,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
activeKey: null,
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
scripted: {
|
|
125
|
+
namePrefix: 'Scripted',
|
|
126
|
+
defaultModel: 'm1',
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
spectatorTeam: 'spectators',
|
|
130
|
+
|
|
131
|
+
teams: {
|
|
132
|
+
team1: 1,
|
|
133
|
+
spectators: 2,
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
playerKeys: {
|
|
137
|
+
forward: {
|
|
138
|
+
key: 1 << 0,
|
|
139
|
+
},
|
|
140
|
+
back: {
|
|
141
|
+
key: 1 << 1,
|
|
142
|
+
},
|
|
143
|
+
fire: {
|
|
144
|
+
key: 1 << 2,
|
|
145
|
+
type: 1,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Игровой scripted-модуль фикстуры: зеркало
|
|
2
|
+
// vimp-tanks/src/host/TanksBotManager.js — тот же контракт (createMap,
|
|
3
|
+
// createScripted, removeScripted, removeOneForHuman, getCount,
|
|
4
|
+
// getCountsPerTeam), проверяющий, что RoundManager/HostGame/CommandProcessor
|
|
5
|
+
// работают с любым HostPlugin.createModules(ctx), а не только с ботами
|
|
6
|
+
// танков. Worker-safe.
|
|
7
|
+
export default class ScriptedManager {
|
|
8
|
+
constructor({ participants, coreAdapter, panel, stat, scripted }) {
|
|
9
|
+
this._participants = participants;
|
|
10
|
+
this._coreAdapter = coreAdapter;
|
|
11
|
+
this._panel = panel;
|
|
12
|
+
this._stat = stat;
|
|
13
|
+
|
|
14
|
+
this._model = scripted.defaultModel;
|
|
15
|
+
this._respawns = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
createMap(mapData) {
|
|
19
|
+
this._respawns = mapData.respawns;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
createScripted(count, teamName = null) {
|
|
23
|
+
if (!this._respawns) {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const playableTeams = this._participants.getPlayableTeams();
|
|
28
|
+
let createdCount = 0;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < count; i += 1) {
|
|
31
|
+
if (this._participants.isFull) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let targetTeam = teamName;
|
|
36
|
+
|
|
37
|
+
if (!targetTeam) {
|
|
38
|
+
targetTeam = playableTeams.sort(
|
|
39
|
+
(a, b) =>
|
|
40
|
+
this._participants.getTeamSize(a) -
|
|
41
|
+
this._participants.getTeamSize(b),
|
|
42
|
+
)[0];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
!targetTeam ||
|
|
47
|
+
!this._respawns[targetTeam] ||
|
|
48
|
+
this._participants.getTeamSize(targetTeam) >=
|
|
49
|
+
this._respawns[targetTeam].length
|
|
50
|
+
) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const gameId = this._participants.createScripted({
|
|
55
|
+
team: targetTeam,
|
|
56
|
+
model: this._model,
|
|
57
|
+
});
|
|
58
|
+
const participant = this._participants.get(gameId);
|
|
59
|
+
|
|
60
|
+
this._stat.addUser(gameId, participant.teamId, {
|
|
61
|
+
name: participant.name,
|
|
62
|
+
status: 'dead',
|
|
63
|
+
latency: 'SCRIPTED',
|
|
64
|
+
});
|
|
65
|
+
this._panel.addUser(gameId);
|
|
66
|
+
|
|
67
|
+
createdCount += 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return createdCount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
removeScripted(teamName = null) {
|
|
74
|
+
const toRemove = teamName
|
|
75
|
+
? this._participants.getScripted().filter(p => p.team === teamName)
|
|
76
|
+
: this._participants.getScripted();
|
|
77
|
+
|
|
78
|
+
toRemove.forEach(p => this._removeById(p.gameId));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
removeOneForHuman(teamName) {
|
|
82
|
+
for (const participant of this._participants.getScripted()) {
|
|
83
|
+
if (participant.team === teamName) {
|
|
84
|
+
this._removeById(participant.gameId);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_removeById(gameId) {
|
|
93
|
+
const participant = this._participants.get(gameId);
|
|
94
|
+
|
|
95
|
+
if (!participant || !participant.isScripted) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._stat.removeUser(gameId, participant.teamId);
|
|
100
|
+
this._panel.removeUser(gameId);
|
|
101
|
+
this._coreAdapter.removePlayer(gameId);
|
|
102
|
+
|
|
103
|
+
this._participants.remove(gameId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getScripted() {
|
|
107
|
+
return this._participants.getScripted();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getCount() {
|
|
111
|
+
return this._participants.getScripted().length;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getCountForTeam(teamName) {
|
|
115
|
+
return this._participants
|
|
116
|
+
.getScripted()
|
|
117
|
+
.filter(p => p.team === teamName).length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getCountsPerTeam() {
|
|
121
|
+
const counts = {};
|
|
122
|
+
|
|
123
|
+
for (const participant of this._participants.getScripted()) {
|
|
124
|
+
counts[participant.team] = (counts[participant.team] || 0) + 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return counts;
|
|
128
|
+
}
|
|
129
|
+
}
|