mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
strict game event types
This commit is contained in:
parent
b3e69f1af4
commit
a7911f2d3d
11 changed files with 3925 additions and 96 deletions
4
Makefile
4
Makefile
|
|
@ -25,3 +25,7 @@ unit: node_modules
|
|||
.PHONY: lint
|
||||
lint: node_modules
|
||||
node_modules/.bin/tslint -p tsconfig.json
|
||||
|
||||
.PHONY: src/Data/GameEventTypes.ts
|
||||
src/Data/GameEventTypes.ts:
|
||||
node bin/analyse.js --create-event-definitions src/tests/data/celt.dem > src/Data/GameEventTypes.ts
|
||||
|
|
|
|||
147
bin/analyse.js
147
bin/analyse.js
|
|
@ -1,30 +1,40 @@
|
|||
require('source-map-support').install();
|
||||
|
||||
var Demo = require('../index');
|
||||
var fs = require('fs');
|
||||
var argv = require('minimist')(process.argv.slice(2), {boolean: true});
|
||||
const Demo = require('../index');
|
||||
const fs = require('fs');
|
||||
const argv = require('minimist')(process.argv.slice(2), {boolean: true});
|
||||
|
||||
if (argv._.length !== 1) {
|
||||
console.log('Usage: "node analyse [--strings] [--dump] [--head] FILE"');
|
||||
console.log('Usage: "node analyse [--strings] [--dump] [--head] [--event-list] [--create-event-definitions] FILE"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var echo = function (data) {
|
||||
var string = JSON.stringify(data, null, 2);
|
||||
const echo = function (data) {
|
||||
const string = JSON.stringify(data, null, 2);
|
||||
console.log(string);
|
||||
};
|
||||
|
||||
fs.readFile(argv._[0], function (err, data) {
|
||||
if (err) throw err;
|
||||
var demo = Demo.fromNodeBuffer(data);
|
||||
var parser = demo.getParser(true);
|
||||
var head = parser.readHeader();
|
||||
const demo = Demo.fromNodeBuffer(data);
|
||||
const parser = demo.getParser(true);
|
||||
const head = parser.readHeader();
|
||||
if (argv.head) {
|
||||
echo(head);
|
||||
return;
|
||||
}
|
||||
var match = parser.parseBody();
|
||||
if (argv.dump) {
|
||||
const match = parser.parseBody();
|
||||
if (argv['create-event-definitions']) {
|
||||
const definitions = Array.from(parser.match.eventDefinitions.values());
|
||||
const definition = definitions
|
||||
.map(createEventDefinition)
|
||||
.join('\n\n')
|
||||
+ '\n\n' + createEventDefinitionUnion(definitions) + '\n\n'
|
||||
+ createEventTpeMap(definitions) + '\n';
|
||||
console.log(definition);
|
||||
} else if (argv['event-list']) {
|
||||
echo(Array.from(parser.match.eventDefinitions.values()));
|
||||
} else if (argv.dump) {
|
||||
echo(parser.match.packets);
|
||||
} else if (argv.strings) {
|
||||
echo(parser.match.strings);
|
||||
|
|
@ -32,3 +42,118 @@ fs.readFile(argv._[0], function (err, data) {
|
|||
echo(match.getState());
|
||||
}
|
||||
});
|
||||
|
||||
function getEventTypeName(s) {
|
||||
const name = s.replace(/(\_\w)/g, function (m) {
|
||||
return m[1].toUpperCase();
|
||||
}).replace(/\b[a-z]/g, function (letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
if (EventNameReplace.has(name)) {
|
||||
return EventNameReplace.get(name);
|
||||
} else {
|
||||
return name
|
||||
.replace('Teamplay', 'TeamPlay')
|
||||
.replace('death', 'Death')
|
||||
.replace('panel', 'Panel')
|
||||
.replace('object', 'Object')
|
||||
.replace('update', 'Update')
|
||||
.replace('ready', 'Ready')
|
||||
.replace('Gameui', 'GameUI')
|
||||
.replace('onhit', 'OnHit')
|
||||
.replace('bymedic', 'ByMedic')
|
||||
.replace('Controlpoint', 'ControlPoint')
|
||||
.replace('Pipebomb', 'PipeBomb')
|
||||
.replace('Scorestats', 'ScoreStats')
|
||||
.replace('Creditbonus', 'CreditBonus')
|
||||
.replace('Sentrybuster', 'SentryBuster')
|
||||
.replace('Questlog', 'QuestLog')
|
||||
.replace('Localplayer', 'LocalPlayer')
|
||||
.replace('Minigame', 'MiniGame')
|
||||
.replace('Winlimit', 'WinLimit')
|
||||
.replace('Hltv', 'HLTV');
|
||||
}
|
||||
}
|
||||
|
||||
function getEntryTypeDefinition(typeId) {
|
||||
switch (typeId) {
|
||||
case 1:
|
||||
return 'string';
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
return 'number';
|
||||
case 6:
|
||||
return 'boolean';
|
||||
case 7:
|
||||
return 'null';
|
||||
}
|
||||
}
|
||||
|
||||
function createEventDefinition(definition) {
|
||||
return `
|
||||
export interface ${getEventTypeName(definition.name)}Event {
|
||||
name: '${definition.name}';
|
||||
values: {
|
||||
${definition.entries.map(entry => ` ${entry.name}: ${getEntryTypeDefinition(entry.type)};`).join('\n')}
|
||||
};
|
||||
}`.trim()
|
||||
}
|
||||
|
||||
function createEventDefinitionUnion(definitions) {
|
||||
return `export type GameEvent = ` +
|
||||
definitions.map(definition => '\t' + getEventTypeName(definition.name) + 'Event')
|
||||
.join(' |\n').trim()
|
||||
+ ';';
|
||||
}
|
||||
|
||||
function createEventTpeMap(definitions) {
|
||||
return `export type GameEventTypeMap = {
|
||||
${definitions.map(definition => ` ${definition.name}: ${getEventTypeName(definition.name)}Event;`).join('\n')}
|
||||
};`;
|
||||
}
|
||||
|
||||
const EventNameReplace = new Map([
|
||||
['ReplayReplaysavailable', 'ReplayReplaysAvailable'],
|
||||
['ServerAddban', 'ServerAddBan'],
|
||||
['ServerRemoveban', 'ServerRemoveBan'],
|
||||
['ClientBeginconnect', 'ClientBeginConnect'],
|
||||
['ClientFullconnect', 'ClientFullConnect'],
|
||||
['PlayerChangename', 'PlayerChangeName'],
|
||||
['PlayerHintmessage', 'PlayerHintMessage'],
|
||||
['GameNewmap', 'GameNewMap'],
|
||||
['IntroNextcamera', 'IntroNextCamera'],
|
||||
['PlayerChangeclass', 'PlayerChangeClass'],
|
||||
['ControlpointInitialized', 'ControlPointInitialized'],
|
||||
['ControlpointUpdateimages', 'ControlPointUpdateImages'],
|
||||
['ControlpointUpdatelayout', 'ControlPointUpdateLayout'],
|
||||
['ControlpointUpdatecapping', 'ControlPointUpdateCapping'],
|
||||
['ControlpointUpdateowner', 'ControlPointUpdateOwner'],
|
||||
['ControlpointStarttouch', 'ControlPointStartTouch'],
|
||||
['ControlpointEndtouch', 'ControlPointEndTouch'],
|
||||
['ControlpointPulseElement', 'ControlPointPulseElement'],
|
||||
['ControlpointFakeCapture', 'ControlPointFakeCapture'],
|
||||
['ControlpointFakeCaptureMult', 'ControlPointFakeCaptureMult'],
|
||||
['TeamplayWaitingAbouttoend', 'TeamPlayWaitingAboutToEnd'],
|
||||
['TeamplayPointStartcapture', 'TeamPlayPointStartCapture'],
|
||||
['FreezecamStarted', 'FreezeCamStarted'],
|
||||
['LocalplayerChangeteam', 'LocalPlayerChangeTeam'],
|
||||
['LocalplayerChangeclass', 'LocalPlayerChangeClass'],
|
||||
['LocalplayerChangedisguise', 'LocalPlayerChangeDisguise'],
|
||||
['FlagstatusUpdate', 'FlagStatusUpdate'],
|
||||
['TournamentEnablecountdown', 'TournamentEnableCountdown'],
|
||||
['PlayerCalledformedic', 'PlayerCalledForMedic'],
|
||||
['PlayerAskedforball', 'PlayerAskedForBall'],
|
||||
['LocalplayerBecameobserver', 'LocalPlayerBecameObserver'],
|
||||
['PlayerHealedmediccall', 'PlayerHealedMedicCall'],
|
||||
['ArenaMatchMaxstreak', 'ArenaMatchMaxStreak'],
|
||||
['StatsResetround', 'StatsResetRound'],
|
||||
['FishNotice_arm', 'FishNoticeArm'],
|
||||
['PlayerBonuspoints', 'PlayerBonusPoints'],
|
||||
['PlayerUsedPowerupBottle', 'PlayerUsedPowerUpBottle'],
|
||||
['ReplayStartrecord', 'ReplayStartRecord'],
|
||||
['ReplaySessioninfo', 'ReplaySessionInfo'],
|
||||
['ReplayEndrecord', 'ReplayEndRecord'],
|
||||
['ReplayServererror', 'ReplayServerError']
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
export interface GameEventDefinition {
|
||||
id: number;
|
||||
name: string;
|
||||
entries: GameEventEntry[];
|
||||
}
|
||||
import {GameEvent} from './GameEventTypes';
|
||||
|
||||
export interface GameEvent {
|
||||
name: string;
|
||||
values: GameEventValues;
|
||||
export interface GameEventDefinition<T extends GameEvent['name']> {
|
||||
id: number;
|
||||
name: T;
|
||||
entries: GameEventEntry[];
|
||||
}
|
||||
|
||||
export interface GameEventEntry {
|
||||
name: string;
|
||||
type: GameEventType;
|
||||
type: GameEventValueType;
|
||||
}
|
||||
|
||||
export enum GameEventType {
|
||||
export enum GameEventValueType {
|
||||
STRING = 1,
|
||||
FLOAT = 2,
|
||||
LONG = 3,
|
||||
|
|
@ -24,42 +21,8 @@ export enum GameEventType {
|
|||
LOCAL = 7,
|
||||
}
|
||||
|
||||
export interface DeathEventValues {
|
||||
attacker: number;
|
||||
userid: number;
|
||||
assister: number;
|
||||
weapon: string;
|
||||
}
|
||||
|
||||
export interface RoundWinEventValues {
|
||||
winreason: number;
|
||||
team: number;
|
||||
round_time: number;
|
||||
}
|
||||
|
||||
export interface PlayerSpawnEventValues {
|
||||
userid: number;
|
||||
team: number;
|
||||
'class': number;
|
||||
}
|
||||
|
||||
export interface ObjectDestroyedValues {
|
||||
userid: number;
|
||||
attacker: number;
|
||||
weapon: string;
|
||||
weapinid: number;
|
||||
objecttype: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export type GameEventValue = string | number | boolean;
|
||||
|
||||
export interface GameEventValueMap {
|
||||
export interface GameEventValues {
|
||||
[name: string]: GameEventValue;
|
||||
}
|
||||
|
||||
export type GameEventValues = GameEventValueMap |
|
||||
DeathEventValues |
|
||||
RoundWinEventValues |
|
||||
PlayerSpawnEventValues |
|
||||
ObjectDestroyedValues;
|
||||
|
|
|
|||
3730
src/Data/GameEventTypes.ts
Normal file
3730
src/Data/GameEventTypes.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@ import {Weapon} from './Weapon';
|
|||
import {World} from './World';
|
||||
import {Round} from './Round';
|
||||
import {Chat} from './Chat';
|
||||
import {GameEvent} from './GameEventTypes';
|
||||
|
||||
export class Match {
|
||||
public tick: number = 0;
|
||||
|
|
@ -30,7 +31,7 @@ export class Match {
|
|||
public startTick: number = 0;
|
||||
public intervalPerTick: number = 0;
|
||||
public staticBaseLines: BitStream[] = [];
|
||||
public eventDefinitions: Map<number, GameEventDefinition> = new Map();
|
||||
public eventDefinitions: Map<number, GameEventDefinition<GameEvent['name']>> = new Map();
|
||||
public world: World = {
|
||||
boundaryMin: {x: 0, y: 0, z: 0},
|
||||
boundaryMax: {x: 0, y: 0, z: 0},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEvent, GameEventDefinition} from './GameEvent';
|
||||
import {GameEventDefinition} from './GameEvent';
|
||||
import {PacketEntity} from './PacketEntity';
|
||||
import {SendTable} from './SendTable';
|
||||
import {ServerClass} from './ServerClass';
|
||||
import {StringTable, StringTableEntry} from './StringTable';
|
||||
import {Vector} from './Vector';
|
||||
import {GameEvent, GameEventType} from './GameEventTypes';
|
||||
|
||||
export interface BasePacket {
|
||||
}
|
||||
|
|
@ -70,7 +71,7 @@ export interface GameEventPacket extends BasePacket {
|
|||
|
||||
export interface GameEventListPacket extends BasePacket {
|
||||
packetType: 'gameEventList';
|
||||
eventList: Map<number, GameEventDefinition>;
|
||||
eventList: Map<number, GameEventDefinition<GameEvent['name']>>;
|
||||
}
|
||||
|
||||
export interface PacketEntitiesPacket extends BasePacket {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
import {DeathEventValues, ObjectDestroyedValues, PlayerSpawnEventValues, RoundWinEventValues} from '../Data/GameEvent';
|
||||
import {Match} from '../Data/Match';
|
||||
import {GameEventPacket} from '../Data/Packet';
|
||||
import {
|
||||
ObjectDestroyedEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundStartEvent, TeamPlayRoundWinEvent
|
||||
} from '../Data/GameEventTypes';
|
||||
|
||||
export function handleGameEvent(packet: GameEventPacket, match: Match) {
|
||||
switch (packet.event.name) {
|
||||
case 'player_death':
|
||||
handlePlayerDeath(packet, match);
|
||||
handlePlayerDeath(packet.event, match);
|
||||
break;
|
||||
case 'teamplay_round_win':
|
||||
handleRoundWin(packet, match);
|
||||
handleRoundWin(packet.event, match);
|
||||
break;
|
||||
case 'player_spawn':
|
||||
handlePlayerSpawn(packet, match);
|
||||
handlePlayerSpawn(packet.event, match);
|
||||
break;
|
||||
case 'object_destroyed':
|
||||
handleObjectDestroyed(packet, match);
|
||||
handleObjectDestroyed(packet.event, match);
|
||||
break;
|
||||
case 'teamplay_round_start':
|
||||
handleRoundStart(packet, match);
|
||||
handleRoundStart(packet.event, match);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePlayerDeath(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as DeathEventValues;
|
||||
function handlePlayerDeath(event: PlayerDeathEvent, match: Match) {
|
||||
const values = event.values;
|
||||
while (values.assister > 256 && values.assister < (1024 * 16)) {
|
||||
values.assister -= 256;
|
||||
}
|
||||
|
|
@ -44,8 +46,8 @@ function handlePlayerDeath(packet: GameEventPacket, match: Match) {
|
|||
});
|
||||
}
|
||||
|
||||
function handleRoundWin(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as RoundWinEventValues;
|
||||
function handleRoundWin(event: TeamPlayRoundWinEvent, match: Match) {
|
||||
const values = event.values;
|
||||
if (values.winreason !== 6) {// 6 = timelimit
|
||||
match.rounds.push({
|
||||
winner: values.team === 2 ? 'red' : 'blue',
|
||||
|
|
@ -55,8 +57,8 @@ function handleRoundWin(packet: GameEventPacket, match: Match) {
|
|||
}
|
||||
}
|
||||
|
||||
function handlePlayerSpawn(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as PlayerSpawnEventValues;
|
||||
function handlePlayerSpawn(event: PlayerSpawnEvent, match: Match) {
|
||||
const values = event.values;
|
||||
const userId = values.userid;
|
||||
const userState = match.getUserInfo(userId);
|
||||
const player = match.playerEntityMap.get(userState.entityId);
|
||||
|
|
@ -72,11 +74,11 @@ function handlePlayerSpawn(packet: GameEventPacket, match: Match) {
|
|||
userState.classes[classId]++;
|
||||
}
|
||||
|
||||
function handleObjectDestroyed(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as ObjectDestroyedValues;
|
||||
function handleObjectDestroyed(event: ObjectDestroyedEvent, match: Match) {
|
||||
const values = event.values;
|
||||
match.buildings.delete(values.index);
|
||||
}
|
||||
|
||||
function handleRoundStart(packet: GameEventPacket, match: Match) {
|
||||
function handleRoundStart(event: TeamPlayRoundStartEvent, match: Match) {
|
||||
match.buildings.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,48 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {
|
||||
GameEvent as IGameEvent, GameEventDefinition, GameEventEntry, GameEventType,
|
||||
GameEventValue, GameEventValueMap,
|
||||
GameEventDefinition, GameEventEntry,
|
||||
GameEventValue, GameEventValueType,
|
||||
} from '../../Data/GameEvent';
|
||||
import {GameEvent, GameEventType} from '../../Data/GameEventTypes';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {GameEventPacket} from '../../Data/Packet';
|
||||
|
||||
function parseGameEvent(eventId: number, stream: BitStream, events: Map<number, GameEventDefinition>): IGameEvent {
|
||||
function parseGameEvent(eventId: number, stream: BitStream, events: Map<number, GameEventDefinition<GameEventType>>) {
|
||||
const eventDescription = events.get(eventId);
|
||||
if (!eventDescription) {
|
||||
throw new Error('unknown event type');
|
||||
}
|
||||
const values: GameEventValueMap = {};
|
||||
const values: GameEvent['values'] = {};
|
||||
for (const entry of eventDescription.entries) {
|
||||
const value = getGameEventValue(stream, entry);
|
||||
if (value) {
|
||||
values[entry.name] = value;
|
||||
}
|
||||
}
|
||||
const name = eventDescription.name;
|
||||
|
||||
return {
|
||||
name: eventDescription.name,
|
||||
name,
|
||||
values,
|
||||
};
|
||||
}
|
||||
|
||||
function getGameEventValue(stream: BitStream, entry: GameEventEntry): GameEventValue | null {
|
||||
switch (entry.type) {
|
||||
case GameEventType.STRING:
|
||||
case GameEventValueType.STRING:
|
||||
return stream.readUTF8String();
|
||||
case GameEventType.FLOAT:
|
||||
case GameEventValueType.FLOAT:
|
||||
return stream.readFloat32();
|
||||
case GameEventType.LONG:
|
||||
case GameEventValueType.LONG:
|
||||
return stream.readUint32();
|
||||
case GameEventType.SHORT:
|
||||
case GameEventValueType.SHORT:
|
||||
return stream.readUint16();
|
||||
case GameEventType.BYTE:
|
||||
case GameEventValueType.BYTE:
|
||||
return stream.readUint8();
|
||||
case GameEventType.BOOLEAN:
|
||||
case GameEventValueType.BOOLEAN:
|
||||
return stream.readBoolean();
|
||||
case GameEventType.LOCAL:
|
||||
case GameEventValueType.LOCAL:
|
||||
return null;
|
||||
default:
|
||||
throw new Error('invalid game event type');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +54,6 @@ export function ParseGameEvent(stream: BitStream, match: Match): GameEventPacket
|
|||
stream.index = end;
|
||||
return {
|
||||
packetType: 'gameEvent',
|
||||
event,
|
||||
event: event as GameEvent,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEventDefinition, GameEventEntry} from '../../Data/GameEvent';
|
||||
import {GameEventListPacket} from '../../Data/Packet';
|
||||
import {GameEvent} from '../../Data/GameEventTypes';
|
||||
|
||||
export function ParseGameEventList(stream: BitStream): GameEventListPacket { // 30: gameEventList
|
||||
const s = stream.index;
|
||||
|
|
@ -8,10 +9,10 @@ export function ParseGameEventList(stream: BitStream): GameEventListPacket { //
|
|||
// list of game events and parameters
|
||||
const numEvents = stream.readBits(9);
|
||||
const length = stream.readBits(20);
|
||||
const eventList: Map<number, GameEventDefinition> = new Map();
|
||||
const eventList: Map<number, GameEventDefinition<GameEvent['name']>> = new Map();
|
||||
for (let i = 0; i < numEvents; i++) {
|
||||
const id = stream.readBits(9);
|
||||
const name = stream.readASCIIString();
|
||||
const name = stream.readASCIIString() as GameEvent['name'];
|
||||
let type = stream.readBits(3);
|
||||
const entries: GameEventEntry[] = [];
|
||||
while (type !== 0) {
|
||||
|
|
@ -57,8 +58,8 @@ export function EncodeGameEventList(packet: GameEventListPacket, stream: BitStre
|
|||
stream.writeBitStream(eventListStream);
|
||||
}
|
||||
|
||||
function getEventListLength(eventList: GameEventDefinition[]) {
|
||||
return eventList.reduce((length: number, entry: GameEventDefinition) => {
|
||||
function getEventListLength(eventList: GameEventDefinition<GameEvent['name']>[]) {
|
||||
return eventList.reduce((length: number, entry: GameEventDefinition<GameEvent['name']>) => {
|
||||
return length +
|
||||
9 +
|
||||
(entry.name.length + 1) * 8 +
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export {StreamParser} from './StreamParser';
|
|||
export {Match} from './Data/Match';
|
||||
export {Player} from './Data/Player';
|
||||
export {PlayerCondition} from './Data/PlayerCondition';
|
||||
export {GameEvent} from './Data/GameEvent';
|
||||
export {GameEvent} from './Data/GameEventTypes';
|
||||
export {PacketEntity} from './Data/PacketEntity';
|
||||
export {SendPropDefinition, SendPropFlag, SendPropType} from './Data/SendPropDefinition';
|
||||
export {SendProp} from './Data/SendProp';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {assertEncoder, assertParser, getStream} from './PacketTest';
|
|||
import {readFileSync} from 'fs';
|
||||
import {EncodeGameEventList, ParseGameEventList} from '../../../../Parser/Packet/GameEventList';
|
||||
import {GameEventListPacket} from '../../../../Data/Packet';
|
||||
import {GameEvent} from '../../../../Data/GameEventTypes';
|
||||
|
||||
const data = JSON.parse(readFileSync(__dirname + '/../../../data/gameEventListData.json', 'utf8'));
|
||||
const expectedSource = JSON.parse(readFileSync(__dirname + '/../../../data/gameEventList.json', 'utf8'));
|
||||
|
|
@ -17,7 +18,7 @@ const eventList: GameEventListPacket = {
|
|||
'eventList': new Map([
|
||||
[0, {
|
||||
'id': 0,
|
||||
'name': 'server_spawn',
|
||||
'name': 'server_spawn' as GameEvent['name'],
|
||||
'entries': [
|
||||
{
|
||||
'type': 1,
|
||||
|
|
@ -63,7 +64,7 @@ const eventList: GameEventListPacket = {
|
|||
}],
|
||||
[1, {
|
||||
'id': 1,
|
||||
'name': 'server_changelevel_failed',
|
||||
'name': 'server_changelevel_failed' as GameEvent['name'],
|
||||
'entries': [
|
||||
{
|
||||
'type': 1,
|
||||
|
|
@ -73,7 +74,7 @@ const eventList: GameEventListPacket = {
|
|||
}],
|
||||
[2, {
|
||||
'id': 2,
|
||||
'name': 'server_shutdown',
|
||||
'name': 'server_shutdown' as GameEvent['name'],
|
||||
'entries': [
|
||||
{
|
||||
'type': 1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue