Skip to content

Commit 5561dd1

Browse files
committed
Add HP, abilities and bug-fallback support (requiring auxiliary client)
1 parent 6e4db1a commit 5561dd1

File tree

11 files changed

+310
-25
lines changed

11 files changed

+310
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "spectra-server",
3-
"version": "0.2.15",
3+
"version": "0.2.16",
44
"author": {
55
"name": "Spectra Team",
66
"url": "https://valospectra.com"

src/connector/websocketIncoming.ts

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
require("dotenv").config();
22
import { Server, Socket } from "socket.io";
3-
import { DataTypes, IAUthenticationData, isAuthedData } from "../model/eventData";
3+
import {
4+
DataTypes,
5+
IAuthenticationData,
6+
IAuxAuthenticationData,
7+
isAuthedAuxData,
8+
isAuthedData,
9+
} from "../model/eventData";
410
import { MatchController } from "../controller/MatchController";
511
import logging from "../util/Logging";
612
import { readFileSync } from "fs";
@@ -59,7 +65,10 @@ export class WebsocketIncoming {
5965

6066
ws.once("obs_logon", async (msg) => {
6167
try {
62-
const authenticationData: IAUthenticationData = JSON.parse(msg.toString());
68+
const authenticationData: IAuthenticationData = JSON.parse(msg.toString());
69+
70+
if (WebsocketIncoming.authedClients.find((client) => client.ws.id === ws.id) != undefined)
71+
return;
6372

6473
// Check if the packet is valid
6574
if (authenticationData.type !== DataTypes.AUTH) {
@@ -139,6 +148,94 @@ export class WebsocketIncoming {
139148
Log.error(e);
140149
}
141150
});
151+
152+
ws.once("aux_logon", async (msg) => {
153+
try {
154+
const authenticationData: IAuxAuthenticationData = JSON.parse(msg.toString());
155+
156+
if (WebsocketIncoming.authedClients.find((client) => client.ws.id === ws.id) != undefined)
157+
return;
158+
159+
// Check if the packet is valid
160+
if (authenticationData.type !== DataTypes.AUX_AUTH) {
161+
ws.emit(
162+
"aux_logon_ack",
163+
JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Invalid packet.` }),
164+
);
165+
ws.disconnect();
166+
Log.info(`Received BAD aux auth request, invalid packet.`);
167+
return;
168+
}
169+
170+
// Check if the client version is compatible with the server version
171+
if (!isCompatibleVersion(authenticationData.clientVersion)) {
172+
ws.emit(
173+
"aux_logon_ack",
174+
JSON.stringify({
175+
type: DataTypes.AUTH,
176+
value: false,
177+
reason: `Client version ${authenticationData.clientVersion} is not compatible with server version ${module.exports.version}.`,
178+
}),
179+
);
180+
ws.disconnect();
181+
Log.info(
182+
`Received BAD aux auth request from ${authenticationData.playerId} for match ${authenticationData.matchId}, incompatible client version ${authenticationData.clientVersion}.`,
183+
);
184+
return;
185+
}
186+
187+
const groupCode = this.matchController.findMatch(authenticationData.matchId);
188+
// Check if the match exists
189+
if (groupCode == null) {
190+
ws.emit(
191+
"aux_logon_ack",
192+
JSON.stringify({
193+
type: DataTypes.AUTH,
194+
value: false,
195+
reason: `Game with Match ID ${authenticationData.matchId} not found.`,
196+
}),
197+
);
198+
ws.disconnect();
199+
Log.info(
200+
`Received BAD aux auth request from ${authenticationData.playerId} for match ${authenticationData.matchId}, match not found.`,
201+
);
202+
return;
203+
}
204+
205+
// All checks passed, send logon acknolwedgement
206+
ws.emit(
207+
"aux_logon_ack",
208+
JSON.stringify({ type: DataTypes.AUX_AUTH, value: true, reason: groupCode }),
209+
);
210+
user.name = authenticationData.name;
211+
user.groupCode = groupCode;
212+
user.isAuxiliary = true;
213+
user.playerId = authenticationData.playerId;
214+
WebsocketIncoming.authedClients.push(user);
215+
216+
Log.info(
217+
`Received VALID aux auth request from ${authenticationData.playerId} for Group Code ${groupCode}`,
218+
);
219+
this.onAuthSuccess(user);
220+
} catch (e) {
221+
Log.error(`Error parsing incoming auth request: ${e}`);
222+
Log.error(e);
223+
}
224+
});
225+
226+
ws.on("disconnect", () => {
227+
const index = WebsocketIncoming.authedClients.findIndex((client) => client.ws.id === ws.id);
228+
if (index != -1) {
229+
const client = WebsocketIncoming.authedClients[index];
230+
if (client.playerId !== "") {
231+
Log.info(`Auxiliary player ${client.playerId} disconnected.`);
232+
this.matchController.setAuxDisconnected(client.groupCode, client.playerId);
233+
}
234+
if (client.isAuxiliary) {
235+
WebsocketIncoming.authedClients.splice(index, 1);
236+
}
237+
}
238+
});
142239
});
143240

144241
serverInstance.listen(5100);
@@ -156,6 +253,20 @@ export class WebsocketIncoming {
156253
Log.error(`Error parsing obs_data: ${e}`);
157254
}
158255
});
256+
257+
user.ws.on("aux_data", async (msg: any) => {
258+
try {
259+
const data = JSON.parse(msg.toString());
260+
if (isAuthedAuxData(data)) {
261+
await this.matchController.receiveMatchData(data);
262+
if (data.type === DataTypes.AUX_SCOREBOARD && user.playerId === "") {
263+
user.playerId = data.playerId;
264+
}
265+
}
266+
} catch (e) {
267+
Log.error(`Error parsing aux_data: ${e}`);
268+
}
269+
});
159270
}
160271

161272
private async isValidKey(key: string): Promise<KeyValidity> {
@@ -184,6 +295,8 @@ export class WebsocketIncoming {
184295
class ClientUser {
185296
name: string;
186297
groupCode: string;
298+
isAuxiliary: boolean = false;
299+
playerId: string = "";
187300
ws: Socket;
188301

189302
constructor(name: string, groupCode: string, ws: Socket) {

src/controller/MatchController.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DatabaseConnector } from "../connector/databaseConnector";
22
import { WebsocketIncoming } from "../connector/websocketIncoming";
33
import { WebsocketOutgoing } from "../connector/websocketOutgoing";
44
import { Match } from "../model/Match";
5-
import { IAuthedData, IAUthenticationData } from "../model/eventData";
5+
import { IAuthedAuxData, IAuthedData, IAuthenticationData } from "../model/eventData";
66
import logging from "../util/Logging";
77
const Log = logging("MatchController");
88

@@ -22,7 +22,7 @@ export class MatchController {
2222
return MatchController.instance;
2323
}
2424

25-
async createMatch(data: IAUthenticationData) {
25+
async createMatch(data: IAuthenticationData) {
2626
try {
2727
if (this.matches[data.groupCode] != null) {
2828
return false;
@@ -40,6 +40,10 @@ export class MatchController {
4040
}
4141
}
4242

43+
findMatch(matchId: string) {
44+
return Object.values(this.matches).find((match) => match.matchId == matchId)?.groupCode ?? null;
45+
}
46+
4347
removeMatch(groupCode: string) {
4448
if (this.matches[groupCode] != null) {
4549
delete this.matches[groupCode];
@@ -58,10 +62,7 @@ export class MatchController {
5862
return Object.keys(this.matches).length;
5963
}
6064

61-
async receiveMatchData(data: IAuthedData) {
62-
if (data.timestamp == null) {
63-
data.timestamp = Date.now();
64-
}
65+
async receiveMatchData(data: IAuthedData | IAuthedAuxData) {
6566
data.timestamp = Date.now();
6667
const trackedMatch = this.matches[data.groupCode];
6768
if (trackedMatch == null) {
@@ -73,6 +74,12 @@ export class MatchController {
7374
await trackedMatch.receiveMatchSpecificData(data);
7475
}
7576

77+
setAuxDisconnected(groupCode: string, playerId: string) {
78+
if (this.matches[groupCode] != null) {
79+
this.matches[groupCode].setAuxDisconnected(playerId);
80+
}
81+
}
82+
7683
sendMatchDataForLogon(groupCode: string) {
7784
if (this.matches[groupCode] != null) {
7885
this.outgoingWebsocketServer.sendMatchData(groupCode, this.matches[groupCode]);

src/model/Match.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Team } from "./Team";
22
import {
33
DataTypes,
4+
IAuthedAuxData,
45
IAuthedData,
5-
IAUthenticationData,
6+
IAuthenticationData,
67
IFormattedRoundInfo,
78
IFormattedScore,
89
IFormattedScoreboard,
@@ -44,7 +45,7 @@ export class Match {
4445
public organizationId: string = "";
4546
public isRegistered: boolean = false;
4647

47-
constructor(data: IAUthenticationData) {
48+
constructor(data: IAuthenticationData) {
4849
this.groupCode = data.groupCode;
4950

5051
this.replayLog = new ReplayLogging(data);
@@ -62,7 +63,7 @@ export class Match {
6263
}
6364
}
6465

65-
async receiveMatchSpecificData(data: IAuthedData) {
66+
async receiveMatchSpecificData(data: IAuthedData | IAuthedAuxData) {
6667
this.replayLog.write(data);
6768

6869
let correctTeam = null;
@@ -92,6 +93,18 @@ export class Match {
9293
}
9394
break;
9495

96+
case DataTypes.AUX_SCOREBOARD:
97+
this.teams.forEach((team) => team.receiveTeamSpecificData(data));
98+
break;
99+
100+
case DataTypes.AUX_ABILITIES:
101+
this.teams.forEach((team) => team.receiveTeamSpecificData(data));
102+
break;
103+
104+
case DataTypes.AUX_HEALTH:
105+
this.teams.forEach((team) => team.receiveTeamSpecificData(data));
106+
break;
107+
95108
case DataTypes.SPIKE_PLANTED:
96109
if (this.roundPhase !== "combat") break;
97110
this.spikeState.planted = true;
@@ -249,6 +262,10 @@ export class Match {
249262
}
250263
}
251264

265+
public setAuxDisconnected(playerId: string) {
266+
this.teams.forEach((team) => team.setAuxDisconnected(playerId));
267+
}
268+
252269
private debugLogRoundInfo() {
253270
Log.debug(`Round ${this.roundNumber} - ${this.roundPhase}`);
254271
Log.debug(`Round Timeout: ${this.roundTimeoutTime}`);

src/model/Player.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import { Agents, Armor, WeaponsAndAbilities } from "../util/valorantInternalTranslator";
22
import { IFormattedKillfeed, IFormattedRoster, IFormattedScoreboard } from "./eventData";
3+
import logging from "../util/Logging";
4+
5+
const Log = logging("Player").level(1);
36

47
type ValueOf<T> = T[keyof T];
58

9+
export class AvailableAbilities {
10+
grenade: number = 0;
11+
ability1: number = 0;
12+
ability2: number = 0;
13+
}
14+
15+
class AvailableAuxiliary {
16+
health: boolean = false;
17+
abilities: boolean = false;
18+
scoreboard: boolean = false;
19+
}
20+
621
export class Player {
722
private name: string;
823
private tagline: string;
@@ -18,6 +33,9 @@ export class Player {
1833
private hasSpike: boolean = false;
1934
private isObserved: boolean = false;
2035

36+
private health: number = 100;
37+
private abilities: AvailableAbilities = new AvailableAbilities();
38+
2139
private kills: number = 0;
2240
private deaths: number = 0;
2341
private assists: number = 0;
@@ -46,6 +64,7 @@ export class Player {
4664
private assistsFromTeammate: Record<string, number> = {};
4765

4866
private scoreboardAvailable: boolean = false;
67+
private auxiliaryAvailable: AvailableAuxiliary = new AvailableAuxiliary();
4968

5069
constructor(data: IFormattedRoster) {
5170
this.name = data.name;
@@ -94,6 +113,14 @@ export class Player {
94113
this.agentInternal = data.agentInternal;
95114
this.agentProper = Agents[data.agentInternal] || data.agentInternal;
96115

116+
// Player dies
117+
if (!data.isAlive && this.isAlive) {
118+
this.health = 0;
119+
}
120+
// Player revives
121+
if (data.isAlive && !this.isAlive) {
122+
this.health = 100;
123+
}
97124
this.isAlive = data.isAlive;
98125
this.hasSpike = data.hasSpike;
99126

@@ -155,6 +182,7 @@ export class Player {
155182

156183
if (victim) {
157184
this.isAlive = false;
185+
this.health = 0;
158186
this.deaths++;
159187
} else {
160188
// The teamkill field is unreliable at the moment, so we're not using it for fallbacks
@@ -171,6 +199,30 @@ export class Player {
171199
}
172200
}
173201

202+
// Only take partial data from aux scoreboard, still get rest from observer
203+
public updateFromAuxiliaryScoreboard(data: IFormattedScoreboard) {
204+
if (this.scoreboardAvailable) return;
205+
this.hasSpike = data.hasSpike;
206+
this.highestWeapon = WeaponsAndAbilities[data.scoreboardWeaponInternal];
207+
this.maxUltPoints = data.maxUltPoints;
208+
this.currUltPoints = data.currUltPoints;
209+
this.armorName = Armor[data.initialArmor];
210+
this.assists = data.assists;
211+
this.money = data.money;
212+
213+
this.auxiliaryAvailable.scoreboard = true;
214+
}
215+
216+
public updateAbilities(data: AvailableAbilities) {
217+
this.abilities = data;
218+
this.auxiliaryAvailable.abilities = true;
219+
}
220+
221+
public setHeatlh(health: number) {
222+
this.health = health;
223+
this.auxiliaryAvailable.health = true;
224+
}
225+
174226
public resetRoundSpecificValues(isSideSwitch: boolean) {
175227
this.resetKillsThisRound();
176228
this.resetMoneyThisRound();
@@ -180,7 +232,9 @@ export class Player {
180232
}
181233

182234
this.scoreboardAvailable = false;
235+
this.auxiliaryAvailable.scoreboard = false;
183236
this.isAlive = true;
237+
this.health = 100;
184238
}
185239

186240
public getName(): string {
@@ -215,4 +269,9 @@ export class Player {
215269
this.moneySpent = 0;
216270
this.spentMoneyThisRound = false;
217271
}
272+
273+
public setAuxDisconnected() {
274+
this.auxiliaryAvailable = new AvailableAuxiliary();
275+
Log.info(`Auxiliary data for ${this.name} has been disconnected`);
276+
}
218277
}

0 commit comments

Comments
 (0)