Traveller/scripts/character/CharacterTemplate.cs

544 lines
17 KiB
C#
Raw Normal View History

2024-04-28 13:55:19 +00:00
using System;
using System.Collections.Generic;
using ColdMint.scripts.camp;
using ColdMint.scripts.damage;
using ColdMint.scripts.debug;
2024-04-28 13:55:19 +00:00
using ColdMint.scripts.health;
using ColdMint.scripts.inventory;
using ColdMint.scripts.utils;
2024-04-28 13:55:19 +00:00
using ColdMint.scripts.weapon;
using Godot;
namespace ColdMint.scripts.character;
/// <summary>
/// <para>CharacterTemplate</para>
/// <para>角色模板</para>
/// </summary>
/// <remarks>
///<para>Behavior shared by all characters</para>
///<para>所有角色共有的行为</para>
/// </remarks>
public partial class CharacterTemplate : CharacterBody2D
{
// Get the gravity from the project settings to be synced with RigidBody nodes.
// 从项目设置中获取与RigidBody节点同步的重力。
protected float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
protected const float Speed = 300.0f;
2024-04-28 13:55:19 +00:00
protected const float JumpVelocity = -240;
2024-04-28 13:55:19 +00:00
public string? ReadOnlyCharacterName => CharacterName;
protected string? CharacterName;
//Item containers are used to store items.
//物品容器用于存储物品。
private IItemContainer? _itemContainer;
public IItemContainer? ItemContainer
{
get => _itemContainer;
set => _itemContainer = value;
}
//Items currently held
//当前持有的物品
private Node2D? _currentItem;
public Node2D? CurrentItem
{
get => _currentItem;
set
{
_currentItem = value;
WhenUpdateCurrentItem(_currentItem);
}
}
/// <summary>
/// <para>When the items the character holds are updated</para>
/// <para>当角色持有的物品更新时</para>
/// </summary>
/// <param name="currentItem">
///<para>Update finished items</para>
///<para>更新完成后的物品</para>
/// </param>
protected virtual void WhenUpdateCurrentItem(Node2D? currentItem)
{
}
//Define a pick up range
//定义一个拾起范围
private Area2D? _pickingArea;
private AnimatedSprite2D? _animatedSprite2D;
//A marker that defines the location of the item
//一个标记,定义物品的位置
protected Marker2D? ItemMarker2D;
//The original X-coordinate of the item marker
//物品标记的原始X坐标
private float _itemMarkerOriginalX;
protected float ReadOnlyItemMarkerOriginalX => _itemMarkerOriginalX;
//Face left?
//面向左边吗
public bool FacingLeft = false;
//The force added by the AddForce method
//由AddForce方法追加的力
private Vector2 _additionalForce = Vector2.Zero;
protected int CurrentHp;
//The initial health of the character after creation
//角色创建后的初始血量
private int _initialHp;
protected int MaxHp;
/// <summary>
/// <para>The camp ID of the role</para>
/// <para>角色的阵营ID</para>
/// </summary>
public string CampId = null!;
private DamageNumberNodeSpawn? _damageNumber;
private HealthBar? _healthBar;
private DateTime _lastDamageTime;
/// <summary>
/// <para>Pick up all items within range</para>
/// <para>拾捡范围内的所有物品</para>
/// </summary>
protected List<Node>? PickingRangeBodiesList;
public Node[] PickingRangeBodies => PickingRangeBodiesList?.ToArray() ?? Array.Empty<Node>();
/// <summary>
/// <para>Find the nearest item within the pick up area(Does not include items currently held)</para>
/// <para>在拾捡范围内查找距离最近的物品(不包括当前持有的物品)</para>
/// </summary>
/// <returns></returns>
public Node2D? FindTheNearestItem()
{
if (PickingRangeBodiesList == null || PickingRangeBodiesList.Count == 0)
{
return null;
}
HashSet<Node>? exclude = null;
if (_currentItem != null)
{
//Prevent picking up objects in your hands again.
//防止再次捡起自己手上的物品。
exclude = new HashSet<Node> { _currentItem };
}
return NodeUtils.GetTheNearestNode(this, PickingRangeBodiesList.ToArray(), exclude);
}
/// <summary>
/// <para>Get all weapons within range of the pick up</para>
/// <para>获取所有在拾捡范围内的武器</para>
/// </summary>
/// <returns></returns>
public WeaponTemplate[] GetCanPickedWeapon()
{
var weaponTemplates = new List<WeaponTemplate>();
foreach (var pickingRangeBody in PickingRangeBodies)
{
if (pickingRangeBody is not WeaponTemplate weaponTemplate) continue;
if (weaponTemplate.Owner != null)
{
continue;
}
weaponTemplates.Add(weaponTemplate);
}
return weaponTemplates.ToArray();
}
public override void _Ready()
{
base._Ready();
PickingRangeBodiesList = new List<Node>();
CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
if (MaxHp <= 0)
{
//If Max blood volume is 0 or less, set Max blood volume to 10
//若最大血量为0或小于0则将最大血量设置为10
MaxHp = Config.DefaultMaxHp;
}
_initialHp = GetMeta("InitialHp", "0").AsInt32();
if (_initialHp <= 0 || _initialHp > MaxHp)
{
//If the initial health is less than or equal to 0 or greater than the maximum health, then set it to the maximum health
//如果初始血量小于等于0或者大于最大血量那么将其设置为最大血量
_initialHp = MaxHp;
}
CurrentHp = _initialHp;
//The health bar of a creature may be null.
//生物的健康条可能为null。
_healthBar = GetNodeOrNull<HealthBar>("HealthBar");
if (_healthBar != null)
{
_healthBar.MaxValue = MaxHp;
}
ItemMarker2D = GetNode<Marker2D>("ItemMarker2D");
_itemMarkerOriginalX = ItemMarker2D.Position.X;
_animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
_pickingArea = GetNode<Area2D>("Area2DPickingArea");
_damageNumber = GetNode<Marker2D>("DamageNumber") as DamageNumberNodeSpawn;
if (_pickingArea != null)
{
//If true, the zone will detect objects or areas entering and leaving the zone.
//如果为true该区域将检测进出该区域的物体或区域。
_pickingArea.Monitoring = true;
//Other regions cannot detect our pick region
//其他区域不能检测到我们的拾取区域
_pickingArea.Monitorable = false;
_pickingArea.BodyEntered += EnterThePickingRangeBody;
_pickingArea.BodyExited += ExitThePickingRangeBody;
}
}
/// <summary>
/// <para>Pick up the specified items</para>
/// <para>将指定物品拾起来</para>
/// </summary>
/// <param name="pickAbleItem"></param>
/// <returns>
///<para>Whether successfully picked up</para>
///<para>是否成功拾起</para>
/// </returns>
public bool PickItem(Node2D? pickAbleItem)
{
if (pickAbleItem == null)
{
return false;
}
if (_itemContainer == null)
{
return false;
}
//Get the currently selected node
//拿到当前选择的节点
var itemSlotNode = _itemContainer.GetSelectItemSlotNode();
if (itemSlotNode == null)
{
return false;
}
if (pickAbleItem is not IItem item)
{
return false;
}
//First check if we can pick up the item.
//先检查我们能否拾起此物品。
var canPick = _itemContainer.CanAddItem(item);
if (!canPick)
{
return false;
}
//Is it successfully added to the container?
//再检查是否成功的添加到容器内了?
var addSuccess = _itemContainer.AddItem(item);
if (!addSuccess)
{
return false;
}
//Set up routine handling of picked up items.
//设置捡起物品的常规处理。
//You can supplement picking up state handling for more types of objects here.
//您可以在这里补充更多类型对象的捡起状态处理。
if (pickAbleItem is WeaponTemplate weaponTemplate)
{
weaponTemplate.Owner = this;
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, false);
weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, false);
weaponTemplate.EnableContactInjury = false;
weaponTemplate.Sleeping = true;
}
if (itemSlotNode.GetItem() != null && itemSlotNode.GetItem() == item && _currentItem == null)
{
//If the selected item slot in the item container is a newly picked item, and there is no item in the hand, then we put the selected item into the hand.
//如果物品容器内选中的物品槽是刚刚捡到的物品,且手里没有物品持有,那么我们将选中的物品放到手上。
CurrentItem = pickAbleItem;
}
else
{
pickAbleItem.Visible = false;
pickAbleItem.ProcessMode = ProcessModeEnum.Disabled;
}
pickAbleItem.Reparent(ItemMarker2D);
pickAbleItem.Position = Vector2.Zero;
return true;
}
/// <summary>
/// <para>Use what you have in your hand</para>
/// <para>使用手中的物品</para>
/// </summary>
public bool UseItem(Vector2 position)
{
if (_currentItem == null)
{
return false;
}
if (_currentItem is WeaponTemplate weaponTemplate)
{
weaponTemplate.Fire(this, position);
}
return true;
}
public override void _Process(double delta)
{
base._Process(delta);
//If the time difference between the last injury and the current time is greater than the time displayed in the health bar, the health bar is hidden
//如果上次受到伤害的时间与当前时间的时间差大于健康条显示时间,则隐藏健康条
var timeSpan = DateTime.Now - _lastDamageTime;
if (timeSpan > Config.HealthBarDisplaysTime)
{
if (_healthBar != null)
{
_healthBar.Visible = false;
}
}
}
/// <summary>
/// <para>Update the role's health bar</para>
/// <para>更新角色的健康条</para>
/// </summary>
private void UpDataHealthBar(DamageTemplate damageTemplate)
{
if (_healthBar == null)
{
return;
}
if (GameSceneNodeHolder.Player == null)
{
//We didn't know who the player was, so we showed it as a hostile color
//我们不知道玩家是谁,所以我们将其显示为敌对颜色
_healthBar.SetEnemyTones();
}
else
{
//If we set up a player node, then compare the injured party ID to the player's party ID
//如果我们设置了玩家节点那么将受伤者的阵营ID与玩家的阵营ID进行比较
var targetCamp = CampManager.GetCamp(CampId);
var playerCamp = CampManager.GetCamp(GameSceneNodeHolder.Player.CampId);
if (CampManager.CanCauseHarm(targetCamp, playerCamp))
{
if (targetCamp != null && playerCamp != null)
{
if (targetCamp.Id == playerCamp.Id)
{
//If an attack is allowed and you are on the same side, it is displayed as a friendly color (friend damage).
//如果允许攻击,且属于同一阵营,则显示为友好颜色(友伤)
_healthBar.SetFriendlyTones();
}
else
{
//If the injured target is an enemy of the player, it is displayed as an enemy color
//如果受伤的目标是玩家的敌人,则显示为敌对颜色
_healthBar.SetEnemyTones();
}
}
}
else
{
_healthBar.SetFriendlyTones();
}
}
_healthBar.Visible = true;
_healthBar.Value = CurrentHp;
}
/// <summary>
/// <para>Deal damage to the character</para>
/// <para>对角色造成伤害</para>
/// </summary>
/// <param name="damageTemplate">
///<para>Damage template</para>
///<para>伤害模板</para>
/// </param>
/// <returns>
///<para>Return whether the damage was done successfully</para>
///<para>返回是否成功造成了伤害</para>
/// </returns>
public bool Damage(DamageTemplate damageTemplate)
{
_lastDamageTime = DateTime.Now;
_damageNumber?.Display(damageTemplate);
CurrentHp -= damageTemplate.Damage;
OnHit(damageTemplate);
if (CurrentHp <= 0)
{
//Character death
//角色死亡
OnDie(damageTemplate);
return true;
}
UpDataHealthBar(damageTemplate);
return true;
}
/// <summary>
/// <para>Add power to the character</para>
/// <para>在角色身上添加力</para>
/// </summary>
/// <param name="force"></param>
public void AddForce(Vector2 force)
{
_additionalForce = force;
}
protected virtual void OnHit(DamageTemplate damageTemplate)
{
}
/// <summary>
/// <para>Handle the event of character death</para>
/// <para>处理角色死亡的事件</para>
/// </summary>
/// <param name="damageTemplate"></param>
protected virtual void OnDie(DamageTemplate damageTemplate)
{
//If the attacker is not empty and the role name is not empty, then the role death message is printed
//如果攻击者不为空,且角色名不为空,那么打印角色死亡信息
if (damageTemplate.Attacker != null && !string.IsNullOrEmpty(CharacterName))
{
if (damageTemplate.Attacker is CharacterTemplate characterTemplate &&
!string.IsNullOrEmpty(characterTemplate.CharacterName))
{
LogCat.LogWithFormat("death_info", CharacterName, characterTemplate.CharacterName);
}
else
{
LogCat.LogWithFormat("death_info", CharacterName, damageTemplate.Attacker.Name);
}
}
QueueFree();
}
/// <summary>
/// <para>When an object enters the picking range</para>
/// <para>当有物体进入拾捡范围时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void EnterThePickingRangeBody(Node node)
{
if (node is not IItem)
{
return;
}
PickingRangeBodiesList?.Add(node);
}
/// <summary>
/// <para>When an object exit the picking range</para>
/// <para>当有物体离开拾捡范围时</para>
/// </summary>
/// <param name="node"></param>
protected virtual void ExitThePickingRangeBody(Node node)
{
if (node is not IItem)
{
return;
}
PickingRangeBodiesList?.Remove(node);
}
/// <summary>
/// <para>Flip sprites or animations</para>
/// <para>翻转精灵或动画</para>
/// </summary>
protected virtual void Flip()
{
if (_animatedSprite2D == null)
{
return;
}
_animatedSprite2D.FlipH = FacingLeft;
}
public sealed override void _PhysicsProcess(double delta)
{
//We continuously set the position of the items to prevent them from changing as we zoom in and out of the window.
//我们持续设置物品的位置,为了防止放大缩小窗口时物品位置的变化。
if (_currentItem != null)
{
_currentItem.Position = Vector2.Zero;
}
var velocity = Velocity;
// Add the gravity.
//增加重力。
if (!IsOnFloor())
velocity.Y += Gravity * (float)delta;
// The ref keyword passes its pointer to the following method so that it can be modified in the method.
// ref关键字将其指针传递给下面的方法以便在方法中修改它。
HookPhysicsProcess(ref velocity, delta);
Velocity = velocity + _additionalForce;
_additionalForce = Vector2.Zero;
MoveAndSlide();
}
/// <summary>
/// <para>Aim the held item at a point</para>
/// <para>使持有的物品瞄准某个点</para>
/// </summary>
public void AimTheCurrentItemAtAPoint(Vector2 position)
{
if (_currentItem == null)
{
//Do not currently hold any items.
//当前没有持有任何物品。
return;
}
// Apply the rotation Angle to the node
// 将旋转角度应用于节点
_currentItem.LookAt(position);
}
protected virtual void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
}
}