模拟攻击

This commit is contained in:
wuwenbo 2026-03-28 16:15:28 +08:00
parent 28991eefb4
commit c040c58724
2 changed files with 641 additions and 0 deletions

601
.github/skills/th1-logic/SKILL.md vendored Normal file
View File

@ -0,0 +1,601 @@
---
name: th1-logic
description: '用于 TH1 回合制策略游戏逻辑开发。使用场景:新增技能(Skill)、行为(Action)、数据(Data)、逻辑(Logic)的标准开发流程。触发词技能、action、skill、game logic、th1'
user-invocable: true
---
# TH1_Logic 游戏逻辑开发 Skill
> 用于 TH1 回合制策略游戏的逻辑层开发,包括:新增技能(Skill)、行为(Action)、数据(Data)、逻辑(Logic) 的标准开发流程。
---
## 架构概览
```
TH1_Data/ ← 纯数据层 (namespace RuntimeData)
TH1_Logic/
├── Action/ ← 行为系统 (namespace Logic.Action)
├── Skill/ ← 技能系统 (namespace Logic.Skill)
├── Unit/ ← 单位逻辑 (namespace Logic)
├── Player/ ← 玩家逻辑 (namespace Logic)
└── City/ ← 城市逻辑 (namespace Logic)
```
### 核心设计原则
1. **数据与逻辑分离**`RuntimeData` 命名空间放纯数据,逻辑通过 `Main.UnitLogic``Main.PlayerLogic``Main.CityLogic` 静态入口访问
2. **MapData 是中心枢纽**:所有跨实体查询都通过 `MapData` 中转(如 `mapData.GetPlayerDataByUnitId()``mapData.GetGridDataByUnitId()`
3. **IdentifierBase 四大实体**`PlayerData``UnitData``CityData``GridData` 均继承 `IdentifierBase`,拥有 `uint Id``List<SkillBase> Skills`
4. **逻辑-视觉分离**:逻辑操作不直接操控视觉,通过 `Renderer(mapData)?.InstantUpdateXxx()` 间接更新
5. **序列化兼容**:数据类使用 `[MemoryPackable]`,必须提供 `[MemoryPackConstructor]`,私有字段需 `[MemoryPackInclude]`,非序列化字段用 `[MemoryPackIgnore]`
---
## 一、新增技能 (Skill)
### 步骤
#### Step 1`SkillType` 枚举中注册
`SkillBase.cs``SkillType` 枚举末尾(`Max = 999` 之前)新增枚举项。**只能新增,不能删除已有项**。
```csharp
// SkillBase.cs 中的 SkillType 枚举
public enum SkillType
{
// ... 已有技能 ...
NewSkillName = <下一个可用数字>,
Max = 999,
}
```
#### Step 2创建技能类文件
`TH1_Logic/Skill/AllSkill/` 目录下创建 `NewSkillNameSkill.cs`
```csharp
using RuntimeData;
using MemoryPack;
namespace Logic.Skill
{
public partial class NewSkillNameSkill : SkillBase
{
public NewSkillNameSkill()
{
IsPermanent = true; // true=永久技能, false=有回合限制
TurnsLimit = 0; // 如果非永久,设置持续回合数
Score = 2; // AI 评估用的分数权重
// IsLevelSkill = false; // 是否为叠层技能
// _autoDisappear = true; // 叠层为0时是否自动消失
}
public override SkillType GetSkillType()
{
return SkillType.NewSkillName;
}
// === 根据需求选择性重写以下生命周期/判断方法 ===
}
}
```
#### Step 3选择性重写方法
根据技能功能,从以下分类中选择需要重写的方法:
**生命周期类**(事件触发时调用):
| 方法 | 触发时机 | 典型场景 |
|------|---------|---------|
| `OnTurnStart(IdentifierBase self, MapData map)` | 回合开始 | 自动治疗、回合buff |
| `OnAfterTurnStart(IdentifierBase self, MapData map)` | 回合开始之后 | 避免技能时序冲突 |
| `OnTurnEnd(IdentifierBase self, MapData map)` | 回合结束 | 回合结束效果 |
| `OnMove(UnitData self, GridData grid, MapData map, MoveType moveType, ...)` | 移动后 | 冲锋(Dash)、移动触发技能 |
| `BeforeMove(UnitData self, GridData grid, MapData map, MoveType moveType, ...)` | 移动前 | 移动前判断 |
| `BeforeDamagedSupportStage(MapData map, SettlementInfo info)` | 受伤结算前(支援阶段) | 承伤减免 |
| `BeforeDamagedTransformStage(MapData map, SettlementInfo info)` | 受伤结算前(转移阶段) | 伤害转移 |
| `OnDamaged(MapData map, SettlementInfo info)` | 受伤结算时 | 受伤反应 |
| `BeforeDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算前 | 额外伤害 |
| `OnDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算时 | 击杀/转化效果 |
| `AfterDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算后 | 击后效果 |
| `BeforeActiveAttackOther(MapData map, UnitData origin, UnitData target, out int addDmg)` | 主动攻击之前 | 额外伤害加成 |
| `AfterActiveAttackOther(MapData map, UnitData origin, UnitData target)` | 主动攻击之后 | 攻击后效果 |
| `OnSkillAdd(MapData map, uint originId)` | 技能被添加时 | 初始化效果 |
| `OnSkillOverride(MapData map, uint originId, uint selfId)` | 技能被覆盖时 | 叠层技能增层 |
| `OnFinished(IdentifierBase self, MapData map)` | 技能结束(超时/移除)时 | 清理效果 |
**全局事件类**(附着在任意实体上,监听全局事件):
| 方法 | 触发时机 |
|------|---------|
| `OnAnyUnitMove(MapData, IdentifierBase, UnitData, GridData, MoveType)` | 任何单位移动时 |
| `OnAnyUnitDie(MapData, UnitData self, UnitData dieUnit)` | 任何单位死亡时 |
| `OnAnyUnitCreate(MapData, IdentifierBase, UnitData newUnit)` | 任何单位创建时 |
| `BeforeUnitDamaged(UnitData self, MapData, SettlementInfo)` | 任何单位受伤前 |
| `OnUnitDamaged(UnitData self, MapData, SettlementInfo)` | 任何单位受伤时 |
**判断属性类**(影响单位能力判定):
| 方法 | 返回值 | 用途 |
|------|--------|------|
| `IsLimitSelfMove(UnitData, MapData)` | `bool` | 限制自身移动 |
| `IsLimitSelfAttack(UnitData, MapData)` | `bool` | 限制自身攻击 |
| `IsLimitTargetCounterAttack(UnitData, MapData)` | `bool` | 限制敌方反击 |
| `IsLimitSelfCounterAttack(UnitData, MapData)` | `bool` | 限制自身反击 |
| `IsIgnoreControlArea(UnitData, MapData)` | `bool` | 无视敌人控制区 |
| `IsCanMoveOnTerrain(UnitData, MapData, TerrainType)` | `bool` | 能在指定地形移动 |
| `IsCanMoveOnFeature(UnitData, MapData, TerrainFeature)` | `bool` | 能在指定地貌移动 |
| `IsIgnoreMoveLoss(UnitData, MapData, TerrainFeature/Vegetation)` | `bool` | 无视地形移动惩罚 |
| `IsInvisible(UnitData, MapData)` | `bool` | 隐身状态 |
| `CanAttackAll(UnitData, MapData)` | `bool` | 能攻击所有人 |
| `IsCanBeKill(UnitData, MapData)` | `bool` | 能否被击杀 |
**数值修正类**(影响战斗计算):
| 方法 | 返回值 | 用途 |
|------|--------|------|
| `GetAttackAdditionParam(MapData, UnitData, UnitData)` | `float` | 攻击加法修正 |
| `GetAttackMultiplicationParam(MapData, UnitData, UnitData)` | `float` | 攻击乘法修正 (1.0=无修正) |
| `GetDefenseAdditionParam(MapData, UnitData, UnitData)` | `float` | 防御加法修正 |
| `GetDefenseMultiplicationParam(MapData, UnitData, UnitData)` | `float` | 防御乘法修正 |
| `GetExtraSight(UnitData, MapData)` | `int` | 额外视野范围 |
| `GetExtraMoveRange(MapData, UnitData)` | `int` | 额外移动范围 |
| `GetExtraAttackRange(MapData, UnitData)` | `int` | 额外攻击范围 |
| `GetCriticalHitRate(UnitData, MapData)` | `float` | 暴击率 |
**变形保留判断**
| 方法 | 说明 |
|------|------|
| `ReservedOnTransform(UnitData self, UnitFullType fullType)` | 单位类型变换时是否保留此技能 |
| `ReservedOnTransformBoat(UnitData, UnitFullType)` | 陆→船变换时保留 |
| `ReservedOnTransformFromBoat(UnitData, UnitFullType)` | 船→陆变换时保留 |
| `ReservedOnTransformUpgrade(UnitData, UnitFullType)` | 升级变换时保留 |
#### Step 4配置技能数据
在表格配置 `SkillDataAssets` 中注册新技能的 `skillPriority` 等信息。
#### Step 5挂载技能
技能通过 `IdentifierBase` 的方法挂载到实体:
```csharp
// 标准添加
entity.AddOrOverrideSkill(SkillType.NewSkillName, mapData, originId);
// 初始化添加(新建单位时)
entity.AddInitSkill(SkillType.NewSkillName, mapData);
// Legacy 添加(兼容旧逻辑,含回合限制/叠层/强制覆盖)
entity.AddSkill_Legacy(SkillType.NewSkillName, mapData,
IsPermanent, turnLimit, IsLevel, level, autoDisappear,
SpecialAddSkillType.Normal, originId);
```
### 技能示例模板
**简单属性技能攻击力翻倍1回合**
```csharp
public partial class AttackUpSkill : SkillBase
{
public AttackUpSkill()
{
IsPermanent = false;
TurnsLimit = 1;
Score = 2;
}
public override SkillType GetSkillType() => SkillType.ATTACKUP;
public override float GetAttackMultiplicationParam(MapData mapData, UnitData self, UnitData target = null) => 1.5f;
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType) => true;
}
```
**生命周期技能(回合开始治疗周围友军)**
```csharp
public partial class AutoHealSkill : SkillBase
{
public AutoHealSkill()
{
IsPermanent = true;
TurnsLimit = 0;
Score = 2;
}
public override SkillType GetSkillType() => SkillType.AUTOHEAL;
public override void OnTurnStart(IdentifierBase identifier, MapData mapData)
{
var self = identifier as UnitData;
if (self == null) return;
var player = self.Player(mapData);
if (player == null) return;
var selfUnitList = new HashSet<UnitData>();
mapData.GetUnitDataListByPlayerId(player.Id, selfUnitList);
if (!mapData.GetGridDataByUnitId(self.Id, out var targetGrid)) return;
var roundGrid = mapData.GridMap.GetAroundGridData(1, 1, targetGrid);
foreach (var grid in roundGrid)
{
if (!mapData.GetUnitDataByGid(grid.Id, out var unit)) continue;
if (!selfUnitList.Contains(unit)) continue;
unit.Health += 2;
if (unit.Health > unit.GetMaxHealth())
unit.Health = unit.GetMaxHealth();
}
}
}
```
**事件触发技能(移动后保留攻击力)**
```csharp
public partial class DashSkill : SkillBase
{
public bool IsTrigger = false;
public override void OnRefresh() { IsTrigger = false; }
public override void OnTurnStart(IdentifierBase identifier, MapData mapData) { IsTrigger = false; }
public DashSkill() { IsPermanent = true; Score = 4; }
public override SkillType GetSkillType() => SkillType.DASH;
public override void OnDamageOther(MapData mapData, SettlementInfo info)
{
if (info.DamageType != DamageType.ActiveAttack) return;
IsTrigger = true;
}
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType, List<Vector2Int> path = null)
{
if (IsTrigger) return;
if (moveType == MoveType.ActiveMove) self.AP = 1;
}
}
```
---
## 二、新增行为 (Action)
### 行为类型
| ActionType | 说明 | 基类 | 操作主体 |
|-----------|------|------|---------|
| `Build` | 建造建筑 | `BuildAction` | Grid |
| `TrainUnit` | 训练单位 | `TrainUnitAction` | City/Grid |
| `UnitAction` | 单位行动 | `UnitActionAction` | Unit |
| `GridMisc` | 格子杂项 | `GridMiscAction` | Grid |
| `PlayerAction` | 玩家行动 | `PlayerActionAction` | Player |
| `UnitMove` | 单位移动 | `UnitMoveAction` | Unit |
| `UnitAttack` | 单位攻击 | `UnitAttackAction` | Unit |
### 标准开发流程
#### Step 1注册行为子类型枚举如需
在对应枚举中新增,如 `UnitActionType``GridMiscActionType``PlayerActionType`
#### Step 2创建或扩展行为类
每个行为必须实现三个核心方法:
```csharp
public class NewAction : ActionLogicBase
{
public NewAction(CommonActionId id) : base(id) { }
// 执行行为逻辑
protected override bool Execute(CommonActionParams actionParams)
{
// Step 1: 鲁棒性检查
if (!CheckCan(actionParams)) return false;
// Step 2: 扣费/消耗
actionParams.PlayerData.SpendCoin(GetCost(actionParams));
// Step 3: 核心逻辑修改 MapData
// ...
// Step 4: 视觉更新
// grid.Renderer(mapData)?.InstantUpdateGrid(true);
// unit.Renderer(mapData)?.InstantUpdateUnit(true);
// Step 5: 后续逻辑(更新连通性、建筑等级等)
// Main.PlayerLogic.UpdateCityConnect(mapData, playerData);
// Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(mapData, playerId);
return true;
}
// 判断能否执行逻辑严格判断用于AI和玩家操作
public override bool CheckCan(CommonActionParams actionParam)
{
// 1. 鲁棒性MainObjectType检查
// 2. 科技检查playerData.TechTree.CheckActionCan(_actionId)
// 3. 资源检查GetCost() <= playerData.PlayerCoin
// 4. 领土检查:是否在自己领土
// 5. 敌人检查:格子上有无敌人
// 6. 特殊条件
return true;
}
// 判断是否显示在UI中比CheckCan宽松不检查金钱
public override bool CheckShow(CommonActionParams actionParam, out ShowType showType)
{
showType = ShowType.None;
// 类似CheckCan但不检查金钱
return true;
}
}
```
#### Step 3注册到工厂
`ActionLogicFactory` 中注册新行为,使 `GetActionLogic()` 能通过 `CommonActionId` 创建实例。
#### Step 4构建调用参数
```csharp
var actionId = new CommonActionId
{
ActionType = CommonActionType.UnitAction,
UnitActionType = UnitActionType.NewAction
};
var action = ActionLogicFactory.GetActionLogic(actionId);
var param = new CommonActionParams(
mapData: mapData,
playerData: playerData,
unitData: unitData,
gridData: gridData,
mainObjectType: MainObjectType.Unit
);
action?.CompleteExecute(param);
```
### CommonActionParams 参数规范
| 字段 | 类型 | 说明 |
|------|------|------|
| `MapData` | `MapData` | 当前地图数据(必填) |
| `PlayerData` | `PlayerData` | 执行操作的玩家 |
| `UnitData` | `UnitData` | 操作的单位 |
| `CityData` | `CityData` | 操作的城市 |
| `GridData` | `GridData` | 操作的格子 |
| `TargetUnitData` | `UnitData` | 目标单位 |
| `TargetGridData` | `GridData` | 目标格子 |
| `TargetPlayerData` | `PlayerData` | 目标玩家 |
| `MainObjectType` | `MainObjectType` | 操作主体类型(Unit/City/Grid/Player) |
---
## 三、数据类 (Data) 开发规范
### IdentifierBase 继承体系
```
IdentifierBase (uint Id, List<SkillBase> Skills)
├── PlayerData ← 玩家数据
├── UnitData ← 单位数据
├── CityData ← 城市数据
└── GridData ← 格子数据
```
### 数据类必须遵循
1. 标记 `[MemoryPackable]``partial`
2. 提供无参 `[MemoryPackConstructor]` 构造函数
3. 提供拷贝构造函数 `new XxxData(XxxData copyData)` 用于 AI 深拷贝
4. 提供 `DeepCopy(XxxData copyData)` 方法用于复用对象池的深拷贝
5. 如有字典等非序列化字段,提供 `[MemoryPackOnDeserialized]` 标记的后处理方法
6. 视觉标记字段用 `[MemoryPackIgnore]`
### 实体关系查询 (通过 MapData)
```csharp
// Unit → Player
mapData.GetPlayerDataByUnitId(unitId, out PlayerData player);
// Unit → Grid
mapData.GetGridDataByUnitId(unitId, out GridData grid);
// Unit → City
mapData.GetCityDataByUnitId(unitId, out CityData city);
// Grid → Unit
mapData.GetUnitDataByGid(gridId, out UnitData unit);
// Grid → City (领土)
mapData.GetCityDataByTerritoryGid(gridId, out CityData city);
// Grid → Player (领土)
mapData.GetPlayerDataByTerritoryGridId(gridId, out PlayerData player);
// City → Player
mapData.GetPlayerDataByCityId(cityId, out PlayerData player);
// City → Grid (城中心)
mapData.GetGridDataByCityId(cityId, out GridData grid);
// Player → 首都
mapData.GetCapitalCityDataByPlayerId(playerId, out CityData capital);
// 快捷方式从UnitData直接访问
unitData.Player(mapData) // → PlayerData
unitData.Grid(mapData) // → GridData
unitData.City(mapData) // → CityData
```
### MapData 核心子结构
| 字段 | 类型 | 说明 |
|------|------|------|
| `GridMap` | `GridMapData` | 所有格子(按坐标/ID索引 |
| `UnitMap` | `UnitMapData` | 所有单位按ID索引 |
| `CityMap` | `CityMapData` | 所有城市按ID索引 |
| `PlayerMap` | `PlayerMapData` | 所有玩家 |
| `Net` | `NetData` | 网络/回合信息 |
| `MapConfig` | `MapConfig` | 地图配置 |
---
## 四、逻辑层 (Logic) 开发规范
### 入口访问
```csharp
Main.UnitLogic // UnitLogic 实例
Main.PlayerLogic // PlayerLogic 实例
Main.CityLogic // CityLogic 实例
Main.MapData // 当前真实 MapData非 AI 模拟用)
```
### 视觉更新模式
```csharp
// 格子视觉更新
gridData.Renderer(mapData)?.InstantUpdateGrid(true);
// 单位视觉更新
unitData.Renderer(mapData)?.InstantUpdateUnit(true);
// 城市信息视觉更新
cityData.SetCityRenderer(mapData);
cityData.CityInfoRenderer(mapData)?.InstantUpdateCityInfo();
// 播放特效(仅在玩家视野内)
gridData.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
// 判断是否在玩家视野内
gridData.InMainSight()
unitData.InMainSight()
```
### AI 模拟 vs 真实 MapData
```csharp
// 判断是否为真实地图非AI模拟副本
if (mapData == Main.MapData) { /* 只在真实地图执行 */ }
// 判断是否为当前显示的地图
if (mapData.IsCurrentShowMap()) { /* 执行视觉更新 */ }
// AI模拟使用的DeepCopy不拷贝 Players 和 Actions性能优化
```
### 随机数规范
```csharp
// 必须使用 MapData 的确定性随机数(保证网络同步)
var random = mapData.Net.GetRandom(mapData);
var value = random.Next(0, 100);
// 禁止使用 System.Random 或 UnityEngine.Random会导致不同步
```
### 常用操作模板
```csharp
// 视野更新
var gidList = mapData.GridMap.GetAroundGridIdList(sightRange, gridData);
Main.PlayerLogic.UpdateSight_LogicView(mapData, playerData, gidList);
// 城市经验更新
Main.CityLogic.GridGiveCityExp_LogicView(mapData, playerData, gridData, cityData, expAmount);
// 建筑等级刷新
Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(mapData, playerData.Id);
// 城市连通性更新
Main.PlayerLogic.UpdateCityConnect(mapData, playerData);
// 伤害结算
Main.UnitLogic.DamageSettlement(mapData, attacker, target, damage, DamageType.ActiveAttack);
// 单位移动
Main.UnitLogic.MoveToLogic(mapData, unitData, gridData, MoveType.ActiveMove);
// 单位死亡
Main.UnitLogic.UnitUnnaturalDie(mapData, unitData);
// 单位类型变换
Main.UnitLogic.UnitTypeTransform(mapData, unitData, newUnitType);
```
---
## 五、关键枚举速查
### UnitType单位类型
```
Warrior, Archer, Rider, Defender, Swordsman, Catapult, Knight,
Ship, RammerShip, BomberShip, Giant(英雄), Minder, ...
```
### TerrainType地形
```
Land, ShallowSea, DeepSea
```
### TerrainFeature地貌
```
None, Mountain, Road
```
### ResourceType资源/建筑)
```
None, Crop, Metal, Fruit, Animal, Fish,
Farm, Mine, LumberHut, Port, Bridge,
Windmill, Sawmill, Forge, Market, Academy, Military,
Temple, ForestTemple, WaterTemple, MountainTemple, KingTemple,
CityCenter, Wonder, NavalBase, ...
```
### MoveType移动方式
```
ActiveMove, PassiveMove, AttackMove, SkillMove
```
### DamageType伤害类型
```
ActiveAttack, CounterAttack, FollowAttack, Splash, True, KillSelf, DelayAttack, PushAttack
```
---
## 六、开发检查清单
### 新增技能时
- [ ] `SkillType` 枚举已添加(只增不删)
- [ ] 技能类文件在 `AllSkill/` 目录下,类名为 `XxxSkill`,使用 `partial class`
- [ ] 构造函数设置了 `IsPermanent``TurnsLimit``Score`
- [ ] `GetSkillType()` 返回正确的枚举值
- [ ] 如有自定义字段需要序列化,用 `[MemoryPackInclude]` 标记
- [ ] 如有自定义字段需要拷贝,重写 `GetSkillCopy()``DeepCopy()`
- [ ] 表格配置已注册 `skillPriority`
- [ ] 涉及视觉更新时判断了 `mapData == Main.MapData``InMainSight()`
### 新增行为时
- [ ] 行为子类型枚举已添加
- [ ] 实现了 `Execute()``CheckCan()``CheckShow()` 三个方法
- [ ] `Execute()` 开头调用 `CheckCan()` 做鲁棒性保护
- [ ] 已在 `ActionLogicFactory` 注册
- [ ] 金钱消耗使用 `playerData.SpendCoin()`,金钱增加使用 `playerData.AddCoin()`
- [ ] 建设/改造后调用 `UpdateTerritoryAllBuildingLevel``UpdateCityConnect`
### 修改数据类时
- [ ] 新增字段兼容 MemoryPack 序列化
- [ ] 拷贝构造函数和 `DeepCopy()` 已更新
- [ ] `OnAfterMemoryPackDeserialize()` 已更新(如涉及字典)
---
## 七、常见问题
**Q: 技能应该挂到哪个实体上?**
A: 看技能作用范围。影响单个单位→挂UnitData影响玩家全局→挂PlayerData影响城市→挂CityData影响特定格子→挂GridData。
**Q: `CheckCan``CheckShow` 有什么区别?**
A: `CheckCan` 用于实际执行前的完整校验(包括金钱);`CheckShow` 用于UI展示不检查金钱只检查条件是否可见
**Q: 为什么要用 `mapData.GetXxxByYyy()` 而不是直接引用?**
A: 实体间关系是动态的(如单位可以被转化、城市可以被占领),必须通过 MapData 的关系表实时查询。
**Q: AI 模拟时要注意什么?**
A: AI 模拟使用 MapData 的深拷贝副本。不要在非 `Main.MapData` 上执行视觉更新。随机数必须使用 `mapData.Net.GetRandom(mapData)` 保证确定性。

View File

@ -545,6 +545,46 @@ namespace Logic.AI
return CalMap;
}
// 模拟攻击,获取伤害和是否死亡
public static void SimulateAttack(MapData map, UnitData attacker, UnitData defender, out bool isDie, out float dmg)
{
isDie = false;
dmg = 0;
if (attacker == null || defender == null) return;
if (!attacker.IsAlive() || !defender.IsAlive()) return;
// 记录攻击前血量
var defenderHealthBefore = defender.Health;
// 在副本 map 上执行完整攻击 action
RefreshCalMap(map);
CalMap.DeepCopy(map);
var attackId = new CommonActionId { ActionType = CommonActionType.UnitAttack };
var attackAction = new UnitAttackAction(attackId);
if (!CalMap.GetPlayerDataByUnitId(attacker.Id, out var calPlayer)) return;
if (!CalMap.UnitMap.GetUnitDataByUnitId(attacker.Id, out var calAttacker)) return;
if (!CalMap.UnitMap.GetUnitDataByUnitId(defender.Id, out var calDefender)) return;
var param = new CommonActionParams(
mapData: CalMap,
playerData: calPlayer,
unitData: calAttacker,
targetUnit: calDefender,
mainObjectType: MainObjectType.Unit
);
param.OnParamChanged();
attackAction.CompleteExecute(param);
// 读取结果
isDie = !calDefender.IsAlive();
dmg = isDie ? defenderHealthBefore : defenderHealthBefore - calDefender.Health;
}
// 这里的得分是和当前情况的差值
public static AIActionBase CalculateAIActionScore(AICalculatorData data, List<CalculateType> types, out string log)
{