Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff4545d468 | ||
|
|
7fd6f74986 | ||
|
|
2478bc5ceb | ||
|
|
3bee10d948 | ||
|
|
2bd9ce919a | ||
|
|
a3df4df277 | ||
|
|
ec214d5beb | ||
|
|
a5dbdef33f | ||
|
|
be48619a4a | ||
|
|
c6a67bd647 | ||
|
|
d1c3051a5e | ||
|
|
f874f56816 | ||
|
|
4f044de9fb | ||
|
|
33e2f55dce | ||
|
|
85510ee0e1 | ||
|
|
9c06d6635f | ||
|
|
c2b0bcabc4 | ||
|
|
7c96253160 | ||
|
|
faaeb53cd9 | ||
|
|
1aaae51eab | ||
|
|
98f6fb9d5b | ||
|
|
4d7a0d64a1 | ||
|
|
9dd45558ec | ||
|
|
af8cdf3072 | ||
|
|
a3705730ce | ||
|
|
83ddfcc022 | ||
|
|
5c6b8f27a4 | ||
|
|
6a89e6a15a | ||
|
|
1e809f59a3 | ||
|
|
cb5f281236 | ||
|
|
2a365651a8 | ||
|
|
2f48efdbb9 | ||
|
|
0f67c00664 | ||
|
|
2fd8081a8f | ||
|
|
ddad7efd67 | ||
|
|
6be956ad3c | ||
|
|
5d8e135c07 | ||
|
|
2392aaa205 | ||
|
|
dfdd775370 | ||
|
|
5b6534ae9a | ||
|
|
fecc0e7981 | ||
|
|
5bac5b6abc | ||
|
|
6b58c4d722 |
+52
-3
@@ -12,7 +12,6 @@ create table "idz"."profile" (
|
||||
references "aime"."player"("id")
|
||||
on delete cascade,
|
||||
-- TODO shop_id
|
||||
"ext_id" integer not null,
|
||||
"name" text not null,
|
||||
"lv" smallint not null,
|
||||
"exp" integer not null,
|
||||
@@ -21,8 +20,7 @@ create table "idz"."profile" (
|
||||
"mileage" integer not null,
|
||||
"register_time" timestamp not null,
|
||||
"access_time" timestamp not null,
|
||||
constraint "profile_player_uq" unique ("player_id"),
|
||||
constraint "profile_ext_id_uq" unique ("ext_id")
|
||||
constraint "profile_player_uq" unique ("player_id")
|
||||
);
|
||||
|
||||
create table "idz"."chara" (
|
||||
@@ -192,3 +190,54 @@ create table "idz"."unlocks" (
|
||||
"music" integer not null,
|
||||
"last_mileage_reward" integer not null
|
||||
);
|
||||
|
||||
create table "idz"."team" (
|
||||
"id" bigint primary key not null,
|
||||
"ext_id" integer not null,
|
||||
"name" text not null,
|
||||
"name_bg" smallint not null,
|
||||
"name_fx" smallint not null,
|
||||
"register_time" timestamp not null,
|
||||
constraint "team_uq" unique ("ext_id")
|
||||
);
|
||||
|
||||
create table "idz"."team" (
|
||||
"id" bigint primary key not null,
|
||||
"ext_id" integer not null,
|
||||
"name" text not null,
|
||||
"name_bg" smallint not null,
|
||||
"name_fx" smallint not null,
|
||||
"register_time" timestamp not null,
|
||||
constraint "team_uq" unique ("ext_id")
|
||||
);
|
||||
|
||||
create table "idz"."team_auto" (
|
||||
"id" bigint primary key not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"serial_no" smallint not null,
|
||||
"name_idx" smallint not null,
|
||||
constraint "team_auto_uq" unique ("serial_no", "name_idx")
|
||||
);
|
||||
|
||||
create table "idz"."team_member" (
|
||||
"id" bigint primary key not null
|
||||
references "idz"."profile"("id")
|
||||
on delete cascade,
|
||||
"team_id" bigint not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"join_time" timestamp not null,
|
||||
"leader" boolean not null
|
||||
);
|
||||
|
||||
create table "idz"."team_reservation" (
|
||||
"id" bigint primary key not null
|
||||
references "aime"."player"("id")
|
||||
on delete cascade,
|
||||
"team_id" bigint not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"join_time" timestamp not null,
|
||||
"leader" boolean not null
|
||||
);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
create table "meta" ("schemaver" integer not null);
|
||||
insert into "meta" values (0);
|
||||
insert into "meta" values (3);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
-- Helper functions which might be useful in one-off queries.
|
||||
-- These are not utilised by any services; those have their own ID generators.
|
||||
|
||||
create or replace function idnum(
|
||||
str text)
|
||||
returns bigint as $$
|
||||
declare
|
||||
num bigint;
|
||||
x integer;
|
||||
begin
|
||||
num := 0;
|
||||
|
||||
for i in 1..11 loop
|
||||
x := ascii(substr(str, i, 1));
|
||||
num := num << 6;
|
||||
|
||||
if x >= 65 and x <= 90 then
|
||||
-- A to Z
|
||||
num = num | (x - 65);
|
||||
elsif x >= 97 and x <= 122 then
|
||||
-- a to z
|
||||
num = num | (x - 71);
|
||||
elsif x >= 48 and x <= 58 then
|
||||
-- 0 to 9
|
||||
num = num | (x + 4);
|
||||
elsif x = 45 then
|
||||
-- Dash
|
||||
num = num | 62;
|
||||
elsif x = 95 then
|
||||
-- Underscore
|
||||
num = num | 63;
|
||||
else
|
||||
raise 'Bad input';
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
return num;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create or replace function idstr(
|
||||
num bigint)
|
||||
returns text as $$
|
||||
declare
|
||||
a text;
|
||||
pos integer;
|
||||
str text;
|
||||
begin
|
||||
a := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
str := '';
|
||||
|
||||
for i in 1..11 loop
|
||||
pos := (num & 63);
|
||||
str := substr(a, pos + 1, 1) || str;
|
||||
num := num >> 6;
|
||||
end loop;
|
||||
|
||||
return str;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create or replace function newid()
|
||||
returns bigint as $$
|
||||
declare
|
||||
bytes bytea;
|
||||
result bigint;
|
||||
begin
|
||||
bytes := gen_random_bytes(8);
|
||||
result := 0;
|
||||
|
||||
for i in 0..7 loop
|
||||
result := (result << 8) | get_byte(bytes, i);
|
||||
end loop;
|
||||
|
||||
-- Truncate high bit to ensure result is positive
|
||||
-- This value is just the decimal representation of 0x7FFFFFFF`FFFFFFFF.
|
||||
|
||||
return result & 9223372036854775807;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
-- Helper functions which might be useful in one-off queries.
|
||||
-- These are not utilised by any services; those have their own ID generators.
|
||||
|
||||
create or replace function idnum(
|
||||
str text)
|
||||
returns bigint as $$
|
||||
declare
|
||||
num bigint;
|
||||
x integer;
|
||||
begin
|
||||
num := 0;
|
||||
|
||||
for i in 1..11 loop
|
||||
x := ascii(substr(str, i, 1));
|
||||
num := num << 6;
|
||||
|
||||
if x >= 65 and x <= 90 then
|
||||
-- A to Z
|
||||
num = num | (x - 65);
|
||||
elsif x >= 97 and x <= 122 then
|
||||
-- a to z
|
||||
num = num | (x - 71);
|
||||
elsif x >= 48 and x <= 58 then
|
||||
-- 0 to 9
|
||||
num = num | (x + 4);
|
||||
elsif x = 45 then
|
||||
-- Dash
|
||||
num = num | 62;
|
||||
elsif x = 95 then
|
||||
-- Underscore
|
||||
num = num | 63;
|
||||
else
|
||||
raise 'Bad input';
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
return num;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create or replace function idstr(
|
||||
num bigint)
|
||||
returns text as $$
|
||||
declare
|
||||
a text;
|
||||
pos integer;
|
||||
str text;
|
||||
begin
|
||||
a := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
str := '';
|
||||
|
||||
for i in 1..11 loop
|
||||
pos := (num & 63);
|
||||
str := substr(a, pos + 1, 1) || str;
|
||||
num := num >> 6;
|
||||
end loop;
|
||||
|
||||
return str;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create or replace function newid()
|
||||
returns bigint as $$
|
||||
declare
|
||||
bytes bytea;
|
||||
result bigint;
|
||||
begin
|
||||
bytes := gen_random_bytes(8);
|
||||
result := 0;
|
||||
|
||||
for i in 0..7 loop
|
||||
result := (result << 8) | get_byte(bytes, i);
|
||||
end loop;
|
||||
|
||||
-- Truncate high bit to ensure result is positive
|
||||
-- This value is just the decimal representation of 0x7FFFFFFF`FFFFFFFF.
|
||||
|
||||
return result & 9223372036854775807;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
alter table "idz"."profile" drop column "ext_id";
|
||||
|
||||
update "meta" set "schemaver" = 1;
|
||||
@@ -0,0 +1,42 @@
|
||||
create table "idz"."team" (
|
||||
"id" bigint primary key not null,
|
||||
"ext_id" integer not null,
|
||||
"name" text not null,
|
||||
"name_bg" smallint not null,
|
||||
"name_fx" smallint not null,
|
||||
"register_time" timestamp not null,
|
||||
constraint "team_uq" unique ("ext_id")
|
||||
);
|
||||
|
||||
create table "idz"."team_auto" (
|
||||
"id" bigint primary key not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"serial_no" smallint not null,
|
||||
"name_idx" smallint not null,
|
||||
constraint "team_auto_uq" unique ("serial_no", "name_idx")
|
||||
);
|
||||
|
||||
create table "idz"."team_member" (
|
||||
"id" bigint primary key not null
|
||||
references "idz"."profile"("id")
|
||||
on delete cascade,
|
||||
"team_id" bigint not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"join_time" timestamp not null,
|
||||
"leader" boolean not null
|
||||
);
|
||||
|
||||
create table "idz"."team_reservation" (
|
||||
"id" bigint primary key not null
|
||||
references "aime"."player"("id")
|
||||
on delete cascade,
|
||||
"team_id" bigint not null
|
||||
references "idz"."team"("id")
|
||||
on delete cascade,
|
||||
"join_time" timestamp not null,
|
||||
"leader" boolean not null
|
||||
);
|
||||
|
||||
update "meta" set "schemaver" = 2;
|
||||
@@ -19,6 +19,15 @@ function readRegisterRequest(msg: Buffer): Request.RegisterRequest {
|
||||
};
|
||||
}
|
||||
|
||||
function readFeliCaLookupRequest(msg: Buffer): Request.FeliCaLookupRequest {
|
||||
return {
|
||||
...begin(msg),
|
||||
type: "felica_lookup",
|
||||
idm: msg.slice(0x0020, 0x0028).toString("hex"),
|
||||
pmm: msg.slice(0x0028, 0x0030).toString("hex"),
|
||||
};
|
||||
}
|
||||
|
||||
function readLogRequest(msg: Buffer): Request.LogRequest {
|
||||
// idk what any of this stuff means yet
|
||||
// field20 and field28 appear to be an aime id but that is all.
|
||||
@@ -79,6 +88,7 @@ function readGoodbyeRequest(msg: Buffer): Request.GoodbyeRequest {
|
||||
|
||||
const readers = new Map<number, (msg: Buffer) => Request.AimeRequest>();
|
||||
|
||||
readers.set(0x0001, readFeliCaLookupRequest);
|
||||
readers.set(0x0004, readLookupRequest);
|
||||
readers.set(0x0005, readRegisterRequest);
|
||||
readers.set(0x0009, readLogRequest);
|
||||
|
||||
@@ -32,6 +32,14 @@ export class Encoder extends Transform {
|
||||
let buf: Buffer;
|
||||
|
||||
switch (msg.type) {
|
||||
case "felica_lookup":
|
||||
buf = begin(0x0030);
|
||||
buf.writeUInt16LE(0x0003, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(msg.status, 0x0008);
|
||||
buf.write(msg.accessCode, 0x0024, "hex");
|
||||
|
||||
break;
|
||||
|
||||
case "hello":
|
||||
buf = begin(0x0020);
|
||||
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
||||
@@ -92,6 +100,8 @@ export class Encoder extends Transform {
|
||||
break;
|
||||
|
||||
default:
|
||||
const exhaust: never = msg;
|
||||
|
||||
return callback(new Error("Unimplemented response type"));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,26 @@ function campaign(
|
||||
return { type: req.type, status: 1 };
|
||||
}
|
||||
|
||||
function feliCaLookup(
|
||||
rep: Repositories,
|
||||
req: Req.FeliCaLookupRequest,
|
||||
now: Date
|
||||
): Res.FeliCaLookupResponse {
|
||||
console.log("Aimedb: FeliCa access code lookup");
|
||||
|
||||
// Well, this access code transformation is the million dollar question eh
|
||||
// Return a decimal representation for now.
|
||||
|
||||
const num = BigInt("0x" + req.idm);
|
||||
let accessCode = num.toString();
|
||||
|
||||
while (accessCode.length < 20) {
|
||||
accessCode = "0" + accessCode;
|
||||
}
|
||||
|
||||
return { type: req.type, status: 1, accessCode };
|
||||
}
|
||||
|
||||
async function lookup(
|
||||
rep: Repositories,
|
||||
req: Req.LookupRequest,
|
||||
@@ -88,6 +108,9 @@ export async function dispatch(
|
||||
case "campaign":
|
||||
return campaign(rep, req, now);
|
||||
|
||||
case "felica_lookup":
|
||||
return feliCaLookup(rep, req, now);
|
||||
|
||||
case "lookup":
|
||||
return lookup(rep, req, now);
|
||||
|
||||
@@ -106,6 +129,8 @@ export async function dispatch(
|
||||
return undefined;
|
||||
|
||||
default:
|
||||
const exhaust: never = req;
|
||||
|
||||
throw new Error("Aimedb: Handler not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ export interface AimeRequestBase {
|
||||
keychipId: string;
|
||||
}
|
||||
|
||||
export interface FeliCaLookupRequest extends AimeRequestBase {
|
||||
type: "felica_lookup";
|
||||
idm: string;
|
||||
pmm: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest extends AimeRequestBase {
|
||||
type: "register";
|
||||
luid: string;
|
||||
@@ -43,6 +49,7 @@ export interface GoodbyeRequest {
|
||||
}
|
||||
|
||||
export type AimeRequest =
|
||||
| FeliCaLookupRequest
|
||||
| CampaignRequest
|
||||
| GoodbyeRequest
|
||||
| HelloRequest
|
||||
|
||||
@@ -6,6 +6,11 @@ export interface AimeResponseBase {
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface FeliCaLookupResponse extends AimeResponseBase {
|
||||
type: "felica_lookup";
|
||||
accessCode: string;
|
||||
}
|
||||
|
||||
export interface CampaignResponse extends AimeResponseBase {
|
||||
type: "campaign";
|
||||
}
|
||||
@@ -38,6 +43,7 @@ export interface RegisterResponse extends AimeResponseBase {
|
||||
}
|
||||
|
||||
export type AimeResponse =
|
||||
| FeliCaLookupResponse
|
||||
| CampaignResponse
|
||||
| HelloResponse
|
||||
| LogResponse
|
||||
|
||||
@@ -3,9 +3,14 @@ import { Pool, PoolClient } from "pg";
|
||||
|
||||
export type Id<T> = bigint & { __id: T };
|
||||
|
||||
const currentSchemaVer = 2;
|
||||
|
||||
const pool = new Pool();
|
||||
const fence = testConnection();
|
||||
|
||||
export async function connect(): Promise<PoolClient> {
|
||||
await fence;
|
||||
|
||||
export function connect(): Promise<PoolClient> {
|
||||
return pool.connect();
|
||||
}
|
||||
|
||||
@@ -30,3 +35,22 @@ export function generateExtId(): number {
|
||||
|
||||
return buf.readUInt32BE(0);
|
||||
}
|
||||
|
||||
async function testConnection(): Promise<void> {
|
||||
const conn = await pool.connect();
|
||||
|
||||
try {
|
||||
const { rows } = await conn.query("select schemaver from meta");
|
||||
const { schemaver } = rows[0];
|
||||
|
||||
if (schemaver !== currentSchemaVer) {
|
||||
throw new Error(
|
||||
`Expected schema version ${currentSchemaVer}, db is on ${schemaver}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("SQL DB: Connection established");
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import * as sql from "sql-bricks";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Id } from "../../db";
|
||||
|
||||
export async function _findProfile(
|
||||
conn: ClientBase,
|
||||
extId: ExtId<Profile>
|
||||
): Promise<Id<Profile>> {
|
||||
const lookupSql = sql
|
||||
.select("r.id")
|
||||
.from("idz.profile r")
|
||||
.where("r.ext_id", extId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await conn.query(lookupSql);
|
||||
|
||||
if (rows.length > 0) {
|
||||
return rows[0].id as Id<Profile>;
|
||||
} else {
|
||||
throw new Error("Profile not found");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { BackgroundCode, ExtId } from "../model/base";
|
||||
import { BackgroundCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { generateId } from "../../db";
|
||||
import { generateId, Id } from "../../db";
|
||||
|
||||
export class SqlBackgroundsRepository
|
||||
implements FlagRepository<BackgroundCode> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async loadAll(extId: ExtId<Profile>): Promise<Set<BackgroundCode>> {
|
||||
async loadAll(id: Id<Profile>): Promise<Set<BackgroundCode>> {
|
||||
const loadSql = sql
|
||||
.select("bg.background_no")
|
||||
.from("idz.background_unlock bg")
|
||||
.join("idz.profile p", { "bg.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("p.id", id)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -30,11 +29,10 @@ export class SqlBackgroundsRepository
|
||||
}
|
||||
|
||||
async saveAll(
|
||||
extId: ExtId<Profile>,
|
||||
profileId: Id<Profile>,
|
||||
flags: Set<BackgroundCode>
|
||||
): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
const existing = await this.loadAll(extId);
|
||||
const existing = await this.loadAll(profileId);
|
||||
|
||||
for (const flag of flags) {
|
||||
if (existing.has(flag)) {
|
||||
|
||||
+10
-17
@@ -1,12 +1,10 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Car, CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CarRepository } from "../repo";
|
||||
import { generateId } from "../../db";
|
||||
import { generateId, Id } from "../../db";
|
||||
|
||||
function _extractRow(row: any): Car {
|
||||
return {
|
||||
@@ -31,12 +29,11 @@ function _extractRow(row: any): Car {
|
||||
export class SqlCarRepository implements CarRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async countCars(extId: ExtId<Profile>): Promise<number> {
|
||||
async countCars(profileId: Id<Profile>): Promise<number> {
|
||||
const countSql = sql
|
||||
.select("count(*) result")
|
||||
.from("idz.car c")
|
||||
.join("idz.profile p", { "c.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("c.profile_id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(countSql);
|
||||
@@ -45,12 +42,11 @@ export class SqlCarRepository implements CarRepository {
|
||||
return parseInt(row.result, 10);
|
||||
}
|
||||
|
||||
async loadAllCars(extId: ExtId<Profile>): Promise<Car[]> {
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz.car c")
|
||||
.join("idz.profile p", { "c.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("c.profile_id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -58,13 +54,12 @@ export class SqlCarRepository implements CarRepository {
|
||||
return rows.map(_extractRow);
|
||||
}
|
||||
|
||||
async loadSelectedCar(extId: ExtId<Profile>): Promise<Car> {
|
||||
async loadSelectedCar(profileId: Id<Profile>): Promise<Car> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz.car c")
|
||||
.join("idz.car_selection s", { "c.id": "s.car_id" })
|
||||
.join("idz.profile p", { "s.id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("s.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -72,11 +67,11 @@ export class SqlCarRepository implements CarRepository {
|
||||
return _extractRow(rows[0]);
|
||||
}
|
||||
|
||||
async saveCar(extId: ExtId<Profile>, car: Car): Promise<void> {
|
||||
async saveCar(profileId: Id<Profile>, car: Car): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz.car", {
|
||||
id: generateId(),
|
||||
profile_id: await _findProfile(this._conn, extId),
|
||||
profile_id: profileId,
|
||||
selector: car.selector,
|
||||
field_00: car.field_00,
|
||||
field_02: car.field_02,
|
||||
@@ -116,11 +111,9 @@ export class SqlCarRepository implements CarRepository {
|
||||
}
|
||||
|
||||
async saveSelection(
|
||||
extId: ExtId<Profile>,
|
||||
profileId: Id<Profile>,
|
||||
selector: CarSelector
|
||||
): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
const findSql = sql
|
||||
.select("c.id")
|
||||
.from("idz.car c")
|
||||
|
||||
+21
-21
@@ -1,43 +1,43 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Chara } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
|
||||
export function _extractChara(row: any): Chara {
|
||||
return {
|
||||
gender: row.gender,
|
||||
field_02: row.field_02,
|
||||
field_04: row.field_04,
|
||||
field_06: row.field_06,
|
||||
field_08: row.field_08,
|
||||
field_0A: row.field_0A,
|
||||
field_0C: row.field_0C,
|
||||
field_0E: row.field_0E,
|
||||
title: row.title,
|
||||
background: row.background,
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlCharaRepository implements FacetRepository<Chara> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Chara> {
|
||||
async load(profileId: Id<Profile>): Promise<Chara> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz.profile p")
|
||||
.join("idz.chara c", { "p.id": "c.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.from("idz.chara c")
|
||||
.where("c.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
|
||||
return {
|
||||
gender: row.gender,
|
||||
field_02: row.field_02,
|
||||
field_04: row.field_04,
|
||||
field_06: row.field_06,
|
||||
field_08: row.field_08,
|
||||
field_0A: row.field_0A,
|
||||
field_0C: row.field_0C,
|
||||
field_0E: row.field_0E,
|
||||
title: row.title,
|
||||
background: row.background,
|
||||
};
|
||||
return _extractChara(row);
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, chara: Chara): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
async save(profileId: Id<Profile>, chara: Chara): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz.chara", {
|
||||
id: profileId,
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { CourseNo, ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CoursePlaysRepository } from "../repo";
|
||||
import { generateId } from "../../db";
|
||||
import { generateId, Id } from "../../db";
|
||||
|
||||
export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async loadAll(extId: ExtId<Profile>): Promise<Map<CourseNo, number>> {
|
||||
async loadAll(profileId: Id<Profile>): Promise<Map<CourseNo, number>> {
|
||||
const loadSql = sql
|
||||
.select("cp.course_no", "cp.count")
|
||||
.from("idz.course_plays cp")
|
||||
.join("idz.profile p", { "cp.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("cp.profile_id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -29,11 +27,9 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
}
|
||||
|
||||
async saveAll(
|
||||
extId: ExtId<Profile>,
|
||||
profileId: Id<Profile>,
|
||||
plays: Map<CourseNo, number>
|
||||
): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
for (const [k, v] of plays) {
|
||||
const saveSql = sql
|
||||
.insert("idz.course_plays", {
|
||||
|
||||
@@ -8,6 +8,10 @@ import { SqlMissionsRepository } from "./missions";
|
||||
import { SqlProfileRepository } from "./profile";
|
||||
import { SqlSettingsRepository } from "./settings";
|
||||
import { SqlStoryRepository } from "./story";
|
||||
import { SqlTeamRepository } from "./team";
|
||||
import { SqlTeamAutoRepository } from "./teamAuto";
|
||||
import { SqlTeamMemberRepository } from "./teamMember";
|
||||
import { SqlTeamReservationRepository } from "./teamReservation";
|
||||
import { SqlTicketsRepository } from "./tickets";
|
||||
import { SqlTimeAttackRepository } from "./timeAttack";
|
||||
import { SqlTitlesRepository } from "./titles";
|
||||
@@ -51,6 +55,22 @@ class TransactionImpl implements Repo.Transaction {
|
||||
return new SqlStoryRepository(this._conn);
|
||||
}
|
||||
|
||||
teams(): Repo.TeamRepository {
|
||||
return new SqlTeamRepository(this._conn);
|
||||
}
|
||||
|
||||
teamAuto(): Repo.TeamAutoRepository {
|
||||
return new SqlTeamAutoRepository(this._conn);
|
||||
}
|
||||
|
||||
teamMembers(): Repo.TeamMemberRepository {
|
||||
return new SqlTeamMemberRepository(this._conn);
|
||||
}
|
||||
|
||||
teamReservations(): Repo.TeamReservationRepository {
|
||||
return new SqlTeamReservationRepository(this._conn);
|
||||
}
|
||||
|
||||
tickets(): Repo.FacetRepository<Model.Tickets> {
|
||||
return new SqlTicketsRepository(this._conn);
|
||||
}
|
||||
|
||||
+4
-16
@@ -1,8 +1,6 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { MissionGrid, MissionState } from "../model/mission";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
@@ -11,9 +9,7 @@ import { generateId, Id } from "../../db";
|
||||
export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
private async _load(
|
||||
extId: ExtId<Profile>
|
||||
): Promise<[MissionState, Id<Profile>]> {
|
||||
async load(profileId: Id<Profile>): Promise<MissionState> {
|
||||
const result: MissionState = {
|
||||
solo: new Array<MissionGrid>(),
|
||||
team: new Array<MissionGrid>(),
|
||||
@@ -32,8 +28,6 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
result.team.push(teamGrid);
|
||||
}
|
||||
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
const loadSoloSql = sql
|
||||
.select("sm.*")
|
||||
.from("idz.solo_mission_state sm")
|
||||
@@ -46,17 +40,11 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
result.solo[row.grid_no].cells[row.cell_no] = row.value;
|
||||
}
|
||||
|
||||
return [result, profileId];
|
||||
return result;
|
||||
}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<MissionState> {
|
||||
const [mission] = await this._load(extId);
|
||||
|
||||
return mission;
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, mission: MissionState): Promise<void> {
|
||||
const [existing, profileId] = await this._load(extId);
|
||||
async save(profileId: Id<Profile>, mission: MissionState): Promise<void> {
|
||||
const existing = await this.load(profileId);
|
||||
|
||||
for (let i = 0; i < mission.solo.length; i++) {
|
||||
const exGrid = existing.solo[i].cells;
|
||||
|
||||
+32
-47
@@ -1,35 +1,41 @@
|
||||
import * as sql from "sql-bricks";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team } from "../model/team";
|
||||
import { ProfileSpec, ProfileRepository } from "../repo";
|
||||
import { generateExtId, generateId, Id } from "../../db";
|
||||
import { ProfileRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
function _extractRow(row: any): Profile {
|
||||
export function _extractProfile(row: any): Profile {
|
||||
return {
|
||||
id: row.ext_id,
|
||||
teamId: 2 as ExtId<Team>, // TODO
|
||||
aimeId: row.aime_id,
|
||||
name: row.name,
|
||||
lv: row.lv,
|
||||
exp: row.exp,
|
||||
fame: row.fame,
|
||||
dpoint: row.dpoint,
|
||||
mileage: row.mileage,
|
||||
accessTime: row.access_time,
|
||||
registerTime: row.register_time,
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlProfileRepository implements ProfileRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
private async _tryLoadByAimeId(
|
||||
aimeId: AimeId
|
||||
): Promise<Profile | undefined> {
|
||||
async find(aimeId: AimeId): Promise<Id<Profile>> {
|
||||
const profileId = await this.peek(aimeId);
|
||||
|
||||
if (profileId === undefined) {
|
||||
throw new Error(`Profile not found for Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
return profileId;
|
||||
}
|
||||
|
||||
async peek(aimeId: AimeId): Promise<Id<Profile> | undefined> {
|
||||
const lookupSql = sql
|
||||
.select("p.*")
|
||||
.select("p.id")
|
||||
.from("idz.profile p")
|
||||
.join("aime.player r", { "p.player_id": "r.id" })
|
||||
.where("r.ext_id", aimeId)
|
||||
@@ -42,38 +48,23 @@ export class SqlProfileRepository implements ProfileRepository {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return _extractRow(row);
|
||||
return row.id;
|
||||
}
|
||||
|
||||
async discoverByAimeId(aimeId: AimeId): Promise<boolean> {
|
||||
const result = await this._tryLoadByAimeId(aimeId);
|
||||
|
||||
return result !== undefined;
|
||||
}
|
||||
|
||||
async loadByAimeId(aimeId: AimeId): Promise<Profile> {
|
||||
const result = await this._tryLoadByAimeId(aimeId);
|
||||
|
||||
if (result === undefined) {
|
||||
throw new Error("Profile not found for Aime ID");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Profile> {
|
||||
async load(id: Id<Profile>): Promise<Profile> {
|
||||
const loadSql = sql
|
||||
.select("p.*")
|
||||
.select("p.*", "r.ext_id as aime_id")
|
||||
.from("idz.profile p")
|
||||
.where("ext_id", extId)
|
||||
.join("aime.player r", { "p.player_id": "r.id" })
|
||||
.where("p.id", id)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
|
||||
return _extractRow(rows[0]);
|
||||
return _extractProfile(rows[0]);
|
||||
}
|
||||
|
||||
async save(profile: Profile, timestamp: Date): Promise<void> {
|
||||
async save(id: Id<Profile>, profile: Profile): Promise<void> {
|
||||
const saveSql = sql
|
||||
.update("idz.profile", {
|
||||
lv: profile.lv,
|
||||
@@ -81,23 +72,19 @@ export class SqlProfileRepository implements ProfileRepository {
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
access_time: timestamp,
|
||||
access_time: profile.accessTime,
|
||||
})
|
||||
.where("ext_id", profile.id)
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
}
|
||||
|
||||
async create(
|
||||
aimeId: AimeId,
|
||||
profile: ProfileSpec,
|
||||
timestamp: Date
|
||||
): Promise<ExtId<Profile>> {
|
||||
async create(profile: Profile): Promise<Id<Profile>> {
|
||||
const findSql = sql
|
||||
.select("r.id")
|
||||
.from("aime.player r")
|
||||
.where("r.ext_id", aimeId)
|
||||
.where("r.ext_id", profile.aimeId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
@@ -108,27 +95,25 @@ export class SqlProfileRepository implements ProfileRepository {
|
||||
}
|
||||
|
||||
const id = generateId();
|
||||
const extId = generateExtId() as ExtId<Profile>;
|
||||
const playerId = row.id;
|
||||
|
||||
const createSql = sql
|
||||
.insert("idz.profile", {
|
||||
id: id,
|
||||
player_id: playerId,
|
||||
ext_id: extId,
|
||||
name: profile.name,
|
||||
lv: profile.lv,
|
||||
exp: profile.exp,
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
register_time: timestamp,
|
||||
access_time: timestamp,
|
||||
register_time: profile.registerTime,
|
||||
access_time: profile.accessTime,
|
||||
})
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(createSql);
|
||||
|
||||
return extId;
|
||||
return id as Id<Profile>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Settings } from "../model/settings";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
|
||||
export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Settings> {
|
||||
async load(profileId: Id<Profile>): Promise<Settings> {
|
||||
const loadSql = sql
|
||||
.select("s.*")
|
||||
.from("idz.profile p")
|
||||
.join("idz.settings s", { "p.id": "s.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.from("idz.settings s")
|
||||
.where("s.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -29,9 +27,7 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
};
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, settings: Settings): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
async save(profileId: Id<Profile>, settings: Settings): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz.settings", {
|
||||
id: profileId,
|
||||
|
||||
+5
-15
@@ -1,8 +1,6 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Story, StoryRow, StoryCell } from "../model/story";
|
||||
import { FacetRepository } from "../repo";
|
||||
@@ -11,9 +9,7 @@ import { generateId, Id } from "../../db";
|
||||
export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
private async _load(extId: ExtId<Profile>): Promise<[Story, Id<Profile>]> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Story> {
|
||||
const loadSql = sql
|
||||
.select("s.*")
|
||||
.from("idz.story_state s")
|
||||
@@ -31,7 +27,7 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
rows: new Array<StoryRow>(),
|
||||
};
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const row: StoryRow = { cells: new Array<StoryCell>() };
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
@@ -56,17 +52,11 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
cell.b = row.b;
|
||||
}
|
||||
|
||||
return [result, profileId];
|
||||
return result;
|
||||
}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Story> {
|
||||
const [story] = await this._load(extId);
|
||||
|
||||
return story;
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, story: Story): Promise<void> {
|
||||
const [existing, profileId] = await this._load(extId);
|
||||
async save(profileId: Id<Profile>, story: Story): Promise<void> {
|
||||
const existing = await this.load(profileId);
|
||||
|
||||
const headSql = sql
|
||||
.insert("idz.story_state", {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks";
|
||||
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { TeamSpec, TeamRepository } from "../repo";
|
||||
import { Id, generateExtId, generateId } from "../../db";
|
||||
|
||||
export class SqlTeamRepository implements TeamRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async find(extId: ExtId<Team>): Promise<Id<Team>> {
|
||||
const findSql = sql
|
||||
.select("t.id")
|
||||
.from("idz.team t")
|
||||
.where("t.ext_id", extId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Team not found for ExtID ${extId}`);
|
||||
}
|
||||
|
||||
return row.id;
|
||||
}
|
||||
|
||||
async load(id: Id<Team>): Promise<Team> {
|
||||
const loadSql = sql
|
||||
.select("t.*")
|
||||
.from("idz.team t")
|
||||
.where("t.id", id)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row == undefined) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
|
||||
return {
|
||||
extId: row.ext_id,
|
||||
name: row.name,
|
||||
nameBg: row.name_bg,
|
||||
nameFx: row.name_fx,
|
||||
registerTime: new Date(row.register_time),
|
||||
};
|
||||
}
|
||||
|
||||
async save(id: Id<Team>, team: Team): Promise<void> {
|
||||
const saveSql = sql
|
||||
.update("idz.team", {
|
||||
name_bg: team.nameBg,
|
||||
name_fx: team.nameFx,
|
||||
})
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
}
|
||||
|
||||
async create(team: TeamSpec): Promise<[Id<Team>, ExtId<Team>]> {
|
||||
const id = generateId() as Id<Team>;
|
||||
const extId = generateExtId() as ExtId<Team>;
|
||||
|
||||
const createSql = sql
|
||||
.insert("idz.team", {
|
||||
id: id,
|
||||
ext_id: extId,
|
||||
name: team.name,
|
||||
name_bg: team.nameBg,
|
||||
name_fx: team.nameFx,
|
||||
register_time: team.registerTime,
|
||||
})
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(createSql);
|
||||
|
||||
return [id, extId];
|
||||
}
|
||||
|
||||
async delete(id: Id<Team>): Promise<void> {
|
||||
const deleteSql = sql
|
||||
.delete("idz.team")
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(deleteSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Team, TeamAuto } from "../model/team";
|
||||
import { TeamAutoRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
|
||||
export class SqlTeamAutoRepository implements TeamAutoRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async peek(): Promise<[TeamAuto, Id<Team>] | undefined> {
|
||||
const peekSql = sql
|
||||
.select("tt.*")
|
||||
.from("idz.team_auto tt")
|
||||
.orderBy("serial_no desc", "name_idx desc")
|
||||
.limit(1)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(peekSql);
|
||||
const row = rows[0];
|
||||
|
||||
return (
|
||||
row && [
|
||||
{
|
||||
serialNo: row.serial_no,
|
||||
nameIdx: row.name_idx,
|
||||
},
|
||||
row.id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async push(teamId: Id<Team>, auto: TeamAuto): Promise<void> {
|
||||
const pushSql = sql
|
||||
.insert("idz.team_auto", {
|
||||
id: teamId,
|
||||
serial_no: auto.serialNo,
|
||||
name_idx: auto.nameIdx,
|
||||
})
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(pushSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team, TeamMember } from "../model/team";
|
||||
import { TeamMemberRepository } from "../repo";
|
||||
import { Id, generateId } from "../../db";
|
||||
import { _extractProfile } from "./profile";
|
||||
import { _extractChara } from "./chara";
|
||||
|
||||
export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async findTeam(profileId: Id<Profile>): Promise<Id<Team> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.team_id")
|
||||
.from("idz.team_member tm")
|
||||
.where("tm.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.team_id;
|
||||
}
|
||||
|
||||
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.id")
|
||||
.from("idz.team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.where("tm.leader", true)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.id;
|
||||
}
|
||||
|
||||
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
|
||||
const loadSql = sql
|
||||
.select("tm.*", "p.*", "c.*", "r.ext_id as aime_id")
|
||||
.from("idz.team_member tm")
|
||||
.join("idz.profile p", { "tm.id": "p.id" })
|
||||
.join("idz.chara c", { "tm.id": "c.id" })
|
||||
.join("aime.player r", { "p.player_id": "r.id" })
|
||||
.where("tm.team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
|
||||
return rows.map((row: any) => ({
|
||||
profile: _extractProfile(row),
|
||||
chara: _extractChara(row),
|
||||
leader: row.leader,
|
||||
joinTime: new Date(row.join_time),
|
||||
}));
|
||||
}
|
||||
|
||||
async join(
|
||||
teamId: Id<Team>,
|
||||
profileId: Id<Profile>,
|
||||
timestamp: Date
|
||||
): Promise<void> {
|
||||
// Lock the team record to avoid race conditions. This way
|
||||
|
||||
const lockSql = sql
|
||||
.select("id")
|
||||
.from("idz.team")
|
||||
.where("id", teamId)
|
||||
.forUpdate()
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(lockSql);
|
||||
|
||||
// Double-check (with lock held) that there is room to join this team.
|
||||
// If this fails then the error will propagate to the client and it will
|
||||
// retry, and, assuming we have a race between two new registrations to
|
||||
// take up the last slot in the current auto-team, hopefully succeed.
|
||||
//
|
||||
// There is arguably some business logic pollution here, since we have a
|
||||
// hard-coded maximum team size imposed by the protocol. This is why a
|
||||
// three-layered server would be better than our two-layered server.
|
||||
|
||||
const countSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz.team_member")
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(countSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row.count >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
// Do upsert
|
||||
|
||||
const joinSql = sql
|
||||
.insert("idz.team_member", {
|
||||
id: profileId,
|
||||
team_id: teamId,
|
||||
leader: false,
|
||||
join_time: timestamp,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id", "leader", "join_time"])
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(joinSql);
|
||||
}
|
||||
|
||||
async leave(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const leaveSql = sql
|
||||
.delete("idz.team_member")
|
||||
.where("team_id", teamId)
|
||||
.where("id", profileId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(leaveSql);
|
||||
}
|
||||
|
||||
async makeLeader(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const clearSql = sql
|
||||
.update("idz.team_member", {
|
||||
leader: false,
|
||||
})
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(clearSql);
|
||||
|
||||
const setSql = sql
|
||||
.update("idz.team_member", {
|
||||
leader: true,
|
||||
})
|
||||
.where("id", profileId)
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(setSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Team } from "../model/team";
|
||||
import { TeamReservationRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
export class SqlTeamReservationRepository
|
||||
implements TeamReservationRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
private async _lockTeam(teamId: Id<Team>): Promise<void> {
|
||||
const lockSql = sql
|
||||
.select("t.id")
|
||||
.from("idz.team t")
|
||||
.where("t.id", teamId)
|
||||
.forUpdate()
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(lockSql);
|
||||
}
|
||||
|
||||
async occupancyHack(teamId: Id<Team>): Promise<number> {
|
||||
await this._lockTeam(teamId);
|
||||
|
||||
// counts get returned as strings, so 1 + 0 = 10.
|
||||
// it hardly needs to be said but fuck javascript.
|
||||
|
||||
const memberSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz.team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const memberRes = await this._conn.query(memberSql);
|
||||
const memberCount = parseInt(memberRes.rows[0].count, 10);
|
||||
|
||||
const reservSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz.team_reservation tr")
|
||||
.where("tr.team_id", teamId)
|
||||
.toParams();
|
||||
|
||||
const reservRes = await this._conn.query(reservSql);
|
||||
const reservCount = parseInt(reservRes.rows[0].count, 10);
|
||||
|
||||
return memberCount + reservCount;
|
||||
}
|
||||
|
||||
async reserveHack(
|
||||
teamId: Id<Team>,
|
||||
aimeId: AimeId,
|
||||
timestamp: Date,
|
||||
leader?: "leader"
|
||||
): Promise<void> {
|
||||
const lookupSql = sql
|
||||
.select("r.id")
|
||||
.from("aime.player r")
|
||||
.where("r.ext_id", aimeId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(lookupSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Unknown Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
const playerId = row.id;
|
||||
|
||||
const insertSql = sql
|
||||
.insert("idz.team_reservation", {
|
||||
id: playerId,
|
||||
team_id: teamId,
|
||||
join_time: timestamp,
|
||||
leader: leader === "leader",
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id"])
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(insertSql);
|
||||
}
|
||||
|
||||
async commitHack(aimeId: AimeId): Promise<void> {
|
||||
const lookupSql = sql
|
||||
.select("p.id as profile_id", "tr.*")
|
||||
.from("idz.profile p")
|
||||
.join("aime.player r", { "p.player_id": "r.id" })
|
||||
.join("idz.team_reservation tr", { "r.id": "tr.id" })
|
||||
.where("r.ext_id", aimeId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(lookupSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Reservation not found for Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
console.log(row);
|
||||
|
||||
const insertSql = sql
|
||||
.insert("idz.team_member", {
|
||||
id: row.profile_id,
|
||||
team_id: row.team_id,
|
||||
join_time: row.join_time,
|
||||
leader: row.leader,
|
||||
})
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(insertSql);
|
||||
|
||||
const cleanupSql = sql
|
||||
.delete("idz.team_reservation")
|
||||
.where("id", row.id)
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(cleanupSql);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Tickets } from "../model/tickets";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
|
||||
// TODO free continue
|
||||
|
||||
export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Tickets> {
|
||||
async load(profileId: Id<Profile>): Promise<Tickets> {
|
||||
const loadSql = sql
|
||||
.select("fc.*")
|
||||
.from("idz.profile p")
|
||||
.join("idz.free_car fc", { "p.id": "fc.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.from("idz.free_car fc")
|
||||
.where("fc.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -30,8 +28,7 @@ export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
||||
};
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, tickets: Tickets): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
async save(profileId: Id<Profile>, tickets: Tickets): Promise<void> {
|
||||
const { freeCar } = tickets;
|
||||
|
||||
if (!freeCar) {
|
||||
|
||||
+10
-10
@@ -1,22 +1,25 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId, RouteNo } from "../model/base";
|
||||
import { RouteNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { TimeAttackScore } from "../model/timeAttack";
|
||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||
import { generateId } from "../../db";
|
||||
import { generateId, Id } from "../../db";
|
||||
|
||||
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async loadTopTen(routeNo: RouteNo): Promise<TopTenResult[]> {
|
||||
async loadTopTen(
|
||||
routeNo: RouteNo,
|
||||
minTimestamp: Date
|
||||
): Promise<TopTenResult[]> {
|
||||
const loadSql = sql
|
||||
.select("p.name", "ta.*")
|
||||
.from("idz.ta_best ta")
|
||||
.join("idz.profile p", { "ta.profile_id": "p.id" })
|
||||
.where("ta.route_no", routeNo)
|
||||
.where(sql.gt("ta.timestamp", minTimestamp))
|
||||
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
||||
.limit(10)
|
||||
.toParams();
|
||||
@@ -37,12 +40,11 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAll(extId: ExtId<Profile>): Promise<TimeAttackScore[]> {
|
||||
async loadAll(profileId: Id<Profile>): Promise<TimeAttackScore[]> {
|
||||
const loadSql = sql
|
||||
.select("ta.*")
|
||||
.from("idz.ta_best ta")
|
||||
.join("idz.profile p", { "ta.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("ta.profile_id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -58,9 +60,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
const logSql = sql
|
||||
.insert("idz.ta_result", {
|
||||
id: generateId(),
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { TitleCode, ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { generateId } from "../../db";
|
||||
import { generateId, Id } from "../../db";
|
||||
|
||||
export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async loadAll(extId: ExtId<Profile>): Promise<Set<TitleCode>> {
|
||||
async loadAll(profileId: Id<Profile>): Promise<Set<TitleCode>> {
|
||||
const loadSql = sql
|
||||
.select("t.title_no")
|
||||
.from("idz.title_unlock t")
|
||||
.join("idz.profile p", { "t.profile_id": "p.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.where("t.profile_id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -28,9 +26,8 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
return result;
|
||||
}
|
||||
|
||||
async saveAll(extId: ExtId<Profile>, flags: Set<TitleCode>): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
const existing = await this.loadAll(extId);
|
||||
async saveAll(profileId: Id<Profile>, flags: Set<TitleCode>): Promise<void> {
|
||||
const existing = await this.loadAll(profileId);
|
||||
|
||||
for (const flag of flags) {
|
||||
if (existing.has(flag)) {
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { ClientBase } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { _findProfile } from "./_util";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Unlocks } from "../model/unlocks";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
|
||||
export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
|
||||
async load(extId: ExtId<Profile>): Promise<Unlocks> {
|
||||
async load(profileId: Id<Profile>): Promise<Unlocks> {
|
||||
const loadSql = sql
|
||||
.select("u.*")
|
||||
.from("idz.profile p")
|
||||
.join("idz.unlocks u", { "p.id": "u.id" })
|
||||
.where("p.ext_id", extId)
|
||||
.from("idz.unlocks u")
|
||||
.where("u.id", profileId)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
@@ -29,9 +28,7 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
};
|
||||
}
|
||||
|
||||
async save(extId: ExtId<Profile>, unlocks: Unlocks): Promise<void> {
|
||||
const profileId = await _findProfile(this._conn, extId);
|
||||
|
||||
async save(profileId: Id<Profile>, unlocks: Unlocks): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz.unlocks", {
|
||||
id: profileId,
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import iconv = require("iconv-lite");
|
||||
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CreateTeamRequest } from "../request/createTeam";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
createTeam.msgCode = 0x0071 as RequestCode;
|
||||
createTeam.msgLen = 0x0050;
|
||||
@@ -11,13 +10,16 @@ createTeam.msgLen = 0x0050;
|
||||
export function createTeam(buf: Buffer): CreateTeamRequest {
|
||||
return {
|
||||
type: "create_team_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
teamName: iconv.decode(buf.slice(0x0008, 0x0028), "shift_jis"),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamName: iconv.decode(
|
||||
buf.slice(0x0008, buf.indexOf("\0", 0x0008)),
|
||||
"shift_jis"
|
||||
),
|
||||
field_0028: buf.readUInt16LE(0x0028),
|
||||
field_002C: buf.readUInt32LE(0x002c),
|
||||
field_0030: buf.readUInt8(0x0030),
|
||||
nameBg: buf.readUInt8(0x0030),
|
||||
field_0032: buf.readUInt16LE(0x0032),
|
||||
prevTeamId: buf.readUInt32LE(0x0034),
|
||||
field_0038: buf.slice(0x0038, 0x0045),
|
||||
pcbId: buf.slice(0x0038, buf.indexOf("\0", 0x0038)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
+43
-18
@@ -3,41 +3,52 @@ import { Transform } from "stream";
|
||||
import { checkTeamName } from "./checkTeamName";
|
||||
import { createProfile } from "./createProfile";
|
||||
import { createTeam } from "./createTeam";
|
||||
import { joinAutoTeam } from "./joinAutoTeam";
|
||||
import { createAutoTeam } from "./createAutoTeam";
|
||||
import { discoverProfile } from "./discoverProfile";
|
||||
import { load2on2 } from "./load2on2";
|
||||
import { load2on2_v1, load2on2_v2 } from "./load2on2";
|
||||
import { loadConfig } from "./loadConfig";
|
||||
import { loadConfig2 } from "./loadConfig2";
|
||||
import { loadEventInfo } from "./loadEventInfo";
|
||||
import { loadGacha } from "./loadGacha";
|
||||
import { loadGarage } from "./loadGarage";
|
||||
import { loadGeneralReward } from "./loadGeneralReward";
|
||||
import { loadGeneralReward1, loadGeneralReward2 } from "./loadGeneralReward";
|
||||
import { loadGhost } from "./loadGhost";
|
||||
import { loadProfile } from "./loadProfile";
|
||||
import { loadProfile2, loadProfile3 } from "./loadProfile";
|
||||
import { loadRewardTable } from "./loadRewardTable";
|
||||
import { loadServerList } from "./loadServerList";
|
||||
import { loadStocker } from "./loadStocker";
|
||||
import { loadTeam } from "./loadTeam";
|
||||
import { loadTeamRanking, loadTeamRanking2 } from "./loadTeamRanking";
|
||||
import { loadTopTen1 } from "./loadTopTen1";
|
||||
import { loadTopTen2 } from "./loadTopTen2";
|
||||
import { lockGarage } from "./lockGarage";
|
||||
import { lockProfile } from "./lockProfile";
|
||||
import { msg00AD } from "./msg00AD";
|
||||
import { saveExpedition } from "./saveExpedition";
|
||||
import { saveExpedition1, saveExpedition2 } from "./saveExpedition";
|
||||
import { saveGarage } from "./saveGarage";
|
||||
import { saveNewCar } from "./saveNewCar";
|
||||
import { saveProfile } from "./saveProfile";
|
||||
import { saveProfile2 } from "./saveProfile2";
|
||||
import { saveProfile3 } from "./saveProfile3";
|
||||
import { saveSettings } from "./saveSettings";
|
||||
import { saveStocker } from "./saveStocker";
|
||||
import { saveTimeAttack } from "./saveTimeAttack";
|
||||
import { saveTeamBanner } from "./saveTeamBanner";
|
||||
import { saveTimeAttack1, saveTimeAttack2 } from "./saveTimeAttack";
|
||||
import { saveTopic } from "./saveTopic";
|
||||
import { unlockProfile } from "./unlockProfile";
|
||||
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
|
||||
import { updateStoryClearNum } from "./updateStoryClearNum";
|
||||
import { updateTeamLeader } from "./updateTeamLeader";
|
||||
import { updateTeamMember } from "./updateTeamMember";
|
||||
import {
|
||||
updateStoryClearNum1,
|
||||
updateStoryClearNum2,
|
||||
} from "./updateStoryClearNum";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { Request } from "../request";
|
||||
import { loadTopTen } from "./loadTopTen";
|
||||
import { updateResult } from "./updateResult";
|
||||
import { updateTeamPoints } from "./updateTeamPoints";
|
||||
import { updateUiReport } from "./updateUiReport";
|
||||
import { updateUserLog } from "./updateUserLog";
|
||||
import { lockProfileExtend } from "./lockProfileExtend";
|
||||
|
||||
export type ReaderFn = ((buf: Buffer) => Request) & {
|
||||
msgCode: RequestCode;
|
||||
@@ -46,39 +57,53 @@ export type ReaderFn = ((buf: Buffer) => Request) & {
|
||||
|
||||
const funcList: ReaderFn[] = [
|
||||
checkTeamName,
|
||||
createAutoTeam,
|
||||
createProfile,
|
||||
createTeam,
|
||||
joinAutoTeam,
|
||||
discoverProfile,
|
||||
load2on2,
|
||||
load2on2_v1,
|
||||
load2on2_v2,
|
||||
loadConfig,
|
||||
loadConfig2,
|
||||
loadEventInfo,
|
||||
loadGacha,
|
||||
loadGarage,
|
||||
loadGeneralReward,
|
||||
loadGeneralReward1,
|
||||
loadGeneralReward2,
|
||||
loadGhost,
|
||||
loadProfile,
|
||||
loadProfile2,
|
||||
loadProfile3,
|
||||
loadRewardTable,
|
||||
loadServerList,
|
||||
loadStocker,
|
||||
loadTeam,
|
||||
loadTeamRanking,
|
||||
loadTeamRanking2,
|
||||
loadTopTen,
|
||||
loadTopTen1,
|
||||
loadTopTen2,
|
||||
lockGarage,
|
||||
lockProfile,
|
||||
lockProfileExtend,
|
||||
msg00AD,
|
||||
saveExpedition,
|
||||
saveExpedition1,
|
||||
saveExpedition2,
|
||||
saveGarage,
|
||||
saveNewCar,
|
||||
saveProfile,
|
||||
saveProfile2,
|
||||
saveProfile3,
|
||||
saveSettings,
|
||||
saveStocker,
|
||||
saveTimeAttack,
|
||||
saveTeamBanner,
|
||||
saveTimeAttack1,
|
||||
saveTimeAttack2,
|
||||
saveTopic,
|
||||
unlockProfile,
|
||||
updateProvisionalStoreRank,
|
||||
updateResult,
|
||||
updateStoryClearNum,
|
||||
updateStoryClearNum1,
|
||||
updateStoryClearNum2,
|
||||
updateTeamLeader,
|
||||
updateTeamMember,
|
||||
updateTeamPoints,
|
||||
updateUiReport,
|
||||
updateUserLog,
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,31 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { Load2on2Request } from "../request/load2on2";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
load2on2.msgCode = 0x00b0 as RequestCode;
|
||||
load2on2.msgLen = 0x0010;
|
||||
load2on2_v1.msgCode = 0x00b0 as RequestCode;
|
||||
load2on2_v1.msgLen = 0x0010;
|
||||
|
||||
export function load2on2(buf: Buffer): Load2on2Request {
|
||||
export function load2on2_v1(buf: Buffer): Load2on2Request1 {
|
||||
return {
|
||||
type: "load_2on2_req",
|
||||
format: 1,
|
||||
field_0002: buf.readUInt16LE(0x0002),
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
field_0008: buf.readUInt32LE(0x0008),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
||||
};
|
||||
}
|
||||
|
||||
load2on2_v2.msgCode = 0x0132 as RequestCode;
|
||||
load2on2_v2.msgLen = 0x0010;
|
||||
|
||||
export function load2on2_v2(buf: Buffer): Load2on2Request2 {
|
||||
return {
|
||||
type: "load_2on2_req",
|
||||
format: 2,
|
||||
field_0002: buf.readUInt16LE(0x0002),
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadEventInfo.msgCode = 0x00be as RequestCode;
|
||||
loadEventInfo.msgLen = 0x0010;
|
||||
|
||||
export function loadEventInfo(buf: Buffer): LoadEventInfoRequest {
|
||||
return {
|
||||
type: "load_event_info_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LoadGachaRequest } from "../request/loadGacha";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGacha.msgCode = 0x00c1 as RequestCode;
|
||||
loadGacha.msgLen = 0x0010;
|
||||
|
||||
export function loadGacha(buf: Buffer): LoadGachaRequest {
|
||||
return {
|
||||
type: "load_gacha_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { LoadGarageRequest } from "../request/loadGarage";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGarage.msgCode = 0x0090 as RequestCode;
|
||||
loadGarage.msgLen = 0x0010;
|
||||
@@ -9,7 +8,7 @@ loadGarage.msgLen = 0x0010;
|
||||
export function loadGarage(buf: Buffer): LoadGarageRequest {
|
||||
return {
|
||||
type: "load_garage_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
fetchOffset: buf.readUInt8(0x0008),
|
||||
field_000A: buf.readUInt16LE(0x000a),
|
||||
};
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LoadGeneralRewardRequest } from "../request/loadGeneralReward";
|
||||
import {
|
||||
LoadGeneralRewardRequest1,
|
||||
LoadGeneralRewardRequest2,
|
||||
} from "../request/loadGeneralReward";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadGeneralReward.msgCode = 0x009c as RequestCode;
|
||||
loadGeneralReward.msgLen = 0x0010;
|
||||
loadGeneralReward1.msgCode = 0x009c as RequestCode;
|
||||
loadGeneralReward1.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward(buf: Buffer): LoadGeneralRewardRequest {
|
||||
export function loadGeneralReward1(buf: Buffer): LoadGeneralRewardRequest1 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
format: 1,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
loadGeneralReward2.msgCode = 0x013b as RequestCode;
|
||||
loadGeneralReward2.msgLen = 0x0010;
|
||||
|
||||
export function loadGeneralReward2(buf: Buffer): LoadGeneralRewardRequest2 {
|
||||
return {
|
||||
type: "load_general_reward_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LoadProfileRequest } from "../request/loadProfile";
|
||||
import {
|
||||
LoadProfileRequest2,
|
||||
LoadProfileRequest3,
|
||||
} from "../request/loadProfile";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadProfile.msgCode = 0x0067 as RequestCode;
|
||||
loadProfile.msgLen = 0x0020;
|
||||
loadProfile2.msgCode = 0x0067 as RequestCode;
|
||||
loadProfile2.msgLen = 0x0020;
|
||||
|
||||
export function loadProfile(buf: Buffer): LoadProfileRequest {
|
||||
export function loadProfile2(buf: Buffer): LoadProfileRequest2 {
|
||||
return {
|
||||
type: "load_profile_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
loadProfile3.msgCode = 0x0012f as RequestCode;
|
||||
loadProfile3.msgLen = 0x0020;
|
||||
|
||||
export function loadProfile3(buf: Buffer): LoadProfileRequest3 {
|
||||
return {
|
||||
type: "load_profile_req",
|
||||
format: 3,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { LoadStockerRequest } from "../request/loadStocker";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadStocker.msgCode = 0x00a7 as RequestCode;
|
||||
loadStocker.msgLen = 0x0010;
|
||||
@@ -9,6 +8,6 @@ loadStocker.msgLen = 0x0010;
|
||||
export function loadStocker(buf: Buffer): LoadStockerRequest {
|
||||
return {
|
||||
type: "load_stocker_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LoadTeamRequest } from "../request/loadTeam";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
|
||||
loadTeam.msgCode = 0x0077 as RequestCode;
|
||||
loadTeam.msgLen = 0x0010;
|
||||
|
||||
export function loadTeam(buf: Buffer): LoadTeamRequest {
|
||||
const extId = buf.readUInt32LE(0x0008);
|
||||
|
||||
return {
|
||||
type: "load_team_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
teamId: buf.readUInt32LE(0x0008),
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
teamExtId: extId !== 0xffffffff ? (extId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId, RouteNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team } from "../model/team";
|
||||
import {
|
||||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen.msgCode = 0x00b5 as RequestCode;
|
||||
loadTopTen.msgLen = 0x00e0;
|
||||
loadTopTen1.msgCode = 0x00b5 as RequestCode;
|
||||
loadTopTen1.msgLen = 0x00e0;
|
||||
|
||||
export function loadTopTen(buf: Buffer): LoadTopTenRequest {
|
||||
export function loadTopTen1(buf: Buffer): LoadTopTenRequest {
|
||||
const selectors = new Array<LoadTopTenRequestSelector>();
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
selectors.push({
|
||||
routeNo: (buf.readUInt16LE(0x0004 + 2 * i) >> 1) as RouteNo,
|
||||
field_44: buf.readUInt32LE(0x0044 + 4 * i),
|
||||
minTimestamp: new Date(buf.readUInt32LE(0x0044 + 4 * i) * 1000 + 1000),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function loadTopTen(buf: Buffer): LoadTopTenRequest {
|
||||
field_C4: buf.readUInt8(0x00c4), // Boolean, true if profile ID is set
|
||||
field_C5: buf.readUInt8(0x00c5), // Always zero
|
||||
field_C6: buf.readUInt16LE(0x00c6),
|
||||
profileId: profileId !== 0 ? (profileId as ExtId<Profile>) : undefined,
|
||||
aimeId: profileId !== 0 ? (profileId as AimeId) : undefined,
|
||||
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId, RouteNo } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import {
|
||||
LoadTopTenRequest,
|
||||
LoadTopTenRequestSelector,
|
||||
} from "../request/loadTopTen";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
loadTopTen2.msgCode = 0x012c as RequestCode;
|
||||
loadTopTen2.msgLen = 0x0110;
|
||||
|
||||
export function loadTopTen2(buf: Buffer): LoadTopTenRequest {
|
||||
const selectors = new Array<LoadTopTenRequestSelector>();
|
||||
|
||||
for (let i = 0; i < 40; i++) {
|
||||
selectors.push({
|
||||
routeNo: (buf.readUInt16LE(0x0004 + 2 * i) >> 1) as RouteNo,
|
||||
minTimestamp: new Date(buf.readUInt32LE(0x0054 + 4 * i) * 1000 + 1000),
|
||||
});
|
||||
}
|
||||
|
||||
const profileId = buf.readUInt32LE(0x00f8);
|
||||
const teamId = buf.readUInt32LE(0x00fc);
|
||||
|
||||
return {
|
||||
type: "load_top_ten_req",
|
||||
field_2: buf.readUInt16LE(0x0002), // Bitmask selector
|
||||
selectors,
|
||||
field_C4: buf.readUInt8(0x00f4), // Boolean, true if profile ID is set
|
||||
field_C5: buf.readUInt8(0x00f5), // Always zero
|
||||
field_C6: buf.readUInt16LE(0x00f6),
|
||||
aimeId: profileId !== 0 ? (profileId as AimeId) : undefined,
|
||||
teamId: teamId !== 0xffffffff ? (teamId as ExtId<Team>) : undefined,
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LockAccountRequest } from "../request/lockProfile";
|
||||
import { LockProfileRequest } from "../request/lockProfile";
|
||||
|
||||
lockProfile.msgCode = 0x0069 as RequestCode;
|
||||
lockProfile.msgLen = 0x0020;
|
||||
|
||||
export function lockProfile(buf: Buffer): LockAccountRequest {
|
||||
export function lockProfile(buf: Buffer): LockProfileRequest {
|
||||
return {
|
||||
type: "lock_profile_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
field_0018: buf.readUInt16LE(0x0018),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
lockProfileExtend.msgCode = 0x006d as RequestCode;
|
||||
lockProfileExtend.msgLen = 0x0020;
|
||||
|
||||
export function lockProfileExtend(buf: Buffer): LockProfileExtendRequest {
|
||||
return {
|
||||
type: "lock_profile_extend_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf("\0")).toString("ascii"),
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { SaveExpeditionRequest } from "../request/saveExpedition";
|
||||
import {
|
||||
SaveExpeditionRequest1,
|
||||
SaveExpeditionRequest2,
|
||||
} from "../request/saveExpedition";
|
||||
|
||||
saveExpedition.msgCode = 0x008c as RequestCode;
|
||||
saveExpedition.msgLen = 0x0010;
|
||||
saveExpedition1.msgCode = 0x008c as RequestCode;
|
||||
saveExpedition1.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition(buf: Buffer): SaveExpeditionRequest {
|
||||
export function saveExpedition1(buf: Buffer): SaveExpeditionRequest1 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 1,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
|
||||
saveExpedition2.msgCode = 0x013f as RequestCode;
|
||||
saveExpedition2.msgLen = 0x0010;
|
||||
|
||||
export function saveExpedition2(buf: Buffer): SaveExpeditionRequest2 {
|
||||
return {
|
||||
type: "save_expedition_req",
|
||||
format: 2,
|
||||
field_0004: buf.readUInt32LE(0x0004),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function saveGarage(buf: Buffer): SaveGarageRequest {
|
||||
|
||||
return {
|
||||
type: "save_garage_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
payload: car(buf.slice(0x0008, 0x0068)),
|
||||
field_0068,
|
||||
field_0080: buf.readUInt8(0x0080),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { car } from "./_car";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveNewCarRequest } from "../request/saveNewCar";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveNewCar.msgCode = 0x0079 as RequestCode;
|
||||
saveNewCar.msgLen = 0x0090;
|
||||
@@ -10,7 +9,7 @@ saveNewCar.msgLen = 0x0090;
|
||||
export function saveNewCar(buf: Buffer): SaveNewCarRequest {
|
||||
return {
|
||||
type: "save_new_car_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
luid: buf.slice(0x0008, buf.indexOf(0, 0x0008)).toString("ascii"),
|
||||
car: car(buf.slice(0x0020, 0x0080)),
|
||||
field_0080: buf.readUInt32LE(0x0080),
|
||||
|
||||
@@ -2,14 +2,14 @@ import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { BackgroundCode, CourseNo, ExtId, TitleCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveProfileRequest } from "../request/saveProfile";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile.msgCode = 0x0068 as RequestCode;
|
||||
saveProfile.msgLen = 0x0940;
|
||||
saveProfile2.msgCode = 0x0068 as RequestCode;
|
||||
saveProfile2.msgLen = 0x0940;
|
||||
|
||||
export function saveProfile(buf: Buffer): SaveProfileRequest {
|
||||
export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
@@ -46,7 +46,8 @@ export function saveProfile(buf: Buffer): SaveProfileRequest {
|
||||
|
||||
return {
|
||||
type: "save_profile_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
lv: buf.readUInt16LE(0x0026),
|
||||
exp: buf.readUInt32LE(0x0028),
|
||||
fame: buf.readUInt32LE(0x0468),
|
||||
@@ -0,0 +1,100 @@
|
||||
import { car } from "./_car";
|
||||
import { mission } from "./_mission";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveProfile3.msgCode = 0x0138 as RequestCode;
|
||||
saveProfile3.msgLen = 0x0a70;
|
||||
|
||||
export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
||||
const storyRows = new Array();
|
||||
|
||||
// Story layout has changed somewhat...
|
||||
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const cells = new Array();
|
||||
const rowOffset = 0x01ac + i * 0x18;
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
const a = buf.readUInt8(rowOffset + 0x00 + j);
|
||||
const b = buf.readUInt8(rowOffset + 0x09 + j);
|
||||
const cell = { a, b };
|
||||
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const row = { cells };
|
||||
|
||||
storyRows.push(row);
|
||||
}
|
||||
|
||||
const coursePlays = new Map<CourseNo, number>();
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
coursePlays.set(i as CourseNo, buf.readUInt16LE(0x0554 + 2 * i));
|
||||
}
|
||||
|
||||
const freeCar = {
|
||||
validFrom: buf.readUInt32LE(0x0138),
|
||||
};
|
||||
|
||||
const freeContinue = {
|
||||
validFrom: buf.readUInt32LE(0x0038),
|
||||
validTo: buf.readUInt32LE(0x003c),
|
||||
};
|
||||
|
||||
return {
|
||||
type: "save_profile_req",
|
||||
format: 2,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
lv: buf.readUInt16LE(0x0026),
|
||||
exp: buf.readUInt32LE(0x0028),
|
||||
fame: buf.readUInt32LE(0x04fc),
|
||||
dpoint: buf.readUInt32LE(0x04f8),
|
||||
mileage: buf.readUInt32LE(0x0008),
|
||||
title: buf.readUInt16LE(0x0040) as TitleCode,
|
||||
titles: bitmap(buf.slice(0x0042, 0x00f6)),
|
||||
background: buf.readUInt8(0x0874) as BackgroundCode,
|
||||
coursePlays,
|
||||
missions: {
|
||||
team: mission(buf.slice(0x0430, 0x0452)),
|
||||
solo: mission(buf.slice(0x0848, 0x086a)),
|
||||
},
|
||||
car: car(buf.slice(0x0958, 0x09b8)),
|
||||
story: {
|
||||
x: buf.readUInt16LE(0x0818),
|
||||
y: buf.readUInt8(0x07fc),
|
||||
rows: storyRows,
|
||||
},
|
||||
unlocks: {
|
||||
cup: buf.readUInt8(0x0110),
|
||||
gauges: buf.readUInt16LE(0x0114),
|
||||
music: buf.readUInt16LE(0x0140),
|
||||
lastMileageReward: buf.readUInt32LE(0x013c),
|
||||
},
|
||||
tickets: {
|
||||
freeCar:
|
||||
freeCar.validFrom !== 0
|
||||
? {
|
||||
validFrom: new Date(freeCar.validFrom * 1000),
|
||||
}
|
||||
: undefined,
|
||||
freeContinue:
|
||||
freeContinue.validFrom !== 0 && freeContinue.validTo !== 0
|
||||
? {
|
||||
validFrom: new Date(freeContinue.validFrom * 1000),
|
||||
validTo: new Date(freeContinue.validTo * 1000),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
settings: {
|
||||
music: buf.readUInt16LE(0x04ee),
|
||||
pack: buf.readUInt32LE(0x0034),
|
||||
paperCup: buf.readUInt8(0x00f6),
|
||||
gauges: buf.readUInt8(0x00f7),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveSettingsRequest } from "../request/saveSettings";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveSettings.msgCode = 0x00a5 as RequestCode;
|
||||
saveSettings.msgLen = 0x0020;
|
||||
@@ -13,7 +12,7 @@ export function saveSettings(buf: Buffer): SaveSettingsRequest {
|
||||
|
||||
return {
|
||||
type: "save_settings_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
dpoint: buf.readUInt32LE(0x0008),
|
||||
settings: {
|
||||
music: buf.readUInt16LE(0x0002),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { chara } from "./_chara";
|
||||
import { RequestCode } from "./_defs";
|
||||
import { BackgroundCode, ExtId } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveStockerRequest } from "../request/saveStocker";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveStocker.msgCode = 0x00a6 as RequestCode;
|
||||
saveStocker.msgLen = 0x00c0;
|
||||
@@ -12,7 +11,7 @@ saveStocker.msgLen = 0x00c0;
|
||||
export function saveStocker(buf: Buffer): SaveStockerRequest {
|
||||
return {
|
||||
type: "save_stocker_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
|
||||
backgrounds: bitmap(buf.slice(0x0008, 0x002c)),
|
||||
selectedCar: buf.readUInt16LE(0x009c) as CarSelector,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { SaveTeamBannerRequest } from "../request/saveTeamBanner";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
|
||||
saveTeamBanner.msgCode = 0x0089 as RequestCode;
|
||||
saveTeamBanner.msgLen = 0x0010;
|
||||
|
||||
export function saveTeamBanner(buf: Buffer): SaveTeamBannerRequest {
|
||||
return {
|
||||
type: "save_team_banner_req",
|
||||
teamExtId: buf.readUInt32LE(0x0004) as ExtId<Team>,
|
||||
nameBg: buf.readUInt32LE(0x0008),
|
||||
nameFx: buf.readUInt32LE(0x000c),
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId, RouteNo } from "../model/base";
|
||||
import { RouteNo } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
saveTimeAttack.msgCode = 0x00cd as RequestCode;
|
||||
saveTimeAttack.msgLen = 0x0080;
|
||||
|
||||
export function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
||||
function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
||||
return {
|
||||
type: "save_time_attack_req",
|
||||
profileId: buf.readUInt32LE(0x0004) as ExtId<Profile>,
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
dayNight: buf.readUInt8(0x0054) & 1,
|
||||
payload: {
|
||||
routeNo: (buf.readUInt8(0x0054) >> 1) as RouteNo,
|
||||
@@ -34,3 +31,21 @@ export function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
||||
field_0060: buf.readUInt16LE(0x0060),
|
||||
};
|
||||
}
|
||||
|
||||
// There is ... literally no difference between these messages other than their
|
||||
// request code..? Even the response uses the same response code, despite
|
||||
// the request codes differing.
|
||||
|
||||
saveTimeAttack1.msgCode = 0x00cd as RequestCode;
|
||||
saveTimeAttack1.msgLen = 0x0080;
|
||||
|
||||
export function saveTimeAttack1(buf: Buffer): SaveTimeAttackRequest {
|
||||
return saveTimeAttack(buf);
|
||||
}
|
||||
|
||||
saveTimeAttack2.msgCode = 0x0136 as RequestCode;
|
||||
saveTimeAttack2.msgLen = 0x0080;
|
||||
|
||||
export function saveTimeAttack2(buf: Buffer): SaveTimeAttackRequest {
|
||||
return saveTimeAttack(buf);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ unlockProfile.msgLen = 0x0020;
|
||||
export function unlockProfile(buf: Buffer): UnlockProfileRequest {
|
||||
return {
|
||||
type: "unlock_profile_req",
|
||||
profileId: buf.readUInt32LE(0x0004),
|
||||
aimeId: buf.readUInt32LE(0x0004),
|
||||
pcbId: buf.slice(0x0008, buf.indexOf("\0", 0x0008)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { UpdateStoryClearNumRequest } from "../request/updateStoryClearNum";
|
||||
import {
|
||||
UpdateStoryClearNumRequest1,
|
||||
UpdateStoryClearNumRequest2,
|
||||
} from "../request/updateStoryClearNum";
|
||||
|
||||
updateStoryClearNum.msgCode = 0x007f as RequestCode;
|
||||
updateStoryClearNum.msgLen = 0x0010;
|
||||
updateStoryClearNum1.msgCode = 0x007f as RequestCode;
|
||||
updateStoryClearNum1.msgLen = 0x0010;
|
||||
|
||||
export function updateStoryClearNum(buf: Buffer): UpdateStoryClearNumRequest {
|
||||
export function updateStoryClearNum1(
|
||||
buf: Buffer
|
||||
): UpdateStoryClearNumRequest1 {
|
||||
return {
|
||||
type: "update_story_clear_num_req",
|
||||
format: 1,
|
||||
};
|
||||
}
|
||||
|
||||
updateStoryClearNum2.msgCode = 0x013d as RequestCode;
|
||||
updateStoryClearNum2.msgLen = 0x0010;
|
||||
|
||||
export function updateStoryClearNum2(
|
||||
buf: Buffer
|
||||
): UpdateStoryClearNumRequest2 {
|
||||
return {
|
||||
type: "update_story_clear_num_req",
|
||||
format: 2,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
updateTeamLeader.msgCode = 0x008a as RequestCode;
|
||||
updateTeamLeader.msgLen = 0x0020;
|
||||
|
||||
export function updateTeamLeader(buf: Buffer): UpdateTeamLeaderRequest {
|
||||
return {
|
||||
type: "update_team_leader_req",
|
||||
aimeId: buf.readUInt32LE(0x0004) as AimeId,
|
||||
teamExtId: buf.readUInt32LE(0x0008) as ExtId<Team>,
|
||||
field_000C: buf.slice(0x000c, buf.indexOf("\0", 0x000c)).toString("ascii"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { RequestCode } from "./_defs";
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
|
||||
import { AimeId } from "../../model";
|
||||
|
||||
updateTeamMember.msgCode = 0x0073 as RequestCode;
|
||||
updateTeamMember.msgLen = 0x0010;
|
||||
|
||||
export function updateTeamMember(buf: Buffer): UpdateTeamMemberRequest {
|
||||
return {
|
||||
type: "update_team_member_req",
|
||||
action: buf.readUInt8(0x0004) === 0 ? "add" : "remove",
|
||||
aimeId: buf.readUInt32LE(0x0008) as AimeId,
|
||||
teamExtId: buf.readUInt32LE(0x000c) as ExtId<Team>,
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export function bitmap(items: Set<number>, nbytes: number): Buffer {
|
||||
export function encodeBitmap(items: Set<number>, nbytes: number): Buffer {
|
||||
const buf = Buffer.alloc(nbytes);
|
||||
|
||||
for (const item of items) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Car } from "../model/car";
|
||||
|
||||
export function car(car: Car): Buffer {
|
||||
export function encodeCar(car: Car): Buffer {
|
||||
const buf = Buffer.alloc(0x0060);
|
||||
|
||||
buf.writeUInt16LE(car.field_00, 0x0000);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Chara } from "../model/chara";
|
||||
|
||||
export function chara(chara: Chara): Buffer {
|
||||
export function encodeChara(chara: Chara): Buffer {
|
||||
const buf = Buffer.alloc(0x0014);
|
||||
|
||||
buf.writeUInt8(chara.gender === "male" ? 0 : 1, 0x00);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MissionGrid } from "../model/mission";
|
||||
|
||||
export function mission(grids: MissionGrid[]): Buffer {
|
||||
export function encodeMission(grids: MissionGrid[]): Buffer {
|
||||
const buf = Buffer.alloc(0x24);
|
||||
|
||||
for (let gridNo = 0; gridNo < grids.length; gridNo++) {
|
||||
|
||||
+38
-22
@@ -1,42 +1,58 @@
|
||||
import iconv = require("iconv-lite");
|
||||
import { JoinAutoTeamResponse } from "../response/joinAutoTeam";
|
||||
import { LoadTeamResponse } from "../response/loadTeam";
|
||||
|
||||
export function _team(res: JoinAutoTeamResponse | LoadTeamResponse) {
|
||||
import { CreateAutoTeamResponse } from "../response/createAutoTeam";
|
||||
import { LoadTeamResponse } from "../response/loadTeam";
|
||||
import { encodeChara } from "./_chara";
|
||||
|
||||
export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
|
||||
const buf = Buffer.alloc(0x0ca0);
|
||||
|
||||
buf.writeUInt32LE(res.team.id, 0x000c);
|
||||
if (res.type === "create_auto_team_res") {
|
||||
buf.writeInt16LE(0x007c, 0x0000);
|
||||
} else {
|
||||
buf.writeInt16LE(0x0078, 0x0000);
|
||||
}
|
||||
|
||||
const leader = res.members.find(item => item.leader);
|
||||
|
||||
buf.writeUInt32LE(res.team.extId, 0x000c);
|
||||
iconv.encode(res.team.name, "shift_jis").copy(buf, 0x0024);
|
||||
buf.writeUInt32LE(res.team.nameBg, 0x00d8);
|
||||
buf.writeUInt32LE(res.team.nameFx, 0x00dc);
|
||||
buf.fill(0xff, 0x00e0, 0x00f9); // Bitset: Unlocked BGs probably
|
||||
buf.fill(0xff, 0x00f9, 0x0101); // Bitset: Unlocked FX probably
|
||||
buf.writeUInt32LE(leader ? leader.profile.aimeId : 0, 0x0080);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const base = 0x011c + i * 0x004c;
|
||||
const base = 0x011c + i * 0x005c;
|
||||
const member = res.members[i];
|
||||
|
||||
if (member === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf.writeInt32LE(1, base + 0x0000); // Presence
|
||||
iconv.encode(member.name + "\0", "shift_jis").copy(buf, base + 0x0004);
|
||||
buf.writeInt32LE(member.lv, base + 0x0018);
|
||||
buf.writeInt32LE(member.monthPoints, base + 0x0024);
|
||||
const { profile, chara } = member;
|
||||
const accessTime = (profile.accessTime.getTime() / 1000) | 0;
|
||||
|
||||
buf.writeInt32LE(profile.aimeId, base + 0x0000);
|
||||
iconv.encode(profile.name + "\0", "shift_jis").copy(buf, base + 0x0004);
|
||||
buf.writeInt32LE(profile.lv, base + 0x0018);
|
||||
buf.writeInt32LE(0, base + 0x0024); // Month points, TODO
|
||||
buf.writeUInt32LE(accessTime, base + 0x0034);
|
||||
encodeChara(chara).copy(buf, base + 0x0044);
|
||||
}
|
||||
|
||||
// xM
|
||||
// Team Time Attack:
|
||||
|
||||
/*
|
||||
buf.writeInt16LE(0x00001, 0x0344 + 0x0000);
|
||||
buf.writeInt8(0x02, 0x0344 + 0x0003);
|
||||
buf.writeInt32LE(0x00000003, 0x0344 + 0x0004);
|
||||
iconv.encode("str\0", sjis).copy(buf, 0x0344 + 0x0008);
|
||||
buf.writeInt32LE(0x00000004, 0x0344 + 0x001c);
|
||||
*/
|
||||
/*for (let i = 0; i < 6; i++) {
|
||||
const base = 0x0344 + 0x20 * i;
|
||||
|
||||
if (res.type === "join_auto_team_res") {
|
||||
buf.writeInt16LE(0x007c, 0x0000);
|
||||
} else {
|
||||
buf.writeInt16LE(0x0078, 0x0000);
|
||||
}
|
||||
buf.writeInt16LE(0x00001, base + 0x0000);
|
||||
buf.writeInt8(0x02, base + 0x0003);
|
||||
buf.writeInt32LE(0x00000003, base + 0x0004);
|
||||
iconv.encode("str\0", "shift_jis").copy(buf, base + 0x0008);
|
||||
buf.writeInt32LE(0x00000004, base + 0x001c);
|
||||
}*/
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function createTeam(res: CreateTeamResponse): Buffer {
|
||||
|
||||
buf.writeUInt16LE(0x0072, 0x0000);
|
||||
buf.writeUInt32LE(res.status, 0x0004);
|
||||
buf.writeUInt32LE(res.teamId, 0x0008);
|
||||
buf.writeUInt32LE(res.teamExtId, 0x0008);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@ import { createTeam } from "./createTeam";
|
||||
import { discoverProfile } from "./discoverProfile";
|
||||
import { generic } from "./generic";
|
||||
import { lockProfile } from "./lockProfile";
|
||||
import { lockProfileExtend } from "./lockProfileExtend";
|
||||
import { load2on2 } from "./load2on2";
|
||||
import { loadConfig } from "./loadConfig";
|
||||
import { loadConfig2 } from "./loadConfig2";
|
||||
import { loadEventInfo } from "./loadEventInfo";
|
||||
import { loadGacha } from "./loadGacha";
|
||||
import { loadGarage } from "./loadGarage";
|
||||
import { loadGeneralReward } from "./loadGeneralReward";
|
||||
import { loadGhost } from "./loadGhost";
|
||||
import { loadProfile } from "./loadProfile";
|
||||
import { loadProfile2 } from "./loadProfile2";
|
||||
import { loadRewardTable } from "./loadRewardTable";
|
||||
import { loadServerList } from "./loadServerList";
|
||||
import { loadStocker } from "./loadStocker";
|
||||
@@ -27,6 +29,8 @@ import { saveTopic } from "./saveTopic";
|
||||
import { unlockProfile } from "./unlockProfile";
|
||||
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
|
||||
import { updateStoryClearNum } from "./updateStoryClearNum";
|
||||
import { updateTeamLeader } from "./updateTeamLeader";
|
||||
import { updateTeamMember } from "./updateTeamMember";
|
||||
import { Response } from "../response";
|
||||
|
||||
function encode(res: Response): Buffer {
|
||||
@@ -34,15 +38,15 @@ function encode(res: Response): Buffer {
|
||||
case "check_team_name_res":
|
||||
return checkTeamName(res);
|
||||
|
||||
case "create_auto_team_res":
|
||||
return _team(res);
|
||||
|
||||
case "create_team_res":
|
||||
return createTeam(res);
|
||||
|
||||
case "discover_profile_res":
|
||||
return discoverProfile(res);
|
||||
|
||||
case "join_auto_team_res":
|
||||
return _team(res);
|
||||
|
||||
case "generic_res":
|
||||
return generic(res);
|
||||
|
||||
@@ -55,6 +59,12 @@ function encode(res: Response): Buffer {
|
||||
case "load_config_v2_res":
|
||||
return loadConfig2(res);
|
||||
|
||||
case "load_event_info_res":
|
||||
return loadEventInfo(res);
|
||||
|
||||
case "load_gacha_res":
|
||||
return loadGacha(res);
|
||||
|
||||
case "load_garage_res":
|
||||
return loadGarage(res);
|
||||
|
||||
@@ -64,12 +74,9 @@ function encode(res: Response): Buffer {
|
||||
case "load_ghost_res":
|
||||
return loadGhost(res);
|
||||
|
||||
case "load_profile_v1_res":
|
||||
case "load_profile_res":
|
||||
return loadProfile(res);
|
||||
|
||||
case "load_profile_v2_res":
|
||||
return loadProfile2(res);
|
||||
|
||||
case "load_reward_table_res":
|
||||
return loadRewardTable(res);
|
||||
|
||||
@@ -88,6 +95,9 @@ function encode(res: Response): Buffer {
|
||||
case "load_top_ten_res":
|
||||
return loadTopTen(res);
|
||||
|
||||
case "lock_profile_extend_res":
|
||||
return lockProfileExtend(res);
|
||||
|
||||
case "lock_profile_res":
|
||||
return lockProfile(res);
|
||||
|
||||
@@ -112,6 +122,12 @@ function encode(res: Response): Buffer {
|
||||
case "update_story_clear_num_res":
|
||||
return updateStoryClearNum(res);
|
||||
|
||||
case "update_team_leader_res":
|
||||
return updateTeamLeader(res);
|
||||
|
||||
case "update_team_member_res":
|
||||
return updateTeamMember(res);
|
||||
|
||||
case "save_topic_res":
|
||||
return saveTopic(res);
|
||||
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
import { Load2on2Response } from "../response/load2on2";
|
||||
import {
|
||||
Load2on2Response,
|
||||
Load2on2Response1,
|
||||
Load2on2Response2,
|
||||
} from "../response/load2on2";
|
||||
|
||||
export function load2on2(res: Load2on2Response) {
|
||||
export function load2on2(res: Load2on2Response): Buffer {
|
||||
switch (res.format) {
|
||||
case 1:
|
||||
return load2on2_v1(res);
|
||||
|
||||
case 2:
|
||||
return load2on2_v2(res);
|
||||
|
||||
default:
|
||||
const exhaust: never = res;
|
||||
|
||||
throw new Error(`Unsupported 2on2 response format ${res["format"]}`);
|
||||
}
|
||||
}
|
||||
|
||||
function load2on2_v1(res: Load2on2Response1): Buffer {
|
||||
const buf = Buffer.alloc(0x04c0);
|
||||
|
||||
buf.writeInt16LE(0x00b1, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Same size but presumably incompatible somehow
|
||||
function load2on2_v2(res: Load2on2Response2): Buffer {
|
||||
const buf = Buffer.alloc(0x04c0);
|
||||
|
||||
buf.writeInt16LE(0x0133, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export function loadConfig(res: LoadConfigResponse) {
|
||||
|
||||
buf.writeInt16LE(0x0005, 0x0000);
|
||||
buf.writeInt8(res.status, 0x0002);
|
||||
buf.writeUInt16LE(res.serverVersion, 0x0016);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LoadEventInfoResponse } from "../response/loadEventInfo";
|
||||
|
||||
export function loadEventInfo(res: LoadEventInfoResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x01b0);
|
||||
|
||||
buf.writeUInt16LE(0x00bf, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LoadGachaResponse } from "../response/loadGacha";
|
||||
|
||||
export function loadGacha(res: LoadGachaResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0090);
|
||||
|
||||
buf.writeUInt16LE(0x00c2, 0x0000);
|
||||
buf.writeUInt8(res.awardedToday ? 0x01 : 0x00, 0x0002);
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { car } from "./_car";
|
||||
import { encodeCar } from "./_car";
|
||||
import { LoadGarageResponse } from "../response/loadGarage";
|
||||
|
||||
export function loadGarage(res: LoadGarageResponse): Buffer {
|
||||
@@ -8,7 +8,7 @@ export function loadGarage(res: LoadGarageResponse): Buffer {
|
||||
buf.writeUInt16LE(res.cars.length, 0x0002);
|
||||
|
||||
for (let i = 0; i < res.cars.length; i++) {
|
||||
car(res.cars[i]).copy(buf, 0x0004 + 0x0060 * i);
|
||||
encodeCar(res.cars[i]).copy(buf, 0x0004 + 0x0060 * i);
|
||||
}
|
||||
|
||||
return buf;
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { loadProfile1 } from "./loadProfile1";
|
||||
import { loadProfile2 } from "./loadProfile2";
|
||||
import { loadProfile3 } from "./loadProfile3";
|
||||
import { LoadProfileResponse } from "../response/loadProfile";
|
||||
|
||||
// Sending this causes an error
|
||||
export function loadProfile(res: LoadProfileResponse) {
|
||||
const buf = Buffer.alloc(0x0c60);
|
||||
switch (res.format) {
|
||||
case 1:
|
||||
return loadProfile1(res);
|
||||
|
||||
buf.writeInt16LE(0x0064, 0x0000);
|
||||
case 2:
|
||||
return loadProfile2(res);
|
||||
|
||||
return buf;
|
||||
case 3:
|
||||
return loadProfile3(res);
|
||||
|
||||
default:
|
||||
const exhaust: never = res;
|
||||
|
||||
throw new Error(`Unsupported profile response format ${res["format"]}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { LoadProfileResponse1 } from "../response/loadProfile";
|
||||
|
||||
// Sending this causes an error in v1.21, so it is currently unmapped and
|
||||
// unimplemented.
|
||||
|
||||
export function loadProfile1(res: LoadProfileResponse1) {
|
||||
const buf = Buffer.alloc(0x0c60);
|
||||
|
||||
buf.writeInt16LE(0x0064, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import iconv = require("iconv-lite");
|
||||
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { car } from "./_car";
|
||||
import { chara } from "./_chara";
|
||||
import { mission } from "./_mission";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile2";
|
||||
import { encodeBitmap } from "./_bitmap";
|
||||
import { encodeCar } from "./_car";
|
||||
import { encodeChara } from "./_chara";
|
||||
import { encodeMission } from "./_mission";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile";
|
||||
|
||||
export function loadProfile2(res: LoadProfileResponse2) {
|
||||
const buf = Buffer.alloc(0x0d30);
|
||||
@@ -53,7 +53,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
|
||||
for (const [courseId, playCount] of res.coursePlays.entries()) {
|
||||
if (courseId < 0 || courseId >= 16) {
|
||||
throw new Error(`Course id out of range: ${courseId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(playCount, 0x0460 + 2 * courseId);
|
||||
@@ -75,10 +75,10 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8);
|
||||
buf.writeUInt16LE(res.unlocks.music, 0x01ec);
|
||||
buf.writeUInt16LE(0, 0x037c); // Team leader
|
||||
mission(res.missions.team).copy(buf, 0x038a);
|
||||
buf.writeUInt16LE(res.teamLeader ? 1 : 0, 0x037c);
|
||||
encodeMission(res.missions.team).copy(buf, 0x038a);
|
||||
buf.writeUInt16LE(0xffff, 0x0388); // [1]
|
||||
buf.writeUInt32LE(res.profileId, 0x03b8);
|
||||
buf.writeUInt32LE(res.aimeId, 0x03b8);
|
||||
buf.writeUInt32LE(res.mileage, 0x03bc);
|
||||
buf.writeUInt16LE(res.settings.music, 0x03c8);
|
||||
buf.writeUInt16LE(res.lv, 0x03cc);
|
||||
@@ -87,15 +87,15 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
||||
buf.writeUInt32LE(res.dpoint, 0x03e8);
|
||||
buf.writeUInt32LE(res.fame, 0x0404);
|
||||
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x03ee);
|
||||
buf.writeUInt16LE(res.story.x, 0x06bc);
|
||||
buf.writeUInt8(res.story.y, 0x0670);
|
||||
mission(res.missions.solo).copy(buf, 0x06e4);
|
||||
chara(res.chara).copy(buf, 0x070c);
|
||||
bitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||
buf.writeUInt16LE(res.story.x, 0x06bc);
|
||||
encodeMission(res.missions.solo).copy(buf, 0x06e4);
|
||||
encodeChara(res.chara).copy(buf, 0x070c);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x07d9);
|
||||
buf.writeUInt8(res.settings.gauges, 0x07da);
|
||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x07e0);
|
||||
car(res.car).copy(buf, 0x0c5c);
|
||||
encodeCar(res.car).copy(buf, 0x0c5c);
|
||||
buf.writeUInt32LE(res.carCount, 0x0c58);
|
||||
|
||||
// [1] Currently unknown, but if this field is zero then the player will have
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import iconv = require("iconv-lite");
|
||||
|
||||
import { encodeBitmap } from "./_bitmap";
|
||||
import { encodeCar } from "./_car";
|
||||
import { encodeChara } from "./_chara";
|
||||
import { encodeMission } from "./_mission";
|
||||
import { LoadProfileResponse3 } from "../response/loadProfile";
|
||||
|
||||
export function loadProfile3(res: LoadProfileResponse3) {
|
||||
const buf = Buffer.alloc(0x0ea0);
|
||||
|
||||
// Initialize all TA grades to uhh... fuck knows
|
||||
buf.fill(0xff, 0x07e4, 0x080c);
|
||||
|
||||
for (const score of res.timeAttack) {
|
||||
const { routeNo } = score;
|
||||
|
||||
buf.writeUInt32LE(
|
||||
(new Date(score.timestamp).getTime() / 1000) | 0, // Date ctor hack
|
||||
0x00e4 + routeNo * 4
|
||||
);
|
||||
|
||||
buf.writeUInt16LE(0, 0x067c + 2 * routeNo); // ???
|
||||
buf.writeUInt16LE(0xffff, 0x0184 + 2 * routeNo); // National rank
|
||||
buf.writeUInt32LE((score.totalTime * 1000) | 0, 0x05dc + 4 * routeNo);
|
||||
buf.writeUInt8(score.flags, 0x06cc + routeNo);
|
||||
buf.writeUInt8(score.grade, 0x07e4 + routeNo);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
buf.writeUInt16LE(
|
||||
(score.sectionTimes[i] * 1000) >> 2,
|
||||
0x06f4 + 6 * routeNo + 2 * i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Not sure it actually goes up to 27, but there seem to be 512 bytes of
|
||||
// space for story cells, and 27 rows * 19 bytes per row = 513 bytes, which
|
||||
// is the max that will fit (the final byte of each row is unused).
|
||||
|
||||
for (let i = 0; i < 27 && i < res.story.rows.length; i++) {
|
||||
const row = res.story.rows[i];
|
||||
const rowOffset = 0x0256 + i * 0x13;
|
||||
|
||||
for (let j = 0; j < 9 && j < row.cells.length; j++) {
|
||||
const cell = row.cells[j];
|
||||
const cellOffset = rowOffset + j * 2;
|
||||
|
||||
buf.writeUInt8(cell.a, cellOffset + 0);
|
||||
buf.writeUInt8(cell.b, cellOffset + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [courseId, playCount] of res.coursePlays.entries()) {
|
||||
if (courseId < 0 || courseId >= 20) {
|
||||
throw new Error(`Course id out of range: ${courseId}`);
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(playCount, 0x053c + 2 * courseId);
|
||||
}
|
||||
|
||||
const { freeCar, freeContinue } = res.tickets;
|
||||
|
||||
if (freeCar) {
|
||||
buf.writeUInt32LE((freeCar.validFrom.getTime() / 1000) | 0, 0x0214);
|
||||
}
|
||||
|
||||
if (freeContinue) {
|
||||
buf.writeUInt32LE((freeContinue.validFrom.getTime() / 1000) | 0, 0x04b8);
|
||||
buf.writeUInt32LE((freeContinue.validTo.getTime() / 1000) | 0, 0x04bc);
|
||||
}
|
||||
|
||||
buf.writeUInt16LE(0x012e, 0x0000);
|
||||
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
|
||||
buf.writeUInt16LE(res.unlocks.music, 0x021c);
|
||||
buf.writeUInt16LE(res.teamLeader ? 1 : 0, 0x0456); // Team leader
|
||||
encodeMission(res.missions.team).copy(buf, 0x0460);
|
||||
buf.writeUInt16LE(0xffff, 0x0462); // [1]
|
||||
buf.writeUInt32LE(res.aimeId, 0x0494);
|
||||
buf.writeUInt32LE(res.mileage, 0x0498);
|
||||
buf.writeUInt16LE(res.settings.music, 0x04a4);
|
||||
buf.writeUInt16LE(res.lv, 0x04a8);
|
||||
buf.writeUInt32LE(res.exp, 0x04ac);
|
||||
buf.writeUInt32LE(res.settings.pack, 0x04b4);
|
||||
buf.writeUInt32LE(res.dpoint, 0x04c4);
|
||||
buf.writeUInt32LE(res.fame, 0x04e0);
|
||||
iconv.encode(res.name + "\0", "shift_jis").copy(buf, 0x04ca);
|
||||
buf.writeUInt8(res.story.y, 0x080c);
|
||||
buf.writeUInt16LE(res.story.x, 0x0828);
|
||||
encodeMission(res.missions.solo).copy(buf, 0x0858);
|
||||
encodeChara(res.chara).copy(buf, 0x0880);
|
||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
|
||||
buf.writeUInt8(res.settings.paperCup, 0x094d);
|
||||
buf.writeUInt8(res.settings.gauges, 0x094e);
|
||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x0954);
|
||||
buf.writeUInt32LE(res.carCount, 0x0dd0);
|
||||
encodeCar(res.car).copy(buf, 0x0dd4);
|
||||
|
||||
// [1] Currently unknown, but if this field is zero then the player will have
|
||||
// a "model record" emblem in their profile card.
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { bitmap } from "./_bitmap";
|
||||
import { encodeBitmap } from "./_bitmap";
|
||||
import { LoadStockerResponse } from "../response/loadStocker";
|
||||
|
||||
export function loadStocker(res: LoadStockerResponse) {
|
||||
@@ -6,7 +6,7 @@ export function loadStocker(res: LoadStockerResponse) {
|
||||
|
||||
buf.writeInt16LE(0x00a8, 0x0000);
|
||||
buf.writeUInt8(res.status, 0x0002);
|
||||
bitmap(res.backgrounds, 0x24).copy(buf, 0x0003);
|
||||
encodeBitmap(res.backgrounds, 0x24).copy(buf, 0x0003);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LockProfileExtendResponse } from "../response/lockProfileExtend";
|
||||
|
||||
export function lockProfileExtend(res: LockProfileExtendResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x006e, 0x0000);
|
||||
buf.writeUInt8(res.status, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -1,6 +1,25 @@
|
||||
import { SaveExpeditionResponse } from "../response/saveExpedition";
|
||||
import {
|
||||
SaveExpeditionResponse,
|
||||
SaveExpeditionResponse1,
|
||||
SaveExpeditionResponse2,
|
||||
} from "../response/saveExpedition";
|
||||
|
||||
export function saveExpedition(res: SaveExpeditionResponse) {
|
||||
export function saveExpedition(res: SaveExpeditionResponse): Buffer {
|
||||
switch (res.format) {
|
||||
case 1:
|
||||
return saveExpedition1(res);
|
||||
|
||||
case 2:
|
||||
return saveExpedition2(res);
|
||||
|
||||
default:
|
||||
const exhaust: never = res;
|
||||
|
||||
throw new Error(`Unsupported data format ${res["format"]}`);
|
||||
}
|
||||
}
|
||||
|
||||
function saveExpedition1(res: SaveExpeditionResponse1): Buffer {
|
||||
// in awe of the size of this lad
|
||||
const buf = Buffer.alloc(0x17c0);
|
||||
|
||||
@@ -8,3 +27,12 @@ export function saveExpedition(res: SaveExpeditionResponse) {
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
function saveExpedition2(res: SaveExpeditionResponse2): Buffer {
|
||||
// absolute unit
|
||||
const buf = Buffer.alloc(0x18ac);
|
||||
|
||||
buf.writeUInt16LE(0x0140, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
import { UpdateStoryClearNumResponse } from "../response/updateStoryClearNum";
|
||||
import {
|
||||
UpdateStoryClearNumResponse,
|
||||
UpdateStoryClearNumResponse1,
|
||||
UpdateStoryClearNumResponse2,
|
||||
} from "../response/updateStoryClearNum";
|
||||
|
||||
export function updateStoryClearNum(res: UpdateStoryClearNumResponse) {
|
||||
export function updateStoryClearNum(res: UpdateStoryClearNumResponse): Buffer {
|
||||
switch (res.format) {
|
||||
case 1:
|
||||
return updateStoryClearNum1(res);
|
||||
|
||||
case 2:
|
||||
return updateStoryClearNum2(res);
|
||||
|
||||
default:
|
||||
const exhaust: never = res;
|
||||
|
||||
throw new Error(`Unsupported data format ${res["format"]}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateStoryClearNum1(res: UpdateStoryClearNumResponse1): Buffer {
|
||||
const buf = Buffer.alloc(0x0220);
|
||||
|
||||
buf.writeInt16LE(0x0080, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
function updateStoryClearNum2(res: UpdateStoryClearNumResponse2): Buffer {
|
||||
const buf = Buffer.alloc(0x04f0);
|
||||
|
||||
buf.writeInt16LE(0x013e, 0x0000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { UpdateTeamLeaderResponse } from "../response/updateTeamLeader";
|
||||
|
||||
export function updateTeamLeader(res: UpdateTeamLeaderResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x008b, 0x0000);
|
||||
buf.writeUInt32LE(res.status, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { UpdateTeamMemberResponse } from "../response/updateTeamMember";
|
||||
|
||||
export function updateTeamMember(res: UpdateTeamMemberResponse): Buffer {
|
||||
const buf = Buffer.alloc(0x0010);
|
||||
|
||||
buf.writeUInt16LE(0x0074, 0x0000);
|
||||
buf.writeUInt32LE(res.status, 0x0004);
|
||||
|
||||
return buf;
|
||||
}
|
||||
+26
-19
@@ -1,26 +1,33 @@
|
||||
import { ExtId } from "../model/base";
|
||||
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 { Id } from "../../db";
|
||||
|
||||
export function _team(
|
||||
// Bleh. This factorization is kind of messy.
|
||||
|
||||
export async function _fixupPrevTeam(
|
||||
w: Repositories,
|
||||
req: JoinAutoTeamRequest | LoadTeamRequest
|
||||
): JoinAutoTeamResponse | LoadTeamResponse {
|
||||
const bits = {
|
||||
team: {
|
||||
id: 2 as ExtId<Team>,
|
||||
name: process.env.TEAM_NAME || "",
|
||||
},
|
||||
members: [],
|
||||
};
|
||||
prevTeamId: Id<Team> | undefined
|
||||
): Promise<void> {
|
||||
if (prevTeamId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.type === "join_auto_team_req") {
|
||||
return { type: "join_auto_team_res", ...bits };
|
||||
} else {
|
||||
return { type: "load_team_res", ...bits };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import { ExtId } from "../model/base";
|
||||
import { MissionState } from "../model/mission";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Settings } from "../model/settings";
|
||||
import { Story } from "../model/story";
|
||||
import { Team } from "../model/team";
|
||||
import { Unlocks } from "../model/unlocks";
|
||||
import { CreateProfileRequest } from "../request/createProfile";
|
||||
import { GenericResponse } from "../response/generic";
|
||||
import { ProfileSpec, Repositories } from "../repo";
|
||||
import { Repositories } from "../repo";
|
||||
|
||||
export async function createProfile(
|
||||
w: Repositories,
|
||||
req: CreateProfileRequest
|
||||
): Promise<GenericResponse> {
|
||||
const { aimeId, name } = req;
|
||||
const now = new Date();
|
||||
const profile: ProfileSpec = {
|
||||
teamId: 2 as ExtId<Team>, // TODO
|
||||
name: req.name,
|
||||
|
||||
const profile: Profile = {
|
||||
aimeId,
|
||||
name,
|
||||
lv: 1,
|
||||
exp: 0,
|
||||
fame: 0,
|
||||
dpoint: 0,
|
||||
mileage: 0,
|
||||
accessTime: now,
|
||||
registerTime: now,
|
||||
};
|
||||
|
||||
const missions: MissionState = { team: [], solo: [] };
|
||||
@@ -33,7 +36,7 @@ export async function createProfile(
|
||||
lastMileageReward: 0,
|
||||
};
|
||||
|
||||
const profileId = await w.profile().create(req.aimeId, profile, now);
|
||||
const profileId = await w.profile().create(profile);
|
||||
|
||||
await w.chara().save(profileId, req.chara);
|
||||
await w.car().saveCar(profileId, req.car);
|
||||
@@ -44,8 +47,10 @@ export async function createProfile(
|
||||
await w.unlocks().save(profileId, unlocks);
|
||||
await w.tickets().save(profileId, {});
|
||||
|
||||
await w.teamReservations().commitHack(aimeId);
|
||||
|
||||
return {
|
||||
type: "generic_res",
|
||||
status: profileId, // "Generic response" my fucking *ass*
|
||||
status: aimeId, // "Generic response" my fucking *ass*
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,58 @@
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { _fixupPrevTeam } from "./_team";
|
||||
import { CreateTeamRequest } from "../request/createTeam";
|
||||
import { CreateTeamResponse } from "../response/createTeam";
|
||||
import { Repositories } from "../repo";
|
||||
|
||||
export function createTeam(
|
||||
export async function createTeam(
|
||||
w: Repositories,
|
||||
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 {
|
||||
type: "create_team_res",
|
||||
status: 0,
|
||||
teamId: 3 as ExtId<Team>,
|
||||
teamExtId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ export async function discoverProfile(
|
||||
w: Repositories,
|
||||
req: DiscoverProfileRequest
|
||||
): Promise<DiscoverProfileResponse> {
|
||||
const profileId = await w.profile().peek(req.aimeId);
|
||||
|
||||
return {
|
||||
type: "discover_profile_res",
|
||||
exists: await w.profile().discoverByAimeId(req.aimeId),
|
||||
exists: profileId !== undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { _team } from "./_team";
|
||||
import { checkTeamName } from "./checkTeamName";
|
||||
import { createAutoTeam } from "./createAutoTeam";
|
||||
import { createProfile } from "./createProfile";
|
||||
import { createTeam } from "./createTeam";
|
||||
import { discoverProfile } from "./discoverProfile";
|
||||
import { load2on2 } from "./load2on2";
|
||||
import { loadConfig } from "./loadConfig";
|
||||
import { loadConfig2 } from "./loadConfig2";
|
||||
import { loadEventInfo } from "./loadEventInfo";
|
||||
import { loadGacha } from "./loadGacha";
|
||||
import { loadGarage } from "./loadGarage";
|
||||
import { loadGeneralReward } from "./loadGeneralReward";
|
||||
import { loadGhost } from "./loadGhost";
|
||||
@@ -13,10 +15,12 @@ import { loadProfile } from "./loadProfile";
|
||||
import { loadReward as loadRewardTable } from "./loadRewardTable";
|
||||
import { loadServerList } from "./loadServerList";
|
||||
import { loadStocker } from "./loadStocker";
|
||||
import { loadTeam } from "./loadTeam";
|
||||
import { loadTeamRanking } from "./loadTeamRanking";
|
||||
import { loadTopTen } from "./loadTopTen";
|
||||
import { lockGarage } from "./lockGarage";
|
||||
import { lockProfile } from "./lockProfile";
|
||||
import { lockProfileExtend } from "./lockProfileExtend";
|
||||
import { msg00AD } from "./msg00AD";
|
||||
import { saveExpedition } from "./saveExpedition";
|
||||
import { saveGarage } from "./saveGarage";
|
||||
@@ -24,12 +28,15 @@ import { saveNewCar } from "./saveNewCar";
|
||||
import { saveProfile } from "./saveProfile";
|
||||
import { saveSettings } from "./saveSettings";
|
||||
import { saveStocker } from "./saveStocker";
|
||||
import { saveTeamBanner } from "./saveTeamBanner";
|
||||
import { saveTimeAttack } from "./saveTimeAttack";
|
||||
import { saveTopic } from "./saveTopic";
|
||||
import { unlockProfile } from "./unlockProfile";
|
||||
import { updateProvisionalStoreRank } from "./updateProvisionalStoreRank";
|
||||
import { updateResult } from "./updateResult";
|
||||
import { updateStoryClearNum } from "./updateStoryClearNum";
|
||||
import { updateTeamLeader } from "./updateTeamLeader";
|
||||
import { updateTeamMember } from "./updateTeamMember";
|
||||
import { updateTeamPoints } from "./updateTeamPoints";
|
||||
import { updateUiReport } from "./updateUiReport";
|
||||
import { updateUserLog } from "./updateUserLog";
|
||||
@@ -45,15 +52,15 @@ export async function dispatch(
|
||||
case "check_team_name_req":
|
||||
return checkTeamName(w, req);
|
||||
|
||||
case "create_auto_team_req":
|
||||
return createAutoTeam(w, req);
|
||||
|
||||
case "create_profile_req":
|
||||
return createProfile(w, req);
|
||||
|
||||
case "create_team_req":
|
||||
return createTeam(w, req);
|
||||
|
||||
case "join_auto_team_req":
|
||||
return _team(w, req);
|
||||
|
||||
case "load_2on2_req":
|
||||
return load2on2(w, req);
|
||||
|
||||
@@ -66,6 +73,12 @@ export async function dispatch(
|
||||
case "discover_profile_req":
|
||||
return discoverProfile(w, req);
|
||||
|
||||
case "load_event_info_req":
|
||||
return loadEventInfo(w, req);
|
||||
|
||||
case "load_gacha_req":
|
||||
return loadGacha(w, req);
|
||||
|
||||
case "load_garage_req":
|
||||
return loadGarage(w, req);
|
||||
|
||||
@@ -88,7 +101,7 @@ export async function dispatch(
|
||||
return loadStocker(w, req);
|
||||
|
||||
case "load_team_req":
|
||||
return _team(w, req);
|
||||
return loadTeam(w, req);
|
||||
|
||||
case "load_top_ten_req":
|
||||
return loadTopTen(w, req);
|
||||
@@ -96,6 +109,9 @@ export async function dispatch(
|
||||
case "lock_garage_request":
|
||||
return lockGarage(w, req);
|
||||
|
||||
case "lock_profile_extend_req":
|
||||
return lockProfileExtend(w, req);
|
||||
|
||||
case "lock_profile_req":
|
||||
return lockProfile(w, req);
|
||||
|
||||
@@ -126,6 +142,9 @@ export async function dispatch(
|
||||
case "save_stocker_req":
|
||||
return saveStocker(w, req);
|
||||
|
||||
case "save_team_banner_req":
|
||||
return saveTeamBanner(w, req);
|
||||
|
||||
case "save_time_attack_req":
|
||||
return saveTimeAttack(w, req);
|
||||
|
||||
@@ -141,6 +160,12 @@ export async function dispatch(
|
||||
case "update_story_clear_num_req":
|
||||
return updateStoryClearNum(w, req);
|
||||
|
||||
case "update_team_leader_req":
|
||||
return updateTeamLeader(w, req);
|
||||
|
||||
case "update_team_member_req":
|
||||
return updateTeamMember(w, req);
|
||||
|
||||
case "update_team_points_req":
|
||||
return updateTeamPoints(w, req);
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ export function load2on2(
|
||||
): Load2on2Response {
|
||||
return {
|
||||
type: "load_2on2_res",
|
||||
format: req.format as any,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ export function loadConfig(
|
||||
return {
|
||||
type: "load_config_res",
|
||||
status: 1,
|
||||
serverVersion: 130,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
||||
import { LoadEventInfoResponse } from "../response/loadEventInfo";
|
||||
|
||||
export function loadEventInfo(
|
||||
w: Repositories,
|
||||
req: LoadEventInfoRequest
|
||||
): LoadEventInfoResponse {
|
||||
return {
|
||||
type: "load_event_info_res",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { LoadGachaRequest } from "../request/loadGacha";
|
||||
import { LoadGachaResponse } from "../response/loadGacha";
|
||||
|
||||
export function loadGacha(
|
||||
w: Repositories,
|
||||
req: LoadGachaRequest
|
||||
): LoadGachaResponse {
|
||||
return {
|
||||
type: "load_gacha_res",
|
||||
awardedToday: true, // Disable for now, not even mapped out yet.
|
||||
};
|
||||
}
|
||||
@@ -6,8 +6,10 @@ export async function loadGarage(
|
||||
w: Repositories,
|
||||
req: LoadGarageRequest
|
||||
): Promise<LoadGarageResponse> {
|
||||
const profileId = await w.profile().find(req.aimeId);
|
||||
|
||||
return {
|
||||
type: "load_garage_res",
|
||||
cars: await w.car().loadAllCars(req.profileId),
|
||||
cars: await w.car().loadAllCars(profileId),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { LoadGeneralRewardRequest } from "../request/loadGeneralReward";
|
||||
import { GenericResponse } from "../response/generic";
|
||||
import { LoadGeneralRewardResponse } from "../response/loadGeneralReward";
|
||||
import { Repositories } from "../repo";
|
||||
|
||||
export function loadGeneralReward(
|
||||
w: Repositories,
|
||||
req: LoadGeneralRewardRequest
|
||||
): LoadGeneralRewardResponse | GenericResponse {
|
||||
// A non-generic response is also accepted, but why bother
|
||||
): GenericResponse {
|
||||
// A version-specific response is also accepted. Format TBD.
|
||||
return { type: "generic_res" };
|
||||
}
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import { LoadProfileRequest } from "../request/loadProfile";
|
||||
import { LoadProfileResponse2 } from "../response/loadProfile2";
|
||||
import { LoadProfileResponse } from "../response/loadProfile";
|
||||
import { Repositories } from "../repo";
|
||||
|
||||
export async function loadProfile(
|
||||
w: Repositories,
|
||||
req: LoadProfileRequest
|
||||
): Promise<LoadProfileResponse2> {
|
||||
): Promise<LoadProfileResponse> {
|
||||
const { aimeId } = req;
|
||||
|
||||
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
|
||||
// be a high-performance server.
|
||||
|
||||
const profile = await w.profile().loadByAimeId(req.aimeId);
|
||||
const settings = await w.settings().load(profile.id);
|
||||
const chara = await w.chara().load(profile.id);
|
||||
const titles = await w.titles().loadAll(profile.id);
|
||||
const coursePlays = await w.coursePlays().loadAll(profile.id);
|
||||
const missions = await w.missions().load(profile.id);
|
||||
const car = await w.car().loadSelectedCar(profile.id);
|
||||
const carCount = await w.car().countCars(profile.id);
|
||||
const story = await w.story().load(profile.id);
|
||||
const timeAttack = await w.timeAttack().loadAll(profile.id);
|
||||
const unlocks = await w.unlocks().load(profile.id);
|
||||
const tickets = await w.tickets().load(profile.id);
|
||||
const profile = await w.profile().load(profileId);
|
||||
const settings = await w.settings().load(profileId);
|
||||
const chara = await w.chara().load(profileId);
|
||||
const titles = await w.titles().loadAll(profileId);
|
||||
const coursePlays = await w.coursePlays().loadAll(profileId);
|
||||
const missions = await w.missions().load(profileId);
|
||||
const car = await w.car().loadSelectedCar(profileId);
|
||||
const carCount = await w.car().countCars(profileId);
|
||||
const story = await w.story().load(profileId);
|
||||
const timeAttack = await w.timeAttack().loadAll(profileId);
|
||||
const unlocks = await w.unlocks().load(profileId);
|
||||
const tickets = await w.tickets().load(profileId);
|
||||
const team = teamId && (await w.teams().load(teamId));
|
||||
|
||||
return {
|
||||
type: "load_profile_v2_res",
|
||||
type: "load_profile_res",
|
||||
format: req.format as any, // TS fart
|
||||
name: profile.name,
|
||||
profileId: profile.id,
|
||||
aimeId,
|
||||
lv: profile.lv,
|
||||
exp: profile.exp,
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
teamId: profile.teamId,
|
||||
teamId: team && team.extId,
|
||||
teamLeader: profileId === leaderId,
|
||||
settings,
|
||||
chara,
|
||||
titles,
|
||||
|
||||
@@ -6,7 +6,8 @@ export async function loadStocker(
|
||||
w: Repositories,
|
||||
req: LoadStockerRequest
|
||||
): Promise<LoadStockerResponse> {
|
||||
const backgrounds = await w.backgrounds().loadAll(req.profileId);
|
||||
const profileId = await w.profile().find(req.aimeId);
|
||||
const backgrounds = await w.backgrounds().loadAll(profileId);
|
||||
|
||||
return {
|
||||
type: "load_stocker_res",
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
LoadTopTenResponseCourse,
|
||||
LoadTopTenResponseRow,
|
||||
} from "../response/loadTopTen";
|
||||
import { Repositories } from "../repo";
|
||||
import { Repositories, TopTenResult } from "../repo";
|
||||
|
||||
export async function loadTopTen(
|
||||
w: Repositories,
|
||||
@@ -17,12 +17,13 @@ export async function loadTopTen(
|
||||
break;
|
||||
}
|
||||
|
||||
if (selector.field_44 === 0) {
|
||||
const { routeNo, minTimestamp } = selector;
|
||||
const src = await w.timeAttack().loadTopTen(routeNo, minTimestamp);
|
||||
|
||||
if (src.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { routeNo } = selector;
|
||||
const src = await w.timeAttack().loadTopTen(routeNo);
|
||||
const dest = new Array<LoadTopTenResponseRow>();
|
||||
|
||||
for (const srcItem of src) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { LockAccountRequest } from "../request/lockProfile";
|
||||
import { LockProfileRequest } from "../request/lockProfile";
|
||||
import { LockProfileResponse } from "../response/lockProfile";
|
||||
import { Repositories } from "../repo";
|
||||
|
||||
export function lockProfile(
|
||||
w: Repositories,
|
||||
req: LockAccountRequest
|
||||
req: LockProfileRequest
|
||||
): LockProfileResponse {
|
||||
return {
|
||||
type: "lock_profile_res",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
||||
import { LockProfileExtendResponse } from "../response/lockProfileExtend";
|
||||
|
||||
export function lockProfileExtend(
|
||||
w: Repositories,
|
||||
req: LockProfileExtendRequest
|
||||
): LockProfileExtendResponse {
|
||||
return {
|
||||
type: "lock_profile_extend_res",
|
||||
status: 1,
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export function saveExpedition(
|
||||
} else {
|
||||
return {
|
||||
type: "save_expedition_res",
|
||||
format: req.format as any,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user