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 count: number = 1;
|
||||||
let level: number = 1;
|
let level: number = 1;
|
||||||
let rank: number = 1;
|
let rank: number = 0;
|
||||||
let promotion: number = 1;
|
let promotion: number = 0;
|
||||||
|
|
||||||
for (let i = 2; i < command.args.length; i++) {
|
for (let i = 2; i < command.args.length; i++) {
|
||||||
const arg = command.args[i];
|
const arg = command.args[i];
|
||||||
@ -52,6 +52,9 @@ export default async function handle(command: Command) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync session.
|
||||||
|
await player.session.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleGive(player: Player, itemId: number, count:number, level: number, rank: number, promotion: number) {
|
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();
|
const inventory = await player.getInventory();
|
||||||
|
|
||||||
for (const entry of ItemExcel.all()) {
|
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);
|
await inventory.addItem(entry.ID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.log(`All materials added to ${player.uid}`);
|
c.log(`All materials added to ${player.uid}`);
|
||||||
}
|
}
|
@ -49,9 +49,9 @@ export default class Avatar {
|
|||||||
baseAvatarId: 1001,
|
baseAvatarId: 1001,
|
||||||
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
||||||
level: 1,
|
level: 1,
|
||||||
exp: 1,
|
exp: 0,
|
||||||
promotion: 1,
|
promotion: 0,
|
||||||
rank: 1,
|
rank: 0,
|
||||||
equipmentUniqueId: 20003,
|
equipmentUniqueId: 20003,
|
||||||
equipRelicList: [],
|
equipRelicList: [],
|
||||||
skilltreeList: [],
|
skilltreeList: [],
|
||||||
@ -111,9 +111,9 @@ export default class Avatar {
|
|||||||
baseAvatarId: baseAvatarId,
|
baseAvatarId: baseAvatarId,
|
||||||
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
avatarType: AvatarType.AVATAR_FORMAL_TYPE,
|
||||||
level: 1,
|
level: 1,
|
||||||
exp: 1,
|
exp: 0,
|
||||||
promotion: 1,
|
promotion: 0,
|
||||||
rank: 1,
|
rank: 0,
|
||||||
equipmentUniqueId: 20003,
|
equipmentUniqueId: 20003,
|
||||||
equipRelicList: [],
|
equipRelicList: [],
|
||||||
skilltreeList: [],
|
skilltreeList: [],
|
||||||
@ -144,6 +144,11 @@ export default class Avatar {
|
|||||||
return res;
|
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.
|
Get avatar info.
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
@ -101,13 +101,24 @@ export default class Inventory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (itemData.ItemType) {
|
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;
|
case "Material": return this.db.materials[id] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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.
|
Add items to the inventory.
|
||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
@ -151,6 +162,14 @@ export default class Inventory {
|
|||||||
*/
|
*/
|
||||||
public async addVirtualItem(id: number, count: number) {
|
public async addVirtualItem(id: number, count: number) {
|
||||||
// ToDo: Figure out which virtual item ID is what.
|
// 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,
|
heroBasicType: HeroBasicType.BoyWarrior,
|
||||||
basicInfo: {
|
basicInfo: {
|
||||||
exp: 0,
|
exp: 0,
|
||||||
level: 1,
|
level: 70,
|
||||||
hcoin: 0,
|
hcoin: 0,
|
||||||
mcoin: 0,
|
mcoin: 0,
|
||||||
nickname: acc.name,
|
nickname: acc.name,
|
||||||
scoin: 0,
|
scoin: 0,
|
||||||
stamina: 100,
|
stamina: 180,
|
||||||
worldLevel: 1,
|
worldLevel: 6,
|
||||||
},
|
},
|
||||||
lineup: {
|
lineup: {
|
||||||
curIndex: 0,
|
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