Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d8d5845c9 | ||
|
|
143f534ba1 | ||
|
|
4db3d94ee8 | ||
|
|
e195cf77cd | ||
|
|
ac9622f1bd | ||
|
|
e8535b1a80 | ||
|
|
d81b763d9b | ||
|
|
1c5726be4c | ||
|
|
6da5abcefc | ||
|
|
c5e39f104f | ||
|
|
60d2f7cfd3 | ||
|
|
0ac8b44ce0 | ||
|
|
59fec74d08 |
Generated
+6
-6
@@ -2614,9 +2614,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"handlebars": {
|
"handlebars": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
|
||||||
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
|
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"neo-async": "^2.6.0",
|
"neo-async": "^2.6.0",
|
||||||
@@ -5376,9 +5376,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.6.8",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz",
|
||||||
"integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
|
"integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|||||||
@@ -82,6 +82,29 @@ create table "cm_user_activity" (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "cm_user_course" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"course_id" integer not null,
|
||||||
|
"class_id" integer not null,
|
||||||
|
"play_count" integer not null,
|
||||||
|
"score_max" integer not null,
|
||||||
|
"is_full_combo" text not null,
|
||||||
|
"is_all_justice" text not null,
|
||||||
|
"is_success" text not null,
|
||||||
|
"score_rank" integer not null,
|
||||||
|
"event_id" integer not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
"is_clear" text not null,
|
||||||
|
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||||
|
);
|
||||||
|
|
||||||
create table "cm_user_data_ex" (
|
create table "cm_user_data_ex" (
|
||||||
"id" integer primary key not null
|
"id" integer primary key not null
|
||||||
references "cm_user_data"("id")
|
references "cm_user_data"("id")
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ create table "idz_settings" (
|
|||||||
on delete cascade,
|
on delete cascade,
|
||||||
"music" integer not null,
|
"music" integer not null,
|
||||||
"pack" integer not null,
|
"pack" integer not null,
|
||||||
|
"aura" integer not null,
|
||||||
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
"paper_cup" integer not null, -- Not a boolean, oddly enough
|
||||||
"gauges" integer not null
|
"gauges" integer not null
|
||||||
);
|
);
|
||||||
@@ -192,6 +193,7 @@ create table "idz_unlocks" (
|
|||||||
"id" integer primary key not null
|
"id" integer primary key not null
|
||||||
references "idz_profile"("id")
|
references "idz_profile"("id")
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
|
"auras" integer not null,
|
||||||
"cup" integer not null,
|
"cup" integer not null,
|
||||||
"gauges" integer not null,
|
"gauges" integer not null,
|
||||||
"music" integer not null,
|
"music" integer not null,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- This adds the required columns to save player aura settings and unlocks
|
||||||
|
-- Default values are set according to what the game expects
|
||||||
|
|
||||||
|
alter table "idz_settings" add column "aura" integer NOT NULL DEFAULT '0';
|
||||||
|
alter table "idz_unlocks" add column "auras" integer NOT NULL DEFAULT '1';
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
create table "cm_user_duel_list" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"duel_id" integer not null,
|
||||||
|
"progress" integer not null,
|
||||||
|
"point" integer not null,
|
||||||
|
"is_clear" boolean not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
|
||||||
|
);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
create table "cm_user_course" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"course_id" integer not null,
|
||||||
|
"class_id" integer not null,
|
||||||
|
"play_count" integer not null,
|
||||||
|
"score_max" integer not null,
|
||||||
|
"is_full_combo" text not null,
|
||||||
|
"is_all_justice" text not null,
|
||||||
|
"is_success" text not null,
|
||||||
|
"score_rank" integer not null,
|
||||||
|
"event_id" integer not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
"is_clear" text not null,
|
||||||
|
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- This was not present in new DBs initialized to schema version 11.
|
||||||
|
create table if not exists "cm_user_course" (
|
||||||
|
"id" integer primary key not null,
|
||||||
|
"profile_id" integer not null
|
||||||
|
references "cm_user_data"("id")
|
||||||
|
on delete cascade,
|
||||||
|
"course_id" integer not null,
|
||||||
|
"class_id" integer not null,
|
||||||
|
"play_count" integer not null,
|
||||||
|
"score_max" integer not null,
|
||||||
|
"is_full_combo" text not null,
|
||||||
|
"is_all_justice" text not null,
|
||||||
|
"is_success" text not null,
|
||||||
|
"score_rank" integer not null,
|
||||||
|
"event_id" integer not null,
|
||||||
|
"last_play_date" text not null,
|
||||||
|
"param1" integer not null,
|
||||||
|
"param2" integer not null,
|
||||||
|
"param3" integer not null,
|
||||||
|
"param4" integer not null,
|
||||||
|
"is_clear" text not null,
|
||||||
|
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
|
||||||
|
);
|
||||||
@@ -1,17 +1,28 @@
|
|||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetUserCourseRequest } from "../request/getUserCourse";
|
import { GetUserCourseRequest } from "../request/getUserCourse";
|
||||||
import { GetUserCourseResponse } from "../response/getUserCourse";
|
import { GetUserCourseResponse } from "../response/getUserCourse";
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
import { paginationCookie } from "./_util";
|
||||||
|
import { writeUserCourse } from "../proto/userCourse";
|
||||||
|
|
||||||
export default async function getUserCourse(
|
export default async function getUserCourse(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetUserCourseRequest
|
req: GetUserCourseRequest
|
||||||
): Promise<GetUserCourseResponse> {
|
): Promise<GetUserCourseResponse> {
|
||||||
|
const aimeId = readAimeId(req.userId);
|
||||||
|
const maxCount = parseInt(req.maxCount);
|
||||||
|
const nextIndex = parseInt(req.nextIndex);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
const items = await rep
|
||||||
|
.userCourse()
|
||||||
|
.load(profileId, { limit: maxCount, offset: nextIndex });
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: "0",
|
length: items.length.toString(),
|
||||||
nextIndex: "-1",
|
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
|
||||||
userCourseList: [
|
userCourseList: items.map(writeUserCourse)
|
||||||
// This will get saved at some point I think
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetUserDuelRequest } from "../request/getUserDuel";
|
import { GetUserDuelRequest } from "../request/getUserDuel";
|
||||||
import { GetUserDuelResponse } from "../response/getUserDuel";
|
import { GetUserDuelResponse } from "../response/getUserDuel";
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
import { writeUserDuelList } from "../proto/userDuelList";
|
||||||
|
|
||||||
export default async function getUserDuel(
|
export default async function getUserDuel(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetUserDuelRequest
|
req: GetUserDuelRequest
|
||||||
): Promise<GetUserDuelResponse> {
|
): Promise<GetUserDuelResponse> {
|
||||||
|
const aimeId = readAimeId(req.userId);
|
||||||
|
// Get all duel entities if the client specifies to.
|
||||||
|
const duelId = req.isAllDuel ? undefined : parseInt(req.duelId);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
const items = await rep.userDuelList().load(profileId, duelId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: "0",
|
length: items.length.toString(),
|
||||||
userDuelList: [],
|
userDuelList: items.map(writeUserDuelList),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { Repositories } from "../repo";
|
import { Repositories } from "../repo";
|
||||||
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
||||||
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
import { writeUserRecentRatingFromLog } from "../proto/userRecentRating";
|
||||||
|
|
||||||
export default async function getUserRecentRating(
|
export default async function getUserRecentRating(
|
||||||
rep: Repositories,
|
rep: Repositories,
|
||||||
req: GetUserRecentRatingRequest
|
req: GetUserRecentRatingRequest
|
||||||
): Promise<GetUserRecentRatingResponse> {
|
): Promise<GetUserRecentRatingResponse> {
|
||||||
// I don't really know what subset of data to return here.
|
const aimeId = readAimeId(req.userId);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
// Return recent 30 plays to calculate rating
|
||||||
|
const items = await rep.userPlaylog().loadLatest(profileId, 30);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: req.userId,
|
userId: req.userId,
|
||||||
length: "0",
|
length: items.length.toString(),
|
||||||
userRecentRatingList: [],
|
userRecentRatingList: items.map(writeUserRecentRatingFromLog),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import { readUserItem } from "../proto/userItem";
|
|||||||
import { readUserMusicDetail } from "../proto/userMusic";
|
import { readUserMusicDetail } from "../proto/userMusic";
|
||||||
import { readUserActivity } from "../proto/userActivity";
|
import { readUserActivity } from "../proto/userActivity";
|
||||||
import { readUserDataEx } from "../proto/userDataEx";
|
import { readUserDataEx } from "../proto/userDataEx";
|
||||||
|
import { readUserDuelList } from "../proto/userDuelList";
|
||||||
import { readUserPlaylog } from "../proto/userPlaylog";
|
import { readUserPlaylog } from "../proto/userPlaylog";
|
||||||
|
import { readUserCourse } from "../proto/userCourse";
|
||||||
|
|
||||||
// It shouldn't need to be said really, but seeing as this message (A) requires
|
// It shouldn't need to be said really, but seeing as this message (A) requires
|
||||||
// fairly lengthy processing and (B) is only sent right at the end of a credit,
|
// fairly lengthy processing and (B) is only sent right at the end of a credit,
|
||||||
@@ -65,9 +67,17 @@ export default async function upsertUserAll(
|
|||||||
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of payload.userCourseList || []) {
|
||||||
|
await rep.userCourse().save(profileId, readUserCourse(item));
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of payload.userDataEx || []) {
|
for (const item of payload.userDataEx || []) {
|
||||||
await rep.userDataEx().save(profileId, readUserDataEx(item));
|
await rep.userDataEx().save(profileId, readUserDataEx(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of payload.userDuelList || []) {
|
||||||
|
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
||||||
|
}
|
||||||
|
|
||||||
return { returnCode: "1" };
|
return { returnCode: "1" };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export interface UserCourseItem {
|
||||||
|
courseId: number;
|
||||||
|
classId: number;
|
||||||
|
playCount: number;
|
||||||
|
scoreMax: number;
|
||||||
|
isFullCombo: boolean;
|
||||||
|
isAllJustice: boolean;
|
||||||
|
isSuccess: boolean;
|
||||||
|
scoreRank: number;
|
||||||
|
eventId: number;
|
||||||
|
lastPlayDate: Date;
|
||||||
|
param1: number;
|
||||||
|
param2: number;
|
||||||
|
param3: number;
|
||||||
|
param4: number;
|
||||||
|
isClear: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export interface UserDuelListItem {
|
||||||
|
duelId: number;
|
||||||
|
progress: number;
|
||||||
|
point: number;
|
||||||
|
isClear: boolean;
|
||||||
|
lastPlayDate: Date;
|
||||||
|
param1: number;
|
||||||
|
param2: number;
|
||||||
|
param3: number;
|
||||||
|
param4: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { UserCourseItem } from "../model/userCourse";
|
||||||
|
import { Crush, readBoolean, readDate, writeObject } from "./base";
|
||||||
|
|
||||||
|
export type UserCourseJson = Crush<UserCourseItem>;
|
||||||
|
|
||||||
|
export function readUserCourse(json: UserCourseJson): UserCourseItem {
|
||||||
|
return {
|
||||||
|
classId: parseInt(json.classId),
|
||||||
|
courseId: parseInt(json.courseId),
|
||||||
|
eventId: parseInt(json.eventId),
|
||||||
|
isAllJustice: readBoolean(json.isAllJustice),
|
||||||
|
isClear: readBoolean(json.isClear),
|
||||||
|
isFullCombo: readBoolean(json.isFullCombo),
|
||||||
|
isSuccess: readBoolean(json.isSuccess),
|
||||||
|
lastPlayDate: readDate(json.lastPlayDate),
|
||||||
|
param1: parseInt(json.param1),
|
||||||
|
param2: parseInt(json.param2),
|
||||||
|
param3: parseInt(json.param3),
|
||||||
|
param4: parseInt(json.param4),
|
||||||
|
playCount: parseInt(json.playCount),
|
||||||
|
scoreMax: parseInt(json.scoreMax),
|
||||||
|
scoreRank: parseInt(json.scoreRank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeUserCourse(obj: UserCourseItem): UserCourseJson {
|
||||||
|
return writeObject(obj);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Crush, readBoolean, readDate, writeObject } from "./base";
|
||||||
|
import { UserDuelListItem } from "../model/userDuelList";
|
||||||
|
|
||||||
|
export type UserDuelListJson = Crush<UserDuelListItem>;
|
||||||
|
|
||||||
|
export function readUserDuelList(
|
||||||
|
json: UserDuelListJson
|
||||||
|
): UserDuelListItem {
|
||||||
|
return {
|
||||||
|
duelId: parseInt(json.duelId),
|
||||||
|
progress: parseInt(json.progress),
|
||||||
|
point: parseInt(json.point),
|
||||||
|
isClear: readBoolean(json.isClear),
|
||||||
|
lastPlayDate: readDate(json.lastPlayDate),
|
||||||
|
param1: parseInt(json.param1),
|
||||||
|
param2: parseInt(json.param2),
|
||||||
|
param3: parseInt(json.param3),
|
||||||
|
param4: parseInt(json.param4),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeUserDuelList(
|
||||||
|
obj: UserDuelListItem
|
||||||
|
): UserDuelListJson {
|
||||||
|
return writeObject(obj);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UserPlaylogItem } from "./../model/userPlaylog";
|
||||||
import { Crush, writeObject } from "./base";
|
import { Crush, writeObject } from "./base";
|
||||||
import { UserRecentRatingItem } from "../model/userRecentRating";
|
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||||
|
|
||||||
@@ -19,3 +20,15 @@ export function writeUserRecentRating(
|
|||||||
): UserRecentRatingJson {
|
): UserRecentRatingJson {
|
||||||
return writeObject(obj);
|
return writeObject(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function writeUserRecentRatingFromLog(
|
||||||
|
obj: UserPlaylogItem
|
||||||
|
): UserRecentRatingJson {
|
||||||
|
return {
|
||||||
|
musicId: obj.musicId.toString(),
|
||||||
|
difficultId: obj.level.toString(),
|
||||||
|
// game version not saved in play log, just return a fixed version now
|
||||||
|
romVersionCode: "1030000",
|
||||||
|
score: obj.score.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { UserCourseRepository } from "./userCourse";
|
||||||
|
|
||||||
export { Page } from "./_defs";
|
export { Page } from "./_defs";
|
||||||
|
|
||||||
import { UserActivityRepository } from "./userActivity";
|
import { UserActivityRepository } from "./userActivity";
|
||||||
import { UserCharacterRepository } from "./userCharacter";
|
import { UserCharacterRepository } from "./userCharacter";
|
||||||
import { UserDataRepository } from "./userData";
|
import { UserDataRepository } from "./userData";
|
||||||
import { UserDataExRepository } from "./userDataEx";
|
import { UserDataExRepository } from "./userDataEx";
|
||||||
|
import { UserDuelListRepository } from "./userDuelList";
|
||||||
import { UserGameOptionRepository } from "./userGameOption";
|
import { UserGameOptionRepository } from "./userGameOption";
|
||||||
import { UserGameOptionExRepository } from "./userGameOptionEx";
|
import { UserGameOptionExRepository } from "./userGameOptionEx";
|
||||||
import { UserItemRepository } from "./userItem";
|
import { UserItemRepository } from "./userItem";
|
||||||
@@ -20,6 +23,8 @@ export interface Repositories {
|
|||||||
|
|
||||||
userDataEx(): UserDataExRepository;
|
userDataEx(): UserDataExRepository;
|
||||||
|
|
||||||
|
userDuelList(): UserDuelListRepository;
|
||||||
|
|
||||||
userGameOption(): UserGameOptionRepository;
|
userGameOption(): UserGameOptionRepository;
|
||||||
|
|
||||||
userGameOptionEx(): UserGameOptionExRepository;
|
userGameOptionEx(): UserGameOptionExRepository;
|
||||||
@@ -31,4 +36,6 @@ export interface Repositories {
|
|||||||
userMusic(): UserMusicRepository;
|
userMusic(): UserMusicRepository;
|
||||||
|
|
||||||
userPlaylog(): UserPlaylogRepository;
|
userPlaylog(): UserPlaylogRepository;
|
||||||
|
|
||||||
|
userCourse(): UserCourseRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { UserCourseItem } from "../model/userCourse";
|
||||||
|
import { RepositoryN } from "./_defs";
|
||||||
|
|
||||||
|
export type UserCourseRepository = RepositoryN<UserCourseItem>;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Page } from "./_defs";
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserDuelListItem } from "../model/userDuelList";
|
||||||
|
import { Id } from "../../model";
|
||||||
|
|
||||||
|
export interface UserDuelListRepository {
|
||||||
|
load(
|
||||||
|
profileId: Id<UserDataItem>,
|
||||||
|
duelId?: number,
|
||||||
|
): Promise<UserDuelListItem[]>;
|
||||||
|
|
||||||
|
save(profileId: Id<UserDataItem>, obj: UserDuelListItem): Promise<void>;
|
||||||
|
}
|
||||||
@@ -7,4 +7,5 @@ export interface UserPlaylogRepository {
|
|||||||
// is nice to have.
|
// is nice to have.
|
||||||
|
|
||||||
save(profileId: Id<UserDataItem>, obj: UserPlaylogItem): Promise<void>;
|
save(profileId: Id<UserDataItem>, obj: UserPlaylogItem): Promise<void>;
|
||||||
|
loadLatest(profileId: Id<UserDataItem>, size: number): Promise<UserPlaylogItem[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { UserMusicDetailJson } from "../proto/userMusic";
|
|||||||
import { UserActivityJson } from "../proto/userActivity";
|
import { UserActivityJson } from "../proto/userActivity";
|
||||||
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
||||||
import { UserPlaylogJson } from "../proto/userPlaylog";
|
import { UserPlaylogJson } from "../proto/userPlaylog";
|
||||||
|
import { UserCourseJson } from "../proto/userCourse";
|
||||||
import { UserDataExJson } from "../proto/userDataEx";
|
import { UserDataExJson } from "../proto/userDataEx";
|
||||||
|
import { UserDuelListJson } from "../proto/userDuelList";
|
||||||
|
|
||||||
export interface UpsertUserAllRequest {
|
export interface UpsertUserAllRequest {
|
||||||
/** Integer, AiMe ID */
|
/** Integer, AiMe ID */
|
||||||
@@ -25,7 +27,9 @@ export interface UpsertUserAllRequest {
|
|||||||
userActivityList?: UserActivityJson[];
|
userActivityList?: UserActivityJson[];
|
||||||
userRecentRatingList?: UserRecentRatingJson[];
|
userRecentRatingList?: UserRecentRatingJson[];
|
||||||
userPlaylogList?: UserPlaylogJson[];
|
userPlaylogList?: UserPlaylogJson[];
|
||||||
|
userCourseList?: UserCourseJson[];
|
||||||
userDataEx?: UserDataExJson[];
|
userDataEx?: UserDataExJson[];
|
||||||
|
userDuelList?: UserDuelListJson[];
|
||||||
|
|
||||||
/** String of binary digits */
|
/** String of binary digits */
|
||||||
isNewMapList: string;
|
isNewMapList: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { UserCourseJson } from "../proto/userCourse";
|
||||||
|
|
||||||
export interface GetUserCourseResponse {
|
export interface GetUserCourseResponse {
|
||||||
/** Integer, AiMe ID */
|
/** Integer, AiMe ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -8,6 +10,5 @@ export interface GetUserCourseResponse {
|
|||||||
/** Integer, pagination cookie */
|
/** Integer, pagination cookie */
|
||||||
nextIndex: string;
|
nextIndex: string;
|
||||||
|
|
||||||
/** TBD */
|
userCourseList: UserCourseJson[];
|
||||||
userCourseList: [];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { UserDuelListJson } from "../proto/userDuelList";
|
||||||
|
|
||||||
export interface GetUserDuelResponse {
|
export interface GetUserDuelResponse {
|
||||||
/** Integer, AiMe ID */
|
/** Integer, AiMe ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -5,6 +7,9 @@ export interface GetUserDuelResponse {
|
|||||||
/** Integer, number of results returned */
|
/** Integer, number of results returned */
|
||||||
length: string;
|
length: string;
|
||||||
|
|
||||||
/** TBD */
|
/**
|
||||||
userDuelList: [];
|
* List of duel entities, either all duel entities associated with `userId`
|
||||||
|
* or single requested by `duelId` in request.
|
||||||
|
*/
|
||||||
|
userDuelList: UserDuelListJson[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { SqlUserActivityRepository } from "./userActivity";
|
|||||||
import { SqlUserCharacterRepository } from "./userCharacter";
|
import { SqlUserCharacterRepository } from "./userCharacter";
|
||||||
import { SqlUserDataRepository } from "./userData";
|
import { SqlUserDataRepository } from "./userData";
|
||||||
import { SqlUserDataExRepository } from "./userDataEx";
|
import { SqlUserDataExRepository } from "./userDataEx";
|
||||||
|
import { SqlUserDuelListRepository } from "./userDuelList";
|
||||||
import { SqlUserGameOptionRepository } from "./userGameOption";
|
import { SqlUserGameOptionRepository } from "./userGameOption";
|
||||||
import { SqlUserGameOptionExRepository } from "./userGameOptionEx";
|
import { SqlUserGameOptionExRepository } from "./userGameOptionEx";
|
||||||
import { SqlUserItemRepository } from "./userItem";
|
import { SqlUserItemRepository } from "./userItem";
|
||||||
@@ -13,6 +14,7 @@ import { UserActivityRepository } from "../repo/userActivity";
|
|||||||
import { UserCharacterRepository } from "../repo/userCharacter";
|
import { UserCharacterRepository } from "../repo/userCharacter";
|
||||||
import { UserDataRepository } from "../repo/userData";
|
import { UserDataRepository } from "../repo/userData";
|
||||||
import { UserDataExRepository } from "../repo/userDataEx";
|
import { UserDataExRepository } from "../repo/userDataEx";
|
||||||
|
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||||
import { UserGameOptionRepository } from "../repo/userGameOption";
|
import { UserGameOptionRepository } from "../repo/userGameOption";
|
||||||
import { UserGameOptionExRepository } from "../repo/userGameOptionEx";
|
import { UserGameOptionExRepository } from "../repo/userGameOptionEx";
|
||||||
import { UserItemRepository } from "../repo/userItem";
|
import { UserItemRepository } from "../repo/userItem";
|
||||||
@@ -20,6 +22,8 @@ import { UserMapRepository } from "../repo/userMap";
|
|||||||
import { UserMusicRepository } from "../repo/userMusic";
|
import { UserMusicRepository } from "../repo/userMusic";
|
||||||
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||||
import { Transaction } from "../../sql";
|
import { Transaction } from "../../sql";
|
||||||
|
import { UserCourseRepository } from "../repo/userCourse";
|
||||||
|
import { SqlUserCourseRepository } from "./userCourse";
|
||||||
|
|
||||||
export class SqlRepositories implements Repositories {
|
export class SqlRepositories implements Repositories {
|
||||||
constructor(private readonly _txn: Transaction) {}
|
constructor(private readonly _txn: Transaction) {}
|
||||||
@@ -40,6 +44,10 @@ export class SqlRepositories implements Repositories {
|
|||||||
return new SqlUserDataExRepository(this._txn);
|
return new SqlUserDataExRepository(this._txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userDuelList(): UserDuelListRepository {
|
||||||
|
return new SqlUserDuelListRepository(this._txn);
|
||||||
|
}
|
||||||
|
|
||||||
userGameOption(): UserGameOptionRepository {
|
userGameOption(): UserGameOptionRepository {
|
||||||
return new SqlUserGameOptionRepository(this._txn);
|
return new SqlUserGameOptionRepository(this._txn);
|
||||||
}
|
}
|
||||||
@@ -63,4 +71,8 @@ export class SqlRepositories implements Repositories {
|
|||||||
userPlaylog(): UserPlaylogRepository {
|
userPlaylog(): UserPlaylogRepository {
|
||||||
return new SqlUserPlaylogRepository(this._txn);
|
return new SqlUserPlaylogRepository(this._txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userCourse(): UserCourseRepository {
|
||||||
|
return new SqlUserCourseRepository(this._txn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import sql from "sql-bricks-postgres";
|
||||||
|
import { createSqlMapper, T } from "../../sql/util";
|
||||||
|
import { UserCourseRepository } from "../repo/userCourse";
|
||||||
|
import { Id } from "../../model";
|
||||||
|
import { Page } from "../repo";
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserCourseItem } from "../model/userCourse";
|
||||||
|
import { Transaction } from "../../sql";
|
||||||
|
|
||||||
|
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||||
|
courseId: T.number,
|
||||||
|
classId: T.number,
|
||||||
|
playCount: T.number,
|
||||||
|
scoreMax: T.number,
|
||||||
|
isFullCombo: T.boolean,
|
||||||
|
isAllJustice: T.boolean,
|
||||||
|
isSuccess: T.boolean,
|
||||||
|
scoreRank: T.number,
|
||||||
|
eventId: T.number,
|
||||||
|
lastPlayDate: T.Date,
|
||||||
|
param1: T.number,
|
||||||
|
param2: T.number,
|
||||||
|
param3: T.number,
|
||||||
|
param4: T.number,
|
||||||
|
isClear: T.boolean
|
||||||
|
});
|
||||||
|
|
||||||
|
export class SqlUserCourseRepository implements UserCourseRepository {
|
||||||
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
||||||
|
async load(
|
||||||
|
profileId: Id<UserDataItem>,
|
||||||
|
page?: Page
|
||||||
|
): Promise<UserCourseItem[]> {
|
||||||
|
const stmt = sql
|
||||||
|
.select("*")
|
||||||
|
.from("cm_user_course")
|
||||||
|
.where("profile_id", profileId);
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
stmt.limit(page.limit).offset(page.offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
return rows.map(readRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(profileId: Id<UserDataItem>, obj: UserCourseItem): Promise<void> {
|
||||||
|
const stmt = sql
|
||||||
|
.insert("cm_user_course", {
|
||||||
|
id: this._txn.generateId(),
|
||||||
|
profile_id: profileId,
|
||||||
|
...writeRow(obj),
|
||||||
|
})
|
||||||
|
.onConflict("profile_id", "course_id")
|
||||||
|
.doUpdate(colNames);
|
||||||
|
|
||||||
|
return this._txn.modify(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import sql from "sql-bricks-postgres";
|
||||||
|
|
||||||
|
import { UserDataItem } from "../model/userData";
|
||||||
|
import { UserDuelListItem } from "../model/userDuelList";
|
||||||
|
import { Page } from "../repo";
|
||||||
|
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||||
|
import { Id } from "../../model";
|
||||||
|
import { Transaction } from "../../sql";
|
||||||
|
import { T, createSqlMapper } from "../../sql/util";
|
||||||
|
|
||||||
|
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||||
|
duelId: T.number,
|
||||||
|
progress: T.number,
|
||||||
|
point: T.number,
|
||||||
|
isClear: T.boolean,
|
||||||
|
lastPlayDate: T.Date,
|
||||||
|
param1: T.number,
|
||||||
|
param2: T.number,
|
||||||
|
param3: T.number,
|
||||||
|
param4: T.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class SqlUserDuelListRepository implements UserDuelListRepository {
|
||||||
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
||||||
|
async load(
|
||||||
|
profileId: Id<UserDataItem>,
|
||||||
|
duelId?: number
|
||||||
|
): Promise<UserDuelListItem[]> {
|
||||||
|
const stmt = sql
|
||||||
|
.select("*")
|
||||||
|
.from("cm_user_duel_list")
|
||||||
|
.where("profile_id", profileId);
|
||||||
|
|
||||||
|
if (duelId !== undefined) {
|
||||||
|
stmt.where("duel_id", duelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
return rows.map(readRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(profileId: Id<UserDataItem>, obj: UserDuelListItem): Promise<void> {
|
||||||
|
const stmt = sql
|
||||||
|
.insert("cm_user_duel_list", {
|
||||||
|
id: this._txn.generateId(),
|
||||||
|
profile_id: profileId,
|
||||||
|
...writeRow(obj),
|
||||||
|
})
|
||||||
|
.onConflict("profile_id", "duel_id")
|
||||||
|
.doUpdate(colNames);
|
||||||
|
|
||||||
|
return this._txn.modify(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { UserPlaylogRepository } from "../repo/userPlaylog";
|
|||||||
import { Transaction } from "../../sql";
|
import { Transaction } from "../../sql";
|
||||||
import { T, createSqlMapper } from "../../sql/util";
|
import { T, createSqlMapper } from "../../sql/util";
|
||||||
|
|
||||||
const { writeRow } = createSqlMapper({
|
const { readRow, writeRow } = createSqlMapper({
|
||||||
orderId: T.number,
|
orderId: T.number,
|
||||||
sortNumber: T.number,
|
sortNumber: T.number,
|
||||||
placeId: T.number,
|
placeId: T.number,
|
||||||
@@ -72,4 +72,16 @@ export class SqlUserPlaylogRepository implements UserPlaylogRepository {
|
|||||||
|
|
||||||
return this._txn.modify(stmt);
|
return this._txn.modify(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadLatest(profileId: Id<UserDataItem>, size: number): Promise<UserPlaylogItem[]> {
|
||||||
|
const stmt = sql
|
||||||
|
.select("music_id", "score", "level","user_play_date")
|
||||||
|
.from("cm_user_playlog")
|
||||||
|
.where("profile_id", profileId)
|
||||||
|
.limit(size).order("user_play_date DESC");
|
||||||
|
|
||||||
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
return rows.map(readRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
|||||||
rows: storyRows,
|
rows: storyRows,
|
||||||
},
|
},
|
||||||
unlocks: {
|
unlocks: {
|
||||||
|
auras: buf.readUInt16LE(0x010c),
|
||||||
cup: buf.readUInt8(0x0110),
|
cup: buf.readUInt8(0x0110),
|
||||||
gauges: buf.readUInt16LE(0x0114),
|
gauges: buf.readUInt16LE(0x0114),
|
||||||
music: buf.readUInt16LE(0x0140),
|
music: buf.readUInt16LE(0x0140),
|
||||||
@@ -90,6 +91,7 @@ export function saveProfile2(buf: Buffer): SaveProfileRequest2 {
|
|||||||
settings: {
|
settings: {
|
||||||
music: buf.readUInt16LE(0x045a),
|
music: buf.readUInt16LE(0x045a),
|
||||||
pack: buf.readUInt32LE(0x0034),
|
pack: buf.readUInt32LE(0x0034),
|
||||||
|
aura: buf.readUInt8(0x002c),
|
||||||
paperCup: buf.readUInt8(0x00f6),
|
paperCup: buf.readUInt8(0x00f6),
|
||||||
gauges: buf.readUInt8(0x00f7),
|
gauges: buf.readUInt8(0x00f7),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
|||||||
rows: storyRows,
|
rows: storyRows,
|
||||||
},
|
},
|
||||||
unlocks: {
|
unlocks: {
|
||||||
|
auras: buf.readUInt16LE(0x010c),
|
||||||
cup: buf.readUInt8(0x0110),
|
cup: buf.readUInt8(0x0110),
|
||||||
gauges: buf.readUInt16LE(0x0114),
|
gauges: buf.readUInt16LE(0x0114),
|
||||||
music: buf.readUInt16LE(0x0140),
|
music: buf.readUInt16LE(0x0140),
|
||||||
@@ -92,6 +93,7 @@ export function saveProfile3(buf: Buffer): SaveProfileRequest2 {
|
|||||||
settings: {
|
settings: {
|
||||||
music: buf.readUInt16LE(0x04ee),
|
music: buf.readUInt16LE(0x04ee),
|
||||||
pack: buf.readUInt32LE(0x0034),
|
pack: buf.readUInt32LE(0x0034),
|
||||||
|
aura: buf.readUInt8(0x002c),
|
||||||
paperCup: buf.readUInt8(0x00f6),
|
paperCup: buf.readUInt8(0x00f6),
|
||||||
gauges: buf.readUInt8(0x00f7),
|
gauges: buf.readUInt8(0x00f7),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export function saveSettings(buf: Buffer): SaveSettingsRequest {
|
|||||||
pack: buf.readUInt32LE(0x000c),
|
pack: buf.readUInt32LE(0x000c),
|
||||||
paperCup: buf.readUInt8(0x0011),
|
paperCup: buf.readUInt8(0x0011),
|
||||||
gauges: buf.readUInt8(0x0012),
|
gauges: buf.readUInt8(0x0012),
|
||||||
|
aura: buf.readUInt8(0x0013),
|
||||||
},
|
},
|
||||||
field_0010: buf.readUInt8(0x0010),
|
field_0010: buf.readUInt8(0x0010),
|
||||||
field_0013: buf.readUInt8(0x0013),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf.writeUInt16LE(0x0065, 0x0000);
|
buf.writeUInt16LE(0x0065, 0x0000);
|
||||||
|
buf.writeUInt16LE(res.unlocks.auras, 0x00b0);
|
||||||
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
||||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8);
|
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x01e8);
|
||||||
@@ -92,6 +93,7 @@ export function loadProfile2(res: LoadProfileResponse2) {
|
|||||||
encodeMission(res.missions.solo).copy(buf, 0x06e4);
|
encodeMission(res.missions.solo).copy(buf, 0x06e4);
|
||||||
encodeChara(res.chara).copy(buf, 0x070c);
|
encodeChara(res.chara).copy(buf, 0x070c);
|
||||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
|
encodeBitmap(res.titles, 0xb4).copy(buf, 0x720);
|
||||||
|
buf.writeUInt8(res.settings.aura, 0x07d6);
|
||||||
buf.writeUInt8(res.settings.paperCup, 0x07d9);
|
buf.writeUInt8(res.settings.paperCup, 0x07d9);
|
||||||
buf.writeUInt8(res.settings.gauges, 0x07da);
|
buf.writeUInt8(res.settings.gauges, 0x07da);
|
||||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x07e0);
|
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x07e0);
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export function loadProfile3(res: LoadProfileResponse3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf.writeUInt16LE(0x012e, 0x0000);
|
buf.writeUInt16LE(0x012e, 0x0000);
|
||||||
|
buf.writeUInt16LE(res.unlocks.auras, 0x00b0);
|
||||||
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
buf.writeUInt8(res.unlocks.cup, 0x00b4);
|
||||||
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
buf.writeUInt16LE(res.unlocks.gauges, 0x00b8);
|
||||||
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
|
buf.writeUInt32LE(res.unlocks.lastMileageReward, 0x0218);
|
||||||
@@ -92,6 +93,7 @@ export function loadProfile3(res: LoadProfileResponse3) {
|
|||||||
encodeMission(res.missions.solo).copy(buf, 0x0858);
|
encodeMission(res.missions.solo).copy(buf, 0x0858);
|
||||||
encodeChara(res.chara).copy(buf, 0x0880);
|
encodeChara(res.chara).copy(buf, 0x0880);
|
||||||
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
|
encodeBitmap(res.titles, 0xb4).copy(buf, 0x0894);
|
||||||
|
buf.writeUInt8(res.settings.aura, 0x094a);
|
||||||
buf.writeUInt8(res.settings.paperCup, 0x094d);
|
buf.writeUInt8(res.settings.paperCup, 0x094d);
|
||||||
buf.writeUInt8(res.settings.gauges, 0x094e);
|
buf.writeUInt8(res.settings.gauges, 0x094e);
|
||||||
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x0954);
|
buf.writeUInt32LE(res.teamId || 0xffffffff, 0x0954);
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ export function loadTopTen(res: LoadTopTenResponse): Buffer {
|
|||||||
buf.writeUInt8(row.field_0F ? 1 : 0, innerOff + 0x000f); // Boolean
|
buf.writeUInt8(row.field_0F ? 1 : 0, innerOff + 0x000f); // Boolean
|
||||||
buf.writeUInt8(row.field_10, innerOff + 0x0010);
|
buf.writeUInt8(row.field_10, innerOff + 0x0010);
|
||||||
iconv.encode(row.driverName, "shift_jis").copy(buf, innerOff + 0x0014);
|
iconv.encode(row.driverName, "shift_jis").copy(buf, innerOff + 0x0014);
|
||||||
iconv.encode(row.teamName, "shift_jis").copy(buf, innerOff + 0x0028);
|
iconv.encode(row.team.name, "shift_jis").copy(buf, innerOff + 0x0028);
|
||||||
iconv.encode(row.shopName, "shift_jis").copy(buf, innerOff + 0x0048);
|
iconv.encode(row.shopName, "shift_jis").copy(buf, innerOff + 0x0048);
|
||||||
buf.writeUInt32LE(row.field_74, innerOff + 0x0074);
|
buf.writeUInt32LE(row.team.nameBg, innerOff + 0x0074);
|
||||||
buf.writeUInt16LE(row.field_78, innerOff + 0x0078);
|
buf.writeUInt16LE(row.team.nameFx, innerOff + 0x0078);
|
||||||
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
|
buf.writeUInt8(row.field_7C, innerOff + 0x007c);
|
||||||
buf.writeUInt8(row.field_7D, innerOff + 0x007d);
|
buf.writeUInt8(row.field_7D, innerOff + 0x007d);
|
||||||
// 66 bytes of fucking nothing what
|
// 66 bytes of empty space at the end. Maybe a string used to live here?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ export async function createProfile(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const missions: MissionState = { team: [], solo: [] };
|
const missions: MissionState = { team: [], solo: [] };
|
||||||
const settings: Settings = { music: 0, pack: 13640, paperCup: 0, gauges: 5 };
|
const settings: Settings = { music: 0, pack: 13640, aura: 0, paperCup: 0, gauges: 5 };
|
||||||
const story: Story = { x: 0, y: 0, rows: [] };
|
const story: Story = { x: 0, y: 0, rows: [] };
|
||||||
const unlocks: Unlocks = {
|
const unlocks: Unlocks = {
|
||||||
|
auras: 1,
|
||||||
cup: 0,
|
cup: 0,
|
||||||
gauges: 1 << 5,
|
gauges: 1 << 5,
|
||||||
music: 0,
|
music: 0,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function loadTopTen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { routeNo, minTimestamp } = selector;
|
const { routeNo, minTimestamp } = selector;
|
||||||
const src = await w.timeAttack().loadTopTen(routeNo, minTimestamp);
|
const src = await w.timeAttack().loadTop(routeNo, minTimestamp, 10);
|
||||||
|
|
||||||
if (src.length === 0) {
|
if (src.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
@@ -33,10 +33,8 @@ export async function loadTopTen(
|
|||||||
field_0F: false,
|
field_0F: false,
|
||||||
field_10: 0,
|
field_10: 0,
|
||||||
driverName: srcItem.driverName,
|
driverName: srcItem.driverName,
|
||||||
teamName: process.env.TEAM_NAME || "",
|
|
||||||
shopName: process.env.SHOP_NAME || "",
|
shopName: process.env.SHOP_NAME || "",
|
||||||
field_74: 0,
|
team: srcItem.team,
|
||||||
field_78: 0,
|
|
||||||
field_7C: 0,
|
field_7C: 0,
|
||||||
field_7D: 0,
|
field_7D: 0,
|
||||||
ta: srcItem.ta,
|
ta: srcItem.ta,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface Settings {
|
export interface Settings {
|
||||||
music: number;
|
music: number;
|
||||||
pack: number;
|
pack: number;
|
||||||
|
aura: number;
|
||||||
paperCup: number;
|
paperCup: number;
|
||||||
gauges: number;
|
gauges: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface Unlocks {
|
export interface Unlocks {
|
||||||
|
auras: number;
|
||||||
cup: number;
|
cup: number;
|
||||||
gauges: number;
|
gauges: number;
|
||||||
music: number;
|
music: number;
|
||||||
|
|||||||
+4
-2
@@ -111,13 +111,15 @@ export interface TeamReservationRepository {
|
|||||||
// TODO extend and factorize
|
// TODO extend and factorize
|
||||||
export interface TopTenResult {
|
export interface TopTenResult {
|
||||||
driverName: string;
|
driverName: string;
|
||||||
|
team: Model.Team;
|
||||||
ta: Model.TimeAttackScore;
|
ta: Model.TimeAttackScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeAttackRepository {
|
export interface TimeAttackRepository {
|
||||||
loadTopTen(
|
loadTop(
|
||||||
routeNo: Model.RouteNo,
|
routeNo: Model.RouteNo,
|
||||||
minTimestamp: Date
|
minTimestamp: Date,
|
||||||
|
limit: number
|
||||||
): Promise<TopTenResult[]>;
|
): Promise<TopTenResult[]>;
|
||||||
|
|
||||||
loadAll(profileId: Id<Model.Profile>): Promise<Model.TimeAttackScore[]>;
|
loadAll(profileId: Id<Model.Profile>): Promise<Model.TimeAttackScore[]>;
|
||||||
|
|||||||
@@ -7,5 +7,4 @@ export interface SaveSettingsRequest {
|
|||||||
dpoint: number; // ?? why
|
dpoint: number; // ?? why
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
field_0010: number;
|
field_0010: number;
|
||||||
field_0013: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ export interface LoadTopTenResponseRow {
|
|||||||
field_0F: boolean;
|
field_0F: boolean;
|
||||||
field_10: number;
|
field_10: number;
|
||||||
driverName: string;
|
driverName: string;
|
||||||
teamName: string;
|
|
||||||
shopName: string;
|
shopName: string;
|
||||||
field_74: number;
|
team: {
|
||||||
field_78: number;
|
name: string;
|
||||||
|
nameBg: number;
|
||||||
|
nameFx: number;
|
||||||
|
};
|
||||||
field_7C: number;
|
field_7C: number;
|
||||||
field_7D: number;
|
field_7D: number;
|
||||||
ta: TimeAttackScore;
|
ta: TimeAttackScore;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
|||||||
return {
|
return {
|
||||||
music: parseInt(row.music),
|
music: parseInt(row.music),
|
||||||
pack: parseInt(row.pack),
|
pack: parseInt(row.pack),
|
||||||
|
aura: parseInt(row.aura),
|
||||||
paperCup: parseInt(row.paper_cup),
|
paperCup: parseInt(row.paper_cup),
|
||||||
gauges: parseInt(row.gauges),
|
gauges: parseInt(row.gauges),
|
||||||
};
|
};
|
||||||
@@ -35,11 +36,12 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
|||||||
id: profileId,
|
id: profileId,
|
||||||
music: settings.music,
|
music: settings.music,
|
||||||
pack: settings.pack,
|
pack: settings.pack,
|
||||||
|
aura: settings.aura,
|
||||||
paper_cup: settings.paperCup,
|
paper_cup: settings.paperCup,
|
||||||
gauges: settings.gauges,
|
gauges: settings.gauges,
|
||||||
})
|
})
|
||||||
.onConflict("id")
|
.onConflict("id")
|
||||||
.doUpdate(["music", "pack", "paper_cup", "gauges"]);
|
.doUpdate(["music", "pack", "aura", "paper_cup", "gauges"]);
|
||||||
|
|
||||||
await this._txn.modify(saveSql);
|
await this._txn.modify(saveSql);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||||||
.where("id", teamId)
|
.where("id", teamId)
|
||||||
.forUpdate();
|
.forUpdate();
|
||||||
|
|
||||||
await this._txn.modify(lockSql);
|
await this._txn.fetchRow(lockSql);
|
||||||
|
|
||||||
// Double-check (with lock held) that there is room to join this team.
|
// 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
|
// If this fails then the error will propagate to the client and it will
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import sql from "sql-bricks-postgres";
|
import sql from "sql-bricks-postgres";
|
||||||
|
|
||||||
import { RouteNo } from "../model/base";
|
import { ExtId, RouteNo } from "../model/base";
|
||||||
import { CarSelector } from "../model/car";
|
import { CarSelector } from "../model/car";
|
||||||
|
import { Team } from "../model/team";
|
||||||
import { Profile } from "../model/profile";
|
import { Profile } from "../model/profile";
|
||||||
import { TimeAttackScore } from "../model/timeAttack";
|
import { TimeAttackScore } from "../model/timeAttack";
|
||||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||||
@@ -23,23 +24,51 @@ function _extractRow(row: Row): TimeAttackScore {
|
|||||||
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||||
constructor(private readonly _txn: Transaction) {}
|
constructor(private readonly _txn: Transaction) {}
|
||||||
|
|
||||||
async loadTopTen(
|
async loadTop(
|
||||||
routeNo: RouteNo,
|
routeNo: RouteNo,
|
||||||
minTimestamp: Date
|
minTimestamp: Date,
|
||||||
|
limit: number
|
||||||
): Promise<TopTenResult[]> {
|
): Promise<TopTenResult[]> {
|
||||||
|
// We're not using an ORM here so this join-heavy SQL is unfortunately
|
||||||
|
// going to be a boilerplated mess.
|
||||||
|
|
||||||
const loadSql = sql
|
const loadSql = sql
|
||||||
.select("p.name", "ta.*")
|
.select(
|
||||||
|
// Profile
|
||||||
|
"p.name as profile_name",
|
||||||
|
// Team
|
||||||
|
"t.ext_id as team_ext_id",
|
||||||
|
"t.name as team_name",
|
||||||
|
"t.name_bg as team_name_bg",
|
||||||
|
"t.name_fx as team_name_fx",
|
||||||
|
"t.register_time as team_register_time",
|
||||||
|
// Time Attack
|
||||||
|
"ta.*"
|
||||||
|
)
|
||||||
.from("idz_ta_best ta")
|
.from("idz_ta_best ta")
|
||||||
.join("idz_profile p", { "ta.profile_id": "p.id" })
|
.join("idz_profile p", { "ta.profile_id": "p.id" })
|
||||||
|
// This is an inner join, so it will exclude anybody who is not currently
|
||||||
|
// a member of a team from the leader boards. Since the only way to play
|
||||||
|
// without a team is to be ejected from one we can consider this an edge
|
||||||
|
// case that we probably don't need to worry about.
|
||||||
|
.join("idz_team_member tm", { "ta.profile_id": "tm.id" })
|
||||||
|
.join("idz_team t", { "tm.team_id": "t.id" })
|
||||||
.where("ta.route_no", routeNo)
|
.where("ta.route_no", routeNo)
|
||||||
.where(sql.gt("ta.timestamp", minTimestamp))
|
.where(sql.gt("ta.timestamp", minTimestamp))
|
||||||
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
||||||
.limit(10);
|
.limit(limit);
|
||||||
|
|
||||||
const rows = await this._txn.fetchRows(loadSql);
|
const rows = await this._txn.fetchRows(loadSql);
|
||||||
|
|
||||||
return rows.map(row => ({
|
return rows.map(row => ({
|
||||||
driverName: row.name,
|
driverName: row.profile_name,
|
||||||
|
team: {
|
||||||
|
extId: parseInt(row.team_ext_id) as ExtId<Team>,
|
||||||
|
name: row.team_name,
|
||||||
|
nameBg: parseInt(row.team_name_bg),
|
||||||
|
nameFx: parseInt(row.team_name_fx),
|
||||||
|
registerTime: new Date(row.team_register_time),
|
||||||
|
},
|
||||||
ta: _extractRow(row),
|
ta: _extractRow(row),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
auras: parseInt(row.auras),
|
||||||
cup: parseInt(row.cup),
|
cup: parseInt(row.cup),
|
||||||
gauges: parseInt(row.gauges),
|
gauges: parseInt(row.gauges),
|
||||||
music: parseInt(row.music),
|
music: parseInt(row.music),
|
||||||
@@ -33,13 +34,14 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
|||||||
const saveSql = sql
|
const saveSql = sql
|
||||||
.insert("idz_unlocks", {
|
.insert("idz_unlocks", {
|
||||||
id: profileId,
|
id: profileId,
|
||||||
|
auras: unlocks.auras,
|
||||||
cup: unlocks.cup,
|
cup: unlocks.cup,
|
||||||
gauges: unlocks.gauges,
|
gauges: unlocks.gauges,
|
||||||
music: unlocks.music,
|
music: unlocks.music,
|
||||||
last_mileage_reward: unlocks.lastMileageReward,
|
last_mileage_reward: unlocks.lastMileageReward,
|
||||||
})
|
})
|
||||||
.onConflict("id")
|
.onConflict("id")
|
||||||
.doUpdate(["cup", "gauges", "music", "last_mileage_reward"]);
|
.doUpdate(["auras", "cup", "gauges", "music", "last_mileage_reward"]);
|
||||||
|
|
||||||
await this._txn.modify(saveSql);
|
await this._txn.modify(saveSql);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
where /q node
|
where /q node
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo NodeJS does not appear to be installed.
|
echo NodeJS does not appear to be installed.
|
||||||
@@ -40,8 +42,10 @@ if not exist node_modules (
|
|||||||
echo.
|
echo.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rem Project Diva requires TLSv1.0
|
||||||
|
|
||||||
set DEBUG=app:*
|
set DEBUG=app:*
|
||||||
node .\bin\index.js
|
node --tls-min-v1.0 .\bin\index.js
|
||||||
|
|
||||||
:end
|
:end
|
||||||
echo.
|
echo.
|
||||||
|
|||||||
Reference in New Issue
Block a user