idz: Wire up team create and load

This commit is contained in:
Tau
2019-11-06 17:28:30 -05:00
parent ec214d5beb
commit a3df4df277
21 changed files with 286 additions and 76 deletions
+15
View File
@@ -0,0 +1,15 @@
import { RequestCode } from "./_defs";
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
import { AimeId } from "../../model";
createAutoTeam.msgCode = 0x007b as RequestCode;
createAutoTeam.msgLen = 0x0010;
export function createAutoTeam(buf: Buffer): CreateAutoTeamRequest {
return {
type: "create_auto_team_req",
aimeId: buf.readUInt32LE(0x0004) as AimeId,
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt8(0x000c),
};
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { Transform } from "stream";
import { checkTeamName } from "./checkTeamName"; import { checkTeamName } from "./checkTeamName";
import { createProfile } from "./createProfile"; import { createProfile } from "./createProfile";
import { createTeam } from "./createTeam"; import { createTeam } from "./createTeam";
import { joinAutoTeam } from "./joinAutoTeam"; import { createAutoTeam } from "./createAutoTeam";
import { discoverProfile } from "./discoverProfile"; import { discoverProfile } from "./discoverProfile";
import { load2on2_v1, load2on2_v2 } from "./load2on2"; import { load2on2_v1, load2on2_v2 } from "./load2on2";
import { loadConfig } from "./loadConfig"; import { loadConfig } from "./loadConfig";
@@ -54,9 +54,9 @@ export type ReaderFn = ((buf: Buffer) => Request) & {
const funcList: ReaderFn[] = [ const funcList: ReaderFn[] = [
checkTeamName, checkTeamName,
createAutoTeam,
createProfile, createProfile,
createTeam, createTeam,
joinAutoTeam,
discoverProfile, discoverProfile,
load2on2_v1, load2on2_v1,
load2on2_v2, load2on2_v2,
-14
View File
@@ -1,14 +0,0 @@
import { RequestCode } from "./_defs";
import { JoinAutoTeamRequest } from "../request/joinAutoTeam";
joinAutoTeam.msgCode = 0x007b as RequestCode;
joinAutoTeam.msgLen = 0x0010;
export function joinAutoTeam(buf: Buffer): JoinAutoTeamRequest {
return {
type: "join_auto_team_req",
field_0004: buf.readUInt32LE(0x0004),
field_0008: buf.readUInt32LE(0x0008),
field_000C: buf.readUInt8(0x000c),
};
}
+3 -3
View File
@@ -1,13 +1,13 @@
import iconv = require("iconv-lite"); import iconv = require("iconv-lite");
import { JoinAutoTeamResponse } from "../response/joinAutoTeam"; import { CreateAutoTeamResponse } from "../response/createAutoTeam";
import { LoadTeamResponse } from "../response/loadTeam"; import { LoadTeamResponse } from "../response/loadTeam";
import { encodeChara } from "./_chara"; import { encodeChara } from "./_chara";
export function _team(res: JoinAutoTeamResponse | LoadTeamResponse) { export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
const buf = Buffer.alloc(0x0ca0); const buf = Buffer.alloc(0x0ca0);
if (res.type === "join_auto_team_res") { if (res.type === "create_auto_team_res") {
buf.writeInt16LE(0x007c, 0x0000); buf.writeInt16LE(0x007c, 0x0000);
} else { } else {
buf.writeInt16LE(0x0078, 0x0000); buf.writeInt16LE(0x0078, 0x0000);
+3 -3
View File
@@ -36,15 +36,15 @@ function encode(res: Response): Buffer {
case "check_team_name_res": case "check_team_name_res":
return checkTeamName(res); return checkTeamName(res);
case "create_auto_team_res":
return _team(res);
case "create_team_res": case "create_team_res":
return createTeam(res); return createTeam(res);
case "discover_profile_res": case "discover_profile_res":
return discoverProfile(res); return discoverProfile(res);
case "join_auto_team_res":
return _team(res);
case "generic_res": case "generic_res":
return generic(res); return generic(res);
+1 -1
View File
@@ -75,7 +75,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8); buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8); buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8);
buf.writeUInt16LE(res.unlocks.music, 0x01ec); buf.writeUInt16LE(res.unlocks.music, 0x01ec);
buf.writeUInt16LE(0, 0x037c); // Team leader buf.writeUInt16LE(res.teamLeader ? 1 : 0, 0x037c);
encodeMission(res.missions.team).copy(buf, 0x038a); encodeMission(res.missions.team).copy(buf, 0x038a);
buf.writeUInt16LE(0xffff, 0x0388); // [1] buf.writeUInt16LE(0xffff, 0x0388); // [1]
buf.writeUInt32LE(res.aimeId, 0x03b8); buf.writeUInt32LE(res.aimeId, 0x03b8);
+1 -1
View File
@@ -75,7 +75,7 @@ export function loadProfile3(res: LoadProfileResponse3) {
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8); buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218); buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
buf.writeUInt16LE(res.unlocks.music, 0x021c); buf.writeUInt16LE(res.unlocks.music, 0x021c);
buf.writeUInt16LE(0, 0x0456); // Team leader buf.writeUInt16LE(res.teamLeader ? 1 : 0, 0x0456); // Team leader
encodeMission(res.missions.team).copy(buf, 0x0460); encodeMission(res.missions.team).copy(buf, 0x0460);
buf.writeUInt16LE(0xffff, 0x0462); // [1] buf.writeUInt16LE(0xffff, 0x0462); // [1]
buf.writeUInt32LE(res.aimeId, 0x0494); buf.writeUInt32LE(res.aimeId, 0x0494);
+25 -26
View File
@@ -1,34 +1,33 @@
import { ExtId } from "../model/base";
import { Team } from "../model/team"; import { Team } from "../model/team";
import { JoinAutoTeamRequest } from "../request/joinAutoTeam";
import { LoadTeamRequest } from "../request/loadTeam";
import { JoinAutoTeamResponse } from "../response/joinAutoTeam";
import { LoadTeamResponse } from "../response/loadTeam";
import { Repositories } from "../repo"; import { Repositories } from "../repo";
import { Id } from "../../db";
// Even if a profile does not belong to a team, a team must still be loaded // Bleh. This factorization is kind of messy.
// (and then ignored by the client).
const dummy: Team = { export async function _fixupPrevTeam(
extId: 0 as ExtId<Team>,
name: "",
nameBg: 0,
nameFx: 0,
registerTime: new Date(0),
};
export function _team(
w: Repositories, w: Repositories,
req: JoinAutoTeamRequest | LoadTeamRequest prevTeamId: Id<Team> | undefined
): JoinAutoTeamResponse | LoadTeamResponse { ): Promise<void> {
const bits = { if (prevTeamId === undefined) {
team: dummy, return;
members: [], }
};
if (req.type === "join_auto_team_req") { const remaining = await w.teamMembers().loadRoster(prevTeamId);
return { type: "join_auto_team_res", ...bits };
} else { if (remaining.length === 0) {
return { type: "load_team_res", ...bits }; // Last member left, GC previous team
await w.teams().delete(prevTeamId);
} else if (remaining.find(member => member.leader) === undefined) {
// Leader left, appoint new leader by seniority
remaining.sort((x, y) => x.joinTime.getTime() - y.joinTime.getTime());
// (need to look up new leader's db id from aime id. ick)
const newLeader = remaining[remaining.length - 1];
const newLeaderId = await w.profile().find(newLeader.profile.aimeId);
await w.teamMembers().makeLeader(prevTeamId, newLeaderId);
} }
} }
+121
View File
@@ -0,0 +1,121 @@
import { TeamAuto } from "../model/team";
import { Repositories } from "../repo";
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
import { CreateAutoTeamResponse } from "../response/createAutoTeam";
interface AutoTeamTemplate {
prefix: string;
nameBg: number;
}
// Hard-code these for the time being, since AFAIK there are only three.
// These are all references to teams in the Initial D manga/anime.
// If any more auto-teams are added/discovered they *MUST* be added to the
// *END* of this list. Otherwise duplicate auto-teams will be created.
const autoTeams: AutoTeamTemplate[] = [
{
// "Speed Stars"
prefix: "スピードスターズ",
nameBg: 0,
},
{
// "Red Suns"
prefix: "レッドサンズ",
nameBg: 1,
},
{
// "Night Kids" (even though it's written like "Night Keys"...)
prefix: "ナイトキッズ",
nameBg: 2,
},
];
function incrementAuto(prev: TeamAuto): TeamAuto {
if (prev.nameIdx < autoTeams.length - 1) {
return { nameIdx: prev.nameIdx + 1, serialNo: prev.serialNo };
} else {
return { nameIdx: 0, serialNo: prev.serialNo + 1 };
}
}
export async function createAutoTeam(
w: Repositories,
req: CreateAutoTeamRequest
): Promise<CreateAutoTeamResponse> {
const now = new Date();
const { aimeId } = req;
const peek = await w.teamAuto().peek();
let nextAuto: TeamAuto;
//
// Determine if we need to create a new team or not
//
if (peek !== undefined) {
// Look at the highest-numbered auto team. Is it full?
const [lastAuto, lastTeamId] = peek;
const occupancy = await w.teamReservations().occupancyHack(lastTeamId);
console.log(occupancy);
if (occupancy < 6) {
// Team isn't full, so return this one
await w.teamReservations().reserveHack(lastTeamId, aimeId, now);
return {
type: "create_auto_team_res",
team: await w.teams().load(lastTeamId),
members: await w.teamMembers().loadRoster(lastTeamId),
};
}
// Team full, need to create a new one
nextAuto = incrementAuto(lastAuto);
} else {
// No teams exist at all, seed the system with SpeedStars001.
nextAuto = { serialNo: 1, nameIdx: 0 };
}
//
// Build the new team
//
// Make a three-digit serial number using full-width digits
let { serialNo, nameIdx } = nextAuto;
let name = "";
for (let i = 0; i < 3; i++) {
name = String.fromCodePoint(0xff10 + (serialNo % 10)) + name;
serialNo = (serialNo / 10) | 0;
}
// Prepend the name prefix
name = autoTeams[nameIdx].prefix + name;
// Register the new team, make the requestor its leader
const spec = {
name,
nameBg: autoTeams[nameIdx].nameBg,
nameFx: 0,
registerTime: now,
};
const [newTeamId, newTeamExtId] = await w.teams().create(spec);
await w.teamAuto().push(newTeamId, nextAuto);
await w.teamReservations().reserveHack(newTeamId, aimeId, now, "leader");
return {
type: "create_auto_team_res",
team: { ...spec, extId: newTeamExtId },
members: [],
};
}
+2
View File
@@ -47,6 +47,8 @@ export async function createProfile(
await w.unlocks().save(profileId, unlocks); await w.unlocks().save(profileId, unlocks);
await w.tickets().save(profileId, {}); await w.tickets().save(profileId, {});
await w.teamReservations().commitHack(aimeId);
return { return {
type: "generic_res", type: "generic_res",
status: aimeId, // "Generic response" my fucking *ass* status: aimeId, // "Generic response" my fucking *ass*
+47 -5
View File
@@ -1,16 +1,58 @@
import { ExtId } from "../model/base"; import { _fixupPrevTeam } from "./_team";
import { Team } from "../model/team";
import { CreateTeamRequest } from "../request/createTeam"; import { CreateTeamRequest } from "../request/createTeam";
import { CreateTeamResponse } from "../response/createTeam"; import { CreateTeamResponse } from "../response/createTeam";
import { Repositories } from "../repo"; import { Repositories } from "../repo";
export function createTeam( export async function createTeam(
w: Repositories, w: Repositories,
req: CreateTeamRequest req: CreateTeamRequest
): CreateTeamResponse { ): Promise<CreateTeamResponse> {
const profileId = await w.profile().find(req.aimeId);
const prevTeamId = await w.teamMembers().findTeam(profileId);
const now = new Date();
// Create the new team...
const teamSpec = {
name: req.teamName,
nameBg: req.nameBg,
nameFx: 0,
registerTime: now,
};
const [teamId, teamExtId] = await w.teams().create(teamSpec);
await w.teamMembers().join(teamId, profileId, now);
await w.teamMembers().makeLeader(teamId, profileId);
await _fixupPrevTeam(w, prevTeamId);
// Fix up previous team. The previous team's extid is explicitly sent in the
// request, but why rely on it if you don't have to?
if (prevTeamId !== undefined) {
const remaining = await w.teamMembers().loadRoster(prevTeamId);
if (remaining.length === 0) {
// Last member left, GC previous team
await w.teams().delete(prevTeamId);
} else if (remaining.find(member => member.leader) === undefined) {
// Leader left, appoint new leader by seniority
remaining.sort((x, y) => x.joinTime.getTime() - y.joinTime.getTime());
// (need to look up new leader's db id from aime id. ick)
const newLeader = remaining[remaining.length - 1];
const newLeaderId = await w.profile().find(newLeader.profile.aimeId);
await w.teamMembers().makeLeader(prevTeamId, newLeaderId);
}
}
return { return {
type: "create_team_res", type: "create_team_res",
status: 0, status: 0,
teamExtId: 3 as ExtId<Team>, teamExtId,
}; };
} }
+6 -5
View File
@@ -1,5 +1,5 @@
import { _team } from "./_team";
import { checkTeamName } from "./checkTeamName"; import { checkTeamName } from "./checkTeamName";
import { createAutoTeam } from "./createAutoTeam";
import { createProfile } from "./createProfile"; import { createProfile } from "./createProfile";
import { createTeam } from "./createTeam"; import { createTeam } from "./createTeam";
import { discoverProfile } from "./discoverProfile"; import { discoverProfile } from "./discoverProfile";
@@ -15,6 +15,7 @@ import { loadProfile } from "./loadProfile";
import { loadReward as loadRewardTable } from "./loadRewardTable"; import { loadReward as loadRewardTable } from "./loadRewardTable";
import { loadServerList } from "./loadServerList"; import { loadServerList } from "./loadServerList";
import { loadStocker } from "./loadStocker"; import { loadStocker } from "./loadStocker";
import { loadTeam } from "./loadTeam";
import { loadTeamRanking } from "./loadTeamRanking"; import { loadTeamRanking } from "./loadTeamRanking";
import { loadTopTen } from "./loadTopTen"; import { loadTopTen } from "./loadTopTen";
import { lockGarage } from "./lockGarage"; import { lockGarage } from "./lockGarage";
@@ -48,15 +49,15 @@ export async function dispatch(
case "check_team_name_req": case "check_team_name_req":
return checkTeamName(w, req); return checkTeamName(w, req);
case "create_auto_team_req":
return createAutoTeam(w, req);
case "create_profile_req": case "create_profile_req":
return createProfile(w, req); return createProfile(w, req);
case "create_team_req": case "create_team_req":
return createTeam(w, req); return createTeam(w, req);
case "join_auto_team_req":
return _team(w, req);
case "load_2on2_req": case "load_2on2_req":
return load2on2(w, req); return load2on2(w, req);
@@ -97,7 +98,7 @@ export async function dispatch(
return loadStocker(w, req); return loadStocker(w, req);
case "load_team_req": case "load_team_req":
return _team(w, req); return loadTeam(w, req);
case "load_top_ten_req": case "load_top_ten_req":
return loadTopTen(w, req); return loadTopTen(w, req);
+5 -1
View File
@@ -9,6 +9,8 @@ export async function loadProfile(
const { aimeId } = req; const { aimeId } = req;
const profileId = await w.profile().find(aimeId); const profileId = await w.profile().find(aimeId);
const teamId = await w.teamMembers().findTeam(profileId);
const leaderId = teamId && (await w.teamMembers().findLeader(teamId));
// Promise.all would be messy here, who cares anyway this isn't supposed to // Promise.all would be messy here, who cares anyway this isn't supposed to
// be a high-performance server. // be a high-performance server.
@@ -25,6 +27,7 @@ export async function loadProfile(
const timeAttack = await w.timeAttack().loadAll(profileId); const timeAttack = await w.timeAttack().loadAll(profileId);
const unlocks = await w.unlocks().load(profileId); const unlocks = await w.unlocks().load(profileId);
const tickets = await w.tickets().load(profileId); const tickets = await w.tickets().load(profileId);
const team = teamId && (await w.teams().load(teamId));
return { return {
type: "load_profile_res", type: "load_profile_res",
@@ -36,7 +39,8 @@ export async function loadProfile(
fame: profile.fame, fame: profile.fame,
dpoint: profile.dpoint, dpoint: profile.dpoint,
mileage: profile.mileage, mileage: profile.mileage,
// teamId: TODO teamId: team && team.extId,
teamLeader: profileId === leaderId,
settings, settings,
chara, chara,
titles, titles,
+37
View File
@@ -0,0 +1,37 @@
import { ExtId } from "../model/base";
import { Team } from "../model/team";
import { LoadTeamRequest } from "../request/loadTeam";
import { LoadTeamResponse } from "../response/loadTeam";
import { Repositories } from "../repo";
// Even if a profile does not belong to a team, a team must still be loaded
// (and then ignored by the client).
const dummyResp: LoadTeamResponse = {
type: "load_team_res",
team: {
extId: 0 as ExtId<Team>,
name: "",
nameBg: 0,
nameFx: 0,
registerTime: new Date(0),
},
members: [],
};
export async function loadTeam(
w: Repositories,
req: LoadTeamRequest
): Promise<LoadTeamResponse> {
if (req.teamExtId === undefined) {
return dummyResp;
}
const teamId = await w.teams().find(req.teamExtId);
return {
type: "load_team_res",
team: await w.teams().load(teamId),
members: await w.teamMembers().loadRoster(teamId),
};
}
+8
View File
@@ -0,0 +1,8 @@
import { AimeId } from "../../model";
export interface CreateAutoTeamRequest {
type: "create_auto_team_req";
aimeId: AimeId;
field_0008: number;
field_000C: number;
}
+2 -2
View File
@@ -2,7 +2,7 @@ import { CheckTeamNameRequest } from "./checkTeamName";
import { CreateProfileRequest } from "./createProfile"; import { CreateProfileRequest } from "./createProfile";
import { CreateTeamRequest } from "./createTeam"; import { CreateTeamRequest } from "./createTeam";
import { DiscoverProfileRequest } from "./discoverProfile"; import { DiscoverProfileRequest } from "./discoverProfile";
import { JoinAutoTeamRequest } from "./joinAutoTeam"; import { CreateAutoTeamRequest } from "./createAutoTeam";
import { Load2on2Request } from "./load2on2"; import { Load2on2Request } from "./load2on2";
import { LoadConfigRequest } from "./loadConfig"; import { LoadConfigRequest } from "./loadConfig";
import { LoadConfigRequest2 } from "./loadConfig2"; import { LoadConfigRequest2 } from "./loadConfig2";
@@ -40,10 +40,10 @@ import { UpdateUserLogRequest } from "./updateUserLog";
export type Request = export type Request =
| CheckTeamNameRequest | CheckTeamNameRequest
| CreateAutoTeamRequest
| CreateProfileRequest | CreateProfileRequest
| CreateTeamRequest | CreateTeamRequest
| DiscoverProfileRequest | DiscoverProfileRequest
| JoinAutoTeamRequest
| Load2on2Request | Load2on2Request
| LoadConfigRequest | LoadConfigRequest
| LoadConfigRequest2 | LoadConfigRequest2
-6
View File
@@ -1,6 +0,0 @@
export interface JoinAutoTeamRequest {
type: "join_auto_team_req";
field_0004: number;
field_0008: number;
field_000C: number;
}
+5
View File
@@ -0,0 +1,5 @@
import { BaseTeamResponse } from "./_team";
export interface CreateAutoTeamResponse extends BaseTeamResponse {
type: "create_auto_team_res";
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { CheckTeamNameResponse } from "./checkTeamName"; import { CheckTeamNameResponse } from "./checkTeamName";
import { CreateAutoTeamResponse } from "./createAutoTeam";
import { CreateTeamResponse } from "./createTeam"; import { CreateTeamResponse } from "./createTeam";
import { DiscoverProfileResponse } from "./discoverProfile"; import { DiscoverProfileResponse } from "./discoverProfile";
import { GenericResponse } from "./generic"; import { GenericResponse } from "./generic";
import { JoinAutoTeamResponse } from "./joinAutoTeam";
import { Load2on2Response } from "./load2on2"; import { Load2on2Response } from "./load2on2";
import { LoadConfigResponse } from "./loadConfig"; import { LoadConfigResponse } from "./loadConfig";
import { LoadConfigResponse2 } from "./loadConfig2"; import { LoadConfigResponse2 } from "./loadConfig2";
@@ -31,10 +31,10 @@ import { UnlockProfileResponse } from "./unlockProfile";
export type Response = export type Response =
| CheckTeamNameResponse | CheckTeamNameResponse
| CreateAutoTeamResponse
| CreateTeamResponse | CreateTeamResponse
| DiscoverProfileResponse | DiscoverProfileResponse
| GenericResponse | GenericResponse
| JoinAutoTeamResponse
| Load2on2Response | Load2on2Response
| LoadConfigResponse | LoadConfigResponse
| LoadConfigResponse2 | LoadConfigResponse2
-5
View File
@@ -1,5 +0,0 @@
import { BaseTeamResponse } from "./_team";
export interface JoinAutoTeamResponse extends BaseTeamResponse {
type: "join_auto_team_res";
}
+1
View File
@@ -19,6 +19,7 @@ interface LoadProfileResponseBase {
dpoint: number; dpoint: number;
mileage: number; mileage: number;
teamId?: number; teamId?: number;
teamLeader: boolean;
settings: Settings; settings: Settings;
chara: Chara; chara: Chara;
titles: Set<TitleCode>; titles: Set<TitleCode>;