diff --git a/src/idz/db/timeAttack.ts b/src/idz/db/timeAttack.ts new file mode 100644 index 0000000..963260d --- /dev/null +++ b/src/idz/db/timeAttack.ts @@ -0,0 +1,90 @@ +import { Client } from "pg"; +import * as sql from "sql-bricks"; + +import { _findProfile } from "./_util"; +import { ExtId } from "../model/base"; +import { Profile } from "../model/profile"; +import { TimeAttackScore } from "../model/timeAttack"; +import { TimeAttackRepository } from "../repo"; +import { generateId } from "../../db"; + +export class SqlTimeAttackRepository implements TimeAttackRepository { + constructor(private readonly _conn: Client) {} + + async loadAll(extId: ExtId): Promise { + const loadSql = sql + .select("ta.*") + .from("idz.time_attack ta") + .join("idz.profile p", { "ta.profile_id": "p.id" }) + .where("p.ext_id", extId) + .toParams(); + + const { rows } = await this._conn.query(loadSql); + + return rows.map(row => ({ + routeNo: row.route_no, + timestamp: new Date(row.timestamp), + flags: row.flags, + totalTime: row.total_time, + sectionTimes: row.section_times, + grade: row.grade, + })); + } + + async save(extId: ExtId, score: TimeAttackScore): Promise { + const profileId = await _findProfile(this._conn, extId); + + const logSql = sql + .insert("idz.ta_result", { + id: generateId(), + profile_id: profileId, + route_no: score.routeNo, + total_time: score.totalTime, + section_times: score.sectionTimes, + flags: score.flags, + grade: score.grade, + }) + .toParams(); + + await this._conn.query(logSql); + + const existSql = sql + .select("ta.total_time") + .from("idz.ta_best ta") + .where("ta.profile_id", profileId) + .where("ta.route_no", score.routeNo) + .toParams(); + + const { rows } = await this._conn.query(existSql); + const row = rows[0]; + + if (row === undefined) { + const insertSql = sql + .insert("idz.ta_best", { + id: generateId(), + profile_id: profileId, + route_no: score.routeNo, + total_time: score.totalTime, + section_times: score.sectionTimes, + flags: score.flags, + grade: score.grade, + }) + .toParams(); + + await this._conn.query(insertSql); + } else if (row.total_time > score.totalTime) { + const updateSql = sql + .update("idz.ta_best", { + total_time: score.totalTime, + section_times: score.sectionTimes, + flags: score.flags, + grade: score.grade, + }) + .where("profile_id", profileId) + .where("route_no", score.routeNo) + .toParams(); + + await this._conn.query(updateSql); + } + } +} diff --git a/src/idz/decoder/saveTimeAttack.ts b/src/idz/decoder/saveTimeAttack.ts index 8916433..ce54cab 100644 --- a/src/idz/decoder/saveTimeAttack.ts +++ b/src/idz/decoder/saveTimeAttack.ts @@ -1,5 +1,5 @@ import { RequestCode } from "./_defs"; -import { ExtId } from "../model/base"; +import { ExtId, RouteNo } from "../model/base"; import { Profile } from "../model/profile"; import { SaveTimeAttackRequest } from "../request/saveTimeAttack"; @@ -12,14 +12,14 @@ export function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest { profileId: buf.readUInt32LE(0x0004) as ExtId, dayNight: buf.readUInt8(0x0054) & 1, payload: { - courseId: buf.readUInt8(0x0054) >> 1, - timestamp: new Date(buf.readUInt32LE(0x0058) * 1000).toISOString(), //hck + routeNo: (buf.readUInt8(0x0054) >> 1) as RouteNo, + timestamp: new Date(buf.readUInt32LE(0x0058) * 1000), flags: buf.readUInt8(0x005c), - totalMsec: buf.readUInt32LE(0x0018), - stageMsec: [ - buf.readUInt32LE(0x0024), - buf.readUInt32LE(0x0028), - buf.readUInt32LE(0x002c), + totalTime: buf.readUInt32LE(0x0018) / 1000, + sectionTimes: [ + buf.readUInt32LE(0x0024) / 1000, + buf.readUInt32LE(0x0028) / 1000, + buf.readUInt32LE(0x002c) / 1000, ], grade: buf.readUInt8(0x0062), }, diff --git a/src/idz/encoder/loadProfile2.ts b/src/idz/encoder/loadProfile2.ts index cf196f9..0054f82 100644 --- a/src/idz/encoder/loadProfile2.ts +++ b/src/idz/encoder/loadProfile2.ts @@ -17,23 +17,23 @@ export function loadProfile2(res: LoadProfileResponse2) { buf.fill(4, 0x0680, 0x06a0); for (const score of res.timeAttack) { - const { courseId } = score; + const { routeNo } = score; buf.writeUInt32LE( (new Date(score.timestamp).getTime() / 1000) | 0, // Date ctor hack - 0x00e4 + courseId * 4 + 0x00e4 + routeNo * 4 ); - buf.writeUInt16LE(0, 0x0560 + 2 * courseId); // ??? - buf.writeUInt16LE(0xffff, 0x0164 + 2 * courseId); // National rank - buf.writeUInt32LE(score.totalMsec, 0x04e0 + 4 * courseId); - buf.writeUInt8(score.flags, 0x05a0 + courseId); - buf.writeUInt8(score.grade, 0x0680 + courseId); + buf.writeUInt16LE(0, 0x0560 + 2 * routeNo); // ??? + buf.writeUInt16LE(0xffff, 0x0164 + 2 * routeNo); // National rank + buf.writeUInt32LE((score.totalTime * 1000) | 0, 0x04e0 + 4 * routeNo); + buf.writeUInt8(score.flags, 0x05a0 + routeNo); + buf.writeUInt8(score.grade, 0x0680 + routeNo); for (let i = 0; i < 3; i++) { buf.writeUInt16LE( - score.stageMsec[i] >> 2, - 0x05c0 + 6 * courseId + 2 * i + (score.sectionTimes[i] * 1000) >> 2, + 0x05c0 + 6 * routeNo + 2 * i ); } } diff --git a/src/idz/handler/saveTimeAttack.ts b/src/idz/handler/saveTimeAttack.ts index 62e3d8f..7d32678 100644 --- a/src/idz/handler/saveTimeAttack.ts +++ b/src/idz/handler/saveTimeAttack.ts @@ -6,15 +6,7 @@ export async function saveTimeAttack( w: Repositories, req: SaveTimeAttackRequest ): Promise { - if (req.payload.totalMsec > 0) { - const existing = await w - .timeAttack() - .load(req.profileId, req.payload.courseId); - - if (existing === undefined || existing.totalMsec > req.payload.totalMsec) { - await w.timeAttack().save(req.profileId, req.payload); - } - } + await w.timeAttack().save(req.profileId, req.payload); return { type: "save_time_attack_res", diff --git a/src/idz/model/base.ts b/src/idz/model/base.ts index 7452722..e19bffb 100644 --- a/src/idz/model/base.ts +++ b/src/idz/model/base.ts @@ -3,4 +3,5 @@ export type BackgroundCode = number & { __brand: "backgroundCode" }; export type ExtId = number & { __extId: T }; +export type RouteNo = number & { __brand: "routeNo" }; export type TitleCode = number & { __brand: "titleCode" }; diff --git a/src/idz/model/timeAttack.ts b/src/idz/model/timeAttack.ts index 63ff2d0..9cd0793 100644 --- a/src/idz/model/timeAttack.ts +++ b/src/idz/model/timeAttack.ts @@ -1,8 +1,10 @@ +import { RouteNo } from "./base"; + export interface TimeAttackScore { - courseId: number; - timestamp: string; // hack + routeNo: RouteNo; + timestamp: Date; flags: number; - totalMsec: number; - stageMsec: number[]; + totalTime: number; + sectionTimes: number[]; grade: number; } diff --git a/src/idz/repo.ts b/src/idz/repo.ts index d0b8d4e..8456b22 100644 --- a/src/idz/repo.ts +++ b/src/idz/repo.ts @@ -64,11 +64,6 @@ export interface TimeAttackRepository { profileId: Model.ExtId ): Promise; - load( - profileId: Model.ExtId, - courseId: number - ): Promise; - save( profileId: Model.ExtId, score: Model.TimeAttackScore