563 lines
18 KiB
C#
563 lines
18 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年04月03日 星期四 11:04:31
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
|
||
|
||
namespace RuntimeData
|
||
{
|
||
// 地图上所有的小兵数据
|
||
[Serializable]
|
||
public class UnitMapData : ISerializationCallbackReceiver
|
||
{
|
||
public List<UnitData> UnitList;
|
||
public Dictionary<uint, UnitData> _unitDict;
|
||
public bool UnitMapRenderMark;
|
||
|
||
|
||
public UnitMapData()
|
||
{
|
||
UnitList = new List<UnitData>();
|
||
_unitDict = new Dictionary<uint, UnitData>();
|
||
UnitMapRenderMark = true;
|
||
}
|
||
|
||
public UnitMapData(UnitMapData copyData)
|
||
{
|
||
UnitList = new List<UnitData>();
|
||
_unitDict = new Dictionary<uint, UnitData>();
|
||
foreach (var unitData in copyData.UnitList)
|
||
{
|
||
var newUnitData = new UnitData(unitData);
|
||
UnitList.Add(newUnitData);
|
||
_unitDict[newUnitData.Id] = newUnitData;
|
||
}
|
||
}
|
||
|
||
public void DeepCopy(UnitMapData copyData)
|
||
{
|
||
_unitDict.Clear();
|
||
|
||
for (int i = 0; i < copyData.UnitList.Count; i++)
|
||
{
|
||
var copyUnit = copyData.UnitList[i];
|
||
if (i >= UnitList.Count)
|
||
{
|
||
var unit = new UnitData(copyUnit);
|
||
UnitList.Add(unit);
|
||
_unitDict[unit.Id] = unit;
|
||
}
|
||
else
|
||
{
|
||
UnitList[i].DeepCopy(copyUnit);
|
||
_unitDict[UnitList[i].Id] = UnitList[i];
|
||
}
|
||
}
|
||
for (int i = UnitList.Count - 1; i >= copyData.UnitList.Count; i--)
|
||
{
|
||
_unitDict.Remove(UnitList[i].Id);
|
||
UnitList.RemoveAt(i);
|
||
}
|
||
}
|
||
|
||
public void OnBeforeSerialize()
|
||
{
|
||
|
||
}
|
||
|
||
public void OnAfterDeserialize()
|
||
{
|
||
_unitDict ??= new Dictionary<uint, UnitData>();
|
||
_unitDict.Clear();
|
||
foreach (var unit in UnitList) _unitDict[unit.Id] = unit;
|
||
}
|
||
|
||
public void OnTurnStart(MapData map, PlayerData player)
|
||
{
|
||
var units = new HashSet<UnitData>();
|
||
map.GetUnitDataListByPlayerId(player.Id, units);
|
||
|
||
foreach (var unit in UnitList)
|
||
{
|
||
if (!units.Contains(unit)) continue;
|
||
unit.OnTurnStart(map);
|
||
}
|
||
}
|
||
|
||
public void OnTurnEnd(MapData map, PlayerData player)
|
||
{
|
||
var units = new HashSet<UnitData>();
|
||
map.GetUnitDataListByPlayerId(player.Id, units);
|
||
|
||
foreach (var unit in UnitList)
|
||
{
|
||
if (!units.Contains(unit)) continue;
|
||
unit.OnTurnEnd(map);
|
||
}
|
||
}
|
||
|
||
// 新建小兵
|
||
public UnitData AddUnitData(UnitType unitType,GiantType giantType, MapIdGenerator idGenerator)
|
||
{
|
||
var unit = new UnitData(unitType, giantType,idGenerator);
|
||
UnitList.Add(unit);
|
||
_unitDict[unit.Id] = unit;
|
||
return unit;
|
||
}
|
||
|
||
// 删除小兵
|
||
public void RemoveUnitData(uint unitId)
|
||
{
|
||
if (_unitDict.TryGetValue(unitId, out var unitData))
|
||
{
|
||
UnitList.Remove(unitData);
|
||
_unitDict.Remove(unitId);
|
||
}
|
||
}
|
||
|
||
// 通过 uid 找小兵数据 unitData
|
||
public bool GetUnitDataByUnitId(uint unitId, out UnitData unitData)
|
||
{
|
||
return _unitDict.TryGetValue(unitId, out unitData);
|
||
}
|
||
}
|
||
|
||
|
||
//小兵数据
|
||
[Serializable]
|
||
public class UnitData : IdentifierBase
|
||
{
|
||
private static System.Random _random;
|
||
|
||
// 是否存活
|
||
public bool Alive;
|
||
// 单位类型
|
||
public UnitType UnitType;
|
||
public GiantType GiantType;
|
||
// 血量
|
||
public int Health;
|
||
|
||
// move points,行动值
|
||
public int MP;
|
||
// attack points,攻击值
|
||
public int AP;
|
||
// capture city points, 城市行动值,用来占领或者建设城市
|
||
public int CP;
|
||
// 攻击范围
|
||
//public int AttackRange;
|
||
// 移动范围
|
||
//public int MoveRange;
|
||
// 经验值
|
||
public int Exp;
|
||
//是否老兵
|
||
public bool Veteran;
|
||
//存储carry的UnitType
|
||
public UnitType CarryUnitType;
|
||
public GiantType CarryGiantType;
|
||
//存储carry的exp状态
|
||
public int CarryExp;
|
||
//存储carry的Veteran状态
|
||
public bool CarryVeteran;
|
||
// 防御削减系数
|
||
public float DefenseReduction;
|
||
|
||
//Attack信息的RenderMakr
|
||
public bool AttackRenderMark;
|
||
public Vector3 AttackRenderMarkTargetPos;
|
||
public bool AttackRenderMarkNeedBack;
|
||
public AttackAnimType AttackRenderMarkAttackAnimType;
|
||
public bool BounceRenderMark;
|
||
public float BounceRenderMakrWaitTime;
|
||
|
||
// 军团编号
|
||
public uint LegionId;
|
||
|
||
|
||
//获取最大血量
|
||
public int GetMaxHealth()
|
||
{
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var unitTypeInfo))
|
||
return 0;
|
||
if (unitTypeInfo.NoMaxHealth)
|
||
{
|
||
if (CarryUnitType != UnitType.None)
|
||
{
|
||
if(Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(CarryUnitType, CarryGiantType,out var carryUnitTypeInfo))
|
||
return carryUnitTypeInfo.MaxHealth + (CarryVeteran?5:0);
|
||
return 0;
|
||
}
|
||
return 0;
|
||
}
|
||
else
|
||
return unitTypeInfo.MaxHealth + (Veteran?5:0);
|
||
}
|
||
|
||
// 获取血量百分比
|
||
public float GetHealthRatio()
|
||
{
|
||
return (float)Health / GetMaxHealth();
|
||
}
|
||
|
||
// 获取军事分
|
||
public float GetMilitary()
|
||
{
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info))
|
||
return 0;
|
||
if (GetHealthRatio() <= 0.5f) return info.Cost * 0.5f;
|
||
return info.Cost;
|
||
}
|
||
|
||
// 获取造价
|
||
public float GetCost()
|
||
{
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info))
|
||
return 0;
|
||
return info.Cost;
|
||
}
|
||
|
||
public UnitData()
|
||
{
|
||
InitData();
|
||
}
|
||
|
||
public UnitData(UnitData copyData)
|
||
{
|
||
Id = copyData.Id;
|
||
Alive = copyData.Alive;
|
||
UnitType = copyData.UnitType;
|
||
GiantType = copyData.GiantType;
|
||
Health = copyData.Health;
|
||
MP = copyData.MP;
|
||
AP = copyData.AP;
|
||
CP = copyData.CP;
|
||
//AttackRange = copyData.AttackRange;
|
||
//MoveRange = copyData.MoveRange;
|
||
Exp = copyData.Exp;
|
||
Veteran = copyData.Veteran;
|
||
CarryUnitType = copyData.CarryUnitType;
|
||
CarryGiantType = copyData.CarryGiantType;
|
||
CarryExp = copyData.CarryExp;
|
||
LegionId = copyData.LegionId;
|
||
CarryVeteran = copyData.CarryVeteran;
|
||
DefenseReduction = copyData.DefenseReduction;
|
||
|
||
foreach (var skill in copyData.Skills) Skills.Add(skill.GetCopySkill());
|
||
}
|
||
|
||
public void DeepCopy(UnitData copyData)
|
||
{
|
||
Id = copyData.Id;
|
||
Alive = copyData.Alive;
|
||
UnitType = copyData.UnitType;
|
||
GiantType = copyData.GiantType;
|
||
Health = copyData.Health;
|
||
MP = copyData.MP;
|
||
AP = copyData.AP;
|
||
CP = copyData.CP;
|
||
//AttackRange = copyData.AttackRange;
|
||
//MoveRange = copyData.MoveRange;
|
||
Exp = copyData.Exp;
|
||
Veteran = copyData.Veteran;
|
||
CarryUnitType = copyData.CarryUnitType;
|
||
CarryGiantType = copyData.CarryGiantType;
|
||
CarryExp = copyData.CarryExp;
|
||
LegionId = copyData.LegionId;
|
||
CarryVeteran = copyData.CarryVeteran;
|
||
DefenseReduction = copyData.DefenseReduction;
|
||
|
||
foreach (var skill in copyData.Skills)
|
||
{
|
||
AddSkill(skill.GetSkillType());
|
||
if (!GetSkill(skill.GetSkillType(), out var selfSkill)) continue;
|
||
selfSkill.DeepCopy(skill);
|
||
}
|
||
for (int i = Skills.Count - 1; i >= 0; i--)
|
||
{
|
||
if (copyData.GetSkill(Skills[i].GetSkillType(), out _)) continue;
|
||
Skills.RemoveAt(i);
|
||
}
|
||
}
|
||
|
||
// 无参数初始化
|
||
public UnitData(UnitType unitType,GiantType giantType, MapIdGenerator idGenerator)
|
||
{
|
||
Id = idGenerator.GeneratorId();
|
||
UnitType = unitType;
|
||
GiantType = giantType;
|
||
InitData();
|
||
}
|
||
|
||
public void InitData()
|
||
{
|
||
if (Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var unitTypeInfo)
|
||
&& unitTypeInfo.NoMaxHealth)
|
||
{
|
||
//TODO 目前 nomaxhealth和carry不是一个概念
|
||
CarryUnitType = UnitType.Warrior;
|
||
}
|
||
|
||
|
||
Alive = true;
|
||
Health = GetMaxHealth();
|
||
MP = 0;
|
||
AP = 0;
|
||
CP = 0;
|
||
//AttackRange = Table.Instance.QueryUnitAttackRange(UnitType);
|
||
//MoveRange = Table.Instance.QueryUnitMoveRange(UnitType);
|
||
Exp = 0;
|
||
Veteran = false;
|
||
LegionId = 0;
|
||
}
|
||
|
||
// 全局通知调用
|
||
public void OnTurnStart(MapData map)
|
||
{
|
||
for (int i = Skills.Count - 1; i >= 0; i--)
|
||
{
|
||
var skill = Skills[i];
|
||
if (skill.IsFinished())
|
||
{
|
||
Skills.RemoveAt(i);
|
||
continue;
|
||
}
|
||
skill.OnTurnStart(this, map);
|
||
}
|
||
}
|
||
|
||
public void OnTurnEnd(MapData map)
|
||
{
|
||
foreach (var skill in Skills)skill.OnTurnEnd(this, map);
|
||
}
|
||
|
||
//返回该unit的视野半径
|
||
public int GetSightRange()
|
||
{
|
||
if (Table.Instance.QueryUnitHasSkill(UnitType, SkillType.SCOUT))
|
||
return 2;
|
||
return 1;
|
||
}
|
||
|
||
public int GetAttackRange()
|
||
{
|
||
return Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType,GiantType,out var info)?info.AttackRange:0;
|
||
}
|
||
|
||
public int GetMoveRange()
|
||
{
|
||
return Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType,GiantType,out var info)?info.MoveRange:0;
|
||
}
|
||
|
||
//返回该unit是否是静止经验(无法升级)
|
||
public bool CheckIsStaticExp(MapData map)
|
||
{
|
||
var isStaticExp = false;
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsLimitSelfExp(this, map)) isStaticExp = true;
|
||
}
|
||
|
||
return Table.Instance.QueryUnitHasSkill(UnitType, SkillType.STATIC) || isStaticExp;
|
||
}
|
||
|
||
//返回该unit处于城市的时候是否获得防御加成
|
||
public bool CheckIsCityDefenseBuff()
|
||
{
|
||
return Table.Instance.QueryUnitHasSkill(UnitType, SkillType.FORTIFY);
|
||
}
|
||
|
||
//LandAndPort, WaterAndAshore, LandAndWater, WaterOnly, LandOnly, Fly
|
||
public LandType GetLandType()
|
||
{
|
||
return Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType,GiantType,out var Info)?Info.LandType:LandType.None;
|
||
}
|
||
|
||
// 获得总攻击力的string显示
|
||
public string GetAttackShowString(MapData map)
|
||
{
|
||
if(!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info)) return "0";
|
||
float extra = GetExtraAttack(map);
|
||
if (extra == 1f)
|
||
return info.Attack.ToString();
|
||
return info.Attack + "(×" + extra + ")";
|
||
}
|
||
|
||
// 获得总攻击力
|
||
public float GetAttackValue(MapData map)
|
||
{
|
||
if(!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info)) return 0;
|
||
if (IsCriticalHitRate(map)) return info.Attack * GetExtraAttack(map) * 2;
|
||
return info.Attack * GetExtraAttack(map);
|
||
}
|
||
|
||
// 获取总防御力的string显示
|
||
public string GetDefenseShowString(MapData map)
|
||
{
|
||
if(!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info)) return "0";
|
||
float extra = GetExtraDefense(map);
|
||
if (extra == 1f)
|
||
return info.Defense.ToString();
|
||
return info.Defense + "(×" + extra + ")";
|
||
}
|
||
|
||
// 获取总防御力
|
||
public float GetDefenseValue(MapData map)
|
||
{
|
||
if(!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(UnitType, GiantType, out var info)) return 0;
|
||
return info.Defense * GetExtraDefense(map);
|
||
}
|
||
|
||
// 获取防御倍数
|
||
public float GetExtraDefense(MapData map)
|
||
{
|
||
var extraDefense = 1f;
|
||
foreach (var skill in Skills)
|
||
{
|
||
extraDefense *= skill.GetExtraDefense(this, map);
|
||
}
|
||
return extraDefense;
|
||
}
|
||
|
||
// 获取攻击倍数
|
||
public float GetExtraAttack(MapData map)
|
||
{
|
||
var extraAttack = 1f;
|
||
foreach (var skill in Skills) extraAttack *= skill.GetExtraAttack(this, map);
|
||
return extraAttack;
|
||
}
|
||
|
||
// 获取暴击率
|
||
public float GetCriticalHitRate(MapData map)
|
||
{
|
||
var criticalHitRate = 0f;
|
||
foreach (var skill in Skills) criticalHitRate += skill.GetCriticalHitRate(this, map);
|
||
return criticalHitRate;
|
||
}
|
||
|
||
|
||
// 是否能移动到伟人身周
|
||
public bool IsCanMoveGiantNearbyGrid(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanMoveGiantNearbyGrid(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 是否拥有特别伤害技能,例如ROYALFLAME
|
||
public bool IsHasAttackSkill(MapData map)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 是否能移动无人城市
|
||
public bool IsCanMoveNoUnitSelfCity(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanMoveToNoUnitSelfCity(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
// 是否可以死亡
|
||
public bool CanBeKilled(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanBeKill(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 是否可以被伤害结算
|
||
public bool CanBeDamaged(MapData map, int dmg)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanBeDamaged(this, map, dmg)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 是否经验锁定
|
||
public bool IsExpLock(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsLimitSelfExp(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//返回该unit是否能在terrain上移动
|
||
public bool IsCanMoveOnTerrain(MapData map, TerrainType terrainType)
|
||
{
|
||
var landType = GetLandType();
|
||
if (terrainType == TerrainType.Land && (landType == LandType.LandAndPort || landType == LandType.LandOnly))
|
||
return true;
|
||
if (terrainType == TerrainType.ShallowSea && (landType == LandType.WaterAndAshore || landType == LandType.WaterOnly))
|
||
return true;
|
||
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanMoveOnTerrain(this, map, terrainType)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//返回该unit是否能在feature上移动
|
||
public bool IsCanMoveOnFeature(MapData map, TerrainFeature featureType)
|
||
{
|
||
if (featureType != TerrainFeature.Mountain)
|
||
return true;
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsCanMoveOnFeature(this, map,featureType)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//返回该unit是否能反击
|
||
public bool IsLimitSelfCounterAttack(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsLimitSelfCounterAttack(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//返回目标 unit 是否能反击
|
||
public bool IsLimitTargetCounterAttack(MapData map)
|
||
{
|
||
foreach (var skill in Skills)
|
||
{
|
||
if (skill.IsLimitTargetCounterAttack(this, map)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 是否能暴击
|
||
private bool IsCriticalHitRate(MapData map)
|
||
{
|
||
if (_random == null) _random = new System.Random();
|
||
return _random.NextDouble() <= GetCriticalHitRate(map);
|
||
}
|
||
|
||
// 移动后的变化
|
||
public void OnMove(MapData map, GridData target, MoveType moveType)
|
||
{
|
||
foreach (var skill in Skills)
|
||
skill.OnMove(this, target, map,moveType);
|
||
}
|
||
}
|
||
|
||
} |