1
0
mirror of synced 2024-12-04 19:17:58 +01:00

Merge pull request #26 from ghkkk090/master

OCM *hopefully no error*
This commit is contained in:
Luna 2022-08-17 11:50:16 +01:00 committed by GitHub
commit b2144b38fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 7825 additions and 3265 deletions

BIN
README - OCM Event.pdf Normal file

Binary file not shown.

View File

@ -1,10 +1,10 @@
{
"shopName": "Bayshore",
"shopNickname": "BSH",
"regionName": "Bayshore",
"shopNickname": "Bayshore",
"regionName": "JPN0123",
"serverIp": "127.0.0.1",
"gameOptions": {
"grantFullTuneTicketToNewUsers": 3,
"grantFullTuneTicketToNewUsers": 5,
"giftCarsFullyTuned": 0,
"scratchEnabled": 1,
"scratchType": 1,

View File

@ -0,0 +1,119 @@
-- AlterTable
ALTER TABLE "Car" ALTER COLUMN "stLoseBits" SET DEFAULT 0;
-- AlterTable
ALTER TABLE "CarState" ADD COLUMN "competitionState" INTEGER NOT NULL DEFAULT 0;
-- AlterTable
ALTER TABLE "GhostBattleRecord" ADD COLUMN "ocmBattle" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "ocmMainDay" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "GhostTrail" ADD COLUMN "ocmBattle" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "ocmMainDay" BOOLEAN NOT NULL DEFAULT false;
-- CreateTable
CREATE TABLE "OCMEvent" (
"dbId" SERIAL NOT NULL,
"competitionId" INTEGER NOT NULL,
"qualifyingPeriodStartAt" INTEGER NOT NULL,
"qualifyingPeriodCloseAt" INTEGER NOT NULL,
"competitionStartAt" INTEGER NOT NULL,
"competitionCloseAt" INTEGER NOT NULL,
"competitionEndAt" INTEGER NOT NULL,
"lengthOfPeriod" INTEGER NOT NULL,
"lengthOfInterval" INTEGER NOT NULL,
"area" INTEGER NOT NULL DEFAULT 0,
"minigamePatternId" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "OCMEvent_pkey" PRIMARY KEY ("dbId")
);
-- CreateTable
CREATE TABLE "OCMPlayRecord" (
"dbId" SERIAL NOT NULL,
"carId" INTEGER NOT NULL,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"brakingPoint" INTEGER,
"playedAt" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "OCMPlayRecord_pkey" PRIMARY KEY ("dbId")
);
-- CreateTable
CREATE TABLE "OCMTop1Ghost" (
"dbId" SERIAL NOT NULL,
"carId" INTEGER NOT NULL,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"result" INTEGER NOT NULL,
"tunePower" INTEGER NOT NULL DEFAULT 0,
"tuneHandling" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "OCMTop1Ghost_pkey" PRIMARY KEY ("dbId")
);
-- CreateTable
CREATE TABLE "OCMTally" (
"dbId" SERIAL NOT NULL,
"carId" INTEGER NOT NULL,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"result" INTEGER NOT NULL,
"tunePower" INTEGER NOT NULL DEFAULT 0,
"tuneHandling" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "OCMTally_pkey" PRIMARY KEY ("dbId")
);
-- CreateTable
CREATE TABLE "OCMGhostBattleRecord" (
"dbId" SERIAL NOT NULL,
"carId" INTEGER NOT NULL,
"tunePower" INTEGER NOT NULL DEFAULT 0,
"tuneHandling" INTEGER NOT NULL DEFAULT 0,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"result" INTEGER NOT NULL,
"area" INTEGER NOT NULL DEFAULT 0,
"playedAt" INTEGER NOT NULL DEFAULT 0,
"playedShopName" TEXT NOT NULL DEFAULT 'Bayshore',
"ocmMainDraw" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "OCMGhostBattleRecord_pkey" PRIMARY KEY ("dbId")
);
-- CreateTable
CREATE TABLE "OCMGhostTrail" (
"dbId" SERIAL NOT NULL,
"carId" INTEGER NOT NULL,
"area" INTEGER NOT NULL,
"ramp" INTEGER NOT NULL,
"path" INTEGER NOT NULL,
"trail" BYTEA NOT NULL,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"playedAt" INTEGER NOT NULL DEFAULT 0,
"tunePower" INTEGER NOT NULL DEFAULT 0,
"tuneHandling" INTEGER NOT NULL DEFAULT 0,
"ocmBattle" BOOLEAN NOT NULL DEFAULT false,
"ocmMainDraw" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "OCMGhostTrail_pkey" PRIMARY KEY ("dbId")
);
-- AddForeignKey
ALTER TABLE "OCMPlayRecord" ADD CONSTRAINT "OCMPlayRecord_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("carId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OCMTop1Ghost" ADD CONSTRAINT "OCMTop1Ghost_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("carId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OCMTally" ADD CONSTRAINT "OCMTally_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("carId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OCMGhostBattleRecord" ADD CONSTRAINT "OCMGhostBattleRecord_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("carId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OCMGhostTrail" ADD CONSTRAINT "OCMGhostTrail_carId_fkey" FOREIGN KEY ("carId") REFERENCES "Car"("carId") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,17 @@
-- AlterTable
ALTER TABLE "Car" ALTER COLUMN "stLoseBits" SET DEFAULT 0;
-- CreateTable
CREATE TABLE "OCMPeriod" (
"dbId" SERIAL NOT NULL,
"competitionDbId" INTEGER NOT NULL,
"competitionId" INTEGER NOT NULL,
"periodId" INTEGER NOT NULL,
"startAt" INTEGER NOT NULL,
"closeAt" INTEGER NOT NULL,
CONSTRAINT "OCMPeriod_pkey" PRIMARY KEY ("dbId")
);
-- AddForeignKey
ALTER TABLE "OCMPeriod" ADD CONSTRAINT "OCMPeriod_competitionDbId_fkey" FOREIGN KEY ("competitionDbId") REFERENCES "OCMEvent"("dbId") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,17 @@
/*
Warnings:
- You are about to drop the column `ocmBattle` on the `GhostTrail` table. All the data in the column will be lost.
- You are about to drop the column `ocmMainDay` on the `GhostTrail` table. All the data in the column will be lost.
- You are about to drop the column `ocmBattle` on the `OCMGhostTrail` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Car" ALTER COLUMN "stLoseBits" SET DEFAULT 0;
-- AlterTable
ALTER TABLE "GhostTrail" DROP COLUMN "ocmBattle",
DROP COLUMN "ocmMainDay";
-- AlterTable
ALTER TABLE "OCMGhostTrail" DROP COLUMN "ocmBattle";

View File

@ -0,0 +1,13 @@
/*
Warnings:
- You are about to drop the column `ocmBattle` on the `GhostBattleRecord` table. All the data in the column will be lost.
- You are about to drop the column `ocmMainDay` on the `GhostBattleRecord` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Car" ALTER COLUMN "stLoseBits" SET DEFAULT 0;
-- AlterTable
ALTER TABLE "GhostBattleRecord" DROP COLUMN "ocmBattle",
DROP COLUMN "ocmMainDay";

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Car" ADD COLUMN "country" TEXT NOT NULL DEFAULT 'JPN',
ALTER COLUMN "stLoseBits" SET DEFAULT 0;

View File

@ -2,274 +2,387 @@
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_URL")
provider = "postgresql"
url = env("POSTGRES_URL")
}
model User {
id Int @id @default(autoincrement())
chipId String @unique
accessCode String
cars Car[]
carOrder Int[]
items UserItem[]
tutorials Boolean[]
userBanned Boolean @default(false)
bookmarks Int[]
ScratchSheet ScratchSheet[]
currentSheet Int @default(1)
lastScratched Int @default(0) // Timestamp
id Int @id @default(autoincrement())
chipId String @unique
accessCode String
cars Car[]
carOrder Int[]
items UserItem[]
tutorials Boolean[]
userBanned Boolean @default(false)
bookmarks Int[]
ScratchSheet ScratchSheet[]
currentSheet Int @default(1)
lastScratched Int @default(0) // Timestamp
}
model ScratchSheet {
id Int @id @default(autoincrement())
User User @relation(fields: [userId], references: [id])
userId Int
sheetNo Int // Player's sheet number (i.e. first sheet)
squares ScratchSquare[]
id Int @id @default(autoincrement())
User User @relation(fields: [userId], references: [id])
userId Int
sheetNo Int // Player's sheet number (i.e. first sheet)
squares ScratchSquare[]
}
model ScratchSquare {
id Int @id @default(autoincrement())
Sheet ScratchSheet @relation(fields: [sheetId], references: [id])
sheetId Int
category Int
itemId Int
earned Boolean
id Int @id @default(autoincrement())
Sheet ScratchSheet @relation(fields: [sheetId], references: [id])
sheetId Int
category Int
itemId Int
earned Boolean
}
model UserItem {
userItemId Int @id @default(autoincrement())
category Int
itemId Int
User User @relation(fields: [userId], references: [id])
userId Int
type Int @default(0)
earnedAt Int @default(0)
userItemId Int @id @default(autoincrement())
category Int
itemId Int
User User @relation(fields: [userId], references: [id])
userId Int
type Int @default(0)
earnedAt Int @default(0)
}
model Car {
user User @relation(fields: [userId], references: [id])
userId Int
user User @relation(fields: [userId], references: [id])
userId Int
// This is the Car object itself
carId Int @id @default(autoincrement())
name String
manufacturer Int
regionId Int @default(0)
model Int
visualModel Int
customColor Int @default(0)
defaultColor Int
wheel Int @default(0)
wheelColor Int @default(0)
aero Int @default(0)
bonnet Int @default(0)
wing Int @default(0)
mirror Int @default(0)
neon Int @default(0)
trunk Int @default(0)
plate Int @default(0)
plateColor Int @default(0)
plateNumber Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
title String @default("New Car")
level Int @default(0)
windowSticker Boolean @default(false)
windowStickerString String @default("")
windowStickerFont Int @default(0)
windowDecoration Int @default(0)
rivalMarker Int @default(0)
lastPlayedAt Int @default(0)
aura Int @default(0)
auraMotif Int @default(0)
ghostLevel Int @default(1)
// This is the Car object itself
carId Int @id @default(autoincrement())
name String
manufacturer Int
regionId Int @default(0)
country String @default("JPN")
model Int
visualModel Int
customColor Int @default(0)
defaultColor Int
wheel Int @default(0)
wheelColor Int @default(0)
aero Int @default(0)
bonnet Int @default(0)
wing Int @default(0)
mirror Int @default(0)
neon Int @default(0)
trunk Int @default(0)
plate Int @default(0)
plateColor Int @default(0)
plateNumber Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
title String @default("New Car")
level Int @default(0)
windowSticker Boolean @default(false)
windowStickerString String @default("")
windowStickerFont Int @default(0)
windowDecoration Int @default(0)
rivalMarker Int @default(0)
lastPlayedAt Int @default(0)
aura Int @default(0)
auraMotif Int @default(0)
ghostLevel Int @default(1)
// This is more data about the car
tuningPoints Int @default(0)
odometer Int @default(0)
playCount Int @default(0)
earnedCustomColor Boolean @default(false)
carSettingsDbId Int @unique
settings CarSettings @relation(fields: [carSettingsDbId], references: [dbId])
vsPlayCount Int @default(0)
vsBurstCount Int @default(0)
vsStarCount Int @default(0)
vsCoolOrWild Int @default(0)
vsSmoothOrRough Int @default(0)
vsTripleStarMedals Int @default(0)
vsDoubleStarMedals Int @default(0)
vsSingleStarMedals Int @default(0)
vsPlainMedals Int @default(0)
rgPlayCount Int @default(0)
rgWinCount Int @default(0)
rgTrophy Int @default(0)
rgScore Int @default(0)
rgStamp Int @default(0)
rgAcquireAllCrowns Boolean @default(false)
rgRegionMapScore Int[]
dressupLevel Int @default(0)
dressupPoint Int @default(0)
stPlayCount Int @default(0)
stClearBits Int @default(0)
stClearDivCount Int @default(0)
stClearCount Int @default(0)
stLoseBits BigInt @default(0)
stConsecutiveWins Int @default(0)
stConsecutiveWinsMax Int @default(0)
stCompleted100Episodes Boolean @default(false)
// This is more data about the car
tuningPoints Int @default(0)
odometer Int @default(0)
playCount Int @default(0)
earnedCustomColor Boolean @default(false)
carSettingsDbId Int @unique
settings CarSettings @relation(fields: [carSettingsDbId], references: [dbId])
vsPlayCount Int @default(0)
vsBurstCount Int @default(0)
vsStarCount Int @default(0)
vsCoolOrWild Int @default(0)
vsSmoothOrRough Int @default(0)
vsTripleStarMedals Int @default(0)
vsDoubleStarMedals Int @default(0)
vsSingleStarMedals Int @default(0)
vsPlainMedals Int @default(0)
rgPlayCount Int @default(0)
rgWinCount Int @default(0)
rgTrophy Int @default(0)
rgScore Int @default(0)
rgStamp Int @default(0)
rgAcquireAllCrowns Boolean @default(false)
rgRegionMapScore Int[]
dressupLevel Int @default(0)
dressupPoint Int @default(0)
stPlayCount Int @default(0)
stClearBits Int @default(0)
stClearDivCount Int @default(0)
stClearCount Int @default(0)
stLoseBits BigInt @default(0)
stConsecutiveWins Int @default(0)
stConsecutiveWinsMax Int @default(0)
stCompleted100Episodes Boolean @default(false)
items CarItem[]
carGTWingDbId Int @unique
gtWing CarGTWing @relation(fields: [carGTWingDbId], references: [dbId])
carStateDbId Int @unique
state CarState @relation(fields: [carStateDbId], references: [dbId])
TimeAttackRecord TimeAttackRecord[]
CarCrown CarCrown[]
GhostTrail GhostTrail[]
GhostBattleRecord GhostBattleRecord[]
CarPathandTuning CarPathandTuning[]
items CarItem[]
carGTWingDbId Int @unique
gtWing CarGTWing @relation(fields: [carGTWingDbId], references: [dbId])
carStateDbId Int @unique
state CarState @relation(fields: [carStateDbId], references: [dbId])
TimeAttackRecord TimeAttackRecord[]
CarCrown CarCrown[]
GhostTrail GhostTrail[]
GhostBattleRecord GhostBattleRecord[]
CarPathandTuning CarPathandTuning[]
OCMGhostBattleRecord OCMGhostBattleRecord[]
OCMTally OCMTally[]
OCMTop1Ghost OCMTop1Ghost[]
OCMGhostTrail OCMGhostTrail[]
OCMPlayRecord OCMPlayRecord[]
}
model CarGTWing {
dbId Int @id @default(autoincrement())
car Car?
dbId Int @id @default(autoincrement())
car Car?
pillar Int @default(0)
pillarMaterial Int @default(0)
mainWing Int @default(0)
mainWingColor Int @default(0)
wingTip Int @default(0)
material Int @default(0)
pillar Int @default(0)
pillarMaterial Int @default(0)
mainWing Int @default(0)
mainWingColor Int @default(0)
wingTip Int @default(0)
material Int @default(0)
}
model CarItem {
dbId Int @id @default(autoincrement())
Car Car @relation(fields: [carId], references: [carId])
carId Int
category Int
itemId Int
amount Int
dbId Int @id @default(autoincrement())
Car Car @relation(fields: [carId], references: [carId])
carId Int
category Int
itemId Int
amount Int
}
model CarSettings {
dbId Int @id @default(autoincrement())
car Car?
dbId Int @id @default(autoincrement())
car Car?
view Boolean @default(true)
transmission Boolean @default(false)
retire Boolean @default(false)
meter Int @default(0)
navigationMap Boolean @default(true)
volume Int @default(1)
bgm Int @default(0)
nameplate Int @default(0)
nameplateColor Int @default(0)
terminalBackground Int @default(0)
view Boolean @default(true)
transmission Boolean @default(false)
retire Boolean @default(false)
meter Int @default(0)
navigationMap Boolean @default(true)
volume Int @default(1)
bgm Int @default(0)
nameplate Int @default(0)
nameplateColor Int @default(0)
terminalBackground Int @default(0)
}
model CarState {
dbId Int @id @default(autoincrement())
car Car?
dbId Int @id @default(autoincrement())
car Car?
hasOpponentGhost Boolean @default(false)
eventJoined Boolean @default(false)
transferred Boolean @default(false)
toBeDeleted Boolean @default(false)
}
model TimeAttackRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
model Int // Car model, literally just the `model` field from Car
time Int
course Int
isMorning Boolean
section1Time Int @map("section1Time")
section2Time Int @map("section2Time")
section3Time Int @map("section3Time")
section4Time Int @map("section4Time")
section5Time Int? @map("section5Time")
section6Time Int? @map("section6Time")
section7Time Int? @map("section7Time")
tunePower Int @default(0) // Car Power
tuneHandling Int @default(0) // Car Handling
}
model CarCrown {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int @unique
ramp Int
path Int
playedAt Int @default(0)
tunePower Int
tuneHandling Int
}
model GhostTrail {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int
ramp Int
path Int
trail Bytes
time Int?
driveData Bytes? @db.ByteA
driveDMergeSerial Int?
trendBinaryByUser Bytes? @db.ByteA
byUserMergeSerial Int?
trendBinaryByArea Bytes? @db.ByteA
byAreaMergeSerial Int?
trendBinaryByCar Bytes? @db.ByteA
byCarMergeSerial Int?
playedAt Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
crownBattle Boolean @default(false)
}
model GhostBattleRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
tunePower Int @default(0)
tuneHandling Int @default(0)
opponent1CarId Int
opponent1Result Int
opponent1TunePower Int
opponent1TuneHandling Int
opponent2CarId Int?
opponent2Result Int?
opponent2TunePower Int?
opponent2TuneHandling Int?
opponent3CarId Int?
opponent3Result Int?
opponent3TunePower Int?
opponent3TuneHandling Int?
area Int @default(0)
playedAt Int @default(0)
playedShopName String @default("Bayshore")
hasOpponentGhost Boolean @default(false)
eventJoined Boolean @default(false)
transferred Boolean @default(false)
toBeDeleted Boolean @default(false)
competitionState Int @default(0)
}
model CarPathandTuning {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int @default(0)
ramp Int @default(0)
path Int @default(0)
tunePower Int @default(17)
tuneHandling Int @default(17)
lastPlayedAt Int @default(0)
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int @default(0)
ramp Int @default(0)
path Int @default(0)
tunePower Int @default(17)
tuneHandling Int @default(17)
lastPlayedAt Int @default(0)
}
model CarCrown {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int @unique
ramp Int
path Int
playedAt Int @default(0)
tunePower Int
tuneHandling Int
}
model TimeAttackRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
model Int // Car model, literally just the `model` field from Car
time Int
course Int
isMorning Boolean
section1Time Int @map("section1Time")
section2Time Int @map("section2Time")
section3Time Int @map("section3Time")
section4Time Int @map("section4Time")
section5Time Int? @map("section5Time")
section6Time Int? @map("section6Time")
section7Time Int? @map("section7Time")
tunePower Int @default(0) // Car Power
tuneHandling Int @default(0) // Car Handling
}
model GhostTrail {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int
ramp Int
path Int
trail Bytes
time Int?
driveData Bytes? @db.ByteA
driveDMergeSerial Int?
trendBinaryByUser Bytes? @db.ByteA
byUserMergeSerial Int?
trendBinaryByArea Bytes? @db.ByteA
byAreaMergeSerial Int?
trendBinaryByCar Bytes? @db.ByteA
byCarMergeSerial Int?
playedAt Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
crownBattle Boolean @default(false)
}
model GhostBattleRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
tunePower Int @default(0)
tuneHandling Int @default(0)
opponent1CarId Int
opponent1Result Int
opponent1TunePower Int
opponent1TuneHandling Int
opponent2CarId Int?
opponent2Result Int?
opponent2TunePower Int?
opponent2TuneHandling Int?
opponent3CarId Int?
opponent3Result Int?
opponent3TunePower Int?
opponent3TuneHandling Int?
area Int @default(0)
playedAt Int @default(0)
playedShopName String @default("Bayshore")
}
model OCMEvent {
dbId Int @id @default(autoincrement())
competitionId Int
qualifyingPeriodStartAt Int
qualifyingPeriodCloseAt Int
competitionStartAt Int
competitionCloseAt Int
competitionEndAt Int
lengthOfPeriod Int
lengthOfInterval Int
area Int @default(0)
minigamePatternId Int @default(0)
OCMPeriod OCMPeriod[]
}
model OCMPlayRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
competitionId Int
periodId Int
brakingPoint Int?
playedAt Int @default(0)
}
model OCMTop1Ghost {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
competitionId Int
periodId Int
result Int
tunePower Int @default(0)
tuneHandling Int @default(0)
}
model OCMTop1GhostTrail {
dbId Int @id @default(autoincrement())
carId Int
area Int
ramp Int
path Int
trail Bytes
competitionId Int
periodId Int
playedAt Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
ocmMainDraw Boolean @default(false)
}
model OCMTally {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
competitionId Int
periodId Int
result Int
tunePower Int @default(0)
tuneHandling Int @default(0)
}
model OCMGhostBattleRecord {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
tunePower Int @default(0)
tuneHandling Int @default(0)
competitionId Int
periodId Int
result Int
area Int @default(0)
playedAt Int @default(0)
playedShopName String @default("Bayshore")
ocmMainDraw Boolean @default(false)
}
model OCMGhostTrail {
dbId Int @id @default(autoincrement())
car Car @relation(fields: [carId], references: [carId])
carId Int
area Int
ramp Int
path Int
trail Bytes
competitionId Int
periodId Int
playedAt Int @default(0)
tunePower Int @default(0)
tuneHandling Int @default(0)
ocmMainDraw Boolean @default(false)
}
model OCMPeriod {
dbId Int @id @default(autoincrement())
OCMEvent OCMEvent @relation(fields: [competitionDbId], references: [dbId])
competitionDbId Int
competitionId Int
periodId Int
startAt Int
closeAt Int
}

View File

@ -73,15 +73,26 @@ allnetApp.use((req, res, next) => {
next()
});
// Get all of the files in the modules directory
let dirs = fs.readdirSync('dist/modules');
for (let i of dirs) {
if (i.endsWith('.js')) {
// Loop over the files
for (let i of dirs)
{
// If the file is a .js file
if (i.endsWith('.js'))
{
// Require the module file
let mod = require(`./modules/${i.substring(0, i.length - 3)}`); // .js extension
// Create an instance of the module
let inst = new mod.default();
// Register the module with the app
inst.register(appRouter);
}
}
// Host on / and /wmmt6/ path
app.use('/', appRouter);
app.use('/wmmt6/', appRouter);
@ -90,15 +101,22 @@ app.all('*', (req, res) => {
res.status(200).end();
})
// Register the ALL.NET / Mucha Server
new AllnetModule().register(allnetApp);
new MuchaModule().register(muchaApp);
// Sentry is in use
if (useSentry)
{
// Use the sentry error handler
app.use(Sentry.Handlers.errorHandler());
}
// Get the wangan key / certificate file
let key = fs.readFileSync('./server_wangan.key');
let cert = fs.readFileSync('./server_wangan.crt');
// Create the (ALL.Net) server
http.createServer(allnetApp).listen(PORT_ALLNET, '0.0.0.0', 511, () => {
console.log(`ALL.net server listening on port ${PORT_ALLNET}!`);
let unix = Config.getConfig().unix;
@ -110,10 +128,12 @@ http.createServer(allnetApp).listen(PORT_ALLNET, '0.0.0.0', 511, () => {
}
})
// Create the mucha server
https.createServer({key, cert}, muchaApp).listen(PORT_MUCHA, '0.0.0.0', 511, () => {
console.log(`Mucha server listening on port ${PORT_MUCHA}!`);
})
// Create the game server
https.createServer({key, cert}, app).listen(PORT_BNGI, '0.0.0.0', 511, () => {
console.log(`Game server listening on port ${PORT_BNGI}!`);
})

619
src/modules/cars.ts Normal file
View File

@ -0,0 +1,619 @@
import { Application } from "express";
import { Config } from "../config";
import { Module } from "module";
import { prisma } from "..";
import { User } from "@prisma/client";
import Long from "long";
// Import Proto
import * as wm from "../wmmt/wm.proto";
// Import Util
import * as common from "../util/common";
import * as scratch from "../util/scratch";
export default class CarModule extends Module {
register(app: Application): void {
// Load Car
app.post('/method/load_car', async (req, res) => {
// Get the request body for the load car request
let body = wm.wm.protobuf.LoadCarRequest.decode(req.body);
// Get the car (required data only) with the given id
let car = await prisma.car.findFirst({
where: {
carId: body.carId
},
include: {
settings: true,
items: true,
gtWing: true
}
});
// Error handling if ghostLevel accidentally set to 0 or more than 10
if(car!.ghostLevel < 1){
car!.ghostLevel = 1;
}
if(car!.ghostLevel > 11){
car!.ghostLevel = 10;
}
// Convert the database lose bits to a Long
let longLoseBits = Long.fromString(car!.stLoseBits.toString());
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get current / previous active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where:{
competitionEndAt:
{
lte: date // competitionEndAt less than current date
}
},
orderBy: [
{
dbId: 'desc'
},
{
competitionEndAt: 'desc',
},
],
});
// Declare GhostCompetitionSchedule
let ghostCarsNo1;
let trailIdNo1: number = 0;
if(ocmEventDate)
{
let pastDay = date - ocmEventDate.competitionEndAt
if(pastDay < 604800)
{
let getNo1OCM = await prisma.oCMTally.findFirst({
where:{
competitionId: ocmEventDate.competitionId,
periodId: 999999999
},
orderBy:{
result: 'desc'
}
});
if(getNo1OCM)
{
let carId = getNo1OCM.carId
// Get Car Data
let cars = await prisma.car.findFirst({
where:{
carId: carId
},
include:{
gtWing: true
}
});
// Get Place
let playedPlace = wm.wm.protobuf.Place.create({
placeId: 'JPN0123',
shopName: Config.getConfig().shopName,
regionId: 18,
country: 'JPN'
});
// Get Ghost Trail
let ghostTrailNo1 = await prisma.oCMTop1GhostTrail.findFirst({
where:{
carId: carId,
competitionId: ocmEventDate.competitionId,
periodId: 999999999
}
});
trailIdNo1 = ghostTrailNo1!.dbId;
ghostCarsNo1 = wm.wm.protobuf.GhostCar.create({
car: {
...cars!,
lastPlayedPlace: playedPlace
},
area: ghostTrailNo1!.area,
ramp: ghostTrailNo1!.ramp,
path: ghostTrailNo1!.path,
nonhuman: false,
type: wm.wm.protobuf.GhostType.GHOST_NORMAL,
trailId: trailIdNo1
});
}
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
car: {
...car!
},
tuningPoint: car!.tuningPoints,
setting: car!.settings,
vsStarCountMax: car!.vsStarCount,
rgPreviousVersionPlayCount: 0,
stCompleted_100Episodes: car!.stCompleted100Episodes,
auraMotifAutoChange: false,
screenshotCount: 0,
transferred: false,
...car!,
stLoseBits: longLoseBits,
ownedItems: car!.items,
lastPlayedAt: car!.lastPlayedAt,
announceEventModePrize: true,
opponentGhost: ghostCarsNo1 || null,
opponentTrailId: trailIdNo1 || null,
opponentCompetitionId: ocmEventDate?.competitionId || null
};
// Generate the load car response message
let message = wm.wm.protobuf.LoadCarResponse.encode(msg);
// Send the response
common.sendResponse(message, res);
});
// Create new car
app.post('/method/create_car', async (req, res) => {
// Get the request body for the create car request
let body = wm.wm.protobuf.CreateCarRequest.decode(req.body);
// Get the current date/time (unix epoch)
let date = Math.floor(new Date().getTime() / 1000)
// Retrieve user from card chip / user id
let user: User | null;
// User ID provided, use that
if (body.userId) {
user = await prisma.user.findFirst({
where: {
id: body.userId
},
});
} else { // No user id, use card chip
user = await prisma.user.findFirst({
where: {
chipId: body.cardChipId,
accessCode: body.accessCode
},
})
}
// User not found, terminate
if (!user) throw new Error();
// Generate blank car settings object
let settings = await prisma.carSettings.create({
data: {}
});
// Generate blank car state object
let state = await prisma.carState.create({
data: {}
})
let gtWing = await prisma.carGTWing.create({
data: {}
})
// Sets if full tune is used or not
// let fullyTuned = false;
// 0: Stock Tune
// 1: Basic Tune (600 HP)
// 2: Fully Tuned (840 HP)
let tune = 0;
// If a user item has been used
if (body.userItemId)
{
console.log(`Item used - ID ${body.userItemId}`);
// Remove the user item from the database
let item = await prisma.userItem.delete({
where: {
userItemId: body.userItemId
}
});
console.log('Item deleted!');
switch(item.category)
{
case 203: // Car Tune Ticket
// Switch on item id
switch(item.itemId)
{
// Discarded Vehicle Card
case 1: tune = 1; break;
case 2: tune = 1; break;
case 3: tune = 1; break;
// Fully Tuned Ticket
case 5: tune = 2; break;
default: // Unknown item type, throw unsupported error
throw Error("Unsupported itemId: " + item.itemId);
}
break;
case 201: // Special Car Ticket
// Fully tuned special cars
if (scratch.fullyTunedCars.includes(item.itemId))
{
// Car is fully tuned
tune = 2;
}
// Basic tuned special cars
if (scratch.basicTunedCars.includes(item.itemId))
{
// If gift cars fully tuned is set
if (Config.getConfig().gameOptions.giftCarsFullyTuned)
{
// Car is fully tuned
tune = 2;
}
else // Gift cars fully tuned not set
{
// Car is basic tuned
tune = 1;
}
}
// Stock tuned special cars
if (scratch.stockTunedCars.includes(item.itemId))
{
// If gift cars fully tuned is set
if (Config.getConfig().gameOptions.giftCarsFullyTuned)
{
// Car is fully tuned
tune = 2;
}
else // Gift cars fully tuned not set
{
// Car is stock
tune = 0;
}
}
break;
}
console.log(`Item category was ${item.category} and item game ID was ${item.itemId}`);
}
// Other cases, may occur if item is not detected as 'used'
// User item not used, but car has 740 HP by default
else if (body.car &&
(body.car.tunePower == 17) && (body.car.tuneHandling == 17))
{
// Car is fully tuned
tune = 2;
}
// User item not used, but car has 600 HP by default
else if (body.car &&
(body.car.tunePower == 10) && (body.car.tuneHandling == 10))
{
// Car is basic tuned
tune = 1;
}
// User item not used, but gift cars fully tuned switch is set
else if (Config.getConfig().gameOptions.giftCarsFullyTuned)
{
// List of event / exclusive car IDs
let event_cars = [
0x7A, // Mini
0x82, // S660
0x83, // S2000
0x89, // NDERC
0x8B, // GS130 (Starts at 20 Stories by default)
];
// If the car visual model is not null and is in the list of event cars
if (body.car.visualModel && event_cars.includes(body.car.visualModel))
{
// Set full tune used to be true
tune = 2;
}
}
// Default car values
let randomRegionId = Math.floor(Math.random() * 47) + 1;
let carInsert = {
userId: user.id,
manufacturer: body.car.manufacturer!,
defaultColor: body.car.defaultColor!,
model: body.car.model!,
visualModel: body.car.visualModel!,
name: body.car.name!,
title: body.car.title!,
level: body.car.level!,
tunePower: body.car.tunePower!,
tuneHandling: body.car.tuneHandling!,
carSettingsDbId: settings.dbId,
carStateDbId: state.dbId,
carGTWingDbId: gtWing.dbId,
regionId: randomRegionId,
lastPlayedAt: date,
};
// Check if user have more than one cars
let checkWindowSticker = await prisma.car.findFirst({
where: {
userId: body.userId
},
select: {
windowStickerString: true,
windowStickerFont: true
}
});
let additionalWindowStickerInsert = {
}
// If more than one cars, get the window sticker string
if(checkWindowSticker)
{
additionalWindowStickerInsert = {
windowStickerString: checkWindowSticker.windowStickerString,
windowStickerFont: checkWindowSticker.windowStickerFont,
}
}
// Additional car values (for basic / full tune)
let additionalInsert = {
}
// Switch on tune status
switch(tune)
{
// 0: Stock, nothing extra
case 1: // Basic Tune
// Updated default values
carInsert.level = 2; // C8
carInsert.tunePower = 10; // 600 HP
carInsert.tuneHandling = 10; // 600 HP
// Additional basic tune values
additionalInsert = {
ghostLevel: 4,
stClearBits: 0,
stLoseBits: 0,
stClearCount: 20,
stClearDivCount: 1,
stConsecutiveWins: 20,
stConsecutiveWinsMax: 20
};
break;
case 2: // Fully Tuned
// Updated default values
carInsert.level = 8; // C3
carInsert.tunePower = 17; // 740 HP
carInsert.tuneHandling = 17; // 740 HP
// Additional full tune values
additionalInsert = {
ghostLevel: 10,
stClearBits: 0,
stLoseBits: 0,
stClearCount: 80,
stClearDivCount: 4,
stConsecutiveWins: 80,
stConsecutiveWinsMax: 80
};
}
// Insert the car into the database
let car = await prisma.car.create({
data: {
...carInsert,
...additionalInsert,
...additionalWindowStickerInsert
}
});
// Get the user's current car order
let carOrder = user.carOrder;
// Add the new car to the front of the id
carOrder.unshift(car.carId);
// Add the car to the front of the order
await prisma.user.update({
where: {
id: user.id
},
data: {
carOrder: carOrder
}
});
console.log(`Created new car ${car.name} with ID ${car.carId}`);
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
carId: car.carId,
car,
...carInsert,
...additionalInsert
}
// Generate the load car response message
let message = wm.wm.protobuf.CreateCarResponse.encode(msg);
// Send the response
common.sendResponse(message, res);
})
// Saving the certain car update (on saving bannapass screen or after exiting user's detail car data or after editing car dress up on terminal)
app.post('/method/update_car', async (req, res) => {
// Get the request body for the update car request
let body = wm.wm.protobuf.UpdateCarRequest.decode(req.body);
// Get the ghost result for the car
let cars = body?.car;
// Declare data
let data : any;
// Car is set
if (cars)
{
// Car update data
data = {
customColor: cars.customColor || undefined,
wheel: cars.wheel || undefined,
wheelColor: cars.wheelColor || undefined,
aero: cars.aero || undefined,
bonnet: cars.bonnet || undefined,
wing: cars.wing || undefined,
mirror: cars.mirror || undefined,
neon: cars.neon || undefined,
trunk: cars.trunk || undefined,
plate: cars.plate || undefined,
plateColor: cars.plateColor || undefined,
plateNumber: cars.plateNumber || undefined,
windowSticker: cars.windowSticker || undefined,
windowDecoration: cars.windowDecoration || undefined,
rivalMarker: cars.rivalMarker || undefined,
aura: cars.aura || undefined,
auraMotif: cars.auraMotif || undefined,
rgStamp: body?.rgStamp! || undefined,
}
// Update the car info
await prisma.car.update({
where: {
carId: body.carId
},
data: data
})
}
// Get the car with the given id
let car = await prisma.car.findFirst({
where: {
carId: body.carId
},
include: {
settings: true,
gtWing: true
}
});
// Update the car settings
await prisma.carSettings.update({
where: {
dbId: car?.carSettingsDbId,
},
data: {
...body.setting
}
});
// Update the user data
let userData = await prisma.car.findFirst({
where:{
carId: body.carId
},
select:{
userId: true,
windowStickerString: true
}
})
// Newer window sticker string is different from the older one
// Check if window sticker string is not null
// (windowStickerString value when changing custom color in driver unit is undefined)
if(body.car?.windowStickerString)
{
if(userData!.windowStickerString !== body.car.windowStickerString){
console.log('Updating Window Sticker');
await prisma.car.updateMany({
where: {
userId: userData!.userId
},
data: {
windowStickerString: body.car.windowStickerString,
windowStickerFont: body.car.windowStickerFont!
}
})
}
}
// Get car item (custom color or discarded card)
if(body.earnedItems.length !== 0){
console.log('Car Item reward available, continuing ...');
for(let i=0; i<body.earnedItems.length; i++){
await prisma.carItem.create({
data: {
carId: body.carId,
category: body.earnedItems[i].category,
itemId: body.earnedItems[i].itemId,
amount: 1
}
});
}
}
// Update the GT Wing (custom wing) info
// Get the GT Wing data for the car
let gtWing = body.car?.gtWing;
// GT Wing is set
if (gtWing)
{
let dataGTWing : any = {
pillar: gtWing.pillar || undefined,
pillarMaterial: gtWing.pillarMaterial || undefined,
mainWing: gtWing.mainWing || undefined,
mainWingColor: gtWing.mainWingColor || undefined,
wingTip: gtWing.wingTip || undefined,
material: gtWing.material || undefined,
}
await prisma.carGTWing.update({
where: {
dbId: body.carId
},
data: dataGTWing
})
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
}
// Encode the response
let message = wm.wm.protobuf.UpdateCarResponse.encode(msg);
// Send the response
common.sendResponse(message, res);
})
}
}

File diff suppressed because it is too large Load Diff

640
src/modules/ghost.ts Normal file
View File

@ -0,0 +1,640 @@
import { Application } from "express";
import { Module } from "module";
import { prisma } from "..";
import { CarPathandTuning, GhostTrail } from "@prisma/client";
import Long from "long";
// Import Proto
import * as wm from "../wmmt/wm.proto";
import * as wmsrv from "../wmmt/service.proto";
// Import Util
import * as common from "../util/common";
import * as ghost_save_trail from "../util/games/ghost_save_trail";
import * as ghost_trail from "../util/games/ghost_trail";
export default class GhostModule extends Module {
register(app: Application): void {
// Load Ghost Battle Info
app.post('/method/load_ghost_battle_info', async (req, res) => {
// ---For testing only---
let cars = await prisma.car.findMany({
where: {
OR: [
{
name: { startsWith: '' }
},
{
name: { startsWith: 'きつ' }
},
],
},
include:{
gtWing: true
},
orderBy: {
carId: 'asc'
},
take: 10
});
for(let i=0; i<cars.length; i++)
{
// If regionId is 0 or not set, game will crash after defeating the ghost
if(cars[i].regionId === 0)
{
let randomRegionId = Math.floor(Math.random() * 47) + 1;
cars[i].regionId = randomRegionId; // Change car region id to 1 (Hokkaido)
}
}
// ----------------------
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
stampSheetCount: 100,
history: cars || null,
};
// Encode the response
let message = wm.wm.protobuf.LoadGhostBattleInfoResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Load Stamp Target (Still not working)
app.post('/method/load_stamp_target', async (req, res) => {
// Get the request body for the load stamp target request
let body = wm.wm.protobuf.LoadStampTargetRequest.encode(req.body);
// TODO: Actual stamp stuff here
// This is literally just bare-bones
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
};
// Encode the response
let message = wm.wm.protobuf.LoadStampTargetResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Ghost Mode - Search ghost car by level
app.post('/method/search_cars_by_level', async (req, res) => {
// Get the request body for the search cars by level request
let body = wm.wm.protobuf.SearchCarsByLevelRequest.decode(req.body);
// Find ghost car by selected level
let car = await prisma.car.findMany({
where: {
ghostLevel: body.ghostLevel
},
include:{
gtWing: true
}
});
// Randomizing the starting ramp and path based on selected area
let rampVal = 0;
let pathVal = 0;
if(body.area === 0){ // GID_RUNAREA_C1
rampVal = Math.floor(Math.random() * 4);
pathVal = Math.floor(Math.random() * 10);
}
else if(body.area === 1){ // GID_RUNAREA_RING
rampVal = Math.floor(Math.random() * 2) + 4;
pathVal = Math.floor(Math.random() * 6) + 10;
}
else if(body.area === 2){ // GID_RUNAREA_SUBTOKYO_3_4
rampVal = Math.floor(Math.random() * 2) + 6;
pathVal = Math.floor(Math.random() * 2) + 16;
}
else if(body.area === 3){ // GID_RUNAREA_SUBTOKYO_5
rampVal = Math.floor(Math.random() * 2) + 8;
pathVal = Math.floor(Math.random() * 2) + 18;
}
else if(body.area === 4){ // GID_RUNAREA_WANGAN
rampVal = Math.floor(Math.random() * 4) + 10;
pathVal = Math.floor(Math.random() * 7) + 20;
}
else if(body.area === 5){ // GID_RUNAREA_K1
rampVal = Math.floor(Math.random() * 4) + 14;
pathVal = Math.floor(Math.random() * 7) + 27;
}
else if(body.area === 6){ // GID_RUNAREA_YAESU
rampVal = Math.floor(Math.random() * 3) + 18;
pathVal = Math.floor(Math.random() * 4) + 34;
}
else if(body.area === 7){ // GID_RUNAREA_YOKOHAMA
rampVal = Math.floor(Math.random() * 4) + 21;
pathVal = Math.floor(Math.random() * 11) + 38;
}
else if(body.area === 8){ // GID_RUNAREA_NAGOYA
rampVal = 25;
pathVal = 49;
}
else if(body.area === 9){ // GID_RUNAREA_OSAKA
rampVal = 26;
pathVal = Math.floor(Math.random() * 4) + 50;
}
else if(body.area === 10){ // GID_RUNAREA_KOBE
rampVal = Math.floor(Math.random() * 2) + 27;
pathVal = Math.floor(Math.random() * 2) + 54;
}
else if(body.area === 11){ // GID_RUNAREA_FUKUOKA
rampVal = Math.floor(Math.random() * 4) + 29;
pathVal = Math.floor(Math.random() * 4) + 58;
}
else if(body.area === 12){ // GID_RUNAREA_HAKONE
rampVal = Math.floor(Math.random() * 2) + 33;
pathVal = Math.floor(Math.random() * 2) + 62;
}
else if(body.area === 13){ // GID_RUNAREA_TURNPIKE
rampVal = Math.floor(Math.random() * 2) + 35;
pathVal = Math.floor(Math.random() * 2) + 64;
}
//14 - 16 are dummy area, 17 is GID_RUNAREA_C1_CLOSED
else if(body.area === 18){ // GID_RUNAREA_HIROSHIMA
rampVal = Math.floor(Math.random() * 2) + 37;
pathVal = Math.floor(Math.random() * 2) + 56;
}
// Empty list of ghost car records
let lists_ghostcar: wm.wm.protobuf.GhostCar[] = [];
let arr = [];
let maxNumber = 0;
// If all user car data available is more than 10 for certain level
if(car.length > 10){
maxNumber = 10 // Limit to 10 (game default)
}
// If no more than 10
else{
maxNumber = car.length;
}
// Choose the user's car data randomly to appear
while(arr.length < maxNumber){
// Pick random car Id
let randomNumber: number = Math.floor(Math.random() * car.length);
if(arr.indexOf(randomNumber) === -1){
// Push current number to array
arr.push(randomNumber);
// Check if ghost trail for certain car is available
let ghost_trails = await prisma.ghostTrail.findFirst({
where: {
carId: car[randomNumber].carId,
area: body.area,
crownBattle: false
},
orderBy: {
playedAt: 'desc'
}
});
// If regionId is 0 or not set, game will crash after defeating the ghost
if(car[randomNumber]!.regionId === 0)
{
let randomRegionId = Math.floor(Math.random() * 47) + 1;
car[randomNumber].regionId = randomRegionId; // Hokkaido
}
// Push user's car data without ghost trail
if(!(ghost_trails)){
lists_ghostcar.push(wm.wm.protobuf.GhostCar.create({
car: car[randomNumber]
}));
}
// Push user's car data with ghost trail
else{
// Set the tunePower used when playing certain area
car[randomNumber].tunePower = ghost_trails!.tunePower;
// Set the tuneHandling used when playing certain area
car[randomNumber].tuneHandling = ghost_trails!.tuneHandling;
// Push data to Ghost car proto
lists_ghostcar.push(wm.wm.protobuf.GhostCar.create({
car: car[randomNumber],
nonhuman: false,
type: wm.wm.protobuf.GhostType.GHOST_NORMAL,
trailId: ghost_trails!.dbId!
}));
}
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
ramp: rampVal,
path: pathVal,
ghosts: lists_ghostcar,
selectionMethod: Number(wm.wm.protobuf.GhostSelectionMethod.GHOST_SELECT_BY_LEVEL)
};
// Encode the response
let message = wm.wm.protobuf.SearchCarsByLevelResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Load Normal Ghost Trail data
app.post('/method/load_ghost_drive_data', async (req, res) => {
// Get the request body for the load ghost drive data request
let body = wm.wm.protobuf.LoadGhostDriveDataRequest.decode(req.body);
// Get the path value from request body
let path = body.path;
// Empty list of ghost drive data records
let lists_ghostcar: wm.wm.protobuf.LoadGhostDriveDataResponse.GhostDriveData[] = [];
// Check how many opponent ghost data available (including user data)
for(let i=0; i<body.carTunings.length; i++){
// Check if opponent ghost have trail
let ghost_trails = await prisma.ghostTrail.findFirst({
where: {
carId: body.carTunings[i].carId!,
path: body.path,
},
orderBy: {
playedAt: 'desc'
}
});
// ---Get the user and opponent ghost trail data---
let ghostType: number = wm.wm.protobuf.GhostType.GHOST_NORMAL;
let lists_binarydriveData;
if(ghost_trails?.driveData !== null && ghost_trails?.driveData !== undefined){
lists_binarydriveData = wm.wm.protobuf.BinaryData.create({
data: ghost_trails!.driveData!,
mergeSerial: ghost_trails!.driveDMergeSerial!
});
}
let lists_binaryByArea
if(ghost_trails?.trendBinaryByArea !== null && ghost_trails?.trendBinaryByArea !== undefined){
lists_binaryByArea = wm.wm.protobuf.BinaryData.create({
data: ghost_trails!.trendBinaryByArea!,
mergeSerial: ghost_trails!.byAreaMergeSerial!
});
}
let lists_binaryByCar
if(ghost_trails?.trendBinaryByCar !== null && ghost_trails?.trendBinaryByCar !== undefined){
lists_binaryByCar = wm.wm.protobuf.BinaryData.create({
data: ghost_trails!.trendBinaryByCar!,
mergeSerial: ghost_trails!.byCarMergeSerial!
});
}
let lists_binaryByUser
if(ghost_trails?.trendBinaryByUser !== null && ghost_trails?.trendBinaryByUser !== undefined){
lists_binaryByUser = wm.wm.protobuf.BinaryData.create({
data: ghost_trails!.trendBinaryByUser!,
mergeSerial: ghost_trails!.byUserMergeSerial!
});
}
// ------------------------------------------------
// Push the data
lists_ghostcar.push(wm.wm.protobuf.LoadGhostDriveDataResponse.GhostDriveData.create({ // Push the data
carId: Number(body.carTunings[i].carId!),
type: ghostType,
driveData: lists_binarydriveData || null,
trendBinaryByArea: lists_binaryByArea || null,
trendBinaryByCar: lists_binaryByCar || null,
trendBinaryByUser: lists_binaryByUser || null,
}));
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
path: path,
data: lists_ghostcar
};
// Encode the response
let message = wm.wm.protobuf.LoadGhostDriveDataResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Saving the ghost trail on mileage screen
app.post('/method/register_ghost_trail', async (req, res) => {
// Get the request body for the register ghost trail request
let body = wm.wm.protobuf.RegisterGhostTrailRequest.decode(req.body);
// Get the session id
let actualSessionId: number = 0;
// If the session are set, and are long data
if(Long.isLong(body.ghostSessionId))
{
// Convert them to BigInt and add to the data
actualSessionId = common.getBigIntFromLong(body.ghostSessionId);
}
// OCM game mode session id
if(actualSessionId > 100 && actualSessionId < 201)
{
console.log('OCM Ghost Battle Game found');
// User playing ocm battle game mode
await ghost_save_trail.saveOCMGhostTrail(body);
}
// Ghost Battle or Crown Ghost Battle game Mode session id
else if(actualSessionId > 0 && actualSessionId < 101)
{
// Check if it is crown ghost battle or not (crown ghost battle don't have time, driveData, trendBinaryByArea, trendBinaryByCar, trendBinaryByUser value from request body)
if(!(body.trendBinaryByArea) && !(body.trendBinaryByCar) && !(body.trendBinaryByUser))
{
console.log('Crown Ghost Battle Game found');
// User is playing crown ghost battle game mode
await ghost_save_trail.saveCrownGhostTrail(body);
}
else
{
console.log('Normal Ghost Battle Game found');
// User is playing normal ghost battle game mode
await ghost_save_trail.saveNormalGhostTrail(body);
// Saving Ghost Path and Tuning
await ghost_save_trail.savePathAndTuning(body);
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS
}
// Encode the response
let message = wm.wm.protobuf.RegisterGhostTrailResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Load Crown Ghost Trail or Top 1 OCM Ghost Trail
app.get('/resource/ghost_trail', async (req, res) => {
// Get url query parameter [For Crown Ghost Battle]
let pCarId = Number(req.query.car_id);
let pArea = Number(req.query.area);
// Get url query parameter [For OCM Ghost Battle]
let pTrailId = Number(req.query.trail_id) || undefined;
// Declare variable
let rampVal: number = 0;
let pathVal: number = 0;
let playedAt: number = 0;
let ghostTrail;
// Query parameter from OCM Battle available
if(pTrailId)
{
// Get the trail data
let ghost_trails = await ghost_trail.getOCMGhostTrail(pCarId, pTrailId);
pArea = ghost_trails.areaVal;
rampVal = ghost_trails.rampVal;
pathVal = ghost_trails.pathVal;
playedAt = ghost_trails.playedAt;
ghostTrail = ghost_trails.ghostTrail;
}
// Query parameter from Crown Ghost Battle available
else
{
// Get the crown trail data
let ghost_trails = await ghost_trail.getCrownGhostTrail(pCarId, pArea);
rampVal = ghost_trails.rampVal;
pathVal = ghost_trails.pathVal;
playedAt = ghost_trails.playedAt;
ghostTrail = ghost_trails.ghostTrail;
}
// Response data
let msg = {
carId: pCarId,
area: pArea,
ramp: rampVal,
path: pathVal,
playedAt: playedAt,
trail: ghostTrail
};
// Encode the response
let message = wm.wm.protobuf.GhostTrail.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Load ghost path and tunings per area
app.post('/method/load_paths_and_tunings', async (req, res) => {
// Get the request body for the load path and tunings request
let body = wm.wm.protobuf.LoadPathsAndTuningsRequest.decode(req.body);
// Empty list of car path and tuning records
let carTbyP: wm.wm.protobuf.LoadPathsAndTuningsResponse.CarTuningsByPath[] = [];
// Loop GID_RUNAREA_*
for(let j=0; j<19; j++)
{
// 14 - 16 are dummy area, 17 is C1 Closed
if(j >= 14){
j = 18; // GID_RUNAREA_HIROSHIMA
}
let rampVal = 0;
let pathVal = 0;
if(j === 0){ // GID_RUNAREA_C1
rampVal = Math.floor(Math.random() * 4);
pathVal = Math.floor(Math.random() * 10);
}
else if(j === 1){ // GID_RUNAREA_RING
rampVal = Math.floor(Math.random() * 2) + 4;
pathVal = Math.floor(Math.random() * 6) + 10;
}
else if(j === 2){ // GID_RUNAREA_SUBTOKYO_3_4
rampVal = Math.floor(Math.random() * 2) + 6;
pathVal = Math.floor(Math.random() * 2) + 16;
}
else if(j === 3){ // GID_RUNAREA_SUBTOKYO_5
rampVal = Math.floor(Math.random() * 2) + 8;
pathVal = Math.floor(Math.random() * 2) + 18;
}
else if(j === 4){ // GID_RUNAREA_WANGAN
rampVal = Math.floor(Math.random() * 4) + 10;
pathVal = Math.floor(Math.random() * 7) + 20;
}
else if(j === 5){ // GID_RUNAREA_K1
rampVal = Math.floor(Math.random() * 4) + 14;
pathVal = Math.floor(Math.random() * 7) + 27;
}
else if(j === 6){ // GID_RUNAREA_YAESU
rampVal = Math.floor(Math.random() * 3) + 18;
pathVal = Math.floor(Math.random() * 4) + 34;
}
else if(j === 7){ // GID_RUNAREA_YOKOHAMA
rampVal = Math.floor(Math.random() * 4) + 21;
pathVal = Math.floor(Math.random() * 11) + 38;
}
else if(j === 8){ // GID_RUNAREA_NAGOYA
rampVal = 25;
pathVal = 49;
}
else if(j === 9){ // GID_RUNAREA_OSAKA
rampVal = 26;
pathVal = Math.floor(Math.random() * 4) + 50;
}
else if(j === 10){ // GID_RUNAREA_KOBE
rampVal = Math.floor(Math.random() * 2) + 27;
pathVal = Math.floor(Math.random() * 2) + 54;
}
else if(j === 11){ // GID_RUNAREA_FUKUOKA
rampVal = Math.floor(Math.random() * 4) + 29;
pathVal = Math.floor(Math.random() * 4) + 58;
}
else if(j === 12){ // GID_RUNAREA_HAKONE
rampVal = Math.floor(Math.random() * 2) + 33;
pathVal = Math.floor(Math.random() * 2) + 62;
}
else if(j === 13){ // GID_RUNAREA_TURNPIKE
rampVal = Math.floor(Math.random() * 2) + 35;
pathVal = Math.floor(Math.random() * 2) + 64;
}
//14 - 16 are dummy area, 17 is GID_RUNAREA_C1_CLOSED
else if(j === 18){ // GID_RUNAREA_HIROSHIMA
rampVal = Math.floor(Math.random() * 2) + 37;
pathVal = Math.floor(Math.random() * 2) + 56;
}
// Empty list of car tuning records
let carTuning: wm.wm.protobuf.CarTuning[] = [];
let pathAndTuning: CarPathandTuning | null;
// Loop to how many opponent ghost selected by user
for(let i=0; i<body.selectedCars.length; i++)
{
// Get path and tuning per area
pathAndTuning = await prisma.carPathandTuning.findFirst({
where: {
carId: body.selectedCars[i],
area: j
},
orderBy: {
area: 'asc'
}
});
// Opponent ghost have path and tuning record for certain area
if(pathAndTuning)
{
// Push the data
carTuning.push(wm.wm.protobuf.CarTuning.create({
carId: body.selectedCars[i],
tunePower: pathAndTuning!.tunePower,
tuneHandling: pathAndTuning!.tuneHandling,
lastPlayedAt: pathAndTuning!.lastPlayedAt
}));
}
// Opponent ghost doesn't have path and tuning record for certain area
else
{
// Get user's car last used tunePower and tuneHandling
let car = await prisma.car.findFirst({
where: {
carId: body.carId
},
select:{
tunePower: true,
tuneHandling: true
}
});
// Push the data
carTuning.push(wm.wm.protobuf.CarTuning.create({
carId: body.selectedCars[i],
// Set the tunePower used when last played the game
tunePower: car!.tunePower,
// Set the tuneHandling used when last played the game
tuneHandling: car!.tuneHandling
}));
}
}
// Push the data
carTbyP.push(wm.wm.protobuf.LoadPathsAndTuningsResponse.CarTuningsByPath.create({
area: j,
ramp: rampVal,
path: pathVal,
carTunings: carTuning,
selectionMethod: wm.wm.protobuf.PathSelectionMethod.PATH_NORMAL // idk what this is
}));
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
data: carTbyP
};
// Encode the response
let message = wm.wm.protobuf.LoadPathsAndTuningsResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// idk what this is this for
app.post('/method/lock_crown', (req, res) => {
// Response data
let msg = {
error: wmsrv.wm.protobuf.ErrorCode.ERR_SUCCESS,
};
// Encode the response
let message = wmsrv.wm.protobuf.LockCrownResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
}
}

551
src/modules/ghost_ocm.ts Normal file
View File

@ -0,0 +1,551 @@
import { Application } from "express";
import { Module } from "module";
import { prisma } from "..";
import { Car, CarGTWing } from "@prisma/client";
// Import Proto
import * as wm from "../wmmt/wm.proto";
// Import Util
import * as common from "../util/common";
import * as ghost_ocm from "../util/games/ghost_ocm";
import * as ghost_ocm_area from "../util/games/games_util/ghost_ocm_area";
import { Config } from "../config";
export default class GhostModule extends Module {
register(app: Application): void {
// Get OCM Battle event info
app.post('/method/load_ghost_competition_info', async (req, res) => {
// Get the request body for the load stamp target request
let body = wm.wm.protobuf.LoadGhostCompetitionInfoRequest.decode(req.body);
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get currently active OCM event (query still not complete)
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
let msg: any;
if(ocmEventDate)
{
// Check OCM Period
let ocmPeriodCount = await prisma.oCMPeriod.count({
where:{
competitionId: ocmEventDate.competitionId
}
});
if(ocmPeriodCount === 0)
{
console.log('Calculating how many period(s) are available');
let competitionPeriodStartTimestamp = ocmEventDate.competitionStartAt;
let competitionPeriodEndTimeStamp = 0;
let period = 1;
// Count how many period
while(competitionPeriodStartTimestamp < ocmEventDate.competitionCloseAt)
{
// Count period closing timestamp
competitionPeriodEndTimeStamp = competitionPeriodStartTimestamp + ocmEventDate.lengthOfPeriod;
// competitionPeriodEndTimeStamp is more than competitionCloseAt
if(competitionPeriodEndTimeStamp > ocmEventDate.competitionCloseAt)
{
competitionPeriodEndTimeStamp = ocmEventDate.competitionCloseAt;
}
// Insert to table
await prisma.oCMPeriod.create({
data:{
competitionDbId: ocmEventDate.dbId,
competitionId: ocmEventDate.competitionId,
periodId: period,
startAt: competitionPeriodStartTimestamp,
closeAt: competitionPeriodEndTimeStamp
}
});
period++;
competitionPeriodStartTimestamp = competitionPeriodEndTimeStamp + ocmEventDate.lengthOfInterval;
}
if(ocmEventDate.lengthOfInterval !== 0)
{
let qualifyingEndTimeStamp = ocmEventDate.qualifyingPeriodCloseAt - ocmEventDate.lengthOfInterval
await prisma.oCMEvent.update({
where:{
dbId: ocmEventDate.dbId
},
data:{
qualifyingPeriodCloseAt: qualifyingEndTimeStamp
}
})
}
console.log('Calculating Period Completed!');
}
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt <= date && ocmEventDate!.competitionCloseAt >= date)
{
console.log('Current OCM Day : Competition Day / Main Draw');
// Get Current OCM Period
let OCMCurrentPeriod = await prisma.oCMPeriod.findFirst({
where: {
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId,
startAt:
{
lte: date, // competitionStartAt is less than current date
},
closeAt:
{
gte: date, // competitionCloseAt is greater than current date
}
}
});
if(OCMCurrentPeriod)
{
// Get OCM Tally Count
let OCMTallyCount = await prisma.oCMTally.count({
where: {
competitionId: OCMCurrentPeriod.competitionId,
periodId: OCMCurrentPeriod.periodId
},
orderBy:{
periodId: 'desc'
}
});
// If not yet tallying
if(OCMTallyCount === 0)
{
await ghost_ocm.ocmTallying(body, OCMCurrentPeriod.periodId, false);
// Completed
console.log('Tally Completed!');
}
// Get Competition Day Event Data for the car
let ocmCompetitionDay = await ghost_ocm.ocmCompetitionDay(body, OCMCurrentPeriod!.competitionId, OCMCurrentPeriod!.periodId);
// Response Data
msg = ocmCompetitionDay.msg;
}
else
{
// Response data
msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
closed: true,
qualified: false, // if this set to false, user cannot enter OCM Battle game mode
};
}
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
console.log('Current OCM Day : Qualifying Day');
// Get Competition Day Event Data for the car
let ocmCompetitionDay = await ghost_ocm.ocmQualifyingDay(body, ocmEventDate.competitionId);
// Response Data
msg = ocmCompetitionDay.msg;
}
// OCM has ended
else if(ocmEventDate!.competitionCloseAt < date && ocmEventDate!.competitionEndAt > date)
{
console.log('Current OCM Day : OCM has Ended');
// Tallying
// Get Current OCM Period
let OCMCurrentPeriod = await prisma.oCMPeriod.findFirst({
where: {
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId
},
orderBy: {
periodId: 'desc'
}
});
if(OCMCurrentPeriod)
{
// Get OCM Tally Count
let OCMTallyCount = await prisma.oCMTally.count({
where: {
competitionId: OCMCurrentPeriod.competitionId,
periodId: 999999999
},
orderBy:{
periodId: 'desc'
}
});
// If not yet tallying
if(OCMTallyCount === 0)
{
console.log('Tallying');
await ghost_ocm.ocmTallying(body, OCMCurrentPeriod.periodId, true);
// Completed
console.log('Last Tally Completed!');
}
// Checking if nameplate reward is given
let checkOneParticipant = await prisma.oCMPlayRecord.findFirst({
orderBy:{
dbId: 'desc'
}
});
if(checkOneParticipant)
{
let itemId = 0;
// 16th - C1
if(ocmEventDate.competitionId === 1)
{
itemId = 204;
}
// 17th - Osaka
else if(ocmEventDate.competitionId === 2)
{
itemId = 210;
}
// 18th - Fukuoka
else if(ocmEventDate.competitionId === 3)
{
itemId = 216;
}
// 19th - Nagoya
else if(ocmEventDate.competitionId === 4)
{
itemId = 222;
}
// 6th - C1
else if(ocmEventDate.competitionId === 5)
{
itemId = 35;
}
// 20th - Kobe
else if(ocmEventDate.competitionId === 6)
{
itemId = 228;
}
// 7th - Fukutoshin
else if(ocmEventDate.competitionId === 7)
{
itemId = 41;
}
// 21st - Hiroshima
else if(ocmEventDate.competitionId === 8)
{
itemId = 234;
}
// 8th - Hakone
else if(ocmEventDate.competitionId === 9)
{
itemId = 47;
}
let checkNameplate = await prisma.carItem.count({
where:{
carId: checkOneParticipant.carId,
category: 17,
itemId: itemId
},
orderBy:{
itemId: 'desc'
}
});
if(checkNameplate === 0)
{
await ghost_ocm.ocmGiveNamePlateReward(ocmEventDate.competitionId);
}
// else{} nameplate reward already given
}
}
// Response data
msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
closed: true
};
}
}
// No OCM Event
else{
msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
closed: true
};
}
// Encode the response
let message = wm.wm.protobuf.LoadGhostCompetitionInfoResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Get the Top 1 OCM Ghost for qualifying day and competition day (this still not completed)
app.get('/resource/ghost_competition_target', async (req, res) => {
// Get url query parameter (competition_id)
let competition_id = Number(req.query.competition_id);
// Calling OCM Area function (BASE_PATH/src/util/games/games_util/ghost_ocm.ts)
let OCMArea = await ghost_ocm_area.OCMArea(competition_id);
// Set the value from OCMArea
let areaVal: number = OCMArea.areaVal;
let rampVal: number = OCMArea.rampVal;
let pathVal: number = OCMArea.pathVal;
// Get url query parameter (period_id)
let period_id = Number(req.query.period_id);
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get currently active OCM event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
// Declare variable for Top 1 OCM Ghost
let ghostCars: wm.wm.protobuf.GhostCar;
let ghostTypes;
let cars: wm.wm.protobuf.ICar | (Car & { gtWing: CarGTWing; }) | null;
let playedPlace = wm.wm.protobuf.Place.create({
placeId: 'JPN0123',
shopName: Config.getConfig().shopName,
regionId: 18,
country: 'JPN'
});
// Get default trail id
let ghostTrailId = 0;
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
console.log('OCM Competition Day / Main Draw');
// --- Tally (still not complete) ---
// Get Top 1 qualifying car data
let ocmTallyRecord = await prisma.oCMTop1Ghost.findFirst({
where:{
competitionId: competition_id,
periodId: period_id
},
orderBy:{
result: 'desc'
},
});
// Get Top 1 qualifying ghost trail id
let checkGhostTrail = await prisma.oCMTop1GhostTrail.findFirst({
where:{
carId: ocmTallyRecord!.carId,
area: areaVal,
ramp: rampVal,
path: pathVal,
},
orderBy:{
playedAt: 'desc'
},
});
// ----------------------------------
// Top 1 OCM Ghost trail data available
if(checkGhostTrail)
{
// Get the Top 1 OCM car data
cars = await prisma.car.findFirst({
where:{
carId: checkGhostTrail!.carId
},
include:{
gtWing: true
}
});
// If regionId is 0 or not set, game will crash after defeating the ghost
if(cars!.regionId === 0)
{
let randomRegionId = Math.floor(Math.random() * 47) + 1;
cars!.regionId = randomRegionId;
}
// Set the tunePower used when playing ghost crown
cars!.tunePower = ocmTallyRecord!.tunePower;
// Set the tuneHandling used when playing ghost crown
cars!.tuneHandling = ocmTallyRecord!.tuneHandling;
// Set Ghost stuff Value
cars!.lastPlayedAt = checkGhostTrail.playedAt
ghostTrailId = checkGhostTrail.dbId!;
ghostTypes = wm.wm.protobuf.GhostType.GHOST_NORMAL;
}
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
console.log('OCM Qualifying Day');
// Get the default ghost trail
let checkGhostTrail = await prisma.oCMTop1GhostTrail.findFirst({
where:{
area: areaVal,
ramp: rampVal,
path: pathVal,
competitionId: ocmEventDate!.competitionId,
periodId: 0
},
orderBy:{
playedAt: 'desc'
}
});
// Generate default S660 car data
cars = wm.wm.protobuf.Car.create({
carId: 999999999, // Don't change this
name: '',
regionId: 18, // IDN (福井)
manufacturer: 12, // HONDA
model: 105, // S660 [JW5]
visualModel: 130, // S660 [JW5]
defaultColor: 0,
customColor: 0,
wheel: 20,
wheelColor: 0,
aero: 0,
bonnet: 0,
wing: 0,
mirror: 0,
neon: 0,
trunk: 0,
plate: 0,
plateColor: 0,
plateNumber: 0,
tunePower: checkGhostTrail!.tunePower,
tuneHandling: checkGhostTrail!.tuneHandling,
rivalMarker: 32,
aura: 551,
windowSticker: true,
windowStickerString: '',
windowStickerFont: 0,
title: 'You don\'t have S660? LMAO',
level: 65, // SSSSS
lastPlayedAt: checkGhostTrail!.playedAt,
country: 'GLB'
});
// Set Ghost stuff Value
ghostTrailId = checkGhostTrail!.dbId!;
ghostTypes = wm.wm.protobuf.GhostType.GHOST_NORMAL;
}
// Push the Top 1 OCM ghost car data
ghostCars = wm.wm.protobuf.GhostCar.create({
car: {
...cars!,
lastPlayedPlace: playedPlace
},
area: areaVal,
ramp: rampVal,
path: pathVal,
nonhuman: false,
type: ghostTypes,
trailId: ghostTrailId
});
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
competitionId: competition_id,
ghostCar: ghostCars!,
trailId: ghostTrailId,
updatedAt: date
};
// Encode the response
let message = wm.wm.protobuf.GhostCompetitionTarget.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
}
}

View File

@ -1,18 +1,128 @@
import { Application } from "express";
import {Module} from "module";
import { Config } from "../config";
import { prisma } from "..";
// Import Proto
import * as wm from "../wmmt/wm.proto";
import * as wmsrv from "../wmmt/service.proto";
import { prisma } from "..";
import { count } from "console";
// Import Util
import * as common from "../util/common";
export default class StartupModule extends Module {
register(app: Application): void {
app.post('/method/register_system_info', (req, res) => {
// Register system info upon booting
app.post('/method/register_system_info', async (req, res) => {
// Get the request body for the load stamp target request
let body = wm.wm.protobuf.RegisterSystemInfoRequest.decode(req.body);
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get current / previous active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy: [
{
dbId: 'desc'
},
{
competitionEndAt: 'desc',
},
],
});
if(!(ocmEventDate))
{
ocmEventDate = await prisma.oCMEvent.findFirst({
orderBy: [
{
dbId: 'desc'
},
{
competitionEndAt: 'desc',
},
],
});
}
// Declare GhostCompetitionSchedule
let compeSch;
if(ocmEventDate)
{
let pastDay = date - ocmEventDate.competitionEndAt
if(pastDay < 604800)
{
// Creating GhostCompetitionSchedule
compeSch = wm.wm.protobuf.GhostCompetitionSchedule.create({
// OCM Competition ID (1 = C1 (Round 16), 4 = Nagoya (Round 19), 8 = Hiroshima (Round 21))
competitionId: ocmEventDate.competitionId,
// OCM Qualifying Start Timestamp
qualifyingPeriodStartAt: ocmEventDate.qualifyingPeriodStartAt,
// OCM Qualifying Close Timestamp
qualifyingPeriodCloseAt: ocmEventDate.qualifyingPeriodCloseAt,
// OCM Competition (Main Draw) Start Timestamp
competitionStartAt: ocmEventDate.competitionStartAt,
// OCM Competition (Main Draw) Close Timestamp
competitionCloseAt: ocmEventDate.competitionCloseAt,
// OCM Competition (Main Draw) End Timestamp
competitionEndAt: ocmEventDate.competitionEndAt,
// idk what this is
lengthOfPeriod: ocmEventDate.lengthOfPeriod,
// idk what this is
lengthOfInterval: ocmEventDate.lengthOfInterval,
// Area for the event (GID_RUNAREA_*, 8 is GID_RUNAREA_NAGOYA)
area: ocmEventDate.area,
// idk what this is
minigamePatternId: ocmEventDate.minigamePatternId
});
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
regionId: 1,
placeId: "JPN0123",
regionId: body.allnetRegion0,
placeId: body.regionName0,
allowedClientLogTypes: [],
ghostSelectionMinRedoWait: 30,
ghostSelectionMaxRedoWait: 4000,
@ -22,62 +132,80 @@ export default class StartupModule extends Module {
month: 7,
pluses: 0,
releaseAt: 0 // idk what this is
}
},
competitionSchedule: compeSch || null // OCM Event Available or not
}
let resp = wm.wm.protobuf.RegisterSystemInfoResponse.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
// Encode the response
let message = wm.wm.protobuf.RegisterSystemInfoResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Place List
app.get('/resource/place_list', (req, res) => {
console.log('place list');
// Empty list of place records
let places: wm.wm.protobuf.Place[] = [];
// Response data
places.push(new wm.wm.protobuf.Place({
placeId: "JPN0123",
regionId: 1,
shopName: Config.getConfig().shopName,
country: "JPN"
}));
let resp = wm.wm.protobuf.PlaceList.encode({places});
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
// Encode the response
let message = wm.wm.protobuf.PlaceList.encode({places});
// Send the response to the client
common.sendResponse(message, res);
})
// Get Ranking data for attract screen (TA, Ghost, VS)
app.get('/resource/ranking', async (req, res) => {
console.log('ranking');
// Empty list of all ranking records (Combination of TA, VS Stars, and Ghost Battle Win)
let lists: wmsrv.wm.protobuf.Ranking.List[] = [];
// Get TA Ranking
for(let i=0; i<25; i++){
let ta_time = await prisma.timeAttackRecord.findMany({
for(let i=0; i<25; i++){ // GID_TACOURSE ID
// Get the TA time per course
let ta_time = await prisma.timeAttackRecord.findMany({
where: {
course: i
},
orderBy: {
time: 'asc'
},
take: 10,
take: 10, // Take top 10
});
if(ta_time.length > 0){
let list_ta: wmsrv.wm.protobuf.Ranking.Entry[] = [];
for(let j=0; j<ta_time.length; j++){
let car_ta = await prisma.car.findFirst({
// TA time record by user is available for certain course
if(ta_time.length > 0){
// Empty list of ranking records for user time attack
let list_ta: wmsrv.wm.protobuf.Ranking.Entry[] = [];
// Get the TA time data
for(let j=0; j<ta_time.length; j++){
// Get the car data
let car_ta = await prisma.car.findFirst({
where: {
carId: ta_time[j].carId
}
});
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// Push the data to the ranking data
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: ta_time[j].carId,
rank: car_ta!.level,
result: ta_time[j].time,
@ -86,31 +214,39 @@ export default class StartupModule extends Module {
model: car_ta!.model,
visualModel: car_ta!.visualModel,
defaultColor: car_ta!.defaultColor,
tunePower: ta_time[j].tunePower,
tuneHandling: ta_time[j].tuneHandling,
tunePower: ta_time[j].tunePower, // Set the tunePower used when playing TA
tuneHandling: ta_time[j].tuneHandling, // Set the tuneHandling used when playing TA
title: car_ta!.title,
level: car_ta!.level
}));
}
if(ta_time.length < 11){
for(let j=ta_time.length; j<11; j++){
let resultTime = 599999;
if(i === 22 || i === 23){
resultTime = 1199999;
// If the TA time record by user is less than 10 user
if(ta_time.length < 11){
// Take the remaining unfilled
for(let j=ta_time.length; j<11; j++){
let resultTime = 599999; // 9:59:999 time
// GID_TACOURSE_TOKYOALL & GID_TACOURSE_KANAGAWAALL area
if(i === 22 || i === 23){
resultTime = 1199999; // 19:59:999 time
}
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// Push the data to the ranking data
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: 0,
rank: 0,
result: resultTime,
name: '',
regionId: 0,
model: Math.floor(Math.random() * 50),
visualModel: Math.floor(Math.random() * 100),
regionId: 1, // Hokkaido
model: Math.floor(Math.random() * 50), // Randomizing Car data
visualModel: Math.floor(Math.random() * 100), // Randomizing Car data
defaultColor: 0,
tunePower: 0,
tuneHandling: 0,
title: 'Wangan Beginner',
level: 0
title: 'Wangan Beginner', // 湾岸の新人
level: 0 // N
}));
}
}
@ -120,47 +256,64 @@ export default class StartupModule extends Module {
topRecords: list_ta
}));
}
else{
let list_ta: wmsrv.wm.protobuf.Ranking.Entry[] = [];
for(let j=0; j<11; j++){
let resulttime = 599999;
if(i === 22 || i === 23){
resulttime = 1199999
// There is no user's TA record for certain area
else{
// Empty list of ranking records for time attack
let list_ta: wmsrv.wm.protobuf.Ranking.Entry[] = [];
// Generate the top 10 TA time data
for(let j=0; j<11; j++){
let resulttime = 599999; // 9:59:999 time
// GID_TACOURSE_TOKYOALL & GID_TACOURSE_KANAGAWAALL area
if(i === 22 || i === 23){
resulttime = 1199999 // 19:59:999 time
}
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// Push the data to the ranking data
list_ta.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: 0,
rank: 0,
result: resulttime,
name: '',
regionId: 0,
model: Math.floor(Math.random() * 50),
visualModel: Math.floor(Math.random() * 100),
regionId: 1, // Hokkaido
model: Math.floor(Math.random() * 50), // Randomizing Car data
visualModel: Math.floor(Math.random() * 100), // Randomizing Car data
defaultColor: 0,
tunePower: 0,
tuneHandling: 0,
title: 'Wangan Beginner',
level: 0
title: 'Wangan Beginner', // 湾岸の新人
level: 0 // N
}));
}
lists.push(new wmsrv.wm.protobuf.Ranking.List({
// Push the certain area ranking data to the list
lists.push(new wmsrv.wm.protobuf.Ranking.List({
rankingType: i, // RANKING_TA_*AREA*
topRecords: list_ta
topRecords: list_ta // Top 10 TA time record data
}));
}
}
// Get VS Star Ranking
let car_vs = await prisma.car.findMany({
// Get the user's VS Stars data
let car_vs = await prisma.car.findMany({
orderBy: {
vsStarCount: 'desc'
},
take: 20,
take: 20, // Take top 20
});
let list_vs: wmsrv.wm.protobuf.Ranking.Entry[] = [];
for(let i=0; i<car_vs.length; i++){
list_vs.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// Empty list of ranking records for VS Stars
let list_vs: wmsrv.wm.protobuf.Ranking.Entry[] = [];
// Get the VS stars data
for(let i=0; i<car_vs.length; i++){
// Push the car data to the ranking data
list_vs.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: car_vs[i].carId,
rank: car_vs[i].level,
result: car_vs[i].vsStarCount,
@ -175,40 +328,55 @@ export default class StartupModule extends Module {
level: car_vs[i].level
}));
}
if(car_vs.length < 20){
for(let j=car_vs.length; j<21; j++){
list_vs.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// If the VS stars record by user is less than 20 user
if(car_vs.length < 20){
// Take the remaining unfilled
for(let j=car_vs.length; j<21; j++){
// Push the data to the ranking data
list_vs.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: 0,
rank: 0,
result: 0,
name: '',
regionId: 0,
model: Math.floor(Math.random() * 50),
visualModel: Math.floor(Math.random() * 100),
regionId: 1, // Hokkaido
model: Math.floor(Math.random() * 50), // Randomizing Car data
visualModel: Math.floor(Math.random() * 100), // Randomizing Car data
defaultColor: 0,
tunePower: 0,
tuneHandling: 0,
title: 'Wangan Beginner',
level: 0
title: 'Wangan Beginner', // 湾岸の新人
level: 0 // N
}));
}
}
// Push the data
lists.push(new wmsrv.wm.protobuf.Ranking.List({
rankingType: 100, // RANKING_VS_STAR
topRecords: list_vs
topRecords: list_vs // Top 20 VS stars record data
}));
// Get Ghost Defeated Ranking
let car_ghost = await prisma.car.findMany({
// Get the user's Ghost Win data
let car_ghost = await prisma.car.findMany({
orderBy: {
rgWinCount: 'desc'
},
take: 20,
take: 20, // Take top 20
});
let list_ghost: wmsrv.wm.protobuf.Ranking.Entry[] = [];
for(let i=0; i<car_ghost.length; i++){
list_ghost.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// Empty list of ranking records for Ghost Battle Win
let list_ghost: wmsrv.wm.protobuf.Ranking.Entry[] = [];
// Get the Ghost Battle Win data
for(let i=0; i<car_ghost.length; i++){
// Push the car data to the ranking data
list_ghost.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: car_ghost[i].carId,
rank: car_ghost[i].level,
result: car_ghost[i].rgWinCount,
@ -223,72 +391,94 @@ export default class StartupModule extends Module {
level: car_ghost[i].level
}));
}
if(car_ghost.length < 20){
for(let j=car_ghost.length; j<21; j++){
list_ghost.push(wmsrv.wm.protobuf.Ranking.Entry.create({
// If the Ghost Win record by user is less than 20 user
if(car_ghost.length < 20){
// Take the remaining unfilled
for(let j=car_ghost.length; j<21; j++){
// Push the data to the ranking data
list_ghost.push(wmsrv.wm.protobuf.Ranking.Entry.create({
carId: 0,
rank: 0,
result: 0,
name: '',
regionId: 0,
model: Math.floor(Math.random() * 50),
visualModel: Math.floor(Math.random() * 100),
regionId: 1, // Hokkaido
model: Math.floor(Math.random() * 50), // Randomizing Car data
visualModel: Math.floor(Math.random() * 100), // Randomizing Car data
defaultColor: 0,
tunePower: 0,
tuneHandling: 0,
title: 'Wangan Beginner',
level: 0
title: 'Wangan Beginner', // 湾岸の新人
level: 0 // N
}));
}
}
// Push the data
lists.push(new wmsrv.wm.protobuf.Ranking.List({
rankingType: 101, // RANKING_GHOST_DEFEATED_COUNT
topRecords: list_ghost
topRecords: list_ghost // Top 20 Ghost Win record data
}));
let resp = wmsrv.wm.protobuf.Ranking.encode({lists});
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
})
// Encode the response
let message = wmsrv.wm.protobuf.Ranking.encode({lists});
// Send the response to the client
common.sendResponse(message, res);
})
// Ping
app.post('/method/ping', (req, res) => {
console.log('ping');
let body = wm.wm.protobuf.PingRequest.decode(req.body);
// Response data
let ping = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
pong: body.ping || 1
};
let resp = wm.wm.protobuf.PingResponse.encode(ping);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
// Encode the response
let message = wm.wm.protobuf.PingResponse.encode(ping);
// Send the response to the client
common.sendResponse(message, res);
})
// Crown List for attract screen and Crown Ghost Battle mode
app.get('/resource/crown_list', async (req, res) => {
console.log('crown_list');
let list_crown: wmsrv.wm.protobuf.Crown[] = [];
let car_crown = await prisma.carCrown.findMany({
// Empty list of crown records
let list_crown: wmsrv.wm.protobuf.Crown[] = [];
// Get the crown holder data
let car_crown = await prisma.carCrown.findMany({
orderBy: {
area: 'asc'
}
});
if(car_crown.length !== 0){
// Crown holder data available
if(car_crown.length !== 0){
let counter = 0;
for(let i=0; i<19; i++){
if(i >= 14){
i = 18;
// Loop GID_RUNAREA
for(let i=0; i<19; i++){
// 14 - 16 are dummy area, 17 is C1 Closed
if(i >= 14){
i = 18; // GID_RUNAREA_HIROSHIMA
}
if(car_crown[counter].area === i){
// Crown holder for certain area available
if(car_crown[counter].area === i){
// Get user's data
let car = await prisma.car.findFirst({
where: {
carId: car_crown[counter].carId
@ -297,14 +487,37 @@ export default class StartupModule extends Module {
gtWing: true
}
});
if(car!.regionId === 0){
car!.regionId = 1; // Hokkaido
// If regionId is 0 or not set, game will crash after defeating the ghost
if(car!.regionId === 0)
{
/* Region Id
01 = Hokkaido
02 = Aomori
03 = Iwate
04 = Miyagi
05 = Akita
06 = Yamagata
07 = Fukushima
08 = Ibaraki
09 = Tochigi
10 = Gunma
11 = Saitama
12 = Chiba
13 = Tokyo
19 = Yamanashi
*/
car!.regionId = i + 1; // Change car region id
}
car!.tunePower = car_crown[counter].tunePower;
car!.tuneHandling = car_crown[counter].tuneHandling;
// Error handling if played At value is current date
if(car_crown[counter].playedAt !== 0 && car_crown[counter].playedAt >= 1659805200)
// Set the tunePower used when capturing ghost crown
car!.tunePower = car_crown[counter].tunePower;
// Set the tunePower used when capturing ghost crown
car!.tuneHandling = car_crown[counter].tuneHandling;
// Error handling if played At timestamp value is current date and timestamp is bigger than 9 July 2022 (using GMT+7 timestamp)
if(car_crown[counter].playedAt !== 0 && car_crown[counter].playedAt >= 1657299600)
{
// Acquired crown timestamp - 1 day
car!.lastPlayedAt = car_crown[counter].playedAt - 172800;
@ -312,29 +525,43 @@ export default class StartupModule extends Module {
// Acquired crown timestamp - 1 day
car_crown[counter].playedAt = car_crown[counter].playedAt - 172800;
}
// Error handling if played At value is 0
else if(car_crown[counter].playedAt === 0 || car_crown[counter].playedAt < 1659805200)
// Error handling if played At timestamp value is 0 or timestamp is less than 9 July 2022 (using GMT+7 timestamp)
else if(car_crown[counter].playedAt === 0 || car_crown[counter].playedAt < 1657299600)
{
// Acquired crown timestamp become 7 August 2022
car!.lastPlayedAt = 1659805200;
// Acquired crown timestamp become 9 July 2022 (using GMT+7 timestamp)
car!.lastPlayedAt = 1657299600;
// Acquired crown timestamp become 7 August 2022
car_crown[counter].playedAt = 1659805200;
// Acquired crown timestamp become 9 July 2022 (using GMT+7 timestamp)
car_crown[counter].playedAt = 1657299600;
}
list_crown.push(wmsrv.wm.protobuf.Crown.create({
let playedPlace = wm.wm.protobuf.Place.create({
placeId: 'JPN0123',
shopName: Config.getConfig().shopName,
regionId: 18,
country: 'JPN'
});
// Push the car data to the crown holder data
list_crown.push(wmsrv.wm.protobuf.Crown.create({
carId: car_crown[counter].carId,
area: car_crown[counter].area, // GID_RUNAREA_C1 - GID_RUNAREA_TURNPIKE & GID_RUNAREA_HIROSHIMA
unlockAt: car_crown[counter].playedAt,
car: car
car: {
...car!,
lastPlayedPlace: playedPlace
}
}));
if(counter < car_crown.length-1){
counter++;
}
}
else{
list_crown.push(wmsrv.wm.protobuf.Crown.create({
// Crown holder for certain area not available
else{
// Push the default data by the game to the crown holder data
list_crown.push(wmsrv.wm.protobuf.Crown.create({
carId: i,
area: i, // GID_RUNAREA_C1 - GID_RUNAREA_TURNPIKE & GID_RUNAREA_HIROSHIMA
unlockAt: 0,
@ -342,12 +569,19 @@ export default class StartupModule extends Module {
}
}
}
else{
for(let i=0; i<19; i++){
if(i >= 14){
i = 18;
// There is no user's crown holder data available
else{
// Loop GID_RUNAREA
for(let i=0; i<19; i++){
// 14 - 16 are dummy area, 17 is C1 Closed
if(i >= 14){
i = 18; // GID_RUNAREA_HIROSHIMA
}
list_crown.push(wmsrv.wm.protobuf.Crown.create({
// Push the default data by the game to the crown holder data
list_crown.push(wmsrv.wm.protobuf.Crown.create({
carId: i,
area: i, // GID_RUNAREA_C1 - GID_RUNAREA_TURNPIKE & GID_RUNAREA_HIROSHIMA
unlockAt: 0,
@ -355,17 +589,16 @@ export default class StartupModule extends Module {
}
}
// Response data
let msg = {
crowns: list_crown
};
let resp = wmsrv.wm.protobuf.CrownList.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
// Encode the response
let message = wmsrv.wm.protobuf.CrownList.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
}
}

892
src/modules/terminal.ts Normal file
View File

@ -0,0 +1,892 @@
import { Application } from "express";
import { Config } from "../config";
import { Module } from "module";
import { prisma } from "..";
import { Car } from "@prisma/client";
// Import Proto
import * as wm from "../wmmt/wm.proto";
// Import Util
import * as scratch from "../util/scratch";
import * as common from "../util/common";
export default class TerminalModule extends Module {
register(app: Application): void {
// Load upon enter terminal
app.post('/method/load_terminal_information', (req, res) => {
// Get the request body for the load terminal information request
let body = wm.wm.protobuf.LoadTerminalInformationRequest.decode(req.body);
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
prizeReceivable: false,
transferNotice: {
needToSeeTransferred: false
},
announceFeature: false,
freeScratched: true
}
// Encode the response
let message = wm.wm.protobuf.LoadTerminalInformationResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Recieve user items
app.post('/method/receive_user_items', async (req, res) => {
// Get the request body
let body = wm.wm.protobuf.ReceiveUserItemsRequest.decode(req.body);
// Loop over all of the item IDs
for(let targetItem of body.targetItemIds)
{
// Get the item info for the target item
let item = await prisma.userItem.findFirst({
where: {
userItemId: targetItem
}
});
// Item is returned
if (item)
{
// Insert the item into the car items
await prisma.carItem.create({
data: {
carId: body.carId,
category: item.category,
itemId: item.itemId,
amount: 1
}
});
// Delete the accepted item
await prisma.userItem.delete({
where: {
userItemId: targetItem
}
});
}
else // No item found
{
console.log("Warning: Item " + targetItem + " not found. Item not added.");
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS
}
// Encode the response
let message = wm.wm.protobuf.LoadBookmarksResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Load user bookmarks
app.post('/method/load_bookmarks', async (req, res) => {
// Get the save bookmark request
let body = wm.wm.protobuf.LoadBookmarksRequest.decode(req.body);
// Check if the user has any existing bookmarks
let user = await prisma.user.findFirst({
where: {
id: Number(body.userId)
}
});
// Car bookmarks placeholder
let cars : Car[] = [];
// User is not null
if (user)
{
// Loop over the bookmarked cars
for (let carId of user.bookmarks)
{
// Get the car with the bookmarked car id
let car = await prisma.car.findFirst({
where: {
carId: carId
}
});
// If the car is not null
if (car)
{
// Add the car to the cars list
cars.push(car);
}
}
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
cars: cars
}
// Encode the response
let message = wm.wm.protobuf.LoadBookmarksResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Save user bookmarks
app.post('/method/save_bookmarks', async (req, res) => {
// Get the save bookmark request
let body = wm.wm.protobuf.SaveBookmarksRequest.decode(req.body);
// Update existing bookmarks
await prisma.user.update({
where: {
id: body.userId
},
data: {
bookmarks: body.cars
}
})
// Generate the response to the terminal (success messsage)
let resp = wm.wm.protobuf.LoadBookmarksResponse.encode({
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS
});
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
})
// Car Summary Request (for bookmarks, also for search ghost by name)
app.get('/resource/car_summary', async (req, res) => {
// Get the query from the request
let query = req.query;
// Get all of the cars matching the query
let cars = await prisma.car.findMany({
take: Number(query.limit),
where: {
name: {
startsWith: String(query.name)
}
},
include:{
gtWing: true
}
});
for(let i=0; i<cars.length; i++)
{
if(cars[i].regionId === 0)
{
let randomRegionId = Math.floor(Math.random() * 47) + 1;
cars[i].regionId = randomRegionId;
}
}
let msg = {
hitCount: cars.length,
cars: cars
}
let resp = wm.wm.protobuf.CarSummary.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
})
// Save upon timeout / exit terminal
app.post('/method/save_terminal_result', async (req, res) => {
// Get the contents from the request
let body = wm.wm.protobuf.SaveTerminalResultRequest.decode(req.body);
// user id is required field
let user = await prisma.user.findFirst({
where: {
id: body.userId
},
});
if(user)
{
// Update the completed tutorials
let storedTutorials = user.tutorials;
body.confirmedTutorials.forEach(
(idx) => storedTutorials[idx] = true
);
await prisma.user.update({
where: {
id: body.userId
},
data: {
tutorials: storedTutorials
}
});
// If the car order was modified
// Update the car order in the table
if (body.carOrder.length > 0)
{
await prisma.user.update({
where: {
id: body.userId
},
data: {
carOrder: body.carOrder
}
});
}
}
let msg = {
// Success error code
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
}
// Encode the save terminal result response
let resp = wm.wm.protobuf.SaveTerminalResultResponse.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
})
// Terminal scratch
app.post('/method/load_scratch_information', async (req, res) => {
// Get the information from the request
let body = wm.wm.protobuf.LoadScratchInformationRequest.decode(req.body);
// Get the current date/time (unix epoch)
let date = Math.floor(new Date().getTime() / 1000)
// Scratch sheet proto
let scratchSheetProto : wm.wm.protobuf.ScratchSheet[] = [];
// Current scratch sheet (default: Sheet 1 (R2))
let currentSheet = 1;
// User has scratched already (default: True)
let scratched = 1;
// Get the scratch sheet configuration
let scratchEnabled = Config.getConfig().gameOptions.scratchEnabled;
// If the scratch game is enabled
if (scratchEnabled)
{
// Get all of the info for the user
let user = await prisma.user.findFirst({
where: {
id: body.userId
}
});
// Get the updated scratch sheet proto
scratchSheetProto = await scratch.getScratchSheetProto(body.userId);
// User is defined
if (user)
{
// Update the currentSheet, scratched values
currentSheet = user.currentSheet;
// If unlimited scratches is set
if (scratchEnabled == 2)
{
// User can scratch again
scratched = 0;
}
else // Otherwise, daily scratches
{
// If a day has passed, allow the user to scratch again
scratched = scratch.dayPassed(
new Date(date*1000), // Todays date
new Date(user.lastScratched*1000) // Last Scratched date
);
}
}
}
// Owned user items list
let ownedUserItems : wm.wm.protobuf.UserItem[] = [];
// Get the user items list
ownedUserItems = await scratch.getScratchItemList(body.userId, date);
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
scratchSheets: scratchSheetProto,
currentSheet: currentSheet,
numOfScratched: scratched,
ownedUserItems: ownedUserItems
}
// Encode the response
let message = wm.wm.protobuf.LoadScratchInformationResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
});
// Change terminal scratch page
app.post('/method/turn_scratch_sheet', async (req, res) => {
// Get the request body for the turn scratch sheet request
let body = wm.wm.protobuf.TurnScratchSheetRequest.decode(req.body);
// Update the active scratch sheet
await prisma.user.update({
where: {
id: body.userId
},
data: {
currentSheet: body.targetSheet
}
});
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
}
// Encode the response
let message = wm.wm.protobuf.TurnScratchSheetResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Update scratch sheet
app.post('/method/save_scratch_sheet', async (req, res) => {
// Get the information from the request
let body = wm.wm.protobuf.SaveScratchSheetRequest.decode(req.body);
// Get the current date/time (unix epoch)
let date = Math.floor(new Date().getTime() / 1000)
// Get all of the info for the user
let user = await prisma.user.findFirst({
where: {
id: body.userId
}
});
// Get all of the scratch sheets for the user
let scratchSheets = await prisma.scratchSheet.findMany({
where: {
userId: body.userId
},
include: {
squares: {
orderBy: {
id: 'asc'
}
}
}
})
// Get the target scratch sheet (subtract one for zero-index)
let scratchSheet = scratchSheets[Number(body.targetSheet)-1];
// Get all of the squares for the scratch sheet
let scratchSquares = await prisma.scratchSquare.findMany({
where: {
sheetId: scratchSheet.id
},
orderBy: {
id: 'asc'
}
});
// Get the target scratch square
let scratchSquare = scratchSquares[Number(body.targetSquare)];
// Get the item from the scratch square
let earnedItem = wm.wm.protobuf.UserItem.create({
category: scratchSquare.category,
itemId: scratchSquare.itemId,
earnedAt: date
});
try // Attempt to update scratch sheet
{
// Add the item to the user's scratch items list
await prisma.userItem.create({
data: {
userId: body.userId,
category: scratchSquare.category,
itemId: scratchSquare.itemId,
type: 1, // Scratch item
earnedAt: date
}
});
// Update the revealed scratch square
await prisma.scratchSquare.update({
where: {
id: scratchSquare.id
},
data: {
earned: true
}
});
// Update the last scratched timestamp
await prisma.user.update({
where: {
id: body.userId
},
data: {
lastScratched: date
}
});
// If the box we uncovered is the car
if (scratchSquare.category == 201)
{
// Generate a new scratch sheet for the user
await scratch.generateScratchSheet(body.userId, body.targetSheet + 1)
}
}
catch (error) // Failed to update scratch sheet
{
console.log("Failed to update scratch sheet! Reason:", error);
}
// Get the updated content for the scratch sheet
// Scratch sheet proto
let scratchSheetProto : wm.wm.protobuf.ScratchSheet[] = []
// Get the updated scratch sheet proto
scratchSheetProto = await scratch.getScratchSheetProto(body.userId);
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
scratchSheets : scratchSheetProto,
currentSheet: body.targetSheet,
numOfScratched: 1,
earnedItem: earnedItem
}
// Encode the response
let message = wm.wm.protobuf.SaveScratchSheetResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Terminal OCM Ranking
app.post('/method/load_ghost_competition_ranking', async (req, res) => {
// Get the information from the request
let body = wm.wm.protobuf.LoadGhostCompetitionRankingRequest.decode(req.body);
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get current active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
competitionId: body.competitionId,
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
competitionId: body.competitionId,
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
competitionId: body.competitionId,
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
if(!(ocmEventDate))
{
ocmEventDate = await prisma.oCMEvent.findFirst({
orderBy:{
dbId: 'desc'
}
});
if(ocmEventDate)
{
console.log('Previous OCM found');
}
}
// Declare GhostCompetitionSchedule
let compeSch;
let msg: any;
if(ocmEventDate){
// Creating GhostCompetitionSchedule
compeSch = wm.wm.protobuf.GhostCompetitionSchedule.create({
// OCM Competition ID (1 = C1 (Round 16), 4 = Nagoya (Round 19), 8 = Hiroshima (Round 21))
competitionId: ocmEventDate.competitionId,
// OCM Qualifying Start Timestamp
qualifyingPeriodStartAt: ocmEventDate.qualifyingPeriodStartAt,
// OCM Qualifying Close Timestamp
qualifyingPeriodCloseAt: ocmEventDate.qualifyingPeriodCloseAt,
// OCM Competition (Main Draw) Start Timestamp
competitionStartAt: ocmEventDate.competitionStartAt,
// OCM Competition (Main Draw) Close Timestamp
competitionCloseAt: ocmEventDate.competitionCloseAt,
// OCM Competition (Main Draw) End Timestamp
competitionEndAt: ocmEventDate.competitionEndAt,
// idk what this is
lengthOfPeriod: ocmEventDate.lengthOfPeriod,
// idk what this is
lengthOfInterval: ocmEventDate.lengthOfInterval,
// Area for the event (GID_RUNAREA_*, 8 is GID_RUNAREA_NAGOYA)
area: ocmEventDate.area,
// idk what this is
minigamePatternId: ocmEventDate.minigamePatternId
});
// Get Participant
let numOfParticipants: number = 0;
let periodId: number = 0;
let ownRecords;
let topRecords: wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry[] = [];
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
console.log('Current OCM Day : Competition Day / Main Draw');
// Get Current OCM Period
let OCMCurrentPeriod = await prisma.oCMPeriod.findFirst({
where: {
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId,
startAt:
{
lte: date, // competitionStartAt is less than current date
},
closeAt:
{
gte: date, // competitionCloseAt is greater than current date
}
}
});
let ocmParticipant = await prisma.oCMTally.findMany({
where:{
competitionId: ocmEventDate!.competitionId,
periodId: OCMCurrentPeriod!.periodId
},
orderBy: {
result: 'desc'
}
})
numOfParticipants = ocmParticipant.length;
periodId = 0;
let ranking = 0;
if(ocmParticipant)
{
for(let i=0; i<ocmParticipant.length; i++)
{
let cars = await prisma.car.findFirst({
where:{
carId: ocmParticipant[i].carId
}
});
let ocmGhostrecord = await prisma.oCMGhostBattleRecord.findFirst({
where:{
carId: ocmParticipant[i].carId,
competitionId: ocmEventDate!.competitionId,
}
});
if(ocmParticipant[i].carId === body.carId && ranking === 0)
{
// User car setting
ownRecords = wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmGhostrecord!.playedShopName,
playedAt: ocmGhostrecord!.playedAt
});
ranking++;
}
// Generate OCM Top Records
topRecords.push(wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmGhostrecord!.playedShopName,
playedAt: ocmGhostrecord!.playedAt
}));
}
}
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
console.log('Current OCM Day : Qualifying Day');
let ocmParticipant = await prisma.oCMGhostBattleRecord.findMany({
where:{
competitionId: ocmEventDate!.competitionId
},
orderBy: {
result: 'desc'
}
})
numOfParticipants = ocmParticipant.length;
periodId = 0;
let ranking = 0;
if(ocmParticipant)
{
for(let i=0; i<ocmParticipant.length; i++)
{
let cars = await prisma.car.findFirst({
where:{
carId: ocmParticipant[i].carId
}
})
if(ocmParticipant[i].carId === body.carId && ranking === 0)
{
// User car setting
ownRecords = wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmParticipant[i].playedShopName,
playedAt: ocmParticipant[i].playedAt
});
ranking++;
}
// Generate OCM Top Records
topRecords.push(wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmParticipant[i].playedShopName,
playedAt: ocmParticipant[i].playedAt
}));
}
}
}
// OCM has ended
else
{
console.log('Current / Previous OCM Day : OCM has Ended');
let ocmParticipant = await prisma.oCMTally.findMany({
where:{
competitionId: ocmEventDate!.competitionId,
periodId: 999999999
},
orderBy: {
result: 'desc'
}
})
numOfParticipants = ocmParticipant.length;
periodId = 0;
let ranking = 0;
if(ocmParticipant)
{
for(let i=0; i<ocmParticipant.length; i++)
{
let cars = await prisma.car.findFirst({
where:{
carId: ocmParticipant[i].carId
}
});
let ocmGhostrecord = await prisma.oCMGhostBattleRecord.findFirst({
where:{
carId: ocmParticipant[i].carId,
competitionId: ocmEventDate!.competitionId,
}
});
if(ocmParticipant[i].carId === body.carId && ranking === 0)
{
// User car setting
ownRecords = wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmGhostrecord!.playedShopName,
playedAt: ocmGhostrecord!.playedAt
});
ranking++;
}
// Generate OCM Top Records
topRecords.push(wm.wm.protobuf.LoadGhostCompetitionRankingResponse.Entry.create({
rank: i + 1,
result: ocmParticipant[i].result,
carId: ocmParticipant[i].carId,
name: cars!.name,
regionId: cars!.regionId,
model: cars!.model,
visualModel: cars!.visualModel,
defaultColor: cars!.defaultColor,
title: cars!.title,
level: cars!.level,
windowStickerString: cars!.windowStickerString,
playedShopName: ocmGhostrecord!.playedShopName,
playedAt: ocmGhostrecord!.playedAt
}));
}
}
}
// Response data
msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
periodId: periodId,
numOfParticipants: numOfParticipants,
competitionSchedule: compeSch, // OCM Event Available or not
ownRecord: ownRecords,
topRecords: topRecords
}
}
else
{
msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
numOfParticipants: 0,
}
}
// Encode the response
let message = wm.wm.protobuf.LoadGhostCompetitionRankingResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
}
}

View File

@ -0,0 +1,89 @@
import { Application } from "express";
import { Module } from "module";
import { prisma } from "..";
// Import Proto
import * as wm from "../wmmt/wm.proto";
// Import Util
import * as common from "../util/common";
export default class TimeAttackModule extends Module {
register(app: Application): void {
// Load time Attack Record
app.post('/method/load_time_attack_record', async (req, res) => {
// Get the request body for the load terminal information request
let body = wm.wm.protobuf.LoadTimeAttackRecordRequest.decode(req.body);
let taRecordsForModel = await prisma.timeAttackRecord.findMany({
take: 100,
where: {
model: body.model,
course: body.course
},
orderBy: {
time: 'asc'
}
});
let taRecordsOverall = await prisma.timeAttackRecord.findMany({
take: 100,
where: {
course: body.course
},
orderBy: {
time: 'asc'
}
});
let taRecordPb = await prisma.timeAttackRecord.findFirst({
where: {
carId: body.carId,
course: body.course
},
orderBy: {
time: 'asc'
}
});
if (!taRecordPb) {
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
wholeRanking: taRecordsOverall.map(a => a.time),
modelRanking: taRecordsForModel.map(a => a.time)
};
// Encode the response
let message = wm.wm.protobuf.LoadTimeAttackRecordResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
return;
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
wholeRanking: taRecordsOverall.map(a => a.time),
modelRanking: taRecordsForModel.map(a => a.time),
personalBestTime: taRecordPb.time,
pbSection_1Time: taRecordPb.section1Time,
pbSection_2Time: taRecordPb.section2Time,
pbSection_3Time: taRecordPb.section3Time,
pbSection_4Time: taRecordPb.section4Time,
pbSection_5Time: taRecordPb.section5Time,
pbSection_6Time: taRecordPb.section6Time,
pbSection_7Time: taRecordPb.section7Time,
};
// Encode the response
let message = wm.wm.protobuf.LoadTimeAttackRecordResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
}
}

647
src/modules/users.ts Normal file
View File

@ -0,0 +1,647 @@
import e, { Application } from "express";
import { Config } from "../config";
import { Module } from "module";
import { prisma } from "..";
// Import Proto
import * as wm from "../wmmt/wm.proto";
// Import Util
import * as scratch from "../util/scratch";
import * as common from "../util/common";
export default class UserModule extends Module {
register(app: Application): void {
// Load user data when entering the game or after tapping the bannapass card
app.post('/method/load_user', async (req, res) => {
// Get the request body for the load user request
let body = wm.wm.protobuf.LoadUserRequest.decode(req.body);
// Get the user from the database
let user = await prisma.user.findFirst({
where: {
chipId: body.cardChipId,
accessCode: body.accessCode
},
include: {
cars: {
include: {
state: true,
gtWing: true
}
}
}
});
// No user returned
if (!user) {
console.log('no such user');
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
numOfOwnedCars: 0,
cars: [],
spappState: wm.wm.protobuf.SmartphoneAppState.SPAPP_UNREGISTERED,
transferState: wm.wm.protobuf.TransferState.NOT_REGISTERED,
};
if (!body.cardChipId || !body.accessCode) {
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_ID_BANNED,
numOfOwnedCars: 0,
spappState: wm.wm.protobuf.SmartphoneAppState.SPAPP_UNREGISTERED,
transferState: wm.wm.protobuf.TransferState.NOT_REGISTERED
}
let resp = wm.wm.protobuf.LoadUserResponse.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
return;
}
let user = await prisma.user.create({
data: {
chipId: body.cardChipId,
accessCode: body.accessCode,
tutorials: [
false, //TUTORIAL_ID_STORY
false, //TUTORIAL_ID_TIME_ATTACK
false, //TUTORIAL_ID_GHOST
false, //TUTORIAL_ID_GHOST_CHALLENGE
false, //TUTORIAL_ID_GHOST_LEVEL
false, //TUTORIAL_ID_UNUSED_5
false, //TUTORIAL_ID_GHOST_SEARCH
false, //TUTORIAL_ID_GHOST_COMPETITION
false, //TUTORIAL_ID_HP600_CARD
false, //TUTORIAL_ID_UNUSED_9
false, //TUTORIAL_ID_COMPETITION_QUALIFIED
false, //TUTORIAL_ID_COMPETITION_TERMINAL
false, //TUTORIAL_ID_COMPETITION_NOTICE
false, //TUTORIAL_ID_COMPETITION_FINISHED
false, //TUTORIAL_ID_UNUSED_14
false, //TUTORIAL_ID_UNUSED_15
false, //TUTORIAL_ID_UNUSED_16
false, //TUTORIAL_ID_UNUSED_17
false, //TUTORIAL_ID_UNUSED_18
false, //TUTORIAL_ID_UNUSED_19
false, //TUTORIAL_ID_GHOST_STAMP
false, //TUTORIAL_ID_GHOST_STAMP_DECLINED
false, //TUTORIAL_ID_GHOST_STAMP_FRIENDS
true, //TUTORIAL_ID_TERMINAL_SCRATCH
true, //TUTORIAL_ID_TURN_SCRATCH_SHEET
false, //TUTORIAL_ID_INVITE_FRIEND_CAMPAIGN
false, //TUTORIAL_ID_CAR_COUPON_FULL_TUNED_RECEIVABLE
false, //TUTORIAL_ID_VS_CONTINUE_TICKET
false, //TUTORIAL_ID_UNUSED_28
false, //TUTORIAL_ID_UNUSED_29
false, //TUTORIAL_ID_UNUSED_30
false, //TUTORIAL_ID_DRESS_UP
true, //TUTORIAL_ID_MULTI_GHOST
true, //TUTORIAL_ID_STORY_NEW_FEATURE
true, //TUTORIAL_ID_GHOST_NEW_FEATURE
true, //TUTORIAL_ID_GHOST_REGION_MAP
],
}
});
console.log('user made')
if (!user) {
msg.error = wm.wm.protobuf.ErrorCode.ERR_REQUEST;
}
let ftTicketGrant = Config.getConfig().gameOptions.grantFullTuneTicketToNewUsers;
if (ftTicketGrant > 0) {
console.log(`Granting Full-Tune Ticket x${ftTicketGrant} to new user...`);
for (let i=0; i<ftTicketGrant; i++) {
await prisma.userItem.create({
data: {
userId: user.id,
category: wm.wm.protobuf.ItemCategory.CAT_CAR_TICKET_FREE,
itemId: 5,
type: 0 // Car Ticket
}
});
}
console.log('Done!');
}
let resp = wm.wm.protobuf.LoadUserResponse.encode(msg);
let end = resp.finish();
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
r.send(Buffer.from(end));
return;
}
// Get the number of scratch cards for the user
let scratchSheetCount = await prisma.scratchSheet.count({
where: {
userId: user!.id
}
})
console.log("Current sheet count:", scratchSheetCount);
// If the user has no scratch sheets
if (scratchSheetCount == 0)
{
console.log("Generating first sheet ...");
// Generate a new scratch sheet for the user
await scratch.generateScratchSheet(user!.id, 1);
// Set the current scratch sheet to 1
await prisma.user.update({
where: {
id: user!.id
},
data: {
currentSheet: 1
}
});
}
// If the car order array has not been created
if (user.carOrder.length > 0)
{
// Sort the player's car list using the car order property
user.cars = user.cars.sort(function(a, b){
// User, and both car IDs exist
if (user)
{
// Compare both values using the car order array
let compare: number = user?.carOrder.indexOf(a!.carId) - user?.carOrder.indexOf(b!.carId);
// Return the comparison
return compare;
}
else // Car IDs not present in car order list
{
throw Error("UserNotFoundException");
}
});
}
else // Car order undefined
{
// We will define it here
let carOrder : number[] = [];
// Loop over all of the user cars
for(let car of user.cars)
{
// Add the car id to the list
carOrder.push(car.carId);
}
// Update the car id property for the user
await prisma.user.update({
where: {
id: user.id
},
data: {
carOrder: carOrder
}
})
}
// Get the states of the user's cars
let carStates = user.cars.map(e => e.state);
// Get all of the user's tickets
let tickets = await prisma.userItem.findMany({
where: {
userId: user.id,
type: 0
},
select: {
itemId: true,
category: true,
userItemId: true
}
})
// Error handling if windowStickerString and windowStickerFont is undefined or null
// User is registering bannapass from terminal unit first instead of driver unit
// Default value for windowStickerString and windowStickerFont
let wsString = '';
let wsFont = 0;
// user.cars found
if(user.cars)
{
// User atleast have 1 car
if(user.cars[0]?.windowStickerString !== null && user.cars[0]?.windowStickerString !== undefined &&
user.cars[0]?.windowStickerString !== '')
{
wsString = user.cars[0].windowStickerString;
wsFont = user.cars[0].windowStickerFont;
}
// else{} User don't have a car... returning default windowStickerString and windowStickerFont value
}
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
numOfOwnedCars: user.cars.length,
spappState: wm.wm.protobuf.SmartphoneAppState.SPAPP_UNREGISTERED,
transferState: wm.wm.protobuf.TransferState.TRANSFERRED,
carStates,
userId: user.id,
banapassportAmId: 1,
mbId: 1,
tutorials: user.tutorials,
unusedCarTickets: tickets,
// 5 cars in-game, 200 cars on terminal
cars: user.cars.slice(0, body.maxCars),
// Set the window sticker string for all cars
windowStickerString: wsString,
// Set the window sticker font for all cars
windowStickerFont: wsFont,
// Set initial value for competition (OCM) participation
competitionUserState: wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_NOT_PARTICIPATED
}
// Check OCM Participation
let ParticipationMainDrawCounter = 0;
let ParticipationQualifyingCounter = 0;
let ParticipationEndedCounter = 0;
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Check each car record
for(let i=0; i<msg.cars.length; i++)
{
// Get current active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
// Check Competition (OCM) Participation, and available OCM event
if(user.cars.length > 0 && ocmEventDate)
{
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
// Check ghost battle record
let checkParticipation = await prisma.oCMPlayRecord.findFirst({
where:{
carId: user.cars[i].carId,
competitionId: ocmEventDate!.competitionId
}
});
// Record found
if(checkParticipation)
{
ParticipationMainDrawCounter++
}
// Check Car State
// Get OCM Data
let ocmTallyRecord = await prisma.oCMTally.findMany({
where:{
competitionId: ocmEventDate!.competitionId
},
orderBy: [
{
competitionId: 'desc',
},
{
periodId: 'desc',
},
{
result: 'desc',
},
],
});
for(let j=0; j<ocmTallyRecord.length; j++)
{
if(carStates[i].dbId === ocmTallyRecord[j].carId)
{
carStates[i].eventJoined = true;
carStates[i].competitionState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_QUALIFIED
}
if(carStates[i].dbId === ocmTallyRecord[j].carId && j === 0)
{
carStates[i].competitionState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_WON
}
}
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
// Check ghost battle record
let checkParticipation = await prisma.oCMPlayRecord.findFirst({
where:{
carId: user.cars[i].carId,
competitionId: ocmEventDate!.competitionId
}
});
// Record found
if(checkParticipation)
{
ParticipationQualifyingCounter++
}
// Check Car State
// Get OCM Data
let ocmRecord = await prisma.oCMPlayRecord.findMany({
where:{
competitionId: ocmEventDate!.competitionId
},
orderBy: [
{
dbId: 'asc',
},
{
competitionId: 'desc',
},
{
periodId: 'desc',
},
],
});
for(let j=0; j<ocmRecord.length; j++)
{
if(carStates[i].dbId === ocmRecord[j].carId)
{
carStates[i].eventJoined = true;
carStates[i].competitionState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_PARTICIPATED
}
}
}
// Current date is OCM ended
else if(ocmEventDate!.competitionCloseAt < date && ocmEventDate!.competitionEndAt > date)
{
// Check ghost battle record
let checkParticipation = await prisma.oCMPlayRecord.findFirst({
where:{
carId: user.cars[i].carId,
competitionId: ocmEventDate!.competitionId
}
});
// Record found
if(checkParticipation)
{
ParticipationEndedCounter++
}
// Check Car State
// Get OCM Data
let ocmTallyRecord = await prisma.oCMTally.findMany({
where:{
competitionId: ocmEventDate!.competitionId
},
orderBy: [
{
competitionId: 'desc',
},
{
periodId: 'desc',
},
{
result: 'desc',
},
],
});
for(let j=0; j<ocmTallyRecord.length; j++)
{
if(carStates[i].dbId === ocmTallyRecord[j].carId)
{
carStates[i].eventJoined = true;
carStates[i].competitionState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_QUALIFIED
}
if(carStates[i].dbId === ocmTallyRecord[j].carId && j === 0)
{
carStates[i].competitionState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_WON
}
}
}
}
}
// Participated to OCM Event
if(ParticipationMainDrawCounter > 0)
{
console.log('OCM Participation : '+ParticipationMainDrawCounter+' car(s) Qualified');
msg.competitionUserState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_QUALIFIED;
}
else if(ParticipationQualifyingCounter > 0)
{
console.log('OCM Participation : '+ParticipationQualifyingCounter+' car(s) Participated');
msg.competitionUserState = wm.wm.protobuf.GhostCompetitionParticipantState.COMPETITION_PARTICIPATED;
}
else if(ParticipationEndedCounter > 0)
{
console.log('OCM Participation : '+ParticipationEndedCounter+' car(s) played OCM Event');
}
else{
console.log('OCM Participation : Not Participated / Qualified');
}
// Response data if user is banned
if (user.userBanned) {
msg.error = wm.wm.protobuf.ErrorCode.ERR_ID_BANNED;
}
// Encode the response
let message = wm.wm.protobuf.LoadUserResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Create User Request
app.post('/method/create_user', async (req, res) => {
// This request is sent by the terminal when you
// select 'yes' to register on the starting menu
// if you have not created your account yet.
// However, we don't really need to process it as
// the load_user command already creates the user.
// we do, however need to send a valid response
// otherwise the terminal crashes.
// Get the request body for the create user request
let body = wm.wm.protobuf.CreateUserRequest.decode(req.body);
// Get the user info via the card chip id
let user = await prisma.user.findFirst({
where: {
chipId: body.cardChipId,
accessCode: body.accessCode
}
});
// Message object
let msg;
// User exists
if (user)
{
msg = {
// Success error message
error : wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
// User's user id
userId : user?.id
}
}
else // User does not exist
{
msg = {
// User not found error message
error : wm.wm.protobuf.ErrorCode.ERR_NOT_FOUND,
// No user id
userId : 0
}
}
// Generate the response for the create user request
let message = wm.wm.protobuf.CreateUserResponse.encode(msg);
// Send response to client
common.sendResponse(message, res);
});
// Load Drive Information
app.post('/method/load_drive_information', async (req, res) => {
// Get the request body for the load drive information request
let body = wm.wm.protobuf.LoadDriveInformationRequest.decode(req.body);
// Get all of the user's tickets
let tickets = await prisma.userItem.findMany({
where: {
userId: body.userId,
type: 0
},
select: {
itemId: true,
category: true,
userItemId: true
}
})
// TODO: Add notices to config
let notice = (Config.getConfig().notices || []);
// Create the notice window objects
let noticeWindows = notice.map(a => wm.wm.protobuf.NoticeEntry.NOTICE_UNUSED_1);
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
noticeWindow: noticeWindows,
noticeWindowMessage: notice,
transferNotice: {
needToSeeTransferred: false,
totalMaxiGold: 0,
numOfPorscheCars: 0,
porscheModels: [],
hasR35: false,
},
restrictedModels: [],
announceFeature: false,
announceMobile: false,
availableTickets: tickets,
}
// Encode the response
let message = wm.wm.protobuf.LoadDriveInformationResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
// Update User Session
app.post('/method/update_user_session', (req, res) => {
// Response data
let msg = {
error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS,
}
// Encode the response
let message = wm.wm.protobuf.UpdateUserSessionResponse.encode(msg);
// Send the response to the client
common.sendResponse(message, res);
})
/*
// Start Transfer
app.post('/method/start_transfer', (req, res) => {
});
// Grant Car Right
app.post('/method/grant_car_right', (req, res) => {
});
// Ask Access Code
app.post('/method/ask_access_code', (req, res) => {
});
// Participate In Invite Friend Campaign
app.post('/method/participate_in_invite_friend_campaign', (req, res) => {
});
*/
}
}

44
src/util/common.ts Normal file
View File

@ -0,0 +1,44 @@
import { Response } from "express";
import Long from "long";
import { Writer } from "protobufjs";
// sendResponse(message, res): Void
// Sends the server response to the client
export function sendResponse(message: Writer, res: Response)
{
// Get the end of the message
let end = message.finish();
// Built the response data
let r = res
.header('Server', 'v388 wangan')
.header('Content-Type', 'application/x-protobuf; revision=8053')
.header('Content-Length', end.length.toString())
.status(200);
// Send the response to the client
r.send(Buffer.from(end));
}
// getBigIntFromLong(n: Long): BigInt
// Given a Long data object, converts
// it into a BigInt and returns it.
export function getBigIntFromLong(n: Long)
{
// Create the default value
let bigInt = BigInt(0);
// If 'n' is a long data type
if (n instanceof Long)
{
// Perform the bit-wise operations
bigInt = bigInt | BigInt(n.high);
bigInt = bigInt << BigInt(32);
bigInt = bigInt | BigInt(n.low);
}
// Return the finished value
return Number(bigInt);
}

View File

@ -0,0 +1,57 @@
// Import Proto
import { wm } from "../../../wmmt/wm.proto";
// Save story result
export async function checkCurrentStep(body: wm.protobuf.SaveGameResultRequest)
{
// Get current step for updating the user's ghost level
let currentStep = 0;
currentStep = body.car!.tunePower! + body.car!.tuneHandling!;
// Set current ghost level based on current step
let ghostLevel = 1;
if(currentStep >= 0 && currentStep <= 5)
{
ghostLevel = 1
}
else if(currentStep >= 6 && currentStep <= 10)
{
ghostLevel = 2
}
else if(currentStep >= 11 && currentStep <= 15)
{
ghostLevel = 3
}
else if(currentStep >= 16 && currentStep <= 20)
{
ghostLevel = 4
}
else if(currentStep >= 21 && currentStep <= 26)
{
ghostLevel = 5
}
else if(currentStep >= 27 && currentStep <= 28)
{
ghostLevel = 6
}
else if(currentStep >= 29 && currentStep <= 30)
{
ghostLevel = 7
}
else if(currentStep === 31)
{
ghostLevel = 8
}
else if(currentStep >= 32 && currentStep <= 33)
{
ghostLevel = 9
}
else if(currentStep === 34)
{
ghostLevel = 10
}
// Return the value to 'BASE_PATH/src/util/games/story.ts'
return {ghostLevel}
}

View File

@ -0,0 +1,473 @@
// Import Proto
import { prisma } from "../../..";
import { Config } from "../../../config";
import { wm } from "../../../wmmt/wm.proto";
// Save ghost history battle
export async function saveGhostHistory(body: wm.protobuf.SaveGameResultRequest)
{
console.log('Saving Ghost Battle History');
let updateNewTrail: boolean = true;
let saveExGhostHistory: any = {};
if (body.car?.carId !== null && body.car?.carId !== undefined) {
saveExGhostHistory.carId = body.car?.carId!;
}
if (body.car?.tunePower !== null && body.car?.tunePower !== undefined) {
saveExGhostHistory.tunePower = body.car?.tunePower!;
}
if (body.car?.tuneHandling !== null && body.car?.tuneHandling !== undefined) {
saveExGhostHistory.tuneHandling = body.car?.tuneHandling!;
}
if (body.playedAt !== null && body.playedAt !== undefined){
saveExGhostHistory.playedAt = body.playedAt!;
}
// Get shop name
saveExGhostHistory.playedShopName = Config.getConfig().shopName;
// Get how many opponents available
for(let i=0; i<body.rgResult!.opponents!.length; i++)
{
// First opponent data
if(i == 0)
{
// Get first opponent carId
saveExGhostHistory.opponent1CarId = body.rgResult!.opponents![0].carId;
// Get first opponent tunePower
saveExGhostHistory.opponent1TunePower = body.rgResult!.opponents![0].tunePower;
// Get first opponent tunePower
saveExGhostHistory.opponent1TuneHandling = body.rgResult!.opponents![0].tuneHandling;
// Get the advantage distance between first opponent and user
saveExGhostHistory.opponent1Result = body.rgResult!.opponents![0].result;
}
// Second opponent data
else if(i == 1)
{
// Get second opponent carId
saveExGhostHistory.opponent2CarId = body.rgResult!.opponents![1].carId;
// Get second opponent tunePower
saveExGhostHistory.opponent2TunePower = body.rgResult!.opponents![1].tunePower;
// Get second opponent tuneHandling
saveExGhostHistory.opponent2TuneHandling = body.rgResult!.opponents![1].tuneHandling;
// Get the advantage distance between second opponent and user
saveExGhostHistory.opponent2Result = body.rgResult!.opponents![1].result;
}
// Third opponent data
else if(i == 2)
{
// Get third opponent carId
saveExGhostHistory.opponent3CarId = body.rgResult!.opponents![2].carId;
// Get third opponent tunePower
saveExGhostHistory.opponent3TunePower = body.rgResult!.opponents![2].tunePower;
// Get third opponent tuneHandling
saveExGhostHistory.opponent3TuneHandling = body.rgResult!.opponents![2].tuneHandling;
// Get the advantage distance between third opponent and user
saveExGhostHistory.opponent3Result = body.rgResult!.opponents![2].result;
}
}
// Get played Area
if(body.rgResult?.path !== null && body.rgResult?.path !== undefined)
{
if(body.rgResult?.path >= 0 && body.rgResult?.path <= 9){ // GID_PATH_C1
saveExGhostHistory.area = Number(0);
}
else if(body.rgResult?.path >= 10 && body.rgResult?.path <= 15){ // GID_PATH_N9
saveExGhostHistory.area = Number(1);
}
else if(body.rgResult?.path >= 16 && body.rgResult?.path <= 17){ // GID_PATH_WTEAST
saveExGhostHistory.area = Number(2);
}
else if(body.rgResult?.path >= 18 && body.rgResult?.path <= 19){ // GID_PATH_WT_UP_DOWN
saveExGhostHistory.area = Number(3);
}
else if(body.rgResult?.path >= 20 && body.rgResult?.path <= 26){ // GID_PATH_WG
saveExGhostHistory.area = Number(4);
}
else if(body.rgResult?.path >= 27 && body.rgResult?.path <= 33){ // GID_PATH_KG
saveExGhostHistory.area = Number(5);
}
else if(body.rgResult?.path >= 34 && body.rgResult?.path <= 37){ // GID_PATH_YS
saveExGhostHistory.area = Number(6);
}
else if(body.rgResult?.path >= 38 && body.rgResult?.path <= 48){ // GID_PATH_KG_SHINYAMASHITA_MINATOMIRAI
saveExGhostHistory.area = Number(7);
}
else if(body.rgResult?.path === 49){ // GID_PATH_NGR
saveExGhostHistory.area = Number(8);
}
else if(body.rgResult?.path >= 50 && body.rgResult?.path <= 53){ // GID_PATH_OS
saveExGhostHistory.area = Number(9);
}
else if(body.rgResult?.path >= 54 && body.rgResult?.path <= 55){ // GID_PATH_KB
saveExGhostHistory.area = Number(10);
}
else if(body.rgResult?.path >= 58 && body.rgResult?.path <= 61){ // GID_PATH_FK
saveExGhostHistory.area = Number(11);
}
else if(body.rgResult?.path >= 62 && body.rgResult?.path <= 63){ // GID_PATH_HK
saveExGhostHistory.area = Number(12);
}
else if(body.rgResult?.path >= 64 && body.rgResult?.path <= 65){ // GID_PATH_TP
saveExGhostHistory.area = Number(13);
}
else if(body.rgResult?.path >= 56 && body.rgResult?.path <= 57){ // GID_PATH_HS
saveExGhostHistory.area = Number(18);
}
}
await prisma.ghostBattleRecord.create({
data: saveExGhostHistory
});
// Return the value to 'BASE_PATH/src/util/games/ghost.ts'
return { updateNewTrail }
}
export async function saveOCMGhostHistory(body: wm.protobuf.SaveGameResultRequest)
{
let updateNewTrail: boolean = true;
let saveExGhostHistory: any = {};
if (body.car?.carId !== null && body.car?.carId !== undefined) {
saveExGhostHistory.carId = body.car?.carId!;
}
if (body.car?.tunePower !== null && body.car?.tunePower !== undefined) {
saveExGhostHistory.tunePower = body.car?.tunePower!;
}
if (body.car?.tuneHandling !== null && body.car?.tuneHandling !== undefined) {
saveExGhostHistory.tuneHandling = body.car?.tuneHandling!;
}
if (body.playedAt !== null && body.playedAt !== undefined){
saveExGhostHistory.playedAt = body.playedAt!;
}
// Get shop name
saveExGhostHistory.playedShopName = Config.getConfig().shopName;
// Get the advantage distance between first opponent and user
saveExGhostHistory.result = body.rgResult!.opponents![0].result;
// Get played Area
if(body.rgResult?.path !== null && body.rgResult?.path !== undefined)
{
if(body.rgResult?.path >= 0 && body.rgResult?.path <= 9){ // GID_PATH_C1
saveExGhostHistory.area = Number(0);
}
else if(body.rgResult?.path >= 10 && body.rgResult?.path <= 15){ // GID_PATH_N9
saveExGhostHistory.area = Number(1);
}
else if(body.rgResult?.path >= 16 && body.rgResult?.path <= 17){ // GID_PATH_WTEAST
saveExGhostHistory.area = Number(2);
}
else if(body.rgResult?.path >= 18 && body.rgResult?.path <= 19){ // GID_PATH_WT_UP_DOWN
saveExGhostHistory.area = Number(3);
}
else if(body.rgResult?.path >= 20 && body.rgResult?.path <= 26){ // GID_PATH_WG
saveExGhostHistory.area = Number(4);
}
else if(body.rgResult?.path >= 27 && body.rgResult?.path <= 33){ // GID_PATH_KG
saveExGhostHistory.area = Number(5);
}
else if(body.rgResult?.path >= 34 && body.rgResult?.path <= 37){ // GID_PATH_YS
saveExGhostHistory.area = Number(6);
}
else if(body.rgResult?.path >= 38 && body.rgResult?.path <= 48){ // GID_PATH_KG_SHINYAMASHITA_MINATOMIRAI
saveExGhostHistory.area = Number(7);
}
else if(body.rgResult?.path === 49){ // GID_PATH_NGR
saveExGhostHistory.area = Number(8);
}
else if(body.rgResult?.path >= 50 && body.rgResult?.path <= 53){ // GID_PATH_OS
saveExGhostHistory.area = Number(9);
}
else if(body.rgResult?.path >= 54 && body.rgResult?.path <= 55){ // GID_PATH_KB
saveExGhostHistory.area = Number(10);
}
else if(body.rgResult?.path >= 58 && body.rgResult?.path <= 61){ // GID_PATH_FK
saveExGhostHistory.area = Number(11);
}
else if(body.rgResult?.path >= 62 && body.rgResult?.path <= 63){ // GID_PATH_HK
saveExGhostHistory.area = Number(12);
}
else if(body.rgResult?.path >= 64 && body.rgResult?.path <= 65){ // GID_PATH_TP
saveExGhostHistory.area = Number(13);
}
else if(body.rgResult?.path >= 56 && body.rgResult?.path <= 57){ // GID_PATH_HS
saveExGhostHistory.area = Number(18);
}
}
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get currently active OCM event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
// ----------Get available OCM Record (Qualifying or Main Draw)----------
// Current date is OCM main draw
let countGBR;
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
// Set OCM Main draw value to true
saveExGhostHistory.ocmMainDraw = true
// Get the user's available OCM Battle Record data
countGBR = await prisma.oCMTally.findFirst({
where:{
carId: saveExGhostHistory.carId,
competitionId: ocmEventDate!.competitionId,
}
});
}
// Current date is OCM qualifying day
else
{
// Set OCM Main draw value to false
saveExGhostHistory.ocmMainDraw = false
// Get the user's available OCM Battle Record data
countGBR = await prisma.oCMGhostBattleRecord.findFirst({
where:{
carId: saveExGhostHistory.carId,
ocmMainDraw: saveExGhostHistory.ocmMainDraw,
area: saveExGhostHistory.area,
competitionId: ocmEventDate!.competitionId,
periodId: 0
}
});
}
// ----------------------------------------------------------------
// User have OCM Battle Record data available
if(countGBR)
{
// Check if the newest advantage distance is bigger than the older advantage distance
if(countGBR!.result < saveExGhostHistory.result)
{
console.log('OCM Ghost Tally found');
// Current date is OCM Main Draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
// Get OCM Period ID
let OCM_periodId = await prisma.oCMPeriod.findFirst({
where:{
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId,
startAt:
{
lte: date, // competitionStartAt is less than current date
},
closeAt:
{
gte: date, // competitionCloseAt is greater than current date
}
},
select:{
periodId: true
}
});
let checkGhost = await prisma.oCMGhostBattleRecord.findFirst({
where:{
carId: saveExGhostHistory.carId,
competitionId: ocmEventDate!.competitionId,
}
});
if(checkGhost)
{
console.log('Updating OCM Ghost Battle Record entry');
// Get the user's available OCM Battle Record data
let getGBR = await prisma.oCMGhostBattleRecord.findFirst({
where:{
carId: saveExGhostHistory.carId,
area: saveExGhostHistory.area,
competitionId: ocmEventDate!.competitionId,
}
});
// Update ghost battle record
await prisma.oCMGhostBattleRecord.update({
where:{
dbId: getGBR!.dbId
},
data: {
...saveExGhostHistory,
competitionId: ocmEventDate!.competitionId,
periodId: OCM_periodId!.periodId
}
});
}
else
{
console.log('Creating new OCM Ghost Battle Record entry');
// Create new ghost battle record
await prisma.oCMGhostBattleRecord.create({
data: {
...saveExGhostHistory,
competitionId: ocmEventDate!.competitionId,
periodId: OCM_periodId!.periodId
}
});
}
console.log('Updating OCM Tally Record');
// Update the OCM Tally Record
await prisma.oCMTally.update({
where:{
dbId: countGBR.dbId
},
data: {
periodId: OCM_periodId!.periodId,
result: body.rgResult!.opponents![0].result,
tunePower: body.car?.tunePower!,
tuneHandling: body.car?.tuneHandling!
}
});
}
// Current date is OCM Qualifying
else
{
// Update ghost battle record
await prisma.oCMGhostBattleRecord.update({
where:{
dbId: countGBR.dbId
},
data: {
...saveExGhostHistory,
competitionId: ocmEventDate!.competitionId,
periodId: 0
}
});
}
}
// Newest advantage distance is smaller than the older advantage distance
else
{
console.log('Result record is lower than previous record');
// Don't update the User's OCM ghost trail
updateNewTrail = false;
}
}
// User don't have OCM Battle Record data available
else
{
console.log('OCM Ghost Battle Record not found');
console.log('Creating new OOCM Ghost Battle Record entry');
// Current date is OCM Main Draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
// Get OCM Period ID
let OCM_periodId = await prisma.oCMPeriod.findFirst({
where:{
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId
},
orderBy:{
periodId: 'desc'
},
select:{
periodId: true
}
});
// Update ghost battle record
await prisma.oCMGhostBattleRecord.create({
data: {
...saveExGhostHistory,
competitionId: ocmEventDate!.competitionId,
periodId: OCM_periodId!.periodId
}
});
let ocmTallyfind = await prisma.oCMTally.findFirst({
where:{
carId: saveExGhostHistory.carId,
competitionId: ocmEventDate!.competitionId,
},
});
if(ocmTallyfind)
{
console.log('Updating OCM Tally Record');
// Update the OCM Tally Record
await prisma.oCMTally.update({
where:{
dbId: ocmTallyfind.dbId
},
data: {
periodId: OCM_periodId!.periodId,
result: body.rgResult!.opponents![0].result,
tunePower: body.car?.tunePower!,
tuneHandling: body.car?.tuneHandling!
}
});
}
}
// Current date is OCM Qualifying
else
{
// Update ghost battle record
await prisma.oCMGhostBattleRecord.create({
data: {
...saveExGhostHistory,
competitionId: ocmEventDate!.competitionId,
periodId: 0
}
});
}
}
// Return the value to 'BASE_PATH/src/util/games/ghost.ts'
return { updateNewTrail }
}

View File

@ -0,0 +1,120 @@
// Save ghost battle result
export async function OCMArea(competition_id: number)
{
let areaVal = 0;
let rampVal = 0;
let pathVal = 0;
// 16th - C1
if(competition_id === 1)
{
// GID_RUNAREA_C1
areaVal = 0;
// GID_RAMP_C1_OUT_KANDABASHI
rampVal = 2;
// GID_PATH_C1OUT_KANDABASHI03
pathVal = 6;
}
// 17th - Osaka
else if(competition_id === 2)
{
// GID_RUNAREA_OSAKA
areaVal = 9;
// GID_RAMP_OOSAKA_DOUTONBORI
rampVal = 26;
// GID_PATH_OS_TONBORI04
pathVal = 53;
}
// 18th - Fukuoka
else if(competition_id === 3)
{
// GID_RUNAREA_FUKUOKA
areaVal = 11;
// GID_RAMP_FUKUOKA_EAST_NISHI
rampVal = 31
// GID_PATH_FK_NISHIKOUEN;
pathVal = 60;
}
// 19th - Nagoya
else if(competition_id === 4)
{
// GID_RUNAREA_NAGOYA
areaVal = 8;
// GID_RAMP_NAGOYA_MARUNOUCHI
rampVal = 25;
// GID_PATH_NGR_MARUNOUCHI
pathVal = 49;
}
// 6th - C1
else if(competition_id === 5)
{
// GID_RUNAREA_C1
areaVal = 0;
// GID_RAMP_C1_IN_KANDABASHI
rampVal = 0;
// GID_PATH_C1IN_KANDABASHI02
pathVal = 1;
}
// 20th - Kobe
else if(competition_id === 6)
{
// GID_RUNAREA_KOBE
areaVal = 10;
// GID_RAMP_KOBE_NADAOOHASHI
rampVal = 28;
// GID_PATH_KB_NADA
pathVal = 55;
}
// 7th - Fukutoshin
else if(competition_id === 7)
{
// GID_RUNAREA_SUBTOKYO_3_4
areaVal = 2;
// GID_RAMP_SUBTOKYO_GAIEN
rampVal = 7;
// GID_PATH_WTWEST_GAIEN
pathVal = 17;
}
// 21st - Hiroshima
else if(competition_id === 8)
{
// GID_RUNAREA_HIROSHIMA
areaVal = 18;
// GID_RAMP_HIROSHIMA_SHINONOME
rampVal = 37;
// GID_PATH_HS_SHINONOME
pathVal = 56;
}
// 8th - Hakone
else if(competition_id === 9)
{
// GID_RUNAREA_HAKONE
areaVal = 12;
// GID_RAMP_HAKONE_FOR
rampVal = 33;
// GID_PATH_HKFOR
pathVal = 62;
}
// Return the value to 'BASE_PATH/src/modules/ghost_ocm.ts'
return {areaVal, rampVal, pathVal};
}

345
src/util/games/ghost.ts Normal file
View File

@ -0,0 +1,345 @@
import { prisma } from "../..";
// Import Proto
import { wm } from "../../wmmt/wm.proto";
import wmproto from "../../wmmt/wm.proto";
import * as ghost_history from "../games/games_util/ghost_history";
// Save ghost battle result
export async function saveGhostBattleResult(body: wm.protobuf.SaveGameResultRequest, car: any)
{
// Declare variable for return
let ghostModePlay: boolean = false;
let updateNewTrail: boolean = false;
let OCMModePlay: boolean = false;
// If the game was not retired / timed out
if (!(body.retired || body.timeup))
{
console.log('Saving Ghost Battle Result');
// Set ghost mode play to true for saving the ghost trail later
ghostModePlay = true;
// Set update new trail to true for updating the user's ghost trail after playing OCM ghost battle mode later
updateNewTrail = true;
// Get the ghost result for the car
let ghostResult = body?.rgResult;
// Declare data
let dataGhost : any;
let dataCar : any;
// ghostResult is set
if (ghostResult)
{
// Ghost update data
dataGhost = {
rgRegionMapScore: ghostResult.rgRegionMapScore || undefined,
rgPlayCount: ghostResult.rgPlayCount || undefined,
dressupLevel: ghostResult.dressupLevel || undefined,
dressupPoint: ghostResult.dressupPoint || undefined,
}
}
// Get the ghost result for the car
let cars = body?.car;
// Car is set
if (cars)
{
// Error handling to prevent set ghost level to out of range value
if(cars.ghostLevel)
{
if(cars.ghostLevel < 1)
{
cars.ghostLevel = 1;
}
else if(cars.ghostLevel > 11)
{
cars.ghostLevel = 10;
}
}
// Car update data
dataCar = {
wheel: cars.wheel || undefined,
wheelColor: cars.wheelColor || undefined,
aero: cars.aero || undefined,
bonnet: cars.bonnet || undefined,
wing: cars.wing || undefined,
mirror: cars.mirror || undefined,
neon: cars.neon || undefined,
trunk: cars.trunk || undefined,
plate: cars.plate || undefined,
plateColor: cars.plateColor || undefined,
plateNumber: cars.plateNumber || undefined,
ghostLevel: cars.ghostLevel || undefined,
}
}
// Count total win based on region map score
if(body.rgResult?.rgRegionMapScore && body.rgResult?.rgRegionMapScore.length !== 0)
{
let winCounter = 0;
// Count the total win
for(let i=0; i<body.rgResult.rgRegionMapScore.length; i++)
{
winCounter += body.rgResult.rgRegionMapScore[i];
}
// Set the data
dataGhost.rgWinCount = winCounter;
dataGhost.rgScore = winCounter;
dataGhost.rgTrophy = winCounter;
}
// Update the car properties
await prisma.car.update({
where: {
carId: body.carId
},
data: {
...dataGhost,
...dataCar
}
});
// --------------GHOST BATTLE SELECTION MODE--------------
// Calling save ghost history battle function (BASE_PATH/src/util/games/games_util/ghost_history.ts)
let ghost_historys: any;
switch (body.rgResult!.selectionMethod)
{
case wmproto.wm.protobuf.GhostSelectionMethod.GHOST_SELECT_BY_LEVEL:
{
console.log('Normal Ghost Mode Found');
ghost_historys = await ghost_history.saveGhostHistory(body);
// Update the updateNewTrail value
updateNewTrail = ghost_historys.updateNewTrail;
break;
}
// Crown Ghost Battle Mode
case wmproto.wm.protobuf.GhostSelectionMethod.GHOST_SELECT_CROWN_MATCH:
{
// If not losing to the crown ghost battle
if (body.rgResult?.acquireCrown !== false && body.rgResult?.acquireCrown)
{
console.log('Crown Ghost Mode Found');
// Get the ghost crown result
let ghostResultCrown = body?.rgResult;
// Declare data
let dataCrown : any;
// ghostResultCrown is set
if (ghostResultCrown)
{
let carId: number = 0;
if(body.carId)
{
carId = Number(body.carId);
}
// Ghost Crown update data
dataCrown = {
carId: carId,
playedAt: body.playedAt || undefined,
tunePower: body.car?.tunePower || undefined,
tuneHandling: body.car?.tuneHandling || undefined,
}
// Get the area id and ramp id
let area = 0;
let ramp = 0;
let path = 0;
if(body.rgResult?.path)
{
if(body.rgResult?.path >= 0 && body.rgResult?.path <= 9){ // GID_PATH_C1
area = Number(0);
ramp = Number(Math.floor(Math.random() * 4));
}
else if(body.rgResult?.path >= 10 && body.rgResult?.path <= 15){ // GID_PATH_N9
area = Number(1);
ramp = Number(Math.floor(Math.random() * 2) + 4);
}
else if(body.rgResult?.path >= 16 && body.rgResult?.path <= 17){ // GID_PATH_WTEAST
area = Number(2);
ramp = Number(Math.floor(Math.random() * 2) + 6);
}
else if(body.rgResult?.path >= 18 && body.rgResult?.path <= 19){ // GID_PATH_WT_UP_DOWN
area = Number(3);
ramp = Number(Math.floor(Math.random() * 2) + 8);
}
else if(body.rgResult?.path >= 20 && body.rgResult?.path <= 26){ // GID_PATH_WG
area = Number(4);
ramp = Number(Math.floor(Math.random() * 4) + 10);
}
else if(body.rgResult?.path >= 27 && body.rgResult?.path <= 33){ // GID_PATH_KG
area = Number(5);
ramp = Number(Math.floor(Math.random() * 4) + 14);
}
else if(body.rgResult?.path >= 34 && body.rgResult?.path <= 37){ // GID_PATH_YS
area = Number(6);
ramp = Number(Math.floor(Math.random() * 3) + 18);
}
else if(body.rgResult?.path >= 38 && body.rgResult?.path <= 48){ // GID_PATH_KG_SHINYAMASHITA_MINATOMIRAI
area = Number(7);
ramp = Number(Math.floor(Math.random() * 4) + 21);
}
else if(body.rgResult?.path === 49){ // GID_PATH_NGR
area = Number(8);
ramp = Number(25);
}
else if(body.rgResult?.path >= 50 && body.rgResult?.path <= 53){ // GID_PATH_OS
area = Number(9);
ramp = Number(26);
}
else if(body.rgResult?.path >= 54 && body.rgResult?.path <= 55){ // GID_PATH_KB
area = Number(10);
ramp = Number(Math.floor(Math.random() * 2) + 27);
}
else if(body.rgResult?.path >= 58 && body.rgResult?.path <= 61){ // GID_PATH_FK
area = Number(11);
ramp = Number(Math.floor(Math.random() * 4) + 29);
}
else if(body.rgResult?.path >= 62 && body.rgResult?.path <= 63){ // GID_PATH_HK
area = Number(12);
ramp = Number(Math.floor(Math.random() * 2) + 33);
}
else if(body.rgResult?.path >= 64 && body.rgResult?.path <= 65){ // GID_PATH_TP
area = Number(13);
ramp = Number(Math.floor(Math.random() * 2) + 35);
}
else if(body.rgResult?.path >= 56 && body.rgResult?.path <= 57){ // GID_PATH_HS
area = Number(18);
ramp = Number(Math.floor(Math.random() * 2) + 27);
}
path = Number(body.rgResult.path);
}
// Get the available crown holder data
let carCrowns = await prisma.carCrown.count({
where: {
area: area
}
});
// Crown holder data available
if(carCrowns !== 0)
{
// Update it to the new one
let areaVal = Number(area);
await prisma.carCrown.update({
where: {
area: areaVal
},
data: {
...dataCrown,
area: area,
ramp: ramp,
path: path
}
});
}
// Crown holder data not available or still default crown holder data
else
{
await prisma.carCrown.create({
data: {
...dataCrown,
area: area,
ramp: ramp,
path: path
}
});
}
}
}
ghost_historys = await ghost_history.saveGhostHistory(body);
// Update the updateNewTrail value
updateNewTrail = ghost_historys.updateNewTrail;
break;
}
// OCM Ghost Battle Mode
case wmproto.wm.protobuf.GhostSelectionMethod.GHOST_COMPETITION:
{
console.log('OCM Ghost Mode Found');
OCMModePlay = true;
let saveExOCM: any = {};
saveExOCM.carId = body.carId;
if(body.rgResult?.competitionId){
saveExOCM.competitionId = body.rgResult?.competitionId!;
}
if(body.rgResult?.periodId){
saveExOCM.periodId = body.rgResult?.periodId!;
}
else
{
saveExOCM.periodId = 0;
}
if(body.rgResult?.brakingPoint){
saveExOCM.brakingPoint = body.rgResult?.brakingPoint!;
}
if(body?.playedAt){
saveExOCM.playedAt = body?.playedAt!;
}
// Get the user's available OCM Battle data
let countOCM = await prisma.oCMPlayRecord.count({
where: {
competitionId: saveExOCM.competitionId,
carId: body.carId
}
});
// User's OCM Battle data available
if(countOCM !== 0){
console.log('OCM Play Record found');
console.log('Updaing OCM Play Record entry');
await prisma.oCMPlayRecord.updateMany({
where:{
carId: saveExOCM.carId,
competitionId: saveExOCM.competitionId,
},
data: saveExOCM
});
}
// First time User playing OCM Battle
else{
console.log('OCM Play Record not found');
console.log('Creating new OCM Play Record entry');
await prisma.oCMPlayRecord.create({
data: saveExOCM
});
}
ghost_historys = await ghost_history.saveOCMGhostHistory(body);
// Update the updateNewTrail value
updateNewTrail = ghost_historys.updateNewTrail;
break;
}
}
}
// Return the value to 'BASE_PATH/src/modules/game.ts'
return { ghostModePlay, updateNewTrail, OCMModePlay }
}

1130
src/util/games/ghost_ocm.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,430 @@
import { prisma } from "../..";
// Import Proto
import { wm } from "../../wmmt/wm.proto";
// Save OCM ghost battle result
export async function saveOCMGhostTrail(body: wm.protobuf.RegisterGhostTrailRequest)
{
console.log('Checking OCM Ghost Battle trail history');
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get current active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy:{
dbId: 'desc'
}
});
if(ocmEventDate)
{
// Get OCM Period ID
let OCM_periodId = await prisma.oCMPeriod.findFirst({
where:{
competitionDbId: ocmEventDate!.dbId,
competitionId: ocmEventDate!.competitionId,
startAt:
{
lte: date, // competitionStartAt is less than current date
},
closeAt:
{
gte: date, // competitionCloseAt is greater than current date
}
},
select:{
periodId: true
}
});
let ocmMainDraws: boolean = false;
let periodId = 0;
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
periodId = OCM_periodId!.periodId;
ocmMainDraws = true;
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
ocmMainDraws = false;
}
// Get the ghost result for the car
let ghostResult = body?.ghost;
// Declare data
let data : any;
// ghostResult is set
if (ghostResult)
{
// Ghost update data
let grArea: number = 0;
let grRamp: number = 0;
let grPath: number = 0;
if(ghostResult.area)
{
grArea = ghostResult.area;
}
if(ghostResult.ramp)
{
grRamp = ghostResult.ramp;
}
if(ghostResult.path)
{
grPath = ghostResult.path;
}
data = {
carId: Number(ghostResult.car.carId),
area: grArea,
ramp: grRamp,
path: grPath,
trail: body.trail || undefined,
competitionId: ocmEventDate!.competitionId,
playedAt: ghostResult.car.lastPlayedAt || undefined,
tunePower: ghostResult.car.tunePower || undefined,
tuneHandling: ghostResult.car.tuneHandling || undefined,
ocmMainDraw: ocmMainDraws
}
// Check OCM Ghost Battle Record if playing OCM Ghost Battle Mode
let gtCount = await prisma.oCMGhostTrail.findFirst({
where:{
carId: ghostResult.car.carId!,
competitionId: ocmEventDate!.competitionId,
area: ghostResult.area!,
},
orderBy: {
playedAt: 'desc'
}
});
// Record exist, update it
if(gtCount)
{
console.log('OCM Ghost Trail history found');
console.log('Updating OCM ghost trail to the newest trail');
// Update the data
await prisma.oCMGhostTrail.update({
where: {
dbId: gtCount.dbId
},
data: {
...data,
periodId: periodId
}
});
}
// Record does not exist, create new
else
{
console.log('No OCM ghost trail history');
console.log('Creating new OCM ghost trail entry');
// Create new data
await prisma.oCMGhostTrail.create({
data: {
...data,
periodId: periodId
}
});
}
}
}
}
// Save Crown ghost battle result
export async function saveCrownGhostTrail(body: wm.protobuf.RegisterGhostTrailRequest)
{
console.log('Checking Crown Ghost Battle trail history');
// Get the ghost result for the car
let ghostResult = body?.ghost;
// Declare data
let data : any;
// ghostResult is set
if (ghostResult)
{
// Ghost update data
let grArea: number = 0;
let grRamp: number = 0;
let grPath: number = 0;
if(ghostResult.area)
{
grArea = ghostResult.area;
}
if(ghostResult.ramp)
{
grRamp = ghostResult.ramp;
}
if(ghostResult.path)
{
grPath = ghostResult.path;
}
data = {
carId: Number(ghostResult.car.carId),
area: grArea,
ramp: grRamp,
path: grPath,
trail: body.trail || undefined,
time: body.time || undefined,
driveData: body.driveData?.data || undefined,
driveDMergeSerial: body.driveData?.mergeSerial || undefined,
trendBinaryByArea: body.trendBinaryByArea?.data || undefined,
byAreaMergeSerial: body.trendBinaryByArea?.mergeSerial || undefined,
trendBinaryByCar: body.trendBinaryByCar?.data || undefined,
byCarMergeSerial: body.trendBinaryByCar?.mergeSerial || undefined,
trendBinaryByUser: body.trendBinaryByUser?.data || undefined,
byUserMergeSerial: body.trendBinaryByUser?.mergeSerial || undefined,
playedAt: ghostResult.car.lastPlayedAt || undefined,
tunePower: ghostResult.car.tunePower || undefined,
tuneHandling: ghostResult.car.tuneHandling || undefined,
crownBattle: true,
}
// Check Crown Ghost Battle Record if playing Crown Ghost Battle Mode
let gtCount = await prisma.ghostTrail.findFirst({
where:{
area: ghostResult.area!,
crownBattle: true,
},
orderBy: {
playedAt: 'desc'
}
});
// Record exist, update it
if(gtCount)
{
console.log('Crown Trail history found');
console.log('Updating crown trail to the newest trail');
// Update the data
await prisma.ghostTrail.update({
where: {
dbId: gtCount.dbId
},
data: data
});
}
// Record does not exist, create new
else
{
console.log('No crown trail history');
console.log('Creating new crown trail entry');
// Create new data
await prisma.ghostTrail.create({
data: data
});
}
// Update crown randomized ramp and path to the correct value
console.log('Updating crown\'s area records to the correct value')
await prisma.carCrown.update({
where: {
area: ghostResult.area!
},
data: {
area: ghostResult.area!,
ramp: ghostResult.ramp!,
path: ghostResult.path!,
playedAt: ghostResult.car.lastPlayedAt!
}
});
}
}
// Save Crown ghost battle result
export async function saveNormalGhostTrail(body: wm.protobuf.RegisterGhostTrailRequest)
{
console.log('Checking Normal Ghost Battle trail history');
// Get the ghost result for the car
let ghostResult = body?.ghost;
// Declare data
let data : any;
// ghostResult is set
if (ghostResult)
{
// Ghost update data
let grArea: number = 0;
let grRamp: number = 0;
let grPath: number = 0;
if(ghostResult.area)
{
grArea = ghostResult.area;
}
if(ghostResult.ramp)
{
grRamp = ghostResult.ramp;
}
if(ghostResult.path)
{
grPath = ghostResult.path;
}
data = {
carId: Number(ghostResult.car.carId),
area: grArea,
ramp: grRamp,
path: grPath,
trail: body.trail || undefined,
time: Number(body.time) || undefined,
driveData: body.driveData?.data || undefined,
driveDMergeSerial: body.driveData?.mergeSerial || undefined,
trendBinaryByArea: body.trendBinaryByArea?.data || undefined,
byAreaMergeSerial: body.trendBinaryByArea?.mergeSerial || undefined,
trendBinaryByCar: body.trendBinaryByCar?.data || undefined,
byCarMergeSerial: body.trendBinaryByCar?.mergeSerial || undefined,
trendBinaryByUser: body.trendBinaryByUser?.data || undefined,
byUserMergeSerial: body.trendBinaryByUser?.mergeSerial || undefined,
playedAt: ghostResult.car.lastPlayedAt || undefined,
tunePower: ghostResult.car.tunePower || undefined,
tuneHandling: ghostResult.car.tuneHandling || undefined,
crownBattle: false,
}
// Check Normal Ghost Battle Record if playing Crown Ghost Battle Mode
let gtCount = await prisma.ghostTrail.findFirst({
where:{
carId: ghostResult.car.carId!,
area: ghostResult.area!,
path: ghostResult.path!,
crownBattle: false,
},
orderBy: {
playedAt: 'desc'
}
});
// Record exist, update it
if(gtCount)
{
console.log('Trail history found');
console.log('Updating trail to the newest trail');
// Update the data
let gdbId = gtCount.dbId;
await prisma.ghostTrail.update({
where: {
dbId: gdbId
},
data: data
});
}
// Record does not exist, create new
else
{
console.log('No trail history');
console.log('Creating new trail entry');
// Create new data
await prisma.ghostTrail.create({
data: data
});
}
}
}
// Save path and tuning result
export async function savePathAndTuning(body: wm.protobuf.RegisterGhostTrailRequest)
{
console.log('Saving Car Path and Tuning');
// Get the ghost result for the car
let ghostResult = body?.ghost;
// ghostResult is set
if (ghostResult)
{
//Check Car Path and Tuning Record for certain area
let cPaT_count = await prisma.carPathandTuning.findFirst({
where:{
carId: ghostResult.car.carId!,
area: ghostResult.area!,
path: ghostResult.path!,
},
orderBy: {
lastPlayedAt: 'desc'
}
});
// Record (Car Path and Tuning) exist, update it
if(cPaT_count)
{
console.log('Updating path and tuning record');
await prisma.carPathandTuning.update({
where: {
dbId: cPaT_count.dbId
},
data:{
carId: ghostResult.car.carId!,
area: ghostResult.area!,
ramp: ghostResult.ramp!,
path: ghostResult.path!,
tunePower: ghostResult.car.tunePower!,
tuneHandling: ghostResult.car.tuneHandling!,
lastPlayedAt: ghostResult.car.lastPlayedAt!
}
});
}
// Record (Car Path and Tuning) does not exist, create new
else
{
console.log('Creating new path and tuning record');
await prisma.carPathandTuning.create({
data: {
carId: ghostResult.car.carId!,
area: ghostResult.area!,
ramp: ghostResult.ramp!,
path: ghostResult.path!,
tunePower: ghostResult.car.tunePower!,
tuneHandling: ghostResult.car.tuneHandling!,
lastPlayedAt: ghostResult.car.lastPlayedAt!
}
});
}
}
}

View File

@ -0,0 +1,309 @@
import { prisma } from "../..";
import { OCMTop1GhostTrail } from "@prisma/client";
// Import Proto
import * as ghost_ocm_area from "../games/games_util/ghost_ocm_area";
// Save ghost battle result
export async function getOCMGhostTrail(carId: number, trailId: number)
{
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get current / previous active OCM Event
let ocmEventDate = await prisma.oCMEvent.findFirst({
where: {
OR: [
{
// qualifyingPeriodStartAt is less than current date
qualifyingPeriodStartAt: { lte: date },
// qualifyingPeriodCloseAt is greater than current date
qualifyingPeriodCloseAt: { gte: date },
},
{
// competitionStartAt is less than current date
competitionStartAt: { lte: date },
// competitionCloseAt is greater than current date
competitionCloseAt: { gte: date },
},
{
// competitionCloseAt is less than current date
competitionCloseAt: { lte: date },
// competitionEndAt is greater than current date
competitionEndAt: {gte: date },
}
],
},
orderBy: [
{
dbId: 'desc'
},
{
competitionEndAt: 'desc',
},
],
});
if(!(ocmEventDate))
{
ocmEventDate = await prisma.oCMEvent.findFirst({
orderBy: [
{
dbId: 'desc'
},
{
competitionEndAt: 'desc',
},
],
});
}
let ghost_trails: OCMTop1GhostTrail | null;
// Current date is OCM main draw
if(ocmEventDate!.competitionStartAt < date && ocmEventDate!.competitionCloseAt > date)
{
// Get Current OCM Period
let OCMCurrentPeriod = await prisma.oCMPeriod.findFirst({
where: {
competitionDbId: ocmEventDate!.dbId,
startAt:
{
lte: date, // competitionStartAt is less than current date
},
closeAt:
{
gte: date, // competitionCloseAt is greater than current date
}
}
});
ghost_trails = await prisma.oCMTop1GhostTrail.findFirst({
where: {
carId: carId,
dbId: trailId,
competitionId: ocmEventDate!.competitionId,
periodId: OCMCurrentPeriod!.periodId
},
orderBy: {
playedAt: 'desc'
}
});
}
// Current date is OCM qualifying day
else if(ocmEventDate!.qualifyingPeriodStartAt < date && ocmEventDate!.qualifyingPeriodCloseAt > date)
{
ghost_trails = await prisma.oCMTop1GhostTrail.findFirst({
where: {
carId: carId,
dbId: trailId,
competitionId: ocmEventDate!.competitionId,
periodId: 0
},
orderBy: {
playedAt: 'desc'
}
});
}
else
{
ghost_trails = await prisma.oCMTop1GhostTrail.findFirst({
where: {
carId: carId,
dbId: trailId,
competitionId: ocmEventDate!.competitionId,
periodId: 999999999
},
orderBy: {
playedAt: 'desc'
}
});
}
let areaVal = 0;
let rampVal = 0;
let pathVal = 0;
let playedAt = 0;
let ghostTrail;
// Trails data for certain area is available
if(ghost_trails)
{
console.log('OCM Ghost Trail found');
// Set to OCM starting ramp data
areaVal = ghost_trails!.area;
// Set to OCM starting ramp data
rampVal = ghost_trails!.ramp;
// Set to OCM starting path data
pathVal = ghost_trails!.path;
// Set to OCM starting path data
playedAt = ghost_trails!.playedAt;
// Set to OCM trail data
ghostTrail = ghost_trails!.trail;
}
// Trails data for certain area is not available
else
{
console.log('OCM Ghost Trail not found');
// Calling OCM Area function (BASE_PATH/src/util/games/ghost_ocm.ts)
let OCMArea = await ghost_ocm_area.OCMArea(ocmEventDate!.competitionId);
// Set the value from OCMArea
areaVal = OCMArea.areaVal;
rampVal = OCMArea.rampVal;
pathVal = OCMArea.pathVal;
// Random value lmao, for default ghost trail stuff (any value maybe works)
playedAt = date
ghostTrail = new Uint8Array([1, 2, 3, 4]);
}
return { areaVal, rampVal, pathVal, playedAt, ghostTrail }
}
export async function getCrownGhostTrail(carId: number, areaId: number)
{
// Get current date
let date = Math.floor(new Date().getTime() / 1000);
// Get the trail data for certain area
let ghost_trails = await prisma.ghostTrail.findFirst({
where: {
carId: carId,
area: areaId,
crownBattle: true,
},
orderBy: {
playedAt: 'desc'
}
});
let rampVal = 0;
let pathVal = 0;
let playedAt = 0;
let ghostTrail: Uint8Array;
// Trails data for certain area is available
if(ghost_trails)
{
console.log('Crown Ghost Trail found');
// Set to crown holder starting ramp data
rampVal = ghost_trails!.ramp;
// Set to crown holder starting path data
pathVal = ghost_trails!.path;
let time = await prisma.carCrown.findFirst({
where: {
carId: carId,
area: areaId
},
orderBy: {
playedAt: 'desc'
},
select: {
playedAt: true
}
});
// Error handling if played At timestamp value is current date and timestamp is bigger than 9 July 2022 (using GMT+7 timestamp)
if(time!.playedAt !== 0 && time!.playedAt >= 1657299600)
{
// Acquired crown timestamp - 1 day
playedAt = time!.playedAt - 172800;
}
// Error handling if played At timestamp value is 0 or timestamp is less than 9 July 2022 (using GMT+7 timestamp)
else if(time!.playedAt === 0 || time!.playedAt < 1657299600)
{
// Acquired crown timestamp become 9 July 2022 (using GMT+7 timestamp)
playedAt = 1657299600;
}
// Set to crown holder trails data
ghostTrail = ghost_trails!.trail;
}
// Trails data for certain area is not available
else
{
console.log('Crown Ghost Trail not found');
if(areaId === 0){ // GID_RUNAREA_C1
rampVal = Math.floor(Math.random() * 4);
pathVal = Math.floor(Math.random() * 10);
}
else if(areaId === 1){ // GID_RUNAREA_RING
rampVal = Math.floor(Math.random() * 2) + 4;
pathVal = Math.floor(Math.random() * 6) + 10;
}
else if(areaId === 2){ // GID_RUNAREA_SUBTOKYO_3_4
rampVal = Math.floor(Math.random() * 2) + 6;
pathVal = Math.floor(Math.random() * 2) + 16;
}
else if(areaId === 3){ // GID_RUNAREA_SUBTOKYO_5
rampVal = Math.floor(Math.random() * 2) + 8;
pathVal = Math.floor(Math.random() * 2) + 18;
}
else if(areaId === 4){ // GID_RUNAREA_WANGAN
rampVal = Math.floor(Math.random() * 4) + 10;
pathVal = Math.floor(Math.random() * 7) + 20;
}
else if(areaId === 5){ // GID_RUNAREA_K1
rampVal = Math.floor(Math.random() * 4) + 14;
pathVal = Math.floor(Math.random() * 7) + 27;
}
else if(areaId === 6){ // GID_RUNAREA_YAESU
rampVal = Math.floor(Math.random() * 3) + 18;
pathVal = Math.floor(Math.random() * 4) + 34;
}
else if(areaId === 7){ // GID_RUNAREA_YOKOHAMA
rampVal = Math.floor(Math.random() * 4) + 21;
pathVal = Math.floor(Math.random() * 11) + 38;
}
else if(areaId === 8){ // GID_RUNAREA_NAGOYA
rampVal = 25;
pathVal = 49;
}
else if(areaId === 9){ // GID_RUNAREA_OSAKA
rampVal = 26;
pathVal = Math.floor(Math.random() * 4) + 50;
}
else if(areaId === 10){ // GID_RUNAREA_KOBE
rampVal = 28;
pathVal = 55;
}
else if(areaId === 11){ // GID_RUNAREA_FUKUOKA
rampVal = Math.floor(Math.random() * 4) + 29;
pathVal = Math.floor(Math.random() * 4) + 58;
}
else if(areaId === 12){ // GID_RUNAREA_HAKONE
rampVal = Math.floor(Math.random() * 2) + 33;
pathVal = Math.floor(Math.random() * 2) + 62;
}
else if(areaId === 13){ // GID_RUNAREA_TURNPIKE
rampVal = Math.floor(Math.random() * 2) + 35;
pathVal = Math.floor(Math.random() * 2) + 64;
}
//14 - 16 are dummy area, 17 is GID_RUNAREA_C1_CLOSED
else if(areaId === 18){ // GID_RUNAREA_HIROSHIMA
rampVal = Math.floor(Math.random() * 2) + 37;
pathVal = Math.floor(Math.random() * 2) + 56;
}
// Random value lmao, for default ghost trail stuff (any value maybe works)
playedAt = date
ghostTrail = new Uint8Array([1, 2, 3, 4]);
}
return { areaId, rampVal, pathVal, playedAt, ghostTrail }
}

85
src/util/games/story.ts Normal file
View File

@ -0,0 +1,85 @@
import Long from "long";
import { prisma } from "../..";
// Import Proto
import { wm } from "../../wmmt/wm.proto";
// Import Util
import * as common from "../common";
import * as check_step from "../games/games_util/check_step";
// Save story result
export async function saveStoryResult(body: wm.protobuf.SaveGameResultRequest, car: any)
{
// If the game was not retired / timed out
if (!(body.retired || body.timeup))
{
// Get the story result for the car
let storyResult = body?.stResult;
// storyResult is set
if (storyResult)
{
// Check if stClearDivCount is not 0
let stClearDivCount = undefined;
if(storyResult.stClearDivCount && storyResult.stClearDivCount !== 0)
{
stClearDivCount = storyResult.stClearDivCount
}
// Check if stClearCount is not 0
let stClearCount = undefined;
if(storyResult.stClearCount && storyResult.stClearCount !== 0)
{
stClearCount = storyResult.stClearCount
}
// Story update data
let data : any = {
stClearDivCount: stClearDivCount || undefined,
stPlayCount: storyResult.stPlayCount || undefined,
stClearCount: stClearCount || undefined,
stClearBits: storyResult.stClearBits || undefined,
stConsecutiveWins: storyResult.stConsecutiveWins || undefined,
tuningPoints: storyResult.tuningPoint || 0,
stCompleted100Episodes: storyResult.stCompleted_100Episodes || undefined,
}
// If the current consecutive wins is greater than the previous max
if (body.stResult!.stConsecutiveWins! > car!.stConsecutiveWinsMax)
{
// Update the maximum consecutive wins;
data.stConsecutiveWinsMax = body.stResult!.stConsecutiveWins!;
}
// If the lose bits are set, and are long data
if (Long.isLong(storyResult.stLoseBits))
{
// Convert them to BigInt and add to the data
data.stLoseBits = common.getBigIntFromLong(storyResult.stLoseBits);
// If a loss has been recorded
if (data.stLoseBits > 0)
{
// End the win streak
data.stConsecutiveWins = 0;
}
}
// Calling give meter reward function (BASE_PATH/src/util/meter_reward.ts)
let check_steps = await check_step.checkCurrentStep(body);
// Set the ghost level to the correct level
data.ghostLevel = check_steps.ghostLevel;
// Update the car properties
await prisma.car.update({
where: {
carId: body.carId
},
data: data
});
}
}
}

View File

@ -0,0 +1,72 @@
import { prisma } from "../..";
// Import Proto
import { wm } from "../../wmmt/wm.proto";
// Save time attack result
export async function saveTimeAttackResult(body: wm.protobuf.SaveGameResultRequest)
{
// If the game was not retired / timed out
if (!(body.retired || body.timeup)) {
console.log('Game not retired / timed out, continuing ...')
// Get the current time attack record for the car
let currentRecord = await prisma.timeAttackRecord.findFirst({
where: {
carId: body.carId, // , model: body.car!.model!,
course: body.taResult!.course
}
});
// Record already exists
if (currentRecord)
{
// If the existing record is faster, do not continue
if (body.taResult!.time < currentRecord.time){
console.log('Updating time attack record...')
await prisma.timeAttackRecord.update({
where: {
// Could be null - if it is null, this will insert.
dbId: currentRecord!.dbId
},
data: {
time: body.taResult!.time,
section1Time: body!.taResult!.section_1Time,
section2Time: body!.taResult!.section_2Time,
section3Time: body!.taResult!.section_3Time,
section4Time: body!.taResult!.section_4Time,
section5Time: body!.taResult!.section_5Time,
section6Time: body!.taResult!.section_6Time,
section7Time: body!.taResult!.section_7Time,
tunePower: body!.car!.tunePower,
tuneHandling: body!.car!.tuneHandling
}
});
}
}
else // Creating a new record
{
console.log('Creating new time attack record');
await prisma.timeAttackRecord.create({
data: {
carId: body.carId,
model: body.car!.model!,
time: body.taResult!.time,
isMorning: body.taResult!.isMorning,
course: body.taResult!.course,
section1Time: body!.taResult!.section_1Time,
section2Time: body!.taResult!.section_2Time,
section3Time: body!.taResult!.section_3Time,
section4Time: body!.taResult!.section_4Time,
section5Time: body!.taResult!.section_5Time,
section6Time: body!.taResult!.section_6Time,
section7Time: body!.taResult!.section_7Time,
tunePower: body!.car!.tunePower,
tuneHandling: body!.car!.tuneHandling
}
});
}
}
}

63
src/util/games/versus.ts Normal file
View File

@ -0,0 +1,63 @@
import { prisma } from "../..";
// Import Proto
import { wm } from "../../wmmt/wm.proto";
// Save versus battle result
export async function saveVersusBattleResult(body: wm.protobuf.SaveGameResultRequest)
{
if (!(body.retired || body.timeup))
{
// Get the vs result for the car
let vsResult = body?.vsResult;
// storyResult is set
if (vsResult)
{
// Check if vsTripleStarMedals is not 0
let vsTripleStarMedals = undefined;
if(vsResult.vsTripleStarMedals && vsResult.vsTripleStarMedals !== 0){
vsTripleStarMedals = vsResult.vsTripleStarMedals!;
}
// Check if vsDoubleStarMedals is not 0
let vsDoubleStarMedals = undefined;
if(vsResult.vsDoubleStarMedals && vsResult.vsDoubleStarMedals !== 0){
vsDoubleStarMedals = vsResult.vsDoubleStarMedals!;
}
// Check if vsSingleStarMedals is not 0
let vsSingleStarMedals = undefined;
if(vsResult.vsSingleStarMedals && vsResult.vsSingleStarMedals !== 0){
vsSingleStarMedals = vsResult.vsSingleStarMedals!;
}
// Check if vsPlainMedals is not 0
let vsPlainMedals = undefined;
if(vsResult.vsPlainMedals && vsResult.vsPlainMedals !== 0){
vsPlainMedals = vsResult.vsPlainMedals!;
}
// vs result update data
let data : any = {
vsPlayCount: vsResult.vsPlayCount || undefined,
vsBurstCount: vsResult.vsBurstCount || undefined,
vsStarCount: vsResult.vsStarCount || undefined,
vsCoolOrWild: vsResult.vsCoolOrWild || undefined,
vsSmoothOrRough: vsResult.vsSmoothOrRough || undefined,
vsTripleStarMedals: vsTripleStarMedals || undefined,
vsDoubleStarMedals: vsDoubleStarMedals || undefined,
vsSingleStarMedals: vsSingleStarMedals || undefined,
vsPlainMedals: vsPlainMedals || undefined,
}
await prisma.car.update({
where: {
carId: body.carId
},
data: data
});
}
}
}

112
src/util/meter_reward.ts Normal file
View File

@ -0,0 +1,112 @@
import { prisma } from "..";
// Import Proto
import { wm } from "../wmmt/wm.proto";
// Save story result
export async function giveMeterRewards(body: wm.protobuf.SaveGameResultRequest)
{
// Get user's available meter data
let carItemCount = await prisma.carItem.count({
where: {
carId: body.carId,
category: 15,
itemId: {
lte: 34,
gte: 1,
},
NOT: {
itemId: { in: [2, 3, 5, 6, 29, 30, 31, 32, 33 ,34] }, // Except story meter
},
},
/*where: {
itemId: { notIn: [2, 3, 5, 6, 29, 30, 31, 32, 33 ,34] },
},*/
})
let itemIdVal = 0;
if(carItemCount === 0){
itemIdVal = 1; // Namco Meter
}
else if(carItemCount === 1){
itemIdVal = 4; // Special Meter
}
else if(carItemCount === 2){
itemIdVal = 7; // Metal 1 (Black)
}
else if(carItemCount === 3){
itemIdVal = 8; // Metal 2 (Red)
}
else if(carItemCount === 4){
itemIdVal = 9; // Cyber 1 (Blue)
}
else if(carItemCount === 5){
itemIdVal = 10; // Cyber 2 (Red)
}
else if(carItemCount === 6){
itemIdVal = 11; // Aluminium 1 (Blue)
}
else if(carItemCount === 7){
itemIdVal = 12; // Aluminium 1 (Red)
}
else if(carItemCount === 8){
itemIdVal = 13; // Jungle 1 (Green)
}
else if(carItemCount === 9){
itemIdVal = 14; // Jungle 2 (Brown)
}
else if(carItemCount === 10){
itemIdVal = 15; // Dessert 1 (Red)
}
else if(carItemCount === 11){
itemIdVal = 16; // Dessert 2 (Brown)
}
else if(carItemCount === 12){
itemIdVal = 17; // Pirate 1 (Red)
}
else if(carItemCount === 13){
itemIdVal = 18; // Pirate 2 (Blue)
}
else if(carItemCount === 14){
itemIdVal = 19; // Fire Pattern 1 (Red)
}
else if(carItemCount === 15){
itemIdVal = 20; // Fire Pattern 2 (Blue)
}
else if(carItemCount === 16){
itemIdVal = 21; // Silver Access
}
else if(carItemCount === 17){
itemIdVal = 22; // Gold Access
}
else if(carItemCount === 18){
itemIdVal = 23; // Steampunk 1 (Gold)
}
else if(carItemCount === 19){
itemIdVal = 24; // Steampunk 2 (Green)
}
else if(carItemCount === 20){
itemIdVal = 25; // Dragon 1 (Gold)
}
else if(carItemCount === 21){
itemIdVal = 26; // Dragon 2 (Blue)
}
else if(carItemCount === 22){
itemIdVal = 27; // Light Line 1 (Blue)
}
else if(carItemCount === 23){
itemIdVal = 28; // Light Line 2 (Orange)
}
if(itemIdVal !== 0){
console.log(`carID ${body.carId} do n*100 play, continue give reward... meter ID ${itemIdVal}`);
await prisma.carItem.create({
data: {
carId: body.carId,
category: 15,
itemId: itemIdVal,
amount: 1
}
});
}
}

View File

@ -1,6 +1,7 @@
import { isRateLimited } from "@sentry/utils";
import { prisma } from "..";
import { Config } from "../config";
//Import Proto
import * as wm from "../wmmt/wm.proto";
// *** CONSTANTS ***