kozukata-toa/src/servers/titles/chunithm/versions/100-base.ts

525 lines
13 KiB
TypeScript

import { GetByCardIdAndVersion } from "../utils";
import { ChunithmUserCharacter, ChunithmUserItem } from "external/db/entity/chunithm/item";
import {
ChunithmUserActivity,
ChunithmUserData,
ChunithmUserDataEx,
ChunithmUserOption,
ChunithmUserOptionEx,
} from "external/db/entity/chunithm/profile";
import { ChunithmUserRecentRating } from "external/db/entity/chunithm/score";
import { ChunithmStaticCharge, ChunithmStaticEvent } from "external/db/entity/chunithm/static";
import CreateLogCtx from "lib/logger/logger";
import { Config } from "lib/setup/config";
import { DateTime } from "luxon";
import { BaseTitle } from "servers/titles/types/titles";
import { ParseStrInt } from "servers/titles/utils/string-checks";
import { LessThanOrEqual } from "typeorm";
import type { Request, Response } from "express";
const logger = CreateLogCtx(__filename);
export class Chunithm extends BaseTitle {
private readonly dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
constructor(gameCode?: string, version?: string, servletName?: string) {
super(gameCode ?? "SDBT", version ?? "100", servletName ?? "ChuniServlet");
}
protected createGetGameSettingsApiResponse(_req: Request, _res: Response) {
// HACK: This is not the way to go if you're somehow hosting an arcade server
// from my code, and wants your machines to actually restart sometimes.
const rebootStartTime = DateTime.now().minus({ hours: 4 }).toFormat(this.dateTimeFormat);
const rebootEndTime = DateTime.now().minus({ hours: 3 }).toFormat(this.dateTimeFormat);
return {
gameSetting: {
dataVersion: "1.00.00",
isMaintenance: "false",
requestInterval: "10",
rebootStartTime,
rebootEndTime,
isBackgroundDistribute: "false",
maxCountCharacter: "300",
maxCountItem: "300",
maxCountMusic: "300",
},
isDumpUpload: "false",
isAou: "false",
};
}
/**
* @note override {@link createGetGameSettingsApiResponse} in order to modify this
* method's response
* @since CHUNITHM
*/
handle_GetGameSettingsApi(req: Request, res: Response) {
const resp = this.createGetGameSettingsApiResponse(req, res);
return res.status(200).send(resp);
}
handle_UpsertClientSettingApi(req: Request, res: Response) {
return res.status(200).send({ returnCode: "1" });
}
handle_UpsertClientTestmodeApi(req: Request, res: Response) {
return res.status(200).send({ returnCode: "1" });
}
handle_GetGameIdlistApi(req: Request, res: Response) {
return res.status(200).send({ type: req.safeBody.type, length: "0", gameIdlistList: [] });
}
async handle_GetGameEventApi(req: Request, res: Response) {
const events = await ChunithmStaticEvent.find({
where: {
enabled: true,
version: this.numericVersion,
},
});
if (events.length === 0) {
logger.warn("No events are enabled.", {
gameCode: this.gameCode,
version: this.version,
});
}
// HACK: We may want to rotate events...
return res.send({
type: req.safeBody.type,
length: events.length,
gameEventList: events.map((e) => ({
id: e.eventId,
type: e.type,
startDate: DateTime.fromJSDate(e.startDate).toFormat(this.dateTimeFormat),
endDate: "2099-12-31 00:00:00",
})),
});
}
handle_GetGameRankingApi(req: Request, res: Response) {
// TODO: Get most played songs from playlog
return res.send({
type: req.safeBody.type,
length: "0",
gameRankingList: [],
});
}
async handle_GetGameChargeApi(req: Request, res: Response) {
const charges = await ChunithmStaticCharge.find({
where: {
enabled: true,
version: this.numericVersion,
},
});
if (charges.length === 0) {
logger.warn("No charges (tickets) are enabled.", {
gameCode: this.gameCode,
version: this.version,
});
}
// HACK
return res.send({
length: charges.length,
gameChargeList: charges.map((c, i) => ({
orderId: i,
chargeId: c.chargeId,
price: "1",
startDate: "2017-12-05 07:00:00.0",
endDate: "2099-12-31 00:00:00.0",
salePrice: "1",
saleStartDate: "2017-12-05 07:00:00.0",
saleEndDate: "2099-12-31 00:00:00.0",
})),
});
}
async handle_GetUserPreviewApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const profile = await GetByCardIdAndVersion(ChunithmUserData, userId, this.numericVersion);
if (!profile) {
logger.error(`No users found.`, {
userId,
version: this.numericVersion,
});
return res.send({ returnCode: "1" });
}
const options = await GetByCardIdAndVersion(
ChunithmUserOption,
userId,
this.numericVersion
);
const character = await ChunithmUserCharacter.findOne({
where: {
characterId: profile.characterId,
card: {
id: profile.id,
},
},
});
const lastPlayDate = DateTime.fromJSDate(profile.lastPlayDate ?? new Date(0)).toFormat(
this.dateTimeFormat
);
return res.send({
userId,
// TODO: This should probably be false if the user is having a session somewhere else.
isLogin: false,
lastLoginDate: lastPlayDate,
userName: profile.userName,
reincarnationNum: profile.reincarnationNum,
level: profile.level,
exp: profile.exp,
playerRating: profile.playerRating,
lastGameId: profile.lastGameId,
lastRomVersion: profile.lastRomVersion,
lastDataVersion: profile.lastDataVersion,
lastPlayDate,
trophyId: profile.trophyId,
nameplateId: profile.nameplateId,
playerLevel: profile.level,
rating: profile.playerRating,
headphone: options?.headphone ?? 0,
chargeState: 1,
userNameEx: profile.userNameEx,
userCharacter: character ? { ...character, id: undefined, profile: undefined } : {},
});
}
handle_GameLoginApi(req: Request, res: Response) {
if (!Config.CHUNITHM_CONFIG.MODS.USE_LOGIN_BONUS) {
return res.send({ returnCode: "1" });
}
// TODO: Add login bonuses
return res.send({ returnCode: "1" });
}
async handle_GetUserDataApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const profile = await GetByCardIdAndVersion(ChunithmUserData, userId, this.numericVersion);
if (!profile) {
logger.error(`No users found.`, {
userId,
version: this.numericVersion,
});
return res.send({ returnCode: "1" });
}
return res.send({
userId,
userData: { ...profile, id: undefined, card: undefined, version: undefined },
});
}
async handle_GetUserDataExApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const profile = await GetByCardIdAndVersion(
ChunithmUserDataEx,
userId,
this.numericVersion
);
if (!profile) {
logger.error(`No users found.`, {
userId,
version: this.numericVersion,
});
return res.send({ returnCode: "1" });
}
return res.send({
userId,
userDataEx: { ...profile, id: undefined, card: undefined, version: undefined },
});
}
async handle_GetUserOptionApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const options = await GetByCardIdAndVersion(
ChunithmUserOption,
userId,
this.numericVersion
);
if (!options) {
logger.error(`No options found for requested user.`, {
userId,
version: this.numericVersion,
});
return res.send({ returnCode: "1" });
}
return res.send({
userId,
userGameOption: { ...options, id: undefined, card: undefined, version: undefined },
});
}
async handle_GetUserOptionExApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const options = await GetByCardIdAndVersion(
ChunithmUserOptionEx,
userId,
this.numericVersion
);
if (!options) {
logger.error(`No options found for requested user.`, {
userId,
version: this.numericVersion,
});
return res.send({ returnCode: "1" });
}
return res.send({
userId,
userGameOption: { ...options, id: undefined, card: undefined, version: undefined },
});
}
async handle_GetUserCharacterApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
const nextIndex = ParseStrInt(req.safeBody.nextIndex);
const maxCount = ParseStrInt(req.safeBody.maxCount);
if (!userId || !nextIndex || !maxCount) {
logger.error("Invalid request: received userId/nextIndex/maxCount is not a number.", {
userId: req.safeBody.userId,
nextIndex: req.safeBody.nextIndex,
maxCount: req.safeBody.maxCount,
});
return res.send({ returnCode: "1" });
}
const [characters, characterCount] = await ChunithmUserCharacter.findAndCount({
where: {
card: {
id: userId,
},
},
take: maxCount,
skip: nextIndex,
});
const nextNextIndex = nextIndex + maxCount;
return res.send({
userId,
length: characters.length,
nextIndex: nextNextIndex < characterCount ? nextNextIndex : -1,
userCharacterList: characters.map((c) => ({ ...c, id: undefined, card: undefined })),
});
}
async handle_GetUserActivityApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
const kind = ParseStrInt(req.safeBody.kind);
if (!userId || !kind) {
logger.error("Invalid request: received userId/kind is not a number.", {
userId: req.safeBody.userId,
kind: req.safeBody.kind,
});
return res.send({ returnCode: "1" });
}
const activities = await ChunithmUserActivity.find({
where: {
card: {
id: userId,
},
kind,
},
});
return res.send({
userId,
length: activities.length,
kind,
userActivityList: activities.map((a) => ({
...a,
id: a.activityId,
activityId: undefined,
})),
});
}
async handle_GetUserItemApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
const nextIndex = ParseStrInt(req.safeBody.nextIndex);
const maxCount = ParseStrInt(req.safeBody.maxCount);
if (!userId || !nextIndex || !maxCount) {
logger.error("Invalid request: received userId/nextIndex/maxCount is not a number.", {
userId: req.safeBody.userId,
nextIndex: req.safeBody.nextIndex,
maxCount: req.safeBody.maxCount,
});
return res.send({ returnCode: "1" });
}
const itemKind = Math.trunc(nextIndex / 1e10);
const skip = nextIndex % 1e10;
const [items, itemsCount] = await ChunithmUserItem.findAndCount({
where: {
card: {
id: userId,
},
itemKind,
},
take: maxCount,
skip,
});
const nextIndexWithinKind = skip + maxCount;
return res.send({
userId,
nextIndex: nextIndexWithinKind < itemsCount ? itemKind * 1e10 + nextIndexWithinKind : 0,
itemKind,
length: items.length,
userItemList: items.map((i) => ({ ...i, id: undefined, card: undefined })),
});
}
async handle_GetUserRecentRatingApi(req: Request, res: Response) {
const userId = ParseStrInt(req.safeBody.userId);
if (!userId) {
logger.error("Invalid request: received userId is not a number.", {
userId: req.safeBody.userId,
});
return res.send({ returnCode: "1" });
}
const recentRatings = await ChunithmUserRecentRating.find({
where: {
card: {
id: userId,
},
version: LessThanOrEqual(this.numericVersion),
},
order: {
order: "asc",
},
});
const maxVersion = Math.max(...recentRatings.map((r) => r.version));
const userRecentRatingList = recentRatings
.filter((r) => r.version === maxVersion)
.map((r) => ({
...r,
id: undefined,
card: undefined,
version: undefined,
order: undefined,
}));
return res.send({
userId,
length: userRecentRatingList.length,
userRecentRatingList,
});
}
handle_GetUserMusicApi(req: Request, res: Response) {
throw new Error("Unimplemented");
}
handle_GetUserRegionApi(req: Request, res: Response) {
// TODO: What the hell is a region anyways?
return res.send({
userId: req.safeBody.userId,
length: 0,
userRegionList: [],
});
}
handle_GetUserLoginBonusApi(req: Request, res: Response) {
throw new Error("Unimplemented");
}
handle_GetUserMapAreaApi(req: Request, res: Response) {
throw new Error("Unimplemented");
}
handle_GetUserSymbolChatSettingApi(req: Request, res: Response) {
throw new Error("Unimplemented");
}
/**
* @since NEW
*/
handle_GetUserNetBattleDataApi(req: Request, res: Response) {
throw new Error("Unimplemented");
}
handle_GameLogoutApi(req: Request, res: Response) {
return res.status(200).send({ returnCode: "1" });
}
}