Implement Light Cone Enhancement (#52)
* Implement avatar levelup and promotion. * Implement equipment enhancement features.
This commit is contained in:
parent
c59789180b
commit
9e7ae5bc3f
@ -119,6 +119,15 @@ export default class Inventory {
|
|||||||
return 0;
|
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.
|
Add items to the inventory.
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
@ -376,17 +385,28 @@ export default class Inventory {
|
|||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
Player updating.
|
Player updating.
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
private sendMaterialUpdate() {
|
/**
|
||||||
|
* Send `PlayerSyncScNotify` for materials.
|
||||||
|
*/
|
||||||
|
public sendMaterialUpdate() {
|
||||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||||
materialList: this.getMaterialList()
|
materialList: this.getMaterialList()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
private sendEquipmentUpdate() {
|
|
||||||
|
/**
|
||||||
|
* Send `PlayerSyncScNotify` for equipments.
|
||||||
|
*/
|
||||||
|
public sendEquipmentUpdate() {
|
||||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||||
equipmentList: this.getEquipmentList()
|
equipmentList: this.getEquipmentList()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
private sendRelicUpdate() {
|
|
||||||
|
/**
|
||||||
|
* Send `PlayerSyncScNotify` for relics.
|
||||||
|
*/
|
||||||
|
public sendRelicUpdate() {
|
||||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||||
relicList: this.getRelicsList()
|
relicList: this.getRelicsList()
|
||||||
}));
|
}));
|
||||||
|
@ -80,7 +80,6 @@ export default class Session {
|
|||||||
|
|
||||||
public async sync() {
|
public async sync() {
|
||||||
const avatars = await Avatar.loadAvatarsForPlayer(this.player);
|
const avatars = await Avatar.loadAvatarsForPlayer(this.player);
|
||||||
//const avatars = await Avatar.fromUID(this.player.db._id);
|
|
||||||
const inventory = await this.player.getInventory();
|
const inventory = await this.player.getInventory();
|
||||||
|
|
||||||
this.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
this.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||||
@ -92,8 +91,6 @@ export default class Session {
|
|||||||
relicList: inventory.getRelicsList(),
|
relicList: inventory.getRelicsList(),
|
||||||
basicInfo: this.player.db.basicInfo
|
basicInfo: this.player.db.basicInfo
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//this.player.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async send<Class extends MessageType<any>,>(type: Class, data: UnWrapMessageType<Class>) {
|
public async send<Class extends MessageType<any>,>(type: Class, data: UnWrapMessageType<Class>) {
|
||||||
|
@ -65,9 +65,9 @@ export default async function handle(session: Session, packet: Packet) {
|
|||||||
|
|
||||||
// Calculate the character's new EXP and any excess EXP.
|
// Calculate the character's new EXP and any excess EXP.
|
||||||
let excessExp = 0;
|
let excessExp = 0;
|
||||||
if (avatar.db.level == levelCap && currentAvatarExp >= nextRequiredExp) {
|
if (avatar.db.level == levelCap) {
|
||||||
avatar.db.exp = nextRequiredExp;
|
avatar.db.exp = 0;
|
||||||
excessExp = currentAvatarExp - nextRequiredExp;
|
excessExp = currentAvatarExp;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
avatar.db.exp = currentAvatarExp;
|
avatar.db.exp = currentAvatarExp;
|
||||||
|
100
src/server/packets/ExpUpEquipmentCsReq.ts
Normal file
100
src/server/packets/ExpUpEquipmentCsReq.ts
Normal file
@ -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);
|
||||||
|
}
|
26
src/server/packets/LockEquipmentCsReq.ts
Normal file
26
src/server/packets/LockEquipmentCsReq.ts
Normal file
@ -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);
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
import { PromoteAvatarCsReq, PromoteAvatarScRsp } from "../../data/proto/StarRail";
|
import { PromoteAvatarCsReq, PromoteAvatarScRsp } from "../../data/proto/StarRail";
|
||||||
import Avatar from "../../db/Avatar";
|
import Avatar from "../../db/Avatar";
|
||||||
import { PayItemData } from "../../db/Inventory";
|
import { PayItemData } from "../../db/Inventory";
|
||||||
import AvatarExcel from "../../util/excel/AvatarExcel";
|
|
||||||
import AvatarExpItemExcel from "../../util/excel/AvatarExpItemExcel";
|
|
||||||
import AvatarPromotionExcel from "../../util/excel/AvatarPromotionExcel";
|
import AvatarPromotionExcel from "../../util/excel/AvatarPromotionExcel";
|
||||||
import ExpTypeExcel from "../../util/excel/ExpTypeExcel";
|
|
||||||
import Packet from "../kcp/Packet";
|
import Packet from "../kcp/Packet";
|
||||||
import Session from "../kcp/Session";
|
import Session from "../kcp/Session";
|
||||||
|
|
||||||
|
36
src/server/packets/PromoteEquipmentCsReq.ts
Normal file
36
src/server/packets/PromoteEquipmentCsReq.ts
Normal file
@ -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);
|
||||||
|
}
|
32
src/server/packets/RankUpEquipmentCsReq.ts
Normal file
32
src/server/packets/RankUpEquipmentCsReq.ts
Normal file
@ -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);
|
||||||
|
}
|
20
src/util/excel/EquipmentExcel.ts
Normal file
20
src/util/excel/EquipmentExcel.ts
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
20
src/util/excel/EquipmentExpItemExcel.ts
Normal file
20
src/util/excel/EquipmentExpItemExcel.ts
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
20
src/util/excel/EquipmentExpTypeExcel.ts
Normal file
20
src/util/excel/EquipmentExpTypeExcel.ts
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
20
src/util/excel/EquipmentPromotionExcel.ts
Normal file
20
src/util/excel/EquipmentPromotionExcel.ts
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user