194 lines
7.0 KiB
Dart
194 lines
7.0 KiB
Dart
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;
|
||
}
|
||
}
|