Traveller/scripts/character/AiCharacter.cs
Cold-Mint a7a5a309a2
Migrate TileMap to TileMapLayer. Development using version 4.3 of Godot.
迁移TileMap到TileMapLayer。使用4.3版本Godot进行研发。
2024-07-26 22:38:18 +08:00

498 lines
15 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 ColdMint.scripts.bubble;
using ColdMint.scripts.camp;
using ColdMint.scripts.inventory;
using ColdMint.scripts.stateMachine;
using ColdMint.scripts.utils;
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.character;
/// <summary>
/// <para>The role played by computers</para>
/// <para>由电脑扮演的角色</para>
/// </summary>
public sealed partial class AiCharacter : CharacterTemplate
{
//Used to detect rays on walls
//用于检测墙壁的射线
private RayCast2D? _wallDetection;
private Vector2 _wallDetectionOrigin;
private Area2D? _attackArea;
/// <summary>
/// <para>Reconnaissance area</para>
/// <para>侦察区域</para>
/// </summary>
/// <remarks>
///<para>Most of the time, when the enemy enters the reconnaissance area, the character will issue a "question mark" and try to move slowly towards the event point.</para>
///<para>大多数情况下,当敌人进入侦察区域后,角色会发出“疑问(问号)”,并尝试向事件点缓慢移动。</para>
/// </remarks>
private Area2D? _scoutArea;
/// <summary>
/// <para>All enemies within striking distance</para>
/// <para>在攻击范围内的所有敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheAttackRange;
/// <summary>
/// <para>Scout all enemies within range</para>
/// <para>在侦察范围内所有的敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheScoutRange;
/// <summary>
/// <para>Every weapon in the recon area</para>
/// <para>在侦察范围内所有的武器</para>
/// </summary>
private List<WeaponTemplate>? _weaponInTheScoutRange;
/// <summary>
/// <para>Obstacle detection ray during attack</para>
/// <para>攻击时的障碍物检测射线</para>
/// </summary>
/// <remarks>
///<para></para>
///<para>检测与目标点直接是否间隔墙壁</para>
/// </remarks>
private RayCast2D? _attackObstacleDetection;
private VisibleOnScreenEnabler2D? _screenEnabler2D;
/// <summary>
/// <para>Navigation agent</para>
/// <para>导航代理</para>
/// </summary>
private NavigationAgent2D? NavigationAgent2D { get; set; }
/// <summary>
/// <para>State machine</para>
/// <para>状态机</para>
/// </summary>
private IStateMachine? StateMachine { get; set; }
/// <summary>
/// <para>Attack obstacle detection</para>
/// <para>攻击障碍物检测</para>
/// </summary>
private RayCast2D? AttackObstacleDetection => _attackObstacleDetection;
/// <summary>
/// <para>Exclamation bubble id</para>
/// <para>感叹气泡Id</para>
/// </summary>
private const int PlaintBubbleId = 0;
/// <summary>
/// <para>Query bubble id</para>
/// <para>疑问气泡Id</para>
/// </summary>
private const int QueryBubbleId = 1;
/// <summary>
/// <para>BubbleMarker</para>
/// <para>气泡标记</para>
/// </summary>
/// <remarks>
///<para>Subsequent production of dialogue bubbles can be put into the parent class for players to use.</para>
///<para>后续制作对话泡时可进其放到父类,供玩家使用。</para>
/// </remarks>
private BubbleMarker? _bubbleMarker;
/// <summary>
/// <para>The initial weapons scene</para>
/// <para>初始的武器场景</para>
/// </summary>
[Export] public string? InitWeaponRes;
public override void _Ready()
{
base._Ready();
_enemyInTheAttackRange = new List<CharacterTemplate>();
_enemyInTheScoutRange = new List<CharacterTemplate>();
_weaponInTheScoutRange = new List<WeaponTemplate>();
_screenEnabler2D = GetNode<VisibleOnScreenEnabler2D>("VisibleOnScreenEnabler2D");
_screenEnabler2D.ScreenEntered += () =>
{
//When the character enters the screen.
//当角色进入屏幕。
ProcessMode = ProcessModeEnum.Disabled;
};
_screenEnabler2D.ScreenExited += () =>
{
//When the character leaves the screen.
//当角色离开屏幕。
ProcessMode = ProcessModeEnum.Inherit;
};
_bubbleMarker = GetNode<BubbleMarker>("BubbleMarker");
if (_bubbleMarker != null)
{
using var plaintScene = GD.Load<PackedScene>("res://prefab/ui/plaint.tscn");
var plaint = NodeUtils.InstantiatePackedScene<Control>(plaintScene);
if (plaint != null)
{
_bubbleMarker.AddBubble(PlaintBubbleId, plaint);
}
using var queryScene = GD.Load<PackedScene>("res://prefab/ui/query.tscn");
var query = NodeUtils.InstantiatePackedScene<Control>(queryScene);
if (query != null)
{
_bubbleMarker.AddBubble(QueryBubbleId, query);
}
}
_wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D");
_scoutArea = GetNode<Area2D>("ScoutArea2D");
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
if (ItemMarker2D != null)
{
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
}
if (_attackArea != null)
{
//If true, the zone will detect objects or areas entering and leaving the zone.
//如果为true该区域将检测进出该区域的物体或区域。
_attackArea.Monitoring = true;
//Other areas can't detect our attack zone
//其他区域不能检测到我们的攻击区域
_attackArea.Monitorable = false;
_attackArea.BodyEntered += EnterTheAttackArea;
_attackArea.BodyExited += ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.Monitoring = true;
_scoutArea.Monitorable = false;
_scoutArea.BodyEntered += EnterTheScoutArea;
_scoutArea.BodyExited += ExitTheScoutArea;
}
_wallDetectionOrigin = _wallDetection.TargetPosition;
StateMachine = new PatrolStateMachine();
StateMachine.Context = new StateContext
{
CurrentState = State.Patrol,
Owner = this
};
if (StateMachine != null)
{
StateMachine.Start();
}
//You must create an item container for the character before you can pick up the item.
//必须为角色创建物品容器后才能拾起物品。
var universalItemContainer = new UniversalItemContainer();
var itemSlotNode = universalItemContainer.AddItemSlot(this);
itemSlotNode?.Hide();
ProtectedItemContainer = universalItemContainer;
//Add initial weapon
//添加初始武器
AddInitialWeapon(InitWeaponRes);
}
/// <summary>
/// <para>Adds an initial weapon to the character</para>
/// <para>为角色添加初始的武器</para>
/// </summary>
private void AddInitialWeapon(string? initWeaponRes)
{
if (initWeaponRes == null)
{
return;
}
//Set the resource path of the initial weapon and try to create the object of the initial weapon.
//设置了初始武器的资源路径,尝试创建初始武器的对象。
var packedScene = GD.Load<PackedScene>(initWeaponRes);
var weaponTemplate = NodeUtils.InstantiatePackedScene<WeaponTemplate>(packedScene);
if (weaponTemplate == null)
{
return;
}
NodeUtils.CallDeferredAddChild(this, weaponTemplate);
PickItem(weaponTemplate);
}
/// <summary>
/// <para>Display exclamation marks</para>
/// <para>显示感叹号</para>
/// </summary>
public void DispladyPlaint()
{
_bubbleMarker?.ShowBubble(PlaintBubbleId);
}
public void HidePlaint()
{
_bubbleMarker?.HideBubble(PlaintBubbleId);
}
/// <summary>
/// <para>Displady Query</para>
/// <para>显示疑问</para>
/// </summary>
public void DispladyQuery()
{
_bubbleMarker?.ShowBubble(QueryBubbleId);
}
public void HideQuery()
{
_bubbleMarker?.HideBubble(QueryBubbleId);
}
/// <summary>
/// <para>Whether the enemy has been detected in the reconnaissance area</para>
/// <para>侦察范围是否发现敌人</para>
/// </summary>
/// <returns>
///<para>Have you spotted the enemy?</para>
///<para>是否发现敌人</para>
/// </returns>
public bool ScoutEnemyDetected()
{
if (_enemyInTheScoutRange == null)
{
return false;
}
return _enemyInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Any weapons found in the recon area</para>
/// <para>侦察范围内是否发现武器</para>
/// </summary>
/// <returns></returns>
public bool ScoutWeaponDetected()
{
if (_weaponInTheScoutRange == null)
{
return false;
}
return _weaponInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Get weapons in the recon area</para>
/// <para>获取侦察范围内的武器</para>
/// </summary>
/// <returns></returns>
public WeaponTemplate[]? GetWeaponInScoutArea()
{
if (_weaponInTheScoutRange == null)
{
return null;
}
return _weaponInTheScoutRange.ToArray();
}
/// <summary>
/// <para>Get the first enemy in range</para>
/// <para>获取第一个进入侦察范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInScoutArea()
{
if (_enemyInTheScoutRange == null || _enemyInTheScoutRange.Count == 0)
{
return null;
}
return _enemyInTheScoutRange[0];
}
/// <summary>
/// <para>Get the first enemy within striking range</para>
/// <para>获取第一个进入攻击范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInAttackArea()
{
if (_enemyInTheAttackRange == null || _enemyInTheAttackRange.Count == 0)
{
return null;
}
return _enemyInTheAttackRange[0];
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
StateMachine?.Execute();
if (NavigationAgent2D != null && IsOnFloor())
{
var nextPathPosition = NavigationAgent2D.GetNextPathPosition();
var direction = (nextPathPosition - GlobalPosition).Normalized();
velocity = direction * Config.CellSize * Speed * ProtectedSpeedScale;
}
}
/// <summary>
/// <para>When the node enters the reconnaissance area</para>
/// <para>当节点进入侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheScoutArea(Node node)
{
if (node is WeaponTemplate weaponTemplate)
{
if (CanPickItem(weaponTemplate))
{
_weaponInTheScoutRange?.Add(weaponTemplate);
}
}
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheScoutRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>When the node exits the reconnaissance area</para>
/// <para>当节点退出侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void ExitTheScoutArea(Node node)
{
if (node == this)
{
return;
}
if (node is WeaponTemplate weaponTemplate)
{
_weaponInTheScoutRange?.Remove(weaponTemplate);
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheScoutRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>When a node enters the attack zone</para>
/// <para>当节点进入攻击区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheAttackArea(Node node)
{
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheAttackRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>CanCauseHarmNode</para>
/// <para>是否可伤害某个节点</para>
/// </summary>
/// <param name="node"></param>
/// <param name="action"></param>
private void CanCauseHarmNode(Node node, Action<bool, CharacterTemplate?> action)
{
if (node == this)
{
//The target can't be yourself.
//攻击目标不能是自己。
action.Invoke(false, null);
return;
}
if (node is not CharacterTemplate characterTemplate)
{
action.Invoke(false, null);
return;
}
//Determine if damage can be done between factions
//判断阵营间是否可造成伤害
var camp = CampManager.GetCamp(CampId);
var enemyCamp = CampManager.GetCamp(characterTemplate.CampId);
if (enemyCamp != null && camp != null)
{
action.Invoke(CampManager.CanCauseHarm(camp, enemyCamp), characterTemplate);
return;
}
action.Invoke(false, characterTemplate);
}
private void ExitTheAttackArea(Node node)
{
if (node == this)
{
return;
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheAttackRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>Set target location</para>
/// <para>设置目标位置</para>
/// </summary>
/// <param name="targetPosition"></param>
public void SetTargetPosition(Vector2 targetPosition)
{
if (NavigationAgent2D == null)
{
return;
}
NavigationAgent2D.TargetPosition = targetPosition;
}
public override void _ExitTree()
{
base._ExitTree();
if (_attackArea != null)
{
_attackArea.BodyEntered -= EnterTheAttackArea;
_attackArea.BodyExited -= ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.BodyEntered -= EnterTheScoutArea;
_scoutArea.BodyExited -= ExitTheScoutArea;
}
if (StateMachine != null)
{
StateMachine.Stop();
}
}
}