guru_sdk/guru_app/lib/inventory/inventory_manager.dart

194 lines
7.0 KiB
Dart
Raw Normal View History

import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/database/guru_db.dart';
import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/inventory/db/inventory_database.dart';
import 'package:guru_utils/database/batch/batch_aware.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/manifest/manifest.dart';
class InventoryCategory {
static const String prop = "prop"; // 道具类
}
class PropCategory {
// 增益类:提供临时或永久的能力提升,如速度增加、力量提升、生命值恢复等。
static const String boosts = 'Boosts';
// 减益类:施加在对手或玩家身上,造成能力降低、速度减慢、伤害增加等负面效果。
static const String debuffs = 'Debuffs';
// 治疗类:恢复生命值或状态,解除负面效果。
static const String healing = 'Healing';
// 防御类:提供防护,减少受到的伤害或完全避免某些类型的伤害。
static const String defensive = 'Defensive';
// 攻击类:用于对敌人造成伤害或施加减益效果,如武器、陷阱、魔法等。
static const String offensive = 'Offensive';
// 辅助类:提供团队增益、增强队友能力、提供战术优势等非直接攻击手段的道具。
static const String supportive = 'Supportive';
// 探索类:用于揭示地图、发现隐藏物品、解锁新区域或提供关键信息的道具。
static const String exploratory = 'Exploratory';
// 交互类:与游戏世界中的其他元素或玩家进行交互的道具,如钥匙、开关、通信设备等。
static const String interactive = 'Interactive';
// 资源类:用于制造、升级或交易的材料和货币类型道具。
static const String resources = 'Resources';
// 限定类:只能使用一定次数的道具,使用后消失或需要充能。
static const String limitedUse = 'LimitedUse';
}
mixin InventoryDelegate {
// 返回指定id的库存类别
// 如 hint,zoom这些道具统一返回
String getInventoryCategory(String id) {
return InventoryCategory.prop;
}
Future<List<StockItem>> getMigrateStockItems() async {
return [];
}
}
class StockItem {
final String sku;
final int amount;
final int attr;
final DateTime? expired;
const StockItem.consumable(this.sku, this.amount, {this.expired}) : attr = DetailsAttr.consumable;
const StockItem.permanent(this.sku, this.amount, {this.expired}) : attr = DetailsAttr.permanent;
const StockItem.igc(this.amount, {this.sku = "igc"})
: attr = DetailsAttr.consumable,
expired = null;
StockItem.fromDetails(Details details)
: sku = details.sku,
attr = details.attr,
amount = details.amount,
expired = null;
}
class InventoryManager with BatchAware<InventoryItem> {
static final InventoryManager instance = InventoryManager._();
InventoryManager._();
String getInventoryCategory(String id) {
return GuruApp.instance.protocol.inventoryDelegate?.getInventoryCategory(id) ??
InventoryCategory.prop;
}
Future init() async {
final batch = await GuruDB.instance.loadInventoryItems();
processBatchData(batch);
await _migrate();
}
Future _migrate() async {
final migrateStockItems =
await GuruApp.instance.protocol.inventoryDelegate?.getMigrateStockItems() ?? [];
if (migrateStockItems.isNotEmpty) {
final needMigrateItems = migrateStockItems.where((item) => !exists(item.sku)).toList();
await acquire(needMigrateItems, TransactionMethod.migrate, "migrate");
}
}
///
/// 通过[method]中的的特定[specific]方式 获得了指定的 [items]
/// * method: iap -> specific: sku
/// * method: igc -> specific: coin/gems...
/// * method: reward -> specific: ads/lottery/daily/...
/// * method: bonus -> specific: ads/other/...
/// * method: igb -> specific: hint/hammer/swap/magic/..
///
/// method 最终会在 earnVirtualCurrency 中成为 item_category
/// specific 最终会在 earnVirtualCurrency 中成为 item_name
///
Future acquire(List<StockItem> items, TransactionMethod method, String specific,
{String? scene}) async {
final acquired = <InventoryItem>[];
for (var item in items) {
final category = getInventoryCategory(item.sku);
final invItem = getData(item.sku)
?.acquire(method, item.amount, expiredAt: item.expired?.millisecondsSinceEpoch) ??
InventoryItem.create(item.sku, category, item.attr,
balance: item.amount,
method: method,
expireAt: item.expired?.millisecondsSinceEpoch ?? -1);
acquired.add(invItem);
GuruAnalytics.instance.logEarnVirtualCurrency(
virtualCurrencyName: item.sku,
method: convertTransactionMethodName(method),
specific: specific,
balance: invItem.balance,
value: item.amount,
scene: scene);
}
final batchData = await GuruDB.instance.updateInventoryItems(acquired);
processBatchData(batchData);
}
ConsumeResult? _consumeItem(StockItem item, Manifest redeemed, {bool timeSensitiveOnly = false}) {
final inventoryItem = getData(item.sku);
return timeSensitiveOnly
? inventoryItem?.consumeTimeSensitiveOnly(item.amount,
scene: redeemed.scene, transactionTs: redeemed.transactionTs)
: inventoryItem?.consume(item.amount, scene: redeemed.scene);
}
/// 消耗指定的道具[items],如果可以成功消费,便可得到相应的[redeemed]清单
Future<bool> consume(List<StockItem> items, Manifest redeemed,
{bool timeSensitiveOnly = false}) async {
final consumed = <InventoryItem>[];
bool isConsumed = true;
for (var item in items) {
/// 这里只是针对对应的 SKU 商品进行 consume标记真正的消耗在下面的updateInventoryItems中
final result = _consumeItem(item, redeemed, timeSensitiveOnly: timeSensitiveOnly);
if (result == null) {
Log.e("consume failed! Not Found inventory item: ${item.sku}");
return false;
}
consumed.add(result.item);
// 这里如果没有消耗掉,将跳出
if (!result.consumed) {
Log.w("consume failed: ${item.sku} ${item.amount}");
isConsumed = false;
break;
}
}
if (consumed.isEmpty) {
return false;
} else {
if (isConsumed && consumed.length == items.length) {
for (int i = 0; i < items.length; ++i) {
GuruAnalytics.instance.logSpendCredits(
redeemed.contentId, redeemed.category, items[i].amount,
virtualCurrencyName: consumed[i].sku,
balance: consumed[i].balance,
scene: redeemed.scene);
}
}
}
/// 这里会更新消耗掉的道具
final batchData = await GuruDB.instance.updateInventoryItems(consumed);
processBatchData(batchData);
return isConsumed;
}
bool canAfford(String id, int amount) {
final item = getData(id);
return item != null && item.balance >= amount;
}
}