Implement Character Level-up and Ascension (#48)

This commit is contained in:
GanyusLeftHorn 2022-08-05 05:28:46 +02:00 committed by GitHub
parent 118df5656d
commit 2fe4f6a2ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 14 deletions

View File

@ -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,7 +99,10 @@ 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);
}

View File

@ -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.
********************************************************************************/

View File

@ -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();
}
/**

View File

@ -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,

View 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);
}

View 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);
}

View 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));
}
}

View 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));
}
}

View 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));
}
}