guru_sdk/guru_app/lib/inventory/inventory_manager.dart

194 lines
7.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
}
}