From 9e7ae5bc3fca8ee3d654aeff1c11c33bf0a0dbda Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Fri, 5 Aug 2022 17:28:34 +0200 Subject: [PATCH 1/6] Implement Light Cone Enhancement (#52) * Implement avatar levelup and promotion. * Implement equipment enhancement features. --- src/db/Inventory.ts | 26 ++++- src/server/kcp/Session.ts | 3 - src/server/packets/AvatarExpUpCsReq.ts | 6 +- src/server/packets/ExpUpEquipmentCsReq.ts | 100 ++++++++++++++++++++ src/server/packets/LockEquipmentCsReq.ts | 26 +++++ src/server/packets/PromoteAvatarCsReq.ts | 3 - src/server/packets/PromoteEquipmentCsReq.ts | 36 +++++++ src/server/packets/RankUpEquipmentCsReq.ts | 32 +++++++ src/util/excel/EquipmentExcel.ts | 20 ++++ src/util/excel/EquipmentExpItemExcel.ts | 20 ++++ src/util/excel/EquipmentExpTypeExcel.ts | 20 ++++ src/util/excel/EquipmentPromotionExcel.ts | 20 ++++ 12 files changed, 300 insertions(+), 12 deletions(-) create mode 100644 src/server/packets/ExpUpEquipmentCsReq.ts create mode 100644 src/server/packets/LockEquipmentCsReq.ts create mode 100644 src/server/packets/PromoteEquipmentCsReq.ts create mode 100644 src/server/packets/RankUpEquipmentCsReq.ts create mode 100644 src/util/excel/EquipmentExcel.ts create mode 100644 src/util/excel/EquipmentExpItemExcel.ts create mode 100644 src/util/excel/EquipmentExpTypeExcel.ts create mode 100644 src/util/excel/EquipmentPromotionExcel.ts diff --git a/src/db/Inventory.ts b/src/db/Inventory.ts index 3aa3787..6c80380 100644 --- a/src/db/Inventory.ts +++ b/src/db/Inventory.ts @@ -119,6 +119,15 @@ export default class Inventory { return 0; } + /** + * Fetch the equipment with the given unique ID from the player's inventory. + * @param uniqueId The unique ID of the equipment to fetch. + * @returns The `Equipment` with the given unique ID, or `undefined` if the player does not have that equipment. + */ + public getEquipmentByUid(uniqueId: number) { + return this.db.equipments.filter(e => e.uniqueId == uniqueId)?.[0]; + } + /******************************************************************************** Add items to the inventory. ********************************************************************************/ @@ -376,17 +385,28 @@ export default class Inventory { /******************************************************************************** Player updating. ********************************************************************************/ - private sendMaterialUpdate() { + /** + * Send `PlayerSyncScNotify` for materials. + */ + public sendMaterialUpdate() { this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({ materialList: this.getMaterialList() })); } - private sendEquipmentUpdate() { + + /** + * Send `PlayerSyncScNotify` for equipments. + */ + public sendEquipmentUpdate() { this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({ equipmentList: this.getEquipmentList() })); } - private sendRelicUpdate() { + + /** + * Send `PlayerSyncScNotify` for relics. + */ + public sendRelicUpdate() { this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({ relicList: this.getRelicsList() })); diff --git a/src/server/kcp/Session.ts b/src/server/kcp/Session.ts index f810f8d..ff4164a 100644 --- a/src/server/kcp/Session.ts +++ b/src/server/kcp/Session.ts @@ -80,7 +80,6 @@ export default class Session { public async sync() { const avatars = await Avatar.loadAvatarsForPlayer(this.player); - //const avatars = await Avatar.fromUID(this.player.db._id); const inventory = await this.player.getInventory(); this.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({ @@ -92,8 +91,6 @@ export default class Session { relicList: inventory.getRelicsList(), basicInfo: this.player.db.basicInfo })); - - //this.player.save(); } public async send,>(type: Class, data: UnWrapMessageType) { diff --git a/src/server/packets/AvatarExpUpCsReq.ts b/src/server/packets/AvatarExpUpCsReq.ts index 8bac1b3..6f061d0 100644 --- a/src/server/packets/AvatarExpUpCsReq.ts +++ b/src/server/packets/AvatarExpUpCsReq.ts @@ -65,9 +65,9 @@ export default async function handle(session: Session, packet: Packet) { // Calculate the character's new EXP and any excess EXP. let excessExp = 0; - if (avatar.db.level == levelCap && currentAvatarExp >= nextRequiredExp) { - avatar.db.exp = nextRequiredExp; - excessExp = currentAvatarExp - nextRequiredExp; + if (avatar.db.level == levelCap) { + avatar.db.exp = 0; + excessExp = currentAvatarExp; } else { avatar.db.exp = currentAvatarExp; diff --git a/src/server/packets/ExpUpEquipmentCsReq.ts b/src/server/packets/ExpUpEquipmentCsReq.ts new file mode 100644 index 0000000..786b778 --- /dev/null +++ b/src/server/packets/ExpUpEquipmentCsReq.ts @@ -0,0 +1,100 @@ +import { ExpUpEquipmentCsReq, ExpUpEquipmentScRsp } from "../../data/proto/StarRail"; +import { PayItemData } from "../../db/Inventory"; +import EquipmentExcel from "../../util/excel/EquipmentExcel"; +import EquipmentExpItemExcel from "../../util/excel/EquipmentExpItemExcel"; +import EquipmentExpTypeExcel from "../../util/excel/EquipmentExpTypeExcel"; +import EquipmentPromotionExcel from "../../util/excel/EquipmentPromotionExcel"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; + +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as ExpUpEquipmentCsReq; + const inventory = await session.player.getInventory(); + + // Get the target equipment. + const equipmentId = body.equipmentUniqueId; + const equipment = inventory.getEquipmentByUid(equipmentId); + const equipmentExcelData = EquipmentExcel.fromId(equipment.tid); + + // Determine the next level cap based on the equipment's current promotion. + const levelCap = EquipmentPromotionExcel.fromId(`${equipment.tid}:${equipment.promotion}`).MaxLevel; + + // Determine the EXP we get from the consumed items, and the coins it will cost. + let exp = 0; + let cost = 0; + + const costMaterialList = []; + const costEquipmentList = [] + + for (const item of body.costData!.itemList) { + // Determine amount of EXP given by that item. + // If the consumed item is a PileItem, we can fetch the EXP it gives from the excels. + if (item.pileItem) { + const expItemData = EquipmentExpItemExcel.fromId(item.pileItem.itemId); + exp += expItemData.ExpProvide * item.pileItem.itemNum; + cost += expItemData.CoinCost; + + costMaterialList.push({ id: item.pileItem.itemId, count: item.pileItem.itemNum } as PayItemData); + } + else if (item.equipmentUniqueId) { + const consumedEquipment = inventory.getEquipmentByUid(item.equipmentUniqueId); + const consumedEquipmentExcelData = EquipmentExcel.fromId(consumedEquipment.tid); + + exp += consumedEquipmentExcelData.ExpProvide; + cost += consumedEquipmentExcelData.CoinCost; + + costEquipmentList.push(item.equipmentUniqueId); + } + } + + costMaterialList.push({ id: 2, count: cost } as PayItemData); + + // Try consuming materials. + const success = await inventory.payItems(costMaterialList); + if (!success) { + // ToDo: Correct retcode. + session.send(ExpUpEquipmentScRsp, { retcode: 1, returnItemList: [] } as ExpUpEquipmentScRsp); + return; + } + + for (const id of costEquipmentList) { + await inventory.removeEquipment(id); + } + + // Cost has been paid - now level up. + let currentEquipmentExp = equipment.exp + exp; + let nextRequiredExp = EquipmentExpTypeExcel.fromId(`${equipmentExcelData.ExpType}:${equipment.level}`).Exp; + while (currentEquipmentExp >= nextRequiredExp && equipment.level < levelCap) { + // Increase level. + equipment.level++; + + // Deduct EXP necessary for this level. + currentEquipmentExp -= nextRequiredExp; + + // Determine EXP necessary for the next level. + nextRequiredExp = EquipmentExpTypeExcel.fromId(`${equipmentExcelData.ExpType}:${equipment.level}`).Exp; + } + + // Calculate the equipment's new EXP and any excess EXP. + let excessExp = 0; + if (equipment.level == levelCap) { + equipment.exp = 0; + excessExp = currentEquipmentExp; + } + else { + equipment.exp = currentEquipmentExp; + } + + // Save. + await inventory.save(); + + // ToDo: Handle return items. + + // Done. Sync and send response. + await session.sync(); + + session.send(ExpUpEquipmentScRsp, { + retcode: 0, + returnItemList: [] + } as ExpUpEquipmentScRsp); +} \ No newline at end of file diff --git a/src/server/packets/LockEquipmentCsReq.ts b/src/server/packets/LockEquipmentCsReq.ts new file mode 100644 index 0000000..b280b49 --- /dev/null +++ b/src/server/packets/LockEquipmentCsReq.ts @@ -0,0 +1,26 @@ +import { LockEquipmentCsReq, LockEquipmentScRsp } from "../../data/proto/StarRail"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; + +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as LockEquipmentCsReq; + + // Fetch the equipment in question. + const inventory = await session.player.getInventory(); + const equipment = inventory.getEquipmentByUid(body.equipmentUniqueId); + + if (!equipment) { + session.send(LockEquipmentScRsp, { retcode: 1 } as LockEquipmentScRsp); + } + + // Toggle the equipment's lock. + equipment.isProtected = !equipment.isProtected; + await inventory.save(); + + // Done. Send and sync. + inventory.sendEquipmentUpdate(); + session.send(LockEquipmentScRsp, { + retcode: 0, + equipmentUniqueId: body.equipmentUniqueId + } as LockEquipmentScRsp); +} \ No newline at end of file diff --git a/src/server/packets/PromoteAvatarCsReq.ts b/src/server/packets/PromoteAvatarCsReq.ts index dfb8a30..098bdfa 100644 --- a/src/server/packets/PromoteAvatarCsReq.ts +++ b/src/server/packets/PromoteAvatarCsReq.ts @@ -1,10 +1,7 @@ import { PromoteAvatarCsReq, PromoteAvatarScRsp } from "../../data/proto/StarRail"; import Avatar from "../../db/Avatar"; import { PayItemData } from "../../db/Inventory"; -import AvatarExcel from "../../util/excel/AvatarExcel"; -import AvatarExpItemExcel from "../../util/excel/AvatarExpItemExcel"; import AvatarPromotionExcel from "../../util/excel/AvatarPromotionExcel"; -import ExpTypeExcel from "../../util/excel/ExpTypeExcel"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; diff --git a/src/server/packets/PromoteEquipmentCsReq.ts b/src/server/packets/PromoteEquipmentCsReq.ts new file mode 100644 index 0000000..219c2cb --- /dev/null +++ b/src/server/packets/PromoteEquipmentCsReq.ts @@ -0,0 +1,36 @@ +import { PromoteEquipmentCsReq, PromoteEquipmentScRsp } from "../../data/proto/StarRail"; +import { PayItemData } from "../../db/Inventory"; +import EquipmentPromotionExcel from "../../util/excel/EquipmentPromotionExcel"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; + +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as PromoteEquipmentCsReq; + const inventory = await session.player.getInventory(); + + // Get the target avatar. + const equipmentId = body.equipmentUniqueId; + const equipment = inventory.getEquipmentByUid(equipmentId); + const promotionExcelData = EquipmentPromotionExcel.fromId(`${equipment.tid}:${equipment.promotion}`); + + // Build list of consumed items. We take this from the excel, instead of the Req. + const costMaterialList = promotionExcelData.PromotionCostList.map(c => { return { id: c.ItemID, count: c.ItemNum } as PayItemData }); + + // Try consuming materials. + const success = await inventory.payItems(costMaterialList); + if (!success) { + // ToDo: Correct retcode. + session.send(PromoteEquipmentScRsp, { retcode: 1 } as PromoteEquipmentScRsp); + return; + } + + await inventory.save(); + + // Promote the avatar and save. + equipment.promotion++; + await inventory.save(); + + // Done. Sync and send response. + await session.sync(); + session.send(PromoteEquipmentScRsp, { retcode: 0 } as PromoteEquipmentScRsp); +} \ No newline at end of file diff --git a/src/server/packets/RankUpEquipmentCsReq.ts b/src/server/packets/RankUpEquipmentCsReq.ts new file mode 100644 index 0000000..2a701a0 --- /dev/null +++ b/src/server/packets/RankUpEquipmentCsReq.ts @@ -0,0 +1,32 @@ +import { RankUpEquipmentCsReq, RankUpEquipmentScRsp } from "../../data/proto/StarRail"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; + +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as RankUpEquipmentCsReq; + const inventory = await session.player.getInventory(); + + // Get equipment. + const equipment = inventory.getEquipmentByUid(body.equipmentUniqueId); + + // Check if all sacrificed equipments exist. + for (const id of body.equipmentIdList) { + if (!inventory.getEquipmentByUid(id)) { + session.send(RankUpEquipmentScRsp, { retcode: 1 } as RankUpEquipmentScRsp); + return; + } + } + + // Remove sacrificed equipments. + for (const id of body.equipmentIdList) { + await inventory.removeEquipment(id); + } + + // Increase rank and save. + equipment.rank += body.equipmentIdList.length; + await inventory.save(); + + // Done. Send and sync. + await session.sync(); + session.send(RankUpEquipmentScRsp, { retcode: 0 } as RankUpEquipmentScRsp); +} \ No newline at end of file diff --git a/src/util/excel/EquipmentExcel.ts b/src/util/excel/EquipmentExcel.ts new file mode 100644 index 0000000..5a12acf --- /dev/null +++ b/src/util/excel/EquipmentExcel.ts @@ -0,0 +1,20 @@ +import _EquipmentExcelTable from "../../data/excel/EquipmentExcelTable.json"; +type EquipmentExcelTableEntry = typeof _EquipmentExcelTable[keyof typeof _EquipmentExcelTable] +const EquipmentExcelTable = _EquipmentExcelTable as { [key: string]: EquipmentExcelTableEntry }; + +export default class EquipmentExcel { + private constructor() { + } + + public static all() : EquipmentExcelTableEntry[] { + return Object.values(EquipmentExcelTable); + } + + public static fromId(id: number) : EquipmentExcelTableEntry { + return EquipmentExcelTable[id]; + } + + public static fromIds(ids: number[]): EquipmentExcelTableEntry[] { + return ids.map(id => EquipmentExcel.fromId(id)); + } +} \ No newline at end of file diff --git a/src/util/excel/EquipmentExpItemExcel.ts b/src/util/excel/EquipmentExpItemExcel.ts new file mode 100644 index 0000000..6c01578 --- /dev/null +++ b/src/util/excel/EquipmentExpItemExcel.ts @@ -0,0 +1,20 @@ +import _EquipmentExpItemExcelTable from "../../data/excel/EquipmentExpItemExcelTable.json"; +type EquipmentExpItemExcelTableEntry = typeof _EquipmentExpItemExcelTable[keyof typeof _EquipmentExpItemExcelTable] +const EquipmentExpItemExcelTable = _EquipmentExpItemExcelTable as { [key: string]: EquipmentExpItemExcelTableEntry }; + +export default class EquipmentExpItemExcel { + private constructor() { + } + + public static all() : EquipmentExpItemExcelTableEntry[] { + return Object.values(EquipmentExpItemExcelTable); + } + + public static fromId(id: number) : EquipmentExpItemExcelTableEntry { + return EquipmentExpItemExcelTable[id]; + } + + public static fromIds(ids: number[]): EquipmentExpItemExcelTableEntry[] { + return ids.map(id => EquipmentExpItemExcel.fromId(id)); + } +} \ No newline at end of file diff --git a/src/util/excel/EquipmentExpTypeExcel.ts b/src/util/excel/EquipmentExpTypeExcel.ts new file mode 100644 index 0000000..febea53 --- /dev/null +++ b/src/util/excel/EquipmentExpTypeExcel.ts @@ -0,0 +1,20 @@ +import _EquipmentExpTypeExcelTable from "../../data/excel/EquipmentExpTypeExcelTable.json"; +type EquipmentExpTypeExcelTableEntry = typeof _EquipmentExpTypeExcelTable[keyof typeof _EquipmentExpTypeExcelTable] +const EquipmentExpTypeExcelTable = _EquipmentExpTypeExcelTable as { [key: string]: EquipmentExpTypeExcelTableEntry }; + +export default class EquipmentExpTypeExcel { + private constructor() { + } + + public static all() : EquipmentExpTypeExcelTableEntry[] { + return Object.values(EquipmentExpTypeExcelTable); + } + + public static fromId(id: string) : EquipmentExpTypeExcelTableEntry { + return EquipmentExpTypeExcelTable[id]; + } + + public static fromIds(ids: string[]): EquipmentExpTypeExcelTableEntry[] { + return ids.map(id => EquipmentExpTypeExcel.fromId(id)); + } +} \ No newline at end of file diff --git a/src/util/excel/EquipmentPromotionExcel.ts b/src/util/excel/EquipmentPromotionExcel.ts new file mode 100644 index 0000000..c1500d1 --- /dev/null +++ b/src/util/excel/EquipmentPromotionExcel.ts @@ -0,0 +1,20 @@ +import _EquipmentPromotionExcelTable from "../../data/excel/EquipmentPromotionExcelTable.json"; +type EquipmentPromotionExcelTableEntry = typeof _EquipmentPromotionExcelTable[keyof typeof _EquipmentPromotionExcelTable] +const EquipmentPromotionExcelTable = _EquipmentPromotionExcelTable as { [key: string]: EquipmentPromotionExcelTableEntry }; + +export default class EquipmentPromotionExcel { + private constructor() { + } + + public static all() : EquipmentPromotionExcelTableEntry[] { + return Object.values(EquipmentPromotionExcelTable); + } + + public static fromId(id: string) : EquipmentPromotionExcelTableEntry { + return EquipmentPromotionExcelTable[id]; + } + + public static fromIds(ids: string[]): EquipmentPromotionExcelTableEntry[] { + return ids.map(id => EquipmentPromotionExcel.fromId(id)); + } +} \ No newline at end of file From ac22e03f282d6d59d061324c2d8495ba16f6cd1c Mon Sep 17 00:00:00 2001 From: GanyusRightThigh <109291347+GanyusRightThigh@users.noreply.github.com> Date: Sat, 6 Aug 2022 16:51:08 +0700 Subject: [PATCH 2/6] Update README.md (#58) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9421490..a57967a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # CrepeSR -[Discord](https://discord.gg/sCAC282C) +[Discord](https://discord.gg/crepe) From 27557a0bbde39dd6f9d85ca4a5541fcc6421d97a Mon Sep 17 00:00:00 2001 From: Midrooms <108638658+Midrooms@users.noreply.github.com> Date: Sat, 6 Aug 2022 06:59:48 -0400 Subject: [PATCH 3/6] Don't use vanity invite. (#60) Discord often kills vanity invites for up to 8 hours at a time, even if it shows it's correct in settings. Requires manual update to fix, pretty stupid, so just use this permanent invite. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a57967a..00f0b5e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # CrepeSR -[Discord](https://discord.gg/crepe) +[Discord](https://discord.gg/KA4HqktWYG) From 5e1209deba109368967ac4464150b570dee3c854 Mon Sep 17 00:00:00 2001 From: Dang Hoang Phuc <13364457+phuchptty@users.noreply.github.com> Date: Sun, 7 Aug 2022 02:36:41 +0700 Subject: [PATCH 4/6] Fix & implement some lineup stuff (#53) * fix: add avatar for each lineup * feat: switch lineup --- src/server/packets/JoinLineupCsReq.ts | 18 ++++++++++++------ src/server/packets/SwitchLineupIndexCsReq.ts | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/server/packets/SwitchLineupIndexCsReq.ts diff --git a/src/server/packets/JoinLineupCsReq.ts b/src/server/packets/JoinLineupCsReq.ts index c394b50..7ecdf26 100644 --- a/src/server/packets/JoinLineupCsReq.ts +++ b/src/server/packets/JoinLineupCsReq.ts @@ -1,20 +1,26 @@ -import { AvatarType, JoinLineupCsReq, JoinLineupScRsp, SyncLineupNotify, SyncLineupReason } from "../../data/proto/StarRail"; -import Avatar from "../../db/Avatar"; +import { + JoinLineupCsReq, + JoinLineupScRsp, + SyncLineupNotify, + SyncLineupReason +} from "../../data/proto/StarRail"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; // JoinLineupCsReq { baseAvatarId: 1002, slot: 1 } export default async function handle(session: Session, packet: Packet) { const body = packet.body as JoinLineupCsReq; - session.send(JoinLineupScRsp, { retcode: 0 }); + session.send(JoinLineupScRsp, {retcode: 0}); // Replace avatar in the player's lineup. const slot = body.slot ?? 0; - session.player.db.lineup.lineups[session.player.db.lineup.curIndex].avatarList[slot] = body.baseAvatarId; + const index = body.index ?? 1; + + session.player.db.lineup.lineups[index].avatarList[slot] = body.baseAvatarId; await session.player.save(); session.send(SyncLineupNotify, { - lineup: await session.player.getLineup(), + lineup: await session.player.getLineup(index), reasonList: [SyncLineupReason.SYNC_REASON_NONE] } as SyncLineupNotify); -} \ No newline at end of file +} diff --git a/src/server/packets/SwitchLineupIndexCsReq.ts b/src/server/packets/SwitchLineupIndexCsReq.ts new file mode 100644 index 0000000..2aca878 --- /dev/null +++ b/src/server/packets/SwitchLineupIndexCsReq.ts @@ -0,0 +1,19 @@ +import Session from "../kcp/Session"; +import Packet from "../kcp/Packet"; +import {SwitchLineupIndexCsReq, SwitchLineupIndexScRsp} from "../../data/proto/StarRail"; + +// SwitchLineupIndexCsReq { index: 0 } +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as SwitchLineupIndexCsReq + const index = body.index ?? 0 + + session.send(SwitchLineupIndexScRsp, { + retcode: 0, + index: index + }) + + session.player.db.lineup.curIndex = index + await session.player.save() + + // Todo: figure need to send SyncLineupNotify again ? +} From 22cd1f6ba11e22e702ec380a66068c95b03aa7ba Mon Sep 17 00:00:00 2001 From: GanyusLeftHorn <1244229+GanyusLeftHorn@users.noreply.github.com> Date: Sun, 7 Aug 2022 16:16:40 +0200 Subject: [PATCH 5/6] (Only) Enter Forgotten Hall, Set Game Mode, Clean Up Map/Maze Excels (#56) * Implement avatar levelup and promotion. * Clean up maz and map excels, set the correct game mode based on plane type, make forgotten hall enterable. * Remove non-working spawn command. --- src/commands/scene.ts | 9 ++- src/game/Scene.ts | 6 +- src/game/entities/Actor.ts | 17 +++-- src/server/packets/EnterMazeCsReq.ts | 6 +- src/server/packets/GetCurSceneInfoCsReq.ts | 28 ++++--- src/server/packets/GetMazeMapInfoCsReq.ts | 3 +- src/server/packets/SceneEntityMoveCsReq.ts | 1 + src/server/packets/StartChallengeCsReq.ts | 87 +++++++++++++--------- src/util/excel/MapEntryExcel.ts | 24 ++++++ src/util/excel/MazePlaneExcel.ts | 55 +++++--------- src/util/excel/MonsterExcel.ts | 20 +++++ 11 files changed, 157 insertions(+), 99 deletions(-) create mode 100644 src/util/excel/MapEntryExcel.ts create mode 100644 src/util/excel/MonsterExcel.ts diff --git a/src/commands/scene.ts b/src/commands/scene.ts index 8f95087..919c87d 100644 --- a/src/commands/scene.ts +++ b/src/commands/scene.ts @@ -3,6 +3,7 @@ import { ActorEntity } from "../game/entities/Actor"; import Interface, { Command } from "./Interface"; import { GetCurSceneInfoScRsp } from "../data/proto/StarRail"; import MazePlaneExcel from "../util/excel/MazePlaneExcel"; +import MapEntryExcel from "../util/excel/MapEntryExcel"; const c = new Logger("/scene", "blue"); export default async function handle(command: Command) { @@ -11,8 +12,8 @@ export default async function handle(command: Command) { return; } - const planeID = MazePlaneExcel.fromPlaneId(parseInt(command.args[0])) - const uid = Interface.target.player.db._id; + const planeID = MazePlaneExcel.fromPlaneId(parseInt(command.args[0])); + const entryId = MapEntryExcel.fromFloorId(planeID.StartFloorID).ID; const posData = Interface.target.player.db.posData; const lineup2 = await Interface.target.player.getLineup(); @@ -35,9 +36,9 @@ export default async function handle(command: Command) { curAvatarEntity ], entityBuffList: [], - entryId: 10001, + entryId: entryId, envBuffList: [], - gameModeType: 1, + gameModeType: MazePlaneExcel.getGameModeForPlaneType(planeID.PlaneType), lightenSectionList: [] }, } as unknown as GetCurSceneInfoScRsp); diff --git a/src/game/Scene.ts b/src/game/Scene.ts index 2162b90..f04c6e7 100644 --- a/src/game/Scene.ts +++ b/src/game/Scene.ts @@ -13,9 +13,11 @@ export class Scene { public spawnEntity(entity: Entity, silent: boolean = false) { this.entities.set(entity.entityId, entity); if (!silent) { - this.player.session.send(SceneEntityUpdateScNotify, { + const dataObj : SceneEntityUpdateScNotify = { entityList: [entity.getSceneEntityInfo()] - } as SceneEntityUpdateScNotify); + }; + + this.player.session.send(SceneEntityUpdateScNotify, dataObj); } } diff --git a/src/game/entities/Actor.ts b/src/game/entities/Actor.ts index ccd37e5..5c98bc3 100644 --- a/src/game/entities/Actor.ts +++ b/src/game/entities/Actor.ts @@ -10,15 +10,16 @@ export class ActorEntity extends Entity super(scene, pos, rot); } - public getSceneEntityInfo(): SceneEntityInfo { - const sceneEntityInfo = super.getSceneEntityInfo(); - sceneEntityInfo.actor = { - avatarType: AvatarType.AVATAR_FORMAL_TYPE, - baseAvatarId: this.avatarId, - uid: this.scene.player.db._id, - mapLayer: 0 //? + public getSceneActorInfo(): SceneEntityInfo { + return { + ...super.getSceneEntityInfo(), + actor: { + avatarType: AvatarType.AVATAR_FORMAL_TYPE, + baseAvatarId: this.avatarId, + uid: this.scene.player.db._id, + mapLayer: 0 //? + } }; - return sceneEntityInfo; } public getEntityType(): EntityType { diff --git a/src/server/packets/EnterMazeCsReq.ts b/src/server/packets/EnterMazeCsReq.ts index fba4ff6..ebf73d9 100644 --- a/src/server/packets/EnterMazeCsReq.ts +++ b/src/server/packets/EnterMazeCsReq.ts @@ -1,4 +1,5 @@ import { EnterMazeCsReq, EnterMazeScRsp } from "../../data/proto/StarRail"; +import MapEntryExcel from "../../util/excel/MapEntryExcel"; import MazePlaneExcel from "../../util/excel/MazePlaneExcel"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; @@ -6,7 +7,8 @@ import Session from "../kcp/Session"; export default async function handle(session: Session, packet: Packet) { const body = packet.body as EnterMazeCsReq; - const mazeEntry = MazePlaneExcel.getEntry(body.entryId); + const mazeEntry = MapEntryExcel.fromId(body.entryId); + const mazePlane = MazePlaneExcel.fromPlaneId(mazeEntry.PlaneID); let curLineup = await session.player.getLineup(); curLineup.planeId = mazeEntry.PlaneID; @@ -23,7 +25,7 @@ export default async function handle(session: Session, packet: Packet) { scene: { planeId: mazeEntry.PlaneID, floorId: mazeEntry.FloorID, - gameModeType: 1, + gameModeType: MazePlaneExcel.getGameModeForPlaneType(mazePlane.PlaneType), } }, id: mazeEntry.PlaneID, diff --git a/src/server/packets/GetCurSceneInfoCsReq.ts b/src/server/packets/GetCurSceneInfoCsReq.ts index 87cf150..1de7db7 100644 --- a/src/server/packets/GetCurSceneInfoCsReq.ts +++ b/src/server/packets/GetCurSceneInfoCsReq.ts @@ -1,28 +1,38 @@ -import { GetCurSceneInfoScRsp, Vector } from "../../data/proto/StarRail"; +import { GetCurSceneInfoScRsp, MotionInfo, SceneEntityInfo, SceneNpcMonsterInfo, Vector } from "../../data/proto/StarRail"; import { ActorEntity } from "../../game/entities/Actor"; +import MapEntryExcel from "../../util/excel/MapEntryExcel"; +import MazePlaneExcel from "../../util/excel/MazePlaneExcel"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; export default async function handle(session: Session, packet: Packet) { + // Get data. const posData = session.player.db.posData; const _lineup = session.player.db.lineup; const lineup = _lineup.lineups[_lineup.curIndex]; - const curAvatarEntity = new ActorEntity(session.player.scene, lineup.avatarList[0], posData.pos); + const curAvatarEntity = new ActorEntity(session.player.scene, lineup.avatarList[lineup.leaderSlot], posData.pos); + const entryId = MapEntryExcel.fromFloorId(posData.floorID).ID; + const mazePlane = MazePlaneExcel.fromPlaneId(posData.planeID); + + // Scene management. + session.player.scene.spawnEntity(curAvatarEntity, true); + session.player.scene.entryId = entryId; + + // Send response. session.send(GetCurSceneInfoScRsp, { retcode: 0, scene: { planeId: posData.planeID, floorId: posData.floorID, entityList: [ - curAvatarEntity + curAvatarEntity.getSceneEntityInfo() ], + lightenSectionList: [], + leaderEntityId: curAvatarEntity.entityId, entityBuffList: [], - entryId: 10001, + entryId: entryId, envBuffList: [], - gameModeType: 1, - lightenSectionList: [] + gameModeType: MazePlaneExcel.getGameModeForPlaneType(mazePlane.PlaneType), }, - } as unknown as GetCurSceneInfoScRsp); - session.player.scene.spawnEntity(curAvatarEntity, true); - session.player.scene.entryId = 10001; + } as GetCurSceneInfoScRsp); } \ No newline at end of file diff --git a/src/server/packets/GetMazeMapInfoCsReq.ts b/src/server/packets/GetMazeMapInfoCsReq.ts index c81b883..b97116b 100644 --- a/src/server/packets/GetMazeMapInfoCsReq.ts +++ b/src/server/packets/GetMazeMapInfoCsReq.ts @@ -1,4 +1,5 @@ import { GetMazeMapInfoCsReq, GetMazeMapInfoScRsp } from "../../data/proto/StarRail"; +import MapEntryExcel from "../../util/excel/MapEntryExcel"; import MappingInfoExcel from "../../util/excel/MappingInfoExcel"; import MazePlaneExcel from "../../util/excel/MazePlaneExcel"; import Packet from "../kcp/Packet"; @@ -24,7 +25,7 @@ export default async function handle(session: Session, packet: Packet) { dataObj.lightenSectionList.push(i) } - dataObj.unlockTeleportList = MazePlaneExcel.getAllEntries().map(x => x.ID); + dataObj.unlockTeleportList = MapEntryExcel.all().map(x => x.ID); session.send(GetMazeMapInfoScRsp, dataObj); } \ No newline at end of file diff --git a/src/server/packets/SceneEntityMoveCsReq.ts b/src/server/packets/SceneEntityMoveCsReq.ts index c222884..e229e5a 100644 --- a/src/server/packets/SceneEntityMoveCsReq.ts +++ b/src/server/packets/SceneEntityMoveCsReq.ts @@ -5,6 +5,7 @@ import Session from "../kcp/Session"; export default async function handle(session: Session, packet: Packet) { const body = packet.body as SceneEntityMoveCsReq; + if (session.player.scene.entryId !== body.entryId) { return; } diff --git a/src/server/packets/StartChallengeCsReq.ts b/src/server/packets/StartChallengeCsReq.ts index 5b7bb92..d3f757e 100644 --- a/src/server/packets/StartChallengeCsReq.ts +++ b/src/server/packets/StartChallengeCsReq.ts @@ -1,59 +1,76 @@ -import { ChallengeStatus, ExtraLineupType, StartChallengeCsReq, StartChallengeScRsp } from "../../data/proto/StarRail"; +import { ChallengeStatus, CurChallenge, ExtraLineupType, Maze, SceneActorInfo, SceneEntityInfo, SceneInfo, SceneNpcInfo, SceneNpcMonsterInfo, ScenePropInfo, StartChallengeCsReq, StartChallengeScRsp } from "../../data/proto/StarRail"; +import { ActorEntity } from "../../game/entities/Actor"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; // StartChallengeCsReq { challengeId: 101 } export default async function handle(session: Session, packet: Packet) { const body = packet.body as StartChallengeCsReq; + console.log(JSON.stringify(body, undefined, 4)); // TODO: This packet is just a base + const _lineup = session.player.db.lineup; + const lineup = _lineup.lineups[_lineup.curIndex]; + const curAvatarEntity = new ActorEntity(session.player.scene, lineup.avatarList[lineup.leaderSlot], { x: 0, y: 0, z: 0 }); session.send(StartChallengeScRsp, { retcode: 0, curChallenge: { challengeId: body.challengeId, - deadAvatarNum: 0, - extraLineupType: ExtraLineupType.LINEUP_CHALLENGE, rounds: 1, status: ChallengeStatus.CHALLENGE_DOING, - killMonsterList: [{ - monsterId: 8013010, - killNum: 1, - }] - }, + extraLineupType: ExtraLineupType.LINEUP_NONE, + killMonsterList: [], + deadAvatarNum: 0, + } as CurChallenge, maze: { // ? Data from MappingInfoExcelTable - id: 30101, - mapEntryId: 10001, + id: 30104, + mapEntryId: 3000401, floor: { floorId: 20121001, scene: { - planeId: 20121, - entryId: 1000, - floorId: 20121001, - gameModeType: 1, - entityList: [{ - entityId: 10010101, - npcMonster: { - monsterId: 8013010, - worldLevel: 1, - }, - groupId: 11, - motion: { - pos: { - x: 0, - y: 100, - z: 0, + planeId: 30104, + entryId: 3000401, + floorId: 30104001, + lightenSectionList: [], + gameModeType: 4, + entityList: [ + curAvatarEntity.getSceneEntityInfo(), + { + entityId: 10000, + motion: { + pos: { + x: 74719, + y: 2014, + z: -94205, + }, + rot: { + x: 0, + y: 0, + z: 0 + } }, - rot: { - x: 0, - y: 0, - z: 0 - } - }, - }] - } + groupId: 3, + instId: 1, + actor: {} as SceneActorInfo, + npc: {} as SceneNpcInfo, + prop: {} as ScenePropInfo, + npcMonster: { + monsterId: 1003020, + isGenMonster: false, + eventId: 0, + isSetWorldLevel: false, + worldLevel: 6 + } as SceneNpcMonsterInfo + } as SceneEntityInfo, + ], + leaderEntityId: curAvatarEntity.entityId, + entityBuffList: [], + envBuffList: [], + snar: "" + } as SceneInfo } - } + } as Maze } as StartChallengeScRsp); } \ No newline at end of file diff --git a/src/util/excel/MapEntryExcel.ts b/src/util/excel/MapEntryExcel.ts new file mode 100644 index 0000000..3317565 --- /dev/null +++ b/src/util/excel/MapEntryExcel.ts @@ -0,0 +1,24 @@ +import _MapEntryExcelTable from "../../data/excel/MapEntryExcelTable.json"; +type MapEntryExcelTableEntry = typeof _MapEntryExcelTable[keyof typeof _MapEntryExcelTable] +const MapEntryExcelTable = _MapEntryExcelTable as { [key: string]: MapEntryExcelTableEntry }; + +export default class MapEntryExcel { + private constructor() { + } + + public static all() : MapEntryExcelTableEntry[] { + return Object.values(MapEntryExcelTable); + } + + public static fromId(id: number) : MapEntryExcelTableEntry { + return MapEntryExcelTable[id]; + } + + public static fromIds(ids: number[]): MapEntryExcelTableEntry[] { + return ids.map(id => MapEntryExcel.fromId(id)); + } + + public static fromFloorId(id: number) : MapEntryExcelTableEntry { + return Object.values(MapEntryExcelTable).filter(e => e.FloorID == id)?.[0]; + } +} \ No newline at end of file diff --git a/src/util/excel/MazePlaneExcel.ts b/src/util/excel/MazePlaneExcel.ts index 03d3bd6..392c26a 100644 --- a/src/util/excel/MazePlaneExcel.ts +++ b/src/util/excel/MazePlaneExcel.ts @@ -1,5 +1,5 @@ -import _MapEntryExcelTable from "../../data/excel/MapEntryExcelTable.json"; import _MazePlaneExcelTable from "../../data/excel/MazePlaneExcelTable.json"; +import MapEntryExcel from "./MapEntryExcel"; interface MazePlaneExcelTableEntry { PlaneID: number; @@ -11,44 +11,13 @@ interface MazePlaneExcelTableEntry { FloorIDList: number[]; } -interface TextMap { - hash: number; -} - -type EntranceType = "Town" | "Mission" | "Explore"; - -interface MapEntryExcelTableEntry { - ID: number; - IsShowInMapMenu: boolean; - MapMenuSortID: number; - EntranceType: EntranceType | number; // Actually an enum. Town | Mission | Explore - EntranceGroupID: number; - Name: TextMap; - Desc: TextMap; - EntranceListIcon: string; - ImagePath: string; - MiniMapIconHintList: any[]; - ShowReward: number; - PlaneID: number; - FloorID: number; - StartGroupID: number; - StartAnchorID: number; - TargetMission: number; - TargetMainMissionList: number[]; - BeginMainMissionList: number[]; - FinishMainMissionList: number[]; - FinishQuestList: number[]; - UnlockQuest: number; -} - const MazePlaneExcelTable = _MazePlaneExcelTable as { [key: string]: MazePlaneExcelTableEntry }; -const MapEntryExcelTable = _MapEntryExcelTable as { [key: string]: MapEntryExcelTableEntry }; export default class MazePlaneExcel { private constructor() { } public static fromEntryId(entryId: number): MazePlaneExcelTableEntry { - const mapEntry = MapEntryExcelTable[entryId.toString()]; + const mapEntry = MapEntryExcel.fromId(entryId); return MazePlaneExcelTable[mapEntry.PlaneID.toString()]; } @@ -56,11 +25,21 @@ export default class MazePlaneExcel { return MazePlaneExcelTable[planeId.toString()]; } - public static getEntry(entryId: number): MapEntryExcelTableEntry { - return MapEntryExcelTable[entryId.toString()]; - } + public static getGameModeForPlaneType(planeType: string): number { + switch (planeType) { + case "Town": return 1; + case "Maze": return 2; + case "Train": return 3; + case "Challenge": return 4; + case "RogueExplore": return 5; + case "RogueChallenge": return 6; + case "TownRoom": return 7; + case "Raid": return 8; + case "FarmRelic": return 9; + case "Client": return 10; + case "ChallengeActivity": return 11; + } - public static getAllEntries(): MapEntryExcelTableEntry[] { - return Object.values(MapEntryExcelTable); + return 0; } } \ No newline at end of file diff --git a/src/util/excel/MonsterExcel.ts b/src/util/excel/MonsterExcel.ts new file mode 100644 index 0000000..ae33d17 --- /dev/null +++ b/src/util/excel/MonsterExcel.ts @@ -0,0 +1,20 @@ +import _MonsterExcelTable from "../../data/excel/MonsterExcelTable.json"; +type MonsterExcelTableEntry = typeof _MonsterExcelTable[keyof typeof _MonsterExcelTable] +const MonsterExcelTable = _MonsterExcelTable as { [key: string]: MonsterExcelTableEntry }; + +export default class MonsterExcel { + private constructor() { + } + + public static all() : MonsterExcelTableEntry[] { + return Object.values(MonsterExcelTable); + } + + public static fromId(id: number) : MonsterExcelTableEntry { + return MonsterExcelTable[id]; + } + + public static fromIds(ids: number[]): MonsterExcelTableEntry[] { + return ids.map(id => MonsterExcel.fromId(id)); + } +} \ No newline at end of file From 794042d65f1fec1cb44791a57b16f74765982828 Mon Sep 17 00:00:00 2001 From: memetrollsXD Date: Sun, 7 Aug 2022 18:57:46 +0200 Subject: [PATCH 6/6] Implement LeaveChallengeCsReq (#63) * Implement LeaveChallengeCsReq * CodeFactor LeaveChallengeCsReq This commit resolves the "no-unsafe-finally" check --- src/server/packets/LeaveChallengeCsReq.ts | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/server/packets/LeaveChallengeCsReq.ts diff --git a/src/server/packets/LeaveChallengeCsReq.ts b/src/server/packets/LeaveChallengeCsReq.ts new file mode 100644 index 0000000..48333a2 --- /dev/null +++ b/src/server/packets/LeaveChallengeCsReq.ts @@ -0,0 +1,70 @@ +import { GetCurSceneInfoScRsp, LeaveChallengeScRsp } from "../../data/proto/StarRail"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; +import MapEntryExcel from "../../util/excel/MapEntryExcel"; +import MazePlaneExcel from "../../util/excel/MazePlaneExcel"; +import { ActorEntity } from "../../game/entities/Actor"; + +export default async function handle(session: Session, packet: Packet) { + const bfArray: number[] = []; + for (let i = 0; i < 500; i++) { + bfArray.push(i); + } + + try { + // Challenge maze data doesn't get saved to DB.. We can abuse this and fall back to default + const posData = session.player.db.posData; + const entry = MapEntryExcel.fromFloorId(posData.floorID); + const maze = MazePlaneExcel.fromPlaneId(posData.planeID); + if (!entry || !maze) return session.send(LeaveChallengeScRsp, LeaveChallengeScRsp.fromPartial({ retcode: 2 })); + + const dataObj = LeaveChallengeScRsp.fromPartial({ + retcode: 0, + maze: { + floor: { + floorId: posData.floorID, + scene: { + entityList: [], + entryId: entry.ID, + floorId: entry.FloorID, + planeId: entry.PlaneID, + gameModeType: MazePlaneExcel.getGameModeForPlaneType(maze.PlaneType), + lightenSectionList: bfArray, + entityBuffList: [], + envBuffList: [], + } + }, + id: posData.planeID, + mapEntryId: entry.ID + } + }); + + session.send(LeaveChallengeScRsp, dataObj); + } catch (e) { + session.c.error(e as Error); + session.send(LeaveChallengeScRsp, LeaveChallengeScRsp.fromPartial({ retcode: 2 })); + } finally { + // Force us back + const posData = session.player.db.posData; + const entry = MapEntryExcel.fromFloorId(posData.floorID); + const maze = MazePlaneExcel.fromPlaneId(posData.planeID); + if (entry && maze) { + const actor = new ActorEntity(session.player.scene, session.player.db.lineup.lineups[session.player.db.lineup.curIndex].avatarList[0], posData.pos).getSceneActorInfo(); + + session.send(GetCurSceneInfoScRsp, { + retcode: 0, + scene: { + planeId: posData.planeID, + floorId: posData.floorID, + lightenSectionList: bfArray, + gameModeType: MazePlaneExcel.getGameModeForPlaneType(maze.PlaneType), + entityBuffList: [], + envBuffList: [], + entryId: entry.ID, + entityList: [actor], + leaderEntityId: actor.entityId + } + }); + } + } +} \ No newline at end of file