Basic Inventory Management, /give
Command, Gacha Cost (#29)
* Basic inventory management, materials. * Add await to player save. * Adding equipment and relics. * Added method for paying items, add payment to gacha. * Remove database log. * Simplyfy give command
This commit is contained in:
parent
e24ff7d25d
commit
719b3200be
104
src/commands/item.ts
Normal file
104
src/commands/item.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Equipment } from "../data/proto/StarRail";
|
||||
import Player from "../db/Player";
|
||||
import ItemExcel from "../util/excel/ItemExcel";
|
||||
import Logger from "../util/Logger";
|
||||
import Interface, { Command } from "./Interface";
|
||||
const c = new Logger("/item", "blue");
|
||||
|
||||
export default async function handle(command: Command) {
|
||||
if (!Interface.target) {
|
||||
c.log("No target specified");
|
||||
return;
|
||||
}
|
||||
|
||||
const player = Interface.target.player;
|
||||
const actionType = command.args[0];
|
||||
const itemId = Number(command.args[1]);
|
||||
|
||||
let count: number = 1;
|
||||
let level: number = 1;
|
||||
let rank: number = 1;
|
||||
let promotion: number = 1;
|
||||
|
||||
for (let i = 2; i < command.args.length; i++) {
|
||||
const arg = command.args[i];
|
||||
const number = Number(command.args[i].substring(1));
|
||||
|
||||
if (arg.startsWith("x")) {
|
||||
count = number;
|
||||
}
|
||||
else if (arg.startsWith("l")) {
|
||||
level = number;
|
||||
}
|
||||
else if (arg.startsWith("r")) {
|
||||
rank = number;
|
||||
}
|
||||
else if (arg.startsWith("p")) {
|
||||
promotion = number;
|
||||
}
|
||||
}
|
||||
|
||||
switch (actionType) {
|
||||
case "give": {
|
||||
await handleGive(player, itemId, count, level, rank, promotion);
|
||||
break;
|
||||
}
|
||||
case "giveall": {
|
||||
await handleGiveAll(player);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
c.log(`Usage: /item <give|giveall> <itemId> [x<count>|l<level>|r<rank>|p<promotion>]*`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGive(player: Player, itemId: number, count:number, level: number, rank: number, promotion: number) {
|
||||
if (!itemId) {
|
||||
return c.log("No avatarId specified");
|
||||
}
|
||||
|
||||
// Check if this item exists.
|
||||
const itemData = ItemExcel.fromId(itemId);
|
||||
if (!itemData) {
|
||||
return c.log(`Item ID ${itemId} does not exist.`);
|
||||
}
|
||||
|
||||
const inventory = await player.getInventory();
|
||||
switch (itemData.ItemType) {
|
||||
case "Material":
|
||||
await inventory.addMaterial(itemId, count);
|
||||
break;
|
||||
case "Equipment":
|
||||
for (let i = 0; i < count; i++) {
|
||||
await inventory.addEquipment({
|
||||
tid: itemId,
|
||||
uniqueId: 0,
|
||||
level: level,
|
||||
rank: rank,
|
||||
exp: 1,
|
||||
isProtected: false,
|
||||
promotion: promotion,
|
||||
baseAvatarId: 0
|
||||
} as Equipment);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return c.log(`Unsupported item type: ${itemData.ItemType}.`);
|
||||
break;
|
||||
}
|
||||
|
||||
c.log(`Added ${count} of item ${itemId} to player ${player.uid}`);
|
||||
}
|
||||
|
||||
async function handleGiveAll(player: Player) {
|
||||
const inventory = await player.getInventory();
|
||||
|
||||
for (const entry of ItemExcel.all()) {
|
||||
const count = entry.ItemType == "Material" ? 100 : 1;
|
||||
await inventory.addItem(entry.ID, count);
|
||||
}
|
||||
|
||||
c.log(`All materials added to ${player.uid}`);
|
||||
}
|
369
src/db/Inventory.ts
Normal file
369
src/db/Inventory.ts
Normal file
@ -0,0 +1,369 @@
|
||||
import { Equipment, Item, Material, PlayerSyncScNotify, Relic } from "../data/proto/StarRail";
|
||||
import Logger from "../util/Logger";
|
||||
import Database from "./Database";
|
||||
import Player from "./Player";
|
||||
import ItemExcel from "../util/excel/ItemExcel";
|
||||
import { Timestamp } from "mongodb";
|
||||
|
||||
const c = new Logger("Inventory");
|
||||
|
||||
export interface PayItemData {
|
||||
id: number,
|
||||
count: number
|
||||
}
|
||||
|
||||
interface InventoryI {
|
||||
_id: number,
|
||||
nextItemUid: number,
|
||||
materials: { [key: number]: number },
|
||||
relics: Relic[],
|
||||
equipments: Equipment[]
|
||||
}
|
||||
|
||||
export default class Inventory {
|
||||
public readonly player : Player;
|
||||
public readonly db: InventoryI;
|
||||
|
||||
private constructor(player: Player, db: InventoryI) {
|
||||
this.player = player;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public static async loadOrCreate(player: Player) : Promise<Inventory> {
|
||||
// Try to load the player's inventory from the database.
|
||||
const db = Database.getInstance();
|
||||
const inventory = await db.get("inventory", { _id: player.uid }) as unknown as InventoryI; // How to get rid of this ugly fuck?!
|
||||
|
||||
// If successfull, we are done.
|
||||
if (inventory) {
|
||||
return new Inventory(player, inventory);
|
||||
}
|
||||
|
||||
// Otherwise, we create a default inventory.
|
||||
const data : InventoryI = {
|
||||
_id: player.uid,
|
||||
nextItemUid: 1,
|
||||
materials: {},
|
||||
relics: [],
|
||||
equipments: []
|
||||
};
|
||||
await db.set("inventory", data);
|
||||
return new Inventory(player, data);
|
||||
}
|
||||
|
||||
public async save() {
|
||||
const db = Database.getInstance();
|
||||
await db.update("inventory", { _id: this.db._id }, this.db);
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Get inventory info.
|
||||
********************************************************************************/
|
||||
/**
|
||||
* Get list of all `Material`s as proto.
|
||||
* @returns List of materials.
|
||||
*/
|
||||
public getMaterialList() : Material[] {
|
||||
const res: Material[] = [];
|
||||
|
||||
Object.keys(this.db.materials).forEach(key => {
|
||||
res.push({ tid: Number(key), num: this.db.materials[Number(key)] } as Material);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all `Equipment`s as proto.
|
||||
* @returns List of equipments.
|
||||
*/
|
||||
public getEquipmentList() : Equipment[] {
|
||||
return this.db.equipments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all `Relic`s as proto.
|
||||
* @returns List of relics.
|
||||
*/
|
||||
public getRelicsList() : Relic[] {
|
||||
return this.db.relics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of the given item (material or virtual) in the player's inventory.
|
||||
* @param id The item id.
|
||||
* @returns The count in the player's inventory.
|
||||
*/
|
||||
public async getItemCount(id: number) : Promise<number> {
|
||||
// Get item data.
|
||||
const itemData = ItemExcel.fromId(id);
|
||||
if (!itemData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (itemData.ItemType) {
|
||||
case "Virtual": return 0; // ToDo: Handle virtual items.
|
||||
case "Material": return this.db.materials[id] ?? 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Add items to the inventory.
|
||||
********************************************************************************/
|
||||
|
||||
/**
|
||||
* Add the given amount of the given item to the player's inventory.
|
||||
* @param id The item id. For equipment and relics, this is the base id.
|
||||
* @param count The amount of items to add.
|
||||
*/
|
||||
public async addItem(id: number, count: number) {
|
||||
// Get info for the particular item we are trying to add.
|
||||
const itemData = ItemExcel.fromId(id);
|
||||
if (!itemData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle adding depending on item type.
|
||||
const t = itemData.ItemType;
|
||||
if (t == "Virtual") {
|
||||
await this.addVirtualItem(id, count);
|
||||
}
|
||||
else if (t == "Material") {
|
||||
await this.addMaterial(id, count);
|
||||
}
|
||||
else if (t == "Equipment") {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await this.addEquipment(id);
|
||||
}
|
||||
}
|
||||
else if (t == "Relic") {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await this.addRelic(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given amount of the virtual item with the given id to the player's inventory.
|
||||
* @param id The item id.
|
||||
* @param count The amount.
|
||||
*/
|
||||
public async addVirtualItem(id: number, count: number) {
|
||||
// ToDo: Figure out which virtual item ID is what.
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given amount of the material with the given id to the player's inventory.
|
||||
* @param id The material id.
|
||||
* @param count The amount.
|
||||
*/
|
||||
public async addMaterial(id: number, count: number) {
|
||||
// Get info for the particular item we are trying to add.
|
||||
const itemData = ItemExcel.fromId(id);
|
||||
if (!itemData || itemData.ItemType != "Material") {
|
||||
return;
|
||||
}
|
||||
const t = itemData.ItemType;
|
||||
if (t != "Material") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current item count for this ID and calculate new count.
|
||||
const currentCount = this.db.materials[id] ?? 0;
|
||||
const newCount = Math.min(currentCount + count, itemData.PileLimit);
|
||||
|
||||
// Update.
|
||||
this.db.materials[id] = newCount;
|
||||
await this.save();
|
||||
|
||||
// Send update.
|
||||
this.sendMaterialUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given equipment to the player's inventory.
|
||||
* @param equipment Either an `Equipment`, or the base id.
|
||||
*/
|
||||
public async addEquipment(equipment: number | Equipment) {
|
||||
// If the parameter is a number, add a new equipment with this item ID as base.
|
||||
if (typeof(equipment) == "number") {
|
||||
const equip : Equipment = {
|
||||
tid: equipment,
|
||||
uniqueId: this.db.nextItemUid++,
|
||||
level: 1,
|
||||
rank: 1,
|
||||
exp: 1,
|
||||
isProtected: false,
|
||||
promotion: 1,
|
||||
baseAvatarId: 0
|
||||
};
|
||||
|
||||
this.db.equipments.push(equip);
|
||||
await this.save();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add the equipment object directly, but reset it's UID.
|
||||
equipment.uniqueId = this.db.nextItemUid++;
|
||||
this.db.equipments.push(equipment);
|
||||
await this.save();
|
||||
|
||||
// Send update.
|
||||
this.sendEquipmentUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given relic to the player's inventory.
|
||||
* @param relic Either a `Relic`, or the base id.
|
||||
*/
|
||||
public async addRelic(relic: number | Relic) {
|
||||
// Don't add relics for now until we figure out affix IDs, since the game kinda breaks with
|
||||
// incorrect ones.
|
||||
return;
|
||||
|
||||
// If the parameter is a number, add a new equipment with this item ID as base.
|
||||
/*if (typeof(relic) == "number") {
|
||||
const rel : Relic = {
|
||||
tid: relic,
|
||||
uniqueId: this.db.nextItemUid++,
|
||||
level: 1,
|
||||
exp: 1,
|
||||
isProtected: false,
|
||||
baseAvatarId: 0,
|
||||
mainAffixId: 1,
|
||||
subAffixList: []
|
||||
};
|
||||
|
||||
this.db.relics.push(rel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add the equipment object directly, but reset it's UID.
|
||||
relic.uniqueId = this.db.nextItemUid++;
|
||||
this.db.relics.push(relic);*/
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Remove items from the inventory directly.
|
||||
********************************************************************************/
|
||||
|
||||
/**
|
||||
* Removes the the given number of the given item (virtual or material) with the given ID.
|
||||
* @param id The item ID.
|
||||
* @param count The number to remove.
|
||||
*/
|
||||
public async removeItem(id: number, count: number) {
|
||||
const itemData = ItemExcel.fromId(id);
|
||||
if (!itemData) {
|
||||
return ;
|
||||
}
|
||||
|
||||
switch (itemData.ItemType) {
|
||||
case "Virtual": await this.removeVirtualItem(id, count); break;
|
||||
case "Material": await this.removeMaterial(id, count); break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given amount of the given virtual item from the player's inventory.
|
||||
* @param id The item id.
|
||||
* @param count The amount.
|
||||
*/
|
||||
public async removeVirtualItem(id: number, count: number) {
|
||||
await this.addVirtualItem(id, -count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given amount of the given material from the player's inventory.
|
||||
* @param id The item id.
|
||||
* @param count The amount.
|
||||
*/
|
||||
public async removeMaterial(id: number, count: number) {
|
||||
await this.addMaterial(id, -count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given equipment player's inventory.
|
||||
* @param equipment Either an `Equipment`, or the equipment's unique id.
|
||||
*/
|
||||
public async removeEquipment(equipment: number | Equipment) {
|
||||
// Find index to delete.
|
||||
const toDelete: number = (typeof(equipment) == "number") ? equipment : equipment.uniqueId;
|
||||
const index = this.db.equipments.findIndex(i => i.uniqueId == toDelete);
|
||||
|
||||
// Delete and save.
|
||||
this.db.equipments.splice(index, 1);
|
||||
this.save();
|
||||
|
||||
// Send update.
|
||||
this.sendEquipmentUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given relic player's inventory.
|
||||
* @param relic Either a `Relic`, or the relic's unique id.
|
||||
*/
|
||||
public async removeRelic(relic: number | Relic) {
|
||||
// Find index to delete.
|
||||
const toDelete: number = (typeof(relic) == "number") ? relic : relic.uniqueId;
|
||||
const index = this.db.relics.findIndex(i => i.uniqueId == toDelete);
|
||||
|
||||
// Delete and save.
|
||||
this.db.relics.splice(index, 1);
|
||||
this.save();
|
||||
|
||||
// Send update.
|
||||
this.sendRelicUpdate()
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Pay items.
|
||||
********************************************************************************/
|
||||
|
||||
/**
|
||||
* Pay items (virtual items and materials).
|
||||
* @param items The items to be paid.
|
||||
* @returns True if paying succeeded, false otherwise.
|
||||
*/
|
||||
public async payItems(items: PayItemData[]) : Promise<boolean> {
|
||||
// Check if the player has a sufficient amount of all necessary items.
|
||||
for (const item of items) {
|
||||
const currentCount = await this.getItemCount(item.id);
|
||||
if (currentCount < item.count) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We have enough of everything - pay.
|
||||
for (const item of items) {
|
||||
await this.removeItem(item.id, item.count);
|
||||
}
|
||||
|
||||
// Send update.
|
||||
this.sendMaterialUpdate();
|
||||
|
||||
// Done.
|
||||
return true;
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
Player updating.
|
||||
********************************************************************************/
|
||||
private sendMaterialUpdate() {
|
||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||
materialList: this.getMaterialList()
|
||||
}));
|
||||
}
|
||||
private sendEquipmentUpdate() {
|
||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||
equipmentList: this.getEquipmentList()
|
||||
}));
|
||||
}
|
||||
private sendRelicUpdate() {
|
||||
this.player.session.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||
relicList: this.getRelicsList()
|
||||
}));
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import Account from "./Account";
|
||||
import Avatar from "./Avatar";
|
||||
import Database from "./Database";
|
||||
import { Scene } from "../game/Scene";
|
||||
import Inventory from "./Inventory";
|
||||
const c = new Logger("Player");
|
||||
|
||||
export interface LineupI {
|
||||
@ -49,6 +50,7 @@ interface PlayerI {
|
||||
export default class Player {
|
||||
public readonly uid: number
|
||||
public readonly scene: Scene;
|
||||
private inventory!: Inventory;
|
||||
|
||||
private constructor(readonly session: Session, public db: PlayerI) {
|
||||
this.uid = db._id;
|
||||
@ -105,6 +107,15 @@ export default class Player {
|
||||
this.db.lineup.curIndex = curIndex;
|
||||
}
|
||||
|
||||
public async getInventory() : Promise<Inventory> {
|
||||
// If this players inventory has not been loaded yet, do so now.
|
||||
if (!this.inventory) {
|
||||
this.inventory = await Inventory.loadOrCreate(this);
|
||||
}
|
||||
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
public static async create(session: Session, uid: number | string): Promise<Player | undefined> {
|
||||
if (typeof uid == "string") uid = Number(uid);
|
||||
const acc = await Account.fromUID(uid);
|
||||
|
@ -80,10 +80,15 @@ export default class Session {
|
||||
|
||||
public async sync() {
|
||||
const avatars = await Avatar.fromUID(this.player.db._id);
|
||||
const inventory = await this.player.getInventory();
|
||||
|
||||
this.send(PlayerSyncScNotify, PlayerSyncScNotify.fromPartial({
|
||||
avatarSync: {
|
||||
avatarList: avatars.map(x => x.data),
|
||||
avatarList: avatars.map(x => x.data)
|
||||
},
|
||||
materialList: inventory.getMaterialList(),
|
||||
equipmentList: inventory.getEquipmentList(),
|
||||
relicList: inventory.getRelicsList(),
|
||||
basicInfo: this.player.db.basicInfo
|
||||
}));
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DoGachaCsReq, DoGachaScRsp, GachaItem, Item, ItemList } from "../../data/proto/StarRail";
|
||||
import { PayItemData } from "../../db/Inventory";
|
||||
import Banners from "../../util/Banner";
|
||||
import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
@ -8,6 +9,17 @@ export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as DoGachaCsReq;
|
||||
const banner = Banners.config.find(banner => banner.gachaId === body.gachaId)!;
|
||||
const combined = banner.rateUpItems4.concat(banner.rateUpItems5)
|
||||
|
||||
// Pay currency.
|
||||
const inventory = await session.player.getInventory();
|
||||
const success = await inventory.payItems([{ id: banner.costItemId, count: body.gachaNum } as PayItemData]);
|
||||
|
||||
if (!success) {
|
||||
session.send(DoGachaScRsp, {
|
||||
retcode: 1
|
||||
} as DoGachaScRsp);
|
||||
}
|
||||
|
||||
//bad gachaing but whatever....
|
||||
//TODO: pity system, proper logic
|
||||
for(let i = 0; i < body.gachaNum; i++){
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { GetBagScRsp } from "../../data/proto/StarRail";
|
||||
import { Equipment, GetBagScRsp, Material, Relic } from "../../data/proto/StarRail";
|
||||
import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const inventory = await session.player.getInventory();
|
||||
|
||||
session.send(GetBagScRsp, {
|
||||
equipmentList: [],
|
||||
materialList: [],
|
||||
relicList: [],
|
||||
equipmentList: inventory.getEquipmentList(),
|
||||
materialList: inventory.getMaterialList(),
|
||||
relicList: inventory.getRelicsList(),
|
||||
retcode: 0,
|
||||
rogueItemList: [],
|
||||
waitDelResourceList: []
|
||||
|
@ -33,7 +33,7 @@ export default class Banners {
|
||||
rateUpItems5: [
|
||||
1102
|
||||
],
|
||||
costItemId: -1 //unused for now
|
||||
costItemId: 101 // Star Rail Pass
|
||||
} as Banner
|
||||
];
|
||||
|
||||
|
20
src/util/excel/ItemExcel.ts
Normal file
20
src/util/excel/ItemExcel.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import _ItemExcelTable from "../../data/excel/ItemExcelTable.json";
|
||||
type ItemExcelTableEntry = typeof _ItemExcelTable[keyof typeof _ItemExcelTable]
|
||||
const ItemExcelTable = _ItemExcelTable as { [key: string]: ItemExcelTableEntry };
|
||||
|
||||
export default class ItemExcel {
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static all() : ItemExcelTableEntry[] {
|
||||
return Object.values(ItemExcelTable);
|
||||
}
|
||||
|
||||
public static fromId(id: number) : ItemExcelTableEntry {
|
||||
return ItemExcelTable[id];
|
||||
}
|
||||
|
||||
public static fromIds(ids: number[]): ItemExcelTableEntry[] {
|
||||
return ids.map(id => ItemExcel.fromId(id));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user