Files
ft/Client/Assets/Scripts/Event1v1Battle/Data/Event1v1BattleSlapManager.cs
2026-06-29 21:18:33 +08:00

1124 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using asap.core;
using cfg;
using Event1v1Battle.Panel;
using Event1v1Battle.Panel.MainPanel;
using Event1v1Battle.Panel.RewardPanel;
using game;
using GameCore;
using Newtonsoft.Json.Linq;
using SideGameFromFishing;
using UI.Home.Event;
using UI.PackPanel;
using UniRx;
using UnityEngine;
using IEventData = Activity.IEventData;
using Random = UnityEngine.Random;
using Activity;
namespace Event1v1Battle.Data
{
/// <summary>
/// 1v1扇耳光大赛活动管理器
/// </summary>
public class Event1v1BattleSlapManager : Activity.AEventDataManager<Event1v1BattleSlapData>, IActivityEventRedPoint<Event1v1BattleSlapData>,
ISideGameBombRaidProgress, ISideGameFishRaidProgress
{
#region
private const int SOURCE_BOMB = 0; // 轰炸
private const int SOURCE_SHOOT = 1; // 射鱼
private const int SOURCE_FISHING = 2; // 钓鱼
#endregion
#region
private Event1v1BattleSlapPresentationData _presentationData = new();
/// <summary>
/// 获取表现数据
/// </summary>
public Event1v1BattleSlapPresentationData PresentationData => _presentationData;
#endregion
#region
public Event1v1BattleSlapManager()
{
GContext.OnEvent<FishingEndEvent>().Subscribe(OnFishingEnd).AddTo(CompositeDisposable);
GContext.OnEvent<HomeStartEvent>().Subscribe(OnHomeStart).AddTo(CompositeDisposable);
}
private void OnFishingEnd(FishingEndEvent e)
{
var data = LoadData();
#if UNITY_EDITOR
if ((!data.IsActive || !data.IsOnGoing) && Tables.TbEventSlapSpinach.TryGetByItemID(e.ItemId, out _))
{
Debug.LogError($"有人作弊触发了菠菜,需要匹配对少才能触发正确流程");
}
#endif
if (!data.IsActive || !data.IsOnGoing)
return;
Tables.TbEventSlapSpinach.TryGetByItemID(e.ItemId, out var spinachConfig);
if (spinachConfig == null) return;
OnSpinachCaught(data);
}
private void OnHomeStart(HomeStartEvent e)
{
var data = LoadData();
if (CheckAndHandleBattleTimeout(data)) return;
var needShowNpcAttackTv = CheckAndSimulatePendingAttacks(data, needClear: !_presentationData.IsPlayAttacking);
if (needShowNpcAttackTv)
ShowFaceTv(data);
}
#endregion
#region
#region DataEx移入
/// <summary>
/// 活动激活时初始化
/// </summary>
public void InitOnActive(Event1v1BattleSlapData data)
{
InitRound(data);
}
/// <summary>
/// 初始化当前轮次
/// </summary>
public void InitRound(Event1v1BattleSlapData data)
{
data.CurrentStageID = data.InitConfig.InitialStage;
data.StageHistory?.Clear();
data.PlayerLifeCount = 3;
data.SlapPaidReviveCount = 0;
InitializeStageHp(data);
}
/// <summary>
/// 初始化当前关卡的HP
/// </summary>
public void InitializeStageHp(Event1v1BattleSlapData data)
{
var stageConfig = data.StageConfig;
if (stageConfig == null)
{
Debug.LogError($"[EventSlap] Stage config not found for ID: {data.CurrentStageID}");
return;
}
RandomNpcData(data);
int maxHp = GetPlayerMaxHp(data);
data.NpcHp = maxHp;
data.MaxHp = maxHp;
data.PlayerHp = maxHp;
}
/// <summary>
/// 获取玩家最大HP关卡表 StageHP 经 <see cref="PlayerItemData.IntInflation"/> 与体力一致的膨胀系数处理)
/// </summary>
public int GetPlayerMaxHp(Event1v1BattleSlapData data)
{
var stageConfig = data.StageConfig;
if (stageConfig == null)
{
Debug.LogError($"[EventSlap] Stage config not found for ID: {data.CurrentStageID}");
return 0;
}
return GContext.container.Resolve<PlayerItemData>().IntInflation(stageConfig.StageHP, data.InflationRate);
}
/// <summary>
/// 随机NPC数据
/// </summary>
private void RandomNpcData(Event1v1BattleSlapData data)
{
var robotList = data.Tables.TbRobot.DataList;
var index = Random.Range(0, robotList.Count);
var npcConfig = robotList[index];
while (npcConfig == null)
{
Debug.LogError($"npcConfig 配置错误 id {index}");
index = Random.Range(0, robotList.Count);
npcConfig = robotList[index];
}
data.NpcIcon = npcConfig.Avatar;
data.NpcName = npcConfig.Name;
}
/// <summary>
/// Buff是否有效
/// </summary>
public bool BuffIsValid(Event1v1BattleSlapData data)
{
var center = GContext.container?.Resolve<BuffDataCenter>();
return center != null && center.GetWeelyBuffTimeData<EventSlapDamageBuff>() != null;
}
/// <summary>
/// 获取Buff加成
/// </summary>
public float GetBuffBenefit(Event1v1BattleSlapData data)
{
if (!BuffIsValid(data))
return 0;
var buff = GContext.container?.Resolve<BuffDataCenter>()?.GetWeelyBuffTimeData<EventSlapDamageBuff>();
return buff?.Param ?? 0;
}
/// <summary>
/// 从 <see cref="TbFishBuff"/> 中取扇耳光伤害加成(<see cref="EventSlapDamageBuff.Param"/>),命中第一条配置即可。
/// </summary>
private float GetEventSlapDamageBuffParamFromTable()
{
foreach (var fishBuff in Tables.TbFishBuff.DataList)
{
if (fishBuff.BuffParam is EventSlapDamageBuff slapDamageBuff)
return slapDamageBuff.Param;
}
return 0f;
}
#endregion
protected override void OnDataInitialized(IEventData data, FishingEvent eventConfig)
{
base.OnDataInitialized(data, eventConfig);
// 新一期活动EventID 变化)时重置 Slap01配合主面板 TriggerGuide(..., false) 实现每期仅可触发一次
GContext.container.Resolve<GuideDataCenter>().ClearGuide(GroupName.Slap01.ToString());
var slapData = (Event1v1BattleSlapData)data;
InitOnActive(slapData);
ShowFacePanel(slapData);
SaveData(slapData);
}
#endregion
#region
/// <summary>
/// 玩家执行攻击
/// </summary>
public void ExecutePlayerAttack(int multiplier, int sourceIndex, Event1v1BattleSlapData data)
{
if (!data.IsActive || !data.IsOnGoing) return;
if (CheckAndHandleBattleTimeout(data, simulateNpcAttacks: false)) return;
_presentationData.Clear();
_presentationData.Multiplier = multiplier;
var damage = CalculateDamageBySource(data, multiplier, sourceIndex, out _presentationData.CanQuality);
var qualityForAgg = _presentationData.CanQuality;
damage = (int)(damage * (1 + GetBuffBenefit(data)));
Agg_event_slap(GetSpinachSourceAggType(sourceIndex), multiplier, qualityForAgg, data.PlayerHp,
BuffIsValid(data), data.NpcHp);
_presentationData.IsPlayAttacking = true;
_presentationData.SyncFromRealData(data);
data.NpcHp -= damage;
_presentationData.NpcHpAfterPlayerAttack = data.NpcHp;
if (data.NpcHp <= 0)
{
_presentationData.KilledNpc = true;
_presentationData.NextStageId = data.IsFinalStage ? data.InitConfig.InitialStage : data.StageConfig.NextStage;
HandleStageHpDepleted(data);
}
else
{
CheckAndSimulatePendingAttacks(data, false);
}
SaveData(data);
ShowFaceTv(data);
}
private void ShowFaceTv(Event1v1BattleSlapData data)
{
if (data == null || !data.IsActive) return;
if (string.IsNullOrEmpty(data.InitConfig?.TvPopUpPanel)) return;
UITypes.Event1v1BattleSlapTvPopupPanel.SetType(data.InitConfig.TvPopUpPanel);
GContext.container.Resolve<IFaceUIService>().AddPostFaceUI(UITypes.Event1v1BattleSlapTvPopupPanel);
}
#region
/// <summary>
/// 用Weight随机选择倍率
/// </summary>
private int SelectDamageByWeight(Event1v1BattleSlapData data)
{
var stageConfig = data.StageConfig;
var damageList = stageConfig.DamageList;
var weightList = stageConfig.Weight;
if (damageList == null || damageList.Count == 0)
return 1;
if (weightList == null || weightList.Count != damageList.Count)
return damageList[0];
int totalWeight = weightList.Sum();
if (totalWeight <= 0)
return damageList[0];
int randomValue = Random.Range(0, totalWeight);
int currentWeight = 0;
for (int i = 0; i < damageList.Count; i++)
{
currentWeight += weightList[i];
if (randomValue < currentWeight)
return damageList[i];
}
return damageList[0];
}
/// <summary>
/// 根据来源计算最终伤害
/// </summary>
public int CalculateDamageBySource(Event1v1BattleSlapData data, int multiplier, int sourceIndex, out int quality)
{
var spinachID = Tables.TbEventSlapMag.GetSpinachIDFromMag(multiplier);
var config = data.GetSpinachConfig(spinachID);
quality = config?.Quality ?? 0;
switch (sourceIndex)
{
case SOURCE_FISHING:
return CalculateFishingDamage(config, multiplier);
case SOURCE_BOMB:
return CalculateBombDamage(multiplier);
case SOURCE_SHOOT:
return CalculateRaidDamage(multiplier);
default:
return 0;
}
}
private int CalculateFishingDamage(EventSlapSpinach config,int multiplier)
{
return multiplier;
}
private int CalculateBombDamage(int multiplier)
{
return multiplier * Tables.TbGlobalConfig.EventSlapBombPoint;
}
private int CalculateRaidDamage(int multiplier)
{
return multiplier * Tables.TbGlobalConfig.EventSlapRaidPoint;
}
#endregion
#endregion
#region NPC攻击
/// <summary>
/// 模拟单次NPC攻击不修改真实数据用于判断是否致命
/// </summary>
public bool SimulateStageAttack(Event1v1BattleSlapData data, int currentHp, out int damage, out int attackQuality,
out int attackMultiplier, out bool npcBuffApplied)
{
damage = CalculateStageDamage(data, out attackQuality, out attackMultiplier, out npcBuffApplied);
int newHp = currentHp - damage;
return newHp <= 0;
}
/// <summary>
/// 计算NPC对玩家的伤害完整流程
/// </summary>
public int CalculateStageDamage(Event1v1BattleSlapData data, out int attackQuality, out int attackMultiplier,
out bool npcBuffApplied)
{
attackMultiplier = 0;
npcBuffApplied = false;
var stageConfig = data.StageConfig;
if (stageConfig == null)
{
Debug.LogError($"[EventSlap] Stage config not found for ID: {data.CurrentStageID}");
attackQuality = 0;
return -1;
}
attackMultiplier = SelectDamageByWeight(data);
int finalDamage = CalculateDamageBySource(data, attackMultiplier, SOURCE_FISHING, out attackQuality);
float coverage = Mathf.Clamp01(stageConfig.BuffCoverage);
if (finalDamage > 0 && coverage > 0f && Random.value < coverage)
{
float buffParam = GetEventSlapDamageBuffParamFromTable();
if (buffParam > 0f)
{
npcBuffApplied = true;
finalDamage = Mathf.Max(1, Mathf.RoundToInt(finalDamage * (1f + buffParam)));
}
}
#if UNITY_EDITOR
Debug.Log($"[EventSlap] Stage Damage: multiplier={attackMultiplier}, final={finalDamage}, quality={attackQuality}, buffCoverage={stageConfig.BuffCoverage}");
#endif
return finalDamage;
}
/// <summary>
/// 检查并结算所有已到时的 NPC 攻击(<c>while (now &gt;= NextAttackTime)</c> 可在一帧内连续多段)。
/// TV 只播一次敌方扇耳光:表现层仅展示最后一击——
/// <see cref="Event1v1BattleSlapPresentationData.PlayerHp"/> 为最后一击前血量,
/// <see cref="Event1v1BattleSlapPresentationData.AttackDamage"/> 为最后一击配置伤害;
/// 飘字/血条动画用表现前血量与真实结算后 <see cref="Event1v1BattleSlapData.PlayerHp"/> 的差值故未死时等价于只展示最后一击的扣血20 血连挨 5、8表现为从 15 扣 8
/// </summary>
public bool CheckAndSimulatePendingAttacks(Event1v1BattleSlapData data, bool needClear = true)
{
var nowSeconds = new DateTimeOffset(ZZTimeHelper.UtcNow()).ToUnixTimeSeconds();
if (!data.IsActive || !data.IsOnGoing)
return false;
// 超时由 HandleBattleTimeout 内 ShowFaceTv 入待播;此处勿返回 true否则 OnHomeStart 会再走立刻弹 TV 重复一次
if (CheckAndHandleBattleTimeout(data))
return false;
if (nowSeconds < data.NextAttackTime)
return false;
if (needClear)
{
_presentationData.Clear();
_presentationData.SyncFromRealData(data);
}
int lastDamage = 0;
int lastAttackQuality = 0;
int hpBeforeLastAttack = data.PlayerHp;
bool hasLethalAttack = false;
while (nowSeconds >= data.NextAttackTime)
{
hpBeforeLastAttack = data.PlayerHp;
bool isLethal = SimulateStageAttack(data, data.PlayerHp, out int damage, out int attackQuality,
out int attackMultiplier, out bool npcBuffApplied);
lastDamage = damage;
lastAttackQuality = attackQuality;
PresentationData.IsNpcBuffValid = npcBuffApplied;
if (damage >= 0)
Agg_event_slap_attacked(data.CurrentStageID, data.CurrentRoundForm1, attackMultiplier, npcBuffApplied,
isLethal);
if (isLethal)
{
hasLethalAttack = true;
data.PlayerHp = 0;
break;
}
data.PlayerHp -= damage;
int intervalSeconds = data.GetNextAttackInterval();
data.NextAttackTime += intervalSeconds;
}
if (!hasLethalAttack && nowSeconds >= data.NextAttackTime)
{
RefreshNextAttackTime(data);
}
_presentationData.IsNpcAttacking = true;
_presentationData.AttackDamage = lastDamage;
_presentationData.NpcAttackCanQuality = lastAttackQuality;
_presentationData.PlayerHp = hpBeforeLastAttack;
if (data.PlayerHp <= 0)
{
_presentationData.KilledPlayer = true;
data.PlayerHp = 0;
HandlePlayerHpDepleted(data);
}
SaveData(data);
return true;
}
#endregion
#region
/// <summary>
/// 钓到菠菜后直接攻击NPC
/// </summary>
public void OnSpinachCaught(Event1v1BattleSlapData data)
{
if (!data.IsActive || !data.IsOnGoing) return;
var mag = GContext.container.Resolve<PlayerData>().GetMagnification();
ExecutePlayerAttack(mag, SOURCE_FISHING, data);
}
public void OnSpinachFromBomb(int mag, Event1v1BattleSlapData data)
{
if (!data.IsActive || !data.IsOnGoing) return;
ExecutePlayerAttack(mag, SOURCE_BOMB, data);
}
public void OnSpinachFromShoot(Event1v1BattleSlapData data, int mag)
{
if (!data.IsActive || !data.IsOnGoing) return;
ExecutePlayerAttack(mag, SOURCE_SHOOT, data);
}
#endregion
#region
public void StartBattle(Event1v1BattleSlapData data)
{
data.IsOnGoing = true;
data.BattleStartTime = new DateTimeOffset(ZZTimeHelper.UtcNow()).ToUnixTimeSeconds();
RefreshNextAttackTime(data);
SaveData(data);
}
/// <summary>
/// 刷新下次NPC攻击时间
/// </summary>
public void RefreshNextAttackTime(Event1v1BattleSlapData data)
{
var now = ZZTimeHelper.UtcNow();
int intervalSeconds = data.GetNextAttackInterval();
Debug.Log($" slap NPC攻击时间刷新 {now} + {intervalSeconds}秒");
data.NextAttackTime = new DateTimeOffset(now).ToUnixTimeSeconds() + intervalSeconds;
}
/// <summary>
/// 处理玩家HP归零
/// </summary>
public void HandlePlayerHpDepleted(Event1v1BattleSlapData data)
{
data.NextAttackTime = 0;
data.PlayerLifeCount--;
data.IsOnGoing = false;
data.BattleStartTime = 0;
if (data.HasLifeRemaining)
{
InitializeStageHp(data);
}
else if (TryEnterPaidRevivePendingState(data))
{
// 三命耗尽:有配置则停留当前进度,主界面下方走付费复活
}
else if (data.ShouldStartNewRound())
{
HandleStartNewRound(data);
}
}
/// <summary>
/// 当前付费复活应使用的 TbIAPItemList 配置 id来自 GlobalConfig.EventSlapIapList下标封顶为最后一项
/// </summary>
public int GetPaidReviveIapTableId(Event1v1BattleSlapData data)
{
var list = data.Tables.TbGlobalConfig.EventSlapIapList;
if (list == null || list.Count == 0)
return 0;
int idx = Math.Min(data.SlapPaidReviveCount, list.Count - 1);
return list[idx];
}
/// <summary>
/// 三命为 0、活动仍有效、且配置了可用 IAP 时,等待玩家付费复活(不自动开新轮)
/// </summary>
public bool IsAwaitingPaidRevive(Event1v1BattleSlapData data)
{
if (data == null || !data.IsActive || data.PlayerLifeCount > 0)
{
return false;
}
var iapId = GetPaidReviveIapTableId(data);
if (iapId <= 0)
{
Debug.LogError($"[EventSlap] IsAwaitingPaidRevive, return false");
return false;
}
var iapItem = data.Tables.TbIAPItemList?.GetOrDefault(iapId);
if (iapItem == null)
{
Debug.LogError($"[EventSlap] IsAwaitingPaidRevive, iapItem not found");
return false;
}
return true;
}
bool TryEnterPaidRevivePendingState(Event1v1BattleSlapData data)
{
if (!data.IsActive)
return false;
var iapId = GetPaidReviveIapTableId(data);
if (iapId <= 0)
return false;
if (data.Tables.TbIAPItemList?.GetOrDefault(iapId) == null)
{
Debug.LogError($"[EventSlap] EventSlapIapList id {iapId} not found in TbIAPItemList, fallback to new round.");
return false;
}
return true;
}
/// <summary>
/// 付费成功后:满血(当前关 MaxHp并恢复 3 条命,保持关卡进度
/// </summary>
private void ApplyPaidRevive(Event1v1BattleSlapData data)
{
PresentationData.Clear();
data.SlapPaidReviveCount++;
data.PlayerLifeCount = 3;
InitializeStageHp(data);
}
/// <summary>
/// 发起付费复活(无额外掉落展示)
/// </summary>
public async Task<bool> TryPurchasePaidReviveAsync(Event1v1BattleSlapData data)
{
if (!IsAwaitingPaidRevive(data))
return false;
int iapTableId = GetPaidReviveIapTableId(data);
GContext.container.Resolve<PlayerItemData>().ResolveIapId(iapTableId, out var iapItem, out _);
if (iapItem == null)
return false;
var emptyGift = new List<ItemData>();
var buyType = new ShopBuyTypeData
{
type = ShopBuyType.None,
ID = data.EventID,
IsHide = true,
NoShow = true,
extraPurcheData = new JObject
{
["event_slap_paid_revive"] = true,
["slap_paid_revive_index"] = data.SlapPaidReviveCount
}
};
bool ok = await GContext.container.Resolve<PlayerShopData>()
.OnBuy(0, buyType, iapItem, emptyGift);
if (ok)
{
ApplyPaidRevive(data);
SaveData(data);
}
return ok;
}
/// <summary>
/// 处理关卡HP归零
/// </summary>
public void HandleStageHpDepleted(Event1v1BattleSlapData data)
{
Agg_event_slap_stage(data.CurrentRoundForm1, data.CurrentStageID);
data.needSendRewardStageID = data.CurrentStageID;
data.StageHistory.Add(data.CurrentStageID, true);
data.IsOnGoing = false;
if (data.IsFinalStage)
HandleStartNewRound(data);
else
ProceedToNextStage(data);
}
/// <summary>
/// 进入下一关
/// </summary>
public void ProceedToNextStage(Event1v1BattleSlapData data)
{
data.CurrentStageID = data.GetNextStageID();
InitializeStageHp(data);
}
/// <summary>
/// 开始新轮次
/// </summary>
public void HandleStartNewRound(Event1v1BattleSlapData data)
{
data.CurrentRoundForm1++;
InitRound(data);
Debug.Log($"[EventSlap] Starting new round: {data.CurrentRoundForm1}");
}
#endregion
#region
/// <summary>
/// 检查战斗是否已超时30分钟倒计时结束
/// </summary>
public bool IsBattleTimeout(Event1v1BattleSlapData data)
{
if (!data.IsOnGoing || data.BattleStartTime <= 0)
return false;
var nowSeconds = new DateTimeOffset(ZZTimeHelper.UtcNow()).ToUnixTimeSeconds();
return nowSeconds - data.BattleStartTime >= Tables.TbGlobalConfig.EventBattleDuration*60;
}
/// <summary>
/// 处理战斗超时。<paramref name="simulateNpcAttacks"/> 为 true 时先模拟超时期间 NPC 待定攻击,
/// 若能打死玩家则按 NPC 攻击致死结算;否则(或 <paramref name="simulateNpcAttacks"/> 为 false 时)直接判超时负。
/// </summary>
public void HandleBattleTimeout(Event1v1BattleSlapData data, bool simulateNpcAttacks = true)
{
if (!data.IsOnGoing) return;
if (simulateNpcAttacks)
{
Debug.Log($"[EventSlap] Battle timeout! 30min elapsed, simulating NPC attacks during timeout period...");
long battleEndTime = data.BattleStartTime + Tables.TbGlobalConfig.EventBattleDuration * 60;
if (SimulateNpcAttacksDuringTimeout(data, battleEndTime))
return;
}
Debug.Log($"[EventSlap] Battle timeout, settling as timeout defeat.");
Agg_event_slap_timeout(data.PlayerHp, data.NpcHp, data.CurrentRoundForm1, data.CurrentStageID);
_presentationData.Clear();
_presentationData.SyncFromRealData(data);
_presentationData.IsTimeoutDefeat = true;
_presentationData.KilledPlayer = true;
data.PlayerHp = 0;
HandlePlayerHpDepleted(data);
SaveData(data);
ShowFaceTv(data);
}
/// <summary>
/// 模拟超时期间 NPC 的攻击:从 <see cref="Event1v1BattleSlapData.NextAttackTime"/> 到 battleEndTime 逐次模拟。
/// 若 NPC 在此期间打死玩家,填充表现数据为 NPC 攻击致死TV 播 NPC 攻击动画),并完成结算。
/// </summary>
/// <returns>true 表示 NPC 在超时期间打死了玩家已完成结算false 表示玩家存活,调用方应走超时判负</returns>
private bool SimulateNpcAttacksDuringTimeout(Event1v1BattleSlapData data, long battleEndTime)
{
if (data.NextAttackTime <= 0 || data.NextAttackTime > battleEndTime)
return false;
_presentationData.Clear();
_presentationData.SyncFromRealData(data);
int lastDamage = 0;
int lastAttackQuality = 0;
int hpBeforeLastAttack = data.PlayerHp;
long simTime = data.NextAttackTime;
while (simTime <= battleEndTime)
{
hpBeforeLastAttack = data.PlayerHp;
bool isLethal = SimulateStageAttack(data, data.PlayerHp, out int damage, out int attackQuality,
out int attackMultiplier, out bool npcBuffApplied);
lastDamage = damage;
lastAttackQuality = attackQuality;
_presentationData.IsNpcBuffValid = npcBuffApplied;
if (damage >= 0)
Agg_event_slap_attacked(data.CurrentStageID, data.CurrentRoundForm1, attackMultiplier,
npcBuffApplied, isLethal);
if (isLethal)
{
Debug.Log($"[EventSlap] NPC killed player during timeout period at simTime={simTime}.");
data.PlayerHp = 0;
_presentationData.IsNpcAttacking = true;
_presentationData.AttackDamage = lastDamage;
_presentationData.NpcAttackCanQuality = lastAttackQuality;
_presentationData.PlayerHp = hpBeforeLastAttack;
_presentationData.KilledPlayer = true;
HandlePlayerHpDepleted(data);
SaveData(data);
ShowFaceTv(data);
return true;
}
data.PlayerHp -= damage;
int intervalSeconds = data.GetNextAttackInterval();
simTime += intervalSeconds;
}
return false;
}
/// <summary>
/// 在各业务入口调用,检查超时并自动处理
/// </summary>
/// <returns>true 表示已触发超时处理,调用方应终止后续逻辑</returns>
public bool CheckAndHandleBattleTimeout(Event1v1BattleSlapData data, bool simulateNpcAttacks = true)
{
if (!IsBattleTimeout(data)) return false;
HandleBattleTimeout(data, simulateNpcAttacks);
return true;
}
#endregion
#region
public void SendStageReward(Event1v1BattleSlapData data)
{
var change = new ChangeSource(data.EventID, "Event_Minigame4", "Event_Minigame4_1V1Slap", "StageReward");
var config = Tables.TbEventSlapStage.GetOrDefault(data.needSendRewardStageID);
GContext.container.Resolve<PlayerItemData>()
.AddItemByDropLureInflation(data.InflationRate, config.DropId, change);
GContext.Publish(new ShowData());
data.needSendRewardStageID = 0;
SetRedPoint(data);
SaveData(data);
}
#endregion
#region
public async Task ShowSlapPanel(Event1v1BattleSlapData data = null)
{
try
{
if (data == null)
data = LoadData();
var panel = await GetBattleSlapContainer(data);
panel.SetData(this).ShowBattleSlapPanel();
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private async Task<Event1v1BattleSlapContainerPanel> GetBattleSlapContainer(Event1v1BattleSlapData data)
{
if (data?.InitConfig == null) return null;
UITypes.Event1v1BattleSlapPanel.SetType(data.InitConfig.UIPanel);
GameObject obj;
if (!UIManager.Instance.ContainsUI(UITypes.Event1v1BattleSlapPanel))
obj = await UIManager.Instance.ShowUILoad(UITypes.Event1v1BattleSlapPanel);
else
obj = UIManager.Instance.GetContainedUI(UITypes.Event1v1BattleSlapPanel);
var panel = obj.GetComponent<Event1v1BattleSlapContainerPanel>();
if (panel) return panel;
Debug.LogError("[EventSlap] Failed to get Event1v1BattleSlapPanel");
return null;
}
public async Task ShowSlapMatchPanelAndRandom(Event1v1BattleSlapData data)
{
try
{
var panel = await GetBattleSlapContainer(data);
panel.SetData(this).PlayRandomRpcAni();
}
catch (Exception e)
{
Debug.LogError(e);
}
}
#endregion
#region
public async Task<Event1v1BattleSlapTvPopupPanel> ShowTvPanel(Event1v1BattleSlapData data)
{
try
{
if (data?.InitConfig == null) return null;
UITypes.Event1v1BattleSlapTvPopupPanel.SetType(data.InitConfig.TvPopUpPanel);
GameObject obj = await UIManager.Instance.ShowUILoad(UITypes.Event1v1BattleSlapTvPopupPanel);
return obj.GetComponent<Event1v1BattleSlapTvPopupPanel>();
}
catch (Exception e)
{
Debug.LogError(e);
return null;
}
}
public async void ShowLoadingPanel(Event1v1BattleSlapData data)
{
try
{
if (data?.InitConfig == null) return;
UITypes.Event1v1BattleSlapLoadingPanel.SetType(data.InitConfig.LoadingPanel);
var obj = await UIManager.Instance.ShowUILoad(UITypes.Event1v1BattleSlapLoadingPanel);
obj.GetComponent<Event1v1BattleSlapLoadingPanel>()?.SetData(this, data);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public async void ShowStagePanel(Event1v1BattleSlapData data)
{
try
{
if (data?.InitConfig == null) return;
UITypes.Event1v1BattleSlapPreviewPopupPanel.SetType(data.InitConfig.EventPreviewPanel);
await UIManager.Instance.ShowUILoad(UITypes.Event1v1BattleSlapPreviewPopupPanel);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public async void ShowAllStageRewardPanel(Event1v1BattleSlapData data)
{
try
{
if (data?.InitConfig == null) return;
UITypes.Event1v1BattleSlapRewardPopupPanel.SetType(data.InitConfig.RewardPanel);
var panelObj = await UIManager.Instance.ShowUILoad(UITypes.Event1v1BattleSlapRewardPopupPanel);
var panel = panelObj.GetComponent<Event1v1BattleSlapRewardPopupPanel>();
panel.SetData(data);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public void ShowFacePanel(Event1v1BattleSlapData data)
{
if (data == null) return;
UITypes.Event1v1BattleSlapFacePopupPanel.SetType(data.InitConfig.StageReviewPanel);
GContext.container.Resolve<IFaceUIService>()
.AddGiftFaceUI(data.EventID, UITypes.Event1v1BattleSlapFacePopupPanel, 0);
}
#endregion
#region
public async void ShowFestPackPanel()
{
try
{
var data = LoadData();
if (data?.InitConfig == null) return;
var packConfig = Tables.TbEventPackManager.GetOrDefault(data.InitConfig.PackID);
if (packConfig == null)
{
Debug.LogError($"[EventSlap] PackID {data.InitConfig.PackID} not found");
return;
}
var panelObj = await UIManager.Instance.ShowUILoad(new UIType(data.InitConfig.PackPanel));
var panel = panelObj.GetComponent<GeneralEventNormalAndFreePackPanel>();
panel.ShowUI(packConfig, data.EventID, data.IsClaimedFreePack, data.RemainingTime,
() =>
{
data.IsClaimedFreePack = true;
SetRedPoint(data);
SaveData(data);
}, null);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
#endregion
#region
private string GetRedPointKey()
{
return "eventBattleSlap.red";
}
private bool GetRedPointState(Event1v1BattleSlapData data)
{
if (!data.IsActive) return false;
return !data.IsClaimedFreePack || data.IsNeedSendReward;
}
public void SetRedPoint(Event1v1BattleSlapData data)
{
RedPointManager.Instance?.SetRedPointState(GetRedPointKey(), GetRedPointState(data));
}
#endregion
#region
public int GetActivateItemId(Event1v1BattleSlapData data = null)
{
if (data == null)
data = LoadData();
if (!data.IsActive || !data.IsOnGoing) return 0;
float basicProb = Random.Range(0, 1f);
if (!(basicProb <= data.Tables.TbGlobalConfig.EventSlapProb)) return 0;
try
{
var mag = GContext.container.Resolve<PlayerData>().GetMagnification();
var spinachID = data.Tables.TbEventSlapMag.GetSpinachIDFromMag(mag);
return data.GetSpinachConfig(spinachID).FishItemID;
}
catch (Exception e)
{
Debug.LogError($"GetActivateItemId Error: {e.Message}");
return 0;
}
}
#endregion
#region
public List<TargetAddData> AddBombRaidProgress(int multiple)
{
var data = LoadData();
if(!data.IsActive || !data.IsOnGoing) return null;
OnSpinachFromBomb(multiple, data);
var config = data.GetSpinachConfig(Tables.TbEventSlapMag.GetSpinachIDFromMag(multiple));
return config == null ? null : new List<TargetAddData> { new TargetAddData(config.FishItemID, 0, multiple) };
}
public List<TargetAddData> AddFishRaidProgress(int multiple)
{
var data = LoadData();
if(!data.IsActive || !data.IsOnGoing) return null;
OnSpinachFromShoot(data, multiple);
var config = data.GetSpinachConfig(Tables.TbEventSlapMag.GetSpinachIDFromMag(multiple));
return config == null ? null : new List<TargetAddData> { new TargetAddData(config.FishItemID, 0, multiple) };
}
#endregion
#region Agg
private static string GetSpinachSourceAggType(int sourceIndex)
{
return sourceIndex switch
{
SOURCE_BOMB => "bomb",
SOURCE_SHOOT => "raid",
SOURCE_FISHING => "fish",
_ => "unknown"
};
}
/// <summary>
/// event_slap 获得辣椒罐头(发起攻击)时上报
/// </summary>
public void Agg_event_slap(string type, int multiple, int quality, int player_hp, bool is_buff, int enemy_hp)
{
#if AGG
using (var e = GEvent.GameEvent("event_slap"))
{
e.AddContent(nameof(type), type)
.AddContent(nameof(multiple), multiple)
.AddContent(nameof(quality), quality)
.AddContent(nameof(player_hp), player_hp)
.AddContent(nameof(is_buff), is_buff)
.AddContent(nameof(enemy_hp), enemy_hp);
}
#endif
}
/// <summary>
/// event_slap_stage 击败对手时上报
/// </summary>
public void Agg_event_slap_stage(int round, int stage_id)
{
#if AGG
using (var e = GEvent.GameEvent("event_slap_stage"))
{
e.AddContent(nameof(round), round)
.AddContent(nameof(stage_id), stage_id);
}
#endif
}
/// <summary>
/// event_slap_attacked 被对手攻击时上报
/// </summary>
public void Agg_event_slap_attacked(int stage_id, int round, int multiple, bool is_buff, bool is_defeat)
{
#if AGG
using (var e = GEvent.GameEvent("event_slap_attacked"))
{
e.AddContent(nameof(stage_id), stage_id)
.AddContent(nameof(round), round)
.AddContent(nameof(multiple), multiple)
.AddContent(nameof(is_buff), is_buff)
.AddContent(nameof(is_defeat), is_defeat);
}
#endif
}
/// <summary>
/// event_slap_timeout 战斗超时时上报
/// </summary>
public void Agg_event_slap_timeout(int player_hp, int enemy_hp, int round, int stage_id)
{
#if AGG
using (var e = GEvent.GameEvent("event_slap_timeout"))
{
e.AddContent(nameof(player_hp), player_hp)
.AddContent(nameof(enemy_hp), enemy_hp)
.AddContent(nameof(round), round)
.AddContent(nameof(stage_id), stage_id);
}
#endif
}
#endregion
#if UNITY_EDITOR
#region
public void Debug_SetStage(int stageID)
{
var data = LoadData();
data.CurrentStageID = stageID;
data.StageHistory = new Dictionary<int, bool>();
var index = data.GetStageIndex(Tables.TbEventSlapStage.GetOrDefault(stageID));
for (int i = 0; i < index; i++)
{
data.StageHistory.Add(data.GetAllStage()[i].ID, true);
}
InitializeStageHp(data);
SaveData(data);
}
public void Debug_SetLife(int lifeCount)
{
var data = LoadData();
data.PlayerLifeCount = lifeCount;
SaveData(data);
}
#endregion
#endif
}
}