diff --git a/config.example.json b/config.example.json index a11cc11..93059f7 100644 --- a/config.example.json +++ b/config.example.json @@ -4,6 +4,8 @@ "regionName": "Bayshore", "serverIp": "127.0.0.1", "gameOptions": { - "grantFullTuneTicketToNewUsers": 0 + "grantFullTuneTicketToNewUsers": 0, + "grantAllScratchRewards": 0, + "grantBonusScratchCars": 0 } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 95d9dad..522091c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -2,158 +2,182 @@ // 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[] - unusedTickets UserItem[] - tutorials Boolean[] - userBanned Boolean @default(false) + id Int @id @default(autoincrement()) + chipId String @unique + accessCode String + cars Car[] + carOrder Int[] // Order of cars + unusedTickets UserItem[] + tutorials Boolean[] + userBanned Boolean @default(false) + bookmarks Int[] + // ScratchSheet ScratchSheet[] + currentSheet Int @default(0) + lastSheet Int @default(0) } +// Not working yet, removing from schema +// model ScratchSheet { +// id Int @id @default(autoincrement()) +// User User @relation(fields: [userId], references: [id]) +// userId Int @unique +// squares ScratchSquare[] +// } + +// model ScratchSquare { +// id Int @id @default(autoincrement()) +// Sheet ScratchSheet @relation(fields: [sheetId], references: [id]) +// sheetId Int +// category Int +// itemId Int +// earned Boolean +// } + model UserItem { - dbId Int @id @default(autoincrement()) - category Int - itemId Int - User User @relation(fields: [userId], references: [id]) - userId Int + dbId Int @id @default(autoincrement()) + category Int + itemId Int + User User @relation(fields: [userId], references: [id]) + userId Int } 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("WANGAN") - windowStickerFont Int @default(0) - rivalMarker 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) + 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("WANGAN") + windowStickerFont Int @default(0) + rivalMarker 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) - 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) + 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[] + items CarItem[] - carStateDbId Int @unique - state CarState @relation(fields: [carStateDbId], references: [dbId]) - TimeAttackRecord TimeAttackRecord[] + carStateDbId Int @unique + state CarState @relation(fields: [carStateDbId], references: [dbId]) + TimeAttackRecord TimeAttackRecord[] } model CarItem { - Car Car? @relation(fields: [carId], references: [carId]) - dbId Int @id @default(autoincrement()) - carId Int - category Int - itemId Int - amount Int + Car Car? @relation(fields: [carId], references: [carId]) + carId Int @unique + + 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) + hasOpponentGhost Boolean @default(false) + eventJoined Boolean @default(false) + transferred Boolean @default(false) + toBeDeleted Boolean @default(false) } model TimeAttackRecord { - dbId Int @id @default(autoincrement()) + dbId Int @id @default(autoincrement()) - car Car @relation(fields: [carId], references: [carId]) - carId Int + 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") + 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 } diff --git a/src/config.ts b/src/config.ts index 2a77aef..fa4ecae 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,6 +17,17 @@ export interface UnixOptions { } export interface GameOptions { + // If set to 1, all scratch rewards will be granted to players (including cars) + grantAllScratchRewards: number; + + // If set to 1, gives the player a random colour for each of the special cars. + // If set to 2, allows the player to pick any of the colours (more cluttered) + grantBonusScratchCars: number; + + // If set to 1, all gift cars (i.e. S2000, S660, etc. will be fully tuned.) + // If set to 0, they will be left at their default tune (i.e. stock, basic tune, etc.) + giftCarsFullyTuned: number, + // Amount of full-tunes to grant to newly registered cards grantFullTuneTicketToNewUsers: number; } diff --git a/src/modules/game.ts b/src/modules/game.ts index 52d4bfb..4f65489 100644 --- a/src/modules/game.ts +++ b/src/modules/game.ts @@ -3,9 +3,11 @@ import { Module } from "../module"; import * as wm from "../wmmt/wm.proto"; import * as svc from "../wmmt/service.proto"; import { prisma } from ".."; -import { User } from "@prisma/client"; +import { Car, User } from "@prisma/client"; import { Config } from "../config"; import Long from "long"; +import { userInfo } from "os"; +import { config } from "dotenv"; export default class GameModule extends Module { register(app: Application): void { @@ -102,8 +104,6 @@ export default class GameModule extends Module { } case wm.wm.protobuf.GameMode.MODE_TIME_ATTACK: { - console.log(body); - // If the game was not timed out / retired if (!(body.retired || body.timeup)) { @@ -139,13 +139,15 @@ export default class GameModule extends Module { 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, @@ -160,6 +162,8 @@ export default class GameModule extends Module { section5Time: body!.taResult!.section_5Time, section6Time: body!.taResult!.section_6Time, section7Time: body!.taResult!.section_7Time, + tunePower: body!.car!.tunePower, + tuneHandling: body!.car!.tuneHandling } }); break; @@ -221,7 +225,9 @@ export default class GameModule extends Module { }) app.post('/method/load_user', async (req, res) => { + let body = wm.wm.protobuf.LoadUserRequest.decode(req.body); + let user = await prisma.user.findFirst({ where: { chipId: body.cardChipId, @@ -236,7 +242,10 @@ export default class GameModule extends Module { unusedTickets: true } }); + + // No user returned if (!user) { + console.log('no such user'); let msg = { error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, @@ -334,7 +343,11 @@ export default class GameModule extends Module { r.send(Buffer.from(end)); return; } + + // console.log(user); + let carStates = user.cars.map(e => e.state); + let tickets = (user.unusedTickets || []).map(x => { return { itemId: x.itemId, @@ -342,6 +355,7 @@ export default class GameModule extends Module { category: x.category } }); + let msg = { error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, numOfOwnedCars: user.cars.length, @@ -482,7 +496,7 @@ export default class GameModule extends Module { r.send(Buffer.from(end)); }) - //terminal specific + // Load upon enter terminal app.post('/method/load_terminal_information', (req, res) => { let msg = { error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, @@ -503,13 +517,387 @@ export default class GameModule extends Module { .status(200); r.send(Buffer.from(end)); }) - - app.post('/method/load_scratch_information', (req, res) => { + + // Load unrecieved user items + app.post('/method/load_unrecieved_user_items', (req, res) => { + + // In future, might want to check db for player items + let msg = { error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, - currentSheet: 21, - numOfScratched: 0, + owned_user_items: [ + wm.wm.protobuf.UserItem.create({ + category: 17, + itemId: 1 + }) + ] } + + let resp = wm.wm.protobuf.LoadUnreceivedUserItemsResponse.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)); + }) + + // 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); + } + } + } + + let msg = { + error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, + cars: cars + } + + let resp = wm.wm.protobuf.LoadBookmarksResponse.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 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) + 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) + } + }, + }); + + 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 + }, + }); + + // Update the car order (NOT IMPLEMENTED) + + // 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 + } + }); + + 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 (VERY WIP) + app.post('/method/load_scratch_information', async (req, res) => { + + // Get the information from the request + let body = wm.wm.protobuf.LoadScratchInformationRequest.decode(req.body); + + // Commenting all of the scratch card shit out for now + + /* + // Get all of the scratch sheets for the user + let scratchSheets = await prisma.scratchSheet.findMany({ + where: { + userId: body.userId + }, + include: { + squares: true + } + }); + + // No scratch sheets for user + if (scratchSheets.length == 0) + { + // Create a new scratch sheet for the user + let sheet = await prisma.scratchSheet.create({ + data: { + userId: body.userId + } + }) + + // Populate each square (with FT ticket for now) + for (let i=0; i<50; i++) { + await prisma.scratchSquare.create({ + data: { + sheetId: sheet.id, + category: wm.wm.protobuf.ItemCategory.CAT_RIVAL_MARKER, + itemId: 1, + earned: false + } + }); + } + + // In future, I will very the way this is populated based on settings + // i.e. totally random items, items based upon real arcade, etc. + + // Get the data for the newly created sheet + let newSheet = await prisma.scratchSheet.findFirst({ + where: { + userId: body.userId + }, + include: { + squares: true + } + }); + + // Sheet is created successfully + if (newSheet) + { + // Update the scratch sheet list + scratchSheets = [newSheet]; + } + } + + // Generate the scratch sheet proto + let scratch_sheets : wm.wm.protobuf.ScratchSheet[] = []; + + // Loop over all of the protos + for(let sheet of scratchSheets) + { + // Get all of the scratch squares + let scratch_squares : wm.wm.protobuf.ScratchSheet.ScratchSquare[] = []; + + // Loop over all of the squares + for (let square of sheet.squares) + { + // Add the current square to the protobuf array + scratch_squares.push(wm.wm.protobuf.ScratchSheet.ScratchSquare.create({ + category: square.category, + itemId: square.itemId, + earned: square.earned + })); + } + + // Add the scratch sheet to the sheets list + scratch_sheets.push( + wm.wm.protobuf.ScratchSheet.create({ + squares: scratch_squares + }) + ); + } + */ + + // Debug: Add all items to the 'acceptable items' list + + // Owned user items list + let ownedUserItems : wm.wm.protobuf.UserItem[] = []; + + // Get the current date + let date = Date.now(); + + // If the grant all scratch rewards switch is set, add them to the list + if (Config.getConfig().gameOptions.grantAllScratchRewards) + { + // Grant one of each item every time the menu is loaded + + // Loop over all of the window sticker styles + for(let i=1; i<=60; i++) + { + // Add one of each of the window sticker styles to the list + ownedUserItems.push(wm.wm.protobuf.UserItem.create({ + category: wm.wm.protobuf.ItemCategory.CAT_WINDOW_DECORATION, + itemId: i, + earnedAt: date, + })); + } + + // Loop over all of the rival markers + for(let i=1; i<=80; i++) + { + // Add one of each of the rival markers to the list + ownedUserItems.push(wm.wm.protobuf.UserItem.create({ + category: wm.wm.protobuf.ItemCategory.CAT_RIVAL_MARKER, + itemId: i, + earnedAt: date, + })); + } + + // Item ID for the scratch cars + let scratchCarIds = [4, 3, 1, 2, 5, 6, 16, 17, 18, 19, 20, 21]; + + // I literally ripped this from mozilla.org lmfao + let getValueInRange = (min: number, max: number) => { + return Math.random() * (max - min) + min; + } + + // If the grant bonus scratch cars switch is set, switch on the value + switch(Config.getConfig().gameOptions.grantBonusScratchCars) + { + case 1: // Grant one of each bonus scratch car (random colour) + + // Mini Cooper + scratchCarIds.push(getValueInRange(7, 15)); + + // S660 + scratchCarIds.push(getValueInRange(22, 27)); + + // S2000 + scratchCarIds.push(getValueInRange(28, 36)); + + // Roadster RF, 280ZT, Leopard + scratchCarIds = scratchCarIds.concat([37, 38, 39]); + + break; + case 2: // Grant every colour of each bonus scratch cars + + // Mini Cooper + scratchCarIds = scratchCarIds.concat([7, 8, 9, 10, 11, 12, 13, 14, 15]); + + // S660 + scratchCarIds = scratchCarIds.concat([22, 23, 24, 25, 26, 27]); + + // S2000 + scratchCarIds = scratchCarIds.concat([28, 29, 30, 31, 32, 33, 34, 35, 36]); + + // Roadster RF, 280ZT, Leopard + scratchCarIds = scratchCarIds.concat([37, 38, 39]); + + break; + default: // Do not grant bonus scratch cars + break; + } + + // Loop over all of the scratch cars + for(let car of scratchCarIds) + { + // Add one of each of the rival markers to the list + ownedUserItems.push(wm.wm.protobuf.UserItem.create({ + category: 201, // Scratch Car + itemId: car, + earnedAt: date, + })); + } + } + + let msg = { + error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, + scratchSheets: [], + currentSheet: 0, + numOfScratched: 0, + ownedUserItems: ownedUserItems + } + + // console.log(scratch_sheets); + let resp = wm.wm.protobuf.LoadScratchInformationResponse.encode(msg); let end = resp.finish(); let r = res @@ -520,6 +908,99 @@ export default class GameModule extends Module { r.send(Buffer.from(end)); }); + 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 all of the scratch sheets for the user + let scratchSheets = await prisma.scratchSheet.findMany({ + where: { + userId: body.userId + }, + include: { + squares: true + } + }) + + // Get the target scratch sheet + let scratchSheet = scratchSheets[Number(body.targetSheet)]; + + // Get all of the squares for the scratch sheet + let scratchSquares = await prisma.scratchSquare.findMany({ + where: { + sheetId: scratchSheet.id + } + }); + + // Get the target scratch square + let scratchSquare = scratchSquares[Number(body.targetSquare)]; + + // Update the revealed scratch square + await prisma.scratchSquare.update({ + where: { + id: scratchSquare.id + }, + data: { + earned: true + } + }); + + // Get the number of scratched squares on the page + let numOfScratched = await prisma.scratchSquare.count({ + where: { + sheetId: scratchSheet.id, + earned: true + } + }) + + // Generate the scratch sheet proto + let scratch_sheets : wm.wm.protobuf.ScratchSheet[] = []; + + // Loop over all of the protos + for(let sheet of scratchSheets) + { + // Get all of the scratch squares + let scratch_squares : wm.wm.protobuf.ScratchSheet.ScratchSquare[] = []; + + // Loop over all of the squares + for (let square of sheet.squares) + { + // Add the current square to the protobuf array + scratch_squares.push(wm.wm.protobuf.ScratchSheet.ScratchSquare.create({ + category: square.category, + itemId: square.itemId, + earned: square.earned + })); + } + + // Add the scratch sheet to the sheets list + scratch_sheets.push( + wm.wm.protobuf.ScratchSheet.create({ + squares: scratch_squares + }) + ); + } + */ + + let msg = { + error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, + scratchSheets : [], + currentSheet: body.targetSheet, + numOfScratched: 0, + } + + let resp = wm.wm.protobuf.SaveScratchSheetResponse.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)); + }) + app.post('/method/update_car', async (req, res) => { let body = wm.wm.protobuf.UpdateCarRequest.decode(req.body); let car = await prisma.car.findFirst({ @@ -567,15 +1048,23 @@ export default class GameModule extends Module { }) app.post('/method/create_car', async (req, res) => { + + // Get the create car request body let body = wm.wm.protobuf.CreateCarRequest.decode(req.body); + + console.log(body); + + // 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 { + } else { // No user id, use card chip user = await prisma.user.findFirst({ where: { chipId: body.cardChipId, @@ -583,30 +1072,75 @@ export default class GameModule extends Module { }, }) } + + // 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 fullTuneUsed = false; - if (body.userItemId) { + + // Sets if full tune is used or not + let fullyTuned = false; + + // 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: { dbId: body.userItemId } }); + console.log(`Item category was ${item.category} and item game ID was ${item.itemId}`); + + // If the item used was a full tune ticket if (item.category == wm.wm.protobuf.ItemCategory.CAT_CAR_TICKET_FREE && item.itemId == 5) { - // This is a full-tune ticket - fullTuneUsed = true; + // Fully tuned is true + fullyTuned = true; } + console.log('Item deleted!'); } + // User item not used, but car has 740 HP by default + else if (body.car && + (body.car.tunePower == 17) && (body.car.tuneHandling == 17)) + { + // Set fully tuned to be true + fullyTuned = true; + } + // 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 + fullyTuned = true; + } + } + + // Default car values let carInsert = { userId: user.id, manufacturer: body.car.manufacturer!, @@ -622,8 +1156,21 @@ export default class GameModule extends Module { carStateDbId: state.dbId, regionId: body.car.regionId!, }; - let additionalInsert = {} - if (fullTuneUsed) { + + // Additional car values (for full tune) + let additionalInsert = { + + } + + // Car is fully tuned + if (fullyTuned) { + + // Updated default values + carInsert.level = 8; // C3 + carInsert.tunePower = 17; // 740 HP + carInsert.tuneHandling = 17; // 740 HP + + // Additional full tune values additionalInsert = { stClearBits: 0, stLoseBits: 0, @@ -632,6 +1179,8 @@ export default class GameModule extends Module { stConsecutiveWins: 80 }; } + + // Insert the car into the database let car = await prisma.car.create({ data: { ...carInsert, @@ -697,14 +1246,122 @@ export default class GameModule extends Module { r.send(Buffer.from(end)); }); - app.post('/method/load_game_history', (req, res) => { - let msg = { + app.post('/method/load_game_history', async (req, res) => { + + // Get the request content + let body = wm.wm.protobuf.LoadGameHistoryRequest.decode(req.body); + + // Empty list of time attack records for the player's car + let ta_records : wm.wm.protobuf.LoadGameHistoryResponse.TimeAttackRecord[] = []; + + // Get the car info + let car = await prisma.car.findFirst({ + where: { + carId: body.carId + } + }); + + // Get the car's time attack records + let records = await prisma.timeAttackRecord.findMany({ + where: { + carId: body.carId + } + }); + + console.log(records); + + // Loop over all of the records + for(let record of records) + { + // This code could probably be done with less DB calls in the future + + // Calculate the total rank, total participants for the record + let wholeData = await prisma.timeAttackRecord.findMany({ + where: { + course: record.course + }, + orderBy: { + time: 'asc' + } + }); + + // Get the overall number of participants + let wholeParticipants = wholeData.length; + + // Whole rank (default: 1) + let wholeRank = 1; + + // Loop over all of the participants + for(let row of wholeData) + { + // If the car ID does not match + if (row.carId !== body.carId) + { + // Increment whole rank + wholeRank++; + } + else // Model ID matches + { + // Break the loop + break; + } + } + + // Calculate the model rank, model participants for the record + let modelData = await prisma.timeAttackRecord.findMany({ + where: { + course: record.course, + model: record.model + }, + orderBy: { + time: 'asc' + } + }); + + // Get the overall number of participants (with the same car model) + let modelParticipants = modelData.length; + + // Model rank (default: 1) + let modelRank = 1; + + // Loop over all of the participants + for(let row of modelData) + { + // If the car ID does not match + if (row.carId !== body.carId) + { + // Increment whole rank + modelRank++; + } + else // Model ID matches + { + // Break the loop + break; + } + } + + // Generate the time attack record object and add it to the list + ta_records.push(wm.wm.protobuf.LoadGameHistoryResponse.TimeAttackRecord.create({ + course: record.course, + time: record.time, + tunePower: record.tunePower, + tuneHandling: record.tuneHandling, + wholeParticipants: wholeParticipants, + wholeRank: wholeRank, + modelParticipants: modelParticipants, + modelRank: modelRank + })); + } + + let msg = { error: wm.wm.protobuf.ErrorCode.ERR_SUCCESS, - taRankingUpdatedAt: 0, + taRecords: ta_records, + taRankingUpdatedAt: 1, ghostBattleCount: 0, ghostBattleWinCount: 0, - stampSheetCount: 100, + stampSheetCount: 0, } + let resp = wm.wm.protobuf.LoadGameHistoryResponse.encode(msg); let end = resp.finish(); let r = res