Implement Character Level-up and Ascension (#48)
This commit is contained in:
parent
118df5656d
commit
2fe4f6a2ea
@ -17,8 +17,8 @@ export default async function handle(command: Command) {
|
||||
|
||||
let count: number = 1;
|
||||
let level: number = 1;
|
||||
let rank: number = 1;
|
||||
let promotion: number = 1;
|
||||
let rank: number = 0;
|
||||
let promotion: number = 0;
|
||||
|
||||
for (let i = 2; i < command.args.length; i++) {
|
||||
const arg = command.args[i];
|
||||
@ -52,6 +52,9 @@ export default async function handle(command: Command) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync session.
|
||||
await player.session.sync();
|
||||
}
|
||||
|
||||
async function handleGive(player: Player, itemId: number, count:number, level: number, rank: number, promotion: number) {
|
||||
@ -96,9 +99,12 @@ async function handleGiveAll(player: Player) {
|
||||
const inventory = await player.getInventory();
|
||||
|
||||
for (const entry of ItemExcel.all()) {
|
||||
const count = entry.ItemType == "Material" ? 100 : 1;
|
||||
const count =
|
||||
(entry.ItemType == "Material") ? 1000 :
|
||||
(entry.ItemType == "Virtual") ? 10_000_000 :
|
||||
1;
|
||||
await inventory.addItem(entry.ID, count);
|
||||
}
|
||||
|
||||
|
||||
c.log(`All materials added to ${player.uid}`);
|
||||
}
|
@ -49,9 +49,9 @@ export default class Avatar {
|
||||
baseAvatarId: 1001,
|
||||
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
||||
level: 1,
|
||||
exp: 1,
|
||||
promotion: 1,
|
||||
rank: 1,
|
||||
exp: 0,
|
||||
promotion: 0,
|
||||
rank: 0,
|
||||
equipmentUniqueId: 20003,
|
||||
equipRelicList: [],
|
||||
skilltreeList: [],
|
||||
@ -111,9 +111,9 @@ export default class Avatar {
|
||||
baseAvatarId: baseAvatarId,
|
||||
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
||||
level: 1,
|
||||
exp: 1,
|
||||
promotion: 1,
|
||||
rank: 1,
|
||||
exp: 0,
|
||||
promotion: 0,
|
||||
rank: 0,
|
||||
equipmentUniqueId: 20003,
|
||||
equipRelicList: [],
|
||||
skilltreeList: [],
|
||||
@ -144,6 +144,11 @@ export default class Avatar {
|
||||
return res;
|
||||
}
|
||||
|
||||
public async save() {
|
||||
const db = Database.getInstance();
|
||||
await db.update("avatars", { ownerUid: this.player.uid, baseAvatarId: this.db.baseAvatarId }, this.db);
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Get avatar info.
|
||||
********************************************************************************/
|
||||
|
@ -101,13 +101,24 @@ export default class Inventory {
|
||||
}
|
||||
|
||||
switch (itemData.ItemType) {
|
||||
case "Virtual": return 0; // ToDo: Handle virtual items.
|
||||
case "Virtual": return this.getVirtualItemCount(id);
|
||||
case "Material": return this.db.materials[id] ?? 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private getVirtualItemCount(id: number) : number {
|
||||
// ToDo: Figure out which virtual item ID is what.
|
||||
switch (id) {
|
||||
case 2:
|
||||
return this.player.db.basicInfo.scoin;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Add items to the inventory.
|
||||
********************************************************************************/
|
||||
@ -151,6 +162,14 @@ export default class Inventory {
|
||||
*/
|
||||
public async addVirtualItem(id: number, count: number) {
|
||||
// ToDo: Figure out which virtual item ID is what.
|
||||
switch (id) {
|
||||
case 2:
|
||||
this.player.db.basicInfo.scoin += count;
|
||||
break;
|
||||
}
|
||||
|
||||
// Save.
|
||||
this.player.save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,13 +137,13 @@ export default class Player {
|
||||
heroBasicType: HeroBasicType.BoyWarrior,
|
||||
basicInfo: {
|
||||
exp: 0,
|
||||
level: 1,
|
||||
level: 70,
|
||||
hcoin: 0,
|
||||
mcoin: 0,
|
||||
nickname: acc.name,
|
||||
scoin: 0,
|
||||
stamina: 100,
|
||||
worldLevel: 1,
|
||||
stamina: 180,
|
||||
worldLevel: 6,
|
||||
},
|
||||
lineup: {
|
||||
curIndex: 0,
|
||||
|
88
src/server/packets/AvatarExpUpCsReq.ts
Normal file
88
src/server/packets/AvatarExpUpCsReq.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { AvatarExpUpCsReq, AvatarExpUpScRsp } 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";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as AvatarExpUpCsReq;
|
||||
const inventory = await session.player.getInventory();
|
||||
|
||||
// Get the target avatar.
|
||||
const avatarId = body.baseAvatarId;
|
||||
const avatar = await Avatar.loadAvatarForPlayer(session.player, avatarId);
|
||||
const avatarExcelData = AvatarExcel.fromId(avatarId);
|
||||
|
||||
// Determine the next level cap based on the avatar's current promotion.
|
||||
const levelCap = AvatarPromotionExcel.fromId(`${avatarId}:${avatar.db.promotion}`).MaxLevel;
|
||||
|
||||
// Determine the EXP we get from the consumed items.
|
||||
let exp = 0;
|
||||
const costMaterialList = [];
|
||||
for (const item of body.itemCost!.itemList) {
|
||||
// Determine amount of EXP given by that item.
|
||||
// We know that the cost items given in this Req will be `PileItem`s.
|
||||
const expPerItem = AvatarExpItemExcel.fromId(item.pileItem!.itemId).Exp;
|
||||
|
||||
// Add EXP for the number of items consumed.
|
||||
exp += expPerItem * item.pileItem!.itemNum;
|
||||
|
||||
// Add material to cost.
|
||||
costMaterialList.push({ id: item.pileItem!.itemId, count: item.pileItem!.itemNum } as PayItemData);
|
||||
}
|
||||
|
||||
// Determine cost, which is always 10% of EXP, and add to the list of cost materials.
|
||||
const coinCost = exp * 0.1;
|
||||
costMaterialList.push({ id: 2, count: coinCost } as PayItemData);
|
||||
|
||||
// Try consuming materials.
|
||||
const success = await inventory.payItems(costMaterialList);
|
||||
if (!success) {
|
||||
// ToDo: Correct retcode.
|
||||
session.send(AvatarExpUpScRsp, { retcode: 1, returnItemList: [] } as AvatarExpUpScRsp);
|
||||
return;
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
|
||||
// Cost has been paid - now level up.
|
||||
let currentAvatarExp = avatar.db.exp + exp;
|
||||
let nextRequiredExp = ExpTypeExcel.fromId(`${avatarExcelData.ExpGroup}:${avatar.db.level}`).Exp;
|
||||
while (currentAvatarExp >= nextRequiredExp && avatar.db.level < levelCap) {
|
||||
// Increase level.
|
||||
avatar.db.level++;
|
||||
|
||||
// Deduct EXP necessary for this level.
|
||||
currentAvatarExp -= nextRequiredExp;
|
||||
|
||||
// Determine EXP necessary for the next level.
|
||||
nextRequiredExp = ExpTypeExcel.fromId(`${avatarExcelData.ExpGroup}:${avatar.db.level}`).Exp;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
else {
|
||||
avatar.db.exp = currentAvatarExp;
|
||||
}
|
||||
|
||||
// Save.
|
||||
await avatar.save();
|
||||
|
||||
// ToDo: Handle return items.
|
||||
|
||||
// Done. Sync and send response.
|
||||
await session.sync();
|
||||
|
||||
session.send(AvatarExpUpScRsp, {
|
||||
retcode: 0,
|
||||
returnItemList: []
|
||||
} as AvatarExpUpScRsp);
|
||||
}
|
40
src/server/packets/PromoteAvatarCsReq.ts
Normal file
40
src/server/packets/PromoteAvatarCsReq.ts
Normal file
@ -0,0 +1,40 @@
|
||||
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";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as PromoteAvatarCsReq;
|
||||
const inventory = await session.player.getInventory();
|
||||
|
||||
// Get the target avatar.
|
||||
const avatarId = body.baseAvatarId;
|
||||
const avatar = await Avatar.loadAvatarForPlayer(session.player, avatarId);
|
||||
const promotionExcelData = AvatarPromotionExcel.fromId(`${avatarId}:${avatar.db.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(PromoteAvatarScRsp, { retcode: 1 } as PromoteAvatarScRsp);
|
||||
return;
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
|
||||
// Promote the avatar and save.
|
||||
avatar.db.promotion++;
|
||||
await avatar.save();
|
||||
|
||||
// Done. Sync and send response.
|
||||
await session.sync();
|
||||
session.send(PromoteAvatarScRsp, { retcode: 0 } as PromoteAvatarScRsp);
|
||||
}
|
20
src/util/excel/AvatarExpItemExcel.ts
Normal file
20
src/util/excel/AvatarExpItemExcel.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import _AvatarExpItemConfigExcelTable from "../../data/excel/AvatarExpItemConfigExcelTable.json";
|
||||
type AvatarExpItemConfigExcelTableEntry = typeof _AvatarExpItemConfigExcelTable[keyof typeof _AvatarExpItemConfigExcelTable]
|
||||
const AvatarExpItemConfigExcelTable = _AvatarExpItemConfigExcelTable as { [key: string]: AvatarExpItemConfigExcelTableEntry };
|
||||
|
||||
export default class AvatarExpItemExcel {
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static all() : AvatarExpItemConfigExcelTableEntry[] {
|
||||
return Object.values(AvatarExpItemConfigExcelTable);
|
||||
}
|
||||
|
||||
public static fromId(id: number) : AvatarExpItemConfigExcelTableEntry {
|
||||
return AvatarExpItemConfigExcelTable[id];
|
||||
}
|
||||
|
||||
public static fromIds(ids: number[]): AvatarExpItemConfigExcelTableEntry[] {
|
||||
return ids.map(id => AvatarExpItemExcel.fromId(id));
|
||||
}
|
||||
}
|
20
src/util/excel/AvatarPromotionExcel.ts
Normal file
20
src/util/excel/AvatarPromotionExcel.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import _AvatarPromotionExcelTable from "../../data/excel/AvatarPromotionExcelTable.json";
|
||||
type AvatarPromotionExcelTableEntry = typeof _AvatarPromotionExcelTable[keyof typeof _AvatarPromotionExcelTable]
|
||||
const AvatarPromotionExcelTable = _AvatarPromotionExcelTable as { [key: string]: AvatarPromotionExcelTableEntry };
|
||||
|
||||
export default class AvatarPromotionExcel {
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static all() : AvatarPromotionExcelTableEntry[] {
|
||||
return Object.values(AvatarPromotionExcelTable);
|
||||
}
|
||||
|
||||
public static fromId(id: string) : AvatarPromotionExcelTableEntry {
|
||||
return AvatarPromotionExcelTable[id];
|
||||
}
|
||||
|
||||
public static fromIds(ids: string[]): AvatarPromotionExcelTableEntry[] {
|
||||
return ids.map(id => AvatarPromotionExcel.fromId(id));
|
||||
}
|
||||
}
|
20
src/util/excel/ExpTypeExcel.ts
Normal file
20
src/util/excel/ExpTypeExcel.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import _ExpTypeExcelTable from "../../data/excel/ExpTypeExcelTable.json";
|
||||
type ExpTypeExcelTableEntry = typeof _ExpTypeExcelTable[keyof typeof _ExpTypeExcelTable]
|
||||
const ExpTypeExcelTable = _ExpTypeExcelTable as { [key: string]: ExpTypeExcelTableEntry };
|
||||
|
||||
export default class ExpTypeExcel {
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static all() : ExpTypeExcelTableEntry[] {
|
||||
return Object.values(ExpTypeExcelTable);
|
||||
}
|
||||
|
||||
public static fromId(id: string) : ExpTypeExcelTableEntry {
|
||||
return ExpTypeExcelTable[id];
|
||||
}
|
||||
|
||||
public static fromIds(ids: string[]): ExpTypeExcelTableEntry[] {
|
||||
return ids.map(id => ExpTypeExcel.fromId(id));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user