feat: 商店系统实现

This commit is contained in:
AncTe committed 2026-10-12 19:17:06 +08:00
1 parent acf20f548d
commit a15b79b29f
7 files changed
+362 -13

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
import { IDataCommon, SaveCompression } from '@user/data-common';
import { IShopManager, IShopManagerSave, IShopState } from './types';
import { logger } from '@motajs/common';
import { ShopState } from './state';
import { isNil } from 'lodash-es';
export class ShopManager implements IShopManager {
/** 当前管理器包含的商店列表 */
private readonly shops: Map<string, IShopState> = new Map();
constructor(readonly state: IDataCommon) {}
/**
* 传入商店 id 或实例,输出商店实例
* @param shop 商店的 id 或其实例
*/
private getShopState(shop: string | IShopState): IShopState | null {
if (typeof shop === 'string') {
return this.shops.get(shop) ?? null;
} else {
return shop;
}
}
/**
* 传入商店 id 或实例,输出商店 id
* @param shop 商店的 id 或其实例
*/
private getShopId(shop: string | IShopState): string | undefined {
if (typeof shop === 'string') {
return shop;
} else {
return shop.id;
}
}
addShop(shop: IShopState): void {
if (this.shops.has(shop.id)) {
logger.warn(198, shop.id);
return;
}
this.shops.set(shop.id, shop);
}
removeShop(shop: string | IShopState): void {
const id = this.getShopId(shop);
if (!isNil(id)) this.shops.delete(id);
}
getShop(id: string): IShopState | null {
return this.shops.get(id) ?? null;
}
hasShop(shop: string | IShopState): boolean {
const id = this.getShopId(shop);
return isNil(id) || this.shops.has(id);
}
iterateShops(): Iterable<[string, IShopState]> {
return this.shops;
}
*iterateEnabledShops(): Iterable<[string, IShopState]> {
for (const [key, state] of this.shops) {
if (state.enabled) yield [key, state];
}
}
*iterateDisabledShops(): Iterable<[string, IShopState]> {
for (const [key, state] of this.shops) {
if (!state.enabled) yield [key, state];
}
}
enableShop(shop: string | IShopState): void {
const state = this.getShopState(shop);
if (state) state.enable();
}
disableShop(shop: string | IShopState): void {
const state = this.getShopState(shop);
if (state) state.disable();
}
saveState(compression: SaveCompression): IShopManagerSave {
const list = [...this.shops.values()].map(v =>
v.saveState(compression)
);
return {
list
};
}
loadState(state: IShopManagerSave, compression: SaveCompression): void {
this.shops.clear();
for (const save of state.list) {
const shop: IShopState = new ShopState(save.id, this.state);
shop.loadState(save, compression);
this.shops.set(save.id, shop);
}
}
}
+105
View File
@@ -0,0 +1,105 @@
import { IDataCommon } from '@user/data-common';
import { IShopState, IShopStateSave } from './types';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
export class ShopState implements IShopState {
enabled: boolean = false;
/** 此商店拥有的所有道具及其库存 */
private readonly items: Map<number, number> = new Map();
/** 此商店中,每个道具的买入价格 */
private readonly buyPrice: Map<number, number> = new Map();
/** 此商店中,每个道具的售出价格 */
private readonly sellPrice: Map<number, number> = new Map();
constructor(
readonly id: string,
readonly state: IDataCommon
) {}
addItem(token: number | string, count: number): void {
const num = this.state.tileStore.num(token);
if (isNil(num)) {
logger.warn(196, token.toString());
return;
}
// 注意,这里不把库存为 0 的置空是因为置空之后迭代不到,商店将不再显示这个道具
// 但是商店理应显示库存为 0 的道具,一是因为玩家可能需要卖出,二是因为玩家可能需要 0 库存这一信息
const before = this.items.getOrInsert(num, 0);
this.items.set(num, before + count);
}
getCount(token: number | string): number {
const num = this.state.tileStore.num(token);
if (isNil(num)) {
logger.warn(196, token.toString());
return -1;
}
return this.items.get(num) ?? -1;
}
iterateItems(): Iterable<[number, number]> {
return this.items;
}
setPrice(item: number | string, buy: number, sell: number): void {
const num = this.state.tileStore.num(item);
if (isNil(num)) {
logger.warn(196, item.toString());
return;
}
this.buyPrice.set(num, buy);
this.sellPrice.set(num, sell);
}
getPrice(item: number | string): [buy: number, sell: number] {
const num = this.state.tileStore.num(item);
if (isNil(num)) {
logger.warn(196, item.toString());
return [-1, -1];
}
const buy = this.buyPrice.get(num);
const sell = this.sellPrice.get(num);
if (isNil(buy) || isNil(sell)) return [-1, -1];
else return [buy, sell];
}
enable(): void {
this.enabled = true;
}
disable(): void {
this.enabled = false;
}
saveState(): IShopStateSave {
return {
id: this.id,
enabled: this.enabled,
items: this.items,
buyPrice: this.buyPrice,
sellPrice: this.sellPrice
};
}
loadState(state: IShopStateSave): void {
if (state.id !== this.id) {
logger.warn(197, this.id, state.id);
return;
}
this.enabled = state.enabled;
this.items.clear();
for (const [key, value] of state.items) {
this.items.set(key, value);
}
this.buyPrice.clear();
for (const [key, value] of state.buyPrice) {
this.buyPrice.set(key, value);
}
this.sellPrice.clear();
for (const [key, value] of state.sellPrice) {
this.sellPrice.set(key, value);
}
}
}
+13 -7
View File
@@ -1,4 +1,4 @@
import { ISaveableContent } from '@user/data-common';
import { IDataCommonExtended, ISaveableContent } from '@user/data-common';
export interface IShopStateSave {
/** 商店 id */
@@ -7,13 +7,18 @@ export interface IShopStateSave {
readonly enabled: boolean;
/** 商店拥有的库存 */
readonly items: ReadonlyMap<number, number>;
/** 商店道具的买入价与售出价 */
readonly prices: ReadonlyMap<number, [buy: number, sell: number]>;
/** 商店道具的买入价 */
readonly buyPrice: ReadonlyMap<number, number>;
/** 商店道具的售出价 */
readonly sellPrice: ReadonlyMap<number, number>;
}
export interface IShopState extends ISaveableContent<IShopStateSave> {
export interface IShopState
extends IDataCommonExtended, ISaveableContent<IShopStateSave> {
/** 当前商店是否已经启用 */
readonly enabled: boolean;
/** 当前商店的标识符 */
readonly id: string;
/**
* 向此商店添加或减少道具库存,会与原本的库存叠加
@@ -29,7 +34,7 @@ export interface IShopState extends ISaveableContent<IShopStateSave> {
getCount(token: number | string): number;
/**
* 迭代商店拥有的所有道具
* 迭代商店拥有的所有道具,键表示道具的图块数字,值表示数量
*/
iterateItems(): Iterable<[number, number]>;
@@ -42,7 +47,7 @@ export interface IShopState extends ISaveableContent<IShopStateSave> {
setPrice(item: number | string, buy: number, sell: number): void;
/**
* 获取道具价格
* 获取道具价格,如果道具不存在则返回 -1
* @param item 道具数字或 id
*/
getPrice(item: number | string): [buy: number, sell: number];
@@ -63,7 +68,8 @@ export interface IShopManagerSave {
readonly list: IShopStateSave[];
}
export interface IShopManager extends ISaveableContent<IShopManagerSave> {
export interface IShopManager
extends IDataCommonExtended, ISaveableContent<IShopManagerSave> {
/**
* 添加新的商店
* @param shop 商店状态对象