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> 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 { 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 items, TransactionMethod method, String specific, {String? scene}) async { final acquired = []; 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 consume(List items, Manifest redeemed, {bool timeSensitiveOnly = false}) async { final consumed = []; 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; } }